]> git.tdb.fi Git - r2c2.git/commitdiff
Add editable terrain objects
authorMikko Rasa <tdb@tdb.fi>
Thu, 21 Nov 2013 15:12:12 +0000 (17:12 +0200)
committerMikko Rasa <tdb@tdb.fi>
Thu, 21 Nov 2013 15:12:12 +0000 (17:12 +0200)
These will soon replace the base object in layout.  It's still a bit
rough along the edges, especially the editing tool.  References to
surface materials are also just a hint of things to come.

17 files changed:
data/terrain.tech [new file with mode: 0644]
source/3d/layout.cpp
source/3d/terrain.cpp [new file with mode: 0644]
source/3d/terrain.h [new file with mode: 0644]
source/designer/designer.cpp
source/designer/terraintool.cpp [new file with mode: 0644]
source/designer/terraintool.h [new file with mode: 0644]
source/engineer/engineer.cpp
source/libr2c2/catalogue.cpp
source/libr2c2/catalogue.h
source/libr2c2/layout.cpp
source/libr2c2/layout.h
source/libr2c2/terrain.cpp [new file with mode: 0644]
source/libr2c2/terrain.h [new file with mode: 0644]
source/libr2c2/terraintype.cpp [new file with mode: 0644]
source/libr2c2/terraintype.h [new file with mode: 0644]
terrain.dat [new file with mode: 0644]

diff --git a/data/terrain.tech b/data/terrain.tech
new file mode 100644 (file)
index 0000000..35fb0e0
--- /dev/null
@@ -0,0 +1,7 @@
+pass ""
+{
+       material
+       {
+               diffuse 0.88 0.9 0.6 1.0;
+       };
+};
index be19b0020a9a2680b64e4750690cd288b0b8c1c6..fdb4dfc24d5477672ae6fd361f989880dcc5659a 100644 (file)
@@ -1,6 +1,7 @@
 #include "beamgate.h"
 #include "layout.h"
 #include "signal.h"
+#include "terrain.h"
 #include "track.h"
 #include "utility.h"
 #include "vehicle.h"
@@ -86,6 +87,8 @@ void Layout3D::object_added(Object &o)
                new Vehicle3D(*this, *v);
        else if(BeamGate *g = dynamic_cast<BeamGate *>(&o))
                new BeamGate3D(*this, *g);
+       else if(Terrain *r = dynamic_cast<Terrain *>(&o))
+               new Terrain3D(*this, *r);
 }
 
 void Layout3D::object_removed(Object &o)
