1 /**
2  * Defines the Mesh class, which controls all meshes loaded into the world.
3  */
4 module dash.components.mesh;
5 import dash.core, dash.components, dash.graphics, dash.utility;
6 
7 import derelict.opengl3.gl3, derelict.assimp3.assimp;
8 import std.stdio, std.stream, std.format, std.math, std..string;
9 
10 mixin( registerComponents!() );
11 
12 /**
13  * Loads and manages meshes into OpenGL.
14  *
15  * Supported formats:
16  *  3DS
17  *  BLEND (Blender 3D)
18  *  DAE/Collada
19  *  FBX
20  *  IFC-STEP
21  *  ASE
22  *  DXF
23  *  HMP
24  *  MD2
25  *  MD3
26  *  MD5
27  *  MDC
28  *  MDL
29  *  NFF
30  *  PLY
31  *  STL
32  *  X
33  *  OBJ
34  *  SMD
35  *  LWO
36  *  LXO
37  *  LWS
38  *  TER
39  *  AC3D
40  *  MS3D
41  *  COB
42  *  Q3BSP
43  *  XGL
44  *  CSM
45  *  BVH
46  *  B3D
47  *  NDO
48  *  Ogre XML
49  *  Q3D
50  */
51 class MeshAsset : Asset
52 {
53 private:
54     uint _glVertexArray, _numVertices, _numIndices, _glIndexBuffer, _glVertexBuffer;
55     bool _animated;
56     box3f _boundingBox;
57     AnimationData _animationData;
58 
59 public:
60     /// TODO
61     mixin( Property!_glVertexArray );
62     /// TODO
63     mixin( Property!_numVertices );
64     /// TODO
65     mixin( Property!_numIndices );
66     /// TODO
67     mixin( Property!_glIndexBuffer );
68     /// TODO
69     mixin( Property!_glVertexBuffer );
70     /// TODO
71     mixin( Property!_animated );
72     /// Stores all data about animations on the mesh.
73     mixin( Property!( _animationData, AccessModifier.Package ) );
74     /// The bounding box of the mesh.
75     mixin( RefGetter!_boundingBox );
76 
77     /**
78      * Creates a mesh.
79      *
80      * Params:
81      *      filePath =          The path to the file.
82      *      mesh =              The AssImp mesh object to pull data from.
83      */
84     this( Resource filePath, const(aiMesh*) mesh )
85     {
86         super( filePath );
87         int floatsPerVertex, vertexSize;
88         float[] outputData;
89         uint[] indices;
90         animated = false;
91 
92         if( mesh )
93         {
94             // If there is animation data
95             if( mesh.mNumBones > 0 )
96             {
97                 // (8 floats for animation data)
98                 animated = true;
99                 floatsPerVertex = 19;
100                 vertexSize = cast(int)(float.sizeof * floatsPerVertex);
101 
102                 // Get the vertex anim data
103                 float[][] vertBones = new float[][ mesh.mNumVertices ];
104                 float[][] vertWeights = new float[][ mesh.mNumVertices ];
105                 for( int bone = 0; bone < mesh.mNumBones; bone++ )
106                 {
107                     for( int weight = 0; weight < mesh.mBones[ bone ].mNumWeights; weight++ )
108                     {
109                         vertBones[ cast(int)mesh.mBones[ bone ].mWeights[ weight ].mVertexId ] ~= bone;
110                         vertWeights[ cast(int)mesh.mBones[ bone ].mWeights[ weight ].mVertexId ] ~= mesh.mBones[ bone ].mWeights[ weight ].mWeight;
111                     }
112                 }
113 
114                 // Make sure each is 4, if not bring or truncate to 4
115                 int maxBonesAttached = 0;
116                 for( int i = 0; i < mesh.mNumVertices; i++)
117                 {
118                     if ( vertBones[i].length > maxBonesAttached )
119                         maxBonesAttached = cast(int)vertBones[i].length;
120 
121                     while(vertBones[i].length < 4)
122                     {
123                         vertBones[i] ~= 0;
124                     }
125 
126                     while(vertWeights[i].length < 4)
127                     {
128                         vertWeights[i] ~= 0.0f;
129                     }
130 
131                 }
132                 if( maxBonesAttached > 4 )
133                 {
134                     warningf( "%s has more than 4 bones for some vertex, data will be truncated. (has %s)", filePath, maxBonesAttached );
135                 }
136 
137                 // For each vertex on each face
138                 int meshFaces = mesh.mNumFaces;
139                 for( int i = 0; i < meshFaces; i++ )
140                 {
141                     auto face = mesh.mFaces[i];
142                     for( int j = 0; j < 3; j++ )
143                     {
144                         // Get the vertex data
145                         aiVector3D pos = mesh.mVertices[ face.mIndices[ j ] ];
146                         aiVector3D uv = mesh.mTextureCoords[ 0 ][ face.mIndices[ j ] ];
147                         aiVector3D normal = mesh.mNormals[ face.mIndices[ j ] ];
148                         aiVector3D tangent = mesh.mTangents[ face.mIndices[ j ] ];
149                         aiVector3D bitangent = mesh.mBitangents[ face.mIndices[ j ] ];
150 
151                         // Append the data
152                         outputData ~= pos.x;
153                         outputData ~= pos.y;
154                         outputData ~= pos.z;
155                         outputData ~= uv.x;
156                         outputData ~= uv.y;
157                         outputData ~= normal.x;
158                         outputData ~= normal.y;
159                         outputData ~= normal.z;
160                         outputData ~= tangent.x;
161                         outputData ~= tangent.y;
162                         outputData ~= tangent.z;
163                         outputData ~= vertBones[ face.mIndices[ j ] ][0..4];
164                         outputData ~= vertWeights[ face.mIndices[ j ] ][0..4];
165 
166                         // Save the position in verts
167                         boundingBox.expandInPlace( vec3f( pos.x, pos.y, pos.z ) );
168                     }
169                 }
170             }
171             // Otherwise render without animation
172             if( mesh.mNumBones == 0 || animated == false ) // No animation or animation failed
173             {
174                 animated = false;
175                 floatsPerVertex = 11;
176                 vertexSize = cast(int)(float.sizeof * floatsPerVertex);
177 
178                 // For each vertex on each face
179                 int meshFaces = mesh.mNumFaces;
180                 for( int i = 0; i < meshFaces; i++ )
181                 {
182                     auto face = mesh.mFaces[i];
183                     for( int j = 0; j < 3; j++ )
184                     {
185                         // Get the vertex data
186                         aiVector3D pos = mesh.mVertices[ face.mIndices[ j ] ];
187                         aiVector3D uv = mesh.mTextureCoords[ 0 ][ face.mIndices[ j ] ];
188                         aiVector3D normal = mesh.mNormals[ face.mIndices[ j ] ];
189                         aiVector3D tangent = mesh.mTangents[ face.mIndices[ j ] ];
190                         aiVector3D bitangent = mesh.mBitangents[ face.mIndices[ j ] ];
191 
192                         // Append the data
193                         outputData ~= pos.x;
194                         outputData ~= pos.y;
195                         outputData ~= pos.z;
196                         outputData ~= uv.x;
197                         outputData ~= uv.y;
198                         outputData ~= normal.x;
199                         outputData ~= normal.y;
200                         outputData ~= normal.z;
201                         outputData ~= tangent.x;
202                         outputData ~= tangent.y;
203                         outputData ~= tangent.z;
204                         //outputData ~= bitangent.x;
205                         //outputData ~= bitangent.y;
206                         //outputData ~= bitangent.z;
207 
208                         // Save the position in verts
209                         boundingBox.expandInPlace( vec3f( pos.x, pos.y, pos.z ) );
210                     }
211                 }
212             }
213 
214             numVertices = cast(uint)( outputData.length / floatsPerVertex );
215             numIndices = numVertices;
216 
217             indices = new uint[ numIndices ];
218             foreach( ii; 0..numIndices )
219                 indices[ ii ] = ii;
220         }
221         else
222         {
223             // Did not load
224             fatalf( "Mesh not loaded: %s", filePath );
225         }
226 
227         // make and bind the VAO
228         glGenVertexArrays( 1, &_glVertexArray );
229         glBindVertexArray( glVertexArray );
230 
231         // make and bind the VBO
232         glGenBuffers( 1, &_glVertexBuffer );
233         glBindBuffer( GL_ARRAY_BUFFER, glVertexBuffer );
234 
235         // Buffer the data
236         glBufferData( GL_ARRAY_BUFFER, outputData.length * GLfloat.sizeof, outputData.ptr, GL_STATIC_DRAW );
237 
238         uint POSITION_ATTRIBUTE = 0;
239         uint UV_ATTRIBUTE = 1;
240         uint NORMAL_ATTRIBUTE = 2;
241         uint TANGENT_ATTRIBUTE = 3;
242         //uint BINORMAL_ATTRIBUTE = 4;
243 
244         // Connect the position to the inputPosition attribute of the vertex shader
245         glEnableVertexAttribArray( POSITION_ATTRIBUTE );
246         glVertexAttribPointer( POSITION_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, vertexSize, cast(const(void)*)0 );
247         // Connect uv to the textureCoordinate attribute of the vertex shader
248         glEnableVertexAttribArray( UV_ATTRIBUTE );
249         glVertexAttribPointer( UV_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 3 ) );
250         // Connect normals to the shaderPosition attribute of the vertex shader
251         glEnableVertexAttribArray( NORMAL_ATTRIBUTE );
252         glVertexAttribPointer( NORMAL_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 5 ) );
253         // Connect the tangent to the vertex shader
254         glEnableVertexAttribArray( TANGENT_ATTRIBUTE );
255         glVertexAttribPointer( TANGENT_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 8 ) );
256         // Connect the binormal to the vertex shader (Remember to change animation data values properly!!!)
257         //glEnableVertexAttribArray( BINORMAL_ATTRIBUTE );
258         //glVertexAttribPointer( BINORMAL_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 11 ) );
259 
260         if( animated )
261         {
262             uint BONE_ATTRIBUTE = 4;
263             uint WEIGHT_ATTRIBUTE = 5;
264 
265             glEnableVertexAttribArray( BONE_ATTRIBUTE );
266             glVertexAttribPointer( BONE_ATTRIBUTE, 4, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 11 ) );
267             glEnableVertexAttribArray( WEIGHT_ATTRIBUTE );
268             glVertexAttribPointer( WEIGHT_ATTRIBUTE, 4, GL_FLOAT, GL_FALSE, vertexSize, cast(char*)0 + ( GLfloat.sizeof * 15 ) );
269         }
270 
271         // Generate index buffer
272         glGenBuffers( 1, cast(uint*)&_glIndexBuffer );
273         glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, glIndexBuffer );
274 
275         // Buffer index data
276         glBufferData( GL_ELEMENT_ARRAY_BUFFER, uint.sizeof * numVertices, indices.ptr, GL_STATIC_DRAW );
277 
278         // unbind the VBO and VAO
279         glBindVertexArray( 0 );
280     }
281 
282     /**
283      * Refresh the asset.
284      */
285     override void refresh()
286     {
287         shutdown();
288 
289         // Load mesh
290         const aiScene* scene = aiImportFile( resource.fullPath.toStringz,
291                                              aiProcess_CalcTangentSpace | aiProcess_Triangulate |
292                                              aiProcess_JoinIdenticalVertices | aiProcess_SortByPType );
293         assert( scene, "Failed to load scene file '" ~ resource.fullPath ~ "' Error: " ~ aiGetErrorString().fromStringz );
294 
295         // Add mesh
296         if( scene.mNumMeshes > 0 )
297         {
298             auto tempMesh = new MeshAsset( resource, scene.mMeshes[ 0 ] );
299 
300             if( scene.mNumAnimations > 0 )
301                 tempMesh.animationData = new AnimationData( resource, scene.mAnimations, scene.mNumAnimations, scene.mMeshes[ 0 ], scene.mRootNode );
302 
303             // Copy attributes
304             _glVertexArray = tempMesh._glVertexArray;
305             _numVertices = tempMesh._numVertices;
306             _numIndices = tempMesh._numIndices;
307             _glIndexBuffer = tempMesh._glIndexBuffer;
308             _glVertexBuffer = tempMesh._glVertexBuffer;
309             _animated = tempMesh._animated;
310             _boundingBox = tempMesh._boundingBox;
311         }
312         else
313         {
314             warning( "Assimp did not contain mesh data, ensure you are loading a valid mesh." );
315             return;
316         }
317 
318         // Release mesh
319         aiReleaseImport( scene );
320     }
321 
322     /**
323      * Deletes mesh data stored on the GPU.
324      */
325     override void shutdown()
326     {
327         glDeleteBuffers( 1, &_glVertexBuffer );
328         glDeleteBuffers( 1, &_glVertexArray );
329     }
330 }
331 
332 class Mesh : AssetRef!MeshAsset
333 {
334     alias asset this;
335 
336     this() { }
337     this( MeshAsset ass )
338     {
339         super( ass );
340     }
341 
342     override void initialize()
343     {
344         super.initialize();
345 
346         if( animated && owner )
347             owner.addComponent( animationData.getComponent() );
348     }
349     
350 }
351 
352 /**
353  * Helper function that calculates a modifier for the reconstructed bitangent based on regenerating them
354  * May be needed elsewhere
355  *
356  * Params: TODO
357  *
358  * Returns:
359  */
360 private float calcTangentHandedness( aiVector3D nor, aiVector3D tan, aiVector3D bit )
361 {
362     vec3f n = vec3f( nor.x, nor.y, nor.z );
363     vec3f t = vec3f( tan.x, tan.y, tan.z );
364     vec3f b = vec3f( bit.x, bit.y, bit.z );
365 
366     //Gramm-schmidt
367     t = (t - n * dot( n, t )).normalized();
368 
369     return (dot(cross(n,t),b) > 0.0f) ? -1.0f : 1.0f;
370 }