Model Rotation

Jun 23, 2010 at 7:49 PM
How do I change the rotation of a model? I've got a skinned model and I need to know how to changes it's orientation along an axis, facing it to the left or right.
Coordinator
Jun 23, 2010 at 7:56 PM

To change the rotation of a model, (dynamically at runtime), then you will need to modify the world matrix. Several tutorials demonstrate this, if you search for "PushWorldMatrix" (or WorldMatrix.Push if you are using the Xen 2.0 Alpha) then you will find them.

Matrices are a complex subject, so I'd highly recommend investing some time to learn and understand them. A good starting point may be reading a post I made on the xna forums (here). It covers some of the basics (but trust me, there is a lot more to them!)

Jun 24, 2010 at 3:26 PM

My current solution is this

Matrix leftMatrix = Matrix.CreateRotationZ(3);

where the Z is the up axis, and the (3) is the angle whereby my model faces left. (the model was created in blender, and it's orientation is a little weird.)

When I press the 'A' key, I call this method in the actor class

public void RotLeft()
{
worldMatrix *= leftMatrix;
}

This has the effect of changing the rotation of the model's world matrix every time it is pressed, I understand that. If I use worldMatrix = leftmatrix, I get the orientation I need, but lose the position of the model.  How do I modify the orientation of the model, while preserving the position?

 

 

Coordinator
Jun 24, 2010 at 4:28 PM
Edited Jun 24, 2010 at 4:30 PM

Either store the position separately in a Vector3.

Or, the position is stored in elements {M41, M42, M43} in the matrix. (The 'Translation' property will get / set these values).

Btw, the value 3 means 3 radians. This is roughly 171 degrees. (In radians, PI represents 180 degrees)

Jun 24, 2010 at 4:58 PM

Thanks for the tip about the radians, I had forgotten the Mathhelper.Pi :)

Now I use

 

 Matrix leftMatrix = Matrix.CreateRotationZ(MathHelper.TwoPi);
 Matrix rightMatrix = Matrix.CreateRotationZ(MathHelper.Pi);

public void RotLeft()
        {
            worldMatrix = leftMatrix;
        }

        public void Rotright()
        {
            worldMatrix = rightMatrix;
        }
 //Movement code
            Vector3 playerPositionAdd = Vector3.Zero;

            if (state.KeyboardState.KeyState.A.OnPressed)
            {               
                actor.PlayAnimation3();
                playerPositionAdd.X += 100;
                actor.RotLeft();
            }            

            if (state.KeyboardState.KeyState.D.OnPressed)
            {
                actor.PlayAnimation3();
                playerPositionAdd.X -= 100;
                actor.Rotright();
            }
            this.playerPosition += playerPositionAdd;

To change the characters facing. What I cant understand is how to apply leftRot and rightRot to the world matrix so only the model's initial rotation is changed.

 

Jun 24, 2010 at 5:50 PM
Edited Jun 24, 2010 at 8:30 PM

How would I lock the actor.Rotright() so the model only turns once? I tried using a bool Isleft,

 

if (keyboardState.KeyState.D.IsDown)
            {
                actor.PlayAnimation3();
                playerPositionAdd.Y += 0.2f;
                if (isLeft == true)
                {
                    actor.RotRight();
                }
                isLeft = false;
            }
            this.playerPosition += playerPositionAdd;
            actor.Position = playerPosition;

My idea is that I can press the key to turn my character 180 to the right, but then lock the rotRight, so the character just walks forward on the subsequent button press. Unfortunately, it isn't working, the bool doesn't seem to be setting false once rotright is called.

 

Jun 24, 2010 at 9:31 PM

I cant get this working to my satisfaction, I can either modify the position, or the rotation, but not both. I'm probably overcomplicating things. Here's my actor class.

 
namespace myXen
{
    class Actor : IDraw, IContentOwner
    {
        private ModelInstance model;
        private AnimationController animationController;
        private AnimationInstance animation;
        private Matrix worldMatrix;
        Matrix leftMatrix = Matrix.CreateRotationZ(MathHelper.TwoPi);
        Matrix rightMatrix = Matrix.CreateRotationZ(MathHelper.Pi);
        
        public AnimationInstance Animation
        {
            get { return animation; }
            set { animation = value; }
        }

        public Vector3 Position
        {
            get { return worldMatrix.Translation; }
            set { worldMatrix = Matrix.CreateTranslation(value); }
        }

        public void RotLeft()
        {
            worldMatrix *= leftMatrix;
        }

