diff options
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, ¢re](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}}}); } }; |