Representation of objects on tracks? (railroads/traffic/etc)

Started by
3 comments, last by Syranide 13 years, 1 month ago
So I'm currently experimenting with a gameplay idea which involves "objects" that are constrained to and moved on/by "tracks", think of factory conveyor belts, railroad tracks or city traffic. Practically put, a system not entirely unlike "transport tycoon" or other railroad games, but in this case, the intention is more along the lines of factory conveyor belts.

I already have the basics of the track-system working, objects can have varying size (rectangular or circular) and will always be centered on a track, all track pieces are laid out on a grid... an object transitions from track to track as it moves along... splitters/mergers will be implemented via logic (transition to one of many tracks). So all track pieces are represented as single lines. Collisions should now be solvable by simply checking the distance between objects. Objects can only move in ONE predefined direction, they can never go backwards.



The short version: how do you best implement and represent locomotives/cars/objects moving on railroads/roads/conveyors?



However, I'm not sure how to best implement the collisions and representation of the objects on the tracks, also for triggers (object entered area, object fully in area, etc).

I could simply store the center point of each object, which feels like the most straight-forward approach, although I can already think of a whole heap of pitfalls with this... as what you actually want to know is where both sides of the object is on the track, it makes triggers more flexible and simpler (remember, multi-tracks and complex systems are designed as stitching and switching between "hidden" tracks with logic). But one can't "compute" both sides on an object from a single point on a track without additional state keeping, as it is very possible that the center point just went through a merger, at this point, it is impossible to know where the back is, track A or B. So then the merger would have to keep track of its "alignment" until both front and back has passed through (and it can become much more complicated), another issue here is that, packages cannot go backwards... but this method would require "looking backwards".

It seems to me that the best approach is to not insert one object, but TWO, the front and the back. And then make sure that these are always "synchronized", mergers/splitters still have to keep some state, but it comes more natural now, it checks for the front to enter and then choses a track, and simply waits for the back to come and puts it on the same track. Basically, all the logic is now internal to the track pieces.


So, as you may notice, I'm kind of debating with myself, and from what I can tell, representing an object as both the FRONT and the BACK as separate by synchronized entities on the track is the best way (there would still be a center point, but it can be computed from FRONT and BACK at any time). But I'm curious, I'm certainly not the first one to do something like this, are there other better methods, did I miss something trivial about storing the "center point"? Or am I on the right track with separate entities for FRONT/BACK? Any thoughts and comments are welcome, including if I make absolutely no sense at all ;).

If there are any articles on this, I would be more than happy.


Advertisement
I implemented this as a sort of railway simulation a while back, and found it interesting. I arranged the structure like this. (Similar to your approach)

Rail : contains start point, end point, a function for querying a point in space given a distance along the track. Also stores a list of pointers to other rail objects which are connected to this object's start and end points.

Wheel: stores a pointer to the rail which this wheel is currently sitting on. Also stores the distance (>0..<1) along the current rail. Its absolute position can be computed by querying Rail

Bogie: This has two Wheel objects, one at each end seperate by a length. Position is exactly at the midpoint between the two wheels. Rotation is calculated from angle between the two.

Stock: Two Bogie objects, one at each end seperated by a length. Position is always at the exact midpoint between the two bogies, Rotation is calculated from angle betwteen the two.

Train : A number of Stock objects seperated by their own lengths.

If a position query on a Rail lies over the end of that rail (ie: you pass <0 or >1 ) it will calculate for you the new rail on which the wheel resides, the new distance along that rail, and the absolute position. This is the part where you'll have to work out whether switches are involved, whether the rails are connected all in the same sequence (start->end->start->end) etc.. etc..

Moving a train involves giving it a velocity. On each update that then cascades down to each Wheel which alters its current rail distance, then queries its absolute position from Rail. Then, the absolute position and heading of each item of stock on the train can be calculated.

