]> git.tdb.fi Git - r2c2.git/blob - libr2c2/terrain.cpp
Don't crash if a train has no router
[r2c2.git] / libr2c2 / terrain.cpp
1 #include <cmath>
2 #include <msp/geometry/box.h>
3 #include <msp/geometry/transformedshape.h>
4 #include "layout.h"
5 #include "terrain.h"
6 #include "terraintype.h"
7
8 using namespace std;
9 using namespace Msp;
10
11 namespace R2C2 {
12
13 Terrain::Terrain(Layout &l, const TerrainType &t):
14         Object(l),
15         type(t),
16         width(0),
17         height(0)
18 {
19         set_size(1, 1);
20
21         layout.add(*this);
22 }
23
24 Terrain::~Terrain()
25 {
26         layout.remove(*this);
27 }
28
29 Terrain *Terrain::clone(Layout *to_layout) const
30 {
31         Terrain *terrain = new Terrain((to_layout ? *to_layout : layout), type);
32         terrain->set_size(width, height);
33         for(unsigned i=0; i<tiles.size(); ++i)
34                 terrain->tiles[i] = tiles[i];
35         return terrain;
36 }
37
38 void Terrain::set_position(const Vector &p)
39 {
40         position = p;
41         signal_moved.emit();
42 }
43
44 void Terrain::set_rotation(const Angle &r)
45 {
46         rotation = r;
47         signal_moved.emit();
48 }
49
50 void Terrain::set_size(unsigned w, unsigned h)
51 {
52         if(!w || !h)
53                 throw invalid_argument("Terrain::set_size");
54
55         vector<Tile> new_tiles(w*h);
56         for(unsigned y=0; (y<h && y<height); ++y)
57                 for(unsigned x=0; (x<w && x<width); ++x)
58                         new_tiles[x+y*w] = tiles[x+y*width];
59
60         width = w;
61         height = h;
62         swap(tiles, new_tiles);
63
64         delete shape;
65         float ts = type.get_tile_size();
66         Vector dim(width*ts, height*ts, ts);
67         shape = new Geometry::TransformedShape<float, 3>(
68                 Geometry::Box<float>(dim),
69                 Transform::translation(dim/2.0f));
70
71         signal_size_changed.emit(width, height);
72 }
73
74 const Terrain::Tile &Terrain::get_tile(unsigned x, unsigned y) const
75 {
76         if(x>=width || y>=height)
77                 throw out_of_range("Terrain::get_tile");
78         return tiles[x+y*width];
79 }
80
81 void Terrain::set_node_elevation(const NodeCoordinates &c, float elev, bool joined)
82 {
83         if(c.x>=width || c.y>=height || c.i>=4)
84                 throw out_of_range("Terrain::set_node_elevation");
85
86         float eg = type.get_elevation_granularity();
87         elev = floor(elev/eg+0.5)*eg;
88
89         if(joined)
90         {
91                 float ref = tiles[c.x+c.y*width].nodes[c.i].elevation;
92                 for(unsigned i=0; i<4; ++i)
93                 {
94                         unsigned x = c.x+c.i%2-i%2;
95                         unsigned y = c.y+c.i/2-i/2;
96                         if(x<width && y<height)
97                         {
98                                 Tile &tile = tiles[x+y*width];
99                                 if(tile.nodes[i].elevation==ref)
100                                 {
101                                         tile.set_node_elevation(i, elev);
102                                         signal_tile_changed.emit(x, y);
103                                 }
104                         }
105                 }
106         }
107         else
108         {
109                 tiles[c.x+c.y*width].set_node_elevation(c.i, elev);
110                 signal_tile_changed.emit(c.x, c.y);
111         }
112 }
113
114 float Terrain::get_node_elevation(const NodeCoordinates &c) const
115 {
116         if(c.x>=width || c.y>=height || c.i>=4)
117                 throw out_of_range("Terrain::get_node_elevation");
118
119         return tiles[c.x+c.y*width].nodes[c.i].elevation;
120 }
121
122 Vector Terrain::get_node_position(const NodeCoordinates &c) const
123 {
124         if(c.x>=width || c.y>=height || c.i>=4)
125                 throw out_of_range("Terrain::get_node_position");
126
127         const Tile &tile = tiles[c.x+c.y*width];
128         float tile_size = type.get_tile_size();
129         Transform trans = Transform::translation(position)*
130                 Transform::rotation(rotation, Vector(0, 0, 1));
131         return trans.transform(Vector((c.x+c.i%2)*tile_size, (c.y+c.i/2)*tile_size, tile.nodes[c.i].elevation));
132 }
133
134 Terrain::NodeCoordinates Terrain::get_closest_node(const Ray &ray) const
135 {
136         Transform reverse_trans = Transform::rotation(rotation, Vector(0, 0, -1))*
137                 Transform::translation(-position);
138         Ray local_ray = reverse_trans.transform(ray);
139
140         float ts = type.get_tile_size();
141         NodeCoordinates coords;
142         float closest_dist = -1;
143         for(unsigned y=0; y<height; ++y)
144                 for(unsigned x=0; x<width; ++x)
145                         for(unsigned i=0; i<4; ++i)
146                         {
147                                 NodeCoordinates c(x, y, i);
148                                 Vector node_pos((x+0.25+(i%2)*0.5)*ts, (y+0.25+(i/2)*0.5)*ts, get_node_elevation(c));
149                                 Vector v = node_pos-local_ray.get_start();
150                                 float dist = (v-local_ray.get_direction()*dot(local_ray.get_direction(), v)).norm();
151                                 if(closest_dist<0 || dist<closest_dist)
152                                 {
153                                         coords = c;
154                                         closest_dist = dist;
155                                 }
156                         }
157
158         return coords;
159 }
160
161 void Terrain::save(list<DataFile::Statement> &st) const
162 {
163         st.push_back((DataFile::Statement("position"), position.x, position.y, position.z));
164         st.push_back((DataFile::Statement("rotation"), rotation.radians()));
165         st.push_back((DataFile::Statement("size"), width, height));
166         for(vector<Tile>::const_iterator i=tiles.begin(); i!=tiles.end(); ++i)
167         {
168                 DataFile::Statement ss("tile");
169                 i->save(ss.sub);
170                 st.push_back(ss);
171         }
172 }
173
174
175 Terrain::Node::Node():
176         elevation(0),
177         ground(0),
178         wall(0)
179 { }
180
181 void Terrain::Node::save(list<DataFile::Statement> &st) const
182 {
183         st.push_back((DataFile::Statement("elevation"), elevation));
184         st.push_back((DataFile::Statement("ground"), ground));
185 }
186
187
188 Terrain::Tile::Tile():
189         secondary_axis(false)
190 { }
191
192 void Terrain::Tile::set_node_elevation(unsigned i, float e)
193 {
194         nodes[i].elevation = e;
195         update_axis();
196 }
197
198 void Terrain::Tile::update_axis()
199 {
200         secondary_axis = (abs(nodes[1].elevation-nodes[2].elevation) < abs(nodes[0].elevation-nodes[3].elevation));
201 }
202
203 void Terrain::Tile::save(list<DataFile::Statement> &st) const
204 {
205         float elevation_mode = find_mode(&Node::elevation);
206         unsigned ground_mode = find_mode(&Node::ground);
207         if(elevation_mode)
208                 st.push_back((DataFile::Statement("elevation"), elevation_mode));
209         if(ground_mode)
210                 st.push_back((DataFile::Statement("ground"), ground_mode));
211
212         for(unsigned i=0; i<4; ++i)
213         {
214                 if(nodes[i].elevation==elevation_mode && nodes[i].ground==ground_mode)
215                         continue;
216
217                 DataFile::Statement ss("node");
218                 ss.append(i);
219                 if(nodes[i].elevation!=elevation_mode)
220                         ss.sub.push_back((DataFile::Statement("elevation"), nodes[i].elevation));
221                 if(nodes[i].ground!=ground_mode)
222                         ss.sub.push_back((DataFile::Statement("ground"), nodes[i].ground));
223                 st.push_back(ss);
224         }
225 }
226
227 template<typename T>
228 T Terrain::Tile::find_mode(T Node::*member) const
229 {
230         for(unsigned i=0; i<3; ++i)
231                 for(unsigned j=i+1; j<4; ++j)
232                         if(nodes[i].*member==nodes[j].*member)
233                                 return nodes[i].*member;
234         return T();
235 }
236
237
238 Terrain::NodeCoordinates::NodeCoordinates():
239         x(0),
240         y(0),
241         i(0)
242 { }
243
244 Terrain::NodeCoordinates::NodeCoordinates(unsigned x_, unsigned y_, unsigned i_):
245         x(x_),
246         y(y_),
247         i(i_)
248 { }
249
250
251 Terrain::Loader::Loader(Terrain &t):
252         DataFile::ObjectLoader<Terrain>(t),
253         next_tile(0)
254 {
255         add("position", &Loader::position);
256         add("rotation", &Loader::rotation);
257         add("size", &Loader::size);
258         add("tile", &Loader::tile);
259         add("tile", &Loader::tile_coords);
260 }
261
262 void Terrain::Loader::position(float x, float y, float z)
263 {
264         obj.set_position(Vector(x, y, z));
265 }
266
267 void Terrain::Loader::rotation(float a)
268 {
269         obj.set_rotation(Angle::from_radians(a));
270 }
271
272 void Terrain::Loader::size(unsigned w, unsigned h)
273 {
274         obj.set_size(w, h);
275 }
276
277 void Terrain::Loader::tile()
278 {
279         if(next_tile>=obj.tiles.size())
280                 throw runtime_error("Terrain::Loader::tile");
281
282         Tile &t = obj.tiles[next_tile];
283         Tile::Loader ldr(obj, t);
284         load_sub_with(ldr);
285         t.update_axis();
286         unsigned x = next_tile%obj.width;
287         unsigned y = next_tile/obj.width;
288         ++next_tile;
289         obj.signal_tile_changed.emit(x, y);
290 }
291
292 void Terrain::Loader::tile_coords(unsigned x, unsigned y)
293 {
294         if(x>=obj.width || y>=obj.height)
295                 throw out_of_range("Terrain::Loader::tile");
296         next_tile = x+y*obj.width;
297         tile();
298 }
299
300
301 Terrain::Node::Loader::Loader(Terrain &t, Node &n):
302         DataFile::ObjectLoader<Node>(n),
303         terrain(t)
304 {
305         add("ground", &Loader::ground);
306         add("elevation", &Node::elevation);
307 }
308
309 void Terrain::Node::Loader::ground(unsigned g)
310 {
311         if(g>=terrain.type.get_n_surface_types())
312                 throw out_of_range("Tile::Loader::surface");
313         obj.ground = g;
314 }
315
316
317 Terrain::Tile::Loader::Loader(Terrain &t, Tile &l):
318         DataFile::ObjectLoader<Tile>(l),
319         terrain(t)
320 {
321         add("ground", &Loader::ground);
322         add("elevation", &Loader::elevation);
323         add("node", &Loader::node);
324 }
325
326 void Terrain::Tile::Loader::ground(unsigned g)
327 {
328         if(g>=terrain.type.get_n_surface_types())
329                 throw out_of_range("Tile::Loader::surface");
330         for(unsigned i=0; i<4; ++i)
331                 obj.nodes[i].ground = g;
332 }
333
334 void Terrain::Tile::Loader::elevation(float h)
335 {
336         for(unsigned i=0; i<4; ++i)
337                 obj.nodes[i].elevation = h;
338 }
339
340 void Terrain::Tile::Loader::node(unsigned i)
341 {
342         if(i>=4)
343                 throw out_of_range("Tile::Loader::node");
344         Node::Loader ldr(terrain, obj.nodes[i]);
345         load_sub_with(ldr);
346 }
347
348 } // namespace R2C2