//Parses an XML dynamic lighting definition public static bool ReadLightingXML(string fileName) { //Prep Renderer.LightDefinitions = new Renderer.LightDefinition[0]; //The current XML file to load XmlDocument currentXML = new XmlDocument(); //Load the object's XML file try { currentXML.Load(fileName); } catch { return(false); } bool defined = false; //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/Brightness"); //Check this file actually contains OpenBVE light definition nodes if (DocumentNodes != null) { foreach (XmlNode n in DocumentNodes) { Renderer.LightDefinition currentLight = new Renderer.LightDefinition(); if (n.HasChildNodes) { bool tf = false, al = false, dl = false, ld = false, cb = false; string ts = null; foreach (XmlNode c in n.ChildNodes) { string[] Arguments = c.InnerText.Split(','); switch (c.Name.ToLowerInvariant()) { case "cablighting": double b; if (NumberFormats.TryParseDoubleVb6(Arguments[0].Trim(), out b)) { cb = true; } if (b > 255 || b < 0) { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " is not a valid brightness value in file " + fileName); currentLight.CabBrightness = 255; break; } currentLight.CabBrightness = b; break; case "time": double t; if (Interface.TryParseTime(Arguments[0].Trim(), out t)) { currentLight.Time = (int)t; tf = true; //Keep back for error report later ts = Arguments[0]; } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not parse to a valid time in file " + fileName); } break; case "ambientlight": if (Arguments.Length == 3) { double R, G, B; if (NumberFormats.TryParseDoubleVb6(Arguments[0].Trim(), out R) && NumberFormats.TryParseDoubleVb6(Arguments[1].Trim(), out G) && NumberFormats.TryParseDoubleVb6(Arguments[2].Trim(), out B)) { currentLight.AmbientColor = new Color24((byte)R, (byte)G, (byte)B); al = true; } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not parse to a valid color in file " + fileName); } } else { if (Arguments.Length == 1) { if (Color24.TryParseHexColor(Arguments[0], out currentLight.DiffuseColor)) { al = true; break; } } Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not contain three arguments in file " + fileName); } break; case "directionallight": if (Arguments.Length == 3) { double R, G, B; if (NumberFormats.TryParseDoubleVb6(Arguments[0].Trim(), out R) && NumberFormats.TryParseDoubleVb6(Arguments[1].Trim(), out G) && NumberFormats.TryParseDoubleVb6(Arguments[2].Trim(), out B)) { currentLight.DiffuseColor = new Color24((byte)R, (byte)G, (byte)B); dl = true; } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not parse to a valid color in file " + fileName); } } else { if (Arguments.Length == 1) { if (Color24.TryParseHexColor(Arguments[0], out currentLight.DiffuseColor)) { dl = true; break; } } Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not contain three arguments in file " + fileName); } break; case "cartesianlightdirection": case "lightdirection": if (Arguments.Length == 3) { double X, Y, Z; if (NumberFormats.TryParseDoubleVb6(Arguments[0].Trim(), out X) && NumberFormats.TryParseDoubleVb6(Arguments[1].Trim(), out Y) && NumberFormats.TryParseDoubleVb6(Arguments[2].Trim(), out Z)) { currentLight.LightPosition = new Vector3(X, Y, Z); ld = true; } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not parse to a valid direction in file " + fileName); } } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not contain three arguments in file " + fileName); } break; case "sphericallightdirection": if (Arguments.Length == 2) { double theta, phi; if (NumberFormats.TryParseDoubleVb6(Arguments[0].Trim(), out theta) && NumberFormats.TryParseDoubleVb6(Arguments[1].Trim(), out phi)) { currentLight.LightPosition = new Vector3(Math.Cos(theta) * Math.Sin(phi), -Math.Sin(theta), Math.Cos(theta) * Math.Cos(phi)); ld = true; } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not parse to a valid direction in file " + fileName); } } else { Interface.AddMessage(Interface.MessageType.Error, false, c.InnerText + " does not contain two arguments in file " + fileName); } break; } } //We want to be able to add a completely default light element, but not one that's not been defined in the XML properly if (tf || al || ld || dl || cb) { //HACK: No way to break out of the first loop and continue with the second, so we've got to use a variable bool Break = false; int l = Renderer.LightDefinitions.Length; for (int i = 0; i > l; i++) { if (Renderer.LightDefinitions[i].Time == currentLight.Time) { Break = true; if (ts == null) { Interface.AddMessage(Interface.MessageType.Error, false, "Multiple undefined times were encountered in file " + fileName); } else { Interface.AddMessage(Interface.MessageType.Error, false, "Duplicate time found: " + ts + " in file " + fileName); } break; } } if (Break) { continue; } //We've got there, so now figure out where to add the new light into our list of light definitions int t = 0; if (l == 1) { t = currentLight.Time > Renderer.LightDefinitions[0].Time ? 1 : 0; } else if (l > 1) { for (int i = 1; i < l; i++) { t = i + 1; if (currentLight.Time > Renderer.LightDefinitions[i - 1].Time && currentLight.Time < Renderer.LightDefinitions[i].Time) { break; } } } //Resize array defined = true; Array.Resize(ref Renderer.LightDefinitions, l + 1); if (t == l) { //Straight insert at the end of the array Renderer.LightDefinitions[l] = currentLight; } else { for (int u = t; u < l; u++) { //Otherwise, shift all elements to compensate Renderer.LightDefinitions[u + 1] = Renderer.LightDefinitions[u]; } Renderer.LightDefinitions[t] = currentLight; } } } } } } //We couldn't find any valid XML, so return false return(defined); }
/// <summary>Loads a Loksim3D object from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="LoadMode">The texture load mode.</param> /// <param name="Rotation">The rotation to be applied.</param> /// <returns>The object loaded.</returns> internal static ObjectManager.StaticObject ReadObject(string FileName, ObjectManager.ObjectLoadMode LoadMode, Vector3 Rotation) { string BaseDir = System.IO.Path.GetDirectoryName(FileName); XmlDocument currentXML = new XmlDocument(); //May need to be changed to use de-DE System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; //Initialise the 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[] { }; MeshBuilder Builder = new MeshBuilder(); Vector3[] Normals = new Vector3[4]; bool PropertiesFound = false; World.Vertex[] tempVertices = new World.Vertex[0]; Vector3[] tempNormals = new Vector3[0]; Color24 transparentColor = new Color24(); string tday = null; string transtex = null; string tnight = null; bool TransparencyUsed = false; bool TransparentTypSet = false; bool FirstPxTransparent = false; Color24 FirstPxColor = new Color24(); bool Face2 = false; int TextureWidth = 0; int TextureHeight = 0; if (File.Exists(FileName)) { try { currentXML.Load(FileName); } catch { return(null); } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Loksim3D object " + FileName + " does not exist."); return(null); } //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT"); //Check this file actually contains Loksim3D object nodes if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.HasChildNodes) { foreach (XmlNode node in outerNode.ChildNodes) { //I think there should only be one properties node?? //Need better format documentation if (node.Name == "Props" && PropertiesFound == false) { if (node.Attributes != null) { //Our node has child nodes, therefore this properties node should be valid //Needs better validation PropertiesFound = true; foreach (XmlAttribute attribute in node.Attributes) { switch (attribute.Name) { //Sets the texture //Loksim3D objects only support daytime textures case "Texture": tday = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory); if (!File.Exists(tday)) { Interface.AddMessage(Interface.MessageType.Warning, true, "Ls3d Texture file " + attribute.Value + " not found."); break; } try { using (Bitmap TextureInformation = new Bitmap(tday)) { TextureWidth = TextureInformation.Width; TextureHeight = TextureInformation.Height; Color color = TextureInformation.GetPixel(0, 0); FirstPxColor = new Color24(color.R, color.G, color.B); } } catch { Interface.AddMessage(Interface.MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName); tday = null; } break; //Defines whether the texture uses transparency //May be omitted case "Transparent": if (TransparentTypSet) { //Appears to be ignored with TransparentTyp set continue; } if (attribute.Value == "TRUE") { TransparencyUsed = true; transparentColor = new Color24(0, 0, 0); } break; case "TransTexture": if (string.IsNullOrEmpty(attribute.Value)) { //Empty.... continue; } transtex = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory); if (!File.Exists(transtex)) { Interface.AddMessage(Interface.MessageType.Error, true, "AlphaTexture " + transtex + " could not be found in file " + FileName); transtex = null; } break; //Sets the transparency type case "TransparentTyp": TransparentTypSet = true; switch (attribute.Value) { case "0": //Transparency is disabled TransparencyUsed = false; break; case "1": //Transparency is solid black TransparencyUsed = true; transparentColor = new Color24(0, 0, 0); FirstPxTransparent = false; break; case "2": //Transparency is the color at Pixel 1,1 TransparencyUsed = true; FirstPxTransparent = true; break; case "3": case "4": //This is used when transparency is used with an alpha bitmap TransparencyUsed = false; FirstPxTransparent = false; break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Unrecognised transparency type " + attribute.Value + " detected in " + attribute.Name + " in Loksim3D object file " + FileName); break; } break; //Sets whether the rears of the faces are to be drawn case "Drawrueckseiten": if (attribute.Value == "TRUE" || string.IsNullOrEmpty(attribute.Value)) { Face2 = true; } else { Face2 = false; } break; /* * MISSING PROPERTIES: * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system. * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color? * FileAuthor * FileInfo * FilePicture */ } } } } //The point command is eqivilant to a vertex else if (node.Name == "Point" && node.HasChildNodes) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Vertex double vx = 0.0, vy = 0.0, vz = 0.0; //Normals double nx = 0.0, ny = 0.0, nz = 0.0; foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { //Sets the vertex normals case "Normal": string[] NormalPoints = attribute.Value.Split(';'); if (!double.TryParse(NormalPoints[0], out nx)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nX in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(NormalPoints[1], out ny)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nY in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(NormalPoints[2], out nz)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument nZ in " + attribute.Name + " in Loksim3D object file " + FileName); } break; //Sets the vertex 3D co-ordinates case "Vekt": string[] VertexPoints = attribute.Value.Split(';'); if (!double.TryParse(VertexPoints[0], out vx)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vX in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(VertexPoints[1], out vy)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument yY in " + attribute.Name + " in Loksim3D object file " + FileName); } if (!double.TryParse(VertexPoints[2], out vz)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid argument vZ in " + attribute.Name + " in Loksim3D object file " + FileName); } break; } } World.Normalize(ref nx, ref ny, ref nz); //Resize temp arrays Array.Resize <World.Vertex>(ref tempVertices, tempVertices.Length + 1); Array.Resize <Vector3>(ref tempNormals, tempNormals.Length + 1); //Add vertex and normals to temp array tempVertices[tempVertices.Length - 1].Coordinates = new Vector3(vx, vy, vz); tempNormals[tempNormals.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz); Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1); while (Builder.Vertices.Length >= Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new Vector3(vx, vy, vz); Normals[Builder.Vertices.Length - 1] = new Vector3((float)nx, (float)ny, (float)nz); } } } //The Flaeche command creates a face else if (node.Name == "Flaeche" && node.HasChildNodes) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Defines the verticies in this face //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates if (childNode.Attributes["Points"] != null) { string[] Verticies = childNode.Attributes["Points"].Value.Split(';'); int f = Builder.Faces.Length; //Add 1 to the length of the face array Array.Resize <World.MeshFace>(ref Builder.Faces, f + 1); Builder.Faces[f] = new World.MeshFace(); //Create the vertex array for the face Builder.Faces[f].Vertices = new World.MeshFaceVertex[Verticies.Length]; while (Builder.Vertices.Length > Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } //Run through the vertices list and grab from the temp array int smallestX = TextureWidth; int smallestY = TextureHeight; for (int j = 0; j < Verticies.Length; j++) { //This is the position of the vertex in the temp array int currentVertex; if (!int.TryParse(Verticies[j], out currentVertex)) { Interface.AddMessage(Interface.MessageType.Error, false, Verticies[j] + " does not parse to a valid Vertex in " + node.Name + " in Loksim3D object file " + FileName); continue; } //Add one to the actual vertex array Array.Resize <World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1); //Set coordinates Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = tempVertices[currentVertex].Coordinates; //Set the vertex index Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1); //Set the normals Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex]; //Now deal with the texture //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex if (childNode.Attributes["Texture"] != null) { string[] TextureCoords = childNode.Attributes["Texture"].Value.Split(';'); Vector2 currentCoords; float OpenBVEWidth; float OpenBVEHeight; string[] splitCoords = TextureCoords[j].Split(','); if (!float.TryParse(splitCoords[0], out OpenBVEWidth)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid texture width specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (!float.TryParse(splitCoords[1], out OpenBVEHeight)) { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid texture height specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (OpenBVEWidth <= smallestX && OpenBVEHeight <= smallestY) { //Clamp texture width and height smallestX = (int)OpenBVEWidth; smallestY = (int)OpenBVEHeight; } if (TextureWidth != 0 && TextureHeight != 0) { //Calculate openBVE co-ords currentCoords.X = (OpenBVEWidth / TextureWidth); currentCoords.Y = (OpenBVEHeight / TextureHeight); } else { //Invalid, so just return zero currentCoords.X = 0; currentCoords.Y = 0; } Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords; } } if (Face2) { //Add face2 flag if required Builder.Faces[f].Flags = (byte)World.MeshFace.Face2Mask; } } } } } } } } } //Apply rotation /* * NOTES: * No rotation order is specified * The rotation string in a .l3dgrp file is ordered Y, Z, X ??? Can't find a good reason for this ??? * Rotations must still be performed in X,Y,Z order to produce correct results */ if (Rotation.Z != 0.0) { //Convert to radians Rotation.Z *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 1, 0, 0, Rotation.Z); } if (Rotation.X != 0.0) { //This is actually the Y-Axis rotation //Convert to radians Rotation.X *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 0, 1, 0, Rotation.X); } if (Rotation.Y != 0.0) { //This is actually the X-Axis rotation //Convert to radians Rotation.Y *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 0, 0, 1, Rotation.Y); } //These files appear to only have one texture defined //Therefore import later- May have to change if (File.Exists(tday)) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].DaytimeTexture = tday; Builder.Materials[j].TransparencyTexture = transtex; Builder.Materials[j].NighttimeTexture = tnight; } } if (TransparencyUsed == true) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = FirstPxTransparent ? FirstPxColor : transparentColor; Builder.Materials[j].TransparentColorUsed = true; } } } ApplyMeshBuilder(ref Object, Builder, LoadMode, false, false); World.CreateNormals(ref Object.Mesh); return(Object); }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Color24 rawColor = (Color24)value; return(System.Windows.Media.Color.FromRgb(rawColor.R, rawColor.G, rawColor.B)); }
// --- functions --- /// <summary>Gets the texture from this origin.</summary> /// <param name="texture">Receives the texture.</param> /// <returns>Whether the texture could be obtained successfully.</returns> internal override bool GetTexture(out OpenBveApi.Textures.Texture texture) { Bitmap bitmap = this.Bitmap; Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); /* * If the bitmap format is not already 32-bit BGRA, * then convert it to 32-bit BGRA. * */ Color24[] p = null; if (bitmap.PixelFormat != PixelFormat.Format32bppArgb && bitmap.PixelFormat != PixelFormat.Format24bppRgb) { /* Only store the color palette data for * textures using a restricted palette * With a large number of textures loaded at * once, this can save a decent chunk of memory * */ p = new Color24[bitmap.Palette.Entries.Length]; for (int i = 0; i < bitmap.Palette.Entries.Length; i++) { p[i] = bitmap.Palette.Entries[i]; } } if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { Bitmap compatibleBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); Graphics graphics = Graphics.FromImage(compatibleBitmap); graphics.DrawImage(bitmap, rect, rect, GraphicsUnit.Pixel); graphics.Dispose(); bitmap = compatibleBitmap; } /* * Extract the raw bitmap data. * */ BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat); if (data.Stride == 4 * data.Width) { /* * Copy the data from the bitmap * to the array in BGRA format. * */ byte[] raw = new byte[data.Stride * data.Height]; System.Runtime.InteropServices.Marshal.Copy(data.Scan0, raw, 0, data.Stride * data.Height); bitmap.UnlockBits(data); int width = bitmap.Width; int height = bitmap.Height; /* * Change the byte order from BGRA to RGBA. * */ for (int i = 0; i < raw.Length; i += 4) { byte temp = raw[i]; raw[i] = raw[i + 2]; raw[i + 2] = temp; } texture = new OpenBveApi.Textures.Texture(width, height, 32, raw, p); texture = texture.ApplyParameters(this.Parameters); return(true); } /* * The stride is invalid. This indicates that the * CLI either does not implement the conversion to * 32-bit BGRA correctly, or that the CLI has * applied additional padding that we do not * support. * */ bitmap.UnlockBits(data); texture = null; return(false); }
public void SetLightDiffuse(Color24 LightDiffuse) { GL.Uniform3(UniformLayout.LightDiffuse, LightDiffuse.R / 255.0f, LightDiffuse.G / 255.0f, LightDiffuse.B / 255.0f); }
public void SetMaterialEmission(Color24 MaterialEmission) { GL.Uniform3(UniformLayout.MaterialEmission, MaterialEmission.R / 255.0f, MaterialEmission.G / 255.0f, MaterialEmission.B / 255.0f); }
// load texture rgba private static void LoadTextureRGBAforData(Bitmap Bitmap, Color24 TransparentColor, byte TransparentColorUsed, int TextureIndex) { try { // load bytes int Width, Height, Stride; byte[] Data; { if (Textures[TextureIndex].ClipWidth == 0) { Textures[TextureIndex].ClipWidth = Bitmap.Width; } if (Textures[TextureIndex].ClipHeight == 0) { Textures[TextureIndex].ClipHeight = Bitmap.Height; } Width = Textures[TextureIndex].ClipWidth; Height = Textures[TextureIndex].ClipHeight; Bitmap c = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); Graphics g = Graphics.FromImage(c); Rectangle dst = new Rectangle(0, 0, Width, Height); Rectangle src = new Rectangle(Textures[TextureIndex].ClipLeft, Textures[TextureIndex].ClipTop, Textures[TextureIndex].ClipWidth, Textures[TextureIndex].ClipHeight); g.DrawImage(Bitmap, dst, src, GraphicsUnit.Pixel); g.Dispose(); BitmapData d = c.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, c.PixelFormat); Stride = d.Stride; Data = new byte[Stride * Height]; System.Runtime.InteropServices.Marshal.Copy(d.Scan0, Data, 0, Stride * Height); c.UnlockBits(d); c.Dispose(); } // load mode if (Textures[TextureIndex].LoadMode == TextureLoadMode.Bve4SignalGlow) { // bve 4 signal glow int p = 0, pn = Stride - 4 * Width; byte tr, tg, tb; if (TransparentColorUsed != 0) { tr = TransparentColor.R; tg = TransparentColor.G; tb = TransparentColor.B; } else { tr = 0; tg = 0; tb = 0; } // invert lightness byte[] Temp = new byte[Stride * Height]; for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { if (Data[p] == tb & Data[p + 1] == tg & Data[p + 2] == tr) { Temp[p] = 0; Temp[p + 1] = 0; Temp[p + 2] = 0; } else if (Data[p] != 255 | Data[p + 1] != 255 | Data[p + 2] != 255) { int b = Data[p], g = Data[p + 1], r = Data[p + 2]; InvertLightness(ref r, ref g, ref b); int l = r >= g & r >= b ? r : g >= b ? g : b; Temp[p] = (byte)(l * b / 255); Temp[p + 1] = (byte)(l * g / 255); Temp[p + 2] = (byte)(l * r / 255); } else { Temp[p] = Data[p]; Temp[p + 1] = Data[p + 1]; Temp[p + 2] = Data[p + 2]; } p += 4; } p += pn; } p = 0; // blur the image and multiply by lightness int s = 4; int n = Stride - (2 * s + 1 << 2); for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { int q = p - s * (Stride + 4); int r = 0, g = 0, b = 0, c = 0; for (int yr = y - s; yr <= y + s; yr++) { if (yr >= 0 & yr < Height) { for (int xr = x - s; xr <= x + s; xr++) { if (xr >= 0 & xr < Width) { b += (int)Temp[q]; g += (int)Temp[q + 1]; r += (int)Temp[q + 2]; c++; } q += 4; } q += n; } else { q += Stride; } } if (c == 0) { Data[p] = 0; Data[p + 1] = 0; Data[p + 2] = 0; Data[p + 3] = 255; } else { r /= c; g /= c; b /= c; int l = r >= g & r >= b ? r : g >= b ? g : b; Data[p] = (byte)(l * b / 255); Data[p + 1] = (byte)(l * g / 255); Data[p + 2] = (byte)(l * r / 255); Data[p + 3] = 255; } p += 4; } p += pn; } Textures[TextureIndex].Transparency = TextureTransparencyMode.None; Textures[TextureIndex].DontAllowUnload = true; } else if (TransparentColorUsed != 0) { // transparent color int p = 0, pn = Stride - 4 * Width; byte tr = TransparentColor.R; byte tg = TransparentColor.G; byte tb = TransparentColor.B; bool used = false; // check if alpha is actually used int y; for (y = 0; y < Height; y++) { int x; for (x = 0; x < Width; x++) { if (Data[p + 3] != 255) { break; } p += 4; } if (x < Width) { break; } p += pn; } if (y == Height) { Textures[TextureIndex].Transparency = TextureTransparencyMode.TransparentColor; } // duplicate color data from adjacent pixels p = 0; pn = Stride - 4 * Width; for (y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { if (Data[p] == tb & Data[p + 1] == tg & Data[p + 2] == tr) { used = true; if (x == 0) { int q = p; int v; for (v = y; v < Height; v++) { int u; for (u = v == y ? x + 1 : 0; u < Width; u++) { if (Data[q] != tb | Data[q + 1] != tg | Data[q + 2] != tr) { Data[p] = Data[q]; Data[p + 1] = Data[q + 1]; Data[p + 2] = Data[q + 2]; Data[p + 3] = 0; break; } q += 4; } if (u < Width) { break; } else { q += pn; } } if (v == Height) { if (y == 0) { Data[p] = 128; Data[p + 1] = 128; Data[p + 2] = 128; Data[p + 3] = 0; } else { Data[p] = Data[p - Stride]; Data[p + 1] = Data[p - Stride + 1]; Data[p + 2] = Data[p - Stride + 2]; Data[p + 3] = 0; } } } else { Data[p] = Data[p - 4]; Data[p + 1] = Data[p - 3]; Data[p + 2] = Data[p - 2]; Data[p + 3] = 0; } } p += 4; } p += pn; } // transparent color is not actually used if (!used & Textures[TextureIndex].Transparency == TextureTransparencyMode.TransparentColor) { Textures[TextureIndex].Transparency = TextureTransparencyMode.None; } } else if (Textures[TextureIndex].Transparency == TextureTransparencyMode.Alpha) { // check if alpha is actually used int p = 0, pn = Stride - 4 * Width; int y; for (y = 0; y < Height; y++) { int x; for (x = 0; x < Width; x++) { if (Data[p + 3] != 255) { break; } p += 4; } if (x < Width) { break; } p += pn; } if (y == Height) { Textures[TextureIndex].Transparency = TextureTransparencyMode.None; } } // non-power of two int TargetWidth = Interface.RoundToPowerOfTwo(Width); int TargetHeight = Interface.RoundToPowerOfTwo(Height); if (TargetWidth != Width | TargetHeight != Height) { Bitmap b = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); BitmapData d = b.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, b.PixelFormat); System.Runtime.InteropServices.Marshal.Copy(Data, 0, d.Scan0, d.Stride * d.Height); b.UnlockBits(d); Bitmap c = new Bitmap(TargetWidth, TargetHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb); Graphics g = Graphics.FromImage(c); g.DrawImage(b, 0, 0, TargetWidth, TargetHeight); g.Dispose(); b.Dispose(); d = c.LockBits(new Rectangle(0, 0, TargetWidth, TargetHeight), ImageLockMode.ReadOnly, c.PixelFormat); Stride = d.Stride; Data = new byte[Stride * TargetHeight]; System.Runtime.InteropServices.Marshal.Copy(d.Scan0, Data, 0, Stride * TargetHeight); c.UnlockBits(d); c.Dispose(); } Textures[TextureIndex].Width = TargetWidth; Textures[TextureIndex].Height = TargetHeight; Textures[TextureIndex].Data = Data; } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, "Internal error in TextureManager.cs::LoadTextureRGBAForData: " + ex.Message); throw; } }
private byte[] GetRawBitmapData(Bitmap bitmap, out int width, out int height, out Color24[] p) { p = null; if (EnabledHacks.ReduceTransparencyColorDepth && (bitmap.PixelFormat != PixelFormat.Format32bppArgb && bitmap.PixelFormat != PixelFormat.Format24bppRgb)) { /* * Our source bitmap is *not* a 256 color bitmap but has been made for BVE2 / BVE4. * These process transparency in 256 colors (even if the file is 24bpp / 32bpp), thus: * Let's open the bitmap, and attempt to construct a reduced color pallette * If our bitmap contains more than 256 unique colors, we break out of the loop * and assume that this file is an incorrect match * * WARNING NOTE: * Unfortunately, we can't just pull out the color pallette from the bitmap, as there * is no native way to remove unused entries. We therefore have to itinerate through * each pixel..... * This is *slow* so use with caution! * */ BitmapData inputData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); HashSet <Color24> reducedPallette = new HashSet <Color24>(); unsafe { byte *bmpPtr = (byte *)inputData.Scan0.ToPointer(); int ic, oc, r; if (bitmap.PixelFormat == PixelFormat.Format24bppRgb) { for (r = 0; r < inputData.Height; r++) { for (ic = oc = 0; oc < inputData.Width; ic += 3, oc++) { byte blue = bmpPtr[r * inputData.Stride + ic]; byte green = bmpPtr[r * inputData.Stride + ic + 1]; byte red = bmpPtr[r * inputData.Stride + ic + 2]; Color24 c = new Color24(red, green, blue); if (!reducedPallette.Contains(c)) { reducedPallette.Add(c); } if (reducedPallette.Count > 256) { //as breaking out of nested loops is a pita goto EndLoop; } } } } else { for (r = 0; r < inputData.Height; r++) { for (ic = oc = 0; oc < inputData.Width; ic += 4, oc++) { byte blue = bmpPtr[r * inputData.Stride + ic]; byte green = bmpPtr[r * inputData.Stride + ic + 1]; byte red = bmpPtr[r * inputData.Stride + ic + 2]; Color24 c = new Color24(red, green, blue); if (!reducedPallette.Contains(c)) { reducedPallette.Add(c); } if (reducedPallette.Count > 256) { //as breaking out of nested loops is a pita goto EndLoop; } } } } } p = reducedPallette.ToArray(); EndLoop: bitmap.UnlockBits(inputData); } if (bitmap.PixelFormat != PixelFormat.Format32bppArgb && bitmap.PixelFormat != PixelFormat.Format24bppRgb && p == null) { /* Otherwise, only store the color palette data for * textures using a restricted palette * With a large number of textures loaded at * once, this can save a decent chunk of memory */ p = new Color24[bitmap.Palette.Entries.Length]; for (int i = 0; i < bitmap.Palette.Entries.Length; i++) { p[i] = bitmap.Palette.Entries[i]; } } Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); /* * If the bitmap format is not already 32-bit BGRA, * then convert it to 32-bit BGRA. */ if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { Bitmap compatibleBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); Graphics graphics = Graphics.FromImage(compatibleBitmap); graphics.DrawImage(bitmap, rect, rect, GraphicsUnit.Pixel); graphics.Dispose(); bitmap.Dispose(); bitmap = compatibleBitmap; } /* * Extract the raw bitmap data. */ BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat); if (data.Stride == 4 * data.Width) { /* * Copy the data from the bitmap * to the array in BGRA format. */ byte[] raw = new byte[data.Stride * data.Height]; System.Runtime.InteropServices.Marshal.Copy(data.Scan0, raw, 0, data.Stride * data.Height); bitmap.UnlockBits(data); width = bitmap.Width; height = bitmap.Height; /* * Change the byte order from BGRA to RGBA. */ for (int i = 0; i < raw.Length; i += 4) { byte temp = raw[i]; raw[i] = raw[i + 2]; raw[i + 2] = temp; } return(raw); } /* * The stride is invalid. This indicates that the * CLI either does not implement the conversion to * 32-bit BGRA correctly, or that the CLI has * applied additional padding that we do not * support. */ bitmap.UnlockBits(data); bitmap.Dispose(); CurrentHost.ReportProblem(ProblemType.InvalidOperation, "Invalid stride encountered."); width = 0; height = 0; return(null); }
private static void MeshBuilder(ref StaticObject obj, ref MeshBuilder builder, AssimpNET.X.Mesh mesh) { if (builder.Vertices.Count != 0) { builder.Apply(ref obj); builder = new MeshBuilder(Plugin.currentHost); } int nVerts = mesh.Positions.Count; if (nVerts == 0) { //Some null objects contain an empty mesh Plugin.currentHost.AddMessage(MessageType.Warning, false, "nVertices should be greater than zero in Mesh " + mesh.Name); } for (int i = 0; i < nVerts; i++) { builder.Vertices.Add(new Vertex(mesh.Positions[i])); } int nFaces = mesh.PosFaces.Count; for (int i = 0; i < nFaces; i++) { int fVerts = mesh.PosFaces[i].Indices.Count; if (nFaces == 0) { throw new Exception("fVerts must be greater than zero"); } MeshFace f = new MeshFace(); f.Vertices = new MeshFaceVertex[fVerts]; for (int j = 0; j < fVerts; j++) { f.Vertices[j].Index = (ushort)mesh.PosFaces[i].Indices[j]; } builder.Faces.Add(f); } int nMaterials = mesh.Materials.Count; int nFaceIndices = mesh.FaceMaterials.Count; for (int i = 0; i < nFaceIndices; i++) { int fMaterial = (int)mesh.FaceMaterials[i]; MeshFace f = builder.Faces[i]; f.Material = (ushort)(fMaterial + 1); builder.Faces[i] = f; } for (int i = 0; i < nMaterials; i++) { int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new OpenBveApi.Objects.Material(); builder.Materials[m].Color = new Color32((byte)(255 * mesh.Materials[i].Diffuse.R), (byte)(255 * mesh.Materials[i].Diffuse.G), (byte)(255 * mesh.Materials[i].Diffuse.B), (byte)(255 * mesh.Materials[i].Diffuse.A)); double mPower = mesh.Materials[i].SpecularExponent; //TODO: Unsure what this does... Color24 mSpecular = new Color24((byte)mesh.Materials[i].Specular.R, (byte)mesh.Materials[i].Specular.G, (byte)mesh.Materials[i].Specular.B); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * mesh.Materials[i].Emissive.R), (byte)(255 * mesh.Materials[i].Emissive.G), (byte)(255 * mesh.Materials[i].Emissive.B)); builder.Materials[m].Flags |= MaterialFlags.Emissive; //TODO: Check exact behaviour if (Plugin.EnabledHacks.BlackTransparency) { builder.Materials[m].TransparentColor = Color24.Black; //TODO: Check, also can we optimise which faces have the transparent color set? builder.Materials[m].Flags |= MaterialFlags.TransparentColor; } if (mesh.Materials[i].Textures.Count > 0) { string texturePath = mesh.Materials[i].Textures[0].Name; // If the specified file name is an absolute path, make it the file name only. // Some object files specify absolute paths. // And BVE4/5 doesn't allow textures to be placed in a different directory than the object file. if (Plugin.EnabledHacks.BveTsHacks && OpenBveApi.Path.IsAbsolutePath(texturePath)) { texturePath = texturePath.Split('/', '\\').Last(); } try { builder.Materials[m].DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, texturePath); } catch (Exception e) { Plugin.currentHost.AddMessage(MessageType.Error, false, $"Texture file path {texturePath} in file {currentFile} has the problem: {e.Message}"); builder.Materials[m].DaytimeTexture = null; } if (builder.Materials[m].DaytimeTexture != null && !System.IO.File.Exists(builder.Materials[m].DaytimeTexture)) { Plugin.currentHost.AddMessage(MessageType.Error, true, "Texure " + builder.Materials[m].DaytimeTexture + " was not found in file " + currentFile); builder.Materials[m].DaytimeTexture = null; } } } if (mesh.TexCoords.Length > 0 && mesh.TexCoords[0] != null) { int nCoords = mesh.TexCoords[0].Count; for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = new Vector2(mesh.TexCoords[0][i].X, mesh.TexCoords[0][i].Y); } } int nNormals = mesh.Normals.Count; Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = new Vector3(mesh.Normals[i].X, mesh.Normals[i].Y, mesh.Normals[i].Z); normals[i].Normalize(); } int nFaceNormals = mesh.NormFaces.Count; if (nFaceNormals > builder.Faces.Count) { throw new Exception("nFaceNormals must match the number of faces in the mesh"); } for (int i = 0; i < nFaceNormals; i++) { int nVertexNormals = mesh.NormFaces[i].Indices.Count; if (nVertexNormals > builder.Faces[i].Vertices.Length) { throw new Exception("nVertexNormals must match the number of verticies in the face"); } for (int j = 0; j < nVertexNormals; j++) { builder.Faces[i].Vertices[j].Normal = normals[(int)mesh.NormFaces[i].Indices[j]]; } } int nVertexColors = (int)mesh.NumColorSets; for (int i = 0; i < nVertexColors; i++) { builder.Vertices[i] = new ColoredVertex((Vertex)builder.Vertices[i], new Color128(mesh.Colors[0][i].R, mesh.Colors[0][i].G, mesh.Colors[0][i].B, mesh.Colors[0][i].A)); } }
internal static int RegisterTexture(string FileName, Color24 TransparentColor, byte TransparentColorUsed, TextureWrapMode WrapModeX, TextureWrapMode WrapModeY, bool DontAllowUnload) { return(RegisterTexture(FileName, TransparentColor, TransparentColorUsed, TextureLoadMode.Normal, WrapModeX, WrapModeY, DontAllowUnload, 0, 0, 0, 0)); }
// --- apply transparent color --- /// <summary>Applies a transparent color onto a texture.</summary> /// <param name="texture">The original texture.</param> /// <param name="color">The transparent color, or a null reference.</param> /// <returns>The texture with the transparent color applied.</returns> /// <exception cref="System.NotSupportedException">Raised when the number of bits per pixel in the texture is not supported.</exception> internal static Texture ApplyTransparentColor(Texture texture, Color24?color) { if (color == null) { return(texture); } if (texture.Palette != null && texture.CompatibleTransparencyMode == true) { switch (texture.Palette.Length) { case 0: //ignore if no reduced pallette break; default: Color24 c = (Color24)color; color = GetClosestColor(texture.Palette, c); break; } } if (texture.BitsPerPixel == 32) { int width = texture.Width; int height = texture.Height; byte[] source = texture.Bytes; byte[] target = new byte[4 * width * height]; byte r = color.Value.R; byte g = color.Value.G; byte b = color.Value.B; if (source[0] == r && source[1] == g && source[2] == b) { target[0] = 128; target[1] = 128; target[2] = 128; target[3] = 0; } else { target[0] = source[0]; target[1] = source[1]; target[2] = source[2]; target[3] = source[3]; } for (int i = 4; i < source.Length; i += 4) { if (source[i] == r && source[i + 1] == g && source[i + 2] == b) { target[i + 0] = target[i - 4]; target[i + 1] = target[i - 3]; target[i + 2] = target[i - 2]; target[i + 3] = 0; } else { target[i + 0] = source[i + 0]; target[i + 1] = source[i + 1]; target[i + 2] = source[i + 2]; target[i + 3] = source[i + 3]; } } return(new Texture(width, height, 32, target, texture.Palette)); } throw new NotSupportedException(); }
private static int GetDiff(Color24 c1, Color24 c2) { return((int)System.Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R) + (c1.G - c2.G) * (c1.G - c2.G) + (c1.B - c2.B) * (c1.B - c2.B))); }
private RawMapEntity LoadMapEntityFromStream(string chunkFourCC, EndianBinaryReader reader, MapEntityDataDescriptor template) { RawMapEntity obj = new RawMapEntity(); obj.Fields = new PropertyCollection(); obj.FourCC = chunkFourCC; // We're going to examine the Template's properties and load based on the current template type. for (int i = 0; i < template.Fields.Count; i++) { var templateProperty = template.Fields[i]; string propertyName = templateProperty.FieldName; PropertyType type = templateProperty.FieldType; object value = null; switch (type) { case PropertyType.FixedLengthString: value = reader.ReadString(templateProperty.Length).Trim(new[] { '\0' }); break; case PropertyType.String: value = reader.ReadStringUntil('\0'); break; case PropertyType.Byte: value = reader.ReadByte(); break; case PropertyType.Short: value = reader.ReadInt16(); break; case PropertyType.Int32BitField: case PropertyType.Int32: value = reader.ReadInt32(); break; case PropertyType.Float: value = reader.ReadSingle(); break; case PropertyType.Vector3: value = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); break; case PropertyType.Vector2: value = new Vector2(reader.ReadSingle(), reader.ReadSingle()); break; case PropertyType.Enum: byte enumIndexBytes = reader.ReadByte(); // ToDo: Resolve to actual Enum later. value = enumIndexBytes; break; case PropertyType.ObjectReference: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. value = (int)reader.ReadByte(); break; case PropertyType.ObjectReferenceShort: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. value = (int)reader.ReadUInt16(); break; case PropertyType.ObjectReferenceArray: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. var refList = new BindingList <object>(); for (int refArray = 0; refArray < templateProperty.Length; refArray++) { refList.Add((int)reader.ReadByte()); } value = refList; break; case PropertyType.XYRotation: { Vector3 eulerAngles = new Vector3(); for (int f = 0; f < 2; f++) { eulerAngles[f] = (reader.ReadInt16() * (180 / 32786f)); } Quaternion xAxis = Quaternion.FromAxisAngle(new Vector3(1, 0, 0), eulerAngles.X * MathE.Deg2Rad); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), eulerAngles.Y * MathE.Deg2Rad); // Swizzling to the ZYX order seems to be the right one. Quaternion finalRot = yAxis * xAxis; value = finalRot; } break; case PropertyType.XYZRotation: { Vector3 eulerAngles = new Vector3(); for (int f = 0; f < 3; f++) { eulerAngles[f] = (reader.ReadInt16() * (180 / 32786f)); } Quaternion xAxis = Quaternion.FromAxisAngle(new Vector3(1, 0, 0), eulerAngles.X * MathE.Deg2Rad); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), eulerAngles.Y * MathE.Deg2Rad); Quaternion zAxis = Quaternion.FromAxisAngle(new Vector3(0, 0, 1), eulerAngles.Z * MathE.Deg2Rad); // Swizzling to the ZYX order seems to be the right one. Quaternion finalRot = zAxis * yAxis * xAxis; value = finalRot; } break; case PropertyType.YRotation: { float yRotation = reader.ReadInt16() * (180 / 32786f); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), yRotation * MathE.Deg2Rad); value = yAxis; } break; case PropertyType.Color32: value = new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Color24: value = new Color24(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Vector3Byte: type = PropertyType.Vector3Byte; value = new Vector3(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Bits: value = (byte)reader.ReadBits(templateProperty.Length); break; } // This... this could get dicy. If the template we just read was a "Name" then we now have the technical name (and value) // of the object. We can then search for the MapObjectDataDescriptor that matches the technical name, and then edit the // remaining fields. However, this gets somewhat dicey, because we're modifying the length of the Fields array for templates // while iterating through it. However, the Name field always comes before any of the fields we'd want to modify, we're going to // do an in-place replacement of the fields (since Fields.Count will increase) and then we get free loading of the complex templates // without later post-processing them. if (templateProperty.FieldName == "Name") { // See if our template list has a complex version of this file, otherwise grab the default. MapObjectDataDescriptor complexDescriptor = m_editorCore.Templates.MapObjectDataDescriptors.Find(x => x.FourCC == chunkFourCC && x.TechnicalName == templateProperty.FieldName); if (complexDescriptor == null) { complexDescriptor = m_editorCore.Templates.DefaultMapObjectDataDescriptor; } // Determine which field we need to remove, and then insert in the other fields (in order) where it used to be. foreach (var fieldToReplace in complexDescriptor.DataOverrides) { for (int k = 0; k < template.Fields.Count; k++) { if (template.Fields[k].FieldName == fieldToReplace.ParameterName) { // Remove the old field. template.Fields.RemoveAt(k); // Now insert the new fields starting at the location of the one we just replaced. template.Fields.InsertRange(k, fieldToReplace.Values); break; } } } } Property instanceProp = new Property(templateProperty.FieldName, type, value); obj.Fields.Properties.Add(instanceProp); } return(obj); }
// render scene internal void RenderScene(double TimeElapsed) { // initialize ResetOpenGlState(); if (OptionWireFrame) { if (Program.CurrentRoute.CurrentFog.Start < Program.CurrentRoute.CurrentFog.End) { const float fogDistance = 600.0f; float n = (fogDistance - Program.CurrentRoute.CurrentFog.Start) / (Program.CurrentRoute.CurrentFog.End - Program.CurrentRoute.CurrentFog.Start); float cr = n * inv255 * Program.CurrentRoute.CurrentFog.Color.R; float cg = n * inv255 * Program.CurrentRoute.CurrentFog.Color.G; float cb = n * inv255 * Program.CurrentRoute.CurrentFog.Color.B; GL.ClearColor(cr, cg, cb, 1.0f); } else { GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); } } GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); UpdateViewport(ViewportChangeMode.ChangeToScenery); // set up camera CurrentViewMatrix = Matrix4D.LookAt(Vector3.Zero, new Vector3(Camera.AbsoluteDirection.X, Camera.AbsoluteDirection.Y, -Camera.AbsoluteDirection.Z), new Vector3(Camera.AbsoluteUp.X, Camera.AbsoluteUp.Y, -Camera.AbsoluteUp.Z)); GL.Light(LightName.Light0, LightParameter.Position, new[] { (float)Lighting.OptionLightPosition.X, (float)Lighting.OptionLightPosition.Y, (float)-Lighting.OptionLightPosition.Z, 0.0f }); // fog double fd = Program.CurrentRoute.NextFog.TrackPosition - Program.CurrentRoute.PreviousFog.TrackPosition; if (fd != 0.0) { float fr = (float)((World.CameraTrackFollower.TrackPosition - Program.CurrentRoute.PreviousFog.TrackPosition) / fd); float frc = 1.0f - fr; Program.CurrentRoute.CurrentFog.Start = Program.CurrentRoute.PreviousFog.Start * frc + Program.CurrentRoute.NextFog.Start * fr; Program.CurrentRoute.CurrentFog.End = Program.CurrentRoute.PreviousFog.End * frc + Program.CurrentRoute.NextFog.End * fr; Program.CurrentRoute.CurrentFog.Color.R = (byte)(Program.CurrentRoute.PreviousFog.Color.R * frc + Program.CurrentRoute.NextFog.Color.R * fr); Program.CurrentRoute.CurrentFog.Color.G = (byte)(Program.CurrentRoute.PreviousFog.Color.G * frc + Program.CurrentRoute.NextFog.Color.G * fr); Program.CurrentRoute.CurrentFog.Color.B = (byte)(Program.CurrentRoute.PreviousFog.Color.B * frc + Program.CurrentRoute.NextFog.Color.B * fr); } else { Program.CurrentRoute.CurrentFog = Program.CurrentRoute.PreviousFog; } // render background GL.Disable(EnableCap.DepthTest); Program.CurrentRoute.UpdateBackground(TimeElapsed, Game.CurrentInterface != Game.InterfaceType.Normal); events.Render(Camera.AbsolutePosition); // fog float aa = Program.CurrentRoute.CurrentFog.Start; float bb = Program.CurrentRoute.CurrentFog.End; if (aa < bb & aa < Program.CurrentRoute.CurrentBackground.BackgroundImageDistance) { OptionFog = true; Fog.Start = aa; Fog.End = bb; Fog.Color = Program.CurrentRoute.CurrentFog.Color; SetFogForImmediateMode(); } else { OptionFog = false; } // world layer // opaque face ResetOpenGlState(); foreach (FaceState face in VisibleObjects.OpaqueFaces) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } // alpha face ResetOpenGlState(); VisibleObjects.SortPolygonsInAlphaFaces(); if (Interface.CurrentOptions.TransparencyMode == TransparencyMode.Performance) { SetBlendFunc(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.DepthMask(false); foreach (FaceState face in VisibleObjects.AlphaFaces) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } else { UnsetBlendFunc(); SetAlphaFunc(AlphaFunction.Equal, 1.0f); GL.DepthMask(true); foreach (FaceState face in VisibleObjects.AlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Normal && face.Object.Prototype.Mesh.Materials[face.Face.Material].GlowAttenuationData == 0) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].Color.A == 255) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } } SetBlendFunc(); SetAlphaFunc(AlphaFunction.Less, 1.0f); GL.DepthMask(false); bool additive = false; foreach (FaceState face in VisibleObjects.AlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Additive) { if (!additive) { UnsetAlphaFunc(); additive = true; } if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } else { if (additive) { SetAlphaFunc(); additive = false; } if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } } // motion blur ResetOpenGlState(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.Disable(EnableCap.DepthTest); GL.DepthMask(false); OptionLighting = false; if (Interface.CurrentOptions.MotionBlur != MotionBlurMode.None) { MotionBlur.RenderFullscreen(Interface.CurrentOptions.MotionBlur, FrameRate, Math.Abs(Camera.CurrentSpeed)); } // overlay layer OptionFog = false; UpdateViewport(ViewportChangeMode.ChangeToCab); CurrentViewMatrix = Matrix4D.LookAt(Vector3.Zero, new Vector3(Camera.AbsoluteDirection.X, Camera.AbsoluteDirection.Y, -Camera.AbsoluteDirection.Z), new Vector3(Camera.AbsoluteUp.X, Camera.AbsoluteUp.Y, -Camera.AbsoluteUp.Z)); if (Camera.CurrentRestriction == CameraRestrictionMode.NotAvailable || Camera.CurrentRestriction == CameraRestrictionMode.Restricted3D) { ResetOpenGlState(); // TODO: inserted GL.Clear(ClearBufferMask.DepthBufferBit); OptionLighting = true; Color24 prevOptionAmbientColor = Lighting.OptionAmbientColor; Color24 prevOptionDiffuseColor = Lighting.OptionDiffuseColor; Lighting.OptionAmbientColor = new Color24(178, 178, 178); Lighting.OptionDiffuseColor = new Color24(178, 178, 178); GL.Light(LightName.Light0, LightParameter.Ambient, new[] { inv255 * 178, inv255 * 178, inv255 * 178, 1.0f }); GL.Light(LightName.Light0, LightParameter.Diffuse, new[] { inv255 * 178, inv255 * 178, inv255 * 178, 1.0f }); // overlay opaque face foreach (FaceState face in VisibleObjects.OverlayOpaqueFaces) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } // overlay alpha face ResetOpenGlState(); VisibleObjects.SortPolygonsInOverlayAlphaFaces(); if (Interface.CurrentOptions.TransparencyMode == TransparencyMode.Performance) { SetBlendFunc(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.DepthMask(false); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } else { UnsetBlendFunc(); SetAlphaFunc(AlphaFunction.Equal, 1.0f); GL.DepthMask(true); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Normal && face.Object.Prototype.Mesh.Materials[face.Face.Material].GlowAttenuationData == 0) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].Color.A == 255) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } } SetBlendFunc(); SetAlphaFunc(AlphaFunction.Less, 1.0f); GL.DepthMask(false); bool additive = false; foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Additive) { if (!additive) { UnsetAlphaFunc(); additive = true; } if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } else { if (additive) { SetAlphaFunc(); additive = false; } if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } } Lighting.OptionAmbientColor = prevOptionAmbientColor; Lighting.OptionDiffuseColor = prevOptionDiffuseColor; Lighting.Initialize(); } else { /* * Render 2D Cab * This is actually an animated object generated on the fly and held in memory */ ResetOpenGlState(); OptionLighting = false; SetBlendFunc(); UnsetAlphaFunc(); GL.Disable(EnableCap.DepthTest); GL.DepthMask(false); VisibleObjects.SortPolygonsInOverlayAlphaFaces(); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (Interface.CurrentOptions.IsUseNewRenderer) { DefaultShader.Activate(); ResetShader(DefaultShader); RenderFace(DefaultShader, face); } else { RenderFaceImmediateMode(face); } } } // render touch OptionLighting = false; Touch.RenderScene(); // render overlays ResetOpenGlState(); UnsetAlphaFunc(); GL.Disable(EnableCap.DepthTest); overlays.Render(TimeElapsed); OptionLighting = true; }
// render scene internal void RenderScene(double TimeElapsed, double RealTimeElapsed) { ReleaseResources(); // initialize ResetOpenGlState(); if (OptionWireFrame) { if (Program.CurrentRoute.CurrentFog.Start < Program.CurrentRoute.CurrentFog.End) { const float fogDistance = 600.0f; float n = (fogDistance - Program.CurrentRoute.CurrentFog.Start) / (Program.CurrentRoute.CurrentFog.End - Program.CurrentRoute.CurrentFog.Start); float cr = n * inv255 * Program.CurrentRoute.CurrentFog.Color.R; float cg = n * inv255 * Program.CurrentRoute.CurrentFog.Color.G; float cb = n * inv255 * Program.CurrentRoute.CurrentFog.Color.B; GL.ClearColor(cr, cg, cb, 1.0f); } else { GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); } } GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); UpdateViewport(ViewportChangeMode.ChangeToScenery); // set up camera CurrentViewMatrix = Matrix4D.LookAt(Vector3.Zero, new Vector3(Camera.AbsoluteDirection.X, Camera.AbsoluteDirection.Y, -Camera.AbsoluteDirection.Z), new Vector3(Camera.AbsoluteUp.X, Camera.AbsoluteUp.Y, -Camera.AbsoluteUp.Z)); TransformedLightPosition = new Vector3(Lighting.OptionLightPosition.X, Lighting.OptionLightPosition.Y, -Lighting.OptionLightPosition.Z); TransformedLightPosition.Transform(CurrentViewMatrix); if (!AvailableNewRenderer) { GL.Light(LightName.Light0, LightParameter.Position, new[] { (float)TransformedLightPosition.X, (float)TransformedLightPosition.Y, (float)TransformedLightPosition.Z, 0.0f }); } Lighting.OptionLightingResultingAmount = (Lighting.OptionAmbientColor.R + Lighting.OptionAmbientColor.G + Lighting.OptionAmbientColor.B) / 480.0f; if (Lighting.OptionLightingResultingAmount > 1.0f) { Lighting.OptionLightingResultingAmount = 1.0f; } // fog double fd = Program.CurrentRoute.NextFog.TrackPosition - Program.CurrentRoute.PreviousFog.TrackPosition; if (fd != 0.0) { float fr = (float)((CameraTrackFollower.TrackPosition - Program.CurrentRoute.PreviousFog.TrackPosition) / fd); float frc = 1.0f - fr; Program.CurrentRoute.CurrentFog.Start = Program.CurrentRoute.PreviousFog.Start * frc + Program.CurrentRoute.NextFog.Start * fr; Program.CurrentRoute.CurrentFog.End = Program.CurrentRoute.PreviousFog.End * frc + Program.CurrentRoute.NextFog.End * fr; Program.CurrentRoute.CurrentFog.Color.R = (byte)(Program.CurrentRoute.PreviousFog.Color.R * frc + Program.CurrentRoute.NextFog.Color.R * fr); Program.CurrentRoute.CurrentFog.Color.G = (byte)(Program.CurrentRoute.PreviousFog.Color.G * frc + Program.CurrentRoute.NextFog.Color.G * fr); Program.CurrentRoute.CurrentFog.Color.B = (byte)(Program.CurrentRoute.PreviousFog.Color.B * frc + Program.CurrentRoute.NextFog.Color.B * fr); if (!Program.CurrentRoute.CurrentFog.IsLinear) { Program.CurrentRoute.CurrentFog.Density = (byte)(Program.CurrentRoute.PreviousFog.Density * frc + Program.CurrentRoute.NextFog.Density * fr); } } else { Program.CurrentRoute.CurrentFog = Program.CurrentRoute.PreviousFog; } // render background GL.Disable(EnableCap.DepthTest); Program.CurrentRoute.UpdateBackground(TimeElapsed, Program.Renderer.CurrentInterface != InterfaceType.Normal); events.Render(Camera.AbsolutePosition); // fog float aa = Program.CurrentRoute.CurrentFog.Start; float bb = Program.CurrentRoute.CurrentFog.End; if (aa < bb & aa < Program.CurrentRoute.CurrentBackground.BackgroundImageDistance) { OptionFog = true; Fog.Start = aa; Fog.End = bb; Fog.Color = Program.CurrentRoute.CurrentFog.Color; Fog.Density = Program.CurrentRoute.CurrentFog.Density; Fog.IsLinear = Program.CurrentRoute.CurrentFog.IsLinear; Fog.SetForImmediateMode(); } else { OptionFog = false; } // world layer // opaque face if (AvailableNewRenderer) { //Setup the shader for rendering the scene DefaultShader.Activate(); if (OptionLighting) { DefaultShader.SetIsLight(true); DefaultShader.SetLightPosition(TransformedLightPosition); DefaultShader.SetLightAmbient(Lighting.OptionAmbientColor); DefaultShader.SetLightDiffuse(Lighting.OptionDiffuseColor); DefaultShader.SetLightSpecular(Lighting.OptionSpecularColor); DefaultShader.SetLightModel(Lighting.LightModel); } if (OptionFog) { DefaultShader.SetIsFog(true); DefaultShader.SetFog(Fog); } DefaultShader.SetTexture(0); DefaultShader.SetCurrentProjectionMatrix(CurrentProjectionMatrix); } ResetOpenGlState(); foreach (FaceState face in VisibleObjects.OpaqueFaces) { face.Draw(); } // alpha face ResetOpenGlState(); VisibleObjects.SortPolygonsInAlphaFaces(); if (Interface.CurrentOptions.TransparencyMode == TransparencyMode.Performance) { SetBlendFunc(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.DepthMask(false); foreach (FaceState face in VisibleObjects.AlphaFaces) { face.Draw(); } } else { UnsetBlendFunc(); SetAlphaFunc(AlphaFunction.Equal, 1.0f); GL.DepthMask(true); foreach (FaceState face in VisibleObjects.AlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Normal && face.Object.Prototype.Mesh.Materials[face.Face.Material].GlowAttenuationData == 0) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].Color.A == 255) { face.Draw(); } } } SetBlendFunc(); SetAlphaFunc(AlphaFunction.Less, 1.0f); GL.DepthMask(false); bool additive = false; foreach (FaceState face in VisibleObjects.AlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Additive) { if (!additive) { UnsetAlphaFunc(); additive = true; } face.Draw(); } else { if (additive) { SetAlphaFunc(); additive = false; } face.Draw(); } } } // motion blur ResetOpenGlState(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.Disable(EnableCap.DepthTest); GL.DepthMask(false); OptionLighting = false; if (Interface.CurrentOptions.MotionBlur != MotionBlurMode.None) { DefaultShader.Deactivate(); MotionBlur.RenderFullscreen(Interface.CurrentOptions.MotionBlur, FrameRate, Math.Abs(Camera.CurrentSpeed)); } // overlay layer OptionFog = false; UpdateViewport(ViewportChangeMode.ChangeToCab); if (AvailableNewRenderer) { /* * We must reset the shader between overlay and world layers for correct lighting results. * Additionally, the viewport change updates the projection matrix */ DefaultShader.Activate(); ResetShader(DefaultShader); DefaultShader.SetCurrentProjectionMatrix(CurrentProjectionMatrix); } CurrentViewMatrix = Matrix4D.LookAt(Vector3.Zero, new Vector3(Camera.AbsoluteDirection.X, Camera.AbsoluteDirection.Y, -Camera.AbsoluteDirection.Z), new Vector3(Camera.AbsoluteUp.X, Camera.AbsoluteUp.Y, -Camera.AbsoluteUp.Z)); if (Camera.CurrentRestriction == CameraRestrictionMode.NotAvailable || Camera.CurrentRestriction == CameraRestrictionMode.Restricted3D) { ResetOpenGlState(); // TODO: inserted GL.Clear(ClearBufferMask.DepthBufferBit); OptionLighting = true; Color24 prevOptionAmbientColor = Lighting.OptionAmbientColor; Color24 prevOptionDiffuseColor = Lighting.OptionDiffuseColor; Lighting.OptionAmbientColor = Color24.LightGrey; Lighting.OptionDiffuseColor = Color24.LightGrey; if (AvailableNewRenderer) { DefaultShader.SetIsLight(true); TransformedLightPosition = new Vector3(Lighting.OptionLightPosition.X, Lighting.OptionLightPosition.Y, -Lighting.OptionLightPosition.Z); DefaultShader.SetLightPosition(TransformedLightPosition); DefaultShader.SetLightAmbient(Lighting.OptionAmbientColor); DefaultShader.SetLightDiffuse(Lighting.OptionDiffuseColor); DefaultShader.SetLightSpecular(Lighting.OptionSpecularColor); DefaultShader.SetLightModel(Lighting.LightModel); } else { GL.Light(LightName.Light0, LightParameter.Ambient, new[] { inv255 * 178, inv255 * 178, inv255 * 178, 1.0f }); GL.Light(LightName.Light0, LightParameter.Diffuse, new[] { inv255 * 178, inv255 * 178, inv255 * 178, 1.0f }); } // overlay opaque face foreach (FaceState face in VisibleObjects.OverlayOpaqueFaces) { face.Draw(); } // overlay alpha face ResetOpenGlState(); VisibleObjects.SortPolygonsInOverlayAlphaFaces(); if (Interface.CurrentOptions.TransparencyMode == TransparencyMode.Performance) { SetBlendFunc(); SetAlphaFunc(AlphaFunction.Greater, 0.0f); GL.DepthMask(false); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { face.Draw(); } } else { UnsetBlendFunc(); SetAlphaFunc(AlphaFunction.Equal, 1.0f); GL.DepthMask(true); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Normal && face.Object.Prototype.Mesh.Materials[face.Face.Material].GlowAttenuationData == 0) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].Color.A == 255) { face.Draw(); } } } SetBlendFunc(); SetAlphaFunc(AlphaFunction.Less, 1.0f); GL.DepthMask(false); bool additive = false; foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { if (face.Object.Prototype.Mesh.Materials[face.Face.Material].BlendMode == MeshMaterialBlendMode.Additive) { if (!additive) { UnsetAlphaFunc(); additive = true; } face.Draw(); } else { if (additive) { SetAlphaFunc(); additive = false; } face.Draw(); } } } Lighting.OptionAmbientColor = prevOptionAmbientColor; Lighting.OptionDiffuseColor = prevOptionDiffuseColor; Lighting.Initialize(); } else { /* * Render 2D Cab * This is actually an animated object generated on the fly and held in memory */ ResetOpenGlState(); OptionLighting = false; if (AvailableNewRenderer) { DefaultShader.SetIsLight(false); } SetBlendFunc(); UnsetAlphaFunc(); GL.Disable(EnableCap.DepthTest); GL.DepthMask(false); VisibleObjects.SortPolygonsInOverlayAlphaFaces(); foreach (FaceState face in VisibleObjects.OverlayAlphaFaces) { face.Draw(); } } if (AvailableNewRenderer) { /* * Must remember to de-activate at the end of the render sequence if in GL3 mode. * The overlays currently use immediate mode and do not work correctly with the shader active */ DefaultShader.Deactivate(); } // render touch OptionLighting = false; Touch.RenderScene(); // render overlays ResetOpenGlState(); UnsetAlphaFunc(); SetBlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); //FIXME: Remove when text switches between two renderer types GL.Disable(EnableCap.DepthTest); overlays.Render(RealTimeElapsed); OptionLighting = true; }
public void SetFogColor(Color24 FogColor) { GL.Uniform3(UniformLayout.FogColor, FogColor.R / 255.0f, FogColor.G / 255.0f, FogColor.B / 255.0f); }
internal static ObjectManager.StaticObject ReadObject(string FileName) { currentFolder = System.IO.Path.GetDirectoryName(FileName); currentFile = FileName; #if !DEBUG try { #endif ObjFileParser parser = new ObjFileParser(System.IO.File.ReadAllLines(currentFile), null, System.IO.Path.GetFileNameWithoutExtension(currentFile), currentFile); Model model = parser.GetModel(); ObjectManager.StaticObject obj = new ObjectManager.StaticObject(); MeshBuilder builder = new MeshBuilder(); List <Vertex> allVertices = new List <Vertex>(); foreach (var vertex in model.Vertices) { allVertices.Add(new Vertex(vertex.X, vertex.Y, vertex.Z)); } List <Vector2> allTexCoords = new List <Vector2>(); foreach (var texCoord in model.TextureCoord) { allTexCoords.Add(new Vector2(texCoord.X, texCoord.Y)); } List <Vector3> allNormals = new List <Vector3>(); foreach (var normal in model.Normals) { allNormals.Add(new Vector3(normal.X, normal.Y, normal.Z)); } foreach (AssimpNET.Obj.Mesh mesh in model.Meshes) { foreach (Face face in mesh.Faces) { int nVerts = face.Vertices.Count; if (nVerts == 0) { throw new Exception("nVertices must be greater than zero"); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = allVertices[(int)face.Vertices[i]]; } int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + 1); builder.Faces[f] = new MeshFace(); builder.Faces[f].Vertices = new MeshFaceVertex[nVerts]; for (int i = 0; i < nVerts; i++) { builder.Faces[f].Vertices[i].Index = (ushort)i; } builder.Faces[f].Material = 1; int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); uint materialIndex = mesh.MaterialIndex; if (materialIndex != AssimpNET.Obj.Mesh.NoMaterial) { AssimpNET.Obj.Material material = model.MaterialMap[model.MaterialLib[(int)materialIndex]]; builder.Materials[m].Color = new Color32((byte)(255 * material.Diffuse.R), (byte)(255 * material.Diffuse.G), (byte)(255 * material.Diffuse.B), (byte)(255 * material.Diffuse.A)); Color24 mSpecular = new Color24((byte)material.Specular.R, (byte)material.Specular.G, (byte)material.Specular.B); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * material.Emissive.R), (byte)(255 * material.Emissive.G), (byte)(255 * material.Emissive.B)); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = new Color24((byte)(255 * material.Transparent.R), (byte)(255 * material.Transparent.G), (byte)(255 * material.Transparent.B)); builder.Materials[m].TransparentColorUsed = true; if (material.Texture != null) { builder.Materials[m].DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, material.Texture); if (!System.IO.File.Exists(builder.Materials[m].DaytimeTexture)) { Interface.AddMessage(MessageType.Error, true, "Texure " + builder.Materials[m].DaytimeTexture + " was not found in file " + currentFile); builder.Materials[m].DaytimeTexture = null; } } } int nCoords = face.TexturCoords.Count; for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = allTexCoords[(int)face.TexturCoords[i]]; } int nNormals = face.Normals.Count; Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = allNormals[(int)face.Normals[i]]; normals[i].Normalize(); } for (int i = 0; i < nNormals; i++) { builder.Faces[0].Vertices[i].Normal = normals[i]; } builder.Apply(ref obj); builder = new MeshBuilder(); } } obj.Mesh.CreateNormals(); return(obj); #if !DEBUG } catch (Exception e) { Interface.AddMessage(MessageType.Error, false, e.Message + " in " + FileName); return(null); } #endif }
public void SetLightAmbient(Color24 LightAmbient) { GL.Uniform3(UniformLayout.LightAmbient, LightAmbient.R / 255.0f, LightAmbient.G / 255.0f, LightAmbient.B / 255.0f); }
private static void MeshBuilder(ref ObjectManager.StaticObject obj, ref MeshBuilder builder, AssimpNET.X.Mesh mesh) { if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(); } int nVerts = mesh.Positions.Count; if (nVerts == 0) { //Some null objects contain an empty mesh Interface.AddMessage(MessageType.Warning, false, "nVertices should be greater than zero in Mesh " + mesh.Name); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = new Vertex(mesh.Positions[i].X, mesh.Positions[i].Y, mesh.Positions[i].Z); } int nFaces = mesh.PosFaces.Count; int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + nFaces); for (int i = 0; i < nFaces; i++) { int fVerts = mesh.PosFaces[i].Indices.Count; if (nFaces == 0) { throw new Exception("fVerts must be greater than zero"); } builder.Faces[f + i] = new MeshFace(); builder.Faces[f + i].Vertices = new MeshFaceVertex[fVerts]; for (int j = 0; j < fVerts; j++) { builder.Faces[f + i].Vertices[j].Index = (ushort)mesh.PosFaces[i].Indices[j]; } } int nMaterials = mesh.Materials.Count; int nFaceIndices = mesh.FaceMaterials.Count; for (int i = 0; i < nFaceIndices; i++) { int fMaterial = (int)mesh.FaceMaterials[i]; builder.Faces[i].Material = (ushort)(fMaterial + 1); } for (int i = 0; i < nMaterials; i++) { int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); builder.Materials[m].Color = new Color32((byte)(255 * mesh.Materials[i].Diffuse.R), (byte)(255 * mesh.Materials[i].Diffuse.G), (byte)(255 * mesh.Materials[i].Diffuse.B), (byte)(255 * mesh.Materials[i].Diffuse.A)); double mPower = mesh.Materials[i].SpecularExponent; //TODO: Unsure what this does... Color24 mSpecular = new Color24((byte)mesh.Materials[i].Specular.R, (byte)mesh.Materials[i].Specular.G, (byte)mesh.Materials[i].Specular.B); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * mesh.Materials[i].Emissive.R), (byte)(255 * mesh.Materials[i].Emissive.G), (byte)(255 * mesh.Materials[i].Emissive.B)); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = Color24.Black; //TODO: Check, also can we optimise which faces have the transparent color set? builder.Materials[m].TransparentColorUsed = true; if (mesh.Materials[i].Textures.Count > 0) { builder.Materials[m].DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, mesh.Materials[i].Textures[0].Name); if (!System.IO.File.Exists(builder.Materials[m].DaytimeTexture)) { Interface.AddMessage(MessageType.Error, true, "Texure " + builder.Materials[m].DaytimeTexture + " was not found in file " + currentFile); builder.Materials[m].DaytimeTexture = null; } } } if (mesh.TexCoords.Length > 0 && mesh.TexCoords[0] != null) { int nCoords = mesh.TexCoords[0].Count; for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = new Vector2(mesh.TexCoords[0][i].X, mesh.TexCoords[0][i].Y); } } int nNormals = mesh.Normals.Count; Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = new Vector3(mesh.Normals[i].X, mesh.Normals[i].Y, mesh.Normals[i].Z); normals[i].Normalize(); } int nFaceNormals = mesh.NormFaces.Count; if (nFaceNormals > builder.Faces.Length) { throw new Exception("nFaceNormals must match the number of faces in the mesh"); } for (int i = 0; i < nFaceNormals; i++) { int nVertexNormals = mesh.NormFaces[i].Indices.Count; if (nVertexNormals > builder.Faces[i].Vertices.Length) { throw new Exception("nVertexNormals must match the number of verticies in the face"); } for (int j = 0; j < nVertexNormals; j++) { builder.Faces[i].Vertices[j].Normal = normals[(int)mesh.NormFaces[i].Indices[j]]; } } int nVertexColors = (int)mesh.NumColorSets; for (int i = 0; i < nVertexColors; i++) { builder.Vertices[i] = new ColoredVertex((Vertex)builder.Vertices[i], new Color128(mesh.Colors[0][i].R, mesh.Colors[0][i].G, mesh.Colors[0][i].B, mesh.Colors[0][i].A)); } }
public void SetLightSpecular(Color24 LightSpecular) { GL.Uniform3(UniformLayout.LightSpecular, LightSpecular.R / 255.0f, LightSpecular.G / 255.0f, LightSpecular.B / 255.0f); }
public void Execute(int index) { var r = (byte)(GrayInput[index] * byte.MaxValue); RgbOutput[index] = new Color24(r, r, r); }
/// <summary>Loads a texture from the specified file.</summary> /// <param name="file">The file that holds the texture.</param> /// <param name="texture">Receives the texture.</param> /// <returns>Whether loading the texture was successful.</returns> private bool Parse(string file, out Texture texture) { /* * Read the bitmap. This will be a bitmap of just * any format, not necessarily the one that allows * us to extract the bitmap data easily. */ Bitmap bitmap = new Bitmap(file); Color24[] p = null; if (file.ToLowerInvariant().EndsWith(".bmp") && EnabledHacks.ReduceTransparencyColorDepth && (bitmap.PixelFormat == PixelFormat.Format32bppArgb || bitmap.PixelFormat == PixelFormat.Format24bppRgb)) { /* * Reduce our bitmap to 256 colors * WARNING: This is *slow* for lots of textures, so should * be used sparingly, if at all! */ bitmap = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), PixelFormat.Format8bppIndexed); } if (bitmap.PixelFormat != PixelFormat.Format32bppArgb && bitmap.PixelFormat != PixelFormat.Format24bppRgb) { /* Otherwise, only store the color palette data for * textures using a restricted palette * With a large number of textures loaded at * once, this can save a decent chunk of memory */ p = new Color24[bitmap.Palette.Entries.Length]; for (int i = 0; i < bitmap.Palette.Entries.Length; i++) { p[i] = bitmap.Palette.Entries[i]; } } Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height); /* * If the bitmap format is not already 32-bit BGRA, * then convert it to 32-bit BGRA. */ if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { Bitmap compatibleBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); Graphics graphics = Graphics.FromImage(compatibleBitmap); graphics.DrawImage(bitmap, rect, rect, GraphicsUnit.Pixel); graphics.Dispose(); bitmap.Dispose(); bitmap = compatibleBitmap; } /* * Extract the raw bitmap data. */ BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat); if (data.Stride == 4 * data.Width) { /* * Copy the data from the bitmap * to the array in BGRA format. */ byte[] raw = new byte[data.Stride * data.Height]; System.Runtime.InteropServices.Marshal.Copy(data.Scan0, raw, 0, data.Stride * data.Height); bitmap.UnlockBits(data); int width = bitmap.Width; int height = bitmap.Height; /* * Change the byte order from BGRA to RGBA. */ for (int i = 0; i < raw.Length; i += 4) { byte temp = raw[i]; raw[i] = raw[i + 2]; raw[i + 2] = temp; } texture = new Texture(width, height, 32, raw, p); bitmap.Dispose(); return(true); } /* * The stride is invalid. This indicates that the * CLI either does not implement the conversion to * 32-bit BGRA correctly, or that the CLI has * applied additional padding that we do not * support. */ bitmap.UnlockBits(data); bitmap.Dispose(); CurrentHost.ReportProblem(ProblemType.InvalidOperation, "Invalid stride encountered."); texture = null; return(false); }
public void SetLightAmbient(Color24 LightAmbient) { GL.ProgramUniform3(handle, UniformLayout.LightAmbient, LightAmbient.R / 255.0f, LightAmbient.G / 255.0f, LightAmbient.B / 255.0f); }
internal TimetableElement() { Width = 0.0; Height = 0.0; TransparentColor = Color24.Blue; }
public void SetLightDiffuse(Color24 LightDiffuse) { GL.ProgramUniform3(handle, UniformLayout.LightDiffuse, LightDiffuse.R / 255.0f, LightDiffuse.G / 255.0f, LightDiffuse.B / 255.0f); }
private static void ParseSubBlock(Block block, ref StaticObject obj, ref MeshBuilder builder, ref Material material) { Block subBlock; switch (block.Token) { default: return; case TemplateID.Template: string GUID = block.ReadString(); /* * Valid Microsoft templates are listed here: * https://docs.microsoft.com/en-us/windows/desktop/direct3d9/dx9-graphics-reference-x-file-format-templates * However, an application may define it's own template (or by the looks of things override another) * by declaring this at the head of the file, and using a unique GUID * * Mesquoia does this by defining a copy of the Boolean template using a WORD as opposed to a DWORD * No practical effect in this case, however be wary of this.... */ return; case TemplateID.Header: int majorVersion = block.ReadUInt16(); int minorVersion = block.ReadUInt16(); int flags = block.ReadUInt16(); switch (flags) { /* According to http://paulbourke.net/dataformats/directx/#xfilefrm_Template_Header * it is possible for a file to contain a mix of both binary and textual blocks. * * The Header block controls the format of the file from this point onwards. * majorVersion and minorVersion relate to the legacy Direct3D retained mode API * and can probably be ignored. (Assume that features are cumulative and backwards compatible) * flags sets whether the blocks from this point onwards are binary or textual. * * TODO: Need a mixed mode file sample if we want this to work. * Probably exceedingly uncommon, so low priority */ case 0: if (block is TextualBlock) { throw new Exception("Mixed-mode text and binary objects are not supported by this parser."); } break; default: if (block is BinaryBlock) { throw new Exception("Mixed-mode text and binary objects are not supported by this parser."); } break; } return; case TemplateID.Frame: currentLevel++; if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(Plugin.currentHost); } while (block.Position() < block.Length() - 5) { /* * TODO: Whilst https://docs.microsoft.com/en-us/windows/desktop/direct3d9/frame suggests the Frame template should only contain * Mesh, FrameTransformMatrix or Frame templates by default, 3DS Max stuffs all manner of things into here * * It would be nice to get 3DS max stuff detected specifically, especially as we don't support most of this */ //TemplateID[] validTokens = { TemplateID.Mesh , TemplateID.FrameTransformMatrix, TemplateID.Frame }; subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } currentLevel--; break; case TemplateID.FrameTransformMatrix: double[] matrixValues = new double[16]; for (int i = 0; i < 16; i++) { matrixValues[i] = block.ReadSingle(); } if (currentLevel > 1) { builder.TransformMatrix = new Matrix4D(matrixValues); } else { rootMatrix = new Matrix4D(matrixValues); } break; case TemplateID.Mesh: if (builder.Vertices.Length != 0) { builder.Apply(ref obj); builder = new MeshBuilder(Plugin.currentHost); } int nVerts = block.ReadUInt16(); if (nVerts == 0) { //Some null objects contain an empty mesh Plugin.currentHost.AddMessage(MessageType.Warning, false, "nVertices should be greater than zero in Mesh " + block.Label); } int v = builder.Vertices.Length; Array.Resize(ref builder.Vertices, v + nVerts); for (int i = 0; i < nVerts; i++) { builder.Vertices[v + i] = new Vertex(new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle())); } int nFaces = block.ReadUInt16(); if (nFaces == 0) { try { /* * A mesh has been defined with no faces. * If we are not at the end of the block, * attempt to read the next sub-block * * If this fails, the face count is probably incorrect * * NOTE: In this case, the face statement will be an empty string / whitespace * hence the block.ReadString() call */ block.ReadString(); if (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } goto NoFaces; } catch { throw new Exception("nFaces was declared as zero, but unrecognised data remains in the block"); } } int f = builder.Faces.Length; Array.Resize(ref builder.Faces, f + nFaces); for (int i = 0; i < nFaces; i++) { int fVerts = block.ReadUInt16(); if (nFaces == 0) { throw new Exception("fVerts must be greater than zero"); } builder.Faces[f + i] = new MeshFace(); builder.Faces[f + i].Vertices = new MeshFaceVertex[fVerts]; for (int j = 0; j < fVerts; j++) { builder.Faces[f + i].Vertices[j].Index = block.ReadUInt16(); } } NoFaces: while (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } break; case TemplateID.MeshMaterialList: int nMaterials = block.ReadUInt16(); int nFaceIndices = block.ReadUInt16(); if (nFaceIndices == 1 && builder.Faces.Length > 1) { //Single material for all faces int globalMaterial = block.ReadUInt16(); for (int i = 0; i < builder.Faces.Length; i++) { builder.Faces[i].Material = (ushort)(globalMaterial + 1); } } else if (nFaceIndices == builder.Faces.Length) { for (int i = 0; i < nFaceIndices; i++) { int fMaterial = block.ReadUInt16(); builder.Faces[i].Material = (ushort)(fMaterial + 1); } } else { throw new Exception("nFaceIndices must match the number of faces in the mesh"); } for (int i = 0; i < nMaterials; i++) { subBlock = block.ReadSubBlock(TemplateID.Material); ParseSubBlock(subBlock, ref obj, ref builder, ref material); } break; case TemplateID.Material: int m = builder.Materials.Length; Array.Resize(ref builder.Materials, m + 1); builder.Materials[m] = new Material(); builder.Materials[m].Color = new Color32((byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle())); double mPower = block.ReadSingle(); //TODO: Unsure what this does... Color24 mSpecular = new Color24((byte)block.ReadSingle(), (byte)block.ReadSingle(), (byte)block.ReadSingle()); builder.Materials[m].EmissiveColor = new Color24((byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle()), (byte)(255 * block.ReadSingle())); builder.Materials[m].EmissiveColorUsed = true; //TODO: Check exact behaviour builder.Materials[m].TransparentColor = Color24.Black; //TODO: Check, also can we optimise which faces have the transparent color set? builder.Materials[m].TransparentColorUsed = true; if (block.Position() < block.Length() - 5) { subBlock = block.ReadSubBlock(TemplateID.TextureFilename); ParseSubBlock(subBlock, ref obj, ref builder, ref builder.Materials[m]); } break; case TemplateID.TextureFilename: try { material.DaytimeTexture = OpenBveApi.Path.CombineFile(currentFolder, block.ReadString()); } catch { //Empty / malformed texture argument material.DaytimeTexture = null; } if (!System.IO.File.Exists(material.DaytimeTexture) && material.DaytimeTexture != null) { Plugin.currentHost.AddMessage(MessageType.Error, true, "Texure " + material.DaytimeTexture + " was not found in file " + currentFile); material.DaytimeTexture = null; } break; case TemplateID.MeshTextureCoords: int nCoords = block.ReadUInt16(); for (int i = 0; i < nCoords; i++) { builder.Vertices[i].TextureCoordinates = new Vector2(block.ReadSingle(), block.ReadSingle()); } break; case TemplateID.MeshNormals: int nNormals = block.ReadUInt16(); Vector3[] normals = new Vector3[nNormals]; for (int i = 0; i < nNormals; i++) { normals[i] = new Vector3(block.ReadSingle(), block.ReadSingle(), block.ReadSingle()); normals[i].Normalize(); } int nFaceNormals = block.ReadUInt16(); if (nFaceNormals != builder.Faces.Length) { throw new Exception("nFaceNormals must match the number of faces in the mesh"); } for (int i = 0; i < nFaceNormals; i++) { int nVertexNormals = block.ReadUInt16(); if (nVertexNormals != builder.Faces[i].Vertices.Length) { throw new Exception("nVertexNormals must match the number of verticies in the face"); } for (int j = 0; j < nVertexNormals; j++) { builder.Faces[i].Vertices[j].Normal = normals[block.ReadUInt16()]; } } break; case TemplateID.MeshVertexColors: int nVertexColors = block.ReadUInt16(); for (int i = 0; i < nVertexColors; i++) { builder.Vertices[i] = new ColoredVertex((Vertex)builder.Vertices[i], new Color128(block.ReadSingle(), block.ReadSingle(), block.ReadSingle())); } break; } }
public void SetLightSpecular(Color24 LightSpecular) { GL.ProgramUniform3(handle, UniformLayout.LightSpecular, LightSpecular.R / 255.0f, LightSpecular.G / 255.0f, LightSpecular.B / 255.0f); }
/// <summary>Updates the lighting model on a per frame basis</summary> public void UpdateLighting(double Time) { //Check that we have more than one light definition & that the array is not null if (DynamicLighting == false || LightDefinitions == null || LightDefinitions.Length < 2) { return; } //Convert to absolute time of day //Use a while loop as it's possible to run through two days while (Time > 86400) { Time -= 86400; } //Run through the array int j = 0; for (int i = j; i < LightDefinitions.Length; i++) { if (Time < LightDefinitions[i].Time) { break; } j = i; } //We now know that our light definition is between the values defined in j and j + 1 (Or 0 if this is the end of the array) int k; if (j == 0) { //Our NEW light is to be the first entry in the array //This means the OLD light is the last entry, but j and k do not need reversing k = 0; j = LightDefinitions.Length - 1; } else if (j == LightDefinitions.Length - 1) { //We are wrapping around to the end of the array //Reverse j and k, as we have not yet passed the time for the first array entry j = 0; k = LightDefinitions.Length - 1; } else { //Somewhere in the middle, so the NEW light is simply one greater k = j + 1; } int t1 = LightDefinitions[j].Time, t2 = LightDefinitions[k].Time; double cb1 = LightDefinitions[j].CabBrightness, cb2 = LightDefinitions[k].Time; //Calculate, inverting if necessary //Ensure we're not about to divide by zero if (t2 == 0) { t2 = 1; } if (t1 == 0) { t1 = 1; } //Calculate the percentage double mu; if (k == LightDefinitions.Length - 1) { //Wrapping around mu = (86400 - Time + t1) / (86400 - t2 + t1); } else { mu = (Time - t1) / (t2 - t1); } //Calculate the final colors and positions OptionDiffuseColor = Color24.CosineInterpolate(LightDefinitions[j].DiffuseColor, LightDefinitions[k].DiffuseColor, mu); OptionAmbientColor = Color24.CosineInterpolate(LightDefinitions[j].AmbientColor, LightDefinitions[k].AmbientColor, mu); OptionLightPosition = Vector3.CosineInterpolate(LightDefinitions[j].LightPosition, LightDefinitions[k].LightPosition, mu); //Interpolate the cab brightness value double mu2 = (1 - Math.Cos(mu * Math.PI)) / 2; DynamicCabBrightness = (cb1 * (1 - mu2) + cb2 * mu2); //Reinitialize the lighting model with the new information Initialize(); //NOTE: This does not refresh the display lists //If we sit in place with extreme time acceleration (1000x) lighting for faces may appear a little inconsistant }
public void SetMaterialEmission(Color24 MaterialEmission) { GL.ProgramUniform3(handle, UniformLayout.MaterialEmission, MaterialEmission.R / 255.0f, MaterialEmission.G / 255.0f, MaterialEmission.B / 255.0f); }
private RawMapEntity LoadMapEntityFromStream(string chunkFourCC, EndianBinaryReader reader, MapEntityDataDescriptor template) { RawMapEntity obj = new RawMapEntity(); obj.Fields = new PropertyCollection(); obj.FourCC = chunkFourCC; // We're going to examine the Template's properties and load based on the current template type. for (int i = 0; i < template.Fields.Count; i++) { var templateProperty = template.Fields[i]; string propertyName = templateProperty.FieldName; PropertyType type = templateProperty.FieldType; object value = null; switch (type) { case PropertyType.FixedLengthString: value = reader.ReadString(templateProperty.Length).Trim(new[] { '\0' }); break; case PropertyType.String: value = reader.ReadStringUntil('\0'); break; case PropertyType.Byte: value = reader.ReadByte(); break; case PropertyType.Short: value = reader.ReadInt16(); break; case PropertyType.Int32BitField: case PropertyType.Int32: value = reader.ReadInt32(); break; case PropertyType.Float: value = reader.ReadSingle(); break; case PropertyType.Vector3: value = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); break; case PropertyType.Vector2: value = new Vector2(reader.ReadSingle(), reader.ReadSingle()); break; case PropertyType.Enum: byte enumIndexBytes = reader.ReadByte(); // ToDo: Resolve to actual Enum later. value = enumIndexBytes; break; case PropertyType.ObjectReference: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. value = (int)reader.ReadByte(); break; case PropertyType.ObjectReferenceShort: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. value = (int)reader.ReadUInt16(); break; case PropertyType.ObjectReferenceArray: // When we first resolve them, we're going to keep the value as the reference byte, // and then when they are post-processed they'll be turned into a proper type. var refList = new BindingList<object>(); for (int refArray = 0; refArray < templateProperty.Length; refArray++) { refList.Add((int)reader.ReadByte()); } value = refList; break; case PropertyType.XYRotation: { Vector3 eulerAngles = new Vector3(); for (int f = 0; f < 2; f++) eulerAngles[f] = (reader.ReadInt16() * (180 / 32786f)); Quaternion xAxis = Quaternion.FromAxisAngle(new Vector3(1, 0, 0), eulerAngles.X * MathE.Deg2Rad); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), eulerAngles.Y * MathE.Deg2Rad); // Swizzling to the ZYX order seems to be the right one. Quaternion finalRot = yAxis * xAxis; value = finalRot; } break; case PropertyType.XYZRotation: { Vector3 eulerAngles = new Vector3(); for (int f = 0; f < 3; f++) eulerAngles[f] = (reader.ReadInt16() * (180 / 32786f)); Quaternion xAxis = Quaternion.FromAxisAngle(new Vector3(1, 0, 0), eulerAngles.X * MathE.Deg2Rad); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), eulerAngles.Y * MathE.Deg2Rad); Quaternion zAxis = Quaternion.FromAxisAngle(new Vector3(0, 0, 1), eulerAngles.Z * MathE.Deg2Rad); // Swizzling to the ZYX order seems to be the right one. Quaternion finalRot = zAxis * yAxis * xAxis; value = finalRot; } break; case PropertyType.YRotation: { float yRotation = reader.ReadInt16() * (180 / 32786f); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), yRotation * MathE.Deg2Rad); value = yAxis; } break; case PropertyType.Color32: value = new Color32(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Color24: value = new Color24(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Vector3Byte: type = PropertyType.Vector3Byte; value = new Vector3(reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); break; case PropertyType.Bits: value = (byte)reader.ReadBits(templateProperty.Length); break; } // This... this could get dicy. If the template we just read was a "Name" then we now have the technical name (and value) // of the object. We can then search for the MapObjectDataDescriptor that matches the technical name, and then edit the // remaining fields. However, this gets somewhat dicey, because we're modifying the length of the Fields array for templates // while iterating through it. However, the Name field always comes before any of the fields we'd want to modify, we're going to // do an in-place replacement of the fields (since Fields.Count will increase) and then we get free loading of the complex templates // without later post-processing them. if (templateProperty.FieldName == "Name") { // See if our template list has a complex version of this file, otherwise grab the default. MapObjectDataDescriptor complexDescriptor = m_editorCore.Templates.MapObjectDataDescriptors.Find(x => x.FourCC == chunkFourCC && x.TechnicalName == templateProperty.FieldName); if (complexDescriptor == null) complexDescriptor = m_editorCore.Templates.DefaultMapObjectDataDescriptor; // Determine which field we need to remove, and then insert in the other fields (in order) where it used to be. foreach (var fieldToReplace in complexDescriptor.DataOverrides) { for (int k = 0; k < template.Fields.Count; k++) { if (template.Fields[k].FieldName == fieldToReplace.ParameterName) { // Remove the old field. template.Fields.RemoveAt(k); // Now insert the new fields starting at the location of the one we just replaced. template.Fields.InsertRange(k, fieldToReplace.Values); break; } } } } Property instanceProp = new Property(templateProperty.FieldName, type, value); obj.Fields.Properties.Add(instanceProp); } return obj; }
/// <summary>Loads a Loksim3D object from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Rotation">The rotation to be applied</param> /// <returns>The object loaded.</returns> internal static StaticObject ReadObject(string FileName, Vector3 Rotation) { string BaseDir = System.IO.Path.GetDirectoryName(FileName); XmlDocument currentXML = new XmlDocument(); //Initialise the object StaticObject Object = new StaticObject(Plugin.currentHost); MeshBuilder Builder = new MeshBuilder(Plugin.currentHost); Vector3[] Normals = new Vector3[4]; bool PropertiesFound = false; VertexTemplate[] tempVertices = new VertexTemplate[0]; Vector3[] tempNormals = new Vector3[0]; Color24 transparentColor = new Color24(); string tday = null; string tnight = null; string transtex = null; bool TransparencyUsed = false; bool TransparentTypSet = false; bool FirstPxTransparent = false; Color24 FirstPxColor = new Color24(); bool Face2 = false; int TextureWidth = 0; int TextureHeight = 0; if (File.Exists(FileName)) { try { currentXML.Load(FileName); } catch { return(null); } } else { Plugin.currentHost.AddMessage(MessageType.Error, false, "Loksim3D object " + FileName + " does not exist."); return(null); } //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/OBJECT"); //Check this file actually contains Loksim3D object nodes if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode node in outerNode.ChildNodes) { //I think there should only be one properties node?? //Need better format documentation if (node.Name == "Props" && PropertiesFound == false) { if (node.Attributes != null) { //Our node has child nodes, therefore this properties node should be valid //Needs better validation PropertiesFound = true; foreach (XmlAttribute attribute in node.Attributes) { switch (attribute.Name) { //Sets the texture //Loksim3D objects only support daytime textures case "Texture": tday = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Plugin.LoksimPackageFolder); if (!File.Exists(tday)) { Plugin.currentHost.AddMessage(MessageType.Warning, true, "Ls3d Texture file " + attribute.Value + " not found."); break; } try { using (Bitmap TextureInformation = new Bitmap(tday)) { TextureWidth = TextureInformation.Width; TextureHeight = TextureInformation.Height; Color color = TextureInformation.GetPixel(0, 0); FirstPxColor = new Color24(color.R, color.G, color.B); } } catch { Plugin.currentHost.AddMessage(MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName); tday = null; } break; //Defines whether the texture uses transparency //May be omitted case "Transparent": if (TransparentTypSet) { //Appears to be ignored with TransparentTyp set continue; } if (attribute.Value == "TRUE") { TransparencyUsed = true; transparentColor = Color24.Black; } break; case "TransTexture": if (string.IsNullOrEmpty(attribute.Value)) { //Empty.... continue; } transtex = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Plugin.LoksimPackageFolder); if (!File.Exists(transtex)) { Plugin.currentHost.AddMessage(MessageType.Error, true, "AlphaTexture " + transtex + " could not be found in file " + FileName); transtex = null; } break; //Sets the transparency type case "TransparentTyp": TransparentTypSet = true; switch (attribute.Value) { case "0": //Transparency is disabled TransparencyUsed = false; break; case "1": //Transparency is solid black TransparencyUsed = true; transparentColor = Color24.Black; FirstPxTransparent = false; break; case "2": //Transparency is the color at Pixel 1,1 TransparencyUsed = true; FirstPxTransparent = true; break; case "3": case "4": //This is used when transparency is used with an alpha bitmap TransparencyUsed = false; FirstPxTransparent = false; break; case "5": //Use the alpha channel from the image, so we don't need to do anything fancy //TODO: (Low priority) Check what happens in Loksim itself when an image uses the Alpha channel, but doesn't actually specify type 5 break; default: Plugin.currentHost.AddMessage(MessageType.Error, false, "Unrecognised transparency type " + attribute.Value + " detected in " + attribute.Name + " in Loksim3D object file " + FileName); break; } break; //Sets whether the rears of the faces are to be drawn case "Drawrueckseiten": if (attribute.Value == "TRUE" || string.IsNullOrEmpty(attribute.Value)) { Face2 = true; } else { Face2 = false; } break; /* * MISSING PROPERTIES: * AutoRotate - Rotate with tracks?? LS3D presumably uses a 3D world system. * Beleuchtet- Translates as illuminated. Presume something to do with lighting? - What emissive color? * FileAuthor * FileInfo * FilePicture */ } } } } //The point command is eqivilant to a vertex else if (node.Name == "Point" && node.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Vertex Vector3 v = new Vector3(); bool vF = false; //Normals Vector3 n = new Vector3(); foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { //Sets the vertex normals case "Normal": if (!Vector3.TryParse(attribute.Value, ';', out n)) { Plugin.currentHost.AddMessage(MessageType.Warning, true, "Invalid vertex normal vector " + attribute.Value + " supplied in Ls3d object file."); } break; //Sets the vertex 3D co-ordinates case "Vekt": if (!Vector3.TryParse(attribute.Value, ';', out v)) { Plugin.currentHost.AddMessage(MessageType.Warning, true, "Invalid vertex coordinate vector " + attribute.Value + " supplied in Ls3d object file."); } vF = true; break; } } //Resize temp arrays Array.Resize <VertexTemplate>(ref tempVertices, tempVertices.Length + 1); Array.Resize <Vector3>(ref tempNormals, tempNormals.Length + 1); //Add vertex and normals to temp array if (vF == false) { Plugin.currentHost.AddMessage(MessageType.Warning, true, "Vertex with no co-ordinates supplied encountered in Ls3d object file."); continue; } tempVertices[tempVertices.Length - 1] = new Vertex((Vector3)v); tempNormals[tempNormals.Length - 1] = new Vector3(n); tempNormals[tempNormals.Length - 1].Normalize(); Array.Resize <VertexTemplate>(ref Builder.Vertices, Builder.Vertices.Length + 1); while (Builder.Vertices.Length >= Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1] = new Vertex(new Vector3((Vector3)v)); Normals[Builder.Vertices.Length - 1] = new Vector3(n); } } } //The Flaeche command creates a face else if (node.Name == "Flaeche" && node.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { //Defines the verticies in this face //**NOTE**: A vertex may appear in multiple faces with different texture co-ordinates if (childNode.Attributes["Points"] != null) { string[] Verticies = childNode.Attributes["Points"].Value.Split(';'); int f = Builder.Faces.Length; //Add 1 to the length of the face array Array.Resize <MeshFace>(ref Builder.Faces, f + 1); Builder.Faces[f] = new MeshFace(); //Create the vertex array for the face Builder.Faces[f].Vertices = new MeshFaceVertex[Verticies.Length]; while (Builder.Vertices.Length > Normals.Length) { Array.Resize <Vector3>(ref Normals, Normals.Length << 1); } //Run through the vertices list and grab from the temp array int smallestX = TextureWidth; int smallestY = TextureHeight; for (int j = 0; j < Verticies.Length; j++) { //This is the position of the vertex in the temp array int currentVertex; if (!int.TryParse(Verticies[j], out currentVertex)) { Plugin.currentHost.AddMessage(MessageType.Error, false, Verticies[j] + " does not parse to a valid Vertex in " + node.Name + " in Loksim3D object file " + FileName); continue; } //Add one to the actual vertex array Array.Resize <VertexTemplate>(ref Builder.Vertices, Builder.Vertices.Length + 1); //Set coordinates Builder.Vertices[Builder.Vertices.Length - 1] = new Vertex(tempVertices[currentVertex].Coordinates); //Set the vertex index Builder.Faces[f].Vertices[j].Index = (ushort)(Builder.Vertices.Length - 1); //Set the normals Builder.Faces[f].Vertices[j].Normal = tempNormals[currentVertex]; //Now deal with the texture //Texture mapping points are in pixels X,Y and are relative to the face in question rather than the vertex if (childNode.Attributes["Texture"] != null) { string[] TextureCoords = childNode.Attributes["Texture"].Value.Split(';'); Vector2 currentCoords; float OpenBVEWidth; float OpenBVEHeight; string[] splitCoords = TextureCoords[j].Split(','); if (!float.TryParse(splitCoords[0], out OpenBVEWidth)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid texture width specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (!float.TryParse(splitCoords[1], out OpenBVEHeight)) { Plugin.currentHost.AddMessage(MessageType.Error, false, "Invalid texture height specified in " + node.Name + " in Loksim3D object file " + FileName); continue; } if (OpenBVEWidth <= smallestX && OpenBVEHeight <= smallestY) { //Clamp texture width and height smallestX = (int)OpenBVEWidth; smallestY = (int)OpenBVEHeight; } if (TextureWidth != 0 && TextureHeight != 0) { //Calculate openBVE co-ords currentCoords.X = (OpenBVEWidth / TextureWidth); currentCoords.Y = (OpenBVEHeight / TextureHeight); } else { //Invalid, so just return zero currentCoords.X = 0; currentCoords.Y = 0; } Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords; } } if (Face2) { //Add face2 flag if required Builder.Faces[f].Flags = (byte)MeshFace.Face2Mask; } } } } } } } } } //Apply rotation /* * NOTES: * No rotation order is specified * The rotation string in a .l3dgrp file is ordered Y, Z, X ??? Can't find a good reason for this ??? * Rotations must still be performed in X,Y,Z order to produce correct results */ if (Rotation.Z != 0.0) { Rotation.Z = Rotation.Z.ToRadians(); //Apply rotation Builder.ApplyRotation(Vector3.Right, Rotation.Z); } if (Rotation.X != 0.0) { //This is actually the Y-Axis rotation Rotation.X = Rotation.X.ToRadians(); //Apply rotation Builder.ApplyRotation(Vector3.Down, Rotation.X); } if (Rotation.Y != 0.0) { //This is actually the X-Axis rotation Rotation.Y = Rotation.Y.ToRadians(); //Apply rotation Builder.ApplyRotation(Vector3.Forward, Rotation.Y); } //These files appear to only have one texture defined //Therefore import later- May have to change if (File.Exists(tday)) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].DaytimeTexture = tday; Builder.Materials[j].NighttimeTexture = tnight; Builder.Materials[j].TransparencyTexture = transtex; } } if (TransparencyUsed == true) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = FirstPxTransparent ? FirstPxColor : transparentColor; Builder.Materials[j].TransparentColorUsed = true; } } } Builder.Apply(ref Object); Object.Mesh.CreateNormals(); return(Object); }