Skip to content

Commit 635a3c9

Browse files
committed
Add new shape descriptor controls, refactor various systems to handle the new possibilities
1 parent 073cb17 commit 635a3c9

File tree

23 files changed

+904
-276
lines changed

23 files changed

+904
-276
lines changed

Source/Documentation/Manual/features-rollingstock.rst

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,121 @@ some steps of the content creation process or allow more control over content th
411411
possible. The goal of these features is to save content creators' time, give additional power to
412412
creators, and to simplify the installation process for end users.
413413

414+
Runtime shape manipulation
415+
--------------------------
416+
417+
.. _features-shape-manipulation:
418+
419+
MSTS shape files can be clunky to work with due to the proprietary nature of the file format
420+
and the limitations of the tools available to manipulate them. Even when a shape has been
421+
manipulated, it's often not appropriate to redistribute those changes (unless the end user
422+
is to replicate the manipulation themselves) to avoid plagarism. To improve this, OR now
423+
supports additional ways to manipulate shape file data in memory without editing the original
424+
shape.
425+
426+
These features use the shape descriptor (.sd) file to provide the information needed to change
427+
the shape file data at runtime. The .sd file can be edited in plain text in standard text editors,
428+
and there is no risk of plagarism when including .sd files in a download, so changes can be
429+
made and distributed easily. However, it may be desired to use a different .sd file than the
430+
default one (the .sd default file has the same name as the .s file) out of a desire to not overwrite
431+
the original, or to re-use the same shape file repeatedly with different data, saving file space.
432+
To facilitate this, most places in wagons and engines where a .s file can be specified now accept
433+
an additional input to *specify a different .sd file than the default*.
434+
435+
For example, ``WagonShape ( "dash9.s" "dash9_reskin.sd" )`` would load the dash9 shape, but
436+
instead of loading dash9.sd, would load dash9_reskin.sd and apply any settings in that alternate .sd file.
437+
Similar can be applied to ORTS freight animations (not MSTS freight animations), passenger views,
438+
3D cabs, and animated couplers. Note that both the .s and .sd file specified can be
439+
in a different folder from the .eng or .wag by using relative file paths. If only the .s file is
440+
given, OR will assume the .sd file has the same name as the given .s file, just like MSTS. OR does
441+
not require .sd files to function, so if the .sd file is missing OR will continue with default settings.
442+
443+
While .sd file replacement is currently unique to engines and wagons, the new features inside .sd files
444+
can be applied to any .sd file, including scenery.
445+
446+
.. index::
447+
single: ESD_ORTSTextureReplacement
448+
449+
Texture Replacement
450+
^^^^^^^^^^^^^^^^^^^
451+
452+
Perhaps one of the most useful additional shape descriptor features is the ability to replace the
453+
textures used by a shape without editing, copying, or even moving the shape file. To achieve this,
454+
add the ``ESD_ORTSTextureReplacement`` parameter to the .sd file, and enter texture names in the
455+
format ``ESD_ORTSTextureReplacement ( OriginalTexture.ace ReplacementTexture.ace )``. Any
456+
OriginalTexture not specified won't be changed, and if the shape doesn't have a texture specified,
457+
then a warning message is produced. This works with both .ace and .dds textures. Multiple
458+
textures can be replaced in one go by adding additional pairs of textures::
459+
460+
SIMISA@@@@@@@@@@JINX0t1t______
461+
462+
shape ( BNSF_C44_9W_4614.s
463+
ESD_Detail_Level ( 0 )
464+
ESD_Alternative_Texture ( 0 )
465+
ESD_Bounding_Box ( -1.632 -0.095 -11.05 1.684 4.628 11.05 )
466+
ESD_ORTSTextureReplacement (
467+
"BNSF_C449W_4410a.dds" "BNSF_C449W_4614a.dds"
468+
"BNSF_C449W_4410b.dds" "BNSF_C449W_4614b.dds"
469+
)
470+
)
471+
472+
This would reskin BNSF 4410 into BNSF 4614 without any need to edit the original shape. Remember
473+
to reference the original .s file, but with a custom .sd file ``WagonShape ( "..\\BNSF_MULLAN_GE_ENGINES\\BNSF_C44_9W_4410.s"
474+
"BNSF_C44_9W_4614.sd" )`` so that the original shape file doesn't need to be copied. It is recommended
475+
that content creators making multiple skins of a new model, or reskinning an existing model, use this
476+
method to provide different textures for different engines/wagons as only one copy of the shape file is
477+
needed, reducing install size and simplifying installation as users don't need to move/copy/edit any
478+
shape files.
479+
480+
Translation Matrix Modification
481+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
482+
483+
All shape files are organized into one or more *sub objects*, where each sub object is positioned/scaled/rotated
484+
relative to the other sub objects using a *transformation matrix*, which are the named parts of the
485+
shape that can be seen in shape viewing utilities. A consequence of this is that the
486+
position/scale/rotation of a sub object can be changed without changing any of the 3D data used
487+
to draw the sub object. This can be useful to correct errors in the position/scale/rotation
488+
of the whole, or part of, a model without changing a tremendous amount of data.
489+
490+
.. index::
491+
single: ESD_ORTSMatrixTranslation
492+
single: ESD_ORTSMatrixScale
493+
single: ESD_ORTSMatrixRotation
494+
495+
- To change the position of a sub object, use ``ESD_ORTSMatrixTranslation ( MATRIX x y z )`` where
496+
``MATRIX`` is the name of the matrix and ``x y z`` are respectively the +right/-left, +up/-down,
497+
and +front/-back offsets from the original position, measured in units of distance (meters by default).
498+
- To change the scale (size) of a sub object, use ``ESD_ORTSMatrixScale ( MATRIX x y z )`` where
499+
``MATRIX`` is the name of the matrix and ``x y z`` are the horizontal, vertical, and lengthwise
500+
scale factors from the original scale. Scale factors larger than 1 increase size in that dimension,
501+
between 0 and 1 reduce size, and negative scale factor mirrors the object in that dimension.
502+
- To change the rotation of a sub object, use ``ESD_ORTSMatrixRotation ( MATRIX y p r )`` where
503+
``MATRIX`` is the name of the matrix and ``y p r`` are the yaw (+left/-right), pitch (+up/-down)
504+
and roll (+ccw/-cw) angle change from the original rotation, measured in radians by default. The ``deg``
505+
unit suffix can be used to give angles in degrees.
506+
507+
Only one of each type of parameter can be provided for each matrix. If all 3 transformations are
508+
applied to a matrix, they will be applied in the order of *translation, scale, then rotation*.
509+
An important consideration when manipulating these properties of transformation matrices is that
510+
the transformation will also be applied to any sub objects *below* the current matrix in the hierarchy.
511+
The transformation also affects 3D interiors, particle emitters, lights, and sounds attached to any
512+
affected sub objects.
513+
Should the transformation be desired only for something higher in the hierarchy, an equal but opposite
514+
transformation would be required on the lower-level sub object. It should also be considered that
515+
changes made to matrices are not purely graphical and could have consequences with some simulation
516+
systems. Extreme settings may break simulation behavior.
517+
518+
.. index::
519+
single: ESD_ORTSMatrixRename
520+
521+
The name of the transformation matrix is itself important for simulator behaviors, particularly
522+
animations and determining the structure of rolling stock. Should a matrix be named incorrectly,
523+
or a change in behavior be desired, a matrix can be renamed using ``ESD_ORTSMatrixReanme ( OLDNAME NEWNAME )``
524+
in the .sd file. OR will scan for any matrix "OLDNAME" and change the name to "NEWNAME". If no
525+
matrix with the old name can be found, a warning will be produced and nothing will change. Note
526+
that the matrix rename step occurs *after* the previously described matrix modifications, so
527+
the old matrix name must be used by any other .sd parameters that require a matrix name.
528+
414529
Automatic wagon size calculation
415530
--------------------------------
416531

