public void OnInteract(Actor actor) { var v = orientation * vec3.UnitX; // This determins which side of the door is being opened. if (Utilities.Dot(v, actor.Position - position) < 0) { v *= -1; } // TODO: Retrieve static map body in a better way. var world = Scene.World; var body = world.RigidBodies.First(b => b.BodyType == RigidBodyTypes.Static && b.Shape is TriangleMeshShape); if (!PhysicsUtilities.Raycast(world, body, position + v, -vec3.UnitY, RaycastLength, out var results)) { Debug.Fail("Door raycast failed (this likely means that the raycast length needs to be extended)."); } // TODO: To animate properly, probably need to mark the body manually-controlled and apply step. actor.GroundPosition = results.Position; actor.ControllingBody.LinearVelocity = JVector.Zero; var player = (PlayerCharacter)actor; player.Lock(); // Attaching a null controller freezes the camera in place while the door opens. Scene.Camera.Attach(null); }
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(); }
/// <summary> /// Performs the collision detection method used in the "Corner Alignment" feature. /// </summary> CornerAlignmentResult CornerAlignCollisions(bool positiveDirection, float cornerDetectionDistance, LayerMask layerMask) { CornerAlignmentResult result = new CornerAlignmentResult(); result.Reset(); CollisionHitInfo hitInfo = new CollisionHitInfo(); hitInfo.Reset(); if (cornerDetectionDistance < 0) { return(result); } float castDistance = characterBody.SkinWidth + cornerDetectionDistance; Vector2 rayOrigin = characterController2D.transform.position + (positiveDirection ? -1 : 1) * characterController2D.transform.right * characterBody.verticalArea / 2 - characterController2D.transform.up * characterBody.SkinWidth; hitInfo = PhysicsUtilities.Raycast( characterBody.Is3D(), rayOrigin, (positiveDirection ? -1 : 1) * characterController2D.transform.right, castDistance, layerMask ); if (hitInfo.collision) { result.point = hitInfo.point; result.normalRotation = Quaternion.LookRotation(characterBody.bodyTransform.Forward, hitInfo.normal); result.success = true; } return(result); }