There are 2 issues here:
1) time of impact
2) tunneling (fast moving sphere passing through triangles)
I will not go into #1, in many practical situations you can ignore it. Collision response will be less accurate, but hardly noticeable in most situations. I also have no idea how to implement swept sphere collision correctly, some else might explain it.
There is a workaround for #2 however, usually sufficient for most games; after you've integrated the sphere position, do a ray-test from the old sphere position to the new position. Then, whenever the sphere center has passed the triangle plane, you can project an approximate contact point onto the triangle, and move the sphere back so it no longer intersects. Unless your trimesh has lots of concave features such as 'v-cliffs' or other tight spots, this should work fine. If it does, you can do another ray-test when correcting the sphere position (recursively; every time you apply a correction). It should never be able to pass through triangles, but remember to add a max recursion or you may get stuck in an infinite loop (3-5 is usually ok) in tight situations.
One thing I would suggest though, is to collect all sphere-triangle intersection points, before correcting the sphere position. Not just the deepest contact intersection, but every single sphere-triangle intersection point. Store them in an array, something like this:
float3 position; // intersection point on triangle surface
float3 normal; // triangle normal
float depth; // penetration depth
int faceIndex; // index of trimesh face (useful to look up surface info such as bounce, friction etc)
Some raw trimesh-sphere col-det code that may give you some ideas:
// collides collider with sphere
void CCollider::SphereTest(const float3 &pos, float radius, CContactGroup &contacts) const
// quick bounds test
if (!bounds.QuickSphereTest(pos, radius)) return;
// test faces
for (int i=0; i<facenum; i++)
const float3 &v1 = vert[ face[i].v1 ];
const float3 &v2 = vert[ face[i].v2 ];
const float3 &v3 = vert[ face[i].v3 ];
const float3 &fn = face[i].n;
// backface culling
if (DotProduct(fn, pos - v1 ) < 0.0) continue;
// get closest point on triangle
const float3 cp = ClosestPointTriangle(pos, v1, v2, v3);
// dist to point
const float depth = Distance(pos,cp) - radius;
// no penetration
if (depth > 0.0) continue;
// create sphere contact
c.pos = cp;
c.norm = Normalize(pos - cp);
c.depth = -depth;
c.collider = this;
c.face = i;
c.id = face[i].id;
contacts.Add( c );
Then once you've collided the sphere against every trimesh and other spheres in your world, you can filter out double/similar contacts. Loop through the contacts, and compute the average of contacts that lie on the same plane/position (don't forget to re-normalize the contact normal).
For example when a sphere rests on the center of flat quad (consisting of two triangles), the sphere will intersect both edges, resulting into two contacts. Both contacts will have the exact same position, but contact normal (and depth) may be diverging. If you merge these, it will result into a single contact with a normal facing straight up.
Once your contacts are all cleaned up, you can correct (solve) the sphere intersections all at once.
And this code shows how the contacts are solved. 'pos' is the position of the sphere, and 'vel' is it's motion vector.
void Constrain(const float3 &plane, float3 &force)
const float dp = MAX( DotProduct(-plane,force) ,0.0);
return force += plane * dp;
// solves constraints
void CContactGroup::Solve(float3 &pos, float3 &vel) const
for (int i=0; i<contacts.size(); i++)
const contact &c = contacts[i];
// correct the sphere position
pos += c.norm * c.depth;
// correct the motion vector as well
Constrain( c.norm, vel );
Notice that, as well as the sphere's position, you need to 'correct' the velocity vector. Otherwise gravity force applied will increase the velocity each timestep, even though the sphere is not moving. Perhaps this is related to the problem you mentioned.
In the code above, the motion vector is simply 'zeroed out' in the contact normal direction, but you can easily add friction and 'bounce' effect.
Hope that helps.