diff --git a/source/3d/terrain.cpp b/source/3d/terrain.cpp
new file mode 100644 (file)
index 0000000..8fbaa98
--- /dev/null
@@ -0,0 +1,190 @@
+#include <msp/gl/renderer.h>
+#include "libr2c2/terraintype.h"
+#include "layout.h"
+#include "terrain.h"
+
+using namespace Msp;
+
+namespace R2C2 {
+
+unsigned Terrain3D::surface_indices[] =
+{
+       0, 1, 3, 0, 3, 2,
+       1, 3, 2, 1, 2, 0
+};
+
+unsigned Terrain3D::wall_indices[] =
+{
+       1, 4, 5, 1, 5, 3,
+       3, 7, 6, 3, 6, 2
+};
+
+Terrain3D::Terrain3D(Layout3D &l, Terrain &t):
+       Object3D(l, t),
+       terrain(t),
+       mesh((GL::VERTEX3, GL::NORMAL3)),
+       tech(layout.get_catalogue().get<GL::Technique>("terrain.tech"))
+{
+       terrain.signal_size_changed.connect(sigc::mem_fun(this, &Terrain3D::size_changed));
+       terrain.signal_tile_changed.connect(sigc::bind<2>(sigc::mem_fun(this, &Terrain3D::tile_changed), true));
+
+       create_mesh();
+
+       layout.get_scene().add(*this);
+}
+
+Terrain3D::~Terrain3D()
+{
+       layout.get_scene().remove(*this);
+}
+
+Vector Terrain3D::get_node() const
+{
+       float ts = terrain.get_type().get_tile_size();
+       return terrain.get_position()+Vector(terrain.get_width()*ts/2, terrain.get_height()*ts/2, 0);
+}
+
+void Terrain3D::create_mesh()
+{
+       mesh.clear();
+
+       GL::MeshBuilder bld(mesh);
+       unsigned width = terrain.get_width();
+       unsigned height = terrain.get_height();
+       bld.begin(GL::TRIANGLES);
+       for(unsigned y=0; y<height; ++y)
+               for(unsigned x=0; x<width; ++x)
+               {
+                       const Terrain::Tile &tile = terrain.get_tile(x, y);
+                       Vector nodes[8];
+                       gather_nodes(x, y, nodes);
+
+                       const unsigned *indices = surface_indices+tile.secondary_axis*6;
+                       build_triangle(bld, nodes, indices);
+                       build_triangle(bld, nodes, indices+3);
+
+                       if(x+1<width)
+                       {
+                               build_triangle(bld, nodes, wall_indices);
+                               build_triangle(bld, nodes, wall_indices+3);
+                       }
+
+                       if(y+1<height)
+                       {
+                               build_triangle(bld, nodes, wall_indices+6);
+                               build_triangle(bld, nodes, wall_indices+9);
+                       }
+               }
+       bld.end();
+}
+
+void Terrain3D::gather_nodes(unsigned x, unsigned y, Vector *nodes)
+{
+       const Terrain::Tile &tile = terrain.get_tile(x, y);
+       float ts = terrain.get_type().get_tile_size();
+
+       for(unsigned i=0; i<4; ++i)
+               nodes[i] = Vector((x+i%2)*ts, (y+i/2)*ts, tile.nodes[i].elevation);
+
+       if(x+1<terrain.get_width())
+       {
+               const Terrain::Tile &neighbor = terrain.get_tile(x+1, y);
+               nodes[4] = Vector((x+1)*ts, y*ts, neighbor.nodes[0].elevation);
+               nodes[5] = Vector((x+1)*ts, (y+1)*ts, neighbor.nodes[2].elevation);
+       }
+
+       if(y+1<terrain.get_height())
+       {
+               const Terrain::Tile &neighbor = terrain.get_tile(x, y+1);
+               nodes[6] = Vector(x*ts, (y+1)*ts, neighbor.nodes[0].elevation);
+               nodes[7] = Vector((x+1)*ts, (y+1)*ts, neighbor.nodes[1].elevation);
+       }
+}
+
+void Terrain3D::build_triangle(GL::PrimitiveBuilder &bld, const Vector *nodes, const unsigned *indices)
+{
+       bld.normal(normalize(cross(nodes[indices[1]]-nodes[indices[0]], nodes[indices[2]]-nodes[indices[0]])));
+       for(unsigned i=0; i<3; ++i)
+               bld.vertex(nodes[indices[i]]);
+}
+
+void Terrain3D::update_triangle(unsigned offset, const Vector *nodes, const unsigned *indices)
+{
+       const GL::VertexFormat &format = mesh.get_vertices().get_format();
+       unsigned position_offset = format.offset(GL::VERTEX3);
+       unsigned normal_offset = format.offset(GL::NORMAL3);
+
+       Vector normal = normalize(cross(nodes[indices[1]]-nodes[indices[0]], nodes[indices[2]]-nodes[indices[0]]));
+       for(unsigned i=0; i<3; ++i)
+       {
+               float *vertex = mesh.modify_vertex(offset+i);
+               for(unsigned j=0; j<3; ++j)
+               {
+                       vertex[position_offset+j] = nodes[indices[i]][j];
+                       vertex[normal_offset+j] = normal[j];
+               }
+       }
+}
+
+void Terrain3D::size_changed(unsigned, unsigned)
+{
+       create_mesh();
+}
+
+void Terrain3D::tile_changed(unsigned x, unsigned y, bool propagate)
+{
+       Vector nodes[8];
+       gather_nodes(x, y, nodes);
+
+       unsigned base = y*(terrain.get_width()*18-6);
+       if(y+1==terrain.get_height())
+               base += x*12;
+       else
+               base += x*18;
+
+       const Terrain::Tile &tile = terrain.get_tile(x, y);
+       const unsigned *indices = surface_indices+tile.secondary_axis*6;
+       update_triangle(base, nodes, indices);
+       update_triangle(base+3, nodes, indices+3);
+
+       if(x+1<terrain.get_width())
+       {
+               base += 6;
+               update_triangle(base, nodes, wall_indices);
+               update_triangle(base+3, nodes, wall_indices+3);
+       }
+
+       if(y+1<terrain.get_height())
+       {
+               base += 6;
+               update_triangle(base, nodes, wall_indices+6);
+               update_triangle(base+3, nodes, wall_indices+9);
+       }
+
+       if(propagate)
+       {
+               if(x>0)
+                       tile_changed(x-1, y, false);
+               if(y>0)
+                       tile_changed(x, y-1, false);
+       }
+}
+
+void Terrain3D::render(GL::Renderer &renderer, const GL::Tag &tag) const
+{
+       if(!tech.has_pass(tag))
+               return;
+
+       GL::Renderer::Push push(renderer);
+
+       renderer.matrix_stack() *= matrix;
+
+       const GL::RenderPass &pass = tech.get_pass(tag);
+       renderer.set_material(pass.get_material());
+       renderer.set_texturing(pass.get_texturing());
+       renderer.set_shader_program(pass.get_shader_program(), pass.get_shader_data());
+
+       mesh.draw(renderer);
+}
+
+} // namespace R2C2
diff --git a/source/3d/terrain.h b/source/3d/terrain.h
new file mode 100644 (file)
index 0000000..2cd9854
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef R2C2_3D_TERRAIN_H_
+#define R2C2_3D_TERRAIN_H_
+
+#include <msp/gl/mesh.h>
+#include <msp/gl/primitivebuilder.h>
+#include <msp/gl/renderable.h>
+#include <msp/gl/technique.h>
+#include "libr2c2/terrain.h"
+#include "object.h"
+
+namespace R2C2 {
+
+class Terrain3D: public Object3D, public Msp::GL::Renderable
+{
+private:
+       Terrain &terrain;
+       Msp::GL::Mesh mesh;
+       const Msp::GL::Technique &tech;
+
+       static unsigned surface_indices[];
+       static unsigned wall_indices[];
+
+public:
+       Terrain3D(Layout3D &, Terrain &);
+       virtual ~Terrain3D();
+
+       virtual Vector get_node() const;
+       virtual bool is_visible() const { return true; }
+
+private:
+       void create_mesh();
+       void gather_nodes(unsigned, unsigned, Vector *);
+       void build_triangle(Msp::GL::PrimitiveBuilder &, const Vector *, const unsigned *);
+       void update_triangle(unsigned, const Vector *, const unsigned *);
+
+       void size_changed(unsigned, unsigned);
+       void tile_changed(unsigned, unsigned, bool);
+
+public:
+       virtual void render(Msp::GL::Renderer &, const Msp::GL::Tag &) const;
+};
+
+} // namespace R2C2
+
+#endif
index 6ae9c0726c45701a5915adc9f852d9fdc1b75e66..32fe42391d13691d7d1d45a14d614bcd1251fa07 100644 (file)
@@ -17,6 +17,7 @@
 #include <msp/time/units.h>
 #include <msp/time/utils.h>
 #include "libr2c2/route.h"
