summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2023-03-05 01:59:16 +0000
committerDan Goodliffe <dan@randomdan.homeip.net>2023-03-05 01:59:16 +0000
commitd4c073a18adaed73973f34c6c39fc15664d9211d (patch)
tree44536af3af0284ab75eae79ef81a5e4452019269
parentAdd helper operator to perform vec3*mat4 and perspective divide (diff)
parentRemove old hard coded asset factory test, run entirely from XML load and rend... (diff)
downloadilt-d4c073a18adaed73973f34c6c39fc15664d9211d.tar.bz2
ilt-d4c073a18adaed73973f34c6c39fc15664d9211d.tar.xz
ilt-d4c073a18adaed73973f34c6c39fc15664d9211d.zip
Merge branch 'model-factory'
-rw-r--r--Jamroot.jam4
-rw-r--r--assetFactory/asset.cpp34
-rw-r--r--assetFactory/asset.h37
-rw-r--r--assetFactory/assetFactory.cpp85
-rw-r--r--assetFactory/assetFactory.h30
-rw-r--r--assetFactory/cuboid.cpp29
-rw-r--r--assetFactory/cuboid.h8
-rw-r--r--assetFactory/cylinder.cpp46
-rw-r--r--assetFactory/cylinder.h8
-rw-r--r--assetFactory/faceController.cpp91
-rw-r--r--assetFactory/faceController.h31
-rw-r--r--assetFactory/factoryMesh.cpp40
-rw-r--r--assetFactory/factoryMesh.h18
-rw-r--r--assetFactory/modelFactoryMesh.cpp15
-rw-r--r--assetFactory/modelFactoryMesh.h39
-rw-r--r--assetFactory/modelFactoryMesh_fwd.h3
-rw-r--r--assetFactory/mutation.cpp10
-rw-r--r--assetFactory/mutation.h14
-rw-r--r--assetFactory/object.cpp23
-rw-r--r--assetFactory/object.h26
-rw-r--r--assetFactory/plane.cpp15
-rw-r--r--assetFactory/plane.h8
-rw-r--r--assetFactory/shape.cpp17
-rw-r--r--assetFactory/shape.h25
-rw-r--r--assetFactory/style.cpp46
-rw-r--r--assetFactory/style.h34
-rw-r--r--assetFactory/use.cpp33
-rw-r--r--assetFactory/use.h27
-rw-r--r--game/vehicles/railVehicle.cpp8
-rw-r--r--game/vehicles/railVehicle.h2
-rw-r--r--game/vehicles/railVehicleClass.cpp21
-rw-r--r--game/vehicles/railVehicleClass.h10
-rw-r--r--gfx/gl/shaders/basicShader.fs3
-rw-r--r--gfx/gl/shaders/basicShader.vs3
-rw-r--r--gfx/gl/shaders/landmassShader.vs3
-rw-r--r--gfx/gl/shadowMapper.cpp7
-rw-r--r--gfx/gl/shadowMapper.h1
-rw-r--r--gfx/models/mesh.cpp2
-rw-r--r--gfx/models/vertex.hpp5
-rw-r--r--lib/collections.hpp41
-rw-r--r--lib/filesystem.cpp9
-rw-r--r--lib/filesystem.h5
-rw-r--r--lib/jsonParse-persistence.h4
-rw-r--r--lib/persistence.cpp9
-rw-r--r--lib/persistence.h158
-rw-r--r--lib/saxParse-persistence.cpp50
-rw-r--r--lib/saxParse-persistence.h40
-rw-r--r--lib/saxParse.cpp41
-rw-r--r--lib/saxParse.h21
-rw-r--r--res/brush47.xml36
-rw-r--r--test/Jamfile.jam6
-rw-r--r--test/fixtures/json/abs.json2
-rw-r--r--test/fixtures/json/bad/empty_abs.json2
-rw-r--r--test/fixtures/json/bad/implicit_abs.json2
-rw-r--r--test/fixtures/json/bad/late_type.json2
-rw-r--r--test/fixtures/json/bad/wrong_type.json2
-rw-r--r--test/fixtures/json/bad_type.json2
-rw-r--r--test/fixtures/json/conv.json4
-rw-r--r--test/fixtures/json/empty.json2
-rw-r--r--test/fixtures/json/implicit.json2
-rw-r--r--test/fixtures/json/load_object.json4
-rw-r--r--test/fixtures/json/nested.json8
-rw-r--r--test/fixtures/json/shared_ptr_diff.json10
-rw-r--r--test/fixtures/json/shared_ptr_diff_default.json6
-rw-r--r--test/fixtures/json/shared_ptr_null.json6
-rw-r--r--test/fixtures/json/shared_ptr_same.json6
-rw-r--r--test/fixtures/json/shared_ptr_wrong_type.json6
-rw-r--r--test/fixtures/json/vector_ptr.json2
-rw-r--r--test/fixtures/rgb.txt20
-rw-r--r--test/perf-assetFactory.cpp32
-rw-r--r--test/test-assetFactory.cpp127
-rw-r--r--test/test-persistence.cpp26
-rw-r--r--test/test-render.cpp8
73 files changed, 1467 insertions, 95 deletions
diff --git a/Jamroot.jam b/Jamroot.jam
index 14703eb..27db410 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -12,7 +12,9 @@ pkg-config.import sdl2 ;
pkg-config.import glew ;
pkg-config.import freetype2 ;
pkg-config.import glib-2.0 ;
+pkg-config.import mxml ;
lib pthread ;
+lib OpenMeshCore ;
variant coverage : debug ;
project : requirements
@@ -111,7 +113,9 @@ lib ilt :
<library>glew
<library>freetype2
<library>glib-2.0
+ <library>mxml
<library>pthread
+ <library>OpenMeshCore
: :
<include>.
<include>lib
diff --git a/assetFactory/asset.cpp b/assetFactory/asset.cpp
new file mode 100644
index 0000000..3ab2f1c
--- /dev/null
+++ b/assetFactory/asset.cpp
@@ -0,0 +1,34 @@
+#include "asset.h"
+
+bool
+Asset::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_MEMBER(id) && STORE_MEMBER(name);
+}
+
+Asset::MeshConstruct::MeshConstruct(Mesh::Ptr & m) :
+ Persistence::SelectionPtrBase<FactoryMesh::Ptr> {fmesh}, out {m} { }
+
+void
+Asset::MeshConstruct::endObject(Persistence::Stack & stk)
+{
+ out = fmesh->createMesh();
+ Persistence::SelectionPtrBase<FactoryMesh::Ptr>::endObject(stk);
+}
+
+Asset::MeshArrayConstruct::MeshArrayConstruct(std::span<Mesh::Ptr> m) :
+ Persistence::SelectionPtrBase<FactoryMesh::Ptr> {fmesh}, out {m}
+{
+}
+
+void
+Asset::MeshArrayConstruct::endObject(Persistence::Stack & stk)
+{
+ for (auto & outMesh : out) {
+ if (!outMesh) {
+ outMesh = fmesh->createMesh();
+ break;
+ }
+ }
+ Persistence::SelectionPtrBase<FactoryMesh::Ptr>::endObject(stk);
+}
diff --git a/assetFactory/asset.h b/assetFactory/asset.h
new file mode 100644
index 0000000..e3318e4
--- /dev/null
+++ b/assetFactory/asset.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "factoryMesh.h"
+#include "persistence.h"
+#include <stdTypeDefs.hpp>
+
+class Asset : public Persistence::Persistable, public StdTypeDefs<Asset> {
+public:
+ std::string id;
+ std::string name;
+
+protected:
+ struct MeshConstruct : public Persistence::SelectionPtrBase<FactoryMesh::Ptr> {
+ using Persistence::SelectionPtrBase<FactoryMesh::Ptr>::setValue;
+
+ MeshConstruct(Mesh::Ptr & m);
+
+ void endObject(Persistence::Stack & stk) override;
+
+ FactoryMesh::Ptr fmesh;
+ Mesh::Ptr & out;
+ };
+
+ struct MeshArrayConstruct : public Persistence::SelectionPtrBase<FactoryMesh::Ptr> {
+ using Persistence::SelectionPtrBase<FactoryMesh::Ptr>::setValue;
+
+ MeshArrayConstruct(std::span<Mesh::Ptr> m);
+
+ void endObject(Persistence::Stack & stk) override;
+
+ FactoryMesh::Ptr fmesh;
+ std::span<Mesh::Ptr> out;
+ };
+
+ friend Persistence::SelectionPtrBase<std::shared_ptr<Asset>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+};
diff --git a/assetFactory/assetFactory.cpp b/assetFactory/assetFactory.cpp
new file mode 100644
index 0000000..f5fc2b3
--- /dev/null
+++ b/assetFactory/assetFactory.cpp
@@ -0,0 +1,85 @@
+#include "assetFactory.h"
+#include "collections.hpp"
+#include "cuboid.h"
+#include "cylinder.h"
+#include "modelFactoryMesh_fwd.h"
+#include "object.h"
+#include "plane.h"
+#include "saxParse-persistence.h"
+#include <filesystem.h>
+
+AssetFactory::AssetFactory() :
+ shapes {
+ {"plane", std::make_shared<Plane>()},
+ {"cuboid", std::make_shared<Cuboid>()},
+ {"cylinder", std::make_shared<Cylinder>()},
+ },
+ colours {parseX11RGB("/usr/share/X11/rgb.txt")}
+{
+}
+
+std::shared_ptr<AssetFactory>
+AssetFactory::loadXML(const std::filesystem::path & filename)
+{
+ filesystem::FileStar file {filename.c_str(), "r"};
+ return Persistence::SAXParsePersistence {}.loadState<std::shared_ptr<AssetFactory>>(file);
+}
+
+AssetFactory::Colours
+AssetFactory::parseX11RGB(const char * path)
+{
+ filesystem::FileStar rgb {path, "r"};
+ Colours out;
+ Colour colour;
+ char inname[BUFSIZ];
+ while (fscanf(rgb, "%f %f %f %[^\n\r]s", &colour.r, &colour.g, &colour.b, inname) == 4) {
+ std::string name {inname};
+ normalizeColourName(name);
+ out.emplace(std::move(name), colour / 255.f);
+ }
+ return out;
+}
+
+void
+AssetFactory::normalizeColourName(std::string & name)
+{
+ std::erase_if(name, ::isblank);
+ name *= [l = std::locale {}](auto & ch) {
+ ch = std::tolower(ch, l);
+ };
+}
+
+AssetFactory::ColourAlpha
+AssetFactory::parseColour(std::string_view in) const
+{
+ if (in.empty()) {
+ throw std::runtime_error("Empty colour specification");
+ }
+ if (in[0] == '#') {
+ if (in.length() > 9 || in.length() % 2 == 0) {
+ throw std::runtime_error("Invalid hex colour specification");
+ }
+ ColourAlpha out {0, 0, 0, 1};
+ std::generate_n(&out.r, (in.length() - 1) / 2, [in = in.data() + 1]() mutable {
+ uint8_t channel;
+ std::from_chars(in, in + 2, channel, 16);
+ in += 2;
+ return static_cast<float>(channel) / 255.f;
+ });
+ return out;
+ }
+ if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
+ if (const auto colour = mf->colours.find(in); colour != mf->colours.end()) {
+ return {colour->second, 1};
+ }
+ }
+ throw std::runtime_error("No such asset factory colour");
+}
+bool
+AssetFactory::persist(Persistence::PersistenceStore & store)
+{
+ using MapObjects = Persistence::MapByMember<Shapes, std::shared_ptr<Object>>;
+ using MapAssets = Persistence::MapByMember<Assets>;
+ return STORE_TYPE && STORE_NAME_HELPER("object", shapes, MapObjects)
+ && STORE_NAME_HELPER("asset", assets, MapAssets);
+}
diff --git a/assetFactory/assetFactory.h b/assetFactory/assetFactory.h
new file mode 100644
index 0000000..b47d408
--- /dev/null
+++ b/assetFactory/assetFactory.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "asset.h"
+#include "persistence.h"
+#include "shape.h"
+#include <filesystem>
+
+class AssetFactory : public Persistence::Persistable {
+public:
+ using Shapes = std::map<std::string, Shape::Ptr, std::less<>>;
+ using Assets = std::map<std::string, Asset::Ptr, std::less<>>;
+ using Colour = glm::vec3;
+ using ColourAlpha = glm::vec4;
+ using Colours = std::map<std::string, Colour, std::less<>>;
+
+ AssetFactory();
+ [[nodiscard]] static std::shared_ptr<AssetFactory> loadXML(const std::filesystem::path &);
+ [[nodiscard]] ColourAlpha parseColour(std::string_view) const;
+
+ Shapes shapes;
+ Assets assets;
+ Colours colours;
+
+ static Colours parseX11RGB(const char * rgbtxtpath);
+ static void normalizeColourName(std::string &);
+
+private:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<AssetFactory>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+};
diff --git a/assetFactory/cuboid.cpp b/assetFactory/cuboid.cpp
new file mode 100644
index 0000000..24fe4a4
--- /dev/null
+++ b/assetFactory/cuboid.cpp
@@ -0,0 +1,29 @@
+#include "cuboid.h"
+#include "modelFactoryMesh.h"
+
+Cuboid::CreatedFaces
+Cuboid::createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const
+{
+ static constexpr std::array<glm::vec3, 8> VERTICES {{
+ // bottom
+ {n, n, z},
+ {n, y, z},
+ {y, y, z},
+ {y, n, z},
+ // top
+ {y, n, o},
+ {y, y, o},
+ {n, y, o},
+ {n, n, o},
+ }};
+
+ const auto vhs = addMutatedToMesh(mesh, VERTICES, mutation);
+ return {
+ mesh.add_namedFace("top", {vhs[4], vhs[5], vhs[6], vhs[7]}),
+ mesh.add_namedFace("bottom", {vhs[0], vhs[1], vhs[2], vhs[3]}),
+ mesh.add_namedFace("left", {vhs[0], vhs[7], vhs[6], vhs[1]}),
+ mesh.add_namedFace("right", {vhs[2], vhs[5], vhs[4], vhs[3]}),
+ mesh.add_namedFace("front", {vhs[0], vhs[3], vhs[4], vhs[7]}),
+ mesh.add_namedFace("back", {vhs[2], vhs[1], vhs[6], vhs[5]}),
+ };
+}
diff --git a/assetFactory/cuboid.h b/assetFactory/cuboid.h
new file mode 100644
index 0000000..5a4072a
--- /dev/null
+++ b/assetFactory/cuboid.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "shape.h"
+
+class Cuboid : public Shape {
+public:
+ CreatedFaces createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const override;
+};
diff --git a/assetFactory/cylinder.cpp b/assetFactory/cylinder.cpp
new file mode 100644
index 0000000..cf0dbfb
--- /dev/null
+++ b/assetFactory/cylinder.cpp
@@ -0,0 +1,46 @@
+#include "cylinder.h"
+#include "maths.h"
+#include "modelFactoryMesh.h"
+
+Cylinder::CreatedFaces
+Cylinder::createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const
+{
+ const glm::vec2 scale {std::accumulate(&mutation[0][0], &mutation[0][3], 0.f),
+ std::accumulate(&mutation[1][0], &mutation[1][3], 0.f)};
+ const unsigned int P = static_cast<unsigned int>(std::round(15.F * std::sqrt(glm::length(scale))));
+ std::vector<OpenMesh::VertexHandle> bottom(P), top(P);
+ std::generate_n(bottom.begin(), P, [a = 0.f, step = two_pi / static_cast<float>(P), &mesh, &mutation]() mutable {
+ const auto xy = sincosf(a += step) * .5F;
+ const auto xyz = (xy ^ 0) % mutation;
+ return mesh.add_vertex({xyz.x, xyz.y, xyz.z});
+ });
+ std::generate_n(top.begin(), P, [a = 0.f, step = two_pi / static_cast<float>(P), &mesh, &mutation]() mutable {
+ const auto xy = sincosf(a -= step) * .5F;
+ const auto xyz = (xy ^ 1) % mutation;
+ return mesh.add_vertex({xyz.x, xyz.y, xyz.z});
+ });
+ CreatedFaces surface;
+ std::generate_n(std::inserter(surface, surface.end()), P,
+ [a = 0.f, step = two_pi / static_cast<float>(P), &mesh, &mutation]() mutable {
+ const auto xy1 = sincosf(a) * .5F;
+ const auto xy2 = sincosf(a -= step) * .5F;
+ const auto xyz1b = (xy1 ^ 0) % mutation;
+ const auto xyz2b = (xy2 ^ 0) % mutation;
+ const auto xyz1t = (xy1 ^ 1) % mutation;
+ const auto xyz2t = (xy2 ^ 1) % mutation;
+ return mesh.add_namedFace("edge",
+ {
+ mesh.add_vertex({xyz1b.x, xyz1b.y, xyz1b.z}),
+ mesh.add_vertex({xyz2b.x, xyz2b.y, xyz2b.z}),
+ mesh.add_vertex({xyz2t.x, xyz2t.y, xyz2t.z}),
+ mesh.add_vertex({xyz1t.x, xyz1t.y, xyz1t.z}),
+ });
+ });
+ for (const auto & [name, face] : surface) {
+ mesh.property(mesh.smoothFaceProperty, face) = true;
+ }
+ surface.insert(mesh.add_namedFace("bottom", bottom));
+ surface.insert(mesh.add_namedFace("top", top));
+
+ return surface;
+}
diff --git a/assetFactory/cylinder.h b/assetFactory/cylinder.h
new file mode 100644
index 0000000..65ca5e5
--- /dev/null
+++ b/assetFactory/cylinder.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "shape.h"
+
+class Cylinder : public Shape {
+public:
+ CreatedFaces createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const override;
+};
diff --git a/assetFactory/faceController.cpp b/assetFactory/faceController.cpp
new file mode 100644
index 0000000..b485c80
--- /dev/null
+++ b/assetFactory/faceController.cpp
@@ -0,0 +1,91 @@
+#include "faceController.h"
+#include "collections.hpp"
+#include "maths.h"
+#include "modelFactoryMesh.h"
+
+void
+FaceController::apply(ModelFactoryMesh & mesh, const StyleStack & parents, const std::string & name,
+ Shape::CreatedFaces & faces) const
+{
+ const auto getAdjacentFaceName = [&mesh](const auto & ofrange, OpenMesh::FaceHandle nf) -> std::string {
+ const auto nfrange = mesh.ff_range(nf);
+ if (const auto target = std::find_first_of(ofrange.begin(), ofrange.end(), nfrange.begin(), nfrange.end());
+ target != ofrange.end()) {
+ return mesh.property(mesh.nameFaceProperty, *target);
+ };
+ return {};
+ };
+
+ const auto controlledFaces {materializeRange(faces.equal_range(name))};
+ if (controlledFaces.empty()) {
+ throw std::runtime_error("Named face(s) do not exist: " + name);
+ }
+
+ if (!type.empty()) {
+ const auto mutation = getMatrix();
+ faces.erase(name);
+ for (const auto & cf : controlledFaces) {
+ // get points
+ const auto baseVertices {materializeRange(mesh.fv_range(cf.second))};
+ auto points = std::accumulate(baseVertices.begin(), baseVertices.end(), std::vector<glm::vec3> {},
+ [&mesh](auto && out, auto && v) {
+ out.push_back(mesh.point(v));
+ return std::move(out);
+ });
+ const auto vertexCount = points.size();
+ const auto centre
+ = std::accumulate(points.begin(), points.end(), glm::vec3 {}) / static_cast<float>(vertexCount);
+ if (type == "extrude") {
+ Shape::CreatedFaces newFaces;
+ // mutate points
+ std::for_each(points.begin(), points.end(), [&mutation, &centre](auto && p) {
+ p = centre + ((p - centre) % mutation);
+ });
+ // create new vertices
+ std::vector<OpenMesh::VertexHandle> vertices;
+ std::transform(points.begin(), points.end(), std::back_inserter(vertices), [&mesh](auto && p) {
+ return mesh.add_vertex({p.x, p.y, p.z});
+ });
+ // create new faces
+ const auto ofrange = materializeRange(mesh.ff_range(cf.second));
+ mesh.delete_face(cf.second);
+ for (size_t idx {}; idx < vertexCount; ++idx) {
+ const auto next = (idx + 1) % vertexCount;
+ const auto newFace
+ = mesh.add_face({baseVertices[idx], baseVertices[next], vertices[next], vertices[idx]});
+ auto & name = mesh.property(mesh.nameFaceProperty, newFace);
+ name = getAdjacentFaceName(ofrange, newFace);
+ newFaces.emplace(name, newFace);
+ }
+ newFaces.emplace(name, mesh.add_face(vertices));
+ if (smooth) {
+ for (const auto & [name, face] : newFaces) {
+ mesh.property(mesh.smoothFaceProperty, face) = true;
+ }
+ }
+ applyStyle(mesh, parents + this, newFaces);
+ for (const auto & [name, faceController] : faceControllers) {
+ faceController->apply(mesh, parents + this, name, newFaces);
+ }
+ faces.merge(std::move(newFaces));
+ }
+ else {
+ mesh.property(mesh.smoothFaceProperty, cf.second) = smooth;
+ applyStyle(mesh, parents + this, cf.second);
+ }
+ }
+ }
+ else {
+ for (const auto & cf : controlledFaces) {
+ applyStyle(mesh, parents + this, cf.second);
+ }
+ }
+}
+
+bool
+FaceController::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(id) && Style::persist(store) && STORE_MEMBER(type) && STORE_MEMBER(smooth)
+ && STORE_MEMBER(scale) && STORE_MEMBER(position) && STORE_MEMBER(rotation)
+ && STORE_NAME_HELPER("face", faceControllers, Persistence::MapByMember<FaceControllers>);
+}
diff --git a/assetFactory/faceController.h b/assetFactory/faceController.h
new file mode 100644
index 0000000..10a226a
--- /dev/null
+++ b/assetFactory/faceController.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "modelFactoryMesh_fwd.h"
+#include "mutation.h"
+#include "persistence.h"
+#include "shape.h"
+#include "style.h"
+#include <map>
+#include <string>
+
+class FaceController : public Mutation, public Style, public Persistence::Persistable {
+public:
+ using FaceControllers = std::map<std::string, std::unique_ptr<FaceController>>;
+
+ void apply(ModelFactoryMesh & mesh, const Style::StyleStack & parents, const std::string & name,
+ Shape::CreatedFaces & faces) const;
+
+ std::string id;
+ std::string type;
+ bool smooth {false};
+ FaceControllers faceControllers;
+
+private:
+ friend Persistence::SelectionPtrBase<std::unique_ptr<FaceController>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+ std::string
+ getId() const override
+ {
+ return {};
+ };
+};
diff --git a/assetFactory/factoryMesh.cpp b/assetFactory/factoryMesh.cpp
new file mode 100644
index 0000000..0cfed85
--- /dev/null
+++ b/assetFactory/factoryMesh.cpp
@@ -0,0 +1,40 @@
+#include "factoryMesh.h"
+#include "collections.hpp"
+#include "gfx/models/vertex.hpp"
+#include "modelFactoryMesh.h"
+#include <glm/ext/matrix_transform.hpp>
+
+Mesh::Ptr
+FactoryMesh::createMesh() const
+{
+ constexpr glm::vec2 NullUV {};
+
+ ModelFactoryMesh mesh;
+ for (const auto & use : uses) {
+ use->createMesh(mesh, glm::identity<Mutation::Matrix>());
+ }
+ mesh.garbage_collection();
+
+ mesh.triangulate();
+ mesh.update_face_normals();
+ mesh.update_vertex_normals();
+ std::vector<Vertex> vertices;
+ for (const auto & face : mesh.faces()) {
+ const auto smooth = mesh.property(mesh.smoothFaceProperty, face);
+ const auto colour = mesh.color(face);
+ for (const auto & vertex : mesh.fv_range(face)) {
+ vertices.emplace_back(mesh.point(vertex), NullUV,
+ smooth ? mesh.property(mesh.vertex_normals_pph(), vertex)
+ : mesh.property(mesh.face_normals_pph(), face),
+ colour);
+ }
+ }
+ return std::make_shared<Mesh>(vertices, vectorOfN(vertices.size()));
+}
+
+bool
+FactoryMesh::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(id) && STORE_MEMBER(size)
+ && STORE_NAME_HELPER("use", uses, Persistence::Appender<Use::Collection>);
+}
diff --git a/assetFactory/factoryMesh.h b/assetFactory/factoryMesh.h
new file mode 100644
index 0000000..bbeb870
--- /dev/null
+++ b/assetFactory/factoryMesh.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "gfx/models/mesh.h"
+#include "stdTypeDefs.hpp"
+#include "use.h"
+
+class FactoryMesh : public Persistence::Persistable, public StdTypeDefs<FactoryMesh> {
+public:
+ Mesh::Ptr createMesh() const;
+
+ std::string id;
+ glm::vec3 size;
+ Use::Collection uses;
+
+private:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<FactoryMesh>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+};
diff --git a/assetFactory/modelFactoryMesh.cpp b/assetFactory/modelFactoryMesh.cpp
new file mode 100644
index 0000000..f9ee6a0
--- /dev/null
+++ b/assetFactory/modelFactoryMesh.cpp
@@ -0,0 +1,15 @@
+#include "modelFactoryMesh.h"
+
+ModelFactoryMesh::ModelFactoryMesh()
+{
+ add_property(smoothFaceProperty);
+ add_property(nameFaceProperty);
+}
+
+std::pair<std::string, OpenMesh::FaceHandle>
+ModelFactoryMesh::add_namedFace(std::string name, std::vector<OpenMesh::VertexHandle> p)
+{
+ const auto handle = add_face(std::move(p));
+ property(nameFaceProperty, handle) = name;
+ return std::make_pair(name, handle);
+}
diff --git a/assetFactory/modelFactoryMesh.h b/assetFactory/modelFactoryMesh.h
new file mode 100644
index 0000000..258913b
--- /dev/null
+++ b/assetFactory/modelFactoryMesh.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "modelFactoryMesh_fwd.h"
+#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
+#include <OpenMesh/Core/Mesh/Traits.hh>
+#include <glm/geometric.hpp>
+#include <glm/vec3.hpp>
+#include <glm/vec4.hpp>
+
+namespace OpenMesh {
+ template<typename Scalar, int DIM> struct glmvec : public VectorT<Scalar, DIM> {
+ using VectorT<Scalar, DIM>::VectorT;
+ glmvec(const VectorT<Scalar, DIM> & v) : VectorT<Scalar, DIM> {v} { }
+ operator glm::vec<DIM, Scalar>() const
+ {
+ glm::vec<DIM, Scalar> out;
+ std::copy(this->begin(), this->end(), &out[0]);
+ return out;
+ }
+ };
+}
+
+struct ModelFactoryTraits : public OpenMesh::DefaultTraits {
+ FaceAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status | OpenMesh::Attributes::Color);
+ EdgeAttributes(OpenMesh::Attributes::Status);
+ VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status);
+ using Point = OpenMesh::glmvec<float, 3>;
+ using Normal = OpenMesh::glmvec<float, 3>;
+ using Color = glm::vec4;
+};
+
+struct ModelFactoryMesh : public OpenMesh::PolyMesh_ArrayKernelT<ModelFactoryTraits> {
+ ModelFactoryMesh();
+
+ OpenMesh::FPropHandleT<bool> smoothFaceProperty;
+ OpenMesh::FPropHandleT<std::string> nameFaceProperty;
+
+ std::pair<std::string, OpenMesh::FaceHandle> add_namedFace(std::string name, std::vector<OpenMesh::VertexHandle> p);
+};
diff --git a/assetFactory/modelFactoryMesh_fwd.h b/assetFactory/modelFactoryMesh_fwd.h
new file mode 100644
index 0000000..ac10f2d
--- /dev/null
+++ b/assetFactory/modelFactoryMesh_fwd.h
@@ -0,0 +1,3 @@
+#pragma once
+
+struct ModelFactoryMesh;
diff --git a/assetFactory/mutation.cpp b/assetFactory/mutation.cpp
new file mode 100644
index 0000000..21d2a24
--- /dev/null
+++ b/assetFactory/mutation.cpp
@@ -0,0 +1,10 @@
+#include "mutation.h"
+#include <glm/gtx/transform.hpp>
+#include <maths.h>
+
+Mutation::Matrix
+Mutation::getMatrix() const
+{
+ return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr(rotation)
+ * glm::scale(glm::identity<Matrix>(), scale);
+}
diff --git a/assetFactory/mutation.h b/assetFactory/mutation.h
new file mode 100644
index 0000000..440fab0
--- /dev/null
+++ b/assetFactory/mutation.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <glm/mat4x4.hpp>
+#include <glm/vec3.hpp>
+
+struct Mutation {
+ using Matrix = glm::mat4;
+
+ Matrix getMatrix() const;
+
+ glm::vec3 position {};
+ glm::vec3 rotation {};
+ glm::vec3 scale {1};
+};
diff --git a/assetFactory/object.cpp b/assetFactory/object.cpp
new file mode 100644
index 0000000..ae5a301
--- /dev/null
+++ b/assetFactory/object.cpp
@@ -0,0 +1,23 @@
+#include "object.h"
+#include <algorithm>
+
+Object::Object(std::string i) : id {std::move(i)} { }
+
+Object::CreatedFaces
+Object::createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const
+{
+ CreatedFaces faces;
+ for (const auto & use : uses) {
+ auto useFaces = use->createMesh(mesh, mutation);
+ std::transform(useFaces.begin(), useFaces.end(), std::inserter(faces, faces.end()), [this](auto && face) {
+ return std::make_pair(id + ":" + face.first, std::move(face.second));
+ });
+ }
+ return faces;
+}
+
+bool
+Object::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(id) && STORE_NAME_HELPER("use", uses, Persistence::Appender<Use::Collection>);
+}
diff --git a/assetFactory/object.h b/assetFactory/object.h
new file mode 100644
index 0000000..f3726c7
--- /dev/null
+++ b/assetFactory/object.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "persistence.h"
+#include "shape.h"
+#include "stdTypeDefs.hpp"
+#include "use.h"
+
+class Object : public StdTypeDefs<Object>, public Shape, public Persistence::Persistable {
+public:
+ Object() = default;
+ Object(std::string i);
+
+ CreatedFaces createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const override;
+
+ Use::Collection uses;
+ std::string id;
+
+private:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<Object>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+ std::string
+ getId() const override
+ {
+ return id;
+ };
+};
diff --git a/assetFactory/plane.cpp b/assetFactory/plane.cpp
new file mode 100644
index 0000000..563c4e9
--- /dev/null
+++ b/assetFactory/plane.cpp
@@ -0,0 +1,15 @@
+#include "plane.h"
+#include "modelFactoryMesh.h"
+
+Plane::CreatedFaces
+Plane::createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const
+{
+ static constexpr std::array<glm::vec3, 4> VERTICES {{
+ {n, n, z},
+ {y, n, z},
+ {y, y, z},
+ {n, y, z},
+ }};
+
+ return {mesh.add_namedFace("plane", addMutatedToMesh(mesh, VERTICES, mutation))};
+}
diff --git a/assetFactory/plane.h b/assetFactory/plane.h
new file mode 100644
index 0000000..5e93ee4
--- /dev/null
+++ b/assetFactory/plane.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "shape.h"
+
+class Plane : public Shape {
+public:
+ CreatedFaces createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const override;
+};
diff --git a/assetFactory/shape.cpp b/assetFactory/shape.cpp
new file mode 100644
index 0000000..f6e55e8
--- /dev/null
+++ b/assetFactory/shape.cpp
@@ -0,0 +1,17 @@
+#include "shape.h"
+#include "gfx/models/vertex.hpp"
+#include "maths.h"
+#include "modelFactoryMesh.h"
+#include "shape.h"
+
+std::vector<OpenMesh::VertexHandle>
+Shape::addMutatedToMesh(
+ ModelFactoryMesh & mesh, const std::span<const glm::vec3> vertices, const Mutation::Matrix & mutation)
+{
+ std::vector<OpenMesh::VertexHandle> vhs;
+ std::transform(vertices.begin(), vertices.end(), std::back_inserter(vhs), [&mesh, &mutation](const auto & v) {
+ const auto p = v % mutation;
+ return mesh.add_vertex({p.x, p.y, p.z});
+ });
+ return vhs;
+}
diff --git a/assetFactory/shape.h b/assetFactory/shape.h
new file mode 100644
index 0000000..5a2b59c
--- /dev/null
+++ b/assetFactory/shape.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "modelFactoryMesh_fwd.h"
+#include "mutation.h"
+#include "stdTypeDefs.hpp"
+#include <OpenMesh/Core/Mesh/Handles.hh>
+#include <map>
+#include <span>
+#include <string>
+
+class Vertex;
+
+class Shape : public StdTypeDefs<Shape> {
+public:
+ using CreatedFaces = std::multimap<std::string, OpenMesh::FaceHandle>;
+
+ static constexpr float z {}, y {.5}, n {-y}, o {1};
+
+ virtual ~Shape() = default;
+
+ virtual CreatedFaces createMesh(ModelFactoryMesh &, const Mutation::Matrix & mutation) const = 0;
+
+ static std::vector<OpenMesh::VertexHandle> addMutatedToMesh(
+ ModelFactoryMesh & mesh, const std::span<const glm::vec3> vertices, const Mutation::Matrix & mutation);
+};
diff --git a/assetFactory/style.cpp b/assetFactory/style.cpp
new file mode 100644
index 0000000..fc5c34e
--- /dev/null
+++ b/assetFactory/style.cpp
@@ -0,0 +1,46 @@
+#include "style.h"
+#include "assetFactory.h"
+
+void
+Style::applyStyle(ModelFactoryMesh & mesh, const StyleStack & parents, const Shape::CreatedFaces & faces) const
+{
+ if (const auto effectiveColour = getProperty(parents, &Style::colour,
+ [](auto && style) {
+ return style->colour.a > 0;
+ });
+ effectiveColour.has_value()) {
+ for (const auto & face : faces) {
+ mesh.set_color(face.second, effectiveColour->get());
+ }
+ }
+}
+
+void
+Style::applyStyle(ModelFactoryMesh & mesh, const StyleStack & parents, const ModelFactoryMesh::FaceHandle & face) const
+{
+ if (const auto effectiveColour = getProperty(parents, &Style::colour,
+ [](auto && style) {
+ return style->colour.a > 0;
+ });
+ effectiveColour.has_value()) {
+ mesh.set_color(face, effectiveColour->get());
+ }
+}
+
+bool
+Style::persist(Persistence::PersistenceStore & store)
+{
+ struct ColourParser : public Persistence::SelectionV<ColourAlpha> {
+ using Persistence::SelectionV<ColourAlpha>::SelectionV;
+ using Persistence::SelectionV<ColourAlpha>::setValue;
+ void
+ setValue(std::string && str) override
+ {
+ if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
+ v = mf->parseColour(str);
+ }
+ }
+ };
+
+ return STORE_HELPER(colour, ColourParser);
+}
diff --git a/assetFactory/style.h b/assetFactory/style.h
new file mode 100644
index 0000000..e8fd012
--- /dev/null
+++ b/assetFactory/style.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "modelFactoryMesh.h"
+#include "persistence.h"
+#include "shape.h"
+#include <optional>
+#include <string>
+#include <utility>
+
+class Style {
+public:
+ using StyleStack = std::vector<const Style *>;
+ using Colour = glm::vec3;
+ using ColourAlpha = glm::vec4;
+
+ void applyStyle(ModelFactoryMesh &, const StyleStack & parents, const Shape::CreatedFaces &) const;
+ void applyStyle(ModelFactoryMesh &, const StyleStack & parents, const ModelFactoryMesh::FaceHandle &) const;
+
+ template<typename T>
+ static std::optional<std::reference_wrapper<const T>>
+ getProperty(const StyleStack & parents, T Style::*member, auto && test)
+ {
+ if (const auto itr = std::find_if(parents.rbegin(), parents.rend(), std::forward<decltype(test)>(test));
+ itr != parents.rend()) {
+ return (*itr)->*member;
+ }
+ return {};
+ }
+
+ ColourAlpha colour {};
+
+protected:
+ bool persist(Persistence::PersistenceStore & store);
+};
diff --git a/assetFactory/use.cpp b/assetFactory/use.cpp
new file mode 100644
index 0000000..708e310
--- /dev/null
+++ b/assetFactory/use.cpp
@@ -0,0 +1,33 @@
+#include "use.h"
+#include "assetFactory.h"
+
+Shape::CreatedFaces
+Use::createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const
+{
+ auto faces = type->createMesh(mesh, mutation * getMatrix());
+ applyStyle(mesh, {this}, faces);
+ for (const auto & [name, faceController] : faceControllers) {
+ faceController->apply(mesh, {this}, name, faces);
+ }
+ return faces;
+}
+
+struct Lookup : public Persistence::SelectionV<Shape::CPtr> {
+ using Persistence::SelectionV<Shape::CPtr>::SelectionV;
+ using Persistence::SelectionV<Shape::CPtr>::setValue;
+ void
+ setValue(std::string && str) override
+ {
+ if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
+ v = mf->shapes.at(str);
+ }
+ }
+};
+
+bool
+Use::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_HELPER(type, Lookup) && STORE_MEMBER(position) && STORE_MEMBER(scale)
+ && STORE_MEMBER(rotation) && Style::persist(store)
+ && STORE_NAME_HELPER("face", faceControllers, Persistence::MapByMember<FaceControllers>);
+}
diff --git a/assetFactory/use.h b/assetFactory/use.h
new file mode 100644
index 0000000..5e4c35f
--- /dev/null
+++ b/assetFactory/use.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "faceController.h"
+#include "modelFactoryMesh_fwd.h"
+#include "persistence.h"
+#include "shape.h"
+#include "stdTypeDefs.hpp"
+#include "style.h"
+
+class Use : public StdTypeDefs<Use>, public Mutation, public Style, public Persistence::Persistable {
+public:
+ using FaceControllers = std::map<std::string, std::unique_ptr<FaceController>>;
+
+ Shape::CreatedFaces createMesh(ModelFactoryMesh & mesh, const Mutation::Matrix & mutation) const;
+
+ Shape::CPtr type;
+ FaceControllers faceControllers;
+
+private:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<Use>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+ std::string
+ getId() const override
+ {
+ return {};
+ };
+};
diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp
index bcde68f..77907be 100644
--- a/game/vehicles/railVehicle.cpp
+++ b/game/vehicles/railVehicle.cpp
@@ -11,6 +11,12 @@
#include <memory>
#include <ray.hpp>
+RailVehicle::RailVehicle(RailVehicleClassPtr rvc) : rvClass {std::move(rvc)}
+{
+ bogies.front().pos.y = rvClass->wheelBase / 2.F;
+ bogies.back().pos.y = -bogies.front().pos.y;
+}
+
void
RailVehicle::render(const SceneShader & shader) const
{
@@ -20,7 +26,7 @@ RailVehicle::render(const SceneShader & shader) const
void
RailVehicle::shadows(const ShadowMapper & shadowMapper) const
{
- rvClass->shadows(shadowMapper, location);
+ rvClass->shadows(shadowMapper, location, bogies);
}
void
diff --git a/game/vehicles/railVehicle.h b/game/vehicles/railVehicle.h
index f172a28..bbf4df9 100644
--- a/game/vehicles/railVehicle.h
+++ b/game/vehicles/railVehicle.h
@@ -16,7 +16,7 @@ class Ray;
class Train;
class RailVehicle : public Renderable, Selectable {
public:
- explicit RailVehicle(RailVehicleClassPtr rvc) : rvClass {std::move(rvc)} { }
+ explicit RailVehicle(RailVehicleClassPtr rvc);
void move(const Train *, float & trailBy);
diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp
index dff1416..41ef5e9 100644
--- a/game/vehicles/railVehicleClass.cpp
+++ b/game/vehicles/railVehicleClass.cpp
@@ -39,12 +39,24 @@ RailVehicleClass::RailVehicleClass(std::unique_ptr<ObjParser> o, std::shared_ptr
bogies[1] = m.at("Bogie2");
}
+RailVehicleClass::RailVehicleClass() { }
+
+bool
+RailVehicleClass::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(length) && STORE_MEMBER(wheelBase) && STORE_MEMBER(maxSpeed)
+ && STORE_NAME_HELPER("bogie", bogies, Asset::MeshArrayConstruct)
+ && STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store);
+}
+
void
RailVehicleClass::render(
const SceneShader & shader, const Location & location, const std::array<Location, 2> & bl) const
{
shader.basic.use(location);
- texture->bind();
+ if (texture) {
+ texture->bind();
+ }
bodyMesh->Draw();
for (auto b = 0U; b < bogies.size(); ++b) {
shader.basic.setModel(bl[b]);
@@ -52,10 +64,15 @@ RailVehicleClass::render(
}
}
void
-RailVehicleClass::shadows(const ShadowMapper & shadowMapper, const Location & location) const
+RailVehicleClass::shadows(
+ const ShadowMapper & shadowMapper, const Location & location, const std::array<Location, 2> & bl) const
{
shadowMapper.dynamicPoint.use(location);
bodyMesh->Draw();
+ for (auto b = 0U; b < bogies.size(); ++b) {
+ shadowMapper.dynamicPoint.setModel(bl[b]);
+ bogies[b]->Draw();
+ }
}
float
diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h
index bd74ab9..a2222fb 100644
--- a/game/vehicles/railVehicleClass.h
+++ b/game/vehicles/railVehicleClass.h
@@ -1,5 +1,6 @@
#pragma once
+#include "assetFactory/asset.h"
#include "gfx/models/mesh.h"
#include <array>
#include <memory>
@@ -11,12 +12,13 @@ class Texture;
class ObjParser;
class Location;
-class RailVehicleClass {
+class RailVehicleClass : public Asset {
public:
explicit RailVehicleClass(const std::string & name);
+ RailVehicleClass();
void render(const SceneShader &, const Location &, const std::array<Location, 2> &) const;
- void shadows(const ShadowMapper &, const Location &) const;
+ void shadows(const ShadowMapper &, const Location &, const std::array<Location, 2> &) const;
std::array<Mesh::Ptr, 2> bogies;
Mesh::Ptr bodyMesh;
@@ -25,6 +27,10 @@ public:
float length;
float maxSpeed;
+protected:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<RailVehicleClass>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+
private:
RailVehicleClass(std::unique_ptr<ObjParser> obj, std::shared_ptr<Texture>);
static float bogieOffset(ObjParser & o);
diff --git a/gfx/gl/shaders/basicShader.fs b/gfx/gl/shaders/basicShader.fs
index 9c4945b..93f0a3f 100644
--- a/gfx/gl/shaders/basicShader.fs
+++ b/gfx/gl/shaders/basicShader.fs
@@ -3,6 +3,7 @@
in vec3 FragPos;
in vec2 TexCoords;
in vec3 Normal;
+in vec4 Colour;
out vec4 gPosition;
out vec4 gNormal;
@@ -16,5 +17,5 @@ main()
float clear = round(texture(texture0, TexCoords).a);
gPosition = vec4(FragPos, clear);
gNormal = vec4(Normal, clear);
- gAlbedoSpec = texture(texture0, TexCoords);
+ gAlbedoSpec = mix(texture(texture0, TexCoords), vec4(Colour.rgb, 1), Colour.a);
}
diff --git a/gfx/gl/shaders/basicShader.vs b/gfx/gl/shaders/basicShader.vs
index bc7ea5d..ff9a401 100644
--- a/gfx/gl/shaders/basicShader.vs
+++ b/gfx/gl/shaders/basicShader.vs
@@ -3,10 +3,12 @@
in vec3 position;
in vec2 texCoord;
in vec3 normal;
+in vec4 colour;
out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;
+out vec4 Colour;
uniform mat4 viewProjection;
uniform mat4 model;
@@ -19,6 +21,7 @@ main()
FragPos = worldPos.xyz;
TexCoords = texCoord;
Normal = (model * vec4(normal, 0.0)).xyz;
+ Colour = colour;
gl_Position = viewProjection * worldPos;
}
diff --git a/gfx/gl/shaders/landmassShader.vs b/gfx/gl/shaders/landmassShader.vs
index 6bf39b0..30c4ef4 100644
--- a/gfx/gl/shaders/landmassShader.vs
+++ b/gfx/gl/shaders/landmassShader.vs
@@ -3,10 +3,12 @@
in vec3 position;
in vec2 texCoord;
in vec3 normal;
+in vec4 colour;
out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;
+out vec4 Colour;
uniform mat4 viewProjection;
@@ -16,6 +18,7 @@ main()
FragPos = position;
TexCoords = texCoord;
Normal = normal;
+ Colour = colour;
gl_Position = viewProjection * vec4(position, 1.0);
}
diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp
index 1787ee6..7649a54 100644
--- a/gfx/gl/shadowMapper.cpp
+++ b/gfx/gl/shadowMapper.cpp
@@ -184,6 +184,13 @@ void
ShadowMapper::DynamicPoint::use(const Location & location) const
{
glUseProgram(*this);
+ setModel(location);
+ const auto model = glm::translate(location.pos) * rotate_ypr(location.rot);
+ glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
+}
+void
+ShadowMapper::DynamicPoint::setModel(const Location & location) const
+{
const auto model = glm::translate(location.pos) * rotate_ypr(location.rot);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
}
diff --git a/gfx/gl/shadowMapper.h b/gfx/gl/shadowMapper.h
index b4793ed..a5c2c7b 100644
--- a/gfx/gl/shadowMapper.h
+++ b/gfx/gl/shadowMapper.h
@@ -34,6 +34,7 @@ public:
DynamicPoint();
void setViewProjection(const glm::mat4 &) const;
void use(const Location &) const;
+ void setModel(const Location &) const;
private:
RequiredUniformLocation viewProjectionLoc;
diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp
index 85be84c..3db1ad5 100644
--- a/gfx/models/mesh.cpp
+++ b/gfx/models/mesh.cpp
@@ -7,7 +7,7 @@
Mesh::Mesh(const std::span<const Vertex> vertices, const std::span<const unsigned int> indices, GLenum m) :
m_numIndices {static_cast<GLsizei>(indices.size())}, mode {m}
{
- VertexArrayObject<Vertex>::configure<&Vertex::pos, &Vertex::texCoord, &Vertex::normal>(
+ VertexArrayObject<Vertex>::configure<&Vertex::pos, &Vertex::texCoord, &Vertex::normal, &Vertex::colour>(
m_vertexArrayObject, m_vertexArrayBuffers[0], m_vertexArrayBuffers[1], vertices, indices);
}
diff --git a/gfx/models/vertex.hpp b/gfx/models/vertex.hpp
index 095c82f..325aab1 100644
--- a/gfx/models/vertex.hpp
+++ b/gfx/models/vertex.hpp
@@ -4,12 +4,13 @@
class Vertex {
public:
- constexpr Vertex(glm::vec3 pos, glm::vec2 texCoord, glm::vec3 normal) :
- pos {std::move(pos)}, texCoord {std::move(texCoord)}, normal {std::move(normal)}
+ constexpr Vertex(glm::vec3 pos, glm::vec2 texCoord, glm::vec3 normal, glm::vec4 colour = {}) :
+ pos {std::move(pos)}, texCoord {std::move(texCoord)}, normal {std::move(normal)}, colour {std::move(colour)}
{
}
glm::vec3 pos;
glm::vec2 texCoord;
glm::vec3 normal;
+ glm::vec4 colour;
};
diff --git a/lib/collections.hpp b/lib/collections.hpp
index 47967b2..16870be 100644
--- a/lib/collections.hpp
+++ b/lib/collections.hpp
@@ -15,9 +15,11 @@ concept SequentialCollection = requires(T c) {
c.data()
} -> std::same_as<const E *>;
};
+template<typename T>
+concept IterableCollection = std::is_same_v<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>;
template<typename T, std::size_t first, std::size_t second>
-constexpr std::array<T, first + second>
+[[nodiscard]] constexpr std::array<T, first + second>
operator+(const std::array<T, first> & a, const std::array<T, second> & b)
{
std::array<T, first + second> r;
@@ -28,7 +30,7 @@ operator+(const std::array<T, first> & a, const std::array<T, second> & b)
}
template<typename T, typename V, std::size_t first, std::size_t second>
-constexpr std::array<std::pair<T, V>, first * second>
+[[nodiscard]] constexpr std::array<std::pair<T, V>, first * second>
operator*(const std::array<T, first> & a, const std::array<V, second> & b)
{
std::array<std::pair<T, V>, first * second> r;
@@ -42,7 +44,7 @@ operator*(const std::array<T, first> & a, const std::array<V, second> & b)
}
template<typename T, std::size_t N>
-constexpr auto
+[[nodiscard]] constexpr auto
operator*(const std::array<T, N> & in, auto && f)
{
std::array<decltype(f(in[0])), N> out;
@@ -53,9 +55,8 @@ operator*(const std::array<T, N> & in, auto && f)
return out;
}
-template<typename T>
constexpr auto &
-operator*=(std::span<T> & in, auto && f)
+operator*=(IterableCollection auto & in, auto && f)
{
for (auto & v : in) {
f(v);
@@ -64,7 +65,7 @@ operator*=(std::span<T> & in, auto && f)
}
template<template<typename...> typename C, typename... T>
-constexpr auto
+[[nodiscard]] constexpr auto
operator*(const C<T...> & in, auto && f)
{
C<decltype(f(in[0]))> out;
@@ -82,17 +83,17 @@ operator+=(std::vector<T...> & in, std::vector<T...> && src)
return in;
}
-template<typename... T>
-constexpr auto
-operator+(std::vector<T...> && in, std::vector<T...> && src)
+template<typename... T, typename Vn>
+[[nodiscard]] constexpr auto
+operator+(const std::vector<T...> & in, Vn && vn)
{
- in.reserve(in.size() + src.size());
- std::move(src.begin(), src.end(), std::back_inserter(in));
- return in;
+ auto out(in);
+ out.emplace_back(std::forward<Vn>(vn));
+ return out;
}
template<template<typename> typename Direction = std::plus>
-static auto
+[[nodiscard]] static auto
vectorOfN(std::integral auto N, unsigned int start = {}, unsigned int step = 1)
{
std::vector<unsigned int> v;
@@ -102,3 +103,17 @@ vectorOfN(std::integral auto N, unsigned int start = {}, unsigned int step = 1)
});
return v;
}
+
+template<template<typename...> typename Rtn = std::vector, IterableCollection In>
+[[nodiscard]] auto
+materializeRange(In && in)
+{
+ return Rtn(in.begin(), in.end());
+}
+
+template<template<typename...> typename Rtn = std::vector, typename In>
+[[nodiscard]] auto
+materializeRange(const std::pair<In, In> & in)
+{
+ return Rtn(in.first, in.second);
+}
diff --git a/lib/filesystem.cpp b/lib/filesystem.cpp
index 0e19e8d..7e8ab9c 100644
--- a/lib/filesystem.cpp
+++ b/lib/filesystem.cpp
@@ -62,4 +62,13 @@ namespace filesystem {
{
return memmap {length, prot, flags, h, static_cast<off_t>(offset)};
}
+
+ FILE *
+ checked_fopen(const char * pathname, const char * mode)
+ {
+ if (auto file = fopen(pathname, mode)) {
+ return file;
+ }
+ throw_filesystem_error("fopen", errno, pathname);
+ }
}
diff --git a/lib/filesystem.h b/lib/filesystem.h
index 0c44236..5315183 100644
--- a/lib/filesystem.h
+++ b/lib/filesystem.h
@@ -1,7 +1,9 @@
#pragma once
+#include "ptr.hpp"
#include "special_members.hpp"
#include <cstddef>
+#include <cstdio>
#include <sys/types.h>
namespace filesystem {
@@ -39,4 +41,7 @@ namespace filesystem {
private:
int h;
};
+
+ FILE * checked_fopen(const char * pathname, const char * mode);
+ using FileStar = wrapped_ptrt<FILE, &checked_fopen, &fclose>;
}
diff --git a/lib/jsonParse-persistence.h b/lib/jsonParse-persistence.h
index fa5e772..a676282 100644
--- a/lib/jsonParse-persistence.h
+++ b/lib/jsonParse-persistence.h
@@ -9,7 +9,7 @@
#include <string_view>
namespace Persistence {
- class JsonParsePersistence : public json::jsonParser {
+ class JsonParsePersistence : public json::jsonParser, ParseBase {
public:
template<typename T>
inline T
@@ -34,8 +34,6 @@ namespace Persistence {
void endArray() override;
void endObject() override;
- Stack stk;
-
template<typename T> inline void pushValue(T && value);
inline SelectionPtr & current();
};
diff --git a/lib/persistence.cpp b/lib/persistence.cpp
index 239e425..8c7c6a4 100644
--- a/lib/persistence.cpp
+++ b/lib/persistence.cpp
@@ -81,12 +81,12 @@ namespace Persistence {
void
PersistenceWrite::setType(const std::string_view tn, const Persistable * p)
{
- out.pushKey("@typeid");
+ out.pushKey("p.typeid");
out.pushValue(tn);
first = false;
if (shared) {
out.nextValue();
- out.pushKey("@id");
+ out.pushKey("p.id");
out.pushValue(p->getId());
}
}
@@ -157,4 +157,9 @@ namespace Persistence {
throw std::logic_error("Default write op shouldn't ever get called");
}
/// LCOV_EXCL_STOP
+
+ ParseBase::ParseBase() : sharedObjectsInstance {std::make_shared<SharedObjects>()}
+ {
+ sharedObjects = sharedObjectsInstance;
+ }
}
diff --git a/lib/persistence.h b/lib/persistence.h
index 4fbff4c..05cb49b 100644
--- a/lib/persistence.h
+++ b/lib/persistence.h
@@ -1,11 +1,13 @@
#pragma once
+#include <charconv>
#include <functional>
#include <glm/glm.hpp>
#include <iosfwd>
#include <map>
#include <memory>
#include <special_members.hpp>
+#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
@@ -86,13 +88,53 @@ namespace Persistence {
T & v;
};
- template<typename T> struct SelectionT : public SelectionV<T> {
+ template<typename T>
+ concept Scalar = std::is_scalar_v<T>;
+ template<typename T>
+ concept NotScalar = (!Scalar<T>);
+
+ template<Scalar T> struct SelectionT<T> : public SelectionV<T> {
+ using SelectionV<T>::SelectionV;
+ using Selection::setValue;
+
+ void
+ setValue(T evalue) override
+ {
+ std::swap(this->v, evalue);
+ }
+
+ void
+ setValue(std::string && evalue) override
+ {
+ if constexpr (std::same_as<T, bool>) {
+ using namespace std::literals;
+ if (!(this->v = evalue == "true"sv)) {
+ if (evalue != "false"sv) {
+ throw std::runtime_error("Value conversion failure");
+ }
+ }
+ }
+ else {
+ if (auto res = std::from_chars(evalue.c_str(), evalue.c_str() + evalue.length(), this->v).ec;
+ res != std::errc {}) {
+ throw std::runtime_error("Value conversion failure");
+ }
+ }
+ }
+
+ void
+ write(const Writer & out) const override
+ {
+ out.pushValue(this->v);
+ }
+ };
+
+ template<NotScalar T> struct SelectionT<T> : public SelectionV<T> {
using SelectionV<T>::SelectionV;
using Selection::setValue;
- using P = std::conditional_t<std::is_scalar_v<T>, T, T &&>;
void
- setValue(P evalue) override
+ setValue(T && evalue) override
{
std::swap(this->v, evalue);
}
@@ -113,14 +155,14 @@ namespace Persistence {
template<typename T> [[nodiscard]] inline bool persistType(const T * const, const std::type_info & ti);
enum class NameAction { Push, HandleAndContinue, Ignore };
- template<typename T>
+ template<typename Helper, typename T>
[[nodiscard]] inline bool
persistValue(const std::string_view key, T & value)
{
- SelectionT<T> s {value};
- const auto act {setName(key, s)};
+ auto s = std::make_unique<Helper>(value);
+ const auto act {setName(key, *s)};
if (act != NameAction::Ignore) {
- sel = std::make_unique<decltype(s)>(std::move(s));
+ sel = std::move(s);
if (act == NameAction::HandleAndContinue) {
selHandler();
}
@@ -187,6 +229,17 @@ namespace Persistence {
};
using SelectionV<V>::SelectionV;
+ using SelectionV<V>::setValue;
+
+ void
+ setValue(std::string && s) override
+ {
+ std::stringstream ss {std::move(s)};
+ for (glm::length_t n = 0; n < L; n += 1) {
+ ss >> this->v[n];
+ ss.get();
+ }
+ }
void
beginArray(Stack & stk) override
@@ -244,6 +297,39 @@ namespace Persistence {
}
};
+ template<typename Map, typename Type = typename Map::mapped_type, auto Key = &Type::element_type::id>
+ struct MapByMember : public Persistence::SelectionT<Type> {
+ MapByMember(Map & m) : Persistence::SelectionT<Type> {s}, map {m} { }
+
+ using Persistence::SelectionT<Type>::SelectionT;
+ void
+ endObject(Persistence::Stack & stk) override
+ {
+ map.emplace(std::invoke(Key, s), std::move(s));
+ stk.pop();
+ }
+
+ private:
+ Type s;
+ Map & map;
+ };
+
+ template<typename Container, typename Type = typename Container::value_type>
+ struct Appender : public Persistence::SelectionT<Type> {
+ Appender(Container & c) : Persistence::SelectionT<Type> {s}, container {c} { }
+ using Persistence::SelectionT<Type>::SelectionT;
+ void
+ endObject(Persistence::Stack & stk) override
+ {
+ container.emplace_back(std::move(s));
+ stk.pop();
+ }
+
+ private:
+ Type s;
+ Container & container;
+ };
+
struct Persistable {
Persistable() = default;
virtual ~Persistable() = default;
@@ -289,13 +375,41 @@ namespace Persistence {
return true;
}
+ class ParseBase {
+ public:
+ using SharedObjects = std::map<std::string, std::shared_ptr<Persistable>>;
+ using SharedObjectsWPtr = std::weak_ptr<SharedObjects>;
+ using SharedObjectsPtr = std::shared_ptr<SharedObjects>;
+
+ ParseBase();
+ DEFAULT_MOVE_NO_COPY(ParseBase);
+
+ template<typename T>
+ static auto
+ getShared(auto && k)
+ {
+ return std::dynamic_pointer_cast<T>(Persistence::ParseBase::sharedObjects.lock()->at(k));
+ }
+ template<typename... T>
+ static auto
+ emplaceShared(T &&... v)
+ {
+ return sharedObjects.lock()->emplace(std::forward<T>(v)...);
+ }
+
+ protected:
+ Stack stk;
+
+ private:
+ inline static thread_local SharedObjectsWPtr sharedObjects;
+ SharedObjectsPtr sharedObjectsInstance;
+ };
// TODO Move these
- using SharedObjects = std::map<std::string, std::shared_ptr<Persistable>>;
- inline SharedObjects sharedObjects;
using SeenSharedObjects = std::map<void *, std::string>;
inline SeenSharedObjects seenSharedObjects;
- template<typename Ptr, bool shared> struct SelectionPtrBase : public SelectionV<Ptr> {
+ template<typename Ptr> struct SelectionPtrBase : public SelectionV<Ptr> {
+ static constexpr auto shared = std::is_copy_assignable_v<Ptr>;
using T = typename Ptr::element_type;
struct SelectionObj : public SelectionV<Ptr> {
struct MakeObjectByTypeName : public SelectionV<Ptr> {
@@ -330,7 +444,7 @@ namespace Persistence {
void
setValue(std::string && id) override
{
- sharedObjects.emplace(id, this->v);
+ ParseBase::emplaceShared(id, this->v);
}
};
@@ -340,14 +454,15 @@ namespace Persistence {
select(const std::string & mbr) override
{
using namespace std::literals;
- if (mbr == "@typeid"sv) {
+ if (mbr == "p.typeid"sv) {
if (this->v) {
throw std::runtime_error("cannot set object type after creation");
}
return this->template make_s<MakeObjectByTypeName>(this->v);
}
if constexpr (shared) {
- if (mbr == "@id"sv) {
+ if (mbr == "p.id"sv) {
+ make_default_as_needed(this->v);
return this->template make_s<RememberObjectById>(this->v);
}
}
@@ -439,18 +554,18 @@ namespace Persistence {
}
};
- template<typename T> struct SelectionT<std::unique_ptr<T>> : public SelectionPtrBase<std::unique_ptr<T>, false> {
- using SelectionPtrBase<std::unique_ptr<T>, false>::SelectionPtrBase;
+ template<typename T> struct SelectionT<std::unique_ptr<T>> : public SelectionPtrBase<std::unique_ptr<T>> {
+ using SelectionPtrBase<std::unique_ptr<T>>::SelectionPtrBase;
};
- template<typename T> struct SelectionT<std::shared_ptr<T>> : public SelectionPtrBase<std::shared_ptr<T>, true> {
- using SelectionPtrBase<std::shared_ptr<T>, true>::SelectionPtrBase;
- using SelectionPtrBase<std::shared_ptr<T>, true>::setValue;
+ template<typename T> struct SelectionT<std::shared_ptr<T>> : public SelectionPtrBase<std::shared_ptr<T>> {
+ using SelectionPtrBase<std::shared_ptr<T>>::SelectionPtrBase;
+ using SelectionPtrBase<std::shared_ptr<T>>::setValue;
void
setValue(std::string && id) override
{
- if (auto teo = std::dynamic_pointer_cast<T>(sharedObjects.at(id))) {
+ if (auto teo = ParseBase::getShared<T>(id)) {
this->v = std::move(teo);
}
else {
@@ -461,4 +576,7 @@ namespace Persistence {
}
#define STORE_TYPE store.persistType(this, typeid(*this))
-#define STORE_MEMBER(mbr) store.persistValue(#mbr, mbr)
+#define STORE_MEMBER(mbr) STORE_NAME_MEMBER(#mbr, mbr)
+#define STORE_NAME_MEMBER(name, mbr) store.persistValue<Persistence::SelectionT<decltype(mbr)>>(name, mbr)
+#define STORE_HELPER(mbr, Helper) STORE_NAME_HELPER(#mbr, mbr, Helper)
+#define STORE_NAME_HELPER(name, mbr, Helper) store.persistValue<Helper>(name, mbr)
diff --git a/lib/saxParse-persistence.cpp b/lib/saxParse-persistence.cpp
new file mode 100644
index 0000000..a6a0d23
--- /dev/null
+++ b/lib/saxParse-persistence.cpp
@@ -0,0 +1,50 @@
+#include "saxParse-persistence.h"
+
+namespace Persistence {
+
+ void
+ SAXParsePersistence::loadStateInternal(FILE * in)
+ {
+ stk.top()->beforeValue(stk);
+ stk.top()->beginObject(stk);
+ parseFile(in);
+ stk.pop();
+ stk.pop();
+ }
+
+ void
+ SAXParsePersistence::elementOpen(mxml_node_t * n)
+ {
+ stk.push(stk.top()->select(mxmlGetElement(n)));
+ stk.top()->beforeValue(stk);
+ stk.top()->beginObject(stk);
+ for (int attrCount = mxmlElementGetAttrCount(n), attrIdx {0}; attrIdx < attrCount; ++attrIdx) {
+ const char *name, *value = mxmlElementGetAttrByIndex(n, attrIdx, &name);
+ auto sel = stk.top()->select(name);
+ sel->beforeValue(stk);
+ sel->setValue(std::string {value});
+ }
+ }
+
+ void
+ SAXParsePersistence::elementClose(mxml_node_t *)
+ {
+ stk.top()->endObject(stk);
+ stk.top()->endObject(stk);
+ }
+
+ void
+ SAXParsePersistence::data(mxml_node_t *)
+ {
+ }
+
+ void
+ SAXParsePersistence::directive(mxml_node_t *)
+ {
+ }
+
+ void
+ SAXParsePersistence::cdata(mxml_node_t *)
+ {
+ }
+}
diff --git a/lib/saxParse-persistence.h b/lib/saxParse-persistence.h
new file mode 100644
index 0000000..6043b25
--- /dev/null
+++ b/lib/saxParse-persistence.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "persistence.h"
+#include "saxParse.h"
+#include <cstdio>
+#include <mxml.h>
+
+namespace Persistence {
+ class SAXParsePersistence : public SAXParse, ParseBase {
+ private:
+ template<typename T> struct Root : public Persistable {
+ T t {};
+ bool
+ persist(PersistenceStore & store)
+ {
+ return STORE_TYPE && STORE_NAME_MEMBER("ilt", t);
+ }
+ };
+
+ void loadStateInternal(FILE * in);
+
+ public:
+ template<typename T>
+ auto
+ loadState(FILE * in)
+ {
+ std::unique_ptr<Root<T>> root;
+ stk.push(std::make_unique<SelectionT<decltype(root)>>(std::ref(root)));
+ loadStateInternal(in);
+ return std::move(root->t);
+ }
+
+ protected:
+ void elementOpen(mxml_node_t * n) override;
+ void elementClose(mxml_node_t *) override;
+ void data(mxml_node_t *) override;
+ void directive(mxml_node_t *) override;
+ void cdata(mxml_node_t *) override;
+ };
+}
diff --git a/lib/saxParse.cpp b/lib/saxParse.cpp
new file mode 100644
index 0000000..5c597aa
--- /dev/null
+++ b/lib/saxParse.cpp
@@ -0,0 +1,41 @@
+#include "saxParse.h"
+#include "mxml.h"
+
+namespace Persistence {
+ void
+ SAXParse::comment(mxml_node_t *)
+ {
+ // Default to just ignore comments
+ }
+
+ void
+ SAXParse::parseFile(FILE * file)
+ {
+ mxmlRelease(mxmlSAXLoadFile(
+ nullptr, file, MXML_TEXT_CALLBACK,
+ [](mxml_node_t * n, mxml_sax_event_t e, void * data) {
+ SAXParse * self = static_cast<SAXParse *>(data);
+ switch (e) {
+ case MXML_SAX_ELEMENT_OPEN:
+ return self->elementOpen(n);
+ break;
+ case MXML_SAX_ELEMENT_CLOSE:
+ return self->elementClose(n);
+ break;
+ case MXML_SAX_COMMENT:
+ return self->comment(n);
+ break;
+ case MXML_SAX_DATA:
+ return self->data(n);
+ break;
+ case MXML_SAX_DIRECTIVE:
+ return self->directive(n);
+ break;
+ case MXML_SAX_CDATA:;
+ return self->cdata(n);
+ break;
+ }
+ },
+ this));
+ }
+}
diff --git a/lib/saxParse.h b/lib/saxParse.h
new file mode 100644
index 0000000..d5baaca
--- /dev/null
+++ b/lib/saxParse.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <cstdio>
+
+typedef struct _mxml_node_s mxml_node_t;
+
+namespace Persistence {
+ class SAXParse {
+ public:
+ virtual ~SAXParse() = default;
+
+ virtual void elementOpen(mxml_node_t *) = 0;
+ virtual void elementClose(mxml_node_t *) = 0;
+ virtual void comment(mxml_node_t *);
+ virtual void data(mxml_node_t *) = 0;
+ virtual void directive(mxml_node_t *) = 0;
+ virtual void cdata(mxml_node_t *) = 0;
+
+ void parseFile(FILE * file);
+ };
+}
diff --git a/res/brush47.xml b/res/brush47.xml
new file mode 100644
index 0000000..9c7d2a8
--- /dev/null
+++ b/res/brush47.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<ilt p.id="assetFactory">
+ <object id="wheel">
+ <use type="cylinder" position="0,0,0.571" scale="1.142,1.142,0.07" rotation="0,0,1.5708"/>
+ </object>
+ <object id="axel">
+ <use type="wheel" position="-0.717,0,0"/>
+ <use type="wheel" position="0.717,0,0" rotation="0,3.14159,0"/>
+ </object>
+ <object id="bogie">
+ <use type="axel" position="0,0,0"/>
+ <use type="axel" position="0,2,0"/>
+ <use type="axel" position="0,-2,0"/>
+ </object>
+ <asset p.typeid="RailVehicleClass" id="brush-47" name="Brush 47" length="19.38" wheelBase="15.70" maxSpeed="42.4688">
+ <bodyMesh id="body" size="2.69,19.38,3.9">
+ <use type="cuboid" position="0,0,1.2" scale="2.69,19.38,1.5" colour="#2c4f5a">
+ <face id="bottom" colour="#2c3539"/>
+ <face id="front" colour="goldenrod"/>
+ <face id="back" colour="goldenrod"/>
+ <face id="top" type="extrude" scale="1,0.95,1" position="0,0,0.8">
+ <face id="front" colour="#e1eff3"/>
+ <face id="back" colour="#e1eff3"/>
+ <face id="top" type="extrude" scale="0.5,0.8,0" position="0,0,0.4" smooth="true" colour="#aeb0b0"/>
+ </face>
+ </use>
+ <use type="cuboid" position="0,0,0.2" scale="2.6,4.5,1" colour="grey30"/>
+ </bodyMesh>
+ <bogie id="bogie1">
+ <use type="bogie" position="0,-1.85,0" colour="#413b3a"/>
+ </bogie>
+ <bogie id="bogie2">
+ <use type="bogie" position="0,1.85,0" rotation="0,3.14159,0" colour="#413b3a"/>
+ </bogie>
+ </asset>
+</ilt>
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index cefad7b..907632e 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -2,6 +2,7 @@ import testing ;
import sequence ;
lib boost_unit_test_framework ;
+lib benchmark ;
path-constant res : ../res ;
path-constant fixtures : fixtures ;
@@ -39,7 +40,7 @@ project : requirements
<toolset>tidy:<xcheckxx>hicpp-vararg
<toolset>tidy:<librarydef>boost
;
-lib test : [ glob *.cpp : test-*.cpp ] ;
+lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ;
run test-collection.cpp ;
run test-obj.cpp ;
@@ -52,5 +53,8 @@ run test-text.cpp ;
run test-enumDetails.cpp ;
run test-render.cpp : : : <library>test ;
run test-glContextBhvr.cpp ;
+run test-assetFactory.cpp : -- : ../res/brush47.xml : <library>test ;
+run perf-assetFactory.cpp : : : <library>benchmark <library>test ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
+explicit perf-assetFactory ;
diff --git a/test/fixtures/json/abs.json b/test/fixtures/json/abs.json
index 8492df3..1b489ef 100644
--- a/test/fixtures/json/abs.json
+++ b/test/fixtures/json/abs.json
@@ -1,6 +1,6 @@
{
"aptr": {
- "@typeid": "SubObject",
+ "p.typeid": "SubObject",
"base": "set base",
"sub": "set sub"
}
diff --git a/test/fixtures/json/bad/empty_abs.json b/test/fixtures/json/bad/empty_abs.json
index 7d22001..5cc3598 100644
--- a/test/fixtures/json/bad/empty_abs.json
+++ b/test/fixtures/json/bad/empty_abs.json
@@ -1,5 +1,5 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 1,
"aptr": {},
"str": "after"
diff --git a/test/fixtures/json/bad/implicit_abs.json b/test/fixtures/json/bad/implicit_abs.json
index 573b323..bf81ee4 100644
--- a/test/fixtures/json/bad/implicit_abs.json
+++ b/test/fixtures/json/bad/implicit_abs.json
@@ -1,5 +1,5 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 1,
"aptr": {
"str": "trigger"
diff --git a/test/fixtures/json/bad/late_type.json b/test/fixtures/json/bad/late_type.json
index 171575a..d1d6f6c 100644
--- a/test/fixtures/json/bad/late_type.json
+++ b/test/fixtures/json/bad/late_type.json
@@ -1,4 +1,4 @@
{
"str": "trigger",
- "@typeid": "doesn't matter"
+ "p.typeid": "doesn't matter"
}
diff --git a/test/fixtures/json/bad/wrong_type.json b/test/fixtures/json/bad/wrong_type.json
index 777d791..4f19033 100644
--- a/test/fixtures/json/bad/wrong_type.json
+++ b/test/fixtures/json/bad/wrong_type.json
@@ -1,5 +1,5 @@
{
"ptr": {
- "@typeid": "SubObject"
+ "p.typeid": "SubObject"
}
}
diff --git a/test/fixtures/json/bad_type.json b/test/fixtures/json/bad_type.json
index f316bd1..70b9d1a 100644
--- a/test/fixtures/json/bad_type.json
+++ b/test/fixtures/json/bad_type.json
@@ -1,3 +1,3 @@
{
- "@typeid": "no such type"
+ "p.typeid": "no such type"
}
diff --git a/test/fixtures/json/conv.json b/test/fixtures/json/conv.json
new file mode 100644
index 0000000..1b690d5
--- /dev/null
+++ b/test/fixtures/json/conv.json
@@ -0,0 +1,4 @@
+{
+ "bl": "true",
+ "flt": "3.14"
+}
diff --git a/test/fixtures/json/empty.json b/test/fixtures/json/empty.json
index a9193a3..9575565 100644
--- a/test/fixtures/json/empty.json
+++ b/test/fixtures/json/empty.json
@@ -1,5 +1,5 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 1,
"ptr": {},
"str": "after"
diff --git a/test/fixtures/json/implicit.json b/test/fixtures/json/implicit.json
index 478cec6..6efc2ba 100644
--- a/test/fixtures/json/implicit.json
+++ b/test/fixtures/json/implicit.json
@@ -1,5 +1,5 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 1,
"ptr": {
"str": "trigger",
diff --git a/test/fixtures/json/load_object.json b/test/fixtures/json/load_object.json
index bb32298..c622885 100644
--- a/test/fixtures/json/load_object.json
+++ b/test/fixtures/json/load_object.json
@@ -1,5 +1,5 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 3.14,
"str": "Lovely string",
"bl": true,
@@ -48,7 +48,7 @@
[]
],
"ptr": {
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 3.14,
"str": "Lovely string"
}
diff --git a/test/fixtures/json/nested.json b/test/fixtures/json/nested.json
index 98951fc..1e271e3 100644
--- a/test/fixtures/json/nested.json
+++ b/test/fixtures/json/nested.json
@@ -1,14 +1,14 @@
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 1,
"ptr": {
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 2,
"ptr": {
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 3,
"ptr": {
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"flt": 4,
"ptr": null,
"str": "four"
diff --git a/test/fixtures/json/shared_ptr_diff.json b/test/fixtures/json/shared_ptr_diff.json
index bb18e33..043be4a 100644
--- a/test/fixtures/json/shared_ptr_diff.json
+++ b/test/fixtures/json/shared_ptr_diff.json
@@ -1,11 +1,11 @@
{
- "@typeid": "SharedTestObject",
+ "p.typeid": "SharedTestObject",
"sptr": {
- "@typeid": "SubObject",
- "@id": "someid"
+ "p.typeid": "SubObject",
+ "p.id": "someid"
},
"ssptr": {
- "@typeid": "SubObject",
- "@id": "some other id"
+ "p.typeid": "SubObject",
+ "p.id": "some other id"
}
}
diff --git a/test/fixtures/json/shared_ptr_diff_default.json b/test/fixtures/json/shared_ptr_diff_default.json
index 69f5e85..c78bc12 100644
--- a/test/fixtures/json/shared_ptr_diff_default.json
+++ b/test/fixtures/json/shared_ptr_diff_default.json
@@ -1,8 +1,8 @@
{
- "@typeid": "SharedTestObject",
+ "p.typeid": "SharedTestObject",
"sptr": {
- "@typeid": "SubObject",
- "@id": "someid"
+ "p.typeid": "SubObject",
+ "p.id": "someid"
},
"ssptr": {}
}
diff --git a/test/fixtures/json/shared_ptr_null.json b/test/fixtures/json/shared_ptr_null.json
index c2461e8..8669bf2 100644
--- a/test/fixtures/json/shared_ptr_null.json
+++ b/test/fixtures/json/shared_ptr_null.json
@@ -1,8 +1,8 @@
{
- "@typeid": "SharedTestObject",
+ "p.typeid": "SharedTestObject",
"sptr": {
- "@typeid": "SubObject",
- "@id": "someid"
+ "p.typeid": "SubObject",
+ "p.id": "someid"
},
"ssptr": null
}
diff --git a/test/fixtures/json/shared_ptr_same.json b/test/fixtures/json/shared_ptr_same.json
index 4115493..2838e82 100644
--- a/test/fixtures/json/shared_ptr_same.json
+++ b/test/fixtures/json/shared_ptr_same.json
@@ -1,8 +1,8 @@
{
- "@typeid": "SharedTestObject",
+ "p.typeid": "SharedTestObject",
"sptr": {
- "@typeid": "SubObject",
- "@id": "someid"
+ "p.typeid": "SubObject",
+ "p.id": "someid"
},
"ssptr": "someid"
}
diff --git a/test/fixtures/json/shared_ptr_wrong_type.json b/test/fixtures/json/shared_ptr_wrong_type.json
index 68f7533..5d4e655 100644
--- a/test/fixtures/json/shared_ptr_wrong_type.json
+++ b/test/fixtures/json/shared_ptr_wrong_type.json
@@ -1,8 +1,8 @@
{
- "@typeid": "SharedTestObject",
+ "p.typeid": "SharedTestObject",
"sptr": {
- "@typeid": "SubObject2",
- "@id": "someid"
+ "p.typeid": "SubObject2",
+ "p.id": "someid"
},
"ssptr": "someid"
}
diff --git a/test/fixtures/json/vector_ptr.json b/test/fixtures/json/vector_ptr.json
index 8a07a2e..654bd6c 100644
--- a/test/fixtures/json/vector_ptr.json
+++ b/test/fixtures/json/vector_ptr.json
@@ -1,7 +1,7 @@
{
"vptr": [
{
- "@typeid": "TestObject",
+ "p.typeid": "TestObject",
"str": "type"
},
{
diff --git a/test/fixtures/rgb.txt b/test/fixtures/rgb.txt
new file mode 100644
index 0000000..2fab7af
--- /dev/null
+++ b/test/fixtures/rgb.txt
@@ -0,0 +1,20 @@
+127 255 0 chartreuse1
+190 190 190 x11 gray
+169 169 169 DarkGrey
+ 0 255 255 cyan
+173 173 173 gray68
+202 225 255 LightSteelBlue1
+ 72 209 204 medium turquoise
+224 238 224 honeydew2
+238 197 145 burlywood2
+205 133 63 peru
+ 28 28 28 gray11
+ 83 134 139 CadetBlue4
+139 76 57 salmon4
+238 232 170 pale goldenrod
+112 128 144 slate grey
+255 255 0 yellow1
+159 121 238 MediumPurple2
+190 190 190 gray
+ 66 66 66 grey26
+0 0 139 DarkBlue
diff --git a/test/perf-assetFactory.cpp b/test/perf-assetFactory.cpp
new file mode 100644
index 0000000..f702fe7
--- /dev/null
+++ b/test/perf-assetFactory.cpp
@@ -0,0 +1,32 @@
+#include "assetFactory/assetFactory.h"
+#include "assetFactory/factoryMesh.h"
+#include "testMainWindow.h"
+#include "ui/applicationBase.h"
+#include <benchmark/benchmark.h>
+
+static void
+brush47xml_load(benchmark::State & state)
+{
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/brush47.xml"));
+ }
+}
+
+static void
+brush47xml_mesh(benchmark::State & state)
+{
+ TestMainWindow window;
+
+ const auto mf = AssetFactory::loadXML(RESDIR "/brush47.xml");
+ const auto brush47 = mf->assets.at("brush-47");
+ for (auto _ : state) {
+ std::for_each(brush47->meshes.begin(), brush47->meshes.end(), [](const FactoryMesh::CPtr & factoryMesh) {
+ factoryMesh->createMesh();
+ });
+ }
+}
+
+BENCHMARK(brush47xml_load);
+BENCHMARK(brush47xml_mesh);
+
+BENCHMARK_MAIN();
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
new file mode 100644
index 0000000..ae5a88a
--- /dev/null
+++ b/test/test-assetFactory.cpp
@@ -0,0 +1,127 @@
+#define BOOST_TEST_MODULE test_asset_factory
+
+#include "testHelpers.h"
+#include "testRenderOutput.h"
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include "assetFactory/assetFactory.h"
+#include "assetFactory/object.h"
+#include "game/vehicles/railVehicle.h"
+#include "game/vehicles/railVehicleClass.h"
+#include "gfx/gl/sceneRenderer.h"
+#include "gfx/renderable.h"
+#include "lib/collection.hpp"
+#include "lib/location.hpp"
+#include "lib/stream_support.hpp"
+#include "testMainWindow.h"
+#include "ui/applicationBase.h"
+
+BOOST_GLOBAL_FIXTURE(ApplicationBase);
+BOOST_GLOBAL_FIXTURE(TestMainWindow);
+
+const std::filesystem::path TMP {"/tmp"};
+class FactoryFixture : public TestRenderOutputSize<glm::ivec2 {2048, 2048}>, public SceneProvider {
+public:
+ FactoryFixture() : sceneRenderer {size, output} { }
+ ~FactoryFixture()
+ {
+ glDisable(GL_DEBUG_OUTPUT);
+ auto outpath = (TMP / boost::unit_test::framework::current_test_case().full_name()).replace_extension(".tga");
+ std::filesystem::create_directories(outpath.parent_path());
+ Texture::save(outImage, size, outpath.c_str());
+ }
+ void
+ content(const SceneShader & shader) const override
+ {
+ shader.basic.use(Location {{0, 0, 0}, {0, 0, 0}});
+ objects.apply(&Renderable::render, shader);
+ }
+ void
+ lights(const SceneShader & shader) const override
+ {
+ shader.pointLight.add({-3, 1, 5}, {1, 1, 1}, .1F);
+ }
+ void
+ environment(const SceneShader &, const SceneRenderer & sceneRenderer) const override
+ {
+ sceneRenderer.setAmbientLight({.2, .2, .2});
+ sceneRenderer.setDirectionalLight({.3, .3, .3}, east + south + south + down, *this);
+ }
+ void
+ shadows(const ShadowMapper & mapper) const override
+ {
+ mapper.dynamicPoint.use(Location {{0, 0, 0}, {0, 0, 0}});
+ objects.apply(&Renderable::shadows, mapper);
+ }
+ void
+ render(float dist = 10.f)
+ {
+ sceneRenderer.camera.setView({dist, dist, dist}, south + west + down);
+ sceneRenderer.render(*this);
+ }
+ Collection<const Renderable> objects;
+
+private:
+ SceneRenderer sceneRenderer;
+};
+
+BOOST_FIXTURE_TEST_SUITE(m, FactoryFixture);
+BOOST_AUTO_TEST_CASE(brush47xml)
+{
+ auto mf = AssetFactory::loadXML(RESDIR "/brush47.xml");
+ BOOST_REQUIRE(mf);
+ BOOST_REQUIRE_EQUAL(6, mf->shapes.size());
+ BOOST_CHECK(mf->shapes.at("plane"));
+ BOOST_CHECK(mf->shapes.at("cylinder"));
+ BOOST_CHECK(mf->shapes.at("cuboid"));
+ BOOST_CHECK(mf->shapes.at("wheel"));
+ BOOST_CHECK(mf->shapes.at("axel"));
+ auto bogie = mf->shapes.at("bogie");
+ BOOST_REQUIRE(bogie);
+ auto bogieObj = std::dynamic_pointer_cast<const Object>(bogie);
+ BOOST_CHECK_EQUAL(3, bogieObj->uses.size());
+ BOOST_CHECK_EQUAL(1, mf->assets.size());
+ auto brush47 = mf->assets.at("brush-47");
+ BOOST_REQUIRE(brush47);
+ auto brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(brush47);
+ BOOST_REQUIRE(brush47rvc);
+ BOOST_REQUIRE(brush47rvc->bodyMesh);
+ BOOST_REQUIRE(brush47rvc->bogies.front());
+ BOOST_REQUIRE(brush47rvc->bogies.back());
+
+ auto railVehicle = std::make_shared<RailVehicle>(brush47rvc);
+ objects.objects.push_back(railVehicle);
+
+ render(20);
+}
+BOOST_AUTO_TEST_SUITE_END();
+
+template<typename T> using InOut = std::tuple<T, T>;
+BOOST_DATA_TEST_CASE(normalizeColourName,
+ boost::unit_test::data::make<InOut<std::string>>({
+ {"", ""},
+ {"black", "black"},
+ {" black ", "black"},
+ {" b l a c k ", "black"},
+ {" B L A c k ", "black"},
+ {"BLAck ", "black"},
+ {"BLACK ", "black"},
+ {"BlAck ", "black"},
+ {"Bl Ack ", "black"},
+ }),
+ in_, exp)
+{
+ auto in {in_};
+ BOOST_CHECK_NO_THROW(AssetFactory::normalizeColourName(in));
+ BOOST_CHECK_EQUAL(in, exp);
+}
+
+BOOST_AUTO_TEST_CASE(parseX11RGB)
+{
+ const auto parsedColours = AssetFactory::parseX11RGB(FIXTURESDIR "rgb.txt");
+ BOOST_REQUIRE_EQUAL(parsedColours.size(), 20);
+ BOOST_CHECK_CLOSE_VEC(parsedColours.at("cyan"), AssetFactory::Colour(0, 1, 1));
+ BOOST_CHECK_CLOSE_VEC(parsedColours.at("slategrey"), AssetFactory::Colour(0.44F, 0.5, 0.56F));
+ BOOST_CHECK_CLOSE_VEC(parsedColours.at("lightsteelblue1"), AssetFactory::Colour(0.79, 0.88, 1));
+}
diff --git a/test/test-persistence.cpp b/test/test-persistence.cpp
index e13cb7a..7bca91a 100644
--- a/test/test-persistence.cpp
+++ b/test/test-persistence.cpp
@@ -83,16 +83,14 @@ struct TestObject : public Persistence::Persistable {
}
};
-struct JPP : public Persistence::JsonParsePersistence {
+struct JPP {
template<typename T>
T
load_json(const std::filesystem::path & path)
{
BOOST_TEST_CONTEXT(path) {
std::ifstream ss {path};
- auto to = loadState<T>(ss);
- Persistence::sharedObjects.clear();
- BOOST_CHECK(stk.empty());
+ auto to = Persistence::JsonParsePersistence {}.loadState<T>(ss);
BOOST_REQUIRE(to);
return to;
}
@@ -208,6 +206,14 @@ BOOST_FIXTURE_TEST_CASE(load_vector_ptr, JPP)
BOOST_CHECK(to->vptr.at(3)->str.empty());
}
+BOOST_FIXTURE_TEST_CASE(test_conversion, JPP)
+{
+ auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/conv.json");
+ BOOST_REQUIRE(to);
+ BOOST_CHECK_EQUAL(to->bl, true);
+ BOOST_CHECK_EQUAL(to->flt, 3.14F);
+}
+
struct SharedTestObject : public Persistence::Persistable {
SharedTestObject() = default;
@@ -281,10 +287,10 @@ auto const TEST_STRINGS_DECODE_ONLY = boost::unit_test::data::make<svs>({
{R"J("\u056b ARMENIAN SMALL LETTER INI")J", "ի ARMENIAN SMALL LETTER INI"},
{R"J("\u0833 SAMARITAN PUNCTUATION BAU")J", "࠳ SAMARITAN PUNCTUATION BAU"},
});
-BOOST_DATA_TEST_CASE_F(JPP, load_strings, TEST_STRINGS + TEST_STRINGS_DECODE_ONLY, in, exp)
+BOOST_DATA_TEST_CASE(load_strings, TEST_STRINGS + TEST_STRINGS_DECODE_ONLY, in, exp)
{
std::stringstream str {in};
- BOOST_CHECK_EQUAL(loadState<std::string>(str), exp);
+ BOOST_CHECK_EQUAL(Persistence::JsonParsePersistence {}.loadState<std::string>(str), exp);
}
using cpstr = std::tuple<unsigned long, std::string_view>;
@@ -320,7 +326,7 @@ BOOST_AUTO_TEST_CASE(write_test_dfl)
std::stringstream ss;
Persistence::JsonWritePersistence {ss}.saveState(to);
BOOST_CHECK_EQUAL(ss.str(),
- R"({"@typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]})");
+ R"({"p.typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]})");
}
BOOST_FIXTURE_TEST_CASE(write_test_loaded, JPP)
@@ -329,7 +335,7 @@ BOOST_FIXTURE_TEST_CASE(write_test_loaded, JPP)
std::stringstream ss;
Persistence::JsonWritePersistence {ss}.saveState(to);
BOOST_CHECK_EQUAL(ss.str(),
- R"({"@typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":true,"pos":[3.14,6.28,1.57],"flts":[3.14,6.28,1.57,0,-1,-3.14],"poss":[[3.14,6.28,1.57],[0,-1,-3.14]],"nest":[[["a","b"],["c","d","e"]],[["f"]],[]],"ptr":{"@typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]},"vptr":[]})");
+ R"({"p.typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":true,"pos":[3.14,6.28,1.57],"flts":[3.14,6.28,1.57,0,-1,-3.14],"poss":[[3.14,6.28,1.57],[0,-1,-3.14]],"nest":[[["a","b"],["c","d","e"]],[["f"]],[]],"ptr":{"p.typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]},"vptr":[]})");
}
BOOST_FIXTURE_TEST_CASE(write_test_loaded_abs, JPP)
@@ -338,7 +344,7 @@ BOOST_FIXTURE_TEST_CASE(write_test_loaded_abs, JPP)
std::stringstream ss;
Persistence::JsonWritePersistence {ss}.saveState(to);
BOOST_CHECK_EQUAL(ss.str(),
- R"({"@typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"aptr":{"@typeid":"SubObject","base":"set base","sub":"set sub"},"vptr":[]})");
+ R"({"p.typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"aptr":{"p.typeid":"SubObject","base":"set base","sub":"set sub"},"vptr":[]})");
}
BOOST_FIXTURE_TEST_CASE(write_test_loaded_shared, JPP)
@@ -349,7 +355,7 @@ BOOST_FIXTURE_TEST_CASE(write_test_loaded_shared, JPP)
Persistence::JsonWritePersistence {ss}.saveState(to);
BOOST_CHECK_EQUAL(Persistence::seenSharedObjects.size(), 1);
BOOST_CHECK_EQUAL(ss.str(),
- R"({"@typeid":"SharedTestObject","sptr":{"@typeid":"SubObject","@id":"someid","base":"","sub":""},"ssptr":"someid"})");
+ R"({"p.typeid":"SharedTestObject","sptr":{"p.typeid":"SubObject","p.id":"someid","base":"","sub":""},"ssptr":"someid"})");
}
BOOST_DATA_TEST_CASE(write_special_strings, TEST_STRINGS, exp, in)
diff --git a/test/test-render.cpp b/test/test-render.cpp
index 7db847d..7771760 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -29,8 +29,8 @@ class TestScene : public SceneProvider {
content(const SceneShader & shader) const override
{
terrain.render(shader);
- train.render(shader, Location {{52, 50, 2}}, {Location {}, Location {}});
- train.render(shader, Location {{52, 30, 2}}, {Location {}, Location {}});
+ train.render(shader, Location {{52, 50, 2}}, {Location {{52, 56, 2}}, Location {{52, 44, 2}}});
+ train.render(shader, Location {{52, 30, 2}}, {Location {{52, 36, 2}}, Location {{52, 24, 2}}});
}
void
lights(const SceneShader &) const override
@@ -40,8 +40,8 @@ class TestScene : public SceneProvider {
shadows(const ShadowMapper & shadowMapper) const override
{
terrain.shadows(shadowMapper);
- train.shadows(shadowMapper, Location {{52, 50, 2}});
- train.shadows(shadowMapper, Location {{52, 30, 2}});
+ train.shadows(shadowMapper, Location {{52, 50, 2}}, {Location {{52, 56, 2}}, Location {{52, 44, 2}}});
+ train.shadows(shadowMapper, Location {{52, 30, 2}}, {Location {{52, 36, 2}}, Location {{52, 24, 2}}});
}
};