public override float HitTest(Ball ball, float dTime, CollisionEvent coll, PlayerPhysics physics) { if (!IsEnabled) { return(-1.0f); } var bnv = _normal.Dot(ball.Hit.Vel); // speed in normal direction if (bnv > PhysicsConstants.ContactVel) { // return if clearly ball is receding from object return(-1.0f); } var bnd = _normal.Dot(ball.State.Pos) - ball.Data.Radius - _d; // distance from plane to ball surface //!! solely responsible for ball through playfield?? check other places, too (radius*2??) if (bnd < ball.Data.Radius * -2.0) { // excessive penetration of plane ... no collision HACK return(-1.0f); } if (MathF.Abs(bnv) <= PhysicsConstants.ContactVel) { if (MathF.Abs(bnd) <= PhysicsConstants.PhysTouch) { coll.IsContact = true; coll.HitNormal.Set(_normal); coll.HitOrgNormalVelocity = bnv; // remember original normal velocity coll.HitDistance = bnd; return(0.0f); // hit time is ignored for contacts } return(-1.0f); // large distance, small velocity -> no hit } var hitTime = bnd / (-bnv); if (hitTime < 0) { hitTime = 0.0f; // already penetrating? then collide immediately } if (float.IsNaN(hitTime) || float.IsInfinity(hitTime) || hitTime < 0 || hitTime > dTime) { // time is outside this frame ... no collision return(-1.0f); } coll.HitNormal.Set(_normal); coll.HitDistance = bnd; // actual contact distance return(hitTime); }
public override void Contact(CollisionEvent coll, float dTime, PlayerPhysics physics) { var ball = coll.Ball; var normal = coll.HitNormal; //#ifdef PhysicsConstants.EMBEDDED if (coll.HitDistance < -PhysicsConstants.Embedded) { // magic to avoid balls being pushed by each other through resting flippers! ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(0.1f)); } //#endif var vRel = new Vertex3D(); var rB = new Vertex3D(); var rF = new Vertex3D(); GetRelativeVelocity(normal, ball, vRel, rB, rF); // this should be zero, but only up to +/- C_CONTACTVEL var normVel = vRel.Dot(normal); // If some collision has changed the ball's velocity, we may not have to do anything. if (normVel <= PhysicsConstants.ContactVel) { // compute accelerations of point on ball and flipper var aB = ball.Hit.SurfaceAcceleration(rB, physics); var aF = _mover.SurfaceAcceleration(rF); var aRel = aB.Clone().Sub(aF); // time derivative of the normal vector var normalDeriv = Vertex3D.CrossZ(_mover.AngleSpeed, normal); // relative acceleration in the normal direction var normAcc = aRel.Dot(normal) + 2.0f * normalDeriv.Dot(vRel); if (normAcc >= 0) { return; // objects accelerating away from each other, nothing to do } // hypothetical accelerations arising from a unit contact force in normal direction var aBc = normal.Clone().MultiplyScalar(ball.Hit.InvMass); var pv2 = normal.Clone().MultiplyScalar(-1); var cross = Vertex3D.CrossProduct(rF, pv2); var pv1 = cross.Clone().DivideScalar(_mover.Inertia); var aFc = Vertex3D.CrossProduct(pv1, rF); var contactForceAcc = normal.Dot(aBc.Clone().Sub(aFc)); // find j >= 0 such that normAcc + j * contactForceAcc >= 0 (bodies should not accelerate towards each other) var j = -normAcc / contactForceAcc; // kill any existing normal velocity ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(j * dTime * ball.Hit.InvMass - coll.HitOrgNormalVelocity)); _mover.ApplyImpulse(cross.Clone().MultiplyScalar(j * dTime)); // apply friction // first check for slippage var slip = vRel.Clone().Sub(normal.Clone().MultiplyScalar(normVel)); // calc the tangential slip velocity var maxFriction = j * Friction; var slipSpeed = slip.Length(); Vertex3D slipDir; Vertex3D crossF; float numer; float denomF; Vertex3D pv13; if (slipSpeed < PhysicsConstants.Precision) { // slip speed zero - static friction case var slipAcc = aRel.Clone().Sub(normal.Clone().MultiplyScalar(aRel.Dot(normal))); // calc the tangential slip acceleration // neither slip velocity nor slip acceleration? nothing to do here if (slipAcc.LengthSq() < 1e-6) { return; } slipDir = slipAcc.Normalize(); numer = -slipDir.Dot(aRel); crossF = Vertex3D.CrossProduct(rF, slipDir); pv13 = crossF.Clone().DivideScalar(-_mover.Inertia); denomF = slipDir.Dot(Vertex3D.CrossProduct(pv13, rF)); } else { // nonzero slip speed - dynamic friction case slipDir = slip.Clone().DivideScalar(slipSpeed); numer = -slipDir.Dot(vRel); crossF = Vertex3D.CrossProduct(rF, slipDir); pv13 = crossF.Clone().DivideScalar(_mover.Inertia); denomF = slipDir.Dot(Vertex3D.CrossProduct(pv13, rF)); } var crossB = Vertex3D.CrossProduct(rB, slipDir); var pv12 = crossB.Clone().DivideScalar(ball.Hit.Inertia); var denomB = ball.Hit.InvMass + slipDir.Dot(Vertex3D.CrossProduct(pv12, rB)); var friction = Functions.Clamp(numer / (denomB + denomF), -maxFriction, maxFriction); ball.Hit.ApplySurfaceImpulse( crossB.Clone().MultiplyScalar(dTime * friction), slipDir.Clone().MultiplyScalar(dTime * friction) ); _mover.ApplyImpulse(crossF.Clone().MultiplyScalar(-dTime * friction)); } }
public override void Collide(CollisionEvent coll, PlayerPhysics physics) { var ball = coll.Ball; var normal = coll.HitNormal; var vRel = new Vertex3D(); var rB = new Vertex3D(); var rF = new Vertex3D(); GetRelativeVelocity(normal, ball, vRel, rB, rF); var bnv = normal.Dot(vRel); // relative normal velocity if (bnv >= -PhysicsConstants.LowNormVel) { // nearly receding ... make sure of conditions if (bnv > PhysicsConstants.LowNormVel) { // otherwise if clearly approaching .. process the collision // is this velocity clearly receding (i.E must > a minimum) return; } //#ifdef PhysicsConstants.EMBEDDED if (coll.HitDistance < -PhysicsConstants.Embedded) { bnv = -PhysicsConstants.EmbedShot; // has ball become embedded???, give it a kick } else { return; } //#endif } //#ifdef PhysicsConstants.DISP_GAIN // correct displacements, mostly from low velocity blindness, an alternative to true acceleration processing var hitDist = -PhysicsConstants.DispGain * coll.HitDistance; // distance found in hit detection if (hitDist > 1.0e-4) { if (hitDist > PhysicsConstants.DispLimit) { hitDist = PhysicsConstants.DispLimit; // crossing ramps, delta noise } // push along norm, back to free area; use the norm, but is not correct ball.State.Pos.Add(coll.HitNormal.Clone().MultiplyScalar(hitDist)); } //#endif // angular response to impulse in normal direction var angResp = Vertex3D.CrossProduct(rF, normal); /* * Check if flipper is in contact with its stopper and the collision impulse * would push it beyond the stopper. In that case, don"t allow any transfer * of kinetic energy from ball to flipper. This avoids overly dead bounces * in that case. */ var angImp = -angResp.Z; // minus because impulse will apply in -normal direction var flipperResponseScaling = 1.0f; if (_mover.IsInContact && _mover.ContactTorque * angImp >= 0f) { // if impulse pushes against stopper, allow no loss of kinetic energy to flipper // (still allow flipper recoil, but a diminished amount) angResp.SetZero(); flipperResponseScaling = 0.5f; } /* * Rubber has a coefficient of restitution which decreases with the impact velocity. * We use a heuristic model which decreases the COR according to a falloff parameter: * 0 = no falloff, 1 = half the COR at 1 m/s (18.53 speed units) */ var epsilon = Functions.ElasticityWithFalloff(Elasticity, ElasticityFalloff, bnv); var pv1 = angResp.Clone().DivideScalar(_mover.Inertia); var impulse = -(1.0f + epsilon) * bnv / (ball.Hit.InvMass + normal.Dot(Vertex3D.CrossProduct(pv1, rF))); var flipperImp = normal.Clone().MultiplyScalar(-(impulse * flipperResponseScaling)); var rotI = Vertex3D.CrossProduct(rF, flipperImp); if (_mover.IsInContact) { if (rotI.Z * _mover.ContactTorque < 0) { // pushing against the solenoid? // Get a bound on the time the flipper needs to return to static conditions. // If it"s too short, we treat the flipper as static during the whole collision. var recoilTime = -rotI.Z / _mover.ContactTorque; // time flipper needs to eliminate this impulse, in 10ms // Check ball normal velocity after collision. If the ball rebounded // off the flipper, we need to make sure it does so with full // reflection, i.E., treat the flipper as static, otherwise // we get overly dead bounces. var bnvAfter = bnv + impulse * ball.Hit.InvMass; if (recoilTime <= 0.5 || bnvAfter > 0) { // treat flipper as static for this impact impulse = -(1.0f + epsilon) * bnv * ball.Data.Mass; flipperImp.SetZero(); rotI.SetZero(); } } } ball.Hit.Vel.Add(normal.Clone().MultiplyScalar(impulse * ball.Hit.InvMass)); // new velocity for ball after impact _mover.ApplyImpulse(rotI); // apply friction var tangent = vRel.Clone().Sub(normal.Clone().MultiplyScalar(vRel.Dot(normal))); // calc the tangential velocity var tangentSpSq = tangent.LengthSq(); if (tangentSpSq > 1e-6) { tangent.DivideScalar(MathF.Sqrt(tangentSpSq)); // normalize to get tangent direction var vt = vRel.Dot(tangent); // get speed in tangential direction // compute friction impulse var crossB = Vertex3D.CrossProduct(rB, tangent); var pv12 = crossB.Clone().DivideScalar(ball.Hit.Inertia); var kt = ball.Hit.InvMass + tangent.Dot(Vertex3D.CrossProduct(pv12, rB)); var crossF = Vertex3D.CrossProduct(rF, tangent); var pv13 = crossF.Clone().DivideScalar(_mover.Inertia); kt += tangent.Dot(Vertex3D.CrossProduct(pv13, rF)); // flipper only has angular response // friction impulse can't be greater than coefficient of friction times collision impulse (Coulomb friction cone) var maxFriction = Friction * impulse; var jt = Functions.Clamp(-vt / kt, -maxFriction, maxFriction); ball.Hit.ApplySurfaceImpulse( crossB.Clone().MultiplyScalar(jt), tangent.Clone().MultiplyScalar(jt) ); _mover.ApplyImpulse(crossF.Clone().MultiplyScalar(-jt)); } if (bnv < -0.25 && physics.TimeMsec - _lastHitTime > 250) { // limit rate to 250 milliseconds per event var flipperHit = coll.HitMomentBit ? -1.0 : -bnv; // move event processing to end of collision handler... if (flipperHit < 0) { _events.FireGroupEvent(Event.HitEventsHit); // simple hit event } else { // collision velocity (normal to face) _events.FireVoidEventParam(Event.FlipperEventsCollide, flipperHit); } } _lastHitTime = physics.TimeMsec; // keep resetting until idle for 250 milliseconds }
public override float HitTest(Ball ball, float dTime, CollisionEvent coll, PlayerPhysics physics) { if (!IsEnabled) { return(-1.0f); } // speed in Normal-vector direction var bnv = _normal.Dot(ball.Hit.Vel); // return if clearly ball is receding from object if (ObjType != CollisionType.Trigger && bnv > PhysicsConstants.LowNormVel) { return(-1.0f); } // Point on the ball that will hit the polygon, if it hits at all var normRadius = _normal.Clone().MultiplyScalar(ball.Data.Radius); var hitPos = ball.State.Pos.Clone().Sub(normRadius); // nearest point on ball ... projected radius along norm var planeToBall = hitPos.Clone().Sub(_rgv[0]); var bnd = _normal.Dot(planeToBall); // distance from plane to ball var bUnHit = bnv > PhysicsConstants.LowNormVel; var inside = bnd <= 0; // in ball inside object volume var rigid = ObjType != CollisionType.Trigger; float hitTime; if (rigid) { // rigid polygon if (bnd < -ball.Data.Radius) { // (ball normal distance) excessive penetration of object skin ... no collision HACK //!! *2 necessary? return(-1.0f); } if (bnd <= PhysicsConstants.PhysTouch) { if (inside || MathF.Abs(bnv) > PhysicsConstants.ContactVel // fast velocity, return zero time //zero time for rigid fast bodies || bnd <= -PhysicsConstants.PhysTouch) { // slow moving but embedded hitTime = 0; } else { hitTime = bnd * (float)(1.0 / (2.0 * PhysicsConstants.PhysTouch)) + 0.5f; // don't compete for fast zero time events } } else if (MathF.Abs(bnv) > PhysicsConstants.LowNormVel) { // not velocity low? hitTime = bnd / -bnv; // rate ok for safe divide } else { return(-1.0f); // wait for touching } } else { // non-rigid polygon if (bnv * bnd >= 0) { // outside-receding || inside-approaching if (!ball.Hit.IsRealBall() || // temporary ball MathF.Abs(bnd) >= ball.Data.Radius * 0.5 || // not too close ... nor too far away inside == ball.Hit.VpVolObjs.Contains(Obj)) { // ...Ball outside and hit set or ball inside and no hit set return(-1.0f); } hitTime = 0; bUnHit = !inside; // ball on outside is UnHit, otherwise it"s a Hit } else { hitTime = bnd / -bnv; } } if (float.IsNaN(hitTime) || float.IsInfinity(hitTime) || hitTime < 0 || hitTime > dTime) { // time is outside this frame ... no collision return(-1.0f); } var adv = ball.Hit.Vel.Clone().MultiplyScalar(hitTime); hitPos.Add(adv); // advance hit point to contact // Do a point in poly test, using the xy plane, to see if the hit point is inside the polygon // this need to be changed to a point in polygon on 3D plane var x2 = _rgv[0].X; var y2 = _rgv[0].Y; var hx2 = hitPos.X >= x2; var hy2 = hitPos.Y <= y2; var crossCount = 0; // count of lines which the hit point is to the left of for (var i = 0; i < _rgv.Length; i++) { var x1 = x2; var y1 = y2; var hx1 = hx2; var hy1 = hy2; var j = i < _rgv.Length - 1 ? i + 1 : 0; x2 = _rgv[j].X; y2 = _rgv[j].Y; hx2 = hitPos.X >= x2; hy2 = hitPos.Y <= y2; if (y1 == y2 || hy1 && hy2 || !hy1 && !hy2 || hx1 && hx2) { // Hit point is on the right of the line continue; } if (!hx1 && !hx2) { crossCount ^= 1; continue; } if (x2 == x1) { if (!hx2) { crossCount ^= 1; } continue; } // Now the hard part - the hit point is in the line bounding box if (x2 - (y2 - hitPos.Y) * (x1 - x2) / (y1 - y2) > hitPos.X) { crossCount ^= 1; } } if ((crossCount & 1) != 0) { coll.HitNormal.Set(_normal); if (!rigid) { // non rigid body collision? return direction coll.HitFlag = bUnHit; // UnHit signal is receding from outside target } coll.HitDistance = bnd; // 3dhit actual contact distance ... //coll.M_hitRigid = rigid; // collision type return(hitTime); } return(-1.0f); }
public void Collide3DWall(Vertex3D hitNormal, float elasticity, float elasticityFalloff, float friction, float scatterAngle) { // speed normal to wall var dot = Vel.Dot(hitNormal); if (dot >= -PhysicsConstants.LowNormVel) { // nearly receding ... make sure of conditions if (dot > PhysicsConstants.LowNormVel) { // otherwise if clearly approaching .. process the collision return; // is this velocity clearly receding (i.E must > a minimum) } //#ifdef PhysicsConstants.Embedded if (Coll.HitDistance < -PhysicsConstants.Embedded) { dot = -PhysicsConstants.EmbedShot; // has ball become embedded???, give it a kick } else { return; } //#endif } //#ifdef PhysicsConstants.DispGain // correct displacements, mostly from low velocity, alternative to acceleration processing var hDist = -PhysicsConstants.DispGain * Coll.HitDistance; // limit delta noise crossing ramps, if (hDist > 1.0e-4) { // when hit detection checked it what was the displacement if (hDist > PhysicsConstants.DispLimit) { hDist = PhysicsConstants.DispLimit; // crossing ramps, delta noise } // push along norm, back to free area _state.Pos.Add(hitNormal.Clone().MultiplyScalar(hDist)); // use the norm, but this is not correct, reverse time is correct } //#endif // magnitude of the impulse which is just sufficient to keep the ball from // penetrating the wall (needed for friction computations) var reactionImpulse = _data.Mass * MathF.Abs(dot); elasticity = Functions.ElasticityWithFalloff(elasticity, elasticityFalloff, dot); dot *= -(1.0f + elasticity); Vel.Add(hitNormal.Clone().MultiplyScalar(dot)); // apply collision impulse (along normal, so no torque) // compute friction impulse var surfP = hitNormal.Clone().MultiplyScalar(-_data.Radius); // surface contact point relative to center of mass var surfVel = SurfaceVelocity(surfP); // velocity at impact point var tangent = surfVel.Clone() // calc the tangential velocity .Sub(hitNormal.Clone() .MultiplyScalar(surfVel.Dot(hitNormal))); var tangentSpSq = tangent.LengthSq(); if (tangentSpSq > 1e-6) { tangent.DivideScalar(MathF.Sqrt(tangentSpSq)); // normalize to get tangent direction var vt = surfVel.Dot(tangent); // get speed in tangential direction // compute friction impulse var cross = Vertex3D.CrossProduct(surfP, tangent); var crossInertia = cross.Clone().DivideScalar(Inertia); var kt = InvMass + tangent.Dot(Vertex3D.CrossProduct(crossInertia, surfP)); // friction impulse can"t be greather than coefficient of friction times collision impulse (Coulomb friction cone) var maxFric = friction * reactionImpulse; var jt = Functions.Clamp(-vt / kt, -maxFric, maxFric); if (!float.IsNaN(jt) && !float.IsInfinity(jt)) { ApplySurfaceImpulse( cross.Clone().MultiplyScalar(jt), tangent.Clone().MultiplyScalar(jt) ); } } if (scatterAngle < 0.0) { scatterAngle = HardScatter; } // if < 0 use global value scatterAngle *= _tableData.GlobalDifficulty; // apply difficulty weighting if (dot > 1.0 && scatterAngle > 1.0e-5) { // no scatter at low velocity var scatter = MathF.Random() * 2 - 1; // -1.0f..1.0f scatter *= (1.0f - scatter * scatter) * 2.59808f * scatterAngle; // shape quadratic distribution and scale var radSin = MathF.Sin(scatter); // Green's transform matrix... rotate angle delta var radCos = MathF.Cos(scatter); // rotational transform from current position to position at time t var vxt = Vel.X; var vyt = Vel.Y; Vel.X = vxt * radCos - vyt * radSin; // rotate to random scatter angle Vel.Y = vyt * radCos + vxt * radSin; } }