#region File Description
//-----------------------------------------------------------------------------
// SkinningSample.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SkinnedModel;
using Primitives3D;
#endregion
namespace SkinningSample
{
///
/// Sample game showing how to display skinned character animation.
///
public class SkinningSampleGame : Microsoft.Xna.Framework.Game
{
#region Fields
GraphicsDeviceManager graphics;
KeyboardState currentKeyboardState = new KeyboardState();
GamePadState currentGamePadState = new GamePadState();
KeyboardState previousKeyboardState = new KeyboardState();
GamePadState previousGamePadState = new GamePadState();
SkinnedSphere[] skinnedSpheres;
BoundingSphere[] boundingSpheres;
bool showSpheres;
SpherePrimitive spherePrimitive;
Model currentModel;
AnimationPlayer animationPlayer;
SkinningData skinningData;
Matrix[] boneTransforms;
Model baseballBat;
float cameraArc = 0;
float cameraRotation = 0;
float cameraDistance = 100;
#endregion
#region Initialization
public SkinningSampleGame()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
#if WINDOWS_PHONE
// Frame rate is 30 fps by default for Windows Phone.
TargetElapsedTime = TimeSpan.FromTicks(333333);
graphics.IsFullScreen = true;
#endif
}
///
/// Load your graphics content.
///
protected override void LoadContent()
{
// Load the model.
currentModel = Content.Load("dude");
// Look up our custom skinning information.
skinningData = currentModel.Tag as SkinningData;
if (skinningData == null)
throw new InvalidOperationException
("This model does not contain a SkinningData tag.");
boneTransforms = new Matrix[skinningData.BindPose.Count];
// Load the baseball bat model.
baseballBat = Content.Load("baseballbat");
// Create an animation player, and start decoding an animation clip.
animationPlayer = new AnimationPlayer(skinningData);
AnimationClip clip = skinningData.AnimationClips["Take 001"];
animationPlayer.StartClip(clip);
// Load the bounding spheres.
skinnedSpheres = Content.Load("CollisionSpheres");
boundingSpheres = new BoundingSphere[skinnedSpheres.Length];
spherePrimitive = new SpherePrimitive(GraphicsDevice, 1, 12);
}
#endregion
#region Update and Draw
///
/// Allows the game to run logic.
///
protected override void Update(GameTime gameTime)
{
HandleInput();
UpdateCamera(gameTime);
// Read gamepad inputs.
float headRotation = currentGamePadState.ThumbSticks.Left.X;
float armRotation = Math.Max(currentGamePadState.ThumbSticks.Left.Y, 0);
// Read keyboard inputs.
if (currentKeyboardState.IsKeyDown(Keys.PageUp))
headRotation = -1;
else if (currentKeyboardState.IsKeyDown(Keys.PageDown))
headRotation = 1;
if (currentKeyboardState.IsKeyDown(Keys.Space))
armRotation = 0.5f;
// Create rotation matrices for the head and arm bones.
Matrix headTransform = Matrix.CreateRotationX(headRotation);
Matrix armTransform = Matrix.CreateRotationY(-armRotation);
// Tell the animation player to compute the latest bone transform matrices.
animationPlayer.UpdateBoneTransforms(gameTime.ElapsedGameTime, true);
// Copy the transforms into our own array, so we can safely modify the values.
animationPlayer.GetBoneTransforms().CopyTo(boneTransforms, 0);
// Modify the transform matrices for the head and upper-left arm bones.
int headIndex = skinningData.BoneIndices["Head"];
int armIndex = skinningData.BoneIndices["L_UpperArm"];
boneTransforms[headIndex] = headTransform * boneTransforms[headIndex];
boneTransforms[armIndex] = armTransform * boneTransforms[armIndex];
// Tell the animation player to recompute the world and skin matrices.
animationPlayer.UpdateWorldTransforms(Matrix.Identity, boneTransforms);
animationPlayer.UpdateSkinTransforms();
UpdateBoundingSpheres();
base.Update(gameTime);
}
///
/// Updates the boundingSpheres array to match the current animation state.
///
void UpdateBoundingSpheres()
{
// Look up the current world space bone positions.
Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();
for (int i = 0; i < skinnedSpheres.Length; i++)
{
// Convert the SkinnedSphere description to a BoundingSphere.
SkinnedSphere source = skinnedSpheres[i];
Vector3 center = new Vector3(source.Offset, 0, 0);
BoundingSphere sphere = new BoundingSphere(center, source.Radius);
// Transform the BoundingSphere by its parent bone matrix,
// and store the result into the boundingSpheres array.
int boneIndex = skinningData.BoneIndices[source.BoneName];
boundingSpheres[i] = sphere.Transform(worldTransforms[boneIndex]);
}
}
///
/// This is called when the game should draw itself.
///
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
device.Clear(Color.CornflowerBlue);
Matrix[] bones = animationPlayer.GetSkinTransforms();
// Compute camera matrices.
Matrix view = Matrix.CreateTranslation(0, -40, 0) *
Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
new Vector3(0, 0, 0), Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
device.Viewport.AspectRatio,
1,
10000);
// Render the skinned mesh.
foreach (ModelMesh mesh in currentModel.Meshes)
{
foreach (SkinnedEffect effect in mesh.Effects)
{
effect.SetBoneTransforms(bones);
effect.View = view;
effect.Projection = projection;
effect.EnableDefaultLighting();
effect.SpecularColor = new Vector3(0.25f);
effect.SpecularPower = 16;
}
mesh.Draw();
}
DrawBaseballBat(view, projection);
if (showSpheres)
{
DrawBoundingSpheres(view, projection);
}
base.Draw(gameTime);
}
///
/// Draws the animated bounding spheres.
///
void DrawBoundingSpheres(Matrix view, Matrix projection)
{
GraphicsDevice.RasterizerState = Wireframe;
foreach (BoundingSphere sphere in boundingSpheres)
{
Matrix world = Matrix.CreateScale(sphere.Radius) *
Matrix.CreateTranslation(sphere.Center);
spherePrimitive.Draw(world, view, projection, Color.White);
}
GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
}
static RasterizerState Wireframe = new RasterizerState
{
FillMode = FillMode.WireFrame
};
///
/// Draws the baseball bat.
///
void DrawBaseballBat(Matrix view, Matrix projection)
{
int handIndex = skinningData.BoneIndices["L_Index1"];
Matrix[] worldTransforms = animationPlayer.GetWorldTransforms();
// Nudge the bat over so it appears between the left thumb and index finger.
Matrix batWorldTransform = Matrix.CreateTranslation(-1.3f, 2.1f, 0.1f) *
worldTransforms[handIndex];
foreach (ModelMesh mesh in baseballBat.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.World = batWorldTransform;
effect.View = view;
effect.Projection = projection;
effect.EnableDefaultLighting();
}
mesh.Draw();
}
}
#endregion
#region Handle Input
///
/// Handles input for quitting the game.
///
private void HandleInput()
{
previousKeyboardState = currentKeyboardState;
previousGamePadState = currentGamePadState;
currentKeyboardState = Keyboard.GetState();
currentGamePadState = GamePad.GetState(PlayerIndex.One);
// Check for exit.
if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
currentGamePadState.Buttons.Back == ButtonState.Pressed)
{
Exit();
}
// Toggle the collision sphere display.
if ((currentKeyboardState.IsKeyDown(Keys.Enter) &&
previousKeyboardState.IsKeyUp(Keys.Enter)) ||
(currentGamePadState.IsButtonDown(Buttons.A) &&
previousGamePadState.IsButtonUp(Buttons.A)))
{
showSpheres = !showSpheres;
}
}
///
/// Handles camera input.
///
private void UpdateCamera(GameTime gameTime)
{
float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Check for input to rotate the camera up and down around the model.
if (currentKeyboardState.IsKeyDown(Keys.Up) ||
currentKeyboardState.IsKeyDown(Keys.W))
{
cameraArc += time * 0.1f;
}
if (currentKeyboardState.IsKeyDown(Keys.Down) ||
currentKeyboardState.IsKeyDown(Keys.S))
{
cameraArc -= time * 0.1f;
}
cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.25f;
// Limit the arc movement.
if (cameraArc > 90.0f)
cameraArc = 90.0f;
else if (cameraArc < -90.0f)
cameraArc = -90.0f;
// Check for input to rotate the camera around the model.
if (currentKeyboardState.IsKeyDown(Keys.Right) ||
currentKeyboardState.IsKeyDown(Keys.D))
{
cameraRotation += time * 0.1f;
}
if (currentKeyboardState.IsKeyDown(Keys.Left) ||
currentKeyboardState.IsKeyDown(Keys.A))
{
cameraRotation -= time * 0.1f;
}
cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.25f;
// Check for input to zoom camera in and out.
if (currentKeyboardState.IsKeyDown(Keys.Z))
cameraDistance += time * 0.25f;
if (currentKeyboardState.IsKeyDown(Keys.X))
cameraDistance -= time * 0.25f;
cameraDistance += currentGamePadState.Triggers.Left * time * 0.5f;
cameraDistance -= currentGamePadState.Triggers.Right * time * 0.5f;
// Limit the camera distance.
if (cameraDistance > 500.0f)
cameraDistance = 500.0f;
else if (cameraDistance < 10.0f)
cameraDistance = 10.0f;
if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
currentKeyboardState.IsKeyDown(Keys.R))
{
cameraArc = 0;
cameraRotation = 0;
cameraDistance = 100;
}
}
#endregion
}
#region Entry Point
///
/// The main entry point for the application.
///
static class Program
{
static void Main()
{
using (SkinningSampleGame game = new SkinningSampleGame())
{
game.Run();
}
}
}
#endregion
}