1 module evael.graphics.models.Iqm; 2 3 import std.stdio; 4 import std.math; 5 import std.exception : enforce; 6 import std.string : format; 7 import std.conv : to; 8 9 import core.stdc.string : memcpy; 10 import core.stdc.string : strlen; 11 12 import evael.graphics.GraphicsDevice; 13 import evael.graphics.shaders.IqmShader; 14 import evael.graphics.Vertex; 15 import evael.graphics.models.Model; 16 import evael.graphics.models.Animation; 17 import evael.graphics.models.BoundingBox; 18 19 import evael.system.Asset; 20 import evael.system.AssetLoader; 21 22 import evael.utils.Math; 23 import evael.utils.Color; 24 25 class Iqm : Model 26 { 27 static enum IQM_VERSION = 2; 28 29 struct IqmHeader 30 { 31 char[16] magic; 32 uint fileVersion; 33 uint fileSize; 34 uint flags; 35 uint num_text, ofs_text; 36 uint num_meshes, ofs_meshes; 37 uint num_vertexarrays, num_vertexes, ofs_vertexarrays; 38 uint num_triangles, ofs_triangles, ofs_adjacency; 39 uint num_joints, ofs_joints; 40 uint num_poses, ofs_poses; 41 uint num_animations, ofs_anims; 42 uint num_frames, num_framechannels, ofs_frames, ofs_bounds; 43 uint num_comment, ofs_comment; 44 uint num_extensions, ofs_extensions; 45 } 46 47 struct IqmMesh 48 { 49 uint name; 50 uint material; 51 uint firstVertex, vertexesNumber; 52 uint firstTriangle, trianglesNumber; 53 } 54 55 enum IqmAttribute: ubyte 56 { 57 Position = 0, 58 TexCoord = 1, 59 Normal = 2, 60 Tangent = 3, 61 BlendIndexes = 4, 62 BlendWeights = 5, 63 Color = 6, 64 Custom = 0x10 65 } 66 67 enum IqmType : ubyte 68 { 69 Byte = 0, 70 UByte = 1, 71 Short = 2, 72 UShort = 3, 73 Int = 4, 74 Uint = 5, 75 Half = 6, 76 Float = 7, 77 Double = 8, 78 } 79 80 struct IqmTriangle 81 { 82 uint[3] vertex; 83 } 84 85 struct IqmJoint 86 { 87 uint name; 88 int parent; 89 float[3] translate; 90 float[4] rotate; 91 float[3] scale; 92 } 93 94 struct IqmPose 95 { 96 int parent; 97 uint mask; 98 float[10] channeloffset; 99 float[10] channelscale; 100 } 101 102 struct IqmAnim 103 { 104 uint name; 105 uint firstFrame, framesNumber; 106 float deltaTime; 107 uint flags; 108 } 109 110 enum IQM_LOOP = 1<<0; 111 112 struct IqmVertexArray 113 { 114 uint type; 115 uint flags; 116 uint format; 117 uint size; 118 uint offset; 119 } 120 121 struct IqmBounds 122 { 123 float[3] bbmin, bbmax; 124 float xyradius, radius; 125 } 126 127 private float* m_inPositions; 128 private float* m_inNormals; 129 private float* m_inTexCoords; 130 131 private ubyte* m_inColors; 132 private ubyte* m_inBlendIndex; 133 private ubyte* m_inBlendWeight; 134 135 /// Model triangles 136 private IqmTriangle* m_triangles; 137 138 /// Model meshes 139 private IqmMesh* m_meshes; 140 141 /// Header 142 private IqmHeader m_header; 143 144 /// Textures for meshes 145 private uint[] m_textures; 146 147 private IqmJoint* m_rawJoints; 148 private IqmPose* m_rawPoses; 149 private IqmAnim* m_rawAnimations; 150 private BoundingBox* m_bounds; 151 152 public mat4[] m_baseframe; 153 public mat4[] m_inversebaseframe; 154 public mat4[] m_outframe; 155 public mat4[] m_frames; 156 157 /// Animations list 158 private Animation[string] m_animations; 159 160 /// Joints list 161 private uint[string] m_joints; 162 163 private BoundingBox[] m_boundingBoxes; 164 165 private uint m_vao, m_vertexBuffer, m_indexBuffer; 166 167 public this(GraphicsDevice graphicsDevice) 168 { 169 super(graphicsDevice); 170 } 171 172 173 public override void dispose() 174 { 175 176 } 177 178 /** 179 * Draws the model 180 */ 181 public override void draw(in float deltaTime, mat4 view, mat4 projection) 182 { 183 auto iqmShader = cast(IqmShader)this.m_graphicsDevice.currentShader; 184 185 this.m_graphicsDevice.setMatrix(this.m_shader.viewMatrix, view.arrayof.ptr); 186 this.m_graphicsDevice.setMatrix(this.m_shader.projectionMatrix, projection.arrayof.ptr); 187 188 this.m_graphicsDevice.setEnvironment(); 189 190 this.m_graphicsDevice.bindVAO(this.m_vao); 191 this.m_graphicsDevice.setMatrix(iqmShader.boneMatrices, this.m_outframe[0].arrayof.ptr, this.m_header.num_joints); 192 193 IqmTriangle* tris = null; 194 foreach(i; 0..this.m_header.num_meshes) 195 { 196 const(IqmMesh*) mesh = &this.m_meshes[i]; 197 198 if(this.m_textures != null) 199 { 200 if(i < this.m_textures.length) 201 { 202 this.m_graphicsDevice.bindTexture(this.m_textures[i]); 203 } 204 } 205 206 this.m_graphicsDevice.drawIndexedPrimitives!(PrimitiveType.Triangle)(mesh.trianglesNumber, &tris[mesh.firstTriangle]); 207 } 208 209 this.m_graphicsDevice.bindVAO(0); 210 } 211 212 213 public void drawShadowPass(in float deltaTime) 214 { 215 auto iqmShader = cast(IqmShader)this.m_graphicsDevice.currentShader; 216 217 this.m_graphicsDevice.bindVAO(this.m_vao); 218 this.m_graphicsDevice.setMatrix(iqmShader.boneMatrices, this.m_outframe[0].arrayof.ptr, this.m_header.num_joints); 219 220 IqmTriangle* tris = null; 221 foreach(i; 0..this.m_header.num_meshes) 222 { 223 const(IqmMesh*) mesh = &this.m_meshes[i]; 224 this.m_graphicsDevice.drawIndexedPrimitives!(PrimitiveType.Triangle)(mesh.trianglesNumber, &tris[mesh.firstTriangle]); 225 } 226 227 this.m_graphicsDevice.bindVAO(0); 228 } 229 230 public override void drawInstances(in bool bindTexture = true) 231 { 232 233 } 234 235 /** 236 * Play a specific frame 237 * Params: 238 * frame : frame to play 239 */ 240 public void playFrame(in float frame) 241 { 242 int frame1 = cast(int)floor(frame); 243 int frame2 = frame1 + 1; 244 245 immutable float frameoffset = frame - frame1; 246 247 frame1 %= this.m_header.num_frames; 248 frame2 %= this.m_header.num_frames; 249 250 mat4* mat1 = &this.m_frames[frame1 * this.m_header.num_joints]; 251 mat4* mat2 = &this.m_frames[frame2 * this.m_header.num_joints]; 252 253 float c = 1 - frameoffset; 254 for(size_t i = 0; i < this.m_header.num_joints; i++) 255 { 256 mat4 mat = (mat1[i] * c) + (mat2[i] * frameoffset); 257 258 if(this.m_rawJoints[i].parent >= 0) 259 { 260 this.m_outframe[i] = this.m_outframe[this.m_rawJoints[i].parent] * mat; 261 } 262 else this.m_outframe[i] = mat; 263 } 264 265 } 266 267 /** 268 * Loads IQM model 269 * Params: 270 * fileName : model to load 271 */ 272 static Iqm load(in string fileName) 273 { 274 import evael.utils.Config; 275 276 auto file = File(Config.Paths.models!string ~ fileName); 277 scope(exit) file.close(); 278 279 auto iqm = new Iqm(GraphicsDevice.getInstance()); 280 281 // Reading header 282 file.rawRead((&iqm.m_header)[0..1]); 283 284 if(iqm.m_header.fileVersion != IQM_VERSION || iqm.m_header.fileSize > (16<<20)) 285 { 286 throw new Exception(format("Model \"%s\" is invalid.", fileName)); 287 } 288 289 ubyte[] buffer = new ubyte[iqm.m_header.fileSize]; 290 291 // Reading model data 292 file.rawRead(buffer[IqmHeader.sizeof..iqm.m_header.fileSize - IqmHeader.sizeof]); 293 294 if(iqm.m_header.num_meshes > 0 && !loadMeshes(iqm, buffer)) 295 { 296 throw new Exception(format("Model \"%s\" : meshes loading failed.", fileName)); 297 } 298 299 if(iqm.m_header.num_animations > 0 && !loadAnimations(iqm, buffer)) 300 { 301 throw new Exception(format("Model \"%s\" : animations loading failed.", fileName)); 302 } 303 304 return iqm; 305 306 } 307 308 /** 309 * Loads iqm meshes 310 * Params: 311 * buffer : model data 312 */ 313 static bool loadMeshes(Iqm iqm, in ubyte[] buffer) 314 { 315 iqm.m_outframe = new mat4[iqm.m_header.num_joints]; 316 317 char* name = cast(char*)&buffer[iqm.m_header.ofs_text + 1]; 318 319 IqmVertexArray* vas = cast(IqmVertexArray*)&buffer[iqm.m_header.ofs_vertexarrays]; 320 321 for(size_t i = 0; i < iqm.m_header.num_vertexarrays; i++) 322 { 323 IqmVertexArray *va = &vas[i]; 324 325 switch(va.type) 326 { 327 case IqmAttribute.Position: 328 if(va.format != IqmType.Float || va.size != 3) 329 return false; 330 331 iqm.m_inPositions = cast(float *)&buffer[va.offset]; 332 break; 333 334 case IqmAttribute.Normal: 335 if(va.format != IqmType.Float || va.size != 3) 336 return false; 337 338 iqm.m_inNormals = cast(float*)&buffer[va.offset]; 339 break; 340 341 case IqmAttribute.TexCoord: 342 if(va.format != IqmType.Float || va.size != 2) 343 return false; 344 345 iqm.m_inTexCoords = cast(float*)&buffer[va.offset]; 346 break; 347 348 case IqmAttribute.BlendIndexes: 349 if(va.size != 4) 350 return false; 351 352 iqm.m_inBlendIndex = cast(ubyte*)&buffer[va.offset]; 353 break; 354 355 case IqmAttribute.BlendWeights: 356 if(va.size != 4) 357 return false; 358 359 iqm.m_inBlendWeight = cast(ubyte*)&buffer[va.offset]; 360 break; 361 362 case IqmAttribute.Color: 363 364 iqm.m_inColors = cast(ubyte*)&buffer[va.offset]; 365 break; 366 367 default: 368 break; 369 } 370 } 371 372 iqm.m_triangles = cast(IqmTriangle*)&buffer[iqm.m_header.ofs_triangles]; 373 iqm.m_meshes = cast(IqmMesh*)&buffer[iqm.m_header.ofs_meshes]; 374 iqm.m_rawJoints = cast(IqmJoint*)&buffer[iqm.m_header.ofs_joints]; 375 376 iqm.m_baseframe = new mat4[iqm.m_header.num_joints]; 377 iqm.m_inversebaseframe = new mat4[iqm.m_header.num_joints]; 378 379 for(size_t i = 0; i < iqm.m_header.num_joints; i++) 380 { 381 IqmJoint* j = &iqm.m_rawJoints[i]; 382 383 Quaternionf quat = Quaternionf(j.rotate); 384 quat.normalize(); 385 386 mat4 joinMatrix = quat.toMatrix4x4(); 387 joinMatrix *= scaleMatrix(vec3(j.scale)); 388 joinMatrix.arrayof[3] = j.translate[0]; 389 joinMatrix.arrayof[7] = j.translate[1]; 390 joinMatrix.arrayof[11] = j.translate[2]; 391 joinMatrix.arrayof[15] = 1; 392 393 iqm.m_baseframe[i] = joinMatrix; 394 iqm.m_inversebaseframe[i] = iqm.m_baseframe[i].inverse(); 395 396 if(j.parent >= 0) 397 { 398 iqm.m_baseframe[i] = iqm.m_baseframe[j.parent] * iqm.m_baseframe[i]; 399 iqm.m_inversebaseframe[i] = iqm.m_inversebaseframe[i] * iqm.m_inversebaseframe[j.parent]; 400 } 401 402 // We add the joint in the list for attaching weapons to models 403 char* joinName = &name[j.name - 1]; 404 iqm.m_joints[cast(string)joinName[0..strlen(joinName)]] = i; 405 } 406 407 import evael.graphics.Texture; 408 409 for(size_t i = 0; i < iqm.m_header.num_meshes; i++) 410 { 411 IqmMesh* mesh = &iqm.m_meshes[i]; 412 char* textureName = &name[mesh.material - 1]; 413 414 if(*textureName != '\0') 415 { 416 debug 417 { 418 char* meshName = &name[mesh.name - 1]; 419 /*writefln("Loaded mesh: %s", meshName[0..strlen(meshName)]); 420 writefln("Loaded texture: %s", textureName[0..strlen(textureName)]);*/ 421 } 422 423 immutable string str = textureName[0..strlen(textureName)].idup; 424 425 if(str.length <= 8 || (str.length > 8 && str[0..8] != "Material")) 426 { 427 iqm.m_textures ~= AssetLoader.getInstance().load!(Texture)(str).id; 428 } 429 } 430 } 431 432 generateBuffers(iqm); 433 434 return true; 435 } 436 437 /** 438 * Loads iqm animations 439 * Params: 440 * buffer : model data 441 */ 442 static bool loadAnimations(Iqm iqm, in ubyte[] buffer) 443 { 444 if(iqm.m_header.num_poses != iqm.m_header.num_joints) 445 return false; 446 447 char* str = cast(char *)&buffer[iqm.m_header.ofs_text + 1]; 448 iqm.m_rawAnimations = cast(IqmAnim*)&buffer[iqm.m_header.ofs_anims]; 449 iqm.m_rawPoses = cast(IqmPose*)&buffer[iqm.m_header.ofs_poses]; 450 iqm.m_bounds = cast(BoundingBox*)&buffer[iqm.m_header.ofs_bounds]; 451 IqmBounds* tB = cast(IqmBounds*)&buffer[iqm.m_header.ofs_bounds]; 452 453 iqm.m_frames = new mat4[iqm.m_header.num_frames * iqm.m_header.num_poses]; 454 ushort* framedata = cast(ushort*)&buffer[iqm.m_header.ofs_frames]; 455 456 // debug writeln("Frames : ", iqm.m_header.num_frames); 457 458 for(size_t i = 0; i < iqm.m_header.num_frames; i++) 459 { 460 BoundingBox* bb = &iqm.m_bounds[i]; 461 iqm.m_boundingBoxes ~= *bb; 462 463 for(size_t j = 0; j < iqm.m_header.num_poses; j++) 464 { 465 IqmPose *p = &iqm.m_rawPoses[j]; 466 Quaternionf rotate; 467 468 vec3 translate, scale; 469 translate.x = p.channeloffset[0]; if(p.mask&0x01) translate.x = translate.x + *framedata++ * p.channelscale[0]; 470 translate.y = p.channeloffset[1]; if(p.mask&0x02) translate.y = translate.y + *framedata++ * p.channelscale[1]; 471 translate.z = p.channeloffset[2]; if(p.mask&0x04) translate.z = translate.z + *framedata++ * p.channelscale[2]; 472 rotate.x = p.channeloffset[3]; if(p.mask&0x08) rotate.x = rotate.x + (*framedata++ * p.channelscale[3]); 473 rotate.y = p.channeloffset[4]; if(p.mask&0x10) rotate.y = rotate.y + (*framedata++ * p.channelscale[4]); 474 rotate.z = p.channeloffset[5]; if(p.mask&0x20) rotate.z = rotate.z + (*framedata++ * p.channelscale[5]); 475 rotate.w = p.channeloffset[6]; if(p.mask&0x40) rotate.w = rotate.w + (*framedata++ * p.channelscale[6]); 476 scale.x = p.channeloffset[7]; if(p.mask&0x80) scale.x = scale.x + *framedata++ * p.channelscale[7]; 477 scale.y = p.channeloffset[8]; if(p.mask&0x100) scale.y = scale.y + *framedata++ * p.channelscale[8]; 478 scale.z = p.channeloffset[9]; if(p.mask&0x200) scale.z = scale.z + *framedata++ * p.channelscale[9]; 479 480 rotate.normalize(); 481 482 mat4 m = rotate.toMatrix4x4(); 483 m *= scaleMatrix(scale); 484 m.arrayof[4] = translate.x; 485 m.arrayof[7] = translate.y; 486 m.arrayof[11] = translate.z; 487 m.arrayof[15] = 1; 488 489 if(p.parent >= 0) 490 { 491 iqm.m_frames[i * iqm.m_header.num_poses + j] = iqm.m_baseframe[p.parent] * m * iqm.m_inversebaseframe[j]; 492 } 493 else 494 { 495 iqm.m_frames[i * iqm.m_header.num_poses + j] = m * iqm.m_inversebaseframe[j]; 496 } 497 498 } 499 } 500 501 /*foreach(b; 0..iqm.m_header.num_frames) 502 { 503 IqmBounds* bb = &tB[b]; 504 writeln("\t\t", *bb); 505 }*/ 506 507 for(size_t i = 0; i < iqm.m_header.num_animations; i++) 508 { 509 IqmAnim* animation = &iqm.m_rawAnimations[i]; 510 511 char* animName = &str[animation.name - 1]; 512 immutable string animationName = to!string(animName[0..strlen(animName)]); 513 514 iqm.m_animations[animationName] = Animation(animation.framesNumber, 515 animation.firstFrame, 516 animation.firstFrame + animation.framesNumber - 1); 517 debug 518 { 519 /*writefln("Loaded anim: %s", animationName); 520 writefln("\t\tFirst frame: %d\n\t\tLast frame: %d", animation.firstFrame, animation.firstFrame + animation.framesNumber - 1);*/ 521 } 522 } 523 524 iqm.playFrame(0); 525 526 return true; 527 } 528 529 /** 530 * Generates vbo and ibo 531 * Params: 532 * iqm : data to send 533 */ 534 static void generateBuffers(Iqm iqm) 535 { 536 IqmVertex[] vertices = new IqmVertex[iqm.m_header.num_vertexes]; 537 538 static immutable ubyte[4] color = 255; 539 540 for(int i = 0; i < cast(int)iqm.m_header.num_vertexes; i++) 541 { 542 IqmVertex *v = &vertices[i]; 543 544 memcpy(cast(float*)v.position.arrayof.ptr, &iqm.m_inPositions[i*3], v.position.sizeof); 545 memcpy(cast(float*)v.normal.arrayof.ptr, &iqm.m_inNormals[i*3], v.normal.sizeof); 546 memcpy(cast(float*)v.textureCoordinate.arrayof.ptr, &iqm.m_inTexCoords[i*2], v.textureCoordinate.sizeof); 547 548 if(iqm.m_inColors != null) 549 { 550 memcpy(cast(ubyte*)v.color.ptr, &iqm.m_inColors[i*4], v.color.sizeof); 551 } 552 else 553 { 554 memcpy(cast(ubyte*)v.color.ptr, &color, v.color.sizeof); 555 } 556 557 // Animated model 558 if(iqm.m_header.num_animations > 0) 559 { 560 memcpy(cast(ubyte*)v.blendIndex.arrayof.ptr, &iqm.m_inBlendIndex[i*4], v.blendIndex.sizeof); 561 memcpy(cast(ubyte*)v.blendWeight.arrayof.ptr, &iqm.m_inBlendWeight[i*4], v.blendWeight.sizeof); 562 } 563 } 564 565 auto graphicsDevice = GraphicsDevice.getInstance(); 566 567 iqm.m_vao = graphicsDevice.generateVAO(); 568 iqm.m_vertexBuffer = graphicsDevice.createVertexBuffer(IqmVertex.sizeof * iqm.m_header.num_vertexes, vertices.ptr); 569 iqm.m_indexBuffer = graphicsDevice.createIndexBuffer(IqmTriangle.sizeof * iqm.m_header.num_triangles, iqm.m_triangles); 570 571 graphicsDevice.setVertexBuffer!(IqmVertex)(iqm.m_vertexBuffer); 572 573 graphicsDevice.bindVAO(0); 574 } 575 576 @property 577 public ref Animation getAnimation(in string name) nothrow 578 { 579 // TODO : this func is called all the fucking day ? 580 /*try 581 { 582 debug writeln("Asking for animation ", name); 583 } 584 catch(Exception e) 585 { 586 587 }*/ 588 589 return this.m_animations[name]; 590 } 591 592 /** 593 * Properties 594 */ 595 @property 596 { 597 public IqmHeader header() const nothrow @nogc 598 { 599 return this.m_header; 600 } 601 602 public uint joints(in string name) const nothrow 603 { 604 return this.m_joints[name]; 605 } 606 607 public ref mat4[] outFrames() nothrow @nogc 608 { 609 return this.m_outframe; 610 } 611 612 public bool hasAnimation() const nothrow @nogc 613 { 614 return this.m_header.num_animations > 0; 615 } 616 617 public BoundingBox* boundingBoxes() nothrow @nogc 618 { 619 return this.m_bounds; 620 } 621 622 public bool hasTextures() const nothrow @nogc 623 { 624 return this.m_textures.length > 0; 625 } 626 } 627 }