6
\$\begingroup\$

XNA 4.0

I am trying to learn how to rotate bones on a very simple tank model I made in Cinema 4D.

It is rigged by 3 bones, Root -> Main -> Turret -> Barrel

I have binded all of the objects to the bones so that all translations/rotations work as planned in C4D. I exported it as .fbx

I based my test project after: http://create.msdn.com/en-US/education/catalog/sample/simple_animation

I can build successfully with no errors but all the rotations I try to do to my bones have no effect. I can transform my Root successfully using below but the bone transforms have no effect:

myModel.Root.Transform = world;

Matrix turretRotation = Matrix.CreateRotationY(MathHelper.ToRadians(37));
Matrix barrelRotation = Matrix.CreateRotationX(barrelRotationValue);

MainBone.Transform = MainTransform;
TurretBone.Transform = turretRotation * TurretTransform;
BarrelBone.Transform = barrelRotation * BarrelTransform;

I am wondering if my model is just not right or something important I am missing in the code.

Here is my Game1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace ModelTesting
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        float aspectRatio;

        Tank myModel;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            myModel = new Tank();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            myModel.Load(Content);
            aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            // Move the pieces
            /*
            myModel.TurretRotation = (float)Math.Sin(time * 0.333f) * 1.25f;
            myModel.BarrelRotation = (float)Math.Sin(time * 0.25f) * 0.333f - 0.333f;
            */

            base.Update(gameTime);
        }



        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // Calculate the camera matrices.
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians(45));

            Matrix view = Matrix.CreateLookAt(new Vector3(2000, 500, 0),
                                              new Vector3(0, 150, 0),
                                              Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    graphics.GraphicsDevice.Viewport.AspectRatio,
                                                                    10,
                                                                    10000);

            // TODO: Add your drawing code here
            myModel.Draw(rotation, view, projection);

            base.Draw(gameTime);
        }




    }
}

