Skip to content

Commit 41382f2

Browse files
committed
Automatically calculate wagon size and centering
1 parent df7bf1a commit 41382f2

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

Source/Documentation/Manual/features-rollingstock.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,71 @@ OR supports tilting trains. A train tilts when its .con file name contains the
403403

404404
.. image:: images/features-tilting.png
405405

406+
Features to assist content creation
407+
===================================
408+
409+
OR now includes some features that don't change the functionality of rolling stock, but simplify
410+
some steps of the content creation process or allow more control over content than was previously
411+
possible. The goal of these features is to save content creators' time, give additional power to
412+
creators, and to simplify the installation process for end users.
413+
414+
Automatic wagon size calculation
415+
--------------------------------
416+
417+
Determining the appropriate values to enter in the ``Size ( w, h, l )`` parameter of an engine or
418+
wagon can be tedious, as reasonable settings for the simulated width, height, and length of rolling
419+
stock depend on measurements of the 3D model used. Many content creators have entered largely
420+
arbitrary values of width and height into the size parameter, only adjusting the length value to
421+
give correct coupler alignement.
422+
423+
.. index::
424+
single: ORTSAutoSize
425+
426+
To simplify this process, and produce more reasonable dimensions for rolling stock, OR can now
427+
automatically calculate the dimensions of rolling stock based on the shape file used. Enter
428+
``ORTSAutoSize`` in the Wagon section of an engine or wagon to allow OR to determine
429+
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.
431+
432+
``ORTSAutoSize`` accepts 3 (optional) arguments, default units in meters, corresponding to offsets from the
433+
shape's width, height, and length respectively. For example, ``ORTSAutoSize ( 0.1m, -0.2m, -0.18m )``
434+
would tell OR to automatically determine the wagon's dimensions from the shape file, then subsequently
435+
add 0.1 meters to the width, subtract 0.2 meters from the height, and subtract 0.18 meters from the length,
436+
using the resulting values to set the simulated size of the wagon. In most cases, the width and height
437+
arguments can be set to 0, and the length argument adjusted to produce the desired coupler spacing. If
438+
no arguments are specified (ie: ``ORTSAutoSize ()`` was entered in the Wagon section) then all three
439+
offsets are assumed to be 0 meters.
440+
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.
446+
447+
Automatic wagon centering
448+
-------------------------
449+
450+
Many MSTS and OR creators have encountered rolling stock shapes that were not correctly centered,
451+
resulting in couplers/buffers clipping at one end of the wagon and separating at the other end.
452+
Normally, this would require inspecting the 3D model to determine exactly how off-center it was
453+
and carefully setting the Z value of ``CentreOfGravity ( x, y, z )`` to re-center the model.
454+
455+
.. index::
456+
single: ORTSAutoCenter
457+
458+
To make this easier, OR now includes the ``ORTSAutoCenter`` parameter. When ``ORTSAutoCenter ( 1 )``
459+
is included in the Wagon section of an engine or wagon, OR will inspect the main shape file used by
460+
the wagon to determine the exact Z value of CentreOfGravity required to re-center the shape in the
461+
simulation. This will overwrite the manually entered Z component of ``CentreOfGravity`` but will
462+
not change the X or Y components. Should no re-centering be required, none will be applied.
463+
464+
Some rolling stock will not align correctly when auto-centered. As with ``ORTSAutoSize``, this
465+
feature should be employed on rolling stock with standard buffers or couplers, and will
466+
not produce suitable results for articulated rolling stock or stock with different coupler
467+
types at each end. Only the highest detail LOD of the main shape is used to auto-center the
468+
rolling stock, other LODs and freight animations are ignored. If the process fails, a warning
469+
will be written to the log and the automatic calculation will be skipped.
470+
406471
Freight animations and pickups
407472
==============================
408473

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

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,112 @@ public virtual void LoadFromWagFile(string wagFilePath)
445445
RearAirHose.Connected.ShapeFileName = null;
446446
}
447447

