Skip to content

Commit 848b9fa

Browse files
committed
Add ability to redefine matrix hierarchy in .sd file
1 parent b7efe2e commit 848b9fa

File tree

4 files changed

+174
-10
lines changed

4 files changed

+174
-10
lines changed

Source/Documentation/Manual/features-rollingstock.rst

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,68 @@ transformation would be required on the lower-level sub object. It should also b
518518
changes made to matrices are not purely graphical and could have consequences with some simulation
519519
systems. Extreme settings may break simulation behavior.
520520

521+
Shape Hierarchy Manipulation
522+
''''''''''''''''''''''''''''
523+
524+
The hierarchy of a shape file is particularly important in representing the physical structure
525+
of the depicted object. Each sub object has a single parent sub object, except for the MAIN object which
526+
is ultimately the (grand)parent of all sub objects. When a sub object's parent moves or rotates,
527+
the sub object will move or rotate in exactly the same way (after which, its own motion or rotation
528+
may be added on), which represents some physical connection (such as a hinge or bearing) to the parent
529+
object. As such, improper hierarchy definition results in unusual or outright missing connections
530+
between components, such as connecting rod that doesn't move along with the wheels it is supposed to
531+
be attached to. The hierarchy of the shape can be determined using shape viewing utilities, and
532+
the correct hierarchy is described in various content creation tutorials for MSTS and OR.
533+
534+
.. index::
535+
single: ESD_ORTSMatrixParent
536+
537+
To fix such broken hierarchies without editing the shape file, ``ESD_ORTSMatrixParent ( MATRIXNAME PARENTNAME )``
538+
can be added to the Shape ( block of the shape descriptor file. The hierarchy will be changed such that
539+
the matrix called "MATRIXNAME" will have its parent in the hierarchy changed to the matrix called
540+
"PARENTNAME". If either matrix name can't be found in the shape, a warning will be added to the log file and the
541+
hierarchy will remain unchanged. It's also possible to cause an infinite loop in the hierarchy where an
542+
object is its own parent or grandparent; each sub object should have only one chain of parents and grandparents
543+
that eventually leads back to the MAIN object. Should the configuration cause an infinite loop, a warning will
544+
be added to the log file. Note that OR won't be able to tell which specific change causes an invalid hierarchy
545+
as each hierarchy change can influence other hierarchy changes.
546+
547+
As an example, this shape had a hierarchy where every sub object was a child of the main object, breaking
548+
bogie animations which require wheels to be children of the bogie, not of the main object. With some
549+
manipulation in the .sd file, the correct hierarchy could be implemented, fixing the bogie animation::
550+
551+
SIMISA@@@@@@@@@@JINX0t1t______
552+
553+
Shape ( RhB_Rew_8262.s
554+
ESD_Detail_Level ( 0 )
555+
ESD_Software_DLev ( 3 )
556+
ESD_Alternative_Texture ( 0 )
557+
ESD_Bounding_Box ( -1.32 0.1 -8.05 1.32 2.88 8.05 )
558+
559+
Comment ( Correcting hierarchy so wheels are below bogies. )
560+
ESD_ORTSMatrixParent (
561+
WHEELS11 BOGIE1
562+
WHEELS12 BOGIE1
563+
WHEELS21 BOGIE2
564+
WHEELS22 BOGIE2
565+
)
566+
567+
Comment ( Correcting position offset of wheels due to hierarchy change. )
568+
ESD_ORTSMatrixTranslation ( WHEELS11 0m -0.426m -5.45m )
569+
ESD_ORTSMatrixTranslation ( WHEELS12 0m -0.426m -5.45m )
570+
ESD_ORTSMatrixTranslation ( WHEELS21 0m -0.426m 5.45m )
571+
ESD_ORTSMatrixTranslation ( WHEELS22 0m -0.426m 5.45m )
572+
)
573+
574+
Note how multiple parent/child relationships can be changed at once by specifying additional pairs of
575+
matrix names, and that changing the hierarchy required adjusting the translation of many sub objects in order
576+
for them to appear in the intended locations. The location of a sub object is measured relative to its parent,
577+
so if the sub object parent is changed, it's position in the 3D world will change as well (unless corrected for,
578+
as was done here).
579+
580+
Transformation Matrix Name Changes
581+
''''''''''''''''''''''''''''''''''
582+
521583
.. index::
522584
single: ESD_ORTSMatrixRename
523585

