Rotate with an offset

Discuss any questions about BEPUphysics or problems encountered.
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Rotate with an offset

Post by snoozbuster »

So, I'm using the function from EntityRotator to get velocity and Entity.AngularVelocity and such. However, I've recently discovered I need to be able to rotate (around Z) around a point offset from the center of the model. To add complication, I need to be able to rotate along another axis, which may or may not be at the same time, and needs to rotate around the center. Is there anything that might help me accomplish this? I know AngularVelocity isn't going to cut it, is there a clever way I can use linear velocity to accomplish a circle movement, or am I doomed to use splines or something similar of which I have no knowledge?
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

It sounds like offsetting the collision shape will do something close to what you want. Setting the entity.CollisionInformation.LocalPosition to (0,1,0) will put the collision shape 1 unit above the center of mass using the entity's current orientation-defined up vector.

For example, if the LocalPosition was (0,1,0), then rotating around the local Y axis would rotate around the center since there is no horizontal offset. Rotating around local X or Z would swing the shape around due to the offset.

Other than that, yes, you can use linear velocity to move an object in a circle. The angular velocity is the same as in the non-offset case. The linear velocity is whateverItWouldBeNormally + Vector3.Cross(angularVelocity, offset).

Note that because you're applying the tangential velocity repeatedly at discrete intervals, there will be position drift away from circular motion. You could introduce a corrective factor to the linear velocity to keep it near the desired location. You could also use this to somewhat sluggishly follow a position goal. Just take the position error, multiply it by a factor, and divide by the timestep (e.g. (goal - currentPosition) * 0.2 / dt).
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Norbo wrote:It sounds like offsetting the collision shape will do something close to what you want. Setting the entity.CollisionInformation.LocalPosition to (0,1,0) will put the collision shape 1 unit above the center of mass using the entity's current orientation-defined up vector.

For example, if the LocalPosition was (0,1,0), then rotating around the local Y axis would rotate around the center since there is no horizontal offset. Rotating around local X or Z would swing the shape around due to the offset.
This is exactly what I'm looking for, but I don't totally understand. Offsetting by (3.6, 0, 0) gives me the proper result for my rotation around Z, but then rotating around Y doesn't rotate correctly. I need the offset to offset on X for the Z rotation, but then rotate around Y without the offset. Is there a way to ignore the offset for certain rotations or something similar? An added complication is that because of the Z rotation, setting AngularVelocity on an X/Y basis will cause incorrect rotation if it's not aligned with on axis, is there a way to use the local axis to rotate around for this case (that is, this axis, if it exists, would rotate with the Entity when it rotates, so rotating around Y would always rotate in the proper direction regardless of orientation)?
Also, the geometry being transformed by the base Entity's WorldTransform isn't correct,Ent.CollisionInformation.WorldTransform.Matrix gives me what I'm looking for, right? It appears to, but will it always be right? (PROTIP: If you've still got that test code I sent you, the objects I'm talking about are the tubes, and the goal is to keep them rotating as they are in the example, but also be able to simultaneously (and independantly) rotate around Z.)
Norbo wrote: Other than that, yes, you can use linear velocity to move an object in a circle. The angular velocity is the same as in the non-offset case. The linear velocity is whateverItWouldBeNormally + Vector3.Cross(angularVelocity, offset).

Note that because you're applying the tangential velocity repeatedly at discrete intervals, there will be position drift away from circular motion. You could introduce a corrective factor to the linear velocity to keep it near the desired location. You could also use this to somewhat sluggishly follow a position goal. Just take the position error, multiply it by a factor, and divide by the timestep (e.g. (goal - currentPosition) * 0.2 / dt).
I may end up having to do this, but that'll be a pain. In this case, fortunately the linear velocity is normally none, except when rotating. I don't really understand how that formula gives me circular motion, though, do I have to do it each frame?
Last edited by snoozbuster on Thu Nov 10, 2011 6:54 am, edited 1 time in total.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

This is exactly what I'm looking for, but I don't totally understand. Offsetting by (3.6, 0, 0) gives me the proper result for my rotation around Z, but then rotating around Y doesn't rotate correctly. I need the offset to offset on X for the Z rotation, but then rotate around Y without the offset.
Offsetting by (3.6, 0, 0) means that rotating around local X will appear to rotate around the center, as the offset and center of mass are on the axis together. Rotating around local Y or Z will swing the shape around.

