After analyzing box2d lite again, i added normal visualization and as i suspected, all normals are always the correct surface normals from the surface.
In my case - sometimes its correct, sometimes its not and i has to do with my contact generation.
For example, a simple halfspace and circle contact is created in my system like this:
- Get the distance vector between any point on the halfspace (in my case this is the halfspaces center - because its defined as normal * -distance) and the position of the circle.
- Now make a vector projection on the planes collision normal against this distance vector and invert the result
- If this projection is greater than the radius - we create a contact, otherwise not
- Contact point is circle position + planes collision normal * negative projection.
- Penetration is circle radius - projection
- The result contact normal is the inverted plane collision normal (If i use the actual plane normal, the relative velocity projection fails in the solver and the circle falls through the plane. (Even with the basic impulse like this: j = -(1.0 + restitution) * velAlongNormal )
I am not sure, what i am doing wrong here.
I also double checked my broadphase system which do simply two for loops, the outer goes from zero to the body count. The inner goes from the outer + 1 to the body count - These should create unique pairs always - of course static vs static are skipped as well.
Secondly i run the actual contact generation function from a jump table based on the bodies shape type:
function flipContacts(list, count) {
if (count > 0) {
for (var i = list.size()-1; i > list.size()-1-count; i--){
var contact = list.item(i);
math.vec2MultScalar(contact.normal, contact.normal, -1);
var tmp = contact.bodyA;
contact.bodyA = contact.bodyB;
contact.bodyB = tmp;
}
}
return count;
}
function createContactCircleToHalfSpace(a, b, list) {
var aDef = a.shape;
var bDef = b.shape;
var normal = bDef.normal;
var pos = a.position;
var pC = b.position;
var sub = Vec2Pool.get();
math.vec2Sub(sub, pC, pos);
var proj = -math.vec2Dot(normal, sub);
if (proj >= aDef.radius) {
return 0;
}
var contact = list.add();
contact.bodyA = a;
contact.bodyB = b;
contact.penetration = aDef.radius - proj;
math.vec2MultScalar(contact.normal, normal, -1);
math.vec2AddMultScalar(contact.contactPoint, pos, normal, -proj);
return 1;
}
function createContactHalfSpaceToCircle(a, b, list) {
return flipContacts(list, createContactCircleToHalfSpace(b, a, list));
}
var contactFunctions = [];
function addContactFunction(typeA, typeB, func) {
if (typeof contactFunctions[typeA] == "undefined") {
contactFunctions[typeA] = [];
}
contactFunctions[typeA][typeB] = func;
}
addContactFunction(ShapeType.Circle,ShapeType.HalfSpace, createContactCircleToHalfSpace);
addContactFunction(ShapeType.HalfSpace,ShapeType.Circle, createContactHalfSpaceToCircle);
As you can see, the createContactHalfSpaceToCircle method is called when, bodyA is a halfspace and bodyB is a circle (inverted implementation of createContactCircleToHalfSpace) - therefore i flip the generated contacts (bodyA is bodyB and vice versa + invert the contact normal)