Collision detection should be easy. If the Rails can store pointers to which Wheels are currently sitting on it (updated as necessary when a wheel moves), then a query can be made to see which vehicles occupy that section of track. If any, then you can perform collision detection between them. Zones may be a little harder, but you could just go all-out and use some kind of spatial partitioning like a quadtree or octree then you can do fairly inexpensive queries on arbritary areas of the world.


<edit: "Bogie" might be a British term, but basically it's one of the wheelsets that the body of the coach/locomotive/truck sits on. A coach will normally have two, one at each end of the coach. Ie: Here>

D
Thank you for your input!

Due to the lack of response I went ahead with my front/back-idea and I'm currently working on implementing it.
To clarify, your solution is pretty much what I went with, although yours is obviously a bit more "fleshed out" as yours actually simulate trains whereas mine just consider separate objects (just the stock). So I'm glad to know that I'm on the right track (ha-ha ;)).

I'm not yet sure how I'll go about solving "exact collisions" in curves with rectangular objects of varying size (to avoid edges overlapping)... might be that it isn't a big problem in the end or that one can easily hide the artifacts in someway. Or perhaps plain old collision detection is fast and easy enough for my purposes... an idea you gave me though is to actually store the connections between all "track sections", even if logic decides which one should be used, could really simplify a bunch of things, especially "exact collisions" and housekeeping of the "track sections".

Also I'm not yet sure how I'll implement the synchronization for the "center- and back-wheels", either by having the objects push "track transitions" to a queue that the other wheels query as they move along (objects could potentially be longer than a "track section", and simply querying the front might not be sufficient... although it could!)... or by keeping a value in the "track pieces" that remembers "the last assigned track" for the last front-wheel that passed. Perhaps the "per object queue" is only worthwhile if objects can move both forward and backwards.


So again, big thanks!



I'm not yet sure how I'll go about solving "exact collisions" in curves with rectangular objects of varying size (to avoid edges overlapping)...


There should be a number of quick ways to do this as the space is already 'partitioned' for you since the objects reside on rails. You could probably assume that objects whose wheels share the same rail as another object's wheels are potential candidates for collision.

A simple technique could be to test to see if any objects sharing the same track section have their start or end points within another object's start or end points (plus a margin of error for overhang). If that test passes, you could then go on to do a more expensive but accurate test like an OBB, convex hull or poly-poly.

If the objects are covering say two or three track sections at a time then the design of linking track sections together comes into play. You can simply start at one wheel/end, scan the track section, then leap to the next, scan it, and so on until you reach the other end wheel. Any objects found occupying those sections should be investigated further. You may be able to skip this step entirely as the likelihood of objects colliding in the middle of other objects should be very small providing they are added to the rails in sequence! Most of the collisions will happen at the end points (wheels).

To be honest, depending on the amount of objects you have and the target hardware you could even just do simple collision detection (sphere or AABB) on all objects then something more accurate if that passes. A Quadtree works wonders for cutting down large chunks of tests too.

... an idea you gave me though is to actually store the connections between all "track sections", even if logic decides which one should be used, could really simplify a bunch of things, especially "exact collisions" and housekeeping of the "track sections".


My reason for doing this was so that I could leap onto the next track section as if it were the next entry in a linked list.. no calculation or lookup required. It also allows you to create 'switches' : just hold a pointer to the track section that is set as the current route, and it'll automatically send objects that way


Also I'm not yet sure how I'll implement the synchronization for the "center- and back-wheels", either by having the objects push "track transitions" to a queue that the other wheels query as they move along (objects could potentially be longer than a "track section", and simply querying the front might not be sufficient... although it could!)... or by keeping a value in the "track pieces" that remembers "the last assigned track" for the last front-wheel that passed. Perhaps the "per object queue" is only worthwhile if objects can move both forward and backwards.



I'm not sure if I understand correctly but... my solution was just to ensure both the front and back wheels have the same velocity (which is one dimensional.. you can only move forwards or backwards). Then each update they move at the same speed along the rail, so they always stay the same distance apart. The actual object which sits on top of the wheels is just positioned at the midpoint of the two.


