Each constraint has a few properties. The RevoluteMotor, for example, has a TestAxis (plus a local version) and a Basis. The Basis is attached to entity A, the test axis is attached to entity B. The test axis is tested against the basis to compute the angle.
Other constraint types have different properties. For angular constraints, the usual fare are axes and bases. For linear constraints, there will also be anchor offsets and things like that. The documentation for individual properties- especially on the Basis properties- is useful for determining how things work together.
So, to use the previously posted code as an example: the pi/4 originates from how the anchor is positioned relative to the connected entities' positions. If the platform is lying flat, the angle between the anchor offsets is Pi/4. The RevoluteJoint constructor is assuming that you want the constraint angles configured according to this. It's a decent heuristic that works intuitively in many cases, but not so much when connecting things to the ghostly World entity. You can, however, configure the joint however you want. For example, if you didn't want to move things around to adhere to the heuristic, you could adjust the constraint like so:
Code: Select all
Entity ground = new Box(Vector3.Zero, 30, 1, 30);
Box platform = new Box(new Vector3(0, 1.5f, 0), 3, .5f, 3, 10);
Cylinder roller = new Cylinder(platform.Position + new Vector3(0, .5f, 0), 3, .25f, 10);
roller.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.PiOver2);
var baseJoint = new RevoluteJoint(null, platform, new Vector3(1.5f, 1.5f, 0), Vector3.UnitZ);
baseJoint.Motor.IsActive = true;
baseJoint.Motor.Settings.Mode = MotorMode.Servomechanism;
baseJoint.Motor.Settings.Servo.Goal = MathHelper.PiOver2;
baseJoint.Motor.Settings.Servo.SpringSettings.StiffnessConstant = 0;
baseJoint.Motor.Settings.Servo.SpringSettings.DampingConstant /= 10;
baseJoint.Motor.Settings.Servo.BaseCorrectiveSpeed = MathHelper.PiOver2 / 1.5f;
//Adjust the motor basis such that 0 corresponds to the platform being horizontal, and pi/2 to vertical.
//The first axis is the rotation axis, the second axis is the 'base' direction corresponding to 0 on the measurement plane,
//and the third axis is the second axis on the measurement plane and completes the basis.
baseJoint.Motor.Basis.SetWorldAxes(Vector3.Forward, Vector3.Left, Vector3.Up);
//The test axis is projected onto the measurement plane to determine the angle. We align it with the base direction we chose initially.
baseJoint.Motor.TestAxis = Vector3.Left;
baseJoint.Limit.MaximumAngle = MathHelper.PiOver2;
baseJoint.Limit.MinimumAngle = 0;
baseJoint.Limit.IsActive = true;
//Similar to the above, set up the bases to match the angle expectations.
//Note the lack of a third axis- it is implicitly defined by the first two axes.
baseJoint.Limit.Basis.SetWorldAxes(Vector3.Forward, Vector3.Left);
baseJoint.Limit.TestAxis = Vector3.Left;
var rollerToBase = new RevoluteJoint(platform, roller, roller.Position, Vector3.UnitZ);
rollerToBase.Motor.IsActive = true;
rollerToBase.Motor.Settings.Mode = MotorMode.Servomechanism;
Space.Add(ground);
Space.Add(platform);
Space.Add(roller);
Space.Add(baseJoint);
Space.Add(rollerToBase);
Also note that the alternative empty joint constructor will not try to guess intent or use any heuristics. It just assumes you will configure everything. So, an equivalent configuration without using the helpy-constructor would look like this:
Code: Select all
Entity ground = new Box(Vector3.Zero, 30, 1, 30);
Box platform = new Box(new Vector3(0, 1.5f, 0), 3, .5f, 3, 10);
Cylinder roller = new Cylinder(platform.Position + new Vector3(0, .5f, 0), 3, .25f, 10);
roller.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.PiOver2);
var baseJoint = new RevoluteJoint();
//The empty constructor does not automatically set anything to active, so we must manually activate and configure everything we want.
baseJoint.IsActive = true;
baseJoint.AngularJoint.IsActive = true;
//When the empty constructor is used, connections should be considered *uninitialized* rather than merely null.
//This is because directly setting a connection to null will set it internally to the world entity.
//Leaving it unset will result in null reference exceptions.
baseJoint.AngularJoint.ConnectionA = null;
baseJoint.AngularJoint.ConnectionB = platform;
baseJoint.AngularJoint.WorldFreeAxisA = Vector3.UnitZ;
baseJoint.AngularJoint.WorldFreeAxisB = Vector3.UnitZ;
baseJoint.BallSocketJoint.IsActive = true;
baseJoint.BallSocketJoint.ConnectionA = null;
baseJoint.BallSocketJoint.ConnectionB = platform;
var anchor = new Vector3(1.5f, 1.5f, 0);
baseJoint.BallSocketJoint.OffsetA = anchor; //"World" entity has a position of (0,0,0).
baseJoint.BallSocketJoint.OffsetB = anchor - platform.Position;
baseJoint.Motor.IsActive = true;
baseJoint.Motor.ConnectionA = null;
baseJoint.Motor.ConnectionB = platform;
baseJoint.Motor.Settings.Mode = MotorMode.Servomechanism;
baseJoint.Motor.Settings.Servo.Goal = MathHelper.PiOver2;
baseJoint.Motor.Settings.Servo.SpringSettings.StiffnessConstant = 0;
baseJoint.Motor.Settings.Servo.SpringSettings.DampingConstant /= 10;
baseJoint.Motor.Settings.Servo.BaseCorrectiveSpeed = MathHelper.PiOver2 / 1.5f;
//Adjust the motor basis such that 0 corresponds to the platform being horizontal, and pi/2 to vertical.
//The first axis is the rotation axis, the second axis is the 'base' direction corresponding to 0 on the measurement plane,
//and the third axis is the second axis on the measurement plane and completes the basis.
baseJoint.Motor.Basis.SetWorldAxes(Vector3.Forward, Vector3.Left, Vector3.Up);
//The test axis is projected onto the measurement plane to determine the angle. We align it with the base direction we chose initially.
baseJoint.Motor.TestAxis = Vector3.Left;
baseJoint.Limit.IsActive = true;
baseJoint.Limit.ConnectionA = null;
baseJoint.Limit.ConnectionB = platform;
baseJoint.Limit.MaximumAngle = MathHelper.PiOver2;
baseJoint.Limit.MinimumAngle = 0;
//Similar to the above, set up the bases to match the angle expectations.
//Note the lack of a third axis- it is implicitly defined by the first two axes.
//(Small oops here: the basis rotation matrix defaults to the zero matrix for 2d bases in the current version.
//That's going to be changed, but in the mean time, the rotation matrix needs to be specified explicitly.)
baseJoint.Limit.Basis.SetWorldAxes(Vector3.Forward, Vector3.Left, Matrix3X3.Identity);
baseJoint.Limit.TestAxis = Vector3.Left;
var rollerToBase = new RevoluteJoint();
rollerToBase.IsActive = true;
rollerToBase.BallSocketJoint.IsActive = true;
rollerToBase.BallSocketJoint.ConnectionA = platform;
rollerToBase.BallSocketJoint.ConnectionB = roller;
rollerToBase.BallSocketJoint.OffsetA = roller.Position- platform.Position;
//offsetB is zero since the roller's position is the roller's position.
rollerToBase.AngularJoint.IsActive = true;
rollerToBase.AngularJoint.ConnectionA = platform;
rollerToBase.AngularJoint.ConnectionB = roller;
rollerToBase.AngularJoint.WorldFreeAxisA = Vector3.UnitZ;
rollerToBase.AngularJoint.WorldFreeAxisB = Vector3.UnitZ;
rollerToBase.Motor.IsActive = true;
rollerToBase.Motor.ConnectionA = platform;
rollerToBase.Motor.ConnectionB = roller;
rollerToBase.Motor.Basis.SetWorldAxes(Vector3.Forward, Vector3.Left, Vector3.Up);
rollerToBase.Motor.TestAxis = Vector3.Left;
rollerToBase.Motor.Settings.Mode = MotorMode.Servomechanism;
Space.Add(ground);
Space.Add(platform);
Space.Add(roller);
Space.Add(baseJoint);
Space.Add(rollerToBase);
I should note that the basis associated with the RevoluteMotor may be changing soon. It implies you have the freedom to assign the third axis to either perpendicular option, but in fact, the third axis is uniquely defined by the first two axes and failure to adhere to this secretive requirement will result in wonky no-goodness. Expect it to become a 2D basis like the RevoluteLimit instead of a 3D basis.