summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jamroot.jam1
-rw-r--r--assetFactory/asset.cpp10
-rw-r--r--assetFactory/asset.h6
-rw-r--r--assetFactory/assetFactory.cpp64
-rw-r--r--assetFactory/assetFactory.h13
-rw-r--r--assetFactory/cuboid.cpp4
-rw-r--r--assetFactory/faceController.cpp186
-rw-r--r--assetFactory/faceController.h25
-rw-r--r--assetFactory/factoryMesh.cpp40
-rw-r--r--assetFactory/modelFactoryMesh.cpp16
-rw-r--r--assetFactory/modelFactoryMesh.h11
-rw-r--r--assetFactory/style.cpp52
-rw-r--r--assetFactory/style.h7
-rw-r--r--assetFactory/textureFragment.cpp7
-rw-r--r--assetFactory/textureFragment.h14
-rw-r--r--assetFactory/texturePacker.cpp80
-rw-r--r--assetFactory/texturePacker.h41
-rw-r--r--game/vehicles/railVehicleClass.cpp6
-rw-r--r--game/vehicles/railVehicleClass.h1
-rw-r--r--gfx/gl/camera.cpp4
-rw-r--r--gfx/gl/camera.h2
-rw-r--r--gfx/gl/shaders/basicShader.fs5
-rw-r--r--gfx/models/texture.cpp32
-rw-r--r--gfx/models/texture.h15
-rw-r--r--gfx/models/vertex.hpp4
-rw-r--r--lib/collections.hpp21
-rw-r--r--lib/geometricPlane.cpp28
-rw-r--r--lib/geometricPlane.h22
-rw-r--r--lib/persistence.cpp27
-rw-r--r--lib/persistence.h28
-rw-r--r--lib/ray.cpp6
-rw-r--r--lib/ray.hpp2
-rw-r--r--lib/stream_support.hpp2
-rw-r--r--res/brush47.xml31
-rw-r--r--res/rail/cabWindowFront.pngbin0 -> 630 bytes
-rw-r--r--res/rail/roofSideWithVents.pngbin0 -> 17011 bytes
-rw-r--r--res/rail/roofTopWithVents.pngbin0 -> 11921 bytes
-rw-r--r--test/Jamfile.jam9
-rw-r--r--test/test-assetFactory.cpp48
-rw-r--r--test/test-persistence.cpp7
-rw-r--r--test/testStructures.cpp6
-rw-r--r--test/testStructures.h3
42 files changed, 731 insertions, 155 deletions
diff --git a/Jamroot.jam b/Jamroot.jam
index 27db410..e2075cf 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -90,6 +90,7 @@ lib ilt :
<warnings-as-errors>off
]
:
+ <define>GLM_FORCE_SWIZZLE
<variant>debug:<warnings>pedantic
<variant>debug:<warnings-as-errors>on
<variant>debug:<cflags>-Wnon-virtual-dtor
diff --git a/assetFactory/asset.cpp b/assetFactory/asset.cpp
index 3ab2f1c..e3f5feb 100644
--- a/assetFactory/asset.cpp
+++ b/assetFactory/asset.cpp
@@ -1,4 +1,5 @@
#include "asset.h"
+#include "assetFactory.h"
bool
Asset::persist(Persistence::PersistenceStore & store)
@@ -6,6 +7,15 @@ Asset::persist(Persistence::PersistenceStore & store)
return STORE_MEMBER(id) && STORE_MEMBER(name);
}
+Asset::TexturePtr
+Asset::getTexture() const
+{
+ if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
+ return mf->getTexture();
+ }
+ return nullptr;
+}
+
Asset::MeshConstruct::MeshConstruct(Mesh::Ptr & m) :
Persistence::SelectionPtrBase<FactoryMesh::Ptr> {fmesh}, out {m} { }
diff --git a/assetFactory/asset.h b/assetFactory/asset.h
index e3318e4..30f40cd 100644
--- a/assetFactory/asset.h
+++ b/assetFactory/asset.h
@@ -4,12 +4,18 @@
#include "persistence.h"
#include <stdTypeDefs.hpp>
+class Texture;
+
class Asset : public Persistence::Persistable, public StdTypeDefs<Asset> {
public:
+ using TexturePtr = std::shared_ptr<Texture>;
+
std::string id;
std::string name;
protected:
+ TexturePtr getTexture() const;
+
struct MeshConstruct : public Persistence::SelectionPtrBase<FactoryMesh::Ptr> {
using Persistence::SelectionPtrBase<FactoryMesh::Ptr>::setValue;
diff --git a/assetFactory/assetFactory.cpp b/assetFactory/assetFactory.cpp
index f5fc2b3..ed1af58 100644
--- a/assetFactory/assetFactory.cpp
+++ b/assetFactory/assetFactory.cpp
@@ -2,11 +2,16 @@
#include "collections.hpp"
#include "cuboid.h"
#include "cylinder.h"
+#include "filesystem.h"
+#include "gfx/image.h"
+#include "gfx/models/texture.h"
#include "modelFactoryMesh_fwd.h"
#include "object.h"
#include "plane.h"
+#include "resource.h"
#include "saxParse-persistence.h"
-#include <filesystem.h>
+#include "texturePacker.h"
+#include <stb/stb_image.h>
AssetFactory::AssetFactory() :
shapes {
@@ -75,11 +80,68 @@ AssetFactory::parseColour(std::string_view in) const
}
throw std::runtime_error("No such asset factory colour");
}
+
+AssetFactory::TextureFragmentCoords
+AssetFactory::getTextureCoords(std::string_view id) const
+{
+ createTexutre();
+ const auto & fragmentUV = textureFragmentPositions.at(id);
+ return {
+ fragmentUV.xy(),
+ fragmentUV.zy(),
+ fragmentUV.zw(),
+ fragmentUV.xw(),
+ };
+}
+
+Asset::TexturePtr
+AssetFactory::getTexture() const
+{
+ createTexutre();
+ return texture;
+}
+
+void
+AssetFactory::createTexutre() const
+{
+ if (!textureFragments.empty() && (!texture || textureFragmentPositions.empty())) {
+ // * load images
+ std::vector<std::unique_ptr<Image>> images;
+ std::transform(
+ textureFragments.begin(), textureFragments.end(), std::back_inserter(images), [](const auto & tf) {
+ return std::make_unique<Image>(Resource::mapPath(tf.second->path), STBI_rgb_alpha);
+ });
+ // * layout images
+ std::vector<TexturePacker::Image> imageSizes;
+ std::transform(images.begin(), images.end(), std::back_inserter(imageSizes), [](const auto & image) {
+ return TexturePacker::Image {image->width, image->height};
+ });
+ const auto [layout, outSize] = TexturePacker {imageSizes}.pack();
+ // * create texture
+ texture = std::make_shared<Texture>(outSize.x, outSize.y, TextureOptions {.wrap = GL_CLAMP_TO_EDGE});
+ std::transform(textureFragments.begin(), textureFragments.end(),
+ std::inserter(textureFragmentPositions, textureFragmentPositions.end()),
+ [position = layout.begin(), image = images.begin(), size = imageSizes.begin(),
+ outSize = glm::vec2 {outSize}](const auto & tf) mutable {
+ const auto positionFraction = glm::vec4 {*position, *position + *size} / outSize.xyxy();
+ glTexSubImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(position->x), static_cast<GLint>(position->y),
+ static_cast<GLint>(size->x), static_cast<GLint>(size->y), GL_RGBA, GL_UNSIGNED_BYTE,
+ image->get()->data.data());
+ position++;
+ image++;
+ size++;
+ return decltype(textureFragmentPositions)::value_type {tf.first, positionFraction};
+ });
+ }
+}
+
bool
AssetFactory::persist(Persistence::PersistenceStore & store)
{
using MapObjects = Persistence::MapByMember<Shapes, std::shared_ptr<Object>>;
using MapAssets = Persistence::MapByMember<Assets>;
+ using MapTextureFragments = Persistence::MapByMember<TextureFragments>;
return STORE_TYPE && STORE_NAME_HELPER("object", shapes, MapObjects)
+ && STORE_NAME_HELPER("textureFragment", textureFragments, MapTextureFragments)
&& STORE_NAME_HELPER("asset", assets, MapAssets);
}
diff --git a/assetFactory/assetFactory.h b/assetFactory/assetFactory.h
index b47d408..52692c4 100644
--- a/assetFactory/assetFactory.h
+++ b/assetFactory/assetFactory.h
@@ -3,23 +3,31 @@
#include "asset.h"
#include "persistence.h"
#include "shape.h"
+#include "textureFragment.h"
#include <filesystem>
+class Texture;
+
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 TextureFragments = std::map<std::string, TextureFragment::Ptr, std::less<>>;
using Colour = glm::vec3;
using ColourAlpha = glm::vec4;
using Colours = std::map<std::string, Colour, std::less<>>;
+ using TextureFragmentCoords = std::array<glm::vec2, 4>;
AssetFactory();
[[nodiscard]] static std::shared_ptr<AssetFactory> loadXML(const std::filesystem::path &);
[[nodiscard]] ColourAlpha parseColour(std::string_view) const;
+ [[nodiscard]] TextureFragmentCoords getTextureCoords(std::string_view) const;
+ [[nodiscard]] Asset::TexturePtr getTexture() const;
Shapes shapes;
Assets assets;
Colours colours;
+ TextureFragments textureFragments;
static Colours parseX11RGB(const char * rgbtxtpath);
static void normalizeColourName(std::string &);
@@ -27,4 +35,9 @@ public:
private:
friend Persistence::SelectionPtrBase<std::shared_ptr<AssetFactory>>;
bool persist(Persistence::PersistenceStore & store) override;
+
+ void createTexutre() const;
+
+ mutable Asset::TexturePtr texture;
+ mutable std::map<std::string_view, glm::vec4, std::less<>> textureFragmentPositions;
};
diff --git a/assetFactory/cuboid.cpp b/assetFactory/cuboid.cpp
index f200258..a8ddcd9 100644
--- a/assetFactory/cuboid.cpp
+++ b/assetFactory/cuboid.cpp
@@ -21,8 +21,8 @@ Cuboid::createMesh(ModelFactoryMesh & mesh, float) const
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("right", vhs[0], vhs[7], vhs[6], vhs[1]),
+ mesh.add_namedFace("left", 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/faceController.cpp b/assetFactory/faceController.cpp
index 1ec1467..b305f1c 100644
--- a/assetFactory/faceController.cpp
+++ b/assetFactory/faceController.cpp
@@ -2,90 +2,140 @@
#include "collections.hpp"
#include "maths.h"
#include "modelFactoryMesh.h"
+#include "ray.hpp"
void
-FaceController::apply(ModelFactoryMesh & mesh, const StyleStack & parents, const std::string & name,
+FaceController::apply(ModelFactoryMesh & mesh, const StyleStack & parents, const std::string & names,
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 {};
- };
+ std::stringstream nameStream {names};
+ std::for_each(std::istream_iterator<std::string>(nameStream), std::istream_iterator<std::string> {},
+ [&](const auto & name) {
+ applySingle(mesh, parents, name, faces);
+ });
+}
- const auto controlledFaces {materializeRange(faces.equal_range(name))};
- if (controlledFaces.empty()) {
- throw std::runtime_error("Named face(s) do not exist: " + name);
- }
+void
+FaceController::applySingle(ModelFactoryMesh & mesh, const StyleStack & parents, const std::string & name,
+ Shape::CreatedFaces & faces) const
+{
+ auto controlledFaces {materializeRange(faces.equal_range(name))};
- if (!type.empty()) {
- const auto mutation = getMatrix();
+ if (!type.empty() || !splits.empty()) {
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);
- });
- // 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);
+ }
+ for (auto & [faceName, faceHandle] : controlledFaces) {
+ Shape::CreatedFaces newFaces;
+ for (const auto & [newFaceSuffix, splitDef] : splits) {
+ newFaces.merge(split(mesh, name + newFaceSuffix, faceHandle, *splitDef));
+ }
+ if (type == "extrude") {
+ newFaces.merge(extrude(mesh, name, faceHandle));
+ }
+ if (!newFaces.empty()) {
+ applyStyle(mesh, parents + this, newFaces);
+ for (const auto & [subFaceName, faceController] : faceControllers) {
+ faceController->apply(mesh, parents + this, subFaceName, newFaces);
}
+ faces.merge(std::move(newFaces));
+ }
+ else {
+ applyStyle(mesh, parents + this, faceHandle);
+ }
+ }
+}
+
+Shape::CreatedFaces
+FaceController::extrude(ModelFactoryMesh & mesh, const std::string & faceName, OpenMesh::FaceHandle faceHandle) const
+{
+ // get points
+ const auto baseVertices {materializeRange(mesh.fv_range(faceHandle))};
+ // create new vertices
+ const auto vertices
+ = baseVertices * [&mesh, mutation = getMatrix(), centre = mesh.calc_face_centroid(faceHandle)](auto && v) {
+ return mesh.add_vertex(centre + ((mesh.point(v) - centre) % mutation));
+ };
+ // get new faces names
+ const auto vertexCount = baseVertices.size();
+ std::vector<std::string> faceNames;
+ for (size_t idx {}; idx < vertexCount; ++idx) {
+ const auto next = (idx + 1) % vertexCount;
+ const auto existingEdge = mesh.find_halfedge(baseVertices[idx], baseVertices[next]);
+ faceNames.push_back(mesh.property(mesh.nameAdjFaceProperty, existingEdge));
+ }
+ // create new faces
+ mesh.delete_face(faceHandle);
+ Shape::CreatedFaces newFaces;
+ for (size_t idx {}; idx < vertexCount; ++idx) {
+ const auto next = (idx + 1) % vertexCount;
+ newFaces.emplace(mesh.add_namedFace(
+ faceNames[idx], baseVertices[idx], baseVertices[next], vertices[next], vertices[idx]));
+ }
+ newFaces.emplace(mesh.add_namedFace(faceName, vertices));
+
+ return newFaces;
+}
+
+Shape::CreatedFaces
+FaceController::split(
+ ModelFactoryMesh & mesh, const std::string & name, OpenMesh::FaceHandle & fh, const Split & split) const
+{
+ // Map face vertex handles to their relationship to the split plane
+ const auto vertices = materializeRange(mesh.fv_range(fh));
+ auto vertexRelations = vertices * [&split, &mesh](OpenMesh::VertexHandle vh) {
+ return std::make_pair(vh, split.getRelation(mesh.point(vh)));
+ };
+ // Insert new vertices where half edges intersect the split plane
+ for (size_t curIdx = 0; curIdx < vertexRelations.size(); ++curIdx) {
+ const size_t nextIdx = (curIdx + 1) % vertexRelations.size();
+ const auto &current = vertexRelations[curIdx], next = vertexRelations[nextIdx];
+ if (GeometricPlane::isIntersect(current.second, next.second)) {
+ const auto ray = Ray::fromPoints(mesh.point(current.first), mesh.point(next.first));
+ const auto intersect = split.getRayIntersectPosition(ray);
+ assert(intersect);
+ const auto newv = mesh.add_vertex(intersect->position);
+ auto where = vertexRelations.begin();
+ ++curIdx;
+ std::advance(where, curIdx);
+ vertexRelations.emplace(where, newv, GeometricPlane::PlaneRelation::On);
}
}
- else {
- for (const auto & cf : controlledFaces) {
- applyStyle(mesh, parents + this, cf.second);
+ // Create vertex vectors
+ std::array<std::vector<OpenMesh::VertexHandle>, 2> out;
+ auto filterVertices = [&vertexRelations](auto & out, auto notRelation) {
+ for (const auto & vhr : vertexRelations) {
+ if (vhr.second != notRelation) {
+ out.emplace_back(vhr.first);
+ }
}
+ };
+ filterVertices(out.front(), GeometricPlane::PlaneRelation::Above);
+ filterVertices(out.back(), GeometricPlane::PlaneRelation::Below);
+
+ if (out.back().size() > 2) {
+ Shape::CreatedFaces newFaces;
+ const auto oldName = mesh.property(mesh.nameFaceProperty, fh);
+ mesh.delete_face(fh);
+ const auto newf1 = newFaces.insert(mesh.add_namedFace(oldName, out.front()))->second;
+ const auto newf2 = newFaces.insert(mesh.add_namedFace(name, out.back()))->second;
+ mesh.copy_property(mesh.smoothFaceProperty, fh, newf1);
+ mesh.copy_property(mesh.smoothFaceProperty, fh, newf2);
+ fh = newf1;
+ return newFaces;
}
+ return {};
}
bool
FaceController::persist(Persistence::PersistenceStore & store)
{
- return STORE_TYPE && STORE_MEMBER(id) && Style::persist(store) && STORE_MEMBER(type) && STORE_MEMBER(smooth)
- && Mutation::persist(store)
+ return STORE_TYPE && STORE_MEMBER(id) && Style::persist(store) && STORE_MEMBER(type) && Mutation::persist(store)
+ && STORE_NAME_HELPER("split", splits, Persistence::MapByMember<Splits>)
&& STORE_NAME_HELPER("face", faceControllers, Persistence::MapByMember<FaceControllers>);
}
+
+bool
+FaceController::Split::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(id) && STORE_MEMBER(origin) && STORE_MEMBER(normal);
+}
diff --git a/assetFactory/faceController.h b/assetFactory/faceController.h
index 10a226a..0376241 100644
--- a/assetFactory/faceController.h
+++ b/assetFactory/faceController.h
@@ -1,5 +1,6 @@
#pragma once
+#include "geometricPlane.h"
#include "modelFactoryMesh_fwd.h"
#include "mutation.h"
#include "persistence.h"
@@ -10,15 +11,29 @@
class FaceController : public Mutation, public Style, public Persistence::Persistable {
public:
+ class Split : public Persistable, public GeometricPlane {
+ public:
+ std::string id;
+
+ private:
+ friend Persistence::SelectionPtrBase<std::unique_ptr<Split>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+ std::string
+ getId() const override
+ {
+ return {};
+ };
+ };
using FaceControllers = std::map<std::string, std::unique_ptr<FaceController>>;
+ using Splits = std::map<std::string, std::unique_ptr<Split>>;
- void apply(ModelFactoryMesh & mesh, const Style::StyleStack & parents, const std::string & name,
+ void apply(ModelFactoryMesh & mesh, const Style::StyleStack & parents, const std::string & names,
Shape::CreatedFaces & faces) const;
std::string id;
std::string type;
- bool smooth {false};
FaceControllers faceControllers;
+ Splits splits;
private:
friend Persistence::SelectionPtrBase<std::unique_ptr<FaceController>>;
@@ -28,4 +43,10 @@ private:
{
return {};
};
+
+ void applySingle(ModelFactoryMesh & mesh, const Style::StyleStack & parents, const std::string & name,
+ Shape::CreatedFaces & faces) const;
+ Shape::CreatedFaces extrude(ModelFactoryMesh & mesh, const std::string & faceName, OpenMesh::FaceHandle) const;
+ Shape::CreatedFaces split(
+ ModelFactoryMesh & mesh, const std::string & faceName, OpenMesh::FaceHandle &, const Split &) const;
};
diff --git a/assetFactory/factoryMesh.cpp b/assetFactory/factoryMesh.cpp
index 1665b90..8869efd 100644
--- a/assetFactory/factoryMesh.cpp
+++ b/assetFactory/factoryMesh.cpp
@@ -2,34 +2,48 @@
#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, 1);
}
- mesh.garbage_collection();
- mesh.triangulate();
mesh.update_face_normals();
mesh.update_vertex_normals();
std::vector<Vertex> vertices;
+ std::vector<unsigned int> indices;
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);
+ const auto & smooth = mesh.property(mesh.smoothFaceProperty, face);
+ const auto & colour = mesh.color(face);
+
+ std::vector<unsigned int> faceIndices;
+ for (const auto & heh : mesh.fh_range(face)) {
+ const auto & vertex = mesh.to_vertex_handle(heh);
+ const auto & textureUV = mesh.texcoord2D(heh);
+ const auto & point = mesh.point(vertex);
+ const auto & normal = smooth ? mesh.property(mesh.vertex_normals_pph(), vertex)
+ : mesh.property(mesh.face_normals_pph(), face);
+ Vertex outVertex {point, textureUV, normal, colour};
+ if (const auto existingItr = std::find(vertices.rbegin(), vertices.rend(), outVertex);
+ existingItr != vertices.rend()) {
+ faceIndices.push_back(static_cast<unsigned int>(std::distance(existingItr, vertices.rend()) - 1));
+ }
+ else {
+ faceIndices.push_back(static_cast<unsigned int>(vertices.size()));
+ vertices.emplace_back(outVertex);
+ }
+ }
+
+ for (unsigned int i = 2; i < faceIndices.size(); i++) {
+ indices.push_back(faceIndices[0]);
+ indices.push_back(faceIndices[i - 1]);
+ indices.push_back(faceIndices[i]);
}
}
- return std::make_shared<Mesh>(vertices, vectorOfN(vertices.size()));
+ return std::make_shared<Mesh>(vertices, indices);
}
bool
diff --git a/assetFactory/modelFactoryMesh.cpp b/assetFactory/modelFactoryMesh.cpp
index 806ac3b..e640502 100644
--- a/assetFactory/modelFactoryMesh.cpp
+++ b/assetFactory/modelFactoryMesh.cpp
@@ -4,4 +4,20 @@ ModelFactoryMesh::ModelFactoryMesh()
{
add_property(smoothFaceProperty);
add_property(nameFaceProperty);
+ add_property(nameAdjFaceProperty);
+}
+
+void
+ModelFactoryMesh::configNamedFace(const std::string & name, OpenMesh::FaceHandle handle)
+{
+ property(nameFaceProperty, handle) = name;
+ const auto halfEdges = fh_range(handle);
+ for (const auto & he : halfEdges) {
+ if (auto ofh = opposite_face_handle(he); ofh.is_valid()) {
+ property(nameAdjFaceProperty, he) = property(nameFaceProperty, ofh);
+ }
+ if (auto oheh = opposite_halfedge_handle(he); oheh.is_valid()) {
+ property(nameAdjFaceProperty, oheh) = name;
+ }
+ }
}
diff --git a/assetFactory/modelFactoryMesh.h b/assetFactory/modelFactoryMesh.h
index 149ff1a..8ac2edd 100644
--- a/assetFactory/modelFactoryMesh.h
+++ b/assetFactory/modelFactoryMesh.h
@@ -4,6 +4,7 @@
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/Mesh/Traits.hh>
#include <glm/geometric.hpp>
+#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
@@ -35,9 +36,11 @@ 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);
+ HalfedgeAttributes(OpenMesh::Attributes::TexCoord2D);
using Point = glm::vec3;
using Normal = glm::vec3;
using Color = glm::vec4;
+ using TexCoord2D = glm::vec2;
};
struct ModelFactoryMesh : public OpenMesh::PolyMesh_ArrayKernelT<ModelFactoryTraits> {
@@ -45,13 +48,17 @@ struct ModelFactoryMesh : public OpenMesh::PolyMesh_ArrayKernelT<ModelFactoryTra
OpenMesh::FPropHandleT<bool> smoothFaceProperty;
OpenMesh::FPropHandleT<std::string> nameFaceProperty;
+ OpenMesh::HPropHandleT<std::string> nameAdjFaceProperty;
template<typename... Vs>
std::pair<std::string, OpenMesh::FaceHandle>
add_namedFace(std::string name, Vs &&... vs)
{
const auto handle = add_face(std::forward<Vs>(vs)...);
- property(nameFaceProperty, handle) = name;
- return std::make_pair(name, handle);
+ configNamedFace(name, handle);
+ return {std::move(name), handle};
}
+
+private:
+ void configNamedFace(const std::string & name, OpenMesh::FaceHandle);
};
diff --git a/assetFactory/style.cpp b/assetFactory/style.cpp
index fc5c34e..12346a6 100644
--- a/assetFactory/style.cpp
+++ b/assetFactory/style.cpp
@@ -4,27 +4,48 @@
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());
- }
+ for (const auto & face : faces) {
+ applyStyle(mesh, face.second, getColour(parents));
}
}
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());
+ applyStyle(mesh, face, getColour(parents));
+}
+
+void
+Style::applyStyle(
+ ModelFactoryMesh & mesh, const ModelFactoryMesh::FaceHandle & face, EffectiveColour effectiveColour) const
+{
+ if (smooth.has_value()) {
+ mesh.property(mesh.smoothFaceProperty, face) = smooth.value();
+ }
+ if (texture.empty()) {
+ if (effectiveColour.has_value()) {
+ mesh.set_color(face, effectiveColour->get());
+ }
}
+ else {
+ mesh.set_color(face, {});
+ if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
+ auto coords = mf->getTextureCoords(texture);
+ auto coord = coords.begin();
+ // Wild assumption that face is a quad and the texture should apply linearly
+ for (const auto & heh : mesh.fh_range(face)) {
+ mesh.set_texcoord2D(heh, *coord++);
+ }
+ }
+ }
+}
+
+Style::EffectiveColour
+Style::getColour(const StyleStack & parents)
+{
+ return getProperty(parents, &Style::colour, [](auto && style) {
+ return style->colour.a > 0;
+ });
}
bool
@@ -42,5 +63,6 @@ Style::persist(Persistence::PersistenceStore & store)
}
};
- return STORE_HELPER(colour, ColourParser);
+ return STORE_HELPER(colour, ColourParser) && STORE_MEMBER(smooth) && STORE_MEMBER(texture)
+ && STORE_MEMBER(textureRotation);
}
diff --git a/assetFactory/style.h b/assetFactory/style.h
index e8fd012..d931f98 100644
--- a/assetFactory/style.h
+++ b/assetFactory/style.h
@@ -12,6 +12,7 @@ public:
using StyleStack = std::vector<const Style *>;
using Colour = glm::vec3;
using ColourAlpha = glm::vec4;
+ using EffectiveColour = std::optional<std::reference_wrapper<const ColourAlpha>>;
void applyStyle(ModelFactoryMesh &, const StyleStack & parents, const Shape::CreatedFaces &) const;
void applyStyle(ModelFactoryMesh &, const StyleStack & parents, const ModelFactoryMesh::FaceHandle &) const;
@@ -27,8 +28,14 @@ public:
return {};
}
+ static EffectiveColour getColour(const StyleStack & parents);
+
ColourAlpha colour {};
+ std::optional<bool> smooth;
+ std::string texture;
+ std::string textureRotation; // Multiples of 90deg, no int/enum support
protected:
bool persist(Persistence::PersistenceStore & store);
+ void applyStyle(ModelFactoryMesh &, const ModelFactoryMesh::FaceHandle &, EffectiveColour) const;
};
diff --git a/assetFactory/textureFragment.cpp b/assetFactory/textureFragment.cpp
new file mode 100644
index 0000000..72107a5
--- /dev/null
+++ b/assetFactory/textureFragment.cpp
@@ -0,0 +1,7 @@
+#include "textureFragment.h"
+
+bool
+TextureFragment::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(id) && STORE_MEMBER(path);
+}
diff --git a/assetFactory/textureFragment.h b/assetFactory/textureFragment.h
new file mode 100644
index 0000000..52f2591
--- /dev/null
+++ b/assetFactory/textureFragment.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "persistence.h"
+#include "stdTypeDefs.hpp"
+
+class TextureFragment : public Persistence::Persistable, public StdTypeDefs<TextureFragment> {
+public:
+ std::string id;
+ std::string path;
+
+private:
+ friend Persistence::SelectionPtrBase<Ptr>;
+ bool persist(Persistence::PersistenceStore & store) override;
+};
diff --git a/assetFactory/texturePacker.cpp b/assetFactory/texturePacker.cpp
new file mode 100644
index 0000000..68a6010
--- /dev/null
+++ b/assetFactory/texturePacker.cpp
@@ -0,0 +1,80 @@
+#include "texturePacker.h"
+#include "collections.hpp"
+#include <algorithm>
+#include <cstdio>
+#include <numeric>
+#include <ostream>
+#include <set>
+
+TexturePacker::TexturePacker(std::span<const Image> in) :
+ inputImages {std::move(in)}, sortedIndexes {vectorOfN(inputImages.size(), size_t {})}
+{
+ std::sort(sortedIndexes.rbegin(), sortedIndexes.rend(), [this](const auto a, const auto b) {
+ return area(inputImages[a]) < area(inputImages[b]);
+ });
+}
+
+TexturePacker::Result
+TexturePacker::pack() const
+{
+ return pack(minSize());
+}
+
+TexturePacker::Result
+TexturePacker::pack(Size size) const
+{
+ using Spaces = std::set<Space>;
+ Spaces spaces {{{}, size}};
+
+ Positions result(inputImages.size());
+ for (const auto & idx : sortedIndexes) {
+ const auto & image = inputImages[idx];
+ if (const auto spaceItr = std::find_if(spaces.begin(), spaces.end(),
+ [image](const Space & s) {
+ return image.x <= s.size.x && image.y <= s.size.y;
+ });
+ spaceItr != spaces.end()) {
+ auto space = *spaceItr;
+ result[idx] = space.position;
+ spaces.erase(spaceItr);
+ if (space.size.x > image.x) {
+ spaces.emplace(Position {space.position.x + image.x, space.position.y},
+ Size {space.size.x - image.x, image.y});
+ }
+ if (space.size.y > image.y) {
+ spaces.emplace(Position {space.position.x, space.position.y + image.y},
+ Size {space.size.x, space.size.y - image.y});
+ }
+ }
+ else {
+ if (size.x < size.y) {
+ return pack({size.x * 2, size.y});
+ }
+ else {
+ return pack({size.x, size.y * 2});
+ }
+ }
+ }
+
+ return {result, size};
+}
+
+TexturePacker::Size
+TexturePacker::minSize() const
+{
+ return std::accumulate(inputImages.begin(), inputImages.end(), Size {1}, [](Size size, const Image & i) {
+ while (size.x < i.x) {
+ size.x *= 2;
+ }
+ while (size.y < i.y) {
+ size.y *= 2;
+ }
+ return size;
+ });
+}
+
+decltype(TexturePacker::Size::x)
+TexturePacker::area(const Size & size)
+{
+ return size.x * size.y;
+}
diff --git a/assetFactory/texturePacker.h b/assetFactory/texturePacker.h
new file mode 100644
index 0000000..ca0d67a
--- /dev/null
+++ b/assetFactory/texturePacker.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <glm/vec2.hpp>
+#include <span>
+#include <vector>
+
+class TexturePacker {
+public:
+ using Position = glm::uvec2;
+ using Size = glm::uvec2;
+
+ struct Area {
+#ifndef __cpp_aggregate_paren_init
+ constexpr Area(Position p, Size s) : position {std::move(p)}, size {std::move(s)} { }
+#endif
+
+ Position position;
+ Size size;
+ bool
+ operator<(const Area & other) const
+ {
+ return area(size) < area(other.size);
+ }
+ };
+ using Image = Size;
+ using Space = Area;
+ using Positions = std::vector<Position>;
+ using Result = std::pair<Positions, Size>;
+
+ TexturePacker(std::span<const Image>);
+
+ Result pack(Size) const;
+ Result pack() const;
+
+ Size minSize() const;
+ static decltype(Size::x) area(const Size & size);
+
+private:
+ std::span<const Image> inputImages;
+ std::vector<size_t> sortedIndexes;
+};
diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp
index 41ef5e9..4e9263c 100644
--- a/game/vehicles/railVehicleClass.cpp
+++ b/game/vehicles/railVehicleClass.cpp
@@ -50,6 +50,12 @@ RailVehicleClass::persist(Persistence::PersistenceStore & store)
}
void
+RailVehicleClass::postLoad()
+{
+ texture = getTexture();
+}
+
+void
RailVehicleClass::render(
const SceneShader & shader, const Location & location, const std::array<Location, 2> & bl) const
{
diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h
index a2222fb..61ec4ec 100644
--- a/game/vehicles/railVehicleClass.h
+++ b/game/vehicles/railVehicleClass.h
@@ -30,6 +30,7 @@ public:
protected:
friend Persistence::SelectionPtrBase<std::shared_ptr<RailVehicleClass>>;
bool persist(Persistence::PersistenceStore & store) override;
+ void postLoad() override;
private:
RailVehicleClass(std::unique_ptr<ObjParser> obj, std::shared_ptr<Texture>);
diff --git a/gfx/gl/camera.cpp b/gfx/gl/camera.cpp
index b596c43..5b7269e 100644
--- a/gfx/gl/camera.cpp
+++ b/gfx/gl/camera.cpp
@@ -6,8 +6,8 @@
#include <ray.hpp>
Camera::Camera(glm::vec3 pos, float fov, float aspect, float zNear, float zFar) :
- position {pos}, forward {::north}, up {::up}, fov {fov}, aspect {aspect}, near {zNear}, far {zFar},
- projection {glm::perspective(fov, aspect, zNear, zFar)},
+ position {pos}, forward {::north}, up {::up}, near {zNear}, far {zFar}, projection {glm::perspective(
+ fov, aspect, zNear, zFar)},
viewProjection {projection * glm::lookAt(position, position + forward, up)}, inverseViewProjection {
glm::inverse(viewProjection)}
{
diff --git a/gfx/gl/camera.h b/gfx/gl/camera.h
index 9685a7d..b5611f8 100644
--- a/gfx/gl/camera.h
+++ b/gfx/gl/camera.h
@@ -72,7 +72,7 @@ private:
glm::vec3 forward;
glm::vec3 up;
- float fov, aspect, near, far;
+ float near, far;
glm::mat4 projection;
glm::mat4 viewProjection, inverseViewProjection;
};
diff --git a/gfx/gl/shaders/basicShader.fs b/gfx/gl/shaders/basicShader.fs
index 93f0a3f..24b2791 100644
--- a/gfx/gl/shaders/basicShader.fs
+++ b/gfx/gl/shaders/basicShader.fs
@@ -14,8 +14,9 @@ uniform sampler2D texture0;
void
main()
{
- float clear = round(texture(texture0, TexCoords).a);
+ vec4 textureColour = texture(texture0, TexCoords);
+ float clear = round(mix(textureColour.a, 1, Colour.a));
gPosition = vec4(FragPos, clear);
gNormal = vec4(Normal, clear);
- gAlbedoSpec = mix(texture(texture0, TexCoords), vec4(Colour.rgb, 1), Colour.a);
+ gAlbedoSpec = mix(textureColour, vec4(Colour.rgb, 1), Colour.a);
}
diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp
index ef6d7e7..efc76e1 100644
--- a/gfx/models/texture.cpp
+++ b/gfx/models/texture.cpp
@@ -12,34 +12,36 @@
Cache<Texture, std::filesystem::path> Texture::cachedTexture;
-Texture::Texture(const std::filesystem::path & fileName) :
- Texture {Image {Resource::mapPath(fileName).c_str(), STBI_rgb_alpha}}
+Texture::Texture(const std::filesystem::path & fileName, TextureOptions to) :
+ Texture {Image {Resource::mapPath(fileName).c_str(), STBI_rgb_alpha}, to}
{
}
-Texture::Texture(const Image & tex) :
- Texture {static_cast<GLsizei>(tex.width), static_cast<GLsizei>(tex.height), tex.data.data()}
+Texture::Texture(const Image & tex, TextureOptions to) :
+ Texture {static_cast<GLsizei>(tex.width), static_cast<GLsizei>(tex.height), tex.data.data(), to}
{
}
-Texture::Texture(GLsizei width, GLsizei height, const void * data)
+Texture::Texture(GLsizei width, GLsizei height, TextureOptions to) : Texture {width, height, nullptr, to} { }
+
+Texture::Texture(GLsizei width, GLsizei height, const void * data, TextureOptions to) : type {to.type}
{
- glBindTexture(GL_TEXTURE_2D, m_texture);
+ glBindTexture(type, m_texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(type, GL_TEXTURE_WRAP_S, to.wrap);
+ glTexParameteri(type, GL_TEXTURE_WRAP_T, to.wrap);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ glTexParameteri(type, GL_TEXTURE_MIN_FILTER, to.minFilter);
+ glTexParameteri(type, GL_TEXTURE_MAG_FILTER, to.magFilter);
+ glTexImage2D(type, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
void
Texture::bind(GLenum unit) const
{
glActiveTexture(unit);
- glBindTexture(GL_TEXTURE_2D, m_texture);
+ glBindTexture(type, m_texture);
}
void
@@ -61,6 +63,12 @@ Texture::save(const glTexture & texture, GLenum format, GLenum type, const glm::
}
void
+Texture::save(const glm::ivec2 & size, const char * path) const
+{
+ save(m_texture, GL_BGR, GL_UNSIGNED_BYTE, size, 3, path, 2);
+}
+
+void
Texture::save(const glTexture & texture, const glm::ivec2 & size, const char * path)
{
save(texture, GL_BGR, GL_UNSIGNED_BYTE, size, 3, path, 2);
diff --git a/gfx/models/texture.h b/gfx/models/texture.h
index 1aad1e0..ffc9a4a 100644
--- a/gfx/models/texture.h
+++ b/gfx/models/texture.h
@@ -8,16 +8,24 @@
// IWYU pragma: no_forward_declare Cache
class Image;
+struct TextureOptions {
+ GLint wrap {GL_REPEAT};
+ GLint minFilter {GL_LINEAR}, magFilter {GL_LINEAR};
+ GLenum type {GL_TEXTURE_2D};
+};
+
class Texture {
public:
- explicit Texture(const std::filesystem::path & fileName);
- explicit Texture(const Image & image);
- explicit Texture(GLsizei width, GLsizei height, const void * data);
+ explicit Texture(const std::filesystem::path & fileName, TextureOptions = {});
+ explicit Texture(const Image & image, TextureOptions = {});
+ explicit Texture(GLsizei width, GLsizei height, TextureOptions = {});
+ explicit Texture(GLsizei width, GLsizei height, const void * data, TextureOptions = {});
static Cache<Texture, std::filesystem::path> cachedTexture;
void bind(GLenum unit = GL_TEXTURE0) const;
+ void save(const glm::ivec2 & size, const char * path) const;
static void save(const glTexture &, const glm::ivec2 & size, const char * path);
static void saveDepth(const glTexture &, const glm::ivec2 & size, const char * path);
static void saveNormal(const glTexture &, const glm::ivec2 & size, const char * path);
@@ -27,4 +35,5 @@ private:
const char * path, short tgaFormat);
glTexture m_texture;
+ GLenum type;
};
diff --git a/gfx/models/vertex.hpp b/gfx/models/vertex.hpp
index 325aab1..64ec3d0 100644
--- a/gfx/models/vertex.hpp
+++ b/gfx/models/vertex.hpp
@@ -4,10 +4,14 @@
class Vertex {
public:
+#ifndef __cpp_aggregate_paren_init
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)}
{
}
+#endif
+
+ bool operator==(const Vertex &) const = default;
glm::vec3 pos;
glm::vec2 texCoord;
diff --git a/lib/collections.hpp b/lib/collections.hpp
index 16870be..59cec6f 100644
--- a/lib/collections.hpp
+++ b/lib/collections.hpp
@@ -92,11 +92,11 @@ operator+(const std::vector<T...> & in, Vn && vn)
return out;
}
-template<template<typename> typename Direction = std::plus>
+template<template<typename> typename Direction = std::plus, typename T = unsigned int>
[[nodiscard]] static auto
-vectorOfN(std::integral auto N, unsigned int start = {}, unsigned int step = 1)
+vectorOfN(std::integral auto N, T start = {}, T step = 1)
{
- std::vector<unsigned int> v;
+ std::vector<T> v;
v.resize(N);
std::generate_n(v.begin(), N, [&start, step, adj = Direction {}]() {
return std::exchange(start, adj(start, step));
@@ -117,3 +117,18 @@ materializeRange(const std::pair<In, In> & in)
{
return Rtn(in.first, in.second);
}
+
+template<typename T> struct pair_range {
+ constexpr auto &
+ begin() const noexcept
+ {
+ return pair.first;
+ }
+ constexpr auto &
+ end() const noexcept
+ {
+ return pair.second;
+ }
+ const std::pair<T, T> & pair;
+};
+template<typename T> pair_range(std::pair<T, T>) -> pair_range<T>;
diff --git a/lib/geometricPlane.cpp b/lib/geometricPlane.cpp
new file mode 100644
index 0000000..71216c1
--- /dev/null
+++ b/lib/geometricPlane.cpp
@@ -0,0 +1,28 @@
+#include "geometricPlane.h"
+#include "ray.hpp"
+#include <glm/geometric.hpp>
+#include <glm/gtx/intersect.hpp>
+
+GeometricPlane::PlaneRelation
+GeometricPlane::getRelation(glm::vec3 p) const
+{
+ const auto d = glm::dot(normal, p - origin);
+ return d < 0.f ? PlaneRelation::Below : d > 0.f ? PlaneRelation::Above : PlaneRelation::On;
+}
+
+bool
+GeometricPlane::isIntersect(PlaneRelation a, PlaneRelation b)
+{
+ return ((a == PlaneRelation::Above && b == PlaneRelation::Below)
+ || (a == PlaneRelation::Below && b == PlaneRelation::Above));
+}
+
+std::optional<GeometricPlane::DistAndPosition>
+GeometricPlane::getRayIntersectPosition(const Ray & ray) const
+{
+ float dist {};
+ if (!glm::intersectRayPlane(ray.start, ray.direction, origin, normal, dist)) {
+ return {};
+ }
+ return DistAndPosition {dist, ray.start + (ray.direction * dist)};
+}
diff --git a/lib/geometricPlane.h b/lib/geometricPlane.h
new file mode 100644
index 0000000..dc8df50
--- /dev/null
+++ b/lib/geometricPlane.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <glm/vec3.hpp>
+#include <optional>
+
+class Ray;
+
+class GeometricPlane {
+public:
+ struct DistAndPosition {
+ float dist;
+ glm::vec3 position;
+ };
+ enum class PlaneRelation { Above, Below, On };
+
+ glm::vec3 origin, normal;
+
+ PlaneRelation getRelation(glm::vec3 point) const;
+ std::optional<DistAndPosition> getRayIntersectPosition(const Ray &) const;
+
+ static bool isIntersect(PlaneRelation a, PlaneRelation b);
+};
diff --git a/lib/persistence.cpp b/lib/persistence.cpp
index 8c7c6a4..e22d74d 100644
--- a/lib/persistence.cpp
+++ b/lib/persistence.cpp
@@ -41,12 +41,22 @@ namespace Persistence {
return ss.str();
}
+ void
+ Persistable::postLoad()
+ {
+ }
+
PersistenceSelect::PersistenceSelect(const std::string & n) : name {n} { }
- PersistenceStore::NameAction
- PersistenceSelect::setName(const std::string_view key, const Selection &)
+ PersistenceStore::NameActionSelection
+ PersistenceSelect::setName(const std::string_view key, SelectionFactory && factory)
{
- return (key == name) ? NameAction::Push : NameAction::Ignore;
+ if (key == name) {
+ return {NameAction::Push, factory()};
+ }
+ else {
+ return {NameAction::Ignore, nullptr};
+ }
}
void
@@ -56,10 +66,11 @@ namespace Persistence {
PersistenceWrite::PersistenceWrite(const Writer & o, bool sh) : out {o}, shared {sh} { }
- PersistenceStore::NameAction
- PersistenceWrite::setName(const std::string_view key, const Selection & s)
+ PersistenceStore::NameActionSelection
+ PersistenceWrite::setName(const std::string_view key, SelectionFactory && factory)
{
- if (s.needsWrite()) {
+ auto s = factory();
+ if (s->needsWrite()) {
if (!first) {
out.nextValue();
}
@@ -67,9 +78,9 @@ namespace Persistence {
first = false;
}
out.pushKey(key);
- return NameAction::HandleAndContinue;
+ return {NameAction::HandleAndContinue, std::move(s)};
}
- return NameAction::Ignore;
+ return {NameAction::Ignore, nullptr};
}
void
diff --git a/lib/persistence.h b/lib/persistence.h
index 35d60ca..cc2e4e5 100644
--- a/lib/persistence.h
+++ b/lib/persistence.h
@@ -6,6 +6,7 @@
#include <iosfwd>
#include <map>
#include <memory>
+#include <optional>
#include <span>
#include <special_members.hpp>
#include <sstream>
@@ -147,8 +148,13 @@ namespace Persistence {
}
};
+ template<typename T> struct SelectionT<std::optional<T>> : public SelectionT<T> {
+ explicit SelectionT(std::optional<T> & value) : SelectionT<T> {value.emplace()} { }
+ };
+
struct Persistable;
struct PersistenceStore {
+ using SelectionFactory = std::function<SelectionPtr()>;
PersistenceStore() = default;
virtual ~PersistenceStore() = default;
DEFAULT_MOVE_NO_COPY(PersistenceStore);
@@ -156,12 +162,14 @@ namespace Persistence {
template<typename T> [[nodiscard]] inline bool persistType(const T * const, const std::type_info & ti);
enum class NameAction { Push, HandleAndContinue, Ignore };
+ using NameActionSelection = std::pair<NameAction, SelectionPtr>;
template<typename Helper, typename T>
[[nodiscard]] inline bool
persistValue(const std::string_view key, T & value)
{
- auto s = std::make_unique<Helper>(value);
- const auto act {setName(key, *s)};
+ auto [act, s] = setName(key, [&value]() {
+ return std::make_unique<Helper>(value);
+ });
if (act != NameAction::Ignore) {
sel = std::move(s);
if (act == NameAction::HandleAndContinue) {
@@ -171,7 +179,7 @@ namespace Persistence {
return (act != NameAction::Push);
}
- virtual NameAction setName(const std::string_view key, const Selection &) = 0;
+ [[nodiscard]] virtual NameActionSelection setName(const std::string_view key, SelectionFactory &&) = 0;
virtual void selHandler() {};
virtual void setType(const std::string_view, const Persistable *) = 0;
@@ -181,7 +189,7 @@ namespace Persistence {
struct PersistenceSelect : public PersistenceStore {
explicit PersistenceSelect(const std::string & n);
- NameAction setName(const std::string_view key, const Selection &) override;
+ NameActionSelection setName(const std::string_view key, SelectionFactory &&) override;
void setType(const std::string_view, const Persistable *) override;
@@ -191,7 +199,7 @@ namespace Persistence {
struct PersistenceWrite : public PersistenceStore {
explicit PersistenceWrite(const Writer & o, bool sh);
- NameAction setName(const std::string_view key, const Selection &) override;
+ NameActionSelection setName(const std::string_view key, SelectionFactory &&) override;
void selHandler() override;
@@ -311,8 +319,9 @@ namespace Persistence {
void
endObject(Persistence::Stack & stk) override
{
+ // TODO test with unique_ptr
map.emplace(std::invoke(Key, s), std::move(s));
- stk.pop();
+ Persistence::SelectionT<Type>::endObject(stk);
}
private:
@@ -327,8 +336,9 @@ namespace Persistence {
void
endObject(Persistence::Stack & stk) override
{
+ // TODO test with unique_ptr
container.emplace_back(std::move(s));
- stk.pop();
+ Persistence::SelectionT<Type>::endObject(stk);
}
private:
@@ -342,6 +352,7 @@ namespace Persistence {
DEFAULT_MOVE_COPY(Persistable);
virtual bool persist(PersistenceStore & store) = 0;
+ virtual void postLoad();
[[nodiscard]] virtual std::string getId() const;
@@ -484,6 +495,9 @@ namespace Persistence {
endObject(Stack & stk) override
{
make_default_as_needed(this->v);
+ if (this->v) {
+ this->v->postLoad();
+ }
stk.pop();
}
diff --git a/lib/ray.cpp b/lib/ray.cpp
index acbb807..f9e3311 100644
--- a/lib/ray.cpp
+++ b/lib/ray.cpp
@@ -1,6 +1,12 @@
#include "ray.hpp"
#include <algorithm>
+Ray
+Ray::fromPoints(glm::vec3 start, glm::vec3 p)
+{
+ return {start, glm::normalize(p - start)};
+}
+
float
Ray::distanceToLine(const glm::vec3 & p1, const glm::vec3 & e1) const
{
diff --git a/lib/ray.hpp b/lib/ray.hpp
index 8bef1c8..9bf47af 100644
--- a/lib/ray.hpp
+++ b/lib/ray.hpp
@@ -7,6 +7,8 @@ class Ray {
public:
Ray(glm::vec3 start, glm::vec3 direction) : start {start}, direction {direction} { }
+ static Ray fromPoints(glm::vec3, glm::vec3);
+
glm::vec3 start;
glm::vec3 direction;
diff --git a/lib/stream_support.hpp b/lib/stream_support.hpp
index 6a3c2cf..52cc9d9 100644
--- a/lib/stream_support.hpp
+++ b/lib/stream_support.hpp
@@ -83,4 +83,4 @@ streamed_string(const T & v)
return std::move(ss).str();
}
-#define CLOG(x) std::cerr << #x " : " << x << "\n";
+#define CLOG(x) std::cerr << __LINE__ << " : " #x " : " << x << "\n";
diff --git a/res/brush47.xml b/res/brush47.xml
index dbd3327..31518c8 100644
--- a/res/brush47.xml
+++ b/res/brush47.xml
@@ -2,8 +2,7 @@
<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" colour="silver">
- <face id="top" colour="#413b3a"/>
- <face id="bottom" colour="#413b3a"/>
+ <face id="top bottom" colour="#413b3a"/>
</use>
<use type="cuboid" scale="0.3,0.5,0.3" position="0.2,0,0.421" colour="grey30"/>
</object>
@@ -59,17 +58,35 @@
</face>
</use>
</object>
+ <textureFragment id="roofSideWithVents" path="rail/roofSideWithVents.png"/>
+ <textureFragment id="roofTopWithVents" path="rail/roofTopWithVents.png"/>
+ <textureFragment id="cabWindowFront" path="rail/cabWindowFront.png"/>
<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,0.3" colour="goldenrod">
<face id="bottom" colour="#2c3539"/>
<face id="top" type="extrude" position="0,0,1.05" colour="#2c4f5a">
- <face id="front" colour="goldenrod"/>
- <face id="back" colour="goldenrod"/>
+ <face id="front back" colour="goldenrod"/>
+ <face id="right left">
+ <split id="frontcorner" origin="0,7.90704,0" normal="0,1,0"/>
+ <split id="backcorner" origin="0,-7.90704,0" normal="0,-1,0"/>
+ <face id="rightfrontcorner leftfrontcorner rightbackcorner leftbackcorner" colour="goldenrod"/>
+ </face>
<face id="top" type="extrude" scale="1,0.96,1" position="0,0,0.775">
- <face id="front" colour="#e1eff3"/>
- <face id="back" colour="#e1eff3"/>
- <face id="top" type="extrude" scale="0.5,0.85,0" smooth="true" position="0,0,0.575" colour="#aeb0b0"/>
+ <face id="front back" texture="cabWindowFront"/>
+ <face id="right left">
+ <split id="frontcorner" origin="0,7.90704,0" normal="0,1,0"/>
+ <split id="backcorner" origin="0,-7.90704,0" normal="0,-1,0"/>
+ <face id="rightfrontcorner leftfrontcorner rightbackcorner leftbackcorner" texture="cabWindowFront"/>
+ </face>
+ <face id="top" type="extrude" scale="0.5,0.85,0" smooth="true" position="0,0,0.575" colour="#aeb0b0">
+ <face id="top" texture="roofTopWithVents"/>
+ <face id="right left">
+ <split id="frontcorner" origin="0,7.90704,0" normal="0,1,0"/>
+ <split id="backcorner" origin="0,-7.90704,0" normal="0,-1,0"/>
+ <face id="right left" texture="roofSideWithVents"/>
+ </face>
+ </face>
</face>
</face>
</use>
diff --git a/res/rail/cabWindowFront.png b/res/rail/cabWindowFront.png
new file mode 100644
index 0000000..3803f21
--- /dev/null
+++ b/res/rail/cabWindowFront.png
Binary files differ
diff --git a/res/rail/roofSideWithVents.png b/res/rail/roofSideWithVents.png
new file mode 100644
index 0000000..67ed851
--- /dev/null
+++ b/res/rail/roofSideWithVents.png
Binary files differ
diff --git a/res/rail/roofTopWithVents.png b/res/rail/roofTopWithVents.png
new file mode 100644
index 0000000..0f64ced
--- /dev/null
+++ b/res/rail/roofTopWithVents.png
Binary files differ
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index e178573..bf1c408 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -1,5 +1,6 @@
import testing ;
import sequence ;
+import path : glob-tree ;
lib boost_unit_test_framework ;
lib benchmark ;
@@ -48,14 +49,14 @@ run test-maths.cpp ;
run test-lib.cpp ;
run test-geo.cpp ;
run test-network.cpp ;
-run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob fixtures/json/*.json fixtures/json/bad/*.json ] ] : <library>test ;
+run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob-tree fixtures : *.json ] ] : <library>test ;
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 ;
-run perf-persistence.cpp : : : <library>benchmark <library>test ;
+run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.xml *.png ] fixtures/rgb.txt ] : <library>test ;
+run perf-assetFactory.cpp : : : <library>benchmark <library>test <dependency>test-assetFactory ;
+run perf-persistence.cpp : : : <library>benchmark <library>test <dependency>test-persistence ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
explicit perf-assetFactory ;
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
index 204ffb3..54168aa 100644
--- a/test/test-assetFactory.cpp
+++ b/test/test-assetFactory.cpp
@@ -7,6 +7,7 @@
#include "assetFactory/assetFactory.h"
#include "assetFactory/object.h"
+#include "assetFactory/texturePacker.h"
#include "game/vehicles/railVehicle.h"
#include "game/vehicles/railVehicleClass.h"
#include "gfx/gl/sceneRenderer.h"
@@ -67,7 +68,7 @@ private:
};
BOOST_FIXTURE_TEST_SUITE(m, FactoryFixture);
-BOOST_AUTO_TEST_CASE(brush47xml)
+BOOST_AUTO_TEST_CASE(brush47xml, *boost::unit_test::timeout(5))
{
auto mf = AssetFactory::loadXML(RESDIR "/brush47.xml");
BOOST_REQUIRE(mf);
@@ -117,7 +118,7 @@ BOOST_DATA_TEST_CASE(normalizeColourName,
BOOST_CHECK_EQUAL(in, exp);
}
-BOOST_AUTO_TEST_CASE(parseX11RGB)
+BOOST_AUTO_TEST_CASE(parseX11RGB, *boost::unit_test::timeout(5))
{
const auto parsedColours = AssetFactory::parseX11RGB(FIXTURESDIR "rgb.txt");
BOOST_REQUIRE_EQUAL(parsedColours.size(), 20);
@@ -125,3 +126,46 @@ BOOST_AUTO_TEST_CASE(parseX11RGB)
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));
}
+
+BOOST_AUTO_TEST_CASE(texturePacker, *boost::unit_test::timeout(5))
+{
+ std::vector<TexturePacker::Image> input {
+ {10, 10},
+ {10, 10},
+ {10, 10},
+ {100, 10},
+ {10, 200},
+ {5, 5},
+ };
+ TexturePacker tp {input};
+ BOOST_CHECK_EQUAL(TexturePacker::Size(128, 256), tp.minSize());
+ const auto result = tp.pack();
+}
+
+BOOST_AUTO_TEST_CASE(texturePacker_many, *boost::unit_test::timeout(5))
+{
+ std::vector<TexturePacker::Image> images(256);
+ std::fill(images.begin(), images.end(), TexturePacker::Image {32, 32});
+ const auto totalSize = std::accumulate(images.begin(), images.end(), 0U, [](auto t, const auto & i) {
+ return t + TexturePacker::area(i);
+ });
+ TexturePacker tp {images};
+ BOOST_CHECK_EQUAL(TexturePacker::Size(32, 32), tp.minSize());
+ const auto result = tp.pack();
+ BOOST_CHECK_EQUAL(result.first.size(), images.size());
+ BOOST_CHECK_GE(TexturePacker::area(result.second), TexturePacker::area(images.front()) * images.size());
+ BOOST_CHECK_EQUAL(totalSize, TexturePacker::area(result.second));
+}
+
+BOOST_AUTO_TEST_CASE(texturePacker_many_random, *boost::unit_test::timeout(5))
+{
+ std::vector<TexturePacker::Image> images(2048);
+ std::mt19937 gen(std::random_device {}());
+ std::uniform_int_distribution<> dim {1, 10};
+ std::generate(images.begin(), images.end(), [&dim, &gen]() {
+ return TexturePacker::Image {2 ^ dim(gen), 2 ^ dim(gen)};
+ });
+ TexturePacker tp {images};
+ const auto result = tp.pack();
+ BOOST_CHECK_EQUAL(result.first.size(), images.size());
+}
diff --git a/test/test-persistence.cpp b/test/test-persistence.cpp
index a72c481..38bbf2f 100644
--- a/test/test-persistence.cpp
+++ b/test/test-persistence.cpp
@@ -34,6 +34,7 @@ struct JPP {
BOOST_FIXTURE_TEST_CASE(load_object, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/load_object.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_CHECK_CLOSE(to->flt, 3.14, 0.01);
BOOST_CHECK_EQUAL(to->str, "Lovely string");
BOOST_CHECK_EQUAL(to->bl, true);
@@ -69,6 +70,7 @@ BOOST_FIXTURE_TEST_CASE(load_object, JPP)
BOOST_FIXTURE_TEST_CASE(load_nested_object, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/nested.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_CHECK_EQUAL(to->flt, 1.F);
BOOST_CHECK_EQUAL(to->str, "one");
BOOST_REQUIRE(to->ptr);
@@ -86,6 +88,7 @@ BOOST_FIXTURE_TEST_CASE(load_nested_object, JPP)
BOOST_FIXTURE_TEST_CASE(load_implicit_object, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/implicit.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_CHECK(to->ptr);
BOOST_CHECK_EQUAL(to->flt, 1.F);
BOOST_CHECK_EQUAL(to->ptr->str, "trigger");
@@ -95,6 +98,7 @@ BOOST_FIXTURE_TEST_CASE(load_implicit_object, JPP)
BOOST_FIXTURE_TEST_CASE(load_empty_object, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/empty.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_CHECK_EQUAL(to->flt, 1.F);
BOOST_CHECK(to->ptr);
BOOST_CHECK_EQUAL(to->str, "after");
@@ -119,6 +123,7 @@ BOOST_FIXTURE_TEST_CASE(load_obj_no_such_type, JPP)
BOOST_FIXTURE_TEST_CASE(load_abs_object, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/abs.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_REQUIRE(to->aptr);
BOOST_CHECK_NO_THROW(to->aptr->dummy());
BOOST_CHECK_EQUAL(to->aptr->base, "set base");
@@ -130,6 +135,7 @@ BOOST_FIXTURE_TEST_CASE(load_abs_object, JPP)
BOOST_FIXTURE_TEST_CASE(load_vector_ptr, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/vector_ptr.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_CHECK(to->str.empty());
BOOST_CHECK_EQUAL(to->vptr.size(), 4);
BOOST_CHECK_EQUAL(to->vptr.at(0)->str, "type");
@@ -141,6 +147,7 @@ BOOST_FIXTURE_TEST_CASE(load_vector_ptr, JPP)
BOOST_FIXTURE_TEST_CASE(test_conversion, JPP)
{
auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/conv.json");
+ BOOST_CHECK_EQUAL(to->postLoadCalled, 1);
BOOST_REQUIRE(to);
BOOST_CHECK_EQUAL(to->bl, true);
BOOST_CHECK_EQUAL(to->flt, 3.14F);
diff --git a/test/testStructures.cpp b/test/testStructures.cpp
index 8305078..469ec37 100644
--- a/test/testStructures.cpp
+++ b/test/testStructures.cpp
@@ -42,6 +42,12 @@ TestObject::persist(Persistence::PersistenceStore & store)
&& STORE_MEMBER(vptr);
}
+void
+TestObject::postLoad()
+{
+ postLoadCalled++;
+}
+
bool
SharedTestObject::persist(Persistence::PersistenceStore & store)
{
diff --git a/test/testStructures.h b/test/testStructures.h
index 666562e..6966052 100644
--- a/test/testStructures.h
+++ b/test/testStructures.h
@@ -39,7 +39,10 @@ struct TestObject : public Persistence::Persistable {
std::unique_ptr<AbsObject> aptr;
std::vector<std::unique_ptr<TestObject>> vptr;
+ unsigned int postLoadCalled {};
+
bool persist(Persistence::PersistenceStore & store) override;
+ void postLoad() override;
};
struct SharedTestObject : public Persistence::Persistable {