void IEndOfFrameUpdateable.Update(float dt)
{
// Since the grabbed position is usually examined graphically, it's good to use the interpolated positions in case the
// engine is using internal time stepping and interpolation.
GrabbedPosition = Matrix3X3.Transform(LocalOffset, Entity.BufferedStates.InterpolatedStates.OrientationMatrix) + Entity.BufferedStates.InterpolatedStates.Position;
//LocalOffset = Vector3.Transform(GrabbedPosition - Entity.BufferedStates.InterpolatedStates.Position, Quaternion.Conjugate(Entity.BufferedStates.InterpolatedStates.Orientation));
angularMotor.Settings.Servo.Goal = Entity.BufferedStates.InterpolatedStates.Orientation;
}
The angularMotor goal doesn't seem to affect the orientation of the entity though.
Creating an orientation from a single direction isn't a well defined operation, so an implementation ends up picking from one of many possible orientations. You may want to look into using the CreateLookAt or CreateWorld XNA Matrix helper methods instead.
It sometime climbs up after reaching a certain point too?!
An angular motor does not apply a linear impulse. Something else is causing it to translate. Does it start inactive, and because changing the motor settings activates it, gravity is allowed to take effect? Or are there other parts of the code which try to modify the linear velocity?
The angular motion appears to be about right, though. The initial flip around would be expected given some values of goal orientation (though I assume you are aiming for an incremental rotation, in which case the goal is currently incorrect in some way), and it appears to follow the cursor after the fact.
public class MotorizedGrabSpring : Updateable, IEndOfFrameUpdateable
{
private SingleEntityAngularMotor angularMotor;
private Game1 game;
private bool isActive;
private bool isEntityDynamic;
private SingleEntityLinearMotor linearMotor;
private CollisionRule originalEntityCollisonRule;
/// <summary>
/// Constructs a grab constraint.
/// </summary>
public MotorizedGrabSpring(Game1 game)
{
this.game = game;
//Note that when the motor is created using the empty constructor,
//it starts deactivated. This prevents explosions from attempting
//to update it without being configured.
linearMotor = new SingleEntityLinearMotor();
angularMotor = new SingleEntityAngularMotor();
linearMotor.Settings.Mode = MotorMode.Servomechanism;
angularMotor.Settings.Mode = MotorMode.Servomechanism;
//You can configure the stiffness and damping of the corrective springs like so.
//For this example, the motors will be just be the nearly rigid default.
linearMotor.Settings.Servo.SpringSettings.StiffnessConstant = 60000f;
linearMotor.Settings.Servo.SpringSettings.DampingConstant = 900f;
//An unlimited motor will gladly push the entity through other objects.
//Putting a limit on the strength of the motor will prevent it from doing so.
linearMotor.Settings.MaximumForce = 10000;
IsUpdating = false;
game.Space.Add(this);
}
/// <summary>
/// Reinitializes the grabbing constraint with new information.
/// </summary>
/// <param name="e">Entity to grab.</param>
/// <param name="grabLocation">Location on the entity being grabbed in world space.</param>
public void Setup(Entity e, Vector3 grabLocation)
{
Entity = e;
// Get the original entity properties that will need to be changed for this GrabSpring to function correctly
isActive = Entity.IsActive;
isEntityDynamic = Entity.IsDynamic;
originalEntityCollisonRule = Entity.CollisionInformation.CollisionRules.Personal;
// Now change those properties so that the entity can be manipulated by the GrabSpring in the desired manner
// Make sure that the grabbed entity is active so it can be moved by the 'GrabSpring' if the simulation is paused
Entity.IsActive = true;
if (!isEntityDynamic)
{
Entity.BecomeDynamic(1f);
}
// Can I assign a rule to this entity's collision pairs?
// This way the other object in the collision pair will also react in the same way as this object for that particular collision.
if (!(Entity.CollisionInformation.Tag is EntityAttractor))
{
Entity.CollisionInformation.CollisionRules.Personal = CollisionRule.NoBroadPhase;
}
LocalOffset = Vector3.Transform(grabLocation - e.Position, Quaternion.Conjugate(e.Orientation));
angularMotor.Settings.Servo.Goal = e.Orientation;
angularMotor.Settings.VelocityMotor.Softness = .1f / e.Mass;
// Allow angular motor to rotate object
angularMotor.Settings.Mode = MotorMode.Servomechanism;
GoalPosition = grabLocation;
// The stiffness, damping, and maximum force could be assigned during setup if the motor
// needs to behave similarly for entities of varying masses.
// When using a fixed configuration, the grabspring will behave weakly when trying to move extremely heavy objects,
// while staying very responsive for sufficiently light objects.
linearMotor.Settings.Servo.SpringSettings.StiffnessConstant = 60000f * Entity.Mass;
linearMotor.Settings.Servo.SpringSettings.DampingConstant = 900f * Entity.Mass;
linearMotor.Settings.MaximumForce = 10000f * Entity.Mass;
}
/// <summary>
/// Releases the entity being held by the grab spring.
/// </summary>
public void Release()
{
if (!isEntityDynamic)
{
Entity.BecomeKinematic();
}
if (!game.IsPaused) // isActive
{
// Remove all forces acting on the entity so it doesn't move in last direction of grabber after unpausing
Entity.AngularVelocity = Vector3.Zero;
Entity.LinearVelocity = Vector3.Zero;
}
Entity.IsActive = isActive;
// Reset the entity's collision rule back to its original state
Entity.CollisionInformation.CollisionRules.Personal = originalEntityCollisonRule;
Entity = null;
}
/// <summary>
/// Updates the grab constraint's grab position after the end of a frame.
/// </summary>
/// <param name="dt">Time since last frame in simulation seconds.</param>
void IEndOfFrameUpdateable.Update(float dt)
{
linearMotor.IsActive = false;
// Since the grabbed position is usually examined graphically, it's good to use the interpolated positions in case the
// engine is using internal time stepping and interpolation.
GrabbedPosition = Matrix3X3.Transform(LocalOffset, Entity.BufferedStates.InterpolatedStates.OrientationMatrix) + Entity.BufferedStates.InterpolatedStates.Position;
Matrix rotation = MathTools.RotationFrom(Vector3.Normalize(GoalPosition - Entity.BufferedStates.InterpolatedStates.Position));
angularMotor.Settings.Servo.Goal = MathTools.QuaternionFromRowMajorRotationMatrix(rotation);
}
public override void OnAdditionToSpace(ISpace space)
{
space.Add(linearMotor);
space.Add(angularMotor);
}
public override void OnRemovalFromSpace(ISpace space)
{
space.Remove(linearMotor);
space.Remove(angularMotor);
}
/// <summary>
/// Gets the grabbed entity.
/// </summary>
public Entity Entity
{
get { return linearMotor.Entity; }
private set
{
// Don't bother changing the entity if it is the same
if (linearMotor.Entity != value)
{
linearMotor.Entity = value;
angularMotor.Entity = value;
//The motors can only be on while the entity isn't null.
if (value != null)
{
linearMotor.IsActive = false;
angularMotor.IsActive = true;
IsUpdating = true;
}
else
{
linearMotor.IsActive = false;
angularMotor.IsActive = false;
IsUpdating = false;
}
}
}
}
/// <summary>
/// Gets the location that the entity will be pulled towards.
/// </summary>
public Vector3 GoalPosition
{
get { return linearMotor.Settings.Servo.Goal; }
set { linearMotor.Settings.Servo.Goal = value; }
}
/// <summary>
/// Gets the offset from the entity to the grabbed location in its local space.
/// </summary>
public Vector3 LocalOffset
{
get { return linearMotor.LocalPoint; }
private set { linearMotor.LocalPoint = value; }
}
/// <summary>
/// Gets the last updated position of the grab location on the surface of the entity.
/// </summary>
public Vector3 GrabbedPosition { get; private set; }
/// <summary>
/// Gets whether or not the grabber is currently grabbing something.
/// </summary>
public bool IsGrabbing
{
get { return Entity != null; }
}
}
I can't work out why the downward movement is occurring even if I do disable (deactivate) the linear motor.
Given that it appears you're making a widget-tool-rotator-thing, I would probably just take the base orientation that the widget rotation 'session' starts with and apply rotations around the relevant axis by the desired angle change. The resulting orientation is the current goal.
I can't work out why the downward movement is occurring even if I do disable (deactivate) the linear motor.
By elimination, if it's not the linear motor, and it can't (directly) be the angular motor, then it must be something external that either changes the linear motion (gravity or anything else) or interprets the angular motor's results to impart linear motion (collisions, other active constraints, etc.).
I would probably just take the base orientation that the widget rotation 'session' starts with and apply rotations around the relevant axis by the desired angle change. The resulting orientation is the current goal.
I've attempted this but the angle change will be too small using the following:
Typically, rotation widgets will do something like:
-Create a plane based on the object position and rotation axis.
-Ray cast the mouse's initial location against that plane. Compute an angle based on the intersection location.
-As the cursor moves, test it against the plane. Compute the angle based on the new intersection. Compare the new angle with the original angle.
The new angle - original angle, combined with the rotation axis, gives you the incremental transform from the original state to the current state. That incremental transform can be made into a matrix or quaternion and multiplied against the original orientation to get the goal orientation.
That would be a discontinuity that you could handle separately. There's no single 'correct' way to interpret the user input. Just pick something that feels good enough.
That would be a discontinuity that you could handle separately.
Does the plane have to be based on the axis of rotation? Can it not be based on the camera forward; the rate of change for the angles would be the same for all camera orientations then?
You "can" do anything, so long as you figure out a way to make it feel right
I would recommend checking out various modelling packages like Blender as an example. If I remember right, it uses a combination of the rotation axis plane approach and a special case for edge-on clicking. Instead of using the rotation axis plane when in a degenerate situation, it uses a perpendicular plane and interprets the input in a still-intuitive way. Modo does something similar too, along with most other modelers.