/// <summary> Constructs a Penetration Sweep object, with all its attributes set. /// This constructor is public only for testing purposes. The static method /// {@link PenetrationSweep#GetPenetrationDepth(Intersection, Intersection, Vector2f, Vector2f[], Vector2f[])} /// should be called to get the penetration depth. /// /// </summary> /// <param name="normal">The collision normal /// </param> /// <param name="sweepDir">The sweep direction /// </param> /// <param name="intersectionStart">The start bound of the intersection Area /// </param> /// <param name="intersectionEnd">The end bound of the intersection Area. /// </param> public PenetrationSweep(Vector2f normal, Vector2f sweepDir, Vector2f intersectionStart, Vector2f intersectionEnd) : base() { this.normal = normal; this.sweepDir = sweepDir; this.startDist = intersectionStart.Dot(sweepDir); this.endDist = intersectionEnd.Dot(sweepDir); }
/// <summary> Get point on this polygon's hull that is closest to p. /// /// TODO: make this thing return a negative value when it is contained in the polygon /// /// </summary> /// <param name="p">The point to search the closest point for /// </param> /// <returns> the nearest point on this vertex' hull /// </returns> public override ROVector2f GetNearestPoint(ROVector2f p) { // TODO: this can be done with a kind of binary search float r = System.Single.MaxValue; float l; Vector2f v; int m = - 1; for (int i = 0; i < vertices.Length; i++) { v = new Vector2f(vertices[i]); v.Sub(p); l = v.x * v.x + v.y * v.y; if (l < r) { r = l; m = i; } } // the closest point could be on one of the closest point's edges // this happens when the angle between v[m-1]-v[m] and p-v[m] is // smaller than 90 degrees, same for v[m+1]-v[m] int length = vertices.Length; Vector2f pm = new Vector2f(p); pm.Sub(vertices[m]); Vector2f l1 = new Vector2f(vertices[(m - 1 + length) % length]); l1.Sub(vertices[m]); Vector2f l2 = new Vector2f(vertices[(m + 1) % length]); l2.Sub(vertices[m]); Vector2f normal; if (pm.Dot(l1) > 0) { normal = MathUtil.GetNormal(vertices[(m - 1 + length) % length], vertices[m]); } else if (pm.Dot(l2) > 0) { normal = MathUtil.GetNormal(vertices[m], vertices[(m + 1) % length]); } else { return vertices[m]; } normal.Scale(- pm.Dot(normal)); normal.Add(p); return normal; }
/// <summary> Apply the friction impulse from each contact. /// /// </summary> /// <param name="dt">The amount of time to Step the simulation by /// </param> /// <param name="invDT">The inverted time /// </param> /// <param name="damping">The percentage of energy to retain through out /// collision. (1 = no loss, 0 = total loss) /// </param> internal virtual void PreStep(float invDT, float dt, float damping) { float allowedPenetration = 0.01f; float biasFactor = 0.8f; for (int i = 0; i < numContacts; ++i) { Contact c = contacts[i]; c.normal.Normalise(); Vector2f r1 = new Vector2f(c.position); r1.Sub(body1.GetPosition()); Vector2f r2 = new Vector2f(c.position); r2.Sub(body2.GetPosition()); // Precompute normal mass, tangent mass, and bias. float rn1 = r1.Dot(c.normal); float rn2 = r2.Dot(c.normal); float kNormal = body1.InvMass + body2.InvMass; kNormal += body1.InvI * (r1.Dot(r1) - rn1 * rn1) + body2.InvI * (r2.Dot(r2) - rn2 * rn2); c.massNormal = damping / kNormal; Vector2f tangent = MathUtil.Cross(c.normal, 1.0f); float rt1 = r1.Dot(tangent); float rt2 = r2.Dot(tangent); float kTangent = body1.InvMass + body2.InvMass; kTangent += body1.InvI * (r1.Dot(r1) - rt1 * rt1) + body2.InvI * (r2.Dot(r2) - rt2 * rt2); c.massTangent = damping / kTangent; // Compute restitution // Relative velocity at contact Vector2f relativeVelocity = new Vector2f(body2.Velocity); relativeVelocity.Add(MathUtil.Cross(r2, body2.AngularVelocity)); relativeVelocity.Sub(body1.Velocity); relativeVelocity.Sub(MathUtil.Cross(r1, body1.AngularVelocity)); float combinedRestitution = (body1.Restitution * body2.Restitution); float relVel = c.normal.Dot(relativeVelocity); c.restitution = combinedRestitution * (- relVel); c.restitution = System.Math.Max(c.restitution, 0); float penVel = (- c.separation) / dt; if (c.restitution >= penVel) { c.bias = 0; } else { c.bias = (- biasFactor) * invDT * System.Math.Min(0.0f, c.separation + allowedPenetration); } // apply damping c.accumulatedNormalImpulse *= damping; // Apply normal + friction impulse Vector2f impulse = MathUtil.Scale(c.normal, c.accumulatedNormalImpulse); impulse.Add(MathUtil.Scale(tangent, c.accumulatedTangentImpulse)); body1.AdjustVelocity(MathUtil.Scale(impulse, - body1.InvMass)); body1.AdjustAngularVelocity((- body1.InvI) * MathUtil.Cross(r1, impulse)); body2.AdjustVelocity(MathUtil.Scale(impulse, body2.InvMass)); body2.AdjustAngularVelocity(body2.InvI * MathUtil.Cross(r2, impulse)); // rest bias c.biasImpulse = 0; } }
// private static Vector2f r1 = new Vector2f(); // private static Vector2f r2 = new Vector2f(); // private static Vector2f relativeVelocity = new Vector2f(); // private static Vector2f impulse = new Vector2f(); // private static Vector2f Pb = new Vector2f(); // private static Vector2f tmp = new Vector2f(); // // /** // * Apply the impulse accumlated at the contact points maintained // * by this arbiter. // */ // void ApplyImpulse() { // Body b1 = body1; // Body b2 = body2; // // for (int i = 0; i < numContacts; ++i) // { // Contact c = contacts[i]; // // r1.set(c.position); // r1.Sub(b1.GetPosition()); // r2.set(c.position); // r2.Sub(b2.GetPosition()); // // // Relative velocity at contact // relativeVelocity.set(b2.getVelocity()); // relativeVelocity.Add(MathUtil.Cross(b2.getAngularVelocity(), r2)); // relativeVelocity.Sub(b1.getVelocity()); // relativeVelocity.Sub(MathUtil.Cross(b1.getAngularVelocity(), r1)); // // // Compute normal impulse with bias. // float vn = relativeVelocity.Dot(c.normal); // // // bias caculations are now handled seperately hence we only // // handle the real impulse caculations here // //float normalImpulse = c.massNormal * ((c.restitution - vn) + c.bias); // float normalImpulse = c.massNormal * (c.restitution - vn); // // // Clamp the accumulated impulse // float oldNormalImpulse = c.accumulatedNormalImpulse; // c.accumulatedNormalImpulse = Math.max(oldNormalImpulse + normalImpulse, 0.0f); // normalImpulse = c.accumulatedNormalImpulse - oldNormalImpulse; // // if (normalImpulse != 0) { // // Apply contact impulse // impulse.set(c.normal); // impulse.Scale(normalImpulse); // // tmp.set(impulse); // tmp.Scale(-b1.getInvMass()); // b1.AdjustVelocity(tmp); // b1.AdjustAngularVelocity(-(b1.getInvI() * MathUtil.Cross(r1, impulse))); // // tmp.set(impulse); // tmp.Scale(b2.getInvMass()); // b2.AdjustVelocity(tmp); // b2.AdjustAngularVelocity(b2.getInvI() * MathUtil.Cross(r2, impulse)); // } // // // Compute bias impulse // // NEW STUFF FOR SEPERATING BIAS // relativeVelocity.set(b2.getBiasedVelocity()); // relativeVelocity.Add(MathUtil.Cross(b2.getBiasedAngularVelocity(), r2)); // relativeVelocity.Sub(b1.getBiasedVelocity()); // relativeVelocity.Sub(MathUtil.Cross(b1.getBiasedAngularVelocity(), r1)); // float vnb = relativeVelocity.Dot(c.normal); // // float biasImpulse = c.massNormal * (-vnb + c.bias); // float oldBiasImpulse = c.biasImpulse; // c.biasImpulse = Math.max(oldBiasImpulse + biasImpulse, 0.0f); // biasImpulse = c.biasImpulse - oldBiasImpulse; // // if (biasImpulse != 0) { // Pb.set(c.normal); // Pb.Scale(biasImpulse); // // tmp.set(Pb); // tmp.Scale(-b1.getInvMass()); // b1.AdjustBiasedVelocity(tmp); // b1.AdjustBiasedAngularVelocity(-(b1.getInvI() * MathUtil.Cross(r1, Pb))); // // tmp.set(Pb); // tmp.Scale(b2.getInvMass()); // b2.AdjustBiasedVelocity(tmp); // b2.AdjustBiasedAngularVelocity((b2.getInvI() * MathUtil.Cross(r2, Pb))); // } // // END NEW STUFF // // // // // Compute friction (tangent) impulse // // // float maxTangentImpulse = friction * c.accumulatedNormalImpulse; // // // Relative velocity at contact // relativeVelocity.set(b2.getVelocity()); // relativeVelocity.Add(MathUtil.Cross(b2.getAngularVelocity(), r2)); // relativeVelocity.Sub(b1.getVelocity()); // relativeVelocity.Sub(MathUtil.Cross(b1.getAngularVelocity(), r1)); // // Vector2f tangent = MathUtil.Cross(c.normal, 1.0f); // float vt = relativeVelocity.Dot(tangent); // float tangentImpulse = c.massTangent * (-vt); // // // Clamp friction // float oldTangentImpulse = c.accumulatedTangentImpulse; // c.accumulatedTangentImpulse = MathUtil.Clamp(oldTangentImpulse + tangentImpulse, -maxTangentImpulse, maxTangentImpulse); // tangentImpulse = c.accumulatedTangentImpulse - oldTangentImpulse; // // // Apply contact impulse // if ((tangentImpulse > 0.1f) || (tangentImpulse < -0.1f)) { // impulse = MathUtil.Scale(tangent, tangentImpulse); // // tmp.set(impulse); // tmp.Scale(-b1.getInvMass()); // b1.AdjustVelocity(tmp); // b1.AdjustAngularVelocity(-b1.getInvI() * MathUtil.Cross(r1, impulse)); // // tmp.set(impulse); // tmp.Scale(b2.getInvMass()); // b2.AdjustVelocity(tmp); // b2.AdjustAngularVelocity(b2.getInvI() * MathUtil.Cross(r2, impulse)); // } // } // } /// <summary> Apply the impulse accumlated at the contact points maintained /// by this arbiter. /// </summary> internal virtual void ApplyImpulse() { Body b1 = body1; Body b2 = body2; for (int i = 0; i < numContacts; ++i) { Contact c = contacts[i]; Vector2f r1 = new Vector2f(c.position); r1.Sub(b1.GetPosition()); Vector2f r2 = new Vector2f(c.position); r2.Sub(b2.GetPosition()); // Relative velocity at contact Vector2f relativeVelocity = new Vector2f(b2.Velocity); relativeVelocity.Add(MathUtil.Cross(b2.AngularVelocity, r2)); relativeVelocity.Sub(b1.Velocity); relativeVelocity.Sub(MathUtil.Cross(b1.AngularVelocity, r1)); // Compute normal impulse with bias. float vn = relativeVelocity.Dot(c.normal); // bias caculations are now handled seperately hence we only // handle the real impulse caculations here //float normalImpulse = c.massNormal * ((c.restitution - vn) + c.bias); float normalImpulse = c.massNormal * (c.restitution - vn); // Clamp the accumulated impulse float oldNormalImpulse = c.accumulatedNormalImpulse; c.accumulatedNormalImpulse = System.Math.Max(oldNormalImpulse + normalImpulse, 0.0f); normalImpulse = c.accumulatedNormalImpulse - oldNormalImpulse; // Apply contact impulse Vector2f impulse = MathUtil.Scale(c.normal, normalImpulse); b1.AdjustVelocity(MathUtil.Scale(impulse, - b1.InvMass)); b1.AdjustAngularVelocity(- (b1.InvI * MathUtil.Cross(r1, impulse))); b2.AdjustVelocity(MathUtil.Scale(impulse, b2.InvMass)); b2.AdjustAngularVelocity(b2.InvI * MathUtil.Cross(r2, impulse)); // Compute bias impulse // NEW STUFF FOR SEPERATING BIAS relativeVelocity.Reconfigure(b2.BiasedVelocity); relativeVelocity.Add(MathUtil.Cross(b2.BiasedAngularVelocity, r2)); relativeVelocity.Sub(b1.BiasedVelocity); relativeVelocity.Sub(MathUtil.Cross(b1.BiasedAngularVelocity, r1)); float vnb = relativeVelocity.Dot(c.normal); float biasImpulse = c.massNormal * (- vnb + c.bias); float oldBiasImpulse = c.biasImpulse; c.biasImpulse = System.Math.Max(oldBiasImpulse + biasImpulse, 0.0f); biasImpulse = c.biasImpulse - oldBiasImpulse; Vector2f Pb = MathUtil.Scale(c.normal, biasImpulse); b1.AdjustBiasedVelocity(MathUtil.Scale(Pb, - b1.InvMass)); b1.AdjustBiasedAngularVelocity(- (b1.InvI * MathUtil.Cross(r1, Pb))); b2.AdjustBiasedVelocity(MathUtil.Scale(Pb, b2.InvMass)); b2.AdjustBiasedAngularVelocity((b2.InvI * MathUtil.Cross(r2, Pb))); // END NEW STUFF // // Compute friction (tangent) impulse // float maxTangentImpulse = friction * c.accumulatedNormalImpulse; // Relative velocity at contact relativeVelocity.Reconfigure(b2.Velocity); relativeVelocity.Add(MathUtil.Cross(b2.AngularVelocity, r2)); relativeVelocity.Sub(b1.Velocity); relativeVelocity.Sub(MathUtil.Cross(b1.AngularVelocity, r1)); Vector2f tangent = MathUtil.Cross(c.normal, 1.0f); float vt = relativeVelocity.Dot(tangent); float tangentImpulse = c.massTangent * (- vt); // Clamp friction float oldTangentImpulse = c.accumulatedTangentImpulse; c.accumulatedTangentImpulse = MathUtil.Clamp(oldTangentImpulse + tangentImpulse, - maxTangentImpulse, maxTangentImpulse); tangentImpulse = c.accumulatedTangentImpulse - oldTangentImpulse; // Apply contact impulse impulse = MathUtil.Scale(tangent, tangentImpulse); b1.AdjustVelocity(MathUtil.Scale(impulse, - b1.InvMass)); b1.AdjustAngularVelocity((- b1.InvI) * MathUtil.Cross(r1, impulse)); b2.AdjustVelocity(MathUtil.Scale(impulse, b2.InvMass)); b2.AdjustAngularVelocity(b2.InvI * MathUtil.Cross(r2, impulse)); } }
/// <summary> Precaculate everything and apply initial impulse before the /// simulation Step takes place /// /// </summary> /// <param name="invDT">The amount of time the simulation is being stepped by /// </param> public virtual void PreStep(float invDT) { // calculate the spring's vector (pointing from body1 to body2) and Length spring = new Vector2f(body2.GetPosition()); spring.Add(r2); spring.Sub(body1.GetPosition()); spring.Sub(r1); springLength = spring.Length(); // the spring vector needs to be normalized for ApplyImpulse as well! spring.Normalise(); // calculate the spring's forces // note that although theoretically invDT could never be 0 // but here it can float springConst; if (springLength < minSpringSize || springLength > maxSpringSize) { // Pre-compute anchors, mass matrix, and bias. Matrix2f rot1 = new Matrix2f(body1.Rotation); Matrix2f rot2 = new Matrix2f(body2.Rotation); r1 = MathUtil.Mul(rot1, localAnchor1); r2 = MathUtil.Mul(rot2, localAnchor2); // the mass normal or 'k' float rn1 = r1.Dot(spring); float rn2 = r2.Dot(spring); float kNormal = body1.InvMass + body2.InvMass; kNormal += body1.InvI * (r1.Dot(r1) - rn1 * rn1) + body2.InvI * (r2.Dot(r2) - rn2 * rn2); massNormal = 1 / kNormal; // The spring is broken so apply force to correct it // note that we use biased velocities for this float springImpulse = invDT != 0?brokenSpringConst * (springLength - springSize) / invDT:0; Vector2f impulse = MathUtil.Scale(spring, springImpulse); body1.AdjustBiasedVelocity(MathUtil.Scale(impulse, body1.InvMass)); body1.AdjustBiasedAngularVelocity((body1.InvI * MathUtil.Cross(r1, impulse))); body2.AdjustBiasedVelocity(MathUtil.Scale(impulse, - body2.InvMass)); body2.AdjustBiasedAngularVelocity(- (body2.InvI * MathUtil.Cross(r2, impulse))); isBroken = true; return ; } else if (springLength < springSize) { springConst = compressedSpringConst; isBroken = false; } else { // if ( springLength >= springSize ) springConst = stretchedSpringConst; isBroken = false; } float springImpulse2 = invDT != 0?springConst * (springLength - springSize) / invDT:0; // apply the spring's forces Vector2f impulse2 = MathUtil.Scale(spring, springImpulse2); body1.AdjustVelocity(MathUtil.Scale(impulse2, body1.InvMass)); body1.AdjustAngularVelocity((body1.InvI * MathUtil.Cross(r1, impulse2))); body2.AdjustVelocity(MathUtil.Scale(impulse2, - body2.InvMass)); body2.AdjustAngularVelocity(- (body2.InvI * MathUtil.Cross(r2, impulse2))); }
/// <summary> Apply the impulse caused by the joint to the bodies attached.</summary> public virtual void ApplyImpulse() { if (isBroken) { // calculate difference in velocity // TODO: share this code with BasicJoint and Arbiter Vector2f relativeVelocity = new Vector2f(body2.Velocity); relativeVelocity.Add(MathUtil.Cross(body2.AngularVelocity, r2)); relativeVelocity.Sub(body1.Velocity); relativeVelocity.Sub(MathUtil.Cross(body1.AngularVelocity, r1)); // project the relative velocity onto the spring vector and apply the mass normal float normalImpulse = massNormal * relativeVelocity.Dot(spring); // // TODO: Clamp the accumulated impulse? // float oldNormalImpulse = accumulatedNormalImpulse; // accumulatedNormalImpulse = Math.max(oldNormalImpulse + normalImpulse, 0.0f); // normalImpulse = accumulatedNormalImpulse - oldNormalImpulse; // only apply the impulse if we are pushing or pulling in the right way // i.e. pulling if the string is overstretched and pushing if it is too compressed if (springLength < minSpringSize && normalImpulse < 0 || springLength > maxSpringSize && normalImpulse > 0) { // now apply the impulses to the bodies Vector2f impulse = MathUtil.Scale(spring, normalImpulse); body1.AdjustVelocity(MathUtil.Scale(impulse, body1.InvMass)); body1.AdjustAngularVelocity((body1.InvI * MathUtil.Cross(r1, impulse))); body2.AdjustVelocity(MathUtil.Scale(impulse, - body2.InvMass)); body2.AdjustAngularVelocity(- (body2.InvI * MathUtil.Cross(r2, impulse))); } } }