Weld one entity to another on collision

Discuss any questions about BEPUphysics or problems encountered.
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

Norbo wrote:The easiest way would be to just grab the Forward (or Right, or Left) vector from the entity's OrientationMatrix. There's a property for them. This is equivalent to taking something like (0,0,1) and transforming it by the rotation matrix, except because a rotation matrix is an orthonormal basis, you can just read the appropriate direction right out of the matrix's components without any extra math.
I'm not sure I understand. What I'm trying to do is adjust the orientation of the physics entity (Box in this case) so that it aligns with its own linear velocity. My arrow's model is tied to the world transform of the Box entity. I was thinking some math had to be done...

Initially I had something like this going:

Code: Select all

Vector3 vel = PhysObj.LinearVelocity;
vel.Normalize();
float z = (float)Math.Atan2(vel.Z, vel.X);
PhysObj.WorldTransform = Matrix.CreateRotationY(initialYaw) * Matrix.CreateRotationZ(z) * Matrix.CreateTranslation(PhysObj.WorldTransform.Translation);
I think I may be confusing local and world spaces. Or an easier way exists?
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

I like the idea of your second approach, but I think I'm doing that wrong too....
Here's what I have:

Code: Select all

Vector3 vel = PhysObj.LinearVelocity;
                        vel.Normalize();
                        Vector3 forward = PhysObj.WorldTransform.Forward;
                        forward.Normalize();
                        Quaternion result = new Quaternion();
                        Toolbox.GetQuaternionBetweenNormalizedVectors(ref forward, ref vel, out result);
                        PhysObj.WorldTransform = Matrix.Transform(PhysObj.WorldTransform, result);
Looks right? Or glaring mistake?
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Weld one entity to another on collision

Post by Norbo »

Here's some notes:

Code: Select all

                        Vector3 vel = PhysObj.LinearVelocity;
                        vel.Normalize(); //Watch out; the velocity can be zero which would cause this to be full of NaNs
                        Vector3 forward = PhysObj.WorldTransform.Forward;
                        forward.Normalize(); //The forward vector is already normalized
                        Quaternion result = new Quaternion(); //The out parameter will initialize the value; setting it to a new quaternion is not needed
                        Toolbox.GetQuaternionBetweenNormalizedVectors(ref forward, ref vel, out result);
                        PhysObj.WorldTransform = Matrix.Transform(PhysObj.WorldTransform, result); //this rotates the entire world transform by the rotation rather than only the orientation
The important one is that last one: rotating the entire world transform will include translation, which will send the object flying all over the place based on its position. Instead, only transform the entity.Orientation (or entity.OrientationMatrix). And of course, be careful about the multiplication order; Quaternion.Multiply does it in the opposite order relative to Matrix.Multiply. Quaternion.Concatenate matches the Matrix.Multiply order.
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

Thanks Norbo! I finally got it right:

Code: Select all

Vector3 vel = PhysObj.LinearVelocity;
                        if (vel != Vector3.Zero)
                        {
                            vel.Normalize();

                            Vector3 forward = PhysObj.WorldTransform.Forward;
                            Quaternion result;
                            Toolbox.GetQuaternionBetweenNormalizedVectors(ref forward, ref vel, out result);
                            Quaternion curQuat = PhysObj.Orientation;
                            Quaternion.Multiply(ref result, ref curQuat, out curQuat);
                            PhysObj.Orientation = curQuat * Quaternion.CreateFromYawPitchRoll(-MathHelper.PiOver2, 0.0f, 0.0f);
                        }
The extra -PI/2 I could probably eliminate by rotating my model in Blender. The arrows look pretty sweet now! This thread will be legendary.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Weld one entity to another on collision

Post by Norbo »

Glad to hear it's working :)

I would recommend changing the Vector3.Zero comparison to a vel.LengthSquared() > Epsilon comparison, though. Values arbitrarily close to zero will not equal Vector3.Zero, but could still cause some weirdness to happen.

Also, you can eliminate the extra rotation by picking Right or Left instead of Forward as the 'forward' axis.
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

I hate to keep beating this thread to death but the matrix math is driving me insane!

I have this event handler for when an arrow first hits a target:

Code: Select all

void Events_InitialCollisionDetected(BEPUphysics.Collidables.MobileCollidables.EntityCollidable sender, BEPUphysics.Collidables.Collidable other, BEPUphysics.NarrowPhaseSystems.Pairs.CollidablePairHandler pair)
        {
            sender.Entity.LinearVelocity = Vector3.Zero;
            sender.Entity.AngularVelocity = Vector3.Zero;
            Arrow arrow = pair.EntityA.Tag as Arrow;
            Cylinder cylinder = pair.EntityB as Cylinder;
            if (arrow == null)
            {
                arrow = pair.EntityB.Tag as Arrow;
                cylinder = pair.EntityA as Cylinder;
            }

            if (arrow != null)
            {
                if (cylinder != null)
                {
                    Target target = cylinder.Tag as Target;
                    if (target != null)
                    {
                        arrow.StuckTo = target;
                        arrow.Offset = arrow.PhysObj.WorldTransform.Translation - target.PhysObj.WorldTransform.Translation;
                        arrow.Orientation = Matrix.Identity;
                        arrow.Orientation = Matrix.Transform(arrow.Orientation, arrow.PhysObj.Orientation);
                    }
                }
            }

            if (arrow.PhysObj.Space != null)
            {
                arrow.PhysObj.Space.Remove(arrow.PhysObj);
            }
        }