+#include "libr2c2/terrain.h"
 #include "libr2c2/tracktype.h"
 #include "libr2c2/zone.h"
 #include "3d/path.h"
@@ -35,6 +36,7 @@
 #include "selection.h"
 #include "slopetool.h"
 #include "svgexporter.h"
+#include "terraintool.h"
 #include "trackbar.h"
 #include "zonebar.h"
 #include "zoneproperties.h"
@@ -61,6 +63,7 @@ Designer::Designer(int argc, char **argv):
 
        // Setup catalogue and layout
        DataFile::load(catalogue, "tracks.dat");
+       DataFile::load(catalogue, "terrain.dat");
 
        cat_layout_3d = new Layout3D(catalogue.get_layout());
 
@@ -428,6 +431,12 @@ void Designer::key_press(unsigned key)
                svg_export();
        else if(key==Msp::Input::KEY_P)
                object_properties();
+       else if(key==Msp::Input::KEY_TAB)
+       {
+               Object *obj = selection.get_object();
+               if(Terrain *terrain = dynamic_cast<Terrain *>(obj))
+                       use_tool(new TerrainTool(*this, keyboard, mouse, *terrain));
+       }
 }
 
 template<typename T>
diff --git a/source/designer/terraintool.cpp b/source/designer/terraintool.cpp
new file mode 100644 (file)
index 0000000..46076ec
--- /dev/null
@@ -0,0 +1,90 @@
+#include <cmath>
+#include <msp/gl/meshbuilder.h>
+#include <msp/input/keys.h>
+#include "designer.h"
+#include "terraintool.h"
+
+using namespace std;
+using namespace Msp;
+using namespace R2C2;
+
+TerrainTool::TerrainTool(Designer &d, Input::Keyboard &k, Input::Mouse &m, Terrain &t):
+       Tool(d, k, m),
+       terrain(t),
+       marker((GL::VERTEX3, GL::COLOR4_UBYTE)),
+       dragging(false),
+       drag_start(0)
+{
+       designer.get_layout_3d().get_scene().add(*this);
+
+       float ts = terrain.get_type().get_tile_size();
+       GL::MeshBuilder bld(marker);
+       bld.begin(GL::TRIANGLE_STRIP);
+       bld.vertex(-ts/2, 0, -ts/4);
+       bld.vertex(ts/2, 0, -ts/4);
+       bld.vertex(-ts/2, 0, ts/4);
+       bld.vertex(ts/2, 0, ts/4);
+       bld.end();
+       bld.begin(GL::TRIANGLE_STRIP);
+       bld.vertex(0, -ts/2, -ts/4);
+       bld.vertex(0, ts/2, -ts/4);
+       bld.vertex(0, -ts/2, ts/4);
+       bld.vertex(0, ts/2, ts/4);
+       bld.end();
+}
+
+TerrainTool::~TerrainTool()
+{
+       designer.get_layout_3d().get_scene().remove(*this);
+}
+
+void TerrainTool::button_press(unsigned btn)
+{
+       if(btn==1)
+       {
+               dragging = true;
+               drag_start = pointer.y;
+       }
+}
+
+void TerrainTool::button_release(unsigned btn)
+{
+       if(btn==1)
+               dragging = false;
+}
+
+void TerrainTool::pointer_motion()
+{
+       if(dragging)
+       {
+               float d = (pointer.y-drag_start)*20;
+
+               if(abs(d)>1)
+               {
+                       float elev = terrain.get_node_elevation(highlight_node);
+                       float eg = terrain.get_type().get_elevation_granularity();
+                       elev += eg*d;
+                       terrain.set_node_elevation(highlight_node, elev, true);
+                       marker_position = terrain.get_node_position(highlight_node);
+
+                       drag_start = pointer.y;
+               }
+       }
+       else
+       {
+               Ray ray = designer.get_view().create_ray(pointer.x, pointer.y);
+               highlight_node = terrain.get_closest_node(ray);
+               marker_position = terrain.get_node_position(highlight_node);
+       }
+}
+
+void TerrainTool::render(GL::Renderer &renderer, const GL::Tag &tag) const
+{
+       if(tag.id)
+               return;
+
+       GL::Renderer::Push push(renderer);
+
+       renderer.matrix_stack() *= GL::Matrix::translation(marker_position);
+       marker.draw(renderer);
+}
diff --git a/source/designer/terraintool.h b/source/designer/terraintool.h
new file mode 100644 (file)
index 0000000..f5f8354
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef TERRAINTOOL_H_
+#define TERRAINTOOL_H_
+
+#include <msp/gl/mesh.h>
+#include <msp/gl/renderable.h>
+#include "libr2c2/terrain.h"
+#include "tool.h"
+
+class TerrainTool: public Tool, public Msp::GL::Renderable
+{
+private:
+       R2C2::Terrain &terrain;
+       Msp::GL::Mesh marker;
+       R2C2::Terrain::NodeCoordinates highlight_node;
+       R2C2::Vector marker_position;
+       bool dragging;
+       float drag_start;
+
+public:
+       TerrainTool(Designer &, Msp::Input::Keyboard &, Msp::Input::Mouse &, R2C2::Terrain &);
+       virtual ~TerrainTool();
+
+private:
+       virtual void button_press(unsigned);
+       virtual void button_release(unsigned);
+       virtual void pointer_motion();
+
+public:
+       virtual void render(Msp::GL::Renderer &, const Msp::GL::Tag &) const;
+};
+
+#endif
index d0c879c02067ebb260d841528c05cd26eafb5649..49edd48d0d86b7cd8e4bdae246e62c72637d0188 100644 (file)
@@ -71,6 +71,7 @@ Engineer::Engineer(int argc, char **argv):
        DataFile::load(catalogue, "tracks.dat");
        DataFile::load(catalogue, "locos.dat");
        DataFile::load(catalogue, "wagons.dat");
