1 /* 2 Copyright (c) 2017-2018 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 28 module dagon.resource.iqm; 29 30 import std.stdio; 31 import std.math; 32 import std..string; 33 import std.path; 34 35 import dlib.core.memory; 36 import dlib.core.stream; 37 import dlib.container.array; 38 import dlib.container.dict; 39 import dlib.filesystem.filesystem; 40 import dlib.math.vector; 41 import dlib.math.matrix; 42 import dlib.math.quaternion; 43 import dlib.math.interpolation; 44 45 import dagon.core.ownership; 46 import dagon.graphics.animmodel; 47 import dagon.graphics.texture; 48 import dagon.resource.asset; 49 import dagon.resource.serialization; 50 import dagon.resource.textureasset; 51 52 enum IQM_VERSION = 2; 53 54 struct IQMHeader 55 { 56 ubyte[16] magic; 57 uint ver; 58 uint filesize; 59 uint flags; 60 uint numText, ofsText; 61 uint numMeshes, ofsMeshes; 62 uint numVertexArrays, numVertices, ofsVertexArrays; 63 uint numTriangles, ofsTriangles, ofsAdjacency; 64 uint numJoints, ofsJoints; 65 uint numPoses, ofsPoses; 66 uint numAnims, ofsAnims; 67 uint numFrames, numFrameChannels, ofsFrames, ofsBounds; 68 uint numComment, ofsComment; 69 uint numExtensions, ofsExtensions; 70 } 71 72 struct IQMVertexArray 73 { 74 uint type; 75 uint flags; 76 uint format; 77 uint size; 78 uint offset; 79 } 80 81 alias uint[3] IQMTriangle; 82 83 struct IQMJoint 84 { 85 uint name; 86 int parent; 87 Vector3f translation; 88 Quaternionf rotation; 89 Vector3f scaling; 90 } 91 92 struct IQMMesh 93 { 94 uint name; 95 uint material; 96 uint firstVertex, numVertices; 97 uint firstTriangle, numTriangles; 98 } 99 100 alias ubyte[4] IQMBlendIndex; 101 alias ubyte[4] IQMBlendWeight; 102 103 enum 104 { 105 IQM_POSITION = 0, 106 IQM_TEXCOORD = 1, 107 IQM_NORMAL = 2, 108 IQM_TANGENT = 3, 109 IQM_BLENDINDEXES = 4, 110 IQM_BLENDWEIGHTS = 5, 111 IQM_COLOR = 6, 112 IQM_CUSTOM = 0x10 113 } 114 115 struct IQMPose 116 { 117 int parent; 118 uint mask; 119 float[10] channelOffset; 120 float[10] channelScale; 121 } 122 123 struct IQMAnim 124 { 125 uint name; 126 uint firstFrame; 127 uint numFrames; 128 float framerate; 129 uint flags; 130 } 131 132 //version = IQMDebug; 133 134 class IQMModel: AnimatedModel 135 { 136 DynamicArray!Vector3f vertices; 137 DynamicArray!Vector3f normals; 138 DynamicArray!Vector2f texcoords; 139 // TODO: tangents 140 141 DynamicArray!IQMBlendIndex blendIndices; 142 DynamicArray!IQMBlendWeight blendWeights; 143 144 IQMTriangle[] tris; 145 IQMVertexArray[] vas; 146 IQMMesh[] meshes; 147 AnimationFacegroup[] facegroups; 148 149 DynamicArray!IQMJoint joints; 150 151 Matrix4x4f[] baseFrame; 152 Matrix4x4f[] invBaseFrame; 153 154 Matrix4x4f[] frames; 155 156 uint numFrames; 157 158 ubyte[] textBuffer; 159 160 Dict!(IQMAnim, string) animations; 161 162 this(InputStream istrm, ReadOnlyFileSystem rofs, AssetManager mngr) 163 { 164 load(istrm, rofs, mngr); 165 } 166 167 ~this() 168 { 169 release(); 170 } 171 172 void release() 173 { 174 vertices.free(); 175 normals.free(); 176 texcoords.free(); 177 178 blendIndices.free(); 179 blendWeights.free(); 180 181 if (tris.length) Delete(tris); 182 if (vas.length) Delete(vas); 183 if (meshes.length) Delete(meshes); 184 185 joints.free(); 186 187 if (baseFrame.length) Delete(baseFrame); 188 if (invBaseFrame.length) Delete(invBaseFrame); 189 if (frames.length) Delete(frames); 190 191 if (textBuffer.length) Delete(textBuffer); 192 193 if (animations) Delete(animations); 194 195 if (facegroups) Delete(facegroups); 196 } 197 198 void load(InputStream istrm, ReadOnlyFileSystem rofs, AssetManager mngr) 199 { 200 // Header part 201 IQMHeader hdr = istrm.read!(IQMHeader, true); 202 203 version(IQMDebug) 204 { 205 writefln("hdr.magic: %s", cast(string)hdr.magic); 206 writefln("hdr.ver: %s", hdr.ver); 207 } 208 assert(cast(string)hdr.magic == "INTERQUAKEMODEL\0"); 209 assert(hdr.ver == IQM_VERSION); 210 211 version(IQMDebug) 212 { 213 writefln("hdr.numText: %s", hdr.numText); 214 writefln("hdr.ofsText: %s", hdr.ofsText); 215 } 216 217 istrm.setPosition(hdr.ofsText); 218 textBuffer = New!(ubyte[])(hdr.numText); 219 istrm.fillArray(textBuffer); 220 version(IQMDebug) 221 writefln("text:\n%s", cast(string)textBuffer); 222 223 // FIXME: 224 //Delete(buf); 225 226 // Vertex data part 227 version(IQMDebug) 228 { 229 writefln("hdr.numVertexArrays: %s", hdr.numVertexArrays); 230 writefln("hdr.ofsVertexArrays: %s", hdr.ofsVertexArrays); 231 } 232 233 vas = New!(IQMVertexArray[])(hdr.numVertexArrays); 234 istrm.setPosition(hdr.ofsVertexArrays); 235 foreach(i; 0..hdr.numVertexArrays) 236 { 237 vas[i] = istrm.read!(IQMVertexArray, true); 238 } 239 240 // FIXME: 241 //Delete(vas); 242 243 foreach(i, va; vas) 244 { 245 version(IQMDebug) 246 { 247 writefln("Vertex array %s:", i); 248 writefln("va.type: %s", va.type); 249 writefln("va.flags: %s", va.flags); 250 writefln("va.format: %s", va.format); 251 writefln("va.size: %s", va.size); 252 writefln("va.offset: %s", va.offset); 253 writeln("---------------"); 254 } 255 256 if (va.type == IQM_POSITION) 257 { 258 assert(va.size == 3); 259 // TODO: format asserion 260 auto verts = New!(Vector3f[])(hdr.numVertices); 261 istrm.setPosition(va.offset); 262 istrm.fillArray(verts); 263 vertices.append(verts); 264 Delete(verts); 265 } 266 else if (va.type == IQM_NORMAL) 267 { 268 assert(va.size == 3); 269 // TODO: format asserion 270 auto norms = New!(Vector3f[])(hdr.numVertices); 271 istrm.setPosition(va.offset); 272 istrm.fillArray(norms); 273 normals.append(norms); 274 Delete(norms); 275 } 276 else if (va.type == IQM_TEXCOORD) 277 { 278 assert(va.size == 2); 279 // TODO: format asserion 280 auto texs = New!(Vector2f[])(hdr.numVertices); 281 istrm.setPosition(va.offset); 282 istrm.fillArray(texs); 283 texcoords.append(texs); 284 Delete(texs); 285 } 286 /* TODO: IQM_TANGENT */ 287 else if (va.type == IQM_BLENDINDEXES) 288 { 289 assert(va.size == 4); 290 // TODO: format asserion 291 auto bi = New!(IQMBlendIndex[])(hdr.numVertices); 292 istrm.setPosition(va.offset); 293 istrm.fillArray(bi); 294 blendIndices.append(bi); 295 Delete(bi); 296 } 297 else if (va.type == IQM_BLENDWEIGHTS) 298 { 299 assert(va.size == 4); 300 // TODO: format asserion 301 auto bw = New!(IQMBlendWeight[])(hdr.numVertices); 302 istrm.setPosition(va.offset); 303 istrm.fillArray(bw); 304 blendWeights.append(bw); 305 Delete(bw); 306 } 307 } 308 309 version(IQMDebug) 310 { 311 writefln("numVertices: %s", vertices.length); 312 writefln("numNormals: %s", normals.length); 313 writefln("numTexcoords: %s", texcoords.length); 314 315 writefln("hdr.numTriangles: %s", hdr.numTriangles); 316 writefln("hdr.ofsTriangles: %s", hdr.ofsTriangles); 317 } 318 319 tris = New!(IQMTriangle[])(hdr.numTriangles); 320 istrm.setPosition(hdr.ofsTriangles); 321 foreach(i; 0..hdr.numTriangles) 322 { 323 tris[i] = istrm.read!IQMTriangle; 324 uint tmp = tris[i][0]; 325 tris[i][0] = tris[i][2]; 326 tris[i][2] = tmp; 327 } 328 329 version(IQMDebug) 330 writefln("hdr.ofsAdjacency: %s", hdr.ofsAdjacency); 331 332 // Skeleton part 333 version(IQMDebug) 334 { 335 writefln("hdr.numJoints: %s", hdr.numJoints); 336 writefln("hdr.ofsJoints: %s", hdr.ofsJoints); 337 } 338 339 baseFrame = New!(Matrix4x4f[])(hdr.numJoints); 340 invBaseFrame = New!(Matrix4x4f[])(hdr.numJoints); 341 istrm.setPosition(hdr.ofsJoints); 342 foreach(i; 0..hdr.numJoints) 343 { 344 IQMJoint j = istrm.read!(IQMJoint, true); 345 346 j.rotation.normalize(); 347 baseFrame[i] = transformationMatrix(j.rotation, j.translation, j.scaling); 348 invBaseFrame[i] = baseFrame[i].inverse; 349 350 if (j.parent >= 0) 351 { 352 baseFrame[i] = baseFrame[j.parent] * baseFrame[i]; 353 invBaseFrame[i] = invBaseFrame[i] * invBaseFrame[j.parent]; 354 } 355 356 assert(validMatrix(baseFrame[i])); 357 assert(validMatrix(invBaseFrame[i])); 358 359 assert(baseFrame[i].isAffine); 360 assert(invBaseFrame[i].isAffine); 361 362 joints.append(j); 363 } 364 365 // Meshes part 366 version(IQMDebug) 367 { 368 writefln("hdr.numMeshes: %s", hdr.numMeshes); 369 writefln("hdr.ofsMeshes: %s", hdr.ofsMeshes); 370 } 371 meshes = New!(IQMMesh[])(hdr.numMeshes); 372 373 facegroups = New!(AnimationFacegroup[])(meshes.length); 374 375 istrm.setPosition(hdr.ofsMeshes); 376 foreach(i; 0..hdr.numMeshes) 377 { 378 meshes[i] = istrm.read!(IQMMesh, true); 379 380 // Load texture 381 uint matIndex = meshes[i].material; 382 version(IQMDebug) 383 writefln("matIndex: %s", matIndex); 384 385 if (matIndex > 0) 386 { 387 char* texFilenamePtr = cast(char*)&textBuffer[matIndex]; 388 string texFilename = cast(string)fromStringz(texFilenamePtr); 389 version(IQMDebug) 390 writefln("material: %s", texFilename); 391 392 facegroups[i].firstTriangle = meshes[i].firstTriangle; 393 facegroups[i].numTriangles = meshes[i].numTriangles; 394 facegroups[i].textureName = texFilename; 395 396 if (!mngr.assetExists(texFilename)) 397 { 398 auto texAsset = New!TextureAsset(mngr.imageFactory, mngr.hdrImageFactory, mngr); 399 mngr.addAsset(texAsset, texFilename); 400 texAsset.threadSafePartLoaded = mngr.loadAssetThreadSafePart(texAsset, texFilename); 401 facegroups[i].texture = texAsset.texture; 402 } 403 else 404 facegroups[i].texture = (cast(TextureAsset)mngr.getAsset(texFilename)).texture; 405 } 406 } 407 408 // Animation part 409 410 // Number of poses should be the same as bindpose joints 411 version(IQMDebug) 412 writefln("hdr.numPoses: %s", hdr.numPoses); 413 assert(hdr.numPoses == hdr.numJoints); 414 415 version(IQMDebug) 416 { 417 writefln("hdr.numFrames: %s", hdr.numFrames); 418 writefln("hdr.numFrameChannels: %s", hdr.numFrameChannels); 419 } 420 421 // Read poses 422 istrm.setPosition(hdr.ofsPoses); 423 IQMPose[] poses = New!(IQMPose[])(hdr.numPoses); 424 foreach(i; 0..hdr.numPoses) 425 { 426 poses[i] = istrm.read!(IQMPose, true); 427 } 428 429 // Read frames 430 numFrames = hdr.numFrames; 431 frames = New!(Matrix4x4f[])(hdr.numFrames * hdr.numPoses); 432 istrm.setPosition(hdr.ofsFrames); 433 uint fi = 0; 434 foreach(i; 0..hdr.numFrames) 435 foreach(j; 0..hdr.numPoses) 436 { 437 auto p = &poses[j]; 438 439 Vector3f trans, scale; 440 Quaternionf rot; 441 trans.x = p.channelOffset[0]; if (p.mask & 0x01) trans.x += istrm.read!(ushort, true) * p.channelScale[0]; 442 trans.y = p.channelOffset[1]; if (p.mask & 0x02) trans.y += istrm.read!(ushort, true) * p.channelScale[1]; 443 trans.z = p.channelOffset[2]; if (p.mask & 0x04) trans.z += istrm.read!(ushort, true) * p.channelScale[2]; 444 rot.x = p.channelOffset[3]; if(p.mask&0x08) rot.x += istrm.read!(ushort, true) * p.channelScale[3]; 445 rot.y = p.channelOffset[4]; if(p.mask&0x10) rot.y += istrm.read!(ushort, true) * p.channelScale[4]; 446 rot.z = p.channelOffset[5]; if(p.mask&0x20) rot.z += istrm.read!(ushort, true) * p.channelScale[5]; 447 rot.w = p.channelOffset[6]; if(p.mask&0x40) rot.w += istrm.read!(ushort, true) * p.channelScale[6]; 448 scale.x = p.channelOffset[7]; if(p.mask&0x80) scale.x += istrm.read!(ushort, true) * p.channelScale[7]; 449 scale.y = p.channelOffset[8]; if(p.mask&0x100) scale.y += istrm.read!(ushort, true) * p.channelScale[8]; 450 scale.z = p.channelOffset[9]; if(p.mask&0x200) scale.z += istrm.read!(ushort, true) * p.channelScale[9]; 451 452 rot.normalize(); 453 Matrix4x4f m = transformationMatrix(rot, trans, scale); 454 assert(validMatrix(m)); 455 456 // Concatenate each pose with the inverse base pose to avoid doing this at animation time. 457 // If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose. 458 if (p.parent >= 0) 459 frames[i * hdr.numPoses + j] = 460 baseFrame[p.parent] * m * invBaseFrame[j]; 461 else 462 frames[i * hdr.numPoses + j] = m * invBaseFrame[j]; 463 } 464 465 // Read animations 466 animations = New!(Dict!(IQMAnim, string)); 467 istrm.setPosition(hdr.ofsAnims); 468 foreach(i; 0..hdr.numAnims) 469 { 470 IQMAnim anim = istrm.read!(IQMAnim, true); 471 472 char* namePtr = cast(char*)&textBuffer[anim.name]; 473 string name = cast(string)fromStringz(namePtr); 474 version(IQMDebug) 475 { 476 writefln("anim.name: %s", name); 477 } 478 479 animations[name] = anim; 480 481 version(IQMDebug) 482 { 483 writefln("anim.firstFrame: %s", anim.firstFrame); 484 writefln("anim.numFrames: %s", anim.numFrames); 485 } 486 } 487 488 if (poses.length) Delete(poses); 489 } 490 491 Vector3f[] getVertices() 492 { 493 return vertices.data; 494 } 495 496 Vector3f[] getNormals() 497 { 498 return normals.data; 499 } 500 501 Vector2f[] getTexcoords() 502 { 503 return texcoords.data; 504 } 505 506 uint[3][] getTriangles() 507 { 508 return tris; 509 } 510 511 size_t numBones() 512 { 513 return joints.length; 514 } 515 516 AnimationFacegroup[] getFacegroups() 517 { 518 return facegroups; 519 } 520 521 uint numAnimationFrames() 522 { 523 return numFrames; 524 } 525 526 void calcBindPose(AnimationFrameData* data) 527 { 528 foreach(i, ref j; joints) 529 { 530 data.frame[i] = baseFrame[i] * invBaseFrame[i]; 531 } 532 533 foreach(i, v; vertices) 534 { 535 auto bi = blendIndices[i]; 536 auto bw = blendWeights[i]; 537 538 float w = (cast(float)bw[0])/255.0f; 539 Matrix4x4f mat = multScalarAffine(data.frame[bi[0]], w); 540 541 for (uint j = 1; j < 4 && bw[j] > 0.0; j++) 542 { 543 w = (cast(float)bw[j])/255.0f; 544 auto tmp = multScalarAffine(data.frame[bi[j]], w); 545 mat = addMatrixAffine(mat, tmp); 546 } 547 548 assert(validMatrix(mat)); 549 assert(mat.isAffine); 550 551 data.vertices[i] = vertices[i] * mat; 552 data.normals[i] = normals[i] * matrix4x4to3x3(mat); 553 data.normals[i].normalize(); 554 } 555 } 556 557 void calcFrame( 558 uint f1, 559 uint f2, 560 float t, 561 AnimationFrameData* data) 562 { 563 Matrix4x4f* mat1 = &frames[f1 * joints.length]; 564 Matrix4x4f* mat2 = &frames[f2 * joints.length]; 565 566 // Interpolate between two frames 567 foreach(i, ref j; joints) 568 { 569 Matrix4x4f mat = mat1[i] * (1.0f - t) + mat2[i] * t; 570 if (j.parent >= 0) 571 data.frame[i] = data.frame[j.parent] * mat; 572 else 573 data.frame[i] = mat; 574 } 575 576 // Update vertex data 577 foreach(i, v; vertices) 578 { 579 auto bi = blendIndices[i]; 580 auto bw = blendWeights[i]; 581 582 float w = (cast(float)bw[0])/255.0f; 583 Matrix4x4f mat = multScalarAffine(data.frame[bi[0]], w); 584 585 for (uint j = 1; j < 4 && bw[j] > 0.0; j++) 586 { 587 w = (cast(float)bw[j])/255.0f; 588 auto tmp = multScalarAffine(data.frame[bi[j]], w); 589 mat = addMatrixAffine(mat, tmp); 590 } 591 592 assert(validMatrix(mat)); 593 assert(mat.isAffine); 594 595 data.vertices[i] = vertices[i] * mat; 596 data.normals[i] = normals[i] * matrix4x4to3x3(mat); 597 data.normals[i].normalize(); 598 } 599 } 600 601 void blendFrame( 602 uint f1, 603 uint f2, 604 float t, 605 AnimationFrameData* data, 606 float blendFactor) 607 { 608 Matrix4x4f* mat1 = &frames[f1 * joints.length]; 609 Matrix4x4f* mat2 = &frames[f2 * joints.length]; 610 611 // Interpolate between two frames 612 foreach(i, ref j; joints) 613 { 614 Matrix4x4f mat = mat1[i] * (1.0f - t) + mat2[i] * t; 615 if (j.parent >= 0) 616 data.frame[i] = data.frame[j.parent] * mat; 617 else 618 data.frame[i] = mat; 619 } 620 621 // Update vertex data 622 foreach(i, v; vertices) 623 { 624 auto bi = blendIndices[i]; 625 auto bw = blendWeights[i]; 626 627 float w = (cast(float)bw[0])/255.0f; 628 Matrix4x4f mat = multScalarAffine(data.frame[bi[0]], w); 629 630 for (uint j = 1; j < 4 && bw[j] > 0.0; j++) 631 { 632 w = (cast(float)bw[j])/255.0f; 633 auto tmp = multScalarAffine(data.frame[bi[j]], w); 634 mat = addMatrixAffine(mat, tmp); 635 } 636 637 assert(validMatrix(mat)); 638 assert(mat.isAffine); 639 640 data.vertices[i] = lerp(data.vertices[i], vertices[i] * mat, blendFactor); 641 data.normals[i] = lerp(data.normals[i], normals[i] * matrix4x4to3x3(mat), blendFactor); 642 //data.normals[i].normalize(); 643 } 644 } 645 646 bool getAnimation(string name, AnimationData* data) 647 { 648 if (!name.length) 649 { 650 data.firstFrame = 0; 651 data.numFrames = numFrames; 652 return true; 653 } 654 655 if (!(name in animations)) 656 return false; 657 658 auto anim = animations[name]; 659 data.firstFrame = anim.firstFrame; 660 data.numFrames = anim.numFrames; 661 data.framerate = anim.framerate; 662 return true; 663 } 664 } 665 666 class IQMAsset: Asset 667 { 668 IQMModel model; 669 670 this(Owner o) 671 { 672 super(o); 673 } 674 675 ~this() 676 { 677 if (model) 678 Delete(model); 679 } 680 681 override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr) 682 { 683 model = New!IQMModel(istrm, fs, mngr); 684 return true; 685 } 686 687 override bool loadThreadUnsafePart() 688 { 689 return true; 690 } 691 692 override void release() 693 { 694 if (model) 695 model.release(); 696 } 697 } 698 699 bool fileExists(ReadOnlyFileSystem rofs, string filename) 700 { 701 FileStat stat; 702 return rofs.stat(filename, stat); 703 } 704 705 Matrix4x4f transformationMatrix(Quaternionf r, Vector3f t, Vector3f s) 706 { 707 Matrix4x4f res = Matrix4x4f.identity; 708 Matrix3x3f rm = r.toMatrix3x3; 709 res.a11 = rm.a11 * s.x; res.a12 = rm.a12 * s.x; res.a13 = rm.a13 * s.x; 710 res.a21 = rm.a21 * s.y; res.a22 = rm.a22 * s.y; res.a23 = rm.a23 * s.y; 711 res.a31 = rm.a31 * s.z; res.a32 = rm.a32 * s.z; res.a33 = rm.a33 * s.z; 712 res.a14 = t.x; 713 res.a24 = t.y; 714 res.a34 = t.z; 715 return res; 716 } 717 718 Matrix4x4f multScalarAffine(Matrix4x4f m, float s) 719 { 720 Matrix4x4f res = m; 721 res.a11 *= s; res.a12 *= s; res.a13 *= s; 722 res.a21 *= s; res.a22 *= s; res.a23 *= s; 723 res.a31 *= s; res.a32 *= s; res.a33 *= s; 724 res.a14 *= s; 725 res.a24 *= s; 726 res.a34 *= s; 727 return res; 728 } 729 730 Matrix4x4f addMatrixAffine(Matrix4x4f m1, Matrix4x4f m2) 731 { 732 Matrix4x4f res = m1; 733 res.a11 += m2.a11; res.a12 += m2.a12; res.a13 += m2.a13; 734 res.a21 += m2.a21; res.a22 += m2.a22; res.a23 += m2.a23; 735 res.a31 += m2.a31; res.a32 += m2.a32; res.a33 += m2.a33; 736 res.a14 += m2.a14; 737 res.a24 += m2.a24; 738 res.a34 += m2.a34; 739 return res; 740 } 741 742 bool validMatrix(T, size_t N)(Matrix!(T, N) m) 743 { 744 foreach (v; m.arrayof) 745 if (isNaN(v)) 746 return false; 747 return true; 748 } 749 750 bool validVector(T, size_t N)(Vector!(T, N) vec) 751 { 752 foreach (v; vec.arrayof) 753 if (isNaN(v)) 754 return false; 755 return true; 756 } 757 758