@@ -2551,7 +2613,7 @@ be loaded instead.
25512613
``ORTSTractionCutOffRelayClosingDelay`` refers to the delay between the closing command of the traction cut-off relay
25522614
and the effective closing of the relay.
25532615

2554-
.. _features-scripting-powerselectors
2616+
.. _features-scripting-powerselectors:
25552617

25562618
Pantograph, voltage and power limitation selectors
25572619
--------------------------------------------------

Source/Orts.Formats.Msts/ShapeDescriptorFile.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public SDShape(STFReader stf)
8787
new STFReader.TokenProcessor("esd_ortscustomanimationfps", ()=>{ ESD_CustomAnimationFPS = stf.ReadFloatBlock(STFReader.UNITS.Frequency, null); }),
8888
new STFReader.TokenProcessor("esd_ortstexturereplacement", ()=>{ ParseReplacementStrings(stf, ref ESD_TextureReplacement); }),
8989
new STFReader.TokenProcessor("esd_ortsmatrixrename", ()=>{ ParseReplacementStrings(stf, ref ESD_MatrixRename); }),
90+
new STFReader.TokenProcessor("esd_ortsmatrixparent", ()=>{ ParseReplacementStrings(stf, ref ESD_MatrixParent); }),
9091
new STFReader.TokenProcessor("esd_ortsmatrixtranslation", ()=>{ ParseMatrixOverride(STFReader.UNITS.Distance, stf, ref ESD_MatrixTranslation); }),
9192
new STFReader.TokenProcessor("esd_ortsmatrixscale", ()=>{ ParseMatrixOverride(STFReader.UNITS.None, stf, ref ESD_MatrixScale); }),
9293
new STFReader.TokenProcessor("esd_ortsmatrixrotation", ()=>{ ParseMatrixOverride(STFReader.UNITS.Angle, stf, ref ESD_MatrixRotation); }),
@@ -107,6 +108,8 @@ public SDShape(STFReader stf)
107108
// Store set of all matrices that got modified
108109
foreach (string mat in ESD_MatrixRename.Keys)
109110
ESD_ModifiedMatrices.Add(mat);
111+
foreach (string mat in ESD_MatrixParent.Keys)
112+
ESD_ModifiedMatrices.Add(mat);
110113
foreach (string mat in ESD_MatrixTranslation.Keys)
111114
ESD_ModifiedMatrices.Add(mat);
112115
foreach (string mat in ESD_MatrixScale.Keys)
@@ -126,6 +129,8 @@ public SDShape(STFReader stf)
126129
public Dictionary<string, string> ESD_TextureReplacement = new Dictionary<string, string>();
127130
// Dictionary of <original matrix name, replacement matrix name>
128131
public Dictionary<string, string> ESD_MatrixRename = new Dictionary<string, string>();
132+
// Dictionary of <matrix name, new matrix parent name>
133+
public Dictionary<string, string> ESD_MatrixParent = new Dictionary<string, string>();
129134
// Set of matrix names that are modified in some way or another
130135
public HashSet<string> ESD_ModifiedMatrices = new HashSet<string>();
131136
// Dictionary of <matrix name, matrix translation x/y/z vector>

Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ public virtual void LoadFromWagFile(string wagFilePath)
492492
// The 'actual' XNA matrix used to determine the vertex transformation
493493
Matrix mat = Matrix.Identity;
494494

