assimp/port/Assimp.NET/Assimp.NET_DEMO/AssimpView.cs

497 lines
21 KiB
C#

/*
---------------------------------------------------------------------------
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<string, Texture>();
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<string, Texture> 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
};
}