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 }