Пример #1
0
        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);
        }
Пример #2
0
        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));
            }
        }
Пример #3
0
        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
        }
Пример #4
0
        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);
        }
Пример #5
0
        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;
            }
        }