495+
// How deep are we in the hierarchy? Set a limit to prevent infinite loops
496+
int depth = 0;
497+
495498
// Determine the overall transformation matrix from the root to the current matrix by following the hierarchy
496499
do
497500
{
@@ -523,8 +526,10 @@ public virtual void LoadFromWagFile(string wagFilePath)
523526

524527
// Determine the index of the next highest matrix in the hierarchy
525528
mIndex = wagShape.shape.lod_controls[0].distance_levels[0].distance_level_header.hierarchy[mIndex];
529+
530+
depth++;
526531
} // Keep calculating until we have calculated the root, or until a loop is encountered
527-
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < wagShape.shape.matrices.Count);
532+
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < wagShape.shape.matrices.Count && depth < 32);
528533

529534
// Determine position of every vertex in this set from point position and tranformed by the matrix
530535
for (int i = vSet.StartVtxIdx; i < vSet.StartVtxIdx + vSet.VtxCount; i++)

Source/RunActivity/Viewer3D/Shapes.cs

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,9 @@ public void UpdateResultMatrices()
362362
int hIndex = Hierarchy[i];
363363

364364
// Transform the matrix repeatedly for all of its parents
365-
while (hIndex > -1 && hIndex < Hierarchy.Length)
365+
int depth = 0;
366+
367+
while (hIndex > -1 && hIndex < Hierarchy.Length && depth < 32)
366368
{
367369
// Can finish calculating sooner if we already have the result matrix for the next level up
368370
if (resultCalculated[hIndex])
@@ -379,6 +381,8 @@ public void UpdateResultMatrices()
379381
else
380382
break;
381383
}
384+
385+
depth++;
382386
}
383387

384388
ResultMatrices[i] = res;
@@ -2160,9 +2164,14 @@ void LoadContent()
21602164

21612165
if (!found)
21622166
Trace.TraceWarning("Shape descriptor file {0} specifies texture {1} to be replaced, " +
2163-
"but shape {2} does not contain this texture.", (filePath + "d"), tex, filePath);
2167+
"but shape {2} does not contain this texture.", DescriptorPath, tex, filePath);
21642168
}
21652169

2170+
int[] modifiedHierarchy = null;
2171+
// Prepare to consider hierarchy changes
2172+
if (sdFile.shape.ESD_MatrixParent.Count > 0)
2173+
modifiedHierarchy = (int[])sFile.shape.lod_controls?.FirstOrDefault()?.distance_levels?.FirstOrDefault()?.distance_level_header.hierarchy.Clone();
2174+
21662175
// Manipulate matrices as defined in the sd file
21672176
foreach (string matName in sdFile.shape.ESD_ModifiedMatrices)
21682177
{
@@ -2210,10 +2219,32 @@ void LoadContent()
22102219
sFile.shape.matrices[i] = MSTSMatrixFromXNA(newMatrix, sFile.shape.matrices[i].Name);
22112220
}
22122221

2213-
// Finally, see if this matrix is to be renamed
2214-
if (sdFile.shape.ESD_MatrixRename.TryGetValue(matName, out string newName))
2222+
// See if this matrix should have its parent changed
2223+
if (sdFile.shape.ESD_MatrixParent.TryGetValue(matName, out string newParent))
22152224
{
2216-
sFile.shape.matrices[i].Name = newName;
2225+
int thisIndex = i;
2226+
int parentIndex = -1;
2227+
2228+
for (int m = 0; m < sFile.shape.matrices.Count; m++)
2229+
{
2230+
if (sFile.shape.matrices[m].Name.ToUpper() == newParent.ToUpper())
2231+
{
2232+
parentIndex = m;
2233+
break;
2234+
}
2235+
}
2236+
2237+
// Found the new parent matrix, update the temporary hierarchy list accordingly
2238+
if (parentIndex >= 0)
2239+
{
2240+
if (modifiedHierarchy != null && thisIndex >= 0 && thisIndex < modifiedHierarchy.Length)
2241+
modifiedHierarchy[thisIndex] = parentIndex;
2242+
}
2243+
else // Couldn't find new parent matrix
2244+
{
2245+
Trace.TraceWarning("Shape descriptor file {0} specifies matrix name {1} to be utilized, " +
2246+
"but shape {2} does not contain this matrix.", DescriptorPath, newParent, filePath);
2247+
}
22172248
}
22182249

