/* * ModelViewer.cs * Copyright (c) 2006 Michael Nikonov, David Astle * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define WINDOWS #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion namespace Xclna.Xna.Animation { /// /// A camera for interfacing with the ModelViewer /// public interface IModelViewerCamera { /// /// The world matrix of the model that is being viewed. /// Matrix ModelWorld { get;} /// /// The view matrix of the camera. /// Matrix View { get;} /// /// The projection matrix used by the camera. /// Matrix Projection { get;} /// /// Updates the camera. /// /// The game time. void Update(GameTime gameTime); } /// /// The default camera used by the model viewer. /// public sealed class DefaultModelViewerCamera : IModelViewerCamera { private Matrix world, view, projection; private Vector3 cameraPosition, up, right; private float fieldOfView, nearDistance, farDistance, windowWidth, windowHeight, centerX, centerY, aspectRatio; private int initialZoom; // Radius of hte arcball private float arcRadius; private float camOffsetX = 0, camOffsetY = 0; private BoundingSphere sphere; private Viewport viewPort; private Vector3 modelPos = Vector3.One; #if WINDOWS private MouseState lastState; private KeyboardState lastKeyboardState; #endif // The model private Model model; /// /// Creates a new instance of the default model viewer camera. /// /// The game. /// The model to view. public DefaultModelViewerCamera(Game game, Model model) { this.model = model; // Basic parameter initialization sphere = new BoundingSphere(Vector3.Zero, 1.0f); game.IsMouseVisible = true; IGraphicsDeviceService graphics = (IGraphicsDeviceService)game.Services.GetService( typeof(IGraphicsDeviceService)); viewPort = graphics.GraphicsDevice.Viewport; windowWidth = (float)viewPort.Width; windowHeight = (float)viewPort.Height; fieldOfView = MathHelper.PiOver4; nearDistance = .1f; centerX = windowWidth / 2.0f; centerY = windowHeight / 2.0f; aspectRatio = windowWidth / windowHeight; up = Vector3.Up; right = Vector3.Right; MouseState state = Mouse.GetState(); initialZoom = state.ScrollWheelValue; this.model = model; // Merge the bounding spheres foreach (ModelMesh mesh in model.Meshes) sphere = BoundingSphere.CreateMerged(sphere, mesh.BoundingSphere); world = Matrix.Identity; farDistance = Math.Min(sphere.Radius * 1000, float.MaxValue); projection = Matrix.CreatePerspectiveFieldOfView( fieldOfView, aspectRatio, nearDistance, farDistance); cameraPosition = new Vector3(0, 0, sphere.Radius * 5); arcRadius = cameraPosition.Length() / 2.0f; view = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, up); } // Returns true if the user clicked on the boundings sphere, false otherwise private bool IntersectPoint(int x, int y, out Vector3 intersectionPoint) { BoundingSphere sphere = new BoundingSphere(new Vector3(), arcRadius); // Convert the mouse position to a 3d vector Vector3 location = new Vector3(x, y, 0); // Location of mouse point converted to true 3d space location = viewPort.Unproject(location, projection, view, Matrix.Identity); // direction vector of camera Vector3 direction = location - cameraPosition; direction.Normalize(); // Ray in the direction of the direction vector Ray r = new Ray(cameraPosition, direction); // Used to calculate where the ray intersects the sphere float? intersectFactor = r.Intersects(sphere); if (intersectFactor == null) { intersectionPoint = Vector3.Zero; return false; } else { intersectionPoint = cameraPosition + (direction * ((float)intersectFactor)); return true; } } #region IModelViewerCamera Members /// /// The world matrix of the model being viewed. /// public Matrix ModelWorld { get { return world; } } /// /// The view matrix of the camera. /// public Matrix View { get { Vector3 lookAt = up * camOffsetY * sphere.Radius / 3.0f + right * camOffsetX * sphere.Radius / 3.0f; return Matrix.CreateLookAt(cameraPosition, lookAt, up); } } /// /// The projection matrix used by the camera. /// public Matrix Projection { get { return projection; } } /// /// Updates the model view camera /// /// The time passed public void Update(GameTime gameTime) { #if WINDOWS MouseState state = Mouse.GetState(); KeyboardState ks = Keyboard.GetState(); // Adjust the zoom if (state.ScrollWheelValue != lastState.ScrollWheelValue) { float zoom = sphere.Radius * 5 - (sphere.Radius / 10.0f) * (((float) (state.ScrollWheelValue - initialZoom) ) / 40.0f); // Clamp the zoom to the model radius if (zoom < sphere.Radius / 4.0f) zoom = sphere.Radius / 4.0f; // Re-adjust the arcball radius arcRadius = zoom / 2.0f; // Re-adjust the camera position Vector3 n = Vector3.Normalize(cameraPosition); cameraPosition = n * zoom; view = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, up); } // Update the view if the user drags the mouse if ((state.LeftButton == ButtonState.Pressed || state.RightButton == ButtonState.Pressed) && (state.X != lastState.X || state.Y != lastState.Y)) { // The current click point and last clik point Vector3 curPt, lastPt; // If the mouse click intersects the arcball bounding sphere if (IntersectPoint(lastState.X, lastState.Y, out lastPt) && IntersectPoint(state.X, state.Y, out curPt)) { // Do all sorts of crazy trig! Vector3 cross = Vector3.Cross(lastPt, curPt); cross.Normalize(); lastPt.Normalize(); curPt.Normalize(); float ang = (float)Math.Acos(Vector3.Dot(lastPt, curPt)); Matrix axisRot = Matrix.CreateFromAxisAngle( cross, ang * 2.0f); if (state.LeftButton == ButtonState.Pressed) { world *= axisRot; } if (state.RightButton == ButtonState.Pressed) { Matrix invertRot = Matrix.Invert(axisRot); cameraPosition = Vector3.Transform(cameraPosition, invertRot); up = Vector3.Normalize(Vector3.Transform(up, invertRot)); right = Vector3.Normalize(Vector3.Transform(right, invertRot)); } } } view = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, up); if (ks.IsKeyDown(Keys.Right)) { this.camOffsetX += .1f; } if (ks.IsKeyDown(Keys.Left)) { this.camOffsetX -= .1f; } if (ks.IsKeyDown(Keys.Up)) { this.camOffsetY += .1f; } if (ks.IsKeyDown(Keys.Down)) { this.camOffsetY -= .1f; } view = Matrix.CreateLookAt( cameraPosition, Vector3.Zero, up); KeyboardState keyboardState = Keyboard.GetState(); lastState = state; lastKeyboardState = keyboardState; #endif } #endregion } /// /// A viewer animated models. /// public class ModelViewer : DrawableGameComponent { // The effects List effects = new List(); // The viewing space bounding sphere. private ModelAnimator animator; private Model model; private IModelViewerCamera cam; /// /// Gets the animator that animates the model for this viewer. /// public ModelAnimator Animator { get { return animator; } } /// /// Creates a new instance of ModelViewer. /// /// The game to which the viewer will be attached. /// The model to view. public ModelViewer(Game game, Model model) : base(game) { cam = new DefaultModelViewerCamera(game, model); UpdateOrder = 3; Add(model); game.Components.Add(this); } /// /// Adds a model to the viewer. /// /// The model to add. private void Add(Model model) { this.model = model; // Create an init the new controller animator = new ModelAnimator(model); //animator.Enabled = true; //animator.Visible = true; InitializeEffects(model); } // Initialize the effects so the models look reasonable and the lighting // Is as it is in the directx Mesh viewer private void InitializeEffects(Model model) { foreach (ModelMesh mesh in model.Meshes) { foreach (Effect ef in mesh.Effects) { effects.Add(ef); ef.Parameters["View"].SetValue(cam.View); ef.Parameters["EyePosition"].SetValue(Matrix.Invert(cam.View).Translation); ef.Parameters["Projection"].SetValue(cam.Projection); ef.Parameters["World"].SetValue(cam.ModelWorld); if (ef is BasicPaletteEffect) { BasicPaletteEffect effect = (BasicPaletteEffect)ef; effect.EnableDefaultLighting(); effect.DirectionalLight0.Direction = new Vector3(0, 0, -1); } else if (ef is BasicEffect) { BasicEffect effect = (BasicEffect)ef; effect.EnableDefaultLighting(); effect.DirectionalLight0.Direction = new Vector3(0, 0, -1); effect.DirectionalLight1.Enabled = false; effect.DirectionalLight2.Enabled = false; effect.AmbientLightColor = Color.Black.ToVector3(); effect.EmissiveColor = Color.Black.ToVector3(); } } } } /// /// Gets or sets the camera used by the viewer. /// public IModelViewerCamera Camera { get { return cam; } set { if (value == null) throw new ArgumentNullException("Camera can not be null."); cam = value; } } /// /// Updates the ModelViewer. /// /// The GameTime. public override void Update(GameTime gameTime) { if (cam != null) { cam.Update(gameTime); animator.World = cam.ModelWorld; foreach (Effect effect in effects) { effect.Parameters["View"].SetValue(cam.View); effect.Parameters["EyePosition"].SetValue(Matrix.Invert(cam.View).Translation); effect.Parameters["Projection"].SetValue(cam.Projection); } } base.Update(gameTime); } } }