summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jamroot.jam27
-rw-r--r--application/main.cpp9
-rw-r--r--assetFactory/asset.h4
-rw-r--r--assetFactory/assetFactory.cpp63
-rw-r--r--assetFactory/assetFactory.h8
-rw-r--r--assetFactory/assimp.cpp130
-rw-r--r--assetFactory/assimp.h14
-rw-r--r--assetFactory/factoryMesh.cpp61
-rw-r--r--assetFactory/modelFactoryMesh.cpp1
-rw-r--r--assetFactory/modelFactoryMesh.h2
-rw-r--r--assetFactory/style.cpp9
-rw-r--r--assetFactory/textureFragment.cpp10
-rw-r--r--assetFactory/textureFragment.h6
-rw-r--r--assetFactory/texturePacker.cpp29
-rw-r--r--assetFactory/texturePacker.h1
-rw-r--r--game/scenary/foliage.cpp33
-rw-r--r--game/scenary/foliage.h22
-rw-r--r--game/scenary/plant.cpp13
-rw-r--r--game/scenary/plant.h22
-rw-r--r--game/vehicles/railVehicleClass.cpp53
-rw-r--r--game/vehicles/railVehicleClass.h9
-rw-r--r--gfx/gl/shaders/basicShader.fs56
-rw-r--r--gfx/gl/shaders/basicShader.vs12
-rw-r--r--gfx/gl/shaders/geometryOut.glsl3
-rw-r--r--gfx/gl/shaders/landmassShader.fs9
-rw-r--r--gfx/gl/shaders/landmassShader.vs12
-rw-r--r--gfx/gl/shaders/lightingShader.vs2
-rw-r--r--gfx/gl/shaders/materialInterface.glsl7
-rw-r--r--gfx/gl/shaders/meshIn.glsl5
-rw-r--r--gfx/gl/shaders/shadowDynamicPoint.vs2
-rw-r--r--gfx/gl/shaders/shadowFixedPoint.vs2
-rw-r--r--gfx/gl/shaders/waterShader.fs9
-rw-r--r--gfx/gl/shaders/waterShader.vs9
-rw-r--r--gfx/gl/vertexArrayObject.hpp2
-rw-r--r--gfx/image.cpp16
-rw-r--r--gfx/image.h1
-rw-r--r--gfx/models/mesh.cpp3
-rw-r--r--gfx/models/obj.h38
-rw-r--r--gfx/models/obj.impl.cpp77
-rw-r--r--gfx/models/obj.ll116
-rw-r--r--gfx/models/texture.cpp95
-rw-r--r--gfx/models/texture.h40
-rw-r--r--gfx/models/tga.h14
-rw-r--r--gfx/models/vertex.hpp9
-rw-r--r--glsl.jam36
-rw-r--r--lib/collections.hpp27
-rw-r--r--lib/gl_traits.hpp45
-rw-r--r--lib/work.h12
-rw-r--r--lib/worker.cpp39
-rw-r--r--lib/worker.h132
-rw-r--r--res/assetTemplate.m45
-rw-r--r--res/brush47.mtl13
-rw-r--r--res/brush47.obj260
-rw-r--r--res/brush47.pngbin111263 -> 0 bytes
-rw-r--r--res/foliage.xml159
-rw-r--r--res/shapespark-low-poly-plants-kit.fbxbin0 -> 20264540 bytes
-rw-r--r--test/Jamfile.jam10
-rw-r--r--test/perf-assetFactory.cpp11
-rw-r--r--test/test-assetFactory.cpp28
-rw-r--r--test/test-obj.cpp39
-rw-r--r--test/test-render.cpp30
-rw-r--r--test/test-worker.cpp104
62 files changed, 1144 insertions, 871 deletions
diff --git a/Jamroot.jam b/Jamroot.jam
index e2075cf..35b48ea 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -1,18 +1,17 @@
using gcc ;
using pkg-config ;
import pkg-config ;
-import type : register ;
-import type : type ;
-import generators : register-standard ;
import testing ;
import lex ;
import sequence ;
+import glsl ;
pkg-config.import sdl2 ;
pkg-config.import glew ;
pkg-config.import freetype2 ;
pkg-config.import glib-2.0 ;
pkg-config.import mxml ;
+pkg-config.import assimp ;
lib pthread ;
lib OpenMeshCore ;
@@ -23,7 +22,6 @@ project : requirements
<variant>profile:<lto>on
<variant>coverage:<coverage>on
<toolset>tidy:<enable>all
- <toolset>tidy:<exclude>bin/link-static/gfx/models/obj.cpp
<toolset>tidy:<exclude>bin/link-static/lib/jsonParse.cpp
<toolset>tidy:<checkxx>boost-*
<toolset>tidy:<checkxx>bugprone-*
@@ -49,26 +47,6 @@ project : requirements
<toolset>tidy:<define>TIDY
;
-type.register GL_VERTEX_SHADER : vs ;
-type.register GL_GEOMETRY_SHADER : gs ;
-type.register GL_FRAGMENT_SHADER : fs ;
-
-generators.register-standard embed.glsl : GL_VERTEX_SHADER : CPP(vs-%) H(vs-%) ;
-generators.register-standard embed.glsl : GL_GEOMETRY_SHADER : CPP(gs-%) H(gs-%) ;
-generators.register-standard embed.glsl : GL_FRAGMENT_SHADER : CPP(fs-%) H(fs-%) ;
-
-actions embed.glsl
-{
- m4 -DNAME=$(2:B) -DTYPE=$(2:S) > $(1[2]) lib/embed-glsl.h.m4
- m4 -DSOURCE=$(2) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(OPTIONS) > $(1[1]) lib/embed-glsl.cpp.m4
-}
-rule embed.glsl ( targets * : sources * : properties * )
-{
- DEPENDS $(targets) : lib/embed-glsl.h.m4 lib/embed-glsl.cpp.m4 ;
- OPTIONS on $(targets) = [ type.type $(sources) ] ;
-}
-IMPORT $(__name__) : embed.glsl : : embed.glsl ;
-
exe iliketrains :
application/main.cpp
:
@@ -115,6 +93,7 @@ lib ilt :
<library>freetype2
<library>glib-2.0
<library>mxml
+ <library>assimp
<library>pthread
<library>OpenMeshCore
: :
diff --git a/application/main.cpp b/application/main.cpp
index 1f91dab..aea3d2e 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -1,4 +1,5 @@
#include <array>
+#include <assetFactory/assetFactory.h>
#include <chrono>
#include <collection.hpp>
#include <game/activities/go.h>
@@ -11,6 +12,8 @@
#include <game/objective.h>
#include <game/objectives/goto.h>
#include <game/orders.h>
+#include <game/scenary/foliage.h>
+#include <game/scenary/plant.h>
#include <game/terrain.h>
#include <game/vehicles/railVehicle.h>
#include <game/vehicles/railVehicleClass.h>
@@ -43,6 +46,7 @@ public:
world.create<Terrain>(geoData);
{
+ const auto assets = AssetFactory::loadAll("res");
auto rl = world.create<RailLinks>();
const glm::vec3 j {-1120, -1100, 3}, k {-1100, -1000, 15}, l {-1000, -800, 20}, m {-900, -600, 30},
n {-600, -500, 32}, o {-500, -800, 30}, p {-600, -900, 25}, q {-1025, -1175, 10},
@@ -66,13 +70,16 @@ public:
rl->addLinksBetween(t, u);
rl->addLinksBetween(u, m);
const std::shared_ptr<Train> train = world.create<Train>(l3);
- auto b47 = std::make_shared<RailVehicleClass>("brush47");
+ auto b47 = std::dynamic_pointer_cast<RailVehicleClass>(assets.at("brush-47"));
for (int N = 0; N < 6; N++) {
train->create<RailVehicle>(b47);
}
train->orders.removeAll();
train->orders.create<GoTo>(&train->orders, l3->ends[1], l3->length, rl->findNodeAt({-1100, -450, 15}));
train->currentActivity = train->orders.current()->createActivity();
+
+ auto foliage = std::dynamic_pointer_cast<Foliage>(assets.at("Tree-01-1"));
+ world.create<Plant>(foliage, Location {{-1100, -1100, 0}});
}
auto t_start = std::chrono::high_resolution_clock::now();
diff --git a/assetFactory/asset.h b/assetFactory/asset.h
index 30f40cd..dba4974 100644
--- a/assetFactory/asset.h
+++ b/assetFactory/asset.h
@@ -4,11 +4,11 @@
#include "persistence.h"
#include <stdTypeDefs.hpp>
-class Texture;
+class TextureAtlas;
class Asset : public Persistence::Persistable, public StdTypeDefs<Asset> {
public:
- using TexturePtr = std::shared_ptr<Texture>;
+ using TexturePtr = std::shared_ptr<TextureAtlas>;
std::string id;
std::string name;
diff --git a/assetFactory/assetFactory.cpp b/assetFactory/assetFactory.cpp
index ed1af58..af0cd54 100644
--- a/assetFactory/assetFactory.cpp
+++ b/assetFactory/assetFactory.cpp
@@ -11,7 +11,7 @@
#include "resource.h"
#include "saxParse-persistence.h"
#include "texturePacker.h"
-#include <stb/stb_image.h>
+#include <numeric>
AssetFactory::AssetFactory() :
shapes {
@@ -30,6 +30,18 @@ AssetFactory::loadXML(const std::filesystem::path & filename)
return Persistence::SAXParsePersistence {}.loadState<std::shared_ptr<AssetFactory>>(file);
}
+AssetFactory::Assets
+AssetFactory::loadAll(const std::filesystem::path & root)
+{
+ return std::accumulate(std::filesystem::recursive_directory_iterator {root},
+ std::filesystem::recursive_directory_iterator {}, Assets {}, [](auto && out, auto && path) {
+ if (path.path().extension() == ".xml") {
+ out.merge(loadXML(path)->assets);
+ }
+ return std::move(out);
+ });
+}
+
AssetFactory::Colours
AssetFactory::parseX11RGB(const char * path)
{
@@ -81,17 +93,11 @@ 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
+GLuint
+AssetFactory::getMaterialIndex(std::string_view id) const
{
createTexutre();
- const auto & fragmentUV = textureFragmentPositions.at(id);
- return {
- fragmentUV.xy(),
- fragmentUV.zy(),
- fragmentUV.zw(),
- fragmentUV.xw(),
- };
+ return textureFragmentPositions.at(id);
}
Asset::TexturePtr
@@ -104,33 +110,31 @@ AssetFactory::getTexture() const
void
AssetFactory::createTexutre() const
{
- if (!textureFragments.empty() && (!texture || textureFragmentPositions.empty())) {
- // * load images
- std::vector<std::unique_ptr<Image>> images;
+ if (!textureFragments.empty() && !texture) {
+ // * layout images
+ std::map<const TextureFragment *, std::unique_ptr<const 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);
+ textureFragments.begin(), textureFragments.end(), std::inserter(images, images.end()), [](auto && tf) {
+ return decltype(images)::value_type {tf.second.get(), tf.second->image->get()};
});
- // * 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};
+ std::transform(images.begin(), images.end(), std::back_inserter(imageSizes), [](const auto & i) {
+ return TexturePacker::Image {i.second->width, i.second->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(),
+ texture = std::make_shared<TextureAtlas>(outSize.x, outSize.y, layout.size());
+ std::transform(images.begin(), images.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 = layout.begin(), size = imageSizes.begin(), this](const auto & i) mutable {
+ const auto m = texture->add(*position, *size, i.second->data.data(),
+ {
+ .wrapU = i.first->mapmodeU,
+ .wrapV = i.first->mapmodeV,
+ });
position++;
- image++;
size++;
- return decltype(textureFragmentPositions)::value_type {tf.first, positionFraction};
+ return decltype(textureFragmentPositions)::value_type {i.first->id, m};
});
}
}
@@ -141,7 +145,8 @@ AssetFactory::persist(Persistence::PersistenceStore & store)
using MapObjects = Persistence::MapByMember<Shapes, std::shared_ptr<Object>>;
using MapAssets = Persistence::MapByMember<Assets>;
using MapTextureFragments = Persistence::MapByMember<TextureFragments>;
+ using MapAssImp = Persistence::MapByMember<AssImps, std::shared_ptr<AssImp>, &AssImp::path>;
return STORE_TYPE && STORE_NAME_HELPER("object", shapes, MapObjects)
&& STORE_NAME_HELPER("textureFragment", textureFragments, MapTextureFragments)
- && STORE_NAME_HELPER("asset", assets, MapAssets);
+ && STORE_NAME_HELPER("assimp", assimps, MapAssImp) && STORE_NAME_HELPER("asset", assets, MapAssets);
}
diff --git a/assetFactory/assetFactory.h b/assetFactory/assetFactory.h
index 52692c4..e449ce2 100644
--- a/assetFactory/assetFactory.h
+++ b/assetFactory/assetFactory.h
@@ -1,6 +1,7 @@
#pragma once
#include "asset.h"
+#include "assimp.h"
#include "persistence.h"
#include "shape.h"
#include "textureFragment.h"
@@ -12,6 +13,7 @@ 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 AssImps = std::map<std::string, AssImp::Ptr, std::less<>>;
using TextureFragments = std::map<std::string, TextureFragment::Ptr, std::less<>>;
using Colour = glm::vec3;
using ColourAlpha = glm::vec4;
@@ -20,12 +22,14 @@ public:
AssetFactory();
[[nodiscard]] static std::shared_ptr<AssetFactory> loadXML(const std::filesystem::path &);
+ [[nodiscard]] static Assets loadAll(const std::filesystem::path &);
[[nodiscard]] ColourAlpha parseColour(std::string_view) const;
- [[nodiscard]] TextureFragmentCoords getTextureCoords(std::string_view) const;
+ [[nodiscard]] GLuint getMaterialIndex(std::string_view) const;
[[nodiscard]] Asset::TexturePtr getTexture() const;
Shapes shapes;
Assets assets;
+ AssImps assimps;
Colours colours;
TextureFragments textureFragments;
@@ -39,5 +43,5 @@ private:
void createTexutre() const;
mutable Asset::TexturePtr texture;
- mutable std::map<std::string_view, glm::vec4, std::less<>> textureFragmentPositions;
+ mutable std::map<std::string_view, GLuint, std::less<>> textureFragmentPositions;
};
diff --git a/assetFactory/assimp.cpp b/assetFactory/assimp.cpp
new file mode 100644
index 0000000..840c5a9
--- /dev/null
+++ b/assetFactory/assimp.cpp
@@ -0,0 +1,130 @@
+#include "assimp.h"
+#include "assetFactory.h"
+#include "collections.hpp"
+#include "ptr.hpp"
+#include "resource.h"
+#include <assimp/cimport.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <future>
+#include <stb/stb_image.h>
+
+template<typename T>
+glm::vec<3, T>
+operator*(const aiVector3t<T> & v)
+{
+ return {v.x, v.y, v.z};
+}
+
+template<typename T>
+glm::vec<2, T>
+operator!(const aiVector3t<T> & v)
+{
+ return {v.x, v.y};
+}
+
+#define AIRANGE(parent, member) \
+ std::span \
+ { \
+ (parent)->m##member, (parent)->mNum##member \
+ }
+
+using SceneCPtr = std::shared_ptr<const aiScene>;
+class AssImpNode : public Shape {
+public:
+ AssImpNode(SceneCPtr scene, const aiNode * node) : scene(std::move(scene)), node(node) { }
+
+ SceneCPtr scene;
+ const aiNode * node;
+
+ CreatedFaces
+ createMesh(ModelFactoryMesh & mesh, float) const
+ {
+ CreatedFaces faces;
+ addMesh(faces, mesh, node);
+ return faces;
+ }
+
+ void
+ addMesh(CreatedFaces & faces, ModelFactoryMesh & mesh, const aiNode * node) const
+ {
+ for (const auto & m : AIRANGE(node, Meshes)) {
+ addMesh(faces, mesh, scene->mMeshes[m]);
+ }
+ for (const auto & n : AIRANGE(node, Children)) {
+ addMesh(faces, mesh, n);
+ }
+ }
+
+ void
+ addMesh(CreatedFaces & faces, ModelFactoryMesh & mesh, const aiMesh * amesh) const
+ {
+ const auto vhs = AIRANGE(amesh, Vertices) * [&mesh](auto && v) {
+ return mesh.add_vertex(*v);
+ };
+ const auto & m = scene->mMaterials[amesh->mMaterialIndex]->GetName();
+
+ GLuint material {};
+ if (auto mf = Persistence::ParseBase::getShared<AssetFactory>("assetFactory")) {
+ material = mf->getMaterialIndex(m.C_Str());
+ }
+
+ for (const auto & f : AIRANGE(amesh, Faces)) {
+ const auto fvhs = AIRANGE(&f, Indices) * [&vhs](auto && i) {
+ return vhs[i];
+ };
+ const auto fh = faces.emplace(mesh.add_namedFace(amesh->mName.C_Str(), fvhs))->second;
+ if (amesh->HasTextureCoords(0)) {
+ for (auto idx = f.mIndices; const auto fheh : mesh.fh_range(fh)) {
+ const auto ouv = !amesh->mTextureCoords[0][*idx++];
+ mesh.set_texcoord2D(fheh, ouv);
+ mesh.property(mesh.materialFaceProperty, fh) = material;
+ }
+ }
+ }
+ }
+};
+
+static_assert(TextureOptions::MapMode::Repeat == static_cast<TextureOptions::MapMode>(aiTextureMapMode_Wrap));
+static_assert(TextureOptions::MapMode::Clamp == static_cast<TextureOptions::MapMode>(aiTextureMapMode_Clamp));
+static_assert(TextureOptions::MapMode::Decal == static_cast<TextureOptions::MapMode>(aiTextureMapMode_Decal));
+static_assert(TextureOptions::MapMode::Mirror == static_cast<TextureOptions::MapMode>(aiTextureMapMode_Mirror));
+
+void
+AssImp::postLoad()
+{
+ SceneCPtr scene {
+ aiImportFile(Resource::mapPath(path).c_str(), aiProcess_RemoveRedundantMaterials), &aiReleaseImport};
+ if (!scene) {
+ throw std::runtime_error("Failed to load asset library: " + path);
+ }
+ if (auto mf = Persistence::ParseBase::getShared<AssetFactory>("assetFactory")) {
+ const auto root = AIRANGE(scene->mRootNode, Children);
+ std::transform(
+ root.begin(), root.end(), std::inserter(mf->shapes, mf->shapes.end()), [&scene](const aiNode * m) {
+ return AssetFactory::Shapes::value_type {m->mName.C_Str(), std::make_shared<AssImpNode>(scene, m)};
+ });
+ const auto materials = AIRANGE(scene, Materials);
+ std::transform(materials.begin(), materials.end(),
+ std::inserter(mf->textureFragments, mf->textureFragments.end()), [&scene](const aiMaterial * m) {
+ aiString path;
+ m->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), path);
+ auto texture = std::make_shared<TextureFragment>();
+ texture->id = m->GetName().C_Str();
+ texture->path = path.C_Str();
+ m->Get(AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0), texture->mapmodeU);
+ m->Get(AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0), texture->mapmodeV);
+ texture->image = Worker::addWork([t = scene->GetEmbeddedTexture(path.C_Str())]() {
+ return std::make_unique<Image>(
+ std::span {reinterpret_cast<unsigned char *>(t->pcData), t->mWidth}, STBI_rgb_alpha);
+ });
+ return AssetFactory::TextureFragments::value_type {texture->id, texture};
+ });
+ }
+}
+
+bool
+AssImp::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_MEMBER(path);
+}
diff --git a/assetFactory/assimp.h b/assetFactory/assimp.h
new file mode 100644
index 0000000..55b551d
--- /dev/null
+++ b/assetFactory/assimp.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "persistence.h"
+
+class AssImp : public Persistence::Persistable {
+public:
+ using Ptr = std::shared_ptr<AssImp>;
+
+ void postLoad() override;
+
+ bool persist(Persistence::PersistenceStore & store) override;
+
+ std::string path;
+};
diff --git a/assetFactory/factoryMesh.cpp b/assetFactory/factoryMesh.cpp
index 8869efd..f7bc7c8 100644
--- a/assetFactory/factoryMesh.cpp
+++ b/assetFactory/factoryMesh.cpp
@@ -6,43 +6,46 @@
Mesh::Ptr
FactoryMesh::createMesh() const
{
- ModelFactoryMesh mesh;
+ std::vector<Vertex> vertices;
+ std::vector<unsigned int> indices;
+
for (const auto & use : uses) {
+ ModelFactoryMesh mesh;
use->createMesh(mesh, 1);
- }
- 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);
+ mesh.update_face_normals();
+ mesh.update_vertex_normals();
+ for (const auto & face : mesh.faces()) {
+ const auto & smooth = mesh.property(mesh.smoothFaceProperty, face);
+ const auto & colour = mesh.color(face);
+ const auto & material = mesh.property(mesh.materialFaceProperty, 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);
+ 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, material};
+ 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]);
+ 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, indices);
}
diff --git a/assetFactory/modelFactoryMesh.cpp b/assetFactory/modelFactoryMesh.cpp
index e640502..3d4b5f3 100644
--- a/assetFactory/modelFactoryMesh.cpp
+++ b/assetFactory/modelFactoryMesh.cpp
@@ -3,6 +3,7 @@
ModelFactoryMesh::ModelFactoryMesh()
{
add_property(smoothFaceProperty);
+ add_property(materialFaceProperty);
add_property(nameFaceProperty);
add_property(nameAdjFaceProperty);
}
diff --git a/assetFactory/modelFactoryMesh.h b/assetFactory/modelFactoryMesh.h
index 8ac2edd..4eac7ee 100644
--- a/assetFactory/modelFactoryMesh.h
+++ b/assetFactory/modelFactoryMesh.h
@@ -1,6 +1,7 @@
#pragma once
#include "modelFactoryMesh_fwd.h"
+#include <GL/glew.h>
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/Mesh/Traits.hh>
#include <glm/geometric.hpp>
@@ -47,6 +48,7 @@ struct ModelFactoryMesh : public OpenMesh::PolyMesh_ArrayKernelT<ModelFactoryTra
ModelFactoryMesh();
OpenMesh::FPropHandleT<bool> smoothFaceProperty;
+ OpenMesh::FPropHandleT<GLuint> materialFaceProperty;
OpenMesh::FPropHandleT<std::string> nameFaceProperty;
OpenMesh::HPropHandleT<std::string> nameAdjFaceProperty;
diff --git a/assetFactory/style.cpp b/assetFactory/style.cpp
index 12346a6..ea67fc2 100644
--- a/assetFactory/style.cpp
+++ b/assetFactory/style.cpp
@@ -30,7 +30,14 @@ Style::applyStyle(
else {
mesh.set_color(face, {});
if (auto mf = Persistence::ParseBase::getShared<const AssetFactory>("assetFactory")) {
- auto coords = mf->getTextureCoords(texture);
+ const auto material = mf->getMaterialIndex(texture);
+ mesh.property(mesh.materialFaceProperty, face) = material;
+ static constexpr std::array<ModelFactoryTraits::TexCoord2D, 4> coords {{
+ {0.f, 0.f},
+ {1.f, 0.f},
+ {1.f, 1.f},
+ {0.f, 1.f},
+ }};
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)) {
diff --git a/assetFactory/textureFragment.cpp b/assetFactory/textureFragment.cpp
index 72107a5..0a4ec1d 100644
--- a/assetFactory/textureFragment.cpp
+++ b/assetFactory/textureFragment.cpp
@@ -1,7 +1,17 @@
#include "textureFragment.h"
+#include "resource.h"
+#include <stb/stb_image.h>
bool
TextureFragment::persist(Persistence::PersistenceStore & store)
{
return STORE_TYPE && STORE_MEMBER(id) && STORE_MEMBER(path);
}
+
+void
+TextureFragment::postLoad()
+{
+ image = Worker::addWork([this]() {
+ return std::make_unique<Image>(Resource::mapPath(path), STBI_rgb_alpha);
+ });
+}
diff --git a/assetFactory/textureFragment.h b/assetFactory/textureFragment.h
index 52f2591..e6f07f3 100644
--- a/assetFactory/textureFragment.h
+++ b/assetFactory/textureFragment.h
@@ -1,14 +1,20 @@
#pragma once
+#include "gfx/image.h"
+#include "gfx/models/texture.h"
#include "persistence.h"
#include "stdTypeDefs.hpp"
+#include "worker.h"
class TextureFragment : public Persistence::Persistable, public StdTypeDefs<TextureFragment> {
public:
std::string id;
std::string path;
+ TextureOptions::MapMode mapmodeU, mapmodeV;
+ Worker::WorkPtrT<std::unique_ptr<Image>> image;
private:
friend Persistence::SelectionPtrBase<Ptr>;
bool persist(Persistence::PersistenceStore & store) override;
+ void postLoad() override;
};
diff --git a/assetFactory/texturePacker.cpp b/assetFactory/texturePacker.cpp
index 68a6010..fcc935f 100644
--- a/assetFactory/texturePacker.cpp
+++ b/assetFactory/texturePacker.cpp
@@ -1,7 +1,9 @@
#include "texturePacker.h"
#include "collections.hpp"
+#include <GL/glew.h>
#include <algorithm>
#include <cstdio>
+#include <glm/common.hpp>
#include <numeric>
#include <ostream>
#include <set>
@@ -12,6 +14,9 @@ TexturePacker::TexturePacker(std::span<const Image> in) :
std::sort(sortedIndexes.rbegin(), sortedIndexes.rend(), [this](const auto a, const auto b) {
return area(inputImages[a]) < area(inputImages[b]);
});
+ int mts;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mts);
+ maxTextureSize = static_cast<unsigned int>(mts);
}
TexturePacker::Result
@@ -23,6 +28,9 @@ TexturePacker::pack() const
TexturePacker::Result
TexturePacker::pack(Size size) const
{
+ if (size.x > maxTextureSize || size.y > maxTextureSize) {
+ return {};
+ }
using Spaces = std::set<Space>;
Spaces spaces {{{}, size}};
@@ -47,14 +55,27 @@ TexturePacker::pack(Size size) const
}
}
else {
- if (size.x < size.y) {
- return pack({size.x * 2, size.y});
+ const auto x = pack({size.x * 2, size.y}), y = pack({size.x, size.y * 2});
+ if (!x.first.empty() && (y.first.empty() || area(x.second) < area(y.second))) {
+ return x;
}
- else {
- return pack({size.x, size.y * 2});
+ else if (!y.first.empty()) {
+ return y;
}
+ return {};
}
}
+ if (GLEW_ARB_texture_non_power_of_two) {
+ // Crop the size back to minimum size
+ size = std::transform_reduce(
+ result.begin(), result.end(), inputImages.begin(), Size {},
+ [](auto && max, auto && limit) {
+ return glm::max(max, limit);
+ },
+ [](auto && pos, auto && size) {
+ return pos + size;
+ });
+ }
return {result, size};
}
diff --git a/assetFactory/texturePacker.h b/assetFactory/texturePacker.h
index ca0d67a..a1b270b 100644
--- a/assetFactory/texturePacker.h
+++ b/assetFactory/texturePacker.h
@@ -38,4 +38,5 @@ public:
private:
std::span<const Image> inputImages;
std::vector<size_t> sortedIndexes;
+ unsigned int maxTextureSize;
};
diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp
new file mode 100644
index 0000000..d39d500
--- /dev/null
+++ b/game/scenary/foliage.cpp
@@ -0,0 +1,33 @@
+#include "foliage.h"
+#include "gfx/gl/sceneShader.h"
+#include "gfx/gl/shadowMapper.h"
+#include "gfx/models/texture.h"
+
+bool
+Foliage::persist(Persistence::PersistenceStore & store)
+{
+ return STORE_TYPE && STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store);
+}
+
+void
+Foliage::postLoad()
+{
+ texture = getTexture();
+}
+
+void
+Foliage::render(const SceneShader & shader, const Location & loc) const
+{
+ shader.basic.use(loc);
+ if (texture) {
+ texture->bind();
+ }
+ bodyMesh->Draw();
+}
+
+void
+Foliage::shadows(const ShadowMapper & mapper, const Location & loc) const
+{
+ mapper.dynamicPoint.use(loc);
+ bodyMesh->Draw();
+}
diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h
new file mode 100644
index 0000000..b85aab2
--- /dev/null
+++ b/game/scenary/foliage.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "assetFactory/asset.h"
+
+class SceneShader;
+class ShadowMapper;
+class Location;
+class Texture;
+
+class Foliage : public Asset, public StdTypeDefs<Foliage> {
+ Mesh::Ptr bodyMesh;
+ std::shared_ptr<Texture> texture;
+
+public:
+ void render(const SceneShader &, const Location &) const;
+ void shadows(const ShadowMapper &, const Location &) const;
+
+protected:
+ friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>;
+ bool persist(Persistence::PersistenceStore & store) override;
+ void postLoad() override;
+};
diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp
new file mode 100644
index 0000000..2b01bee
--- /dev/null
+++ b/game/scenary/plant.cpp
@@ -0,0 +1,13 @@
+#include "plant.h"
+
+void
+Plant::render(const SceneShader & shader) const
+{
+ type->render(shader, position);
+}
+
+void
+Plant::shadows(const ShadowMapper & mapper) const
+{
+ type->shadows(mapper, position);
+}
diff --git a/game/scenary/plant.h b/game/scenary/plant.h
new file mode 100644
index 0000000..55acca1
--- /dev/null
+++ b/game/scenary/plant.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "foliage.h"
+#include "game/worldobject.h"
+#include "gfx/renderable.h"
+#include "location.hpp"
+
+class Plant : public Renderable, public WorldObject {
+ std::shared_ptr<const Foliage> type;
+ Location position;
+
+ void render(const SceneShader & shader) const override;
+ void shadows(const ShadowMapper & shadowMapper) const override;
+
+ void
+ tick(TickDuration) override
+ {
+ }
+
+public:
+ Plant(std::shared_ptr<const Foliage> type, Location position) : type(std::move(type)), position(position) { }
+};
diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp
index 4e9263c..b7a3b1e 100644
--- a/game/vehicles/railVehicleClass.cpp
+++ b/game/vehicles/railVehicleClass.cpp
@@ -2,7 +2,6 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/shadowMapper.h"
#include "gfx/models/mesh.h"
-#include "gfx/models/obj.h"
#include "gfx/models/texture.h"
#include <algorithm>
#include <array>
@@ -22,25 +21,6 @@
#include <utility>
#include <vector>
-RailVehicleClass::RailVehicleClass(const std::string & name) :
- RailVehicleClass {std::make_unique<ObjParser>(Resource::mapPath(name + ".obj")),
- Texture::cachedTexture.get(Resource::mapPath(name + ".png"))}
-{
-}
-
-RailVehicleClass::RailVehicleClass(std::unique_ptr<ObjParser> o, std::shared_ptr<Texture> t) :
- texture {std::move(t)}, maxSpeed(95._mph)
-{
- wheelBase = bogieOffset(*o);
- length = objectLength(*o);
- const auto m = o->createMeshes();
- bodyMesh = m.at("Body");
- bogies[0] = m.at("Bogie1");
- bogies[1] = m.at("Bogie2");
-}
-
-RailVehicleClass::RailVehicleClass() { }
-
bool
RailVehicleClass::persist(Persistence::PersistenceStore & store)
{
@@ -80,36 +60,3 @@ RailVehicleClass::shadows(
bogies[b]->Draw();
}
}
-
-float
-RailVehicleClass::bogieOffset(ObjParser & o)
-{
- float wheelBase {0};
- // offset bogie positions so they can be set directly
- for (auto & object : o.objects) { // bogie object index
- if (!object.first.starts_with("Bogie")) {
- continue;
- }
- std::set<std::pair<float, std::size_t>> vertexIds;
- for (const auto & face : object.second) {
- for (const auto & faceElement : face) {
- vertexIds.emplace(o.vertices[faceElement.x - 1].y, faceElement.x - 1);
- }
- }
- const auto offset = (vertexIds.begin()->first + vertexIds.rbegin()->first) / 2;
- for (const auto & v : vertexIds) {
- o.vertices[v.second].y -= offset;
- }
- wheelBase += std::abs(offset);
- }
- return wheelBase;
-}
-
-float
-RailVehicleClass::objectLength(ObjParser & o)
-{
- const auto mme = std::minmax_element(o.vertices.begin(), o.vertices.end(), [](const auto & v1, const auto & v2) {
- return v1.y < v2.y;
- });
- return mme.second->y - mme.first->y;
-}
diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h
index 61ec4ec..1a52e2b 100644
--- a/game/vehicles/railVehicleClass.h
+++ b/game/vehicles/railVehicleClass.h
@@ -9,14 +9,10 @@
class SceneShader;
class ShadowMapper;
class Texture;
-class ObjParser;
class Location;
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 std::array<Location, 2> &) const;
@@ -31,10 +27,5 @@ 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>);
- static float bogieOffset(ObjParser & o);
- static float objectLength(ObjParser & o);
};
using RailVehicleClassPtr = std::shared_ptr<RailVehicleClass>;
diff --git a/gfx/gl/shaders/basicShader.fs b/gfx/gl/shaders/basicShader.fs
index 24b2791..57eaba2 100644
--- a/gfx/gl/shaders/basicShader.fs
+++ b/gfx/gl/shaders/basicShader.fs
@@ -1,22 +1,54 @@
#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
-in vec3 FragPos;
-in vec2 TexCoords;
-in vec3 Normal;
-in vec4 Colour;
+include(`materialInterface.glsl')
+include(`geometryOut.glsl')
-out vec4 gPosition;
-out vec4 gNormal;
-out vec4 gAlbedoSpec;
+layout(binding = 0) uniform sampler2D texture0;
+layout(binding = 1) uniform usampler2DRect material;
-uniform sampler2D texture0;
+float map(uint mapmode, float value)
+{
+ switch (mapmode) {
+ case 0u: // Repeat
+ return fract(value);
+ case 1u: // Clamp to edge
+ return clamp(0.0, 1.0, value);
+ case 2u: // Mirror
+ discard;
+ case 3u: // Decal
+ if (value != clamp(0.0, 1.0, value)) {
+ discard;
+ }
+ }
+ return 0;
+}
+
+vec2 map(uint mapmodeU, uint mapmodeV, vec2 value)
+{
+ return vec2(map(mapmodeU, value.x), map(mapmodeV, value.y));
+}
+
+vec4 getTextureColour(uint midx, vec2 uv)
+{
+ if (midx > 0u) {
+ const vec2 tSize = textureSize(texture0, 0);
+ const vec4 sPosSize = texture(material, uvec2(0, midx - 1u));
+ const uvec4 sMode = texture(material, uvec2(1, midx - 1u));
+ const uint mapmodeU = sMode.x & 0xFu;
+ const uint mapmodeV = (sMode.x & 0xF0u) >> 1;
+ uv = (sPosSize.xy + sPosSize.zw * map(mapmodeU, mapmodeV, uv)) / tSize;
+ }
+ return texture(texture0, uv);
+}
void
main()
{
- vec4 textureColour = texture(texture0, TexCoords);
- float clear = round(mix(textureColour.a, 1, Colour.a));
- gPosition = vec4(FragPos, clear);
- gNormal = vec4(Normal, clear);
+ vec4 textureColour = getTextureColour(Material, TexCoords);
+ float opaque = step(0.5, mix(textureColour.a, 1, Colour.a));
+ gPosition = vec4(FragPos, opaque);
+ gNormal = vec4(Normal, opaque);
+ gl_FragDepth = mix(1.0, gl_FragCoord.z, opaque);
gAlbedoSpec = mix(textureColour, vec4(Colour.rgb, 1), Colour.a);
}
diff --git a/gfx/gl/shaders/basicShader.vs b/gfx/gl/shaders/basicShader.vs
index ff9a401..e1701ed 100644
--- a/gfx/gl/shaders/basicShader.vs
+++ b/gfx/gl/shaders/basicShader.vs
@@ -1,14 +1,7 @@
#version 330 core
-in vec3 position;
-in vec2 texCoord;
-in vec3 normal;
-in vec4 colour;
-
-out vec3 FragPos;
-out vec2 TexCoords;
-out vec3 Normal;
-out vec4 Colour;
+include(`meshIn.glsl')
+include(`materialInterface.glsl')
uniform mat4 viewProjection;
uniform mat4 model;
@@ -22,6 +15,7 @@ main()
TexCoords = texCoord;
Normal = (model * vec4(normal, 0.0)).xyz;
Colour = colour;
+ Material = material;
gl_Position = viewProjection * worldPos;
}
diff --git a/gfx/gl/shaders/geometryOut.glsl b/gfx/gl/shaders/geometryOut.glsl
new file mode 100644
index 0000000..dc5f8e8
--- /dev/null
+++ b/gfx/gl/shaders/geometryOut.glsl
@@ -0,0 +1,3 @@
+layout(location = 0) out vec4 gPosition;
+layout(location = 1) out vec4 gNormal;
+layout(location = 2) out vec4 gAlbedoSpec;
diff --git a/gfx/gl/shaders/landmassShader.fs b/gfx/gl/shaders/landmassShader.fs
index 7c08e8e..f3cc3e8 100644
--- a/gfx/gl/shaders/landmassShader.fs
+++ b/gfx/gl/shaders/landmassShader.fs
@@ -1,12 +1,7 @@
#version 330 core
-in vec3 FragPos;
-in vec2 TexCoords;
-in vec3 Normal;
-
-layout(location = 0) out vec4 gPosition;
-layout(location = 1) out vec4 gNormal;
-layout(location = 2) out vec4 gAlbedoSpec;
+include(`materialInterface.glsl')
+include(`geometryOut.glsl')
uniform sampler2D texture0;
diff --git a/gfx/gl/shaders/landmassShader.vs b/gfx/gl/shaders/landmassShader.vs
index 30c4ef4..0cc8153 100644
--- a/gfx/gl/shaders/landmassShader.vs
+++ b/gfx/gl/shaders/landmassShader.vs
@@ -1,14 +1,7 @@
#version 330 core
-in vec3 position;
-in vec2 texCoord;
-in vec3 normal;
-in vec4 colour;
-
-out vec3 FragPos;
-out vec2 TexCoords;
-out vec3 Normal;
-out vec4 Colour;
+include(`meshIn.glsl')
+include(`materialInterface.glsl')
uniform mat4 viewProjection;
@@ -19,6 +12,7 @@ main()
TexCoords = texCoord;
Normal = normal;
Colour = colour;
+ Material = material;
gl_Position = viewProjection * vec4(position, 1.0);
}
diff --git a/gfx/gl/shaders/lightingShader.vs b/gfx/gl/shaders/lightingShader.vs
index 2271d2e..e07cd0a 100644
--- a/gfx/gl/shaders/lightingShader.vs
+++ b/gfx/gl/shaders/lightingShader.vs
@@ -1,6 +1,6 @@
#version 330 core
-in vec4 position;
+in ivec4 position;
out vec2 TexCoords;
diff --git a/gfx/gl/shaders/materialInterface.glsl b/gfx/gl/shaders/materialInterface.glsl
new file mode 100644
index 0000000..5735cf9
--- /dev/null
+++ b/gfx/gl/shaders/materialInterface.glsl
@@ -0,0 +1,7 @@
+ifelse(TYPE, .fs, in, out) vec3 FragPos;
+ifelse(TYPE, .fs, in, out) vec2 TexCoords;
+ifelse(TYPE, .fs, in, out) vec3 Normal;
+ifelse(TYPE, .fs, in, out) vec4 Colour;
+flat
+ifelse(TYPE, .fs, in, out)
+uint Material;
diff --git a/gfx/gl/shaders/meshIn.glsl b/gfx/gl/shaders/meshIn.glsl
new file mode 100644
index 0000000..dd84a10
--- /dev/null
+++ b/gfx/gl/shaders/meshIn.glsl
@@ -0,0 +1,5 @@
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec2 texCoord;
+layout(location = 2) in vec3 normal;
+layout(location = 3) in vec4 colour;
+layout(location = 4) in uint material;
diff --git a/gfx/gl/shaders/shadowDynamicPoint.vs b/gfx/gl/shaders/shadowDynamicPoint.vs
index 6ea352f..531d8de 100644
--- a/gfx/gl/shaders/shadowDynamicPoint.vs
+++ b/gfx/gl/shaders/shadowDynamicPoint.vs
@@ -1,6 +1,6 @@
#version 330 core
-in vec3 position;
+include(`meshIn.glsl')
uniform mat4 viewProjection;
uniform mat4 model;
diff --git a/gfx/gl/shaders/shadowFixedPoint.vs b/gfx/gl/shaders/shadowFixedPoint.vs
index 246890c..c9fa19b 100644
--- a/gfx/gl/shaders/shadowFixedPoint.vs
+++ b/gfx/gl/shaders/shadowFixedPoint.vs
@@ -1,6 +1,6 @@
#version 330 core
-layout(location = 0) in vec3 position;
+include(`meshIn.glsl')
uniform mat4 viewProjection;
diff --git a/gfx/gl/shaders/waterShader.fs b/gfx/gl/shaders/waterShader.fs
index d242566..5936da4 100644
--- a/gfx/gl/shaders/waterShader.fs
+++ b/gfx/gl/shaders/waterShader.fs
@@ -1,13 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
-in vec3 FragPos;
-in vec2 TexCoords;
-in vec3 Normal;
-
-out vec4 gPosition;
-out vec4 gNormal;
-out vec4 gAlbedoSpec;
+include(`materialInterface.glsl')
+include(`geometryOut.glsl')
uniform sampler2D texture0;
uniform vec3 waves;
diff --git a/gfx/gl/shaders/waterShader.vs b/gfx/gl/shaders/waterShader.vs
index fe9206f..a21b49f 100644
--- a/gfx/gl/shaders/waterShader.vs
+++ b/gfx/gl/shaders/waterShader.vs
@@ -1,12 +1,7 @@
#version 330 core
-in vec3 position;
-in vec2 texCoord;
-in vec3 normal;
-
-out vec3 FragPos;
-out vec2 TexCoords;
-out vec3 Normal;
+include(`meshIn.glsl')
+include(`materialInterface.glsl')
uniform mat4 viewProjection;
uniform vec3 waves;
diff --git a/gfx/gl/vertexArrayObject.hpp b/gfx/gl/vertexArrayObject.hpp
index c0273c0..5b9fc60 100644
--- a/gfx/gl/vertexArrayObject.hpp
+++ b/gfx/gl/vertexArrayObject.hpp
@@ -61,7 +61,7 @@ private:
{
glEnableVertexAttribArray(vertexArrayId);
using traits = gl_traits<T>;
- glVertexAttribPointer(vertexArrayId, traits::size, traits::type, GL_FALSE, sizeof(Vertex), ptr);
+ traits::vertexAttribFunc(vertexArrayId, traits::size, traits::type, sizeof(Vertex), ptr);
}
template<auto Vertex::*attrib>
diff --git a/gfx/image.cpp b/gfx/image.cpp
index e3e3b01..fb86cb6 100644
--- a/gfx/image.cpp
+++ b/gfx/image.cpp
@@ -19,6 +19,22 @@ Image::Image(const char * fileName, int flags) : width {}, height {}, numCompone
data = {bytes, static_cast<size_t>(width * height * numComponents)};
}
+Image::Image(std::span<unsigned char> buffer, int flags)
+{
+ stbi_set_flip_vertically_on_load(1);
+ int w, h, nc;
+ unsigned char * bytes = stbi_load_from_memory(buffer.data(), static_cast<int>(buffer.size()), &w, &h, &nc, flags);
+ width = static_cast<unsigned int>(w);
+ height = static_cast<unsigned int>(h);
+ numComponents = static_cast<unsigned int>(nc);
+
+ if (!bytes) {
+ throw std::runtime_error {"Unable to load image from memory buffer "};
+ }
+
+ data = {bytes, static_cast<size_t>(width * height * numComponents)};
+}
+
Image::~Image()
{
stbi_image_free(data.data());
diff --git a/gfx/image.h b/gfx/image.h
index 47437ca..97c2731 100644
--- a/gfx/image.h
+++ b/gfx/image.h
@@ -8,6 +8,7 @@ class Image {
public:
Image(const char * fileName, int flags);
Image(const std::string & fileName, int flags) : Image(fileName.c_str(), flags) { }
+ Image(std::span<unsigned char> data, int flags);
~Image();
NO_COPY(Image);
diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp
index 3db1ad5..2719211 100644
--- a/gfx/models/mesh.cpp
+++ b/gfx/models/mesh.cpp
@@ -7,7 +7,8 @@
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, &Vertex::colour>(
+ VertexArrayObject<Vertex>::configure<&Vertex::pos, &Vertex::texCoord, &Vertex::normal, &Vertex::colour,
+ &Vertex::material>(
m_vertexArrayObject, m_vertexArrayBuffers[0], m_vertexArrayBuffers[1], vertices, indices);
}
diff --git a/gfx/models/obj.h b/gfx/models/obj.h
deleted file mode 100644
index e28f7de..0000000
--- a/gfx/models/obj.h
+++ /dev/null
@@ -1,38 +0,0 @@
-#pragma once
-
-#ifndef yyFlexLexer
-# define yyFlexLexer objbaseFlexLexer
-# include <FlexLexer.h>
-#endif
-#include <filesystem>
-#include <fstream>
-#include <glm/glm.hpp>
-#include <map>
-#include <memory>
-#include <vector>
-
-class Mesh;
-class Vertex;
-
-class ObjParser : yyFlexLexer {
-public:
- explicit ObjParser(const std::filesystem::path & fileName);
- explicit ObjParser(std::unique_ptr<std::istream> in);
-
- int yylex() override;
-
- std::vector<glm::vec4> vertices;
- std::vector<glm::vec3> texCoords;
- std::vector<glm::vec3> normals;
- using FaceElement = glm::vec<3, unsigned int>;
- using Face = std::vector<FaceElement>;
- using Faces = std::vector<Face>;
- using Object = std::pair<std::string, Faces>;
- std::vector<Object> objects;
- glm::length_t axis {0};
-
- using NamedMeshesData = std::map<std::string, std::pair<std::vector<Vertex>, std::vector<unsigned int>>>;
- [[nodiscard]] NamedMeshesData createMeshData() const;
- using NamedMeshes = std::map<std::string, std::shared_ptr<const Mesh>>;
- [[nodiscard]] NamedMeshes createMeshes() const;
-};
diff --git a/gfx/models/obj.impl.cpp b/gfx/models/obj.impl.cpp
deleted file mode 100644
index 205abc8..0000000
--- a/gfx/models/obj.impl.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-#include "obj.h"
-#include <algorithm>
-#include <cassert>
-#include <filesystem>
-#include <fstream>
-#include <gfx/models/mesh.h> // IWYU pragma: keep
-#include <gfx/models/vertex.hpp>
-#include <glm/glm.hpp>
-#include <iterator>
-#include <map>
-#include <memory>
-#include <utility>
-#include <vector>
-
-ObjParser::ObjParser(const std::filesystem::path & fileName) : ObjParser {std::make_unique<std::ifstream>(fileName)} { }
-
-ObjParser::ObjParser(std::unique_ptr<std::istream> in) : yyFlexLexer(in.get())
-{
- assert(in);
- ObjParser::yylex();
- assert(in->good());
- constexpr const glm::mat4 obj2ilt {
- -1, 0, 0, 0, // x
- 0, 0, 1, 0, // y
- 0, 1, 0, 0, // z
- 0, 0, 0, 0, // w
- };
- std::for_each(vertices.begin(), vertices.end(), [&obj2ilt](auto & v) {
- v = v * obj2ilt;
- });
- std::for_each(normals.begin(), normals.end(), [&obj2ilt](auto & v) {
- v = glm::vec4 {v, 0} * obj2ilt;
- });
-}
-
-ObjParser::NamedMeshes
-ObjParser::createMeshes() const
-{
- NamedMeshes out;
- const auto data {createMeshData()};
- std::transform(data.begin(), data.end(), std::inserter(out, out.end()), [](auto && obj) {
- return std::make_pair(obj.first, std::make_shared<Mesh>(obj.second.first, obj.second.second));
- });
- return out;
-}
-
-ObjParser::NamedMeshesData
-ObjParser::createMeshData() const
-{
- NamedMeshesData out;
- std::transform(objects.begin(), objects.end(), std::inserter(out, out.end()), [this](auto && obj) {
- std::vector<Vertex> overtices;
- std::vector<ObjParser::FaceElement> vertexOrder;
- std::vector<unsigned int> indices;
- for (const auto & face : obj.second) {
- for (auto idx = 2U; idx < face.size(); idx += 1) {
- auto f = [&](auto idx) {
- const auto & fe {face[idx]};
- if (const auto existing = std::find(vertexOrder.begin(), vertexOrder.end(), fe);
- existing != vertexOrder.end()) {
- indices.push_back(static_cast<unsigned int>(std::distance(vertexOrder.begin(), existing)));
- }
- else {
- indices.push_back(static_cast<unsigned int>(overtices.size()));
- overtices.emplace_back(vertices[fe.x - 1], texCoords[fe.y - 1], -normals[fe.z - 1]);
- vertexOrder.emplace_back(fe);
- }
- };
- f(0U);
- f(idx);
- f(idx - 1);
- }
- }
- return std::make_pair(obj.first, std::make_pair(overtices, indices));
- });
- return out;
-}
diff --git a/gfx/models/obj.ll b/gfx/models/obj.ll
deleted file mode 100644
index 84884fe..0000000
--- a/gfx/models/obj.ll
+++ /dev/null
@@ -1,116 +0,0 @@
-%option batch
-%option c++
-%option noyywrap
-%option 8bit
-%option stack
-%option yyclass="ObjParser"
-%option prefix="objbase"
-
-%{
-#include <gfx/models/obj.h>
-#include <glm/glm.hpp>
-#include <memory>
-#include <string>
-#include <vector>
-class objbaseFlexLexer;
-%}
-
-comment #.*
-float -?[0-9.]+
-index -?[0-9]+
-linestring [^\r\n]*
-truthy (1|on)
-falsey (0|off)
-
-%x FACE
-%x MTLLIB
-%x OBJECT
-%x SMOOTH
-%x USEMTL
-%x VERTEX
-%x NORMAL
-%x TEXCOORD
-
-%%
-
-<INITIAL>{comment} {
- // fprintf(stderr, "COMMENT %s\n", YYText());
-}
-<INITIAL>"f " {
- BEGIN(FACE);
- objects.back().second.emplace_back();
- objects.back().second.back().emplace_back();
- axis = 0;
-}
-<INITIAL>"mtllib " {
- BEGIN(MTLLIB);
-}
-<INITIAL>"o " {
- BEGIN(OBJECT);
-}
-<INITIAL>"s " {
- BEGIN(SMOOTH);
-}
-<INITIAL>"usemtl " {
- BEGIN(USEMTL);
-}
-<INITIAL>"v " {
- BEGIN(VERTEX);
- vertices.emplace_back(0, 0, 0, 1);
- axis = 0;
-}
-<INITIAL>"vn " {
- BEGIN(NORMAL);
- normals.emplace_back(0, 0, 0);
- axis = 0;
-}
-<INITIAL>"vt " {
- BEGIN(TEXCOORD);
- texCoords.emplace_back(0, 1, 1);
- axis = 0;
-}
-<USEMTL>{linestring} {
- // fprintf(stderr, "USEMTL <%s>\n", YYText());
-}
-<MTLLIB>{linestring} {
- // fprintf(stderr, "MTLLIB <%s>\n", YYText());
-}
-<OBJECT>{linestring} {
- objects.emplace_back(YYText(), Faces{});
-}
-<SMOOTH>{truthy} {
- // fprintf(stderr, "Set smooth\n");
-}
-<SMOOTH>{falsey} {
- // fprintf(stderr, "Set flat\n");
-}
-<VERTEX>{float} {
- vertices.back()[axis++] = std::stof(YYText());
-}
-<NORMAL>{float} {
- normals.back()[axis++] = std::stof(YYText());
-}
-<TEXCOORD>{float} {
- texCoords.back()[axis++] = std::stof(YYText());
-}
-<FACE>{index} {
- objects.back().second.back().back()[axis] = std::stoi(YYText());
-}
-<FACE>\/ {
- axis++;
-}
-<FACE>[ \t] {
- objects.back().second.back().emplace_back();
- axis = 0;
-}
-
-<*>[ \t] {
-}
-<*>[\r\n\f] {
- BEGIN(INITIAL);
-}
-
-%%
-
-// Make iwyu think unistd.h is required
-[[maybe_unused]]static auto x=getpid;
diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp
index efc76e1..e014f80 100644
--- a/gfx/models/texture.cpp
+++ b/gfx/models/texture.cpp
@@ -1,5 +1,6 @@
#include "texture.h"
#include "glArrays.h"
+#include "tga.h"
#include <GL/glew.h>
#include <cache.h>
#include <fcntl.h>
@@ -12,6 +13,21 @@
Cache<Texture, std::filesystem::path> Texture::cachedTexture;
+GLint
+TextureOptions::glMapMode(TextureOptions::MapMode mm)
+{
+ switch (mm) {
+ case MapMode::Repeat:
+ return GL_REPEAT;
+ case MapMode::Clamp:
+ return GL_CLAMP_TO_EDGE;
+ case MapMode::Mirror:
+ return GL_MIRRORED_REPEAT;
+ default:
+ throw std::domain_error("Invalid MapMode value");
+ }
+}
+
Texture::Texture(const std::filesystem::path & fileName, TextureOptions to) :
Texture {Image {Resource::mapPath(fileName).c_str(), STBI_rgb_alpha}, to}
{
@@ -29,8 +45,8 @@ Texture::Texture(GLsizei width, GLsizei height, const void * data, TextureOption
glBindTexture(type, m_texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glTexParameteri(type, GL_TEXTURE_WRAP_S, to.wrap);
- glTexParameteri(type, GL_TEXTURE_WRAP_T, to.wrap);
+ glTexParameteri(type, GL_TEXTURE_WRAP_S, TextureOptions::glMapMode(to.wrapU));
+ glTexParameteri(type, GL_TEXTURE_WRAP_T, TextureOptions::glMapMode(to.wrapV));
glTexParameteri(type, GL_TEXTURE_MIN_FILTER, to.minFilter);
glTexParameteri(type, GL_TEXTURE_MAG_FILTER, to.magFilter);
@@ -44,44 +60,91 @@ Texture::bind(GLenum unit) const
glBindTexture(type, m_texture);
}
-void
-Texture::save(const glTexture & texture, GLenum format, GLenum type, const glm::ivec2 & size, unsigned short channels,
- const char * path, short tgaFormat)
+glm::ivec2
+Texture::getSize(const glTexture & texture)
{
- using TGAHead = std::array<short, 9>;
+ glm::ivec2 size;
+ glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_WIDTH, &size.x);
+ glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_HEIGHT, &size.y);
+ return size;
+}
+void
+Texture::save(
+ const glTexture & texture, GLenum format, GLenum type, uint8_t channels, const char * path, uint8_t tgaFormat)
+{
+ const auto size = getSize(texture);
const size_t dataSize = (static_cast<size_t>(size.x * size.y * channels));
const size_t fileSize = dataSize + sizeof(TGAHead);
filesystem::fh out {path, O_RDWR | O_CREAT, 0660};
out.truncate(fileSize);
auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED);
- *tga.get<TGAHead>() = {0, tgaFormat, 0, 0, 0, 0, static_cast<short>(size.x), static_cast<short>(size.y),
- static_cast<short>(8 * channels)};
+ *tga.get<TGAHead>() = {
+ .format = tgaFormat,
+ .size = size,
+ .pixelDepth = static_cast<uint8_t>(8 * channels),
+ };
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetTextureImage(texture, 0, format, type, static_cast<GLsizei>(dataSize), tga.get<TGAHead>() + 1);
tga.msync(MS_ASYNC);
}
void
-Texture::save(const glm::ivec2 & size, const char * path) const
+Texture::save(const char * path) const
{
- save(m_texture, GL_BGR, GL_UNSIGNED_BYTE, size, 3, path, 2);
+ save(m_texture, GL_BGR, GL_UNSIGNED_BYTE, 3, path, 2);
}
void
-Texture::save(const glTexture & texture, const glm::ivec2 & size, const char * path)
+Texture::save(const glTexture & texture, const char * path)
{
- save(texture, GL_BGR, GL_UNSIGNED_BYTE, size, 3, path, 2);
+ save(texture, GL_BGR, GL_UNSIGNED_BYTE, 3, path, 2);
}
void
-Texture::saveDepth(const glTexture & texture, const glm::ivec2 & size, const char * path)
+Texture::saveDepth(const glTexture & texture, const char * path)
{
- save(texture, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, size, 1, path, 3);
+ save(texture, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 1, path, 3);
}
void
-Texture::saveNormal(const glTexture & texture, const glm::ivec2 & size, const char * path)
+Texture::saveNormal(const glTexture & texture, const char * path)
+{
+ save(texture, GL_BGR, GL_BYTE, 3, path, 2);
+}
+
+TextureAtlas::TextureAtlas(GLsizei width, GLsizei height, GLuint count) : Texture(width, height, nullptr, {})
+{
+ glBindTexture(GL_TEXTURE_RECTANGLE, m_atlas);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA16UI, 2, static_cast<GLsizei>(count), 0, GL_RGBA_INTEGER,
+ GL_UNSIGNED_BYTE, nullptr);
+}
+
+void
+TextureAtlas::bind(GLenum unit) const
+{
+ Texture::bind(unit);
+ glActiveTexture(unit + 1);
+ glBindTexture(GL_TEXTURE_RECTANGLE, m_atlas);
+}
+
+GLuint
+TextureAtlas::add(glm::ivec2 position, glm::ivec2 size, void * data, TextureOptions to)
{
- save(texture, GL_BGR, GL_BYTE, size, 3, path, 2);
+ glTextureSubImage2D(m_texture, 0, position.x, position.y, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ struct Material {
+ glm::vec<2, uint16_t> position, size;
+ TextureOptions::MapMode wrapU;
+ TextureOptions::MapMode wrapV;
+ } material {position, size, to.wrapU, to.wrapV};
+ static_assert(sizeof(Material) <= 32);
+ glTextureSubImage2D(m_atlas, 0, 0, static_cast<GLsizei>(used), 2, 1, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, &material);
+ return ++used;
}
diff --git a/gfx/models/texture.h b/gfx/models/texture.h
index ffc9a4a..86e76a0 100644
--- a/gfx/models/texture.h
+++ b/gfx/models/texture.h
@@ -9,13 +9,23 @@
class Image;
struct TextureOptions {
- GLint wrap {GL_REPEAT};
+ enum class MapMode {
+ Repeat,
+ Clamp,
+ Mirror,
+ Decal,
+ };
+ MapMode wrapU {MapMode::Repeat}, wrapV {MapMode::Repeat};
GLint minFilter {GL_LINEAR}, magFilter {GL_LINEAR};
GLenum type {GL_TEXTURE_2D};
+ static GLint glMapMode(MapMode);
};
class Texture {
public:
+ virtual ~Texture() = default;
+ DEFAULT_MOVE_NO_COPY(Texture);
+
explicit Texture(const std::filesystem::path & fileName, TextureOptions = {});
explicit Texture(const Image & image, TextureOptions = {});
explicit Texture(GLsizei width, GLsizei height, TextureOptions = {});
@@ -23,17 +33,29 @@ public:
static Cache<Texture, std::filesystem::path> cachedTexture;
- void bind(GLenum unit = GL_TEXTURE0) const;
+ virtual 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);
+ void save(const char * path) const;
+ static void save(const glTexture &, const char * path);
+ static void saveDepth(const glTexture &, const char * path);
+ static void saveNormal(const glTexture &, const char * path);
-private:
- static void save(const glTexture &, GLenum, GLenum, const glm::ivec2 & size, unsigned short channels,
- const char * path, short tgaFormat);
+protected:
+ static void save(const glTexture &, GLenum, GLenum, uint8_t channels, const char * path, uint8_t tgaFormat);
+ static glm::ivec2 getSize(const glTexture &);
glTexture m_texture;
GLenum type;
};
+
+class TextureAtlas : public Texture {
+public:
+ TextureAtlas(GLsizei width, GLsizei height, GLuint count);
+
+ void bind(GLenum unit = GL_TEXTURE0) const override;
+ GLuint add(glm::ivec2 position, glm::ivec2 size, void * data, TextureOptions = {});
+
+private:
+ glTexture m_atlas;
+ GLuint used {};
+};
diff --git a/gfx/models/tga.h b/gfx/models/tga.h
new file mode 100644
index 0000000..1f400ef
--- /dev/null
+++ b/gfx/models/tga.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <cstdint>
+#include <glm/vec2.hpp>
+
+struct TGAHead {
+ uint8_t idLength {}, colorMapType {}, format {};
+ uint16_t __attribute__((packed)) colorMapFirst {}, colorMapLength {};
+ uint8_t colorMapEntrySize {};
+ glm::vec<2, uint16_t> origin {}, size {};
+ uint8_t pixelDepth {};
+ uint8_t descriptor {};
+};
+static_assert(sizeof(TGAHead) == 18);
diff --git a/gfx/models/vertex.hpp b/gfx/models/vertex.hpp
index 64ec3d0..181e7e7 100644
--- a/gfx/models/vertex.hpp
+++ b/gfx/models/vertex.hpp
@@ -1,12 +1,14 @@
#pragma once
+#include <GL/glew.h>
#include <glm/glm.hpp>
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)}
+ constexpr Vertex(glm::vec3 pos, glm::vec2 texCoord, glm::vec3 normal, glm::vec4 colour = {}, GLuint material = 0) :
+ pos {std::move(pos)}, texCoord {std::move(texCoord)}, normal {std::move(normal)}, colour {std::move(colour)},
+ material {material}
{
}
#endif
@@ -16,5 +18,6 @@ public:
glm::vec3 pos;
glm::vec2 texCoord;
glm::vec3 normal;
- glm::vec4 colour;
+ glm::vec4 colour {};
+ GLuint material {};
};
diff --git a/glsl.jam b/glsl.jam
new file mode 100644
index 0000000..87dee61
--- /dev/null
+++ b/glsl.jam
@@ -0,0 +1,36 @@
+import type : register ;
+import type : type ;
+import generators : register-standard ;
+import scanner : register ;
+
+type.register GL_VERTEX_SHADER : vs ;
+type.register GL_GEOMETRY_SHADER : gs ;
+type.register GL_FRAGMENT_SHADER : fs ;
+
+generators.register-standard glsl.embed : GL_VERTEX_SHADER : CPP(vs-%) H(vs-%) ;
+generators.register-standard glsl.embed : GL_GEOMETRY_SHADER : CPP(gs-%) H(gs-%) ;
+generators.register-standard glsl.embed : GL_FRAGMENT_SHADER : CPP(fs-%) H(fs-%) ;
+
+class m4-scanner : common-scanner {
+ rule pattern ( ) {
+ return "s?include\\(`([^']*)'\\)" ;
+ }
+}
+
+scanner.register m4-scanner : include ;
+
+type.set-scanner GL_VERTEX_SHADER : m4-scanner ;
+type.set-scanner GL_GEOMETRY_SHADER : m4-scanner ;
+type.set-scanner GL_FRAGMENT_SHADER : m4-scanner ;
+
+actions glsl.embed
+{
+ m4 -I$(2:D) -DNAME=$(2:B) -DTYPE=$(2:S) > $(1[2]) lib/embed-glsl.h.m4
+ m4 -I$(2:D) -DSOURCE=$(2) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(OPTIONS) > $(1[1]) lib/embed-glsl.cpp.m4
+}
+
+rule glsl.embed ( targets * : sources * : properties * )
+{
+ DEPENDS $(targets) : lib/embed-glsl.h.m4 lib/embed-glsl.cpp.m4 ;
+ OPTIONS on $(targets) = [ type.type $(sources) ] ;
+}
diff --git a/lib/collections.hpp b/lib/collections.hpp
index 59cec6f..8f5a43d 100644
--- a/lib/collections.hpp
+++ b/lib/collections.hpp
@@ -65,11 +65,36 @@ operator*=(IterableCollection auto & in, auto && f)
}
template<template<typename...> typename C, typename... T>
+ requires IterableCollection<C<T...>>
[[nodiscard]] constexpr auto
operator*(const C<T...> & in, auto && f)
{
- C<decltype(f(in[0]))> out;
+ C<decltype(f(*in.begin()))> out;
+ if constexpr (requires { out.reserve(in.size()); }) {
+ out.reserve(in.size());
+ }
+
+ std::transform(in.begin(), in.end(), std::inserter(out, out.end()), f);
+ return out;
+}
+
+template<typename T, std::size_t N>
+[[nodiscard]] constexpr auto
+operator*(const std::span<T, N> in, auto && f)
+{
+ std::array<decltype(f(std::declval<T>())), N> out;
+
+ std::transform(in.begin(), in.end(), out.begin(), f);
+ return out;
+}
+
+template<typename T>
+[[nodiscard]] constexpr auto
+operator*(const std::span<T, std::dynamic_extent> in, auto && f)
+{
+ std::vector<decltype(f(std::declval<T>()))> out;
+ out.reserve(in.size());
std::transform(in.begin(), in.end(), std::inserter(out, out.end()), f);
return out;
}
diff --git a/lib/gl_traits.hpp b/lib/gl_traits.hpp
index d140de9..e2e689d 100644
--- a/lib/gl_traits.hpp
+++ b/lib/gl_traits.hpp
@@ -6,37 +6,50 @@
#include <glm/fwd.hpp>
template<typename T> struct gl_traits;
-template<> struct gl_traits<glm::f32> {
- static constexpr GLenum type {GL_FLOAT};
+struct gl_traits_base {
static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::f64> {
+struct gl_traits_float : public gl_traits_base {
+ static constexpr auto vertexAttribFunc {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ glVertexAttribPointer(index, size, type, GL_FALSE, stride, pointer);
+ }};
+};
+struct gl_traits_longfloat : public gl_traits_base {
+ static constexpr auto vertexAttribFunc {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ glVertexAttribLPointer(index, size, type, stride, pointer);
+ }};
+};
+struct gl_traits_integer : public gl_traits_base {
+ static constexpr auto vertexAttribFunc {
+ [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) {
+ glVertexAttribIPointer(index, size, type, stride, pointer);
+ }};
+};
+template<> struct gl_traits<glm::f32> : public gl_traits_float {
+ static constexpr GLenum type {GL_FLOAT};
+};
+template<> struct gl_traits<glm::f64> : public gl_traits_longfloat {
static constexpr GLenum type {GL_DOUBLE};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::int8> {
+template<> struct gl_traits<glm::int8> : public gl_traits_integer {
static constexpr GLenum type {GL_BYTE};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::int16> {
+template<> struct gl_traits<glm::int16> : public gl_traits_integer {
static constexpr GLenum type {GL_SHORT};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::int32> {
+template<> struct gl_traits<glm::int32> : public gl_traits_integer {
static constexpr GLenum type {GL_INT};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::uint8> {
+template<> struct gl_traits<glm::uint8> : public gl_traits_integer {
static constexpr GLenum type {GL_UNSIGNED_BYTE};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::uint16> {
+template<> struct gl_traits<glm::uint16> : public gl_traits_integer {
static constexpr GLenum type {GL_UNSIGNED_SHORT};
- static constexpr GLint size {1};
};
-template<> struct gl_traits<glm::uint32> {
+template<> struct gl_traits<glm::uint32> : public gl_traits_integer {
static constexpr GLenum type {GL_UNSIGNED_INT};
- static constexpr GLint size {1};
};
template<typename T, std::size_t S> struct gl_traits<std::array<T, S>> : public gl_traits<T> {
diff --git a/lib/work.h b/lib/work.h
deleted file mode 100644
index c780e13..0000000
--- a/lib/work.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#pragma once
-
-#include <special_members.hpp>
-
-class Work {
-public:
- virtual ~Work() = default;
- NO_COPY(Work);
- NO_MOVE(Work);
-
- virtual void doWork() = 0;
-};
diff --git a/lib/worker.cpp b/lib/worker.cpp
index fd255c7..45fb6df 100644
--- a/lib/worker.cpp
+++ b/lib/worker.cpp
@@ -1,27 +1,24 @@
#include "worker.h"
-#if __cpp_lib_semaphore
-# include "work.h"
-# include <algorithm>
-# include <iterator>
-# include <mutex>
+#include <algorithm>
+#include <iterator>
+#include <mutex>
+
+Worker Worker::instance;
Worker::Worker() : todoLen {0}
{
std::generate_n(std::back_inserter(threads), std::thread::hardware_concurrency(), [this]() {
- return std::thread {&Worker::worker, this};
+ return std::jthread {&Worker::worker, this};
});
}
Worker::~Worker()
{
todoLen.release(std::thread::hardware_concurrency());
- std::for_each(threads.begin(), threads.end(), [](auto & th) {
- th.join();
- });
}
void
-Worker::addWork(WorkPtr j)
+Worker::addWorkPtr(WorkPtr j)
{
std::lock_guard<std::mutex> lck {todoMutex};
todoLen.release();
@@ -45,4 +42,24 @@ Worker::worker()
j->doWork();
}
}
-#endif
+
+void
+Worker::assist()
+{
+ auto job = [this]() {
+ using namespace std::chrono_literals;
+ if (todoLen.try_acquire_for(100us)) {
+ if (std::lock_guard<std::mutex> lck {todoMutex}; todo.size()) {
+ WorkPtr x = std::move(todo.front());
+ if (x) {
+ todo.pop_front();
+ }
+ return x;
+ }
+ }
+ return WorkPtr {};
+ };
+ if (auto j = job()) {
+ j->doWork();
+ }
+}
diff --git a/lib/worker.h b/lib/worker.h
index 7cd06f9..d9a5a6f 100644
--- a/lib/worker.h
+++ b/lib/worker.h
@@ -1,60 +1,122 @@
#pragma once
+#include <deque>
+#include <functional>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <semaphore>
+#include <special_members.hpp>
+#include <thread>
#include <utility>
-class Work;
-
-#if __cpp_lib_semaphore
-# include <deque>
-# include <memory>
-# include <mutex>
-# include <semaphore>
-# include <special_members.hpp>
-# include <thread>
-# include <vector>
+#include <vector>
class Worker {
public:
+ class WorkItem {
+ protected:
+ WorkItem(Worker * worker) : worker {worker} { }
+ virtual ~WorkItem() = default;
+ NO_MOVE(WorkItem);
+ NO_COPY(WorkItem);
+
+ void
+ assist() const
+ {
+ worker->assist();
+ }
+ Worker * worker;
+
+ public:
+ virtual void doWork() = 0;
+ };
+
+ template<typename T> class WorkItemT : public WorkItem {
+ public:
+ T
+ get()
+ {
+ using namespace std::chrono_literals;
+ while (future.wait_for(0s) == std::future_status::timeout) {
+ assist();
+ }
+ return future.get();
+ }
+
+ protected:
+ WorkItemT(Worker * worker) : WorkItem {worker} { }
+
+ std::promise<T> promise;
+ std::future<T> future {promise.get_future()};
+ friend Worker;
+ };
+
+ template<typename... Params>
+ static auto
+ addWork(Params &&... params)
+ {
+ return instance.addWorkImpl(std::forward<Params>(params)...);
+ }
+ template<typename T> using WorkPtrT = std::shared_ptr<WorkItemT<T>>;
+
+private:
+ template<typename T, typename... Params> class WorkItemTImpl : public WorkItemT<T> {
+ public:
+ WorkItemTImpl(Worker * worker, Params &&... params) :
+ WorkItemT<T> {worker}, params {std::forward<Params>(params)...}
+ {
+ }
+
+ private:
+ void
+ doWork() override
+ {
+ try {
+ if constexpr (std::is_void_v<T>) {
+ std::apply(std::invoke<Params &...>, params);
+ WorkItemT<T>::promise.set_value();
+ }
+ else {
+ WorkItemT<T>::promise.set_value(std::apply(std::invoke<Params &...>, params));
+ }
+ }
+ catch (...) {
+ WorkItemT<T>::promise.set_exception(std::current_exception());
+ }
+ }
+
+ std::tuple<Params...> params;
+ };
+
Worker();
~Worker();
NO_COPY(Worker);
NO_MOVE(Worker);
- using WorkPtr = std::unique_ptr<Work>;
+ using WorkPtr = std::shared_ptr<WorkItem>;
- template<typename T, typename... Params>
- void
- addWork(Params &&... params)
- requires std::is_base_of_v<Work, T>
+ template<typename... Params>
+ auto
+ addWorkImpl(Params &&... params)
{
- addWork(std::make_unique<T>(std::forward<Params>(params)...));
+ using T = decltype(std::invoke(std::forward<Params>(params)...));
+ auto work = std::make_shared<WorkItemTImpl<T, Params...>>(this, std::forward<Params>(params)...);
+ addWorkPtr(work);
+ return work;
}
- void addWork(WorkPtr w);
-
-private:
+ void addWorkPtr(WorkPtr w);
void worker();
+ void assist();
- using Threads = std::vector<std::thread>;
+ using Threads = std::vector<std::jthread>;
using ToDo = std::deque<WorkPtr>;
- Threads threads;
ToDo todo;
std::counting_semaphore<16> todoLen;
std::mutex todoMutex;
-};
-
-#else
+ Threads threads;
-class Worker {
-public:
- template<typename T, typename... Params>
- void
- addWork(Params &&... params)
- requires std::is_base_of_v<Work, T>
- {
- T(std::forward<Params>(params)...).doWork();
- }
+ static Worker instance;
};
-
-#endif
diff --git a/res/assetTemplate.m4 b/res/assetTemplate.m4
new file mode 100644
index 0000000..8750c42
--- /dev/null
+++ b/res/assetTemplate.m4
@@ -0,0 +1,5 @@
+ <asset p.typeid="TYPE" id="ID" name="ID">
+ <bodyMesh id="body">
+ <use type="ID"/>
+ </bodyMesh>
+ </asset>
diff --git a/res/brush47.mtl b/res/brush47.mtl
deleted file mode 100644
index a4ba512..0000000
--- a/res/brush47.mtl
+++ /dev/null
@@ -1,13 +0,0 @@
-# Blender MTL File: 'brush47.blend'
-# Material Count: 1
-
-newmtl Brush47
-Ns 225.000000
-Ka 1.000000 1.000000 1.000000
-Kd 0.800000 0.800000 0.800000
-Ks 0.500000 0.500000 0.500000
-Ke 0.000000 0.000000 0.000000
-Ni 1.450000
-d 1.000000
-illum 2
-map_Kd brush47.png
diff --git a/res/brush47.obj b/res/brush47.obj
deleted file mode 100644
index 8fcb2b1..0000000
--- a/res/brush47.obj
+++ /dev/null
@@ -1,260 +0,0 @@
-# Blender v2.91.0 OBJ File: 'brush47.blend'
-# www.blender.org
-mtllib brush47.mtl
-o Body
-v -1.345000 1.200000 -9.690000
-v -1.345000 1.200000 9.690000
-v 1.345000 1.200000 -9.690000
-v 1.345000 1.200000 9.690000
-v -0.991447 3.900000 -8.536446
-v -1.345000 3.546447 -9.440000
-v -0.991447 3.900000 8.536446
-v -1.345000 3.546447 9.440000
-v 0.991447 3.900000 -8.536446
-v 1.345000 3.546447 -9.440000
-v 0.991447 3.900000 8.536446
-v 1.345000 3.546447 9.440000
-v -1.345000 2.723224 9.690000
-v 1.345000 2.723224 9.690000
-v 1.345000 2.723224 -9.690000
-v -1.345000 2.723224 -9.690000
-v 1.340000 0.500000 9.600000
-v -1.340000 0.500000 9.600000
-v -1.340000 1.200000 9.690000
-v -1.340000 0.500000 -9.600000
-v 1.340000 0.500000 -9.600000
-v -1.300000 0.200000 -2.250000
-v -1.300000 1.200000 -2.250000
-v -1.300000 0.200000 2.250000
-v -1.300000 1.200000 2.250000
-v 1.300000 0.200000 -2.250000
-v 1.300000 1.200000 -2.250000
-v 1.300000 0.200000 2.250000
-v 1.300000 1.200000 2.250000
-v 1.345000 1.200000 8.236500
-v 1.345000 1.200000 -8.236500
-v -1.345000 1.200000 -8.236500
-v -1.345000 1.200000 8.236500
-vt 0.025174 0.662500
-vt 0.025174 0.831250
-vt 0.037326 0.925000
-vt 0.957465 0.927083
-vt 0.971354 0.833333
-vt 0.971354 0.662500
-vt 0.900391 0.662500
-vt 0.096137 0.662500
-vt 0.025174 0.662500
-vt 0.025174 0.831250
-vt 0.037326 0.925000
-vt 0.957465 0.927083
-vt 0.971354 0.833333
-vt 0.971354 0.662500
-vt 0.900391 0.662500
-vt 0.096137 0.662500
-vt 0.605035 0.456250
-vt 0.605035 0.581250
-vt 0.359375 0.581250
-vt 0.359375 0.456250
-vt 0.605035 0.581250
-vt 0.605035 0.456250
-vt 0.359375 0.581250
-vt 0.359375 0.456250
-vt 0.026042 0.593750
-vt 0.971354 0.593750
-vt 0.026042 0.593750
-vt 0.971354 0.593750
-vt 0.088542 0.089583
-vt 0.088542 0.206250
-vt 0.918403 0.206250
-vt 0.918403 0.089583
-vt 0.926215 0.527083
-vt 0.926215 0.612500
-vt 0.834201 0.612500
-vt 0.834201 0.527083
-vt 0.083333 0.989583
-vt 0.913194 0.989583
-vt 0.037326 0.927083
-vt 0.152778 0.364583
-vt 0.197049 0.364583
-vt 0.204861 0.310417
-vt 0.144965 0.310417
-vt 0.083333 0.989583
-vt 0.913194 0.989583
-vt 0.037326 0.927083
-vt 0.152778 0.364583
-vt 0.197049 0.364583
-vt 0.204861 0.310417
-vt 0.144965 0.310417
-vt 0.834201 0.527083
-vt 0.834201 0.612500
-vt 0.927083 0.612500
-vt 0.927083 0.527083
-vt 0.114583 0.293750
-vt 0.114583 0.414583
-vt 0.010417 0.414583
-vt 0.010417 0.293750
-vt 0.010417 0.293750
-vt 0.010417 0.414583
-vt 0.114583 0.414583
-vt 0.114583 0.293750
-vt 0.043403 0.014583
-vt 0.002604 0.014583
-vt 0.002604 0.081250
-vt 0.043403 0.081250
-vt 0.043403 0.014583
-vt 0.002604 0.014583
-vt 0.002604 0.081250
-vt 0.043403 0.081250
-vn -1.0000 0.0000 0.0000
-vn 1.0000 0.0000 0.0000
-vn 0.0000 0.0000 -1.0000
-vn 0.0000 0.0000 1.0000
-vn -1.0000 0.0071 0.0000
-vn 1.0000 0.0071 -0.0000
-vn -0.3335 -0.9344 -0.1252
-vn 0.3335 -0.9344 -0.1252
-vn 0.3335 -0.9344 0.1252
-vn -0.3335 -0.9344 0.1252
-vn 0.6755 -0.1083 -0.7294
-vn 0.6276 -0.5509 -0.5500
-vn -0.6276 -0.5509 -0.5500
-vn -0.6755 -0.1083 -0.7294
-vn 0.6276 -0.5509 0.5500
-vn -0.6276 -0.5509 0.5500
-vn -0.6755 -0.1083 0.7294
-vn 0.6755 -0.1083 0.7294
-vn 0.8867 0.0030 -0.4622
-vn -0.6937 0.0482 -0.7186
-vn -0.6937 0.0482 0.7186
-vn 0.6937 0.0482 0.7186
-vn -0.6128 0.1051 -0.7832
-vn 0.6145 0.1049 -0.7819
-vn 0.0000 0.1275 -0.9918
-vn 0.6128 0.1051 0.7832
-vn -0.6128 0.1051 0.7832
-usemtl Brush47
-s off
-f 4/1/1 14/2/1 12/3/1 10/4/1 15/5/1 3/6/1 31/7/1 30/8/1
-f 1/9/2 16/10/2 6/11/2 8/12/2 13/13/2 2/14/2 33/15/2 32/16/2
-f 22/17/2 23/18/2 25/19/2 24/20/2
-f 24/20/3 25/19/3 29/21/3 28/22/3
-f 28/22/1 29/21/1 27/23/1 26/24/1
-f 26/24/4 27/23/4 23/18/4 22/17/4
-f 4/1/5 30/8/5 17/25/5
-f 31/7/5 3/6/5 21/26/5
-f 1/9/6 32/16/6 20/27/6
-f 33/15/6 2/14/6 18/28/6
-s 1
-f 11/29/7 7/30/8 5/31/9 9/32/10
-f 13/33/11 8/34/12 12/35/13 14/36/14
-f 5/37/9 7/38/8 8/12/12 6/39/15
-f 7/40/8 11/41/7 12/42/13 8/43/12
-f 11/44/7 9/45/10 10/4/16 12/46/13
-f 9/47/10 5/48/9 6/49/15 10/50/16
-f 15/51/17 10/52/16 6/53/15 16/54/18
-f 2/55/19 13/56/11 14/57/14 4/58/20
-f 3/59/21 15/60/17 16/61/18 1/62/22
-f 17/63/23 18/64/24 19/65/25 4/66/20
-f 20/67/26 21/68/27 3/69/21 1/70/22
-o Bogie2
-v -0.717500 0.000000 -2.700000
-v -0.717500 0.000000 -9.300000
-v -0.717500 1.200000 -2.700000
-v 0.717500 1.200000 -2.700000
-v 0.717500 0.000000 -2.700000
-v 0.717500 0.000000 -9.300000
-v 1.017500 1.200000 -2.700000
-v 1.017500 0.000000 -2.700000
-v 1.017500 0.000000 -9.300000
-v -1.017500 0.000000 -9.300000
-v -1.017501 0.000000 -2.700000
-v -1.017501 1.200000 -2.700000
-v -0.717500 0.654015 -9.300000
-v -0.717500 1.200000 -8.236500
-v -0.717500 0.661560 -9.285304
-v 0.717500 1.200000 -8.236500
-v 0.717500 0.654015 -9.300000
-v 1.017500 1.200000 -8.236500
-v 1.017500 0.654015 -9.300000
-v -1.017500 0.654015 -9.300000
-v -1.017500 1.200000 -8.236500
-vt 0.342014 0.591667
-vt 0.342014 0.435417
-vt 0.006076 0.435417
-vt 0.006076 0.520575
-vt 0.006824 0.521557
-vt 0.060208 0.591667
-vt 0.006076 0.435417
-vt 0.342014 0.435417
-vt 0.342014 0.591667
-vt 0.060208 0.591667
-vt 0.006076 0.520575
-vt 0.217014 0.270833
-vt 0.552951 0.270833
-vt 0.552951 0.427083
-vt 0.271146 0.427083
-vt 0.217014 0.355992
-vt 0.552083 0.429167
-vt 0.552083 0.272917
-vt 0.216146 0.272917
-vt 0.216146 0.358075
-vt 0.270278 0.429167
-vn 1.0000 0.0000 -0.0000
-vn -1.0000 0.0000 0.0000
-usemtl Brush47
-s off
-f 36/71/28 34/72/28 35/73/28 46/74/28 48/75/28 47/76/28
-f 39/77/29 38/78/29 37/79/29 49/80/29 50/81/29
-f 42/82/29 41/83/29 40/84/29 51/85/29 52/86/29
-f 45/87/28 44/88/28 43/89/28 53/90/28 54/91/28
-o Bogie1
-v 0.717500 0.000000 2.700000
-v 0.717500 1.200000 2.700000
-v 0.717500 0.000000 9.300000
-v -0.717500 0.000000 2.700000
-v -0.717500 1.200000 2.700000
-v -0.717500 0.000000 9.300000
-v 1.017500 0.000000 2.700001
-v 1.017500 1.200000 2.700001
-v 1.017500 0.000000 9.300000
-v -1.017500 0.000000 2.700000
-v -1.017500 1.200000 2.700000
-v -1.017500 0.000000 9.300000
-v 0.717500 0.654015 9.300000
-v 0.717500 1.200000 8.236500
-v 1.017500 0.654015 9.300000
-v 1.017500 1.200000 8.236500
-v -0.717500 1.200000 8.236500
-v -0.717500 0.661372 9.285670
-v -0.717500 0.654015 9.300000
-v -1.017500 1.200000 8.236500
-v -1.017500 0.654015 9.300000
-vt 0.342014 0.435417
-vt 0.006076 0.435417
-vt 0.006076 0.520575
-vt 0.060208 0.591667
-vt 0.342014 0.591667
-vt 0.006076 0.520575
-vt 0.006076 0.435417
-vt 0.342014 0.435417
-vt 0.342014 0.591667
-vt 0.060208 0.591667
-vt 0.006806 0.521533
-vt 0.552083 0.270833
-vt 0.216146 0.270833
-vt 0.216146 0.355992
-vt 0.270278 0.427083
-vt 0.552083 0.427083
-vt 0.215278 0.355992
-vt 0.215278 0.270833
-vt 0.552083 0.270833
-vt 0.552083 0.427083
-vt 0.269549 0.427083
-vn -1.0000 0.0000 -0.0000
-vn 1.0000 -0.0000 0.0000
-usemtl Brush47
-s off
-f 55/92/30 57/93/30 67/94/30 68/95/30 56/96/30
-f 73/97/31 60/98/31 58/99/31 59/100/31 71/101/31 72/102/31
-f 61/103/30 63/104/30 69/105/30 70/106/30 62/107/30
-f 75/108/31 66/109/31 64/110/31 65/111/31 74/112/31
diff --git a/res/brush47.png b/res/brush47.png
deleted file mode 100644
index 3d435d6..0000000
--- a/res/brush47.png
+++ /dev/null
Binary files differ
diff --git a/res/foliage.xml b/res/foliage.xml
new file mode 100644
index 0000000..5345b39
--- /dev/null
+++ b/res/foliage.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0"?>
+<ilt p.id="assetFactory">
+ <assimp path="shapespark-low-poly-plants-kit.fbx"/>
+ <asset p.typeid="Foliage" id="Tree-01-1" name="Tree-01-1">
+ <bodyMesh id="body">
+ <use type="Tree-01-1"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-01-1" name="Tree-01-1">
+ <bodyMesh id="body">
+ <use type="Tree-01-1"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-01-2" name="Tree-01-2">
+ <bodyMesh id="body">
+ <use type="Tree-01-2"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-01-3" name="Tree-01-3">
+ <bodyMesh id="body">
+ <use type="Tree-01-3"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-01-4" name="Tree-01-4">
+ <bodyMesh id="body">
+ <use type="Tree-01-4"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-02-1" name="Tree-02-1">
+ <bodyMesh id="body">
+ <use type="Tree-02-1"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-02-2" name="Tree-02-2">
+ <bodyMesh id="body">
+ <use type="Tree-02-2"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-02-3" name="Tree-02-3">
+ <bodyMesh id="body">
+ <use type="Tree-02-3"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-02-4" name="Tree-02-4">
+ <bodyMesh id="body">
+ <use type="Tree-02-4"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-03-1" name="Tree-03-1">
+ <bodyMesh id="body">
+ <use type="Tree-03-1"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-03-2" name="Tree-03-2">
+ <bodyMesh id="body">
+ <use type="Tree-03-2"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-03-3" name="Tree-03-3">
+ <bodyMesh id="body">
+ <use type="Tree-03-3"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Tree-03-4" name="Tree-03-4">
+ <bodyMesh id="body">
+ <use type="Tree-03-4"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Hedge-01" name="Hedge-01">
+ <bodyMesh id="body">
+ <use type="Hedge-01"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Bush-01" name="Bush-01">
+ <bodyMesh id="body">
+ <use type="Bush-01"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Bush-02" name="Bush-02">
+ <bodyMesh id="body">
+ <use type="Bush-02"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Bush-03" name="Bush-03">
+ <bodyMesh id="body">
+ <use type="Bush-03"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Bush-04" name="Bush-04">
+ <bodyMesh id="body">
+ <use type="Bush-04"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Bush-05" name="Bush-05">
+ <bodyMesh id="body">
+ <use type="Bush-05"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Clover-01" name="Clover-01">
+ <bodyMesh id="body">
+ <use type="Clover-01"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Clover-02" name="Clover-02">
+ <bodyMesh id="body">
+ <use type="Clover-02"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Clover-03" name="Clover-03">
+ <bodyMesh id="body">
+ <use type="Clover-03"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Clover-04" name="Clover-04">
+ <bodyMesh id="body">
+ <use type="Clover-04"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Clover-05" name="Clover-05">
+ <bodyMesh id="body">
+ <use type="Clover-05"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Grass-01" name="Grass-01">
+ <bodyMesh id="body">
+ <use type="Grass-01"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Grass-02" name="Grass-02">
+ <bodyMesh id="body">
+ <use type="Grass-02"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Grass-03" name="Grass-03">
+ <bodyMesh id="body">
+ <use type="Grass-03"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Flowers-02" name="Flowers-02">
+ <bodyMesh id="body">
+ <use type="Flowers-02"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Flowers-04" name="Flowers-04">
+ <bodyMesh id="body">
+ <use type="Flowers-04"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Flowers-01" name="Flowers-01">
+ <bodyMesh id="body">
+ <use type="Flowers-01"/>
+ </bodyMesh>
+ </asset>
+ <asset p.typeid="Foliage" id="Flowers-03" name="Flowers-03">
+ <bodyMesh id="body">
+ <use type="Flowers-03"/>
+ </bodyMesh>
+ </asset>
+</ilt>
diff --git a/res/shapespark-low-poly-plants-kit.fbx b/res/shapespark-low-poly-plants-kit.fbx
new file mode 100644
index 0000000..fe87c0c
--- /dev/null
+++ b/res/shapespark-low-poly-plants-kit.fbx
Binary files differ
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index bf1c408..b0eed5e 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -44,7 +44,6 @@ project : requirements
lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ;
run test-collection.cpp ;
-run test-obj.cpp ;
run test-maths.cpp ;
run test-lib.cpp ;
run test-geo.cpp ;
@@ -52,11 +51,12 @@ run test-network.cpp ;
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-render.cpp : -- : test-assetFactory : <library>test ;
run test-glContextBhvr.cpp ;
-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 ;
+run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.* ] fixtures/rgb.txt ] : <library>test ;
+run perf-assetFactory.cpp : -- : test-assetFactory : <library>benchmark <library>test ;
+run perf-persistence.cpp : -- : test-persistence : <library>benchmark <library>test ;
+run test-worker.cpp ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
explicit perf-assetFactory ;
diff --git a/test/perf-assetFactory.cpp b/test/perf-assetFactory.cpp
index c90ac52..147e4ba 100644
--- a/test/perf-assetFactory.cpp
+++ b/test/perf-assetFactory.cpp
@@ -14,6 +14,17 @@ brush47xml_load(benchmark::State & state)
}
}
+static void
+foliagexml_load(benchmark::State & state)
+{
+ TestMainWindow window;
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/foliage.xml"));
+ }
+}
+
BENCHMARK(brush47xml_load);
+BENCHMARK(foliagexml_load);
BENCHMARK_MAIN();
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
index 54168aa..3d79213 100644
--- a/test/test-assetFactory.cpp
+++ b/test/test-assetFactory.cpp
@@ -8,6 +8,8 @@
#include "assetFactory/assetFactory.h"
#include "assetFactory/object.h"
#include "assetFactory/texturePacker.h"
+#include "game/scenary/foliage.h"
+#include "game/scenary/plant.h"
#include "game/vehicles/railVehicle.h"
#include "game/vehicles/railVehicleClass.h"
#include "gfx/gl/sceneRenderer.h"
@@ -30,7 +32,7 @@ public:
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());
+ Texture::save(outImage, outpath.c_str());
}
void
content(const SceneShader & shader) const override
@@ -96,8 +98,30 @@ BOOST_AUTO_TEST_CASE(brush47xml, *boost::unit_test::timeout(5))
render();
}
+
+BOOST_AUTO_TEST_CASE(foliage, *boost::unit_test::timeout(5))
+{
+ auto mf = AssetFactory::loadXML(RESDIR "/foliage.xml");
+ BOOST_REQUIRE(mf);
+ auto tree_01_1 = mf->assets.at("Tree-01-1");
+ BOOST_REQUIRE(tree_01_1);
+ auto tree_01_1_f = std::dynamic_pointer_cast<Foliage>(tree_01_1);
+ BOOST_REQUIRE(tree_01_1_f);
+
+ auto plant = std::make_shared<Plant>(tree_01_1_f, Location {{-2, 2, 0}, {}});
+ objects.objects.push_back(plant);
+
+ render(5);
+}
BOOST_AUTO_TEST_SUITE_END();
+BOOST_AUTO_TEST_CASE(loadall)
+{
+ const auto assets = AssetFactory::loadAll(RESDIR);
+ BOOST_CHECK(assets.at("brush-47"));
+ BOOST_CHECK(assets.at("Tree-01-1"));
+}
+
template<typename T> using InOut = std::tuple<T, T>;
BOOST_DATA_TEST_CASE(normalizeColourName,
boost::unit_test::data::make<InOut<std::string>>({
@@ -157,7 +181,7 @@ BOOST_AUTO_TEST_CASE(texturePacker_many, *boost::unit_test::timeout(5))
BOOST_CHECK_EQUAL(totalSize, TexturePacker::area(result.second));
}
-BOOST_AUTO_TEST_CASE(texturePacker_many_random, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(texturePacker_many_random, *boost::unit_test::timeout(15))
{
std::vector<TexturePacker::Image> images(2048);
std::mt19937 gen(std::random_device {}());
diff --git a/test/test-obj.cpp b/test/test-obj.cpp
deleted file mode 100644
index e6e725d..0000000
--- a/test/test-obj.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#define BOOST_TEST_MODULE test_obj
-
-#include <boost/test/unit_test.hpp>
-
-#include <gfx/models/obj.h>
-#include <gfx/models/vertex.hpp>
-#include <glm/glm.hpp>
-#include <map>
-#include <memory>
-#include <utility>
-#include <vector>
-
-BOOST_AUTO_TEST_CASE(objparse)
-{
- const ObjParser op {RESDIR "/brush47.obj"};
- BOOST_CHECK_EQUAL(75, op.vertices.size());
- BOOST_CHECK_EQUAL(112, op.texCoords.size());
- BOOST_CHECK_EQUAL(31, op.normals.size());
- BOOST_CHECK_EQUAL(3, op.objects.size());
- const auto & object {op.objects.front()};
- BOOST_CHECK_EQUAL("Body", object.first);
- BOOST_CHECK_EQUAL(21, object.second.size());
- BOOST_CHECK_EQUAL(8, object.second[0].size());
- BOOST_CHECK_EQUAL(8, object.second[1].size());
- BOOST_CHECK_EQUAL(4, object.second[12].size());
-}
-
-BOOST_AUTO_TEST_CASE(create_meshes)
-{
- const ObjParser op {RESDIR "/brush47.obj"};
- const auto ms = op.createMeshData();
- BOOST_REQUIRE_EQUAL(3, ms.size());
- BOOST_REQUIRE_EQUAL("Body", ms.begin()->first);
- const auto & o = ms.at("Body");
- BOOST_REQUIRE_EQUAL(88, o.first.size());
- const auto & v = o.first.front();
- BOOST_REQUIRE_CLOSE(-1.345, v.pos.x, 1);
- BOOST_REQUIRE_EQUAL(138, o.second.size());
-}
diff --git a/test/test-render.cpp b/test/test-render.cpp
index 7771760..45acab5 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -6,8 +6,10 @@
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
+#include <assetFactory/assetFactory.h>
#include <game/geoData.h>
#include <game/terrain.h>
+#include <game/vehicles/railVehicle.h>
#include <game/vehicles/railVehicleClass.h>
#include <gfx/gl/sceneRenderer.h>
#include <gfx/models/texture.h>
@@ -19,18 +21,30 @@
#include <ui/window.h>
class TestScene : public SceneProvider {
- RailVehicleClass train {"brush47"};
+ std::shared_ptr<RailVehicle> train1, train2;
+
Terrain terrain {[]() {
auto gd = std::make_shared<GeoData>(GeoData::Limits {{0, 0}, {100, 100}});
gd->generateRandom();
return gd;
}()};
+
+public:
+ TestScene()
+ {
+ const auto assetFactory = AssetFactory::loadXML(RESDIR "/brush47.xml");
+ const auto brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(assetFactory->assets.at("brush-47"));
+ train1 = std::make_shared<RailVehicle>(brush47rvc);
+ train1->location.pos = {52, 50, 2};
+ train2 = std::make_shared<RailVehicle>(brush47rvc);
+ train2->location.pos = {52, 30, 2};
+ }
void
content(const SceneShader & shader) const override
{
terrain.render(shader);
- 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}}});
+ train1->render(shader);
+ train2->render(shader);
}
void
lights(const SceneShader &) const override
@@ -40,8 +54,8 @@ class TestScene : public SceneProvider {
shadows(const ShadowMapper & shadowMapper) const override
{
terrain.shadows(shadowMapper);
- 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}}});
+ train1->shadows(shadowMapper);
+ train2->shadows(shadowMapper);
}
};
@@ -73,7 +87,7 @@ BOOST_AUTO_TEST_CASE(basic)
const TestScene scene;
ss.render(scene);
glDisable(GL_DEBUG_OUTPUT);
- Texture::save(outImage, size, "/tmp/basic.tga");
+ Texture::save(outImage, "/tmp/basic.tga");
}
BOOST_AUTO_TEST_CASE(pointlight)
@@ -101,7 +115,7 @@ BOOST_AUTO_TEST_CASE(pointlight)
const PointLightScene scene;
ss.render(scene);
glDisable(GL_DEBUG_OUTPUT);
- Texture::save(outImage, size, "/tmp/pointlight.tga");
+ Texture::save(outImage, "/tmp/pointlight.tga");
}
BOOST_AUTO_TEST_CASE(spotlight)
@@ -128,7 +142,7 @@ BOOST_AUTO_TEST_CASE(spotlight)
const PointLightScene scene;
ss.render(scene);
glDisable(GL_DEBUG_OUTPUT);
- Texture::save(outImage, size, "/tmp/spotlight.tga");
+ Texture::save(outImage, "/tmp/spotlight.tga");
}
BOOST_AUTO_TEST_SUITE_END();
diff --git a/test/test-worker.cpp b/test/test-worker.cpp
new file mode 100644
index 0000000..76b7138
--- /dev/null
+++ b/test/test-worker.cpp
@@ -0,0 +1,104 @@
+#define BOOST_TEST_MODULE test_worker
+
+#include "testHelpers.h"
+#include <boost/test/unit_test.hpp>
+#include <set>
+#include <stream_support.hpp>
+#include <worker.h>
+
+uint32_t
+workCounter()
+{
+ static std::atomic_uint32_t n;
+ usleep(1000);
+ return n++;
+}
+
+void
+workVoid()
+{
+ usleep(1000);
+}
+
+void
+workFail()
+{
+ usleep(1000);
+ throw std::runtime_error {"test"};
+}
+
+BOOST_AUTO_TEST_CASE(basic_slow_counter)
+{
+ std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ for (int i {}; i < 30; ++i) {
+ ps.push_back(Worker::addWork(workCounter));
+ }
+ std::set<uint32_t> out;
+ std::transform(ps.begin(), ps.end(), std::inserter(out, out.end()), [](auto && p) {
+ return p->get();
+ });
+ BOOST_REQUIRE_EQUAL(out.size(), ps.size());
+ BOOST_CHECK_EQUAL(*out.begin(), 0);
+ BOOST_CHECK_EQUAL(*out.rbegin(), ps.size() - 1);
+}
+
+BOOST_AUTO_TEST_CASE(basic_error_handler)
+{
+ auto workitem = Worker::addWork(workFail);
+ BOOST_CHECK_THROW(workitem->get(), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(basic_void_work)
+{
+ auto workitem = Worker::addWork(workVoid);
+ BOOST_CHECK_NO_THROW(workitem->get());
+}
+
+BOOST_AUTO_TEST_CASE(lambda_void)
+{
+ BOOST_CHECK_NO_THROW(Worker::addWork([]() {})->get());
+ BOOST_CHECK_NO_THROW(Worker::addWork([](int) {}, 0)->get());
+ BOOST_CHECK_NO_THROW(Worker::addWork([](int, int) {}, 0, 0)->get());
+}
+
+BOOST_AUTO_TEST_CASE(lambda_value)
+{
+ BOOST_CHECK_EQUAL(1, Worker::addWork([]() {
+ return 1;
+ })->get());
+ BOOST_CHECK_EQUAL(2,
+ Worker::addWork(
+ [](int i) {
+ return i;
+ },
+ 2)
+ ->get());
+ BOOST_CHECK_EQUAL(3,
+ Worker::addWork(
+ [](int i, int j) {
+ return i + j;
+ },
+ 1, 2)
+ ->get());
+}
+
+BOOST_AUTO_TEST_CASE(recursive, *boost::unit_test::timeout(5))
+{
+ auto recurse = []() {
+ std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ for (int i {}; i < 30; ++i) {
+ ps.push_back(Worker::addWork(workCounter));
+ }
+ return std::accumulate(ps.begin(), ps.end(), 0U, [](auto && out, auto && p) {
+ return out += p->get();
+ });
+ };
+ std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ for (int i {}; i < 30; ++i) {
+ ps.push_back(Worker::addWork(recurse));
+ }
+ std::set<uint32_t> out;
+ std::transform(ps.begin(), ps.end(), std::inserter(out, out.end()), [](auto && p) {
+ return p->get();
+ });
+}