internal static void ApplyScale(ObjectManager.StaticObject Object, double x, double y, double z) { float rx = (float)(1.0 / x); float ry = (float)(1.0 / y); float rz = (float)(1.0 / z); float rx2 = rx * rx; float ry2 = ry * ry; float rz2 = rz * rz; bool reverse = x * y * z < 0.0; for (int j = 0; j < Object.Mesh.Vertices.Length; j++) { Object.Mesh.Vertices[j].Coordinates.X *= x; Object.Mesh.Vertices[j].Coordinates.Y *= y; Object.Mesh.Vertices[j].Coordinates.Z *= z; } for (int j = 0; j < Object.Mesh.Faces.Length; j++) { for (int k = 0; k < Object.Mesh.Faces[j].Vertices.Length; k++) { float nx2 = Object.Mesh.Faces[j].Vertices[k].Normal.X * Object.Mesh.Faces[j].Vertices[k].Normal.X; float ny2 = Object.Mesh.Faces[j].Vertices[k].Normal.Y * Object.Mesh.Faces[j].Vertices[k].Normal.Y; float nz2 = Object.Mesh.Faces[j].Vertices[k].Normal.Z * Object.Mesh.Faces[j].Vertices[k].Normal.Z; float u = nx2 * rx2 + ny2 * ry2 + nz2 * rz2; if (u != 0.0) { u = (float)Math.Sqrt((double)((nx2 + ny2 + nz2) / u)); Object.Mesh.Faces[j].Vertices[k].Normal.X *= rx * u; Object.Mesh.Faces[j].Vertices[k].Normal.Y *= ry * u; Object.Mesh.Faces[j].Vertices[k].Normal.Z *= rz * u; } } } if (reverse) { for (int j = 0; j < Object.Mesh.Faces.Length; j++) { Object.Mesh.Faces[j].Flip(); } } }
// add object for custom timetable internal static void AddObjectForCustomTimetable(ObjectManager.AnimatedObject obj) { if (CustomObjectsUsed >= CustomObjects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref CustomObjects, CustomObjects.Length << 1); } CustomObjects[CustomObjectsUsed] = obj; CustomObjectsUsed++; }
/// <summary>This method is called once the route and train data have been preprocessed, in order to physically setup the simulation</summary> private void SetupSimulation() { if (Loading.Cancel) { Close(); } lock (Illustrations.Locker) { Timetable.CreateTimetable(); } //Check if any critical errors have occured during the route or train loading for (int i = 0; i < Interface.MessageCount; i++) { if (Interface.LogMessages[i].Type == MessageType.Critical) { MessageBox.Show("A critical error has occured:\n\n" + Interface.LogMessages[i].Text + "\n\nPlease inspect the error log file for further information.", "Load", MessageBoxButtons.OK, MessageBoxIcon.Hand); Close(); } } Program.Renderer.Lighting.Initialize(); Game.LogRouteName = System.IO.Path.GetFileName(MainLoop.currentResult.RouteFile); Game.LogTrainName = System.IO.Path.GetFileName(MainLoop.currentResult.TrainFolder); Game.LogDateTime = DateTime.Now; if (Interface.CurrentOptions.LoadInAdvance) { Textures.LoadAllTextures(); } else { Program.Renderer.TextureManager.UnloadAllTextures(); } // camera Program.Renderer.InitializeVisibility(); TrainManager.PlayerTrain.DriverBody = new DriverBody(TrainManager.PlayerTrain); World.CameraTrackFollower.UpdateAbsolute(0.0, true, false); World.CameraTrackFollower.UpdateAbsolute(-0.1, true, false); World.CameraTrackFollower.UpdateAbsolute(0.1, true, false); World.CameraTrackFollower.TriggerType = EventTriggerType.Camera; // starting time and track position Program.CurrentRoute.SecondsSinceMidnight = 0.0; Game.StartupTime = 0.0; int PlayerFirstStationIndex = -1; double PlayerFirstStationPosition; int os = -1; bool f = false; for (int i = 0; i < Program.CurrentRoute.Stations.Length; i++) { if (!String.IsNullOrEmpty(Game.InitialStationName)) { if (Game.InitialStationName.ToLowerInvariant() == Program.CurrentRoute.Stations[i].Name.ToLowerInvariant()) { PlayerFirstStationIndex = i; } } if (Program.CurrentRoute.Stations[i].StopMode == StationStopMode.AllStop | Program.CurrentRoute.Stations[i].StopMode == StationStopMode.PlayerStop & Program.CurrentRoute.Stations[i].Stops.Length != 0) { if (f == false) { os = i; f = true; } } } if (PlayerFirstStationIndex == -1) { PlayerFirstStationIndex = os; } { int s = Program.CurrentRoute.Stations[PlayerFirstStationIndex].GetStopIndex(TrainManager.PlayerTrain.NumberOfCars); if (s >= 0) { PlayerFirstStationPosition = Program.CurrentRoute.Stations[PlayerFirstStationIndex].Stops[s].TrackPosition; double TrainLength = 0.0; for (int c = 0; c < TrainManager.PlayerTrain.Cars.Length; c++) { TrainLength += TrainManager.PlayerTrain.Cars[c].Length; } for (int j = 0; j < Program.CurrentRoute.BufferTrackPositions.Length; j++) { if (PlayerFirstStationPosition > Program.CurrentRoute.BufferTrackPositions[j] && PlayerFirstStationPosition - TrainLength < Program.CurrentRoute.BufferTrackPositions[j]) { /* * HACK: The initial start position for the player train is stuck on a set of buffers * This means we have to make some one the fly adjustments to the first station stop position */ //Set the start position to be the buffer position plus the train length plus 1m PlayerFirstStationPosition = Program.CurrentRoute.BufferTrackPositions[j] + TrainLength + 1; //Update the station stop location if (s >= 0) { Program.CurrentRoute.Stations[PlayerFirstStationIndex].Stops[s].TrackPosition = PlayerFirstStationPosition; } else { Program.CurrentRoute.Stations[PlayerFirstStationIndex].DefaultTrackPosition = PlayerFirstStationPosition; } break; } } } else { PlayerFirstStationPosition = Program.CurrentRoute.Stations[PlayerFirstStationIndex].DefaultTrackPosition; } if (Game.InitialStationTime != -1) { Program.CurrentRoute.SecondsSinceMidnight = Game.InitialStationTime; Game.StartupTime = Game.InitialStationTime; } else { if (Program.CurrentRoute.Stations[PlayerFirstStationIndex].ArrivalTime < 0.0) { if (Program.CurrentRoute.Stations[PlayerFirstStationIndex].DepartureTime < 0.0) { Program.CurrentRoute.SecondsSinceMidnight = 0.0; Game.StartupTime = 0.0; } else { Program.CurrentRoute.SecondsSinceMidnight = Program.CurrentRoute.Stations[PlayerFirstStationIndex].DepartureTime - Program.CurrentRoute.Stations[PlayerFirstStationIndex].StopTime; Game.StartupTime = Program.CurrentRoute.Stations[PlayerFirstStationIndex].DepartureTime - Program.CurrentRoute.Stations[PlayerFirstStationIndex].StopTime; } } else { Program.CurrentRoute.SecondsSinceMidnight = Program.CurrentRoute.Stations[PlayerFirstStationIndex].ArrivalTime; Game.StartupTime = Program.CurrentRoute.Stations[PlayerFirstStationIndex].ArrivalTime; } } } int OtherFirstStationIndex = -1; double OtherFirstStationPosition = 0.0; double OtherFirstStationTime = 0.0; for (int i = 0; i < Program.CurrentRoute.Stations.Length; i++) { if (Program.CurrentRoute.Stations[i].StopMode == StationStopMode.AllStop | Program.CurrentRoute.Stations[i].StopMode == StationStopMode.PlayerPass & Program.CurrentRoute.Stations[i].Stops.Length != 0) { OtherFirstStationIndex = i; int s = Program.CurrentRoute.Stations[i].GetStopIndex(TrainManager.PlayerTrain.Cars.Length); if (s >= 0) { OtherFirstStationPosition = Program.CurrentRoute.Stations[i].Stops[s].TrackPosition; } else { OtherFirstStationPosition = Program.CurrentRoute.Stations[i].DefaultTrackPosition; } if (Program.CurrentRoute.Stations[i].ArrivalTime < 0.0) { if (Program.CurrentRoute.Stations[i].DepartureTime < 0.0) { OtherFirstStationTime = 0.0; } else { OtherFirstStationTime = Program.CurrentRoute.Stations[i].DepartureTime - Program.CurrentRoute.Stations[i].StopTime; } } else { OtherFirstStationTime = Program.CurrentRoute.Stations[i].ArrivalTime; } break; } } if (Game.PrecedingTrainTimeDeltas.Length != 0) { OtherFirstStationTime -= Game.PrecedingTrainTimeDeltas[Game.PrecedingTrainTimeDeltas.Length - 1]; if (OtherFirstStationTime < Program.CurrentRoute.SecondsSinceMidnight) { Program.CurrentRoute.SecondsSinceMidnight = OtherFirstStationTime; } } // initialize trains for (int i = 0; i < TrainManager.Trains.Length; i++) { TrainManager.Trains[i].Initialize(); int s = TrainManager.Trains[i].IsPlayerTrain ? PlayerFirstStationIndex : OtherFirstStationIndex; if (s >= 0) { if (Program.CurrentRoute.Stations[s].OpenLeftDoors) { for (int j = 0; j < TrainManager.Trains[i].Cars.Length; j++) { TrainManager.Trains[i].Cars[j].Doors[0].AnticipatedOpen = true; } } if (Program.CurrentRoute.Stations[s].OpenRightDoors) { for (int j = 0; j < TrainManager.Trains[i].Cars.Length; j++) { TrainManager.Trains[i].Cars[j].Doors[1].AnticipatedOpen = true; } } } if (Program.CurrentRoute.Sections.Length != 0) { Program.CurrentRoute.Sections[0].Enter(TrainManager.Trains[i]); } for (int j = 0; j < TrainManager.Trains[i].Cars.Length; j++) { double length = TrainManager.Trains[i].Cars[0].Length; TrainManager.Trains[i].Cars[j].Move(-length); TrainManager.Trains[i].Cars[j].Move(length); } } foreach (var Train in TrainManager.TFOs) { Train.Initialize(); foreach (var Car in Train.Cars) { double length = Train.Cars[0].Length; Car.Move(-length); Car.Move(length); } } // score Game.CurrentScore.ArrivalStation = PlayerFirstStationIndex + 1; Game.CurrentScore.DepartureStation = PlayerFirstStationIndex; Game.CurrentScore.Maximum = 0; for (int i = 0; i < Program.CurrentRoute.Stations.Length; i++) { if (i != PlayerFirstStationIndex & Program.CurrentRoute.Stations[i].PlayerStops()) { if (i == 0 || Program.CurrentRoute.Stations[i - 1].Type != StationType.ChangeEnds && Program.CurrentRoute.Stations[i - 1].Type != StationType.Jump) { Game.CurrentScore.Maximum += Game.ScoreValueStationArrival; } } } if (Game.CurrentScore.Maximum <= 0) { Game.CurrentScore.Maximum = Game.ScoreValueStationArrival; } // signals if (Program.CurrentRoute.Sections.Length > 0) { Program.CurrentRoute.UpdateAllSections(); } // move train in position for (int i = 0; i < TrainManager.Trains.Length; i++) { double p; if (TrainManager.Trains[i].IsPlayerTrain) { p = PlayerFirstStationPosition; } else if (TrainManager.Trains[i].State == TrainState.Bogus) { p = Program.CurrentRoute.BogusPreTrainInstructions[0].TrackPosition; TrainManager.Trains[i].AI = new Game.BogusPretrainAI(TrainManager.Trains[i]); } else { p = OtherFirstStationPosition; } for (int j = 0; j < TrainManager.Trains[i].Cars.Length; j++) { TrainManager.Trains[i].Cars[j].Move(p); } } // timetable if (Timetable.DefaultTimetableDescription.Length == 0) { Timetable.DefaultTimetableDescription = Game.LogTrainName; } // initialize camera if (Program.Renderer.Camera.CurrentRestriction == CameraRestrictionMode.NotAvailable) { Program.Renderer.Camera.CurrentMode = CameraViewMode.InteriorLookAhead; } //Place the initial camera in the driver car TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar].UpdateCamera(); World.CameraTrackFollower.UpdateAbsolute(-1.0, true, false); Program.Renderer.UpdateVisibility(World.CameraTrackFollower.TrackPosition + Program.Renderer.Camera.Alignment.Position.Z); World.CameraSavedExterior = new CameraAlignment(new OpenBveApi.Math.Vector3(-2.5, 1.5, -15.0), 0.3, -0.2, 0.0, PlayerFirstStationPosition, 1.0); World.CameraSavedTrack = new CameraAlignment(new OpenBveApi.Math.Vector3(-3.0, 2.5, 0.0), 0.3, 0.0, 0.0, TrainManager.PlayerTrain.Cars[0].TrackPosition - 10.0, 1.0); // signalling sections for (int i = 0; i < TrainManager.Trains.Length; i++) { int s = TrainManager.Trains[i].CurrentSectionIndex; Program.CurrentRoute.Sections[s].Enter(TrainManager.Trains[i]); } if (Program.CurrentRoute.Sections.Length > 0) { Program.CurrentRoute.UpdateAllSections(); } // fast-forward until start time { Game.MinimalisticSimulation = true; const double w = 0.25; double u = Game.StartupTime - Program.CurrentRoute.SecondsSinceMidnight; if (u > 0) { while (true) { double v = u < w ? u : w; u -= v; Program.CurrentRoute.SecondsSinceMidnight += v; TrainManager.UpdateTrains(v); if (u <= 0.0) { break; } TotalTimeElapsedForSectionUpdate += v; if (TotalTimeElapsedForSectionUpdate >= 1.0) { if (Program.CurrentRoute.Sections.Length > 0) { Program.CurrentRoute.UpdateAllSections(); } TotalTimeElapsedForSectionUpdate = 0.0; } } } Game.MinimalisticSimulation = false; } // animated objects ObjectManager.UpdateAnimatedWorldObjects(0.0, true); TrainManager.UpdateTrainObjects(0.0, true); //HACK: This function calls a single update on all objects attached to the player's train // but ignores any specified damping so that all needles etc. are in the correct place // for the first frame, rather than spinning wildly to get to the starting point. TrainManager.PlayerTrain.UpdateCabObjects(); // timetable if (TrainManager.PlayerTrain.Station >= 0) { Timetable.UpdateCustomTimetable(Program.CurrentRoute.Stations[TrainManager.PlayerTrain.Station].TimetableDaytimeTexture, Program.CurrentRoute.Stations[TrainManager.PlayerTrain.Station].TimetableNighttimeTexture); if (Timetable.CustomObjectsUsed != 0 & Timetable.CustomTimetableAvailable && Interface.CurrentOptions.TimeTableStyle != Interface.TimeTableMode.AutoGenerated && Interface.CurrentOptions.TimeTableStyle != Interface.TimeTableMode.None) { Timetable.CurrentTimetable = Timetable.TimetableState.Custom; } } //Create AI driver for the player train if specified via the commmand line if (Game.InitialAIDriver == true) { TrainManager.PlayerTrain.AI = new Game.SimpleHumanDriverAI(TrainManager.PlayerTrain); if (TrainManager.PlayerTrain.Plugin != null && !TrainManager.PlayerTrain.Plugin.SupportsAI) { Game.AddMessage(Translations.GetInterfaceString("notification_aiunable"), MessageDependency.None, GameMode.Expert, OpenBveApi.Colors.MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 10.0, null); } } // warnings / errors if (Interface.MessageCount != 0) { int filesNotFound = 0; int errors = 0; int warnings = 0; for (int i = 0; i < Interface.MessageCount; i++) { if (Interface.LogMessages[i].FileNotFound) { filesNotFound++; } else if (Interface.LogMessages[i].Type == MessageType.Error) { errors++; } else if (Interface.LogMessages[i].Type == MessageType.Warning) { warnings++; } } string NotFound = null; string Messages; if (filesNotFound != 0) { NotFound = filesNotFound.ToString() + " file(s) not found"; Game.AddMessage(NotFound, MessageDependency.None, GameMode.Expert, MessageColor.Magenta, Program.CurrentRoute.SecondsSinceMidnight + 10.0, null); } if (errors != 0 & warnings != 0) { Messages = errors.ToString() + " error(s), " + warnings.ToString() + " warning(s)"; Game.AddMessage(Messages, MessageDependency.None, GameMode.Expert, MessageColor.Magenta, Program.CurrentRoute.SecondsSinceMidnight + 10.0, null); } else if (errors != 0) { Messages = errors.ToString() + " error(s)"; Game.AddMessage(Messages, MessageDependency.None, GameMode.Expert, MessageColor.Magenta, Program.CurrentRoute.SecondsSinceMidnight + 10.0, null); } else { Messages = warnings.ToString() + " warning(s)"; Game.AddMessage(Messages, MessageDependency.None, GameMode.Expert, MessageColor.Magenta, Program.CurrentRoute.SecondsSinceMidnight + 10.0, null); } Game.RouteInformation.FilesNotFound = NotFound; Game.RouteInformation.ErrorsAndWarnings = Messages; //Print the plugin error encountered (If any) for 10s //This must be done after the simulation has init, as otherwise the timeout doesn't work if (Loading.PluginError != null) { Game.AddMessage(Loading.PluginError, MessageDependency.None, GameMode.Expert, OpenBveApi.Colors.MessageColor.Red, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null); Game.AddMessage(Translations.GetInterfaceString("errors_plugin_failure2"), MessageDependency.None, GameMode.Expert, OpenBveApi.Colors.MessageColor.Red, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null); } } loadComplete = true; RenderRealTimeElapsed = 0.0; RenderTimeElapsed = 0.0; World.InitializeCameraRestriction(); Loading.SimulationSetup = true; switch (Game.InitialViewpoint) { case 1: //Switch camera to exterior MainLoop.SaveCameraSettings(); Program.Renderer.Camera.CurrentMode = CameraViewMode.Exterior; MainLoop.RestoreCameraSettings(); for (int j = 0; j < TrainManager.PlayerTrain.Cars.Length; j++) { TrainManager.PlayerTrain.Cars[j].ChangeCarSection(TrainManager.CarSectionType.Exterior); //Make bogies visible TrainManager.PlayerTrain.Cars[j].FrontBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].RearBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].Coupler.ChangeSection(0); } Program.Renderer.Camera.AlignmentDirection = new CameraAlignment(); Program.Renderer.Camera.AlignmentSpeed = new CameraAlignment(); Program.Renderer.UpdateViewport(ViewportChangeMode.NoChange); World.UpdateAbsoluteCamera(0.0); World.UpdateViewingDistances(); break; case 2: //Switch camera to track MainLoop.SaveCameraSettings(); Program.Renderer.Camera.CurrentMode = CameraViewMode.Track; MainLoop.RestoreCameraSettings(); for (int j = 0; j < TrainManager.PlayerTrain.Cars.Length; j++) { TrainManager.PlayerTrain.Cars[j].ChangeCarSection(TrainManager.CarSectionType.Exterior); TrainManager.PlayerTrain.Cars[j].FrontBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].RearBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].Coupler.ChangeSection(0); } Program.Renderer.Camera.AlignmentDirection = new CameraAlignment(); Program.Renderer.Camera.AlignmentSpeed = new CameraAlignment(); Program.Renderer.UpdateViewport(ViewportChangeMode.NoChange); World.UpdateAbsoluteCamera(0.0); World.UpdateViewingDistances(); break; case 3: //Switch camera to flyby MainLoop.SaveCameraSettings(); Program.Renderer.Camera.CurrentMode = CameraViewMode.FlyBy; MainLoop.RestoreCameraSettings(); for (int j = 0; j < TrainManager.PlayerTrain.Cars.Length; j++) { TrainManager.PlayerTrain.Cars[j].ChangeCarSection(TrainManager.CarSectionType.Exterior); TrainManager.PlayerTrain.Cars[j].FrontBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].RearBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].Coupler.ChangeSection(0); } Program.Renderer.Camera.AlignmentDirection = new CameraAlignment(); Program.Renderer.Camera.AlignmentSpeed = new CameraAlignment(); Program.Renderer.UpdateViewport(ViewportChangeMode.NoChange); World.UpdateAbsoluteCamera(0.0); World.UpdateViewingDistances(); break; case 4: //Switch camera to flyby MainLoop.SaveCameraSettings(); Program.Renderer.Camera.CurrentMode = CameraViewMode.FlyByZooming; MainLoop.RestoreCameraSettings(); for (int j = 0; j < TrainManager.PlayerTrain.Cars.Length; j++) { TrainManager.PlayerTrain.Cars[j].ChangeCarSection(TrainManager.CarSectionType.Exterior); TrainManager.PlayerTrain.Cars[j].FrontBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].RearBogie.ChangeSection(0); TrainManager.PlayerTrain.Cars[j].Coupler.ChangeSection(0); } Program.Renderer.Camera.AlignmentDirection = new CameraAlignment(); Program.Renderer.Camera.AlignmentSpeed = new CameraAlignment(); Program.Renderer.UpdateViewport(ViewportChangeMode.NoChange); World.UpdateAbsoluteCamera(0.0); World.UpdateViewingDistances(); break; } }
private static void ApplyMeshBuilder(ref ObjectManager.StaticObject Object, MeshBuilder Builder, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { if (Builder.Faces.Length != 0) { int mf = Object.Mesh.Faces.Length; int mm = Object.Mesh.Materials.Length; int mv = Object.Mesh.Vertices.Length; Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + Builder.Faces.Length); Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Builder.Materials.Length); Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Builder.Vertices.Length); for (int i = 0; i < Builder.Vertices.Length; i++) { Object.Mesh.Vertices[mv + i] = Builder.Vertices[i]; } for (int i = 0; i < Builder.Faces.Length; i++) { Object.Mesh.Faces[mf + i] = Builder.Faces[i]; for (int j = 0; j < Object.Mesh.Faces[mf + i].Vertices.Length; j++) { Object.Mesh.Faces[mf + i].Vertices[j].Index += (ushort)mv; } Object.Mesh.Faces[mf + i].Material += (ushort)mm; } for (int i = 0; i < Builder.Materials.Length; i++) { Object.Mesh.Materials[mm + i].Flags = (byte)((Builder.Materials[i].EmissiveColorUsed ? World.MeshMaterial.EmissiveColorMask : 0) | (Builder.Materials[i].TransparentColorUsed ? World.MeshMaterial.TransparentColorMask : 0)); Object.Mesh.Materials[mm + i].Color = Builder.Materials[i].Color; Object.Mesh.Materials[mm + i].TransparentColor = Builder.Materials[i].TransparentColor; TextureManager.TextureWrapMode WrapX, WrapY; if (ForceTextureRepeatX) { WrapX = TextureManager.TextureWrapMode.Repeat; } else { WrapX = TextureManager.TextureWrapMode.ClampToEdge; } if (ForceTextureRepeatY) { WrapY = TextureManager.TextureWrapMode.Repeat; } else { WrapY = TextureManager.TextureWrapMode.ClampToEdge; } if (WrapX != TextureManager.TextureWrapMode.Repeat | WrapY != TextureManager.TextureWrapMode.Repeat) { for (int j = 0; j < Builder.Vertices.Length; j++) { if (Builder.Vertices[j].TextureCoordinates.X < 0.0 | Builder.Vertices[j].TextureCoordinates.X > 1.0) { WrapX = TextureManager.TextureWrapMode.Repeat; } if (Builder.Vertices[j].TextureCoordinates.Y < 0.0 | Builder.Vertices[j].TextureCoordinates.Y > 1.0) { WrapY = TextureManager.TextureWrapMode.Repeat; } } } if (Builder.Materials[i].DaytimeTexture != null) { int tday = TextureManager.RegisterTexture(Builder.Materials[i].DaytimeTexture, Builder.Materials[i].TransparentColor, Builder.Materials[i].TransparentColorUsed ? (byte)1 : (byte)0, WrapX, WrapY, LoadMode != ObjectManager.ObjectLoadMode.Normal); Object.Mesh.Materials[mm + i].DaytimeTextureIndex = tday; } else { Object.Mesh.Materials[mm + i].DaytimeTextureIndex = -1; } Object.Mesh.Materials[mm + i].EmissiveColor = Builder.Materials[i].EmissiveColor; if (Builder.Materials[i].NighttimeTexture != null) { int tnight = TextureManager.RegisterTexture(Builder.Materials[i].NighttimeTexture, Builder.Materials[i].TransparentColor, Builder.Materials[i].TransparentColorUsed ? (byte)1 : (byte)0, WrapX, WrapY, LoadMode != ObjectManager.ObjectLoadMode.Normal); Object.Mesh.Materials[mm + i].NighttimeTextureIndex = tnight; } else { Object.Mesh.Materials[mm + i].NighttimeTextureIndex = -1; } Object.Mesh.Materials[mm + i].DaytimeNighttimeBlend = 0; Object.Mesh.Materials[mm + i].BlendMode = Builder.Materials[i].BlendMode; Object.Mesh.Materials[mm + i].GlowAttenuationData = Builder.Materials[i].GlowAttenuationData; } } }
// parse animated object config /// <summary>Loads a collection of animated objects from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <returns>The collection of animated objects.</returns> internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[4]; int ObjectCount = 0; // load file string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); bool rpnUsed = false; for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j).Trim(); } else { Lines[i] = Lines[i].Trim(); } if (Program.CurrentProgramType == Program.ProgramType.ObjectViewer | Program.CurrentProgramType == Program.ProgramType.RouteViewer) { if (Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0) { rpnUsed = true; } } } if (rpnUsed) { Interface.AddMessage(Interface.MessageType.Warning, false, "An animated object file contains non-official RPN functions. Please get rid of them in file " + FileName); } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[include]": { i++; World.Vector3D position = new World.Vector3D(0.0, 0.0, 0.0); ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[4]; int objCount = 0; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j > 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "position": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { position = new World.Vector3D(x, y, z); } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { string Folder = System.IO.Path.GetDirectoryName(FileName); if (Interface.ContainsInvalidPathChars(Lines[i])) { Interface.AddMessage(Interface.MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string file = Interface.GetCombinedFileName(Folder, Lines[i]); if (System.IO.File.Exists(file)) { if (obj.Length == objCount) { Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length << 1); } obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false); objCount++; } else { Interface.AddMessage(Interface.MessageType.Error, true, "File " + file + " not found at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } } i++; } i--; for (int j = 0; j < objCount; j++) { if (obj[j] != null) { if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j]; s.Dynamic = 1; if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState(); aos.Object = s; aos.Position = position; a.States = new ObjectManager.AnimatedObjectState[] { aos }; Result.Objects[ObjectCount] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j]; for (int k = 0; k < a.Objects.Length; k++) { if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += position.X; a.Objects[k].States[h].Position.Y += position.Y; a.Objects[k].States[h].Position.Z += position.Z; } Result.Objects[ObjectCount] = a.Objects[k]; ObjectCount++; } } } } } break; case "[object]": { i++; if (Result.Objects.Length == ObjectCount) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } Result.Objects[ObjectCount] = new ObjectManager.AnimatedObject(); Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; Result.Objects[ObjectCount].CurrentState = -1; Result.Objects[ObjectCount].TranslateXDirection = new World.Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].TranslateYDirection = new World.Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].TranslateZDirection = new World.Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].RotateXDirection = new World.Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].RotateYDirection = new World.Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].RotateZDirection = new World.Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].TextureShiftXDirection = new World.Vector2D(1.0, 0.0); Result.Objects[ObjectCount].TextureShiftYDirection = new World.Vector2D(0.0, 1.0); Result.Objects[ObjectCount].RefreshRate = 0.0; Result.Objects[ObjectCount].ObjectIndex = -1; World.Vector3D Position = new World.Vector3D(0.0, 0.0, 0.0); string[] StateFiles = null; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j > 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "position": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Position = new World.Vector3D(x, y, z); } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "states": { string[] s = b.Split(','); if (s.Length >= 1) { string Folder = System.IO.Path.GetDirectoryName(FileName); StateFiles = new string[s.Length]; for (int k = 0; k < s.Length; k++) { s[k] = s[k].Trim(); if (Interface.ContainsInvalidPathChars(s[k])) { Interface.AddMessage(Interface.MessageType.Error, false, "File" + k.ToString(Culture) + " contains illegal characters in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); StateFiles[k] = null; } else { StateFiles[k] = Interface.GetCombinedFileName(Folder, s[k]); if (!System.IO.File.Exists(StateFiles[k])) { Interface.AddMessage(Interface.MessageType.Error, true, "File " + StateFiles[k] + " not found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); StateFiles[k] = null; } } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "At least one argument is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } break; case "statefunction": try { Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "statefunctionrpn": try { Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatexdirection": case "translateydirection": case "translatezdirection": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "translatexdirection": Result.Objects[ObjectCount].TranslateXDirection = new World.Vector3D(x, y, z); break; case "translateydirection": Result.Objects[ObjectCount].TranslateYDirection = new World.Vector3D(x, y, z); break; case "translatezdirection": Result.Objects[ObjectCount].TranslateZDirection = new World.Vector3D(x, y, z); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "translatexfunction": try { Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translateyfunction": try { Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatezfunction": try { Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatexfunctionrpn": try { Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translateyfunctionrpn": try { Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatezfunctionrpn": try { Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexdirection": case "rotateydirection": case "rotatezdirection": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (x == 0.0 & y == 0.0 & z == 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "The direction indicated by X, Y and Z is expected to be non-zero in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "rotatexdirection": Result.Objects[ObjectCount].RotateXDirection = new World.Vector3D(x, y, z); break; case "rotateydirection": Result.Objects[ObjectCount].RotateYDirection = new World.Vector3D(x, y, z); break; case "rotatezdirection": Result.Objects[ObjectCount].RotateZDirection = new World.Vector3D(x, y, z); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "rotatexfunction": try { Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotateyfunction": try { Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatezfunction": try { Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexfunctionrpn": try { Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotateyfunctionrpn": try { Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatezfunctionrpn": try { Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexdamping": case "rotateydamping": case "rotatezdamping": { string[] s = b.Split(','); if (s.Length == 2) { double nf, dr; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out nf)) { Interface.AddMessage(Interface.MessageType.Error, false, "NaturalFrequency is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out dr)) { Interface.AddMessage(Interface.MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (nf <= 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "NaturalFrequency is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (dr <= 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "DampingRatio is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "rotatexdamping": Result.Objects[ObjectCount].RotateXDamping = new ObjectManager.Damping(nf, dr); break; case "rotateydamping": Result.Objects[ObjectCount].RotateYDamping = new ObjectManager.Damping(nf, dr); break; case "rotatezdamping": Result.Objects[ObjectCount].RotateZDamping = new ObjectManager.Damping(nf, dr); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "textureshiftxdirection": case "textureshiftydirection": { string[] s = b.Split(','); if (s.Length == 2) { double x, y; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "textureshiftxdirection": Result.Objects[ObjectCount].TextureShiftXDirection = new World.Vector2D(x, y); break; case "textureshiftydirection": Result.Objects[ObjectCount].TextureShiftYDirection = new World.Vector2D(x, y); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "textureshiftxfunction": try { Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftyfunction": try { Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftxfunctionrpn": try { Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftyfunctionrpn": try { Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "refreshrate": { double r; if (!double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out r)) { Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (r < 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "Value is expected to be non-negative in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Result.Objects[ObjectCount].RefreshRate = r; } } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } i++; } i--; if (StateFiles != null) { Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[StateFiles.Length]; bool ForceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 | Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 | Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; for (int k = 0; k < StateFiles.Length; k++) { Result.Objects[ObjectCount].States[k].Position = new World.Vector3D(0.0, 0.0, 0.0); if (StateFiles[k] != null) { Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY); Result.Objects[ObjectCount].States[k].Object.Dynamic = 1; } else { Result.Objects[ObjectCount].States[k].Object = null; } for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++) { Result.Objects[ObjectCount].States[j].Position = Position; } } } else { Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; } ObjectCount++; } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } } Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, ObjectCount); return Result; }
// get mirrored object private static ObjectManager.UnifiedObject GetMirroredObject(ObjectManager.UnifiedObject Prototype) { if (Prototype is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)Prototype; return GetMirroredStaticObject(s); } else if (Prototype is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)Prototype; ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[a.Objects.Length]; for (int i = 0; i < a.Objects.Length; i++) { Result.Objects[i] = a.Objects[i].Clone(); for (int j = 0; j < a.Objects[i].States.Length; j++) { Result.Objects[i].States[j].Object = GetMirroredStaticObject(a.Objects[i].States[j].Object); } Result.Objects[i].TranslateXDirection.X *= -1.0; Result.Objects[i].TranslateYDirection.X *= -1.0; Result.Objects[i].TranslateZDirection.X *= -1.0; Result.Objects[i].RotateXDirection.X *= -1.0; Result.Objects[i].RotateYDirection.X *= -1.0; Result.Objects[i].RotateZDirection.X *= -1.0; } return Result; } else { return null; } }
// get transformed object private static ObjectManager.StaticObject GetTransformedStaticObject(ObjectManager.StaticObject Prototype, double NearDistance, double FarDistance) { ObjectManager.StaticObject Result = ObjectManager.CloneObject(Prototype); int n = 0; double x2 = 0.0, x3 = 0.0, x6 = 0.0, x7 = 0.0; for (int i = 0; i < Result.Mesh.Vertices.Length; i++) { if (n == 2) { x2 = Result.Mesh.Vertices[i].Coordinates.X; } else if (n == 3) { x3 = Result.Mesh.Vertices[i].Coordinates.X; } else if (n == 6) { x6 = Result.Mesh.Vertices[i].Coordinates.X; } else if (n == 7) { x7 = Result.Mesh.Vertices[i].Coordinates.X; } n++; if (n == 8) { break; } } if (n >= 4) { int m = 0; for (int i = 0; i < Result.Mesh.Vertices.Length; i++) { if (m == 0) { Result.Mesh.Vertices[i].Coordinates.X = NearDistance - x3; } else if (m == 1) { Result.Mesh.Vertices[i].Coordinates.X = FarDistance - x2; if (n < 8) { m = 8; break; } } else if (m == 4) { Result.Mesh.Vertices[i].Coordinates.X = NearDistance - x7; } else if (m == 5) { Result.Mesh.Vertices[i].Coordinates.X = NearDistance - x6; m = 8; break; } m++; if (m == 8) { break; } } } return Result; }
// ================================ // load binary x private static ObjectManager.StaticObject LoadBinaryX(string FileName, byte[] Data, int StartingPosition, System.Text.Encoding Encoding, int FloatingPointSize, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { // parse file Structure Structure; try { bool Result; using (System.IO.MemoryStream Stream = new System.IO.MemoryStream(Data)) { using (System.IO.BinaryReader Reader = new System.IO.BinaryReader(Stream)) { Stream.Position = StartingPosition; BinaryCache Cache = new BinaryCache(); Cache.IntegersRemaining = 0; Cache.FloatsRemaining = 0; Result = ReadBinaryTemplate(FileName, Reader, FloatingPointSize, new Template("", new string[] { "[...]" }), false, ref Cache, out Structure); Reader.Close(); } Stream.Close(); } if (!Result) { return null; } } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, "Unhandled error (" + ex.Message + ") encountered in binary X object file " + FileName); return null; } // process structure ObjectManager.StaticObject Object; if (!ProcessStructure(FileName, Structure, out Object, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY)) { return null; } return Object; }
internal static void AddObjectForCustomTimetable(ObjectManager.AnimatedObject obj) { }
private static void ApplyTranslation(ObjectManager.StaticObject Object, double x, double y, double z) { for (int i = 0; i < Object.Mesh.Vertices.Length; i++) { Object.Mesh.Vertices[i].Coordinates.X += x; Object.Mesh.Vertices[i].Coordinates.Y += y; Object.Mesh.Vertices[i].Coordinates.Z += z; } }
/// <summary>Checks whether the camera can move in the selected direction, due to a bounding box.</summary> /// <returns>True if we are able to move the camera, false otherwise</returns> internal static bool PerformBoundingBoxTest(ref ObjectManager.StaticObject bounding, ref Vector3 cameraLocation) { if (cameraLocation.X < bounding.Mesh.BoundingBox[0].X || cameraLocation.X > bounding.Mesh.BoundingBox[1].X || cameraLocation.Y < bounding.Mesh.BoundingBox[0].Y || cameraLocation.Y > bounding.Mesh.BoundingBox[1].Y || cameraLocation.Z < bounding.Mesh.BoundingBox[0].Z || cameraLocation.Z > bounding.Mesh.BoundingBox[1].Z) { //Our bounding boxes do not intersect return true; } return false; }
//Parses an XML background definition public static BackgroundManager.BackgroundHandle ReadBackgroundXML(string fileName) { List <BackgroundManager.StaticBackground> Backgrounds = new List <BackgroundManager.StaticBackground>(); //The current XML file to load XmlDocument currentXML = new XmlDocument(); //Load the object's XML file currentXML.Load(fileName); string Path = System.IO.Path.GetDirectoryName(fileName); double[] UnitOfLength = { 1.0 }; //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/Background"); //Check this file actually contains OpenBVE light definition nodes if (DocumentNodes != null) { foreach (XmlNode n in DocumentNodes) { if (n.ChildNodes.OfType <XmlElement>().Any()) { double DisplayTime = -1; //The time to transition between backgrounds in seconds double TransitionTime = 0.8; //The texture to use (if static) Texture t = null; //The object to use (if object based) ObjectManager.StaticObject o = null; //The transition mode between backgrounds BackgroundManager.BackgroundTransitionMode mode = BackgroundManager.BackgroundTransitionMode.FadeIn; //The number of times the texture is repeated around the viewing frustrum (if appropriate) double repetitions = 6; foreach (XmlNode c in n.ChildNodes) { string[] Arguments = c.InnerText.Split(','); switch (c.Name.ToLowerInvariant()) { case "mode": switch (c.InnerText.ToLowerInvariant()) { case "fadein": mode = BackgroundManager.BackgroundTransitionMode.FadeIn; break; case "fadeout": mode = BackgroundManager.BackgroundTransitionMode.FadeOut; break; case "none": mode = BackgroundManager.BackgroundTransitionMode.None; break; default: Interface.AddMessage(MessageType.Error, true, c.InnerText + "is not a valid background fade mode in file " + fileName); break; } break; case "object": string f; try { f = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(fileName), c.InnerText); } catch { Interface.AddMessage(MessageType.Error, true, "BackgroundObject FileName is malformed in file " + fileName); break; } if (!System.IO.File.Exists(f)) { Interface.AddMessage(MessageType.Error, true, "FileName " + f + " not found in file " + fileName); } else { UnifiedObject b = ObjectManager.LoadObject(f, System.Text.Encoding.Default, false, false, false); o = (ObjectManager.StaticObject)b; } break; case "repetitions": if (!NumberFormats.TryParseDoubleVb6(Arguments[0], UnitOfLength, out repetitions)) { Interface.AddMessage(MessageType.Error, false, c.InnerText + " does not parse to a valid number of repetitions in " + fileName); } break; case "texture": string file; try { file = OpenBveApi.Path.CombineFile(Path, c.InnerText); } catch { Interface.AddMessage(MessageType.Error, true, "BackgroundTexture FileName is malformed in file " + fileName); break; } if (!System.IO.File.Exists(file)) { Interface.AddMessage(MessageType.Error, false, "The background texture file " + c.InnerText + " does not exist in " + fileName); } else { Textures.RegisterTexture(file, out t); } break; case "time": if (!Interface.TryParseTime(Arguments[0].Trim(), out DisplayTime)) { Interface.AddMessage(MessageType.Error, false, c.InnerText + " does not parse to a valid time in file " + fileName); } break; case "transitiontime": if (!NumberFormats.TryParseDoubleVb6(Arguments[0], UnitOfLength, out TransitionTime)) { Interface.AddMessage(MessageType.Error, false, c.InnerText + " is not a valid background transition time in " + fileName); } break; } } //Create background if texture is not null if (t != null && o == null) { Backgrounds.Add(new BackgroundManager.StaticBackground(t, repetitions, false, TransitionTime, mode, DisplayTime)); } if (t == null && o != null) { //All other parameters are ignored if an object has been defined //TODO: Error message stating they have been ignored return(new BackgroundManager.BackgroundObject(o)); } } } if (Backgrounds.Count == 1) { return(Backgrounds[0]); } if (Backgrounds.Count > 1) { //Sort list- Not worried about when they start or end, so use simple LINQ Backgrounds = Backgrounds.OrderBy(o => o.Time).ToList(); //If more than 2 backgrounds, convert to array and return a new dynamic background return(new BackgroundManager.DynamicBackground(Backgrounds.ToArray())); } } } //We couldn't find any valid XML, so return false throw new InvalidDataException(); }
private void button1_Click(object sender, EventArgs e) { TextureManager.InterpolationMode previousInterpolationMode = Interface.CurrentOptions.Interpolation; int previousAntialasingLevel = Interface.CurrentOptions.AntialiasingLevel; int previousAnsiotropicLevel = Interface.CurrentOptions.AnisotropicFilteringLevel; //Interpolation mode switch (InterpolationMode.SelectedIndex) { case 0: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.NearestNeighbor; break; case 1: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.Bilinear; break; case 2: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.NearestNeighborMipmapped; break; case 3: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.BilinearMipmapped; break; case 4: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.TrilinearMipmapped; break; case 5: Interface.CurrentOptions.Interpolation = TextureManager.InterpolationMode.AnisotropicFiltering; break; } //Ansiotropic filtering level Interface.CurrentOptions.AnisotropicFilteringLevel = (int)AnsiotropicLevel.Value; //Antialiasing level Interface.CurrentOptions.AntialiasingLevel = (int)AntialiasingLevel.Value; if (Interface.CurrentOptions.AntialiasingLevel != previousAntialasingLevel) { Program.currentGraphicsMode = new GraphicsMode(new ColorFormat(8, 8, 8, 8), 24, 8, Interface.CurrentOptions.AntialiasingLevel); } //Transparency quality switch (TransparencyQuality.SelectedIndex) { case 0: Interface.CurrentOptions.TransparencyMode = Renderer.TransparencyMode.Sharp; break; default: Interface.CurrentOptions.TransparencyMode = Renderer.TransparencyMode.Smooth; break; } //Set width and height if (Renderer.ScreenWidth != width.Value || Renderer.ScreenHeight != height.Value) { Renderer.ScreenWidth = (int)width.Value; Renderer.ScreenHeight = (int)height.Value; Program.currentGameWindow.Width = (int)width.Value; Program.currentGameWindow.Height = (int)height.Value; Program.UpdateViewport(); } //Check if interpolation mode or ansiotropic filtering level has changed, and trigger a reload if (previousInterpolationMode != Interface.CurrentOptions.Interpolation || previousAnsiotropicLevel != Interface.CurrentOptions.AnisotropicFilteringLevel) { Program.ReducedMode = false; Program.LightingRelative = -1.0; Game.Reset(); TextureManager.UnuseAllTextures(); Fonts.Initialize(); Interface.ClearMessages(); for (int i = 0; i < Program.Files.Length; i++) { #if !DEBUG try { #endif ObjectManager.UnifiedObject o = ObjectManager.LoadObject(Program.Files[i], System.Text.Encoding.UTF8, ObjectManager.ObjectLoadMode.Normal, false, false, false); ObjectManager.CreateObject(o, new Vector3(0.0, 0.0, 0.0), new World.Transformation(0.0, 0.0, 0.0), new World.Transformation(0.0, 0.0, 0.0), true, 0.0, 0.0, 25.0, 0.0); #if !DEBUG } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Critical, false, "Unhandled error (" + ex.Message + ") encountered while processing the file " + Program.Files[i] + "."); } #endif } ObjectManager.InitializeVisibility(); ObjectManager.UpdateVisibility(0.0, true); ObjectManager.UpdateAnimatedWorldObjects(0.01, true); } Renderer.TransparentColorDepthSorting = Interface.CurrentOptions.TransparencyMode == Renderer.TransparencyMode.Smooth & Interface.CurrentOptions.Interpolation != TextureManager.InterpolationMode.NearestNeighbor & Interface.CurrentOptions.Interpolation != TextureManager.InterpolationMode.Bilinear; Options.SaveOptions(); this.Close(); }
/// <summary>Creates a new background object</summary> /// <param name="Object">The object to use for the background</param> internal BackgroundObject(ObjectManager.StaticObject Object) { this.ObjectBackground = Object; }
internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { XmlDocument currentXML = new XmlDocument(); //May need to be changed to use de-DE System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[0]; int ObjectCount = 0; currentXML.Load(FileName); string BaseDir = System.IO.Path.GetDirectoryName(FileName); GruppenObject[] CurrentObjects = new GruppenObject[0]; //Check for null if (currentXML.DocumentElement != null) { ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[0]; XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT"); if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.HasChildNodes) { foreach (XmlNode node in outerNode.ChildNodes) { if (node.Name == "Object" && node.HasChildNodes) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { Array.Resize<GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1); GruppenObject Object = new GruppenObject(); foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { case "Name": string ObjectFile = OpenBveApi.Path.CombineFile(BaseDir,attribute.Value); Object.Name = ObjectFile; ObjectCount++; break; case "Position": string[] SplitPosition = attribute.Value.Split(';'); double.TryParse(SplitPosition[0], out Object.Position.X); double.TryParse(SplitPosition[1], out Object.Position.Y); double.TryParse(SplitPosition[2], out Object.Position.Z); break; case "Rotation": string[] SplitRotation = attribute.Value.Split(';'); double.TryParse(SplitRotation[0], out Object.RotationX); double.TryParse(SplitRotation[1], out Object.RotationY); double.TryParse(SplitRotation[2], out Object.RotationZ); break; } } CurrentObjects[CurrentObjects.Length - 1] = Object; } } } } } } //We've loaded the XML references, now load the objects into memory for (int i = 0; i < CurrentObjects.Length; i++) { var Object = ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, LoadMode, false, false, false, CurrentObjects[i].RotationX, CurrentObjects[i].RotationY, CurrentObjects[i].RotationZ); if (Object != null) { Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length +1); obj[obj.Length - 1] = Object; } } for (int j = 0; j < obj.Length; j++) { if (obj[j] != null) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1); if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject) obj[j]; s.Dynamic = true; ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState { Object = s, Position = CurrentObjects[j].Position }; a.States = new ObjectManager.AnimatedObjectState[] {aos}; Result.Objects[j] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection) obj[j]; for (int k = 0; k < a.Objects.Length; k++) { for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += CurrentObjects[j].Position.X; a.Objects[k].States[h].Position.Y += CurrentObjects[j].Position.Y; a.Objects[k].States[h].Position.Z += CurrentObjects[j].Position.Z; } Result.Objects[j] = a.Objects[k]; ObjectCount++; } } } } } return Result; } //Didn't find an acceptable XML object //Probably will cause things to throw an absolute wobbly somewhere.... return null; }
// parse animated object config /// <summary>Loads a collection of animated objects from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <returns>The collection of animated objects.</returns> internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[4]; int ObjectCount = 0; // load file string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); bool rpnUsed = false; for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); // cut comments out Lines[i] = j >= 0 ? Lines[i].Substring(0, j).Trim() : Lines[i].Trim(); rpnUsed = Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0; } if (rpnUsed) { Debug.AddMessage(Debug.MessageType.Error, false, "An animated object file contains RPN functions. These were never meant to be used directly, only for debugging. They won't be supported indefinately. Please get rid of them in file " + FileName); } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[include]": { i++; Vector3D position = new Vector3D(0.0, 0.0, 0.0); ObjectManager.UnifiedObject[] obj = new ObjectManager.UnifiedObject[4]; int objCount = 0; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) && Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int equals = Lines[i].IndexOf("=", StringComparison.Ordinal); if (equals > 0) { /* * Process key-value pair, the only supported key is position. */ string before = Lines[i].Substring(0, equals).TrimEnd(); string after = Lines[i].Substring(equals + 1).TrimStart(); switch (before.ToLowerInvariant()) { case "position": ParsePosition(after, ref position, before, i + 1, FileName); break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The attribute " + before + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { /* * Process object with file name relative to the location of this ANIMATED file. */ string Folder = System.IO.Path.GetDirectoryName(FileName); if (Path.ContainsInvalidPathChars(Lines[i])) { Debug.AddMessage(Debug.MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string file = OpenBveApi.Path.CombineFile(Folder, Lines[i]); if (System.IO.File.Exists(file)) { if (obj.Length == objCount) { Array.Resize <ObjectManager.UnifiedObject>(ref obj, obj.Length << 1); } obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false); objCount++; } else { Debug.AddMessage(Debug.MessageType.Error, true, "File " + file + " not found at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } } i++; } i--; for (int j = 0; j < objCount; j++) { if (obj[j] != null) { if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j]; s.Dynamic = true; if (ObjectCount >= Result.Objects.Length) { Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState(); aos.Object = s; aos.Position = position; a.States = new[] { aos }; Result.Objects[ObjectCount] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j]; for (int k = 0; k < a.Objects.Length; k++) { if (ObjectCount >= Result.Objects.Length) { Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += position.X; a.Objects[k].States[h].Position.Y += position.Y; a.Objects[k].States[h].Position.Z += position.Z; } Result.Objects[ObjectCount] = a.Objects[k]; ObjectCount++; } } } } } break; case "[object]": { i++; if (Result.Objects.Length == ObjectCount) { Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } Result.Objects[ObjectCount] = new ObjectManager.AnimatedObject(); Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; Result.Objects[ObjectCount].CurrentState = -1; Result.Objects[ObjectCount].TranslateXDirection = new Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].TranslateYDirection = new Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].TranslateZDirection = new Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].RotateXDirection = new Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].RotateYDirection = new Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].RotateZDirection = new Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].TextureShiftXDirection = new Vector2D(1.0, 0.0); Result.Objects[ObjectCount].TextureShiftYDirection = new Vector2D(0.0, 1.0); Result.Objects[ObjectCount].RefreshRate = 0.0; Result.Objects[ObjectCount].ObjectIndex = -1; Vector3D Position = new Vector3D(0.0, 0.0, 0.0); bool timetableUsed = false; string[] StateFiles = null; string StateFunctionRpn = null; int StateFunctionLine = -1; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) && Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int equals = Lines[i].IndexOf("=", StringComparison.Ordinal); if (equals > 0) { string before = Lines[i].Substring(0, equals).TrimEnd(); string after = Lines[i].Substring(equals + 1).TrimStart(); switch (before.ToLowerInvariant()) { case "position": ParsePosition(after, ref Position, before, i + 1, FileName); break; case "states": if (!ParseState(after, ref StateFiles, before, i + 1, FileName)) { return(null); } break; case "statefunction": try { StateFunctionLine = i; StateFunctionRpn = FunctionScripts.GetPostfixNotationFromInfixNotation(after); } catch (Exception ex) { Debug.AddMessage(Debug.MessageType.Error, false, ex.Message + " in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "statefunctionrpn": { StateFunctionLine = i; StateFunctionRpn = after; } break; case "translatexdirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateXDirection, before, i + 1, FileName); break; case "translateydirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateYDirection, before, i + 1, FileName); break; case "translatezdirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateZDirection, before, i + 1, FileName); break; case "translatexfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateXFunction, before, i + 1, FileName); break; case "translateyfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateYFunction, before, i + 1, FileName); break; case "translatezfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateZFunction, before, i + 1, FileName); break; case "translatexfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateXFunction, before, i + 1, FileName); break; case "translateyfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateYFunction, before, i + 1, FileName); break; case "translatezfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateZFunction, before, i + 1, FileName); break; case "rotatexdirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateXDirection, before, i + 1, FileName); break; case "rotateydirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateYDirection, before, i + 1, FileName); break; case "rotatezdirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateZDirection, before, i + 1, FileName); break; case "rotatexfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].RotateXFunction, before, i + 1, FileName); break; case "rotateyfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].RotateYFunction, before, i + 1, FileName); break; case "rotatezfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].RotateZFunction, before, i + 1, FileName); break; case "rotatexfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].RotateXFunction, before, i + 1, FileName); break; case "rotateyfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].RotateYFunction, before, i + 1, FileName); break; case "rotatezfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].RotateZFunction, before, i + 1, FileName); break; case "rotatexdamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateXDamping, before, i + 1, FileName); break; case "rotateydamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateYDamping, before, i + 1, FileName); break; case "rotatezdamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateZDamping, before, i + 1, FileName); break; case "textureshiftxdirection": ParseTextureShift(after, ref Result.Objects[ObjectCount].TextureShiftXDirection, before, i + 1, FileName); break; case "textureshiftydirection": ParseTextureShift(after, ref Result.Objects[ObjectCount].TextureShiftYDirection, before, i + 1, FileName); break; case "textureshiftxfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftXFunction, before, i + 1, FileName); break; case "textureshiftyfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftYFunction, before, i + 1, FileName); break; case "textureshiftxfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftXFunction, before, i + 1, FileName); break; case "textureshiftyfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftYFunction, before, i + 1, FileName); break; case "textureoverride": switch (after.ToLowerInvariant()) { case "none": break; case "timetable": if (!timetableUsed) { Timetable.AddObjectForCustomTimetable(Result.Objects[ObjectCount]); timetableUsed = true; } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "Unrecognized value in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } break; case "refreshrate": { double r; if (!double.TryParse(after, System.Globalization.NumberStyles.Float, Culture, out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Value is invalid in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (r < 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Value is expected to be non-negative in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Result.Objects[ObjectCount].RefreshRate = r; } } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The attribute " + before + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return(null); } } i++; } i--; if (StateFiles != null) { // create the object if (timetableUsed) { if (StateFunctionRpn != null) { StateFunctionRpn = "timetable 0 == " + StateFunctionRpn + " -1 ?"; } else { StateFunctionRpn = "timetable"; } } if (StateFunctionRpn != null) { try { Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(StateFunctionRpn); } catch (Exception ex) { Debug.AddMessage(Debug.MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName); } } Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[StateFiles.Length]; bool ForceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null && Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 || Result.Objects[ObjectCount].TextureShiftYFunction != null && Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null && Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 || Result.Objects[ObjectCount].TextureShiftYFunction != null && Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; for (int k = 0; k < StateFiles.Length; k++) { Result.Objects[ObjectCount].States[k].Position = new Vector3D(0.0, 0.0, 0.0); if (StateFiles[k] != null) { Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY); if (Result.Objects[ObjectCount].States[k].Object != null) { Result.Objects[ObjectCount].States[k].Object.Dynamic = true; } } else { Result.Objects[ObjectCount].States[k].Object = null; } for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++) { Result.Objects[ObjectCount].States[j].Position = Position; } } } else { Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; } ObjectCount++; } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return(null); } } } Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, ObjectCount); return(Result); }
/// <summary> /// Parses the string representing rotation damping to a ObjectManager.Damping. Error messages are written when necessary. /// </summary> /// <param name="value">String representation.</param> /// <param name="damp">Output damping.</param> /// <param name="field">Actual field name.</param> /// <param name="filename">File name.</param> /// <param name="line">Actual line number string.</param> private static void ParseRotateDamping(string value, ref ObjectManager.Damping damp, string field, int line, string filename){ string[] s = value.Split(','); if (s.Length == 2) { double nf, dr; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out nf)) { Debug.AddMessage(Debug.MessageType.Error, false, "NaturalFrequency is invalid in " + field + " at line " + line.ToString(Culture) + " in file " + filename); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out dr)) { Debug.AddMessage(Debug.MessageType.Error, false, "DampingRatio is invalid in " + field + " at line " + line.ToString(Culture) + " in file " + filename); } else if (nf <= 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "NaturalFrequency is expected to be positive in " + field + " at line " + line.ToString(Culture) + " in file " + filename); } else if (dr <= 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "DampingRatio is expected to be positive in " + field + " at line " + line.ToString(Culture) + " in file " + filename); } else { damp = new ObjectManager.Damping(nf, dr); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Exactly 2 arguments are expected in " + field + " at line " + line.ToString(Culture) + " in file " + filename); } }
// parse animated object config /// <summary>Loads a collection of animated objects from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <returns>The collection of animated objects.</returns> internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[4]; int ObjectCount = 0; // load file string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); bool rpnUsed = false; for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); // cut comments out Lines[i] = j >= 0 ? Lines[i].Substring(0, j).Trim() : Lines[i].Trim(); rpnUsed = Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0; } if (rpnUsed) { Debug.AddMessage(Debug.MessageType.Error, false, "An animated object file contains RPN functions. These were never meant to be used directly, only for debugging. They won't be supported indefinately. Please get rid of them in file " + FileName); } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[include]": { i++; Vector3D position = new Vector3D(0.0, 0.0, 0.0); ObjectManager.UnifiedObject[] obj = new ObjectManager.UnifiedObject[4]; int objCount = 0; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) && Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int equals = Lines[i].IndexOf("=", StringComparison.Ordinal); if (equals > 0) { /* * Process key-value pair, the only supported key is position. */ string before = Lines[i].Substring(0, equals).TrimEnd(); string after = Lines[i].Substring(equals + 1).TrimStart(); switch (before.ToLowerInvariant()) { case "position": ParsePosition(after, ref position, before, i + 1, FileName); break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The attribute " + before + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { /* * Process object with file name relative to the location of this ANIMATED file. */ string Folder = System.IO.Path.GetDirectoryName(FileName); if (Path.ContainsInvalidPathChars(Lines[i])) { Debug.AddMessage(Debug.MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string file = OpenBveApi.Path.CombineFile(Folder, Lines[i]); if (System.IO.File.Exists(file)) { if (obj.Length == objCount) { Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length << 1); } obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false); objCount++; } else { Debug.AddMessage(Debug.MessageType.Error, true, "File " + file + " not found at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } } i++; } i--; for (int j = 0; j < objCount; j++) { if (obj[j] != null) { if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j]; s.Dynamic = true; if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState(); aos.Object = s; aos.Position = position; a.States = new[] { aos }; Result.Objects[ObjectCount] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j]; for (int k = 0; k < a.Objects.Length; k++) { if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += position.X; a.Objects[k].States[h].Position.Y += position.Y; a.Objects[k].States[h].Position.Z += position.Z; } Result.Objects[ObjectCount] = a.Objects[k]; ObjectCount++; } } } } } break; case "[object]": { i++; if (Result.Objects.Length == ObjectCount) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } Result.Objects[ObjectCount] = new ObjectManager.AnimatedObject(); Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; Result.Objects[ObjectCount].CurrentState = -1; Result.Objects[ObjectCount].TranslateXDirection = new Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].TranslateYDirection = new Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].TranslateZDirection = new Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].RotateXDirection = new Vector3D(1.0, 0.0, 0.0); Result.Objects[ObjectCount].RotateYDirection = new Vector3D(0.0, 1.0, 0.0); Result.Objects[ObjectCount].RotateZDirection = new Vector3D(0.0, 0.0, 1.0); Result.Objects[ObjectCount].TextureShiftXDirection = new Vector2D(1.0, 0.0); Result.Objects[ObjectCount].TextureShiftYDirection = new Vector2D(0.0, 1.0); Result.Objects[ObjectCount].RefreshRate = 0.0; Result.Objects[ObjectCount].ObjectIndex = -1; Vector3D Position = new Vector3D(0.0, 0.0, 0.0); bool timetableUsed = false; string[] StateFiles = null; string StateFunctionRpn = null; int StateFunctionLine = -1; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) && Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int equals = Lines[i].IndexOf("=", StringComparison.Ordinal); if (equals > 0) { string before = Lines[i].Substring(0, equals).TrimEnd(); string after = Lines[i].Substring(equals + 1).TrimStart(); switch (before.ToLowerInvariant()) { case "position": ParsePosition(after, ref Position, before, i + 1, FileName); break; case "states": if (!ParseState(after, ref StateFiles, before, i + 1, FileName)) return null; break; case "statefunction": try { StateFunctionLine = i; StateFunctionRpn = FunctionScripts.GetPostfixNotationFromInfixNotation(after); } catch (Exception ex) { Debug.AddMessage(Debug.MessageType.Error, false, ex.Message + " in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "statefunctionrpn": { StateFunctionLine = i; StateFunctionRpn = after; } break; case "translatexdirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateXDirection, before, i + 1, FileName); break; case "translateydirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateYDirection, before, i + 1, FileName); break; case "translatezdirection": ParseTranslateDirection(after, ref Result.Objects[ObjectCount].TranslateZDirection, before, i + 1, FileName); break; case "translatexfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateXFunction, before, i + 1, FileName); break; case "translateyfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateYFunction, before, i + 1, FileName); break; case "translatezfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TranslateZFunction, before, i + 1, FileName); break; case "translatexfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateXFunction, before, i + 1, FileName); break; case "translateyfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateYFunction, before, i + 1, FileName); break; case "translatezfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TranslateZFunction, before, i + 1, FileName); break; case "rotatexdirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateXDirection, before, i + 1, FileName); break; case "rotateydirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateYDirection, before, i + 1, FileName); break; case "rotatezdirection": ParseRotateDirection(after, ref Result.Objects[ObjectCount].RotateZDirection, before, i + 1, FileName); break; case "rotatexfunction": ParseInfixFunc(after,ref Result.Objects[ObjectCount].RotateXFunction, before,i+1,FileName); break; case "rotateyfunction": ParseInfixFunc(after,ref Result.Objects[ObjectCount].RotateYFunction, before,i+1,FileName); break; case "rotatezfunction": ParseInfixFunc(after,ref Result.Objects[ObjectCount].RotateZFunction, before,i+1,FileName); break; case "rotatexfunctionrpn": ParsePostfixFunc(after,ref Result.Objects[ObjectCount].RotateXFunction, before,i+1,FileName); break; case "rotateyfunctionrpn": ParsePostfixFunc(after,ref Result.Objects[ObjectCount].RotateYFunction, before,i+1,FileName); break; case "rotatezfunctionrpn": ParsePostfixFunc(after,ref Result.Objects[ObjectCount].RotateZFunction, before,i+1,FileName); break; case "rotatexdamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateXDamping,before, i + 1, FileName); break; case "rotateydamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateYDamping,before, i + 1, FileName); break; case "rotatezdamping": ParseRotateDamping(after, ref Result.Objects[ObjectCount].RotateZDamping,before, i + 1, FileName); break; case "textureshiftxdirection": ParseTextureShift(after, ref Result.Objects[ObjectCount].TextureShiftXDirection, before, i + 1, FileName); break; case "textureshiftydirection": ParseTextureShift(after, ref Result.Objects[ObjectCount].TextureShiftYDirection, before, i + 1, FileName); break; case "textureshiftxfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftXFunction, before, i + 1, FileName); break; case "textureshiftyfunction": ParseInfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftYFunction, before, i + 1, FileName); break; case "textureshiftxfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftXFunction, before, i + 1, FileName); break; case "textureshiftyfunctionrpn": ParsePostfixFunc(after, ref Result.Objects[ObjectCount].TextureShiftYFunction, before, i + 1, FileName); break; case "textureoverride": switch (after.ToLowerInvariant()) { case "none": break; case "timetable": if (!timetableUsed) { Timetable.AddObjectForCustomTimetable(Result.Objects[ObjectCount]); timetableUsed = true; } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "Unrecognized value in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } break; case "refreshrate": { double r; if (!double.TryParse(after, System.Globalization.NumberStyles.Float, Culture, out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Value is invalid in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (r < 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Value is expected to be non-negative in " + before + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Result.Objects[ObjectCount].RefreshRate = r; } } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The attribute " + before + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } i++; } i--; if (StateFiles != null) { // create the object if (timetableUsed) { if (StateFunctionRpn != null) { StateFunctionRpn = "timetable 0 == " + StateFunctionRpn + " -1 ?"; } else { StateFunctionRpn = "timetable"; } } if (StateFunctionRpn != null) { try { Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(StateFunctionRpn); } catch (Exception ex) { Debug.AddMessage(Debug.MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName); } } Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[StateFiles.Length]; bool ForceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null && Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 || Result.Objects[ObjectCount].TextureShiftYFunction != null && Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null && Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 || Result.Objects[ObjectCount].TextureShiftYFunction != null && Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; for (int k = 0; k < StateFiles.Length; k++) { Result.Objects[ObjectCount].States[k].Position = new Vector3D(0.0, 0.0, 0.0); if (StateFiles[k] != null) { Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY); if (Result.Objects[ObjectCount].States[k].Object != null) { Result.Objects[ObjectCount].States[k].Object.Dynamic = true; } } else { Result.Objects[ObjectCount].States[k].Object = null; } for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++) { Result.Objects[ObjectCount].States[j].Position = Position; } } } else { Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; } ObjectCount++; } break; default: Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } } Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, ObjectCount); return Result; }
private static void ApplyShear(ObjectManager.StaticObject Object, double dx, double dy, double dz, double sx, double sy, double sz, double r) { for (int j = 0; j < Object.Mesh.Vertices.Length; j++) { double n = r * (dx * Object.Mesh.Vertices[j].Coordinates.X + dy * Object.Mesh.Vertices[j].Coordinates.Y + dz * Object.Mesh.Vertices[j].Coordinates.Z); Object.Mesh.Vertices[j].Coordinates.X += sx * n; Object.Mesh.Vertices[j].Coordinates.Y += sy * n; Object.Mesh.Vertices[j].Coordinates.Z += sz * n; } double ux, uy, uz; World.Cross(sx, sy, sz, dx, dy, dz, out ux, out uy, out uz); for (int j = 0; j < Object.Mesh.Faces.Length; j++) { for (int k = 0; k < Object.Mesh.Faces[j].Vertices.Length; k++) { if (Object.Mesh.Faces[j].Vertices[k].Normal.X != 0.0f | Object.Mesh.Faces[j].Vertices[k].Normal.Y != 0.0f | Object.Mesh.Faces[j].Vertices[k].Normal.Z != 0.0f) { double nx = (double)Object.Mesh.Faces[j].Vertices[k].Normal.X; double ny = (double)Object.Mesh.Faces[j].Vertices[k].Normal.Y; double nz = (double)Object.Mesh.Faces[j].Vertices[k].Normal.Z; double n = r * (sx * nx + sy * ny + sz * nz); nx -= dx * n; ny -= dy * n; nz -= dz * n; World.Normalize(ref nx, ref ny, ref nz); Object.Mesh.Faces[j].Vertices[k].Normal.X = (float)nx; Object.Mesh.Faces[j].Vertices[k].Normal.Y = (float)ny; Object.Mesh.Faces[j].Vertices[k].Normal.Z = (float)nz; } } } }
//This renders the frame protected override void OnRenderFrame(FrameEventArgs e) { if (!firstFrame) { //If the load is not complete, then we shouldn't be running the mainloop return; } double TimeElapsed = RenderTimeElapsed; double RealTimeElapsed = RenderRealTimeElapsed; //Next, check if we're in paused/ in a menu if (Game.CurrentInterface != Game.InterfaceType.Normal) { MainLoop.UpdateControlRepeats(0.0); MainLoop.ProcessKeyboard(); MainLoop.ProcessControls(0.0); if (Game.CurrentInterface == Game.InterfaceType.Pause) { System.Threading.Thread.Sleep(10); } //Renderer.UpdateLighting(); Program.Renderer.RenderScene(TimeElapsed); Program.currentGameWindow.SwapBuffers(); if (MainLoop.Quit != MainLoop.QuitMode.ContinueGame) { Close(); if (Program.CurrentlyRunningOnMono && MainLoop.Quit == MainLoop.QuitMode.QuitProgram) { Environment.Exit(0); } } //If the menu state has not changed, don't update the rendered simulation return; } //Use the OpenTK framerate as this is much more accurate //Also avoids running a calculation if (TotalTimeElapsedForInfo >= 0.2) { Program.Renderer.FrameRate = RenderFrequency; TotalTimeElapsedForInfo = 0.0; } if (Game.PreviousInterface != Game.InterfaceType.Normal) { ObjectManager.UpdateAnimatedWorldObjects(0.0, false); Game.PreviousInterface = Game.InterfaceType.Normal; } else { ObjectManager.UpdateAnimatedWorldObjects(TimeElapsed, false); } //We need to update the camera position in the render sequence //Not doing this means that the camera doesn't move // update in one piece if (Program.Renderer.Camera.CurrentMode == CameraViewMode.Interior | Program.Renderer.Camera.CurrentMode == CameraViewMode.InteriorLookAhead) { //Update the in-car camera based upon the current driver car (Cabview or passenger view) TrainManager.PlayerTrain.Cars[World.CameraCar].UpdateCamera(); } else if (Program.Renderer.Camera.CurrentMode == CameraViewMode.Exterior) { //Update the camera position based upon the relative car position TrainManager.PlayerTrain.Cars[World.CameraCar].UpdateCamera(); } if (Program.Renderer.Camera.CurrentRestriction == CameraRestrictionMode.NotAvailable) { TrainManager.PlayerTrain.DriverBody.Update(TimeElapsed); } //Check if we are running at an accelerated time factor- //Camera motion speed should be the same whatever the game speed is if (TimeFactor != 1) { World.UpdateAbsoluteCamera(TimeElapsed / TimeFactor); } else { World.UpdateAbsoluteCamera(TimeElapsed); } TrainManager.UpdateTrainObjects(TimeElapsed, false); if (Program.Renderer.Camera.CurrentMode == CameraViewMode.Interior | Program.Renderer.Camera.CurrentMode == CameraViewMode.InteriorLookAhead | Program.Renderer.Camera.CurrentMode == CameraViewMode.Exterior) { Program.Renderer.UpdateVisibility(World.CameraTrackFollower.TrackPosition + Program.Renderer.Camera.Alignment.Position.Z); int d = TrainManager.PlayerTrain.DriverCar; Program.Renderer.Camera.CurrentSpeed = TrainManager.PlayerTrain.Cars[d].CurrentSpeed; } else { Program.Renderer.Camera.CurrentSpeed = 0.0; } Program.Renderer.Camera.AlignmentDirection = new CameraAlignment(); if (MainLoop.Quit != MainLoop.QuitMode.ContinueGame) { Program.currentGameWindow.Exit(); if (Program.CurrentlyRunningOnMono && MainLoop.Quit == MainLoop.QuitMode.QuitProgram) { Environment.Exit(0); } } Program.Renderer.Lighting.UpdateLighting(Program.CurrentRoute.SecondsSinceMidnight); Program.Renderer.RenderScene(TimeElapsed); Program.Sounds.Update(TimeElapsed, Interface.CurrentOptions.SoundModel); Program.currentGameWindow.SwapBuffers(); Game.UpdateBlackBox(); // pause/menu // limit framerate if (MainLoop.LimitFramerate) { System.Threading.Thread.Sleep(10); } MainLoop.UpdateControlRepeats(RealTimeElapsed); MainLoop.ProcessKeyboard(); MainLoop.UpdateMouse(RealTimeElapsed); MainLoop.ProcessControls(TimeElapsed); for (int i = 0; i < JoystickManager.AttachedJoysticks.Length; i++) { var railDriver = JoystickManager.AttachedJoysticks[i] as JoystickManager.Raildriver; if (railDriver != null) { if (Interface.CurrentOptions.RailDriverMPH) { railDriver.SetDisplay((int)(TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar].Specs .CurrentPerceivedSpeed * 2.23694)); } else { railDriver.SetDisplay((int)(TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar].Specs .CurrentPerceivedSpeed * 3.6)); } } } RenderRealTimeElapsed = 0.0; RenderTimeElapsed = 0.0; #if DEBUG MainLoop.CheckForOpenGlError("MainLoop"); #endif if (Interface.CurrentOptions.UnloadUnusedTextures) { Textures.UnloadUnusedTextures(TimeElapsed); } // finish try { Interface.SaveLogs(); } catch { } }
// ================================ // load textual x private static ObjectManager.StaticObject LoadTextualX(string FileName, string Text, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { // load string[] Lines = Text.Replace("\u000D\u000A", "\u2028").Split(new char[] { '\u000A', '\u000C', '\u000D', '\u0085', '\u2028', '\u2029' }, StringSplitOptions.None); // strip away comments bool Quote = false; for (int i = 0; i < Lines.Length; i++) { for (int j = 0; j < Lines[i].Length; j++) { if (Lines[i][j] == '"') Quote = !Quote; if (!Quote) { if (Lines[i][j] == '#' || j < Lines[i].Length - 1 && Lines[i].Substring(j, 2) == "//") { Lines[i] = Lines[i].Substring(0, j); break; } } } } // strip away header if (Lines.Length == 0 || Lines[0].Length < 16) { Interface.AddMessage(Interface.MessageType.Error, false, "The textual X object file is invalid at line 1 in " + FileName); return null; } Lines[0] = Lines[0].Substring(16); // join lines System.Text.StringBuilder Builder = new System.Text.StringBuilder(); for (int i = 0; i < Lines.Length; i++) { Builder.Append(Lines[i]); } string Content = Builder.ToString(); // parse file int Position = 0; Structure Structure; if (!ReadTextualTemplate(FileName, Content, ref Position, new Template("", new string[] { "[...]" }), false, out Structure)) { return null; } // process structure ObjectManager.StaticObject Object; if (!ProcessStructure(FileName, Structure, out Object, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY)) { return null; } return Object; }
/// <summary>Attempts to load and parse the current train's panel configuration file.</summary> /// <param name="TrainPath">The absolute on-disk path to the train folder.</param> /// <param name="Encoding">The automatically detected or manually set encoding of the panel configuration file.</param> /// <param name="Train">The base train on which to apply the panel configuration.</param> internal static void ParsePanelConfig(string TrainPath, System.Text.Encoding Encoding, Train Train) { Train.Cars[Train.DriverCar].CarSections = new CarSection[1]; Train.Cars[Train.DriverCar].CarSections[0] = new CarSection { Elements = new ObjectManager.AnimatedObject[] { }, Overlay = true }; string File = OpenBveApi.Path.CombineFile(TrainPath, "panel.animated"); if (System.IO.File.Exists(File)) { Program.AppendToLogFile("Loading train panel: " + File); ObjectManager.AnimatedObjectCollection a = AnimatedObjectParser.ReadObject(File, Encoding, ObjectManager.ObjectLoadMode.DontAllowUnloadOfTextures); if (a != null) { //HACK: If a == null , loading our animated object completely failed (Missing objects?). Fallback to trying the panel2.cfg try { for (int i = 0; i < a.Objects.Length; i++) { a.Objects[i].ObjectIndex = ObjectManager.CreateDynamicObject(); } Train.Cars[Train.DriverCar].CarSections[0].Elements = a.Objects; Train.Cars[Train.DriverCar].CameraRestrictionMode = Camera.RestrictionMode.NotAvailable; World.CameraRestriction = Camera.RestrictionMode.NotAvailable; World.UpdateViewingDistances(); return; } catch { var currentError = Interface.GetInterfaceString("errors_critical_file"); currentError = currentError.Replace("[file]", "panel.animated"); MessageBox.Show(currentError, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Hand); Program.RestartArguments = " "; Loading.Cancel = true; return; } } } var Panel2 = false; try { File = OpenBveApi.Path.CombineFile(TrainPath, "panel2.cfg"); if (System.IO.File.Exists(File)) { Program.AppendToLogFile("Loading train panel: " + File); Panel2 = true; Panel2CfgParser.ParsePanel2Config("panel2.cfg", TrainPath, Encoding, Train, Train.DriverCar); Train.Cars[Train.DriverCar].CameraRestrictionMode = Camera.RestrictionMode.On; World.CameraRestriction = Camera.RestrictionMode.On; } else { File = OpenBveApi.Path.CombineFile(TrainPath, "panel.cfg"); if (System.IO.File.Exists(File)) { Program.AppendToLogFile("Loading train panel: " + File); PanelCfgParser.ParsePanelConfig(TrainPath, Encoding, Train); Train.Cars[Train.DriverCar].CameraRestrictionMode = Camera.RestrictionMode.On; World.CameraRestriction = Camera.RestrictionMode.On; } else { World.CameraRestriction = Camera.RestrictionMode.NotAvailable; } } } catch { var currentError = Interface.GetInterfaceString("errors_critical_file"); currentError = currentError.Replace("[file]", Panel2 == true ? "panel2.cfg" : "panel.cfg"); MessageBox.Show(currentError, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Hand); Program.RestartArguments = " "; Loading.Cancel = true; } }
// read object internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { byte[] Data = System.IO.File.ReadAllBytes(FileName); if (Data.Length < 16 || Data[0] != 120 | Data[1] != 111 | Data[2] != 102 | Data[3] != 32) { // not an x object Interface.AddMessage(Interface.MessageType.Error, false, "Invalid X object file encountered in " + FileName); return null; } if (Data[4] != 48 | Data[5] != 51 | Data[6] != 48 | Data[7] != 50 & Data[7] != 51) { // unrecognized version System.Text.ASCIIEncoding Ascii = new System.Text.ASCIIEncoding(); string s = new string(Ascii.GetChars(Data, 4, 4)); Interface.AddMessage(Interface.MessageType.Error, false, "Unsupported X object file version " + s + " encountered in " + FileName); } // floating-point format int FloatingPointSize; if (Data[12] == 48 & Data[13] == 48 & Data[14] == 51 & Data[15] == 50) { FloatingPointSize = 32; } else if (Data[12] == 48 & Data[13] == 48 & Data[14] == 54 & Data[15] == 52) { FloatingPointSize = 64; } else { Interface.AddMessage(Interface.MessageType.Error, false, "Unsupported floating point format encountered in X object file " + FileName); return null; } // supported floating point format if (Data[8] == 116 & Data[9] == 120 & Data[10] == 116 & Data[11] == 32) { // textual flavor return LoadTextualX(FileName, System.IO.File.ReadAllText(FileName), Encoding, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); } else if (Data[8] == 98 & Data[9] == 105 & Data[10] == 110 & Data[11] == 32) { // binary flavor return LoadBinaryX(FileName, Data, 16, Encoding, FloatingPointSize, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); } else if (Data[8] == 116 & Data[9] == 122 & Data[10] == 105 & Data[11] == 112) { // compressed textual flavor #if !DEBUG try { #endif byte[] Uncompressed = Decompress(Data); string Text = Encoding.GetString(Uncompressed); return LoadTextualX(FileName, Text, Encoding, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); #if !DEBUG } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, "An unexpected error occured (" + ex.Message + ") while attempting to decompress the binary X object file encountered in " + FileName); return null; } #endif } else if (Data[8] == 98 & Data[9] == 122 & Data[10] == 105 & Data[11] == 112) { // compressed binary flavor #if !DEBUG try { #endif byte[] Uncompressed = Decompress(Data); return LoadBinaryX(FileName, Uncompressed, 0, Encoding, FloatingPointSize, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); #if !DEBUG } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, "An unexpected error occured (" + ex.Message + ") while attempting to decompress the binary X object file encountered in " + FileName); return null; } #endif } else { // unsupported flavor Interface.AddMessage(Interface.MessageType.Error, false, "Unsupported X object file encountered in " + FileName); return null; } }
internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { XmlDocument currentXML = new XmlDocument(); //May need to be changed to use de-DE System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[0]; int ObjectCount = 0; currentXML.Load(FileName); string BaseDir = System.IO.Path.GetDirectoryName(FileName); GruppenObject[] CurrentObjects = new GruppenObject[0]; //Check for null if (currentXML.DocumentElement != null) { ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[0]; XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT"); if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.HasChildNodes) { foreach (XmlNode node in outerNode.ChildNodes) { if (node.Name == "Object" && node.HasChildNodes) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { Array.Resize <GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1); GruppenObject Object = new GruppenObject(); foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { case "Name": string ObjectFile = OpenBveApi.Path.CombineFile(BaseDir, attribute.Value); Object.Name = ObjectFile; ObjectCount++; break; case "Position": string[] SplitPosition = attribute.Value.Split(';'); double.TryParse(SplitPosition[0], out Object.Position.X); double.TryParse(SplitPosition[1], out Object.Position.Y); double.TryParse(SplitPosition[2], out Object.Position.Z); break; case "Rotation": string[] SplitRotation = attribute.Value.Split(';'); double.TryParse(SplitRotation[0], out Object.RotationX); double.TryParse(SplitRotation[1], out Object.RotationY); double.TryParse(SplitRotation[2], out Object.RotationZ); break; } } CurrentObjects[CurrentObjects.Length - 1] = Object; } } } } } } //We've loaded the XML references, now load the objects into memory for (int i = 0; i < CurrentObjects.Length; i++) { var Object = ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, LoadMode, false, false, false, CurrentObjects[i].RotationX, CurrentObjects[i].RotationY, CurrentObjects[i].RotationZ); if (Object != null) { Array.Resize <ObjectManager.UnifiedObject>(ref obj, obj.Length + 1); obj[obj.Length - 1] = Object; } } for (int j = 0; j < obj.Length; j++) { if (obj[j] != null) { Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1); if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j]; s.Dynamic = true; ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState { Object = s, Position = CurrentObjects[j].Position }; a.States = new ObjectManager.AnimatedObjectState[] { aos }; Result.Objects[j] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j]; for (int k = 0; k < a.Objects.Length; k++) { for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += CurrentObjects[j].Position.X; a.Objects[k].States[h].Position.Y += CurrentObjects[j].Position.Y; a.Objects[k].States[h].Position.Z += CurrentObjects[j].Position.Z; } Result.Objects[j] = a.Objects[k]; ObjectCount++; } } } } } return(Result); } //Didn't find an acceptable XML object //Probably will cause things to throw an absolute wobbly somewhere.... return(null); }
// parse extensions config internal static void ParseExtensionsConfig(string TrainPath, System.Text.Encoding Encoding, out ObjectManager.UnifiedObject[] CarObjects, TrainManager.Train Train) { CarObjects = new ObjectManager.UnifiedObject[Train.Cars.Length]; bool[] CarObjectsReversed = new bool[Train.Cars.Length]; System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; string FileName = Path.CombineFile(TrainPath, "extensions.cfg"); if (System.IO.File.Exists(FileName)) { // load file string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j).Trim(); } else { Lines[i] = Lines[i].Trim(); } } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[exterior]": // exterior i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); int n; if (int.TryParse(a, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { if (Path.ContainsInvalidPathChars(b)) { Debug.AddMessage(Debug.MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { CarObjects[n] = ObjectManager.LoadObject(File, Encoding, ObjectManager.ObjectLoadMode.Normal, false, false, false); } else { Debug.AddMessage(Debug.MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } else { Debug.AddMessage(Debug.MessageType.Error, false, "The car index " + a + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; break; default: if (Lines[i].StartsWith("[car", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = Lines[i].Substring(4, Lines[i].Length - 5); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { bool DefinedLength = false; bool DefinedAxles = false; i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "object": if (Path.ContainsInvalidPathChars(b)) { Debug.AddMessage(Debug.MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { CarObjects[n] = ObjectManager.LoadObject(File, Encoding, ObjectManager.ObjectLoadMode.Normal, false, false, false); } else { Debug.AddMessage(Debug.MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "length": { double m; if (double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out m)) { if (m > 0.0) { Train.Cars[n].Length = m; Train.Cars[n].BeaconReceiverPosition = 0.5 * m; DefinedLength = true; } else { Debug.AddMessage(Debug.MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "axles": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(); string d = b.Substring(k + 1).TrimStart(); double rear, front; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out rear)) { Debug.AddMessage(Debug.MessageType.Error, false, "Rear is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out front)) { Debug.AddMessage(Debug.MessageType.Error, false, "Front is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (rear >= front) { Debug.AddMessage(Debug.MessageType.Error, false, "Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Cars[n].RearAxlePosition = rear; Train.Cars[n].FrontAxlePosition = front; DefinedAxles = true; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "reversed": CarObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; default: Debug.AddMessage(Debug.MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; if (DefinedLength & !DefinedAxles) { double AxleDistance = 0.4 * Train.Cars[n].Length; Train.Cars[n].RearAxlePosition = -AxleDistance; Train.Cars[n].FrontAxlePosition = AxleDistance; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "The car index " + t + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else if (Lines[i].StartsWith("[coupler", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // coupler string t = Lines[i].Substring(8, Lines[i].Length - 9); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Couplers.Length) { i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "distances": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(); string d = b.Substring(k + 1).TrimStart(); double min, max; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out min)) { Debug.AddMessage(Debug.MessageType.Error, false, "Minimum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out max)) { Debug.AddMessage(Debug.MessageType.Error, false, "Maximum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (min > max) { Debug.AddMessage(Debug.MessageType.Error, false, "Minimum is expected to be less than Maximum in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Couplers[n].MinimumDistanceBetweenCars = min; Train.Couplers[n].MaximumDistanceBetweenCars = max; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: Debug.AddMessage(Debug.MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; } else { Debug.AddMessage(Debug.MessageType.Error, false, "The coupler index " + t + " does not reference an existing coupler at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Debug.AddMessage(Debug.MessageType.Error, false, "The coupler index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { // default Debug.AddMessage(Debug.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; } } } // check for car objects and reverse if necessary int carObjects = 0; for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] != null) { carObjects++; if (CarObjectsReversed[i]) { { // reverse axle positions double temp = Train.Cars[i].FrontAxlePosition; Train.Cars[i].FrontAxlePosition = -Train.Cars[i].RearAxlePosition; Train.Cars[i].RearAxlePosition = -temp; } if (CarObjects[i] is ObjectManager.StaticObject) { ObjectManager.StaticObject obj = (ObjectManager.StaticObject)CarObjects[i]; CsvB3dObjectParser.ApplyScale(obj, -1.0, 1.0, -1.0); } else if (CarObjects[i] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection obj = (ObjectManager.AnimatedObjectCollection)CarObjects[i]; for (int j = 0; j < obj.Objects.Length; j++) { for (int h = 0; h < obj.Objects[j].States.Length; h++) { CsvB3dObjectParser.ApplyScale(obj.Objects[j].States[h].Object, -1.0, 1.0, -1.0); obj.Objects[j].States[h].Position.X *= -1.0; obj.Objects[j].States[h].Position.Z *= -1.0; } obj.Objects[j].TranslateXDirection.X *= -1.0; obj.Objects[j].TranslateXDirection.Z *= -1.0; obj.Objects[j].TranslateYDirection.X *= -1.0; obj.Objects[j].TranslateYDirection.Z *= -1.0; obj.Objects[j].TranslateZDirection.X *= -1.0; obj.Objects[j].TranslateZDirection.Z *= -1.0; } } else { throw new NotImplementedException(); } } } } if (carObjects > 0 & carObjects < Train.Cars.Length) { Debug.AddMessage(Debug.MessageType.Warning, false, "An incomplete set of exterior objects was provided in file " + FileName); } } }
private static void LoadEverythingThreaded() { Program.FileSystem.AppendToLogFile("Loading route file: " + CurrentRouteFile); Program.FileSystem.AppendToLogFile("INFO: Route file hash " + CsvRwRouteParser.GetChecksum(CurrentRouteFile)); string RailwayFolder = GetRailwayFolder(CurrentRouteFile); string ObjectFolder = OpenBveApi.Path.CombineDirectory(RailwayFolder, "Object"); string SoundFolder = OpenBveApi.Path.CombineDirectory(RailwayFolder, "Sound"); // reset Game.Reset(true); Game.MinimalisticSimulation = true; // screen World.CameraTrackFollower = new TrackManager.TrackFollower { Train = null, CarIndex = -1 }; Camera.CurrentMode = CameraViewMode.Interior; //First, check the format of the route file //RW routes were written for BVE1 / 2, and have a different command syntax bool IsRW = CsvRwRouteParser.isRWFile(CurrentRouteFile); Program.FileSystem.AppendToLogFile("Route file format is: " + (IsRW ? "RW" : "CSV")); CsvRwRouteParser.ParseRoute(CurrentRouteFile, IsRW, CurrentRouteEncoding, CurrentTrainFolder, ObjectFolder, SoundFolder, false); Thread createIllustrations = new Thread(Game.RouteInformation.LoadInformation) { IsBackground = true }; createIllustrations.Start(); System.Threading.Thread.Sleep(1); if (Cancel) { return; } Game.CalculateSeaLevelConstants(); if (CurrentRoute.BogusPretrainInstructions.Length != 0) { double t = CurrentRoute.BogusPretrainInstructions[0].Time; double p = CurrentRoute.BogusPretrainInstructions[0].TrackPosition; for (int i = 1; i < CurrentRoute.BogusPretrainInstructions.Length; i++) { if (CurrentRoute.BogusPretrainInstructions[i].Time > t) { t = CurrentRoute.BogusPretrainInstructions[i].Time; } else { t += 1.0; CurrentRoute.BogusPretrainInstructions[i].Time = t; } if (CurrentRoute.BogusPretrainInstructions[i].TrackPosition > p) { p = CurrentRoute.BogusPretrainInstructions[i].TrackPosition; } else { p += 1.0; CurrentRoute.BogusPretrainInstructions[i].TrackPosition = p; } } } if (Game.Stations.Length == 1) { //Log the fact that only a single station is present, as this is probably not right Program.FileSystem.AppendToLogFile("The processed route file only contains a single station."); } Program.FileSystem.AppendToLogFile("Route file loaded successfully."); RouteProgress = 1.0; // initialize trains System.Threading.Thread.Sleep(1); if (Cancel) { return; } TrainManager.Trains = new TrainManager.Train[Game.PrecedingTrainTimeDeltas.Length + 1 + (CurrentRoute.BogusPretrainInstructions.Length != 0 ? 1 : 0)]; for (int k = 0; k < TrainManager.Trains.Length; k++) { if (k == TrainManager.Trains.Length - 1 & CurrentRoute.BogusPretrainInstructions.Length != 0) { TrainManager.Trains[k] = new TrainManager.Train(TrainState.Bogus); } else { TrainManager.Trains[k] = new TrainManager.Train(TrainState.Pending); } } TrainManager.PlayerTrain = TrainManager.Trains[Game.PrecedingTrainTimeDeltas.Length]; UnifiedObject[] CarObjects = null; UnifiedObject[] BogieObjects = null; // load trains double TrainProgressMaximum = 0.7 + 0.3 * (double)TrainManager.Trains.Length; for (int k = 0; k < TrainManager.Trains.Length; k++) { //Sleep for 20ms to allow route loading locks to release Thread.Sleep(20); if (TrainManager.Trains[k].State == TrainState.Bogus) { // bogus train string TrainData = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Compatibility", "PreTrain"), "train.dat"); TrainDatParser.ParseTrainData(TrainData, System.Text.Encoding.UTF8, TrainManager.Trains[k]); System.Threading.Thread.Sleep(1); if (Cancel) { return; } TrainManager.Trains[k].InitializeCarSounds(); System.Threading.Thread.Sleep(1); if (Cancel) { return; } TrainProgressCurrentWeight = 0.3 / TrainProgressMaximum; TrainProgressCurrentSum += TrainProgressCurrentWeight; } else { TrainManager.Trains[k].TrainFolder = CurrentTrainFolder; // real train if (TrainManager.Trains[k] == TrainManager.PlayerTrain) { Program.FileSystem.AppendToLogFile("Loading player train: " + TrainManager.Trains[k].TrainFolder); } else { Program.FileSystem.AppendToLogFile("Loading AI train: " + TrainManager.Trains[k].TrainFolder); } TrainProgressCurrentWeight = 0.1 / TrainProgressMaximum; string TrainData = OpenBveApi.Path.CombineFile(TrainManager.Trains[k].TrainFolder, "train.dat"); TrainDatParser.ParseTrainData(TrainData, CurrentTrainEncoding, TrainManager.Trains[k]); TrainProgressCurrentSum += TrainProgressCurrentWeight; System.Threading.Thread.Sleep(1); if (Cancel) { return; } TrainProgressCurrentWeight = 0.2 / TrainProgressMaximum; SoundCfgParser.ParseSoundConfig(TrainManager.Trains[k].TrainFolder, CurrentTrainEncoding, TrainManager.Trains[k]); TrainProgressCurrentSum += TrainProgressCurrentWeight; System.Threading.Thread.Sleep(1); if (Cancel) { return; } // door open/close speed for (int i = 0; i < TrainManager.Trains[k].Cars.Length; i++) { if (TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency <= 0.0) { if (TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer != null & TrainManager.Trains[k].Cars[i].Doors[1].OpenSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer); Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[1].OpenSound.Buffer); double a = TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer.Duration; double b = TrainManager.Trains[k].Cars[i].Doors[1].OpenSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer); double a = TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (TrainManager.Trains[k].Cars[i].Doors[1].OpenSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].OpenSound.Buffer); double b = TrainManager.Trains[k].Cars[i].Doors[1].OpenSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency = 0.8; } } if (TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency <= 0.0) { if (TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer != null & TrainManager.Trains[k].Cars[i].Doors[1].CloseSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer); Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[1].CloseSound.Buffer); double a = TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer.Duration; double b = TrainManager.Trains[k].Cars[i].Doors[1].CloseSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency = a + b > 0.0 ? 2.0 / (a + b) : 0.8; } else if (TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer); double a = TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency = a > 0.0 ? 1.0 / a : 0.8; } else if (TrainManager.Trains[k].Cars[i].Doors[1].CloseSound.Buffer != null) { Sounds.LoadBuffer(TrainManager.Trains[k].Cars[i].Doors[0].CloseSound.Buffer); double b = TrainManager.Trains[k].Cars[i].Doors[1].CloseSound.Buffer.Duration; TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency = b > 0.0 ? 1.0 / b : 0.8; } else { TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency = 0.8; } } const double f = 0.015; const double g = 2.75; TrainManager.Trains[k].Cars[i].Specs.DoorOpenPitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); TrainManager.Trains[k].Cars[i].Specs.DoorClosePitch = Math.Exp(f * Math.Tan(g * (Program.RandomNumberGenerator.NextDouble() - 0.5))); TrainManager.Trains[k].Cars[i].Specs.DoorOpenFrequency /= TrainManager.Trains[k].Cars[i].Specs.DoorOpenPitch; TrainManager.Trains[k].Cars[i].Specs.DoorCloseFrequency /= TrainManager.Trains[k].Cars[i].Specs.DoorClosePitch; /* * Remove the following two lines, then the pitch at which doors play * takes their randomized opening and closing times into account. * */ TrainManager.Trains[k].Cars[i].Specs.DoorOpenPitch = 1.0; TrainManager.Trains[k].Cars[i].Specs.DoorClosePitch = 1.0; } } // add panel section if (TrainManager.Trains[k] == TrainManager.PlayerTrain) { TrainProgressCurrentWeight = 0.7 / TrainProgressMaximum; TrainManager.ParsePanelConfig(TrainManager.Trains[k].TrainFolder, CurrentTrainEncoding, TrainManager.Trains[k]); TrainProgressCurrentSum += TrainProgressCurrentWeight; System.Threading.Thread.Sleep(1); if (Cancel) { return; } Program.FileSystem.AppendToLogFile("Train panel loaded sucessfully."); } // add exterior section if (TrainManager.Trains[k].State != TrainState.Bogus) { bool LoadObjects = false; if (CarObjects == null) { CarObjects = new UnifiedObject[TrainManager.Trains[k].Cars.Length]; BogieObjects = new UnifiedObject[TrainManager.Trains[k].Cars.Length * 2]; LoadObjects = true; } string tXml = OpenBveApi.Path.CombineFile(TrainManager.Trains[k].TrainFolder, "train.xml"); if (System.IO.File.Exists(tXml)) { TrainXmlParser.Parse(tXml, TrainManager.Trains[k], ref CarObjects, ref BogieObjects); } else { ExtensionsCfgParser.ParseExtensionsConfig(TrainManager.Trains[k].TrainFolder, CurrentTrainEncoding, ref CarObjects, ref BogieObjects, TrainManager.Trains[k], LoadObjects); } World.CameraCar = TrainManager.Trains[k].DriverCar; System.Threading.Thread.Sleep(1); if (Cancel) { return; } //Stores the current array index of the bogie object to add //Required as there are two bogies per car, and we're using a simple linear array.... int currentBogieObject = 0; for (int i = 0; i < TrainManager.Trains[k].Cars.Length; i++) { if (CarObjects[i] == null) { // load default exterior object string file = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Compatibility"), "exterior.csv"); StaticObject so = ObjectManager.LoadStaticObject(file, System.Text.Encoding.UTF8, false); if (so == null) { CarObjects[i] = null; } else { double sx = TrainManager.Trains[k].Cars[i].Width; double sy = TrainManager.Trains[k].Cars[i].Height; double sz = TrainManager.Trains[k].Cars[i].Length; so.ApplyScale(sx, sy, sz); CarObjects[i] = so; } } if (CarObjects[i] != null) { // add object TrainManager.Trains[k].Cars[i].LoadCarSections(CarObjects[i]); } //Load bogie objects if (BogieObjects[currentBogieObject] != null) { TrainManager.Trains[k].Cars[i].FrontBogie.LoadCarSections(BogieObjects[currentBogieObject]); } currentBogieObject++; if (BogieObjects[currentBogieObject] != null) { TrainManager.Trains[k].Cars[i].RearBogie.LoadCarSections(BogieObjects[currentBogieObject]); } currentBogieObject++; } } // place cars TrainManager.Trains[k].PlaceCars(0.0); // configure ai / timetable if (TrainManager.Trains[k] == TrainManager.PlayerTrain) { TrainManager.Trains[k].TimetableDelta = 0.0; } else if (TrainManager.Trains[k].State != TrainState.Bogus) { TrainManager.Trains[k].AI = new Game.SimpleHumanDriverAI(TrainManager.Trains[k]); TrainManager.Trains[k].TimetableDelta = Game.PrecedingTrainTimeDeltas[k]; TrainManager.Trains[k].Specs.DoorOpenMode = TrainManager.DoorMode.Manual; TrainManager.Trains[k].Specs.DoorCloseMode = TrainManager.DoorMode.Manual; } } TrainProgress = 1.0; // finished created objects System.Threading.Thread.Sleep(1); if (Cancel) { return; } Array.Resize(ref ObjectManager.Objects, ObjectManager.ObjectsUsed); Array.Resize(ref ObjectManager.AnimatedWorldObjects, ObjectManager.AnimatedWorldObjectsUsed); // update sections if (CurrentRoute.Sections.Length > 0) { Game.UpdateSection(CurrentRoute.Sections.Length - 1); } // load plugin for (int i = 0; i < TrainManager.Trains.Length; i++) { if (TrainManager.Trains[i].State != TrainState.Bogus) { if (TrainManager.Trains[i] == TrainManager.PlayerTrain) { if (!PluginManager.LoadCustomPlugin(TrainManager.Trains[i], TrainManager.Trains[i].TrainFolder, CurrentTrainEncoding)) { PluginManager.LoadDefaultPlugin(TrainManager.Trains[i], TrainManager.Trains[i].TrainFolder); } } else { PluginManager.LoadDefaultPlugin(TrainManager.Trains[i], TrainManager.Trains[i].TrainFolder); } } } }
private static ObjectManager.StaticObject GetMirroredStaticObject(ObjectManager.StaticObject Prototype) { ObjectManager.StaticObject Result = ObjectManager.CloneObject(Prototype); for (int i = 0; i < Result.Mesh.Vertices.Length; i++) { Result.Mesh.Vertices[i].Coordinates.X = -Result.Mesh.Vertices[i].Coordinates.X; } for (int i = 0; i < Result.Mesh.Faces.Length; i++) { for (int k = 0; k < Result.Mesh.Faces[i].Vertices.Length; k++) { Result.Mesh.Faces[i].Vertices[k].Normal.X = -Result.Mesh.Faces[i].Vertices[k].Normal.X; } Result.Mesh.Faces[i].Flip(); } return Result; }
//The XML Parser Class will allow loading of an object with more advanced //properties than are currently available with the CSV and B3D formats, whilst //not requiring backwards incompatible changes public static ObjectManager.UnifiedObject ReadObject(string fileName, Encoding encoding,ObjectManager.ObjectLoadMode LoadMode,bool ForceTextureRepeatX,bool ForceTextureRepeatY) { //The current XML file to load XmlDocument currentXML = new XmlDocument(); ObjectManager.StaticObject Object = null; //Load the object's XML file currentXML.Load(fileName); //Check for null if (currentXML.DocumentElement != null) { XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openbve/object"); //Check this file actually contains OpenBVE object nodes if (DocumentNodes != null) foreach (XmlNode node in DocumentNodes) { string objectPath; try { var fn = System.IO.Path.GetDirectoryName(fileName); var InnerNode = node.SelectSingleNode("filename").InnerText; InnerNode = InnerNode.Trim(); objectPath = OpenBveApi.Path.CombineFile(fn, InnerNode); } catch (Exception) { Interface.AddMessage(Interface.MessageType.Error, false, "The XML does not contain a valid object path: " + fileName); return null; } if (objectPath != null && System.IO.File.Exists(objectPath)) { switch (System.IO.Path.GetExtension(objectPath).ToLowerInvariant()) { case ".csv": case ".b3d": Object = CsvB3dObjectParser.ReadObject(objectPath, encoding, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); break; case ".x": Object = XObjectParser.ReadObject(objectPath, encoding, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); break; case ".animated": //Not currently working. //Object = AnimatedObjectParser.ReadObject(objectPath, encoding, LoadMode); break; } try { var BoundingBoxUpper = node.SelectSingleNode("boundingboxupper").InnerText; var BoundingBoxLower = node.SelectSingleNode("boundingboxlower").InnerText; Object.Mesh.BoundingBox = new Vector3[2]; var splitStrings = BoundingBoxUpper.Split(','); if (splitStrings.Length != 3) { //Throw exception, as this isn't a valid 3D point throw new Exception(); } Object.Mesh.BoundingBox[0].X = Double.Parse(splitStrings[0]); Object.Mesh.BoundingBox[0].Y = Double.Parse(splitStrings[1]); Object.Mesh.BoundingBox[0].Z = Double.Parse(splitStrings[2]); splitStrings = BoundingBoxLower.Split(','); if (splitStrings.Length != 3) { //Throw exception, as this isn't a valid 3D point throw new Exception(); } Object.Mesh.BoundingBox[1].X = Double.Parse(splitStrings[0]); Object.Mesh.BoundingBox[1].Y = Double.Parse(splitStrings[1]); Object.Mesh.BoundingBox[1].Y = Double.Parse(splitStrings[2]); } catch (Exception) { Interface.AddMessage(Interface.MessageType.Error, false, "The XML contained an invalid bounding box entry: " + fileName); } var selectSingleNode = node.SelectSingleNode("author"); if (selectSingleNode != null) { //Attempt to load author information from XML Object.Author = selectSingleNode.InnerText.Trim(); } selectSingleNode = node.SelectSingleNode("copyright"); if (selectSingleNode != null) { //Attempt to load copyright information from XML Object.Copyright = selectSingleNode.InnerText.Trim(); } return Object; } Interface.AddMessage(Interface.MessageType.Error, false, "The file extension is not supported: " + objectPath); return null; } } //We couldn't find any valid XML, so return a null object return null; }
internal CompatibilitySignalData(int[] Numbers, ObjectManager.StaticObject[] Objects) { this.Numbers = Numbers; this.Objects = Objects; }
/// <summary>Loads a Loksim3D GruppenObject</summary> /// <param name="FileName">The filename to load</param> /// <param name="Encoding">The text encoding of the containing file (Currently ignored, REMOVE??)</param> /// <param name="Rotation">A three-dimemsional vector describing the rotation to be applied</param> /// <returns>A new animated object collection, containing the GruppenObject's meshes etc.</returns> internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, Encoding Encoding, Vector3 Rotation) { XmlDocument currentXML = new XmlDocument(); ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection(); Result.Objects = new ObjectManager.AnimatedObject[0]; try { currentXML.Load(FileName); } catch (Exception ex) { //The XML is not strictly valid string[] Lines = File.ReadAllLines(FileName); using (var stringReader = new StringReader(Lines[0])) { var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; using (var xmlReader = XmlReader.Create(stringReader, settings)) { if (xmlReader.Read()) { //Attempt to find the text encoding and re-read the file var result = xmlReader.GetAttribute("encoding"); if (result != null) { var e = System.Text.Encoding.GetEncoding(result); Lines = File.ReadAllLines(FileName, e); //Turf out the old encoding, as our string array should now be UTF-8 Lines[0] = "<?xml version=\"1.0\"?>"; } } } } for (int i = 0; i < Lines.Length; i++) { while (Lines[i].IndexOf("\"\"", StringComparison.InvariantCulture) != -1) { //Loksim parser tolerates multiple quotes, strict XML does not Lines[i] = Lines[i].Replace("\"\"", "\""); } while (Lines[i].IndexOf(" ", StringComparison.InvariantCulture) != -1) { //Replace double-spaces with singles Lines[i] = Lines[i].Replace(" ", " "); } } bool tryLoad = false; try { //Horrible hack: Write out our string array to a new memory stream, then load from this stream //Why can't XmlDocument.Load() just take a string array...... using (var stream = new MemoryStream()) { var sw = new StreamWriter(stream); foreach (var line in Lines) { sw.Write(line); sw.Flush(); } sw.Flush(); stream.Position = 0; currentXML.Load(stream); tryLoad = true; } } catch { //Generic catch-all clause } if (!tryLoad) { //Pass out the *original* XML error, not anything generated when we've tried to correct it Interface.AddMessage(MessageType.Error, false, "Error parsing Loksim3D XML: " + ex.Message); return(null); } } string BaseDir = System.IO.Path.GetDirectoryName(FileName); GruppenObject[] CurrentObjects = new GruppenObject[0]; //Check for null if (currentXML.DocumentElement != null) { UnifiedObject[] obj = new UnifiedObject[0]; XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/GRUPPENOBJECT"); if (DocumentNodes != null) { foreach (XmlNode outerNode in DocumentNodes) { if (outerNode.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode node in outerNode.ChildNodes) { if (node.Name == "Object" && node.ChildNodes.OfType <XmlElement>().Any()) { foreach (XmlNode childNode in node.ChildNodes) { if (childNode.Name == "Props" && childNode.Attributes != null) { GruppenObject Object = new GruppenObject { Rotation = Rotation }; foreach (XmlAttribute attribute in childNode.Attributes) { switch (attribute.Name) { case "Name": string ObjectFile = OpenBveApi.Path.Loksim3D.CombineFile(BaseDir, attribute.Value, Program.FileSystem.LoksimPackageInstallationDirectory); if (!File.Exists(ObjectFile)) { Object.Name = null; Interface.AddMessage(MessageType.Warning, true, "Ls3d Object file " + attribute.Value + " not found."); } else { Object.Name = ObjectFile; } break; case "Position": string[] SplitPosition = attribute.Value.Split(';'); double.TryParse(SplitPosition[0], out Object.Position.X); double.TryParse(SplitPosition[1], out Object.Position.Y); double.TryParse(SplitPosition[2], out Object.Position.Z); break; case "Rotation": string[] SplitRotation = attribute.Value.Split(';'); Vector3 r; double.TryParse(SplitRotation[0], out r.X); double.TryParse(SplitRotation[1], out r.Y); double.TryParse(SplitRotation[2], out r.Z); Object.Rotation += r; break; case "ShowOn": //Defines when the object should be shown Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, false)); break; case "HideOn": //Defines when the object should be hidden Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetAnimatedFunction(attribute.Value, true)); break; case "FixedDynamicVisibility": if (attribute.Value.ToLowerInvariant() == "true") { Object.FixedDynamicVisibility = true; } else { Object.FixedDynamicVisibility = false; } break; case "DynamicVisibility": if (Object.FixedDynamicVisibility) { Object.FunctionScript = FunctionScriptNotation.GetPostfixNotationFromInfixNotation(GetDynamicFunction(attribute.Value)); } break; } } if (Object.Name != null) { Array.Resize <GruppenObject>(ref CurrentObjects, CurrentObjects.Length + 1); CurrentObjects[CurrentObjects.Length - 1] = Object; } } } } } } } //We've loaded the XML references, now load the objects into memory //Single mesh object, containing all static components of the LS3D object //If we use multiples, the Z-sorting throws a wobbly ObjectManager.StaticObject staticObject = null; for (int i = 0; i < CurrentObjects.Length; i++) { if (CurrentObjects[i] == null || string.IsNullOrEmpty(CurrentObjects[i].Name)) { continue; } ObjectManager.StaticObject Object = null; ObjectManager.AnimatedObjectCollection AnimatedObject = null; try { if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dgrp")) { AnimatedObject = ReadObject(CurrentObjects[i].Name, Encoding, CurrentObjects[i].Rotation); } else if (CurrentObjects[i].Name.ToLowerInvariant().EndsWith(".l3dobj")) { Object = (ObjectManager.StaticObject)ObjectManager.LoadObject(CurrentObjects[i].Name, Encoding, false, false, false, CurrentObjects[i].Rotation); } else { throw new Exception("Format " + System.IO.Path.GetExtension(CurrentObjects[i].Name) + " is not currently supported by the Loksim3D object parser"); } } catch (Exception ex) { Interface.AddMessage(MessageType.Error, false, ex.Message); } if (Object != null) { if (!string.IsNullOrEmpty(CurrentObjects[i].FunctionScript)) { //If the function script is not empty, this is a new animated object bit Array.Resize <UnifiedObject>(ref obj, obj.Length + 1); obj[obj.Length - 1] = Object; int aL = Result.Objects.Length; Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, aL + 1); ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState(Object, CurrentObjects[i].Position); a.States = new ObjectManager.AnimatedObjectState[] { aos }; Result.Objects[aL] = a; Result.Objects[aL].StateFunction = new FunctionScript(Program.CurrentHost, CurrentObjects[i].FunctionScript + " 1 == --", false); } else { //Otherwise, join to the main static mesh & update co-ords for (int j = 0; j < Object.Mesh.Vertices.Length; j++) { Object.Mesh.Vertices[j].Coordinates += CurrentObjects[i].Position; } staticObject.JoinObjects(Object); } } else if (AnimatedObject != null) { int rl = Result.Objects.Length; int l = AnimatedObject.Objects.Length; Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + l); for (int o = rl; o < rl + l; o++) { if (AnimatedObject.Objects[o - rl] != null) { Result.Objects[o] = AnimatedObject.Objects[o - rl].Clone(); for (int si = 0; si < Result.Objects[o].States.Length; si++) { Result.Objects[o].States[si].Position += CurrentObjects[i].Position; } } else { Result.Objects[o] = new ObjectManager.AnimatedObject(); Result.Objects[o].States = new ObjectManager.AnimatedObjectState[0]; } } } } if (staticObject != null) { Array.Resize <ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length + 1); ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState(staticObject, Vector3.Zero); a.States = new ObjectManager.AnimatedObjectState[] { aos }; Result.Objects[Result.Objects.Length - 1] = a; } } return(Result); } //Didn't find an acceptable XML object //Probably will cause things to throw an absolute wobbly somewhere.... return(null); }
// read object /// <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="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for the X-axis</param> /// /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for the Y-axis</param> /// <returns>The object loaded.</returns> internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding,ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY, double RotationX, double RotationY, double RotationZ) { 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(); World.Vector3Df[] Normals = new World.Vector3Df[4]; bool PropertiesFound = false; World.Vertex[] tempVertices = new World.Vertex[0]; World.Vector3Df[] tempNormals = new World.Vector3Df[0]; World.ColorRGB transparentColor = new World.ColorRGB(); string tday = null; string tnight = null; bool TransparencyUsed = false; bool Face2 = false; int TextureWidth = 0; int TextureHeight = 0; if (File.Exists(FileName)) { currentXML.Load(FileName); } else { 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.CombineFile(System.IO.Path.GetDirectoryName(FileName), attribute.Value); if (File.Exists(tday)) { try { using (Bitmap TextureInformation = new Bitmap(tday)) { TextureWidth = TextureInformation.Width; TextureHeight = TextureInformation.Height; Color color = TextureInformation.GetPixel(1, 1); transparentColor = new World.ColorRGB((byte) color.R, (byte) color.G, (byte) color.B); } } catch { Interface.AddMessage(Interface.MessageType.Error, true, "An error occured loading daytime texture " + tday + " in file " + FileName); tday = null; } } else { Interface.AddMessage(Interface.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in file " + FileName); } break; //Defines whether the texture uses transparency //May be omitted case "Transparent": if (attribute.Value == "TRUE") { TransparencyUsed = true; } else { TransparencyUsed = false; } break; //Sets the transparency type case "TransparentTyp": switch (attribute.Value) { case "0": //Transparency is disabled TransparencyUsed = false; break; case "1": //Transparency is solid black TransparencyUsed = true; transparentColor = new World.ColorRGB(0,0,0); break; case "2": //Transparency is the color at Pixel 1,1 TransparencyUsed = true; break; case "3": //This is used when transparency is used with an alpha bitmap //Not currently supported TransparencyUsed = false; break; } break; //Sets whether the rears of the faces are to be drawn case "Drawrueckseiten": Face2 = true; 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(';'); double.TryParse(NormalPoints[0], out nx); double.TryParse(NormalPoints[1], out ny); double.TryParse(NormalPoints[2], out nz); break; //Sets the vertex 3D co-ordinates case "Vekt": string[] VertexPoints = attribute.Value.Split(';'); double.TryParse(VertexPoints[0], out vx); double.TryParse(VertexPoints[1], out vy); double.TryParse(VertexPoints[2], out vz); break; } } World.Normalize(ref nx, ref ny, ref nz); { //Resize temp arrays Array.Resize<World.Vertex>(ref tempVertices, tempVertices.Length + 1); Array.Resize<World.Vector3Df>(ref tempNormals, tempNormals.Length + 1); //Add vertex and normals to temp array tempVertices[tempVertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz); tempNormals[tempNormals.Length - 1] = new World.Vector3Df((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<World.Vector3Df>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new World.Vector3D(vx, vy, vz); Normals[Builder.Vertices.Length - 1] = new World.Vector3Df((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<World.Vector3Df>(ref Normals, Normals.Length << 1); } //Run through the vertices list and grab from the temp array for (int j = 0; j < Verticies.Length; j++) { //This is the position of the vertex in the temp array int currentVertex; int.TryParse(Verticies[j], out currentVertex); //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(';'); World.Vector2Df currentCoords; float OpenBVEWidth; float OpenBVEHeight; string[] splitCoords = TextureCoords[j].Split(','); float.TryParse(splitCoords[0], out OpenBVEWidth); float.TryParse(splitCoords[1], out OpenBVEHeight); if (TextureWidth != 0 && TextureHeight != 0) { currentCoords.X = (OpenBVEWidth / TextureWidth); currentCoords.Y = (OpenBVEHeight / TextureHeight); } else { currentCoords.X = 0; currentCoords.Y = 0; } Builder.Vertices[Builder.Vertices.Length - 1].TextureCoordinates = currentCoords; } if (Face2) { 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, X, Z ??? Can't find a good reason for this ??? * Rotations must still be performed in X,Y,Z order to produce correct results */ if (RotationX != 0.0) { //This is actually the Y-Axis rotation //Convert to radians RotationX *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 0, 1, 0, RotationX); } if (RotationY != 0.0) { //This is actually the X-Axis rotation //Convert to radians RotationY *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 1, 0, 0, RotationY); } if (RotationZ != 0.0) { //Convert to radians RotationZ *= 0.0174532925199433; //Apply rotation ApplyRotation(Builder, 0, 0, 1, RotationZ); } //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; } } if (TransparencyUsed == true) { for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = transparentColor; Builder.Materials[j].TransparentColorUsed = true; } } } ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); World.CreateNormals(ref Object.Mesh); return Object; }
protected override void OnRenderFrame(FrameEventArgs e) { Program.MouseMovement(); double timeElapsed = CPreciseTimer.GetElapsedTime(); DateTime time = DateTime.Now; Game.SecondsSinceMidnight = (double)(3600 * time.Hour + 60 * time.Minute + time.Second) + 0.001 * (double)time.Millisecond; lock (Program.LockObj) { ObjectManager.UpdateAnimatedWorldObjects(timeElapsed, false); } if (Program.ReducedMode) { System.Threading.Thread.Sleep(125); } else { System.Threading.Thread.Sleep(1); } bool updatelight = false; bool keep = false; // rotate x if (Program.RotateX == 0) { double d = (1.0 + Math.Abs(RotateXSpeed)) * timeElapsed; if (RotateXSpeed >= -d & RotateXSpeed <= d) { RotateXSpeed = 0.0; } else { RotateXSpeed -= (double)Math.Sign(RotateXSpeed) * d; } } else { double d = (1.0 + 1.0 - 1.0 / (1.0 + RotateXSpeed * RotateXSpeed)) * timeElapsed; double m = 1.0; RotateXSpeed += (double)Program.RotateX * d; if (RotateXSpeed < -m) { RotateXSpeed = -m; } else if (RotateXSpeed > m) { RotateXSpeed = m; } } if (RotateXSpeed != 0.0) { double cosa = Math.Cos(RotateXSpeed * timeElapsed); double sina = Math.Sin(RotateXSpeed * timeElapsed); Program.Renderer.Camera.AbsoluteDirection.Rotate(Vector3.Down, cosa, sina); Program.Renderer.Camera.AbsoluteUp.Rotate(Vector3.Down, cosa, sina); Program.Renderer.Camera.AbsoluteSide.Rotate(Vector3.Down, cosa, sina); keep = true; } // rotate y if (Program.RotateY == 0) { double d = (1.0 + Math.Abs(RotateYSpeed)) * timeElapsed; if (RotateYSpeed >= -d & RotateYSpeed <= d) { RotateYSpeed = 0.0; } else { RotateYSpeed -= (double)Math.Sign(RotateYSpeed) * d; } } else { double d = (1.0 + 1.0 - 1.0 / (1.0 + RotateYSpeed * RotateYSpeed)) * timeElapsed; double m = 1.0; RotateYSpeed += (double)Program.RotateY * d; if (RotateYSpeed < -m) { RotateYSpeed = -m; } else if (RotateYSpeed > m) { RotateYSpeed = m; } } if (RotateYSpeed != 0.0) { double cosa = Math.Cos(RotateYSpeed * timeElapsed); double sina = Math.Sin(RotateYSpeed * timeElapsed); Program.Renderer.Camera.AbsoluteDirection.Rotate(Program.Renderer.Camera.AbsoluteSide, cosa, sina); Program.Renderer.Camera.AbsoluteUp.Rotate(Program.Renderer.Camera.AbsoluteSide, cosa, sina); keep = true; } // move x if (Program.MoveX == 0) { double d = (2.5 + Math.Abs(MoveXSpeed)) * timeElapsed; if (MoveXSpeed >= -d & MoveXSpeed <= d) { MoveXSpeed = 0.0; } else { MoveXSpeed -= (double)Math.Sign(MoveXSpeed) * d; } } else { double d = (5.0 + 10.0 - 10.0 / (1.0 + MoveXSpeed * MoveXSpeed)) * timeElapsed; double m = 25.0; MoveXSpeed += (double)Program.MoveX * d; if (MoveXSpeed < -m) { MoveXSpeed = -m; } else if (MoveXSpeed > m) { MoveXSpeed = m; } } if (MoveXSpeed != 0.0) { Program.Renderer.Camera.AbsolutePosition += MoveXSpeed * timeElapsed * Program.Renderer.Camera.AbsoluteSide; keep = true; } // move y if (Program.MoveY == 0) { double d = (2.5 + Math.Abs(MoveYSpeed)) * timeElapsed; if (MoveYSpeed >= -d & MoveYSpeed <= d) { MoveYSpeed = 0.0; } else { MoveYSpeed -= (double)Math.Sign(MoveYSpeed) * d; } } else { double d = (5.0 + 10.0 - 10.0 / (1.0 + MoveYSpeed * MoveYSpeed)) * timeElapsed; double m = 25.0; MoveYSpeed += (double)Program.MoveY * d; if (MoveYSpeed < -m) { MoveYSpeed = -m; } else if (MoveYSpeed > m) { MoveYSpeed = m; } } if (MoveYSpeed != 0.0) { Program.Renderer.Camera.AbsolutePosition += MoveYSpeed * timeElapsed * Program.Renderer.Camera.AbsoluteUp; keep = true; } // move z if (Program.MoveZ == 0) { double d = (2.5 + Math.Abs(MoveZSpeed)) * timeElapsed; if (MoveZSpeed >= -d & MoveZSpeed <= d) { MoveZSpeed = 0.0; } else { MoveZSpeed -= (double)Math.Sign(MoveZSpeed) * d; } } else { double d = (5.0 + 10.0 - 10.0 / (1.0 + MoveZSpeed * MoveZSpeed)) * timeElapsed; double m = 25.0; MoveZSpeed += (double)Program.MoveZ * d; if (MoveZSpeed < -m) { MoveZSpeed = -m; } else if (MoveZSpeed > m) { MoveZSpeed = m; } } if (MoveZSpeed != 0.0) { Program.Renderer.Camera.AbsolutePosition += MoveZSpeed * timeElapsed * Program.Renderer.Camera.AbsoluteDirection; keep = true; } // lighting if (Program.LightingRelative == -1) { Program.LightingRelative = (double)Program.LightingTarget; updatelight = true; } if (Program.LightingTarget == 0) { if (Program.LightingRelative != 0.0) { Program.LightingRelative -= 0.5 * timeElapsed; if (Program.LightingRelative < 0.0) { Program.LightingRelative = 0.0; } updatelight = true; keep = true; } } else { if (Program.LightingRelative != 1.0) { Program.LightingRelative += 0.5 * timeElapsed; if (Program.LightingRelative > 1.0) { Program.LightingRelative = 1.0; } updatelight = true; keep = true; } } // continue if (Program.ReducedMode) { ReducedModeEnteringTime = 3.0; } else { if (keep) { ReducedModeEnteringTime = 3.0; } else if (ReducedModeEnteringTime <= 0) { Program.ReducedMode = true; Program.Renderer.Camera.AbsoluteSide.Y = 0.0; Program.Renderer.Camera.AbsoluteSide.Normalize(); Program.Renderer.Camera.AbsoluteDirection.Normalize(); Program.Renderer.Camera.AbsoluteUp = Vector3.Cross(Program.Renderer.Camera.AbsoluteDirection, Program.Renderer.Camera.AbsoluteSide); } else { ReducedModeEnteringTime -= timeElapsed; } } if (updatelight) { Program.Renderer.Lighting.OptionAmbientColor.R = (byte)Math.Round(32.0 + 128.0 * Program.LightingRelative * (2.0 - Program.LightingRelative)); Program.Renderer.Lighting.OptionAmbientColor.G = (byte)Math.Round(32.0 + 128.0 * 0.5 * (Program.LightingRelative + Program.LightingRelative * (2.0 - Program.LightingRelative))); Program.Renderer.Lighting.OptionAmbientColor.B = (byte)Math.Round(32.0 + 128.0 * Program.LightingRelative); Program.Renderer.Lighting.OptionDiffuseColor.R = (byte)Math.Round(32.0 + 128.0 * Program.LightingRelative); Program.Renderer.Lighting.OptionDiffuseColor.G = (byte)Math.Round(32.0 + 128.0 * Program.LightingRelative); Program.Renderer.Lighting.OptionDiffuseColor.B = (byte)Math.Round(32.0 + 128.0 * Math.Sqrt(Program.LightingRelative)); Program.Renderer.Lighting.Initialize(); } Program.Renderer.RenderScene(); SwapBuffers(); }
// parse animated object config /// <summary>Loads a collection of animated objects from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <returns>The collection of animated objects.</returns> internal static ObjectManager.AnimatedObjectCollection ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; ObjectManager.AnimatedObjectCollection Result = new ObjectManager.AnimatedObjectCollection { Objects = new ObjectManager.AnimatedObject[4] }; int ObjectCount = 0; // load file string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); bool rpnUsed = false; for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); //Trim out comments Lines[i] = j >= 0 ? Lines[i].Substring(0, j).Trim() : Lines[i].Trim(); //Test whether RPN functions have been used rpnUsed = Lines[i].IndexOf("functionrpn", StringComparison.OrdinalIgnoreCase) >= 0; } if (rpnUsed) { Interface.AddMessage(Interface.MessageType.Error, false, "An animated object file contains RPN functions. These were never meant to be used directly, only for debugging. They won't be supported indefinately. Please get rid of them in file " + FileName); } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[include]": { i++; Vector3 position = new Vector3(0.0, 0.0, 0.0); ObjectManager.UnifiedObject[] obj = new OpenBve.ObjectManager.UnifiedObject[4]; int objCount = 0; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j > 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "position": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { position = new Vector3(x, y, z); } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { string Folder = System.IO.Path.GetDirectoryName(FileName); if (Interface.ContainsInvalidPathChars(Lines[i])) { Interface.AddMessage(Interface.MessageType.Error, false, Lines[i] + " contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string file = OpenBveApi.Path.CombineFile(Folder, Lines[i]); if (System.IO.File.Exists(file)) { if (obj.Length == objCount) { Array.Resize<ObjectManager.UnifiedObject>(ref obj, obj.Length << 1); } obj[objCount] = ObjectManager.LoadObject(file, Encoding, LoadMode, false, false, false); objCount++; } else { Interface.AddMessage(Interface.MessageType.Error, true, "File " + file + " not found at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } } i++; } i--; for (int j = 0; j < objCount; j++) { if (obj[j] != null) { if (obj[j] is ObjectManager.StaticObject) { ObjectManager.StaticObject s = (ObjectManager.StaticObject)obj[j]; s.Dynamic = true; if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } ObjectManager.AnimatedObject a = new ObjectManager.AnimatedObject(); ObjectManager.AnimatedObjectState aos = new ObjectManager.AnimatedObjectState { Object = s, Position = position }; a.States = new ObjectManager.AnimatedObjectState[] { aos }; Result.Objects[ObjectCount] = a; ObjectCount++; } else if (obj[j] is ObjectManager.AnimatedObjectCollection) { ObjectManager.AnimatedObjectCollection a = (ObjectManager.AnimatedObjectCollection)obj[j]; for (int k = 0; k < a.Objects.Length; k++) { if (ObjectCount >= Result.Objects.Length) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } for (int h = 0; h < a.Objects[k].States.Length; h++) { a.Objects[k].States[h].Position.X += position.X; a.Objects[k].States[h].Position.Y += position.Y; a.Objects[k].States[h].Position.Z += position.Z; } Result.Objects[ObjectCount] = a.Objects[k]; ObjectCount++; } } } } } break; case "[object]": { i++; if (Result.Objects.Length == ObjectCount) { Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, Result.Objects.Length << 1); } Result.Objects[ObjectCount] = new ObjectManager.AnimatedObject { States = new ObjectManager.AnimatedObjectState[] {}, CurrentState = -1, TranslateXDirection = new Vector3(1.0, 0.0, 0.0), TranslateYDirection = new Vector3(0.0, 1.0, 0.0), TranslateZDirection = new Vector3(0.0, 0.0, 1.0), RotateXDirection = new Vector3(1.0, 0.0, 0.0), RotateYDirection = new Vector3(0.0, 1.0, 0.0), RotateZDirection = new Vector3(0.0, 0.0, 1.0), TextureShiftXDirection = new Vector2(1.0, 0.0), TextureShiftYDirection = new Vector2(0.0, 1.0), RefreshRate = 0.0, ObjectIndex = -1 }; Vector3 Position = new Vector3(0.0, 0.0, 0.0); double RotateX = 0; bool StaticXRotation = false; double RotateY = 0; bool StaticYRotation = false; double RotateZ = 0; bool StaticZRotation = false; bool timetableUsed = false; string[] StateFiles = null; string StateFunctionRpn = null; int StateFunctionLine = -1; while (i < Lines.Length && !(Lines[i].StartsWith("[", StringComparison.Ordinal) & Lines[i].EndsWith("]", StringComparison.Ordinal))) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j > 0) { string a = Lines[i].Substring(0, j).TrimEnd(); string b = Lines[i].Substring(j + 1).TrimStart(); switch (a.ToLowerInvariant()) { case "position": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Position = new Vector3(x, y, z); } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "states": { string[] s = b.Split(','); if (s.Length >= 1) { string Folder = System.IO.Path.GetDirectoryName(FileName); StateFiles = new string[s.Length]; bool NullObject = true; for (int k = 0; k < s.Length; k++) { s[k] = s[k].Trim(); if (s[k].Length == 0) { Interface.AddMessage(Interface.MessageType.Error, false, "File" + k.ToString(Culture) + " is an empty string - did you mean something else? - in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); StateFiles[k] = null; } else if (Interface.ContainsInvalidPathChars(s[k])) { Interface.AddMessage(Interface.MessageType.Error, false, "File" + k.ToString(Culture) + " contains illegal characters in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); StateFiles[k] = null; } else { StateFiles[k] = OpenBveApi.Path.CombineFile(Folder, s[k]); if (!System.IO.File.Exists(StateFiles[k])) { Interface.AddMessage(Interface.MessageType.Error, true, "File " + StateFiles[k] + " not found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); StateFiles[k] = null; } } if (StateFiles[k] != null) { NullObject = false; } } if (NullObject == true) { Interface.AddMessage(Interface.MessageType.Error, false, "None of the specified files were found in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } else { Interface.AddMessage(Interface.MessageType.Error, false, "At least one argument is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } break; case "statefunction": try { StateFunctionLine = i; StateFunctionRpn = FunctionScripts.GetPostfixNotationFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "statefunctionrpn": { StateFunctionLine = i; StateFunctionRpn = b; } break; case "translatexdirection": case "translateydirection": case "translatezdirection": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "translatexdirection": Result.Objects[ObjectCount].TranslateXDirection = new Vector3(x, y, z); break; case "translateydirection": Result.Objects[ObjectCount].TranslateYDirection = new Vector3(x, y, z); break; case "translatezdirection": Result.Objects[ObjectCount].TranslateZDirection = new Vector3(x, y, z); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "translatexfunction": try { double X; if (double.TryParse(b, NumberStyles.Float, Culture, out X)) { Position.X = X; //A function script must be evaluated every frame, no matter if it is a constant value //If we add this to the position instead, this gives a minor speedup break; } Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatexscript": try { Result.Objects[ObjectCount].TranslateXScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translateyfunction": try { double Y; if (double.TryParse(b, NumberStyles.Float, Culture, out Y)) { Position.Y = Y; //A function script must be evaluated every frame, no matter if it is a constant value //If we add this to the position instead, this gives a minor speedup break; } Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translateyscript": try { Result.Objects[ObjectCount].TranslateYScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatezfunction": try { double Z; if (double.TryParse(b, NumberStyles.Float, Culture, out Z)) { Position.Z = Z; //A function script must be evaluated every frame, no matter if it is a constant value //If we add this to the position instead, this gives a minor speedup break; } Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatezscript": try { Result.Objects[ObjectCount].TranslateZScriptFile = OpenBveApi.Path.CombineDirectory(System.IO.Path.GetDirectoryName(FileName), b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "trackfollowerfunction": try { Result.Objects[ObjectCount].TrackFollowerFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "axles": try { double FrontAxlePosition; double RearAxlePosition; var splitValue = b.Split(','); Double.TryParse(splitValue[0], out FrontAxlePosition); Double.TryParse(splitValue[1], out RearAxlePosition); if (FrontAxlePosition > RearAxlePosition) { Result.Objects[ObjectCount].FrontAxlePosition = FrontAxlePosition; Result.Objects[ObjectCount].RearAxlePosition = RearAxlePosition; } else if (FrontAxlePosition < RearAxlePosition) { Interface.AddMessage(Interface.MessageType.Error, false,"Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " +FileName); } else { Interface.AddMessage(Interface.MessageType.Error, false, "Rear must not equal Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } catch(Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; /* * RPN Functions were added by Michelle, and she stated that they should not be used other than in debugging * Not aware of any uses, but these should stay there anyway * */ case "translatexfunctionrpn": try { Result.Objects[ObjectCount].TranslateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translateyfunctionrpn": try { Result.Objects[ObjectCount].TranslateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "translatezfunctionrpn": try { Result.Objects[ObjectCount].TranslateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexdirection": case "rotateydirection": case "rotatezdirection": { string[] s = b.Split(','); if (s.Length == 3) { double x, y, z; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[2], System.Globalization.NumberStyles.Float, Culture, out z)) { Interface.AddMessage(Interface.MessageType.Error, false, "Z is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (x == 0.0 & y == 0.0 & z == 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "The direction indicated by X, Y and Z is expected to be non-zero in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "rotatexdirection": Result.Objects[ObjectCount].RotateXDirection = new Vector3(x, y, z); break; case "rotateydirection": Result.Objects[ObjectCount].RotateYDirection = new Vector3(x, y, z); break; case "rotatezdirection": Result.Objects[ObjectCount].RotateZDirection = new Vector3(x, y, z); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 3 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "rotatexfunction": try { if (double.TryParse(b, NumberStyles.Float, Culture, out RotateX)) { //A function script must be evaluated every frame, no matter if it is a constant value //If we add this to the position instead, this gives a minor speedup StaticXRotation = true; } Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotateyfunction": try { if (double.TryParse(b, NumberStyles.Float, Culture, out RotateY)) { StaticYRotation = true; } Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatezfunction": try { if (double.TryParse(b, NumberStyles.Float, Culture, out RotateZ)) { StaticZRotation = true; } Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexfunctionrpn": try { Result.Objects[ObjectCount].RotateXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotateyfunctionrpn": try { Result.Objects[ObjectCount].RotateYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatezfunctionrpn": try { Result.Objects[ObjectCount].RotateZFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "rotatexdamping": case "rotateydamping": case "rotatezdamping": { string[] s = b.Split(','); if (s.Length == 2) { double nf, dr; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out nf)) { Interface.AddMessage(Interface.MessageType.Error, false, "NaturalFrequency is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out dr)) { Interface.AddMessage(Interface.MessageType.Error, false, "DampingRatio is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (nf <= 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "NaturalFrequency is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (dr <= 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "DampingRatio is expected to be positive in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "rotatexdamping": Result.Objects[ObjectCount].RotateXDamping = new ObjectManager.Damping(nf, dr); break; case "rotateydamping": Result.Objects[ObjectCount].RotateYDamping = new ObjectManager.Damping(nf, dr); break; case "rotatezdamping": Result.Objects[ObjectCount].RotateZDamping = new ObjectManager.Damping(nf, dr); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "textureshiftxdirection": case "textureshiftydirection": { string[] s = b.Split(','); if (s.Length == 2) { double x, y; if (!double.TryParse(s[0], System.Globalization.NumberStyles.Float, Culture, out x)) { Interface.AddMessage(Interface.MessageType.Error, false, "X is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(s[1], System.Globalization.NumberStyles.Float, Culture, out y)) { Interface.AddMessage(Interface.MessageType.Error, false, "Y is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { switch (a.ToLowerInvariant()) { case "textureshiftxdirection": Result.Objects[ObjectCount].TextureShiftXDirection = new Vector2(x, y); break; case "textureshiftydirection": Result.Objects[ObjectCount].TextureShiftYDirection = new Vector2(x, y); break; } } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Exactly 2 arguments are expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "textureshiftxfunction": try { Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftyfunction": try { Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromInfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftxfunctionrpn": try { Result.Objects[ObjectCount].TextureShiftXFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureshiftyfunctionrpn": try { Result.Objects[ObjectCount].TextureShiftYFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(b); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "textureoverride": switch (b.ToLowerInvariant()) { case "none": break; case "timetable": if (!timetableUsed) { Timetable.AddObjectForCustomTimetable(Result.Objects[ObjectCount]); timetableUsed = true; } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Unrecognized value in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } break; case "refreshrate": { double r; if (!double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out r)) { Interface.AddMessage(Interface.MessageType.Error, false, "Value is invalid in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (r < 0.0) { Interface.AddMessage(Interface.MessageType.Error, false, "Value is expected to be non-negative in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Result.Objects[ObjectCount].RefreshRate = r; } } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "The attribute " + a + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Interface.AddMessage(Interface.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } i++; } i--; if (StateFiles != null) { // create the object if (timetableUsed) { if (StateFunctionRpn != null) { StateFunctionRpn = "timetable 0 == " + StateFunctionRpn + " -1 ?"; } else { StateFunctionRpn = "timetable"; } } if (StateFunctionRpn != null) { try { Result.Objects[ObjectCount].StateFunction = FunctionScripts.GetFunctionScriptFromPostfixNotation(StateFunctionRpn); } catch (Exception ex) { Interface.AddMessage(Interface.MessageType.Error, false, ex.Message + " in StateFunction at line " + (StateFunctionLine + 1).ToString(Culture) + " in file " + FileName); } } Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[StateFiles.Length]; bool ForceTextureRepeatX = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 | Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; bool ForceTextureRepeatY = Result.Objects[ObjectCount].TextureShiftXFunction != null & Result.Objects[ObjectCount].TextureShiftXDirection.X != 0.0 | Result.Objects[ObjectCount].TextureShiftYFunction != null & Result.Objects[ObjectCount].TextureShiftYDirection.Y != 0.0; for (int k = 0; k < StateFiles.Length; k++) { Result.Objects[ObjectCount].States[k].Position = new Vector3(0.0, 0.0, 0.0); if (StateFiles[k] != null) { Result.Objects[ObjectCount].States[k].Object = ObjectManager.LoadStaticObject(StateFiles[k], Encoding, LoadMode, false, ForceTextureRepeatX, ForceTextureRepeatY); if (Result.Objects[ObjectCount].States[k].Object != null) { Result.Objects[ObjectCount].States[k].Object.Dynamic = true; } } else { Result.Objects[ObjectCount].States[k].Object = null; } } for (int j = 0; j < Result.Objects[ObjectCount].States.Length; j++) { //Rotate X if (Result.Objects[ObjectCount].States[j].Object == null) { continue; } for (int l = 0; l < Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices.Length; l++) { //Apply position Result.Objects[ObjectCount].States[j].Position = Position; //Test whether the object contains non static rotation functions //If so, the results may be off so don't optimise if (!StaticXRotation) { if (Result.Objects[ObjectCount].RotateXFunction != null) { break; } } if (!StaticYRotation) { if (Result.Objects[ObjectCount].RotateYFunction != null) { break; } } if (!StaticZRotation) { if (Result.Objects[ObjectCount].RotateZFunction != null) { break; } } if (StaticXRotation) { double x = Result.Objects[ObjectCount].RotateXDirection.X; double y = Result.Objects[ObjectCount].RotateXDirection.Y; double z = Result.Objects[ObjectCount].RotateXDirection.Z; double t = x * x + y * y + z * z; if (t == 0.0) { x = 1.0; y = 0.0; z = 0.0; t = 1.0; } if (RotateX != 0.0) { t = 1.0 / Math.Sqrt(t); x *= t; y *= t; z *= t; } double cosX = Math.Cos(RotateX); double sinX = Math.Sin(RotateX); World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosX, sinX); Result.Objects[ObjectCount].RotateXFunction = null; } if (StaticYRotation) { double x = Result.Objects[ObjectCount].RotateYDirection.X; double y = Result.Objects[ObjectCount].RotateYDirection.Y; double z = Result.Objects[ObjectCount].RotateYDirection.Z; double t = x * x + y * y + z * z; if (t == 0.0) { x = 1.0; y = 0.0; z = 0.0; t = 1.0; } if (RotateY != 0.0) { t = 1.0 / Math.Sqrt(t); x *= t; y *= t; z *= t; } double cosY = Math.Cos(RotateY); double sinY = Math.Sin(RotateY); World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosY, sinY); Result.Objects[ObjectCount].RotateYFunction = null; } if (StaticZRotation) { double x = Result.Objects[ObjectCount].RotateZDirection.X; double y = Result.Objects[ObjectCount].RotateZDirection.Y; double z = Result.Objects[ObjectCount].RotateZDirection.Z; double t = x * x + y * y + z * z; if (t == 0.0) { x = 1.0; y = 0.0; z = 0.0; t = 1.0; } if (RotateZ != 0.0) { t = 1.0 / Math.Sqrt(t); x *= t; y *= t; z *= t; } double cosZ = Math.Cos(RotateZ); double sinZ = Math.Sin(RotateZ); World.Rotate(ref Result.Objects[ObjectCount].States[j].Object.Mesh.Vertices[l].Coordinates, x, y, z, cosZ, sinZ); Result.Objects[ObjectCount].RotateZFunction = null; } } } } else { Result.Objects[ObjectCount].States = new ObjectManager.AnimatedObjectState[] { }; } ObjectCount++; } break; default: Interface.AddMessage(Interface.MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); return null; } } } Array.Resize<ObjectManager.AnimatedObject>(ref Result.Objects, ObjectCount); return Result; }
// apply mesh builder private static void ApplyMeshBuilder(ref ObjectManager.StaticObject Object, MeshBuilder Builder) { if (Builder.Faces.Length != 0) { int mf = Object.Mesh.Faces.Length; int mm = Object.Mesh.Materials.Length; int mv = Object.Mesh.Vertices.Length; Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + Builder.Faces.Length); Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Builder.Materials.Length); Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Builder.Vertices.Length); for (int i = 0; i < Builder.Vertices.Length; i++) { Object.Mesh.Vertices[mv + i] = Builder.Vertices[i]; } for (int i = 0; i < Builder.Faces.Length; i++) { Object.Mesh.Faces[mf + i] = Builder.Faces[i]; for (int j = 0; j < Object.Mesh.Faces[mf + i].Vertices.Length; j++) { Object.Mesh.Faces[mf + i].Vertices[j].Index += (ushort)mv; } Object.Mesh.Faces[mf + i].Material += (ushort)mm; } for (int i = 0; i < Builder.Materials.Length; i++) { Object.Mesh.Materials[mm + i].Flags = (byte)((Builder.Materials[i].EmissiveColorUsed ? World.MeshMaterial.EmissiveColorMask : 0) | (Builder.Materials[i].TransparentColorUsed ? World.MeshMaterial.TransparentColorMask : 0)); Object.Mesh.Materials[mm + i].Color = Builder.Materials[i].Color; Object.Mesh.Materials[mm + i].TransparentColor = Builder.Materials[i].TransparentColor; if (Builder.Materials[i].DaytimeTexture != null || Builder.Materials[i].Text != null) { Textures.Texture tday; if (Builder.Materials[i].Text != null) { Bitmap bitmap = null; if (Builder.Materials[i].DaytimeTexture != null) { bitmap = new Bitmap(Builder.Materials[i].DaytimeTexture); } Bitmap texture = TextOverlay.AddTextToBitmap(bitmap, Builder.Materials[i].Text, Builder.Materials[i].Font, 12, Builder.Materials[i].BackgroundColor, Builder.Materials[i].TextColor, Builder.Materials[i].TextPadding); tday = Textures.RegisterTexture(texture, new OpenBveApi.Textures.TextureParameters(null, new Color24(Builder.Materials[i].TransparentColor.R, Builder.Materials[i].TransparentColor.G, Builder.Materials[i].TransparentColor.B))); } else { if (Builder.Materials[i].TransparentColorUsed) { Textures.RegisterTexture(Builder.Materials[i].DaytimeTexture, new OpenBveApi.Textures.TextureParameters(null, new Color24(Builder.Materials[i].TransparentColor.R, Builder.Materials[i].TransparentColor.G, Builder.Materials[i].TransparentColor.B)), out tday); } else { Textures.RegisterTexture(Builder.Materials[i].DaytimeTexture, out tday); } } Object.Mesh.Materials[mm + i].DaytimeTexture = tday; } else { Object.Mesh.Materials[mm + i].DaytimeTexture = null; } Object.Mesh.Materials[mm + i].EmissiveColor = Builder.Materials[i].EmissiveColor; if (Builder.Materials[i].NighttimeTexture != null) { Textures.Texture tnight; if (Builder.Materials[i].TransparentColorUsed) { Textures.RegisterTexture(Builder.Materials[i].NighttimeTexture, new OpenBveApi.Textures.TextureParameters(null, new Color24(Builder.Materials[i].TransparentColor.R, Builder.Materials[i].TransparentColor.G, Builder.Materials[i].TransparentColor.B)), out tnight); } else { Textures.RegisterTexture(Builder.Materials[i].NighttimeTexture, out tnight); } Object.Mesh.Materials[mm + i].NighttimeTexture = tnight; } else { Object.Mesh.Materials[mm + i].NighttimeTexture = null; } Object.Mesh.Materials[mm + i].DaytimeNighttimeBlend = 0; Object.Mesh.Materials[mm + i].BlendMode = Builder.Materials[i].BlendMode; Object.Mesh.Materials[mm + i].GlowAttenuationData = Builder.Materials[i].GlowAttenuationData; } } }
// read object /// <summary>Loads a CSV or B3D object from a file.</summary> /// <param name="FileName">The text file to load the animated object from. Must be an absolute file name.</param> /// <param name="Encoding">The encoding the file is saved in. If the file uses a byte order mark, the encoding indicated by the byte order mark is used and the Encoding parameter is ignored.</param> /// <param name="LoadMode">The texture load mode.</param> /// <param name="ForceTextureRepeatX">Whether to force TextureWrapMode.Repeat for X axis of referenced textures.</param> /// <param name="ForceTextureRepeatY">Whether to force TextureWrapMode.Repeat for Y axis of referenced textures.</param> /// <returns>The object loaded.</returns> internal static ObjectManager.StaticObject ReadObject(string FileName, System.Text.Encoding Encoding, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; bool IsB3D = string.Equals(System.IO.Path.GetExtension(FileName), ".b3d", StringComparison.OrdinalIgnoreCase); // initialize object ObjectManager.StaticObject Object = new ObjectManager.StaticObject(); Object.Mesh.Faces = new World.MeshFace[] { }; Object.Mesh.Materials = new World.MeshMaterial[] { }; Object.Mesh.Vertices = new World.Vertex[] { }; // read lines string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); // parse lines MeshBuilder Builder = new MeshBuilder(); Vector3f[] Normals = new Vector3f[4]; for (int i = 0; i < Lines.Length; i++) { { // strip away comments int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j); } } // collect arguments string[] Arguments = Lines[i].Split(new char[] { ',' }, StringSplitOptions.None); for (int j = 0; j < Arguments.Length; j++) { Arguments[j] = Arguments[j].Trim(); } { // remove unused arguments at the end of the chain int j; for (j = Arguments.Length - 1; j >= 0; j--) { if (Arguments[j].Length != 0) break; } Array.Resize<string>(ref Arguments, j + 1); } // style string Command; if (IsB3D & Arguments.Length != 0) { // b3d int space = Arguments[0].IndexOf(' '); if (space >= 0) { Command = Arguments[0].Substring(0, space).TrimEnd(); Arguments[0] = Arguments[0].Substring(space + 1).TrimStart(); } else { Command = Arguments[0]; if (Arguments.Length != 1) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid syntax at line " + (i + 1).ToString(Culture) + " in file " + FileName); } Arguments = new string[] { }; } } else if (Arguments.Length != 0) { // csv Command = Arguments[0]; for (int j = 0; j < Arguments.Length - 1; j++) { Arguments[j] = Arguments[j + 1]; } Array.Resize<string>(ref Arguments, Arguments.Length - 1); } else { // empty Command = null; } // parse terms if (Command != null) { string cmd = Command.ToLowerInvariant(); switch(cmd) { case "createmeshbuilder": case "[meshbuilder]": { if (cmd == "createmeshbuilder" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "CreateMeshBuilder is not a supported command - did you mean [MeshBuilder]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "[meshbuilder]" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "[MeshBuilder] is not a supported command - did you mean CreateMeshBuilder? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 0) { Debug.AddMessage(Debug.MessageType.Warning, false, "0 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); Builder = new MeshBuilder(); Normals = new Vector3f[4]; } break; case "addvertex": case "vertex": { if (cmd == "addvertex" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "AddVertex is not a supported command - did you mean Vertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "vertex" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Vertex is not a supported command - did you mean AddVertex? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 6) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 6 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double vx = 0.0, vy = 0.0, vz = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out vx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vx = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out vy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vy = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out vz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument vZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); vz = 0.0; } double nx = 0.0, ny = 0.0, nz = 0.0; if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out nx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); nx = 0.0; } if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[4], out ny)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); ny = 0.0; } if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[5], out nz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument nZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); nz = 0.0; } World.Normalize(ref nx, ref ny, ref nz); Array.Resize<World.Vertex>(ref Builder.Vertices, Builder.Vertices.Length + 1); while (Builder.Vertices.Length >= Normals.Length) { Array.Resize<Vector3f>(ref Normals, Normals.Length << 1); } Builder.Vertices[Builder.Vertices.Length - 1].Coordinates = new Vector3D(vx, vy, vz); Normals[Builder.Vertices.Length - 1] = new Vector3f((float)nx, (float)ny, (float)nz); } break; case "addface": case "addface2": case "face": case "face2": { if (IsB3D) { if (cmd == "addface") { Debug.AddMessage(Debug.MessageType.Warning, false, "AddFace is not a supported command - did you mean Face? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "addface2") { Debug.AddMessage(Debug.MessageType.Warning, false, "AddFace2 is not a supported command - did you mean Face2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { if (cmd == "face") { Debug.AddMessage(Debug.MessageType.Warning, false, "Face is not a supported command - did you mean AddFace? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "face2") { Debug.AddMessage(Debug.MessageType.Warning, false, "Face2 is not a supported command - did you mean AddFace2? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } if (Arguments.Length < 3) { Debug.AddMessage(Debug.MessageType.Error, false, "At least 3 arguments are required in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { bool valid = true; int[] indices = new int[Arguments.Length]; for (int j = 0; j < Arguments.Length; j++) { if (!Conversions.TryParseIntVb6(Arguments[j], out indices[j])) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " is invalid in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } else if (indices[j] < 0 | indices[j] >= Builder.Vertices.Length) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } else if (indices[j] > 65535) { Debug.AddMessage(Debug.MessageType.Error, false, "v" + j.ToString(Culture) + " indexes a vertex above 65535 which is not currently supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); valid = false; break; } } if (valid) { int last = Builder.Faces.Length; Array.Resize<World.MeshFace>(ref Builder.Faces, last + 1); Builder.Faces[last] = new World.MeshFace(); Builder.Faces[last].Vertices = new World.MeshFaceVertex[Arguments.Length]; while (Builder.Vertices.Length > Normals.Length) { Array.Resize<Vector3f>(ref Normals, Normals.Length << 1); } for (int j = 0; j < Arguments.Length; j++) { Builder.Faces[last].Vertices[j].Index = (ushort)indices[j]; Builder.Faces[last].Vertices[j].Normal = Normals[indices[j]]; } if (cmd == "addface2" | cmd == "face2") { Builder.Faces[last].Flags = (byte)World.MeshFace.Face2Mask; } } } } break; case "cube": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfWidth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } double y = x, z = x; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfHeight in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument HalfDepth in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } CreateCube(ref Builder, x, y, z); } break; case "cylinder": { if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int n = 8; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out n)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument n in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); n = 8; } if (n < 2) { Debug.AddMessage(Debug.MessageType.Error, false, "n is expected to be at least 2 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); n = 8; } double r1 = 0.0, r2 = 0.0, h = 1.0; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out r1)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument UpperRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r1 = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out r2)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument LowerRadius in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r2 = 1.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out h)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Height in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); h = 1.0; } CreateCylinder(ref Builder, n, r1, r2, h); } break; case "translate": case "translateall": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0, y = 0.0, z = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 0.0; } ApplyTranslation(Builder, x, y, z); if (cmd == "translateall") { ApplyTranslation(Object, x, y, z); } } break; case "scale": case "scaleall": { if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 1.0, y = 1.0, z = 1.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } else if (x == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "X is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 1.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } else if (y == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Y is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 1.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } else if (z == 0.0) { Debug.AddMessage(Debug.MessageType.Error, false, "Z is required to be different from zero in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 1.0; } ApplyScale(Builder, x, y, z); if (cmd == "scaleall") { ApplyScale(Object, x, y, z); } } break; case "rotate": case "rotateall": { if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double x = 0.0, y = 0.0, z = 0.0, a = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out z)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Z in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); z = 0.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out a)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Angle in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = 0.0; } double t = x * x + y * y + z * z; if (t == 0.0) { x = 1.0; y = 0.0; z = 0.0; t = 1.0; } if (a != 0.0) { t = 1.0 / Math.Sqrt(t); x *= t; y *= t; z *= t; a *= 0.0174532925199433; ApplyRotation(Builder, x, y, z, a); if (cmd == "rotateall") { ApplyRotation(Object, x, y, z, a); } } } break; case "shear": case "shearall": { if (Arguments.Length > 7) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 7 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } double dx = 0.0, dy = 0.0, dz = 0.0; double sx = 0.0, sy = 0.0, sz = 0.0; double r = 0.0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[0], out dx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dx = 0.0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out dy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dy = 0.0; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[2], out dz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument dZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); dz = 0.0; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[3], out sx)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sX in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sx = 0.0; } if (Arguments.Length >= 5 && Arguments[4].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[4], out sy)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sY in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sy = 0.0; } if (Arguments.Length >= 6 && Arguments[5].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[5], out sz)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument sZ in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); sz = 0.0; } if (Arguments.Length >= 7 && Arguments[6].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[6], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Ratio in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0.0; } World.Normalize(ref dx, ref dy, ref dz); World.Normalize(ref sx, ref sy, ref sz); ApplyShear(Builder, dx, dy, dz, sx, sy, sz, r); if (cmd == "shearall") { ApplyShear(Object, dx, dy, dz, sx, sy, sz, r); } } break; case "generatenormals": case "[texture]": if (cmd == "generatenormals" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "GenerateNormals is not a supported command - did you mean [Texture]? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "[texture]" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "[Texture] is not a supported command - did you mean GenerateNormals? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } // TODO do something? break; case "setcolor": case "color": { if (cmd == "setcolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetColor is not a supported command - did you mean Color? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "color" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Color is not a supported command - did you mean SetColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 4) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 4 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0, a = 255; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } if (Arguments.Length >= 4 && Arguments[3].Length > 0 && !Conversions.TryParseIntVb6(Arguments[3], out a)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Alpha in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = 255; } else if (a < 0 | a > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Alpha is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); a = a < 0 ? 0 : 255; } int m = Builder.Materials.Length; Array.Resize<Material>(ref Builder.Materials, m << 1); for (int j = m; j < Builder.Materials.Length; j++) { Builder.Materials[j] = new Material(Builder.Materials[j - m]); Builder.Materials[j].Color = new Color32((byte)r, (byte)g, (byte)b, (byte)a); Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode; Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData; Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture; Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture; Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor; Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed; } for (int j = 0; j < Builder.Faces.Length; j++) { Builder.Faces[j].Material += (ushort)m; } } break; case "setemissivecolor": case "emissivecolor": { if (cmd == "setemissivecolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetEmissiveColor is not a supported command - did you mean EmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "emissivecolor" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "EmissiveColor is not a supported command - did you mean SetEmissiveColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } int m = Builder.Materials.Length; Array.Resize<Material>(ref Builder.Materials, m << 1); for (int j = m; j < Builder.Materials.Length; j++) { Builder.Materials[j] = new Material(Builder.Materials[j - m]); Builder.Materials[j].EmissiveColor = new Color24((byte)r, (byte)g, (byte)b); Builder.Materials[j].EmissiveColorUsed = true; Builder.Materials[j].BlendMode = Builder.Materials[0].BlendMode; Builder.Materials[j].GlowAttenuationData = Builder.Materials[0].GlowAttenuationData; Builder.Materials[j].DaytimeTexture = Builder.Materials[0].DaytimeTexture; Builder.Materials[j].NighttimeTexture = Builder.Materials[0].NighttimeTexture; Builder.Materials[j].TransparentColor = Builder.Materials[0].TransparentColor; Builder.Materials[j].TransparentColorUsed = Builder.Materials[0].TransparentColorUsed; } for (int j = 0; j < Builder.Faces.Length; j++) { Builder.Faces[j].Material += (ushort)m; } } break; case "setdecaltransparentcolor": case "transparent": { if (cmd == "setdecaltransparentcolor" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetDecalTransparentColor is not a supported command - did you mean Transparent? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "transparent" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Transparent is not a supported command - did you mean SetDecalTransparentColor? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int r = 0, g = 0, b = 0; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out r)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Red in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = 0; } else if (r < 0 | r > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Red is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); r = r < 0 ? 0 : 255; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseIntVb6(Arguments[1], out g)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Green in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = 0; } else if (g < 0 | g > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Green is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); g = g < 0 ? 0 : 255; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseIntVb6(Arguments[2], out b)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Blue in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = 0; } else if (b < 0 | b > 255) { Debug.AddMessage(Debug.MessageType.Error, false, "Blue is required to be within the range from 0 to 255 in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); b = b < 0 ? 0 : 255; } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].TransparentColor = new Color24((byte)r, (byte)g, (byte)b); Builder.Materials[j].TransparentColorUsed = true; } } break; case "setblendmode": case "blendmode": { if (cmd == "setblendmode" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetBlendMode is not a supported command - did you mean BlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "blendmode" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "BlendMode is not a supported command - did you mean SetBlendMode? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } World.MeshMaterialBlendMode blendmode = World.MeshMaterialBlendMode.Normal; if (Arguments.Length >= 1 && Arguments[0].Length > 0) { switch (Arguments[0].ToLowerInvariant()) { case "normal": blendmode = World.MeshMaterialBlendMode.Normal; break; case "additive": blendmode = World.MeshMaterialBlendMode.Additive; break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The given BlendMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); blendmode = World.MeshMaterialBlendMode.Normal; break; } } double glowhalfdistance = 0.0; if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseDoubleVb6(Arguments[1], out glowhalfdistance)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument GlowHalfDistance in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); glowhalfdistance = 0; } World.GlowAttenuationMode glowmode = World.GlowAttenuationMode.DivisionExponent4; if (Arguments.Length >= 3 && Arguments[2].Length > 0) { switch (Arguments[2].ToLowerInvariant()) { case "divideexponent2": glowmode = World.GlowAttenuationMode.DivisionExponent2; break; case "divideexponent4": glowmode = World.GlowAttenuationMode.DivisionExponent4; break; default: Debug.AddMessage(Debug.MessageType.Error, false, "The given GlowAttenuationMode is not supported in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].BlendMode = blendmode; Builder.Materials[j].GlowAttenuationData = World.GetGlowAttenuationData(glowhalfdistance, glowmode); } } break; case "loadtexture": case "load": { if (cmd == "loadtexture" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "LoadTexture is not a supported command - did you mean Load? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "load" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Load is not a supported command - did you mean LoadTexture? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 2) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 2 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } string tday = null, tnight = null; if (Arguments.Length >= 1 && Arguments[0].Length != 0) { if (OpenBveApi.Path.ContainsInvalidPathChars(Arguments[0])) { Debug.AddMessage(Debug.MessageType.Error, false, "DaytimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { tday = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[0]); if (!System.IO.File.Exists(tday)) { Debug.AddMessage(Debug.MessageType.Error, true, "DaytimeTexture " + tday + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); tday = null; } } } if (Arguments.Length >= 2 && Arguments[1].Length != 0) { if (Arguments[0].Length == 0) { Debug.AddMessage(Debug.MessageType.Error, true, "DaytimeTexture is required to be specified in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (OpenBveApi.Path.ContainsInvalidPathChars(Arguments[1])) { Debug.AddMessage(Debug.MessageType.Error, false, "NighttimeTexture contains illegal characters in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { tnight = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), Arguments[1]); if (!System.IO.File.Exists(tnight)) { Debug.AddMessage(Debug.MessageType.Error, true, "The NighttimeTexture " + tnight + " could not be found in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); tnight = null; } } } } for (int j = 0; j < Builder.Materials.Length; j++) { Builder.Materials[j].DaytimeTexture = tday; Builder.Materials[j].NighttimeTexture = tnight; } } break; case "settexturecoordinates": case "coordinates": { if (cmd == "settexturecoordinates" & IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "SetTextureCoordinates is not a supported command - did you mean Coordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (cmd == "coordinates" & !IsB3D) { Debug.AddMessage(Debug.MessageType.Warning, false, "Coordinates is not a supported command - did you mean SetTextureCoordinates? - at line " + (i + 1).ToString(Culture) + " in file " + FileName); } if (Arguments.Length > 3) { Debug.AddMessage(Debug.MessageType.Warning, false, "At most 3 arguments are expected in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } int j = 0; float x = 0.0f, y = 0.0f; if (Arguments.Length >= 1 && Arguments[0].Length > 0 && !Conversions.TryParseIntVb6(Arguments[0], out j)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument VertexIndex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); j = 0; } if (Arguments.Length >= 2 && Arguments[1].Length > 0 && !Conversions.TryParseFloatVb6(Arguments[1], out x)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument X in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); x = 0.0f; } if (Arguments.Length >= 3 && Arguments[2].Length > 0 && !Conversions.TryParseFloatVb6(Arguments[2], out y)) { Debug.AddMessage(Debug.MessageType.Error, false, "Invalid argument Y in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); y = 0.0f; } if (j >= 0 & j < Builder.Vertices.Length) { Builder.Vertices[j].TextureCoordinates = new Vector2f(x, y); } else { Debug.AddMessage(Debug.MessageType.Error, false, "VertexIndex references a non-existing vertex in " + Command + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: if (Command.Length != 0) { Debug.AddMessage(Debug.MessageType.Error, false, "The command " + Command + " is not supported at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; } } } // finalize object ApplyMeshBuilder(ref Object, Builder, LoadMode, ForceTextureRepeatX, ForceTextureRepeatY); World.CreateNormals(ref Object.Mesh); return Object; }
/// <summary>Jumps a train to a new station</summary> /// <param name="train">The train</param> /// <param name="stationIndex">The zero-based index of the station</param> internal static void JumpTrain(Train train, int stationIndex) { if (train.IsPlayerTrain) { for (int i = 0; i < ObjectManager.AnimatedWorldObjects.Length; i++) { var obj = ObjectManager.AnimatedWorldObjects[i] as ObjectManager.TrackFollowingObject; if (obj != null) { //Track followers should be reset if we jump between stations obj.FrontAxleFollower.TrackPosition = ObjectManager.AnimatedWorldObjects[i].TrackPosition + obj.FrontAxlePosition; obj.FrontAxleFollower.TrackPosition = ObjectManager.AnimatedWorldObjects[i].TrackPosition + obj.RearAxlePosition; obj.FrontAxleFollower.UpdateWorldCoordinates(false); obj.RearAxleFollower.UpdateWorldCoordinates(false); } } } train.StationState = TrainStopState.Jumping; int stopIndex = CurrentRoute.Stations[stationIndex].GetStopIndex(train.NumberOfCars); if (stopIndex >= 0) { if (train.IsPlayerTrain) { if (train.Plugin != null) { train.Plugin.BeginJump((OpenBveApi.Runtime.InitializationModes)Game.TrainStart); } } for (int h = 0; h < train.Cars.Length; h++) { train.Cars[h].CurrentSpeed = 0.0; } double d = CurrentRoute.Stations[stationIndex].Stops[stopIndex].TrackPosition - train.Cars[0].FrontAxle.Follower.TrackPosition + train.Cars[0].FrontAxle.Position - 0.5 * train.Cars[0].Length; if (train.IsPlayerTrain) { TrackManager.SuppressSoundEvents = true; } while (d != 0.0) { double x; if (Math.Abs(d) > 1.0) { x = (double)Math.Sign(d); } else { x = d; } for (int h = 0; h < train.Cars.Length; h++) { train.Cars[h].Move(x); } if (Math.Abs(d) >= 1.0) { d -= x; } else { break; } } if (train.IsPlayerTrain) { TrainManager.UnderailTrains(); TrackManager.SuppressSoundEvents = false; } if (train.Handles.EmergencyBrake.Driver) { train.ApplyNotch(0, false, 0, true); } else { train.ApplyNotch(0, false, train.Handles.Brake.MaximumNotch, false); train.ApplyAirBrakeHandle(TrainManager.AirBrakeHandleState.Service); } if (CurrentRoute.Sections.Length > 0) { Game.UpdateSection(CurrentRoute.Sections.Length - 1); } if (train.IsPlayerTrain) { if (Game.CurrentScore.ArrivalStation <= stationIndex) { Game.CurrentScore.ArrivalStation = stationIndex + 1; } } if (train.IsPlayerTrain) { if (CurrentRoute.Stations[stationIndex].ArrivalTime >= 0.0) { Game.SecondsSinceMidnight = CurrentRoute.Stations[stationIndex].ArrivalTime; } else if (CurrentRoute.Stations[stationIndex].DepartureTime >= 0.0) { Game.SecondsSinceMidnight = CurrentRoute.Stations[stationIndex].DepartureTime - CurrentRoute.Stations[stationIndex].StopTime; } } for (int i = 0; i < train.Cars.Length; i++) { train.Cars[i].Doors[0].AnticipatedOpen = CurrentRoute.Stations[stationIndex].OpenLeftDoors; train.Cars[i].Doors[1].AnticipatedOpen = CurrentRoute.Stations[stationIndex].OpenRightDoors; } if (train.IsPlayerTrain) { Game.CurrentScore.DepartureStation = stationIndex; Game.CurrentInterface = Game.InterfaceType.Normal; //Game.Messages = new Game.Message[] { }; } ObjectManager.UpdateAnimatedWorldObjects(0.0, true); TrainManager.UpdateTrainObjects(0.0, true); if (train.IsPlayerTrain) { if (train.Plugin != null) { train.Plugin.EndJump(); } } train.StationState = TrainStopState.Pending; if (train.IsPlayerTrain) { train.LastStation = stationIndex; } } }
private static void ApplyRotation(ObjectManager.StaticObject Object, double x, double y, double z, double a) { double cosa = Math.Cos(a); double sina = Math.Sin(a); for (int j = 0; j < Object.Mesh.Vertices.Length; j++) { World.Rotate(ref Object.Mesh.Vertices[j].Coordinates.X, ref Object.Mesh.Vertices[j].Coordinates.Y, ref Object.Mesh.Vertices[j].Coordinates.Z, x, y, z, cosa, sina); } for (int j = 0; j < Object.Mesh.Faces.Length; j++) { for (int k = 0; k < Object.Mesh.Faces[j].Vertices.Length; k++) { World.Rotate(ref Object.Mesh.Faces[j].Vertices[k].Normal.X, ref Object.Mesh.Faces[j].Vertices[k].Normal.Y, ref Object.Mesh.Faces[j].Vertices[k].Normal.Z, x, y, z, cosa, sina); } } }
// process structure private static bool ProcessStructure(string FileName, Structure Structure, out ObjectManager.StaticObject Object, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; Object = new ObjectManager.StaticObject(); Object.Mesh.Faces = new World.MeshFace[] { }; Object.Mesh.Materials = new World.MeshMaterial[] { }; Object.Mesh.Vertices = new World.Vertex[] { }; // file for (int i = 0; i < Structure.Data.Length; i++) { Structure f = Structure.Data[i] as Structure; if (f == null) { Interface.AddMessage(Interface.MessageType.Error, false, "Top-level inlined arguments are invalid in x object file " + FileName); return false; } switch (f.Name) { case "Mesh": { // mesh if (f.Data.Length < 4) { Interface.AddMessage(Interface.MessageType.Error, false, "Mesh is expected to have at least 4 arguments in x object file " + FileName); return false; } else if (!(f.Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nVertices is expected to be a DWORD in Mesh in x object file " + FileName); return false; } else if (!(f.Data[1] is Structure[])) { Interface.AddMessage(Interface.MessageType.Error, false, "vertices[nVertices] is expected to be a Vector array in Mesh in x object file " + FileName); return false; } else if (!(f.Data[2] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaces is expected to be a DWORD in Mesh in x object file " + FileName); return false; } else if (!(f.Data[3] is Structure[])) { Interface.AddMessage(Interface.MessageType.Error, false, "faces[nFaces] is expected to be a MeshFace array in Mesh in x object file " + FileName); return false; } int nVertices = (int)f.Data[0]; if (nVertices < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nVertices is expected to be non-negative in Mesh in x object file " + FileName); return false; } Structure[] vertices = (Structure[])f.Data[1]; if (nVertices != vertices.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nVertices does not match with the length of array vertices in Mesh in x object file " + FileName); return false; } int nFaces = (int)f.Data[2]; if (nFaces < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaces is expected to be non-negative in Mesh in x object file " + FileName); return false; } Structure[] faces = (Structure[])f.Data[3]; if (nFaces != faces.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaces does not match with the length of array faces in Mesh in x object file " + FileName); return false; } // collect vertices World.Vertex[] Vertices = new World.Vertex[nVertices]; for (int j = 0; j < nVertices; j++) { if (vertices[j].Name != "Vector") { Interface.AddMessage(Interface.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to be of template Vertex in Mesh in x object file " + FileName); return false; } else if (vertices[j].Data.Length != 3) { Interface.AddMessage(Interface.MessageType.Error, false, "vertices[" + j.ToString(Culture) + "] is expected to have 3 arguments in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "x is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "y is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(vertices[j].Data[2] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "z is expected to be a float in vertices[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } double x = (double)vertices[j].Data[0]; double y = (double)vertices[j].Data[1]; double z = (double)vertices[j].Data[2]; Vertices[j].Coordinates = new World.Vector3D(x, y, z); } // collect faces int[][] Faces = new int[nFaces][]; World.Vector3Df[][] FaceNormals = new World.Vector3Df[nFaces][]; int[] FaceMaterials = new int[nFaces]; for (int j = 0; j < nFaces; j++) { FaceMaterials[j] = -1; } for (int j = 0; j < nFaces; j++) { if (faces[j].Name != "MeshFace") { Interface.AddMessage(Interface.MessageType.Error, false, "faces[" + j.ToString(Culture) + "] is expected to be of template MeshFace in Mesh in x object file " + FileName); return false; } else if (faces[j].Data.Length != 2) { Interface.AddMessage(Interface.MessageType.Error, false, "face[" + j.ToString(Culture) + "] is expected to have 2 arguments in Mesh in x object file " + FileName); return false; } else if (!(faces[j].Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } else if (!(faces[j].Data[1] is int[])) { Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } int nFaceVertexIndices = (int)faces[j].Data[0]; if (nFaceVertexIndices < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be non-negative in MeshFace in Mesh in x object file " + FileName); return false; } int[] faceVertexIndices = (int[])faces[j].Data[1]; if (nFaceVertexIndices != faceVertexIndices.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } Faces[j] = new int[nFaceVertexIndices]; FaceNormals[j] = new World.Vector3Df[nFaceVertexIndices]; for (int k = 0; k < nFaceVertexIndices; k++) { if (faceVertexIndices[k] < 0 | faceVertexIndices[k] >= nVertices) { Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[" + k.ToString(Culture) + "] does not reference a valid vertex in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } Faces[j][k] = faceVertexIndices[k]; FaceNormals[j][k] = new World.Vector3Df(0.0f, 0.0f, 0.0f); } } // collect additional templates Material[] Materials = new Material[] { }; for (int j = 4; j < f.Data.Length; j++) { Structure g = f.Data[j] as Structure; if (g == null) { Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in Mesh in x object file " + FileName); return false; } switch (g.Name) { case "MeshMaterialList": { // meshmateriallist if (g.Data.Length < 3) { Interface.AddMessage(Interface.MessageType.Error, false, "MeshMaterialList is expected to have at least 3 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes is expected to be a DWORD in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(g.Data[2] is int[])) { Interface.AddMessage(Interface.MessageType.Error, false, "faceIndexes[nFaceIndexes] is expected to be a DWORD array in MeshMaterialList in Mesh in x object file " + FileName); return false; } int nMaterials = (int)g.Data[0]; if (nMaterials < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName); return false; } int nFaceIndexes = (int)g.Data[1]; if (nFaceIndexes < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes is expected to be non-negative in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (nFaceIndexes > nFaces) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes does not reference valid faces in MeshMaterialList in Mesh in x object file " + FileName); return false; } int[] faceIndexes = (int[])g.Data[2]; if (nFaceIndexes != faceIndexes.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceIndexes does not match with the length of array faceIndexes in face[" + j.ToString(Culture) + "] in Mesh in x object file " + FileName); return false; } for (int k = 0; k < nFaceIndexes; k++) { if (faceIndexes[k] < 0 | faceIndexes[k] >= nMaterials) { Interface.AddMessage(Interface.MessageType.Error, false, "faceIndexes[" + k.ToString(Culture) + "] does not reference a valid Material template in MeshMaterialList in Mesh in x object file " + FileName); return false; } } // collect material templates int mn = Materials.Length; Array.Resize<Material>(ref Materials, mn + nMaterials); for (int k = 0; k < nMaterials; k++) { Materials[mn + k].faceColor = new World.ColorRGBA(255, 255, 255, 255); Materials[mn + k].specularColor = new World.ColorRGB(0, 0, 0); Materials[mn + k].emissiveColor = new World.ColorRGB(0, 0, 0); Materials[mn + k].TextureFilename = null; } int MaterialIndex = mn; for (int k = 3; k < g.Data.Length; k++) { Structure h = g.Data[k] as Structure; if (h == null) { Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (h.Name != "Material") { Interface.AddMessage(Interface.MessageType.Error, false, "Material template expected in MeshMaterialList in Mesh in x object file " + FileName); return false; } else { // material if (h.Data.Length < 4) { Interface.AddMessage(Interface.MessageType.Error, false, "Material is expected to have at least 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[0] is Structure)) { Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "power is expected to be a float in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[2] is Structure)) { Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(h.Data[3] is Structure)) { Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } Structure faceColor = (Structure)h.Data[0]; Structure specularColor = (Structure)h.Data[2]; Structure emissiveColor = (Structure)h.Data[3]; double red, green, blue, alpha; // collect face color if (faceColor.Name != "ColorRGBA") { Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (faceColor.Data.Length != 4) { Interface.AddMessage(Interface.MessageType.Error, false, "faceColor is expected to have 4 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[2] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(faceColor.Data[3] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "alpha is expected to be a float in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)faceColor.Data[0]; green = (double)faceColor.Data[1]; blue = (double)faceColor.Data[2]; alpha = (double)faceColor.Data[3]; if (red < 0.0 | red > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } if (alpha < 0.0 | alpha > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "alpha is expected to be in the range from 0.0 to 1.0 in faceColor in Material in MeshMaterialList in Mesh in x object file " + FileName); alpha = alpha < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].faceColor = new World.ColorRGBA((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue), (byte)Math.Round(255.0 * alpha)); // collect specular color if (specularColor.Name != "ColorRGB") { Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to be a ColorRGB in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (specularColor.Data.Length != 3) { Interface.AddMessage(Interface.MessageType.Error, false, "specularColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(specularColor.Data[2] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)specularColor.Data[0]; green = (double)specularColor.Data[1]; blue = (double)specularColor.Data[2]; if (red < 0.0 | red > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in specularColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].specularColor = new World.ColorRGB((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue)); // collect emissive color if (emissiveColor.Name != "ColorRGB") { Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to be a ColorRGBA in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (emissiveColor.Data.Length != 3) { Interface.AddMessage(Interface.MessageType.Error, false, "emissiveColor is expected to have 3 arguments in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(emissiveColor.Data[2] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be a float in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } red = (double)emissiveColor.Data[0]; green = (double)emissiveColor.Data[1]; blue = (double)emissiveColor.Data[2]; if (red < 0.0 | red > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "red is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); red = red < 0.5 ? 0.0 : 1.0; } if (green < 0.0 | green > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "green is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); green = green < 0.5 ? 0.0 : 1.0; } if (blue < 0.0 | blue > 1.0) { Interface.AddMessage(Interface.MessageType.Error, false, "blue is expected to be in the range from 0.0 to 1.0 in emissiveColor in Material in MeshMaterialList in Mesh in x object file " + FileName); blue = blue < 0.5 ? 0.0 : 1.0; } Materials[MaterialIndex].emissiveColor = new World.ColorRGB((byte)Math.Round(255.0 * red), (byte)Math.Round(255.0 * green), (byte)Math.Round(255.0 * blue)); // collect additional templates for (int l = 4; l < h.Data.Length; l++) { Structure e = h.Data[l] as Structure; if (e == null) { Interface.AddMessage(Interface.MessageType.Error, false, "Unexpected inlined argument encountered in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } switch (e.Name) { case "TextureFilename": { // texturefilename if (e.Data.Length != 1) { Interface.AddMessage(Interface.MessageType.Error, false, "filename is expected to have 1 argument in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } else if (!(e.Data[0] is string)) { Interface.AddMessage(Interface.MessageType.Error, false, "filename is expected to be a string in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } string filename = (string)e.Data[0]; if (Interface.ContainsInvalidPathChars(filename)) { Interface.AddMessage(Interface.MessageType.Error, false, "filename contains illegal characters in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(System.IO.Path.GetDirectoryName(FileName), filename); if (System.IO.File.Exists(File)) { Materials[MaterialIndex].TextureFilename = File; } else { Interface.AddMessage(Interface.MessageType.Error, true, "The texture file " + File + " could not be found in TextureFilename in Material in MeshMaterialList in Mesh in x object file " + FileName); } } } break; default: // unknown Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + e.Name + " encountered in MeshMaterialList in Mesh in x object file " + FileName); break; } } // finish MaterialIndex++; } } if (MaterialIndex != mn + nMaterials) { Interface.AddMessage(Interface.MessageType.Error, false, "nMaterials does not match the number of Material templates encountered in Material in MeshMaterialList in Mesh in x object file " + FileName); return false; } // assign materials for (int k = 0; k < nFaceIndexes; k++) { FaceMaterials[k] = faceIndexes[k]; } if (nMaterials != 0) { for (int k = 0; k < nFaces; k++) { if (FaceMaterials[k] == -1) { FaceMaterials[k] = 0; } } } } break; case "MeshTextureCoords": { // meshtexturecoords if (g.Data.Length != 2) { Interface.AddMessage(Interface.MessageType.Error, false, "MeshTextureCoords is expected to have 2 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nTextureCoords is expected to be a DWORD in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is Structure[])) { Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[nTextureCoords] is expected to be a Coords2d array in MeshTextureCoords in Mesh in x object file " + FileName); return false; } int nTextureCoords = (int)g.Data[0]; Structure[] textureCoords = (Structure[])g.Data[1]; if (nTextureCoords < 0 | nTextureCoords > nVertices) { Interface.AddMessage(Interface.MessageType.Error, false, "nTextureCoords does not reference valid vertices in MeshTextureCoords in Mesh in x object file " + FileName); return false; } for (int k = 0; k < nTextureCoords; k++) { if (textureCoords[k].Name != "Coords2d") { Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to be a Coords2d in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (textureCoords[k].Data.Length != 2) { Interface.AddMessage(Interface.MessageType.Error, false, "textureCoords[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(textureCoords[k].Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "u is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName); return false; } else if (!(textureCoords[k].Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "v is expected to be a float in textureCoords[" + k.ToString(Culture) + "] in MeshTextureCoords in Mesh in x object file " + FileName); return false; } double u = (double)textureCoords[k].Data[0]; double v = (double)textureCoords[k].Data[1]; Vertices[k].TextureCoordinates = new World.Vector2Df((float)u, (float)v); } } break; case "MeshNormals": { // meshnormals if (g.Data.Length != 4) { Interface.AddMessage(Interface.MessageType.Error, false, "MeshNormals is expected to have 4 arguments in Mesh in x object file " + FileName); return false; } else if (!(g.Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[1] is Structure[])) { Interface.AddMessage(Interface.MessageType.Error, false, "normals is expected to be a Vector array in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[2] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceNormals is expected to be a DWORD in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(g.Data[3] is Structure[])) { Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals is expected to be a MeshFace array in MeshNormals in Mesh in x object file " + FileName); return false; } int nNormals = (int)g.Data[0]; if (nNormals < 0) { Interface.AddMessage(Interface.MessageType.Error, false, "nNormals is expected to be non-negative in MeshNormals in Mesh in x object file " + FileName); return false; } Structure[] normals = (Structure[])g.Data[1]; if (nNormals != normals.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nNormals does not match with the length of array normals in MeshNormals in Mesh in x object file " + FileName); return false; } int nFaceNormals = (int)g.Data[2]; if (nFaceNormals < 0 | nFaceNormals > nFaces) { Interface.AddMessage(Interface.MessageType.Error, false, "nNormals does not reference valid vertices in MeshNormals in Mesh in x object file " + FileName); return false; } Structure[] faceNormals = (Structure[])g.Data[3]; if (nFaceNormals != faceNormals.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceNormals does not match with the length of array faceNormals in MeshNormals in Mesh in x object file " + FileName); return false; } // collect normals World.Vector3Df[] Normals = new World.Vector3Df[nNormals]; for (int k = 0; k < nNormals; k++) { if (normals[k].Name != "Vector") { Interface.AddMessage(Interface.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to be of template Vertex in MeshNormals in Mesh in x object file " + FileName); return false; } else if (normals[k].Data.Length != 3) { Interface.AddMessage(Interface.MessageType.Error, false, "normals[" + k.ToString(Culture) + "] is expected to have 3 arguments in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[0] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "x is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[1] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "y is expected to be a float in normals[" + k.ToString(Culture) + " ]in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(normals[k].Data[2] is double)) { Interface.AddMessage(Interface.MessageType.Error, false, "z is expected to be a float in normals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } double x = (double)normals[k].Data[0]; double y = (double)normals[k].Data[1]; double z = (double)normals[k].Data[2]; World.Normalize(ref x, ref y, ref z); Normals[k] = new World.Vector3Df((float)x, (float)y, (float)z); } // collect faces for (int k = 0; k < nFaceNormals; k++) { if (faceNormals[k].Name != "MeshFace") { Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to be of template MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } else if (faceNormals[k].Data.Length != 2) { Interface.AddMessage(Interface.MessageType.Error, false, "faceNormals[" + k.ToString(Culture) + "] is expected to have 2 arguments in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(faceNormals[k].Data[0] is int)) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices is expected to be a DWORD in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } else if (!(faceNormals[k].Data[1] is int[])) { Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[nFaceVertexIndices] is expected to be a DWORD array in faceNormals[" + k.ToString(Culture) + "] in MeshNormals in Mesh in x object file " + FileName); return false; } int nFaceVertexIndices = (int)faceNormals[k].Data[0]; if (nFaceVertexIndices < 0 | nFaceVertexIndices > Faces[k].Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not reference a valid vertex in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } int[] faceVertexIndices = (int[])faceNormals[k].Data[1]; if (nFaceVertexIndices != faceVertexIndices.Length) { Interface.AddMessage(Interface.MessageType.Error, false, "nFaceVertexIndices does not match with the length of array faceVertexIndices in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } for (int l = 0; l < nFaceVertexIndices; l++) { if (faceVertexIndices[l] < 0 | faceVertexIndices[l] >= nNormals) { Interface.AddMessage(Interface.MessageType.Error, false, "faceVertexIndices[" + l.ToString(Culture) + "] does not reference a valid normal in faceNormals[" + k.ToString(Culture) + "] in MeshFace in MeshNormals in Mesh in x object file " + FileName); return false; } FaceNormals[k][l] = Normals[faceVertexIndices[l]]; } } } break; default: // unknown Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + g.Name + " encountered in Mesh in x object file " + FileName); break; } } // default material if (Materials.Length == 0) { Materials = new Material[1]; Materials[0].faceColor = new World.ColorRGBA(255, 255, 255, 255); Materials[0].emissiveColor = new World.ColorRGB(0, 0, 0); Materials[0].specularColor = new World.ColorRGB(0, 0, 0); Materials[0].TextureFilename = null; for (int j = 0; j < nFaces; j++) { FaceMaterials[j] = 0; } } // create mesh int mf = Object.Mesh.Faces.Length; int mm = Object.Mesh.Materials.Length; int mv = Object.Mesh.Vertices.Length; Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + nFaces); Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Materials.Length); Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Vertices.Length); for (int j = 0; j < Materials.Length; j++) { bool emissive = Materials[j].emissiveColor.R != 0 | Materials[j].emissiveColor.G != 0 | Materials[j].emissiveColor.B != 0; bool transparent; if (Materials[j].TextureFilename != null) { TextureManager.TextureWrapMode WrapX, WrapY; if (ForceTextureRepeatX) { WrapX = TextureManager.TextureWrapMode.Repeat; } else { WrapX = TextureManager.TextureWrapMode.ClampToEdge; } if (ForceTextureRepeatY) { WrapY = TextureManager.TextureWrapMode.Repeat; } else { WrapY = TextureManager.TextureWrapMode.ClampToEdge; } if (WrapX != TextureManager.TextureWrapMode.Repeat | WrapY != TextureManager.TextureWrapMode.Repeat) { for (int k = 0; k < nFaces; k++) { for (int h = 0; h < Faces[k].Length; h++) { if (Vertices[Faces[k][h]].TextureCoordinates.X < 0.0 | Vertices[Faces[k][h]].TextureCoordinates.X > 1.0) { WrapX = TextureManager.TextureWrapMode.Repeat; } if (Vertices[Faces[k][h]].TextureCoordinates.Y < 0.0 | Vertices[Faces[k][h]].TextureCoordinates.Y > 1.0) { WrapY = TextureManager.TextureWrapMode.Repeat; } } } } int tday = TextureManager.RegisterTexture(Materials[j].TextureFilename, new World.ColorRGB(0, 0, 0), 1, TextureManager.TextureLoadMode.Normal, WrapX, WrapY, LoadMode != ObjectManager.ObjectLoadMode.Normal, 0, 0, 0, 0); Object.Mesh.Materials[mm + j].DaytimeTextureIndex = tday; transparent = true; } else { Object.Mesh.Materials[mm + j].DaytimeTextureIndex = -1; transparent = false; } Object.Mesh.Materials[mm + j].Flags = (byte)((transparent ? World.MeshMaterial.TransparentColorMask : 0) | (emissive ? World.MeshMaterial.EmissiveColorMask : 0)); Object.Mesh.Materials[mm + j].Color = Materials[j].faceColor; Object.Mesh.Materials[mm + j].TransparentColor = new World.ColorRGB(0, 0, 0); Object.Mesh.Materials[mm + j].EmissiveColor = Materials[j].emissiveColor; Object.Mesh.Materials[mm + j].NighttimeTextureIndex = -1; Object.Mesh.Materials[mm + j].BlendMode = World.MeshMaterialBlendMode.Normal; Object.Mesh.Materials[mm + j].GlowAttenuationData = 0; } for (int j = 0; j < nFaces; j++) { Object.Mesh.Faces[mf + j].Material = (ushort)FaceMaterials[j]; Object.Mesh.Faces[mf + j].Vertices = new World.MeshFaceVertex[Faces[j].Length]; for (int k = 0; k < Faces[j].Length; k++) { Object.Mesh.Faces[mf + j].Vertices[mv + k] = new World.MeshFaceVertex(mv + Faces[j][k], FaceNormals[j][k]); } } for (int j = 0; j < Vertices.Length; j++) { Object.Mesh.Vertices[mv + j] = Vertices[j]; } break; } case "Header": break; default: // unknown Interface.AddMessage(Interface.MessageType.Warning, false, "Unsupported template " + f.Name + " encountered in x object file " + FileName); break; } } // return World.CreateNormals(ref Object.Mesh); return true; }
// apply mesh builder private static void ApplyMeshBuilder(ref ObjectManager.StaticObject Object, MeshBuilder Builder, ObjectManager.ObjectLoadMode LoadMode, bool ForceTextureRepeatX, bool ForceTextureRepeatY) { if (Builder.Faces.Length != 0) { int mf = Object.Mesh.Faces.Length; int mm = Object.Mesh.Materials.Length; int mv = Object.Mesh.Vertices.Length; Array.Resize<World.MeshFace>(ref Object.Mesh.Faces, mf + Builder.Faces.Length); Array.Resize<World.MeshMaterial>(ref Object.Mesh.Materials, mm + Builder.Materials.Length); Array.Resize<World.Vertex>(ref Object.Mesh.Vertices, mv + Builder.Vertices.Length); for (int i = 0; i < Builder.Vertices.Length; i++) { Object.Mesh.Vertices[mv + i] = Builder.Vertices[i]; } for (int i = 0; i < Builder.Faces.Length; i++) { Object.Mesh.Faces[mf + i] = Builder.Faces[i]; for (int j = 0; j < Object.Mesh.Faces[mf + i].Vertices.Length; j++) { Object.Mesh.Faces[mf + i].Vertices[j].Index += (ushort)mv; } Object.Mesh.Faces[mf + i].Material += (ushort)mm; } for (int i = 0; i < Builder.Materials.Length; i++) { Object.Mesh.Materials[mm + i].Flags = (byte)((Builder.Materials[i].EmissiveColorUsed ? World.MeshMaterial.EmissiveColorMask : 0) | (Builder.Materials[i].TransparentColorUsed ? World.MeshMaterial.TransparentColorMask : 0)); Object.Mesh.Materials[mm + i].Color = Builder.Materials[i].Color; Object.Mesh.Materials[mm + i].TransparentColor = Builder.Materials[i].TransparentColor; if (Builder.Materials[i].DaytimeTexture != null) { Textures.Texture tday; if (Builder.Materials[i].TransparentColorUsed) { Textures.RegisterTexture(Builder.Materials[i].DaytimeTexture, new OpenBveApi.Textures.TextureParameters(null, new Color24(Builder.Materials[i].TransparentColor.R, Builder.Materials[i].TransparentColor.G, Builder.Materials[i].TransparentColor.B)), out tday); } else { Textures.RegisterTexture(Builder.Materials[i].DaytimeTexture, out tday); } Object.Mesh.Materials[mm + i].DaytimeTexture = tday; } else { Object.Mesh.Materials[mm + i].DaytimeTexture = null; } Object.Mesh.Materials[mm + i].EmissiveColor = Builder.Materials[i].EmissiveColor; if (Builder.Materials[i].NighttimeTexture != null) { Textures.Texture tnight; if (Builder.Materials[i].TransparentColorUsed) { Textures.RegisterTexture(Builder.Materials[i].NighttimeTexture, new OpenBveApi.Textures.TextureParameters(null, new Color24(Builder.Materials[i].TransparentColor.R, Builder.Materials[i].TransparentColor.G, Builder.Materials[i].TransparentColor.B)), out tnight); } else { Textures.RegisterTexture(Builder.Materials[i].NighttimeTexture, out tnight); } Object.Mesh.Materials[mm + i].NighttimeTexture = tnight; } else { Object.Mesh.Materials[mm + i].NighttimeTexture = null; } Object.Mesh.Materials[mm + i].DaytimeNighttimeBlend = 0; Object.Mesh.Materials[mm + i].BlendMode = Builder.Materials[i].BlendMode; Object.Mesh.Materials[mm + i].GlowAttenuationData = Builder.Materials[i].GlowAttenuationData; } } }
// parse extensions config internal static void ParseExtensionsConfig(string TrainPath, System.Text.Encoding Encoding, ref UnifiedObject[] CarObjects, ref UnifiedObject[] BogieObjects, ref UnifiedObject[] CouplerObjects, TrainManager.Train Train, bool LoadObjects) { bool[] CarObjectsReversed = new bool[Train.Cars.Length]; bool[] BogieObjectsReversed = new bool[Train.Cars.Length * 2]; bool[] CarsDefined = new bool[Train.Cars.Length]; bool[] BogiesDefined = new bool[Train.Cars.Length * 2]; System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; string FileName = OpenBveApi.Path.CombineFile(TrainPath, "extensions.cfg"); if (System.IO.File.Exists(FileName)) { TextEncoding.Encoding newEncoding = TextEncoding.GetEncodingFromFile(FileName); if (newEncoding != TextEncoding.Encoding.Unknown) { switch (newEncoding) { case TextEncoding.Encoding.Utf7: Encoding = System.Text.Encoding.UTF7; break; case TextEncoding.Encoding.Utf8: Encoding = System.Text.Encoding.UTF8; break; case TextEncoding.Encoding.Utf16Le: Encoding = System.Text.Encoding.Unicode; break; case TextEncoding.Encoding.Utf16Be: Encoding = System.Text.Encoding.BigEndianUnicode; break; case TextEncoding.Encoding.Utf32Le: Encoding = System.Text.Encoding.UTF32; break; case TextEncoding.Encoding.Utf32Be: Encoding = System.Text.Encoding.GetEncoding(12001); break; case TextEncoding.Encoding.Shift_JIS: Encoding = System.Text.Encoding.GetEncoding(932); break; } } string[] Lines = System.IO.File.ReadAllLines(FileName, Encoding); for (int i = 0; i < Lines.Length; i++) { int j = Lines[i].IndexOf(';'); if (j >= 0) { Lines[i] = Lines[i].Substring(0, j).Trim(new char[] { }); } else { Lines[i] = Lines[i].Trim(new char[] { }); } } for (int i = 0; i < Lines.Length; i++) { if (Lines[i].Length != 0) { switch (Lines[i].ToLowerInvariant()) { case "[exterior]": // exterior i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); int n; if (int.TryParse(a, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { if (LoadObjects) { CarObjects[n] = ObjectManager.LoadObject(File, Encoding, false); } } else { Interface.AddMessage(MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } } else { Interface.AddMessage(MessageType.Error, false, "The car index " + a + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; break; default: if (Lines[i].StartsWith("[car", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = Lines[i].Substring(4, Lines[i].Length - 5); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length) { if (CarsDefined[n]) { Interface.AddMessage(MessageType.Error, false, "Car " + n.ToString(Culture) + " has already been declared at line " + (i + 1).ToString(Culture) + " in file " + FileName); } CarsDefined[n] = true; bool DefinedLength = false; bool DefinedAxles = false; i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "object": if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, "An empty car object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { if (LoadObjects) { CarObjects[n] = ObjectManager.LoadObject(File, Encoding, false); } } else { Interface.AddMessage(MessageType.Error, true, "The car object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "length": { double m; if (double.TryParse(b, System.Globalization.NumberStyles.Float, Culture, out m)) { if (m > 0.0) { Train.Cars[n].Length = m; Train.Cars[n].BeaconReceiverPosition = 0.5 * m; DefinedLength = true; } else { Interface.AddMessage(MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "Value is expected to be a positive floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "axles": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double rear, front; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out rear)) { Interface.AddMessage(MessageType.Error, false, "Rear is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out front)) { Interface.AddMessage(MessageType.Error, false, "Front is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (rear >= front) { Interface.AddMessage(MessageType.Error, false, "Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Cars[n].RearAxle.Position = rear; Train.Cars[n].FrontAxle.Position = front; DefinedAxles = true; } } else { Interface.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "reversed": CarObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; case "loadingsway": Train.Cars[n].EnableLoadingSway = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; default: Interface.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; if (DefinedLength & !DefinedAxles) { double AxleDistance = 0.4 * Train.Cars[n].Length; Train.Cars[n].RearAxle.Position = -AxleDistance; Train.Cars[n].FrontAxle.Position = AxleDistance; } } else { Interface.AddMessage(MessageType.Error, false, "The car index " + t + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else if (Lines[i].StartsWith("[coupler", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // coupler string t = Lines[i].Substring(8, Lines[i].Length - 9); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n >= 0 & n < Train.Cars.Length - 1) { i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "distances": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double min, max; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out min)) { Interface.AddMessage(MessageType.Error, false, "Minimum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out max)) { Interface.AddMessage(MessageType.Error, false, "Maximum is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (min > max) { Interface.AddMessage(MessageType.Error, false, "Minimum is expected to be less than Maximum in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { Train.Cars[n].Coupler.MinimumDistanceBetweenCars = min; Train.Cars[n].Coupler.MaximumDistanceBetweenCars = max; } } else { Interface.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "object": if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, "An empty coupler object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { if (LoadObjects) { CouplerObjects[n] = ObjectManager.LoadObject(File, Encoding, false); } } else { Interface.AddMessage(MessageType.Error, true, "The coupler object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; default: Interface.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; } else { Interface.AddMessage(MessageType.Error, false, "The coupler index " + t + " does not reference an existing coupler at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "The coupler index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else if (Lines[i].StartsWith("[bogie", StringComparison.OrdinalIgnoreCase) & Lines[i].EndsWith("]", StringComparison.Ordinal)) { // car string t = Lines[i].Substring(6, Lines[i].Length - 7); int n; if (int.TryParse(t, System.Globalization.NumberStyles.Integer, Culture, out n)) { if (n > BogiesDefined.Length - 1) { continue; } if (BogiesDefined[n]) { Interface.AddMessage(MessageType.Error, false, "Bogie " + n.ToString(Culture) + " has already been declared at line " + (i + 1).ToString(Culture) + " in file " + FileName); } BogiesDefined[n] = true; //Assuming that there are two bogies per car bool IsOdd = (n % 2 != 0); int CarIndex = n / 2; if (n >= 0 & n < Train.Cars.Length * 2) { bool DefinedAxles = false; i++; while (i < Lines.Length && !Lines[i].StartsWith("[", StringComparison.Ordinal) & !Lines[i].EndsWith("]", StringComparison.Ordinal)) { if (Lines[i].Length != 0) { int j = Lines[i].IndexOf("=", StringComparison.Ordinal); if (j >= 0) { string a = Lines[i].Substring(0, j).TrimEnd(new char[] { }); string b = Lines[i].Substring(j + 1).TrimStart(new char[] { }); switch (a.ToLowerInvariant()) { case "object": if (Path.ContainsInvalidChars(b)) { Interface.AddMessage(MessageType.Error, false, "File contains illegal characters at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (string.IsNullOrEmpty(b)) { Interface.AddMessage(MessageType.Error, true, "An empty bogie object was supplied at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } string File = OpenBveApi.Path.CombineFile(TrainPath, b); if (System.IO.File.Exists(File)) { if (LoadObjects) { BogieObjects[n] = ObjectManager.LoadObject(File, Encoding, false); } } else { Interface.AddMessage(MessageType.Error, true, "The bogie object " + File + " does not exist at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "length": { Interface.AddMessage(MessageType.Error, false, "A defined length is not supported for bogies at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; case "axles": { int k = b.IndexOf(','); if (k >= 0) { string c = b.Substring(0, k).TrimEnd(new char[] { }); string d = b.Substring(k + 1).TrimStart(new char[] { }); double rear, front; if (!double.TryParse(c, System.Globalization.NumberStyles.Float, Culture, out rear)) { Interface.AddMessage(MessageType.Error, false, "Rear is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (!double.TryParse(d, System.Globalization.NumberStyles.Float, Culture, out front)) { Interface.AddMessage(MessageType.Error, false, "Front is expected to be a floating-point number in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else if (rear >= front) { Interface.AddMessage(MessageType.Error, false, "Rear is expected to be less than Front in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { if (IsOdd) { Train.Cars[CarIndex].FrontBogie.RearAxle.Position = rear; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = front; } else { Train.Cars[CarIndex].RearBogie.RearAxle.Position = rear; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = front; } DefinedAxles = true; } } else { Interface.AddMessage(MessageType.Error, false, "An argument-separating comma is expected in " + a + " at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } break; case "reversed": BogieObjectsReversed[n] = b.Equals("true", StringComparison.OrdinalIgnoreCase); break; default: Interface.AddMessage(MessageType.Warning, false, "Unsupported key-value pair " + a + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); break; } } else { Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } i++; } i--; if (!DefinedAxles) { if (IsOdd) { double AxleDistance = 0.4 * Train.Cars[CarIndex].FrontBogie.Length; Train.Cars[CarIndex].FrontBogie.RearAxle.Position = -AxleDistance; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = AxleDistance; } else { double AxleDistance = 0.4 * Train.Cars[CarIndex].RearBogie.Length; Train.Cars[CarIndex].RearBogie.RearAxle.Position = -AxleDistance; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = AxleDistance; } } } else { Interface.AddMessage(MessageType.Error, false, "The car index " + t + " does not reference an existing car at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { Interface.AddMessage(MessageType.Error, false, "The car index is expected to be an integer at line " + (i + 1).ToString(Culture) + " in file " + FileName); } } else { // default if (Lines.Length == 1 && Encoding.Equals(Encoding.Unicode)) { /* * If only one line, there's a good possibility that our file is NOT Unicode at all * and that the misdetection has turned it into garbage * * Try again with ASCII instead */ ParseExtensionsConfig(TrainPath, Encoding.GetEncoding(1252), ref CarObjects, ref BogieObjects, ref CouplerObjects, Train, LoadObjects); return; } Interface.AddMessage(MessageType.Error, false, "Invalid statement " + Lines[i] + " encountered at line " + (i + 1).ToString(Culture) + " in file " + FileName); } break; } } } // check for car objects and reverse if necessary int carObjects = 0; for (int i = 0; i < Train.Cars.Length; i++) { if (CarObjects[i] != null) { carObjects++; if (CarObjectsReversed[i] && LoadObjects) { { // reverse axle positions double temp = Train.Cars[i].FrontAxle.Position; Train.Cars[i].FrontAxle.Position = -Train.Cars[i].RearAxle.Position; Train.Cars[i].RearAxle.Position = -temp; } if (CarObjects[i] is StaticObject) { StaticObject obj = (StaticObject)CarObjects[i]; obj.ApplyScale(-1.0, 1.0, -1.0); } else if (CarObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)CarObjects[i]; for (int j = 0; j < obj.Objects.Length; j++) { for (int h = 0; h < obj.Objects[j].States.Length; h++) { if (obj.Objects[j].States[h].Prototype == null) { continue; //object failed to load? } obj.Objects[j].States[h].Prototype.ApplyScale(-1.0, 1.0, -1.0); Matrix4D t = obj.Objects[j].States[h].Translation; t.Row3.X *= -1.0f; t.Row3.Z *= -1.0f; obj.Objects[j].States[h].Translation = t; } obj.Objects[j].TranslateXDirection.X *= -1.0; obj.Objects[j].TranslateXDirection.Z *= -1.0; obj.Objects[j].TranslateYDirection.X *= -1.0; obj.Objects[j].TranslateYDirection.Z *= -1.0; obj.Objects[j].TranslateZDirection.X *= -1.0; obj.Objects[j].TranslateZDirection.Z *= -1.0; } } else { throw new NotImplementedException(); } } } } //Check for bogie objects and reverse if necessary..... int bogieObjects = 0; for (int i = 0; i < Train.Cars.Length * 2; i++) { bool IsOdd = (i % 2 != 0); int CarIndex = i / 2; if (BogieObjects[i] != null) { bogieObjects++; if (BogieObjectsReversed[i] && LoadObjects) { { // reverse axle positions if (IsOdd) { double temp = Train.Cars[CarIndex].FrontBogie.FrontAxle.Position; Train.Cars[CarIndex].FrontBogie.FrontAxle.Position = -Train.Cars[CarIndex].FrontBogie.RearAxle.Position; Train.Cars[CarIndex].FrontBogie.RearAxle.Position = -temp; } else { double temp = Train.Cars[CarIndex].RearBogie.FrontAxle.Position; Train.Cars[CarIndex].RearBogie.FrontAxle.Position = -Train.Cars[CarIndex].RearBogie.RearAxle.Position; Train.Cars[CarIndex].RearBogie.RearAxle.Position = -temp; } } if (BogieObjects[i] is StaticObject) { StaticObject obj = (StaticObject)BogieObjects[i]; obj.ApplyScale(-1.0, 1.0, -1.0); } else if (BogieObjects[i] is AnimatedObjectCollection) { AnimatedObjectCollection obj = (AnimatedObjectCollection)BogieObjects[i]; for (int j = 0; j < obj.Objects.Length; j++) { for (int h = 0; h < obj.Objects[j].States.Length; h++) { if (obj.Objects[j].States[h].Prototype == null) { continue; //object failed to load? } obj.Objects[j].States[h].Prototype.ApplyScale(-1.0, 1.0, -1.0); Matrix4D t = obj.Objects[j].States[h].Translation; t.Row3.X *= -1.0f; t.Row3.Z *= -1.0f; obj.Objects[j].States[h].Translation = t; } obj.Objects[j].TranslateXDirection.X *= -1.0; obj.Objects[j].TranslateXDirection.Z *= -1.0; obj.Objects[j].TranslateYDirection.X *= -1.0; obj.Objects[j].TranslateYDirection.Z *= -1.0; obj.Objects[j].TranslateZDirection.X *= -1.0; obj.Objects[j].TranslateZDirection.Z *= -1.0; } } else { throw new NotImplementedException(); } } } } if (carObjects > 0 & carObjects < Train.Cars.Length) { Interface.AddMessage(MessageType.Warning, false, "An incomplete set of exterior objects was provided in file " + FileName); } if (bogieObjects > 0 & bogieObjects < Train.Cars.Length * 2) { Interface.AddMessage(MessageType.Warning, false, "An incomplete set of bogie objects was provided in file " + FileName); } } }