Ejemplo n.º 1
0
        // Normal and surface are mutually exclusive. Surfaces are used for the static world mesh, while the direct
        // normal is used for wall jumping off pseudo-static bodies (like platforms).
        public void Refresh(RigidBody body, SurfaceTriangle wall)
        {
            this.wall = wall;

            normal     = wall.Normal;
            flatNormal = wall.FlatNormal;
            wallBody   = body;
        }
Ejemplo n.º 2
0
        public override void PostStep(float step)
        {
            // Floor raycasts are based on step height. The intention is to snap smoothly going both up and down stairs
            // (which should also work fine for normal triangles, which are much less vertically-separated than steps).
            const float RaycastMultiplier = 1.2f;
            const float Epsilon           = 0.001f;

            var body       = Parent.ControllingBody;
            var halfHeight = new vec3(0, Parent.FullHeight / 2, 0);
            var p          = body.Position.ToVec3() - halfHeight;
            var ground     = Parent.Ground;

            // If the projection returns true, that means the actor is still within the current triangle.
            if (ground.Project(p, out vec3 result))
            {
                // This helps prevent very, very slow drift while supposedly stationary (on the order of 0.0001 per
                // second).
                if (Utilities.DistanceSquared(p, result) > Epsilon)
                {
                    body.Position = (result + halfHeight).ToJVector();
                }

                return;
            }

            // TODO: Store a better reference to static meshes (rather than querying the world every step).
            var world  = Parent.Scene.World;
            var map    = world.RigidBodies.First(b => b.Shape is TriangleMeshShape);
            var normal = ground.Normal;
            var offset = PhysicsConstants.StepThreshold * RaycastMultiplier;

            // TODO: Use properties or a constant for the raycast length (and offset).
            // TODO: Sometimes downward step snapping doesn't work. Should be fixed.
            // The raycast needs to be offset enough to catch steps.
            if (PhysicsUtilities.Raycast(world, map, p + normal * offset, -normal, offset * 2, out var results) &&
                results.Triangle != null)
            {
                // This means the actor moved to another triangle.
                var newGround = new SurfaceTriangle(results.Triangle, results.Normal, 0);

                // TODO: Is this projection needed? (since the position is already known from the raycast)
                newGround.Project(results.Position, out result);
                body.Position = (result + halfHeight).ToJVector();

                Parent.OnGroundTransition(newGround);

                return;
            }

            // If the actor has moved past a surface triangle (without transitioning to another one), a very small
            // forgiveness distance is checked before signalling the actor to become airborne. This distance is small
            // enough to not be noticeable during gameplay, but protects against potential floating-point errors near
            // the seams of triangles.
            if (SurfaceTriangle.ComputeForgiveness(p, ground) > EdgeForgiveness)
            {
                Parent.BecomeAirborneFromLedge();
            }
        }
Ejemplo n.º 3
0
        public override void PostStep(float step)
        {
            // This raycast distance is arbitrary. Should be short, but also long enough to catch transitions from
            // one wall triangle to another.
            const float RaycastLength = 0.5f;

            var player = (PlayerCharacter)Parent;
            var radius = player.CapsuleRadius;
            var body   = Parent.ControllingBody;

            // The starting point of the raycast is pulled back by a small amount in order to increase stability
            // (without this offset, the player frequently clipped through walls).
            var p = body.Position.ToVec3() - flatNormal * (radius - 0.1f);

            if (PhysicsUtilities.Raycast(Parent.Scene.World, wallBody, p, -flatNormal, RaycastLength,
                                         out var results))
            {
                // TODO: Might have to re-examine some of this if sliding along a pseudo-static body is allowed (such as the side of a sphere).
                // While sliding along the map mesh, the current triangle can change.
                if (wall != null)
                {
                    var triangle = results.Triangle;

                    // TODO: Verify that the new triangle isn't acute enough to cause a sideways collision instead.
                    if (!wall.IsSame(triangle))
                    {
                        wall = new SurfaceTriangle(triangle, results.Normal, 0, null, true);
                    }
                }

                // Note that for meshes, the triangle (and its flat normal) might have changed here (due to the
                // condition above).
                var result = (results.Position + flatNormal * radius).ToJVector();

                if (wall != null)
                {
                    body.Position = (results.Position + flatNormal * radius).ToJVector();
                }
                else
                {
                    // TODO: Set orientation as well (for rotation platforms).
                    body.SetPosition(result, step);
                }

                return;
            }

            // TODO: Consider adding edge forgiveness.
            // By this point, the player has moved off the current wall (without transitioning to a new triangle).
            player.BecomeAirborneFromWall();
        }
Ejemplo n.º 4
0
        public override void MidStep(float step)
        {
            var body    = Parent.ControllingBody;
            var vectors = new List <vec3>();

            foreach (Arbiter arbiter in body.Arbiters)
            {
                var contacts = arbiter.ContactList;

                for (int i = contacts.Count - 1; i >= 0; i--)
                {
                    var contact = contacts[i];
                    var b1      = contact.Body1;
                    var b2      = contact.Body2;

                    if (!(b1.IsStatic || b2.IsStatic))
                    {
                        continue;
                    }

                    var n = contact.Normal.ToVec3();

                    // A surface is a wall regardless of normal direction (it's negated below if necessary).
                    if (SurfaceTriangle.ComputeSurfaceType(n) != SurfaceTypes.Wall)
                    {
                        continue;
                    }

                    if (body == b1)
                    {
                        n *= -1;
                    }

                    // TODO: Compute proper contact penetration.
                    // This projects the resolution vector back out onto the flat plane (of the triangle on which the
                    // actor is currently standing).
                    var v     = Utilities.Normalize(Utilities.ProjectOntoPlane(n, Parent.Ground.Normal));
                    var angle = Utilities.Angle(n, v);
                    var l     = contact.Penetration / (float)Math.Cos(angle);

                    vectors.Add(v * l);
                    contacts.RemoveAt(i);
                }
            }

            if (vectors.Count > 0)
            {
                ResolveGroundedCollisions(vectors);
            }
        }
Ejemplo n.º 5
0
        private bool ShouldGenerateContact(RigidBody body, JVector[] triangle)
        {
            if (triangle == null)
            {
                return(true);
            }

            if (SurfaceTriangle.ComputeSurfaceType(triangle, WindingTypes.CounterClockwise) == SurfaceTypes.Floor)
            {
                return(false);
            }

            var n = Utilities.ComputeNormal(triangle[0], triangle[1], triangle[2], WindingTypes.CounterClockwise,
                                            false);

            return(JVector.Dot(controllingBody.LinearVelocity, n) < 0);
        }
Ejemplo n.º 6
0
 public void Reset()
 {
     wall     = null;
     wallBody = null;
 }