So:
Is there a way to ignore the offset for certain rotations or something similar?
No; the LocalPosition offset is a consistent physical effect.
Also, the geometry being transformed by the base Entity's WorldTransform isn't correct,Ent.CollisionInformation.WorldTransform.Matrix gives me what I'm looking for, right? It appears to, but will it always be right?
When offsetting the collision shape using the LocalPosition, the entity.CollisionInformation.WorldTransform does indeed include the offset. The entity.WorldTransform only handles the center of mass position of the entity.

The collision information's world transform will be 'wrong' if internal timestepping and interpolation is being used. In this case, the entity.CollisionInformation.WorldTransform will not include interpolated data. Of course, the entity.WorldTransform does not include interpolated data either. If you want to use interpolation, you can offset the interpolated state taken from the interpolation managers.
I don't really understand how that formula gives me circular motion, though, do I have to do it each frame?
Yes. That equation gives you the tangential velocity of the point. More information can be found on wikipedia: http://en.wikipedia.org/wiki/Circular_motion
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

I made an edit to my original post, which said this: "An added complication is that because of the Z rotation, setting AngularVelocity on an X/Y basis will cause incorrect rotation if it's not aligned with an axis, is there a way to use the local axis to rotate around for this case (that is, this axis, if it exists, would rotate with the Entity when it rotates, so rotating around Y would always rotate in the proper direction regardless of orientation)?" There was also a protip I added. I also just realized I was really just asking the same thing as "is there a way to rotate without LocalOffset when there is a LocalOffset," just in a slightly different way. I suppose a better question would be "is there a method of obtaining the information about the center of the entity's mass when there is a LocalOffset?" Certainly the engine has to have it so as to apply the offset.

The irksome part about not being able to modify the behavior of LocalOffset based on axis is that for the things rotating around X I need to offset on Y, and for the things I need to rotate around Y I have to offset on X. My tubes have really odd Up/Down/etc directions, and Up and Down are the only directions that will stay constant no matter what the X/Y rotation is. I was attempting to find the AngularVelocity needed to rotate around the unit vector specified by Down, but my vector math is horribly elementary. I've only done basic 2D vector stuff. Do you know how that might be accomplished?

Another question: How hard would it be to tweak the engine's code to allow for axes that ignore LocalOffset?
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

An added complication is that because of the Z rotation, setting AngularVelocity on an X/Y basis will cause incorrect rotation if it's not aligned with on axis, is there a way to use the local axis to rotate around for this case (that is, this axis, if it exists, would rotate with the Entity when it rotates, so rotating around Y would always rotate in the proper direction regardless of orientation)?
You can get local axes directly from the world transform or orientation matrix. The Matrix class has Up, Left, Right, Down, Forward, and Backward properties. Rotation matrices are orthonormal bases, so grabbing these directions is as simple and fast as looking at the components of the matrix. These directions are the same you would get if you transformed Vector3.Up/Down/Etc. by the rotation matrix.
I suppose a better question would be "is there a method of obtaining the information about the center of the entity's mass when there is a LocalOffset?"
The entity.Position is the center of mass. The entity.WorldTransform translation component is the entity.Position.
I was attempting to find the AngularVelocity needed to rotate around the unit vector specified by Down, but my vector math is horribly elementary. I've only done basic 2D vector stuff. Do you know how that might be accomplished?
The axis of rotation is specified by the direction of the AngularVelocity. The magnitude of the AngularVelocity vector is the speed of rotation around the axis.

