private void ValidateData(FileData data) { var decoder = new CsvB3dDecoder.Decoder(); var manager = new MyTextureManager(); var logger = new MyErrorLogger(); var options = new TrainsimApi.Codecs.MeshDecodingOptions(manager, logger); #if !DEBUG try { #endif Mesh mesh = decoder.Load(data.FullPath, options); Validator.CheckMeshFaces(mesh, logger); data.ErrorMessage = logger.Builder.ToString(); #if !DEBUG } catch (Exception ex) { data.ErrorMessage = "Unhandled exception:" + Environment.NewLine + ex.Message + Environment.NewLine + Environment.NewLine + "Please forward this error to the author of this program along with the file that caused the error."; } #endif listviewFiles.Invoke(new ThreadStart(() => { if (listviewFiles.Items.Contains(data.Item)) { data.Item.SubItems[1].Text = logger.Count.ToString(); if (listviewFiles.SelectedItems.Count != 0 && listviewFiles.SelectedItems[0] == data.Item) { ListviewFilesSelectedIndexChanged(null, null); } } })); }
public abstract Mesh Load(string file, MeshDecodingOptions options);
public override Mesh Load(string file, MeshDecodingOptions options) { const bool strictParsing = true; // --- preparations --- bool isB3d = file.EndsWith(".b3d", StringComparison.OrdinalIgnoreCase); char commandArgumentSeparator = isB3d ? ' ' : ','; FileInformation fileInfo = new FileInformation(isB3d, file, strictParsing, options.Logger); List<MeshBuilder> meshBuilders = new List<MeshBuilder>(); MeshBuilder currentMeshBuilder = new MeshBuilder(); bool meshBuilderPresent = false; // --- line by line --- string[] lines = File.ReadAllLines(file, Encoding.UTF8); for (int l = 0; l < lines.Length; l++) { string line = lines[l]; // --- trim comments and whitespace --- int semicolon = line.IndexOf(';'); if (semicolon >= 0) { line = line.Substring(0, semicolon).Trim(); } else { line = line.Trim(); } if (line.Length != 0) { // --- split into command and argument sequence --- string command; string argumentSequence; int separator = line.IndexOf(commandArgumentSeparator); if (separator >= 0) { command = line.Substring(0, separator).TrimEnd(); argumentSequence = line.Substring(separator + 1).TrimStart(); } else { command = line; argumentSequence = string.Empty; } // --- handle malformed commands --- if (command.Length != 0) { if (isB3d) { int comma = command.IndexOf(','); if (comma >= 0) { argumentSequence = command.Substring(comma + 1).TrimStart() + ',' + argumentSequence; command = command.Substring(0, comma).TrimEnd(); if (strictParsing) { string text = "Command \"" + command + "\" must be separated from its arguments by a space in B3D files on line " + (l + 1).ToString() + " in file \"" + file + "\"."; options.Logger.Add(text); } } } else { int space = command.IndexOf(' '); if (space >= 0) { argumentSequence = command.Substring(space + 1).TrimStart() + ',' + argumentSequence; command = command.Substring(0, space).TrimEnd(); if (strictParsing) { string text = "Command \"" + command + "\" must be separated from its arguments by a comma in CSV files on line " + (l + 1).ToString() + " in file \"" + file + "\"."; options.Logger.Add(text); } } } } if (command.Length != 0) { // --- split into arguments and trim empty arguments from the end --- string[] arguments = argumentSequence.Split(','); for (int a = 0; a < arguments.Length; a++) { arguments[a] = arguments[a].Trim(); } int argumentCount = 0; for (int a = arguments.Length - 1; a >= 0; a--) { if (arguments[a].Length != 0) { argumentCount = a + 1; break; } } // --- process commands --- LineInformation lineInfo = new LineInformation(command, arguments, argumentCount, l + 1, fileInfo); string commandLower = command.ToLowerInvariant(); switch (commandLower) { case "[meshbuilder]": case "createmeshbuilder": { if (strictParsing) { CheckCommand(command, "[MeshBuilder]", "CreateMeshBuilder", lineInfo); CheckArgumentCount(command, argumentCount, 0, 0, lineInfo); } meshBuilders.Add(currentMeshBuilder); currentMeshBuilder = new MeshBuilder(); meshBuilderPresent = true; } break; case "vertex": case "addvertex": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "Vertex", "AddVertex", lineInfo); CheckArgumentCount(command, argumentCount, 0, 8, lineInfo); } double positionX = GetDoubleFromArgument(0, "positionX", 0.0, lineInfo); double positionY = GetDoubleFromArgument(1, "positionY", 0.0, lineInfo); double positionZ = GetDoubleFromArgument(2, "positionZ", 0.0, lineInfo); double normalX = GetDoubleFromArgument(3, "normalX", 0.0, lineInfo); double normalY = GetDoubleFromArgument(4, "normalY", 0.0, lineInfo); double normalZ = GetDoubleFromArgument(5, "normalZ", 0.0, lineInfo); double textureX = GetDoubleFromArgument(6, "textureX", 0.0, lineInfo); double textureY = GetDoubleFromArgument(7, "textureY", 0.0, lineInfo); MeshBuilderVertex vertex = new MeshBuilderVertex(new Vector3d(positionX, positionY, positionZ), new Vector3d(normalX, normalY, normalZ), new Vector2d(textureX, textureY)); currentMeshBuilder.Vertices.Add(vertex); } break; case "face": case "face2": case "addface": case "addface2": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); } bool face2 = commandLower[commandLower.Length - 1] == '2'; if (strictParsing) { string suffix = face2 ? "2" : string.Empty; CheckCommand(command, "Face" + suffix, "AddFace" + suffix, lineInfo); } bool success = CheckArgumentCount(command, argumentCount, 3, int.MaxValue, lineInfo); int[] indices = new int[argumentCount]; for (int i = 0; i < argumentCount; i++) { if (!TryGetInt32FromArgument(i, "vertex" + (i + 1).ToString(), 0, currentMeshBuilder.Vertices.Count - 1, -1, false, lineInfo, out indices[i])) { success = false; } } if (success) { currentMeshBuilder.Faces.Add(new MeshBuilderFace(0, indices, lineInfo.LineNumber)); if (face2) { Array.Reverse(indices); MeshBuilderFace face = new MeshBuilderFace(0, indices, lineInfo.LineNumber); face.Normals = MeshBuilderFaceNormals.Inverse; currentMeshBuilder.Faces.Add(face); } } } break; case "cube": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } double x = GetDoubleFromArgument(0, "x", 0.0, lineInfo); double y = GetDoubleFromArgument(1, "y", x, lineInfo); double z = GetDoubleFromArgument(2, "z", x, lineInfo); currentMeshBuilder.AddCube(x, y, z, lineInfo.LineNumber); } break; case "cylinder": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 1, 4, lineInfo); } int numVertices; bool success = TryGetInt32FromArgument(0, "numVertices", 2, int.MaxValue, 0, false, lineInfo, out numVertices); double upper = GetDoubleFromArgument(1, "upper", 0.0, lineInfo); double lower = GetDoubleFromArgument(2, "lower", 0.0, lineInfo); double height = GetDoubleFromArgument(3, "height", 0.0, lineInfo); if (success) { currentMeshBuilder.AddCylinder(numVertices, upper, lower, height, lineInfo.LineNumber); } } break; case "[texture]": case "generatenormals": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "[Texture]", "GenerateNormals", lineInfo); CheckArgumentCount(command, argumentCount, 0, 0, lineInfo); } } break; case "translate": case "translateall": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } double x = GetDoubleFromArgument(0, "x", 0.0, lineInfo); double y = GetDoubleFromArgument(1, "y", 0.0, lineInfo); double z = GetDoubleFromArgument(2, "z", 0.0, lineInfo); Vector3d offset = new Vector3d(x, y, z); currentMeshBuilder.Translate(offset); if (commandLower == "translateall") { foreach (MeshBuilder meshBuilder in meshBuilders) { meshBuilder.Translate(offset); } } } break; case "scale": case "scaleall": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } double x = GetDoubleFromArgument(0, "x", 1.0, lineInfo); double y = GetDoubleFromArgument(1, "y", 1.0, lineInfo); double z = GetDoubleFromArgument(2, "z", 1.0, lineInfo); Vector3d factor = new Vector3d(x, y, z); currentMeshBuilder.Scale(factor); if (commandLower == "scaleall") { foreach (MeshBuilder meshBuilder in meshBuilders) { meshBuilder.Scale(factor); } } } break; case "rotate": case "rotateall": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 0, 4, lineInfo); } double x = GetDoubleFromArgument(0, "x", 0.0, lineInfo); double y = GetDoubleFromArgument(1, "y", 0.0, lineInfo); double z = GetDoubleFromArgument(2, "z", 0.0, lineInfo); double a = GetDoubleFromArgument(3, "angle", 0.0, lineInfo) * (Math.PI / 180.0); Vector3d direction = Vector3d.Normalize(new Vector3d(x, y, z)); if (direction.IsZero()) { direction = Vector3d.Right; } Vector2d angle = new Vector2d(Math.Cos(a), Math.Sin(a)); currentMeshBuilder.Rotate(direction, angle); if (commandLower == "rotateall") { foreach (MeshBuilder meshBuilder in meshBuilders) { meshBuilder.Rotate(direction, angle); } } } break; case "shear": case "shearall": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckArgumentCount(command, argumentCount, 0, 7, lineInfo); } double dx = GetDoubleFromArgument(0, "dx", 0.0, lineInfo); double dy = GetDoubleFromArgument(1, "dy", 0.0, lineInfo); double dz = GetDoubleFromArgument(2, "dz", 0.0, lineInfo); double sx = GetDoubleFromArgument(3, "sx", 0.0, lineInfo); double sy = GetDoubleFromArgument(4, "sy", 0.0, lineInfo); double sz = GetDoubleFromArgument(5, "sz", 0.0, lineInfo); double r = GetDoubleFromArgument(6, "ratio", 0.0, lineInfo); Vector3d direction = Vector3d.Normalize(new Vector3d(dx, dy, dz)); Vector3d shift = Vector3d.Normalize(new Vector3d(sx, sy, sz)); currentMeshBuilder.Shear(direction, shift, r); if (commandLower == "shearall") { foreach (MeshBuilder meshBuilder in meshBuilders) { meshBuilder.Shear(direction, shift, r); } } } break; case "color": case "setcolor": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "Color", "SetColor", lineInfo); CheckArgumentCount(command, argumentCount, 0, 4, lineInfo); } double r, g, b, a; TryGetDoubleFromArgument(0, "red", 0.0, 255.0, 255.0, true, lineInfo, out r); TryGetDoubleFromArgument(1, "green", 0.0, 255.0, 255.0, true, lineInfo, out g); TryGetDoubleFromArgument(2, "blue", 0.0, 255.0, 255.0, true, lineInfo, out b); TryGetDoubleFromArgument(3, "alpha", 0.0, 255.0, 255.0, true, lineInfo, out a); Vector3f color = new Vector3f((float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f); for (int i = 0; i < currentMeshBuilder.Faces.Count; i++) { currentMeshBuilder.Faces[i].ReflectiveColor = color; currentMeshBuilder.Faces[i].Alpha = (float)a / 255.0f; } } break; case "emissivecolor": case "setemissivecolor": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "EmissiveColor", "SetEmissiveColor", lineInfo); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } double r, g, b; TryGetDoubleFromArgument(0, "red", 0.0, 255.0, 0.0, true, lineInfo, out r); TryGetDoubleFromArgument(1, "green", 0.0, 255.0, 0.0, true, lineInfo, out g); TryGetDoubleFromArgument(2, "blue", 0.0, 255.0, 0.0, true, lineInfo, out b); Vector3f color = new Vector3f((float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f); for (int i = 0; i < currentMeshBuilder.Faces.Count; i++) { currentMeshBuilder.Faces[i].EmissiveColor = color; } } break; case "blendmode": case "setblendmode": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "BlendMode", "SetBlendMode", lineInfo); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } string blendMode = argumentCount >= 1 && arguments[0].Length != 0 ? arguments[0] : "normal"; double glowHalfDistance; TryGetDoubleFromArgument(1, "glowHalfDistance", 0.0, double.MaxValue, 0.0, true, lineInfo, out glowHalfDistance); string glowAttenuationMode = argumentCount >= 3 && arguments[2].Length != 0 ? arguments[2] : "divideexponent2"; switch (blendMode.ToLowerInvariant()) { case "normal": break; case "additive": break; default: string text = "\"" + blendMode + "\" as argument 1 to command " + lineInfo.Command + " is not a valid blend mode on line " + lineInfo.LineNumber.ToString() + " in file \"" + lineInfo.FileInfo.File + "\"."; options.Logger.Add(text); break; } switch (glowAttenuationMode.ToLowerInvariant()) { case "divideexponent2": break; case "divideexponent4": break; default: string text = "\"" + blendMode + "\" as argument 3 to command " + lineInfo.Command + " is not a valid glow attenuation mode on line " + lineInfo.LineNumber.ToString() + " in file \"" + lineInfo.FileInfo.File + "\"."; options.Logger.Add(text); break; } // TODO: Not implemented. } break; case "load": case "loadtexture": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "Load", "LoadTexture", lineInfo); CheckArgumentCount(command, argumentCount, 1, 2, lineInfo); } string daytimeTexture = null; string nighttimeTexture = null; if (argumentCount >= 1 && arguments[0].Length != 0) { daytimeTexture = arguments[0]; } if (argumentCount >= 2 && arguments[1].Length != 0) { nighttimeTexture = arguments[1]; } if (daytimeTexture != null) { currentMeshBuilder.DaytimeTexture = Platform.CombineFile(Path.GetDirectoryName(file), daytimeTexture); } // TODO: Nighttime textures. } break; case "transparent": case "setdecaltransparentcolor": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "Transparent", "SetDecalTransparentColor", lineInfo); CheckArgumentCount(command, argumentCount, 0, 3, lineInfo); } int r, g, b; TryGetInt32FromArgument(0, "red", 0, 255, 0, true, lineInfo, out r); TryGetInt32FromArgument(1, "green", 0, 255, 0, true, lineInfo, out g); TryGetInt32FromArgument(2, "blue", 0, 255, 0, true, lineInfo, out b); currentMeshBuilder.TransparentColor = new Vector3b((byte)r, (byte)g, (byte)b); currentMeshBuilder.TransparentColorUsed = true; } break; case "coordinates": case "settexturecoordinates": { if (strictParsing) { CheckMeshBuilderPresence(command, lineInfo, ref meshBuilderPresent); CheckCommand(command, "Coordinates", "SetTextureCoordinates", lineInfo); CheckArgumentCount(command, argumentCount, 1, 3, lineInfo); } int v = -1; bool success; if (currentMeshBuilder.Vertices.Count == 0) { string text = lineInfo.Command + " cannot be used at this point because no vertices were defined in the mesh builder on line " + lineInfo.LineNumber.ToString() + " in file \"" + lineInfo.FileInfo.File + "\"."; options.Logger.Add(text); success = false; } else { success = TryGetInt32FromArgument(0, "vertex", 0, currentMeshBuilder.Vertices.Count - 1, 0, false, lineInfo, out v); } double x = GetDoubleFromArgument(1, "x", 0.0, lineInfo); double y = GetDoubleFromArgument(2, "y", 0.0, lineInfo); if (success) { currentMeshBuilder.Vertices[v].Texture = new Vector2d(x, y); } } break; default: { string text = "Unsupported command \"" + command + "\" found on line " + (l + 1).ToString() + " in file \"" + file + "\"."; options.Logger.Add(text); } break; } } } } meshBuilders.Add(currentMeshBuilder); currentMeshBuilder = null; // --- assemble mesh --- Dictionary<Vertex, int> hashedVertices = new Dictionary<Vertex, int>(); Dictionary<Material, int> hashedMaterials = new Dictionary<Material, int>(); List<Vertex> vertices = new List<Vertex>(); List<Material> materials = new List<Material>(); List<Face> faces = new List<Face>(); foreach (MeshBuilder meshBuilder in meshBuilders) { foreach (MeshBuilderFace meshBuilderFace in meshBuilder.Faces) { // --- generate normal --- Vector3d normal; { Vector3d a = meshBuilder.Vertices[meshBuilderFace.Vertices[0]].Position; Vector3d b = meshBuilder.Vertices[meshBuilderFace.Vertices[1]].Position; Vector3d c = meshBuilder.Vertices[meshBuilderFace.Vertices[2]].Position; normal = Vector3d.Normalize(Vector3d.Cross(b - a, c - a)); if (normal.IsZero()) { normal = Vector3d.Up; } } // --- vertices --- Vertex[] v = new Vertex[meshBuilderFace.Vertices.Length]; for (int i = 0; i < meshBuilderFace.Vertices.Length; i++) { MeshBuilderVertex meshBuilderVertex = meshBuilder.Vertices[meshBuilderFace.Vertices[i]]; v[i] = new Vertex((Vector3f)meshBuilderVertex.Position, (Vector3f)meshBuilderVertex.Normal, (Vector2f)meshBuilderVertex.Texture); if (meshBuilderFace.Normals == MeshBuilderFaceNormals.Generate || meshBuilderVertex.Normal.IsZero()) { v[i].Normal = (Vector3f)normal; } else if (meshBuilderFace.Normals == MeshBuilderFaceNormals.Inverse) { v[i].Normal = (Vector3f)(-meshBuilderVertex.Normal); } } int[] vertexIndices = new int[v.Length]; for (int i = 0; i < v.Length; i++) { if (!hashedVertices.TryGetValue(v[i], out vertexIndices[i])) { vertexIndices[i] = vertices.Count; vertices.Add(v[i]); hashedVertices.Add(v[i], vertexIndices[i]); } } // --- material --- Material material = new Material(); material.Alpha = meshBuilderFace.Alpha; material.EmissiveColor = meshBuilderFace.EmissiveColor; material.ReflectiveColor = meshBuilderFace.ReflectiveColor; if (meshBuilder.DaytimeTexture != null) { if (meshBuilder.TransparentColorUsed) { material.Texture = options.Manager.Add(meshBuilder.DaytimeTexture, meshBuilder.TransparentColor); } else { material.Texture = options.Manager.Add(meshBuilder.DaytimeTexture); } } int materialIndex; if (!hashedMaterials.TryGetValue(material, out materialIndex)) { materialIndex = materials.Count; materials.Add(material); hashedMaterials.Add(material, materialIndex); } // --- face --- Face face = new Face(vertexIndices, materialIndex, meshBuilderFace.LineNumber); faces.Add(face); } } return new Mesh(vertices.ToArray(), materials.ToArray(), faces.ToArray()); }