Skip to content

Commit 40a9064

Browse files
committed
Use freight animations in auto size and auto center
1 parent 5c427fb commit 40a9064

File tree

4 files changed

+156
-88
lines changed

4 files changed

+156
-88
lines changed

Source/Documentation/Manual/features-rollingstock.rst

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,12 @@ arguments can be set to 0, and the length argument adjusted to produce the desir
438438
no arguments are specified (ie: ``ORTSAutoSize ()`` was entered in the Wagon section) then all three
439439
offsets are assumed to be 0 meters.
440440

441-
Note that automatic sizing uses the nearest LOD of the main shape file. LODs for further distances
442-
and freight animation shape files have no effect on the automatic sizing. This method also works best for rolling
443-
stock with standard buffers/couplers on each end. Automatic sizing generally can't produce reasonable results
444-
for articulated rolling stock. And should something go wrong with the shape file causing automatic sizing to fail,
445-
OR will revert to the values entered in the ``Size`` parameter.
441+
Note that automatic sizing uses the nearest LOD of the main shape file and attached freight animations. LODs for further
442+
distances have no effect on the automatic sizing. :ref:`Shape descriptor overrides <features-shape-manipulation>`
443+
are also not considered at this phase, so if any changes are made in the .sd file, this feature may not provide
444+
good results. This method also works best for rolling stock with standard buffers/couplers on each end.
445+
Automatic sizing generally can't produce reasonable results for articulated rolling stock. And should something go
446+
wrong with the shape file causing automatic sizing to fail, OR will revert to the values entered in the ``Size`` parameter.
446447

447448
Improved wagon alignment tools
448449
------------------------------
@@ -484,9 +485,9 @@ not change the X or Y components. Should no re-centering be required, none will
484485
Some rolling stock will not align correctly when auto-centered. As with ``ORTSAutoSize``, this
485486
feature should be employed on rolling stock with standard buffers or couplers, and will
486487
not produce suitable results for articulated rolling stock or stock with different coupler
487-
types at each end. Only the highest detail LOD of the main shape is used to auto-center the
488-
rolling stock, other LODs and freight animations are ignored. If the process fails, a warning
489-
will be written to the log and the automatic calculation will be skipped.
488+
types at each end. Only the highest detail LOD of the main shape and freight animations are
489+
used, the .sd file is not checked. If the process fails, a warning will be written to the
490+
log and the automatic calculation will be skipped.
490491

491492
Freight animations and pickups
492493
==============================

Source/Orts.Formats.Msts/ShapeFile.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// You should have received a copy of the GNU General Public License
1616
// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
1717

18+
using Microsoft.Xna.Framework;
1819
using Orts.Parsers.Msts;
1920
using System;
2021
using System.Collections.Generic;
@@ -109,7 +110,99 @@ public void ReadAnimationBlock(string orFileName)
109110
file.VerifyEndOfBlock();
110111
}
111112

