-package train
-
-// Update the train position. Called once per tick so don't take too long.
-// The train, which is just a sequence of bogies, will step the first
-// bogie by the train's speed, and step all other bogies forward
-// so that they maintain the same fixed distance to the one in front.
-// This is not an exact solution and will fail in impossible track designs.
-//
-// Well... it's one of the two approaches I can think off, and this is
-// much more robust/elegant than the other I guess.
-// The second approach would be to calculate the intersection point of a
-// circle around the bogie in front and the entire track.
-// This produces 2 or more points and one of them is correct.
-// Lot's of math, and not better, or even worse, in impossible tracks.
-func (t *Train) Update() error {
- // The first bogie can just step forward with the train's speed.
- // This is the leading bogie and is not constrained by anything.
- b := t.Bogies[0]
- // I say forward but if the speed is negative it will go backwards instead.
- b.step(t.speed)
-
- nextBogie := b
- // Go through all the other bogies one after the other
- for i := 1; i < len(t.Bogies); i++ {
- b := t.Bogies[i]
- // This is the current distance to the bogie in front,
- // subtracted with our target distance. We want the
- // result to be 0, but this isn't always possible.
- d := b.dist(nextBogie) - b.distanceToNext
- // This is a factor we'll multiply the step with,
- // and then decrease. We want to take big steps
- // at the initial iterations, but fine tune it
- // later. I'll also use this to limit the amount
- // of max iterations
- var falloff float32 = 1.0
- // If the subtracted distance is too small, or too big, move
- // the bogie until it is small enough.
- // You could also compare abs(d) < 0.1 but technicalities blah blah.
- // ALSO check that the falloff is becoming too small so that
- // our update doesn't take too long.
- for (d < -0.1 || 0.1 < d) && falloff > 0.1 {
- // Step the bogie according to the error in distance, but a
- // little smaller than that. Taking the exact error as
- // step only works on perfectly straight track, and we don't
- // want to get stuck
- b.step(d * falloff)
- // Decrease the falloff by multiplying it with 0.9
- // This basically calculates 0.9^n with n the amount
- // of iterations. With the limit at 0.1 the max
- // amount of iterations is 22.
- falloff *= 0.9
- // After making a step, calculate the error again.
- // This is the distance to the next bogie, subtracted
- // with our target distance.
- d = b.dist(nextBogie) - b.distanceToNext
- }
- // The bogie we just put in the right place is the target
- // for the next bogie
- nextBogie = b
- }
-
- return nil
-}
-
-// Move a bogie along the track that it is on.
-// It is moved by a distance in the game, but
-// a bogie's position is kept as a progress on its track
-func (b *bogie) step(stepSize float32) {
- // First of all turn that distance into a progress increment.
- // This is like a percentage increase.
- incr := stepSize / b.currentTrack.Length()
- b.trackCompletion += incr
-
- // If we overshoot our track (completion is bigger than 1.0 or 100%)
- // we'll go to the beginning of next track.
- // It's not a complete implementation actually.
- // If the completion would be 1.2, that 0.2 overshoot
- // should be turned back into a step size, and this
- // step should be made on the next track.
- // In reality this overshoot is going to be small
- // so I don't bother with it.
- if b.trackCompletion > 1.0 {
- // Start on the new track on the beginning
- b.trackCompletion = 0
- // Ask our target connection, that we're moving towards,
- // what the next track is. Very important to ask because
- // a connection could be a switch, and it needs to decide
- // for us where we're going.
- b.currentTrack = b.target.NextTrack(b.currentTrack)
- // Ask our new track what its connections are.
- // One of the connections will be our current target,
- // because that's the one we just crossed, and the
- // other is our next target.
- nextConnections := b.currentTrack.Connections()
- // Is our target the first connection?
- if nextConnections[0] == b.target {
- // so our new target becomes the other of the track's connections
- b.target = nextConnections[1]
- // Nah its the second one
- } else {
- // so our new target becomes the other of the track's connections
- b.target = nextConnections[0]
- }
- }
-
- // This does the same as the previous block, except for under
- // shoot. This happens when our completion goes below 0 when
- // we're moving backwards.
- // It might look simpler than going forward, but the same
- // code is hidden behind the frontAndBack() function of a bogie.
- if b.trackCompletion < 0.0 {
- // Our bogie is on a track. A track has two connections
- // and one of those is the bogie's target that the bogie
- // looks at. That's the one in front of the bogie, and the
- // other is in back. When undershooting a track
- // this back bogie becomes our new target.
- _, back := b.frontAndBack()
- // Start the new track at the end, we're going backwards
- // after all
- b.trackCompletion = 1
- // The bogie, going backwards, is now looking at the
- // connection it just crossed, which used to be behind it
- b.target = back
- // Ask this connection what the next track is, given
- // that we came from the previous track.
- b.currentTrack = b.target.NextTrack(b.currentTrack)
- }
-}