2 // Copyright (C) 2009-2012 Nick Gasson
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.
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.
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/>.
19 #include "IRollingStock.hpp"
20 #include "ILogger.hpp"
21 #include "TrackCommon.hpp"
22 #include "ISmokeTrail.hpp"
23 #include "OpenGLHelper.hpp"
30 #include <boost/operators.hpp>
32 // Concrete implementation of trains
33 class Train : public ITrain {
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(); }
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)
54 IRollingStockPtr vehicle;
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;
61 track::TravelToken travel_token;
63 // Direction train part is travelling along the track
64 Vector<int> direction;
66 // Handles reversal mid-segment
69 bool operator==(const Part& other) const
71 return this == &other;
76 const Part& engine() const;
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);
84 static track::Connection reverse_token(const track::TravelToken& token);
85 static void transform_to_part(const Part& p);
88 ISmokeTrailPtr smoke_trail;
90 VectorF velocity_vector;
92 // Move part of the train across a connection
93 void enter_segment(Part& a_part, const track::Connection& a_connection);
95 // Seperation between waggons
96 static const double SEPARATION;
99 const double Train::SEPARATION(0.15);
101 Train::Train(IMapPtr a_map)
102 : map(a_map), velocity_vector(make_vector(0.0f, 0.0f, 0.0f))
104 parts.push_front(Part(load_engine("tank")));
106 enter_segment(engine(), a_map->start());
108 // Bit of a hack to put the engine in the right place
112 for (int i = 1; i <= 4; i++)
113 add_part(load_waggon("coal_truck"));
116 smoke_trail = make_smoke_trail();
119 void Train::add_part(IRollingStockPtr a_vehicle)
121 Part part(a_vehicle);
122 enter_segment(part, map->start());
124 // Push the rest of the train along some
125 move(part.vehicle->length() + SEPARATION);
127 parts.push_back(part);
130 Train::Part& Train::engine()
132 assert(parts.size() > 0);
133 return parts.front();
136 const Train::Part& Train::engine() const
138 assert(parts.size() > 0);
139 return parts.front();
142 void Train::move_part(Part& part, double distance)
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;
149 //debug() << "move d=" << distance << " s=" << sign
150 // << " ms=" << part.movement_sign;
153 part.segment_delta += min(step, d) * sign;
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;
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;
174 // Move the train along the line a bit
175 void Train::move(double a_distance)
177 using namespace placeholders;
179 for (list<Part>::iterator it = parts.begin();
180 it != parts.end(); ++it)
181 move_part(*it, a_distance);
184 void Train::update_smoke_position(int a_delta)
186 const Part& e = engine();
190 transform_to_part(e);
192 const float smoke_offX = 0.63f;
193 const float smoke_offY = 1.04f;
194 glTranslatef(smoke_offX, smoke_offY, 0.0f);
197 glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
201 smoke_trail->set_position(matrix[12], matrix[13], matrix[14]);
202 smoke_trail->set_velocity(
206 smoke_trail->update(a_delta);
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;
213 smoke_trail->set_delay(base_delay - (throttle * 15));
216 void Train::update(int delta)
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);
223 if ((*it).direction.x < 0 || (*it).direction.z < 0)
226 gradient *= (*it).movement_sign;
228 const double g = 9.78;
229 gravity_sum += -g * gradient * (*it).vehicle->mass();
232 for (list<Part>::iterator it = parts.begin();
233 it != parts.end(); ++it)
234 (*it).vehicle->update(delta, gravity_sum);
236 update_smoke_position(delta);
238 // How many metres does a tile correspond to?
239 const double M_PER_UNIT = 5.0;
241 const VectorF old_pos = part_position(engine());
243 const double delta_seconds = static_cast<float>(delta) / 1000.0f;
244 move(engine().vehicle->speed() * delta_seconds / M_PER_UNIT);
246 velocity_vector = part_position(engine()) - old_pos;
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)
254 tie(pos, a_part.direction) = a_connection;
257 debug() << "Train part entered segment at " << pos
258 << " moving " << a_part.direction;
261 if (!map->is_valid_track(pos))
262 throw runtime_error("Train fell off end of track!");
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);
269 void Train::transform_to_part(const Part& p)
271 p.travel_token.transform(p.segment_delta);
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);
278 void Train::render() const
280 for (list<Part>::const_iterator it = parts.begin();
281 it != parts.end(); ++it) {
284 transform_to_part(*it);
285 glTranslatef(0.0f, track::RAIL_HEIGHT, 0.0f);
287 (*it).vehicle->render();
292 smoke_trail->render();
295 ITrackSegmentPtr Train::track_segment() const
297 return engine().segment;
300 VectorF Train::front() const
302 return part_position(engine());
305 track::Direction Train::direction() const
307 return engine().direction;
310 // Calculate the position of any train part
311 VectorF Train::part_position(const Part& a_part) const
313 // Call the transformer to compute the world location
317 a_part.travel_token.transform(a_part.segment_delta);
320 glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
324 return make_vector(matrix[12], matrix[13], matrix[14]);
327 // Compute a connection object that reverses the train's
328 // direction of travel
329 track::Connection Train::reverse_token(const track::TravelToken& token)
331 track::Position pos = make_point(
332 token.position.x - token.direction.x,
333 token.position.y - token.direction.z);
335 track::Direction dir = -token.direction;
337 return make_pair(pos, dir);
340 // Make an empty train
341 ITrainPtr make_train(IMapPtr a_map)
343 return ITrainPtr(new Train(a_map));