So, to rotate around an axis with some speed, multiply that normalized axis by the speed in radians per second. Rotating around world Down would be Vector3.Down * speed. Rotating around the entity's local down would be entity.OrientationMatrix.Down * speed.
Another question: How hard would it be to tweak the engine's code to allow for axes that ignore LocalOffset?
That modification would not really make sense. It's not clear what the exact behavior would be. Some arbitrary choices would have to be made. In addition, The LocalPosition is a consistent rigid modification. If the shape were instead constantly changing or moving relative to the center of mass, that amounts to a type of deformation and would complicate the effort. So, it would end up being a lot more work than just fiddling with velocities.
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Norbo wrote: So, to rotate around an axis with some speed, multiply that normalized axis by the speed in radians per second. Rotating around world Down would be Vector3.Down * speed. Rotating around the entity's local down would be entity.OrientationMatrix.Down * speed.
...Oh. I always figured it was the magnitude to rotate around each axis. That's pretty simple. =D

Also, since the offset is all grumpeh, unless I can figure out which direction might work to get the proper Z rotation and the proper X/Y rotation, I'll probably end up using the linear velocity method. Either way, I have enough info to solve the problem, methinks. Thanks!
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Norbo wrote:Other than that, yes, you can use linear velocity to move an object in a circle. The angular velocity is the same as in the non-offset case. The linear velocity is whateverItWouldBeNormally + Vector3.Cross(angularVelocity, offset).

Note that because you're applying the tangential velocity repeatedly at discrete intervals, there will be position drift away from circular motion. You could introduce a corrective factor to the linear velocity to keep it near the desired location. You could also use this to somewhat sluggishly follow a position goal. Just take the position error, multiply it by a factor, and divide by the timestep (e.g. (goal - currentPosition) * 0.2 / dt).
I realized I don't really understand this part. This means LinearVelocity = LinearVelocity + (Vector3.Cross(AngularVelocity, offset) * ((goal - currentPosition) * 0.2f / dt))), right?
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

Ignore the position correction term to begin with. The proper form of the expression would be:

Code: Select all

rotatingObjectLinearVelocity = baseLinearVelocity + Vector3.Cross(AngularVelocity, offset);
'baseLinearVelocity' is the velocity at which the center of rotation translates. Imagine a block rotating around a point. If that point does not move, then baseLinearVelocity is the zero vector. If it's moving to the right, then it's (x,0,0), and so on. The baseLinearVelocity is not the linear velocity of the object swinging around in a circle; that would be the rotatingObjectLinearVelocity.

The Vector3.Cross(AngularVelocity, offset) term is the contribution from angular velocity to the rotating object's linear velocity due to the offset.

The position correction term would be added on top of the others:

Code: Select all

rotatingObjectLinearVelocity = baseLinearVelocity + Vector3.Cross(AngularVelocity, offset) + positionCorrection;
But if you're going this route, it's best to just get the non-position corrected version working first.

This type of thing is getting pretty close to what the path following convenience systems do. It might be a good idea to take a closer look at those before trying to implement something so close to it.
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Last couple of times I looked at PathFollowingDemo it didn't really make sense. I'll take another look, but I dunno if that's the route I'll take. This long method might work better for my case.

EDIT: Wait... Using this method, I'm still going to have to rotate as I move, right? In that case, how do I get the AngularVelocity from a Vector3 specifying the radians to rotate around each axis? I thought I could just take it and multiply it by the timestep, but that doesn't work. I also thought I might be able to normalize it, and then divide the original by the normalization to get speed, then do something with that and the timestep, but... I don't know. =/
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

sing this method, I'm still going to have to rotate as I move, right?
Yes.
In that case, how do I get the AngularVelocity from a Vector3 specifying the radians to rotate around each axis?
Here's two possible (equivalent) ways. The examples below just arbitrarily rotate an entity around both the world up direction and the entity's local right direction. The same approach can be used to find an angular velocity to achieve any rotation.

Every frame, go through and update the angular velocity of each roller. To determine the correct angular velocity, compute the rotation you want to apply over the next frame. Convert this rotation into an angular velocity.