@@ -427,7 +542,9 @@ To simplify this process, and produce more reasonable dimensions for rolling sto
427542
automatically calculate the dimensions of rolling stock based on the shape file used. Enter
428543
``ORTSAutoSize`` in the Wagon section of an engine or wagon to allow OR to determine
429544
the width, height, and length of the rolling stock based on the dimensions of the main shape file,
430-
ignoring any values entered manually in the MSTS Size parameter.
545+
ignoring any values entered manually in the MSTS Size parameter. Note that this process is
546+
not aware of any :ref:`shape descriptor overrides <features-shape-manipulation>` so any changes made
547+
to the shape there will not be reflected in the automatically calculated size.
431548

432549
``ORTSAutoSize`` accepts 3 (optional) arguments, default units in meters, corresponding to offsets from the
433550
shape's width, height, and length respectively. For example, ``ORTSAutoSize ( 0.1m, -0.2m, -0.18m )``
@@ -1157,6 +1274,7 @@ Here below a sample of a ``.load-or`` file::
11571274
{
11581275
"Name" : "triton",
11591276
"Shape" : "COMMON_Container_3d\\Cont_40ftHC\\container-40ftHC_Triton.s",
1277+
"ShapeDescriptor" : "COMMON_Container_3d\\Cont_40ftHC\\container-40ftHC_Triton.sd",
11601278
"ContainerType" : "C40ftHC",
11611279
"IntrinsicShapeOffset": [0,1.175,0],
11621280
"EmptyMassKG": 2100.,
@@ -1167,7 +1285,10 @@ Here below a sample of a ``.load-or`` file::
11671285
- "Container" is a fixed keyword.
11681286
- "Name" has as value a string used by Open Rails when the container must be indentified in a message
11691287
to the player.
1170-
- "Shape" has as value the path of the container shape, having ``Trainset`` as base.
1288+
- "Shape" has as value the path of the container shape (.s) file, having ``Trainset`` as base.
1289+
- "ShapeDescriptor" has the path of the container shape descriptor (.sd) file,
1290+
having ``Trainset`` as base. This is optional; if missing OR assumes the shape
1291+
descriptor is in the same location with the same name as the shape file.
11711292
- "ContainerType" identifies the container type, which may be one of the following ones::
11721293

11731294
* C20ft

Source/Orts.Formats.Msts/ShapeDescriptorFile.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
using System;
1919
using System.Collections.Generic;
20+
using Microsoft.Xna.Framework;
2021
using Orts.Parsers.Msts;
2122

2223
namespace Orts.Formats.Msts
@@ -69,7 +70,7 @@ public SDShape()
6970

7071
public SDShape(STFReader stf)
7172
{
72-
stf.ReadString(); // Ignore the filename string. TODO: Check if it agrees with the SD file name? Is this important?
73+
stf.ReadString();
7374
stf.ParseBlock(new STFReader.TokenProcessor[] {
7475
new STFReader.TokenProcessor("esd_detail_level", ()=>{ ESD_Detail_Level = stf.ReadIntBlock(null); }),
7576
new STFReader.TokenProcessor("esd_alternative_texture", ()=>{ ESD_Alternative_Texture = stf.ReadIntBlock(null); }),
@@ -84,9 +85,22 @@ public SDShape(STFReader stf)
8485
new STFReader.TokenProcessor("esd_ortssoundfilename", ()=>{ ESD_SoundFileName = stf.ReadStringBlock(null); }),
8586
new STFReader.TokenProcessor("esd_ortsbellanimationfps", ()=>{ ESD_CustomAnimationFPS = stf.ReadFloatBlock(STFReader.UNITS.Frequency, null); }),
8687
new STFReader.TokenProcessor("esd_ortscustomanimationfps", ()=>{ ESD_CustomAnimationFPS = stf.ReadFloatBlock(STFReader.UNITS.Frequency, null); }),
88+
new STFReader.TokenProcessor("esd_ortstexturereplacement", ()=>{ ParseReplacementStrings(stf, ref ESD_TextureReplacement); }),
89+
new STFReader.TokenProcessor("esd_ortsmatrixrename", ()=>{ ParseReplacementStrings(stf, ref ESD_MatrixRename); }),
90+
new STFReader.TokenProcessor("esd_ortsmatrixtranslation", ()=>{ ParseMatrixOverride(STFReader.UNITS.Distance, stf, ref ESD_MatrixTranslation); }),
91+
new STFReader.TokenProcessor("esd_ortsmatrixscale", ()=>{ ParseMatrixOverride(STFReader.UNITS.None, stf, ref ESD_MatrixScale); }),
92+
new STFReader.TokenProcessor("esd_ortsmatrixrotation", ()=>{ ParseMatrixOverride(STFReader.UNITS.Angle, stf, ref ESD_MatrixRotation); }),
8793
});
88-
// TODO - some objects have no bounding box - ie JP2BillboardTree1.sd
89-
//if (ESD_Bounding_Box == null) throw new STFException(stf, "Missing ESD_Bound_Box statement");
94+
95+
// Store set of all matrices that got modified
96+
foreach (string mat in ESD_MatrixRename.Keys)
97+
ESD_ModifiedMatrices.Add(mat);
98+
foreach (string mat in ESD_MatrixTranslation.Keys)
99+
ESD_ModifiedMatrices.Add(mat);
100+
foreach (string mat in ESD_MatrixScale.Keys)
101+
ESD_ModifiedMatrices.Add(mat);
102+
foreach (string mat in ESD_MatrixRotation.Keys)
103+
ESD_ModifiedMatrices.Add(mat);
90104
}
91105
public int ESD_Detail_Level;
92106
public int ESD_Alternative_Texture;
@@ -96,6 +110,48 @@ public SDShape(STFReader stf)
96110
public bool ESD_SubObj;
97111
public string ESD_SoundFileName = "";
98112
public float ESD_CustomAnimationFPS = 8;
113+
// Dictionary of <original texture name, replacement texture name>
114+
public Dictionary<string, string> ESD_TextureReplacement = new Dictionary<string, string>();
115+
// Dictionary of <original matrix name, replacement matrix name>
116+
public Dictionary<string, string> ESD_MatrixRename = new Dictionary<string, string>();
117+
// Set of matrix names that are modified in some way or another
118+
public HashSet<string> ESD_ModifiedMatrices = new HashSet<string>();
119+
// Dictionary of <matrix name, matrix translation x/y/z vector>
120+
public Dictionary<string, Vector3> ESD_MatrixTranslation = new Dictionary<string, Vector3>();
121+
// Dictionary of <matrix name, matrix scale x/y/z vector>
122+
public Dictionary<string, Vector3> ESD_MatrixScale = new Dictionary<string, Vector3>();
123+
// Dictionary of <matrix name, matrix rotation x/y/z vector>
124+
public Dictionary<string, Vector3> ESD_MatrixRotation = new Dictionary<string, Vector3>();
125+
126+
// Handle parameters concerning replacement of string values
127+
protected void ParseReplacementStrings(STFReader stf, ref Dictionary<string, string> renamePairs)
128+
{
129+
stf.MustMatch("(");
130+
// Allow for multiple pairs of replaced and replacement values
131+
while (!stf.EndOfBlock())
132+
{
133+
string replaced = stf.ReadString();
134+
string replacement = stf.ReadString();
135+
// Add pair of values so long as we haven't reached the end of block
136+
if (replaced != ")" && replacement != ")")
137+
renamePairs.Add(replaced, replacement);
138+
}
139+
}
140+
141+
// Handle matrix adjustment parameters
142+
protected void ParseMatrixOverride(STFReader.UNITS units, STFReader stf, ref Dictionary<string, Vector3> matrixParams)
143+
{
144+
Vector3 data = new Vector3(0);
145+
146+
stf.MustMatch("(");
147+
string matName = stf.ReadString();
148+
data.X = stf.ReadFloat(units, 0);
149+
data.Y = stf.ReadFloat(units, 0);
150+
data.Z = stf.ReadFloat(units, 0);
151+
stf.SkipRestOfBlock();
152+
153+
matrixParams.Add(matName, data);
154+
}
99155
}
100156