113+
/// <summary>
114+
/// Determines the corners of the 'bounding box' for the shape
115+
/// </summary>
116+
/// <returns>Two Vector3s, the first measuring the minimum extent of the shape,
117+
/// the second measuring the maximum extent of the shape</returns>
118+
public (Vector3, Vector3) GetBoundingLimits()
119+
{
120+
Vector3 mins = new Vector3(float.PositiveInfinity);
121+
Vector3 maxes = new Vector3(float.NegativeInfinity);
122+
123+
// Determine size specifically for LOD0's (nearest LOD) sub objects
124+
foreach (sub_object subObj in shape.lod_controls[0].distance_levels[0].sub_objects)
125+
{
126+
// Use vertex sets in the sub object to determine which vertices to check
127+
foreach (vertex_set vSet in subObj.vertex_sets)
128+
{
129+
// Use the vertex state used by this vertex set to determine the matrix used
130+
vtx_state vState = shape.vtx_states[vSet.VtxStateIdx];
131+
132+
// The index of the matrix used by this vertex state
133+
int mIndex = vState.imatrix;
134+
135+
// The 'actual' XNA matrix used to determine the vertex transformation
136+
Matrix mat = Matrix.Identity;
137+
138+
// How deep are we in the hierarchy? Set a limit to prevent infinite loops
139+
int depth = 0;
112140

141+
// Determine the overall transformation matrix from the root to the current matrix by following the hierarchy
142+
do
143+
{
144+
matrix m = shape.matrices[mIndex];
145+
146+
// Convert the shape file matrix to an XNA matrix
147+
Matrix matTransform = new Matrix
148+
{
149+
M11 = m.AX,
150+
M12 = m.AY,
151+
M13 = m.AZ, //
152+
M14 = 0,
153+
M21 = m.BX,
154+
M22 = m.BY,
155+
M23 = m.BZ, //
156+
M24 = 0,
157+
M31 = m.CX, //
158+
M32 = m.CY, //
159+
M33 = m.CZ,
160+
M34 = 0,
161+
M41 = m.DX,
162+
M42 = m.DY,
163+
M43 = m.DZ, //
164+
M44 = 1.0f
165+
};
166+
167+
// Add the effect of this transformation to the overall transformation
168+
mat = mat * matTransform;
169+
170+
// Determine the index of the next highest matrix in the hierarchy
171+
mIndex = shape.lod_controls[0].distance_levels[0].distance_level_header.hierarchy[mIndex];
172+
173+
depth++;
174+
} // Keep calculating until we have calculated the root, or until a loop is encountered
175+
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < shape.matrices.Count && depth < 32);
176+
177+
// Determine position of every vertex in this set from point position and tranformed by the matrix
178+
for (int i = vSet.StartVtxIdx; i < vSet.StartVtxIdx + vSet.VtxCount; i++)
179+
{
180+
// Determine vertex position from vertex index and point index
181+
point p = shape.points[subObj.vertices[i].ipoint];
182+
Vector3 pPos = new Vector3(p.X, p.Y, p.Z);
183+
184+
pPos = Vector3.Transform(pPos, mat);
185+
186+
if (pPos.X < mins.X)
187+
mins.X = pPos.X;
188+
if (pPos.X > maxes.X)
189+
maxes.X = pPos.X;
190+
191+
if (pPos.Y < mins.Y)
192+
mins.Y = pPos.Y;
193+
if (pPos.Y > maxes.Y)
194+
maxes.Y = pPos.Y;
195+
196+
if (pPos.Z < mins.Z)
197+
mins.Z = pPos.Z;
198+
if (pPos.Z > maxes.Z)
199+
maxes.Z = pPos.Z;
200+
}
201+
}
202+
}
203+
204+
return (mins, maxes);
205+
}
113206
}
114207

115208
public class shape

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

Lines changed: 54 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -458,91 +458,66 @@ public virtual void LoadFromWagFile(string wagFilePath)
458458
// But my hubris has decided we can use for the shape for things other than graphics - Phillip
459459
ShapeFile wagShape = new ShapeFile(wagonFolderSlash + MainShapeFileName, true);
460460

461-
Vector3 mins = new Vector3(float.PositiveInfinity);
462-
Vector3 maxes = new Vector3(float.NegativeInfinity);
461+
(Vector3 mainMins, Vector3 mainMaxes) = wagShape.GetBoundingLimits();
463462