Code: Select all

                //Create the rotation which gets us to our desired orientation.
                var rotation = Quaternion.Concatenate(
                    //The first component of the rotation is the rotation around the entity's local X axis.
                    //The velocity around the local X axis is '2' in this case.  Multiply by dt since we want one frame's worth of rotation.
                    Quaternion.CreateFromAxisAngle(entity.OrientationMatrix.Right, 2 * dt),
                    //The second component of the rotation is the yaw.
                    //The velocity here is 1.5.
                    Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), 1.5f * dt));


                //Use the helper method to convert the rotation into an angular velocity.
                //Technically, you could get this more directly.
                Vector3 axis;
                float angle;
                Toolbox.GetAxisAngleFromQuaternion(ref rotation, out axis, out angle);
                //Divide the angle by dt because we want to get through the angle in a single update.
                entity.AngularVelocity = axis * (angle / dt);

                //Note that dt should be the time each Space.Update covers.
                //If internal time stepping is not used (Space.Update() is called with no parameters), then dt is Space.TimeStepSettings.TimeStepDuration.
                //If internal time stepping is used (Space.Update(gameTimeElapsed)), then it would be best to do this velocity update on each internal time step.
                //That way, you can continue using Space.TimeStepSettings.TimeStepDuration as dt.
                //To do this, either hook an event or create an Updateable which is added to and updated by the Space.
Another similar, but slightly indirect, approach is to compute the starting and ending orientations and compute the angular velocity necessary to span the gap using a helper method.

Code: Select all

                Quaternion start = entity.Orientation;

                //Create the rotation which gets us to our desired orientation.
                var rotation = Quaternion.Concatenate(
                    //The first component of the rotation is the rotation around the entity's local X axis.
                    //The velocity around the local X axis is '2' in this case.  Multiply by dt since we want one frame's worth of rotation.
                    Quaternion.CreateFromAxisAngle(entity.OrientationMatrix.Right, 2 * dt),
                    //The second component of the rotation is the yaw.
                    //The velocity here is 1.5.
                    Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), 1.5f * dt));

                //Compute the end state.
                var end = Quaternion.Concatenate(start, rotation);

                //Use the helper method to convert the rotation into an angular velocity.
                //Specify the time to be one frame.
                Vector3 constructedVelocity = EntityRotator.GetAngularVelocity(start, end, dt);
                entity.AngularVelocity = constructedVelocity;
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Actually, I started poking around with QuaternionSlerpCurve, and I thought I had something that would work, but it doesn't. Let me show you what I have.

Code: Select all

curve = new QuaternionSlerpCurve();
curve.ControlPoints.Add(0, Quaternion.Identity);
Quaternion lastQ = Quaternion.Identity;
for(int i = 1; i < rotationsToOrigin; i++)
{
    // This should a curve that means "wait for machineNo milliseconds, 
    // rotate radiansToRotate for deactivationTime milliseconds, stop for machineNo milliseconds, repeat."
    curve.ControlPoints.Add((i * machineNo / 1000f) + (deactivationTime / 1000f * (i - 1)), lastQ);
    lastQ = Quaternion.CreateFromYawPitchRoll(i * radiansToRotate.Y, i * radiansToRotate.X, i * radiansToRotate.Z);
    curve.ControlPoints.Add((i * machineNo + deactivationTime * i) / 1000f, lastQ);
}
curve.ControlPoints.Add((rotationsToOrigin * machineNo + deactivationTime * (rotationsToOrigin - 1)) / 1000f, Quaternion.Identity);

// In Update...
AngularVelocity = GetAngularVelocity(m.Ent.Orientation, curve.Evaluate(gameTime.ElapsedGameTime.Milliseconds),
                            gameTime.ElapsedGameTime.Milliseconds);
// This is the same function that EntityMover uses, I just copied it over. 
What it does is rotates very slowly at a constant rate, instead of doing nothing, rotating, and then doing nothing again. Is there something about curves I don't know, like if two points are equal one is ignored? If I can get this to work, I can completely forgo my timers, hopefully.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

A curve's Evaluate method gets the value in the curve associated with that parameter. In the code provided, the elapsed time is being passed in. That will always get something close to the same value since it is sampling the same spot over and over again.

Instead, sample using the total time. Accumulate the time each frame and pass the accumulated amount into the Evaluate method.

Additionally, gameTime.ElapsedGameTime.Milliseconds will not give the desired values. For one thing, it's going to be a value approximately 1,000 times too large. That's why it's moving extremely slowly.

