using System; using System.Diagnostics; using BEPUphysics.Collidables; using BEPUphysics.Constraints.TwoEntity.Joints; using BEPUphysics.Constraints.TwoEntity.Motors; using BEPUphysics.Entities.Prefabs; using BEPUphysics.MathExtensions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using BEPUphysics.CollisionRuleManagement; using BEPUphysics.Materials; namespace BEPUphysicsDemos.Demos.Tests { /// /// For random tests and general fiddling. /// /// /// This demo type is initially excluded from the main list in the DemosGame. /// To access it while playing the demos, add an entry to the demoTypes array for this TestDemo. /// public class ReverseTrikeDemo : StandardDemo { private readonly RevoluteMotor drivingMotor1; private readonly RevoluteMotor drivingMotor2; private readonly RevoluteMotor steeringMotor1; private readonly RevoluteMotor steeringMotor2; private float driveSpeed = 30; private float maximumTurnAngle = MathHelper.Pi * .3f; /// /// Constructs a new demo. /// /// Game owning this demo. public ReverseTrikeDemo(DemosGame game) : base(game) { game.Camera.Position = new Vector3(0, 2, 15); game.Camera.Yaw = 0; game.Camera.Pitch = 0; Space.Add(new Box(new Vector3(0, -5, 0), 20, 1, 20)); var body = new Box(new Vector3(0, 0, 0), 2, 1, 3, 10); body.CollisionInformation.LocalPosition = new Vector3(0, .8f, 0); Space.Add(body); #region First Wheel var wheel = new Cylinder(body.Position + new Vector3(-1.3f, 0, -1.5f), .2f, .5f, 4); wheel.Material = new Material(1.5f, 1.5f, 0); wheel.Orientation = Quaternion.CreateFromAxisAngle(Vector3.Forward, MathHelper.PiOver2); //Preventing the occasional pointless collision pair can speed things up. CollisionRules.AddRule(body, wheel, CollisionRule.NoBroadPhase); //Connect the wheel to the body. var ballSocketJoint = new BallSocketJoint(body, wheel, wheel.Position); var swivelHingeAngularJoint = new SwivelHingeAngularJoint(body, wheel, Vector3.Up, Vector3.Right); //Motorize the wheel. drivingMotor1 = new RevoluteMotor(body, wheel, Vector3.Left); drivingMotor1.Settings.VelocityMotor.Softness = .2f; //Let it roll when the user isn't giving specific commands. drivingMotor1.IsActive = false; steeringMotor1 = new RevoluteMotor(body, wheel, Vector3.Up); steeringMotor1.Settings.Mode = MotorMode.Servomechanism; //The constructor makes a guess about how to set up the constraint. //It can't always be right since it doesn't have all the information; //in this case, it chooses the basis and test axis incorrectly. //This leads to a 'flipping' behavior when the wheel is rolling //(the test axis is 'rolling' with the wheel, and passes over //a singularity which causes a flip). //To fix this, we configure the constraint directly. //The basis is aligned with how the wheel is set up; we choose 'up' as //the motorized axis, and right/forward to define the angle measurement plane. //The test axis is set to be perpendicular to the wheel's rotation so that //it only measures the steering angle. //If you're curious, the angle measurement is just a Math.Atan2. //The current world test axis is dotted against the two plane axes (Right and Forward here). //This gives an x and y value. These can be plugged into Atan2 just like when //you compute an angle on a normal 2d graph. steeringMotor1.Basis.SetWorldAxes(Vector3.Up, Vector3.Right, Vector3.Forward); steeringMotor1.TestAxis = Vector3.Right; //Add the wheel and connection to the space. Space.Add(wheel); Space.Add(ballSocketJoint); Space.Add(swivelHingeAngularJoint); Space.Add(drivingMotor1); Space.Add(steeringMotor1); #endregion #region Second Wheel wheel = new Cylinder(body.Position + new Vector3(1.3f, 0, -1.5f), .2f, .5f, 4); wheel.Material = new Material(1.5f, 1.5f, 0); wheel.Orientation = Quaternion.CreateFromAxisAngle(Vector3.Forward, MathHelper.PiOver2); //Preventing the occasional pointless collision pair can speed things up. CollisionRules.AddRule(body, wheel, CollisionRule.NoBroadPhase); //Connect the wheel to the body. ballSocketJoint = new BallSocketJoint(body, wheel, wheel.Position); swivelHingeAngularJoint = new SwivelHingeAngularJoint(body, wheel, Vector3.Up, Vector3.Right); //Motorize the wheel. drivingMotor2 = new RevoluteMotor(body, wheel, Vector3.Left); drivingMotor2.Settings.VelocityMotor.Softness = .2f; //Let it roll when the user isn't giving specific commands. drivingMotor2.IsActive = false; steeringMotor2 = new RevoluteMotor(body, wheel, Vector3.Up); steeringMotor2.Settings.Mode = MotorMode.Servomechanism; //Configure the motor. See wheel 1 for more description. steeringMotor2.Basis.SetWorldAxes(Vector3.Up, Vector3.Right, Vector3.Forward); steeringMotor2.TestAxis = Vector3.Right; //Add the wheel and connection to the space. Space.Add(wheel); Space.Add(ballSocketJoint); Space.Add(swivelHingeAngularJoint); Space.Add(drivingMotor2); Space.Add(steeringMotor2); #endregion #region Third Wheel wheel = new Cylinder(body.Position + new Vector3(0, -.3f, 1.5f), .2f, .5f, 4); wheel.Material = new Material(1.5f, 1.5f, 0); wheel.Orientation = Quaternion.CreateFromAxisAngle(Vector3.Forward, MathHelper.PiOver2); //Preventing the occasional pointless collision pair can speed things up. CollisionRules.AddRule(body, wheel, CollisionRule.NoBroadPhase); //Connect the wheel to the body. ballSocketJoint = new BallSocketJoint(body, wheel, wheel.Position); //Notice that the third wheel isn't a swivel hinge, it's just a revolute axis. //This lets it roll, but prevents flopping around like the wheels of a grocery cart. //Could have used a RevoluteJoint solver group here, but this shows it's possible to do //the same things without using the combo-constraints. var revoluteAngularJoint = new RevoluteAngularJoint(body, wheel, Vector3.Right); //Add the wheel and connection to the space. Space.Add(wheel); Space.Add(ballSocketJoint); Space.Add(revoluteAngularJoint); #endregion int xLength = 256; int zLength = 256; float xSpacing = 8f; float zSpacing = 8f; var heights = new float[xLength, zLength]; for (int i = 0; i < xLength; i++) { for (int j = 0; j < zLength; j++) { float x = i - xLength / 2; float z = j - zLength / 2; //heights[i,j] = (float)(x * y / 1000f); heights[i, j] = (float)(10 * (Math.Sin(x / 8) + Math.Sin(z / 8))); //heights[i,j] = 3 * (float)Math.Sin(x * y / 100f); //heights[i,j] = (x * x * x * y - y * y * y * x) / 1000f; } } //Create the terrain. var terrain = new Terrain(heights, new AffineTransform( new Vector3(xSpacing, 1, zSpacing), Quaternion.Identity, new Vector3(-xLength * xSpacing / 2, -10, -zLength * zSpacing / 2))); Space.Add(terrain); game.ModelDrawer.Add(terrain); } /// /// Gets the name of the simulation. /// public override string Name { get { return "Reverse Trike"; } } public override void Update(float dt) { if (Game.KeyboardInput.IsKeyDown(Keys.NumPad8)) { //Go forward drivingMotor1.Settings.VelocityMotor.GoalVelocity = driveSpeed; drivingMotor2.Settings.VelocityMotor.GoalVelocity = driveSpeed; //The driving motors are disabled when no button is pressed, so need to turn it on. drivingMotor1.IsActive = true; drivingMotor2.IsActive = true; } else if (Game.KeyboardInput.IsKeyDown(Keys.NumPad5)) { //Go backward drivingMotor1.Settings.VelocityMotor.GoalVelocity = -driveSpeed; drivingMotor2.Settings.VelocityMotor.GoalVelocity = -driveSpeed; //The driving motors are disabled when no button is pressed, so need to turn it on. drivingMotor1.IsActive = true; drivingMotor2.IsActive = true; } else { //Let it roll. drivingMotor1.IsActive = false; drivingMotor2.IsActive = false; } if (Game.KeyboardInput.IsKeyDown(Keys.NumPad4)) { //Turn left steeringMotor1.Settings.Servo.Goal = maximumTurnAngle; steeringMotor2.Settings.Servo.Goal = maximumTurnAngle; } else if (Game.KeyboardInput.IsKeyDown(Keys.NumPad6)) { //Turn right steeringMotor1.Settings.Servo.Goal = -maximumTurnAngle; steeringMotor2.Settings.Servo.Goal = -maximumTurnAngle; } else { //Face forward steeringMotor1.Settings.Servo.Goal = 0; steeringMotor2.Settings.Servo.Goal = 0; } base.Update(dt); } public override void DrawUI() { base.DrawUI(); } } }