101157
public class ESD_Bounding_Box

Source/Orts.Formats.Msts/SoundManagmentFile.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Diagnostics;
2222
using System.Linq;
2323
using System.Text;
24+
using Microsoft.Xna.Framework;
2425
using Orts.Parsers.Msts;
2526

2627
namespace Orts.Formats.Msts
@@ -180,6 +181,8 @@ public class SMSStream
180181
public bool[] Weather;
181182
public int[] TimeInterval;
182183
public List<int[]> TimeIntervals;
184+
public Vector3 Position; // TODO: Implement user inputs for positional offset and shape hierarchy
185+
public int ShapeHierarchy;
183186

184187

185188

Source/Orts.Formats.OR/ContainerFile.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ bool TryParse(JsonReader item)
6262
public class ContainerParameters
6363
{
6464
public string Name;
65-
public string ShapeFileName;
65+
public string ShapeFileName;
66+
public string ShapeDescriptor;
6667
public string ContainerType;
6768
public Vector3 IntrinsicShapeOffset = new Vector3(0f, 1.17f, 0f);
6869
public float EmptyMassKG = -1;
@@ -78,7 +79,12 @@ bool TryParse(JsonReader item)
7879
switch (item.Path)
7980
{
8081
case "Name": Name = item.AsString(""); break;
81-
case "Shape": ShapeFileName = item.AsString(ShapeFileName); break;
82+
case "Shape":
83+
ShapeFileName = item.AsString(ShapeFileName);
84+
if (string.IsNullOrEmpty(ShapeDescriptor))
85+
ShapeDescriptor = ShapeFileName + "d";
86+
break;
87+
case "ShapeDescriptor": ShapeDescriptor = item.AsString(ShapeDescriptor); break;
8288
case "ContainerType": ContainerType = item.AsString("40ftHC"); break;
8389
case "IntrinsicShapeOffset[]": IntrinsicShapeOffset = item.AsVector3(Vector3.Zero); break;
8490
case "EmptyMassKG": EmptyMassKG = item.AsFloat(-1); break;

Source/Orts.Simulation/Simulation/Container.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public enum Status
6464

6565
public string Name;
6666
public string ShapeFileName;
67+
public string ShapeDescriptor;
6768
public string BaseShapeFileFolderSlash;
6869
public float MassKG = 2000;
6970
public float EmptyMassKG;
@@ -115,6 +116,7 @@ public virtual void Copy(Container containerCopy)
115116
Name = containerCopy.Name;
116117
BaseShapeFileFolderSlash = containerCopy.BaseShapeFileFolderSlash;
117118
ShapeFileName = containerCopy.ShapeFileName;
119+
ShapeDescriptor = containerCopy.ShapeDescriptor;
118120
IntrinsicShapeOffset = containerCopy.IntrinsicShapeOffset;
119121
ContainerType = containerCopy.ContainerType;
120122
ComputeDimensions();
@@ -131,6 +133,7 @@ public Container(BinaryReader inf, FreightAnimationDiscrete freightAnimDiscrete,
131133
Name = inf.ReadString();
132134
BaseShapeFileFolderSlash = inf.ReadString();
133135
ShapeFileName = inf.ReadString();
136+
ShapeDescriptor = inf.ReadString();
134137
LoadFilePath = inf.ReadString();
135138
IntrinsicShapeOffset.X = inf.ReadSingle();
136139
IntrinsicShapeOffset.Y = inf.ReadSingle();
@@ -231,6 +234,7 @@ public void Save(BinaryWriter outf, bool fromContainerStation = false)
231234
outf.Write(Name);
232235
outf.Write(BaseShapeFileFolderSlash);
233236
outf.Write(ShapeFileName);
237+
outf.Write(ShapeDescriptor);
234238
outf.Write(LoadFilePath);
235239
outf.Write(IntrinsicShapeOffset.X);
236240
outf.Write(IntrinsicShapeOffset.Y);
@@ -253,8 +257,9 @@ public void LoadFromContainerFile(string loadFilePath, string baseFolder)
253257
var containerFile = new ContainerFile(loadFilePath);
254258
var containerParameters = containerFile.ContainerParameters;
255259
Name = containerParameters.Name;
256-
260+
257261
ShapeFileName = @"..\" + containerParameters.ShapeFileName;
262+
ShapeDescriptor = @"..\" + containerParameters.ShapeDescriptor;
258263
var workingString = containerParameters.ShapeFileName.Replace(@"\" , @"/");
259264
var root = workingString.Substring(0, workingString.IndexOf(@"/"));
260265
BaseShapeFileFolderSlash = baseFolder + root + @"\";

Source/Orts.Simulation/Simulation/RollingStocks/Coupling/AnimatedAirHose.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// COPYRIGHT 2022 by the Open Rails project.
1+
// COPYRIGHT 2022 by the Open Rails project.
22
//
33
// This file is part of Open Rails.
44
//
@@ -32,5 +32,6 @@ public struct AnimatedAirHose
3232
public struct AnimatedAirHoseState
3333
{
3434
public string ShapeFileName;
35+
public string ShapeDescriptor;
3536
}
3637
}

Source/Orts.Simulation/Simulation/RollingStocks/Coupling/AnimatedCoupler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// COPYRIGHT 2022 by the Open Rails project.
1+
// COPYRIGHT 2022 by the Open Rails project.
22
//
33
// This file is part of Open Rails.
44
//
@@ -30,5 +30,6 @@ public struct AnimatedCoupler
3030
public struct AnimatedCouplerState
3131
{
3232
public string ShapeFileName;
33+
public string ShapeDescriptor;
3334
}
3435
}

0 commit comments

Comments
 (0)