/* --------------------------------------------------------------------------- Open Asset Import Library (ASSIMP) --------------------------------------------------------------------------- Copyright (c) 2006-2010, ASSIMP Development Team All rights reserved. Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ASSIMP team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of the ASSIMP Development Team. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; using System.Text; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace Assimp.Viewer { public partial class AssimpView : Form { public AssimpView() { InitializeComponent(); // Window title this.Text = "Assimp .NET Viewer"; // Ignore WM_ERASEBKGND messages to prevent flicker this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); // Listen to mouse this.MouseDown += new MouseEventHandler(AssimpView_MouseDown); this.MouseMove += new MouseEventHandler(AssimpView_MouseMove); this.MouseUp += new MouseEventHandler(AssimpView_MouseUp); this.MouseWheel += new MouseEventHandler(AssimpView_MouseWheel); } void AssimpView_MouseWheel(object sender, MouseEventArgs e) { g_sCamera.vPos.Z *= 1.0f - (e.Delta / 1000.0f); Refresh(); } private void AssimpView_MouseDown(object sender, MouseEventArgs e) { UpdateMouseState(e); } private void AssimpView_MouseMove(object sender, MouseEventArgs e) { UpdateMouseState(e); } private void AssimpView_MouseUp(object sender, MouseEventArgs e) { UpdateMouseState(e); } private void UpdateMouseState(MouseEventArgs e) { g_bMousePressed = (e.Button == MouseButtons.Left); g_bMousePressedR = (e.Button == MouseButtons.Right); g_bMousePressedM = (e.Button == MouseButtons.Middle); g_bMousePressedBoth = (e.Button == (MouseButtons.Left | MouseButtons.Right)); Refresh(); } protected override void OnPaintBackground(PaintEventArgs pevent) { /* Do nothing to prevent flicker during resize */ } private void Form1_Load(object sender, EventArgs e) { InitializeDevice(); InitializeAssimp(); } private void Form1_SizeChanged(object sender, EventArgs e) { presentParams.BackBufferWidth = Width; presentParams.BackBufferHeight = Height; device.Reset(presentParams); Invalidate(); } public void InitializeDevice() { // Improve Performance // http://blogs.msdn.com/b/tmiller/archive/2003/11/14/57531.aspx Device.IsUsingEventHandlers = false; // For Windowed mode leave Width and Height at 0 var adapter = Manager.Adapters.Default; presentParams = new PresentParameters(); presentParams.AutoDepthStencilFormat = DepthFormat.D16; presentParams.EnableAutoDepthStencil = true; presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; // Keep precision in floating point calculations // http://blogs.msdn.com/b/tmiller/archive/2004/06/01/145596.aspx var createFlags = CreateFlags.FpuPreserve; // Pure device for performance - all device render states are now write only var deviceType = DeviceType.Hardware; var caps = Manager.GetDeviceCaps(adapter.Adapter, deviceType); if (caps.DeviceCaps.SupportsPureDevice) { createFlags |= CreateFlags.PureDevice; } // Warning: some drivers lie about supporting HT&L. Use Software if you see problems. if (caps.DeviceCaps.SupportsHardwareTransformAndLight) { createFlags |= CreateFlags.HardwareVertexProcessing; } else { createFlags |= CreateFlags.SoftwareVertexProcessing; } // Create Device device = new Device(adapter.Adapter, deviceType, this, createFlags, presentParams); // Add event handlers device.DeviceResizing += new CancelEventHandler(device_DeviceResizing); } private void device_DeviceResizing(object sender, CancelEventArgs e) { device.Reset(); } // default pp steps private static aiPostProcessSteps ppsteps = aiPostProcessSteps.aiProcess_CalcTangentSpace | // calculate tangents and bitangents if possible aiPostProcessSteps.aiProcess_JoinIdenticalVertices | // join identical vertices/ optimize indexing aiPostProcessSteps.aiProcess_ValidateDataStructure | // perform a full validation of the loader's output aiPostProcessSteps.aiProcess_ImproveCacheLocality | // improve the cache locality of the output vertices aiPostProcessSteps.aiProcess_RemoveRedundantMaterials | // remove redundant materials aiPostProcessSteps.aiProcess_FindDegenerates | // remove degenerated polygons from the import aiPostProcessSteps.aiProcess_FindInvalidData | // detect invalid model data, such as invalid normal vectors aiPostProcessSteps.aiProcess_GenUVCoords | // convert spherical, cylindrical, box and planar mapping to proper UVs aiPostProcessSteps.aiProcess_TransformUVCoords | // preprocess UV transformations (scaling, translation ...) aiPostProcessSteps.aiProcess_FindInstances | // search for instanced meshes and remove them by references to one master aiPostProcessSteps.aiProcess_LimitBoneWeights | // limit bone weights to 4 per vertex aiPostProcessSteps.aiProcess_OptimizeMeshes | // join small meshes, if possible; (aiPostProcessSteps)0; public void InitializeAssimp() { var flags = ( ppsteps | aiPostProcessSteps.aiProcess_GenSmoothNormals | // generate smooth normal vectors if not existing aiPostProcessSteps.aiProcess_SplitLargeMeshes | // split large, unrenderable meshes into submeshes aiPostProcessSteps.aiProcess_Triangulate | // triangulate polygons with more than 3 edges aiPostProcessSteps.aiProcess_ConvertToLeftHanded | // convert everything to D3D left handed space aiPostProcessSteps.aiProcess_SortByPType | // make 'clean' meshes which consist of a single typ of primitives (aiPostProcessSteps)0); // default model var path = "../../../../../test/models/3DS/test1.3ds"; importer = new Importer(); string[] args = Environment.GetCommandLineArgs(); if (args.Length > 1) { path = args[1]; } //var path = "man.3ds"; scene = importer.ReadFile(path, flags); if (scene != null) { directory = Path.GetDirectoryName(path); CacheMaterials(scene.mMaterials); CacheMeshes(scene.mMeshes); SetupCamera(scene.mCameras); } else { MessageBox.Show("Failed to open file: " + path + ". Either Assimp screwed up or the path is not valid."); Application.Exit(); } } private void CacheMeshes(aiMeshVector meshes) { var numMeshes = meshes.Count; meshCache = new Mesh[numMeshes]; meshBoundsMin = new Vector3[numMeshes]; meshBoundsMax = new Vector3[numMeshes]; for (int i = 0; i < numMeshes; ++i) { var mesh = meshes[i]; switch (mesh.mPrimitiveTypes) { case aiPrimitiveType.aiPrimitiveType_TRIANGLE: Vector3 min, max; meshCache[i] = CreateMesh(mesh, out min, out max); meshBoundsMin[i] = min; meshBoundsMax[i] = max; break; } } } private void CacheMaterials(aiMaterialVector materials) { var numMaterials = materials.Count; materialCache = new ExtendedMaterial[numMaterials]; textureCache = new Dictionary(); for (int i = 0; i < numMaterials; ++i) { var material = materials[i]; var dxExtendedMaterial = new ExtendedMaterial(); var dxMaterial = new Material(); dxMaterial.AmbientColor = material.Ambient.ToColorValue(); dxMaterial.DiffuseColor = material.Diffuse.ToColorValue(); dxMaterial.EmissiveColor = material.Emissive.ToColorValue(); dxMaterial.SpecularColor = material.Specular.ToColorValue(); dxMaterial.SpecularSharpness = material.ShininessStrength; dxExtendedMaterial.Material3D = dxMaterial; dxExtendedMaterial.TextureFilename = material.TextureDiffuse0; materialCache[i] = dxExtendedMaterial; var textureFilename = dxExtendedMaterial.TextureFilename; if (!string.IsNullOrEmpty(textureFilename) && !textureCache.ContainsKey(textureFilename)) { textureCache.Add(textureFilename, CreateTexture(textureFilename)); } } } private Texture CreateTexture(string fileName) { var path = Path.Combine(directory, fileName); try { using (var data = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { var dxTexture = Texture.FromStream(device, data, Usage.None, Pool.Managed); textureCache[path] = dxTexture; return dxTexture; } } catch (Exception) { return null; } } private Mesh CreateMesh(aiMesh aiMesh, out Vector3 min, out Vector3 max) { var numFaces = (int) aiMesh.mNumFaces; var numVertices = (int) aiMesh.mNumVertices; var dxMesh = new Mesh(numFaces, numVertices, MeshFlags.Managed | MeshFlags.Use32Bit, CustomVertex.PositionNormalTextured.Format, device); var aiPositions = aiMesh.mVertices; var aiNormals = aiMesh.mNormals; var aiTextureCoordsAll = aiMesh.mTextureCoords; var aiTextureCoords = (aiTextureCoordsAll!=null) ? aiTextureCoordsAll[0] : null; var dxVertices = new CustomVertex.PositionNormalTextured[numVertices]; for (int i = 0; i < numVertices; ++i) { dxVertices[i].Position = aiPositions[i].ToVector3(); if (aiNormals != null) { dxVertices[i].Normal = aiNormals[i].ToVector3(); } if (aiTextureCoords != null) { var uv = aiTextureCoords[i]; dxVertices[i].Tu = uv.x; dxVertices[i].Tv = uv.y; } } dxMesh.VertexBuffer.SetData(dxVertices, 0, LockFlags.None); var aiFaces = aiMesh.mFaces; var dxIndices = new uint[numFaces * 3]; for (int i = 0; i < numFaces; ++i) { var aiFace = aiFaces[i]; var aiIndices = aiFace.mIndices; for (int j = 0; j < 3; ++j) { dxIndices[i * 3 + j] = aiIndices[j]; } } dxMesh.IndexBuffer.SetData(dxIndices, 0, LockFlags.None); var dxAttributes = dxMesh.LockAttributeBufferArray(LockFlags.None); // TODO: Set face material index for attributes dxMesh.UnlockAttributeBuffer(dxAttributes); var adjacency = new int[numFaces * 3]; dxMesh.GenerateAdjacency(0.0f, adjacency); dxMesh.OptimizeInPlace(MeshFlags.OptimizeAttributeSort, adjacency); Geometry.ComputeBoundingBox(dxVertices, CustomVertex.PositionNormalTextured.StrideSize, out min, out max); return dxMesh; } //------------------------------------------------------------------------------- // Calculate the boundaries of a given node and all of its children // The boundaries are in Worldspace (AABB) // piNode Input node // min/max Receives the min/max boundaries // piMatrix Transformation matrix of the graph at this position //------------------------------------------------------------------------------- private void CalculateBounds(aiNode piNode, ref Vector3 min, ref Vector3 max, Matrix piMatrix) { Debug.Assert(null != piNode); var mTemp = piNode.mTransformation.ToMatrix(); var aiMe = mTemp * piMatrix; var meshes = piNode.mMeshes; var numMeshes = meshes.Count; for (int i = 0; i < numMeshes; ++i) { var mesh = meshes[i]; var meshMin = Vector3.TransformCoordinate(meshBoundsMin[mesh], aiMe); var meshMax = Vector3.TransformCoordinate(meshBoundsMax[mesh], aiMe); min.X = Math.Min(min.X, meshMin.X); min.Y = Math.Min(min.Y, meshMin.Y); min.Z = Math.Min(min.Z, meshMin.Z); max.X = Math.Max(max.X, meshMax.X); max.Y = Math.Max(max.Y, meshMax.Y); max.Z = Math.Max(max.Z, meshMax.Z); } var children = piNode.mChildren; var numChildren = children.Count; for (int i = 0; i < numChildren; ++i) { CalculateBounds(children[i], ref min, ref max, aiMe); } } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { HandleMouseInputLocal(); Render(); } private void Render() { device.Clear(ClearFlags.Target|ClearFlags.ZBuffer, Color.DarkSlateBlue, 1.0f, 0); device.BeginScene(); SetupMatrices(); if (scene != null && scene.mRootNode != null) { SetupLights(scene.mLights); RenderNode(scene.mRootNode, g_mWorldRotate); } device.EndScene(); device.Present(); } private void SetupMatrices() { var fovy = Geometry.DegreeToRadian(45.0f); var aspect = ((float)this.Width / (float)this.Height); device.Transform.Projection = Matrix.PerspectiveFovLH(fovy, aspect, g_zNear, g_zFar); device.Transform.View = g_sCamera.GetMatrix(); } private void SetupCamera(aiCameraVector cameras) { // Get scene bounds var min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); var max = new Vector3(float.MinValue, float.MinValue, float.MinValue); CalculateBounds(scene.mRootNode, ref min, ref max, Matrix.Identity); // Projection Depth var size = (max-min); g_zFar = Math.Max(Math.Max(size.X, size.Y), size.Z) * 100; // Starting View Position var center = min + size * 0.5f; var lookAt = new Vector3(0,0,1); // Unit vector not coordinate var position = new Vector3(center.X, center.Y, center.Z-(size.Z*10)); var up = new Vector3(0, 1, 0); if (cameras.Count > 0) { var camera = cameras[0]; lookAt = camera.mLookAt.ToVector3(); position = camera.mPosition.ToVector3(); up = camera.mUp.ToVector3(); } g_sCamera.vLookAt = lookAt; g_sCamera.vPos = position; g_sCamera.vUp = up; } private void SetupLights(aiLightVector lights) { var numLights = lights.Count; if (numLights == 0) { var light = device.Lights[0]; light.Type = LightType.Directional; light.Diffuse = Color.LightGray; light.Direction = new Vector3(-1, -1, 1); light.Enabled = true; } else { // TODO: setup lights } // Enables lighting device.RenderState.Lighting = true; device.RenderState.Ambient = Color.DarkGray; device.RenderState.SpecularEnable = true; } private void RenderNode(aiNode node, Matrix parentMatrix) { var nodeMatrix = node.mTransformation.ToMatrix() * parentMatrix; var children = node.mChildren; var numChildren = node.mNumChildren; for (int i = 0; i < numChildren; ++i) { var child = children[i]; RenderNode(child, nodeMatrix); } device.Transform.World = nodeMatrix; var meshes = node.mMeshes; var numMeshes = meshes.Count; for (int i = 0; i < numMeshes; ++i) { var meshId = (int)meshes[i]; var mesh = scene.mMeshes[meshId]; var materialId = (int)mesh.mMaterialIndex; var dxMaterial = materialCache[materialId]; device.Material = dxMaterial.Material3D; Texture dxTexture = null; if (!string.IsNullOrEmpty(dxMaterial.TextureFilename)) { textureCache.TryGetValue(dxMaterial.TextureFilename, out dxTexture); } device.SetTexture(0, dxTexture); var dxMesh = meshCache[meshId]; RenderMesh(dxMesh); } } private void RenderMesh(Mesh mesh) { for (int i = 0; i < mesh.NumberAttributes; ++i) { mesh.DrawSubset(i); } } private Device device; private PresentParameters presentParams; private Importer importer; private string directory; private aiScene scene; private Mesh[] meshCache; private ExtendedMaterial[] materialCache; private Dictionary textureCache; private Vector3[] meshBoundsMin; private Vector3[] meshBoundsMax; private Camera g_sCamera = new Camera(); private float g_zNear = 0.2f; private float g_zFar = 1000.0f; private Matrix g_mProjection = Matrix.Identity; private Matrix g_mWorldRotate = Matrix.Identity; } //------------------------------------------------------------------------------- // Position of the cursor relative to the 3ds max' like control circle //------------------------------------------------------------------------------- public enum EClickPos { // The click was inside the inner circle (x,y axis) Circle, // The click was inside one of tghe vertical snap-ins CircleVert, // The click was inside onf of the horizontal snap-ins CircleHor, // the cklick was outside the circle (z-axis) Outside }; }