/// <summary> /// Open the specified WFile and load all the scenery objects into the viewer. /// If the file doesn't exist, then return an empty WorldFile object. /// </summary> /// <param name="visible">Tiles adjacent to the current visible tile may not be modelled. /// This flag decides whether a missing file leads to a warning message.</param> public WorldFile(Viewer viewer, int tileX, int tileZ, bool visible) { Viewer = viewer; TileX = tileX; TileZ = tileZ; var cancellation = Viewer.LoaderProcess.CancellationToken; // determine file path to the WFile at the specified tile coordinates var WFileName = WorldFileNameFromTileCoordinates(tileX, tileZ); var WFilePath = viewer.Simulator.RoutePath + @"\World\" + WFileName; // if there isn't a file, then return with an empty WorldFile object if (!File.Exists(WFilePath)) { if (visible) { Trace.TraceWarning("World file missing - {0}", WFilePath); } return; } // read the world file var WFile = new Orts.Formats.Msts.WorldFile(WFilePath); // check for existence of world file in OpenRails subfolder WFilePath = viewer.Simulator.RoutePath + @"\World\Openrails\" + WFileName; if (File.Exists(WFilePath)) { // We have an OR-specific addition to world file WFile.InsertORSpecificData(WFilePath); } // to avoid loop checking for every object this pre-check is performed bool containsMovingTable = false; if (Program.Simulator.MovingTables != null) { foreach (var movingTable in Program.Simulator.MovingTables) { if (movingTable.WFile == WFileName) { containsMovingTable = true; break; } } } // create all the individual scenery objects specified in the WFile foreach (var worldObject in WFile.Tr_Worldfile) { if (worldObject.StaticDetailLevel > viewer.Settings.WorldObjectDensity) { continue; } // If the loader has been asked to temrinate, bail out early. if (cancellation.IsCancellationRequested) { break; } // Get the position of the scenery object into ORTS coordinate space. WorldPosition worldMatrix; if (worldObject.Matrix3x3 != null && worldObject.Position != null) { worldMatrix = WorldPositionFromMSTSLocation(WFile.TileX, WFile.TileZ, worldObject.Position, worldObject.Matrix3x3); } else if (worldObject.QDirection != null && worldObject.Position != null) { worldMatrix = WorldPositionFromMSTSLocation(WFile.TileX, WFile.TileZ, worldObject.Position, worldObject.QDirection); } else { Trace.TraceWarning("{0} scenery object {1} is missing Matrix3x3 and QDirection", WFileName, worldObject.UID); continue; } var shadowCaster = (worldObject.StaticFlags & (uint)StaticFlag.AnyShadow) != 0 || viewer.Settings.ShadowAllShapes; var animated = (worldObject.StaticFlags & (uint)StaticFlag.Animate) != 0; var isAnalogClock = GetClockType(worldObject.FileName) == ClockType.Analog; var global = (worldObject is TrackObj) || (worldObject is HazardObj) || (worldObject.StaticFlags & (uint)StaticFlag.Global) != 0; // TransferObj have a FileName but it is not a shape, so we need to avoid sanity-checking it as if it was. var fileNameIsNotShape = (worldObject is TransferObj || worldObject is HazardObj); // Determine the file path to the shape file for this scenery object and check it exists as expected. var shapeFilePath = fileNameIsNotShape || String.IsNullOrEmpty(worldObject.FileName) ? null : global ? viewer.Simulator.BasePath + @"\Global\Shapes\" + worldObject.FileName : viewer.Simulator.RoutePath + @"\Shapes\" + worldObject.FileName; if (shapeFilePath != null) { shapeFilePath = Path.GetFullPath(shapeFilePath); if (!File.Exists(shapeFilePath)) { Trace.TraceWarning("{0} scenery object {1} with StaticFlags {3:X8} references non-existent {2}", WFileName, worldObject.UID, shapeFilePath, worldObject.StaticFlags); shapeFilePath = null; } } if (shapeFilePath != null && File.Exists(shapeFilePath + "d")) { var shape = new ShapeDescriptorFile(shapeFilePath + "d"); if (shape.shape.ESD_Bounding_Box != null) { var min = shape.shape.ESD_Bounding_Box.Min; var max = shape.shape.ESD_Bounding_Box.Max; var transform = Matrix.Invert(worldMatrix.XNAMatrix); // Not sure if this is needed, but it is to correct for center-of-gravity being not the center of the box. //transform.M41 += (max.X + min.X) / 2; //transform.M42 += (max.Y + min.Y) / 2; //transform.M43 += (max.Z + min.Z) / 2; BoundingBoxes.Add(new BoundingBox(transform, new Vector3((max.X - min.X) / 2, (max.Y - min.Y) / 2, (max.Z - min.Z) / 2), worldMatrix.XNAMatrix.Translation.Y)); } } try { if (worldObject.GetType() == typeof(TrackObj)) { var trackObj = (TrackObj)worldObject; // Switch tracks need a link to the simulator engine so they can animate the points. var trJunctionNode = trackObj.JNodePosn != null?viewer.Simulator.TDB.GetTrJunctionNode(TileX, TileZ, (int)trackObj.UID) : null; // We might not have found the junction node; if so, fall back to the static track shape. if (trJunctionNode != null) { if (viewer.Simulator.UseSuperElevation > 0) { SuperElevationManager.DecomposeStaticSuperElevation(viewer, dTrackList, trackObj, worldMatrix, TileX, TileZ, shapeFilePath); } sceneryObjects.Add(new SwitchTrackShape(viewer, shapeFilePath, worldMatrix, trJunctionNode)); } else { //if want to use super elevation, we will generate tracks using dynamic tracks if (viewer.Simulator.UseSuperElevation > 0 && SuperElevationManager.DecomposeStaticSuperElevation(viewer, dTrackList, trackObj, worldMatrix, TileX, TileZ, shapeFilePath)) { //var success = SuperElevation.DecomposeStaticSuperElevation(viewer, dTrackList, trackObj, worldMatrix, TileX, TileZ, shapeFilePath); //if (success == 0) sceneryObjects.Add(new StaticTrackShape(viewer, shapeFilePath, worldMatrix)); } //otherwise, use shapes else if (!containsMovingTable) { sceneryObjects.Add(new StaticTrackShape(viewer, shapeFilePath, worldMatrix)); } else { var found = false; foreach (var movingTable in Program.Simulator.MovingTables) { if (worldObject.UID == movingTable.UID && WFileName == movingTable.WFile) { found = true; if (movingTable is Simulation.Turntable) { var turntable = movingTable as Simulation.Turntable; turntable.ComputeCenter(worldMatrix); var startingY = Math.Asin(-2 * (worldObject.QDirection.A * worldObject.QDirection.C - worldObject.QDirection.B * worldObject.QDirection.D)); sceneryObjects.Add(new TurntableShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None, turntable, startingY)); } else { var transfertable = movingTable as Simulation.Transfertable; transfertable.ComputeCenter(worldMatrix); sceneryObjects.Add(new TransfertableShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None, transfertable)); } break; } } if (!found) { sceneryObjects.Add(new StaticTrackShape(viewer, shapeFilePath, worldMatrix)); } } } if (viewer.Simulator.Settings.Wire == true && viewer.Simulator.TRK.Tr_RouteFile.Electrified == true && worldObject.StaticDetailLevel != 2 && // Make it compatible with routes that use 'HideWire', a workaround for MSTS that worldObject.StaticDetailLevel != 3 // allowed a mix of electrified and non electrified track see http://msts.steam4me.net/tutorials/hidewire.html ) { int success = Wire.DecomposeStaticWire(viewer, dTrackList, trackObj, worldMatrix); //if cannot draw wire, try to see if it is converted. modified for DynaTrax if (success == 0 && trackObj.FileName.Contains("Dyna")) { Wire.DecomposeConvertedDynamicWire(viewer, dTrackList, trackObj, worldMatrix); } } } else if (worldObject.GetType() == typeof(DyntrackObj)) { if (viewer.Simulator.Settings.Wire == true && viewer.Simulator.TRK.Tr_RouteFile.Electrified == true) { Wire.DecomposeDynamicWire(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix); } // Add DyntrackDrawers for individual subsections if (viewer.Simulator.UseSuperElevation > 0 && SuperElevationManager.UseSuperElevationDyn(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix)) { SuperElevationManager.DecomposeDynamicSuperElevation(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix); } else { DynamicTrack.Decompose(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix); } } // end else if DyntrackObj else if (worldObject.GetType() == typeof(ForestObj)) { if (!(worldObject as ForestObj).IsYard) { forestList.Add(new ForestViewer(viewer, (ForestObj)worldObject, worldMatrix)); } } else if (worldObject.GetType() == typeof(SignalObj)) { sceneryObjects.Add(new SignalShape(viewer, (SignalObj)worldObject, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None)); } else if (worldObject.GetType() == typeof(TransferObj)) { sceneryObjects.Add(new TransferShape(viewer, (TransferObj)worldObject, worldMatrix)); } else if (worldObject.GetType() == typeof(LevelCrossingObj)) { sceneryObjects.Add(new LevelCrossingShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None, (LevelCrossingObj)worldObject)); } else if (worldObject.GetType() == typeof(HazardObj)) { var h = HazzardShape.CreateHazzard(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None, (HazardObj)worldObject); if (h != null) { sceneryObjects.Add(h); } } else if (worldObject.GetType() == typeof(SpeedPostObj)) { sceneryObjects.Add(new SpeedPostShape(viewer, shapeFilePath, worldMatrix, (SpeedPostObj)worldObject)); } else if (worldObject.GetType() == typeof(CarSpawnerObj)) { if (Program.Simulator.CarSpawnerLists != null && ((CarSpawnerObj)worldObject).ListName != null) { ((CarSpawnerObj)worldObject).CarSpawnerListIdx = Program.Simulator.CarSpawnerLists.FindIndex(x => x.ListName == ((CarSpawnerObj)worldObject).ListName); if (((CarSpawnerObj)worldObject).CarSpawnerListIdx < 0 || ((CarSpawnerObj)worldObject).CarSpawnerListIdx > Program.Simulator.CarSpawnerLists.Count - 1) { ((CarSpawnerObj)worldObject).CarSpawnerListIdx = 0; } } else { ((CarSpawnerObj)worldObject).CarSpawnerListIdx = 0; } carSpawners.Add(new RoadCarSpawner(viewer, worldMatrix, (CarSpawnerObj)worldObject)); } else if (worldObject.GetType() == typeof(SidingObj)) { sidings.Add(new TrItemLabel(viewer, worldMatrix, (SidingObj)worldObject)); } else if (worldObject.GetType() == typeof(PlatformObj)) { platforms.Add(new TrItemLabel(viewer, worldMatrix, (PlatformObj)worldObject)); } else if (worldObject.GetType() == typeof(StaticObj)) { if (isAnalogClock) { sceneryObjects.Add(new AnalogClockShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None)); } else if (animated) { sceneryObjects.Add(new AnimatedShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None)); } else { sceneryObjects.Add(new StaticShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None)); } } else if (worldObject.GetType() == typeof(PickupObj)) { sceneryObjects.Add(new FuelPickupItemShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None, (PickupObj)worldObject)); PickupList.Add((PickupObj)worldObject); } else // It's some other type of object - not one of the above. { sceneryObjects.Add(new StaticShape(viewer, shapeFilePath, worldMatrix, shadowCaster ? ShapeFlags.ShadowCaster : ShapeFlags.None)); } } catch (Exception error) { Trace.WriteLine(new FileLoadException(String.Format("{0} scenery object {1} failed to load", worldMatrix, worldObject.UID), error)); } } // Check if there are activity restricted speedposts to be loaded if (Viewer.Simulator.ActivityRun != null && Viewer.Simulator.Activity.Tr_Activity.Tr_Activity_File.ActivityRestrictedSpeedZones != null) { foreach (TempSpeedPostItem tempSpeedItem in Viewer.Simulator.ActivityRun.TempSpeedPostItems) { if (tempSpeedItem.WorldPosition.TileX == TileX && tempSpeedItem.WorldPosition.TileZ == TileZ) { if (Viewer.SpeedpostDatFile == null) { Trace.TraceWarning(String.Format("{0} missing; speed posts for temporary speed restrictions in tile {1} {2} will not be visible.", Viewer.Simulator.RoutePath + @"\speedpost.dat", TileX, TileZ)); break; } else { sceneryObjects.Add(new StaticShape(viewer, tempSpeedItem.IsWarning ? Viewer.SpeedpostDatFile.TempSpeedShapeNames[0] : (tempSpeedItem.IsResume ? Viewer.SpeedpostDatFile.TempSpeedShapeNames[2] : Viewer.SpeedpostDatFile.TempSpeedShapeNames[1]), tempSpeedItem.WorldPosition, ShapeFlags.None)); } } } } // Model instancing requires feature level 9_3 or higher. if (Viewer.Settings.ModelInstancing && Viewer.Settings.IsDirectXFeatureLevelIncluded(ORTS.Settings.UserSettings.DirectXFeature.Level9_3)) { // Instancing collapsed multiple copies of the same model in to a single set of data (the normal model // data, plus a list of position information for each copy) and then draws them in a single batch. var instances = new Dictionary <string, List <StaticShape> >(); foreach (var shape in sceneryObjects) { // Only allow StaticShape and StaticTrackShape instances for now. if (shape.GetType() != typeof(StaticShape) && shape.GetType() != typeof(StaticTrackShape)) { continue; } // Must have a file path so we can collapse instances on something. var path = shape.SharedShape.FilePath; if (path == null) { continue; } if (path != null && !instances.ContainsKey(path)) { instances.Add(path, new List <StaticShape>()); } if (path != null) { instances[path].Add(shape); } } foreach (var path in instances.Keys) { if (instances[path].Count >= MinimumInstanceCount) { var sharedInstance = new SharedStaticShapeInstance(Viewer, path, instances[path]); foreach (var model in instances[path]) { sceneryObjects.Remove(model); } sceneryObjects.Add(sharedInstance); } } } if (viewer.Simulator.UseSuperElevation > 0) { SuperElevationManager.DecomposeStaticSuperElevation(Viewer, dTrackList, TileX, TileZ); } if (Viewer.World.Sounds != null) { Viewer.World.Sounds.AddByTile(TileX, TileZ); } }
/// <summary> /// Only one copy of the model is loaded regardless of how many copies are placed in the scene. /// </summary> void LoadContent() { Trace.Write("S"); var filePath = FilePath; // commented lines allow reading the animation block from an additional file in an Openrails subfolder // string dir = Path.GetDirectoryName(filePath); // string file = Path.GetFileName(filePath); // string orFilePath = dir + @"\openrails\" + file; var sFile = new ShapeFile(filePath, viewer.Settings.SuppressShapeWarnings); // if (file.ToLower().Contains("turntable") && File.Exists(orFilePath)) // { // sFile.ReadAnimationBlock(orFilePath); // } var textureFlags = Helpers.TextureFlags.None; if (File.Exists(FilePath + "d")) { var sdFile = new ShapeDescriptorFile(FilePath + "d"); textureFlags = (Helpers.TextureFlags)sdFile.Shape.EsdAlternativeTexture; if (FilePath != null && FilePath.Contains("\\global\\")) { textureFlags |= Helpers.TextureFlags.SnowTrack; //roads and tracks are in global, as MSTS will always use snow texture in snow weather } HasNightSubObj = sdFile.Shape.EsdSubObject; if ((textureFlags & Helpers.TextureFlags.Night) != 0 && FilePath.Contains("\\trainset\\")) { textureFlags |= Helpers.TextureFlags.Underground; } SoundFileName = sdFile.Shape.EsdSoundFileName; BellAnimationFPS = sdFile.Shape.EsdBellAnimationFps; } Matrices = sFile.Shape.Matrices.ToArray(); MatrixNames = sFile.Shape.Matrices.MatrixNames; //var matrixCount = sFile.shape.matrices.Count; //MatrixNames.Capacity = matrixCount; //Matrices = new Matrix[matrixCount]; //for (var i = 0; i < matrixCount; ++i) //{ // MatrixNames.Add(sFile.shape.matrices[i].Name.ToUpper()); // Matrices[i] = XNAMatrixFromMSTS(sFile.shape.matrices[i]); //} Animations = sFile.Shape.Animations; #if DEBUG_SHAPE_HIERARCHY var debugShapeHierarchy = new StringBuilder(); debugShapeHierarchy.AppendFormat("Shape {0}:\n", Path.GetFileNameWithoutExtension(FilePath).ToUpper()); for (var i = 0; i < MatrixNames.Count; ++i) { debugShapeHierarchy.AppendFormat(" Matrix {0,-2}: {1}\n", i, MatrixNames[i]); } for (var i = 0; i < sFile.shape.prim_states.Count; ++i) { debugShapeHierarchy.AppendFormat(" PState {0,-2}: flags={1,-8:X8} shader={2,-15} alpha={3,-2} vstate={4,-2} lstate={5,-2} zbias={6,-5:F3} zbuffer={7,-2} name={8}\n", i, sFile.shape.prim_states[i].flags, sFile.shape.shader_names[sFile.shape.prim_states[i].ishader], sFile.shape.prim_states[i].alphatestmode, sFile.shape.prim_states[i].ivtx_state, sFile.shape.prim_states[i].LightCfgIdx, sFile.shape.prim_states[i].ZBias, sFile.shape.prim_states[i].ZBufMode, sFile.shape.prim_states[i].Name); } for (var i = 0; i < sFile.shape.vtx_states.Count; ++i) { debugShapeHierarchy.AppendFormat(" VState {0,-2}: flags={1,-8:X8} lflags={2,-8:X8} lstate={3,-2} material={4,-3} matrix2={5,-2}\n", i, sFile.shape.vtx_states[i].flags, sFile.shape.vtx_states[i].LightFlags, sFile.shape.vtx_states[i].LightCfgIdx, sFile.shape.vtx_states[i].LightMatIdx, sFile.shape.vtx_states[i].Matrix2); } for (var i = 0; i < sFile.shape.light_model_cfgs.Count; ++i) { debugShapeHierarchy.AppendFormat(" LState {0,-2}: flags={1,-8:X8} uv_ops={2,-2}\n", i, sFile.shape.light_model_cfgs[i].flags, sFile.shape.light_model_cfgs[i].uv_ops.Count); for (var j = 0; j < sFile.shape.light_model_cfgs[i].uv_ops.Count; ++j) { debugShapeHierarchy.AppendFormat(" UV OP {0,-2}: texture_address_mode={1,-2}\n", j, sFile.shape.light_model_cfgs[i].uv_ops[j].TexAddrMode); } } Console.Write(debugShapeHierarchy.ToString()); #endif LodControls = (from Formats.Msts.Models.LodControl lod in sFile.Shape.LodControls select new LodControl(lod, textureFlags, sFile, this)).ToArray(); if (LodControls.Length == 0) { throw new InvalidDataException("Shape file missing lod_control section"); } else if (LodControls[0].DistanceLevels.Length > 0 && LodControls[0].DistanceLevels[0].SubObjects.Length > 0) { // Zero the position offset of the root matrix for compatibility with MSTS if (LodControls[0].DistanceLevels[0].SubObjects[0].ShapePrimitives.Length > 0 && LodControls[0].DistanceLevels[0].SubObjects[0].ShapePrimitives[0].Hierarchy[0] == -1) { Matrices[0].M41 = 0; Matrices[0].M42 = 0; Matrices[0].M43 = 0; } // Look for root subobject, it is not necessarily the first (see ProTrain signal) for (int soIndex = 0; soIndex <= LodControls[0].DistanceLevels[0].SubObjects.Length - 1; soIndex++) { Formats.Msts.Models.SubObject subObject = sFile.Shape.LodControls[0].DistanceLevels[0].SubObjects[soIndex]; if (subObject.SubObjectHeader.GeometryInfo.GeometryNodeMap[0] == 0) { RootSubObjectIndex = soIndex; break; } } } }