484 lines
20 KiB
C#
484 lines
20 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);
|
|
|
|
importer = new Importer();
|
|
var path = "../../../../../../test/models/3DS/jeep1.3ds";
|
|
//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);
|
|
}
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|