+       DataFile::load(catalogue, "terrain.dat");
        DataFile::load(layout, options.layout_fn);
 
        if(layout.has_driver())
index 221a6f0663667cca1665e4388d867e422c16fb5e..779e210b1375ad1e4bb09055e7251a33f9194fa2 100644 (file)
@@ -3,6 +3,7 @@
 #include <msp/datafile/parser.h>
 #include "catalogue.h"
 #include "signaltype.h"
+#include "terraintype.h"
 #include "tracktype.h"
 #include "vehicletype.h"
 
@@ -49,6 +50,7 @@ Catalogue::Loader::Loader(Catalogue &c):
        add("rail_profile", &Loader::rail_profile);
        add("scale", &Loader::scale);
        add("signal", &Loader::signal);
+       add("terrain", &Loader::terrain);
        add("track", &Loader::track);
        add("track_technique", &Catalogue::track_technique);
        add("vehicle", &Loader::vehicle);
@@ -89,6 +91,16 @@ void Catalogue::Loader::signal(ArticleNumber art_nr)
        obj.add(*sig.release());
 }
 
+void Catalogue::Loader::terrain(ArticleNumber art_nr)
+{
+       if(obj.objects.count(art_nr))
+               throw key_error(art_nr);
+
+       RefPtr<TerrainType> ter = new TerrainType(art_nr);
+       load_sub(*ter);
+       obj.add(*ter.release());
+}
+
 void Catalogue::Loader::track(ArticleNumber art_nr)
 {
        if(obj.objects.count(art_nr))
index dcde9f38f9600dfac5a2a7f761e72ec0c54738c5..5a9156097380cbebbc9bb4aa5113da65c0a96838 100644 (file)
@@ -25,6 +25,7 @@ public:
                void rail_profile();
                void scale(float, float);
                void signal(ArticleNumber);
+               void terrain(ArticleNumber);
                void track(ArticleNumber);
                void vehicle(ArticleNumber);
        };
