--- /dev/null
+pass ""
+{
+ material
+ {
+ diffuse 0.88 0.9 0.6 1.0;
+ };
+};
#include "beamgate.h"
#include "layout.h"
#include "signal.h"
+#include "terrain.h"
#include "track.h"
#include "utility.h"
#include "vehicle.h"
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)
--- /dev/null
+#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
--- /dev/null
+#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
#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"
#include "selection.h"
#include "slopetool.h"
#include "svgexporter.h"
+#include "terraintool.h"
#include "trackbar.h"
#include "zonebar.h"
#include "zoneproperties.h"
// Setup catalogue and layout
DataFile::load(catalogue, "tracks.dat");
+ DataFile::load(catalogue, "terrain.dat");
cat_layout_3d = new Layout3D(catalogue.get_layout());
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>
--- /dev/null
+#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);
+}
--- /dev/null
+#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
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())
#include <msp/datafile/parser.h>
#include "catalogue.h"
#include "signaltype.h"
+#include "terraintype.h"
#include "tracktype.h"
#include "vehicletype.h"
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);
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))
void rail_profile();
void scale(float, float);
void signal(ArticleNumber);
+ void terrain(ArticleNumber);
void track(ArticleNumber);
void vehicle(ArticleNumber);
};
#include "route.h"
#include "signal.h"
#include "signaltype.h"
+#include "terrain.h"
#include "track.h"
#include "trackcircuit.h"
#include "tracktype.h"
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
{
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)
{
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);
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));
void beamgate();
void route();
void signal(ArticleNumber);
+ void terrain(ArticleNumber);
void track(ArticleNumber);
void train(ArticleNumber, unsigned, const std::string &);
void turnout(unsigned, unsigned);
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+terrain \100001
+{
+ surface
+ {
+ };
+
+ tile_size 0.05;
+ elevation_granularity 0.0125;
+};