Of course all of the above post might be null and void if your objects are not entirely constrained to the tracks.. ie: if you're wanting them to drive along a road, but not exactly to the set track.

D

[quote name='Syranide' timestamp='1300228147' post='4786235']
I'm not yet sure how I'll go about solving "exact collisions" in curves with rectangular objects of varying size (to avoid edges overlapping)...


There should be a number of quick ways to do this as the space is already 'partitioned' for you since the objects reside on rails. You could probably assume that objects whose wheels share the same rail as another object's wheels are potential candidates for collision.

A simple technique could be to test to see if any objects sharing the same track section have their start or end points within another object's start or end points (plus a margin of error for overhang). If that test passes, you could then go on to do a more expensive but accurate test like an OBB, convex hull or poly-poly.

If the objects are covering say two or three track sections at a time then the design of linking track sections together comes into play. You can simply start at one wheel/end, scan the track section, then leap to the next, scan it, and so on until you reach the other end wheel. Any objects found occupying those sections should be investigated further. You may be able to skip this step entirely as the likelihood of objects colliding in the middle of other objects should be very small providing they are added to the rails in sequence! Most of the collisions will happen at the end points (wheels).

To be honest, depending on the amount of objects you have and the target hardware you could even just do simple collision detection (sphere or AABB) on all objects then something more accurate if that passes. A Quadtree works wonders for cutting down large chunks of tests too.

... an idea you gave me though is to actually store the connections between all "track sections", even if logic decides which one should be used, could really simplify a bunch of things, especially "exact collisions" and housekeeping of the "track sections".


My reason for doing this was so that I could leap onto the next track section as if it were the next entry in a linked list.. no calculation or lookup required. It also allows you to create 'switches' : just hold a pointer to the track section that is set as the current route, and it'll automatically send objects that way


Also I'm not yet sure how I'll implement the synchronization for the "center- and back-wheels", either by having the objects push "track transitions" to a queue that the other wheels query as they move along (objects could potentially be longer than a "track section", and simply querying the front might not be sufficient... although it could!)... or by keeping a value in the "track pieces" that remembers "the last assigned track" for the last front-wheel that passed. Perhaps the "per object queue" is only worthwhile if objects can move both forward and backwards.



I'm not sure if I understand correctly but... my solution was just to ensure both the front and back wheels have the same velocity (which is one dimensional.. you can only move forwards or backwards). Then each update they move at the same speed along the rail, so they always stay the same distance apart. The actual object which sits on top of the wheels is just positioned at the midpoint of the two.


Of course all of the above post might be null and void if your objects are not entirely constrained to the tracks.. ie: if you're wanting them to drive along a road, but not exactly to the set track.

D
[/quote]

I think I mention it somewhere above, but my "objects" of varying size and shape. Meaning, that the only thing they have in common is a "center track" which they all travel along, meaning, on straights it's enough to just check the distance on the track, but in curves, the shape of object adds a variable amount of overhang. Meaning, "exact collisions" is the only way to go, unless the visual artifacts can easily be made insignificant.

The nice thing is that, for my purposes, exact collisions could become dirt cheap, no need to check stationary objects, and I don't even need any kind of "world representation" (quadtree, etc) of the objects, I only really need to traverse all the "forward tracks" a certain short distance (to compensate for overhang) to see if there are any objects there and do collisions tests against those. Meaning, the only collisions that can happen is a HEAD colliding with a TAIL from behind. And since I don't intend to have any complex shapes, I could even simplify the collisions to be simple line-tests (no need to test rectangle-rectangle, since the sides could never collide in a reasonable scenario). Another nice benefit of this, as I mention above is that tracks can easily cross above/under each other, the physics doesn't need to know since only forward collisions on the track is considered (there will be no crossing tracks).


This topic is closed to new replies.

Advertisement