464-
// Determine size specifically for LOD0's (nearest LOD) sub objects
465-
foreach (sub_object subObj in wagShape.shape.lod_controls[0].distance_levels[0].sub_objects)
463+
// Repeat for MSTS freight animation bounds
464+
if (!string.IsNullOrEmpty(FreightShapeFileName))
466465
{
467-
// Use vertex sets in the sub object to determine which vertices to check
468-
foreach (vertex_set vSet in subObj.vertex_sets)
469-
{
470-
// Use the vertex state used by this vertex set to determine the matrix used
471-
vtx_state vState = wagShape.shape.vtx_states[vSet.VtxStateIdx];
466+
ShapeFile freightShape = new ShapeFile(wagonFolderSlash + FreightShapeFileName, true);
472467

473-
// The index of the matrix used by this vertex state
474-
int mIndex = vState.imatrix;
468+
(Vector3 freightMins, Vector3 freightMaxes) = freightShape.GetBoundingLimits();
475469

476-
// The 'actual' XNA matrix used to determine the vertex transformation
477-
Matrix mat = Matrix.Identity;
470+
// MSTS freight animations don't have offsets, so can be simply compared
471+
mainMins = Vector3.Min(mainMins, freightMins);
472+
mainMaxes = Vector3.Max(mainMaxes, freightMaxes);
473+
}
478474

479-
// Determine the overall transformation matrix from the root to the current matrix by following the hierarchy
480-
do
475+
// And also repeat for ORTS freight animations
476+
if (FreightAnimations != null)
477+
{
478+
foreach (var freightAnim in FreightAnimations.Animations)
479+
{
480+
// We will ignore freight animations not attached to the main shape object for simplicity
481+
if (!string.IsNullOrEmpty(freightAnim.ShapeFileName) && freightAnim.ShapeIndex <= 0 && string.IsNullOrEmpty(freightAnim.ShapeHierarchy))
481482
{
482-
matrix m = wagShape.shape.matrices[mIndex];
483+
ShapeFile ortsFreightShape = new ShapeFile(wagonFolderSlash + freightAnim.ShapeFileName, true);
484+
485+
(Vector3 ortsFreightMins, Vector3 ortsFreightMaxes) = ortsFreightShape.GetBoundingLimits();
483486

484-
// Convert the shape file matrix to an XNA matrix
485-
Matrix matTransform = new Matrix
487+
// Account for flipped freight animation by inverting x and z components
488+
if (freightAnim.Flipped)
486489
{
487-
M11 = m.AX,
488-
M12 = m.AY,
489-
M13 = m.AZ, //
490-
M14 = 0,
491-
M21 = m.BX,
492-
M22 = m.BY,
493-
M23 = m.BZ, //
494-
M24 = 0,
495-
M31 = m.CX, //
496-
M32 = m.CY, //
497-
M33 = m.CZ,
498-
M34 = 0,
499-
M41 = m.DX,
500-
M42 = m.DY,
501-
M43 = m.DZ, //
502-
M44 = 1.0f
503-
};
504-
505-
// Add the effect of this transformation to the overall transformation
506-
mat = mat * matTransform;
507-
508-
// Determine the index of the next highest matrix in the hierarchy
509-
mIndex = wagShape.shape.lod_controls[0].distance_levels[0].distance_level_header.hierarchy[mIndex];
510-
} // Keep calculating until we have calculated the root, or until a loop is encountered
511-
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < wagShape.shape.matrices.Count);
512-
513-
// Determine position of every vertex in this set from point position and tranformed by the matrix
514-
for (int i = vSet.StartVtxIdx; i < vSet.StartVtxIdx + vSet.VtxCount; i++)
515-
{
516-
// Determine vertex position from vertex index and point index
517-
point p = wagShape.shape.points[subObj.vertices[i].ipoint];
518-
Vector3 pPos = new Vector3(p.X, p.Y, p.Z);
519-
520-
pPos = Vector3.Transform(pPos, mat);
521-
522-
if (pPos.X < mins.X)
523-
mins.X = pPos.X;
524-
if (pPos.X > maxes.X)
525-
maxes.X = pPos.X;
526-
527-
if (pPos.Y < mins.Y)
528-
mins.Y = pPos.Y;
529-
if (pPos.Y > maxes.Y)
530-
maxes.Y = pPos.Y;
531-
532-
if (pPos.Z < mins.Z)
533-
mins.Z = pPos.Z;
534-
if (pPos.Z > maxes.Z)
535-
maxes.Z = pPos.Z;
490+
Vector3 temp = ortsFreightMins;
491+
temp.X *= -1;
492+
temp.Y = ortsFreightMaxes.Y;
493+
temp.Z *= -1;
494+
495+
ortsFreightMaxes.X *= -1;
496+
ortsFreightMaxes.Y = ortsFreightMins.Y;
497+
ortsFreightMaxes.Z *= -1;
498+
499+
ortsFreightMins = ortsFreightMaxes;
500+
ortsFreightMaxes = temp;
501+
}
502+
503+
// Account for offsets
504+
// Z-axis offset is inverted to match MSTS coordinate system
505+
Vector3 modOffset = new Vector3(freightAnim.Offset.X, freightAnim.Offset.Y, -freightAnim.Offset.Z);
506+
ortsFreightMins += modOffset;
507+
ortsFreightMaxes += modOffset;
508+
509+
mainMins = Vector3.Min(mainMins, ortsFreightMins);
510+
mainMaxes = Vector3.Max(mainMaxes, ortsFreightMaxes);
536511
}
537512
}
538513
}
539514

