Track Data Structures

When Simulator’s constructor creates the TDB object, an instance of class TDBFile, TDB’s constructor opens .tdb files and parses the contents, creating TrackDB.

Track Data Base

Two arrays comprise TrackDB – TrackNodes and TrItemTable.  Each item of TrackNodes is a TrackNode, which contains references to node contents and TrPins, an array of links that describe the route topology.  The references to contents are of three types:

  1. TrVectorNode --Contains data for a collection of track sections with two ends.  (Shared-use curved and straight sections, dynamic track sections, and bumpers/buffers are examples. Yes, bumper sections are contained in track vector nodes.)
  2. TrJunctionNode – Contains data for a collection of track sections with more than two ends (Switches are examples.)
  3. TrEndNode – Terminate sections at the “dead” end.  (TrEndNodes can be thought of as zero-length sections that merely signal that there is a route end here.)

Nodes of these three types constitute the nodes in a directed graph.  TrPins describe the arcs (edges) of the graph, and the graph defines the topology of the network.

The following figure illustrates the hierarchy of objects in the track data base.

Track Sections

When TDB’s constructor returns, Simulator processes tsection.dat files via one or two methods:

  1. Global tsection.dat – TSectionDatFile’s constructor instantiates TrackSections, a Dictionary that uses SectionIndex as a key and retrieves TrackSection objects.  Each TrackSection may have SectionSize (width, length) data and SectionCurve (radius, angle) data.
  2. Local tsection.dat – Adds to the global tsection.dat data.

An element of a TrVectorSection has a SectionIndex member which serves to tie it to the shared data in TSectionDatFile.

The following figure illustrates the hierarchy of objects comprising shared track section data.

When a traveler is constructed, all of the TrackNodes are looped through.  All but TrVectorNodes are ignored.  For each TrVectorSection in a TrVectorNode, either StraightSectionInit or CurvedSectionInit is executed.  And that's all the constructor does.

Using a straight section as an example, StraightSectionInit does a cull based on a square bounding box a little bit bigger than the section is long.  If the desired traveler position is not within this box in (x, z), the section is rejected.  If accepted, a more refined check is done.  M.Survey is used to compute the (longitudinal, lateral) deviation of the traveler point from the section centerline.  If the lateral deviation is greater than 1.5 m (allows for car overhang), the section is rejected.  If the longitudinal deviation is within 0.002 m of the extent of the section, it is accepted.  If none of these tests are satisfied, the test is ignored. 

If the section is accepted, then the traveler is considered on this section, and traveler member variables are initialized.  MoveInStraightSegment is called to position the traveler at an offset equivalent to the longitudinal deviation.  Upon completion StraightSectionInit returns true to the traveler constructor.  So, the loop on TrackNodes and TrackVectorSections is a search for the first section to pass the culling tests.

As a traveler moves along the route, when it exhausts a section, NextSection() takes care of advancing into the next section.  If the present node is a TrVectorNode and another TrVectorSection exists, the traveler advances into that section.  If not (or if it is a TrEndNode or TrJunctionNode),  then the traveler advances into the next TrackNode.

TrPins

The pDirection member variable determines which direction a traveler moves through a track vector (first to last or last to first).  And that apparently is its only use.  Here are the rules, as NextTrackNode is currently implemented:

If the present node is a TrJunctionNode, if pDirection = 0 then the next node is as indicated by the first .Link entry.  Otherwise, it's the second or third .Link entry, whichever is the selected branch.  The new pDirection becomes the respective .Direction value.

If the present node is a TrEndNode, if pDirection = 1, the node is presently ignored.  (When the train discovers it's in an end node, it will come to an abrupt stop, as presently implemented.)  If pDirection = 0, the new pDirection becomes the first (and only) .Direction value.

If the present node is a TrVectorNode, if pDirection = 0, the next node is specified by the .Link entry at Pin-0.  If pDirection = 1, it's the one at Pin-1.  The new pDirection becomes the respective .Direction value.

Finally, once the traveler has stepped into the next node, if it's a TrVectorNode, the traveler starts through it first to last if pDirection is 1 and last to first if pDirection is 0.  Otherwise (TrEndNode or TrJunction node), member variables are set to default values.

The following table summarizes this behavior:

TrackNode pDirection next TrackNode next pDirection
TrJunctionNode 0 TrPins[0].Link TrPins[0].Direction
TrJunctionNode 1 TrPins[selected].Link TrPins[selected].Direction
TrEndNode 0 TrPins[0].Link TrPins[0].Direction
TrEndNode 1 Ignored Ignored
TrVectorNode 0 TrPins[0].Link TrPins[0].Direction
TrVectorNode 1 TrPins[1].Link TrPins[1].Direction

The following diagram depicts the topology of a very simple route consisting of a single turnout and a little bit of track:

There are a few things worth noting that are not widely known:

  • Adding a single turnout to a route adds five nodes -- a junction (0 1 1 .Direction), two vector nodes (exiting branches, each with 0 1 .Direction), and two end nodes (exiting branches, each with 0 .Direction).  I'm uncertain whether this is always the case for an isolated turnout, and I'm also uncertain why the entrance branch of my test route vector node has a 1 1 (instead of 0 1) .Direction.  It probably is because the first TrPin (1 .Direction) links to the entrance of the turnout.  A train reaching a TrJunctionNode from its entrance end spends virtually no time in it before it steps into the TrVectorNode associated with one of it branches.  A train reaching a TrJunctionNode from the direction of one of its branches likewise spends virtually no time at all before stepping into an independent TrVectorNode adjacent to the entrance.
  • All free ends of a route are terminated with a TrEndNode.  If you terminate a free end with a bumper/buffer section, that section’s connectivity and shape information go into the adjacent TrSectionNode.  That TrSectionNode is terminated with a TrEndNode regardless of whether there’s a bumper there or not.

To summarize, essentially, track junctions and track end nodes have zero length. 

Previous section: TDBTraveller Up to: Design Overview Next section: Dynamic Track
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.