@Tomo & z64555: Inelastic collisions with arbitrary CoR, and friction. Maybe I'll omit the friction at this stage if it seems like it's too complicated with it.
@Tomo: No, I don't think I can assume anything like that. Yes, the ground is immobile. And I plan to make it possible for things to "sleep" when they get within some threshold of stationary... eventually.
@z64555: Yes, the collisions will go on forever. When an object falls and hits the ground, it should eventually come to rest.
A little more info about the problem... here's what a contact point looks like:
struct ContactPoint
{
struct Part
{
RigidBody* obj;
Vec3 pos, norm; // both are world coords
Part() : obj(NULL), pos(), norm() { }
} a, b;
};
For every
ContactPoint, my algorithm will have to come up with a
Vec3 impulse, which will be applied equally and oppositely to the two objects in contact (unless one of them is immobile). The impulse could hypothetically be decomposed into a component parallel to the contact point's normal vector (restitution) and a component orthogonal to it (friction).
And rather than waste more time explaining with words what my code is doing, I'll explain with code. This is the
DoCollisionResponse function:
void PhysicsWorld::Imp::DoCollisionResponse(const ContactPoint& cp)
{
RigidBody* ibody = cp.a.obj;
RigidBody* jbody = cp.b.obj;
RigidBody::Imp* iimp = ibody->imp;
RigidBody::Imp* jimp = jbody->imp;
bool j_can_move = jimp->can_move;
float m1 = iimp->mass_info.mass;
float m2 = jimp->mass_info.mass;
if(m1 + m2 > 0)
{
Vec3 i_poi = ibody->GetInvTransform().TransformVec3(cp.a.pos, 1.0f);
Vec3 j_poi = jbody->GetInvTransform().TransformVec3(cp.b.pos, 1.0f);
Vec3 i_v = iimp->GetLocalVelocity(cp.a.pos);
Vec3 j_v = jimp->GetLocalVelocity(cp.b.pos);
Vec3 dv = j_v - i_v;
const Vec3& normal = Vec3::Normalize(cp.a.norm - cp.b.norm);
float nvdot = Vec3::Dot(normal, dv);
if(nvdot < 0.0f)
{
float A, B;
GetUseMass(normal, cp, A, B);
float use_mass = 1.0f / A;
float bounciness = iimp->bounciness * jimp->bounciness;
float impulse_mag = -(1.0f + bounciness) * B * use_mass;
if(impulse_mag < 0)
{
Vec3 impulse = normal * impulse_mag;
if(impulse.ComputeMagnitudeSquared() != 0)
{
ibody->ApplyImpulse(impulse, i_poi);
if(j_can_move)
jbody->ApplyImpulse(-impulse, j_poi);
}
float sfric_coeff = iimp->friction * jimp->friction;
float kfric_coeff = 0.9f * sfric_coeff;
Vec3 t_dv = dv - normal * nvdot;
float t_dv_magsq = t_dv.ComputeMagnitudeSquared();
if(t_dv_magsq > 0.001f) // object is moving; apply kinetic friction
{
float t_dv_mag = sqrtf(t_dv_magsq);
GetUseMass(t_dv / t_dv_mag, cp, A, B);
use_mass = 1.0f / A;
Vec3 fric_impulse = t_dv * min(use_mass, fabs(impulse_mag * kfric_coeff / t_dv_mag));
ibody->ApplyImpulse(fric_impulse, i_poi);
if(j_can_move)
jbody->ApplyImpulse(-fric_impulse, j_poi);
}
else // object isn't moving; apply static friction
{
Vec3 df = jimp->applied_force - iimp->applied_force;
float nfdot = Vec3::Dot(normal, df);
Vec3 t_df = df - normal * nfdot;
float t_df_mag = t_df.ComputeMagnitude();
float fric_i_mag = min(impulse_mag * sfric_coeff, t_df_mag);
if(fric_i_mag > 0)
{
Vec3 fric_impulse = t_df * (-fric_i_mag / t_df_mag);
ibody->ApplyImpulse(fric_impulse, i_poi);
if(j_can_move)
jbody->ApplyImpulse(-fric_impulse, j_poi);
}
}
}
}
}
}
Some explanation of a couple of those other functions:
void ApplyImpulse(Vec3 impulse, Vec3 position); "Impulse" is the impulse to be applied, i.e. the change in linear velocity will be this vector divided by the mass. "Position" is the position where I'm applying the impulse (not necessarily on an axis passing through the CoM).
void GetUseMass(Vec3 direction, ContactPoint cp, float& A, float& B); Could possibly use a better name; it's for preparing the inputs to
ApplyImpulse. "Direction" is the direction of the impulse I want to apply, and "cp" is used for its position relative to the CoMs. After the function completes, "A" and "B" contain values such that the magnitude of the impulse I need to apply is
-(1.0f + bounciness) * B / A. If "bounciness" is one, the collision is perfectly ellastic. If "bounciness" is zero, it is perfectly inelastic (I think).
@Nuke: I've already come up with something to deal with the immobility of the ground... when there's a
ContactPoint between a mobile body and an immobile one, instead of reusing the same node for all
ContactPoints that immobile
RigidBody has, I give that particular
Edge a
NULL other_node pointer.
struct Edge
{
ContactPoint* cp;
ContactPoint::Part* self;
ContactPoint::Part* other;
Node* other_node;
};
struct Node
{
RigidBody* body;
vector<Edge> edges;
};
Edit: @z64555: by
Done correctly, after all the impulses are applied, no contact point should have an "inward" relative local velocity.
I mean that if I were to evaluate
nvdot (from
DoCollisionResponse) again for all of the
ContactPoints, it wouldn't be negative for any of them.
Edit II: Well that was easier to solve than I expected.
My old code:
for(vector<ContactPoint*>::iterator iter = collision_graph.contact_points.begin(); iter != collision_graph.contact_points.end(); ++iter)
DoCollisionResponse(**iter);
My new code:
for(int i = 0; i < MAX_SEQUENTIAL_SOLVER_ITERATIONS; ++i)
{
bool any = false;
for(vector<ContactPoint*>::iterator iter = collision_graph.contact_points.begin(); iter != collision_graph.contact_points.end(); ++iter)
if(DoCollisionResponse(**iter))
any = true;
if(!any)
break;
}
Also
DoCollisionResponse now returns a bool, true if
nvdot was < 0, false otherwise.
And now it works. Mostly.