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