Skip to content

Commit ea74ab6

Browse files
committed
Automatically calculate wagon size and centering
1 parent bfb8295 commit ea74ab6

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
@@ -488,6 +488,112 @@ public virtual void LoadFromWagFile(string wagFilePath)
488488
RearAirHose.Connected.ShapeFileName = null;
489489
}
490490

491+
// If requested, use the shape file to determine the size of the wagon
492+
if ((AutoSize || AutoCenter) && !string.IsNullOrEmpty(MainShapeFileName))
493+
{
494+
try // Shape file discrepancies might cause errors, we don't want to cause a crash here
495+
{
496+
// This might be a bad idea, usually we wait to deal with shape files until within viewing range
497+
// But my hubris has decided we can use for the shape for things other than graphics - Phillip
498+
ShapeFile wagShape = new ShapeFile(wagonFolderSlash + MainShapeFileName, true);
499+
500+
Vector3 mins = new Vector3(float.PositiveInfinity);
501+
Vector3 maxes = new Vector3(float.NegativeInfinity);
502+
503+
// Determine size specifically for LOD0's (nearest LOD) sub objects
504+
foreach (sub_object subObj in wagShape.shape.lod_controls[0].distance_levels[0].sub_objects)
505+
{
506+
// Use vertex sets in the sub object to determine which vertices to check
507+
foreach (vertex_set vSet in subObj.vertex_sets)
508+
{
509+
// Use the vertex state used by this vertex set to determine the matrix used
510+
vtx_state vState = wagShape.shape.vtx_states[vSet.VtxStateIdx];
511+
512+
// The index of the matrix used by this vertex state
513+
int mIndex = vState.imatrix;
514+
515+
// The 'actual' XNA matrix used to determine the vertex transformation
516+
Matrix mat = Matrix.Identity;
517+
518+
// Determine the overall transformation matrix from the root to the current matrix by following the hierarchy
519+
do
520+
{
521+
matrix m = wagShape.shape.matrices[mIndex];
522+
523+
// Convert the shape file matrix to an XNA matrix
524+
Matrix matTransform = new Matrix
525+
{
526+
M11 = m.AX,
527+
M12 = m.AY,
528+
M13 = m.AZ, //
529+
M14 = 0,
530+
M21 = m.BX,
531+
M22 = m.BY,
532+
M23 = m.BZ, //
533+
M24 = 0,
534+
M31 = m.CX, //
535+
M32 = m.CY, //
536+
M33 = m.CZ,
537+
M34 = 0,
538+
M41 = m.DX,
539+
M42 = m.DY,
540+
M43 = m.DZ, //
541+
M44 = 1.0f
542+
};
543+
544+
// Add the effect of this transformation to the overall transformation
545+
mat = mat * matTransform;
546+
547+
// Determine the index of the next highest matrix in the hierarchy
548+
mIndex = wagShape.shape.lod_controls[0].distance_levels[0].distance_level_header.hierarchy[mIndex];
549+
} // Keep calculating until we have calculated the root, or until a loop is encountered
550+
while (mIndex > -1 && mIndex != vState.imatrix && mIndex < wagShape.shape.matrices.Count);
551+
552+
// Determine position of every vertex in this set from point position and tranformed by the matrix
553+
for (int i = vSet.StartVtxIdx; i < vSet.StartVtxIdx + vSet.VtxCount; i++)
554+
{
555+
// Determine vertex position from vertex index and point index
556+
point p = wagShape.shape.points[subObj.vertices[i].ipoint];
557+
Vector3 pPos = new Vector3(p.X, p.Y, p.Z);
558+
559+
pPos = Vector3.Transform(pPos, mat);
560+
561+
if (pPos.X < mins.X)
562+
mins.X = pPos.X;
563+
if (pPos.X > maxes.X)
564+
maxes.X = pPos.X;
565+
566+
if (pPos.Y < mins.Y)
567+
mins.Y = pPos.Y;
568+
if (pPos.Y > maxes.Y)
569+
maxes.Y = pPos.Y;
570+
571+
if (pPos.Z < mins.Z)
572+
mins.Z = pPos.Z;
573+
if (pPos.Z > maxes.Z)
574+
maxes.Z = pPos.Z;
575+
}
576+
}
577+
}
578+
579+
// Set dimensions of wagon if configured as such
580+
if (AutoSize)
581+
{
582+
CarWidthM = Math.Max((maxes.X - mins.X) + AutoWidthOffsetM, 0.1f);
583+
CarHeightM = Math.Max((maxes.Y - mins.Y) + AutoHeightOffsetM, 0.1f);
584+
CarLengthM = Math.Max((maxes.Z - mins.Z) + AutoLengthOffsetM, 0.1f);
585+
}
586+
587+
// Automatically determine the center of gravity offset required to perfectly center the shape (lengthwise)
588+
if (AutoCenter)
589+
InitialCentreOfGravityM.Z = (maxes.Z + mins.Z) / 2.0f;
590+
}
591+
catch
592+
{
593+
Trace.TraceWarning("Could not automatically determine size of shape {0} in wagon {1}, there may be an error in the shape.", MainShapeFileName, wagFilePath);
594+
}
595+
}
596+
491597
// If trailing loco resistance constant has not been defined in WAG/ENG file then assign default value based upon orig Davis values
492598
if (TrailLocoResistanceFactor == 0)
493599
{
@@ -1287,6 +1393,14 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
12871393
CarLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
12881394
stf.SkipRestOfBlock();
12891395
break;
1396+
case "wagon(ortsautosize":
1397+
AutoSize = true;
1398+
stf.MustMatch("(");
1399+
AutoWidthOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1400+
AutoHeightOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1401+
AutoLengthOffsetM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1402+
stf.SkipRestOfBlock();
1403+
break;
12901404
case "wagon(ortslengthbogiecentre": CarBogieCentreLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
12911405
case "wagon(ortslengthcarbody": CarBodyLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
12921406
case "wagon(ortslengthairhose": CarAirHoseLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
@@ -1304,6 +1418,7 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
13041418
stf.SkipRestOfBlock();
13051419
}
13061420
break;
1421+
case "wagon(centerofgravity":
13071422
case "wagon(centreofgravity":
13081423
stf.MustMatch("(");
13091424
InitialCentreOfGravityM.X = stf.ReadFloat(STFReader.UNITS.Distance, null);
@@ -1316,6 +1431,8 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
13161431
}
13171432
stf.SkipRestOfBlock();
13181433
break;
1434+
case "wagon(ortsautocentre":
1435+
case "wagon(ortsautocenter": AutoCenter = stf.ReadBoolBlock(false); break;
13191436
case "wagon(ortsunbalancedsuperelevation": MaxUnbalancedSuperElevationM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
13201437
case "wagon(ortsrigidwheelbase":
13211438
stf.MustMatch("(");
@@ -1723,7 +1840,6 @@ public virtual void Copy(MSTSWagon copy)
17231840
CarLengthM = copy.CarLengthM;
17241841
TrackGaugeM = copy.TrackGaugeM;
17251842
CentreOfGravityM = copy.CentreOfGravityM;
1726-
InitialCentreOfGravityM = copy.InitialCentreOfGravityM;
17271843
MaxUnbalancedSuperElevationM = copy.MaxUnbalancedSuperElevationM;
17281844
RigidWheelBaseM = copy.RigidWheelBaseM;
17291845
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;
@@ -638,6 +642,7 @@ public Direction Direction
638642
protected float TrackGaugeM; // Track gauge - read in MSTSWagon, otherwise uses value given by the route
639643
protected Vector3 InitialCentreOfGravityM = new Vector3(0, 1.8f, 0); // get centre of gravity - read in MSTSWagon
640644
public Vector3 CentreOfGravityM = new Vector3(0, 1.8f, 0); // get centre of gravity after adjusted for freight animation
645+
protected bool AutoCenter = false; // Should CentreOfGravityM.Z be set automatically to center the wagon?
641646
public float SuperElevationM; // Super elevation on the curve
642647
protected float MaxUnbalancedSuperElevationM; // Maximum comfortable cant deficiency, read from MSTS Wagon File
643648
public float SuperElevationAngleRad;

0 commit comments

Comments
 (0)