I've simplified things down to cubes/a single cube colliding with an infinite-mass rectangle and the following code:
The problem is, the boxes tend to spin too much and get stuck together spinning and, if the binary search is included, just hit and spin a lot.
Thanks for any and all help.
/// <summary>
/// Projects an abstract 1D line "perpendicular" to the axis,
/// stretching across the width of the model,
/// measured from that axis.
/// </summary>
/// <param name="Axis"></param>
/// <param name="Min"></param>
/// <param name="Max"></param>
protected virtual void ProjectToAxis(Vector2 Axis, IMotionData motionData, out double Min, out double Max)
{
Double DotP = Axis.Dot(motionData.PositionGS + (this.Vertices[0].Position * this.Model.Scale).Rotate(motionData.RotationGS));
Min = Max = DotP;
for (int t = 1; t < this.Vertices.Count(); ++t)
{
DotP = Axis.Dot(motionData.PositionGS + (this.Vertices[t].Position * this.Model.Scale).Rotate(motionData.RotationGS));
Min = Math.Min(DotP, Min);
Max = Math.Max(DotP, Max);
}
}
/// <summary>
/// Projects two imaginary lines even with each edge,
/// equal to the width of each object while looking at
/// that edge, then checks to see if they intersect.
/// </summary>
/// <param name="B1"></param>
/// <param name="B2"></param>
/// <returns></returns>
public static bool DetectCollision(Body B1, Body B2, Double elapsedSeconds)
{
CollisionData collisionInfo = new CollisionData();
double lowestDistance = double.MaxValue;
double distance;
Vector2 normalB1ToB2 = (B2.MotionHandler.PositionGS - B1.MotionHandler.PositionGS).Normalized;
foreach (Edge edge in B1.Edges)
{
if (edge.Normal.RelativePosition.Dot(normalB1ToB2) >= 0.0)
{
double minA, minB, maxA, maxB;
B1.ProjectToAxis(edge.Normal.RelativePosition, B1.MotionHandler.MotionDataGet, out minA, out maxA);
B2.ProjectToAxis(edge.Normal.RelativePosition, B2.MotionHandler.MotionDataGet, out minB, out maxB);
if (minA < minB)
distance = minB - maxA;
else
distance = minA - maxB;
if (distance > 0.0f)
return false;
else if (Math.Abs(distance) < lowestDistance)
{
lowestDistance = Math.Abs(distance);
collisionInfo.Normal = edge.Normal.RelativePosition;
collisionInfo.Edge = edge;
}
}
}
Vector2 normalB2ToB1 = -normalB1ToB2;
foreach (Edge edge in B2.Edges)
{
if (edge.Normal.RelativePosition.Dot(normalB2ToB1) >= 0.0)
{
double minA, minB, maxA, maxB;
B1.ProjectToAxis(edge.Normal.RelativePosition, B1.MotionHandler.MotionDataGet, out minA, out maxA);
B2.ProjectToAxis(edge.Normal.RelativePosition, B2.MotionHandler.MotionDataGet, out minB, out maxB);
if (minA < minB)
distance = minB - maxA;
else
distance = minA - maxB;
if (distance > 0.0f)
return false;
else if (Math.Abs(distance) < lowestDistance)
{
lowestDistance = Math.Abs(distance);
collisionInfo.Normal = edge.Normal.RelativePosition;
collisionInfo.Edge = edge;
}
}
}
collisionInfo.Depth = lowestDistance;
/* Double lowHighSeconds = elapsedSeconds;
Double highLowSeconds = 0.0;
Double seconds;
IMotionData md1;
IMotionData md2;
bool collision;
do
{
md1 = B1.MotionHandler.MotionDataLastGet.Copy;
md2 = B2.MotionHandler.MotionDataLastGet.Copy;
collision = true;
lowestDistance = Double.MaxValue;
seconds = MathExtensions.MathExt.Lerp(highLowSeconds, lowHighSeconds, 0.5);
B1.MotionHandler.Simulate(seconds, ref md1);
B2.MotionHandler.Simulate(seconds, ref md2);
normalB1ToB2 = (md2.PositionGS - md1.PositionGS).Normalized;
foreach (Edge edge in B1.Edges)
{
if ((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS).Dot(normalB1ToB2) >= 0.0)
{
double minA, minB, maxA, maxB;
B1.ProjectToAxis((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS), md1, out minA, out maxA);
B2.ProjectToAxis((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS), md2, out minB, out maxB);
if (minA < minB)
distance = minB - maxA;
else
distance = minA - maxB;
if (distance > 0.0f)
collision = false;
else if (Math.Abs(distance) < lowestDistance)
{
lowestDistance = Math.Abs(distance);
collisionInfo.Normal = (edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS);
collisionInfo.Edge = edge;
}
}
}
normalB2ToB1 = -normalB1ToB2;
foreach (Edge edge in B2.Edges)
{
if ((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS).Dot(normalB2ToB1) >= 0.0)
{
double minA, minB, maxA, maxB;
B2.ProjectToAxis((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS), md2, out minA, out maxA);
B1.ProjectToAxis((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS), md1, out minB, out maxB);
if (minA < minB)
distance = minB - maxA;
else
distance = minA - maxB;
if (distance > 0.0f)
collision = false;
else if (Math.Abs(distance) < lowestDistance)
{
lowestDistance = Math.Abs(distance);
collisionInfo.Normal = (edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS);
collisionInfo.Edge = edge;
}
}
}
collisionInfo.Depth = lowestDistance;
if (!collision)
{
lowHighSeconds = seconds;
}
else
{
highLowSeconds = seconds;
}
} while (Math.Abs(highLowSeconds - lowHighSeconds) > 0.0001);
B1.MotionHandler.MotionDataSet = md1;
B2.MotionHandler.MotionDataSet = md2; */
// bool flip = false;
if (collisionInfo.Edge.Parent != B2.Model)
{
Body temp = B1;
B1 = B2;
B2 = temp;
}
//This is needed to make sure that the collision normal is pointing at B1
int Sign = Math.Sign(
collisionInfo.Normal.Dot(
B1.MotionHandler.MotionDataGet.PositionGS + (B1.Center * B1.Model.Scale).Rotate(B1.MotionHandler.MotionDataGet.RotationGS) -
B2.MotionHandler.MotionDataGet.PositionGS + (B2.Center * B2.Model.Scale).Rotate(B2.MotionHandler.MotionDataGet.RotationGS)
)
);
//Remember that the line equation is N*( R - R0 ). We choose B2->Center
//as R0; the normal N is given by the collision normal
if (Sign != 1)
collisionInfo.Normal = -collisionInfo.Normal; //Revert the collision normal if it points away from B1
double SmallestD = double.MaxValue; //Initialize the smallest distance to a high value
//Measure the distance of the vertex from the line using the line equation
for (int t = 0; t < B1.Vertices.Count(); ++t)
{
double Distance = collisionInfo.Normal.Dot(B1.Vertices[t].WorldPosition - B2.Center);
// If the measured distance is smaller than the smallest distance reported
// so far, set the smallest distance and the collision vertex
if (Distance < SmallestD)
{
SmallestD = Distance;
collisionInfo.Vertex = B1.Vertices[t];
}
}
if ((Body.CollisionType & CollisionType.Velocity) > 0)
{
Vector2 vab1 = B1.MotionHandler.VelocityGS - B2.MotionHandler.VelocityGS;
Vector2 rap = (B1.MotionHandler.PositionGS - collisionInfo.Normal);
Vector2 rbp = (B2.MotionHandler.PositionGS - collisionInfo.Normal);
Double rap2 = (rap.Cross(collisionInfo.Normal));
Double rbp2 = (rbp.Cross(collisionInfo.Normal));
Vector2 one = (collisionInfo.Vertex.WorldPosition - B1.MotionHandler.PositionGS).GetPerpendicular;
Vector2 two = (collisionInfo.Vertex.WorldPosition - B2.MotionHandler.PositionGS).GetPerpendicular;
Double j = (-(1 + 0.0) * vab1.Dot(collisionInfo.Normal)) /
((collisionInfo.Normal.Dot(collisionInfo.Normal) * (B1.MotionHandler.InverseMassGS + B2.MotionHandler.InverseMassGS)) +
(one.Dot(one) * B1.MotionHandler.InverseInertiaGS) + (two.Dot(two) * B2.MotionHandler.InverseInertiaGS));
B1.MotionHandler.AddImpulse = new Force(
collisionInfo.Normal,
j /* ,
one */
);
B2.MotionHandler.AddImpulse = new Force(
collisionInfo.Normal,
-(j) /* ,
two */
);
NewtonianMotionData data1 = (NewtonianMotionData)B1.MotionHandler.MotionDataGet;
NewtonianMotionData data2 = (NewtonianMotionData)B2.MotionHandler.MotionDataGet;
data1.AngularVelocity += (one.Dot(j * collisionInfo.Normal)) * data1.inverseInertia;
data2.AngularVelocity += (two.Dot(-j * collisionInfo.Normal)) * data2.inverseInertia;
B1.MotionHandler.MotionDataSet = data1;
B2.MotionHandler.MotionDataSet = data2;
}
return true;
}