1 // Open Asset Import Library (assimp) 2 // 3 // Copyright (c) 2006-2016, assimp team, 2018, The openBVE Project 4 // All rights reserved. 5 // 6 // Redistribution and use of this software in source and binary forms, 7 // with or without modification, are permitted provided that the 8 // following conditions are met: 9 // 10 // * Redistributions of source code must retain the above 11 // copyright notice, this list of conditions and the 12 // following disclaimer. 13 // 14 // * Redistributions in binary form must reproduce the above 15 // copyright notice, this list of conditions and the 16 // following disclaimer in the documentation and/or other 17 // materials provided with the distribution. 18 // 19 // * Neither the name of the assimp team, nor the names of its 20 // contributors may be used to endorse or promote products 21 // derived from this software without specific prior 22 // written permission of the assimp team. 23 // 24 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 // 36 // 37 // 38 // ****************************************************************************** 39 // 40 // AN EXCEPTION applies to all files in the ./test/models-nonbsd folder. 41 // These are 3d models for testing purposes, from various free sources 42 // on the internet. They are - unless otherwise stated - copyright of 43 // their respective creators, which may impose additional requirements 44 // on the use of their work. For any of these models, see 45 // <model-name>.source.txt for more legal information. Contact us if you 46 // are a copyright holder and believe that we credited you inproperly or 47 // if you don't want your files to appear in the repository. 48 // 49 // 50 // ****************************************************************************** 51 // 52 // Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors 53 // http://code.google.com/p/poly2tri/ 54 // 55 // All rights reserved. 56 // Redistribution and use in source and binary forms, with or without modification, 57 // are permitted provided that the following conditions are met: 58 // 59 // * Redistributions of source code must retain the above copyright notice, 60 // this list of conditions and the following disclaimer. 61 // * Redistributions in binary form must reproduce the above copyright notice, 62 // this list of conditions and the following disclaimer in the documentation 63 // and/or other materials provided with the distribution. 64 // * Neither the name of Poly2Tri nor the names of its contributors may be 65 // used to endorse or promote products derived from this software without specific 66 // prior written permission. 67 // 68 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 69 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 70 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 71 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 72 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 73 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 74 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 75 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 76 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 77 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 78 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 79 80 using System; 81 using System.Collections.Generic; 82 using System.Linq; 83 using System.Text; 84 using System.IO; 85 using System.Diagnostics; 86 using System.Globalization; 87 using OpenBveApi; 88 using OpenBveApi.Colors; 89 using OpenBveApi.Math; 90 using ZlibWithDictionary; 91 using VectorKey = System.Collections.Generic.KeyValuePair<double, OpenBveApi.Math.Vector3>; 92 using QuatKey = System.Collections.Generic.KeyValuePair<double, OpenBveApi.Math.Quaternion>; 93 using MatrixKey = System.Collections.Generic.KeyValuePair<double, OpenBveApi.Math.Matrix4D>; 94 using Vector2 = OpenBveApi.Math.Vector2; 95 using Vector3 = OpenBveApi.Math.Vector3; 96 97 98 namespace AssimpNET.X 99 { 100 public class XFileParser 101 { 102 // Magic identifier for MSZIP compressed data 103 private const uint MSZIP_MAGIC = 0x4B43; 104 private const uint MSZIP_BLOCK = 32786; 105 106 private readonly byte[] Buffer; 107 108 protected uint MajorVersion, MinorVersion; // version numbers 109 protected readonly bool IsBinaryFormat; // true if the file is in binary, false if it's in text form 110 protected readonly uint BinaryFloatSize; // float size in bytes, either 4 or 8 111 // counter for number arrays in binary format 112 protected uint BinaryNumCount; 113 114 // text encoding of text format 115 private readonly Encoding textEncoding; 116 117 protected int currentPosition; 118 protected int End; 119 120 /// Line number when reading in text format 121 protected uint LineNumber; 122 123 /// Imported data 124 protected Scene Scene; 125 126 // Constructor. Creates a data structure out of the XFile given in the memory block. XFileParser(byte[] buffer)127 public XFileParser(byte[] buffer) 128 { 129 Buffer = buffer; 130 MajorVersion = MinorVersion = 0; 131 IsBinaryFormat = false; 132 BinaryNumCount = 0; 133 currentPosition = End = -1; 134 LineNumber = 0; 135 Scene = null; 136 137 // set up memory pointers 138 currentPosition = 0; 139 //End = P + Buffer.Length - 1; 140 End = currentPosition + Buffer.Length; 141 142 // check header 143 if (Buffer.Length < 16) 144 { 145 throw new InvalidDataException("Header mismatch, file is not an XFile."); 146 } 147 148 ASCIIEncoding Ascii = new ASCIIEncoding(); 149 string header = new string(Ascii.GetChars(Buffer, 0, 12)); 150 151 if (header.Substring(0, 4) != "xof ") 152 { 153 throw new InvalidDataException("Header mismatch, file is not an XFile."); 154 } 155 156 // read version. It comes in a four byte format such as "0302" 157 MajorVersion = (uint)(Buffer[4] - 48) * 10 + (uint)(Buffer[5] - 48); 158 MinorVersion = (uint)(Buffer[6] - 48) * 10 + (uint)(Buffer[7] - 48); 159 160 bool compressed = false; 161 162 // txt - pure ASCII text format 163 if (header.Substring(8, 4) == "txt ") 164 { 165 IsBinaryFormat = false; 166 } 167 // bin - Binary format 168 else if (header.Substring(8, 4) == "bin ") 169 { 170 IsBinaryFormat = true; 171 } 172 // tzip - Inflate compressed text format 173 else if (header.Substring(8, 4) == "tzip") 174 { 175 IsBinaryFormat = false; 176 compressed = true; 177 } 178 // bzip - Inflate compressed binary format 179 else if (header.Substring(8, 4) == "bzip") 180 { 181 IsBinaryFormat = true; 182 compressed = true; 183 } 184 else 185 { 186 throw new FormatException("Unsupported xfile format '" + header.Substring(8, 4) + "'"); 187 } 188 189 // float size 190 BinaryFloatSize = (uint)(Buffer[12] - 48) * 1000 + (uint)(Buffer[13] - 48) * 100 + (uint)(Buffer[14] - 48) * 10 + (uint)(Buffer[15] - 48); 191 192 if (BinaryFloatSize != 32 && BinaryFloatSize != 64) 193 { 194 throw new FormatException("Unknown float size " + BinaryFloatSize + " specified in xfile header."); 195 } 196 197 // The x format specifies size in bits, but we work in bytes 198 BinaryFloatSize /= 8; 199 200 currentPosition += 16; 201 202 // If this is a compressed X file, apply the inflate algorithm to it 203 if (compressed) 204 { 205 /* /////////////////////////////////////////////////////////////////////// 206 * COMPRESSED X FILE FORMAT 207 * /////////////////////////////////////////////////////////////////////// 208 * [xhead] 209 * 2 major 210 * 2 minor 211 * 4 type // bzip,tzip 212 * [mszip_master_head] 213 * 4 unkn // checksum? 214 * 2 unkn // flags? (seems to be constant) 215 * [mszip_head] 216 * 2 ofs // offset to next section 217 * 2 magic // 'CK' 218 * ... ofs bytes of data 219 * ... next mszip_head 220 * 221 * http://www.kdedevelopers.org/node/3181 has been very helpful. 222 * /////////////////////////////////////////////////////////////////////// 223 */ 224 225 // Read file size after decompression excluding header 226 uint uncompressedFinalSize = BitConverter.ToUInt32(Buffer, currentPosition) - 16; 227 currentPosition += 4; 228 229 // Preparing for decompression 230 MemoryStream inputStream = new MemoryStream(Buffer); 231 MemoryStream outputStream = new MemoryStream(); 232 int currentBlock = 0; 233 byte[] previousBlockBytes = new byte[(int)MSZIP_BLOCK]; 234 byte[] blockBytes; 235 while (currentPosition + 3 < End) 236 { 237 #pragma warning disable CS0219 238 // Read compressed block size after decompression 239 ushort uncompressedBlockSize = BitConverter.ToUInt16(Buffer, currentPosition); 240 currentPosition += 2; 241 #pragma warning restore CS0219 242 243 // Read compressed block size 244 ushort compressedBlockSize = BitConverter.ToUInt16(Buffer, currentPosition); 245 currentPosition += 2; 246 247 // Check compressed block size 248 if (compressedBlockSize > MSZIP_BLOCK) 249 { 250 inputStream.Dispose(); 251 outputStream.Dispose(); 252 throw new FormatException("Compressed block size is larger than MSZIP standard."); 253 } 254 255 if (currentPosition + compressedBlockSize > End + 2) 256 { 257 inputStream.Dispose(); 258 outputStream.Dispose(); 259 throw new InvalidDataException("X: Unexpected EOF in compressed chunk"); 260 } 261 262 // Check MSZIP signature of compressed block 263 ushort signature = BitConverter.ToUInt16(Buffer, currentPosition); 264 265 if (signature != MSZIP_MAGIC) 266 { 267 inputStream.Dispose(); 268 outputStream.Dispose(); 269 throw new InvalidDataException("The compressed block's signature is incorrect."); 270 } 271 272 // Skip MSZIP signature 273 inputStream.Position = currentPosition + 2; 274 275 // Decompress the compressed block 276 blockBytes = new byte[compressedBlockSize]; 277 inputStream.Read(blockBytes, 0, compressedBlockSize); 278 byte[] decompressedBytes = DeflateCompression.ZlibDecompressWithDictionary(blockBytes, currentBlock == 0 ? null : previousBlockBytes); 279 280 outputStream.Write(decompressedBytes, 0, decompressedBytes.Length); 281 previousBlockBytes = decompressedBytes; 282 283 // Preparing to move to the next data block 284 currentPosition += compressedBlockSize; 285 currentBlock++; 286 } 287 288 // ok, update pointers to point to the uncompressed file data 289 Buffer = outputStream.ToArray(); 290 outputStream.Dispose(); 291 inputStream.Dispose(); 292 currentPosition = 0; 293 End = Buffer.Length; 294 295 if (Buffer.Length != uncompressedFinalSize) 296 { 297 throw new InvalidDataException("The size after decompression is incorrect."); 298 } 299 } 300 else 301 { 302 // start reading here 303 ReadUntilEndOfLine(); 304 } 305 306 if (!IsBinaryFormat) 307 { 308 textEncoding = TextEncoding.GetSystemEncodingFromBytes(Buffer); 309 } 310 311 Scene = new Scene(); 312 ParseFile(); 313 314 // filter the imported hierarchy for some degenerated cases 315 if (Scene.RootNode != null) 316 { 317 FilterHierarchy(Scene.RootNode); 318 } 319 } 320 321 /** Returns the temporary representation of the imported data */ GetImportedData()322 public Scene GetImportedData() 323 { 324 return Scene; 325 } 326 ParseFile()327 protected void ParseFile() 328 { 329 while (true) 330 { 331 // read name of next object 332 string objectName = GetNextToken(); 333 if (objectName.Length == 0) 334 { 335 break; 336 } 337 338 switch (objectName.ToLowerInvariant()) 339 { 340 // parse specific object 341 case "template": 342 ParseDataObjectTemplate(); 343 break; 344 case "frame": 345 ParseDataObjectFrame(null); 346 break; 347 case "mesh": 348 { 349 // some meshes have no frames at all 350 Mesh mesh; 351 ParseDataObjectMesh(out mesh); 352 Scene.GlobalMeshes.Add(mesh); 353 break; 354 } 355 case "animtickspersecond": 356 ParseDataObjectAnimTicksPerSecond(); 357 break; 358 case "animationset": 359 ParseDataObjectAnimationSet(); 360 break; 361 case "material": 362 { 363 // Material outside of a mesh or node 364 Material material; 365 ParseDataObjectMaterial(out material); 366 Scene.GlobalMaterials.Add(material); 367 break; 368 } 369 case "}": 370 // whatever? 371 Debug.WriteLine("} found in dataObject"); 372 break; 373 default: 374 // unknown format 375 Debug.WriteLine("Unknown data object in animation of .x file"); 376 ParseUnknownDataObject(); 377 break; 378 } 379 } 380 } 381 ParseDataObjectTemplate()382 protected void ParseDataObjectTemplate() 383 { 384 // parse a template data object. Currently not stored. 385 // ReSharper disable once NotAccessedVariable 386 string name; 387 ReadHeadOfDataObject(out name); 388 389 // read GUID 390 string guid = GetNextToken(); 391 392 // read and ignore data members 393 while (true) 394 { 395 string s = GetNextToken(); 396 397 if (s == "}") 398 { 399 break; 400 } 401 402 if (s.Length == 0) 403 { 404 ThrowException("Unexpected end of file reached while parsing template definition"); 405 } 406 } 407 } 408 ParseDataObjectFrame(Node parent)409 protected void ParseDataObjectFrame(Node parent) 410 { 411 // A coordinate frame, or "frame of reference." The Frame template 412 // is open and can contain any object. The Direct3D extensions (D3DX) 413 // mesh-loading functions recognize Mesh, FrameTransformMatrix, and 414 // Frame template instances as child objects when loading a Frame 415 // instance. 416 string name; 417 ReadHeadOfDataObject(out name); 418 419 // create a named node and place it at its parent, if given 420 Node node = new Node(name, parent); 421 422 if (parent != null) 423 { 424 parent.Children.Add(node); 425 } 426 else 427 { 428 // there might be multiple root nodes 429 if (Scene.RootNode != null) 430 { 431 // place a dummy root if not there 432 if (Scene.RootNode.Name != "$dummy_root") 433 { 434 Node exroot = Scene.RootNode; 435 Scene.RootNode = new Node("$dummy_root"); 436 Scene.RootNode.Children.Add(exroot); 437 exroot.Parent = Scene.RootNode; 438 } 439 // put the new node as its child instead 440 Scene.RootNode.Children.Add(node); 441 node.Parent = Scene.RootNode; 442 } 443 else 444 { 445 // it's the first node imported. place it as root 446 Scene.RootNode = node; 447 } 448 } 449 450 // Now inside a frame. 451 // read tokens until closing brace is reached. 452 bool frameFinished = false; 453 while (true) 454 { 455 string objectName = GetNextToken(); 456 if (objectName.Length == 0) 457 { 458 ThrowException("Unexpected end of file reached while parsing frame"); 459 } 460 461 switch (objectName.ToLowerInvariant()) 462 { 463 case "}": 464 frameFinished = true; 465 break; 466 case "frame": 467 ParseDataObjectFrame(node); // child frame 468 break; 469 case "frametransformmatrix": 470 ParseDataObjectTransformationMatrix(out node.TrafoMatrix); 471 break; 472 case "mesh": 473 { 474 Mesh mesh; 475 ParseDataObjectMesh(out mesh); 476 node.Meshes.Add(mesh); 477 break; 478 } 479 default: 480 Debug.WriteLine("Unknown data object in frame in x file"); 481 ParseUnknownDataObject(); 482 break; 483 } 484 485 if (frameFinished) 486 { 487 break; 488 } 489 } 490 } 491 ParseDataObjectTransformationMatrix(out Matrix4D matrix)492 protected void ParseDataObjectTransformationMatrix(out Matrix4D matrix) 493 { 494 // read header, we're not interested if it has a name 495 ReadHeadOfDataObject(); 496 497 // read its components 498 matrix = new Matrix4D(); 499 matrix.Row0.X = ReadFloat(); 500 matrix.Row0.Y = ReadFloat(); 501 matrix.Row0.Z = ReadFloat(); 502 matrix.Row0.W = ReadFloat(); 503 matrix.Row1.X = ReadFloat(); 504 matrix.Row1.Y = ReadFloat(); 505 matrix.Row1.Z = ReadFloat(); 506 matrix.Row1.W = ReadFloat(); 507 matrix.Row2.X = ReadFloat(); 508 matrix.Row2.Y = ReadFloat(); 509 matrix.Row2.Z = ReadFloat(); 510 matrix.Row2.W = ReadFloat(); 511 matrix.Row3.X = ReadFloat(); 512 matrix.Row3.Y = ReadFloat(); 513 matrix.Row3.Z = ReadFloat(); 514 matrix.Row3.W = ReadFloat(); 515 // trailing symbols 516 CheckForSemicolon(); 517 CheckForClosingBrace(); 518 } 519 ParseDataObjectMesh(out Mesh mesh)520 protected void ParseDataObjectMesh(out Mesh mesh) 521 { 522 mesh = new Mesh(); 523 524 // ReSharper disable once NotAccessedVariable 525 string name; 526 ReadHeadOfDataObject(out name); 527 528 // read vertex count 529 uint numVertices = ReadInt(); 530 mesh.Positions = new List<Vector3>((int)numVertices); 531 532 // read vertices 533 for (int a = 0; a < (int)numVertices; a++) 534 { 535 mesh.Positions.Add(ReadVector3()); 536 } 537 538 if ((int) numVertices == 0) 539 { 540 TestForSeparator(); 541 } 542 543 // read position faces 544 uint numPosFaces = ReadInt(); 545 mesh.PosFaces = new List<Face>((int)numPosFaces); 546 for (int a = 0; a < (int)numPosFaces; a++) 547 { 548 // read indices 549 uint numIndices = ReadInt(); 550 Face face = new Face(); 551 for (int b = 0; b < (int)numIndices; b++) 552 { 553 face.Indices.Add(ReadInt()); 554 } 555 mesh.PosFaces.Add(face); 556 TestForSeparator(); 557 } 558 559 if ((int) numPosFaces == 0) 560 { 561 TestForSeparator(); 562 } 563 564 // here, other data objects may follow 565 bool meshFinished = false; 566 while (true) 567 { 568 string objectName = GetNextToken(); 569 switch (objectName.ToLowerInvariant()) 570 { 571 case "": 572 ThrowException("Unexpected end of file while parsing mesh structure"); 573 break; 574 case "}": 575 meshFinished = true; 576 break; 577 case "meshnormals": 578 ParseDataObjectMeshNormals(ref mesh); 579 break; 580 case "meshtexturecoords": 581 ParseDataObjectMeshTextureCoords(ref mesh); 582 break; 583 case "meshvertexcolors": 584 ParseDataObjectMeshVertexColors(ref mesh); 585 break; 586 case "meshmateriallist": 587 ParseDataObjectMeshMaterialList(ref mesh); 588 break; 589 case "vertexduplicationindices": 590 ParseUnknownDataObject(); // we'll ignore vertex duplication indices 591 break; 592 case "xskinmeshheader": 593 ParseDataObjectSkinMeshHeader(ref mesh); 594 break; 595 case "skinweights": 596 ParseDataObjectSkinWeights(ref mesh); 597 break; 598 default: 599 Debug.WriteLine("Unknown data object in mesh in x file"); 600 ParseUnknownDataObject(); 601 break; 602 } 603 if (meshFinished) 604 { 605 break; 606 } 607 } 608 } 609 ParseDataObjectSkinWeights(ref Mesh mesh)610 protected void ParseDataObjectSkinWeights(ref Mesh mesh) 611 { 612 ReadHeadOfDataObject(); 613 614 string transformNodeName; 615 GetNextTokenAsString(out transformNodeName); 616 617 Bone bone = new Bone(); 618 bone.Name = transformNodeName; 619 620 // read vertex weights 621 uint numWeights = ReadInt(); 622 bone.Weights = new List<BoneWeight>((int)numWeights); 623 624 for (int a = 0; a < (int)numWeights; a++) 625 { 626 BoneWeight weight = new BoneWeight(); 627 weight.Vertex = ReadInt(); 628 bone.Weights.Add(weight); 629 } 630 631 // read vertex weights 632 for (int a = 0; a < (int)numWeights; a++) 633 { 634 bone.Weights[a].Weight = ReadFloat(); 635 } 636 637 // read matrix offset 638 bone.OffsetMatrix.Row0.X = ReadFloat(); 639 bone.OffsetMatrix.Row1.X = ReadFloat(); 640 bone.OffsetMatrix.Row2.X = ReadFloat(); 641 bone.OffsetMatrix.Row3.X = ReadFloat(); 642 bone.OffsetMatrix.Row0.Y = ReadFloat(); 643 bone.OffsetMatrix.Row1.Y = ReadFloat(); 644 bone.OffsetMatrix.Row2.Y = ReadFloat(); 645 bone.OffsetMatrix.Row3.Y = ReadFloat(); 646 bone.OffsetMatrix.Row0.Z = ReadFloat(); 647 bone.OffsetMatrix.Row1.Z = ReadFloat(); 648 bone.OffsetMatrix.Row2.Z = ReadFloat(); 649 bone.OffsetMatrix.Row3.Z = ReadFloat(); 650 bone.OffsetMatrix.Row0.W = ReadFloat(); 651 bone.OffsetMatrix.Row1.W = ReadFloat(); 652 bone.OffsetMatrix.Row2.W = ReadFloat(); 653 bone.OffsetMatrix.Row3.W = ReadFloat(); 654 655 mesh.Bones.Add(bone); 656 657 CheckForSemicolon(); 658 CheckForClosingBrace(); 659 } 660 ParseDataObjectSkinMeshHeader(ref Mesh mesh)661 protected void ParseDataObjectSkinMeshHeader(ref Mesh mesh) 662 { 663 ReadHeadOfDataObject(); 664 665 /*unsigned int maxSkinWeightsPerVertex =*/ 666 ReadInt(); 667 /*unsigned int maxSkinWeightsPerFace =*/ 668 ReadInt(); 669 /*unsigned int numBonesInMesh = */ 670 ReadInt(); 671 672 CheckForClosingBrace(); 673 } 674 ParseDataObjectMeshNormals(ref Mesh mesh)675 protected void ParseDataObjectMeshNormals(ref Mesh mesh) 676 { 677 ReadHeadOfDataObject(); 678 679 // read count 680 uint numNormals = ReadInt(); 681 mesh.Normals = new List<Vector3>((int)numNormals); 682 683 // read normal vectors 684 for (int a = 0; a < (int)numNormals; a++) 685 { 686 mesh.Normals.Add(ReadVector3()); 687 } 688 689 if ((int) numNormals == 0) 690 { 691 TestForSeparator(); 692 } 693 694 // read normal indices 695 uint numFaces = ReadInt(); 696 if (numFaces != (uint)mesh.PosFaces.Count) 697 { 698 ThrowException("Normal face count does not match vertex face count."); 699 } 700 701 for (int a = 0; a < (int)numFaces; a++) 702 { 703 uint numIndices = ReadInt(); 704 Face face = new Face(); 705 706 for (int b = 0; b < (int)numIndices; b++) 707 { 708 face.Indices.Add(ReadInt()); 709 } 710 mesh.NormFaces.Add(face); 711 712 TestForSeparator(); 713 } 714 715 if ((int) numFaces == 0) 716 { 717 TestForSeparator(); 718 } 719 CheckForClosingBrace(); 720 } 721 ParseDataObjectMeshTextureCoords(ref Mesh mesh)722 protected void ParseDataObjectMeshTextureCoords(ref Mesh mesh) 723 { 724 ReadHeadOfDataObject(); 725 726 if (mesh.NumTextures + 1 > Mesh.AI_MAX_NUMBER_OF_TEXTURECOORDS) 727 { 728 ThrowException("Too many sets of texture coordinates"); 729 } 730 731 uint numCoords = ReadInt(); 732 if (numCoords != mesh.Positions.Count) 733 { 734 ThrowException("Texture coord count does not match vertex count"); 735 } 736 737 List<Vector2> coords = new List<Vector2>((int)numCoords); 738 for (int a = 0; a < (int)numCoords; a++) 739 { 740 coords.Add(ReadVector2()); 741 } 742 mesh.TexCoords[(int)mesh.NumTextures++] = coords; 743 744 if ((int) numCoords == 0) 745 { 746 TestForSeparator(); 747 } 748 CheckForClosingBrace(); 749 } 750 ParseDataObjectMeshVertexColors(ref Mesh mesh)751 protected void ParseDataObjectMeshVertexColors(ref Mesh mesh) 752 { 753 ReadHeadOfDataObject(); 754 if (mesh.NumColorSets + 1 > Mesh.AI_MAX_NUMBER_OF_COLOR_SETS) 755 { 756 ThrowException("Too many colorsets"); 757 } 758 759 uint numColors = ReadInt(); 760 if (numColors != mesh.Positions.Count) 761 { 762 ThrowException("Vertex color count does not match vertex count"); 763 } 764 765 List<Color128> colors = Enumerable.Repeat(Color128.Black, (int)numColors).ToList(); 766 for (int a = 0; a < (int)numColors; a++) 767 { 768 uint index = ReadInt(); 769 if (index >= mesh.Positions.Count) 770 { 771 ThrowException("Vertex color index out of bounds"); 772 } 773 colors[(int)index] = ReadRGBA(); 774 // HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma. 775 // Ignore gracefully. 776 if (!IsBinaryFormat) 777 { 778 FindNextNoneWhiteSpace(); 779 if (Buffer[currentPosition] == ';' || Buffer[currentPosition] == ',') 780 { 781 currentPosition++; 782 } 783 } 784 } 785 mesh.Colors[(int)mesh.NumColorSets++] = colors; 786 787 if ((int) numColors == 0) 788 { 789 TestForSeparator(); 790 } 791 CheckForClosingBrace(); 792 } 793 ParseDataObjectMeshMaterialList(ref Mesh mesh)794 protected void ParseDataObjectMeshMaterialList(ref Mesh mesh) 795 { 796 ReadHeadOfDataObject(); 797 798 // read material count 799 /*unsigned int numMaterials =*/ 800 ReadInt(); 801 // read non triangulated face material index count 802 uint numMatIndices = ReadInt(); 803 804 // some models have a material index count of 1... to be able to read them we 805 // replicate this single material index on every face 806 if (numMatIndices != mesh.PosFaces.Count && numMatIndices != 1) 807 { 808 ThrowException("Per-Face material index count does not match face count."); 809 } 810 811 // read per-face material indices 812 for (int a = 0; a < (int)numMatIndices; a++) 813 { 814 mesh.FaceMaterials.Add(ReadInt()); 815 } 816 817 // in version 03.02, the face indices end with two semicolons. 818 // commented out version check, as version 03.03 exported from blender also has 2 semicolons 819 if (!IsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2) 820 { 821 if (currentPosition < End && Buffer[currentPosition] == ';') 822 { 823 ++currentPosition; 824 } 825 } 826 827 // if there was only a single material index, replicate it on all faces 828 while (mesh.FaceMaterials.Count < mesh.PosFaces.Count) 829 { 830 mesh.FaceMaterials.Add(mesh.FaceMaterials.First()); 831 } 832 833 // read following data objects 834 while (true) 835 { 836 string objectName = GetNextToken(); 837 if (objectName.Length == 0) 838 { 839 ThrowException("Unexpected end of file while parsing mesh material list."); 840 } 841 else if (objectName == "}") 842 { 843 break; // material list finished 844 } 845 else if (objectName == "{") 846 { 847 // template materials 848 string matName = GetNextToken(); 849 Material material = new Material(); 850 material.IsReference = true; 851 //Use default BVE white color so this is visible if the global material is missing, otherwise will be overwritten 852 material.Diffuse = Color128.White; 853 material.Name = matName; 854 mesh.Materials.Add(material); 855 856 CheckForClosingBrace(); // skip } 857 } 858 else if (objectName == "Material") 859 { 860 Material material; 861 ParseDataObjectMaterial(out material); 862 mesh.Materials.Add(material); 863 } 864 else if (objectName == ";") 865 { 866 // ignore 867 } 868 else 869 { 870 Debug.WriteLine("Unknown data object in material list in x file"); 871 ParseUnknownDataObject(); 872 } 873 } 874 } 875 ParseDataObjectMaterial(out Material material)876 protected void ParseDataObjectMaterial(out Material material) 877 { 878 material = new Material(); 879 880 string matName; 881 ReadHeadOfDataObject(out matName); 882 if (matName.Length == 0) 883 { 884 matName = "material" + LineNumber; 885 } 886 material.Name = matName; 887 material.IsReference = false; 888 889 // read material values 890 material.Diffuse = ReadRGBA(); 891 material.SpecularExponent = ReadFloat(); 892 material.Specular = ReadRGB(); 893 material.Emissive = ReadRGB(); 894 895 // read other data objects 896 while (true) 897 { 898 string objectName = GetNextToken(); 899 if (objectName.Length == 0) 900 { 901 ThrowException("Unexpected end of file while parsing mesh material"); 902 } 903 else if (objectName == "}") 904 { 905 break; // material finished 906 } 907 else if (objectName == "TextureFilename" || objectName == "TextureFileName") 908 { 909 // some exporters write "TextureFileName" instead. 910 string texname; 911 ParseDataObjectTextureFilename(out texname); 912 material.Textures.Add(new TexEntry(texname)); 913 } 914 else if (objectName == "NormalmapFilename" || objectName == "NormalmapFileName") 915 { 916 // one exporter writes out the normal map in a separate filename tag 917 string texname; 918 ParseDataObjectTextureFilename(out texname); 919 material.Textures.Add(new TexEntry(texname, true)); 920 } 921 else 922 { 923 Debug.WriteLine("Unknown data object in material in x file"); 924 ParseUnknownDataObject(); 925 } 926 } 927 } 928 ParseDataObjectAnimTicksPerSecond()929 protected void ParseDataObjectAnimTicksPerSecond() 930 { 931 ReadHeadOfDataObject(); 932 Scene.AnimTicksPerSecond = ReadInt(); 933 CheckForClosingBrace(); 934 } 935 ParseDataObjectAnimationSet()936 protected void ParseDataObjectAnimationSet() 937 { 938 string animName; 939 ReadHeadOfDataObject(out animName); 940 941 Animation anim = new Animation(); 942 anim.Name = animName; 943 944 while (true) 945 { 946 string objectName = GetNextToken(); 947 if (objectName.Length == 0) 948 { 949 ThrowException("Unexpected end of file while parsing animation set."); 950 } 951 else if (objectName == "}") 952 { 953 break; // animation set finished 954 } 955 else if (objectName == "Animation") 956 { 957 ParseDataObjectAnimation(ref anim); 958 } 959 else 960 { 961 Debug.WriteLine("Unknown data object in animation set in x file"); 962 ParseUnknownDataObject(); 963 } 964 } 965 966 Scene.Anims.Add(anim); 967 } 968 ParseDataObjectAnimation(ref Animation anim)969 protected void ParseDataObjectAnimation(ref Animation anim) 970 { 971 ReadHeadOfDataObject(); 972 AnimBone banim = new AnimBone(); 973 974 while (true) 975 { 976 string objectName = GetNextToken(); 977 978 if (objectName.Length == 0) 979 { 980 ThrowException("Unexpected end of file while parsing animation."); 981 } 982 else if (objectName == "}") 983 { 984 break; // animation finished 985 } 986 else if (objectName == "AnimationKey") 987 { 988 ParseDataObjectAnimationKey(ref banim); 989 } 990 else if (objectName == "AnimationOptions") 991 { 992 ParseUnknownDataObject(); // not interested 993 } 994 else if (objectName == "{") 995 { 996 // read frame name 997 banim.BoneName = GetNextToken(); 998 CheckForClosingBrace(); 999 } 1000 else 1001 { 1002 Debug.WriteLine("Unknown data object in animation in x file"); 1003 ParseUnknownDataObject(); 1004 } 1005 } 1006 1007 anim.Anims.Add(banim); 1008 } 1009 ParseDataObjectAnimationKey(ref AnimBone animBone)1010 protected void ParseDataObjectAnimationKey(ref AnimBone animBone) 1011 { 1012 ReadHeadOfDataObject(); 1013 1014 // read key type 1015 uint keyType = ReadInt(); 1016 1017 // read number of keys 1018 uint numKeys = ReadInt(); 1019 1020 for (int a = 0; a < (int)numKeys; a++) 1021 { 1022 // read time 1023 uint time = ReadInt(); 1024 1025 // read keys 1026 switch (keyType) 1027 { 1028 case 0: // rotation quaternion 1029 { 1030 // read count 1031 if (ReadInt() != 4) 1032 { 1033 ThrowException("Invalid number of arguments for quaternion key in animation"); 1034 } 1035 Quaternion quat = new Quaternion(); 1036 quat.W = ReadFloat(); 1037 quat.X = ReadFloat(); 1038 quat.Y = ReadFloat(); 1039 quat.Z = ReadFloat(); 1040 1041 QuatKey key = new QuatKey((double)time, quat); 1042 animBone.RotKeys.Add(key); 1043 1044 CheckForSemicolon(); 1045 } 1046 break; 1047 case 1: // scale vector 1048 case 2: // position vector 1049 { 1050 // read count 1051 if (ReadInt() != 3) 1052 { 1053 ThrowException("Invalid number of arguments for vector key in animation"); 1054 } 1055 VectorKey key = new VectorKey((double)time, ReadVector3()); 1056 1057 if (keyType == 2) 1058 { 1059 animBone.PosKeys.Add(key); 1060 } 1061 else 1062 { 1063 animBone.ScaleKeys.Add(key); 1064 } 1065 } 1066 break; 1067 case 3: // combined transformation matrix 1068 case 4: // denoted both as 3 or as 4 1069 { 1070 // read count 1071 if (ReadInt() != 16) 1072 { 1073 ThrowException("Invalid number of arguments for matrix key in animation"); 1074 } 1075 1076 // read matrix 1077 Matrix4D matrix = new Matrix4D(); 1078 matrix.Row0.X = ReadFloat(); 1079 matrix.Row0.Y = ReadFloat(); 1080 matrix.Row0.Z = ReadFloat(); 1081 matrix.Row0.W = ReadFloat(); 1082 matrix.Row1.X = ReadFloat(); 1083 matrix.Row1.Y = ReadFloat(); 1084 matrix.Row1.Z = ReadFloat(); 1085 matrix.Row1.W = ReadFloat(); 1086 matrix.Row2.X = ReadFloat(); 1087 matrix.Row2.Y = ReadFloat(); 1088 matrix.Row2.Z = ReadFloat(); 1089 matrix.Row2.W = ReadFloat(); 1090 matrix.Row3.X = ReadFloat(); 1091 matrix.Row3.Y = ReadFloat(); 1092 matrix.Row3.Z = ReadFloat(); 1093 matrix.Row3.W = ReadFloat(); 1094 1095 MatrixKey key = new MatrixKey((double)time, matrix); 1096 animBone.TrafoKeys.Add(key); 1097 1098 CheckForSemicolon(); 1099 } 1100 break; 1101 default: 1102 ThrowException("Unknown key type " + keyType + " in animation."); 1103 break; 1104 } // end switch 1105 1106 // key separator 1107 CheckForSeparator(); 1108 } 1109 1110 CheckForClosingBrace(); 1111 } 1112 ParseDataObjectTextureFilename(out string name)1113 protected void ParseDataObjectTextureFilename(out string name) 1114 { 1115 ReadHeadOfDataObject(); 1116 GetNextTokenAsString(out name); 1117 CheckForClosingBrace(); 1118 1119 // FIX: some files (e.g. AnimationTest.x) have "" as texture file name 1120 if (name.Length == 0) 1121 { 1122 Debug.WriteLine("Length of texture file name is zero. Skipping this texture."); 1123 } 1124 1125 // some exporters write double backslash paths out. We simply replace them if we find them 1126 name = name.Replace("\\\\", "\\"); 1127 } 1128 ParseUnknownDataObject()1129 protected void ParseUnknownDataObject() 1130 { 1131 // find opening delimiter 1132 while (true) 1133 { 1134 string t = GetNextToken(); 1135 if (t.Length == 0) 1136 { 1137 ThrowException("Unexpected end of file while parsing unknown segment."); 1138 } 1139 1140 if (t == "{") 1141 { 1142 break; 1143 } 1144 } 1145 1146 uint counter = 1; 1147 1148 // parse until closing delimiter 1149 while (counter > 0) 1150 { 1151 string t = GetNextToken(); 1152 if (t.Length == 0) 1153 { 1154 ThrowException("Unexpected end of file while parsing unknown segment."); 1155 } 1156 1157 if (t == "{") 1158 { 1159 ++counter; 1160 } 1161 else if (t == "}") 1162 { 1163 --counter; 1164 } 1165 } 1166 } 1167 1168 //! places pointer to next begin of a token, and ignores comments FindNextNoneWhiteSpace()1169 protected void FindNextNoneWhiteSpace() 1170 { 1171 if (IsBinaryFormat) 1172 { 1173 return; 1174 } 1175 1176 while (true) 1177 { 1178 while (currentPosition < End && char.IsWhiteSpace((char)Buffer[currentPosition])) 1179 { 1180 if (Buffer[currentPosition] == '\n') 1181 { 1182 LineNumber++; 1183 } 1184 ++currentPosition; 1185 } 1186 1187 if (currentPosition >= End) 1188 { 1189 return; 1190 } 1191 1192 // check if this is a comment 1193 if ((Buffer[currentPosition] == '/' && Buffer[currentPosition + 1] == '/') || Buffer[currentPosition] == '#') 1194 { 1195 ReadUntilEndOfLine(); 1196 } 1197 else 1198 { 1199 break; 1200 } 1201 } 1202 } 1203 1204 //! returns next parseable token. Returns empty string if no token there GetNextToken()1205 protected string GetNextToken() 1206 { 1207 string s = string.Empty; 1208 1209 // process binary-formatted file 1210 if (IsBinaryFormat) 1211 { 1212 // in binary mode it will only return NAME and STRING token 1213 // and (correctly) skip over other tokens. 1214 if (End - currentPosition < 2) 1215 { 1216 return s; 1217 } 1218 uint tok = ReadBinWord(); 1219 uint len; 1220 ASCIIEncoding Ascii = new ASCIIEncoding(); 1221 1222 // standalone tokens 1223 switch (tok) 1224 { 1225 case 1: 1226 // name token 1227 if (End - currentPosition < 4) 1228 { 1229 return s; 1230 } 1231 len = ReadBinDWord(); 1232 if (End - currentPosition < (int)len) 1233 { 1234 return s; 1235 } 1236 s = Ascii.GetString(Buffer, currentPosition, (int)len); 1237 currentPosition += (int)len; 1238 return s; 1239 case 2: 1240 // string token 1241 if (End - currentPosition < 4) 1242 { 1243 return s; 1244 } 1245 len = ReadBinDWord(); 1246 if (End - currentPosition < (int)len) 1247 { 1248 return s; 1249 } 1250 s = Ascii.GetString(Buffer, currentPosition, (int)len); 1251 currentPosition += (int)(len + 2); 1252 return s; 1253 case 3: 1254 // integer token 1255 currentPosition += 4; 1256 return "<integer>"; 1257 case 5: 1258 // GUID token 1259 currentPosition += 16; 1260 return "<guid>"; 1261 case 6: 1262 if (End - currentPosition < 4) return s; 1263 len = ReadBinDWord(); 1264 currentPosition += (int)(len * 4); 1265 return "<int_list>"; 1266 case 7: 1267 if (End - currentPosition < 4) return s; 1268 len = ReadBinDWord(); 1269 currentPosition += (int)(len * BinaryFloatSize); 1270 return "<flt_list>"; 1271 case 0x0a: 1272 return "{"; 1273 case 0x0b: 1274 return "}"; 1275 case 0x0c: 1276 return "("; 1277 case 0x0d: 1278 return ")"; 1279 case 0x0e: 1280 return "["; 1281 case 0x0f: 1282 return "]"; 1283 case 0x10: 1284 return "<"; 1285 case 0x11: 1286 return ">"; 1287 case 0x12: 1288 return "."; 1289 case 0x13: 1290 return ","; 1291 case 0x14: 1292 return ";"; 1293 case 0x1f: 1294 return "template"; 1295 case 0x28: 1296 return "WORD"; 1297 case 0x29: 1298 return "DWORD"; 1299 case 0x2a: 1300 return "FLOAT"; 1301 case 0x2b: 1302 return "DOUBLE"; 1303 case 0x2c: 1304 return "CHAR"; 1305 case 0x2d: 1306 return "UCHAR"; 1307 case 0x2e: 1308 return "SWORD"; 1309 case 0x2f: 1310 return "SDWORD"; 1311 case 0x30: 1312 return "void"; 1313 case 0x31: 1314 return "string"; 1315 case 0x32: 1316 return "unicode"; 1317 case 0x33: 1318 return "cstring"; 1319 case 0x34: 1320 return "array"; 1321 } 1322 } 1323 // process text-formatted file 1324 else 1325 { 1326 FindNextNoneWhiteSpace(); 1327 if (currentPosition > End) 1328 { 1329 return s; 1330 } 1331 1332 while (currentPosition < End && !char.IsWhiteSpace((char)Buffer[currentPosition])) 1333 { 1334 // either keep token delimiters when already holding a token, or return if first valid char 1335 if (Buffer[currentPosition] == ';' || Buffer[currentPosition] == '}' || Buffer[currentPosition] == '{' || Buffer[currentPosition] == ',') 1336 { 1337 if (s.Length == 0) 1338 { 1339 s += (char)Buffer[currentPosition++]; 1340 } 1341 break; // stop for delimiter 1342 } 1343 s += (char)Buffer[currentPosition++]; 1344 } 1345 } 1346 return s; 1347 } 1348 ReadHeadOfDataObject()1349 protected void ReadHeadOfDataObject() 1350 { 1351 string nameOrBrace = GetNextToken(); 1352 if (nameOrBrace != "{") 1353 { 1354 if (GetNextToken() != "{") 1355 { 1356 ThrowException("Opening brace expected."); 1357 } 1358 } 1359 } 1360 ReadHeadOfDataObject(out string name)1361 protected void ReadHeadOfDataObject(out string name) 1362 { 1363 name = string.Empty; 1364 1365 string nameOrBrace = GetNextToken(); 1366 if (nameOrBrace != "{") 1367 { 1368 name = nameOrBrace; 1369 1370 if (GetNextToken() != "{") 1371 { 1372 ThrowException("Opening brace expected."); 1373 } 1374 } 1375 } 1376 1377 //! checks for closing curly brace CheckForClosingBrace()1378 protected void CheckForClosingBrace() 1379 { 1380 if (GetNextToken() != "}") 1381 { 1382 ThrowException("Closing brace expected."); 1383 } 1384 } 1385 1386 //! checks for one following semicolon CheckForSemicolon()1387 protected void CheckForSemicolon() 1388 { 1389 if (IsBinaryFormat) 1390 { 1391 return; 1392 } 1393 1394 if (GetNextToken() != ";") 1395 { 1396 ThrowException("Semicolon expected."); 1397 } 1398 } 1399 1400 //! checks for a separator char, either a ',' or a ';' CheckForSeparator()1401 protected void CheckForSeparator() 1402 { 1403 if (IsBinaryFormat) 1404 { 1405 return; 1406 } 1407 1408 string token = GetNextToken(); 1409 if (token != "," && token != ";") 1410 { 1411 ThrowException("Separator character (';' or ',') expected."); 1412 } 1413 } 1414 1415 // tests and possibly consumes a separator char, but does nothing if there was no separator TestForSeparator()1416 protected void TestForSeparator() 1417 { 1418 if (IsBinaryFormat) 1419 { 1420 return; 1421 } 1422 1423 FindNextNoneWhiteSpace(); 1424 if (currentPosition > End) 1425 { 1426 return; 1427 } 1428 1429 // test and skip 1430 if (Buffer[currentPosition] == ';' || Buffer[currentPosition] == ',') 1431 { 1432 currentPosition++; 1433 } 1434 } 1435 GetNextTokenAsString(out string token)1436 protected void GetNextTokenAsString(out string token) 1437 { 1438 if (IsBinaryFormat) 1439 { 1440 token = GetNextToken(); 1441 return; 1442 } 1443 1444 FindNextNoneWhiteSpace(); 1445 if (currentPosition >= End) 1446 { 1447 ThrowException("Unexpected end of file while parsing string"); 1448 } 1449 1450 if (Buffer[currentPosition] != '"') 1451 { 1452 ThrowException("Expected quotation mark."); 1453 } 1454 ++currentPosition; 1455 1456 List<byte> textBuffer = new List<byte>(); 1457 while (currentPosition < End && Buffer[currentPosition] != '"') 1458 { 1459 textBuffer.Add(Buffer[currentPosition++]); 1460 } 1461 token = textEncoding.GetString(textBuffer.ToArray()); 1462 1463 if (currentPosition >= End - 1) 1464 { 1465 ThrowException("Unexpected end of file while parsing string"); 1466 } 1467 1468 if (Buffer[currentPosition + 1] != ';' || Buffer[currentPosition] != '"') 1469 { 1470 ThrowException("Expected quotation mark and semicolon at the end of a string."); 1471 } 1472 currentPosition += 2; 1473 } 1474 ReadUntilEndOfLine()1475 protected void ReadUntilEndOfLine() 1476 { 1477 if (IsBinaryFormat) 1478 { 1479 return; 1480 } 1481 1482 while (currentPosition < End) 1483 { 1484 if (Buffer[currentPosition] == '\n' || Buffer[currentPosition] == '\r') 1485 { 1486 ++currentPosition; 1487 LineNumber++; 1488 return; 1489 } 1490 1491 ++currentPosition; 1492 } 1493 } 1494 ReadBinWord()1495 protected ushort ReadBinWord() 1496 { 1497 Debug.Assert(End - currentPosition >= 2); 1498 ushort tmp = BitConverter.ToUInt16(Buffer, currentPosition); 1499 currentPosition += 2; 1500 return tmp; 1501 } 1502 ReadBinDWord()1503 protected uint ReadBinDWord() 1504 { 1505 Debug.Assert(End - currentPosition >= 4); 1506 uint tmp = BitConverter.ToUInt32(Buffer, currentPosition); 1507 currentPosition += 4; 1508 return tmp; 1509 } 1510 ReadInt()1511 protected uint ReadInt() 1512 { 1513 if (IsBinaryFormat) 1514 { 1515 if (BinaryNumCount == 0 && End - currentPosition >= 2) 1516 { 1517 ushort tmp = ReadBinWord(); // 0x06 or 0x03 1518 // array of ints follows 1519 if (tmp == 0x06 && End - currentPosition >= 4) 1520 { 1521 BinaryNumCount = ReadBinDWord(); 1522 } 1523 // single int follows 1524 else 1525 { 1526 BinaryNumCount = 1; 1527 } 1528 } 1529 1530 --BinaryNumCount; 1531 if (End - currentPosition >= 4) 1532 { 1533 return ReadBinDWord(); 1534 } 1535 else 1536 { 1537 currentPosition = End; 1538 return 0; 1539 } 1540 } 1541 else 1542 { 1543 FindNextNoneWhiteSpace(); 1544 1545 // TODO: consider using strtol10 instead??? 1546 1547 // check preceding minus sign 1548 bool isNegative = false; 1549 if (Buffer[currentPosition] == '-') 1550 { 1551 isNegative = true; 1552 currentPosition++; 1553 } 1554 1555 // at least one digit expected 1556 if (!char.IsDigit((char)Buffer[currentPosition])) 1557 { 1558 ThrowException("Number expected."); 1559 } 1560 1561 // read digits 1562 uint number = 0; 1563 while (currentPosition < End) 1564 { 1565 if (!char.IsDigit((char)Buffer[currentPosition])) 1566 { 1567 break; 1568 } 1569 number = number * 10 + (uint)(Buffer[currentPosition] - 48); 1570 currentPosition++; 1571 } 1572 1573 CheckForSeparator(); 1574 return isNegative ? (uint)(-(int)number) : number; 1575 } 1576 } 1577 ReadFloat()1578 protected float ReadFloat() 1579 { 1580 float result = 0.0f; 1581 1582 if (IsBinaryFormat) 1583 { 1584 if (BinaryNumCount == 0 && End - currentPosition >= 2) 1585 { 1586 ushort tmp = ReadBinWord(); // 0x07 or 0x42 1587 // array of floats following 1588 if (tmp == 0x07 && End - currentPosition >= 4) 1589 { 1590 BinaryNumCount = ReadBinDWord(); 1591 } 1592 // single float following 1593 else 1594 { 1595 BinaryNumCount = 1; 1596 } 1597 } 1598 1599 --BinaryNumCount; 1600 if (BinaryFloatSize == 8) 1601 { 1602 if (End - currentPosition >= 8) 1603 { 1604 result = (float)BitConverter.ToDouble(Buffer, currentPosition); 1605 currentPosition += 8; 1606 return result; 1607 } 1608 else 1609 { 1610 currentPosition = End; 1611 return 0.0f; 1612 } 1613 } 1614 else 1615 { 1616 if (End - currentPosition >= 4) 1617 { 1618 result = BitConverter.ToSingle(Buffer, currentPosition); 1619 currentPosition += 4; 1620 return result; 1621 } 1622 else 1623 { 1624 currentPosition = End; 1625 return 0.0f; 1626 } 1627 } 1628 } 1629 1630 // text version 1631 FindNextNoneWhiteSpace(); 1632 // check for various special strings to allow reading files from faulty exporters 1633 // I mean you, Blender! 1634 // Reading is safe because of the terminating zero 1635 ASCIIEncoding Ascii = new ASCIIEncoding(); 1636 if (Ascii.GetString(Buffer, currentPosition, 9) == "-1.#IND00" || Ascii.GetString(Buffer, currentPosition, 8) == "1.#IND00") 1637 { 1638 currentPosition += 9; 1639 CheckForSeparator(); 1640 return 0.0f; 1641 } 1642 else if (Ascii.GetString(Buffer, currentPosition, 8) == "1.#QNAN0") 1643 { 1644 currentPosition += 8; 1645 CheckForSeparator(); 1646 return 0.0f; 1647 } 1648 1649 currentPosition = ConvertToFloat(currentPosition, out result); 1650 1651 CheckForSeparator(); 1652 1653 return result; 1654 } 1655 ReadVector2()1656 protected Vector2 ReadVector2() 1657 { 1658 Vector2 vector; 1659 vector.X = ReadFloat(); 1660 vector.Y = ReadFloat(); 1661 TestForSeparator(); 1662 1663 return vector; 1664 } 1665 ReadVector3()1666 protected Vector3 ReadVector3() 1667 { 1668 Vector3 vector; 1669 vector.X = ReadFloat(); 1670 vector.Y = ReadFloat(); 1671 vector.Z = ReadFloat(); 1672 TestForSeparator(); 1673 1674 return vector; 1675 } 1676 ReadRGBA()1677 protected Color128 ReadRGBA() 1678 { 1679 Color128 color; 1680 color.R = ReadFloat(); 1681 color.G = ReadFloat(); 1682 color.B = ReadFloat(); 1683 color.A = ReadFloat(); 1684 TestForSeparator(); 1685 1686 return color; 1687 } 1688 ReadRGB()1689 protected Color128 ReadRGB() 1690 { 1691 Color128 color; 1692 color.R = ReadFloat(); 1693 color.G = ReadFloat(); 1694 color.B = ReadFloat(); 1695 color.A = 1.0f; 1696 TestForSeparator(); 1697 1698 return color; 1699 } 1700 ThrowException(string text)1701 protected void ThrowException(string text) 1702 { 1703 if (IsBinaryFormat) 1704 { 1705 throw new Exception(text); 1706 } 1707 else 1708 { 1709 throw new Exception("Line " + LineNumber + ": " + text); 1710 } 1711 } 1712 1713 // Filters the imported hierarchy for some degenerated cases that some exporters produce. FilterHierarchy(Node node)1714 protected void FilterHierarchy(Node node) 1715 { 1716 // if the node has just a single unnamed child containing a mesh, remove 1717 // the anonymous node between. The 3DSMax kwXport plugin seems to produce this 1718 // mess in some cases 1719 if (node.Children.Count == 1 && node.Meshes.Count == 0) 1720 { 1721 Node child = node.Children.First(); 1722 if (child.Name.Length == 0 && child.Meshes.Count > 0) 1723 { 1724 // transfer its meshes to us 1725 for (int a = 0; a < child.Meshes.Count; a++) 1726 { 1727 node.Meshes.Add(child.Meshes[a]); 1728 } 1729 child.Meshes.Clear(); 1730 1731 // transfer the transform as well 1732 node.TrafoMatrix = node.TrafoMatrix * child.TrafoMatrix; 1733 1734 // then kill it 1735 node.Children.Clear(); 1736 } 1737 } 1738 1739 // recurse 1740 for (int a = 0; a < node.Children.Count; a++) 1741 { 1742 FilterHierarchy(node.Children[a]); 1743 } 1744 } 1745 ConvertToFloat(int position, out float result, bool check_comma = true)1746 protected int ConvertToFloat(int position, out float result, bool check_comma = true) 1747 { 1748 ASCIIEncoding Ascii = new ASCIIEncoding(); 1749 1750 float f = 0.0f; 1751 1752 bool inv = (Buffer[position] == '-'); 1753 if (inv || Buffer[position] == '+') 1754 { 1755 ++position; 1756 } 1757 1758 if (string.Compare(Ascii.GetString(Buffer, position, 3), "nan", StringComparison.OrdinalIgnoreCase) == 0) 1759 { 1760 result = float.NaN; 1761 position += 3; 1762 return position; 1763 } 1764 1765 if (string.Compare(Ascii.GetString(Buffer, position, 3), "inf", StringComparison.OrdinalIgnoreCase) == 0) 1766 { 1767 result = float.PositiveInfinity; 1768 if (inv) 1769 { 1770 result = -result; 1771 } 1772 position += 3; 1773 if (string.Compare(Ascii.GetString(Buffer, position, 5), "inity", StringComparison.OrdinalIgnoreCase) == 0) 1774 { 1775 position += 5; 1776 } 1777 return position; 1778 } 1779 1780 if (!char.IsDigit((char)Buffer[position]) && !((Buffer[position] == '.' || (check_comma && Buffer[position] == ',')) && char.IsDigit((char)Buffer[position + 1]))) 1781 { 1782 ThrowException("Cannot parse string as real number: does not start with digit or decimal point followed by digit."); 1783 } 1784 1785 string tmp = string.Empty; 1786 1787 while (position < End) 1788 { 1789 if (char.IsDigit((char)Buffer[position]) || Buffer[position] == '.' || Buffer[position] == 'e' || Buffer[position] == 'E' || Buffer[position] == '+' || Buffer[position] == '-') 1790 { 1791 tmp += (char)Buffer[position]; 1792 ++position; 1793 } 1794 else 1795 { 1796 break; 1797 } 1798 } 1799 1800 try 1801 { 1802 f = float.Parse(tmp, NumberStyles.Number, CultureInfo.InvariantCulture); 1803 } 1804 catch (Exception e) 1805 { 1806 ThrowException(e.Message); 1807 } 1808 1809 if (inv) 1810 { 1811 f = -f; 1812 } 1813 1814 result = f; 1815 return position; 1816 } 1817 } 1818 } 1819