And here is my tank class:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace ModelTesting
{

    public class Tank
    {
        Model myModel;

        // Array holding all the bone transform matrices for the entire model.
        // We could just allocate this locally inside the Draw method, but it
        // is more efficient to reuse a single array, as this avoids creating
        // unnecessary garbage.
        public Matrix[] boneTransforms;

        // Shortcut references to the bones that we are going to animate.
        // We could just look these up inside the Draw method, but it is more
        // efficient to do the lookups while loading and cache the results.
        ModelBone MainBone;
        ModelBone TurretBone;
        ModelBone BarrelBone;

        // Store the original transform matrix for each animating bone.
        Matrix MainTransform;
        Matrix TurretTransform;
        Matrix BarrelTransform;

        // current animation positions
        float turretRotationValue;
        float barrelRotationValue;

        /// <summary>
        /// Gets or sets the turret rotation amount.
        /// </summary>
        public float TurretRotation
        {
            get { return turretRotationValue; }
            set { turretRotationValue = value; }
        }

        /// <summary>
        /// Gets or sets the barrel rotation amount.
        /// </summary>
        public float BarrelRotation
        {
            get { return barrelRotationValue; }
            set { barrelRotationValue = value; }
        }


        /// <summary>
        /// Load the model
        /// </summary>
        public void Load(ContentManager Content)
        {
            // TODO: use this.Content to load your game content here
            myModel = Content.Load<Model>("Models\\simple_tank02");

            MainBone = myModel.Bones["Main"];
            TurretBone = myModel.Bones["Turret"];
            BarrelBone = myModel.Bones["Barrel"];

            MainTransform = MainBone.Transform;
            TurretTransform = TurretBone.Transform;
            BarrelTransform = BarrelBone.Transform;

            // Allocate the transform matrix array.
            boneTransforms = new Matrix[myModel.Bones.Count];
        }


        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            myModel.Root.Transform = world;

            Matrix turretRotation = Matrix.CreateRotationY(MathHelper.ToRadians(37));
            Matrix barrelRotation = Matrix.CreateRotationX(barrelRotationValue);

            MainBone.Transform = MainTransform;
            TurretBone.Transform = turretRotation * TurretTransform;
            BarrelBone.Transform = barrelRotation * BarrelTransform;

            myModel.CopyAbsoluteBoneTransformsTo(boneTransforms);

            // Draw the model, a model can have multiple meshes, so loop
            foreach (ModelMesh mesh in myModel.Meshes)
            {
                // This is where the mesh orientation is set
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = boneTransforms[mesh.ParentBone.Index];
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting();
                }

                // Draw the mesh, will use the effects set above
                mesh.Draw();
            }
        }


    }
}
\$\endgroup\$
4
  • \$\begingroup\$ what is your drawing code? \$\endgroup\$
    – Vodáček
    May 27, 2012 at 5:05
  • \$\begingroup\$ @Vodáček added it to the op \$\endgroup\$
    – MLM
    May 27, 2012 at 5:08
  • \$\begingroup\$ Try this draw method: t.zvb.cz/imperium/databaze/lidi/vodacek/tancik_zasobnik.cs \$\endgroup\$
    – Vodáček
    May 27, 2012 at 5:25
  • \$\begingroup\$ @Vodáček No change :( - Do you want me to upload my model? \$\endgroup\$
    – MLM
    May 27, 2012 at 19:41

3 Answers 3

5
\$\begingroup\$

Keep in mind I had this problem over 2 years ago and I have since moved onto Unity 3D. This is more of a conclusion than a solution.

The main problem was that moving the bones did not move the mesh. I used Cinema 4D to model and rig the model and exported as fbx. There are many fbx export options in C4D and I tried many variants with no success. Here are two topics I made on C4D Cafe about XNA and C4D but they do not have a solution.

I was only ever able to rotate a mesh in XNA. I was able to mimic the bone binding effect by structuring the different meshes in a hierarchy so that they affect each other appropriately (pictured below). The hierarchical method works for this simple tank model but a more complicated model would require a skeletons in many situations.

The final effect I ended up with in XNA is pictured below. You can download my project here. The code is essentially the same as the original code in the question but simplified to only do turret rotation instead of barrel and turret.

Download the XNA Project

\$\endgroup\$
0
\$\begingroup\$

Draw each mesh apart(body,barrel,transform), applying transform directly do effect.world

effect.world = turretRotation * TurretTransform;

or you can create a Matrix[], where you would store rotation Matrices, and then

effect.world = RotationMatrices[mesh.ParentBone.Index] * boneTransforms[mesh.ParentBone.Index]
\$\endgroup\$
0
\$\begingroup\$

You need to draw each mesh separately, and with the appropriate bone transformations: (The below pulled from Reimer's tutorials as it's simple and clean):

private void DrawModel()
 {
     Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) *
           Matrix.CreateRotationY(MathHelper.Pi) * 
           Matrix.CreateTranslation(new Vector3(19, 12, -5));

     Matrix[] xwingTransforms = new Matrix[xwingModel.Bones.Count];
     xwingModel.CopyAbsoluteBoneTransformsTo(xwingTransforms);
     foreach (ModelMesh mesh in xwingModel.Meshes)
     {
         foreach (Effect currentEffect in mesh.Effects)
         {
             currentEffect.CurrentTechnique = currentEffect.Techniques["Colored"];
             currentEffect.Parameters["xWorld"].SetValue(xwingTransforms[mesh.ParentBone.Index] * worldMatrix);
             currentEffect.Parameters["xView"].SetValue(viewMatrix);
             currentEffect.Parameters["xProjection"].SetValue(projectionMatrix);
         }
         mesh.Draw();
     }
 }

Note the retrieval of the bone transforms from the model, then the world matrix being passed in to the effect becoming:

xwingTransforms[mesh.ParentBone.Index] * worldMatrix

(I.e. the model's original world matrix multipied by the bone transformation matrix).

\$\endgroup\$
1
  • \$\begingroup\$ I'm working on some code now but I think the FBX exporter in Cinema 4D is the culprit (see c4dcafe.com/ipb/topic/75579-c4d-to-xna-pipeline) - Attach a known working simple skeletal rigged model to test code with if you have one though. \$\endgroup\$
    – MLM
    May 11, 2013 at 5:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .