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