540515
// Set dimensions of wagon if configured as such
541516
if (AutoSize)
542517
{
543-
CarWidthM = Math.Max((maxes.X - mins.X) + AutoWidthOffsetM, 0.1f);
544-
CarHeightM = Math.Max((maxes.Y - mins.Y) + AutoHeightOffsetM, 0.1f);
545-
CarLengthM = Math.Max((maxes.Z - mins.Z) + AutoLengthOffsetM, 0.1f);
518+
CarWidthM = Math.Max((mainMaxes.X - mainMins.X) + AutoWidthOffsetM, 0.1f);
519+
CarHeightM = Math.Max((mainMaxes.Y - mainMins.Y) + AutoHeightOffsetM, 0.1f);
520+
CarLengthM = Math.Max((mainMaxes.Z - mainMins.Z) + AutoLengthOffsetM, 0.1f);
546521

547522
if (Simulator.Settings.VerboseConfigurationMessages)
548523
{
@@ -552,9 +527,9 @@ public virtual void LoadFromWagFile(string wagFilePath)
552527
FormatStrings.FormatVeryShortDistanceDisplay(AutoLengthOffsetM, IsMetric));
553528
Trace.TraceInformation("Main shape file {0} calculated to be {1} wide, {2} tall, and {3} long. " +
554529
"Resulting Size ( ) is {4} wide, {5} tall, and {6} long.\n", MainShapeFileName,
555-
FormatStrings.FormatVeryShortDistanceDisplay((maxes.X - mins.X), IsMetric),
556-
FormatStrings.FormatVeryShortDistanceDisplay((maxes.Y - mins.Y), IsMetric),
557-
FormatStrings.FormatVeryShortDistanceDisplay((maxes.Z - mins.Z), IsMetric),
530+
FormatStrings.FormatVeryShortDistanceDisplay((mainMaxes.X - mainMins.X), IsMetric),
531+
FormatStrings.FormatVeryShortDistanceDisplay((mainMaxes.Y - mainMins.Y), IsMetric),
532+
FormatStrings.FormatVeryShortDistanceDisplay((mainMaxes.Z - mainMins.Z), IsMetric),
558533
FormatStrings.FormatVeryShortDistanceDisplay(CarWidthM, IsMetric),
559534
FormatStrings.FormatVeryShortDistanceDisplay(CarHeightM, IsMetric),
560535
FormatStrings.FormatVeryShortDistanceDisplay(CarLengthM, IsMetric));
@@ -564,21 +539,21 @@ public virtual void LoadFromWagFile(string wagFilePath)
564539
// Automatically determine the center of gravity offset required to perfectly center the shape (lengthwise)
565540
if (AutoCenter)
566541
{
567-
InitialCentreOfGravityM.Z = (maxes.Z + mins.Z) / 2.0f;
542+
InitialCentreOfGravityM.Z = (mainMaxes.Z + mainMins.Z) / 2.0f;
568543

569544
if (Simulator.Settings.VerboseConfigurationMessages)
570545
{
571546
Trace.TraceInformation("Rolling stock {0} CoG z-value automatically calculated using ORTSAutoCenter.", shortPath);
572547
if (Math.Abs(InitialCentreOfGravityM.Z) < 0.0001f)
573548
Trace.TraceInformation("Main shape file {0} bounds calculated to be {1} to {2}. Shape is already centered, CoG offset reset to zero.\n",
574549
MainShapeFileName,
575-
FormatStrings.FormatVeryShortDistanceDisplay(mins.Z, IsMetric),
576-
FormatStrings.FormatVeryShortDistanceDisplay(maxes.Z, IsMetric));
550+
FormatStrings.FormatVeryShortDistanceDisplay(mainMins.Z, IsMetric),
551+
FormatStrings.FormatVeryShortDistanceDisplay(mainMaxes.Z, IsMetric));
577552
else
578553
Trace.TraceInformation("Main shape file {0} bounds calculated to be {1} to {2}. CoG offset used to center shape is {0}.\n",
579554
MainShapeFileName,
580-
FormatStrings.FormatVeryShortDistanceDisplay(mins.Z, IsMetric),
581-
FormatStrings.FormatVeryShortDistanceDisplay(maxes.Z, IsMetric),
555+
FormatStrings.FormatVeryShortDistanceDisplay(mainMins.Z, IsMetric),
556+
FormatStrings.FormatVeryShortDistanceDisplay(mainMaxes.Z, IsMetric),
582557
FormatStrings.FormatVeryShortDistanceDisplay(InitialCentreOfGravityM.Z, IsMetric));
583558
}
584559
}
@@ -1393,7 +1368,7 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
13931368
case "wagon(ortscenterofgravity_z":
13941369
case "wagon(ortscentreofgravity_z": InitialCentreOfGravityM.Z = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
13951370
case "wagon(ortsautocentre":
1396-
case "wagon(ortsautocenter": AutoCenter = stf.ReadBoolBlock(false); break;
1371+
case "wagon(ortsautocenter": AutoCenter = stf.ReadBoolBlock(true); break;
13971372
case "wagon(ortsunbalancedsuperelevation": MaxUnbalancedSuperElevationM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
13981373
case "wagon(ortsrigidwheelbase":
13991374
stf.MustMatch("(");

Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ public abstract class TrainCar
112112

113113
// sound related variables
114114
public bool IsPartOfActiveTrain = true;
115-
public List<int> SoundSourceIDs = new List<int>();
116115

117116
public IPowerSupply PowerSupply;
118117

0 commit comments

Comments
 (0)