/// <summary>Checks whether three spatial coordinates are colinear.</summary> /// <param name="a">The first spatial coordinate.</param> /// <param name="b">The second spatial coordinate.</param> /// <param name="c">The third spatial coordinate.</param> /// <returns>A boolean indicating whether the three spatial coordinates are colinear.</returns> public static bool AreColinear(Vector3f a, Vector3f b, Vector3f c) { Vector3f normal = Vector3f.Cross(b - a, c - a); return IsNullVector(normal); }
// read object /// <summary>Loads a CSV or B3D 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="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for X axis of referenced textures.</param> /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for Y axis of referenced textures.</param> /// <returns>The object loaded.</returns> internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; bool IsB3D = string.Equals(System.IO.Path.GetExtension(FileName), ".b3d", StringComparison.OrdinalIgnoreCase); // initialize object ObjectManager.StaticObject Object = new ObjectManager.StaticObject(); Object.Mesh.Faces = new World.MeshFace[] { }; Object.Mesh.Materials = new World.MeshMaterial[] { }; Object.Mesh.Vertices = new World.Vertex[] { }; // read lines string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); // parse lines MeshBuilder Builder = new MeshBuilder(); Vector3f[] Normals = new Vector3f[4]; for (int i = 0; i < Lines.Length; i++) { { // strip away comments int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j); } } // collect arguments string[] Arguments = Lines[i].Split(new char[] { ',' }, StringSplitOptions.None); for (int j = 0; j < Arguments.Length; j++) { Arguments[j] = Arguments[j].Trim(); } { // remove unused arguments at the end of the chain int j; for (j = Arguments.Length - 1; j >= 0; j--) { if (Arguments[j].Length != 0) break; } Array.Resize<string>(ref Arguments, j + 1); } // style string Command; if (IsB3D & Arguments.Length != 0) { // b3d int space = Arguments[0].IndexOf(' '); if (space >= 0) { Command = Arguments[0].Substring(0, space).TrimEnd(); Arguments[0] = Arguments[0].Substring(space + 1).TrimStart(); } else { Command = Arguments[0]; if (Arguments.Length != 1) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid syntax at line " + (i + 1).ToString(Culture) + " in file " + FileName); } Arguments = new string[] { }; } } else if (Arguments.Length != 0) { // csv Command = Arguments[0]; for (int j = 0; j < Arguments.Length - 1; j++) { Arguments[j] = Arguments[j + 1]; } Array.Resize<string>(ref Arguments, Arguments.Length - 1); } else { // empty Command = null; } // parse terms if (Command != null) { string cmd = Command.ToLowerInvariant(); switch(cmd) { case "createmeshbuilder": case "[meshbuilder]": { if (cmd == "createmeshbuilder" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "CreateMeshBuilder is not a supported command - did you mean [MeshBuilder]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "[meshbuilder]" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "[MeshBuilder] is not a supported command - did you mean CreateMeshBuilder? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 0) { Debug.AddMessage(Debug.MessageType.Warning, false, "0 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); Builder = new MeshBuilder(); Normals = new Vector3f[4]; } break; case "addvertex": case "vertex": { if (cmd == "addvertex" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "AddVertex is not a supported command - did you mean Vertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "vertex" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Vertex is not a supported command - did you mean AddVertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 6) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 6 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double vx = 0.0, vy = 0.0, vz = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out vx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vx = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out vy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vy = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out vz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vz = 0.0; } double nx = 0.0, ny = 0.0, nz = 0.0; if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out nx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); nx = 0.0; } if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[4], out ny)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); ny = 0.0; } if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[5], out nz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); nz = 0.0; } World.Normalize(ref nx, ref ny, ref nz); Array.Resize<World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1); while (Builder.Vertices.Length >= Normals.Length) { Array.Resize<Vector3f>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new Vector3D(vx, vy, vz); Normals[Builder.Vertices.Length - 1] = new Vector3f((float)nx, (float)ny, (float)nz); } break; case "addface": case "addface2": case "face": case "face2": { if (IsB3D) { if (cmd == "addface") { Debug.AddMessage(Debug.MessageType.Warning, false, "AddFace is not a supported command - did you mean Face? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "addface2") { Debug.AddMessage(Debug.MessageType.Warning, false, "AddFace2 is not a supported command - did you mean Face2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { if (cmd == "face") { Debug.AddMessage(Debug.MessageType.Warning, false, "Face is not a supported command - did you mean AddFace? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "face2") { Debug.AddMessage(Debug.MessageType.Warning, false, "Face2 is not a supported command - did you mean AddFace2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } if (Arguments.Length < 3) { Debug.AddMessage(Debug.MessageType.Error, false, "At least 3 arguments are required in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { bool valid = true; int[] indices = new int[Arguments.Length]; for (int j = 0; j < Arguments.Length; j++) { if (!Conversions.TryParseIntVb6(Arguments[j], out indices[j])) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " is invalid in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } else if (indices[j] < 0 | indices[j] >= Builder.Vertices.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } else if (indices[j] > 65535) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " indexes a vertex above 65535 which is not currently supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } } if (valid) { int last = Builder.Faces.Length; Array.Resize<World.MeshFace>(ref Builder.Faces, last + 1); Builder.Faces[last] = new World.MeshFace(); Builder.Faces[last].Vertices = new World.MeshFaceVertex[Arguments.Length]; while (Builder.Vertices.Length > Normals.Length) { Array.Resize<Vector3f>(ref Normals, Normals.Length << 1); } for (int j = 0; j < Arguments.Length; j++) { Builder.Faces[last].Vertices[j].Index = (ushort)indices[j]; Builder.Faces[last].Vertices[j].Normal = Normals[indices[j]]; } if (cmd == "addface2" | cmd == "face2") { Builder.Faces[last].Flags = (byte)World.MeshFace.Face2Mask; } } } } break; case "cube": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfWidth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } double y = x, z = x; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfHeight in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfDepth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } CreateCube(ref Builder, x, y, z); } break; case "cylinder": { if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int n = 8; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out n)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument n in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); n = 8; } if (n < 2) { Debug.AddMessage(Debug.MessageType.Error, false, "n is expected to be at least 2 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); n = 8; } double r1 = 0.0, r2 = 0.0, h = 1.0; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out r1)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument UpperRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r1 = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out r2)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument LowerRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r2 = 1.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out h)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Height in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); h = 1.0; } CreateCylinder(ref Builder, n, r1, r2, h); } break; case "translate": case "translateall": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0, y = 0.0, z = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 0.0; } ApplyTranslation(Builder, x, y, z); if (cmd == "translateall") { ApplyTranslation(Object, x, y, z); } } break; case "scale": case "scaleall": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 1.0, y = 1.0, z = 1.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } else if (x == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "X is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } else if (y == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Y is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } else if (z == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Z is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } ApplyScale(Builder, x, y, z); if (cmd == "scaleall") { ApplyScale(Object, x, y, z); } } break; case "rotate": case "rotateall": { if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0, y = 0.0, z = 0.0, a = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 0.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out a)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Angle in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = 0.0; } double t = x * x + y * y + z * z; if (t == 0.0) { x = 1.0; y = 0.0; z = 0.0; t = 1.0; } if (a != 0.0) { t = 1.0 / Math.Sqrt(t); x *= t; y *= t; z *= t; a *= 0.0174532925199433; ApplyRotation(Builder, x, y, z, a); if (cmd == "rotateall") { ApplyRotation(Object, x, y, z, a); } } } break; case "shear": case "shearall": { if (Arguments.Length > 7) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 7 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double dx = 0.0, dy = 0.0, dz = 0.0; double sx = 0.0, sy = 0.0, sz = 0.0; double r = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out dx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dx = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out dy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dy = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out dz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dz = 0.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out sx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sx = 0.0; } if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[4], out sy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sy = 0.0; } if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[5], out sz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sz = 0.0; } if (Arguments.Length >= 7 && Arguments[6].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[6], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Ratio in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0.0; } World.Normalize(ref dx, ref dy, ref dz); World.Normalize(ref sx, ref sy, ref sz); ApplyShear(Builder, dx, dy, dz, sx, sy, sz, r); if (cmd == "shearall") { ApplyShear(Object, dx, dy, dz, sx, sy, sz, r); } } break; case "generatenormals": case "[texture]": if (cmd == "generatenormals" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "GenerateNormals is not a supported command - did you mean [Texture]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "[texture]" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "[Texture] is not a supported command - did you mean GenerateNormals? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } // TODO do something? break; case "setcolor": case "color": { if (cmd == "setcolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetColor is not a supported command - did you mean Color? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "color" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Color is not a supported command - did you mean SetColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0, a = 255; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseIntVb6(Arguments[3], out a)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Alpha in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = 255; } else if (a < 0 | a > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Alpha is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = a < 0 ? 0 : 255; } int m = Builder.Materials.Length; Array.Resize<Material>(ref Builder.Materials, m << 1); for (int j = m; j < Builder.Materials.Length; j++) { Builder.Materials[j] = new Material(Builder.Materials[j - m]); Builder.Materials[j].Color = new Color32((byte)r, (byte)g, (byte)b, (byte)a); Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode; Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData; Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture; Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture; Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor; Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed; } for (int j = 0; j < Builder.Faces.Length; j++) { Builder.Faces[j].Material += (ushort)m; } } break; case "setemissivecolor": case "emissivecolor": { if (cmd == "setemissivecolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetEmissiveColor is not a supported command - did you mean EmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "emissivecolor" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "EmissiveColor is not a supported command - did you mean SetEmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } int m = Builder.Materials.Length; Array.Resize<Material>(ref Builder.Materials, m << 1); for (int j = m; j < Builder.Materials.Length; j++) { Builder.Materials[j] = new Material(Builder.Materials[j - m]); Builder.Materials[j].EmissiveColor = new Color24((byte)r, (byte)g, (byte)b); Builder.Materials[j].EmissiveColorUsed = true; Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode; Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData; Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture; Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture; Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor; Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed; } for (int j = 0; j < Builder.Faces.Length; j++) { Builder.Faces[j].Material += (ushort)m; } } break; case "setdecaltransparentcolor": case "transparent": { if (cmd == "setdecaltransparentcolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetDecalTransparentColor is not a supported command - did you mean Transparent? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "transparent" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Transparent is not a supported command - did you mean SetDecalTransparentColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = new Color24((byte)r, (byte)g, (byte)b); Builder.Materials[j].TransparentColorUsed = true; } } break; case "setblendmode": case "blendmode": { if (cmd == "setblendmode" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetBlendMode is not a supported command - did you mean BlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "blendmode" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "BlendMode is not a supported command - did you mean SetBlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } World.MeshMaterialBlendMode blendmode = World.MeshMaterialBlendMode.Normal; if (Arguments.Length >= 1 && Arguments[0].Length > 0) { switch (Arguments[0].ToLowerInvariant()) { case "normal": blendmode = World.MeshMaterialBlendMode.Normal; break; case "additive": blendmode = World.MeshMaterialBlendMode.Additive; break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The given BlendMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); blendmode = World.MeshMaterialBlendMode.Normal; break; } } double glowhalfdistance = 0.0; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out glowhalfdistance)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument GlowHalfDistance in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); glowhalfdistance = 0; } World.GlowAttenuationMode glowmode = World.GlowAttenuationMode.DivisionExponent4; if (Arguments.Length >= 3 && Arguments[2].Length > 0) { switch (Arguments[2].ToLowerInvariant()) { case "divideexponent2": glowmode = World.GlowAttenuationMode.DivisionExponent2; break; case "divideexponent4": glowmode = World.GlowAttenuationMode.DivisionExponent4; break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The given GlowAttenuationMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].BlendMode = blendmode; Builder.Materials[j].GlowAttenuationData = World.GetGlowAttenuationData(glowhalfdistance, glowmode); } } break; case "loadtexture": case "load": { if (cmd == "loadtexture" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "LoadTexture is not a supported command - did you mean Load? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "load" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Load is not a supported command - did you mean LoadTexture? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 2) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 2 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } string tday = null, tnight = null; if (Arguments.Length >= 1 && Arguments[0].Length != 0) { if (OpenBveApi.Path.ContainsInvalidPathChars(Arguments[0])) { Debug.AddMessage(Debug.MessageType.Error, false, "DaytimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { tday = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[0]); if (!System.IO.File.Exists(tday)) { Debug.AddMessage(Debug.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); tday = null; } } } if (Arguments.Length >= 2 && Arguments[1].Length != 0) { if (Arguments[0].Length == 0) { Debug.AddMessage(Debug.MessageType.Error, true, "DaytimeTexture is required to be specified in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (OpenBveApi.Path.ContainsInvalidPathChars(Arguments[1])) { Debug.AddMessage(Debug.MessageType.Error, false, "NighttimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { tnight = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[1]); if (!System.IO.File.Exists(tnight)) { Debug.AddMessage(Debug.MessageType.Error, true, "The NighttimeTexture " + tnight + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); tnight = null; } } } } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].DaytimeTexture = tday; Builder.Materials[j].NighttimeTexture = tnight; } } break; case "settexturecoordinates": case "coordinates": { if (cmd == "settexturecoordinates" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetTextureCoordinates is not a supported command - did you mean Coordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "coordinates" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Coordinates is not a supported command - did you mean SetTextureCoordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int j = 0; float x = 0.0f, y = 0.0f; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out j)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument VertexIndex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); j = 0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseFloatVb6(Arguments[1], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0f; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseFloatVb6(Arguments[2], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0f; } if (j >= 0 & j < Builder.Vertices.Length) { Builder.Vertices[j].TextureCoordinates = new Vector2f(x, y); } else { Debug.AddMessage(Debug.MessageType.Error, false, "VertexIndex references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: if (Command.Length != 0) { Debug.AddMessage(Debug.MessageType.Error, false, "The command " + Command + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; } } } // finalize object ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); World.CreateNormals(ref Object.Mesh); return Object; }
/// <summary>Translates the vector by a specified offset.</summary> /// <param name="offset">The offset.</param> public void Translate(Vector3f offset) { this.X += offset.X; this.Y += offset.Y; this.Z += offset.Z; }
internal static void RotateUpDown(ref Vector3f Vector, double dx, double dy, double cosa, double sina) { double x = (double)Vector.X, y = (double)Vector.Y, z = (double)Vector.Z; double u = dy * x - dx * z; double v = dx * x + dy * z; Vector.X = (float)(dy * u + dx * v * cosa - dx * y * sina); Vector.Y = (float)(y * cosa + v * sina); Vector.Z = (float)(-dx * u + dy * v * cosa - dy * y * sina); }
internal MeshFaceVertex(int Index, Vector3f Normal) { this.Index = (ushort)Index; this.Normal = Normal; }
/// <summary>Gets the square of the euclidean norm of the specified vector.</summary> /// <param name="vector">The vector.</param> /// <returns>The square of the euclidean norm.</returns> public static float NormSquared(Vector3f vector) { return vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; }
private static void RenderBackground(World.Background Data, double dx, double dy, double dz, float Alpha, float scale) { if (Data.Texture != null && Textures.LoadTexture(Data.Texture, Textures.OpenGlTextureWrapMode.RepeatClamp)) { if (LightingEnabled) { GL.Disable(EnableCap.Lighting); LightingEnabled = false; } if (!TexturingEnabled) { GL.Enable(EnableCap.Texture2D); TexturingEnabled = true; } if (Alpha == 1.0f) { if (BlendEnabled) { GL.Disable(EnableCap.Blend); BlendEnabled = false; } } else if (!BlendEnabled) { GL.Enable(EnableCap.Blend); BlendEnabled = true; } GL.BindTexture(TextureTarget.Texture2D, Data.Texture.OpenGlTextures[(int)Textures.OpenGlTextureWrapMode.RepeatClamp].Name); GL.Color4(1.0f, 1.0f, 1.0f, Alpha); float y0, y1; if (Data.KeepAspectRatio) { int tw = Data.Texture.Width; int th = Data.Texture.Height; double hh = Math.PI * World.BackgroundImageDistance * (double)th / ((double)tw * (double)Data.Repetition); y0 = (float)(-0.5 * hh); y1 = (float)(1.5 * hh); } else { y0 = (float)(-0.125 * World.BackgroundImageDistance); y1 = (float)(0.375 * World.BackgroundImageDistance); } const int n = 32; Vector3f[] bottom = new Vector3f[n]; Vector3f[] top = new Vector3f[n]; double angleValue = 2.61799387799149 - 3.14159265358979 / (double)n; double angleIncrement = 6.28318530717958 / (double)n; /* * To ensure that the whole background cylinder is rendered inside the viewing frustum, * the background is rendered before the scene with z-buffer writes disabled. Then, * the actual distance from the camera is irrelevant as long as it is inside the frustum. * */ for (int i = 0; i < n; i++) { float x = (float)(World.BackgroundImageDistance * Math.Cos(angleValue)); float z = (float)(World.BackgroundImageDistance * Math.Sin(angleValue)); bottom[i] = new Vector3f(scale * x, scale * y0, scale * z); top[i] = new Vector3f(scale * x, scale * y1, scale * z); angleValue += angleIncrement; } float textureStart = 0.5f * (float)Data.Repetition / (float)n; float textureIncrement = -(float)Data.Repetition / (float)n; double textureX = textureStart; for (int i = 0; i < n; i++) { int j = (i + 1) % n; // side wall GL.Begin(PrimitiveType.Quads); GL.TexCoord2(textureX, 0.005f); GL.Vertex3(top[i].X, top[i].Y, top[i].Z); GL.TexCoord2(textureX, 0.995f); GL.Vertex3(bottom[i].X, bottom[i].Y, bottom[i].Z); GL.TexCoord2(textureX + textureIncrement, 0.995f); GL.Vertex3(bottom[j].X, bottom[j].Y, bottom[j].Z); GL.TexCoord2(textureX + textureIncrement, 0.005f); GL.Vertex3(top[j].X, top[j].Y, top[j].Z); GL.End(); // top cap GL.Begin(PrimitiveType.Triangles); GL.TexCoord2(textureX, 0.005f); GL.Vertex3(top[i].X, top[i].Y, top[i].Z); GL.TexCoord2(textureX + textureIncrement, 0.005f); GL.Vertex3(top[j].X, top[j].Y, top[j].Z); GL.TexCoord2(textureX + 0.5 * textureIncrement, 0.1f); GL.Vertex3(0.0f, top[i].Y, 0.0f); // bottom cap GL.TexCoord2(textureX + 0.5 * textureIncrement, 0.9f); GL.Vertex3(0.0f, bottom[i].Y, 0.0f); GL.TexCoord2(textureX + textureIncrement, 0.995f); GL.Vertex3(bottom[j].X, bottom[j].Y, bottom[j].Z); GL.TexCoord2(textureX, 0.995f); GL.Vertex3(bottom[i].X, bottom[i].Y, bottom[i].Z); GL.End(); // finish textureX += textureIncrement; } GL.Disable(EnableCap.Texture2D); TexturingEnabled = false; if (!BlendEnabled) { GL.Enable(EnableCap.Blend); BlendEnabled = true; } } }
/// <summary>Gives the cross product of two vectors.</summary> /// <param name="a">The first vector.</param> /// <param name="b">The second vector.</param> /// <returns>The cross product of the two vectors.</returns> public static Vector3f Cross(Vector3f a, Vector3f b) { return new Vector3f(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); }
/// <summary>Normalizes a vector.</summary> /// <param name="vector">The vector.</param> /// <returns>The normalized vector.</returns> /// <exception cref="System.DivideByZeroException">Raised when the vector is a null vector.</exception> public static Vector3f Normalize(Vector3f vector) { float norm = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z; if (norm == 0.0f) { throw new DivideByZeroException(); } else { float factor = 1.0f / (float)System.Math.Sqrt(norm); return new Vector3f(vector.X * factor, vector.Y * factor, vector.Z * factor); } }
/// <summary>Rotates the vector from the default orientation into a specified orientation.</summary> /// <param name="orientation">The orientation.</param> /// <remarks>The default orientation is X = {1, 0, 0), Y = {0, 1, 0} and Z = {0, 0, 1}.</remarks> public void Rotate(Orientation3f orientation) { float x = orientation.X.X * this.X + orientation.Y.X * this.Y + orientation.Z.X * this.Z; float y = orientation.X.Y * this.X + orientation.Y.Y * this.Y + orientation.Z.Y * this.Z; float z = orientation.X.Z * this.X + orientation.Y.Z * this.Y + orientation.Z.Z * this.Z; this = new Vector3f(x, y, z); }
// --- static functions --- /// <summary>Gives the dot product of two vectors.</summary> /// <param name="a">The first vector.</param> /// <param name="b">The second vector.</param> /// <returns>The dot product of the two vectors.</returns> public static float Dot(Vector3f a, Vector3f b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; }
/// <summary>Rotates the vector on the plane perpendicular to a specified direction by a specified angle.</summary> /// <param name="direction">The direction perpendicular to the plane on which to rotate.</param> /// <param name="cosineOfAngle">The cosine of the angle.</param> /// <param name="sineOfAngle">The sine of the angle.</param> public void Rotate(Vector3f direction, float cosineOfAngle, float sineOfAngle) { float cosineComplement = 1.0f - cosineOfAngle; float x = (cosineOfAngle + cosineComplement * direction.X * direction.X) * this.X + (cosineComplement * direction.X * direction.Y - sineOfAngle * direction.Z) * this.Y + (cosineComplement * direction.X * direction.Z + sineOfAngle * direction.Y) * this.Z; float y = (cosineOfAngle + cosineComplement * direction.Y * direction.Y) * this.Y + (cosineComplement * direction.X * direction.Y + sineOfAngle * direction.Z) * this.X + (cosineComplement * direction.Y * direction.Z - sineOfAngle * direction.X) * this.Z; float z = (cosineOfAngle + cosineComplement * direction.Z * direction.Z) * this.Z + (cosineComplement * direction.X * direction.Z - sineOfAngle * direction.Y) * this.X + (cosineComplement * direction.Y * direction.Z + sineOfAngle * direction.X) * this.Y; this = new Vector3f(x, y, z); }
/// <summary>Scales the vector by a specified factor.</summary> /// <param name="factor">The factor.</param> public void Scale(Vector3f factor) { this.X *= factor.X; this.Y *= factor.Y; this.Z *= factor.Z; }
/// <summary>Translates the vector by a specified offset that is measured in a specified orientation.</summary> /// <param name="orientation">The orientation.</param> /// <param name="offset">The offset measured in the specified orientation.</param> public void Translate(Orientation3f orientation, Vector3f offset) { this.X += orientation.X.X * offset.X + orientation.Y.X * offset.Y + orientation.Z.X * offset.Z; this.Y += orientation.X.Y * offset.X + orientation.Y.Y * offset.Y + orientation.Z.Y * offset.Z; this.Z += orientation.X.Z * offset.X + orientation.Y.Z * offset.Y + orientation.Z.Z * offset.Z; }
/// <summary>Checks whether a vector is a null vector.</summary> /// <returns>A boolean indicating whether the vector is a null vector.</returns> public static bool IsNullVector(Vector3f vector) { return vector.X == 0.0f & vector.Y == 0.0f & vector.Z == 0.0f; }
/// <summary>Translates a vector by a specified offset.</summary> /// <param name="vector">The vector.</param> /// <param name="offset">The offset.</param> /// <returns>The translated vector.</returns> public static Vector3f Translate(Vector3f vector, Vector3f offset) { float x = vector.X + offset.X; float y = vector.Y + offset.Y; float z = vector.Z + offset.Z; return new Vector3f(x, y, z); }
/// <summary>Gets the euclidean norm of the specified vector.</summary> /// <param name="vector">The vector.</param> /// <returns>The euclidean norm.</returns> public static float Norm(Vector3f vector) { return (float)System.Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z); }
/// <summary>Translates a vector by a specified offset that is measured along a specified orientation.</summary> /// <param name="vector">The vector.</param> /// <param name="orientation">The orientation.</param> /// <param name="offset">The offset measured in the specified orientation.</param> public static Vector3f Translate(Vector3f vector, Orientation3f orientation, Vector3f offset) { float x = vector.X + orientation.X.X * offset.X + orientation.Y.X * offset.Y + orientation.Z.X * offset.Z; float y = vector.Y + orientation.X.Y * offset.X + orientation.Y.Y * offset.Y + orientation.Z.Y * offset.Z; float z = vector.Z + orientation.X.Z * offset.X + orientation.Y.Z * offset.Y + orientation.Z.Z * offset.Z; return new Vector3f(x, y, z); }
// reset internal static void Reset() { LoadTexturesImmediately = LoadTextureImmediatelyMode.NotYet; Objects = new Object[256]; ObjectCount = 0; StaticOpaque = new ObjectGroup[] { }; StaticOpaqueForceUpdate = true; DynamicOpaque = new ObjectList(); DynamicAlpha = new ObjectList(); OverlayOpaque = new ObjectList(); OverlayAlpha = new ObjectList(); OptionLighting = true; OptionAmbientColor = new Color24(160, 160, 160); OptionDiffuseColor = new Color24(160, 160, 160); OptionLightPosition = new Vector3f(0.223606797749979f, 0.86602540378444f, -0.447213595499958f); OptionLightingResultingAmount = 1.0f; OptionClock = false; OptionBrakeSystems = false; }
/// <summary>Scales a vector by a specified factor.</summary> /// <param name="vector">The vector.</param> /// <param name="factor">The factor.</param> /// <returns>The scaled vector.</returns> public static Vector3f Scale(Vector3f vector, Vector3f factor) { float x = vector.X * factor.X; float y = vector.Y * factor.Y; float z = vector.Z * factor.Z; return new Vector3f(x, y, z); }
internal static void RotatePlane(ref Vector3f Vector, double cosa, double sina) { double u = (double)Vector.X * cosa - (double)Vector.Z * sina; double v = (double)Vector.X * sina + (double)Vector.Z * cosa; Vector.X = (float)u; Vector.Z = (float)v; }
/// <summary>Rotates a vector on the plane perpendicular to a specified direction by a specified angle.</summary> /// <param name="vector">The vector.</param> /// <param name="direction">The direction perpendicular to the plane on which to rotate.</param> /// <param name="cosineOfAngle">The cosine of the angle.</param> /// <param name="sineOfAngle">The sine of the angle.</param> /// <returns>The rotated vector.</returns> public static Vector3f Rotate(Vector3f vector, Vector3f direction, float cosineOfAngle, float sineOfAngle) { float cosineComplement = 1.0f - cosineOfAngle; float x = (cosineOfAngle + cosineComplement * direction.X * direction.X) * vector.X + (cosineComplement * direction.X * direction.Y - sineOfAngle * direction.Z) * vector.Y + (cosineComplement * direction.X * direction.Z + sineOfAngle * direction.Y) * vector.Z; float y = (cosineOfAngle + cosineComplement * direction.Y * direction.Y) * vector.Y + (cosineComplement * direction.X * direction.Y + sineOfAngle * direction.Z) * vector.X + (cosineComplement * direction.Y * direction.Z - sineOfAngle * direction.X) * vector.Z; float z = (cosineOfAngle + cosineComplement * direction.Z * direction.Z) * vector.Z + (cosineComplement * direction.X * direction.Z - sineOfAngle * direction.Y) * vector.X + (cosineComplement * direction.Y * direction.Z + sineOfAngle * direction.X) * vector.Y; return new Vector3f(x, y, z); }
internal MeshFaceVertex(int Index) { this.Index = (ushort)Index; this.Normal = new Vector3f(0.0f, 0.0f, 0.0f); }
/// <summary>Rotates a vector from the default orientation into a specified orientation.</summary> /// <param name="vector">The vector.</param> /// <param name="orientation">The orientation.</param> /// <returns>The rotated vector.</returns> /// <remarks>The default orientation is X = {1, 0, 0), Y = {0, 1, 0} and Z = {0, 0, 1}.</remarks> public static Vector3f Rotate(Vector3f vector, Orientation3f orientation) { float x = orientation.X.X * vector.X + orientation.Y.X * vector.Y + orientation.Z.X * vector.Z; float y = orientation.X.Y * vector.X + orientation.Y.Y * vector.Y + orientation.Z.Y * vector.Z; float z = orientation.X.Z * vector.X + orientation.Y.Z * vector.Y + orientation.Z.Z * vector.Z; return new Vector3f(x, y, z); }
// --- constructors --- /// <summary>Creates a new orientation in three-dimensional space.</summary> /// <param name="x">The vector pointing right.</param> /// <param name="y">The vector pointing up.</param> /// <param name="z">The vector pointing forward.</param> public Orientation3f(Vector3f x, Vector3f y, Vector3f z) { this.X = x; this.Y = y; this.Z = z; }
/// <summary>Creates a unit vector perpendicular to the plane described by three spatial coordinates, suitable for being a surface normal.</summary> /// <param name="a">The first spatial coordinate.</param> /// <param name="b">The second spatial coordinate.</param> /// <param name="c">The third spatial coordinate.</param> /// <param name="normal">On success, receives the vector perpendicular to the described plane. On failure, receives Vector3f.Up.</param> /// <returns>The success of the operation. This operation fails if the specified three vectors are colinear.</returns> public static bool CreateNormal(Vector3f a, Vector3f b, Vector3f c, out Vector3f normal) { normal = Vector3f.Cross(b - a, c - a); float norm = normal.X * normal.X + normal.Y * normal.Y + normal.Z * normal.Z; if (norm != 0.0f) { normal *= 1.0f / (float)System.Math.Sqrt(norm); return true; } else { normal = Vector3f.Up; return false; } }
// create cylinder private static void CreateCylinder(ref MeshBuilder Builder, int n, double r1, double r2, double h) { // parameters bool uppercap = r1 > 0.0; bool lowercap = r2 > 0.0; int m = (uppercap ? 1 : 0) + (lowercap ? 1 : 0); r1 = Math.Abs(r1); r2 = Math.Abs(r2); double ns = h >= 0.0 ? 1.0 : -1.0; // initialization int v = Builder.Vertices.Length; Array.Resize<World.Vertex>(ref Builder.Vertices, v + 2 * n); Vector3f[] Normals = new Vector3f[2 * n]; double d = 2.0 * Math.PI / (double)n; double g = 0.5 * h; double t = 0.0; double a = h != 0.0 ? Math.Atan((r2 - r1) / h) : 0.0; double cosa = Math.Cos(a); double sina = Math.Sin(a); // vertices and normals for (int i = 0; i < n; i++) { double dx = Math.Cos(t); double dz = Math.Sin(t); double lx = dx * r2; double lz = dz * r2; double ux = dx * r1; double uz = dz * r1; Builder.Vertices[v + 2 * i + 0].Coordinates = new Vector3D(ux, g, uz); Builder.Vertices[v + 2 * i + 1].Coordinates = new Vector3D(lx, -g, lz); double nx = dx * ns, ny = 0.0, nz = dz * ns; double sx, sy, sz; World.Cross(nx, ny, nz, 0.0, 1.0, 0.0, out sx, out sy, out sz); World.Rotate(ref nx, ref ny, ref nz, sx, sy, sz, cosa, sina); Normals[2 * i + 0] = new Vector3f((float)nx, (float)ny, (float)nz); Normals[2 * i + 1] = new Vector3f((float)nx, (float)ny, (float)nz); t += d; } // faces int f = Builder.Faces.Length; Array.Resize<World.MeshFace>(ref Builder.Faces, f + n + m); for (int i = 0; i < n; i++) { Builder.Faces[f + i].Flags = 0; int i0 = (2 * i + 2) % (2 * n); int i1 = (2 * i + 3) % (2 * n); int i2 = 2 * i + 1; int i3 = 2 * i; Builder.Faces[f + i].Vertices = new World.MeshFaceVertex[] { new World.MeshFaceVertex(v + i0, Normals[i0]), new World.MeshFaceVertex(v + i1, Normals[i1]), new World.MeshFaceVertex(v + i2, Normals[i2]), new World.MeshFaceVertex(v + i3, Normals[i3]) }; } for (int i = 0; i < m; i++) { Builder.Faces[f + n + i].Vertices = new World.MeshFaceVertex[n]; for (int j = 0; j < n; j++) { if (i == 0 & lowercap) { // lower cap Builder.Faces[f + n + i].Vertices[j] = new World.MeshFaceVertex(v + 2 * j + 1); } else { // upper cap Builder.Faces[f + n + i].Vertices[j] = new World.MeshFaceVertex(v + 2 * (n - j - 1)); } } } }
// process structure private static bool ProcessStructure(string FileName, Structure Structure, out ObjectManager.StaticObject Object, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; Object = new ObjectManager.StaticObject(); Object.Mesh.Faces = new World.MeshFace[] { }; Object.Mesh.Materials = new World.MeshMaterial[] { }; Object.Mesh.Vertices = new World.Vertex[] { }; // file for (int i = 0; i < Structure.Data.Length; i++) { Structure f = Structure.Data[i] as Structure; if (f == null) { Debug.AddMessage(Debug.MessageType.Error, false, "Top-level inlined arguments are invalid in x object file " + FileName); return false; } switch (f.Name) { case "Mesh": { // mesh if (f.Data.Length < 4) { Debug.AddMessage(Debug.MessageType.Error, false, "Mesh is expected to have at least 4 arguments in x object file " + FileName); return false; } else if (!(f.Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nVertices is expected to be a DWORD in Mesh in x object file " + FileName); return false; } else if (!(f.Data[1] is Structure[])) { Debug.AddMessage(Debug.MessageType.Error, false, "vertices[nVertices] is expected to be a Vector array in Mesh in x object file " + FileName); return false; } else if (!(f.Data[2] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaces is expected to be a DWORD in Mesh in x object file " + FileName); return false; } else if (!(f.Data[3] is Structure[])) { Debug.AddMessage(Debug.MessageType.Error, false, "faces[nFaces] is expected to be a MeshFace array in Mesh in x object file " + FileName); return false; } int nVertices = (int)f.Data[0]; if (nVertices < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nVertices is expected to be non-negative in Mesh in x object file " + FileName); return false; } Structure[] vertices = (Structure[])f.Data[1]; if (nVertices != vertices.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nVertices does not match with the length of array vertices in Mesh in x object file " + FileName); return false; } int nFaces = (int)f.Data[2]; if (nFaces < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaces is expected to be non-negative in Mesh in x object file " + FileName); return false; } Structure[] faces = (Structure[])f.Data[3]; if (nFaces != faces.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaces does not match with the length of array faces in Mesh in x object file " + FileName); return false; } // collect vertices World.Vertex[] Vertices = new World.Vertex[nVertices]; for (int j = 0; j < nVertices; j++) { if (vertices[j].Name != "Vector") { Debug.AddMessage(Debug.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to be of template Vertex in Mesh in x object file " + FileName); return false; } else if (vertices[j].Data.Length != 3) { Debug.AddMessage(Debug.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to have 3 arguments in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "x is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "y is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[2] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "z is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } double x = (double)vertices[j].Data[0]; double y = (double)vertices[j].Data[1]; double z = (double)vertices[j].Data[2]; Vertices[j].Coordinates = new Vector3D(x, y, z); } // collect faces int[][] Faces = new int[nFaces][]; Vector3f[][] FaceNormals = new Vector3f[nFaces][]; int[] FaceMaterials = new int[nFaces]; for (int j = 0; j < nFaces; j++) { FaceMaterials[j] = -1; } for (int j = 0; j < nFaces; j++) { if (faces[j].Name != "MeshFace") { Debug.AddMessage(Debug.MessageType.Error, false, "faces[" + j.ToString(Culture) + "] is expected to be of template MeshFace in Mesh in x object file " + FileName); return false; } else if (faces[j].Data.Length != 2) { Debug.AddMessage(Debug.MessageType.Error, false, "face[" + j.ToString(Culture) + "] is expected to have 2 arguments in Mesh in x object file " + FileName); return false; } else if (!(faces[j].Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(faces[j].Data[1] is int[])) { Debug.AddMessage(Debug.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } int nFaceVertexIndices = (int)faces[j].Data[0]; if (nFaceVertexIndices < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices is expected to be non-negative in MeshFace in Mesh in x object file " + FileName); return false; } int[] faceVertexIndices = (int[])faces[j].Data[1]; if (nFaceVertexIndices != faceVertexIndices.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } Faces[j] = new int[nFaceVertexIndices]; FaceNormals[j] = new Vector3f[nFaceVertexIndices]; for (int k = 0; k < nFaceVertexIndices; k++) { if (faceVertexIndices[k] < 0 | faceVertexIndices[k] >= nVertices) { Debug.AddMessage(Debug.MessageType.Error, false, "faceVertexIndices[" + k.ToString(Culture) + "] does not reference a valid vertex in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } Faces[j][k] = faceVertexIndices[k]; FaceNormals[j][k] = new Vector3f(0.0f, 0.0f, 0.0f); } } // collect additional templates Material[] Materials = new Material[] { }; for (int j = 4; j < f.Data.Length; j++) { Structure g = f.Data[j] as Structure; if (g == null) { Debug.AddMessage(Debug.MessageType.Error, false, "Unexpected inlined argument encountered in Mesh in x object file " + FileName); return false; } switch (g.Name) { case "MeshMaterialList": { // meshmateriallist if (g.Data.Length < 3) { Debug.AddMessage(Debug.MessageType.Error, false, "MeshMaterialList is expected to have at least 3 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nMaterials is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceIndexes is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(g.Data[2] is int[])) { Debug.AddMessage(Debug.MessageType.Error, false, "faceIndexes[nFaceIndexes] is expected to be a DWORD array in MeshMaterialList in Mesh in x object file " + FileName); return false; } int nMaterials = (int)g.Data[0]; if (nMaterials < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nMaterials is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName); return false; } int nFaceIndexes = (int)g.Data[1]; if (nFaceIndexes < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceIndexes is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (nFaceIndexes > nFaces) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceIndexes does not reference valid faces in MeshMaterialList in Mesh in x object file " + FileName); return false; } int[] faceIndexes = (int[])g.Data[2]; if (nFaceIndexes != faceIndexes.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceIndexes does not match with the length of array faceIndexes in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } for (int k = 0; k < nFaceIndexes; k++) { if (faceIndexes[k] < 0 | faceIndexes[k] >= nMaterials) { Debug.AddMessage(Debug.MessageType.Error, false, "faceIndexes[" + k.ToString(Culture) + "] does not reference a valid Material template in MeshMaterialList in Mesh in x object file " + FileName); return false; } } // collect material templates int mn = Materials.Length; Array.Resize<Material>(ref Materials, mn + nMaterials); for (int k = 0; k < nMaterials; k++) { Materials[mn + k].faceColor = new Color32(255, 255, 255, 255); Materials[mn + k].specularColor = new Color24(0, 0, 0); Materials[mn + k].emissiveColor = new Color24(0, 0, 0); Materials[mn + k].TextureFilename = null; } int MaterialIndex = mn; for (int k = 3; k < g.Data.Length; k++) { Structure h = g.Data[k] as Structure; if (h == null) { Debug.AddMessage(Debug.MessageType.Error, false, "Unexpected inlined argument encountered in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (h.Name != "Material") { Debug.AddMessage(Debug.MessageType.Error, false, "Material template expected in MeshMaterialList in Mesh in x object file " + FileName); return false; } else { // material if (h.Data.Length < 4) { Debug.AddMessage(Debug.MessageType.Error, false, "Material is expected to have at least 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[0] is Structure)) { Debug.AddMessage(Debug.MessageType.Error, false, "faceColor is expected to be a Color32 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "power is expected to be a float in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[2] is Structure)) { Debug.AddMessage(Debug.MessageType.Error, false, "specularColor is expected to be a Color32 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[3] is Structure)) { Debug.AddMessage(Debug.MessageType.Error, false, "emissiveColor is expected to be a Color32 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } Structure faceColor = (Structure)h.Data[0]; Structure specularColor = (Structure)h.Data[2]; Structure emissiveColor = (Structure)h.Data[3]; double red, green, blue, alpha; // collect face color if (faceColor.Name != "Color32") { Debug.AddMessage(Debug.MessageType.Error, false, "faceColor is expected to be a Color32 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (faceColor.Data.Length != 4) { Debug.AddMessage(Debug.MessageType.Error, false, "faceColor is expected to have 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[2] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[3] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "alpha is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)faceColor.Data[0]; green = (double)faceColor.Data[1]; blue = (double)faceColor.Data[2]; alpha = (double)faceColor.Data[3]; if (red < 0.0 | red > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } if (alpha < 0.0 | alpha > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "alpha is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); alpha = alpha < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].faceColor = new Color32((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue), (byte)Math.Round(255.0 * alpha)); // collect specular color if (specularColor.Name != "Color24") { Debug.AddMessage(Debug.MessageType.Error, false, "specularColor is expected to be a Color24 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (specularColor.Data.Length != 3) { Debug.AddMessage(Debug.MessageType.Error, false, "specularColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[2] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)specularColor.Data[0]; green = (double)specularColor.Data[1]; blue = (double)specularColor.Data[2]; if (red < 0.0 | red > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].specularColor = new Color24((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue)); // collect emissive color if (emissiveColor.Name != "Color24") { Debug.AddMessage(Debug.MessageType.Error, false, "emissiveColor is expected to be a Color32 in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (emissiveColor.Data.Length != 3) { Debug.AddMessage(Debug.MessageType.Error, false, "emissiveColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[2] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)emissiveColor.Data[0]; green = (double)emissiveColor.Data[1]; blue = (double)emissiveColor.Data[2]; if (red < 0.0 | red > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Debug.AddMessage(Debug.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].emissiveColor = new Color24((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue)); // collect additional templates for (int l = 4; l < h.Data.Length; l++) { Structure e = h.Data[l] as Structure; if (e == null) { Debug.AddMessage(Debug.MessageType.Error, false, "Unexpected inlined argument encountered in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } switch (e.Name) { case "TextureFilename": { // texturefilename if (e.Data.Length != 1) { Debug.AddMessage(Debug.MessageType.Error, false, "filename is expected to have 1 argument in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(e.Data[0] is string)) { Debug.AddMessage(Debug.MessageType.Error, false, "filename is expected to be a string in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } string filename = (string)e.Data[0]; if (OpenBveApi.Path.ContainsInvalidPathChars(filename)) { Debug.AddMessage(Debug.MessageType.Error, false, "filename contains illegal characters in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), filename); if (System.IO.File.Exists(File)) { Materials[MaterialIndex].TextureFilename = File; } else { Debug.AddMessage(Debug.MessageType.Error, true, "The texture file " + File + " could not be found in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); } } } break; default: // unknown Debug.AddMessage(Debug.MessageType.Warning, false, "Unsupported template " + e.Name + " encountered in MeshMaterialList in Mesh in x object file " + FileName); break; } } // finish MaterialIndex++; } } if (MaterialIndex != mn + nMaterials) { Debug.AddMessage(Debug.MessageType.Error, false, "nMaterials does not match the number of Material templates encountered in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } // assign materials for (int k = 0; k < nFaceIndexes; k++) { FaceMaterials[k] = faceIndexes[k]; } if (nMaterials != 0) { for (int k = 0; k < nFaces; k++) { if (FaceMaterials[k] == -1) { FaceMaterials[k] = 0; } } } } break; case "MeshTextureCoords": { // meshtexturecoords if (g.Data.Length != 2) { Debug.AddMessage(Debug.MessageType.Error, false, "MeshTextureCoords is expected to have 2 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nTextureCoords is expected to be a DWORD in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is Structure[])) { Debug.AddMessage(Debug.MessageType.Error, false, "textureCoords[nTextureCoords] is expected to be a Coords2d array in MeshTextureCoords in Mesh in x object file " + FileName); return false; } int nTextureCoords = (int)g.Data[0]; Structure[] textureCoords = (Structure[])g.Data[1]; if (nTextureCoords < 0 | nTextureCoords > nVertices) { Debug.AddMessage(Debug.MessageType.Error, false, "nTextureCoords does not reference valid vertices in MeshTextureCoords in Mesh in x object file " + FileName); return false; } for (int k = 0; k < nTextureCoords; k++) { if (textureCoords[k].Name != "Coords2d") { Debug.AddMessage(Debug.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to be a Coords2d in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (textureCoords[k].Data.Length != 2) { Debug.AddMessage(Debug.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(textureCoords[k].Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "u is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(textureCoords[k].Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "v is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName); return false; } double u = (double)textureCoords[k].Data[0]; double v = (double)textureCoords[k].Data[1]; Vertices[k].TextureCoordinates = new Vector2f((float)u, (float)v); } } break; case "MeshNormals": { // meshnormals if (g.Data.Length != 4) { Debug.AddMessage(Debug.MessageType.Error, false, "MeshNormals is expected to have 4 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is Structure[])) { Debug.AddMessage(Debug.MessageType.Error, false, "normals is expected to be a Vector array in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[2] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[3] is Structure[])) { Debug.AddMessage(Debug.MessageType.Error, false, "faceNormals is expected to be a MeshFace array in MeshNormals in Mesh in x object file " + FileName); return false; } int nNormals = (int)g.Data[0]; if (nNormals < 0) { Debug.AddMessage(Debug.MessageType.Error, false, "nNormals is expected to be non-negative in MeshNormals in Mesh in x object file " + FileName); return false; } Structure[] normals = (Structure[])g.Data[1]; if (nNormals != normals.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nNormals does not match with the length of array normals in MeshNormals in Mesh in x object file " + FileName); return false; } int nFaceNormals = (int)g.Data[2]; if (nFaceNormals < 0 | nFaceNormals > nFaces) { Debug.AddMessage(Debug.MessageType.Error, false, "nNormals does not reference valid vertices in MeshNormals in Mesh in x object file " + FileName); return false; } Structure[] faceNormals = (Structure[])g.Data[3]; if (nFaceNormals != faceNormals.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceNormals does not match with the length of array faceNormals in MeshNormals in Mesh in x object file " + FileName); return false; } // collect normals Vector3f[] Normals = new Vector3f[nNormals]; for (int k = 0; k < nNormals; k++) { if (normals[k].Name != "Vector") { Debug.AddMessage(Debug.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to be of template Vertex in MeshNormals in Mesh in x object file " + FileName); return false; } else if (normals[k].Data.Length != 3) { Debug.AddMessage(Debug.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to have 3 arguments in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[0] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "x is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[1] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "y is expected to be a float in normals[" + k.ToString(Culture) + " ]in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[2] is double)) { Debug.AddMessage(Debug.MessageType.Error, false, "z is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } double x = (double)normals[k].Data[0]; double y = (double)normals[k].Data[1]; double z = (double)normals[k].Data[2]; World.Normalize(ref x, ref y, ref z); Normals[k] = new Vector3f((float)x, (float)y, (float)z); } // collect faces for (int k = 0; k < nFaceNormals; k++) { if (faceNormals[k].Name != "MeshFace") { Debug.AddMessage(Debug.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to be of template MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } else if (faceNormals[k].Data.Length != 2) { Debug.AddMessage(Debug.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(faceNormals[k].Data[0] is int)) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(faceNormals[k].Data[1] is int[])) { Debug.AddMessage(Debug.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } int nFaceVertexIndices = (int)faceNormals[k].Data[0]; if (nFaceVertexIndices < 0 | nFaceVertexIndices > Faces[k].Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices does not reference a valid vertex in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } int[] faceVertexIndices = (int[])faceNormals[k].Data[1]; if (nFaceVertexIndices != faceVertexIndices.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } for (int l = 0; l < nFaceVertexIndices; l++) { if (faceVertexIndices[l] < 0 | faceVertexIndices[l] >= nNormals) { Debug.AddMessage(Debug.MessageType.Error, false, "faceVertexIndices[" + l.ToString(Culture) + "] does not reference a valid normal in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } FaceNormals[k][l] = Normals[faceVertexIndices[l]]; } } } break; default: // unknown Debug.AddMessage(Debug.MessageType.Warning, false, "Unsupported template " + g.Name + " encountered in Mesh in x object file " + FileName); break; } } // default material if (Materials.Length == 0) { Materials = new Material[1]; Materials[0].faceColor = new Color32(255, 255, 255, 255); Materials[0].emissiveColor = new Color24(0, 0, 0); Materials[0].specularColor = new Color24(0, 0, 0); Materials[0].TextureFilename = null; for (int j = 0; j < nFaces; j++) { FaceMaterials[j] = 0; } } // create mesh int mf = Object.Mesh.Faces.Length; int mm = Object.Mesh.Materials.Length; int mv = Object.Mesh.Vertices.Length; Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + nFaces); Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Materials.Length); Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Vertices.Length); for (int j = 0; j < Materials.Length; j++) { bool emissive = Materials[j].emissiveColor.R != 0 | Materials[j].emissiveColor.G != 0 | Materials[j].emissiveColor.B != 0; bool transparent; if (Materials[j].TextureFilename != null) { Textures.RegisterTexture(Materials[j].TextureFilename, new OpenBveApi.Textures.TextureParameters(null, Color24.Black), out Object.Mesh.Materials[mm + j].DaytimeTexture); transparent = true; } else { Object.Mesh.Materials[mm + j].DaytimeTexture = null; transparent = false; } Object.Mesh.Materials[mm + j].Flags = (byte)((transparent ? World.MeshMaterial.TransparentColorMask : 0) | (emissive ? World.MeshMaterial.EmissiveColorMask : 0)); Object.Mesh.Materials[mm + j].Color = Materials[j].faceColor; Object.Mesh.Materials[mm + j].TransparentColor = new Color24(0, 0, 0); Object.Mesh.Materials[mm + j].EmissiveColor = Materials[j].emissiveColor; Object.Mesh.Materials[mm + j].NighttimeTexture = null; Object.Mesh.Materials[mm + j].BlendMode = World.MeshMaterialBlendMode.Normal; Object.Mesh.Materials[mm + j].GlowAttenuationData = 0; } for (int j = 0; j < nFaces; j++) { Object.Mesh.Faces[mf + j].Material = (ushort)FaceMaterials[j]; Object.Mesh.Faces[mf + j].Vertices = new World.MeshFaceVertex[Faces[j].Length]; for (int k = 0; k < Faces[j].Length; k++) { Object.Mesh.Faces[mf + j].Vertices[mv + k] = new World.MeshFaceVertex(mv + Faces[j][k], FaceNormals[j][k]); } } for (int j = 0; j < Vertices.Length; j++) { Object.Mesh.Vertices[mv + j] = Vertices[j]; } break; } case "Header": break; default: // unknown Debug.AddMessage(Debug.MessageType.Warning, false, "Unsupported template " + f.Name + " encountered in x object file " + FileName); break; } } // return World.CreateNormals(ref Object.Mesh); return true; }