Rotate 3D model in XNA using Motion API

Motion API is a great way for you as a developer to get access to sensor data in more sophisticated and more useful way than just getting the raw data from each sensor. There’s a complex math in the background that combines the sensor data for you to get more usable data. This way, you can use Motion API to do interesting things such as rotate 3D objects in space using XNA Framework and Windows Phone. I have to digress first a little bit. I haven’t written anything for a while now because I’ve been busy with a few apps and especially with the Photo Light update. It’s now completely refreshed and, in my personal opinion, much better than the first version. Check it out! Other than that, I’m preparing a session for Microsoft WinDays technology conference that will take place in Rovinj this year from April 23 to April 26. I will be speaking about Windows Phone multimedia and sensors. Why the combination? Because there’s an interesting connection between “sensors” such as camera and microphone and sensors such as gyroscope, accelerometer and compass that allows you to create interesting stuff such as augmented reality experiences. I will post the presentation afterwards. You will also notice that I’ve changed the style of my code to suit this theme better! Now, back to Motion API! We will be displaying a 3D model and rotate it using Motion API. You can find the 3D model in this .zip file

http://go.microsoft.com/fwlink/?LinkId=149817&clcid=0x409

under /GoingBeyond1/Content/Models and /GoingBeyond1/Content/Textures

Create a new Windows Phone Game:

image

Inside the created solution, you will get two projects. One will be the XNA project, and the other will be the content project. Add the model and texture from the downloaded zip file to the content project, so that it looks like this:

image

Next, open file Game1.cs from your XNA project. Add the following code above the class constructor:

float aspectRatio;

Model myModel;  
Vector3 modelPosition = Vector3.Zero;  
float modelRotation = 0.0f;  
Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 4000.0f);

float yaw, pitch, roll;  
Motion motion;

In the game constructor, you should add the following line to prevent your screen from going from LandscapeLeft to LandscapeRight orientations.

graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft;

Next, in the LoadContent method, load the model that you’ve previously added to the project:

spriteBatch = new SpriteBatch(GraphicsDevice);  
myModel = Content.Load<Model>("Modelsp1_wedge");  
aspectRatio = graphics.Graphic

Now, in the initialize method, create a new Motion object, and start it. Call Start method inside the try catch block because it can fail!

protected override void Initialize()  
        {

            base.Initialize();

            if (Motion.IsSupported)
            {
                motion = new Motion();

                try
                {
                    motion.Start();
                }
                catch
                {

                }
            }
        }

Usually, you would have to set the event handler for changed sensor readings every 2ms, which is the default time between updates. You can skip that now, because the XNA framework calls methods Update and Draw in certain frequency, depending on how you set it. This way, you can refresh yaw, pitch and roll values in the Update method, so that it looks like this:

protected override void Update(GameTime gameTime)  
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
        ButtonState.Pressed)
        this.Exit();

    if (motion != null)
    {
        if (Math.Abs(motion.CurrentValue.Attitude.Yaw * 5 - yaw) > 0.06)
            yaw = motion.CurrentValue.Attitude.Yaw * 5;
        if (Math.Abs(motion.CurrentValue.Attitude.Pitch * 5 - pitch) > 0.06)
            pitch = motion.CurrentValue.Attitude.Pitch * 5;
        if (Math.Abs(motion.CurrentValue.Attitude.Roll * 5 - roll) > 0.06)
            roll = motion.CurrentValue.Attitude.Roll * 5;
    }

    base.Update(gameTime);
}

You can see that the motion object exposes CurrentValue property, which exposes Attitude property with properties Yaw, Pitch and Roll. I test if the difference between the new value and the old value is larger than 0.06 so that the 3D model is not sensitive to small changes in sensor readings. I multiplied it by 5 because I want the model to rotate more for small changes on my device so that the effect is visible, because I want to show this app as a demo using a camera above the phone. So what is actually yaw, pitch and roll? Every orientation of an object in space can be described using angles around 3 axis: yaw, pitch and roll. 709px-Yaw_Axis_Corrected.svg The properties you get from the Motion API describe the orientation of your phone using yaw, pitch and roll angles in radians, which you can use to rotate the 3D model. The model then follows the rotation of your phone. The Draw method is responsible for drawing the scene and object. This is the code:

protected override void Draw(GameTime gameTime)  
{
    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

    Matrix[] transforms = new Matrix[myModel.Bones.Count];

    myModel.CopyAbsoluteBoneTransformsTo(transforms);

    foreach (ModelMesh mesh in myModel.Meshes)
    {
        foreach (BasicEffect effect in mesh.Effects)
        {
            effect.EnableDefaultLighting();
            effect.World = transforms[mesh.ParentBone.Index] * 
                Matrix.CreateFromYawPitchRoll(yaw, pitch, roll) * 
                Matrix.CreateTranslation(modelPosition);
            effect.View = Matrix.CreateLookAt
                (cameraPosition, Vector3.Zero, Vector3.Up);
            effect.Projection = Matrix.
                CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), 
                aspectRatio, 1.0f, 10000.0f);
        }

        mesh.Draw();

    }

    base.Draw(gameTime);
}

The most important part is this line of code, which describes how we use the yaw, pitch and roll values to create a matrix and then use to multiply and rotate!

effect.World = transforms[mesh.ParentBone.Index] *  
                Matrix.CreateFromYawPitchRoll(yaw, pitch, roll) * 
                Matrix.CreateTranslation(modelPosition);

And this is it! When you run this on the device, you should get the little 3D airplane model to rotate as you rotate your device! airplane

comments powered by Disqus