public void PhysicsSimulateCycle(float dTime) { // maximum number of static counts var staticCnts = PhysicsConstants.StaticCnts; // it's okay to have this code outside of the inner loop, as the // ball hitrects already include the maximum distance they can // travel in that timespan _hitOcTreeDynamic.Update(); while (dTime > 0) { var hitTime = dTime; // find earliest time where a flipper collides with its stop foreach (var flipperMover in _flipperMovers) { var flipperHitTime = flipperMover.GetHitTime(); if (flipperHitTime > 0 && flipperHitTime < hitTime) { //!! >= 0.F causes infinite loop hitTime = flipperHitTime; } } RecordContacts = true; Contacts.Clear(); foreach (var ball in Balls) { var ballHit = ball.Hit; // don't play with frozen balls if (!ball.State.IsFrozen) { // search upto current hit time ballHit.Coll.HitTime = hitTime; ballHit.Coll.Clear(); // always check for playfield and top glass if (!_table.HasMeshAsPlayfield) { _hitPlayfield.DoHitTest(ball, ball.Coll, this); } _hitTopGlass.DoHitTest(ball, ball.Coll, this); // swap order of dynamic and static obj checks randomly if (MathF.Random() < 0.5) { _hitOcTreeDynamic.HitTestBall(ball, ball.Coll, this); // dynamic objects _hitOcTree.HitTestBall(ball, ball.Coll, this); // find the hit objects and hit times } else { _hitOcTree.HitTestBall(ball, ball.Coll, this); // find the hit objects and hit times _hitOcTreeDynamic.HitTestBall(ball, ball.Coll, this); // dynamic objects } // this ball's hit time var htz = ball.Coll.HitTime; if (htz < 0) { // no negative time allowed ball.Coll.Clear(); } if (ball.Coll.HasHit) { // smaller hit time? if (htz <= hitTime) { // record actual event time hitTime = htz; // less than static time interval if (htz < PhysicsConstants.StaticTime) { if (--staticCnts < 0) { staticCnts = 0; // keep from wrapping hitTime = PhysicsConstants.StaticTime; } } } } } } // end loop over all balls RecordContacts = false; // hit time now set... or full frame if no hit // now update displacements to collide-contact or end of physics frame if (hitTime > PhysicsConstants.StaticTime) { // allow more zeros next round staticCnts = PhysicsConstants.StaticCnts; } foreach (var mover in _movers) { // step 2: move the objects about according to velocities // (spinner, gate, flipper, plunger, ball) mover.UpdateDisplacements(hitTime); } // find balls that need to be collided and scripted (generally // there will be one, but more are possible) for (var i = 0; i < Balls.Count; i++) { var ball = Balls[i]; var hitObject = ball.Coll.Obj; // object that ball hit in trials // find balls with hit objects and minimum time if (hitObject != null && ball.Coll.HitTime <= hitTime) { // now collision, contact and script reactions on active ball (object) ActiveBall = ball; // For script that wants the ball doing the collision hitObject.Collide(ball.Coll, this); // collision on active ball ball.Coll.Clear(); // remove trial hit object pointer // Collide may have changed the velocity of the ball, // and therefore the bounding box for the next hit cycle if (Balls[i] != ball) // Ball still exists? may have been deleted from list // collision script deleted the ball, back up one count { --i; } else { ball.Hit.CalcHitBBox(); // do new boundings } } } // Now handle contacts. // // At this point UpdateDisplacements() was already called, so the state is different // from that at HitTest(). However, contacts have zero relative velocity, so // hopefully nothing catastrophic has happened in the meanwhile. // // Maybe a two-phase setup where we first process only contacts, then only collisions // could also work. if (MathF.Random() < 0.5) // swap order of contact handling randomly { foreach (var ce in Contacts) { ce.Obj.Contact(ce, hitTime, this); } } else { for (var i = Contacts.Count - 1; i != -1; --i) { Contacts[i].Obj.Contact(Contacts[i], hitTime, this); } } Contacts.Clear(); // TODO C_BALL_SPIN_HACK // new delta .. i.e. time remaining dTime -= hitTime; // swap order of ball-ball collisions SwapBallCollisionHandling = !SwapBallCollisionHandling; } }