private static ObjectManager.StaticObject LoadBinaryX(byte[] Data, int FloatingPointSize) { Block block = new BinaryBlock(Data, FloatingPointSize); block.FloatingPointSize = FloatingPointSize; ObjectManager.StaticObject obj = new ObjectManager.StaticObject(); MeshBuilder builder = new MeshBuilder(); Material material = new Material(); while (block.Position() < block.Length()) { Block subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } builder.Apply(ref obj); obj.Mesh.CreateNormals(); return(obj); }
private static ObjectManager.StaticObject LoadTextualX(string Text) { Text = Text.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ").Replace("\t", " ").Trim(); ObjectManager.StaticObject obj = new ObjectManager.StaticObject(); MeshBuilder builder = new MeshBuilder(); Material material = new Material(); Block block = new TextualBlock(Text); while (block.Position() < block.Length() - 5) { Block subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } builder.Apply(ref obj); obj.Mesh.CreateNormals(); if (rootMatrix != Matrix4D.NoTransformation) { for (int i = 0; i < obj.Mesh.Vertices.Length; i++) { obj.Mesh.Vertices[i].Coordinates.Transform(rootMatrix); } } return(obj); }
/// <summary>Loads a Loksim3D object from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Rotation">A three-dimemsional vector describing the rotation to be applied</param> /// <returns>The object loaded.</returns> internal static StaticObject ReadObject(string FileName, Vector3 Rotation) { string BaseDir = System.IO.Path.GetDirectoryName(FileName); XmlDocument currentXML = new XmlDocument(); //Initialise the object StaticObject Object = new StaticObject(Program.CurrentHost); MeshBuilder Builder = new MeshBuilder(Program.CurrentHost); Vector3[] Normals = new Vector3[4]; bool PropertiesFound = false; VertexTemplate[] tempVertices = new VertexTemplate[0]; Vector3[] tempNormals = new Vector3[0]; Color24 transparentColor = new Color24(); string tday = null; string transtex = null; string tnight = null; bool TransparencyUsed = false; bool TransparentTypSet = false; bool FirstPxTransparent = false; Color24 FirstPxColor = new Color24(); bool Face2 = false; int TextureWidth = 0; int TextureHeight = 0; if (File.Exists(FileName)) { try { currentXML.Load(FileName); } catch { return(null); } } else { Interface.AddMessage(MessageType.Error, false, "Loksim3D object " + FileName + " does not exist."); return(null); } //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT"); //Check this file actually contains Loksim3D object nodes if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode node in outerNode.ChildNodes) { //I think there should only be one properties node?? //Need better format documentation if (node.Name == "Props" && PropertiesFound == false) { if (node.Attributes != null) { //Our node has child nodes, therefore this properties node should be valid //Needs better validation PropertiesFound = true; foreach (XmlAttribute attribute in node.Attributes) { switch (attribute.Name) { //Sets the texture //Loksim3D objects only support daytime textures case "Texture": tday = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory); if (!File.Exists(tday)) { Interface.AddMessage(MessageType.Warning, true, "Ls3d Texture file " + attribute.Value + " not found."); break; } try { using (Bitmap TextureInformation = new Bitmap(tday)) { TextureWidth = TextureInformation.Width; TextureHeight = TextureInformation.Height; Color color = TextureInformation.GetPixel(0, 0); FirstPxColor = new Color24(color.R, color.G, color.B); } } catch { Interface.AddMessage(MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName); tday = null; } break; //Defines whether the texture uses transparency //May be omitted case "Transparent": if (TransparentTypSet) { //Appears to be ignored with TransparentTyp set continue; } if (attribute.Value == "TRUE") { TransparencyUsed = true; transparentColor = Color24.Black; } break; case "TransTexture": if (string.IsNullOrEmpty(attribute.Value)) { //Empty.... continue; } transtex = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory); if (!File.Exists(transtex)) { Interface.AddMessage(MessageType.Error, true, "AlphaTexture " + transtex + " could not be found in file " + FileName); transtex = null; } break; //Sets the transparency type case "TransparentTyp": TransparentTypSet = true; switch (attribute.Value) { case "0": //Transparency is disabled TransparencyUsed = false; break; case "1": //Transparency is solid black TransparencyUsed = true; transparentColor = Color24.Black; FirstPxTransparent = false; break; case "2": //Transparency is the color at Pixel 1,1 TransparencyUsed = true; FirstPxTransparent = true; break; case "3": case "4": //This is used when transparency is used with an alpha bitmap TransparencyUsed = false; FirstPxTransparent = false; break; case "5": //Use the alpha channel from the image, so we don't need to do anything fancy //TODO: (Low priority) Check what happens in Loksim itself when an image uses the Alpha channel, but doesn't actually specify type 5 break; default: Interface.AddMessage(MessageType.Error, false, "Unrecognised transparency type " + attribute.Value + " detected in " + attribute.Name + " in Loksim3D object file " + FileName); break; } break; //Sets whether the rears of the faces are to be drawn case "Drawrueckseiten": if (attribute.Value == "TRUE" || string.IsNullOrEmpty(attribute.Value)) { Face2 = true; } else { Face2 = false; } break; /* * MISSING PROPERTIES: * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system. * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color? * FileAuthor * FileInfo * FilePicture */ } } } } //The point command is eqivilant to a vertex else if (node.Name == "Point" && node.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Vertex double vx = 0.0, vy = 0.0, vz = 0.0; //Normals double nx = 0.0, ny = 0.0, nz = 0.0; foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { //Sets the vertex normals case "Normal": string[] NormalPoints = attribute.Value.Split(';'); if (!double.TryParse(NormalPoints[0], out nx)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument nX in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(NormalPoints[1], out ny)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument nY in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(NormalPoints[2], out nz)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument nZ in " + attribute.Name + " in Loksim3D object file " + FileName); } break; //Sets the vertex 3D co-ordinates case "Vekt": string[] VertexPoints = attribute.Value.Split(';'); if (!double.TryParse(VertexPoints[0], out vx)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument vX in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(VertexPoints[1], out vy)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument yY in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(VertexPoints[2], out vz)) { Interface.AddMessage(MessageType.Error, false, "Invalid argument vZ in " + attribute.Name + " in Loksim3D object file " + FileName); } break; } } //Resize temp arrays Array.Resize <VertexTemplate>(ref tempVertices, tempVertices.Length + 1); Array.Resize <Vector3>(ref tempNormals, tempNormals.Length + 1); //Add vertex and normals to temp array tempVertices[tempVertices.Length - 1] = new Vertex(vx, vy, vz); tempNormals[tempNormals.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz); tempNormals[tempNormals.Length - 1].Normalize(); Array.Resize <VertexTemplate>(ref Builder.Vertices, Builder.Vertices.Length + 1); while (Builder.Vertices.Length >= Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1] = new Vertex(vx, vy, vz); Normals[Builder.Vertices.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz); } } } //The Flaeche command creates a face else if (node.Name == "Flaeche" && node.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Defines the verticies in this face //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates if (childNode.Attributes["Points"] != null) { string[] Verticies = childNode.Attributes["Points"].Value.Split(';'); int f = Builder.Faces.Length; //Add 1 to the length of the face array Array.Resize <MeshFace>(ref Builder.Faces, f + 1); Builder.Faces[f] = new MeshFace(); //Create the vertex array for the face Builder.Faces[f].Vertices = new MeshFaceVertex[Verticies.Length]; while (Builder.Vertices.Length > Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } //Run through the vertices list and grab from the temp array int smallestX = TextureWidth; int smallestY = TextureHeight; for (int j = 0; j < Verticies.Length; j++) { //This is the position of the vertex in the temp array int currentVertex; if (!int.TryParse(Verticies[j], out currentVertex)) { Interface.AddMessage(MessageType.Error, false, Verticies[j] + " does not parse to a valid Vertex in " + node.Name + " in Loksim3D object file " + FileName); continue; } //Add one to the actual vertex array Array.Resize <VertexTemplate>(ref Builder.Vertices, Builder.Vertices.Length + 1); //Set coordinates Builder.Vertices[Builder.Vertices.Length - 1] = new Vertex(tempVertices[currentVertex].Coordinates); //Set the vertex index Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1); //Set the normals Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex]; //Now deal with the texture //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex if (childNode.Attributes["Texture"] != null) { string[] TextureCoords = childNode.Attributes["Texture"].Value.Split(';'); Vector2 currentCoords; float OpenBVEWidth; float OpenBVEHeight; string[] splitCoords = TextureCoords[j].Split(','); if (!float.TryParse(splitCoords[0], out OpenBVEWidth)) { Interface.AddMessage(MessageType.Error, false, "Invalid texture width specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (!float.TryParse(splitCoords[1], out OpenBVEHeight)) { Interface.AddMessage(MessageType.Error, false, "Invalid texture height specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (OpenBVEWidth <= smallestX && OpenBVEHeight <= smallestY) { //Clamp texture width and height smallestX = (int)OpenBVEWidth; smallestY = (int)OpenBVEHeight; } if (TextureWidth != 0 && TextureHeight != 0) { //Calculate openBVE co-ords currentCoords.X = (OpenBVEWidth / TextureWidth); currentCoords.Y = (OpenBVEHeight / TextureHeight); } else { //Invalid, so just return zero currentCoords.X = 0; currentCoords.Y = 0; } Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords; } } if (Face2) { //Add face2 flag if required Builder.Faces[f].Flags = (byte)MeshFace.Face2Mask; } } } } } } } } } //Apply rotation /* * NOTES: * No rotation order is specified * The rotation string in a .l3dgrp file is ordered Y, Z, X ??? Can't find a good reason for this ??? * Rotations must still be performed in X,Y,Z order to produce correct results */ if (Rotation.Z != 0.0) { Rotation.Z = Rotation.Z.ToRadians(); //Apply rotation ApplyRotation(Builder, 1, 0, 0, Rotation.Z); } if (Rotation.X != 0.0) { //This is actually the Y-Axis rotation Rotation.X = Rotation.X.ToRadians(); //Apply rotation ApplyRotation(Builder, 0, 1, 0, Rotation.X); } if (Rotation.Y != 0.0) { //This is actually the X-Axis rotation Rotation.Y = Rotation.Y.ToRadians(); //Apply rotation ApplyRotation(Builder, 0, 0, 1, Rotation.Y); } //These files appear to only have one texture defined //Therefore import later- May have to change if (File.Exists(tday)) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].DaytimeTexture = tday; Builder.Materials[j].TransparencyTexture = transtex; Builder.Materials[j].NighttimeTexture = tnight; } } if (TransparencyUsed == true) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = FirstPxTransparent ? FirstPxColor : transparentColor; Builder.Materials[j].TransparentColorUsed = true; } } } Builder.Apply(ref Object); Object.Mesh.CreateNormals(); return(Object); }
private static void MeshBuilder(ref ObjectManager.StaticObject obj, ref MeshBuilder builder, AssimpNET.X.Mesh mesh) { if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(); } int nVerts = mesh.Positions.Count; if (nVerts == 0) { //Some null objects contain an empty mesh Interface.AddMessage(MessageType.Warning, false, "nVertices should be greater than zero in Mesh " + mesh.Name); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = new Vertex(mesh.Positions[i].X, mesh.Positions[i].Y, mesh.Positions[i].Z); } int nFaces = mesh.PosFaces.Count; int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + nFaces); for (int i = 0; i < nFaces; i++) { int fVerts = mesh.PosFaces[i].Indices.Count; if (nFaces == 0) { throw new Exception("fVerts must be greater than zero"); } builder.Faces[f + i] = new MeshFace(); builder.Faces[f + i].Vertices = new MeshFaceVertex[fVerts]; for (int j = 0; j < fVerts; j++) { builder.Faces[f + i].Vertices[j].Index = (ushort)mesh.PosFaces[i].Indices[j]; } } int nMaterials = mesh.Materials.Count; int nFaceIndices = mesh.FaceMaterials.Count; for (int i = 0; i < nFaceIndices; i++) { int fMaterial = (int)mesh.FaceMaterials[i]; builder.Faces[i].Material = (ushort)(fMaterial + 1); } for (int i = 0; i < nMaterials; i++) { int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); builder.Materials[m].Color = new Color32((byte)(255 * mesh.Materials[i].Diffuse.R), (byte)(255 * mesh.Materials[i].Diffuse.G), (byte)(255 * mesh.Materials[i].Diffuse.B), (byte)(255 * mesh.Materials[i].Diffuse.A)); double mPower = mesh.Materials[i].SpecularExponent; //TODO: Unsure what this does... Color24 mSpecular = new Color24((byte)mesh.Materials[i].Specular.R, (byte)mesh.Materials[i].Specular.G, (byte)mesh.Materials[i].Specular.B); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * mesh.Materials[i].Emissive.R), (byte)(255 * mesh.Materials[i].Emissive.G), (byte)(255 * mesh.Materials[i].Emissive.B)); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = Color24.Black; //TODO: Check, also can we optimise which faces have the transparent color set? builder.Materials[m].TransparentColorUsed = true; if (mesh.Materials[i].Textures.Count > 0) { builder.Materials[m].DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, mesh.Materials[i].Textures[0].Name); if (!System.IO.File.Exists(builder.Materials[m].DaytimeTexture)) { Interface.AddMessage(MessageType.Error, true, "Texure " + builder.Materials[m].DaytimeTexture + " was not found in file " + currentFile); builder.Materials[m].DaytimeTexture = null; } } } if (mesh.TexCoords.Length > 0 && mesh.TexCoords[0] != null) { int nCoords = mesh.TexCoords[0].Count; for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = new Vector2(mesh.TexCoords[0][i].X, mesh.TexCoords[0][i].Y); } } int nNormals = mesh.Normals.Count; Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = new Vector3(mesh.Normals[i].X, mesh.Normals[i].Y, mesh.Normals[i].Z); normals[i].Normalize(); } int nFaceNormals = mesh.NormFaces.Count; if (nFaceNormals > builder.Faces.Length) { throw new Exception("nFaceNormals must match the number of faces in the mesh"); } for (int i = 0; i < nFaceNormals; i++) { int nVertexNormals = mesh.NormFaces[i].Indices.Count; if (nVertexNormals > builder.Faces[i].Vertices.Length) { throw new Exception("nVertexNormals must match the number of verticies in the face"); } for (int j = 0; j < nVertexNormals; j++) { builder.Faces[i].Vertices[j].Normal = normals[(int)mesh.NormFaces[i].Indices[j]]; } } int nVertexColors = (int)mesh.NumColorSets; for (int i = 0; i < nVertexColors; i++) { builder.Vertices[i] = new ColoredVertex((Vertex)builder.Vertices[i], new Color128(mesh.Colors[0][i].R, mesh.Colors[0][i].G, mesh.Colors[0][i].B, mesh.Colors[0][i].A)); } }
internal static ObjectManager.StaticObject ReadObject(string FileName) { currentFolder = System.IO.Path.GetDirectoryName(FileName); currentFile = FileName; rootMatrix = Matrix4D.NoTransformation; #if !DEBUG try { #endif XFileParser parser = new XFileParser(System.IO.File.ReadAllBytes(FileName)); Scene scene = parser.GetImportedData(); ObjectManager.StaticObject obj = new ObjectManager.StaticObject(); MeshBuilder builder = new MeshBuilder(); // Global foreach (var mesh in scene.GlobalMeshes) { MeshBuilder(ref obj, ref builder, mesh); } if (scene.RootNode != null) { // Root Node if (scene.RootNode.TrafoMatrix != OpenTK.Matrix4.Zero) { rootMatrix = ConvertMatrix(scene.RootNode.TrafoMatrix); } foreach (var mesh in scene.RootNode.Meshes) { MeshBuilder(ref obj, ref builder, mesh); } // Children Node foreach (var node in scene.RootNode.Children) { ChildrenNode(ref obj, ref builder, node); } } builder.Apply(ref obj); obj.Mesh.CreateNormals(); if (rootMatrix != Matrix4D.NoTransformation) { for (int i = 0; i < obj.Mesh.Vertices.Length; i++) { obj.Mesh.Vertices[i].Coordinates.Transform(rootMatrix); } } return(obj); #if !DEBUG } catch (Exception e) { Interface.AddMessage(MessageType.Error, false, e.Message + " in " + FileName); return(null); } #endif }
internal static ObjectManager.StaticObject ReadObject(string FileName) { currentFolder = System.IO.Path.GetDirectoryName(FileName); currentFile = FileName; #if !DEBUG try { #endif ObjFileParser parser = new ObjFileParser(System.IO.File.ReadAllLines(currentFile), null, System.IO.Path.GetFileNameWithoutExtension(currentFile), currentFile); Model model = parser.GetModel(); ObjectManager.StaticObject obj = new ObjectManager.StaticObject(); MeshBuilder builder = new MeshBuilder(); List <Vertex> allVertices = new List <Vertex>(); foreach (var vertex in model.Vertices) { allVertices.Add(new Vertex(vertex.X, vertex.Y, vertex.Z)); } List <Vector2> allTexCoords = new List <Vector2>(); foreach (var texCoord in model.TextureCoord) { allTexCoords.Add(new Vector2(texCoord.X, texCoord.Y)); } List <Vector3> allNormals = new List <Vector3>(); foreach (var normal in model.Normals) { allNormals.Add(new Vector3(normal.X, normal.Y, normal.Z)); } foreach (AssimpNET.Obj.Mesh mesh in model.Meshes) { foreach (Face face in mesh.Faces) { int nVerts = face.Vertices.Count; if (nVerts == 0) { throw new Exception("nVertices must be greater than zero"); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = allVertices[(int)face.Vertices[i]]; } int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + 1); builder.Faces[f] = new MeshFace(); builder.Faces[f].Vertices = new MeshFaceVertex[nVerts]; for (int i = 0; i < nVerts; i++) { builder.Faces[f].Vertices[i].Index = (ushort)i; } builder.Faces[f].Material = 1; int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); uint materialIndex = mesh.MaterialIndex; if (materialIndex != AssimpNET.Obj.Mesh.NoMaterial) { AssimpNET.Obj.Material material = model.MaterialMap[model.MaterialLib[(int)materialIndex]]; builder.Materials[m].Color = new Color32((byte)(255 * material.Diffuse.R), (byte)(255 * material.Diffuse.G), (byte)(255 * material.Diffuse.B), (byte)(255 * material.Diffuse.A)); Color24 mSpecular = new Color24((byte)material.Specular.R, (byte)material.Specular.G, (byte)material.Specular.B); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * material.Emissive.R), (byte)(255 * material.Emissive.G), (byte)(255 * material.Emissive.B)); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = new Color24((byte)(255 * material.Transparent.R), (byte)(255 * material.Transparent.G), (byte)(255 * material.Transparent.B)); builder.Materials[m].TransparentColorUsed = true; if (material.Texture != null) { builder.Materials[m].DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, material.Texture); if (!System.IO.File.Exists(builder.Materials[m].DaytimeTexture)) { Interface.AddMessage(MessageType.Error, true, "Texure " + builder.Materials[m].DaytimeTexture + " was not found in file " + currentFile); builder.Materials[m].DaytimeTexture = null; } } } int nCoords = face.TexturCoords.Count; for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = allTexCoords[(int)face.TexturCoords[i]]; } int nNormals = face.Normals.Count; Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = allNormals[(int)face.Normals[i]]; normals[i].Normalize(); } for (int i = 0; i < nNormals; i++) { builder.Faces[0].Vertices[i].Normal = normals[i]; } builder.Apply(ref obj); builder = new MeshBuilder(); } } obj.Mesh.CreateNormals(); return(obj); #if !DEBUG } catch (Exception e) { Interface.AddMessage(MessageType.Error, false, e.Message + " in " + FileName); return(null); } #endif }
private static void ParseSubBlock(Block block, ref ObjectManager.StaticObject obj, ref MeshBuilder builder, ref Material material) { Block subBlock; switch (block.Token) { default: return; case TemplateID.Template: string GUID = block.ReadString(); /* * Valid Microsoft templates are listed here: * https://docs.microsoft.com/en-us/windows/desktop/direct3d9/dx9-graphics-reference-x-file-format-templates * However, an application may define it's own template (or by the looks of things override another) * by declaring this at the head of the file, and using a unique GUID * * Mesquoia does this by defining a copy of the Boolean template using a WORD as opposed to a DWORD * No practical effect in this case, however be wary of this.... */ return; case TemplateID.Header: int majorVersion = block.ReadUInt16(); int minorVersion = block.ReadUInt16(); int flags = block.ReadUInt16(); switch (flags) { /* According to http://paulbourke.net/dataformats/directx/#xfilefrm_Template_Header * it is possible for a file to contain a mix of both binary and textual blocks. * * The Header block controls the format of the file from this point onwards. * majorVersion and minorVersion relate to the legacy Direct3D retained mode API * and can probably be ignored. (Assume that features are cumulative and backwards compatible) * flags sets whether the blocks from this point onwards are binary or textual. * * TODO: Need a mixed mode file sample if we want this to work. * Probably exceedingly uncommon, so low priority */ case 0: if (block is TextualBlock) { throw new Exception("Mixed-mode text and binary objects are not supported by this parser."); } break; default: if (block is BinaryBlock) { throw new Exception("Mixed-mode text and binary objects are not supported by this parser."); } break; } return; case TemplateID.Frame: currentLevel++; if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(); } while (block.Position() < block.Length() - 5) { /* * TODO: Whilst https://docs.microsoft.com/en-us/windows/desktop/direct3d9/frame suggests the Frame template should only contain * Mesh, FrameTransformMatrix or Frame templates by default, 3DS Max stuffs all manner of things into here * * It would be nice to get 3DS max stuff detected specifically, especially as we don't support most of this */ //TemplateID[] validTokens = { TemplateID.Mesh , TemplateID.FrameTransformMatrix, TemplateID.Frame }; subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } currentLevel--; break; case TemplateID.FrameTransformMatrix: double[] matrixValues = new double[16]; for (int i = 0; i < 16; i++) { matrixValues[i] = block.ReadSingle(); } if (currentLevel > 1) { builder.TransformMatrix = new Matrix4D(matrixValues); } else { rootMatrix = new Matrix4D(matrixValues); } break; case TemplateID.Mesh: if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(); } int nVerts = block.ReadUInt16(); if (nVerts == 0) { //Some null objects contain an empty mesh Interface.AddMessage(MessageType.Warning, false, "nVertices should be greater than zero in Mesh " + block.Label); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = new Vertex(new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle())); } int nFaces = block.ReadUInt16(); if (nFaces == 0) { try { /* * A mesh has been defined with no faces. * If we are not at the end of the block, * attempt to read the next sub-block * * If this fails, the face count is probably incorrect * * NOTE: In this case, the face statement will be an empty string / whitespace * hence the block.ReadString() call */ block.ReadString(); if (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } goto NoFaces; } catch { throw new Exception("nFaces was declared as zero, but unrecognised data remains in the block"); } } int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + nFaces); for (int i = 0; i < nFaces; i++) { int fVerts = block.ReadUInt16(); if (nFaces == 0) { throw new Exception("fVerts must be greater than zero"); } builder.Faces[f + i] = new MeshFace(); builder.Faces[f + i].Vertices = new MeshFaceVertex[fVerts]; for (int j = 0; j < fVerts; j++) { builder.Faces[f + i].Vertices[j].Index = block.ReadUInt16(); } } NoFaces: while (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } break; case TemplateID.MeshMaterialList: int nMaterials = block.ReadUInt16(); int nFaceIndices = block.ReadUInt16(); if (nFaceIndices == 1 && builder.Faces.Length > 1) { //Single material for all faces int globalMaterial = block.ReadUInt16(); for (int i = 0; i < builder.Faces.Length; i++) { builder.Faces[i].Material = (ushort)(globalMaterial + 1); } } else if (nFaceIndices == builder.Faces.Length) { for (int i = 0; i < nFaceIndices; i++) { int fMaterial = block.ReadUInt16(); builder.Faces[i].Material = (ushort)(fMaterial + 1); } } else { throw new Exception("nFaceIndices must match the number of faces in the mesh"); } for (int i = 0; i < nMaterials; i++) { subBlock = block.ReadSubBlock(TemplateID.Material); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } break; case TemplateID.Material: int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); builder.Materials[m].Color = new Color32((byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle())); double mPower = block.ReadSingle(); //TODO: Unsure what this does... Color24 mSpecular = new Color24((byte)block.ReadSingle(), (byte)block.ReadSingle(), (byte)block.ReadSingle()); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle())); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = Color24.Black; //TODO: Check, also can we optimise which faces have the transparent color set? builder.Materials[m].TransparentColorUsed = true; if (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(TemplateID.TextureFilename); ParseSubBlock(subBlock, ref obj, ref builder, ref builder.Materials[m]); } break; case TemplateID.TextureFilename: try { material.DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, block.ReadString()); } catch { //Empty / malformed texture argument material.DaytimeTexture = null; } if (!System.IO.File.Exists(material.DaytimeTexture) && material.DaytimeTexture != null) { Interface.AddMessage(MessageType.Error, true, "Texure " + material.DaytimeTexture + " was not found in file " + currentFile); material.DaytimeTexture = null; } break; case TemplateID.MeshTextureCoords: int nCoords = block.ReadUInt16(); for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = new Vector2(block.ReadSingle(), block.ReadSingle()); } break; case TemplateID.MeshNormals: int nNormals = block.ReadUInt16(); Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle()); normals[i].Normalize(); } int nFaceNormals = block.ReadUInt16(); if (nFaceNormals != builder.Faces.Length) { throw new Exception("nFaceNormals must match the number of faces in the mesh"); } for (int i = 0; i < nFaceNormals; i++) { int nVertexNormals = block.ReadUInt16(); if (nVertexNormals != builder.Faces[i].Vertices.Length) { throw new Exception("nVertexNormals must match the number of verticies in the face"); } for (int j = 0; j < nVertexNormals; j++) { builder.Faces[i].Vertices[j].Normal = normals[block.ReadUInt16()]; } } break; case TemplateID.MeshVertexColors: int nVertexColors = block.ReadUInt16(); for (int i = 0; i < nVertexColors; i++) { builder.Vertices[i] = new ColoredVertex((Vertex)builder.Vertices[i], new Color128(block.ReadSingle(), block.ReadSingle(), block.ReadSingle())); } break; } }