        public void RotRight()
        {
            worldMatrix *= rightMatrix;
        }

        public Actor(ContentRegister content, Vector3 position, MaterialLightCollection lights)
        {
            Matrix.CreateTranslation(ref position, out this.worldMatrix);

            model = new ModelInstance();
            animationController = model.GetAnimationController();
            model.LightCollection = lights;
            
            content.Add(this);

            PlayAnimation1();
        }

        public void PlayAnimation1()
        {
            int animationIndex = animationController.AnimationIndex("Stance");

            animation.StopAnimation();

            //begin playing the animation, looping
            animation = animationController.PlayLoopingAnimation(animationIndex);
        }

        public void PlayAnimation2()
        {
            int animationIndex = animationController.AnimationIndex("Punch");

            animation.StopAnimation();

            //begin playing the animation
            animation = animationController.PlayAnimation(animationIndex);
        }

        public void PlayAnimation3()
        {
            int animationIndex = animationController.AnimationIndex("Walk");

            animation.StopAnimation();

            //begin playing the animation, looping
            animation = animationController.PlayAnimation(animationIndex);
        }

        public void Draw(DrawState state)
        {
            state.PushWorldMatrix(ref worldMatrix);
            if (model.CullTest(state))
                model.Draw(state);

            state.PopWorldMatrix();
        }

        public bool CullTest(ICuller culler)
        {
            return model.CullTest(culler);
        }

        public void LoadContent(ContentRegister content, DrawState state, ContentManager manager)
        {
            //load the model data into the model instance
            model.ModelData = manager.Load<Xen.Ex.Graphics.Content.ModelData>(@"FightingCharMultipleAnim");
        }

        public void UnloadContent(ContentRegister content, DrawState state)
        {
        }

        public UpdateFrequency Update(UpdateState state)
        {
            return UpdateFrequency.FullUpdate60hz;
        }
    }
}


Here's my Game class

namespace myXen
{
    public class Game1 : Application
    {
        //screen draw target
        private DrawTargetScreen drawToScreen;
        //public Vector3 playerPosition = new Vector3(0, 0, 20);
        private const float diskRadius = 50;
        private MaterialLightCollection lights;
        private Camera3D camera;        

        protected override void Initialise()
        {
            camera = new Camera3D();          

            //create the draw target.
            drawToScreen = new DrawTargetScreen(this, camera);
            drawToScreen.ClearBuffer.ClearColour = new Color(20, 20, 40);

            actor = new Actor(Content, new Vector3(0, 0, 20), lights);
            drawToScreen.Add(actor);
           

            //create the light collection
            lights = new MaterialLightCollection();
            //set a dark blue ambient colour
            lights.AmbientLightColour = new Color(40, 40, 80).ToVector3();

            //positions for two lights
            Vector3[] lightPositions = new Vector3[] 
            { 
                new Vector3(10, -10, 20), 
                new Vector3(-10, 10, 20) 
            };

            //geometry for a light (shared for each light)
            IDraw lightGeometry = null;

            for (int i = 0; i < lightPositions.Length; i++)
            {
                float lightHalfFalloffDistance = 15;
                Color lightColor = Color.LightYellow;
                Color lightSpecularColour = Color.WhiteSmoke;
                bool perPixel = i < 2;//first two lights are per-pixel

                //interface to the light about to be created
                IMaterialPointLight light = null;

                //create the point light
                light = lights.AddPointLight(perPixel, lightPositions[i], lightHalfFalloffDistance, lightColor, lightSpecularColour);

                //adjust the lighting attenuation model, the constant defaults to 1, which prevents the light being brighter than 1.0 in the falloff equation
                //set to 0.25, the light will get really bright in close (up to 4)
                //(see light.QuadraticAttenuation remarks for an explanation of the falloff model)
                light.ConstantAttenuation = 0.25f;

                //create the light geometry (a sphere)
                if (lightGeometry == null)
                    lightGeometry = new Xen.Ex.Geometry.Sphere(Vector3.One, 8, true, false, false);

                //visually show the light with a light drawer
                IDraw lightSourceDrawer = new LightSourceDrawer(lightPositions[i], lightGeometry, lightColor);

                //add the light geometry to the screen
                drawToScreen.Add(lightSourceDrawer);
            }

            //create the ground disk
            GroundDisk ground = new GroundDisk(this.Content, lights, diskRadius);

            //then add it to the screen
            drawToScreen.Add(ground);
        }

      protected override void Draw(DrawState state)
      {
          //rotate the camera around the ground plane
          RotateCamera(state);

          drawToScreen.Draw(state);
      }

