]> git.tdb.fi Git - r2c2.git/blobdiff - source/libr2c2/terrain.cpp
Add editable terrain objects
[r2c2.git] / source / libr2c2 / terrain.cpp
diff --git a/source/libr2c2/terrain.cpp b/source/libr2c2/terrain.cpp
new file mode 100644 (file)
index 0000000..e84c7cd
--- /dev/null
@@ -0,0 +1,317 @@
+#include <msp/geometry/box.h>
+#include <msp/geometry/transformedshape.h>
+#include "layout.h"
+#include "terrain.h"
+#include "terraintype.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace R2C2 {
+
+Terrain::Terrain(Layout &l, const TerrainType &t):
+       Object(l),
+       type(t),
+       width(0),
+       height(0)
+{
+       set_size(1, 1);
+
+       layout.add(*this);
+}
+
+Terrain::~Terrain()
+{
+       layout.remove(*this);
+}
+
+Terrain *Terrain::clone(Layout *to_layout) const
+{
+       Terrain *terrain = new Terrain((to_layout ? *to_layout : layout), type);
+       terrain->set_size(width, height);
+       for(unsigned i=0; i<tiles.size(); ++i)
+               terrain->tiles[i] = tiles[i];
+       return terrain;
+}
+
+void Terrain::set_position(const Vector &p)
+{
+       position = p;
+       signal_moved.emit();
+}
+
+void Terrain::set_rotation(const Angle &r)
+{
+       rotation = r;
+       signal_moved.emit();
+}
+
+void Terrain::set_size(unsigned w, unsigned h)
+{
+       if(!w || !h)
+               throw invalid_argument("Terrain::set_size");
+
+       vector<Tile> new_tiles(w*h);
+       for(unsigned y=0; (y<h && y<height); ++y)
+               for(unsigned x=0; (x<w && x<width); ++x)
+                       new_tiles[x+y*w] = tiles[x+y*width];
+
+       width = w;
+       height = h;
+       swap(tiles, new_tiles);
+
+       delete shape;
+       float ts = type.get_tile_size();
+       Vector dim(width*ts, height*ts, ts);
+       shape = new Geometry::TransformedShape<float, 3>(
+               Geometry::Box<float>(dim),
+               Transform::translation(dim/2.0f));
+
+       signal_size_changed.emit(width, height);
+}
+
+const Terrain::Tile &Terrain::get_tile(unsigned x, unsigned y) const
+{
+       if(x>=width || y>=height)
+               throw out_of_range("Terrain::get_tile");
+       return tiles[x+y*width];
+}
+
+void Terrain::set_node_elevation(const NodeCoordinates &c, float elev, bool joined)
+{
+       if(c.x>=width || c.y>=height || c.i>=4)
+               throw out_of_range("Terrain::set_node_elevation");
+
+       float eg = type.get_elevation_granularity();
+       elev = int(elev/eg+0.5)*eg;
+
+       if(joined)
+       {
+               float ref = tiles[c.x+c.y*width].nodes[c.i].elevation;
+               for(unsigned i=0; i<4; ++i)
+               {
+                       unsigned x = c.x+c.i%2-i%2;
+                       unsigned y = c.y+c.i/2-i/2;
+                       if(x<width && y<height)
+                       {
+                               Tile &tile = tiles[x+y*width];
+                               if(tile.nodes[i].elevation==ref)
+                               {
+                                       tile.nodes[i].elevation = elev;
+                                       signal_tile_changed.emit(x, y);
+                               }
+                       }
+               }
+       }
+       else
+       {
+               tiles[c.x+c.y*width].nodes[c.i].elevation = elev;
+               signal_tile_changed.emit(c.x, c.y);
+       }
+}
+
+float Terrain::get_node_elevation(const NodeCoordinates &c) const
+{
+       if(c.x>=width || c.y>=height || c.i>=4)
+               throw out_of_range("Terrain::get_node_elevation");
+
+       return tiles[c.x+c.y*width].nodes[c.i].elevation;
+}
+
+Vector Terrain::get_node_position(const NodeCoordinates &c) const
+{
+       if(c.x>=width || c.y>=height || c.i>=4)
+               throw out_of_range("Terrain::get_node_position");
+
+       const Tile &tile = tiles[c.x+c.y*width];
+       float tile_size = type.get_tile_size();
+       Transform trans = Transform::translation(position)*
+               Transform::rotation(rotation, Vector(0, 0, 1));
+       return trans.transform(Vector((c.x+c.i%2)*tile_size, (c.y+c.i/2)*tile_size, tile.nodes[c.i].elevation));
+}
+
+Terrain::NodeCoordinates Terrain::get_closest_node(const Ray &ray) const
+{
+       NodeCoordinates coords;
+       float closest_dist = -1;
+       for(unsigned y=0; y<height; ++y)
+               for(unsigned x=0; x<width; ++x)
+                       for(unsigned i=0; i<4; ++i)
+                       {
+                               NodeCoordinates c(x, y, i);
+                               /* XXX This is not very efficient.  Should transform the ray to
+                               local coordinate system. */
+                               Vector node_pos = get_node_position(c);
+                               Vector v = node_pos-ray.get_start();
+                               float dist = (v-ray.get_direction()*dot(ray.get_direction(), v)).norm();
+                               if(closest_dist<0 || dist<closest_dist)
+                               {
+                                       coords = c;
+                                       closest_dist = dist;
+                               }
+                       }
+       return coords;
+}
+
+void Terrain::save(list<DataFile::Statement> &st) const
+{
+       st.push_back((DataFile::Statement("size"), width, height));
+       for(vector<Tile>::const_iterator i=tiles.begin(); i!=tiles.end(); ++i)
+       {
+               DataFile::Statement ss("tile");
+               i->save(ss.sub);
+               st.push_back(ss);
+       }
+}
+
+
+Terrain::Node::Node():
+       elevation(0),
+       ground(0),
+       wall(0)
+{ }
+
+void Terrain::Node::save(list<DataFile::Statement> &st) const
+{
+       st.push_back((DataFile::Statement("elevation"), elevation));
+       st.push_back((DataFile::Statement("ground"), ground));
+}
+
+
+Terrain::Tile::Tile():
+       secondary_axis(false)
+{ }
+
+void Terrain::Tile::save(list<DataFile::Statement> &st) const
+{
+       bool flat = true;
+       for(unsigned i=1; (flat && i<4); ++i)
+               flat = (nodes[i].elevation==nodes[0].elevation && nodes[i].ground==nodes[0].ground && nodes[i].wall==nodes[0].wall);
+       if(flat)
+       {
+               st.push_back((DataFile::Statement("elevation"), nodes[0].elevation));
+               st.push_back((DataFile::Statement("ground"), nodes[0].ground));
+       }
+       else
+       {
+               for(unsigned i=0; i<4; ++i)
+               {
+                       DataFile::Statement ss("node");
+                       ss.append(i);
+                       nodes[i].save(ss.sub);
+                       st.push_back(ss);
+               }
+       }
+}
+
+
+Terrain::NodeCoordinates::NodeCoordinates():
+       x(0),
+       y(0),
+       i(0)
+{ }
+
+Terrain::NodeCoordinates::NodeCoordinates(unsigned x_, unsigned y_, unsigned i_):
+       x(x_),
+       y(y_),
+       i(i_)
+{ }
+
+
+Terrain::Loader::Loader(Terrain &t):
+       DataFile::ObjectLoader<Terrain>(t),
+       next_tile(0)
+{
+       add("position", &Loader::position);
+       add("rotation", &Loader::rotation);
+       add("size", &Loader::size);
+       add("tile", &Loader::tile);
+       add("tile", &Loader::tile_coords);
+}
+
+void Terrain::Loader::position(float x, float y, float z)
+{
+       obj.set_position(Vector(x, y, z));
+}
+
+void Terrain::Loader::rotation(float a)
+{
+       obj.set_rotation(Angle::from_radians(a));
+}
+
+void Terrain::Loader::size(unsigned w, unsigned h)
+{
+       obj.set_size(w, h);
+}
+
+void Terrain::Loader::tile()
+{
+       if(next_tile>=obj.tiles.size())
+               throw runtime_error("Terrain::Loader::tile");
+
+       Tile &t = obj.tiles[next_tile];
+       Tile::Loader ldr(obj, t);
+       load_sub_with(ldr);
+       unsigned x = next_tile%obj.width;
+       unsigned y = next_tile/obj.width;
+       ++next_tile;
+       obj.signal_tile_changed.emit(x, y);
+}
+
+void Terrain::Loader::tile_coords(unsigned x, unsigned y)
+{
+       if(x>=obj.width || y>=obj.height)
+               throw out_of_range("Terrain::Loader::tile");
+       next_tile = x+y*obj.width;
+       tile();
+}
+
+
+Terrain::Node::Loader::Loader(Terrain &t, Node &n):
+       DataFile::ObjectLoader<Node>(n),
+       terrain(t)
+{
+       add("ground", &Loader::ground);
+       add("elevation", &Node::elevation);
+}
+
+void Terrain::Node::Loader::ground(unsigned g)
+{
+       if(g>=terrain.type.get_n_surface_types())
+               throw out_of_range("Tile::Loader::surface");
+       obj.ground = g;
+}
+
+
+Terrain::Tile::Loader::Loader(Terrain &t, Tile &l):
+       DataFile::ObjectLoader<Tile>(l),
+       terrain(t)
+{
+       add("ground", &Loader::ground);
+       add("elevation", &Loader::elevation);
+       add("node", &Loader::node);
+}
+
+void Terrain::Tile::Loader::ground(unsigned g)
+{
+       if(g>=terrain.type.get_n_surface_types())
+               throw out_of_range("Tile::Loader::surface");
+       for(unsigned i=0; i<4; ++i)
+               obj.nodes[i].ground = g;
+}
+
+void Terrain::Tile::Loader::elevation(float h)
+{
+       for(unsigned i=0; i<4; ++i)
+               obj.nodes[i].elevation = h;
+}
+
+void Terrain::Tile::Loader::node(unsigned i)
+{
+       if(i>=4)
+               throw out_of_range("Tile::Loader::node");
+       Node::Loader ldr(terrain, obj.nodes[i]);
+       load_sub_with(ldr);
+}
+
+} // namespace R2C2