/// <summary> /// Apply horizontal movement, detecting and solving collisions with sliding /// </summary> /// <param name="scaledVelocity">The current velocity scaled by deltaTime</param> private void SolveHorizontalMovementSliding(Vector3 scaledVelocity) { // Has horizontal movement? if (Vector3.Dot(scaledVelocity, new Vector3(1f, 0f, 1f)) == 0f) { return; } // Start by moving the Cylinder horizontally RobotCylinder.Center += new Vector3(scaledVelocity.X, 0f, scaledVelocity.Z); // Check intersection for every collider for (var index = 0; index < Colliders.Length; index++) { if (!RobotCylinder.Intersects(Colliders[index]).Equals(BoxCylinderIntersection.Intersecting)) { continue; } // Get the intersected collider and its center var collider = Colliders[index]; var colliderCenter = BoundingVolumesExtensions.GetCenter(collider); // The Robot collided with this thing // Is it a step? Can the Robot climb it? bool stepClimbed = SolveStepCollision(collider, index); // If the Robot collided with a step and climbed it, stop here // Else go on if (stepClimbed) { return; } // Get the cylinder center at the same Y-level as the box var sameLevelCenter = RobotCylinder.Center; sameLevelCenter.Y = colliderCenter.Y; // Find the closest horizontal point from the box var closestPoint = BoundingVolumesExtensions.ClosestPoint(collider, sameLevelCenter); // Calculate our normal vector from the "Same Level Center" of the cylinder to the closest point // This happens in a 2D fashion as we are on the same Y-Plane var normalVector = sameLevelCenter - closestPoint; var normalVectorLength = normalVector.Length(); // Our penetration is the difference between the radius of the Cylinder and the Normal Vector // For precission problems, we push the cylinder with a small increment to prevent re-colliding into the geometry var penetration = RobotCylinder.Radius - normalVector.Length() + EPSILON; // Push the center out of the box // Normalize our Normal Vector using its length first RobotCylinder.Center += (normalVector / normalVectorLength * penetration); } }
/// <summary> /// Solves the intersection between the Robot and a collider. /// </summary> /// <param name="collider">The collider the Robot intersected with</param> /// <param name="colliderIndex">The index of the collider in the collider array the Robot intersected with</param> /// <returns>True if the collider was a step and it was climbed, False otherwise</returns> private bool SolveStepCollision(BoundingBox collider, int colliderIndex) { // Get the collider properties to check if it's a step // Also, to calculate penetration var extents = BoundingVolumesExtensions.GetExtents(collider); var colliderCenter = BoundingVolumesExtensions.GetCenter(collider); // Is this collider a step? // If not, exit if (extents.Y >= 6f) { return(false); } // Is the base of the cylinder close to the step top? // If not, exit var distanceToTop = MathF.Abs((RobotCylinder.Center.Y - RobotCylinder.HalfHeight) - (colliderCenter.Y + extents.Y)); if (distanceToTop >= 12f) { return(false); } // We want to climb the step // It is climbable if we can reposition our cylinder in a way that // it doesn't collide with anything else var pastPosition = RobotCylinder.Center; RobotCylinder.Center += Vector3.Up * distanceToTop; for (int index = 0; index < Colliders.Length; index++) { if (index != colliderIndex && RobotCylinder.Intersects(Colliders[index]).Equals(BoxCylinderIntersection.Intersecting)) { // We found a case in which the cylinder // intersects with other colliders, so the climb is not possible RobotCylinder.Center = pastPosition; return(false); } } // If we got here the climb was possible // (And the Robot position was already updated) return(true); }
/// <summary> /// Apply horizontal movement, detecting and solving collisions /// </summary> /// <param name="scaledVelocity">The current velocity scaled by deltaTime</param> private void SolveVerticalMovement(Vector3 scaledVelocity) { // If the Robot has vertical velocity if (scaledVelocity.Y == 0f) { return; } // Start by moving the Cylinder RobotCylinder.Center += Vector3.Up * scaledVelocity.Y; // Set the OnGround flag on false, update it later if we find a collision OnGround = false; // Collision detection var collided = false; var foundIndex = -1; for (var index = 0; index < Colliders.Length; index++) { if (!RobotCylinder.Intersects(Colliders[index]).Equals(BoxCylinderIntersection.Intersecting)) { continue; } // If we collided with something, set our velocity in Y to zero to reset acceleration RobotVelocity = new Vector3(RobotVelocity.X, 0f, RobotVelocity.Z); // Set our index and collision flag to true // The index is to tell which collider the Robot intersects with collided = true; foundIndex = index; break; } // We correct based on differences in Y until we don't collide anymore // Not usual to iterate here more than once, but could happen while (collided) { var collider = Colliders[foundIndex]; var colliderY = BoundingVolumesExtensions.GetCenter(collider).Y; var cylinderY = RobotCylinder.Center.Y; var extents = BoundingVolumesExtensions.GetExtents(collider); float penetration; // If we are on top of the collider, push up // Also, set the OnGround flag to true if (cylinderY > colliderY) { penetration = colliderY + extents.Y - cylinderY + RobotCylinder.HalfHeight; OnGround = true; } // If we are on bottom of the collider, push down else { penetration = -cylinderY - RobotCylinder.HalfHeight + colliderY - extents.Y; } // Move our Cylinder so we are not colliding anymore RobotCylinder.Center += Vector3.Up * penetration; collided = false; // Check for collisions again for (var index = 0; index < Colliders.Length; index++) { if (!RobotCylinder.Intersects(Colliders[index]).Equals(BoxCylinderIntersection.Intersecting)) { continue; } // Iterate until we don't collide with anything anymore collided = true; foundIndex = index; break; } } }
/// <inheritdoc /> public override void Update(GameTime gameTime) { // Initialize values indicating if the Robot moved, if it rotated, and how much movement should be applied var advanceAmount = 0f; var rotated = false; var moved = false; // Check for key presses and rotate accordingly // We can stack rotations in a given axis by multiplying our past matrix // By a new matrix containing a new rotation to apply if (Game.CurrentKeyboardState.IsKeyDown(Keys.Right)) { RobotRotation *= Matrix.CreateRotationY(-RobotRotatingVelocity); rotated = true; } else if (Game.CurrentKeyboardState.IsKeyDown(Keys.Left)) { RobotRotation *= Matrix.CreateRotationY(RobotRotatingVelocity); rotated = true; } // Check for key presses and set our advance variable accordingly if (Game.CurrentKeyboardState.IsKeyDown(Keys.Up)) { advanceAmount = RobotVelocity; moved = true; } else if (Game.CurrentKeyboardState.IsKeyDown(Keys.Down)) { advanceAmount = -RobotVelocity; moved = true; } // If there was any movement if (moved) { // Calculate the Robot new Position using the last Position, // And adding an increment in the Robot Direction (calculated by rotating a vector by its rotation) var newPosition = RobotPosition + Vector3.Transform(Vector3.Backward, RobotRotation) * advanceAmount; // Set the Center of the Cylinder as our calculated position RobotCylinder.Center = newPosition; // Test against every wall. If there was a collision, move our Cylinder back to its original position for (int index = 0; index < WallBoxes.Length; index++) { if (!RobotCylinder.Intersects(WallBoxes[index]).Equals(BoxCylinderIntersection.None)) { moved = false; RobotCylinder.Center = RobotPosition; break; } } // If there was no collision, update our Robot Position value if (moved) { RobotPosition = newPosition; } } // If we effectively moved (with no collision) or rotated if (moved || rotated) { // Calculate our World Matrix again RobotWorld = RobotScale * RobotRotation * Matrix.CreateTranslation(RobotPosition); // Update the Camera accordingly, as it follows the Robot UpdateCamera(); } // Update Gizmos with the View Projection matrices Game.Gizmos.UpdateViewProjection(Camera.View, Camera.Projection); base.Update(gameTime); }