/// <inheritdoc /> public override void Draw(GameTime gameTime) { // Set the DiffuseColor to white to draw this Robot RobotEffect.DiffuseColor = Color.White.ToVector3(); Robot.Draw(RobotWorld, Camera.View, Camera.Projection); // Set the DiffuseColor to red to draw this Robot RobotEffect.DiffuseColor = Color.Red.ToVector3(); Robot.Draw(RobotTwoWorld, Camera.View, Camera.Projection); // Draw the Chair Chair.Draw(ChairWorld, Camera.View, Camera.Projection); // Draw the Tank Tank.Draw(TankWorld, Camera.View, Camera.Projection); // Draw bounding volumes Game.Gizmos.DrawCube(BoundingVolumesExtensions.GetCenter(RobotBox), BoundingVolumesExtensions.GetExtents(RobotBox) * 2f, Color.Yellow); Game.Gizmos.DrawCube(ChairOBBWorld, TouchingChair ? Color.Orange : Color.Green); Game.Gizmos.DrawSphere(_tankSphere.Center, _tankSphere.Radius * Vector3.One, TouchingTank ? Color.Orange : Color.Purple); Game.Gizmos.DrawCylinder(RobotTwoCylinder.Transform, TouchingOtherRobot ? Color.Orange : Color.White); base.Draw(gameTime); }
/// <summary> /// Creates an <see cref="OrientedBoundingBox">OrientedBoundingBox</see> from a <see cref="BoundingBox">BoundingBox</see>. /// </summary> /// <param name="box">A <see cref="BoundingBox">BoundingBox</see> to create the <see cref="OrientedBoundingBox">OrientedBoundingBox</see> from</param> /// <returns>The generated <see cref="OrientedBoundingBox">OrientedBoundingBox</see></returns> public static OrientedBoundingBox FromAABB(BoundingBox box) { var center = BoundingVolumesExtensions.GetCenter(box); var extents = BoundingVolumesExtensions.GetExtents(box); return(new OrientedBoundingBox(center, extents)); }
/// <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); } }
/// <inheritdoc /> public override void Draw(GameTime gameTime) { // Calculate the ViewProjection matrix var viewProjection = Camera.View * Camera.Projection; // Draw the Robot Robot.Draw(RobotWorld, Camera.View, Camera.Projection); // Set the WorldViewProjection and Texture for the Floor and draw it TilingEffect.Parameters["WorldViewProjection"].SetValue(FloorWorld * viewProjection); TilingEffect.Parameters["Texture"].SetValue(FloorTexture); Quad.Draw(TilingEffect); // Draw each Wall // First, set the Wall Texture TilingEffect.Parameters["Texture"].SetValue(WallTexture); for (int index = 0; index < WallWorldMatrices.Length - 1; index++) { // Set the WorldViewProjection matrix for each Wall TilingEffect.Parameters["WorldViewProjection"].SetValue(WallWorldMatrices[index] * viewProjection); // Draw the Wall Quad.Draw(TilingEffect); } // Draw the traversing Wall // For this, disable Back-Face Culling as we want to draw both sides of the Quad // Save the past RasterizerState var rasterizerState = GraphicsDevice.RasterizerState; // Use a RasterizerState which has Back-Face Culling disabled GraphicsDevice.RasterizerState = RasterizerState.CullNone; // Set the WorldViewProjection matrix and draw the Wall TilingEffect.Parameters["WorldViewProjection"].SetValue(WallWorldMatrices[WallWorldMatrices.Length - 1] * viewProjection); Quad.Draw(TilingEffect); // Restore the old RasterizerState GraphicsDevice.RasterizerState = rasterizerState; // Draw Gizmos for Bounding Boxes and Robot Cylinder for (int index = 0; index < WallBoxes.Length; index++) { var box = WallBoxes[index]; var center = BoundingVolumesExtensions.GetCenter(box); var extents = BoundingVolumesExtensions.GetExtents(box); Game.Gizmos.DrawCube(center, extents * 2f, Color.Red); } Game.Gizmos.DrawCylinder(RobotCylinder.Transform, Color.Yellow); base.Draw(gameTime); }
/// <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); }
/// <inheritdoc /> public override void Draw(GameTime gameTime) { // Calculate the ViewProjection matrix var viewProjection = Camera.View * Camera.Projection; // Robot drawing Robot.Draw(RobotWorld, Camera.View, Camera.Projection); // Floor drawing // Set the Technique inside the TilingEffect to "BaseTiling", we want to control the tiling on the floor // Using its original Texture Coordinates TilingEffect.CurrentTechnique = TilingEffect.Techniques["BaseTiling"]; // Set the Tiling value TilingEffect.Parameters["Tiling"].SetValue(new Vector2(10f, 10f)); // Set the WorldViewProjection matrix TilingEffect.Parameters["WorldViewProjection"].SetValue(FloorWorld * viewProjection); // Set the Texture that the Floor will use TilingEffect.Parameters["Texture"].SetValue(StonesTexture); Quad.Draw(TilingEffect); // Steps drawing // Set the Technique inside the TilingEffect to "WorldTiling" // We want to use the world position of the steps to define how to sample the Texture TilingEffect.CurrentTechnique = TilingEffect.Techniques["WorldTiling"]; // Set the Texture that the Steps will use TilingEffect.Parameters["Texture"].SetValue(CobbleTexture); // Set the Tiling value TilingEffect.Parameters["Tiling"].SetValue(Vector2.One * 0.05f); // Draw every Step for (int index = 0; index < StairsWorld.Length; index++) { // Get the World Matrix var matrix = StairsWorld[index]; // Set the World Matrix TilingEffect.Parameters["World"].SetValue(matrix); // Set the WorldViewProjection Matrix TilingEffect.Parameters["WorldViewProjection"].SetValue(matrix * viewProjection); BoxPrimitive.Draw(TilingEffect); } // Draw the Box, setting every matrix and its Texture BoxesEffect.World = BoxWorld; BoxesEffect.View = Camera.View; BoxesEffect.Projection = Camera.Projection; BoxesEffect.Texture = WoodenTexture; BoxPrimitive.Draw(BoxesEffect); // Gizmos Drawing for (int index = 0; index < Colliders.Length; index++) { var box = Colliders[index]; var center = BoundingVolumesExtensions.GetCenter(box); var extents = BoundingVolumesExtensions.GetExtents(box); Game.Gizmos.DrawCube(center, extents * 2f, Color.Red); } Game.Gizmos.DrawCylinder(RobotCylinder.Transform, Color.Yellow); base.Draw(gameTime); }
/// <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; } } }
/// <summary> /// Calculates an OBB with a given set of points. /// Tests every orientations between initValues and endValues, stepping through angle intervals with a given step size. /// Goes on until it reaches a step less than 0.01 /// </summary> /// <returns>A generated Oriented Bounding Box that contains the set of points</returns> private static OrientedBoundingBox ComputeFromPointsRecursive(Vector3[] points, Vector3 initValues, Vector3 endValues, float step) { var minObb = new OrientedBoundingBox(); var minimumVolume = float.MaxValue; var minInitValues = Vector3.Zero; var minEndValues = Vector3.Zero; var transformedPoints = new Vector3[points.Length]; float y, z; var x = initValues.X; while (x <= endValues.X) { y = initValues.Y; var rotationX = MathHelper.ToRadians(x); while (y <= endValues.Y) { z = initValues.Z; var rotationY = MathHelper.ToRadians(y); while (z <= endValues.Z) { // Rotation matrix var rotationZ = MathHelper.ToRadians(z); var rotationMatrix = Matrix.CreateFromYawPitchRoll(rotationY, rotationX, rotationZ); // Transform every point to OBB-Space for (var index = 0; index < transformedPoints.Length; index++) { transformedPoints[index] = Vector3.Transform(points[index], rotationMatrix); } // Obtain an AABB enclosing every transformed point var aabb = BoundingBox.CreateFromPoints(transformedPoints); // Calculate the volume of the AABB var volume = BoundingVolumesExtensions.GetVolume(aabb); // Find lesser volume if (volume < minimumVolume) { minimumVolume = volume; minInitValues = new Vector3(x, y, z); minEndValues = new Vector3(x + step, y + step, z + step); // Restore the AABB center in World-Space var center = BoundingVolumesExtensions.GetCenter(aabb); center = Vector3.Transform(center, rotationMatrix); // Create OBB minObb = new OrientedBoundingBox(center, BoundingVolumesExtensions.GetExtents(aabb)); minObb.Orientation = rotationMatrix; } z += step; } y += step; } x += step; } // Loop again if the step is higher than a given acceptance threshold if (step > 0.01f) { minObb = ComputeFromPointsRecursive(points, minInitValues, minEndValues, step / 10f); } return(minObb); }