// 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; }
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(); } }
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(); }
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); } }
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); }
public void Reset() { wall = null; wallBody = null; }