#region File Description //----------------------------------------------------------------------------- // ObjectPlacementOnAvatar.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion namespace ObjectPlacementOnAvatar { /// /// This sample demonstrates how to place objects onto an avatar while it animates. /// public class ObjectPlacementOnAvatarGame : Microsoft.Xna.Framework.Game { #region Fields GraphicsDeviceManager graphics; // Description, renderer, and animation for // loading and drawing an animated avatar AvatarDescription avatarDescription; AvatarRenderer avatarRenderer; AvatarAnimation currentAvatarAnimation; AvatarAnimation[] avatarAnimations = new AvatarAnimation[4]; // Model for the baseball bat that is going to // be placed on the avatar Model baseballBat; // Matrices used to draw the avatar Matrix world; Matrix view; Matrix projection; // List of the avatar bones in world space List bonesWorldSpace; // the current input states. These are updated in the HandleInput function, // and used primarily in the UpdateCamera function. GamePadState currentGamePadState; GamePadState lastGamePadState; // the following constants control the speed at which the camera moves // how fast does the camera move up, down, left, and right? const float CameraRotateSpeed = .1f; // how fast does the camera zoom in and out? const float CameraZoomSpeed = .01f; // the camera can't be further away than this distance const float CameraMaxDistance = 10.0f; // and it can't be closer than this const float CameraMinDistance = 2.0f; // the following constants control the camera's default position const float CameraDefaultArc = 30.0f; const float CameraDefaultRotation = 0; const float CameraDefaultDistance = 3.0f; // Camera control values float cameraArc = CameraDefaultArc; float cameraRotation = CameraDefaultRotation; float cameraDistance = CameraDefaultDistance; #endregion #region Initialization /// /// Constructor. /// public ObjectPlacementOnAvatarGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 1280; graphics.PreferredBackBufferHeight = 720; graphics.PreferMultiSampling = true; // Avatars require GamerServices Components.Add(new GamerServicesComponent(this)); } /// /// Load your graphics content. /// protected override void LoadContent() { // Create random avatar description and load the renderer and animation avatarDescription = AvatarDescription.CreateRandom(); avatarRenderer = new AvatarRenderer(avatarDescription); // Load 4 of the preset animations avatarAnimations[0] = new AvatarAnimation(AvatarAnimationPreset.Stand0); avatarAnimations[1] = new AvatarAnimation(AvatarAnimationPreset.Celebrate); avatarAnimations[2] = new AvatarAnimation(AvatarAnimationPreset.Clap); avatarAnimations[3] = new AvatarAnimation(AvatarAnimationPreset.Stand5); // Current animation to play and update currentAvatarAnimation = avatarAnimations[0]; // Load the baseball bat model baseballBat = Content.Load("baseballbat"); // Initialize the rendering matrices world = Matrix.CreateRotationY(MathHelper.Pi); projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, .01f, 200.0f); // Initialize the list of bones in world space bonesWorldSpace = new List(AvatarRenderer.BoneCount); for (int i = 0; i < AvatarRenderer.BoneCount; i++) bonesWorldSpace.Add(Matrix.Identity); } #endregion #region Update and Draw /// /// Allows the game to run logic. /// protected override void Update(GameTime gameTime) { HandleInput(); UpdateCamera(gameTime); // Set avatar rendering matrices avatarRenderer.World = world; avatarRenderer.View = view; avatarRenderer.Projection = projection; // Update the current animation and world space bones if (avatarRenderer.State == AvatarRendererState.Ready) { currentAvatarAnimation.Update(gameTime.ElapsedGameTime, true); BonesToWorldSpace(avatarRenderer, currentAvatarAnimation, bonesWorldSpace); } base.Update(gameTime); } /// /// This is called when the game should draw itself. /// protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); DrawBaseballBat(); avatarRenderer.Draw(currentAvatarAnimation.BoneTransforms, currentAvatarAnimation.Expression); base.Draw(gameTime); } /// /// Draw the baseball bat /// private void DrawBaseballBat() { // Moves the bat closer to where we want it in the hand Matrix baseballBatOffset = Matrix.CreateRotationY(MathHelper.ToRadians(-20)) * Matrix.CreateTranslation(0.01f, 0.05f, 0.0f); foreach (ModelMesh mesh in baseballBat.Meshes) { foreach (BasicEffect effect in mesh.Effects) { effect.EnableDefaultLighting(); // Position the bat to be near the avatars right hand. The position // of the right special bone can be found by looking up the value in // our list of world space bones with the index of the bone we are // looking for. The bat is translated and rotated a small amount to // make it look better in the hand. effect.World = baseballBatOffset * bonesWorldSpace[(int)AvatarBone.SpecialRight]; effect.View = view; effect.Projection = projection; } mesh.Draw(); } } /// /// Updates a list of matrices to represent the location of the /// avatar bones in world space with the avatar animation applied. /// private static void BonesToWorldSpace(AvatarRenderer renderer, AvatarAnimation animation, List boneToUpdate) { // Bind pose of the avatar. // These positions are in local space, and are relative to the parent bone. IList bindPose = renderer.BindPose; // The current animation pose. // These positions are in local space, and are relative to the parent bone. IList animationPose = animation.BoneTransforms; // List of parent bones for each bone in the hierarchy IList parentIndex = renderer.ParentBones; // Loop all of the bones. // Since the bone hierarchy is sorted by depth // we will transform the parent before any child. for (int i = 0; i < AvatarRenderer.BoneCount; i++) { // Find the transform of this bones parent. // If this is the first bone use the world matrix used on the avatar Matrix parentMatrix = (parentIndex[i] != -1) ? boneToUpdate[parentIndex[i]] : renderer.World; // Calculate this bones world space position boneToUpdate[i] = Matrix.Multiply(Matrix.Multiply(animationPose[i], bindPose[i]), parentMatrix); } } #endregion #region Input and Camera /// /// Handles input for quitting the game. /// void HandleInput() { lastGamePadState = currentGamePadState; currentGamePadState = GamePad.GetState(PlayerIndex.One); // Check for exit. if (currentGamePadState.Buttons.Back == ButtonState.Pressed) { Exit(); } // Check to see if we should load another random avatar if (currentGamePadState.Buttons.RightShoulder == ButtonState.Pressed && lastGamePadState.Buttons.RightShoulder != ButtonState.Pressed) { avatarDescription = AvatarDescription.CreateRandom(); avatarRenderer = new AvatarRenderer(avatarDescription); } // Check to see if we need to play another animation if (currentGamePadState.Buttons.A == ButtonState.Pressed && lastGamePadState.Buttons.A != ButtonState.Pressed) { currentAvatarAnimation = avatarAnimations[1]; currentAvatarAnimation.CurrentPosition = TimeSpan.Zero; } else if (currentGamePadState.Buttons.B == ButtonState.Pressed && lastGamePadState.Buttons.B != ButtonState.Pressed) { currentAvatarAnimation = avatarAnimations[2]; currentAvatarAnimation.CurrentPosition = TimeSpan.Zero; } else if (currentGamePadState.Buttons.X == ButtonState.Pressed && lastGamePadState.Buttons.X != ButtonState.Pressed) { currentAvatarAnimation = avatarAnimations[3]; currentAvatarAnimation.CurrentPosition = TimeSpan.Zero; } else if (currentGamePadState.Buttons.Y == ButtonState.Pressed && lastGamePadState.Buttons.Y != ButtonState.Pressed) { currentAvatarAnimation = avatarAnimations[0]; currentAvatarAnimation.CurrentPosition = TimeSpan.Zero; } } /// /// Handles input for moving the camera. /// void UpdateCamera(GameTime gameTime) { float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds; // should we reset the camera? if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed) { cameraArc = CameraDefaultArc; cameraDistance = CameraDefaultDistance; cameraRotation = CameraDefaultRotation; } // Check for input to rotate the camera up and down around the model. cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * CameraRotateSpeed; // Limit the arc movement. cameraArc = MathHelper.Clamp(cameraArc, -90.0f, 90.0f); // Check for input to rotate the camera around the model. cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * CameraRotateSpeed; // Check for input to zoom camera in and out. cameraDistance += currentGamePadState.Triggers.Left * time * CameraZoomSpeed; cameraDistance -= currentGamePadState.Triggers.Right * time * CameraZoomSpeed; // clamp the camera distance so it doesn't get too close or too far away. cameraDistance = MathHelper.Clamp(cameraDistance, CameraMinDistance, CameraMaxDistance); Matrix unrotatedView = Matrix.CreateLookAt( new Vector3(0, 0, cameraDistance), new Vector3(0, 1, 0), Vector3.Up); view = Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) * Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) * unrotatedView; } #endregion } #region Entry Point /// /// The main entry point for the application. /// static class Program { static void Main() { using (ObjectPlacementOnAvatarGame game = new ObjectPlacementOnAvatarGame()) { game.Run(); } } } #endregion }