Rename tank engine model
[traingame.git] / src / Train.cpp
1 //
2 //  Copyright (C) 2009-2012  Nick Gasson
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 //
17
18 #include "ITrain.hpp"
19 #include "IRollingStock.hpp"
20 #include "ILogger.hpp"
21 #include "TrackCommon.hpp"
22 #include "ISmokeTrail.hpp"
23 #include "OpenGLHelper.hpp"
24
25 #include <stdexcept>
26 #include <cassert>
27 #include <queue>
28 #include <sstream>
29
30 #include <boost/operators.hpp>
31
32 // Concrete implementation of trains
33 class Train : public ITrain {
34 public:
35    Train(IMapPtr a_map);
36
37    // ITrain interface
38    void render() const;
39    void update(int a_delta);
40    VectorF front() const;
41    ITrackSegmentPtr track_segment() const;
42    track::Direction direction() const;
43    track::Position tile() const { return engine().travel_token.position; }
44    double speed() const { return parts.front().vehicle->speed(); }
45    IControllerPtr controller() { return parts.front().vehicle->controller(); }
46
47 private:
48    // The different parts of the train are on different track segments
49    struct Part : boost::equality_comparable<Part> {
50       explicit Part(IRollingStockPtr a_vehicle)
51          : vehicle(a_vehicle), segment_delta(0.0), movement_sign(1.0)
52       {}
53
54       IRollingStockPtr vehicle;
55
56       // The length of a track segment can be found by calling
57       // segment_length() This delta value ranges from 0 to that length and
58       // indicates how far along the segment the train is
59       ITrackSegmentPtr segment;
60       float segment_delta;
61       track::TravelToken travel_token;
62
63       // Direction train part is travelling along the track
64       Vector<int> direction;
65
66       // Handles reversal mid-segment
67       float movement_sign;
68
69       bool operator==(const Part& other) const
70       {
71          return this == &other;
72       }
73    };
74    list<Part> parts;
75
76    const Part& engine() const;
77    Part& engine();
78    void move(double a_distance);
79    void add_part(IRollingStockPtr a_vehicle);
80    VectorF part_position(const Part& a_part) const;
81    void update_smoke_position(int a_delta);
82    void move_part(Part& part, double distance);
83
84    static track::Connection reverse_token(const track::TravelToken& token);
85    static void transform_to_part(const Part& p);
86
87    IMapPtr map;
88    ISmokeTrailPtr smoke_trail;
89
90    VectorF velocity_vector;
91
92    // Move part of the train across a connection
93    void enter_segment(Part& a_part, const track::Connection& a_connection);
94
95    // Seperation between waggons
96    static const double SEPARATION;
97 };
98
99 const double Train::SEPARATION(0.15);
100
101 Train::Train(IMapPtr a_map)
102    : map(a_map), velocity_vector(make_vector(0.0f, 0.0f, 0.0f))
103 {
104    parts.push_front(Part(load_engine("tank")));
105
106    enter_segment(engine(), a_map->start());
107
108    // Bit of a hack to put the engine in the right place
109    move(0.275);
110
111 #if 1
112    for (int i = 1; i <= 4; i++)
113       add_part(load_waggon("coal_truck"));
114 #endif
115
116    smoke_trail = make_smoke_trail();
117 }
118
119 void Train::add_part(IRollingStockPtr a_vehicle)
120 {
121    Part part(a_vehicle);
122    enter_segment(part, map->start());
123
124    // Push the rest of the train along some
125    move(part.vehicle->length() + SEPARATION);
126
127    parts.push_back(part);
128 }
129
130 Train::Part& Train::engine()
131 {
132    assert(parts.size() > 0);
133    return parts.front();
134 }
135
136 const Train::Part& Train::engine() const
137 {
138    assert(parts.size() > 0);
139    return parts.front();
140 }
141
142 void Train::move_part(Part& part, double distance)
143 {
144    // Never move in units greater than 1.0
145    double d = abs(distance);
146    double sign = (distance >= 0.0 ? 1.0 : -1.0) * part.movement_sign;
147    const double step = 0.25;
148
149    //debug() << "move d=" << distance << " s=" << sign
150    //        << " ms=" << part.movement_sign;
151
152    do {
153       part.segment_delta += min(step, d) * sign;
154
155       const double segment_length =
156          part.segment->segment_length(part.travel_token);
157       if (part.segment_delta >= segment_length) {
158          // Moved onto a new piece of track
159          const double over = part.segment_delta - segment_length;
160          enter_segment(part, part.segment->next_position(part.travel_token));
161          part.segment_delta = over;
162       }
163       else if (part.segment_delta < 0.0) {
164          track::Connection prev = reverse_token(part.travel_token);
165          enter_segment(part, prev);
166          part.segment_delta *= -1.0;
167          part.movement_sign *= -1.0;
168       }
169
170       d -= step;
171    } while (d > 0.0);
172 }
173
174 // Move the train along the line a bit
175 void Train::move(double a_distance)
176 {
177    using namespace placeholders;
178
179    for (list<Part>::iterator it = parts.begin();
180         it != parts.end(); ++it)
181       move_part(*it, a_distance);
182 }
183
184 void Train::update_smoke_position(int a_delta)
185 {
186    const Part& e = engine();
187    glPushMatrix();
188    glLoadIdentity();
189
190    transform_to_part(e);
191
192    const float smoke_offX = 0.63f;
193    const float smoke_offY = 1.04f;
194    glTranslatef(smoke_offX, smoke_offY, 0.0f);
195
196    float matrix[16];
197    glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
198
199    glPopMatrix();
200
201    smoke_trail->set_position(matrix[12], matrix[13], matrix[14]);
202    smoke_trail->set_velocity(
203       velocity_vector.x,
204       velocity_vector.y,
205       velocity_vector.z);
206    smoke_trail->update(a_delta);
207
208    // Make the rate at which new particles are created proportional
209    // to the throttle of the controller
210    const int throttle = e.vehicle->controller()->throttle();
211    const int base_delay = 200;
212
213    smoke_trail->set_delay(base_delay - (throttle * 15));
214 }
215
216 void Train::update(int delta)
217 {
218    double gravity_sum = 0.0;
219    for (list<Part>::iterator it = parts.begin();
220         it != parts.end(); ++it) {
221       float gradient = (*it).travel_token.gradient((*it).segment_delta);
222
223       if ((*it).direction.x < 0 || (*it).direction.z < 0)
224          gradient *= -1.0f;
225
226       gradient *= (*it).movement_sign;
227
228       const double g = 9.78;
229       gravity_sum += -g * gradient * (*it).vehicle->mass();
230    }
231
232    for (list<Part>::iterator it = parts.begin();
233         it != parts.end(); ++it)
234       (*it).vehicle->update(delta, gravity_sum);
235
236    update_smoke_position(delta);
237
238    // How many metres does a tile correspond to?
239    const double M_PER_UNIT = 5.0;
240
241    const VectorF old_pos = part_position(engine());
242
243    const double delta_seconds = static_cast<float>(delta) / 1000.0f;
244    move(engine().vehicle->speed() * delta_seconds / M_PER_UNIT);
245
246    velocity_vector = part_position(engine()) - old_pos;
247 }
248
249 // Called when the train enters a new segment
250 // Resets the delta and gets the length of the new segment
251 void Train::enter_segment(Part& a_part, const track::Connection& a_connection)
252 {
253    PointI pos;
254    tie(pos, a_part.direction) = a_connection;
255
256 #if 0
257    debug() << "Train part entered segment at " << pos
258            << " moving " << a_part.direction;
259 #endif
260
261    if (!map->is_valid_track(pos))
262       throw runtime_error("Train fell off end of track!");
263
264    a_part.segment_delta = 0.0;
265    a_part.segment = map->track_at(pos);
266    a_part.travel_token = a_part.segment->get_travel_token(pos, a_part.direction);
267 }
268
269 void Train::transform_to_part(const Part& p)
270 {
271    p.travel_token.transform(p.segment_delta);
272
273    // If we're going backwards, flip the train around
274    if (p.movement_sign < 0.0)
275       glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
276 }
277
278 void Train::render() const
279 {
280    for (list<Part>::const_iterator it = parts.begin();
281         it != parts.end(); ++it) {
282       glPushMatrix();
283
284       transform_to_part(*it);
285       glTranslatef(0.0f, track::RAIL_HEIGHT, 0.0f);
286
287       (*it).vehicle->render();
288
289       glPopMatrix();
290    }
291
292    smoke_trail->render();
293 }
294
295 ITrackSegmentPtr Train::track_segment() const
296 {
297    return engine().segment;
298 }
299
300 VectorF Train::front() const
301 {
302    return part_position(engine());
303 }
304
305 track::Direction Train::direction() const
306 {
307    return engine().direction;
308 }
309
310 // Calculate the position of any train part
311 VectorF Train::part_position(const Part& a_part) const
312 {
313    // Call the transformer to compute the world location
314    glPushMatrix();
315    glLoadIdentity();
316
317    a_part.travel_token.transform(a_part.segment_delta);
318
319    float matrix[16];
320    glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
321
322    glPopMatrix();
323
324    return make_vector(matrix[12], matrix[13], matrix[14]);
325 }
326
327 // Compute a connection object that reverses the train's
328 // direction of travel
329 track::Connection Train::reverse_token(const track::TravelToken& token)
330 {
331    track::Position pos = make_point(
332       token.position.x - token.direction.x,
333       token.position.y - token.direction.z);
334
335    track::Direction dir = -token.direction;
336
337    return make_pair(pos, dir);
338 }
339
340 // Make an empty train
341 ITrainPtr make_train(IMapPtr a_map)
342 {
343    return ITrainPtr(new Train(a_map));
344 }