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