Instead, either use gameTime.ElapsedGameTime.TotalSeconds or, even more correctly, do what the note in my previous post mentioned:
//Note that dt should be the time each Space.Update covers.
//If internal time stepping is not used (Space.Update() is called with no parameters), then dt is Space.TimeStepSettings.TimeStepDuration.
//If internal time stepping is used (Space.Update(gameTimeElapsed)), then it would be best to do this velocity update on each internal time step.
//That way, you can continue using Space.TimeStepSettings.TimeStepDuration as dt.
//To do this, either hook an event or create an Updateable which is added to and updated by the Space.
Another related note in the PathFollowingDemo's Update method:
//Increment the time. Note that the space's timestep is used
//instead of the method's dt. This is because the demos, by
//default, update the space once each game update. Using the
//space's update time keeps things synchronized.
//If the engine is using internal time stepping,
//the passed in dt should be used instead (or put this logic into
//an updateable that runs with space updates).

Finally, it's likely that the CreateYawPitchRoll method will not do quite what you want unless the values have been chosen carefully for the way that method works. If it doesn't do what you want, consider computing rotations like I did in my previous post.
snoozbuster
Posts: 172
Joined: Sat Sep 24, 2011 7:31 am

Re: Rotate with an offset

Post by snoozbuster »

Norbo wrote:A curve's Evaluate method gets the value in the curve associated with that parameter. In the code provided, the elapsed time is being passed in. That will always get something close to the same value since it is sampling the same spot over and over again.

Instead, sample using the total time. Accumulate the time each frame and pass the accumulated amount into the Evaluate method.
Derp. It's kinda sad I forgot that.
Norbo wrote: Instead, either use gameTime.ElapsedGameTime.TotalSeconds or, even more correctly, do what the note in my previous post mentioned:
//Note that dt should be the time each Space.Update covers.
//If internal time stepping is not used (Space.Update() is called with no parameters), then dt is Space.TimeStepSettings.TimeStepDuration.
//If internal time stepping is used (Space.Update(gameTimeElapsed)), then it would be best to do this velocity update on each internal time step.
//That way, you can continue using Space.TimeStepSettings.TimeStepDuration as dt.
//To do this, either hook an event or create an Updateable which is added to and updated by the Space.
Which event would that be?

Also, is this related to why things sometimes "flicker" or jump around a tad?

Also also, when I update something that already has an external rotation, such as rotating the rollers to face another direction, they instantly snap to Identity. Is there a way to create a Cylinder and rotate it without using WorldTransform *= Matrix.CreateRotationZ(MathHelper.PiOver2)?
Norbo wrote: Finally, it's likely that the CreateYawPitchRoll method will not do quite what you want unless the values have been chosen carefully for the way that method works. If it doesn't do what you want, consider computing rotations like I did in my previous post.
It seems to work fine for (0, 0, -90), but I haven't tried it with anything else yet. A large portion of what I'm doing is axis-aligned in one way or another, so I think I'll be fine. However, I'm not using that Vector3 as "a vector pointing towards -Z 90 units in length," it means "rotate nowhere around X, nowhere around Y, and -90 degrees around Z." So I think I'll be good.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Rotate with an offset

Post by Norbo »

Which event would that be?
Every Space processing stage has at least two events- Starting and Finishing. The important thing is that it's being updated with the space update with the proper time. The Space.DuringForcesUpdateables.Starting event would be fine. The EntityRotator is an updateable that runs in the DuringForcesUpdateables stage.
Also, is this related to why things sometimes "flicker" or jump around a tad?
Possibly. Bad things are more likely if your game has IsFixedTimeStep = false since the elapsed time would vary.
Also also, when I update something that already has an external rotation, such as rotating the rollers to face another direction, they instantly snap to Identity. Is there a way to create a Cylinder and rotate it without using WorldTransform *= Matrix.CreateRotationZ(MathHelper.PiOver2)?
The Orientation property is the core value. The WorldTransform is a convenience property around the Orientation and Position properties. You could also use the OrientationMatrix property if a matrix format is more convenient.

I don't know exactly what you mean by snapping to identity. Having a pre-existing rotation should not cause any issues by itself. Do note, however, that rotating the entire WorldTransform will also transform the translation. If you only want to handle orientation, it's best to use the orientation properties. The WorldTransform's read value will update automatically when the orientation properties are changed.
Post Reply