index c23ad38fb1d39969c2463ebab919ea165a3b0895..0025ec3b5b3adcd305ebd08ec8b8a9c44b8622ac 100644 (file)
@@ -13,6 +13,7 @@
 #include "route.h"
 #include "signal.h"
 #include "signaltype.h"
+#include "terrain.h"
 #include "track.h"
 #include "trackcircuit.h"
 #include "tracktype.h"
@@ -135,6 +136,12 @@ const set<Vehicle *> &Layout::get_all<Vehicle>() const
        return objects.get<Vehicle>();
 }
 
+template<>
+const set<Terrain *> &Layout::get_all<Terrain>() const
+{
+       return objects.get<Terrain>();
+}
+
 template<>
 const set<TrackChain *> &Layout::get_all<TrackChain>() const
 {
@@ -414,6 +421,15 @@ void Layout::save(const string &fn) const
                writer.write(st);
        }
 
+       const set<Terrain *> &terrains = objects.get<Terrain>();
+       for(set<Terrain *>::const_iterator i=terrains.begin(); i!=terrains.end(); ++i)
+       {
+               DataFile::Statement st("terrain");
+               st.append((*i)->get_type().get_article_number());
+               (*i)->save(st.sub);
+               writer.write(st);
+       }
+
        const set<BeamGate *> &gates = objects.get<BeamGate>();
        for(set<BeamGate *>::const_iterator i=gates.begin(); i!=gates.end(); ++i)
        {
@@ -534,6 +550,7 @@ Layout::Loader::Loader(Layout &l):
        add("beamgate", &Loader::beamgate);
        add("route", &Loader::route);
        add("signal", &Loader::signal);
+       add("terrain", &Loader::terrain);
        add("track", &Loader::track);
        add("train", &Loader::train);
        add("turnout", &Loader::turnout);
@@ -558,6 +575,12 @@ void Layout::Loader::signal(ArticleNumber art_nr)
        load_sub(*sig);
 }
 