22192250
found = true;
@@ -2222,7 +2253,68 @@ void LoadContent()
22222253

22232254
if (!found)
22242255
Trace.TraceWarning("Shape descriptor file {0} specifies matrix name {1} to be modified, " +
2225-
"but shape {2} does not contain this matrix.", (filePath + "d"), matName, filePath);
2256+
"but shape {2} does not contain this matrix.", DescriptorPath, matName, filePath);
2257+
}
2258+
2259+
// Check that hierarchy changes make sense, then apply to shape file
2260+
if (modifiedHierarchy != null && sdFile.shape.ESD_MatrixParent.Count > 0)
2261+
{
2262+
// Iterate through every hierarchy element to make sure it resolves back to the main object
2263+
// If anything in the modified hierarchy fails, do not update the shape hierarchy
2264+
bool valid = false;
2265+
2266+
for (int hIndex = 0; hIndex < modifiedHierarchy.Length; hIndex++)
2267+
{
2268+
int nextIndex = modifiedHierarchy[hIndex];
2269+
int depth = 0;
2270+
2271+
while (nextIndex > -1 && nextIndex < modifiedHierarchy.Length && depth < 32)
2272+
{
2273+
if (nextIndex == hIndex)
2274+
break; // We have reached an infinite loop
2275+
nextIndex = modifiedHierarchy[nextIndex];
2276+
2277+
depth++;
2278+
}
2279+
2280+
// The above calculation should produce a final index of -1
2281+
// This means the hierarchy could be traced back to the main shape
2282+
if (nextIndex == -1)
2283+
valid = true;
2284+
else
2285+
break;
2286+
}
2287+
2288+
if (valid)
2289+
{
2290+
foreach (lod_control lodControl in sFile.shape.lod_controls)
2291+
{
2292+
foreach (distance_level distanceLevel in lodControl.distance_levels)
2293+
{
2294+
// Note: This assumes the hierarchy is the same for every LOD...is this always true?
2295+
distanceLevel.distance_level_header.hierarchy = modifiedHierarchy;
2296+
}
2297+
}
2298+
}
2299+
else
2300+
{
2301+
Trace.TraceWarning("Hierarchy changes specified in shape descriptor file {0} " +
2302+
"produce an invalid hierarchy when applied to shape {1}, hierarchy has not been updated.", DescriptorPath, filePath);
2303+
}
2304+
}
2305+
2306+
// Guarantee that matrix renaming happens after all other operations
2307+
foreach (KeyValuePair<string, string> renamePair in sdFile.shape.ESD_MatrixRename)
2308+
{
2309+
for (int i = 0; i < sFile.shape.matrices.Count; i++)
2310+
{
2311+
// Check if this matrix is to be renamed, and rename accordingly
2312+
// Ignore case
2313+
if (sFile.shape.matrices[i].Name.ToUpper() == renamePair.Key.ToUpper())
2314+
{
2315+
sFile.shape.matrices[i].Name = renamePair.Value;
2316+
}
2317+
}
22262318
}
22272319

22282320
// Manipulate LOD settings as defined in the sd file
@@ -2234,10 +2326,10 @@ void LoadContent()
22342326
{
22352327
lodControl.distance_levels[lodIndex.Key].distance_level_header.dlevel_selection = lodIndex.Value;
22362328
}
2237-
else // LOD index defined doesn't eist
2329+
else // LOD index defined doesn't exist
22382330
{
22392331
Trace.TraceWarning("Shape descriptor file {0} specifies LOD index {1} to be modified, " +
2240-
"but shape {2} does not have this many LODs.", (filePath + "d"), lodIndex.Key, filePath);
2332+
"but shape {2} does not have this many LODs.", DescriptorPath, lodIndex.Key, filePath);
22412333
}
22422334
}
22432335
}

0 commit comments

Comments
 (0)