      private void RotateCamera(DrawState state)
      {
          Vector3 lookAt = new Vector3(0, 0, 0);

          float angle = state.TotalTimeSeconds * 0.15f;

          Vector3 lookFrom = new Vector3((float)Math.Sin(angle) * diskRadius, (float)Math.Cos(angle) * diskRadius, 10);
          lookFrom.Z += (float)Math.Sin(angle) * 5;

          camera.LookAt(lookAt, lookFrom, new Vector3(0, 0, 1));
      }

      //Override this method to setup the graphics device before the application starts.
      //This method is called before Initalise()
      protected override void SetupGraphicsDeviceManager(GraphicsDeviceManager graphics, ref RenderTargetUsage presentation)
      {
          if (graphics != null) // graphics is null when starting within a WinForms host
          {
              graphics.PreferredBackBufferWidth = 1280;
              graphics.PreferredBackBufferHeight = 720;
          }
      }

      protected override void Update(UpdateState state)
      {
            if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)
                this.Shutdown();
            KeyboardInputState keyboardState = state.KeyboardState;

            //Movement code
            Vector3 playerPositionAdd = Vector3.Zero;
            
            if (keyboardState.KeyState.A.IsDown)
            {               
                actor.PlayAnimation3();
                playerPositionAdd.Y -= 0.2f;                               
                actor.RotLeft();
            }            

          if (keyboardState.KeyState.D.IsDown)
            {
                actor.PlayAnimation3();
                playerPositionAdd.Y += 0.2f;
                actor.RotRight();
          }
            actor.Position += playerPositionAdd;
           // actor.Position = playerPosition;
         
            if (actor.Animation.AnimationFinished)
            {
                actor.PlayAnimation1();
            }
            if (keyboardState.KeyState.Space.OnPressed)
            {
                actor.PlayAnimation2();                
            }
            if ((actor.Animation.AnimationName == "Punch" || actor.Animation.AnimationName == "Walk") && actor.Animation.Time > actor.Animation.AnimationDuration - 0.02f)
                actor.PlayAnimation1();
        }
        Actor actor;
    }
}

I've used XNA to set position/rotation before, but I just cant see what's going wrong here. What's the Xen specific code?

 

 

 

Jun 28, 2010 at 8:10 PM

I still haven't figured this out, any help would be appreciated.

Coordinator
Jun 29, 2010 at 10:42 PM
Edited Jun 29, 2010 at 10:49 PM

I can see the problems pretty easily,
However, I'm not going to tell you what they are.

What?! Why?! you might ask. I have my reasons. But I will point you in the right direction.

Programming is like any other art form, you learn through mistakes, and a lot of this is learning through discovering your errors yourself.

I do not want to be rude, but I get the impression that you are attempting something beyond your abilities.
And while that isn't a bad thing (it's the best way to learn!) you have to be prepared to fail and have things not work out.

Now, with that out the way...

I imagine you have been tryings things somewhat randomly, changing numbers and operations - and not getting the results you want.

Your problem (in this case) relates to matrices. Not an easy topic, but a fundamental element of 3D mathematics and computer graphics. You can't easily bluff your way past matrices (no matter how easy XNA tries to make it)
Here is what I suggest you do.

Close visual studio. Don't go back to this code for a few days.
Now I've told you where the problem is, your first instinct will probably be to start randomly changing the matrix code you have - DO NOT DO THIS. Eventually some change will kinda work, but it'll be a mess, probably be incorrect, and you won't understand why it worked (which is the important bit!)