+void Layout::Loader::terrain(ArticleNumber art_nr)
+{
+       Terrain *ter = new Terrain(obj, obj.catalogue.get<TerrainType>(art_nr));
+       load_sub(*ter);
+}
+
 void Layout::Loader::track(ArticleNumber art_nr)
 {
        Track *trk = new Track(obj, obj.catalogue.get<TrackType>(art_nr));
index d1428cf70100070972a8758ee9ad0782e5d8b9fd..498a45a94f7552ba4c36ddf222e938765d919494 100644 (file)
@@ -36,6 +36,7 @@ public:
                void beamgate();
                void route();
                void signal(ArticleNumber);
+               void terrain(ArticleNumber);
                void track(ArticleNumber);
                void train(ArticleNumber, unsigned, const std::string &);
                void turnout(unsigned, unsigned);
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
diff --git a/source/libr2c2/terrain.h b/source/libr2c2/terrain.h
new file mode 100644 (file)
index 0000000..837119e
--- /dev/null
@@ -0,0 +1,118 @@
+#ifndef LIBR2C2_TERRAIN_H_
+#define LIBR2C2_TERRAIN_H_
+
+#include "object.h"
+#include "terraintype.h"
+
+namespace R2C2 {
+
+class Terrain: public Object
+{
+public:
+       class Loader: public Msp::DataFile::ObjectLoader<Terrain>
+       {
+       private:
+               unsigned next_tile;
+
+       public:
+               Loader(Terrain &);
+
+       private:
+               void position(float, float, float);
+               void rotation(float);
+               void size(unsigned, unsigned);
+               void tile();
+               void tile_coords(unsigned, unsigned);
+       };
+
+       struct Node
+       {
+               class Loader: public Msp::DataFile::ObjectLoader<Node>
+               {
+               private:
+                       Terrain &terrain;
+
+               public:
+                       Loader(Terrain &, Node &);
+
+               private:
+                       void ground(unsigned);
+               };
+
+               float elevation;
+               unsigned ground;
+               unsigned wall;
+
+               Node();
+
+               void save(std::list<Msp::DataFile::Statement> &) const;
+       };
+
+       struct Tile
+       {
+               class Loader: public Msp::DataFile::ObjectLoader<Tile>
+               {
+               private:
+                       Terrain &terrain;
+
+               public:
+                       Loader(Terrain &, Tile &);
+
+               private:
+                       void ground(unsigned);
+                       void elevation(float);
+                       void node(unsigned);
+               };
+
+               Node nodes[4];
+               bool secondary_axis;
+
+               Tile();
+
+               void save(std::list<Msp::DataFile::Statement> &) const;
+       };
+
+       struct NodeCoordinates
+       {
+               unsigned x, y;
+               unsigned i;
+
+               NodeCoordinates();
+               NodeCoordinates(unsigned, unsigned, unsigned);
+       };
+
+       sigc::signal<void, unsigned, unsigned> signal_size_changed;
+       sigc::signal<void, unsigned, unsigned> signal_tile_changed;
+
+private:
+       const TerrainType &type;
+       unsigned width;
+       unsigned height;
+       std::vector<Tile> tiles;
+
+public:
+       Terrain(Layout &, const TerrainType &);
+       virtual ~Terrain();
+
+       virtual Terrain *clone(Layout *) const;
+
+       virtual const TerrainType &get_type() const { return type; }
+       virtual void set_position(const Vector &);
+       virtual void set_rotation(const Angle &);
+       virtual void set_tilt(const Angle &) { }
+
+       void set_size(unsigned, unsigned);
+       unsigned get_width() const { return width; }
+       unsigned get_height() const { return height; }
+       const Tile &get_tile(unsigned, unsigned) const;
+       void set_node_elevation(const NodeCoordinates &, float, bool = false);
+       float get_node_elevation(const NodeCoordinates &) const;
+       Vector get_node_position(const NodeCoordinates &) const;
+       NodeCoordinates get_closest_node(const Ray &) const;
+
+       void save(std::list<Msp::DataFile::Statement> &) const;
+};
+
+} // namespace R2C2
+
+#endif
diff --git a/source/libr2c2/terraintype.cpp b/source/libr2c2/terraintype.cpp
new file mode 100644 (file)
index 0000000..ce9551f
--- /dev/null
@@ -0,0 +1,51 @@
+#include <stdexcept>
+#include "terraintype.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace R2C2 {
+
+TerrainType::TerrainType(const ArticleNumber &an):
+       ObjectType(an),
+       tile_size(1),
+       elevation_granularity(0.1)
+{ }
+
+const TerrainType::SurfaceType &TerrainType::get_surface_type(unsigned i) const
+{
+       if(i>=surface_types.size())
+               throw out_of_range("TerrainType::get_surface_type");
+       return surface_types[i];
+}
+
+
+TerrainType::SurfaceType::SurfaceType():
+       r(0.8),
+       g(0.8),
+       b(0.8)
+{ }
+
+
+TerrainType::Loader::Loader(TerrainType &tt):
+       DataFile::ObjectLoader<TerrainType>(tt)
+{
+       add("elevation_granularity", &TerrainType::elevation_granularity);
+       add("surface", &Loader::surface);
+       add("tile_size", &TerrainType::tile_size);
+}
+
+void TerrainType::Loader::surface()
+{
+       SurfaceType srf;
+       load_sub(srf);
+       obj.surface_types.push_back(srf);
+}
+
+
+TerrainType::SurfaceType::Loader::Loader(SurfaceType &st):
+       DataFile::ObjectLoader<SurfaceType>(st)
+{
+}
+
+} // namespace R2C2
diff --git a/source/libr2c2/terraintype.h b/source/libr2c2/terraintype.h
new file mode 100644 (file)
index 0000000..f0013ad
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef LIBR2C2_TERRAINTYPE_H_
+#define LIBR2C2_TERRAINTYPE_H_
+
+#include <vector>
+#include "objecttype.h"
+
+namespace R2C2 {
+
+class TerrainType: public ObjectType
+{
+public:
+       class Loader: public Msp::DataFile::ObjectLoader<TerrainType>
+       {
+       public:
+               Loader(TerrainType &);
+
+       private:
+               void surface();
+       };
+
+       struct SurfaceType
+       {
+               class Loader: public Msp::DataFile::ObjectLoader<SurfaceType>
+               {
+               public:
+                       Loader(SurfaceType &);
+               };
+
+               float r, g, b;
+
+               SurfaceType();
+       };
+
+private:
+       std::vector<SurfaceType> surface_types;
+       float tile_size;
+       float elevation_granularity;
+
+public:
+       TerrainType(const ArticleNumber &);
+
+       unsigned get_n_surface_types() const { return surface_types.size(); }
+       const SurfaceType &get_surface_type(unsigned) const;
+       float get_tile_size() const { return tile_size; }
+       float get_elevation_granularity() const { return elevation_granularity; }
+};
+
+} // namespace R2C2
+
+#endif
diff --git a/terrain.dat b/terrain.dat
new file mode 100644 (file)
index 0000000..df47ef5
--- /dev/null
@@ -0,0 +1,9 @@
+terrain \100001
+{
+       surface
+       {
+       };
+
+       tile_size 0.05;
+       elevation_granularity 0.0125;
+};