448+
// If requested, use the shape file to determine the size of the wagon
449+
if ((AutoSize || AutoCenter) && !string.IsNullOrEmpty(MainShapeFileName))
450+
{
451+
try // Shape file discrepancies might cause errors, we don't want to cause a crash here
452+
{
453+
// This might be a bad idea, usually we wait to deal with shape files until within viewing range
454+
// But my hubris has decided we can use for the shape for things other than graphics - Phillip
455+
ShapeFile wagShape = new ShapeFile(wagonFolderSlash + MainShapeFileName, true);
456+
457+
Vector3 mins = new Vector3(float.PositiveInfinity);
458+
Vector3 maxes = new Vector3(float.NegativeInfinity);
459+
460+
// Determine size specifically for LOD0's (nearest LOD) sub objects
461+
foreach (sub_object subObj in wagShape.shape.lod_controls[0].distance_levels[0].sub_objects)
462+
{
463+
// Use vertex sets in the sub object to determine which vertices to check
464+
foreach (vertex_set vSet in subObj.vertex_sets)
465+
{
466+
// Use the vertex state used by this vertex set to determine the matrix used
467+
vtx_state vState = wagShape.shape.vtx_states[vSet.VtxStateIdx];
468+
469+
// The index of the matrix used by this vertex state
470+
int mIndex = vState.imatrix;
471+
472+
// The 'actual' XNA matrix used to determine the vertex transformation
473+
Matrix mat = Matrix.Identity;
474+
475+
// Determine the overall transformation matrix from the root to the current matrix by following the hierarchy
476+
do
477+
{
478+
matrix m = wagShape.shape.matrices[mIndex];
479+
480+
// Convert the shape file matrix to an XNA matrix
481+
Matrix matTransform = new Matrix
482+
{
483+
M11 = m.AX,
484+
M12 = m.AY,
485+
M13 = m.AZ, //
486+
M14 = 0,
487+
M21 = m.BX,
488+
M22 = m.BY,
489+
M23 = m.BZ, //
490+
M24 = 0,
491+
M31 = m.CX, //
492+
M32 = m.CY, //
493+
M33 = m.CZ,
494+
M34 = 0,
495+
M41 = m.DX,
496+
M42 = m.DY,
497+
M43 = m.DZ, //
498+
M44 = 1.0f
499+
};
500+
501+
// Add the effect of this transformation to the overall transformation
502+
mat = mat * matTransform;
503+
504+
// Determine the index of the next highest matrix in the hierarchy
505+
mIndex = wagShape.shape.lod_controls[0].distance_levels[0].distance_level_header.hierarchy[mIndex];
506+
} // Keep calculating until we have calculated the root, or until a loop is encountered
507+
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < wagShape.shape.matrices.Count);
508+
509+
// Determine position of every vertex in this set from point position and tranformed by the matrix
510+
for (int i = vSet.StartVtxIdx; i < vSet.StartVtxIdx + vSet.VtxCount; i++)
511+
{
512+
// Determine vertex position from vertex index and point index
513+
point p = wagShape.shape.points[subObj.vertices[i].ipoint];
514+
Vector3 pPos = new Vector3(p.X, p.Y, p.Z);
515+
516+
pPos = Vector3.Transform(pPos, mat);
517+
518+
if (pPos.X < mins.X)
519+
mins.X = pPos.X;
520+
if (pPos.X > maxes.X)
521+
maxes.X = pPos.X;
522+
523+
if (pPos.Y < mins.Y)
524+
mins.Y = pPos.Y;
525+
if (pPos.Y > maxes.Y)
526+
maxes.Y = pPos.Y;
527+
528+
if (pPos.Z < mins.Z)
529+
mins.Z = pPos.Z;
530+
if (pPos.Z > maxes.Z)
531+
maxes.Z = pPos.Z;
532+
}
533+
}
534+
}
535+
536+
// Set dimensions of wagon if configured as such
537+
if (AutoSize)
538+
{
539+
CarWidthM = Math.Max((maxes.X - mins.X) + AutoWidthOffsetM, 0.1f);
540+
CarHeightM = Math.Max((maxes.Y - mins.Y) + AutoHeightOffsetM, 0.1f);
541+
CarLengthM = Math.Max((maxes.Z - mins.Z) + AutoLengthOffsetM, 0.1f);
542+
}
543+
544+
// Automatically determine the center of gravity offset required to perfectly center the shape (lengthwise)
545+
if (AutoCenter)
546+
InitialCentreOfGravityM.Z = (maxes.Z + mins.Z) / 2.0f;
547+
}
548+
catch
549+
{
550+
Trace.TraceWarning("Could not automatically determine size of shape {0} in wagon {1}, there may be an error in the shape.", MainShapeFileName, wagFilePath);
551+
}
552+
}
553+
448554
// If trailing loco resistance constant has not been defined in WAG/ENG file then assign default value based upon orig Davis values
449555
if (TrailLocoResistanceFactor == 0)
450556
{
@@ -1203,6 +1309,14 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
12031309
CarLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
12041310
stf.SkipRestOfBlock();
12051311
break;
1312+
case "wagon(ortsautosize":
1313+
AutoSize = true;
1314+
stf.MustMatch("(");
1315+
AutoWidthOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1316+
AutoHeightOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1317+
AutoLengthOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1318+
stf.SkipRestOfBlock();
1319+
break;
12061320
case "wagon(ortslengthbogiecentre": CarBogieCentreLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
12071321
case "wagon(ortslengthcarbody": CarBodyLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
12081322
case "wagon(ortslengthairhose": CarAirHoseLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
@@ -1220,6 +1334,7 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
12201334
stf.SkipRestOfBlock();
12211335
}
12221336
break;
1337+
case "wagon(centerofgravity":
12231338
case "wagon(centreofgravity":
12241339
stf.MustMatch("(");
12251340
InitialCentreOfGravityM.X = stf.ReadFloat(STFReader.UNITS.Distance, null);
@@ -1232,6 +1347,8 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
12321347
}
12331348
stf.SkipRestOfBlock();
12341349
break;
1350+
case "wagon(ortsautocentre":
1351+
case "wagon(ortsautocenter": AutoCenter = stf.ReadBoolBlock(false); break;
12351352
case "wagon(ortsunbalancedsuperelevation": MaxUnbalancedSuperElevationM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
12361353
case "wagon(ortsrigidwheelbase":
12371354
stf.MustMatch("(");
@@ -1614,7 +1731,6 @@ public virtual void Copy(MSTSWagon copy)
16141731
CarLengthM = copy.CarLengthM;
16151732
TrackGaugeM = copy.TrackGaugeM;
16161733
CentreOfGravityM = copy.CentreOfGravityM;
1617-
InitialCentreOfGravityM = copy.InitialCentreOfGravityM;
16181734
MaxUnbalancedSuperElevationM = copy.MaxUnbalancedSuperElevationM;
16191735
RigidWheelBaseM = copy.RigidWheelBaseM;
16201736
CarBogieCentreLengthM = copy.CarBogieCentreLengthM;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ public static Interpolator SteamHeatBoilerFuelUsageGalukpH()
187187
public float CarWidthM = 2.5f;
188188
public float CarLengthM = 40; // derived classes must overwrite these defaults
189189
public float CarHeightM = 4; // derived classes must overwrite these defaults
190+
public bool AutoSize = false; // Are the dimensions of this wagon to be calculated automatically from the shape file?
191+
public float AutoWidthOffsetM;
192+
public float AutoLengthOffsetM;
193+
public float AutoHeightOffsetM;
190194
public float MassKG = 10000; // Mass in KG at runtime; coincides with InitialMassKG if there is no load and no ORTS freight anim
191195
public float InitialMassKG = 10000;
192196
public bool IsDriveable;
@@ -661,6 +665,7 @@ public Direction Direction
661665
protected float TrackGaugeM; // Track gauge - read in MSTSWagon, otherwise uses value given by the route
662666
protected Vector3 InitialCentreOfGravityM = new Vector3(0, 1.8f, 0); // get centre of gravity - read in MSTSWagon
663667
public Vector3 CentreOfGravityM = new Vector3(0, 1.8f, 0); // get centre of gravity after adjusted for freight animation
668+
protected bool AutoCenter = false; // Should CentreOfGravityM.Z be set automatically to center the wagon?
664669
public float SuperElevationM; // Super elevation on the curve
665670
protected float MaxUnbalancedSuperElevationM; // Maximum comfortable cant deficiency, read from MSTS Wagon File
666671
public float SuperElevationAngleRad;

0 commit comments

Comments
 (0)