Read up on matrices, especially 4x4 homogeneous matrices (at first they will make no sense at all  - and the way many people describe them, they still don't) but persist!.

I link to this occasionally, but I wrote a post on the XNA forums describing a small part of how they work. I'd recommend you read this at some point.

Beyond that, look up things like the Matrix and Quaternion FAQ, and use Reflector to investigate the Matrix class in XNA.

Don't read it all at once, read bits one at a time. If something doesn't make sense, read it again. If it still doesn't make sense, stop reading and come back to it the next day.
Hopefully, once you have gained knowledge and understanding you will be able to come back to your code (in several days) and instantly see where you have gone wrong

The important thing is to give yourself a break from the code. Resist all temptation to dive back in too quickly, it won't help.

Good luck

Jul 8, 2010 at 7:33 PM

Ok I've read through, but my problem is still the same, I cannot get the character to rotate the way I want. My problem isn't with understanding the maths and theory of matrices. It's with the actual coding of rotations. What are the steps to change my character to face the other way? I have a worldMatrix, the position (X,Y,Z) coordinates I know how to modify. My current solution is to modify the matrix like this,

if (keyboardState.KeyState.A.IsDown)
            {
                Stance.Weighting = 0;
                Walk.Weighting = 1;
                playerPositionAdd.Y -= 0.2f;
                rotAmount = 0.03f;
            }

Position += playerPositionAdd;
worldMatrix *= Matrix.CreateRotationY(rotAmount);

where rotAmount is set to some value while the key is held down. This produces the result of the model rotating around some point in front of it, but not actually changing it's facing. From my understanding of the article StatusUnknown wrote,  this should rotate around the axis, but should it be rotating around the model's local axis? From the look of things it's causing the model to rotate around the global axis or some other point.

Jul 8, 2010 at 7:46 PM

I feel silly, it works ok, it's the playerPositionAdd that's causing the interference. Where should I put it instead?  i want my character to move only left and right, so even though the rotation is ok, what would be the best way to make sure it finishes a full 180degree turn with each button press? At the moment, releasing the button leaves the character facing an angle based on how long the button was pressed. Would it be possilbe to get vectors equivalent to facing right/left and  turn the character until it's own rotatin equals them, or is there an easier way? Ive seen some of the facing code on the XNA forums, but those solutions are based on random points. Im thinking it'll be simpler with only two fixed directions.

Jul 8, 2010 at 7:53 PM

The problem that you're seeing is due to mis-application of the matrices.  Right now the world matrix is getting rotated, effectively rotating the model around the world's center of origin.  You need to apply the rotation first so that the model rotates around its own center of origin.  This should really be done as part of the creation of the world matrix rather than afterward.  However, for purposes of getting this working, you need to do a couple of things:

1) Track the total rotation of the model, not just .03.

( rotAmount += .03)

2) Rotate the model before applying the world matrix.  Right now you're rotating the model around the world's origin instead of the model's.

(worldMatrix = player.ScalingMatrix * Matrix.CreateRotationY(rotAmount) * worldMatrix;)

 

One last thing, it appears that you may be trying to take a shortcut by modifying the world matrix instead of recalculating it each frame.  Try making a function that calculates the world matrix from scratch instead of updating it.

Jul 8, 2010 at 8:19 PM

not sure I understand, what do you mean by tracking the total rotation?

also what's this player.ScalingMatrix, I'm assuming it sets the model's scale?

If I'm read you right what I should be doing is

worldmatrix = scale *rotation * translation. (I should have remembered this order really, this code has me forgetting fundamentals.)

worldMatrix = Matrix.CreateRotationZ(rotAmount) * Matrix.CreateTranslation(playerPositionAdd) * worldMatrix;
I'm not using scale at the moment, the model is fine as is. If I apply rotation as part of the creation of the worldMatrix, how would I update it according to input?

 

Jul 9, 2010 at 6:03 PM
How would I rotate my model, so that pressing the D key rotates it to the right rather than by a number of degrees? At the moment, each time I press the key, my model does a 180.
Coordinator
Jul 10, 2010 at 1:39 AM

Can you post the code in it's current state.

The RotRight() method you had would certainly cause the model to rotate 180 degrees. The RotLeft() wouldn't change it at all.

Jul 12, 2010 at 6:36 PM

This is the current state of my actor class

 

using System;
using System.Text;
using System.Collections.Generic;