Then I have this for rendering my arrow after its "stuck" to a target:

Code: Select all

foreach (ModelMesh mesh in ArcheryGame.arrowModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.View = viewMatrix;
                    effect.Projection = projectionMatrix;
                    if (StuckTo != null)
                    {
                        effect.World = Matrix.CreateScale(8.0f) * Orientation * Matrix.CreateTranslation(Offset) * StuckTo.PhysObj.WorldTransform;
                    }
                    else
                    {
                        effect.World = Matrix.CreateScale(8.0f) * PhysObj.WorldTransform;
                    }
                }
                mesh.Draw();
            }
This works great as long as I don't apply any manual change to the orientation of the target (using a cylinder prefab). If when I create the target I do this:

Code: Select all

PhysObj = new Cylinder(pos, 1.0f, radius, 100.0f);
            PhysObj.Orientation = PhysObj.Orientation * Quaternion.CreateFromYawPitchRoll(0.0f, -MathHelper.PiOver2, 0.0f);
Then my arrows stick but when they hit they get all wonky in their location and rotation. I didn't notice this before because I was always shooting stationary targets. What am I doing wrong?
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Weld one entity to another on collision

Post by Norbo »

Code: Select all

effect.World = Matrix.CreateScale(8.0f) * Orientation * Matrix.CreateTranslation(Offset) * StuckTo.PhysObj.WorldTransform;
By transforming the arrow's orientation and offset by the StuckTo entity's world transform in that order, it is implied that the Orientation and Offset should be in the local space of the StuckTo entity.

However:

Code: Select all

                        arrow.Offset = arrow.PhysObj.WorldTransform.Translation - target.PhysObj.WorldTransform.Translation;
                        arrow.Orientation = Matrix.Identity;
                        arrow.Orientation = Matrix.Transform(arrow.Orientation, arrow.PhysObj.Orientation);
arrow.Offset is computed as a world offset here, and the Orientation is just the world space orientation of the arrow (Matrix.Identity * something = something). This should be changed to compute the orientation of the arrow in the local space of the StuckTo entity, and transform the world offset into the local space of the StuckTo entity as well.

I'm about to run off to bed so I can't put up a full explanation of the whole world->local process, but as a guide, think about how cameras work: The View matrix is the inverse of the World matrix of the camera. the View matrix 'pulls' stuff from world space, into 'view' space. 'View' space is the local space of the camera.
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

Norbo wrote: arrow.Offset is computed as a world offset here, and the Orientation is just the world space orientation of the arrow (Matrix.Identity * something = something). This should be changed to compute the orientation of the arrow in the local space of the StuckTo entity, and transform the world offset into the local space of the StuckTo entity as well.
This should transform my offset into the local space of the target right?

Code: Select all

arrow.Offset = Vector3.Transform(arrow.Offset,Matrix.Invert(target.PhysObj.WorldTransform)); 
And then i'm still thrown for a loop on the rotation part of it.

I currently have this going on:

Code: Select all

arrow.Orientation = Matrix.Transform(Matrix.Identity, arrow.PhysObj.Orientation);
arrow.Orientation = Matrix.Transform(arrow.Orientation, Quaternion.Inverse( target.PhysObj.Orientation));
arrow.LocalTransform = arrow.Orientation * Matrix.CreateTranslation(arrow.Offset);
This results in the arrows having the correct rotation relative to the target but snap to a position well off the target.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: Weld one entity to another on collision

Post by Norbo »

Code: Select all

arrow.Offset = Vector3.Transform(arrow.Offset,Matrix.Invert(target.PhysObj.WorldTransform)); 
Transforming the offset by the inverse world transform will take into account the target's position a second time, resulting in an incorrect local offset. Either transform the arrow position by the inverse world transform directly, or rotate the offset by the inverse rotation alone.
Mokgra
Posts: 11
Joined: Wed Oct 24, 2012 3:16 am

Re: Weld one entity to another on collision

Post by Mokgra »

Either transform the arrow position by the inverse world transform directly
Thanks again Norbo! The above worked for me. For those in the future who may stumble upon this thread, this worked for me:

Code: Select all

arrow.Offset = Vector3.Transform(arrow.PhysObj.WorldTransform.Translation,Matrix.Invert(target.PhysObj.WorldTransform));
arrow.Orientation = Matrix.Transform(Matrix.Identity, arrow.PhysObj.Orientation);
arrow.Orientation = Matrix.Transform(arrow.Orientation, Quaternion.Inverse( target.PhysObj.Orientation));
arrow.LocalTransform = arrow.Orientation * Matrix.CreateTranslation(arrow.Offset);
And the draw step looks like:

Code: Select all

effect.World = Matrix.CreateScale(8.0f) * LocalTransform * StuckTo.PhysObj.WorldTransform;
Post Reply