using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Graphics.State;
using Xen.Ex;
using Xen.Ex.Graphics;
using Xen.Ex.Graphics2D;
using Xen.Input.State;
using Xen.Ex.Material;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace myXen
{
    class Actor : IDraw, IContentOwner, IUpdate
    {
        private ModelInstance model;
        private AnimationController animationController;
        private AnimationInstance Stance, Walk, Punch;
        private Matrix worldMatrix = Matrix.Identity;
       
        private Vector3 Position
        {
            get { return worldMatrix.Translation; }
            set { worldMatrix = Matrix.CreateTranslation(value); }
        }

        private void InitaliseAnimations(UpdateManager updateManager)
        {
            //create the animationControllerler as an asynchronous animationControllerler.
            //this will process animations as a thread task
            //This occurs between the update loop and the draw loop,
            //which is why the UpdateManager must be provided.
            animationController = model.GetAsyncAnimationController(updateManager);

            //these perform a linear search to find the animation index
            int stanceAnimationIndex = animationController.AnimationIndex("Stance");            
            int walkAnimationIndex = animationController.AnimationIndex("Walk");
            int punchAnimationIndex = animationController.AnimationIndex("Punch");

            //create the idle animation
            Stance = animationController.PlayLoopingAnimation(stanceAnimationIndex);
            Stance.Weighting = 1;

            Walk = animationController.PlayLoopingAnimation(walkAnimationIndex);
            Walk.Weighting = 0;

            Punch = animationController.PlayLoopingAnimation(punchAnimationIndex);
            Punch.Weighting = 0;
        }

        public Actor(ContentRegister content, Vector3 position, UpdateManager updateManager, MaterialLightCollection lights)
        {
            Matrix.CreateTranslation(ref position, out this.worldMatrix);

            model = new ModelInstance();
            model.LightCollection = lights;
            Position = new Vector3(0, 0, 23);

            updateManager.Add(this);
            content.Add(this);

            InitaliseAnimations(updateManager);
        }       

        public void Draw(DrawState state)
        {
            state.PushWorldMatrix(ref worldMatrix);
            if (model.CullTest(state))
                model.Draw(state);

            state.PopWorldMatrix();
        }

        public bool CullTest(ICuller culler)
        {
            return model.CullTest(culler);
        }

        public void LoadContent(ContentRegister content, DrawState state, ContentManager manager)
        {
            //load the model data into the model instance
            model.ModelData = manager.Load<Xen.Ex.Graphics.Content.ModelData>(@"FightingCharMultipleAnim");
        }

        public void UnloadContent(ContentRegister content, DrawState state)
        {
        }
        
        public UpdateFrequency Update(UpdateState state)
        {
            KeyboardInputState keyboardState = state.KeyboardState;
            //Movement code
            Vector3 playerPositionAdd = Vector3.Zero;
            float rotAmount = 0.0f;
            bool isLeft = true;

            if (keyboardState.KeyState.A.OnPressed && !isLeft)
            {
                rotAmount = MathHelper.Pi;
                isLeft = true;
            }
            if (keyboardState.KeyState.A.IsDown)
            {
                Stance.Weighting = 0;
                Walk.Weighting = 1;                
                playerPositionAdd.Y -= 0.2f;                
            }
            else if (keyboardState.KeyState.A.OnReleased)
            {
                Walk.Weighting = 0;
                Stance.Weighting = 1;               
            }

            if (keyboardState.KeyState.D.OnPressed && isLeft)
            {
                rotAmount = MathHelper.Pi;
                isLeft = false;
            }
            if (keyboardState.KeyState.D.IsDown )
            {
                Stance.Weighting = 0;
                Walk.Weighting = 1;               
                playerPositionAdd.Y -= 0.2f;               
            }
            else if (keyboardState.KeyState.D.OnReleased)
            {
                Walk.Weighting = 0;
                Stance.Weighting = 1;
            }          
            worldMatrix = Matrix.CreateRotationZ(rotAmount) * Matrix.CreateTranslation(playerPositionAdd) * worldMatrix;

            if (keyboardState.KeyState.Space.OnPressed)
            {
                Stance.Weighting = 0;
                Punch.Weighting = 1;
            }
            else if (keyboardState.KeyState.Space.OnReleased)
            {
                Punch.Weighting = 0;
                Stance.Weighting = 1;
            }
            return UpdateFrequency.FullUpdate60hz;
        }
    }
}

 

 

Jul 14, 2010 at 10:50 PM

I got this working, so for the sake of those who might read this thread, here is the solution.

I use these three matrices , as class wide variables.

private Matrix worldMatrix = Matrix.Identity;
        Matrix leftFacing = Matrix.CreateRotationZ(MathHelper.PiOver2);
        Matrix rightFacing = Matrix.CreateRotationZ(-MathHelper.PiOver2);
 I set my model's initial position in the scene with these lines.
 
private Vector3 Position
        {
            get { return worldMatrix.Translation; }
            set { worldMatrix = Matrix.CreateTranslation(value); }
        }

Position = new Vector3(0, 0, 23);
The movement code is 
 
if (keyboardState.KeyState.A.OnPressed )
            {
                worldMatrix = leftFacing * Matrix.CreateTranslation(Position);
            }
And the worldmatrix is calculated using
 
worldMatrix = Matrix.CreateTranslation(playerPositionAdd) * worldMatrix;

Coordinator
Jul 16, 2010 at 11:27 PM

Hurray! :-)

That looks like it should work exactly right :-) I know things took a while to sort out (and I know I wasn't especially helpful) but I bet you've learnt a lot from this annoying bug.

Looking back at the code you posted earlier it's pretty obvious why things weren't working!