summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--Jamroot.jam13
-rw-r--r--application/main.cpp97
-rw-r--r--application/resviewer.cpp227
-rw-r--r--assetFactory/asset.cpp6
-rw-r--r--assetFactory/asset.h8
-rw-r--r--assetFactory/assetFactory.cpp3
-rw-r--r--assetFactory/assetFactory.h2
-rw-r--r--assetFactory/assimp.cpp2
-rw-r--r--assetFactory/cylinder.cpp2
-rw-r--r--assetFactory/factoryMesh.cpp11
-rw-r--r--assetFactory/modelFactoryMesh.cpp8
-rw-r--r--assetFactory/modelFactoryMesh.h11
-rw-r--r--assetFactory/mutation.cpp5
-rw-r--r--config/types.h2
-rw-r--r--game/environment.cpp93
-rw-r--r--game/environment.h18
-rw-r--r--game/gamestate.cpp3
-rw-r--r--game/gamestate.h9
-rw-r--r--game/geoData.cpp821
-rw-r--r--game/geoData.h204
-rw-r--r--game/geoDataMesh.cpp150
-rw-r--r--game/geoDataMesh.h116
-rw-r--r--game/network/link.cpp100
-rw-r--r--game/network/link.h14
-rw-r--r--game/network/network.cpp89
-rw-r--r--game/network/network.h59
-rw-r--r--game/network/network.impl.h90
-rw-r--r--game/network/rail.cpp156
-rw-r--r--game/network/rail.h9
-rw-r--r--game/orders.h2
-rw-r--r--game/scenary/foliage.cpp41
-rw-r--r--game/scenary/foliage.h21
-rw-r--r--game/scenary/illuminator.cpp12
-rw-r--r--game/scenary/illuminator.h8
-rw-r--r--game/scenary/plant.cpp3
-rw-r--r--game/terrain.cpp166
-rw-r--r--game/terrain.h51
-rw-r--r--game/vehicles/linkHistory.cpp18
-rw-r--r--game/vehicles/railVehicle.cpp9
-rw-r--r--game/vehicles/railVehicleClass.cpp17
-rw-r--r--game/vehicles/railVehicleClass.h11
-rw-r--r--game/vehicles/train.cpp10
-rw-r--r--game/vehicles/train.h4
-rw-r--r--game/vehicles/vehicle.cpp2
-rw-r--r--game/vehicles/vehicle.h2
-rw-r--r--game/water.cpp4
-rw-r--r--game/water.h12
-rw-r--r--gfx/aabb.h41
-rw-r--r--gfx/camera.cpp (renamed from gfx/gl/camera.cpp)33
-rw-r--r--gfx/camera.h (renamed from gfx/gl/camera.h)26
-rw-r--r--gfx/followCameraController.cpp4
-rw-r--r--gfx/frustum.cpp67
-rw-r--r--gfx/frustum.h45
-rw-r--r--gfx/gl/program.cpp19
-rw-r--r--gfx/gl/program.h11
-rw-r--r--gfx/gl/sceneProvider.cpp4
-rw-r--r--gfx/gl/sceneProvider.h5
-rw-r--r--gfx/gl/sceneRenderer.cpp50
-rw-r--r--gfx/gl/sceneRenderer.h8
-rw-r--r--gfx/gl/sceneShader.cpp15
-rw-r--r--gfx/gl/sceneShader.h12
-rw-r--r--gfx/gl/shader.cpp69
-rw-r--r--gfx/gl/shader.h8
-rw-r--r--gfx/gl/shaders/commonPoint.glsl15
-rw-r--r--gfx/gl/shaders/commonShadowPoint.glsl5
-rw-r--r--gfx/gl/shaders/commonShadowPoint.gs8
-rw-r--r--gfx/gl/shaders/directionalLight.fs29
-rw-r--r--gfx/gl/shaders/dynamicPoint.vs2
-rw-r--r--gfx/gl/shaders/dynamicPointInst.vs2
-rw-r--r--gfx/gl/shaders/fixedPoint.vs2
-rw-r--r--gfx/gl/shaders/getMaterialDetail.glsl12
-rw-r--r--gfx/gl/shaders/landmass.fs6
-rw-r--r--gfx/gl/shaders/landmass.vs3
-rw-r--r--gfx/gl/shaders/material.fs36
-rw-r--r--gfx/gl/shaders/materialCommon.glsl33
-rw-r--r--gfx/gl/shaders/materialDetail.glsl5
-rw-r--r--gfx/gl/shaders/materialInterface.glsl20
-rw-r--r--gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs8
-rw-r--r--gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs14
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.fs16
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.gs36
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.vs17
-rw-r--r--gfx/gl/shaders/shadowStencil.fs18
-rw-r--r--gfx/gl/shaders/shadowStencil.gs28
-rw-r--r--gfx/gl/shaders/shadowStencil.vs20
-rw-r--r--gfx/gl/shaders/uiShader.fs11
-rw-r--r--gfx/gl/shaders/uiShader.vs13
-rw-r--r--gfx/gl/shaders/uiShaderFont.fs12
-rw-r--r--gfx/gl/shadowMapper.cpp98
-rw-r--r--gfx/gl/shadowMapper.h21
-rw-r--r--gfx/gl/shadowStenciller.cpp74
-rw-r--r--gfx/gl/shadowStenciller.h27
-rw-r--r--gfx/gl/uiShader.cpp27
-rw-r--r--gfx/gl/uiShader.h47
-rw-r--r--gfx/gl/vertexArrayObject.h12
-rw-r--r--gfx/lightDirection.cpp12
-rw-r--r--gfx/lightDirection.h39
-rw-r--r--gfx/models/mesh.cpp28
-rw-r--r--gfx/models/mesh.h30
-rw-r--r--gfx/models/texture.cpp19
-rw-r--r--gfx/models/texture.h7
-rw-r--r--gfx/renderable.cpp7
-rw-r--r--gfx/renderable.h8
-rw-r--r--glsl.jam22
-rw-r--r--lib/chronology.cpp12
-rw-r--r--lib/chronology.h2
-rw-r--r--lib/collection.h263
-rw-r--r--lib/collections.h86
-rw-r--r--lib/embed-glsl.cpp.m42
-rw-r--r--lib/enumDetails.h102
-rw-r--r--lib/filesystem.cpp2
-rw-r--r--lib/filesystem.h2
-rw-r--r--lib/glArrays.h26
-rw-r--r--lib/glMappedBufferWriter.cpp4
-rw-r--r--lib/glMappedBufferWriter.h78
-rw-r--r--lib/jsonParse-persistence.h3
-rw-r--r--lib/jsonParse.ll9
-rw-r--r--lib/manyPtr.h86
-rw-r--r--lib/maths.cpp105
-rw-r--r--lib/maths.h517
-rw-r--r--lib/msgException.h23
-rw-r--r--lib/persistence.h3
-rw-r--r--lib/ray.h50
-rw-r--r--lib/sorting.h9
-rw-r--r--lib/stdTypeDefs.h12
-rw-r--r--lib/stream_support.h52
-rw-r--r--lib/triangle.h156
-rw-r--r--lib/util.h21
-rw-r--r--lib/worker.cpp5
-rw-r--r--res/shapespark-low-poly-plants-kit.fbxbin20264540 -> 20407788 bytes
-rw-r--r--res/ui/icon/magnifier.svg50
-rw-r--r--res/ui/icon/network.pngbin16793 -> 0 bytes
-rw-r--r--res/ui/icon/rails.svg36
-rw-r--r--res/ui/icon/road.svg27
-rw-r--r--test/.clang-tidy3
-rw-r--r--test/Jamfile.jam29
-rw-r--r--test/enumDetailsData.h15
-rw-r--r--test/fixtures/geoData/deform/multi1.json21
-rw-r--r--test/perf-assetFactory.cpp32
-rw-r--r--test/perf-geoData.cpp45
-rw-r--r--test/perf-instancing.cpp59
-rw-r--r--test/perf-persistence.cpp25
-rw-r--r--test/perf-terrain.cpp37
-rw-r--r--test/test-assetFactory.cpp258
-rw-r--r--test/test-collection.cpp419
-rw-r--r--test/test-enumDetails.cpp46
-rw-r--r--test/test-environment.cpp64
-rw-r--r--test/test-geoData-counts.cpp65
-rw-r--r--test/test-geoData.cpp246
-rw-r--r--test/test-glContainer.cpp109
-rw-r--r--test/test-glContextBhvr.cpp38
-rw-r--r--test/test-instancing.cpp93
-rw-r--r--test/test-lib.cpp119
-rw-r--r--test/test-maths.cpp402
-rw-r--r--test/test-network.cpp393
-rw-r--r--test/test-pack.cpp2
-rw-r--r--test/test-persistence.cpp369
-rw-r--r--test/test-render.cpp222
-rw-r--r--test/test-static-enumDetails.cpp126
-rw-r--r--test/test-static-stream_support.cpp8
-rw-r--r--test/test-text.cpp93
-rw-r--r--test/test-ui.cpp20
-rw-r--r--test/test-worker.cpp91
-rw-r--r--test/testHelpers.cpp6
-rw-r--r--test/testHelpers.h30
-rw-r--r--test/testMainWindow.cpp2
-rw-r--r--test/testRenderOutput.cpp2
-rw-r--r--test/testRenderOutput.h2
-rw-r--r--test/testStructures.h2
-rw-r--r--thirdparty/Jamfile.jam15
m---------thirdparty/ctre0
m---------thirdparty/imgui0
m---------thirdparty/lunasvg0
-rw-r--r--thirdparty/openmesh/helpers.h60
-rw-r--r--ui/builders/freeExtend.cpp30
-rw-r--r--ui/builders/freeExtend.h6
-rw-r--r--ui/builders/join.cpp18
-rw-r--r--ui/builders/join.h3
-rw-r--r--ui/builders/straight.cpp19
-rw-r--r--ui/builders/straight.h5
-rw-r--r--ui/editNetwork.cpp61
-rw-r--r--ui/editNetwork.h18
-rw-r--r--ui/gameMainSelector.cpp62
-rw-r--r--ui/gameMainSelector.h19
-rw-r--r--ui/gameMainWindow.cpp101
-rw-r--r--ui/gameMainWindow.h15
-rw-r--r--ui/icon.cpp9
-rw-r--r--ui/icon.h3
-rw-r--r--ui/iconButton.cpp57
-rw-r--r--ui/iconButton.h26
-rw-r--r--ui/imgui_extras.cpp33
-rw-r--r--ui/imgui_extras.h10
-rw-r--r--ui/imgui_wrap.h7
-rw-r--r--ui/mainApplication.h2
-rw-r--r--ui/mainWindow.cpp4
-rw-r--r--ui/mainWindow.h5
-rw-r--r--ui/manualCameraController.cpp8
-rw-r--r--ui/manualCameraController.h7
-rw-r--r--ui/modeHelper.h40
-rw-r--r--ui/queryTool.cpp42
-rw-r--r--ui/queryTool.h17
-rw-r--r--ui/svgIcon.cpp34
-rw-r--r--ui/svgIcon.h18
-rw-r--r--ui/text.cpp73
-rw-r--r--ui/text.h34
-rw-r--r--ui/toolbar.cpp35
-rw-r--r--ui/toolbar.h23
-rw-r--r--ui/uiComponent.cpp33
-rw-r--r--ui/uiComponent.h20
-rw-r--r--ui/uiComponentPlacer.cpp27
-rw-r--r--ui/uiComponentPlacer.h19
-rw-r--r--ui/window.cpp7
-rw-r--r--ui/window.h5
-rw-r--r--ui/windowContent.cpp4
-rw-r--r--ui/windowContent.h10
-rw-r--r--ui/worldOverlay.h3
217 files changed, 6722 insertions, 3551 deletions
diff --git a/.gitmodules b/.gitmodules
index 63a1a38..b5aa2c7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,6 @@
[submodule "thirdparty/imgui"]
path = thirdparty/imgui
url = https://github.com/ocornut/imgui
+[submodule "thirdparty/lunasvg"]
+ path = thirdparty/lunasvg
+ url = https://github.com/sammycage/lunasvg
diff --git a/Jamroot.jam b/Jamroot.jam
index b07dfdd..8589861 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -1,4 +1,3 @@
-using gcc ;
using pkg-config ;
import pkg-config ;
import testing ;
@@ -13,6 +12,7 @@ pkg-config.import mxml : : <link>shared ;
pkg-config.import assimp : : <link>shared ;
lib pthread : : <link>shared ;
lib OpenMeshCore : : <link>shared ;
+lib boost_program_options : : <link>shared ;
variant coverage : debug ;
project i-like-trains : requirements
@@ -47,15 +47,15 @@ project i-like-trains : requirements
<toolset>tidy:<define>TIDY
;
-exe iliketrains :
- application/main.cpp
- :
- <library>ilt
- ;
+exe iliketrains : application/main.cpp : <library>ilt ;
+exe resviewer : application/resviewer.cpp : <library>ilt <library>boost_program_options ;
explicit main ;
always main ;
run iliketrains : -- : [ sequence.insertion-sort [ glob-tree-ex res : *.* ] ] : : main ;
+explicit runresviewer ;
+always runresviewer ;
+run resviewer : -- : [ sequence.insertion-sort [ glob-tree-ex res : *.xml ] ] : : runresviewer ;
lib ilt :
[ glob-tree *.cpp *.?s *.t?s : application bin test thirdparty ]
@@ -93,7 +93,6 @@ lib ilt :
<library>thirdparty/<variant>release
<implicit-dependency>thirdparty
<library>sdl2
- <library>thirdparty//imguisdl2
<library>freetype2
<library>glib-2.0
<library>mxml
diff --git a/application/main.cpp b/application/main.cpp
index d58cf6d..a0c87c0 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -11,6 +11,7 @@
#include <game/network/link.h>
#include <game/network/rail.h>
#include <game/objective.h>
+#include <game/objectives/freeroam.h>
#include <game/objectives/goto.h>
#include <game/orders.h>
#include <game/scenary/foliage.h>
@@ -24,72 +25,94 @@
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp> // IWYU pragma: keep
#include <memory>
+#include <random>
+#include <ranges>
#include <special_members.h>
+#include <stream_support.h>
#include <ui/applicationBase.h>
+#include <ui/builders/freeExtend.h>
+#include <ui/builders/join.h>
+#include <ui/builders/straight.h>
#include <ui/gameMainWindow.h>
#include <ui/window.h>
-static const int DISPLAY_WIDTH = 1280;
-static const int DISPLAY_HEIGHT = 1024;
+constexpr ScreenAbsCoord DEFAULT_WINDOW_SIZE {1280, 1024};
class DummyMainApplication : public GameState, public MainApplication {
public:
int
run()
{
- geoData = std::make_shared<GeoData>(GeoData::loadFromAsciiGrid("test/fixtures/height/SD19.asc"));
+ windows.create<MainWindow>(DEFAULT_WINDOW_SIZE, "I Like Trains")->setContent<GameMainWindow>();
- windows.create<MainWindow>(DISPLAY_WIDTH, DISPLAY_HEIGHT)->setContent<GameMainWindow>();
-
- world.create<Terrain>(geoData);
- world.create<Water>(geoData);
+ terrain = world.create<Terrain>(GeoData::loadFromAsciiGrid("test/fixtures/height/SD19.asc"));
+ world.create<Water>(terrain);
assets = AssetFactory::loadAll("res");
{
auto rl = world.create<RailLinks>();
- const GlobalPosition3D j {-1120000, -1100000, 3000}, k {-1100000, -1000000, 15000},
- l {-1000000, -800000, 20000}, m {-900000, -600000, 30000}, n {-600000, -500000, 32000},
- o {-500000, -800000, 30000}, p {-600000, -900000, 25000}, q {-1025000, -1175000, 10000},
- r {-925000, -1075000, 10000}, s {-1100000, -500000, 15000}, t {-1100000, -450000, 15000},
- u {-1000000, -400000, 15000};
- auto l3 = rl->addLinksBetween(j, k);
- rl->addLinksBetween(k, l);
- rl->addLinksBetween(l, m);
- rl->addLinksBetween(m, n);
- rl->addLinksBetween(n, o);
- rl->addLinksBetween(o, p);
- // branch 1
- rl->addLinksBetween(p, q);
- rl->addLinksBetween(q, j);
- // branch 2
- rl->addLinksBetween(p, r);
- rl->addLinksBetween(r, j);
- // early loop
- rl->addLinksBetween(s, t);
- rl->addLinksBetween(l, s);
- rl->addLinksBetween(t, u);
- rl->addLinksBetween(u, m);
- const std::shared_ptr<Train> train = world.create<Train>(l3);
- auto b47 = std::dynamic_pointer_cast<RailVehicleClass>(assets.at("brush-47"));
+ const auto nodes = materializeRange(std::vector<GlobalPosition2D> {
+ {315103000, 491067000},
+ {315977000, 490777000},
+ {316312000, 490557000},
+ {316885000, 491330000},
+ {316510934, 491255979},
+ {316129566, 490893054},
+ {315825622, 490833929},
+ {315106182, 491073714},
+ }
+ | std::views::transform([this](const auto n) {
+ return terrain->positionAt(n);
+ }));
+ auto l3 = BuilderStraight {}.create(rl.get(), terrain.get(), *nodes.begin(), *++nodes.begin()).front();
+ for (const auto & [from, to] : nodes | std::views::drop(1) | std::views::pairwise) {
+ const auto links = BuilderFreeExtend {}.createExtend(rl.get(), terrain.get(), from, to);
+ }
+ for (const auto & [from, to] : std::initializer_list<std::pair<GlobalPosition2D, GlobalPosition2D>> {
+ {{315103000, 491067000}, {315003434, 491076253}},
+ {{315103000, 491067000}, {315016495, 491019224}},
+ {{315016495, 491019224}, {314955393, 490999023}},
+ }) {
+ const auto links = BuilderFreeExtend {}.createExtend(
+ rl.get(), terrain.get(), terrain->positionAt(from), terrain->positionAt(to));
+ }
+ for (const auto & [from, to] : std::initializer_list<std::pair<GlobalPosition2D, GlobalPosition2D>> {
+ {{315106182, 491073714}, {314955393, 490999023}},
+ }) {
+ auto p1 = rl->intersectRayNodes({from || 0, up})->pos;
+ auto p2 = rl->intersectRayNodes({to || 0, up})->pos;
+ const auto links = BuilderFreeExtend {}.createJoin(rl.get(), terrain.get(), p1, p2);
+ }
+
+ const std::shared_ptr<Train> train = world.create<Train>(l3, 800000);
+ auto b47 = assets.at("brush-47").dynamicCast<RailVehicleClass>();
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({-1100000, -450000, 15000}));
+ train->orders.clear();
+ train->orders.create<FreeRoam>(&train->orders);
train->currentActivity = train->orders.current()->createActivity();
- auto foliage = std::dynamic_pointer_cast<Foliage>(assets.at("Tree-01-1"));
+ std::random_device randomdev {};
+ std::uniform_real_distribution<Angle> rotationDistribution {0, two_pi};
+ std::uniform_int_distribution<GlobalDistance> positionOffsetDistribution {-1500, +1500};
+ std::uniform_int_distribution<int> treeDistribution {1, 3};
+ std::uniform_int_distribution<int> treeVariantDistribution {1, 4};
for (auto x = 311000000; x < 311830000; x += 5000) {
for (auto y = 491100000; y < 491130000; y += 5000) {
- world.create<Plant>(foliage, Location {geoData->positionAt({{x, y}})});
+ world.create<Plant>(assets.at(std::format("Tree-{:#02}-{}", treeDistribution(randomdev),
+ treeVariantDistribution(randomdev)))
+ .dynamicCast<Foliage>(),
+ Location {terrain->positionAt({{x + positionOffsetDistribution(randomdev),
+ y + positionOffsetDistribution(randomdev)}}),
+ {0, rotationDistribution(randomdev), 0}});
}
}
}
mainLoop();
- world.objects.clear();
+ world.clear();
return 0;
}
};
diff --git a/application/resviewer.cpp b/application/resviewer.cpp
new file mode 100644
index 0000000..c82017b
--- /dev/null
+++ b/application/resviewer.cpp
@@ -0,0 +1,227 @@
+#include <backends/imgui_impl_opengl3.h>
+#include <backends/imgui_impl_sdl2.h>
+#include <boost/program_options.hpp>
+#include <game/environment.h>
+#include <game/gamestate.h>
+#include <game/terrain.h>
+#include <gfx/gl/sceneProvider.h>
+#include <gfx/gl/sceneRenderer.h>
+#include <gfx/renderable.h>
+#include <location.h>
+#include <ui/applicationBase.h>
+#include <ui/mainApplication.h>
+#include <ui/mainWindow.h>
+
+constexpr ScreenAbsCoord DEFAULT_WINDOW_SIZE {800, 600};
+constexpr GlobalDistance TERRAIN_LIMIT = 1'000'000;
+constexpr GlobalDistance TERRAIN_HEIGHT = 10'000;
+constexpr RelativeDistance DEFAULT_CAMERA_DIST = 7'000;
+constexpr GlobalDistance DEFAULT_CAMERA_HEIGHT = 5'000;
+constexpr GlobalDistance DEFAULT_CAMERA_FOCUS = 3'000;
+constexpr GlobalDistance MAX_CAMERA_HEIGHT = 10'000;
+constexpr GlobalDistance MIN_CAMERA_DIST = 1'000;
+constexpr GlobalDistance MAX_CAMERA_DIST = 30'000;
+
+class ViewerContent : public WindowContent, SceneRenderer, SceneProvider {
+public:
+ ViewerContent(ScreenAbsCoord size, std::span<const std::filesystem::path> files) :
+ SceneRenderer {size, 0}, fileList(files)
+ {
+ camera.setPosition(calcCameraPosition());
+ camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus});
+ gameState->terrain = std::make_shared<Terrain>(
+ GeoData::createFlat({-TERRAIN_LIMIT, -TERRAIN_LIMIT}, {TERRAIN_LIMIT, TERRAIN_LIMIT}, TERRAIN_HEIGHT));
+ }
+
+private:
+ [[nodiscard]]
+ GlobalPosition3D
+ calcCameraPosition() const
+ {
+ return {sincos(cameraAngle) * cameraDistance, TERRAIN_HEIGHT + cameraHeight};
+ }
+
+ void
+ render() override
+ {
+ SceneRenderer::render(*this);
+ controls();
+ }
+
+ void
+ tick(TickDuration tick) override
+ {
+ if (autoRotate != 0) {
+ cameraAngle = normalize(cameraAngle + (autoRotate * tick.count()));
+ camera.setPosition(calcCameraPosition());
+ camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus});
+ }
+ if (selectedFile) {
+ try {
+ if (const auto curmTime = std::filesystem::last_write_time(*selectedFile); curmTime != fileTime) {
+ location.reset();
+ selectedAsset = nullptr;
+ gameState->assets = AssetFactory::loadXML(*selectedFile)->assets;
+ fileTime = curmTime;
+ if (!selectedAssetId.empty() && gameState->assets.contains(selectedAssetId)) {
+ auto asset = gameState->assets.at(selectedAssetId);
+ auto renderable = asset.getAs<const Renderable>();
+ if (renderable) {
+ location = asset->createAt(position);
+ selectedAsset = renderable;
+ }
+ }
+ }
+ }
+ catch (...) {
+ }
+ }
+ }
+
+ bool
+ handleInput(const SDL_Event & event) override
+ {
+ if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
+ SceneRenderer::resize({event.window.data1, event.window.data2});
+ }
+
+ return WindowContent::handleInput(event);
+ }
+
+ void
+ controls()
+ {
+ if (ImGui::Begin("Resource view")) {
+ ImGui::SetWindowSize({});
+ fileSelection();
+ assetSelection();
+ }
+ ImGui::End();
+
+ if (ImGui::Begin("Camera")) {
+ ImGui::SetWindowSize({});
+ if (std::max({ImGui::SliderFloat("Camera position", &cameraAngle, -pi, pi),
+ ImGui::SliderInt("Camera focus", &cameraFocus, 1, MAX_CAMERA_HEIGHT),
+ ImGui::SliderInt("Camera height", &cameraHeight, 1, MAX_CAMERA_HEIGHT),
+ ImGui::SliderFloat("Camera distance", &cameraDistance, MIN_CAMERA_DIST, MAX_CAMERA_DIST)})) {
+ camera.setPosition(calcCameraPosition());
+ camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus});
+ }
+ ImGui::SliderFloat("Auto rotate speed", &autoRotate, -1, 1);
+ }
+ ImGui::End();
+ }
+
+ void
+ fileSelection()
+ {
+ ImGui::BeginListBox("File");
+ for (const auto & file : fileList) {
+ if (ImGui::Selectable(file.c_str(), &file == selectedFile)) {
+ location.reset();
+ selectedAssetId.clear();
+ selectedAsset = nullptr;
+ gameState->assets = AssetFactory::loadXML(file)->assets;
+ fileTime = std::filesystem::last_write_time(file);
+ selectedFile = &file;
+ }
+ }
+ ImGui::EndListBox();
+ }
+
+ void
+ assetSelection()
+ {
+ if (!gameState->assets.empty()) {
+ ImGui::BeginListBox("Asset");
+ for (const auto & asset : gameState->assets) {
+ auto renderable = asset.second.getAs<const Renderable>();
+ if (renderable) {
+ if (ImGui::Selectable(asset.first.c_str(), renderable == selectedAsset)) {
+ selectedAssetId = asset.first;
+ selectedAsset = renderable;
+ location = asset.second->createAt(position);
+ }
+ }
+ }
+ ImGui::EndListBox();
+ }
+ }
+
+ void
+ content(const SceneShader & sceneShader, const Frustum & frustum) const override
+ {
+ gameState->terrain->render(sceneShader, frustum);
+ if (selectedAsset) {
+ selectedAsset->render(sceneShader, frustum);
+ }
+ }
+
+ void
+ lights(const SceneShader & sceneShader) const override
+ {
+ if (selectedAsset) {
+ selectedAsset->lights(sceneShader);
+ }
+ }
+
+ void
+ shadows(const ShadowMapper & mapper, const Frustum & frustum) const override
+ {
+ gameState->terrain->shadows(mapper, frustum);
+ if (selectedAsset) {
+ selectedAsset->shadows(mapper, frustum);
+ }
+ }
+
+ std::span<const std::filesystem::path> fileList;
+ std::filesystem::file_time_type fileTime;
+ const std::filesystem::path * selectedFile {};
+ std::string selectedAssetId;
+ const Renderable * selectedAsset {};
+ Location position {.pos = {0, 0, TERRAIN_HEIGHT}, .rot = {}};
+ std::any location;
+ Angle cameraAngle {0.F};
+ RelativeDistance cameraDistance {DEFAULT_CAMERA_DIST};
+ GlobalDistance cameraHeight {DEFAULT_CAMERA_HEIGHT};
+ GlobalDistance cameraFocus {DEFAULT_CAMERA_FOCUS};
+ float autoRotate {0.F};
+};
+
+int
+main(int argc, char ** argv)
+{
+ class ResViewer : GameState, MainApplication {
+ public:
+ void
+ run(std::span<const std::filesystem::path> fileList)
+ {
+ windows.create<MainWindow>(DEFAULT_WINDOW_SIZE, "ILT - Resource Viewer")
+ ->setContent<ViewerContent>(fileList);
+ mainLoop();
+ }
+ };
+
+ namespace po = boost::program_options;
+ po::options_description opts("ILT - Resource Viewer");
+ std::vector<std::filesystem::path> resources;
+ // clang-format off
+ opts.add_options()
+ ("resource,r", po::value(&resources)->composing(), "Resource file")
+ ("help,h", po::value<bool>()->default_value(false)->zero_tokens(), "Help")
+ ;
+ // clang-format on
+ po::positional_options_description pod;
+ pod.add("resource", -1);
+ po::variables_map varmap;
+ po::store(po::command_line_parser(argc, argv).options(opts).positional(pod).run(), varmap);
+ po::notify(varmap);
+
+ if (varmap.at("help").as<bool>()) {
+ std::cout << opts << '\n';
+ return EXIT_SUCCESS;
+ }
+
+ ResViewer {}.run(resources);
+ return EXIT_SUCCESS;
+}
diff --git a/assetFactory/asset.cpp b/assetFactory/asset.cpp
index e3f5feb..0254943 100644
--- a/assetFactory/asset.cpp
+++ b/assetFactory/asset.cpp
@@ -7,6 +7,12 @@ Asset::persist(Persistence::PersistenceStore & store)
return STORE_MEMBER(id) && STORE_MEMBER(name);
}
+std::any
+Asset::createAt(const Location &) const
+{
+ return {};
+}
+
Asset::TexturePtr
Asset::getTexture() const
{
diff --git a/assetFactory/asset.h b/assetFactory/asset.h
index 5bdd2f2..061a7c8 100644
--- a/assetFactory/asset.h
+++ b/assetFactory/asset.h
@@ -2,14 +2,22 @@
#include "factoryMesh.h"
#include "persistence.h"
+#include <any>
+#include <manyPtr.h>
#include <stdTypeDefs.h>
class TextureAtlas;
+class Renderable;
+class Location;
class Asset : public Persistence::Persistable, public StdTypeDefs<Asset> {
public:
+ using ManyPtr = ManySharedPtr<Asset, const Renderable>;
using TexturePtr = std::shared_ptr<TextureAtlas>;
+ /// Used only for the asset viewer
+ [[nodiscard]] virtual std::any createAt(const Location &) const;
+
std::string id;
std::string name;
diff --git a/assetFactory/assetFactory.cpp b/assetFactory/assetFactory.cpp
index 176e1f5..426fecc 100644
--- a/assetFactory/assetFactory.cpp
+++ b/assetFactory/assetFactory.cpp
@@ -5,6 +5,7 @@
#include "filesystem.h"
#include "gfx/image.h"
#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
#include "object.h"
#include "plane.h"
#include "saxParse-persistence.h"
@@ -146,7 +147,7 @@ bool
AssetFactory::persist(Persistence::PersistenceStore & store)
{
using MapObjects = Persistence::MapByMember<Shapes, std::shared_ptr<Object>>;
- using MapAssets = Persistence::MapByMember<Assets>;
+ using MapAssets = Persistence::MapByMember<Assets, Asset::Ptr>;
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)
diff --git a/assetFactory/assetFactory.h b/assetFactory/assetFactory.h
index 787f0a4..864e882 100644
--- a/assetFactory/assetFactory.h
+++ b/assetFactory/assetFactory.h
@@ -12,7 +12,7 @@ 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 Assets = std::map<std::string, Asset::ManyPtr, std::less<>>;
using AssImps = std::map<std::string, AssImp::Ptr, std::less<>>;
using TextureFragments = std::map<std::string, TextureFragment::Ptr, std::less<>>;
using Colour = RGB;
diff --git a/assetFactory/assimp.cpp b/assetFactory/assimp.cpp
index 1e11eda..e33783a 100644
--- a/assetFactory/assimp.cpp
+++ b/assetFactory/assimp.cpp
@@ -84,8 +84,8 @@ public:
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;
}
+ mesh.property(mesh.materialFaceProperty, fh) = material;
}
}
}
diff --git a/assetFactory/cylinder.cpp b/assetFactory/cylinder.cpp
index f41bfd4..432fb16 100644
--- a/assetFactory/cylinder.cpp
+++ b/assetFactory/cylinder.cpp
@@ -12,7 +12,7 @@ Cylinder::createMesh(ModelFactoryMesh & mesh, Scale3D lodf) const
// Generate 2D circumference points
std::vector<RelativePosition2D> circumference(P);
std::generate(circumference.begin(), circumference.end(), [a = 0.F, step]() mutable {
- return sincosf(a += step) * .5F;
+ return sincos(a += step) * .5F;
});
CreatedFaces surface;
diff --git a/assetFactory/factoryMesh.cpp b/assetFactory/factoryMesh.cpp
index bf4706e..eb9d525 100644
--- a/assetFactory/factoryMesh.cpp
+++ b/assetFactory/factoryMesh.cpp
@@ -25,11 +25,12 @@ FactoryMesh::createMesh() const
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 = useVertexNormals ? mesh.property(mesh.vertex_normals_pph(), vertex)
- : mesh.property(mesh.face_normals_pph(), face);
- Vertex outVertex {point * 1000.F, textureUV, normal, colour, material};
+ Vertex outVertex {.pos = mesh.point(vertex) * 1000.F,
+ .texCoord = mesh.texcoord2D(heh),
+ .normal = useVertexNormals ? mesh.property(mesh.vertex_normals_pph(), vertex)
+ : mesh.property(mesh.face_normals_pph(), face),
+ .colour = colour,
+ .material = 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));
diff --git a/assetFactory/modelFactoryMesh.cpp b/assetFactory/modelFactoryMesh.cpp
index 3d4b5f3..3660fb7 100644
--- a/assetFactory/modelFactoryMesh.cpp
+++ b/assetFactory/modelFactoryMesh.cpp
@@ -1,13 +1,5 @@
#include "modelFactoryMesh.h"
-ModelFactoryMesh::ModelFactoryMesh()
-{
- add_property(smoothFaceProperty);
- add_property(materialFaceProperty);
- add_property(nameFaceProperty);
- add_property(nameAdjFaceProperty);
-}
-
void
ModelFactoryMesh::configNamedFace(const std::string & name, OpenMesh::FaceHandle handle)
{
diff --git a/assetFactory/modelFactoryMesh.h b/assetFactory/modelFactoryMesh.h
index 299986e..6a18155 100644
--- a/assetFactory/modelFactoryMesh.h
+++ b/assetFactory/modelFactoryMesh.h
@@ -8,6 +8,7 @@
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <thirdparty/openmesh/glmcompat.h>
+#include <thirdparty/openmesh/helpers.h>
struct ModelFactoryTraits : public OpenMesh::DefaultTraits {
FaceAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status | OpenMesh::Attributes::Color);
@@ -21,13 +22,11 @@ struct ModelFactoryTraits : public OpenMesh::DefaultTraits {
};
struct ModelFactoryMesh : public OpenMesh::PolyMesh_ArrayKernelT<ModelFactoryTraits> {
- ModelFactoryMesh();
-
bool normalsProvidedProperty {};
- OpenMesh::FPropHandleT<bool> smoothFaceProperty;
- OpenMesh::FPropHandleT<GLuint> materialFaceProperty;
- OpenMesh::FPropHandleT<std::string> nameFaceProperty;
- OpenMesh::HPropHandleT<std::string> nameAdjFaceProperty;
+ const OpenMesh::Helpers::Property<bool, OpenMesh::FPropHandleT> smoothFaceProperty {this};
+ const OpenMesh::Helpers::Property<GLuint, OpenMesh::FPropHandleT> materialFaceProperty {this};
+ const OpenMesh::Helpers::Property<std::string, OpenMesh::FPropHandleT> nameFaceProperty {this};
+ const OpenMesh::Helpers::Property<std::string, OpenMesh::HPropHandleT> nameAdjFaceProperty {this};
template<typename... Vs>
std::pair<std::string, OpenMesh::FaceHandle>
diff --git a/assetFactory/mutation.cpp b/assetFactory/mutation.cpp
index 6695dff..2ab2a76 100644
--- a/assetFactory/mutation.cpp
+++ b/assetFactory/mutation.cpp
@@ -1,12 +1,11 @@
#include "mutation.h"
-#include <algorithm>
#include <glm/gtx/transform.hpp>
#include <maths.h>
Mutation::Matrix
Mutation::getMatrix() const
{
- return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr(rotation)
+ return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr<4>(rotation)
* glm::scale(glm::identity<Matrix>(), scale);
}
@@ -19,7 +18,7 @@ Mutation::getDeformationMatrix() const
Mutation::Matrix
Mutation::getLocationMatrix() const
{
- return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr(rotation);
+ return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr<4>(rotation);
}
bool
diff --git a/config/types.h b/config/types.h
index 081530d..06825b5 100644
--- a/config/types.h
+++ b/config/types.h
@@ -42,6 +42,8 @@ using Normal3D = Normal<3>;
using Rotation2D = Rotation<2>;
using Rotation3D = Rotation<3>;
using TextureRelCoord = glm::vec<2, float>;
+using ImageDimensions = glm::vec<2, GLsizei>;
+using TextureDimensions = glm::vec<3, GLsizei>;
using TextureRelRegion = glm::vec<4, float>;
using TextureAbsCoord = glm::vec<2, GLsizei>;
using TextureAbsRegion = glm::vec<4, GLsizei>;
diff --git a/game/environment.cpp b/game/environment.cpp
new file mode 100644
index 0000000..acb4f21
--- /dev/null
+++ b/game/environment.cpp
@@ -0,0 +1,93 @@
+#include "environment.h"
+#include "gfx/lightDirection.h"
+#include <chronology.h>
+#include <gfx/gl/sceneRenderer.h>
+
+Environment::Environment() : worldTime {"2024-01-01T12:00:00"_time_t} { }
+
+void
+Environment::tick(TickDuration)
+{
+ worldTime += 50;
+}
+
+void
+Environment::render(const SceneRenderer & renderer, const SceneProvider & scene) const
+{
+ constexpr RGB baseAmbient {0.1F}, baseDirectional {0.0F};
+ constexpr RGB relativeAmbient {0.3F, 0.3F, 0.4F}, relativeDirectional {0.6F, 0.6F, 0.5F};
+
+ const LightDirection sunPos = getSunPos({}, worldTime);
+ const auto ambient = baseAmbient + relativeAmbient * sunPos.ambient();
+ const auto directional = baseDirectional + relativeDirectional * sunPos.directional();
+
+ renderer.setAmbientLight(ambient);
+ renderer.setDirectionalLight(directional, sunPos, scene);
+}
+
+// Based on the C++ code published at https://www.psa.es/sdg/sunpos.htm
+// Linked from https://www.pveducation.org/pvcdrom/properties-of-sunlight/suns-position-to-high-accuracy
+Direction2D
+Environment::getSunPos(const Direction2D position, const time_t time)
+{
+ auto & longitude = position.x;
+ auto & latitude = position.y;
+ using std::acos;
+ using std::asin;
+ using std::atan2;
+ using std::cos;
+ using std::floor;
+ using std::sin;
+ using std::tan;
+ static const auto JD2451545 = "2000-01-01T12:00:00"_time_t;
+
+ // Calculate difference in days between the current Julian Day
+ // and JD 2451545.0, which is noon 1 January 2000 Universal Time
+ // Calculate time of the day in UT decimal hours
+ const auto dDecimalHours = static_cast<float>(time % 86400) / 3600.F;
+ const auto dElapsedJulianDays = static_cast<float>(time - JD2451545) / 86400.F;
+
+ // Calculate ecliptic coordinates (ecliptic longitude and obliquity of the
+ // ecliptic in radians but without limiting the angle to be less than 2*Pi
+ // (i.e., the result may be greater than 2*Pi)
+ const auto dOmega = 2.1429F - 0.0010394594F * dElapsedJulianDays;
+ const auto dMeanLongitude = 4.8950630F + 0.017202791698F * dElapsedJulianDays; // Radians
+ const auto dMeanAnomaly = 6.2400600F + 0.0172019699F * dElapsedJulianDays;
+ const auto dEclipticLongitude = dMeanLongitude + 0.03341607F * sin(dMeanAnomaly)
+ + 0.00034894F * sin(2 * dMeanAnomaly) - 0.0001134F - 0.0000203F * sin(dOmega);
+ const auto dEclipticObliquity = 0.4090928F - 6.2140e-9F * dElapsedJulianDays + 0.0000396F * cos(dOmega);
+
+ // Calculate celestial coordinates ( right ascension and declination ) in radians
+ // but without limiting the angle to be less than 2*Pi (i.e., the result may be
+ // greater than 2*Pi)
+ const auto dSin_EclipticLongitude = sin(dEclipticLongitude);
+ const auto dY = cos(dEclipticObliquity) * dSin_EclipticLongitude;
+ const auto dX = cos(dEclipticLongitude);
+ auto dRightAscension = atan2(dY, dX);
+ if (dRightAscension < 0) {
+ dRightAscension = dRightAscension + two_pi;
+ }
+ const auto dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude);
+
+ // Calculate local coordinates ( azimuth and zenith angle ) in degrees
+ const auto dGreenwichMeanSiderealTime = 6.6974243242F + 0.0657098283F * dElapsedJulianDays + dDecimalHours;
+ const auto dLocalMeanSiderealTime
+ = (dGreenwichMeanSiderealTime * 15.0F + (longitude / degreesToRads)) * degreesToRads;
+ const auto dHourAngle = dLocalMeanSiderealTime - dRightAscension;
+ const auto dLatitudeInRadians = latitude;
+ const auto dCos_Latitude = cos(dLatitudeInRadians);
+ const auto dSin_Latitude = sin(dLatitudeInRadians);
+ const auto dCos_HourAngle = cos(dHourAngle);
+ Direction2D udtSunCoordinates;
+ udtSunCoordinates.y
+ = (acos(dCos_Latitude * dCos_HourAngle * cos(dDeclination) + sin(dDeclination) * dSin_Latitude));
+ udtSunCoordinates.x = atan2(-sin(dHourAngle), tan(dDeclination) * dCos_Latitude - dSin_Latitude * dCos_HourAngle);
+ if (udtSunCoordinates.x < 0) {
+ udtSunCoordinates.x = udtSunCoordinates.x + two_pi;
+ }
+ // Parallax Correction
+ const auto dParallax = (earthMeanRadius / astronomicalUnit) * sin(udtSunCoordinates.y);
+ udtSunCoordinates.y = half_pi - (udtSunCoordinates.y + dParallax);
+
+ return udtSunCoordinates;
+}
diff --git a/game/environment.h b/game/environment.h
new file mode 100644
index 0000000..a6f3036
--- /dev/null
+++ b/game/environment.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "config/types.h"
+#include "worldobject.h"
+
+class SceneRenderer;
+class SceneProvider;
+
+class Environment : public WorldObject {
+public:
+ Environment();
+ void tick(TickDuration elapsed) override;
+ void render(const SceneRenderer &, const SceneProvider &) const;
+ static Direction2D getSunPos(const Direction2D position, const time_t time);
+
+private:
+ time_t worldTime;
+};
diff --git a/game/gamestate.cpp b/game/gamestate.cpp
index fcd4248..910e8a7 100644
--- a/game/gamestate.cpp
+++ b/game/gamestate.cpp
@@ -1,4 +1,5 @@
#include "gamestate.h"
+#include "environment.h"
#include <cassert>
GameState * gameState {nullptr};
@@ -7,6 +8,8 @@ GameState::GameState()
{
assert(!gameState);
gameState = this;
+
+ environment = world.create<Environment>();
}
GameState::~GameState()
diff --git a/game/gamestate.h b/game/gamestate.h
index f07f844..85cb5db 100644
--- a/game/gamestate.h
+++ b/game/gamestate.h
@@ -6,7 +6,9 @@
#include <special_members.h>
class WorldObject;
-class GeoData;
+class Terrain;
+class Environment;
+class Renderable;
class GameState {
public:
@@ -15,8 +17,9 @@ public:
NO_MOVE(GameState);
NO_COPY(GameState);
- Collection<WorldObject> world;
- std::shared_ptr<GeoData> geoData;
+ SharedCollection<WorldObject, Renderable> world;
+ std::shared_ptr<Terrain> terrain;
+ std::shared_ptr<Environment> environment;
AssetFactory::Assets assets;
};
diff --git a/game/geoData.cpp b/game/geoData.cpp
index 72aa056..b886efd 100644
--- a/game/geoData.cpp
+++ b/game/geoData.cpp
@@ -1,16 +1,15 @@
#include "geoData.h"
#include "collections.h"
#include "geometricPlane.h"
+#include "util.h"
#include <fstream>
#include <glm/gtx/intersect.hpp>
#include <maths.h>
#include <ranges>
#include <set>
-
-GeoData::GeoData()
-{
- add_property(surface);
-}
+#ifndef NDEBUG
+# include <stream_support.h>
+#endif
GeoData
GeoData::loadFromAsciiGrid(const std::filesystem::path & input)
@@ -36,16 +35,16 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input)
std::vector<VertexHandle> vertices;
vertices.reserve(ncols * nrows);
GeoData mesh;
- mesh.lowerExtent = {xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()};
- mesh.upperExtent = {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)),
- std::numeric_limits<GlobalDistance>::min()};
+ mesh.extents = {{xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()},
+ {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)),
+ std::numeric_limits<GlobalDistance>::min()}};
for (size_t row = 0; row < nrows; ++row) {
for (size_t col = 0; col < ncols; ++col) {
float heightf = 0;
f >> heightf;
const auto height = static_cast<GlobalDistance>(std::round(heightf * 1000.F));
- mesh.upperExtent.z = std::max(mesh.upperExtent.z, height);
- mesh.lowerExtent.z = std::min(mesh.lowerExtent.z, height);
+ mesh.extents.max.z = std::max(mesh.extents.max.z, height);
+ mesh.extents.min.z = std::min(mesh.extents.min.z, height);
vertices.push_back(mesh.add_vertex({xllcorner + (col * cellsize), yllcorner + (row * cellsize), height}));
}
}
@@ -66,7 +65,7 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input)
});
}
}
- mesh.update_vertex_normals_only();
+ mesh.updateAllVertexNormals();
return mesh;
};
@@ -79,8 +78,7 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan
assert((upper - lower) % GRID_SIZE == GlobalPosition2D {});
GeoData mesh;
- mesh.lowerExtent = {lower, h};
- mesh.upperExtent = {upper, h};
+ mesh.extents = {{lower, h}, {upper, h}};
std::vector<VertexHandle> vertices;
for (GlobalDistance row = lower.x; row <= upper.x; row += GRID_SIZE) {
@@ -105,123 +103,11 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan
}
}
- mesh.update_vertex_normals_only();
+ mesh.updateAllVertexNormals();
return mesh;
}
-OpenMesh::FaceHandle
-GeoData::findPoint(GlobalPosition2D p) const
-{
- return findPoint(p, *faces_sbegin());
-}
-
-GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh) :
- PointFace {p, mesh, *mesh->faces_sbegin()}
-{
-}
-
-GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh, FaceHandle start) :
- PointFace {p, mesh->findPoint(p, start)}
-{
-}
-
-GeoData::FaceHandle
-GeoData::PointFace::face(const GeoData * mesh, FaceHandle start) const
-{
- if (_face.is_valid()) {
- assert(mesh->triangleContainsPoint(point, _face));
- return _face;
- }
- else {
- return (_face = mesh->findPoint(point, start));
- }
-}
-
-GeoData::FaceHandle
-GeoData::PointFace::face(const GeoData * mesh) const
-{
- return face(mesh, *mesh->faces_sbegin());
-}
-
-namespace {
- template<template<typename> typename Op>
- [[nodiscard]] constexpr inline auto
- pointLineOp(const GlobalPosition2D p, const GlobalPosition2D e1, const GlobalPosition2D e2)
- {
- return Op {}(CalcDistance(e2.x - e1.x) * CalcDistance(p.y - e1.y),
- CalcDistance(e2.y - e1.y) * CalcDistance(p.x - e1.x));
- }
-
- constexpr auto pointLeftOfLine = pointLineOp<std::greater>;
- constexpr auto pointLeftOfOrOnLine = pointLineOp<std::greater_equal>;
-
- static_assert(pointLeftOfLine({1, 2}, {1, 1}, {2, 2}));
- static_assert(pointLeftOfLine({2, 1}, {2, 2}, {1, 1}));
- static_assert(pointLeftOfLine({2, 2}, {1, 2}, {2, 1}));
- static_assert(pointLeftOfLine({1, 1}, {2, 1}, {1, 2}));
- static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490000000}, {310050000, 490050000}));
- static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310050000, 490050000}, {310000000, 490050000}));
- static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490050000}, {310000000, 490000000}));
-
- [[nodiscard]] constexpr inline bool
- linesCross(
- const GlobalPosition2D a1, const GlobalPosition2D a2, const GlobalPosition2D b1, const GlobalPosition2D b2)
- {
- return (pointLeftOfLine(a2, b1, b2) == pointLeftOfLine(a1, b2, b1))
- && (pointLeftOfLine(b1, a1, a2) == pointLeftOfLine(b2, a2, a1));
- }
-
- static_assert(linesCross({1, 1}, {2, 2}, {1, 2}, {2, 1}));
- static_assert(linesCross({2, 2}, {1, 1}, {1, 2}, {2, 1}));
-
- [[nodiscard]] constexpr inline bool
- linesCrossLtR(
- const GlobalPosition2D a1, const GlobalPosition2D a2, const GlobalPosition2D b1, const GlobalPosition2D b2)
- {
- return pointLeftOfLine(a2, b1, b2) && pointLeftOfLine(a1, b2, b1) && pointLeftOfLine(b1, a1, a2)
- && pointLeftOfLine(b2, a2, a1);
- }
-
- static_assert(linesCrossLtR({1, 1}, {2, 2}, {1, 2}, {2, 1}));
- static_assert(!linesCrossLtR({2, 2}, {1, 1}, {1, 2}, {2, 1}));
-
- constexpr GlobalPosition3D
- positionOnTriangle(const GlobalPosition2D point, const GeoData::Triangle<3> & t)
- {
- const CalcPosition3D a = t[1] - t[0], b = t[2] - t[0];
- const auto n = crossProduct(a, b);
- return {point, ((n.x * t[0].x) + (n.y * t[0].y) + (n.z * t[0].z) - (n.x * point.x) - (n.y * point.y)) / n.z};
- }
-
- static_assert(positionOnTriangle({7, -2}, {{1, 2, 3}, {1, 0, 1}, {-2, 1, 0}}) == GlobalPosition3D {7, -2, 3});
-}
-
-OpenMesh::FaceHandle
-GeoData::findPoint(GlobalPosition2D p, OpenMesh::FaceHandle f) const
-{
- while (f.is_valid() && !triangleContainsPoint(p, triangle<2>(f))) {
- for (auto next = cfh_iter(f); next.is_valid(); ++next) {
- f = opposite_face_handle(*next);
- if (f.is_valid()) {
- const auto e1 = point(to_vertex_handle(*next));
- const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next)));
- if (pointLeftOfLine(p, e1, e2)) {
- break;
- }
- }
- f.reset();
- }
- }
- return f;
-}
-
-GlobalPosition3D
-GeoData::positionAt(const PointFace & p) const
-{
- return positionOnTriangle(p.point, triangle<3>(p.face(this)));
-}
-
[[nodiscard]] GeoData::IntersectionResult
GeoData::intersectRay(const Ray<GlobalPosition3D> & ray) const
{
@@ -233,13 +119,11 @@ GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const
{
GeoData::IntersectionResult out;
walkUntil(PointFace {ray.start, face},
- ray.start.xy() + (ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())),
- [&out, &ray, this](FaceHandle face) {
- BaryPosition bari {};
- RelativeDistance dist {};
- const auto t = triangle<3>(face);
- if (ray.intersectTriangle(t.x, t.y, t.z, bari, dist)) {
- out.emplace(t * bari, face);
+ ray.start.xy() + (ray.direction.xy() * ::difference(extents.max.xy(), extents.min.xy())),
+ [&out, &ray, this](const auto & step) {
+ const auto t = triangle<3>(step.current);
+ if (const auto inter = ray.intersectTriangle(t.x, t.y, t.z)) {
+ out.emplace(t * inter->bary, step.current);
return true;
}
return false;
@@ -248,7 +132,7 @@ GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const
}
void
-GeoData::walk(const PointFace & from, const GlobalPosition2D to, const std::function<void(FaceHandle)> & op) const
+GeoData::walk(const PointFace & from, const GlobalPosition2D to, Consumer<WalkStep> op) const
{
walkUntil(from, to, [&op](const auto & fh) {
op(fh);
@@ -257,41 +141,86 @@ GeoData::walk(const PointFace & from, const GlobalPosition2D to, const std::func
}
void
-GeoData::walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function<bool(FaceHandle)> & op) const
+GeoData::walkUntil(const PointFace & from, const GlobalPosition2D to, Tester<WalkStep> op) const
+{
+ WalkStep step {
+ .current = from.face(this),
+ };
+ if (!step.current.is_valid()) {
+ const auto entryEdge = findEntry(from.point, to);
+ if (!entryEdge.is_valid()) {
+ return;
+ }
+ step.current = opposite_face_handle(entryEdge);
+ }
+ while (step.current.is_valid() && !op(step)) {
+ step.previous = step.current;
+ for (const auto next : fh_range(step.current)) {
+ step.current = opposite_face_handle(next);
+ if (step.current.is_valid() && step.current != step.previous) {
+ const auto nextPoints = points(toVertexHandles(next));
+ if (linesCrossLtR(from.point, to, nextPoints.second, nextPoints.first)) {
+ step.exitHalfedge = next;
+ step.exitPosition
+ = linesIntersectAt(from.point.xy(), to.xy(), nextPoints.second.xy(), nextPoints.first.xy())
+ .value();
+ break;
+ }
+ }
+ step.current.reset();
+ }
+ }
+}
+
+void
+GeoData::walk(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Consumer<WalkStepCurve> op) const
+{
+ walkUntil(from, to, centre, [&op](const auto & fh) {
+ op(fh);
+ return false;
+ });
+}
+
+void
+GeoData::walkUntil(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Tester<WalkStepCurve> op) const
{
- auto f = from.face(this);
- if (!f.is_valid()) {
+ WalkStepCurve step {WalkStep {.current = from.face(this)}};
+ if (!step.current.is_valid()) {
const auto entryEdge = findEntry(from.point, to);
if (!entryEdge.is_valid()) {
return;
}
- f = opposite_face_handle(entryEdge);
+ step.current = opposite_face_handle(entryEdge);
}
- FaceHandle previousFace;
- while (f.is_valid() && !op(f)) {
- for (auto next = cfh_iter(f); next.is_valid(); ++next) {
- f = opposite_face_handle(*next);
- if (f.is_valid() && f != previousFace) {
- const auto e1 = point(to_vertex_handle(*next));
- const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next)));
- if (linesCrossLtR(from.point, to, e1, e2)) {
- previousFace = f;
+ ArcSegment arc {centre, from.point, to};
+ step.angle = arc.first;
+ while (step.current.is_valid() && !op(step)) {
+ step.previous = step.current;
+ for (const auto next : fh_range(step.current)) {
+ step.current = opposite_face_handle(next);
+ if (step.current.is_valid()) {
+ const auto e1 = point(to_vertex_handle(next));
+ const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(next)));
+ if (const auto intersect = arc.crossesLineAt(e1, e2)) {
+ step.exitHalfedge = next;
+ arc.ep0 = step.exitPosition = intersect.value().first;
+ arc.first = std::nextafter(step.angle = intersect.value().second, INFINITY);
break;
}
}
- f.reset();
+ step.current.reset();
}
}
}
void
-GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op) const
+GeoData::boundaryWalk(Consumer<HalfedgeHandle> op) const
{
boundaryWalk(op, findBoundaryStart());
}
void
-GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op, HalfedgeHandle start) const
+GeoData::boundaryWalk(Consumer<HalfedgeHandle> op, HalfedgeHandle start) const
{
assert(is_boundary(start));
boundaryWalkUntil(
@@ -303,13 +232,13 @@ GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op, HalfedgeHa
}
void
-GeoData::boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> & op) const
+GeoData::boundaryWalkUntil(Tester<HalfedgeHandle> op) const
{
boundaryWalkUntil(op, findBoundaryStart());
}
void
-GeoData::boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> & op, HalfedgeHandle start) const
+GeoData::boundaryWalkUntil(Tester<HalfedgeHandle> op, HalfedgeHandle start) const
{
assert(is_boundary(start));
if (!op(start)) {
@@ -337,339 +266,337 @@ GeoData::findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const
return entry;
}
-bool
-GeoData::triangleContainsPoint(const GlobalPosition2D p, const Triangle<2> & t)
-{
- return pointLeftOfOrOnLine(p, t[0], t[1]) && pointLeftOfOrOnLine(p, t[1], t[2])
- && pointLeftOfOrOnLine(p, t[2], t[0]);
-}
-
-bool
-GeoData::triangleContainsPoint(const GlobalPosition2D p, FaceHandle face) const
-{
- return triangleContainsPoint(p, triangle<2>(face));
-}
-
-GeoData::HalfedgeHandle
-GeoData::findBoundaryStart() const
-{
- return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) {
- return is_boundary(heh);
- });
-}
-
-[[nodiscard]] RelativePosition3D
-GeoData::difference(const HalfedgeHandle heh) const
-{
- return point(to_vertex_handle(heh)) - point(from_vertex_handle(heh));
-}
-
-[[nodiscard]] RelativeDistance
-GeoData::length(const HalfedgeHandle heh) const
-{
- return glm::length(difference(heh));
-}
-
-[[nodiscard]] GlobalPosition3D
-GeoData::centre(const HalfedgeHandle heh) const
-{
- return point(from_vertex_handle(heh)) + (difference(heh) / 2.F);
-}
-
void
-GeoData::update_vertex_normals_only()
+GeoData::updateAllVertexNormals()
{
- update_vertex_normals_only(vertices_sbegin());
+ updateAllVertexNormals(vertices());
}
+template<std::ranges::range R>
void
-GeoData::update_vertex_normals_only(VertexIter start)
+GeoData::updateAllVertexNormals(const R & range)
{
- std::for_each(start, vertices_end(), [this](const auto vh) {
- if (normal(vh) == Normal3D {}) {
- Normal3D n;
- calc_vertex_normal_correct(vh, n);
- this->set_normal(vh, glm::normalize(n));
- }
+ std::ranges::for_each(range, [this](const auto vertex) {
+ updateVertexNormal(vertex);
});
}
-bool
-GeoData::triangleOverlapsTriangle(const Triangle<2> & a, const Triangle<2> & b)
-{
- return triangleContainsPoint(a.x, b) || triangleContainsPoint(a.y, b) || triangleContainsPoint(a.z, b)
- || triangleContainsPoint(b.x, a) || triangleContainsPoint(b.y, a) || triangleContainsPoint(b.z, a)
- || linesCross(a.x, a.y, b.x, b.y) || linesCross(a.x, a.y, b.y, b.z) || linesCross(a.x, a.y, b.z, b.x)
- || linesCross(a.y, a.z, b.x, b.y) || linesCross(a.y, a.z, b.y, b.z) || linesCross(a.y, a.z, b.z, b.x)
- || linesCross(a.z, a.x, b.x, b.y) || linesCross(a.z, a.x, b.y, b.z) || linesCross(a.z, a.x, b.z, b.x);
-}
-
-bool
-GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b)
+void
+GeoData::updateVertexNormal(VertexHandle vertex)
{
- return triangleContainsPoint(a.x, b) && triangleContainsPoint(a.y, b) && triangleContainsPoint(a.z, b);
+ Normal3D normal {};
+ { // Lifted from calc_vertex_normal_correct in PolyMeshT_impl.hh but doesn't use Scalar to normalise
+ ConstVertexIHalfedgeIter cvih_it = this->cvih_iter(vertex);
+ if (!cvih_it.is_valid()) { // don't crash on isolated vertices
+ return;
+ }
+ Normal in_he_vec;
+ calc_edge_vector(*cvih_it, in_he_vec);
+ for (; cvih_it.is_valid(); ++cvih_it) { // calculates the sector normal defined by cvih_it and adds it to normal
+ if (this->is_boundary(*cvih_it)) {
+ continue;
+ }
+ HalfedgeHandle out_heh(this->next_halfedge_handle(*cvih_it));
+ Normal out_he_vec;
+ calc_edge_vector(out_heh, out_he_vec);
+ normal += cross(in_he_vec, out_he_vec); // sector area is taken into account
+ in_he_vec = out_he_vec;
+ in_he_vec *= -1; // change the orientation
+ }
+ } // End lift
+ set_normal(vertex, glm::normalize(normal));
}
-void
-GeoData::split(FaceHandle _fh)
+OpenMesh::VertexHandle
+GeoData::setPoint(GlobalPosition3D tsPoint, const RelativeDistance nearNodeTolerance)
{
- // Collect halfedges of face
- const HalfedgeHandle he0 = halfedge_handle(_fh);
- const HalfedgeHandle he1 = next_halfedge_handle(he0);
- const HalfedgeHandle he2 = next_halfedge_handle(he1);
-
- const EdgeHandle eh0 = edge_handle(he0);
- const EdgeHandle eh1 = edge_handle(he1);
- const EdgeHandle eh2 = edge_handle(he2);
-
- // Collect points of face
- const VertexHandle p0 = to_vertex_handle(he0);
- const VertexHandle p1 = to_vertex_handle(he1);
- const VertexHandle p2 = to_vertex_handle(he2);
-
- // Calculate midpoint coordinates
- const Point new0 = centre(he0);
- const Point new1 = centre(he1);
- const Point new2 = centre(he2);
-
- // Add vertices at midpoint coordinates
- const VertexHandle v0 = add_vertex(new0);
- const VertexHandle v1 = add_vertex(new1);
- const VertexHandle v2 = add_vertex(new2);
-
- const bool split0 = !is_boundary(eh0);
- const bool split1 = !is_boundary(eh1);
- const bool split2 = !is_boundary(eh2);
-
- // delete original face
- delete_face(_fh, false);
-
- // split boundary edges of deleted face ( if not boundary )
- if (split0) {
- split(eh0, v0);
- }
-
- if (split1) {
- split(eh1, v1);
+ const auto face = findPoint(tsPoint);
+ const auto distFromTsPoint = vertexDistanceFunction<2>(tsPoint);
+ // Check vertices
+ if (const auto nearest = std::ranges::min(fv_range(face) | std::views::transform(distFromTsPoint), {}, GetSecond);
+ nearest.second < nearNodeTolerance) {
+ point(nearest.first).z = tsPoint.z;
+ return nearest.first;
}
-
- if (split2) {
- split(eh2, v2);
+ // Check edges
+ if (const auto nearest = std::ranges::min(fh_range(face) | std::views::transform(distFromTsPoint), {}, GetSecond);
+ nearest.second < nearNodeTolerance) {
+ const auto from = point(from_vertex_handle(nearest.first)).xy();
+ const auto to = point(to_vertex_handle(nearest.first)).xy();
+ const auto v = vector_normal(from - to);
+ const auto inter = linesIntersectAt(from, to, tsPoint.xy(), tsPoint.xy() + v);
+ if (!inter) [[unlikely]] {
+ throw std::runtime_error("Perpendicular lines do not cross");
+ }
+ return split_copy(edge_handle(nearest.first), *inter || tsPoint.z);
}
+ // Nothing close, split face
+ return split_copy(face, tsPoint);
+};
- // Retriangulate
- add_face(v0, p0, v1);
- add_face(p2, v0, v2);
- add_face(v2, v1, p1);
- add_face(v2, v0, v1);
-}
-
-void
-GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface & newFaceSurface)
+std::set<GeoData::FaceHandle>
+GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts & opts)
{
- static const RelativeDistance MAX_SLOPE = 1.5F;
- static const RelativeDistance MIN_ARC = 0.01F;
-
if (triangleStrip.size() < 3) {
- return;
+ return {};
+ }
+ for (const auto & vertex : triangleStrip) {
+ extents += vertex;
}
- const auto initialVertexCount = static_cast<unsigned int>(n_vertices());
+ class SetHeights {
+ public:
+ SetHeights(GeoData * geoData, const std::span<const GlobalPosition3D> triangleStrip) :
+ geoData(geoData), triangleStrip {triangleStrip},
+ strip {materializeRange(triangleStrip | triangleTriples | std::views::transform([](const auto & newVert) {
+ return std::make_from_tuple<Triangle<3>>(newVert);
+ }))}
+ {
+ }
- // Create new vertices
- std::vector<VertexHandle> newVerts;
- newVerts.reserve(newVerts.size());
- std::transform(triangleStrip.begin(), triangleStrip.end(), std::back_inserter(newVerts), [this](const auto tsVert) {
- return add_vertex(tsVert);
- });
- // Create new faces
- const auto initialFaceCount = static_cast<int>(n_faces());
- std::for_each(strip_begin(newVerts), strip_end(newVerts), [this](const auto & newVert) {
- const auto [a, b, c] = newVert;
- add_face(a, b, c);
- });
- for (auto fhi = FaceIter {*this, FaceHandle {initialFaceCount}, true}; fhi != faces_end(); fhi++) {
- static constexpr auto MAX_FACE_AREA = 100'000'000.F;
- const auto fh = *fhi;
- if (triangle<3>(fh).area() > MAX_FACE_AREA) {
- split(fh);
+ std::vector<VertexHandle>
+ createVerticesForStrip(RelativeDistance nearNodeTolerance)
+ {
+ // New vertices for each vertex in triangleStrip
+ const auto newVerts
+ = materializeRange(triangleStrip | std::views::transform([this, nearNodeTolerance](auto v) {
+ return geoData->setPoint(v, nearNodeTolerance);
+ }));
+ std::ranges::copy(newVerts, std::inserter(newOrChangedVerts, newOrChangedVerts.end()));
+#ifndef NDEBUG
+ geoData->sanityCheck();
+#endif
+ return newVerts;
}
- }
- std::vector<FaceHandle> newFaces;
- std::copy_if(FaceIter {*this, FaceHandle {initialFaceCount}, true}, faces_end(), std::back_inserter(newFaces),
- [this](FaceHandle fh) {
- return !this->status(fh).deleted();
- });
- // Extrude corners
- struct Extrusion {
- VertexHandle boundaryVertex, extrusionVertex;
- Direction3D lowerLimit, upperLimit;
- };
+ const Triangle<3> *
+ getTriangle(const GlobalPosition2D point) const
+ {
+ if (const auto t = std::ranges::find_if(strip,
+ [point](const auto & triangle) {
+ return triangle.containsPoint(point);
+ });
+ t != strip.end()) {
+ return &*t;
+ }
+ return nullptr;
+ }
- std::vector<Extrusion> extrusionExtents;
- boundaryWalk(
- [this, &extrusionExtents](const auto boundaryHeh) {
- const auto prevBoundaryHeh = prev_halfedge_handle(boundaryHeh);
- const auto prevBoundaryVertex = from_vertex_handle(prevBoundaryHeh);
- const auto boundaryVertex = from_vertex_handle(boundaryHeh);
- const auto nextBoundaryVertex = to_vertex_handle(boundaryHeh);
- const auto p0 = point(prevBoundaryVertex);
- const auto p1 = point(boundaryVertex);
- const auto p2 = point(nextBoundaryVertex);
- const auto e0 = glm::normalize(vector_normal(RelativePosition2D(p1 - p0)));
- const auto e1 = glm::normalize(vector_normal(RelativePosition2D(p2 - p1)));
-
- const auto addExtrusionFor = [this, &extrusionExtents, boundaryVertex, p1](Direction2D direction) {
- const auto doExtrusion = [this](VertexHandle & extrusionVertex, Direction2D direction,
- GlobalPosition3D boundaryVertex, RelativeDistance vert) {
- const auto extrusionDir = glm::normalize(direction || vert);
-
- if (!extrusionVertex.is_valid()) {
- if (const auto intersect = intersectRay({boundaryVertex, extrusionDir})) {
- auto splitVertex = split(intersect->second, intersect->first);
- extrusionVertex = splitVertex;
- }
- else if (const auto intersect
- = intersectRay({boundaryVertex + GlobalPosition3D {1, 1, 0}, extrusionDir})) {
- auto splitVertex = split(intersect->second, intersect->first);
- extrusionVertex = splitVertex;
- }
- else if (const auto intersect
- = intersectRay({boundaryVertex + GlobalPosition3D {1, 0, 0}, extrusionDir})) {
- auto splitVertex = split(intersect->second, intersect->first);
- extrusionVertex = splitVertex;
+ void
+ doBoundaryPart(VertexHandle start, VertexHandle end, const Triangle<3> & triangle,
+ const RelativeDistance nearNodeTolerance)
+ {
+ boundaryTriangles.emplace(start, &triangle);
+ const auto endPoint = geoData->point(end);
+ while (!std::ranges::contains(geoData->vv_range(start), end)) {
+ const auto startPoint = geoData->point(start);
+ const auto distanceToEndPoint = distance(startPoint.xy(), endPoint.xy());
+ if (std::ranges::any_of(geoData->vv_range(start), [&](const auto & adjVertex) {
+ const auto adjPoint = geoData->point(adjVertex);
+ if (distance(adjPoint.xy(), endPoint.xy()) < distanceToEndPoint
+ && (Triangle<2> {startPoint, endPoint, adjPoint}.area()
+ / distance(startPoint.xy(), endPoint.xy()))
+ < nearNodeTolerance) {
+ start = adjVertex;
+ newOrChangedVerts.emplace(start);
+ boundaryTriangles.emplace(start, &triangle);
+ geoData->point(start).z = triangle.positionOnPlane(adjPoint).z;
+ return true;
+ }
+ return false;
+ })) {
+ continue;
+ }
+ if (std::ranges::any_of(geoData->voh_range(start), [&](const auto & outHalf) {
+ const auto next = geoData->next_halfedge_handle(outHalf);
+ const auto nexts
+ = std::array {geoData->from_vertex_handle(next), geoData->to_vertex_handle(next)};
+ const auto nextPoints = nexts | std::views::transform([this](const auto v) {
+ return std::make_pair(v, geoData->point(v));
+ });
+ if (linesCross(startPoint, endPoint, nextPoints.front().second, nextPoints.back().second)) {
+ if (const auto intersection = linesIntersectAt(startPoint.xy(), endPoint.xy(),
+ nextPoints.front().second.xy(), nextPoints.back().second.xy())) {
+ if (const auto nextEdge = geoData->shouldFlip(next, startPoint)) {
+ geoData->flip(*nextEdge);
+ return true;
+ }
+ start = geoData->split_copy(
+ geoData->edge_handle(next), triangle.positionOnPlane(*intersection));
+ newOrChangedVerts.emplace(start);
+ boundaryTriangles.emplace(start, &triangle);
+ return true;
}
+ throw std::runtime_error("Crossing lines don't intersect");
}
+ return false;
+ })) {
+ continue;
+ }
+#ifndef NDEBUG
+ CLOG(start);
+ CLOG(startPoint);
+ CLOG(end);
+ CLOG(endPoint);
+ for (const auto v : geoData->vv_range(start)) {
+ CLOG(geoData->point(v));
+ }
+ geoData->sanityCheck();
+#endif
+ throw std::runtime_error(
+ std::format("Could not navigate to ({}, {}, {})", endPoint.x, endPoint.y, endPoint.z));
+ }
+ }
+
+ void
+ cutBoundary(const std::vector<VertexHandle> & newVerts, RelativeDistance nearNodeTolerance)
+ {
+ // Cut along each edge of triangleStrip AB, AC, BC, BD, CD, CE etc
+ std::ranges::for_each(newVerts | std::views::adjacent<3>,
+ [this, nearNodeTolerance, triangle = strip.begin()](const auto & verts) mutable {
+ const auto & [a, _, c] = verts;
+ doBoundaryPart(a, c, *triangle, nearNodeTolerance);
+ triangle++;
+ });
+ doBoundaryPart(*++newVerts.begin(), newVerts.front(), strip.front(), nearNodeTolerance);
+ doBoundaryPart(*++newVerts.rbegin(), newVerts.back(), strip.back(), nearNodeTolerance);
+ }
+
+ using HeightSetTodo = std::multimap<VertexHandle, VertexHandle>;
- return extrusionDir;
- };
-
- VertexHandle extrusionVertex;
- extrusionExtents.emplace_back(boundaryVertex, extrusionVertex,
- doExtrusion(extrusionVertex, direction, p1, -MAX_SLOPE),
- doExtrusion(extrusionVertex, direction, p1, MAX_SLOPE));
- assert(extrusionVertex.is_valid());
- };
- if (const Arc arc {e0, e1}; arc.length() < MIN_ARC) {
- addExtrusionFor(normalize(e0 + e1) / cosf(arc.length() / 2.F));
+ HeightSetTodo
+ setSurfaceHeights(const std::span<const VertexHandle> newVerts)
+ {
+ HeightSetTodo out;
+ auto setSurfaceVertexHeight = [this, &out](const auto & setSurfaceVertexHeight, const VertexHandle vertex,
+ const VertexHandle previousVertex) -> void {
+ if (surfaceVerts.contains(vertex)) {
+ return;
}
- else if (arc.length() < pi) {
- // Previous half edge end to current half end start arc tangents
- const auto limit = std::ceil(arc.length() * 5.F / pi);
- const auto inc = arc.length() / limit;
- for (float step = 0; step <= limit; step += 1.F) {
- addExtrusionFor(sincosf(arc.first + (step * inc)));
+ auto & point = geoData->point(vertex);
+ auto triangle = getTriangle(point);
+ if (!triangle) {
+ if (const auto boundaryVertex = boundaryTriangles.find(vertex);
+ boundaryVertex != boundaryTriangles.end()) {
+ triangle = boundaryVertex->second;
}
}
- else {
- // Single tangent bisecting the difference
- addExtrusionFor(normalize(e0 + e1) / sinf((arc.length() - pi) / 2.F));
+ if (triangle) { // point within the new strip, adjust vertically by triangle
+ point.z = triangle->positionOnPlane(point).z;
+ newOrChangedVerts.emplace(vertex);
+ surfaceVerts.emplace(vertex);
+ for (const auto nextVertex : geoData->vv_range(vertex)) {
+ setSurfaceVertexHeight(setSurfaceVertexHeight, nextVertex, vertex);
+ }
}
- },
- *voh_begin(newVerts.front()));
-
- // Cut existing terrain
- extrusionExtents.emplace_back(extrusionExtents.front()); // Circular next
- std::vector<std::vector<VertexHandle>> boundaryFaces;
- for (const auto & [first, second] : extrusionExtents | std::views::adjacent<2>) {
- const auto p0 = point(first.boundaryVertex);
- const auto p1 = point(second.boundaryVertex);
- const auto bdir = RelativePosition3D(p1 - p0);
- const auto make_plane = [p0](auto y, auto z) {
- return GeometricPlaneT<GlobalPosition3D> {p0, crossProduct(y, z)};
- };
- const auto planes = ((first.boundaryVertex == second.boundaryVertex)
- ? std::array {make_plane(second.lowerLimit, first.lowerLimit),
- make_plane(second.upperLimit, first.upperLimit),
- }
- : std::array {
- make_plane(bdir, second.lowerLimit),
- make_plane(bdir, second.upperLimit),
- });
- assert(planes.front().normal.z > 0.F);
- assert(planes.back().normal.z > 0.F);
-
- auto & out = boundaryFaces.emplace_back();
- out.emplace_back(first.boundaryVertex);
- out.emplace_back(first.extrusionVertex);
- for (auto currentVertex = first.extrusionVertex;
- !find_halfedge(currentVertex, second.extrusionVertex).is_valid();) {
- [[maybe_unused]] const auto n
- = std::any_of(voh_begin(currentVertex), voh_end(currentVertex), [&](const auto currentVertexOut) {
- const auto next = next_halfedge_handle(currentVertexOut);
- const auto nextVertex = to_vertex_handle(next);
- const auto startVertex = from_vertex_handle(next);
- if (nextVertex == *++out.rbegin()) {
- // This half edge goes back to the previous vertex
- return false;
- }
- const auto edge = edge_handle(next);
- const auto ep0 = point(startVertex);
- const auto ep1 = point(nextVertex);
- if (planes.front().getRelation(ep1) == GeometricPlane::PlaneRelation::Below
- || planes.back().getRelation(ep1) == GeometricPlane::PlaneRelation::Above) {
- return false;
- }
- const auto diff = RelativePosition3D(ep1 - ep0);
- const auto length = glm::length(diff);
- const auto dir = diff / length;
- const Ray r {ep1, -dir};
- const auto dists = planes * [r](const auto & plane) {
- RelativeDistance dist {};
- if (r.intersectPlane(plane.origin, plane.normal, dist)) {
- return dist;
- }
- return INFINITY;
- };
- const auto dist = *std::min_element(dists.begin(), dists.end());
- const auto splitPos = ep1 - (dir * dist);
- if (dist <= length) {
- currentVertex = split(edge, splitPos);
- out.emplace_back(currentVertex);
- return true;
- }
- return false;
- });
- assert(n);
+ else if (previousVertex.is_valid()) {
+ out.emplace(vertex, previousVertex);
+ }
+ };
+ for (const auto vertex : newVerts) {
+ setSurfaceVertexHeight(setSurfaceVertexHeight, vertex, VertexHandle {});
+ }
+ return out;
+ }
+
+ void
+ setHalfedgeToHeight(HeightSetTodo & nexts, HeightSetTodo::const_iterator verticesBegin,
+ HeightSetTodo::const_iterator verticesEnd, const RelativeDistance maxSlope)
+ {
+ const auto vertex = verticesBegin->first;
+ auto & point = geoData->point(vertex);
+ const auto minMaxHeight = std::accumulate(verticesBegin, verticesEnd,
+ std::pair<GlobalDistance, GlobalDistance> {
+ std::numeric_limits<GlobalDistance>::min(),
+ std::numeric_limits<GlobalDistance>::max(),
+ },
+ [this, maxSlope, point](auto limit, auto previousVertexItr) {
+ const auto & fromPoint = geoData->point(previousVertexItr.second);
+ const auto maxOffset = static_cast<GlobalDistance>(
+ std::round(maxSlope * ::distance<2>(fromPoint.xy(), point.xy())));
+ limit.first = std::max(limit.first, fromPoint.z - maxOffset);
+ limit.second = std::min(limit.second, fromPoint.z + maxOffset);
+ return limit;
+ });
+
+ const auto newHeight = std::clamp(point.z, minMaxHeight.first, minMaxHeight.second);
+ if (newHeight != point.z) {
+ point.z = newHeight;
+ newOrChangedVerts.emplace(vertex);
+ for (const auto nextVertex : geoData->vv_range(vertex)) {
+ if (!std::ranges::contains(verticesBegin, verticesEnd, nextVertex, GetSecond)
+ && !boundaryTriangles.contains(nextVertex)) {
+ nexts.emplace(nextVertex, vertex);
+ }
+ }
+ }
}
- out.emplace_back(second.extrusionVertex);
- if (first.boundaryVertex != second.boundaryVertex) {
- out.emplace_back(second.boundaryVertex);
+
+ void
+ setHeightsAsRequired(HeightSetTodo starts, RelativeDistance maxSlope)
+ {
+ while (!starts.empty()) {
+ HeightSetTodo nexts;
+
+ for (const auto chunk : starts | std::views::chunk_by([](const auto a, const auto b) {
+ return a.first == b.first;
+ })) {
+ setHalfedgeToHeight(nexts, chunk.begin(), chunk.end(), maxSlope);
+ }
+ starts = std::move(nexts);
+ }
}
- }
- // Remove old faces
- std::set<FaceHandle> visited;
- auto removeOld = [&](auto & self, const auto face) -> void {
- if (visited.insert(face).second) {
- std::for_each(fh_begin(face), fh_end(face), [&](const auto fh) {
- const auto b1 = to_vertex_handle(fh);
- const auto b2 = from_vertex_handle(fh);
- if (opposite_face_handle(fh).is_valid()
- && std::none_of(boundaryFaces.begin(), boundaryFaces.end(), [b2, b1](const auto & bf) {
- return std::adjacent_find(bf.begin(), bf.end(), [b2, b1](const auto v1, const auto v2) {
- return b1 == v1 && b2 == v2;
- }) != bf.end();
- })) {
- self(self, opposite_face_handle(fh));
+ std::set<FaceHandle>
+ setSurface(const Surface * surface)
+ {
+ std::set<FaceHandle> out;
+ auto surfaceStripWalk = [this, surface, &out](const auto & surfaceStripWalk, const auto & face) -> void {
+ if (!out.contains(face)) {
+ geoData->property(geoData->surface, face) = surface;
+ out.emplace(face);
+ std::ranges::for_each(
+ geoData->ff_range(face), [this, &surfaceStripWalk](const auto & adjacentFaceHandle) {
+ if (getTriangle(geoData->triangle<2>(adjacentFaceHandle).centroid())) {
+ surfaceStripWalk(surfaceStripWalk, adjacentFaceHandle);
+ }
+ });
}
- });
+ };
+ for (const auto & triangle : strip) {
+ surfaceStripWalk(surfaceStripWalk, geoData->findPoint(triangle.centroid()));
+ }
+ return out;
+ }
- delete_face(face, false);
+ std::set<GeoData::FaceHandle>
+ run(const SetHeightsOpts & opts)
+ {
+ const std::vector<VertexHandle> newVerts = createVerticesForStrip(opts.nearNodeTolerance);
+ cutBoundary(newVerts, opts.nearNodeTolerance);
+ setHeightsAsRequired(setSurfaceHeights(newVerts), opts.maxSlope);
+ const auto out = setSurface(opts.surface);
+
+ geoData->expandVerts(newOrChangedVerts);
+ geoData->updateAllVertexNormals(newOrChangedVerts);
+ geoData->afterChange();
+
+ return out;
}
- };
- removeOld(removeOld, findPoint(triangleStrip.front()));
- std::for_each(boundaryFaces.begin(), boundaryFaces.end(), [&](auto & boundaryFace) {
- std::reverse(boundaryFace.begin(), boundaryFace.end());
- add_face(boundaryFace);
- });
+ private:
+ GeoData * geoData;
+ const std::span<const GlobalPosition3D> triangleStrip;
+ const std::vector<Triangle<3>> strip;
+ std::set<VertexHandle> newOrChangedVerts;
+ std::set<VertexHandle> surfaceVerts;
+ std::map<VertexHandle, const Triangle<3> *> boundaryTriangles;
+ };
- // Tidy up
- update_vertex_normals_only(VertexIter {*this, vertex_handle(initialVertexCount), true});
+ return SetHeights {this, triangleStrip}.run(opts);
+}
- std::for_each(newFaces.begin(), newFaces.end(), [&newFaceSurface, this](const auto fh) {
- property(surface, fh) = &newFaceSurface;
- });
+void
+GeoData::afterChange()
+{
}
diff --git a/game/geoData.h b/game/geoData.h
index ed1734c..0ff0c42 100644
--- a/game/geoData.h
+++ b/game/geoData.h
@@ -1,193 +1,85 @@
#pragma once
#include "collections.h" // IWYU pragma: keep IterableCollection
-#include "config/types.h"
-#include "ray.h"
+#include "geoDataMesh.h"
+#include "gfx/aabb.h"
#include "surface.h"
-#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <filesystem>
#include <glm/vec2.hpp>
-#include <optional>
-#include <thirdparty/openmesh/glmcompat.h>
-
-struct GeoDataTraits : public OpenMesh::DefaultTraits {
- FaceAttributes(OpenMesh::Attributes::Status);
- EdgeAttributes(OpenMesh::Attributes::Status);
- VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status);
- HalfedgeAttributes(OpenMesh::Attributes::Status);
- using Point = GlobalPosition3D;
- using Normal = Normal3D;
-};
-class GeoData : public OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits> {
+class GeoData : public GeoDataMesh {
private:
- GeoData();
-
- OpenMesh::FPropHandleT<const Surface *> surface;
+ const OpenMesh::Helpers::Property<const Surface *, OpenMesh::FPropHandleT> surface {this};
public:
static GeoData loadFromAsciiGrid(const std::filesystem::path &);
static GeoData createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistance h);
- struct PointFace {
- // NOLINTNEXTLINE(hicpp-explicit-conversions)
- PointFace(const GlobalPosition2D p) : point {p} { }
-
- PointFace(const GlobalPosition2D p, FaceHandle face) : point {p}, _face {face} { }
-
- PointFace(const GlobalPosition2D p, const GeoData *);
- PointFace(const GlobalPosition2D p, const GeoData *, FaceHandle start);
-
- const GlobalPosition2D point;
- [[nodiscard]] FaceHandle face(const GeoData *) const;
- [[nodiscard]] FaceHandle face(const GeoData *, FaceHandle start) const;
-
- [[nodiscard]] bool
- isLocated() const
- {
- return _face.is_valid();
- }
+ using IntersectionLocation = std::pair<GlobalPosition3D, FaceHandle>;
+ using IntersectionResult = std::optional<IntersectionLocation>;
+ [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &) const;
+ [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &, FaceHandle start) const;
- private:
- mutable FaceHandle _face {};
+ struct WalkStep {
+ FaceHandle current;
+ FaceHandle previous {};
+ HalfedgeHandle exitHalfedge {};
+ GlobalPosition2D exitPosition {};
};
- template<glm::length_t Dim> struct Triangle : public glm::vec<3, glm::vec<Dim, GlobalDistance>> {
- using base = glm::vec<3, glm::vec<Dim, GlobalDistance>>;
- using base::base;
-
- template<IterableCollection Range> Triangle(const GeoData * m, Range range)
- {
- assert(std::distance(range.begin(), range.end()) == 3);
- std::transform(range.begin(), range.end(), &base::operator[](0), [m](auto vh) {
- return m->point(vh);
- });
- }
-
- [[nodiscard]] glm::vec<Dim, GlobalDistance>
- operator*(BaryPosition bari) const
- {
- return p(0) + (difference(p(0), p(1)) * bari.x) + (difference(p(0), p(2)) * bari.y);
- }
-
- [[nodiscard]] auto
- area() const
- requires(Dim == 3)
- {
- return glm::length(crossProduct(difference(p(0), p(1)), difference(p(0), p(2)))) / 2.F;
- }
-
- [[nodiscard]] Normal3D
- normal() const
- requires(Dim == 3)
- {
- return crossProduct(difference(p(0), p(1)), difference(p(0), p(2)));
- }
-
- [[nodiscard]] Normal3D
- nnormal() const
- requires(Dim == 3)
- {
- return glm::normalize(normal());
- }
-
- [[nodiscard]] auto
- angle(glm::length_t c) const
- {
- return Arc {P(c), P(c + 2), P(c + 1)}.length();
- }
-
- template<glm::length_t D = Dim>
- [[nodiscard]] auto
- angleAt(const GlobalPosition<D> pos) const
- requires(D <= Dim)
- {
- for (glm::length_t i {}; i < 3; ++i) {
- if (GlobalPosition<D> {p(i)} == pos) {
- return angle(i);
- }
- }
- return 0.F;
- }
-
- [[nodiscard]] inline auto
- p(const glm::length_t i) const
- {
- return base::operator[](i);
- }
-
- [[nodiscard]] inline auto
- P(const glm::length_t i) const
- {
- return base::operator[](i % 3);
- }
+ struct WalkStepCurve : public WalkStep {
+ Angle angle {};
};
- [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const;
- [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const;
+ template<typename T> using Consumer = const std::function<void(const T &)> &;
+ template<typename T> using Tester = const std::function<bool(const T &)> &;
- [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const;
- using IntersectionLocation = std::pair<GlobalPosition3D, FaceHandle>;
- using IntersectionResult = std::optional<IntersectionLocation>;
- [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &) const;
- [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &, FaceHandle start) const;
+ void walk(const PointFace & from, GlobalPosition2D to, Consumer<WalkStep> op) const;
+ void walkUntil(const PointFace & from, GlobalPosition2D to, Tester<WalkStep> op) const;
+ void walk(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Consumer<WalkStepCurve> op) const;
+ void walkUntil(
+ const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Tester<WalkStepCurve> op) const;
+
+ void boundaryWalk(Consumer<HalfedgeHandle>) const;
+ void boundaryWalk(Consumer<HalfedgeHandle>, HalfedgeHandle start) const;
+ void boundaryWalkUntil(Tester<HalfedgeHandle>) const;
+ void boundaryWalkUntil(Tester<HalfedgeHandle>, HalfedgeHandle start) const;
- void walk(const PointFace & from, const GlobalPosition2D to, const std::function<void(FaceHandle)> & op) const;
- void walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function<bool(FaceHandle)> & op) const;
+ [[nodiscard]] HalfedgeHandle findEntry(GlobalPosition2D from, GlobalPosition2D to) const;
- void boundaryWalk(const std::function<void(HalfedgeHandle)> &) const;
- void boundaryWalk(const std::function<void(HalfedgeHandle)> &, HalfedgeHandle start) const;
- void boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> &) const;
- void boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> &, HalfedgeHandle start) const;
+ struct SetHeightsOpts {
+ static constexpr auto DEFAULT_NEAR_NODE_TOLERANACE = 500.F;
+ static constexpr auto DEFAULT_MAX_SLOPE = 0.5F;
- [[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const;
+ const Surface * surface = nullptr;
+ RelativeDistance nearNodeTolerance = DEFAULT_NEAR_NODE_TOLERANACE;
+ RelativeDistance maxSlope = DEFAULT_MAX_SLOPE;
+ };
- void setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface &);
+ std::set<FaceHandle> setHeights(std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts &);
- [[nodiscard]] auto
+ [[nodiscard]] auto &
getExtents() const
{
- return std::tie(lowerExtent, upperExtent);
+ return extents;
}
template<typename HandleT>
+ requires(std::derived_from<HandleT, OpenMesh::BaseHandle>)
[[nodiscard]] auto
- get_surface(const HandleT h)
+ getSurface(const HandleT handle) const
{
- return property(surface, h);
+ assert(handle.is_valid());
+ return property(surface, handle);
}
protected:
- template<glm::length_t Dim>
- [[nodiscard]] Triangle<Dim>
- triangle(FaceHandle f) const
- {
- return {this, fv_range(f)};
- }
-
- [[nodiscard]] static bool triangleContainsPoint(const GlobalPosition2D, const Triangle<2> &);
- [[nodiscard]] bool triangleContainsPoint(const GlobalPosition2D, FaceHandle) const;
- [[nodiscard]] static bool triangleOverlapsTriangle(const Triangle<2> &, const Triangle<2> &);
- [[nodiscard]] static bool triangleContainsTriangle(const Triangle<2> &, const Triangle<2> &);
- [[nodiscard]] HalfedgeHandle findBoundaryStart() const;
- [[nodiscard]] RelativePosition3D difference(const HalfedgeHandle) const;
-
- template<glm::length_t D>
- [[nodiscard]] static RelativePosition<D>
- difference(const GlobalPosition<D> a, const GlobalPosition<D> b)
- {
- return b - a;
- }
-
- [[nodiscard]] RelativeDistance length(const HalfedgeHandle) const;
- [[nodiscard]] GlobalPosition3D centre(const HalfedgeHandle) const;
-
- void update_vertex_normals_only();
- void update_vertex_normals_only(VertexIter start);
-
- using OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits>::split;
- void split(FaceHandle);
+ [[nodiscard]] VertexHandle setPoint(GlobalPosition3D point, RelativeDistance nearNodeTolerance);
+ void updateAllVertexNormals();
+ template<std::ranges::range R> void updateAllVertexNormals(const R &);
+ void updateVertexNormal(VertexHandle);
+ virtual void afterChange();
private:
- GlobalPosition3D lowerExtent {}, upperExtent {};
+ AxisAlignedBoundingBox<GlobalDistance> extents;
};
diff --git a/game/geoDataMesh.cpp b/game/geoDataMesh.cpp
new file mode 100644
index 0000000..8107a5e
--- /dev/null
+++ b/game/geoDataMesh.cpp
@@ -0,0 +1,150 @@
+#include "geoDataMesh.h"
+#include <format>
+#ifndef NDEBUG
+# include <stream_support.h>
+#endif
+
+OpenMesh::FaceHandle
+GeoDataMesh::findPoint(GlobalPosition2D coord) const
+{
+ return findPoint(coord, *faces_sbegin());
+}
+
+GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh) :
+ PointFace {coord, mesh, *mesh->faces_sbegin()}
+{
+}
+
+GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh, FaceHandle start) :
+ PointFace {coord, mesh->findPoint(coord, start)}
+{
+}
+
+OpenMesh::FaceHandle
+GeoDataMesh::PointFace::face(const GeoDataMesh * mesh, FaceHandle start) const
+{
+ if (faceCache.is_valid() && mesh->faceContainsPoint(point, faceCache)) {
+ return faceCache;
+ }
+ return (faceCache = mesh->findPoint(point, start));
+}
+
+OpenMesh::FaceHandle
+GeoDataMesh::PointFace::face(const GeoDataMesh * mesh) const
+{
+ return face(mesh, *mesh->faces_sbegin());
+}
+
+GeoDataMesh::HalfEdgeVertices
+GeoDataMesh::toVertexHandles(HalfedgeHandle halfEdge) const
+{
+ return {from_vertex_handle(halfEdge), to_vertex_handle(halfEdge)};
+}
+
+GeoDataMesh::HalfEdgePoints
+GeoDataMesh::points(HalfEdgeVertices vertices) const
+{
+ return {point(vertices.first), point(vertices.second)};
+}
+
+OpenMesh::FaceHandle
+GeoDataMesh::findPoint(const GlobalPosition2D coord, OpenMesh::FaceHandle face) const
+{
+ while (face.is_valid() && !triangle<2>(face).containsPoint(coord)) {
+ for (auto next = cfh_iter(face); next.is_valid(); ++next) {
+ face = opposite_face_handle(*next);
+ if (face.is_valid()) {
+ const auto nextPoints = points(toVertexHandles(*next));
+ if (pointLeftOfLine(coord, nextPoints.second, nextPoints.first)) {
+ break;
+ }
+ }
+ face.reset();
+ }
+ }
+ return face;
+}
+
+GlobalPosition3D
+GeoDataMesh::positionAt(const PointFace & coord) const
+{
+ return triangle<3>(coord.face(this)).positionOnPlane(coord.point);
+}
+
+bool
+GeoDataMesh::faceContainsPoint(const GlobalPosition2D coord, FaceHandle face) const
+{
+ return triangle<2>(face).containsPoint(coord);
+}
+
+OpenMesh::HalfedgeHandle
+GeoDataMesh::findBoundaryStart() const
+{
+ return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) {
+ return is_boundary(heh);
+ });
+}
+
+[[nodiscard]] RelativePosition3D
+GeoDataMesh::difference(const HalfedgeHandle heh) const
+{
+ return ::difference(point(to_vertex_handle(heh)), point(from_vertex_handle(heh)));
+}
+
+[[nodiscard]] GlobalPosition3D
+GeoDataMesh::centre(const HalfedgeHandle heh) const
+{
+ const auto hehPoints = points(toVertexHandles(heh));
+ return midpoint(hehPoints.first, hehPoints.second);
+}
+
+#ifndef NDEBUG
+void
+GeoDataMesh::sanityCheck(const std::source_location & loc) const
+{
+ if (const auto upSideDown = std::ranges::count_if(faces(), [this](const auto face) {
+ if (!triangle<2>(face).isUp()) {
+ for (const auto vertex : fv_range(face)) {
+ CLOG(point(vertex));
+ }
+ return true;
+ }
+ return false;
+ }) > 0) {
+ throw std::logic_error(std::format(
+ "{} upside down faces detected - checked from {}:{}", upSideDown, loc.function_name(), loc.line()));
+ }
+}
+#endif
+
+bool
+GeoDataMesh::canFlip(const HalfedgeHandle edge) const
+{
+ const auto opposite = opposite_halfedge_handle(edge);
+ const auto pointA = point(to_vertex_handle(edge));
+ const auto pointB = point(to_vertex_handle(opposite));
+ const auto pointC = point(to_vertex_handle(next_halfedge_handle(edge)));
+ const auto pointD = point(to_vertex_handle(next_halfedge_handle(opposite)));
+
+ return Triangle<2> {pointC, pointB, pointD}.isUp() && Triangle<2> {pointA, pointC, pointD}.isUp();
+};
+
+std::optional<OpenMesh::EdgeHandle>
+GeoDataMesh::shouldFlip(const HalfedgeHandle next, const GlobalPosition2D startPoint) const
+{
+ if (const auto nextEdge = edge_handle(next); is_flip_ok(nextEdge) && canFlip(next)) {
+ const auto oppositePoint = point(to_vertex_handle(next_halfedge_handle(opposite_halfedge_handle(next)))).xy();
+ if (distance<2>(startPoint, oppositePoint) < length<2>(next)) {
+ return nextEdge;
+ }
+ }
+ return std::nullopt;
+};
+
+void
+GeoDataMesh::expandVerts(std::set<VertexHandle> & verts) const
+{
+ std::ranges::for_each(std::vector<VertexHandle>(verts.begin(), verts.end()), [&verts, this](auto vertex) {
+ std::ranges::copy(vv_range(vertex), std::inserter(verts, verts.end()));
+ });
+}
diff --git a/game/geoDataMesh.h b/game/geoDataMesh.h
new file mode 100644
index 0000000..c9da8f0
--- /dev/null
+++ b/game/geoDataMesh.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "config/types.h"
+#include "triangle.h"
+#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
+#include <set>
+#include <source_location>
+#include <thirdparty/openmesh/glmcompat.h>
+#include <thirdparty/openmesh/helpers.h>
+
+struct GeoDataTraits : public OpenMesh::DefaultTraits {
+ FaceAttributes(OpenMesh::Attributes::Status);
+ EdgeAttributes(OpenMesh::Attributes::Status);
+ VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status);
+ HalfedgeAttributes(OpenMesh::Attributes::Status);
+ using Point = GlobalPosition3D;
+ using Normal = Normal3D;
+};
+
+class GeoDataMesh : public OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits> {
+public:
+ struct PointFace {
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ PointFace(GlobalPosition2D coord) : point {coord} { }
+
+ PointFace(GlobalPosition2D coord, FaceHandle face) : point {coord}, faceCache {face} { }
+
+ PointFace(GlobalPosition2D coord, const GeoDataMesh *);
+ PointFace(GlobalPosition2D coord, GeoDataMesh const *, FaceHandle start);
+
+ const GlobalPosition2D point;
+ [[nodiscard]] FaceHandle face(const GeoDataMesh *) const;
+ [[nodiscard]] FaceHandle face(const GeoDataMesh *, FaceHandle start) const;
+
+ [[nodiscard]] bool
+ isLocated() const
+ {
+ return faceCache.is_valid();
+ }
+
+ private:
+ mutable FaceHandle faceCache;
+ };
+
+ template<glm::length_t Dim> using Triangle = ::Triangle<Dim, GlobalDistance>;
+
+ [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const;
+ [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle) const;
+
+ [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const;
+
+protected:
+#ifndef NDEBUG
+ void sanityCheck(const std::source_location & = std::source_location::current()) const;
+#endif
+
+ [[nodiscard]] bool faceContainsPoint(GlobalPosition2D, FaceHandle) const;
+ [[nodiscard]] HalfedgeHandle findBoundaryStart() const;
+ [[nodiscard]] RelativePosition3D difference(HalfedgeHandle) const;
+ using HalfEdgeVertices = std::pair<VertexHandle, VertexHandle>;
+ [[nodiscard]] HalfEdgeVertices toVertexHandles(HalfedgeHandle) const;
+ using HalfEdgePoints = std::pair<GlobalPosition3D, GlobalPosition3D>;
+ [[nodiscard]] HalfEdgePoints points(HalfEdgeVertices) const;
+
+ template<glm::length_t D>
+ [[nodiscard]] auto
+ vertexDistanceFunction(GlobalPosition<D> point) const
+ {
+ struct DistanceCalculator {
+ [[nodiscard]] std::pair<VertexHandle, float>
+ operator()(VertexHandle compVertex) const
+ {
+ return std::make_pair(
+ compVertex, ::distance<D, GlobalDistance, glm::defaultp>(point, mesh->point(compVertex)));
+ }
+
+ [[nodiscard]]
+ std::pair<HalfedgeHandle, float>
+ operator()(const HalfedgeHandle compHalfedge) const
+ {
+ const auto edgePoints = mesh->points(mesh->toVertexHandles(compHalfedge));
+ return std::make_pair(compHalfedge, Triangle<2> {edgePoints.second, edgePoints.first, point}.height());
+ };
+
+ const GeoDataMesh * mesh;
+ GlobalPosition<D> point;
+ };
+
+ return DistanceCalculator {this, point};
+ }
+
+ [[nodiscard]] bool canFlip(HalfedgeHandle edge) const;
+ [[nodiscard]] std::optional<EdgeHandle> shouldFlip(HalfedgeHandle next, GlobalPosition2D startPoint) const;
+ void expandVerts(std::set<VertexHandle> & verts) const;
+
+ template<glm::length_t D>
+ [[nodiscard]] RelativeDistance
+ length(HalfedgeHandle heh) const
+ {
+ return ::distance<D, GlobalDistance, glm::defaultp>(
+ point(to_vertex_handle(heh)), point(from_vertex_handle(heh)));
+ }
+
+ [[nodiscard]] GlobalPosition3D centre(HalfedgeHandle) const;
+
+ template<glm::length_t Dim>
+ [[nodiscard]] Triangle<Dim>
+ triangle(FaceHandle face) const
+ {
+ Triangle<Dim> triangle;
+ std::ranges::transform(fv_range(face), triangle.begin(), [this](auto vertex) {
+ return this->point(vertex);
+ });
+ return triangle;
+ }
+};
diff --git a/game/network/link.cpp b/game/network/link.cpp
index 248fe7d..b8ffee2 100644
--- a/game/network/link.cpp
+++ b/game/network/link.cpp
@@ -5,71 +5,115 @@
#include <ray.h>
#include <tuple>
-Link::Link(End a, End b, float l) : ends {{std::move(a), std::move(b)}}, length {l} { }
+Link::Link(End endA, End endB, float len) : ends {{std::move(endA), std::move(endB)}}, length {len} { }
-LinkCurve::LinkCurve(GlobalPosition3D c, RelativeDistance r, Arc a) : centreBase {c}, radius {r}, arc {std::move(a)} { }
+LinkCurve::LinkCurve(GlobalPosition3D centre, RelativeDistance radius, Arc arc) :
+ centreBase {centre}, radius {radius}, arc {std::move(arc)}
+{
+}
bool
-operator<(const GlobalPosition3D & a, const GlobalPosition3D & b)
+operator<(const GlobalPosition3D & left, const GlobalPosition3D & right)
{
// NOLINTNEXTLINE(hicpp-use-nullptr,modernize-use-nullptr)
- return std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z);
+ return std::tie(left.x, left.y, left.z) < std::tie(right.x, right.y, right.z);
}
bool
-operator<(const Node & a, const Node & b)
+operator<(const Node & left, const Node & right)
{
- return a.pos < b.pos;
+ return left.pos < right.pos;
}
Location
LinkStraight::positionAt(RelativeDistance dist, unsigned char start) const
{
- const auto es {std::make_pair(ends[start].node.get(), ends[1 - start].node.get())};
- const RelativePosition3D diff {es.second->pos - es.first->pos};
- const auto dir {glm::normalize(diff)};
- return Location {es.first->pos + (vehiclePositionOffset() + dir * dist), {vector_pitch(dir), vector_yaw(dir), 0}};
+ const auto endNodes = std::make_pair(ends[start].node.get(), ends[1 - start].node.get());
+ const auto diff = ::difference(endNodes.second->pos, endNodes.first->pos);
+ const auto directionVector = glm::normalize(diff);
+ return Location {
+ .pos = endNodes.first->pos + (vehiclePositionOffset() + directionVector * dist),
+ .rot = {vector_pitch(directionVector), vector_yaw(directionVector), 0},
+ };
}
bool
LinkStraight::intersectRay(const Ray<GlobalPosition3D> & ray) const
{
- return ray.passesCloseToEdges(
- std::array {GlobalPosition3D {ends.front().node->pos}, GlobalPosition3D {ends.back().node->pos}}, 1000);
+ static constexpr auto PROXIMITY = 1'000;
+ return ray.passesCloseToEdges(std::array {ends.front().node->pos, ends.back().node->pos}, PROXIMITY);
+}
+
+std::vector<GlobalPosition3D>
+LinkStraight::getBase(RelativeDistance width) const
+{
+ const auto start = ends.front().node->pos;
+ const auto end = ends.back().node->pos;
+ const auto direction = (vector_normal(normalize(::difference(start, end).xy())) * width / 2.F) || 0.F;
+ return {
+ start - direction,
+ start + direction,
+ end - direction,
+ end + direction,
+ };
}
Location
LinkCurve::positionAt(float dist, unsigned char start) const
{
- static constexpr std::array<float, 2> dirOffset {half_pi, -half_pi};
- const auto frac {dist / length};
- const auto es {std::make_pair(ends[start].node.get(), ends[1 - start].node.get())};
- const auto as {std::make_pair(arc[start], arc[1 - start])};
- const auto ang {as.first + ((as.second - as.first) * frac)};
- const auto relPos {(sincosf(ang) || 0.F) * radius};
- const auto relClimb {vehiclePositionOffset()
+ static constexpr std::array DIR_OFFSET {half_pi, -half_pi};
+ const auto frac = dist / length;
+ const auto endNodes = std::make_pair(ends[start].node.get(), ends[1 - start].node.get());
+ const auto arcEndAngles = std::make_pair(arc[start], arc[1 - start]);
+ const auto ang = glm::mix(arcEndAngles.first, arcEndAngles.second, frac);
+ const auto relPos = (sincos(ang) || 0.F) * radius;
+ const auto relClimb = vehiclePositionOffset()
+ RelativePosition3D {0, 0,
- static_cast<RelativeDistance>(es.first->pos.z - centreBase.z)
- + (static_cast<RelativeDistance>(es.second->pos.z - es.first->pos.z) * frac)}};
- const auto pitch {vector_pitch({0, 0, static_cast<RelativeDistance>(es.second->pos.z - es.first->pos.z) / length})};
- return Location {GlobalPosition3D(relPos + relClimb) + centreBase, {pitch, normalize(ang + dirOffset[start]), 0}};
+ static_cast<RelativeDistance>(endNodes.first->pos.z - centreBase.z)
+ + (static_cast<RelativeDistance>(endNodes.second->pos.z - endNodes.first->pos.z) * frac)};
+ const auto pitch {vector_pitch(difference(endNodes.second->pos, endNodes.first->pos) / length)};
+ return Location {
+ .pos = GlobalPosition3D(relPos + relClimb) + centreBase,
+ .rot = {pitch, normalize(ang + DIR_OFFSET[start]), 0},
+ };
}
bool
LinkCurve::intersectRay(const Ray<GlobalPosition3D> & ray) const
{
- const auto & e0p {ends[0].node->pos};
- const auto & e1p {ends[1].node->pos};
+ const auto e0p = ends[0].node->pos.z;
+ const auto e1p = ends[1].node->pos.z;
const auto slength = round_frac(length / 2.F, 5.F);
const auto segs = std::round(15.F * slength / std::pow(radius, 0.7F));
- const auto step {glm::vec<2, RelativeDistance> {arc.length(), e1p.z - e0p.z} / segs};
+ const auto step {glm::vec<2, RelativeDistance> {arc.length(), e1p - e0p} / segs};
auto segCount = static_cast<std::size_t>(std::lround(segs)) + 1;
std::vector<GlobalPosition3D> points;
points.reserve(segCount);
- for (std::remove_const_t<decltype(step)> swing = {arc.first, centreBase.z - e0p.z}; segCount;
+ for (std::remove_const_t<decltype(step)> swing = {arc.first, centreBase.z - e0p}; segCount;
swing += step, --segCount) {
- points.emplace_back(centreBase + ((sincosf(swing.x) * radius) || swing.y));
+ points.emplace_back(centreBase + ((sincos(swing.x) * radius) || swing.y));
}
return ray.passesCloseToEdges(points, 1.F);
}
+
+std::vector<GlobalPosition3D>
+LinkCurve::getBase(RelativeDistance width) const
+{
+ const auto start = ends.front().node->pos;
+ const auto end = ends.back().node->pos;
+ const auto segs = std::ceil(std::sqrt(radius) * 0.02F * arc.length());
+ const auto step = glm::vec<2, RelativeDistance> {arc.length(), end.z - start.z} / segs;
+
+ auto segCount = static_cast<size_t>(segs) + 1;
+ std::vector<GlobalPosition3D> out;
+ out.reserve(segCount);
+ for (RelativePosition2D swing = {arc.first, centreBase.z - start.z}; segCount != 0U; swing += step, --segCount) {
+ const auto direction = sincos(swing.x);
+ const auto linkCentre = centreBase + ((direction * radius) || swing.y);
+ const auto toEdge = (direction * width / 2.F) || 0.F;
+ out.emplace_back(linkCentre + toEdge);
+ out.emplace_back(linkCentre - toEdge);
+ }
+ return out;
+}
diff --git a/game/network/link.h b/game/network/link.h
index 725e023..0b58558 100644
--- a/game/network/link.h
+++ b/game/network/link.h
@@ -16,7 +16,7 @@ template<typename> class Ray;
// it has location
class Node : public StdTypeDefs<Node> {
public:
- explicit Node(GlobalPosition3D p) noexcept : pos(p) {};
+ explicit Node(GlobalPosition3D position) noexcept : pos(position) { };
virtual ~Node() noexcept = default;
NO_COPY(Node);
NO_MOVE(Node);
@@ -35,16 +35,18 @@ public:
struct End {
Node::Ptr node;
float dir;
+ // NOLINTNEXTLINE(readability-redundant-member-init) don't require client to empty initialise this
Nexts nexts {};
};
- Link(End, End, float);
+ Link(End, End, RelativeDistance length);
virtual ~Link() = default;
NO_COPY(Link);
NO_MOVE(Link);
[[nodiscard]] virtual Location positionAt(RelativeDistance dist, unsigned char start) const = 0;
[[nodiscard]] virtual bool intersectRay(const Ray<GlobalPosition3D> &) const = 0;
+ [[nodiscard]] virtual std::vector<GlobalPosition3D> getBase(RelativeDistance width) const = 0;
std::array<End, 2> ends;
float length;
@@ -57,8 +59,8 @@ protected:
}
};
-bool operator<(const GlobalPosition3D & a, const GlobalPosition3D & b);
-bool operator<(const Node & a, const Node & b);
+bool operator<(const GlobalPosition3D &, const GlobalPosition3D &);
+bool operator<(const Node &, const Node &);
class LinkStraight : public virtual Link {
public:
@@ -69,6 +71,7 @@ public:
[[nodiscard]] Location positionAt(RelativeDistance dist, unsigned char start) const override;
[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override;
+ [[nodiscard]] std::vector<GlobalPosition3D> getBase(RelativeDistance width) const override;
};
LinkStraight::~LinkStraight() = default;
@@ -76,12 +79,13 @@ LinkStraight::~LinkStraight() = default;
class LinkCurve : public virtual Link {
public:
inline ~LinkCurve() override = 0;
- LinkCurve(GlobalPosition3D, RelativeDistance, Arc);
+ LinkCurve(GlobalPosition3D centreBase, RelativeDistance radius, Arc);
NO_COPY(LinkCurve);
NO_MOVE(LinkCurve);
[[nodiscard]] Location positionAt(RelativeDistance dist, unsigned char start) const override;
[[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override;
+ [[nodiscard]] std::vector<GlobalPosition3D> getBase(RelativeDistance width) const override;
GlobalPosition3D centreBase;
RelativeDistance radius;
diff --git a/game/network/network.cpp b/game/network/network.cpp
index 65b2a62..c8482de 100644
--- a/game/network/network.cpp
+++ b/game/network/network.cpp
@@ -8,7 +8,13 @@
#include <stdexcept>
#include <utility>
-Network::Network(const std::string & tn) : texture {std::make_shared<Texture>(tn)} { }
+Network::Network(const std::string & textureName) :
+ texture {std::make_shared<Texture>(textureName,
+ TextureOptions {
+ .minFilter = GL_NEAREST_MIPMAP_LINEAR,
+ })}
+{
+}
Node::Ptr
Network::nodeAt(GlobalPosition3D pos)
@@ -19,19 +25,18 @@ Network::nodeAt(GlobalPosition3D pos)
Network::NodeInsertion
Network::newNodeAt(GlobalPosition3D pos)
{
- if (auto [n, i] = candidateNodeAt(pos); i == NodeIs::NotInNetwork) {
- return {*nodes.insert(std::move(n)).first, i};
- }
- else {
- return {std::move(n), NodeIs::InNetwork};
+ auto [node, inNetwork] = candidateNodeAt(pos);
+ if (inNetwork == NodeIs::NotInNetwork) {
+ return {*nodes.insert(std::move(node)).first, inNetwork};
}
+ return {std::move(node), NodeIs::InNetwork};
}
Node::Ptr
Network::findNodeAt(GlobalPosition3D pos) const
{
- if (const auto n = nodes.find(pos); n != nodes.end()) {
- return *n;
+ if (const auto node = nodes.find(pos); node != nodes.end()) {
+ return *node;
}
return {};
}
@@ -39,8 +44,8 @@ Network::findNodeAt(GlobalPosition3D pos) const
Network::NodeInsertion
Network::candidateNodeAt(GlobalPosition3D pos) const
{
- if (const auto n = nodes.find(pos); n != nodes.end()) {
- return {*n, NodeIs::InNetwork};
+ if (const auto node = nodes.find(pos); node != nodes.end()) {
+ return {*node, NodeIs::InNetwork};
}
return {std::make_shared<Node>(pos), NodeIs::NotInNetwork};
}
@@ -48,12 +53,11 @@ Network::candidateNodeAt(GlobalPosition3D pos) const
Node::Ptr
Network::intersectRayNodes(const Ray<GlobalPosition3D> & ray) const
{
+ static constexpr auto MIN_DISTANCE = 2000;
// Click within 2m of a node
if (const auto node = std::find_if(nodes.begin(), nodes.end(),
[&ray](const Node::Ptr & node) {
- GlobalPosition3D ipos;
- Normal3D inorm;
- return ray.intersectSphere(node->pos, 2000, ipos, inorm);
+ return ray.intersectSphere(node->pos, MIN_DISTANCE);
});
node != nodes.end()) {
return *node;
@@ -62,14 +66,14 @@ Network::intersectRayNodes(const Ray<GlobalPosition3D> & ray) const
}
void
-Network::joinLinks(const Link::Ptr & l, const Link::Ptr & ol)
+Network::joinLinks(const Link::Ptr & link, const Link::Ptr & oldLink)
{
- if (l != ol) {
- for (const auto oe : {0U, 1U}) {
- for (const auto te : {0U, 1U}) {
- if (l->ends[te].node == ol->ends[oe].node) {
- l->ends[te].nexts.emplace_back(ol, oe);
- ol->ends[oe].nexts.emplace_back(l, te);
+ if (link != oldLink) {
+ for (const auto oldLinkEnd : {0U, 1U}) {
+ for (const auto linkEnd : {0U, 1U}) {
+ if (link->ends[linkEnd].node == oldLink->ends[oldLinkEnd].node) {
+ link->ends[linkEnd].nexts.emplace_back(oldLink, oldLinkEnd);
+ oldLink->ends[oldLinkEnd].nexts.emplace_back(link, linkEnd);
}
}
}
@@ -87,7 +91,7 @@ Network::routeFromTo(const Link::End & start, GlobalPosition3D dest) const
}
Link::Nexts
-Network::routeFromTo(const Link::End & end, const Node::Ptr & dest) const
+Network::routeFromTo(const Link::End & end, const Node::Ptr & dest)
{
return RouteWalker().findRouteTo(end, dest);
}
@@ -95,12 +99,12 @@ Network::routeFromTo(const Link::End & end, const Node::Ptr & dest) const
GenCurveDef
Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir)
{
- const auto diff {end - start};
- const auto vy {vector_yaw(diff)};
+ const auto diff = difference(end, start);
+ const auto yaw = vector_yaw(diff);
const auto dir = pi + startDir;
- const auto flatStart {start.xy()}, flatEnd {end.xy()};
- const auto n2ed {(vy * 2) - dir - pi};
- const auto centre {find_arc_centre(flatStart, dir, flatEnd, n2ed)};
+ const auto flatStart = start.xy(), flatEnd = end.xy();
+ const auto n2ed = (yaw * 2) - dir - pi;
+ const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed);
if (centre.second) { // right hand arc
return {end, start, centre.first};
@@ -115,24 +119,23 @@ Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & en
endDir += pi;
const auto flatStart {start.xy()}, flatEnd {end.xy()};
auto midheight = [&](auto mid) {
- const auto sm = glm::length(RelativePosition2D(flatStart - mid)),
- em = glm::length(RelativePosition2D(flatEnd - mid));
- return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (sm / (sm + em)));
+ const auto startToMid = ::distance<2>(flatStart, mid);
+ const auto endToMid = ::distance<2>(flatEnd, mid);
+ return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid)));
};
- if (const auto radii = find_arcs_radius(flatStart, startDir, flatEnd, endDir); radii.first < radii.second) {
- const auto radius {radii.first};
- const auto c1 = flatStart + (sincosf(startDir + half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(endDir + half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
- const auto midh = mid || midheight(mid);
- return {{start, midh, c1}, {end, midh, c2}};
- }
- else {
- const auto radius {radii.second};
- const auto c1 = flatStart + (sincosf(startDir - half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(endDir - half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
+ const auto radii = find_arcs_radius(flatStart, startDir, flatEnd, endDir);
+ if (radii.first < radii.second) {
+ const auto radius = radii.first;
+ const auto centre1 = flatStart + (sincos(startDir + half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(endDir + half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
const auto midh = mid || midheight(mid);
- return {{midh, start, c1}, {midh, end, c2}};
+ return {{start, midh, centre1}, {end, midh, centre2}};
}
+ const auto radius = radii.second;
+ const auto centre1 = flatStart + (sincos(startDir - half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(endDir - half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
+ const auto midh = mid || midheight(mid);
+ return {{midh, start, centre1}, {midh, end, centre2}};
}
diff --git a/game/network/network.h b/game/network/network.h
index 5725360..4f5d2b0 100644
--- a/game/network/network.h
+++ b/game/network/network.h
@@ -1,23 +1,24 @@
#pragma once
+#include "collection.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
#include "link.h"
-#include <collection.h>
-#include <gfx/renderable.h>
+#include "sorting.h"
+#include "special_members.h"
#include <glm/glm.hpp>
#include <memory>
#include <set>
-#include <sorting.h>
-#include <special_members.h>
#include <string>
#include <utility>
-#include <variant>
-class Texture;
class SceneShader;
+struct Surface;
+class GeoData;
template<typename> class Ray;
-template<size_t... n> using GenDef = std::tuple<glm::vec<n, Distance>...>;
+template<size_t... N> using GenDef = std::tuple<glm::vec<N, GlobalDistance>...>;
using GenCurveDef = GenDef<3, 3, 2>;
class Network {
@@ -29,7 +30,7 @@ public:
[[nodiscard]] Node::Ptr findNodeAt(GlobalPosition3D) const;
[[nodiscard]] Node::Ptr nodeAt(GlobalPosition3D);
- enum class NodeIs { InNetwork, NotInNetwork };
+ enum class NodeIs : uint8_t { InNetwork, NotInNetwork };
using NodeInsertion = std::pair<Node::Ptr, NodeIs>;
[[nodiscard]] NodeInsertion newNodeAt(GlobalPosition3D);
[[nodiscard]] NodeInsertion candidateNodeAt(GlobalPosition3D) const;
@@ -37,26 +38,29 @@ public:
[[nodiscard]] virtual Node::Ptr intersectRayNodes(const Ray<GlobalPosition3D> &) const;
[[nodiscard]] Link::Nexts routeFromTo(const Link::End &, GlobalPosition3D) const;
- [[nodiscard]] Link::Nexts routeFromTo(const Link::End &, const Node::Ptr &) const;
+ [[nodiscard]] static Link::Nexts routeFromTo(const Link::End &, const Node::Ptr &);
virtual Link::CCollection candidateStraight(GlobalPosition3D, GlobalPosition3D) = 0;
virtual Link::CCollection candidateJoins(GlobalPosition3D, GlobalPosition3D) = 0;
virtual Link::CCollection candidateExtend(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addStraight(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addJoins(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addExtend(GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addStraight(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addJoins(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
[[nodiscard]] virtual float findNodeDirection(Node::AnyCPtr) const = 0;
+ [[nodiscard]] virtual const Surface * getBaseSurface() const = 0;
+ [[nodiscard]] virtual RelativeDistance getBaseWidth() const = 0;
+
protected:
- static void joinLinks(const Link::Ptr & l, const Link::Ptr & ol);
+ static void joinLinks(const Link::Ptr & link, const Link::Ptr & oldLink);
static GenCurveDef genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir);
static std::pair<GenCurveDef, GenCurveDef> genCurveDef(
const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir, float endDir);
using Nodes = std::set<Node::Ptr, PtrMemberSorter<Node::Ptr, &Node::pos>>;
Nodes nodes;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
};
template<typename LinkType> class NetworkLinkHolder {
@@ -73,42 +77,41 @@ class NetworkOf : public Network, public Renderable, public NetworkLinkHolder<Li
protected:
using Network::Network;
- Collection<T> links;
+ SharedCollection<T> links;
void joinLinks(const Link::Ptr &) const;
-protected:
[[nodiscard]] Link::Ptr intersectRayLinks(const Ray<GlobalPosition3D> &) const override;
public:
template<typename L, typename... Params>
std::shared_ptr<L>
- candidateLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params)
+ candidateLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params)
requires std::is_base_of_v<T, L>
{
- const auto node1 = candidateNodeAt(a).first, node2 = candidateNodeAt(b).first;
+ const auto node1 = candidateNodeAt(positionA).first, node2 = candidateNodeAt(positionB).first;
return std::make_shared<L>(*this, node1, node2, std::forward<Params>(params)...);
}
template<typename L, typename... Params>
std::shared_ptr<L>
- addLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params)
+ addLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params)
requires std::is_base_of_v<T, L>
{
- const auto node1 = nodeAt(a), node2 = nodeAt(b);
- auto l {links.template create<L>(*this, node1, node2, std::forward<Params>(params)...)};
- joinLinks(l);
- return l;
+ const auto node1 = nodeAt(positionA), node2 = nodeAt(positionB);
+ auto newLink = links.template create<L>(*this, node1, node2, std::forward<Params>(params)...);
+ joinLinks(newLink);
+ return std::move(newLink);
}
- Link::CCollection candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2) override;
+ Link::CCollection candidateStraight(GlobalPosition3D, GlobalPosition3D) override;
Link::CCollection candidateJoins(GlobalPosition3D, GlobalPosition3D) override;
Link::CCollection candidateExtend(GlobalPosition3D, GlobalPosition3D) override;
- Link::CCollection addStraight(GlobalPosition3D n1, GlobalPosition3D n2) override;
- Link::CCollection addJoins(GlobalPosition3D, GlobalPosition3D) override;
- Link::CCollection addExtend(GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addStraight(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addJoins(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
[[nodiscard]] float findNodeDirection(Node::AnyCPtr) const override;
protected:
- Link::CCollection addJoins();
+ Link::CCollection addCurve(const GeoData *, const GenCurveDef &);
};
diff --git a/game/network/network.impl.h b/game/network/network.impl.h
index ff29088..e339922 100644
--- a/game/network/network.impl.h
+++ b/game/network/network.impl.h
@@ -1,13 +1,15 @@
+#include "collections.h"
#include "network.h"
+#include <game/geoData.h>
#include <gfx/gl/sceneShader.h>
#include <gfx/models/texture.h>
template<typename T, typename... Links>
void
-NetworkOf<T, Links...>::joinLinks(const Link::Ptr & l) const
+NetworkOf<T, Links...>::joinLinks(const Link::Ptr & link) const
{
- for (const auto & ol : links.objects) {
- Network::joinLinks(l, ol);
+ for (const auto & oldLink : links) {
+ Network::joinLinks(link, oldLink);
}
}
@@ -16,11 +18,11 @@ Link::Ptr
NetworkOf<T, Links...>::intersectRayLinks(const Ray<GlobalPosition3D> & ray) const
{
// Click link
- if (const auto link = std::find_if(links.objects.begin(), links.objects.end(),
+ if (const auto link = std::find_if(links.begin(), links.end(),
[&ray](const std::shared_ptr<T> & link) {
return link->intersectRay(ray);
});
- link != links.objects.end()) {
+ link != links.end()) {
return *link;
}
return {};
@@ -30,11 +32,11 @@ template<typename T, typename... Links>
float
NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const
{
- for (const auto & l : links.objects) {
- for (const auto & e : l->ends) {
+ for (const auto & link : links) {
+ for (const auto & end : link->ends) {
// cppcheck-suppress useStlAlgorithm
- if (e.node.get() == n.get()) {
- return e.dir;
+ if (end.node.get() == n.get()) {
+ return end.dir;
}
}
}
@@ -43,16 +45,17 @@ NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2)
+NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D positionA, GlobalPosition3D positionB)
{
- return {candidateLink<typename T::StraightLink>(n1, n2)};
+ return {candidateLink<typename T::StraightLink>(positionA, positionB)};
}
template<typename T, typename... Links>
Link::CCollection
NetworkOf<T, Links...>::candidateJoins(GlobalPosition3D start, GlobalPosition3D end)
{
- if (glm::length(RelativePosition3D(start - end)) < 2000.F) {
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ if (::distance(start, end) < MIN_DISTANCE) {
return {};
}
const auto defs = genCurveDef(
@@ -72,28 +75,71 @@ NetworkOf<T, Links...>::candidateExtend(GlobalPosition3D start, GlobalPosition3D
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addStraight(GlobalPosition3D n1, GlobalPosition3D n2)
+NetworkOf<T, Links...>::addStraight(const GeoData * geoData, GlobalPosition3D positionA, GlobalPosition3D positionB)
{
- return {addLink<typename T::StraightLink>(n1, n2)};
+ Link::CCollection out;
+ geoData->walk(positionA.xy(), positionB, [geoData, &out, this, &positionA](const GeoData::WalkStep & step) {
+ if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) {
+ const auto surfaceEdgePosition = geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current));
+ out.emplace_back(addLink<typename T::StraightLink>(positionA, surfaceEdgePosition));
+ positionA = surfaceEdgePosition;
+ }
+ });
+ out.emplace_back(addLink<typename T::StraightLink>(positionA, positionB));
+ return out;
+}
+
+template<typename T, typename... Links>
+Link::CCollection
+NetworkOf<T, Links...>::addCurve(const GeoData * geoData, const GenCurveDef & curve)
+{
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ auto [cstart, cend, centre] = curve;
+ Link::CCollection out;
+ std::set<GeoData::WalkStepCurve, SortedBy<&GeoData::WalkStepCurve::angle>> breaks;
+ const auto radiusMid = ::distance(cstart.xy(), centre);
+ for (const auto radiusOffset : {-getBaseWidth() / 2.F, 0.F, getBaseWidth() / 2.F}) {
+ const auto radius = radiusOffset + radiusMid;
+ const auto start = centre + (difference(cstart.xy(), centre) * radius) / radiusMid;
+ const auto end = centre + (difference(cend.xy(), centre) * radius) / radiusMid;
+ geoData->walk(start, end, centre, [geoData, &breaks](const GeoData::WalkStepCurve & step) {
+ if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) {
+ breaks.insert(step);
+ }
+ });
+ }
+ std::vector<GlobalPosition3D> points;
+ points.reserve(breaks.size() + 2);
+ points.push_back(cstart);
+ std::ranges::transform(
+ breaks, std::back_inserter(points), [geoData, centre, radiusMid](const GeoData::WalkStepCurve & step) {
+ return (centre + (sincos(step.angle) * radiusMid))
+ || geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current)).z;
+ });
+ points.push_back(cend);
+ mergeClose(points, ::distance<3, GlobalDistance>, ::midpoint<3, GlobalDistance>, MIN_DISTANCE);
+ std::ranges::transform(points | std::views::pairwise, std::back_inserter(out), [this, centre](const auto pair) {
+ const auto [a, b] = pair;
+ return this->addLink<typename T::CurveLink>(a, b, centre);
+ });
+ return out;
}
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addJoins(GlobalPosition3D start, GlobalPosition3D end)
+NetworkOf<T, Links...>::addJoins(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end)
{
- if (glm::length(RelativePosition3D(start - end)) < 2000.F) {
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ if (::distance(start, end) < MIN_DISTANCE) {
return {};
}
const auto defs = genCurveDef(start, end, findNodeDirection(nodeAt(start)), findNodeDirection(nodeAt(end)));
- const auto & [c1s, c1e, c1c] = defs.first;
- const auto & [c2s, c2e, c2c] = defs.second;
- return {addLink<typename T::CurveLink>(c1s, c1e, c1c), addLink<typename T::CurveLink>(c2s, c2e, c2c)};
+ return addCurve(geoData, defs.first) + addCurve(geoData, defs.second);
}
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addExtend(GlobalPosition3D start, GlobalPosition3D end)
+NetworkOf<T, Links...>::addExtend(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end)
{
- const auto [cstart, cend, centre] = genCurveDef(start, end, findNodeDirection(nodeAt(start)));
- return {addLink<typename T::CurveLink>(cstart, cend, centre)};
+ return addCurve(geoData, genCurveDef(start, end, findNodeDirection(nodeAt(start))));
}
diff --git a/game/network/rail.cpp b/game/network/rail.cpp
index fd07ace..c0e597d 100644
--- a/game/network/rail.cpp
+++ b/game/network/rail.cpp
@@ -1,4 +1,5 @@
#include "rail.h"
+#include "game/gamestate.h"
#include "network.h"
#include <game/network/network.impl.h> // IWYU pragma: keep
#include <gfx/gl/sceneShader.h>
@@ -8,7 +9,7 @@
template class NetworkOf<RailLink, RailLinkStraight, RailLinkCurve>;
constexpr auto RAIL_CROSSSECTION_VERTICES {5U};
-constexpr Size3D RAIL_HEIGHT {0, 0, 250.F};
+constexpr Size3D RAIL_HEIGHT {0, 0, 50.F};
RailLinks::RailLinks() : NetworkOf<RailLink, RailLinkStraight, RailLinkCurve> {"rails.jpg"} { }
@@ -32,40 +33,39 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
}
// Find start link/end - opposite entry dir to existing link; so pi +...
const Angle dir = pi + findNodeDirection(node1ins.first);
- if (dir == vector_yaw(end - start)) {
+ if (dir == vector_yaw(difference(end, start))) {
return addLink<RailLinkStraight>(start, end);
}
const auto flatStart {start.xy()}, flatEnd {end.xy()};
if (node2ins.second == NodeIs::InNetwork) {
auto midheight = [&](auto mid) {
- const auto sm = glm::length(RelativePosition2D(flatStart - mid)),
- em = glm::length(RelativePosition2D(flatEnd - mid));
- return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (sm / (sm + em)));
+ const auto startToMid = ::distance<2>(flatStart, mid);
+ const auto endToMid = ::distance<2>(flatEnd, mid);
+ return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid)));
};
const float dir2 = pi + findNodeDirection(node2ins.first);
- if (const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); radii.first < radii.second) {
- const auto radius {radii.first};
- const auto c1 = flatStart + (sincosf(dir + half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(dir2 + half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
+ const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2);
+ if (radii.first < radii.second) {
+ const auto radius = radii.first;
+ const auto centre1 = flatStart + (sincos(dir + half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(dir2 + half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
const auto midh = mid || midheight(mid);
- addLink<RailLinkCurve>(start, midh, c1);
- return addLink<RailLinkCurve>(end, midh, c2);
- }
- else {
- const auto radius {radii.second};
- const auto c1 = flatStart + (sincosf(dir - half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(dir2 - half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
- const auto midh = mid || midheight(mid);
- addLink<RailLinkCurve>(midh, start, c1);
- return addLink<RailLinkCurve>(midh, end, c2);
+ addLink<RailLinkCurve>(start, midh, centre1);
+ return addLink<RailLinkCurve>(end, midh, centre2);
}
+ const auto radius = radii.second;
+ const auto centre1 = flatStart + (sincos(dir - half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(dir2 - half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
+ const auto midh = mid || midheight(mid);
+ addLink<RailLinkCurve>(midh, start, centre1);
+ return addLink<RailLinkCurve>(midh, end, centre2);
}
- const auto diff {end - start};
- const auto vy {vector_yaw(diff)};
- const auto n2ed {(vy * 2) - dir - pi};
- const auto centre {find_arc_centre(flatStart, dir, flatEnd, n2ed)};
+ const auto diff = difference(end, start);
+ const auto yaw = vector_yaw(diff);
+ const auto n2ed = (yaw * 2) - dir - pi;
+ const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed);
if (centre.second) { // right hand arc
std::swap(start, end);
@@ -73,53 +73,61 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
return addLink<RailLinkCurve>(start, end, centre.first);
}
-constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> railCrossSection {{
- {-1900.F, 0.F, 0.F},
- {-608.F, 0.F, RAIL_HEIGHT.z},
- {0, 0.F, RAIL_HEIGHT.z * .7F},
- {608.F, 0.F, RAIL_HEIGHT.z},
- {1900.F, 0.F, 0.F},
-}};
-constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> railTexturePos {
- 0.F,
- .34F,
- .5F,
- .66F,
- 1.F,
-};
-constexpr auto sleepers {5.F}; // There are 5 repetitions of sleepers in the texture
-
-inline auto
-round_sleepers(const float v)
-{
- return round_frac(v, sleepers);
+namespace {
+ constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> RAIL_CROSS_SECTION {{
+ {-1330.F, 0.F, 0},
+ {-608.F, 0.F, RAIL_HEIGHT.z},
+ {0, 0.F, RAIL_HEIGHT.z / 2},
+ {608.F, 0.F, RAIL_HEIGHT.z},
+ {1330.F, 0.F, 0},
+ }};
+ constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> RAIL_TEXTURE_POS {
+ 0.15F,
+ .34F,
+ .5F,
+ .66F,
+ 0.85F,
+ };
+ template<std::floating_point T> constexpr T SLEEPERS_PER_TEXTURE {5};
+ template<std::floating_point T> constexpr T TEXTURE_LENGTH {2'000};
+ template<std::floating_point T> constexpr T SLEEPER_LENGTH {T {1} / SLEEPERS_PER_TEXTURE<T>};
+
+ template<std::floating_point T>
+ constexpr auto
+ roundSleepers(const T length)
+ {
+ return round_frac(length / TEXTURE_LENGTH<T>, SLEEPER_LENGTH<T>);
+ }
}
-RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & a,
- const Node::Ptr & b) : RailLinkStraight(instances, a, b, b->pos - a->pos)
+RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB) : RailLinkStraight(instances, nodeA, nodeB, nodeB->pos - nodeA->pos)
{
}
-RailLinkStraight::RailLinkStraight(
- NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr a, Node::Ptr b, const RelativePosition3D & diff) :
- Link({std::move(a), vector_yaw(diff)}, {std::move(b), vector_yaw(-diff)}, glm::length(diff)),
+RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr nodeA, Node::Ptr nodeB,
+ const RelativePosition3D & diff) :
+ Link({.node = std::move(nodeA), .dir = vector_yaw(diff)}, {.node = std::move(nodeB), .dir = vector_yaw(-diff)},
+ glm::length(diff)),
instance {instances.vertices.acquire(
- ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), round_sleepers(length / 2000.F))}
+ ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), roundSleepers(length))}
{
}
-RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b,
- GlobalPosition2D c) : RailLinkCurve(instances, a, b, c || a->pos.z, {c, a->pos, b->pos})
+RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB, GlobalPosition2D centre) :
+ RailLinkCurve(instances, nodeA, nodeB, centre || nodeA->pos.z, ::distance<2>(nodeA->pos.xy(), centre),
+ {centre, nodeA->pos, nodeB->pos})
{
}
-RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b,
- GlobalPosition3D c, const Arc arc) :
- Link({a, normalize(arc.first + half_pi)}, {b, normalize(arc.second - half_pi)},
- glm::length(RelativePosition3D(a->pos - c)) * arc.length()),
- LinkCurve {c, glm::length(RelativePosition3D(ends[0].node->pos - c)), arc},
- instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, c, round_sleepers(length / 2000.F),
- half_pi - arc.first, half_pi - arc.second, radius)}
+RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB, GlobalPosition3D centre, RelativeDistance radius, const Arc arc) :
+ Link({.node = nodeA, .dir = normalize(arc.first + half_pi)},
+ {.node = nodeB, .dir = normalize(arc.second - half_pi)},
+ glm::length(RelativePosition2D {radius * arc.length(), difference(nodeA->pos, nodeB->pos).z})),
+ LinkCurve {centre, radius, arc}, instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, centre,
+ roundSleepers(length), half_pi - arc.first, half_pi - arc.second, radius)}
{
}
@@ -148,23 +156,39 @@ template<> NetworkLinkHolder<RailLinkCurve>::NetworkLinkHolder()
namespace {
template<typename LinkType>
void
- renderType(const NetworkLinkHolder<LinkType> & n, auto & s)
+ renderType(const NetworkLinkHolder<LinkType> & networkLinks, auto & shader)
{
- if (auto count = n.vertices.size()) {
- s.use(railCrossSection, railTexturePos);
- glBindVertexArray(n.vao);
+ if (auto count = networkLinks.vertices.size()) {
+ shader.use(RAIL_CROSS_SECTION, RAIL_TEXTURE_POS);
+ glBindVertexArray(networkLinks.vao);
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count));
}
};
}
void
-RailLinks::render(const SceneShader & shader) const
+RailLinks::render(const SceneShader & shader, const Frustum &) const
{
- if (!links.objects.empty()) {
+ if (!links.empty()) {
texture->bind();
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(-1, 0);
renderType<RailLinkStraight>(*this, shader.networkStraight);
renderType<RailLinkCurve>(*this, shader.networkCurve);
+ glDisable(GL_POLYGON_OFFSET_FILL);
glBindVertexArray(0);
}
}
+
+const Surface *
+RailLinks::getBaseSurface() const
+{
+ return gameState->assets.at("terrain.surface.gravel").dynamicCast<const Surface>().get();
+}
+
+RelativeDistance
+RailLinks::getBaseWidth() const
+{
+ static constexpr auto BASE_WIDTH = 5'700;
+ return BASE_WIDTH;
+}
diff --git a/game/network/rail.h b/game/network/rail.h
index c8effef..4aef9e3 100644
--- a/game/network/rail.h
+++ b/game/network/rail.h
@@ -62,8 +62,8 @@ public:
};
private:
- RailLinkCurve(
- NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D, const Arc);
+ RailLinkCurve(NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D centreBase,
+ RelativeDistance radius, Arc);
InstanceVertices<Vertex>::InstanceProxy instance;
};
@@ -75,7 +75,10 @@ public:
RailLinks();
std::shared_ptr<RailLink> addLinksBetween(GlobalPosition3D start, GlobalPosition3D end);
- void render(const SceneShader &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
+
+ [[nodiscard]] const Surface * getBaseSurface() const override;
+ [[nodiscard]] RelativeDistance getBaseWidth() const override;
private:
void tick(TickDuration elapsed) override;
diff --git a/game/orders.h b/game/orders.h
index ca5cfdb..840aa3c 100644
--- a/game/orders.h
+++ b/game/orders.h
@@ -5,7 +5,7 @@
class Objective;
-class Orders : public Collection<Objective> {
+class Orders : public SharedCollection<Objective> {
public:
[[nodiscard]] Objective * current() const;
Objective * next();
diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp
index 73d285f..140c4e5 100644
--- a/game/scenary/foliage.cpp
+++ b/game/scenary/foliage.cpp
@@ -2,8 +2,16 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/shadowMapper.h"
#include "gfx/gl/vertexArrayObject.h"
-#include "gfx/models/texture.h"
-#include "location.h"
+#include <location.h>
+
+static_assert(std::is_constructible_v<Foliage>);
+
+std::any
+Foliage::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(
+ instances.acquire(position.getRotationTransform(), position.rot.y, position.pos));
+}
bool
Foliage::persist(Persistence::PersistenceStore & store)
@@ -16,11 +24,22 @@ Foliage::postLoad()
{
texture = getTexture();
bodyMesh->configureVAO(instanceVAO)
- .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1);
+ .addAttribs<LocationVertex, &LocationVertex::rotation, &LocationVertex::position>(
+ instances.bufferName(), 1);
+ VertexArrayObject {instancePointVAO}.addAttribs<LocationVertex, &LocationVertex::position, &LocationVertex::yaw>(
+ instances.bufferName());
+}
+
+void
+Foliage::updateStencil(const ShadowStenciller & ss) const
+{
+ if (instances.size() > 0) {
+ ss.renderStencil(shadowStencil, *bodyMesh, texture);
+ }
}
void
-Foliage::render(const SceneShader & shader) const
+Foliage::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = instances.size()) {
shader.basicInst.use();
@@ -32,13 +51,15 @@ Foliage::render(const SceneShader & shader) const
}
void
-Foliage::shadows(const ShadowMapper & mapper) const
+Foliage::shadows(const ShadowMapper & mapper, const Frustum &) const
{
if (const auto count = instances.size()) {
- mapper.dynamicPointInstWithTextures.use();
- if (texture) {
- texture->bind(GL_TEXTURE3);
- }
- bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count));
+ const auto dimensions = bodyMesh->getDimensions();
+ mapper.stencilShadowProgram.use(dimensions.centre, dimensions.size);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowStencil);
+ glBindVertexArray(instancePointVAO);
+ glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count));
+ glBindVertexArray(0);
}
}
diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h
index 3beda89..d15a8b0 100644
--- a/game/scenary/foliage.h
+++ b/game/scenary/foliage.h
@@ -2,23 +2,34 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/gl/shadowStenciller.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
class SceneShader;
class ShadowMapper;
class Location;
-class Texture;
class Foliage : public Asset, public Renderable, public StdTypeDefs<Foliage> {
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
glVertexArray instanceVAO;
+ glVertexArray instancePointVAO;
public:
- using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>;
+ [[nodiscard]] std::any createAt(const Location &) const override;
+
+ struct LocationVertex {
+ glm::mat3 rotation;
+ float yaw;
+ GlobalPosition3D position;
+ };
+
mutable InstanceVertices<LocationVertex> instances;
- void render(const SceneShader &) const override;
- void shadows(const ShadowMapper &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
+ void updateStencil(const ShadowStenciller &) const override;
+ glTexture shadowStencil = ShadowStenciller::createStencilTexture(256, 256);
protected:
friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>;
diff --git a/game/scenary/illuminator.cpp b/game/scenary/illuminator.cpp
index e3810ec..d8e4c4e 100644
--- a/game/scenary/illuminator.cpp
+++ b/game/scenary/illuminator.cpp
@@ -2,6 +2,16 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/vertexArrayObject.h"
#include "gfx/models/texture.h" // IWYU pragma: keep
+#include <location.h>
+
+static_assert(std::is_constructible_v<Illuminator>);
+
+std::any
+Illuminator::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(
+ instances.acquire(position.getRotationTransform(), position.pos));
+}
bool
Illuminator::SpotLight::persist(Persistence::PersistenceStore & store)
@@ -59,7 +69,7 @@ Illuminator::postLoad()
}
void
-Illuminator::render(const SceneShader & shader) const
+Illuminator::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = instances.size()) {
shader.basicInst.use();
diff --git a/game/scenary/illuminator.h b/game/scenary/illuminator.h
index cd6073c..200ba40 100644
--- a/game/scenary/illuminator.h
+++ b/game/scenary/illuminator.h
@@ -2,19 +2,21 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
class SceneShader;
class Location;
-class Texture;
class Illuminator : public Asset, public Renderable, public StdTypeDefs<Illuminator> {
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
glVertexArray instanceVAO;
std::optional<glVertexArray> instancesSpotLightVAO, instancesPointLightVAO;
public:
+ [[nodiscard]] std::any createAt(const Location &) const override;
+
struct LightCommonVertex {
RelativePosition3D position;
RGB colour;
@@ -45,7 +47,7 @@ public:
mutable InstanceVertices<LocationVertex> instances;
mutable InstanceVertices<SpotLightVertex> instancesSpotLight;
mutable InstanceVertices<PointLightVertex> instancesPointLight;
- void render(const SceneShader &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
void lights(const SceneShader &) const override;
protected:
diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp
index b39c28b..2006225 100644
--- a/game/scenary/plant.cpp
+++ b/game/scenary/plant.cpp
@@ -2,6 +2,7 @@
#include "location.h"
Plant::Plant(std::shared_ptr<const Foliage> type, const Location & position) :
- type {std::move(type)}, location {this->type->instances.acquire(position.getRotationTransform(), position.pos)}
+ type {std::move(type)},
+ location {this->type->instances.acquire(position.getRotationTransform(), position.rot.y, position.pos)}
{
}
diff --git a/game/terrain.cpp b/game/terrain.cpp
index 3b16e79..f10aac6 100644
--- a/game/terrain.cpp
+++ b/game/terrain.cpp
@@ -1,65 +1,118 @@
#include "terrain.h"
-#include "game/geoData.h"
-#include "gfx/models/texture.h"
+#include "gfx/frustum.h"
#include <algorithm>
-#include <cstddef>
#include <gfx/gl/sceneShader.h>
#include <gfx/gl/shadowMapper.h>
#include <gfx/image.h>
#include <gfx/models/mesh.h>
#include <gfx/models/vertex.h>
+#include <glMappedBufferWriter.h>
#include <glm/glm.hpp>
-#include <iterator>
#include <location.h>
#include <maths.h>
#include <utility>
#include <vector>
-static constexpr RGB openSurface {-1};
-
-Terrain::Terrain(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, grass {std::make_shared<Texture>("grass.png")}
-{
- generateMeshes();
-}
+static constexpr RGB OPEN_SURFACE {-1};
+static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide
template<>
VertexArrayObject &
VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, const GLuint divisor)
{
- return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal, &Terrain::Vertex::colourBias>(
- arrayBuffer, divisor);
+ return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal>(arrayBuffer, divisor);
}
-void
-Terrain::generateMeshes()
+bool
+Terrain::SurfaceKey::operator<(const SurfaceKey & other) const
{
- std::vector<unsigned int> indices;
- indices.reserve(geoData->n_faces() * 3);
- std::vector<Vertex> vertices;
- vertices.reserve(geoData->n_vertices());
- std::map<std::pair<GeoData::VertexHandle, const Surface *>, size_t> vertexIndex;
- std::for_each(geoData->vertices_sbegin(), geoData->vertices_end(),
- [this, &vertexIndex, &vertices](const GeoData::VertexHandle v) {
- std::for_each(geoData->vf_begin(v), geoData->vf_end(v),
- [&vertexIndex, v, this, &vertices](const GeoData::FaceHandle f) {
- const auto surface = geoData->get_surface(f);
- if (const auto vertexIndexRef = vertexIndex.emplace(std::make_pair(v, surface), 0);
- vertexIndexRef.second) {
- vertexIndexRef.first->second = vertices.size();
+ return std::tie(surface, basePosition.x, basePosition.y)
+ < std::tie(other.surface, other.basePosition.x, other.basePosition.y);
+}
- vertices.emplace_back(geoData->point(v), geoData->normal(v),
- surface ? surface->colorBias : openSurface);
- }
- });
- });
- std::for_each(
- geoData->faces_sbegin(), geoData->faces_end(), [this, &vertexIndex, &indices](const GeoData::FaceHandle f) {
- std::transform(geoData->fv_begin(f), geoData->fv_end(f), std::back_inserter(indices),
- [&vertexIndex, f, this](const GeoData::VertexHandle v) {
- return vertexIndex[std::make_pair(v, geoData->get_surface(f))];
- });
+inline void
+Terrain::copyVerticesToBuffer() const
+{
+ std::ranges::transform(all_vertices(), glMappedBufferWriter<Vertex> {GL_ARRAY_BUFFER, verticesBuffer, n_vertices()},
+ [this](const auto & vertex) {
+ return Vertex {point(vertex), normal(vertex)};
});
- meshes.create<MeshT<Vertex>>(vertices, indices);
+}
+
+inline GlobalPosition2D
+Terrain::getTile(const FaceHandle & face) const
+{
+ return point(*cfv_begin(face)).xy() / TILE_SIZE;
+};
+
+Terrain::SurfaceIndices
+Terrain::mapSurfaceFacesToIndices() const
+{
+ SurfaceIndices surfaceIndices;
+ const auto indexBySurfaceAndTile = std::views::transform([this](const auto & faceItr) {
+ return std::pair<SurfaceKey, FaceHandle> {{getSurface(*faceItr), getTile(*faceItr)}, *faceItr};
+ });
+ const auto chunkBySurfaceAndTile = std::views::chunk_by([](const auto & face1, const auto & face2) {
+ return face1.first.surface == face2.first.surface && face1.first.basePosition == face2.first.basePosition;
+ });
+ for (const auto & faceRange : faces() | indexBySurfaceAndTile | chunkBySurfaceAndTile) {
+ const SurfaceKey & surfaceKey = faceRange.front().first;
+ auto indexItr = surfaceIndices.find(surfaceKey);
+ if (indexItr == surfaceIndices.end()) {
+ indexItr = surfaceIndices.emplace(surfaceKey, std::vector<GLuint> {}).first;
+ if (auto existing = meshes.find(surfaceKey); existing != meshes.end()) {
+ indexItr->second.reserve(static_cast<size_t>(existing->second.count));
+ }
+ }
+ for (auto push = std::back_inserter(indexItr->second); const auto & [_, face] : faceRange) {
+ std::ranges::transform(fv_range(face), push, &OpenMesh::VertexHandle::idx);
+ }
+ }
+ return surfaceIndices;
+}
+
+void
+Terrain::copyIndicesToBuffers(const SurfaceIndices & surfaceIndices)
+{
+ for (const auto & [surfaceKey, indices] : surfaceIndices) {
+ auto meshItr = meshes.find(surfaceKey);
+ if (meshItr == meshes.end()) {
+ meshItr = meshes.emplace(surfaceKey, SurfaceArrayBuffer {}).first;
+ VertexArrayObject {meshItr->second.vertexArray}
+ .addAttribsFor<Vertex>(verticesBuffer)
+ .addIndices(meshItr->second.indicesBuffer, indices)
+ .data(verticesBuffer, GL_ARRAY_BUFFER);
+ }
+ else {
+ VertexArrayObject {meshItr->second.vertexArray}
+ .addIndices(meshItr->second.indicesBuffer, indices)
+ .data(verticesBuffer, GL_ARRAY_BUFFER);
+ }
+ meshItr->second.count = static_cast<GLsizei>(indices.size());
+ meshItr->second.aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints(
+ indices | std::views::transform([this](const auto vertex) {
+ return this->point(VertexHandle {static_cast<int>(vertex)});
+ }));
+ }
+}
+
+void
+Terrain::pruneOrphanMeshes(const SurfaceIndices & surfaceIndices)
+{
+ if (meshes.size() > surfaceIndices.size()) {
+ std::erase_if(meshes, [&surfaceIndices](const auto & mesh) {
+ return !surfaceIndices.contains(mesh.first);
+ });
+ }
+}
+
+void
+Terrain::generateMeshes()
+{
+ copyVerticesToBuffer();
+ const auto surfaceIndices = mapSurfaceFacesToIndices();
+ copyIndicesToBuffers(surfaceIndices);
+ pruneOrphanMeshes(surfaceIndices);
}
void
@@ -68,16 +121,41 @@ Terrain::tick(TickDuration)
}
void
-Terrain::render(const SceneShader & shader) const
+Terrain::afterChange()
+{
+ generateMeshes();
+}
+
+void
+Terrain::render(const SceneShader & shader, const Frustum & frustum) const
{
- shader.landmass.use();
grass->bind();
- meshes.apply(&Mesh::Draw);
+
+ const auto chunkBySurface = std::views::chunk_by([](const auto & itr1, const auto & itr2) {
+ return itr1.first.surface == itr2.first.surface;
+ });
+ for (const auto & surfaceRange : meshes | chunkBySurface) {
+ const auto surface = surfaceRange.front().first.surface;
+ shader.landmass.use(surface ? surface->colorBias : OPEN_SURFACE);
+ for (const auto & sab : surfaceRange) {
+ if (frustum.contains(sab.second.aabb)) {
+ glBindVertexArray(sab.second.vertexArray);
+ glDrawElements(GL_TRIANGLES, sab.second.count, GL_UNSIGNED_INT, nullptr);
+ }
+ }
+ }
+ glBindVertexArray(0);
}
void
-Terrain::shadows(const ShadowMapper & shadowMapper) const
+Terrain::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const
{
shadowMapper.landmess.use();
- meshes.apply(&Mesh::Draw);
+ for (const auto & [surface, sab] : meshes) {
+ if (frustum.shadedBy(sab.aabb)) {
+ glBindVertexArray(sab.vertexArray);
+ glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr);
+ }
+ }
+ glBindVertexArray(0);
}
diff --git a/game/terrain.h b/game/terrain.h
index d088f89..1a63296 100644
--- a/game/terrain.h
+++ b/game/terrain.h
@@ -1,36 +1,57 @@
#pragma once
#include "chronology.h"
-#include "collection.h"
#include "config/types.h"
#include "game/worldobject.h"
-#include <gfx/models/mesh.h>
-#include <gfx/renderable.h>
-#include <memory>
+#include "geoData.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
class SceneShader;
-class Texture;
-class GeoData;
-class Terrain : public WorldObject, public Renderable {
+class Terrain : public GeoData, public WorldObject, public Renderable {
public:
- explicit Terrain(std::shared_ptr<GeoData>);
+ template<typename... P> explicit Terrain(P &&... params) : GeoData {std::forward<P>(params)...}
+ {
+ generateMeshes();
+ }
- void render(const SceneShader & shader) const override;
- void shadows(const ShadowMapper &) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
void tick(TickDuration) override;
struct Vertex {
GlobalPosition3D pos;
Normal3D normal;
- RGB colourBias;
};
-private:
void generateMeshes();
- std::shared_ptr<GeoData> geoData;
- Collection<MeshT<Vertex>, false> meshes;
- std::shared_ptr<Texture> grass;
+private:
+ void afterChange() override;
+
+ struct SurfaceArrayBuffer {
+ glVertexArray vertexArray;
+ glBuffer indicesBuffer;
+ GLsizei count;
+ AxisAlignedBoundingBox<GlobalDistance> aabb;
+ };
+
+ struct SurfaceKey {
+ const Surface * surface;
+ GlobalPosition2D basePosition;
+ inline bool operator<(const SurfaceKey &) const;
+ };
+
+ using SurfaceIndices = std::map<SurfaceKey, std::vector<GLuint>>;
+ void copyVerticesToBuffer() const;
+ [[nodiscard]] SurfaceIndices mapSurfaceFacesToIndices() const;
+ void copyIndicesToBuffers(const SurfaceIndices &);
+ void pruneOrphanMeshes(const SurfaceIndices &);
+ [[nodiscard]] inline GlobalPosition2D getTile(const FaceHandle &) const;
+
+ glBuffer verticesBuffer;
+ std::map<SurfaceKey, SurfaceArrayBuffer> meshes;
+ Texture::Ptr grass = std::make_shared<Texture>("grass.png");
};
diff --git a/game/vehicles/linkHistory.cpp b/game/vehicles/linkHistory.cpp
index e6bab36..77840ed 100644
--- a/game/vehicles/linkHistory.cpp
+++ b/game/vehicles/linkHistory.cpp
@@ -1,17 +1,27 @@
#include "linkHistory.h"
#include "game/network/link.h"
#include <memory>
+#include <optional>
LinkHistory::Entry
LinkHistory::add(const Link::WPtr & l, unsigned char d)
{
+ constexpr auto HISTORY_KEEP_LENGTH = 500'000.F;
+ while (const auto newLength = [this]() -> std::optional<decltype(totalLen)> {
+ if (!links.empty()) {
+ const auto newLength = totalLen - links.back().first.lock()->length;
+ if (newLength >= HISTORY_KEEP_LENGTH) {
+ return newLength;
+ }
+ }
+ return std::nullopt;
+ }()) {
+ totalLen = newLength.value();
+ links.pop_back();
+ }
links.insert(links.begin(), {l, d});
const auto lp = l.lock();
totalLen += lp->length;
- while (totalLen >= 1000000.F && !links.empty()) {
- totalLen -= links.back().first.lock()->length;
- links.pop_back();
- }
return {lp, d};
}
diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp
index 59d1e83..4a0a22d 100644
--- a/game/vehicles/railVehicle.cpp
+++ b/game/vehicles/railVehicle.cpp
@@ -72,7 +72,12 @@ RailVehicle::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & bary
}};
return std::any_of(
triangles.begin(), triangles.end(), [&cornerVertices, &ray, &baryPos, &distance](const auto & idx) {
- return ray.intersectTriangle(
- cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]], baryPos, distance);
+ if (const auto inter = ray.intersectTriangle(
+ cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]])) {
+ baryPos = inter->bary;
+ distance = inter->distance;
+ return true;
+ };
+ return false;
});
}
diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp
index 34c1359..21f01c8 100644
--- a/game/vehicles/railVehicleClass.cpp
+++ b/game/vehicles/railVehicleClass.cpp
@@ -17,6 +17,19 @@ RailVehicleClass::persist(Persistence::PersistenceStore & store)
&& STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store);
}
+std::any
+RailVehicleClass::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(instances.acquire(LocationVertex {
+ .body = position.getRotationTransform(),
+ .front = position.getRotationTransform(),
+ .back = position.getRotationTransform(),
+ .bodyPos = position.pos,
+ .frontPos = {sincos(position.rot.x) * wheelBase * 0.5F, position.pos.z},
+ .backPos = {sincos(position.rot.x) * wheelBase * -0.5F, position.pos.z},
+ }));
+}
+
void
RailVehicleClass::postLoad()
{
@@ -33,7 +46,7 @@ RailVehicleClass::postLoad()
}
void
-RailVehicleClass::render(const SceneShader & shader) const
+RailVehicleClass::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = static_cast<GLsizei>(instances.size())) {
if (texture) {
@@ -47,7 +60,7 @@ RailVehicleClass::render(const SceneShader & shader) const
}
void
-RailVehicleClass::shadows(const ShadowMapper & mapper) const
+RailVehicleClass::shadows(const ShadowMapper & mapper, const Frustum &) const
{
if (const auto count = static_cast<GLsizei>(instances.size())) {
mapper.dynamicPointInst.use();
diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h
index 9d9d4c2..ccff3e2 100644
--- a/game/vehicles/railVehicleClass.h
+++ b/game/vehicles/railVehicleClass.h
@@ -3,20 +3,21 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
#include <array>
#include <memory>
-#include <string>
class SceneShader;
class ShadowMapper;
-class Texture;
class Location;
class RailVehicleClass : public Renderable, public Asset {
public:
- void render(const SceneShader & shader) const override;
- void shadows(const ShadowMapper & shadowMapper) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
+ void shadows(const ShadowMapper & shadowMapper, const Frustum &) const override;
+
+ [[nodiscard]] std::any createAt(const Location &) const override;
struct LocationVertex {
glm::mat3 body, front, back;
@@ -25,7 +26,7 @@ public:
std::array<Mesh::Ptr, 2> bogies;
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
float wheelBase;
float length;
float maxSpeed;
diff --git a/game/vehicles/train.cpp b/game/vehicles/train.cpp
index 5bddd61..2461d9c 100644
--- a/game/vehicles/train.cpp
+++ b/game/vehicles/train.cpp
@@ -2,7 +2,6 @@
#include "game/vehicles/linkHistory.h"
#include "game/vehicles/railVehicle.h"
#include "game/vehicles/railVehicleClass.h"
-#include "gfx/renderable.h"
#include "location.h"
#include <algorithm>
#include <functional>
@@ -11,6 +10,9 @@
template<typename> class Ray;
+constexpr auto DECELERATION_RATE = 60000.F;
+constexpr auto ACCELERATIONS_RATE = 30000.F;
+
Location
Train::getBogiePosition(float linkDist, float dist) const
{
@@ -41,8 +43,8 @@ Train::doActivity(Go * go, TickDuration dur)
const auto maxSpeed = objects.front()->rvClass->maxSpeed;
if (go->dist) {
*go->dist -= speed * dur.count();
- if (*go->dist < (speed * speed) / 60.F) {
- speed -= std::min(speed, 30.F * dur.count());
+ if (*go->dist < (speed * speed) / DECELERATION_RATE) {
+ speed -= std::min(speed, ACCELERATIONS_RATE * dur.count());
}
else {
if (speed != maxSpeed) {
@@ -61,6 +63,6 @@ void
Train::doActivity(Idle *, TickDuration dur)
{
if (speed != 0.F) {
- speed -= std::min(speed, 30.F * dur.count());
+ speed -= std::min(speed, DECELERATION_RATE * dur.count());
}
}
diff --git a/game/vehicles/train.h b/game/vehicles/train.h
index 4320103..88e30f9 100644
--- a/game/vehicles/train.h
+++ b/game/vehicles/train.h
@@ -15,9 +15,9 @@ class SceneShader;
class ShadowMapper;
template<typename> class Ray;
-class Train : public Vehicle, public Collection<RailVehicle, false>, public Can<Go>, public Can<Idle> {
+class Train : public Vehicle, public UniqueCollection<RailVehicle>, public Can<Go>, public Can<Idle> {
public:
- explicit Train(const Link::Ptr & link, float linkDist = 0) : Vehicle {link, linkDist} { }
+ explicit Train(const Link::CPtr & link, float linkDist = 0) : Vehicle {link, linkDist} { }
[[nodiscard]] const Location &
getLocation() const override
diff --git a/game/vehicles/vehicle.cpp b/game/vehicles/vehicle.cpp
index 0d46017..dd652bc 100644
--- a/game/vehicles/vehicle.cpp
+++ b/game/vehicles/vehicle.cpp
@@ -15,7 +15,7 @@
#include <utility>
#include <vector>
-Vehicle::Vehicle(const Link::Ptr & l, float ld) : linkDist {ld}
+Vehicle::Vehicle(const Link::CPtr & l, float ld) : linkDist {ld}
{
linkHist.add(l, 0);
currentActivity = std::make_unique<Idle>();
diff --git a/game/vehicles/vehicle.h b/game/vehicles/vehicle.h
index 354f904..c3b35b7 100644
--- a/game/vehicles/vehicle.h
+++ b/game/vehicles/vehicle.h
@@ -14,7 +14,7 @@ class Location;
class Vehicle : public WorldObject, public Selectable {
public:
- explicit Vehicle(const Link::Ptr & link, float linkDist = 0);
+ explicit Vehicle(const Link::CPtr & link, float linkDist = 0);
float linkDist; // distance along current link
float speed {}; // speed in m/s (~75 km/h)
diff --git a/game/water.cpp b/game/water.cpp
index f720e3e..527e85a 100644
--- a/game/water.cpp
+++ b/game/water.cpp
@@ -82,7 +82,7 @@ Water::generateMeshes()
const auto pos = (p * TILE_SIZE) + GlobalPosition2D {x, y};
const auto v = vertexIndex.emplace(pos, vertices.size());
if (v.second) {
- const auto cpos = glm::clamp(pos, std::get<0>(extents).xy(), std::get<1>(extents).xy());
+ const auto cpos = glm::clamp(pos, extents.min.xy(), extents.max.xy());
vertices.emplace_back(geoData->positionAt(cpos));
}
*out++ = static_cast<unsigned int>(v.first->second);
@@ -102,7 +102,7 @@ Water::tick(TickDuration dur)
}
void
-Water::render(const SceneShader & shader) const
+Water::render(const SceneShader & shader, const Frustum &) const
{
shader.water.use(waveCycle);
water->bind();
diff --git a/game/water.h b/game/water.h
index ceb7bd2..07d9ae1 100644
--- a/game/water.h
+++ b/game/water.h
@@ -4,19 +4,19 @@
#include "collection.h"
#include "config/types.h"
#include "game/worldobject.h"
-#include <gfx/models/mesh.h>
-#include <gfx/renderable.h>
+#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
#include <memory>
class SceneShader;
-class Texture;
class GeoData;
class Water : public WorldObject, public Renderable {
public:
explicit Water(std::shared_ptr<GeoData>);
- void render(const SceneShader & shader) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
void tick(TickDuration) override;
float waveCycle {0.F};
@@ -29,6 +29,6 @@ private:
void generateMeshes();
std::shared_ptr<GeoData> geoData;
- Collection<MeshT<Vertex>, false> meshes;
- std::shared_ptr<Texture> water;
+ UniqueCollection<MeshT<Vertex>> meshes;
+ Texture::Ptr water;
};
diff --git a/gfx/aabb.h b/gfx/aabb.h
new file mode 100644
index 0000000..ce15a0f
--- /dev/null
+++ b/gfx/aabb.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "maths.h"
+#include <algorithm>
+#include <tuple>
+
+template<Arithmetic T, glm::qualifier Q = glm::defaultp> class AxisAlignedBoundingBox {
+public:
+ using V = glm::vec<3, T, Q>;
+ AxisAlignedBoundingBox() = default;
+
+ AxisAlignedBoundingBox(const V & min, const V & max) : min {min}, max {max} { }
+
+ AxisAlignedBoundingBox &
+ operator+=(const V & point)
+ {
+ min = glm::min(min, point);
+ max = glm::max(max, point);
+ return *this;
+ }
+
+ AxisAlignedBoundingBox
+ operator-(const V & viewPoint) const
+ {
+ return {min - viewPoint, max - viewPoint};
+ }
+
+ [[nodiscard]] static AxisAlignedBoundingBox
+ fromPoints(auto && points)
+ {
+ using Limits = std::numeric_limits<T>;
+ static constexpr const auto INITIAL = std::make_pair(V {Limits::max()}, V {Limits::min()});
+ return std::make_from_tuple<AxisAlignedBoundingBox<T, Q>>(
+ std::ranges::fold_left(points, INITIAL, [](const auto & prev, const auto & point) {
+ auto & [min, max] = prev;
+ return std::make_pair(glm::min(min, point), glm::max(max, point));
+ }));
+ }
+
+ V min {}, max {};
+};
diff --git a/gfx/gl/camera.cpp b/gfx/camera.cpp
index 82b11a6..3bb785d 100644
--- a/gfx/gl/camera.cpp
+++ b/gfx/camera.cpp
@@ -4,28 +4,39 @@
#include <maths.h>
#include <ray.h>
-Camera::Camera(GlobalPosition3D pos, Angle fov, Angle aspect, GlobalDistance zNear, GlobalDistance zFar) :
- position {pos}, forward {::north}, up {::up}, near {zNear}, far {zFar},
- projection {
- glm::perspective(fov, aspect, static_cast<RelativeDistance>(zNear), static_cast<RelativeDistance>(zFar))},
- viewProjection {}, inverseViewProjection {}
+Camera::Camera(GlobalPosition3D pos, Angle fov, Angle aspect, GlobalDistance near, GlobalDistance far) :
+ Camera {pos, fov, aspect, near, far, glm::lookAt({}, ::north, ::up),
+ glm::perspective(fov, aspect, static_cast<RelativeDistance>(near), static_cast<RelativeDistance>(far))}
{
- updateView();
+}
+
+Camera::Camera(GlobalPosition3D pos, Angle fov, Angle aspect, GlobalDistance near, GlobalDistance far,
+ const glm::mat4 & view, const glm::mat4 & projection) :
+ Frustum {pos, view, projection}, fov {fov}, aspect {aspect}, forward {::north}, up {::up}, near {near}, far {far}
+{
+}
+
+void
+Camera::setAspect(Angle aspect)
+{
+ projection = glm::perspective(fov, aspect, static_cast<RelativeDistance>(near), static_cast<RelativeDistance>(far));
+ Frustum::updateCache();
}
Ray<GlobalPosition3D>
Camera::unProject(const ScreenRelCoord & mouse) const
{
- static constexpr const glm::vec4 screen {0, 0, 1, 1};
- const auto mouseProjection = glm::lookAt({}, forward, up);
- return {position, glm::normalize(glm::unProject(mouse || 1.F, mouseProjection, projection, screen))};
+ static constexpr const glm::vec4 SCREEN {0, 0, 1, 1};
+ return {
+ .start = position,
+ .direction = glm::normalize(glm::unProject(mouse || 1.F, view, projection, SCREEN)),
+ };
}
void
Camera::updateView()
{
- viewProjection = projection * glm::lookAt({}, forward, up);
- inverseViewProjection = glm::inverse(viewProjection);
+ Frustum::updateView(glm::lookAt({}, forward, up));
}
Direction3D
diff --git a/gfx/gl/camera.h b/gfx/camera.h
index 8d53261..b17bcbb 100644
--- a/gfx/gl/camera.h
+++ b/gfx/camera.h
@@ -1,22 +1,19 @@
#pragma once
#include "config/types.h"
+#include "frustum.h"
#include <glm/glm.hpp>
#include <maths.h>
#include <ray.h>
-class Camera {
+class Camera : public Frustum {
public:
- Camera(GlobalPosition3D, Angle fov, Angle aspect, GlobalDistance zNear, GlobalDistance zFar);
-
- [[nodiscard]] glm::mat4
- getViewProjection() const
- {
- return viewProjection;
- }
+ Camera(GlobalPosition3D position, Angle fov, Angle aspect, GlobalDistance near, GlobalDistance far);
[[nodiscard]] Ray<GlobalPosition3D> unProject(const ScreenRelCoord &) const;
+ void setAspect(Angle aspect);
+
void
setPosition(const GlobalPosition3D & p)
{
@@ -64,24 +61,17 @@ public:
return forward;
}
- [[nodiscard]] auto
- getPosition() const
- {
- return position;
- }
-
[[nodiscard]] std::array<GlobalPosition4D, 4> extentsAtDist(GlobalDistance) const;
[[nodiscard]] static Direction3D upFromForward(const Direction3D & forward);
private:
+ Camera(GlobalPosition3D position, Angle fov, Angle aspect, GlobalDistance near, GlobalDistance far,
+ const glm::mat4 & view, const glm::mat4 & projection);
void updateView();
- GlobalPosition3D position;
+ Angle fov, aspect;
Direction3D forward;
Direction3D up;
-
GlobalDistance near, far;
- glm::mat4 projection;
- glm::mat4 viewProjection, inverseViewProjection;
};
diff --git a/gfx/followCameraController.cpp b/gfx/followCameraController.cpp
index 52dfb35..d7bbc0b 100644
--- a/gfx/followCameraController.cpp
+++ b/gfx/followCameraController.cpp
@@ -1,6 +1,6 @@
#include "followCameraController.h"
#include "game/vehicles/vehicle.h"
-#include <gfx/gl/camera.h>
+#include <gfx/camera.h>
#include <glm/glm.hpp>
#include <location.h>
#include <maths.h>
@@ -24,7 +24,7 @@ FollowCameraController::updateCamera(Camera * camera) const
break;
case Mode::Ride:
- camera->setView(pos + (up * 4.8F), -sincosf(rot.y) || 0.F);
+ camera->setView(pos + (up * 4.8F), -sincos(rot.y) || 0.F);
break;
case Mode::ISO:
diff --git a/gfx/frustum.cpp b/gfx/frustum.cpp
new file mode 100644
index 0000000..faa676d
--- /dev/null
+++ b/gfx/frustum.cpp
@@ -0,0 +1,67 @@
+#include "frustum.h"
+#include <algorithm>
+#include <collections.h>
+#include <glm/ext/matrix_transform.hpp>
+
+static constexpr auto PLANES = std::array {0, 1, 2} * std::array {-1.F, 1.F};
+
+Frustum::Frustum(const GlobalPosition3D & pos, const glm::mat4 & view, const glm::mat4 & projection) :
+ position {pos}, view {view}, projection {projection}, viewProjection {}, inverseViewProjection {}, planes {}
+{
+ updateCache();
+}
+
+void
+Frustum::updateView(const glm::mat4 & newView)
+{
+ view = newView;
+ updateCache();
+}
+
+bool
+Frustum::contains(const BoundingBox & aabb) const
+{
+ return boundByPlanes(aabb, FACES);
+}
+
+bool
+Frustum::shadedBy(const BoundingBox & aabb) const
+{
+ return boundByPlanes(aabb, FACES - 1);
+}
+
+bool
+Frustum::boundByPlanes(const BoundingBox & aabb, size_t nplanes) const
+{
+ static constexpr auto EXTENT_CORNER_IDXS = [] {
+ using Extent = GlobalPosition3D BoundingBox::*;
+ constexpr auto EXTENTS = std::array {&BoundingBox::min, &BoundingBox::max};
+ std::array<glm::vec<3, Extent>, 2ZU * 2ZU * 2ZU> out {};
+ std::ranges::copy(std::views::cartesian_product(EXTENTS, EXTENTS, EXTENTS)
+ | std::views::transform(
+ std::make_from_tuple<glm::vec<3, Extent>, std::tuple<Extent, Extent, Extent>>),
+ out.begin());
+ return out;
+ }();
+
+ const std::array<RelativePosition4D, 8> corners
+ = EXTENT_CORNER_IDXS * [relativeAabb = aabb - position](auto idxs) -> glm::vec4 {
+ return {(relativeAabb.*(idxs.x)).x, (relativeAabb.*(idxs.y)).y, (relativeAabb.*(idxs.z)).z, 1.F};
+ };
+ return std::ranges::none_of(std::span(planes).subspan(0, nplanes), [&corners](const auto & frustumPlane) {
+ return (std::ranges::all_of(corners, [&frustumPlane](const auto & corner) {
+ return glm::dot(frustumPlane, corner) < 0.F;
+ }));
+ });
+}
+
+void
+Frustum::updateCache()
+{
+ viewProjection = projection * view;
+ inverseViewProjection = glm::inverse(viewProjection);
+ std::ranges::transform(PLANES, planes.begin(), [vpt = glm::transpose(viewProjection)](const auto & idxs) {
+ const auto [idx, sgn] = idxs;
+ return vpt[3] + (vpt[idx] * sgn);
+ });
+}
diff --git a/gfx/frustum.h b/gfx/frustum.h
new file mode 100644
index 0000000..a2d90e9
--- /dev/null
+++ b/gfx/frustum.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "aabb.h"
+#include "config/types.h"
+#include <array>
+#include <glm/mat4x4.hpp>
+
+class Frustum {
+public:
+ Frustum(const GlobalPosition3D & pos, const glm::mat4 & view, const glm::mat4 & projection);
+
+ [[nodiscard]] auto &
+ getFrustumPlanes() const
+ {
+ return planes;
+ }
+
+ [[nodiscard]] auto &
+ getViewProjection() const
+ {
+ return viewProjection;
+ }
+
+ [[nodiscard]] auto
+ getPosition() const
+ {
+ return position;
+ }
+
+ void updateView(const glm::mat4 & view);
+
+ using BoundingBox = AxisAlignedBoundingBox<GlobalDistance>;
+ [[nodiscard]] bool contains(const BoundingBox &) const;
+ [[nodiscard]] bool shadedBy(const BoundingBox &) const;
+
+protected:
+ static constexpr size_t FACES = 6;
+ void updateCache();
+ [[nodiscard]] bool boundByPlanes(const BoundingBox &, size_t nplanes) const;
+
+ GlobalPosition3D position;
+ glm::mat4 view, projection;
+ glm::mat4 viewProjection, inverseViewProjection;
+ std::array<glm::vec4, FACES> planes;
+};
diff --git a/gfx/gl/program.cpp b/gfx/gl/program.cpp
index 7287fde..fdd4c6f 100644
--- a/gfx/gl/program.cpp
+++ b/gfx/gl/program.cpp
@@ -10,10 +10,10 @@ void
Program::linkAndValidate() const
{
glLinkProgram(m_program);
- Shader::CheckShaderError(m_program, GL_LINK_STATUS, true, "Error linking shader program");
+ checkProgramError(m_program, GL_LINK_STATUS, "Error linking shader program");
glValidateProgram(m_program);
- Shader::CheckShaderError(m_program, GL_VALIDATE_STATUS, true, "Invalid shader program");
+ checkProgramError(m_program, GL_VALIDATE_STATUS, "Invalid shader program");
}
void
@@ -22,6 +22,21 @@ Program::use() const
glUseProgram(m_program);
}
+void
+Program::checkProgramError(GLuint program, GLuint flag, std::string_view errorMessage) const
+{
+ GLint success = 0;
+
+ glGetProgramiv(program, flag, &success);
+
+ if (success == GL_FALSE) {
+ std::array<GLchar, 1024> error {};
+ glGetProgramInfoLog(program, error.size(), nullptr, error.data());
+
+ throw std::runtime_error {std::format("{}: '{}'", errorMessage, error.data())};
+ }
+}
+
Program::UniformLocation::UniformLocation(GLuint program, const char * name) :
location {glGetUniformLocation(program, name)}
{
diff --git a/gfx/gl/program.h b/gfx/gl/program.h
index 1a1c306..20be1aa 100644
--- a/gfx/gl/program.h
+++ b/gfx/gl/program.h
@@ -1,6 +1,6 @@
#pragma once
-#include "shader.h"
+#include "shader.h" // IWYU pragma: export
#include <glRef.h>
#include <glad/gl.h>
#include <glm/mat4x4.hpp>
@@ -12,6 +12,8 @@ using ProgramRef = glRef<GLuint, &glCreateProgram, &glDeleteProgram>;
class Program {
public:
+ Program() = delete;
+
template<typename... S> explicit Program(const S &... srcs)
{
(glAttachShader(m_program, srcs.compile()), ...);
@@ -31,6 +33,12 @@ public:
return location;
}
+ explicit
+ operator bool() const
+ {
+ return location >= 0;
+ }
+
protected:
GLint location;
};
@@ -47,6 +55,7 @@ public:
}
protected:
+ void checkProgramError(GLuint program, GLuint flag, std::string_view errorMessage) const;
void use() const;
void linkAndValidate() const;
ProgramRef m_program;
diff --git a/gfx/gl/sceneProvider.cpp b/gfx/gl/sceneProvider.cpp
index 2e8604c..3681c60 100644
--- a/gfx/gl/sceneProvider.cpp
+++ b/gfx/gl/sceneProvider.cpp
@@ -5,10 +5,10 @@ void
SceneProvider::environment(const SceneShader &, const SceneRenderer & renderer) const
{
renderer.setAmbientLight({0.5F, 0.5F, 0.5F});
- renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {-1, 1, -1}, *this);
+ renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {{-quarter_pi, quarter_pi}}, *this);
}
void
-SceneProvider::shadows(const ShadowMapper &) const
+SceneProvider::shadows(const ShadowMapper &, const Frustum &) const
{
}
diff --git a/gfx/gl/sceneProvider.h b/gfx/gl/sceneProvider.h
index f5e8e99..f6b7009 100644
--- a/gfx/gl/sceneProvider.h
+++ b/gfx/gl/sceneProvider.h
@@ -5,6 +5,7 @@
class SceneRenderer;
class ShadowMapper;
class SceneShader;
+class Frustum;
class SceneProvider {
public:
@@ -12,8 +13,8 @@ public:
virtual ~SceneProvider() = default;
DEFAULT_MOVE_COPY(SceneProvider);
- virtual void content(const SceneShader &) const = 0;
+ virtual void content(const SceneShader &, const Frustum &) const = 0;
virtual void environment(const SceneShader &, const SceneRenderer &) const;
virtual void lights(const SceneShader &) const = 0;
- virtual void shadows(const ShadowMapper &) const;
+ virtual void shadows(const ShadowMapper &, const Frustum &) const;
};
diff --git a/gfx/gl/sceneRenderer.cpp b/gfx/gl/sceneRenderer.cpp
index e0938f2..9a4d153 100644
--- a/gfx/gl/sceneRenderer.cpp
+++ b/gfx/gl/sceneRenderer.cpp
@@ -39,7 +39,7 @@ SceneRenderer::SceneRenderer(ScreenAbsCoord s, GLuint o) :
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
configuregdata(gPosition, {GL_RGB32I}, GL_RGB_INTEGER, GL_COLOR_ATTACHMENT0);
- configuregdata(gNormal, {GL_RGB8_SNORM, GL_RGB16F}, GL_RGB, GL_COLOR_ATTACHMENT1);
+ normaliFormat = configuregdata(gNormal, {GL_RGB8_SNORM, GL_RGB16F}, GL_RGB, GL_COLOR_ATTACHMENT1);
configuregdata(gAlbedoSpec, {GL_RGB8}, GL_RGB, GL_COLOR_ATTACHMENT2);
constexpr std::array<unsigned int, 3> attachments {
GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
@@ -57,12 +57,30 @@ SceneRenderer::SceneRenderer(ScreenAbsCoord s, GLuint o) :
}
void
+SceneRenderer::resize(ScreenAbsCoord newSize)
+{
+ size = newSize;
+ camera.setAspect(ratio(size));
+ const auto configuregdata = [this](const GLuint data, const GLint iformat, const GLenum format) {
+ glBindTexture(GL_TEXTURE_2D, data);
+ glTexImage2D(GL_TEXTURE_2D, 0, iformat, size.x, size.y, 0, format, GL_BYTE, nullptr);
+ };
+ configuregdata(gPosition, GL_RGB32I, GL_RGB_INTEGER);
+ configuregdata(gNormal, normaliFormat, GL_RGB);
+ configuregdata(gAlbedoSpec, GL_RGB8, GL_RGB);
+ configuregdata(gIllumination, GL_RGB8, GL_RGB);
+ glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.x, size.y);
+ shader.setViewPort({0, 0, size.x, size.y});
+}
+
+void
SceneRenderer::render(const SceneProvider & scene) const
{
shader.setViewProjection(camera.getPosition(), camera.getViewProjection());
glViewport(0, 0, size.x, size.y);
- // Geometry pass
+ // Geometry/colour pass - writes albedo, normal and position textures
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
@@ -71,9 +89,15 @@ SceneRenderer::render(const SceneProvider & scene) const
glEnable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- scene.content(shader);
+ scene.content(shader, camera);
- // Illumination pass
+ // Environment pass -
+ // * ambient - clears illumination texture - see setAmbientLight
+ // * directional - updates shadowMapper, reads normal and position, writes illumination - see setDirectionalLight
+ scene.environment(shader, *this);
+
+ // Scene lights pass -
+ // * per light - reads normal and position, writes illumination
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
glBlendFunc(GL_ONE, GL_ONE);
glActiveTexture(GL_TEXTURE0);
@@ -82,11 +106,10 @@ SceneRenderer::render(const SceneProvider & scene) const
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
- scene.environment(shader, *this);
glDisable(GL_DEPTH_TEST);
scene.lights(shader);
- // Lighting pass
+ // Composition pass - reads albedo and illumination, writes output
glBindFramebuffer(GL_FRAMEBUFFER, output);
glViewport(0, 0, size.x, size.y);
glCullFace(GL_BACK);
@@ -109,14 +132,22 @@ SceneRenderer::setAmbientLight(const RGB & colour) const
}
void
-SceneRenderer::setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider & scene) const
+SceneRenderer::setDirectionalLight(
+ const RGB & colour, const LightDirection & direction, const SceneProvider & scene) const
{
if (colour.r > 0 || colour.g > 0 || colour.b > 0) {
const auto lvp = shadowMapper.update(scene, direction, camera);
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
+ glBlendFunc(GL_ONE, GL_ONE);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, gPosition);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, gNormal);
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
glViewport(0, 0, size.x, size.y);
dirLight.use();
- dirLight.setDirectionalLight(colour, direction, camera.getPosition(), lvp);
+ dirLight.setDirectionalLight(colour, direction.vector(), camera.getPosition(), lvp);
renderQuad();
}
}
@@ -142,8 +173,7 @@ SceneRenderer::DirectionalLightProgram::setDirectionalLight(
return toTextureSpaceMat * m;
};
glUniform(colourLoc, c);
- const auto nd = glm::normalize(d);
- glUniform(directionLoc, nd);
+ glUniform(directionLoc, d);
glUniform(lightPointLoc, p);
glUniform(lightViewProjectionCountLoc, static_cast<GLuint>(lvp.size()));
glUniform(lightViewProjectionLoc, std::span<const glm::mat4> {lvp * toTextureSpace});
diff --git a/gfx/gl/sceneRenderer.h b/gfx/gl/sceneRenderer.h
index 4195bcf..31f0cda 100644
--- a/gfx/gl/sceneRenderer.h
+++ b/gfx/gl/sceneRenderer.h
@@ -1,20 +1,23 @@
#pragma once
-#include "camera.h"
+#include "gfx/lightDirection.h"
#include "glArrays.h"
#include "program.h"
#include "sceneProvider.h"
#include "sceneShader.h"
#include "shadowMapper.h"
+#include <gfx/camera.h>
#include <glm/fwd.hpp>
class SceneRenderer {
public:
explicit SceneRenderer(ScreenAbsCoord size, GLuint output);
+ void resize(ScreenAbsCoord size);
+
void render(const SceneProvider &) const;
void setAmbientLight(const RGB & colour) const;
- void setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider &) const;
+ void setDirectionalLight(const RGB & colour, const LightDirection & direction, const SceneProvider &) const;
Camera camera;
@@ -24,6 +27,7 @@ protected:
ScreenAbsCoord size;
GLuint output;
glFrameBuffer gBuffer, gBufferIll;
+ GLint normaliFormat;
glTexture gPosition, gNormal, gAlbedoSpec, gIllumination;
glRenderBuffer depth;
diff --git a/gfx/gl/sceneShader.cpp b/gfx/gl/sceneShader.cpp
index 4cbccb3..4b82ae4 100644
--- a/gfx/gl/sceneShader.cpp
+++ b/gfx/gl/sceneShader.cpp
@@ -34,9 +34,9 @@ SceneShader::allPrograms(auto member, auto &&... ps) const
}
SceneShader::SceneShader() :
- basicInst {dynamicPointInst_vs, material_fs}, landmass {landmass_vs, landmass_fs},
- absolute {fixedPoint_vs, material_fs}, spotLightInst {spotLight_vs, spotLight_gs, spotLight_fs},
- pointLightInst {pointLight_vs, pointLight_gs, pointLight_fs},
+ basicInst {dynamicPointInst_vs, material_fs}, absolute {fixedPoint_vs, material_fs},
+ spotLightInst {spotLight_vs, spotLight_gs, spotLight_fs},
+ pointLightInst {pointLight_vs, pointLight_gs, pointLight_fs}, landmass {landmass_vs, landmass_fs},
networkStraight {networkStraight_vs, networkStraight_gs, network_fs},
networkCurve {networkCurve_vs, networkCurve_gs, network_fs}
{
@@ -65,7 +65,7 @@ SceneShader::SceneProgram::setViewProjection(const GlobalPosition3D & viewPoint,
void
SceneShader::SceneProgram::setViewPort(const ViewPort & viewPort) const
{
- if (viewPortLoc >= 0) {
+ if (viewPortLoc) {
glUseProgram(*this);
glUniform(viewPortLoc, viewPort);
}
@@ -88,6 +88,13 @@ SceneShader::BasicProgram::use(Location const & location) const
}
void
+SceneShader::LandmassProgram::use(const glm::vec3 colourBias) const
+{
+ Program::use();
+ glUniform(colourBiasLos, colourBias);
+}
+
+void
SceneShader::NetworkProgram::use(
const std::span<const glm::vec3> profile, const std::span<const float> texturePos) const
{
diff --git a/gfx/gl/sceneShader.h b/gfx/gl/sceneShader.h
index 51f0e21..47d4397 100644
--- a/gfx/gl/sceneShader.h
+++ b/gfx/gl/sceneShader.h
@@ -50,6 +50,15 @@ class SceneShader {
RequiredUniformLocation profileLengthLoc {*this, "profileLength"};
};
+ class LandmassProgram : public AbsolutePosProgram {
+ public:
+ using AbsolutePosProgram::AbsolutePosProgram;
+ void use(const glm::vec3) const;
+
+ private:
+ RequiredUniformLocation colourBiasLos {*this, "colourBias"};
+ };
+
class WaterProgram : public SceneProgram {
public:
WaterProgram();
@@ -64,7 +73,8 @@ public:
BasicProgram basic;
WaterProgram water;
- AbsolutePosProgram basicInst, landmass, absolute, spotLightInst, pointLightInst;
+ AbsolutePosProgram basicInst, absolute, spotLightInst, pointLightInst;
+ LandmassProgram landmass;
NetworkProgram networkStraight, networkCurve;
void setViewProjection(const GlobalPosition3D & viewPoint, const glm::mat4 & viewProjection) const;
diff --git a/gfx/gl/shader.cpp b/gfx/gl/shader.cpp
index 0bc127a..9a4c270 100644
--- a/gfx/gl/shader.cpp
+++ b/gfx/gl/shader.cpp
@@ -1,4 +1,5 @@
#include "shader.h"
+#include "msgException.h"
#include <algorithm>
#include <array>
#include <format>
@@ -18,6 +19,39 @@ namespace {
constexpr std::array<std::tuple<std::string_view, GLenum, LookUpFunction>, 1> LOOKUPS {{
{"GL_MAX_GEOMETRY_OUTPUT_VERTICES", GL_MAX_GEOMETRY_OUTPUT_VERTICES, getInt},
}};
+
+ struct ShaderCompileError : public MsgException<std::invalid_argument> {
+ explicit ShaderCompileError(GLuint shader, Shader::Source src) :
+ MsgException<std::invalid_argument> {"Error compiling shader"}, shader {shader}, source {src},
+ msg {getShaderText(GL_INFO_LOG_LENGTH, glGetShaderInfoLog)}
+ {
+ }
+
+ [[nodiscard]] std::string
+ getMsg() const noexcept override
+ {
+ return std::format("Error compiling shader: '{}'\nSource:\n{}",
+ getShaderText(GL_INFO_LOG_LENGTH, glGetShaderInfoLog), source);
+ }
+
+ private:
+ std::string
+ getShaderText(GLenum param, auto getTextFunc) const
+ {
+ std::string text;
+ text.resize_and_overwrite(static_cast<size_t>(Shader::getShaderParam(shader, param)),
+ [this, getTextFunc](auto buf, auto len) {
+ GLsizei outLen {};
+ getTextFunc(shader, static_cast<GLsizei>(len), &outLen, buf);
+ return outLen;
+ });
+ return text;
+ }
+
+ const GLuint shader;
+ const Shader::Source source;
+ const std::string msg;
+ };
}
Shader::ShaderRef
@@ -29,8 +63,8 @@ Shader::compile() const
};
if (lookups) {
std::basic_string<GLchar> textMod {text};
- for (const auto & match : ctre::range<R"(\bGL_[A-Z_]+\b)">(textMod)) {
- if (const auto lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
+ for (const auto & match : ctre::search_all<R"(\bGL_[A-Z_]+\b)">(textMod)) {
+ if (const auto * const lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
[&match](const auto & lookup) {
return std::get<std::string_view>(lookup) == match;
});
@@ -46,31 +80,22 @@ Shader::compile() const
}
glCompileShader(shader);
- CheckShaderError(shader, GL_COMPILE_STATUS, false, "Error compiling shader!");
+ checkShaderError(shader);
return shader;
}
void
-Shader::CheckShaderError(GLuint shader, GLuint flag, bool isProgram, std::string_view errorMessage)
+Shader::checkShaderError(GLuint shader) const
{
- GLint success = 0;
-
- if (isProgram) {
- glGetProgramiv(shader, flag, &success);
+ if (getShaderParam(shader, GL_COMPILE_STATUS) == GL_FALSE) {
+ throw ShaderCompileError {shader, text};
}
- else {
- glGetShaderiv(shader, flag, &success);
- }
-
- if (success == GL_FALSE) {
- std::array<GLchar, 1024> error {};
- if (isProgram) {
- glGetProgramInfoLog(shader, error.size(), nullptr, error.data());
- }
- else {
- glGetShaderInfoLog(shader, error.size(), nullptr, error.data());
- }
+}
- throw std::runtime_error {std::format("{}: '{}'", errorMessage, error.data())};
- }
+GLint
+Shader::getShaderParam(GLuint shader, GLenum pname)
+{
+ GLint pvalue {};
+ glGetShaderiv(shader, pname, &pvalue);
+ return pvalue;
}
diff --git a/gfx/gl/shader.h b/gfx/gl/shader.h
index c6b45af..ce97734 100644
--- a/gfx/gl/shader.h
+++ b/gfx/gl/shader.h
@@ -7,6 +7,7 @@
class Shader {
public:
+ using Source = std::basic_string_view<GLchar>;
using ShaderRef = glRef<GLuint, &glCreateShader, &glDeleteShader>;
constexpr Shader(const GLchar * text, GLuint type) :
@@ -15,10 +16,13 @@ public:
}
[[nodiscard]] ShaderRef compile() const;
- static void CheckShaderError(GLuint shader, GLuint flag, bool isProgram, std::string_view errorMessage);
+
+ [[nodiscard]] static GLint getShaderParam(GLuint shader, GLenum pname);
private:
- const std::basic_string_view<GLchar> text;
+ void checkShaderError(GLuint shader) const;
+
+ const Source text;
GLuint type;
bool lookups;
};
diff --git a/gfx/gl/shaders/commonPoint.glsl b/gfx/gl/shaders/commonPoint.glsl
index 2d9e388..dc534d5 100644
--- a/gfx/gl/shaders/commonPoint.glsl
+++ b/gfx/gl/shaders/commonPoint.glsl
@@ -1,17 +1,4 @@
-layout(binding = 1) uniform usampler2DRect materialData;
-
-MaterialDetail
-getMaterialDetail(uint midx)
-{
- if (midx > 0u) {
- const vec4 sPosSize = texture(materialData, uvec2(0, midx - 1u));
- const uvec4 sMode = texture(materialData, uvec2(1, midx - 1u));
- const uint mapmodeU = sMode.x & 0xFu;
- const uint mapmodeV = (sMode.x & 0xF0u) >> 1;
- return MaterialDetail(sPosSize.xy, sPosSize.zw, uvec2(mapmodeU, mapmodeV));
- }
- return MaterialDetail(vec2(0, 0), vec2(0, 0), uvec2(0, 0));
-}
+include(`getMaterialDetail.glsl')
void
main()
diff --git a/gfx/gl/shaders/commonShadowPoint.glsl b/gfx/gl/shaders/commonShadowPoint.glsl
index c4ea827..9910d46 100644
--- a/gfx/gl/shaders/commonShadowPoint.glsl
+++ b/gfx/gl/shaders/commonShadowPoint.glsl
@@ -1,11 +1,10 @@
out vec4 vworldPos;
-ifdef(`TEXTURES', out vec2 vtexCoord;);
-
void
main()
{
vec3 worldPos = model * position;
vworldPos = vec4(worldPos - viewPoint + modelPos, 1);
- ifdef(`TEXTURES', vtexCoord = texCoord;);
+ ifdef(`TEXTURES', TexCoords = texCoord;);
+ ifdef(`TEXTURES', Material = getMaterialDetail(material););
}
diff --git a/gfx/gl/shaders/commonShadowPoint.gs b/gfx/gl/shaders/commonShadowPoint.gs
index b99bd20..2413cc0 100644
--- a/gfx/gl/shaders/commonShadowPoint.gs
+++ b/gfx/gl/shaders/commonShadowPoint.gs
@@ -1,13 +1,16 @@
#version 330 core
#extension GL_ARB_viewport_array : enable
+ifdef(`TEXTURES', include(`materialDetail.glsl'))
+
uniform mat4 viewProjection[4];
uniform int viewProjections;
in vec4 vworldPos[];
layout(triangles) in;
layout(triangle_strip, max_vertices = 12) out;
-ifdef(`TEXTURES', in vec2 vtexCoord[]; out vec2 texCoord;);
+ifdef(`TEXTURES', in vec2 TexCoords[]; out vec2 texCoord;)
+ifdef(`TEXTURES', flat in MaterialDetail Material[]; flat out MaterialDetail material;)
void
main()
@@ -17,7 +20,8 @@ main()
gl_Position = viewProjection[vp] * vworldPos[v];
gl_Position.z = max(gl_Position.z, -1);
gl_Layer = vp;
- ifdef(`TEXTURES', texCoord = vtexCoord[v];);
+ ifdef(`TEXTURES', texCoord = TexCoords[v];)
+ ifdef(`TEXTURES', material = Material[v];)
EmitVertex();
}
EndPrimitive();
diff --git a/gfx/gl/shaders/directionalLight.fs b/gfx/gl/shaders/directionalLight.fs
index 24457b8..cdf0389 100644
--- a/gfx/gl/shaders/directionalLight.fs
+++ b/gfx/gl/shaders/directionalLight.fs
@@ -17,27 +17,38 @@ uniform ivec3 lightPoint;
uniform mat4 lightViewProjection[MAX_MAPS];
uniform uint lightViewProjectionCount;
-const vec3 e1 = vec3(0, 0, 0), e2 = vec3(1, 1, 1);
+float
+getShadow(vec3 positionInLightSpace, float m, vec2 texelSize)
+{
+ float shadow = 0.0;
+ for (float x = -texelSize.x; x <= texelSize.x; x += texelSize.x) {
+ for (float y = -texelSize.y; y <= texelSize.y; y += texelSize.y) {
+ const float lightSpaceDepth = texture(shadowMap, vec3(positionInLightSpace.xy + vec2(x, y), m)).r;
+ shadow += step(positionInLightSpace.z, lightSpaceDepth + 0.001);
+ }
+ }
+ return shadow / 9.0;
+}
float
-insideShadowCube(vec3 v)
+insideShadowCube(vec3 v, vec2 texelSize)
{
- const vec3 s = step(e1, v) - step(e2, v);
+ const vec3 s = step(vec3(texelSize, 0), v) - step(vec3(1 - texelSize, 1), v);
return s.x * s.y * s.z;
}
float
isShaded(vec4 Position)
{
+ const vec2 texelSize = 1.0 / textureSize(shadowMap, 0).xy;
for (uint m = 0u; m < lightViewProjectionCount; m++) {
- const vec3 PositionInLightSpace = (lightViewProjection[m] * Position).xyz;
- const float inside = insideShadowCube(PositionInLightSpace);
+ const vec3 positionInLightSpace = (lightViewProjection[m] * Position).xyz;
+ const float inside = insideShadowCube(positionInLightSpace, texelSize);
if (inside > 0) {
- const float lightSpaceDepth = texture(shadowMap, vec3(PositionInLightSpace.xy, m)).r;
- return step(lightSpaceDepth, PositionInLightSpace.z);
+ return getShadow(positionInLightSpace, m, texelSize);
}
}
- return 0;
+ return 1;
}
void
@@ -46,5 +57,5 @@ main()
const vec4 Position = vec4(texture(gPosition, TexCoords).xyz - lightPoint, 1);
const vec3 Normal = texture(gNormal, TexCoords).rgb;
const float shaded = isShaded(Position);
- FragColor = (1 - shaded) * max(dot(-lightDirection, Normal) * lightColour, 0);
+ FragColor = shaded * max(dot(-lightDirection, Normal) * lightColour, 0);
}
diff --git a/gfx/gl/shaders/dynamicPoint.vs b/gfx/gl/shaders/dynamicPoint.vs
index 7551688..97d2983 100644
--- a/gfx/gl/shaders/dynamicPoint.vs
+++ b/gfx/gl/shaders/dynamicPoint.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/dynamicPointInst.vs b/gfx/gl/shaders/dynamicPointInst.vs
index 69eab0c..a85f9c9 100644
--- a/gfx/gl/shaders/dynamicPointInst.vs
+++ b/gfx/gl/shaders/dynamicPointInst.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/fixedPoint.vs b/gfx/gl/shaders/fixedPoint.vs
index 5cfe9b3..435b3d1 100644
--- a/gfx/gl/shaders/fixedPoint.vs
+++ b/gfx/gl/shaders/fixedPoint.vs
@@ -1,6 +1,8 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 1) uniform usampler2DRect materialData;
+
include(`meshIn.glsl')
include(`materialInterface.glsl')
diff --git a/gfx/gl/shaders/getMaterialDetail.glsl b/gfx/gl/shaders/getMaterialDetail.glsl
new file mode 100644
index 0000000..f819fb2
--- /dev/null
+++ b/gfx/gl/shaders/getMaterialDetail.glsl
@@ -0,0 +1,12 @@
+MaterialDetail
+getMaterialDetail(uint midx)
+{
+ if (midx > 0u) {
+ const vec4 sPosSize = texture(materialData, uvec2(0, midx - 1u));
+ const uvec4 sMode = texture(materialData, uvec2(1, midx - 1u));
+ const uint mapmodeU = sMode.x & 0xFu;
+ const uint mapmodeV = (sMode.x & 0xF0u) >> 1;
+ return MaterialDetail(sPosSize.xy, sPosSize.zw, uvec2(mapmodeU, mapmodeV));
+ }
+ return MaterialDetail(vec2(0, 0), vec2(0, 0), uvec2(0, 0));
+}
diff --git a/gfx/gl/shaders/landmass.fs b/gfx/gl/shaders/landmass.fs
index 55e3c24..382260e 100644
--- a/gfx/gl/shaders/landmass.fs
+++ b/gfx/gl/shaders/landmass.fs
@@ -3,10 +3,10 @@
include(`materialOut.glsl')
in vec3 FragPos;
in vec3 Normal;
-flat in vec3 ColourBias;
uniform sampler2D texture0;
uniform ivec3 viewPoint;
+uniform vec3 colourBias;
const vec3 grass = vec3(.1, .4, .05);
const vec3 slope = vec3(.6, .6, .4);
@@ -35,8 +35,8 @@ main()
vec3 color = texture(texture0, vec2(position.xy % 10000) / 10000.0).rgb;
int height = position.z;
- if (ColourBias.r >= 0) {
- color *= ColourBias;
+ if (colourBias.r >= 0) {
+ color *= colourBias;
}
else if (height < beachline) { // Sandy beach
color *= sand;
diff --git a/gfx/gl/shaders/landmass.vs b/gfx/gl/shaders/landmass.vs
index 9617cb9..44cb879 100644
--- a/gfx/gl/shaders/landmass.vs
+++ b/gfx/gl/shaders/landmass.vs
@@ -3,11 +3,9 @@
layout(location = 0) in ivec3 position;
layout(location = 1) in vec3 normal;
-layout(location = 2) in vec3 colourBias;
out vec3 FragPos;
out vec3 Normal;
-flat out vec3 ColourBias;
uniform mat4 viewProjection;
uniform ivec3 viewPoint;
@@ -17,7 +15,6 @@ main()
{
FragPos = position - viewPoint;
Normal = normal;
- ColourBias = colourBias;
gl_Position = viewProjection * vec4(FragPos, 1);
}
diff --git a/gfx/gl/shaders/material.fs b/gfx/gl/shaders/material.fs
index 5b93707..37f2e27 100644
--- a/gfx/gl/shaders/material.fs
+++ b/gfx/gl/shaders/material.fs
@@ -1,41 +1,11 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
+layout(binding = 0) uniform sampler2D textureAlbedo;
+
include(`materialInterface.glsl')
include(`materialOut.glsl')
-
-layout(binding = 0) 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(uvec2 mapmode, vec2 value)
-{
- return vec2(map(mapmode.x, value.x), map(mapmode.y, value.y));
-}
-
-vec4 getTextureColour(MaterialDetail mat, vec2 uv)
-{
- if (mat.textureSize.x > 0) {
- const vec2 tSize = textureSize(texture0, 0);
- uv = (mat.textureOrigin + mat.textureSize * map(mat.mapmode, uv)) / tSize;
- }
- return texture(texture0, uv);
-}
+include(`materialCommon.glsl')
void
main()
diff --git a/gfx/gl/shaders/materialCommon.glsl b/gfx/gl/shaders/materialCommon.glsl
new file mode 100644
index 0000000..3fe2237
--- /dev/null
+++ b/gfx/gl/shaders/materialCommon.glsl
@@ -0,0 +1,33 @@
+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(uvec2 mapmode, vec2 value)
+{
+ return vec2(map(mapmode.x, value.x), map(mapmode.y, value.y));
+}
+
+vec4
+getTextureColour(MaterialDetail mat, vec2 uv)
+{
+ if (mat.textureSize.x > 0) {
+ const vec2 tSize = textureSize(textureAlbedo, 0);
+ uv = (mat.textureOrigin + mat.textureSize * map(mat.mapmode, uv)) / tSize;
+ }
+ return texture(textureAlbedo, uv);
+}
diff --git a/gfx/gl/shaders/materialDetail.glsl b/gfx/gl/shaders/materialDetail.glsl
new file mode 100644
index 0000000..a208d50
--- /dev/null
+++ b/gfx/gl/shaders/materialDetail.glsl
@@ -0,0 +1,5 @@
+struct MaterialDetail {
+ vec2 textureOrigin;
+ vec2 textureSize;
+ uvec2 mapmode;
+};
diff --git a/gfx/gl/shaders/materialInterface.glsl b/gfx/gl/shaders/materialInterface.glsl
index 3a4796b..f2ca297 100644
--- a/gfx/gl/shaders/materialInterface.glsl
+++ b/gfx/gl/shaders/materialInterface.glsl
@@ -1,13 +1,9 @@
-struct MaterialDetail {
- vec2 textureOrigin;
- vec2 textureSize;
- uvec2 mapmode;
-};
+include(`materialDetail.glsl')
-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)
-MaterialDetail Material;
+define(INOUT, ifelse(TYPE, .fs, in, out));
+
+INOUT vec3 FragPos;
+INOUT vec2 TexCoords;
+INOUT vec3 Normal;
+INOUT vec4 Colour;
+flat INOUT MaterialDetail Material;
diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
index 90519e3..47ce9c0 100644
--- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
+++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs
@@ -1,14 +1,18 @@
#version 330 core
#extension GL_ARB_shading_language_420pack : enable
-layout(binding = 3) uniform sampler2D texture0;
+layout(binding = 3) uniform sampler2D textureAlbedo;
+
+include(`materialInterface.glsl')
+include(`materialCommon.glsl')
in vec2 texCoord;
+flat in MaterialDetail material;
void
main()
{
- if (texture(texture0, texCoord).a < 0.5) {
+ if (getTextureColour(material, texCoord).a < 0.5) {
discard;
}
gl_FragDepth = gl_FragCoord.z;
diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
index 27ad9d7..a76c87f 100644
--- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
+++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs
@@ -1,3 +1,15 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 4) uniform usampler2DRect materialData;
+
define(`TEXTURES', 1)
+include(`materialInterface.glsl')
+include(`getMaterialDetail.glsl')
+include(`meshIn.glsl')
+
+uniform ivec3 viewPoint;
+layout(location = 5) in mat3 model;
+layout(location = 8) in ivec3 modelPos;
-include(`shadowDynamicPointInst.vs')
+include(`commonShadowPoint.glsl')
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.fs b/gfx/gl/shaders/shadowDynamicPointStencil.fs
new file mode 100644
index 0000000..fe91b07
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.fs
@@ -0,0 +1,16 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2DArray stencilDepth;
+flat in vec3 scale;
+in vec3 texCoord;
+
+void
+main()
+{
+ float stDepth = texture(stencilDepth, texCoord).r;
+ if (stDepth >= 1) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z + ((stDepth - 0.5) * scale.z);
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.gs b/gfx/gl/shaders/shadowDynamicPointStencil.gs
new file mode 100644
index 0000000..7e81d97
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.gs
@@ -0,0 +1,36 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+const vec2[] corners = vec2[4](vec2(-1, -1), vec2(-1, 1), vec2(1, -1), vec2(1, 1));
+const float tau = 6.28318531;
+
+uniform mat4 viewProjection[4];
+uniform int viewProjections;
+uniform vec3 sizes[4];
+uniform float size;
+
+in float vmodelYaw[];
+in ivec3 vworldPos[];
+
+flat out vec3 scale;
+out vec3 texCoord;
+
+layout(points) in;
+layout(triangle_strip, max_vertices = 16) out;
+
+void
+main()
+{
+ int viewAngle = int(round(4.0 + (vmodelYaw[0] / tau))) % 8;
+ for (gl_Layer = 0; gl_Layer < viewProjections; ++gl_Layer) {
+ scale = 2.0 * size / sizes[gl_Layer];
+ vec4 pos = viewProjection[gl_Layer] * vec4(vworldPos[0], 1);
+ for (int c = 0; c < corners.length(); ++c) {
+ gl_Position = pos + vec4(scale.xy * corners[c], 0, 0);
+ gl_Position.z = max(gl_Position.z, -1);
+ texCoord = vec3((corners[c] * 0.5) + 0.5, viewAngle);
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.vs b/gfx/gl/shaders/shadowDynamicPointStencil.vs
new file mode 100644
index 0000000..0dd2d79
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.vs
@@ -0,0 +1,17 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(location = 0) in ivec3 worldPos;
+layout(location = 1) in float modelYaw;
+uniform ivec3 viewPoint;
+uniform vec3 centre;
+
+out float vmodelYaw;
+out ivec3 vworldPos;
+
+void
+main()
+{
+ vmodelYaw = modelYaw;
+ vworldPos = worldPos - viewPoint + ivec3(centre);
+}
diff --git a/gfx/gl/shaders/shadowStencil.fs b/gfx/gl/shaders/shadowStencil.fs
new file mode 100644
index 0000000..1164cc9
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.fs
@@ -0,0 +1,18 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2D textureAlbedo;
+
+include(`materialDetail.glsl')
+include(`materialCommon.glsl')
+in vec2 gTexCoords;
+flat in MaterialDetail gMaterial;
+
+void
+main()
+{
+ if (getTextureColour(gMaterial, gTexCoords).a < 0.5) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z;
+}
diff --git a/gfx/gl/shaders/shadowStencil.gs b/gfx/gl/shaders/shadowStencil.gs
new file mode 100644
index 0000000..2c3f9bd
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.gs
@@ -0,0 +1,28 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+include(`materialDetail.glsl')
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = 24) out;
+
+uniform mat4 viewProjection[8];
+in vec3 FragPos[];
+in vec2 TexCoords[];
+flat in MaterialDetail Material[];
+out vec2 gTexCoords;
+flat out MaterialDetail gMaterial;
+
+void
+main()
+{
+ for (gl_Layer = 0; gl_Layer < viewProjection.length(); ++gl_Layer) {
+ for (int v = 0; v < FragPos.length(); ++v) {
+ gl_Position = viewProjection[gl_Layer] * vec4(FragPos[v], 1);
+ gTexCoords = TexCoords[v];
+ gMaterial = Material[v];
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowStencil.vs b/gfx/gl/shaders/shadowStencil.vs
new file mode 100644
index 0000000..a15c4fb
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.vs
@@ -0,0 +1,20 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 1) uniform usampler2DRect materialData;
+
+include(`meshIn.glsl')
+include(`materialDetail.glsl')
+include(`getMaterialDetail.glsl')
+
+out vec3 FragPos;
+out vec2 TexCoords;
+flat out MaterialDetail Material;
+
+void
+main()
+{
+ TexCoords = texCoord;
+ Material = getMaterialDetail(material);
+ FragPos = position;
+}
diff --git a/gfx/gl/shaders/uiShader.fs b/gfx/gl/shaders/uiShader.fs
deleted file mode 100644
index c5f4e92..0000000
--- a/gfx/gl/shaders/uiShader.fs
+++ /dev/null
@@ -1,11 +0,0 @@
-#version 330 core
-
-in vec2 texCoord0;
-
-uniform sampler2D sampler;
-
-void
-main()
-{
- gl_FragColor = texture(sampler, texCoord0);
-}
diff --git a/gfx/gl/shaders/uiShader.vs b/gfx/gl/shaders/uiShader.vs
deleted file mode 100644
index e9e4373..0000000
--- a/gfx/gl/shaders/uiShader.vs
+++ /dev/null
@@ -1,13 +0,0 @@
-#version 330 core
-
-in vec4 position;
-
-out vec2 texCoord0;
-uniform mat4 uiProjection;
-
-void
-main()
-{
- gl_Position = uiProjection * vec4(position.xy, 0.0, 1.0);
- texCoord0 = position.zw;
-}
diff --git a/gfx/gl/shaders/uiShaderFont.fs b/gfx/gl/shaders/uiShaderFont.fs
deleted file mode 100644
index a1ef6ef..0000000
--- a/gfx/gl/shaders/uiShaderFont.fs
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 330 core
-
-in vec2 texCoord0;
-
-uniform sampler2D sampler;
-uniform vec3 colour;
-
-void
-main()
-{
- gl_FragColor = vec4(colour, texture(sampler, texCoord0).r);
-}
diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp
index a846a3d..3cb73f7 100644
--- a/gfx/gl/shadowMapper.cpp
+++ b/gfx/gl/shadowMapper.cpp
@@ -1,19 +1,26 @@
#include "shadowMapper.h"
-#include "camera.h"
#include "collections.h"
+#include "game/gamestate.h"
+#include "gfx/aabb.h"
#include "gfx/gl/shaders/fs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/fs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/gs-commonShadowPoint.h"
#include "gfx/gl/shaders/gs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/gs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowDynamicPoint.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInst.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/vs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowLandmass.h"
+#include "gfx/gl/shadowStenciller.h"
+#include "gfx/lightDirection.h"
+#include "gfx/renderable.h"
#include "gl_traits.h"
#include "location.h"
#include "maths.h"
#include "sceneProvider.h"
#include "sceneShader.h"
-#include "sorting.h"
+#include <gfx/camera.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/matrix.hpp>
@@ -45,13 +52,15 @@ ShadowMapper::ShadowMapper(const TextureAbsCoord & s) :
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
-constexpr std::array<GlobalDistance, ShadowMapper::SHADOW_BANDS + 1> shadowBands {
- 1000,
- 250000,
- 750000,
- 2500000,
- 10000000,
-};
+constexpr auto shadowBands
+ = []<GlobalDistance... ints>(const float scaleFactor, std::integer_sequence<GlobalDistance, ints...>) {
+ const auto base = 10'000'000 / pow(scaleFactor, sizeof...(ints) - 1);
+ return std::array {1, static_cast<GlobalDistance>((base * pow(scaleFactor, ints)))...};
+ }(4.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>());
+
+static_assert(shadowBands.front() == 1);
+static_assert(shadowBands.back() == 10'000'000);
+static_assert(shadowBands.size() == ShadowMapper::SHADOW_BANDS + 1);
std::vector<std::array<RelativePosition3D, 4>>
ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightViewDir)
@@ -72,36 +81,51 @@ ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightV
}
ShadowMapper::Definitions
-ShadowMapper::update(const SceneProvider & scene, const Direction3D & dir, const Camera & camera) const
+ShadowMapper::update(const SceneProvider & scene, const LightDirection & dir, const Camera & camera) const
{
+ glCullFace(GL_FRONT);
+ glEnable(GL_DEPTH_TEST);
+
+ shadowStenciller.setLightDirection(dir);
+ for (const auto & [id, asset] : gameState->assets) {
+ if (const auto r = asset.getAs<const Renderable>()) {
+ r->updateStencil(shadowStenciller);
+ }
+ }
+
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
- glCullFace(GL_FRONT);
glViewport(0, 0, size.x, size.y);
- const auto lightViewDir = glm::lookAt({}, dir, up);
+ const auto lightViewDir = glm::lookAt({}, dir.vector(), up);
const auto lightViewPoint = camera.getPosition();
const auto bandViewExtents = getBandViewExtents(camera, lightViewDir);
Definitions out;
- std::transform(bandViewExtents.begin(), std::prev(bandViewExtents.end()), std::next(bandViewExtents.begin()),
- std::back_inserter(out),
- [bands = bandViewExtents.size() - 2, &lightViewDir](const auto & near, const auto & far) mutable {
- const auto extents_minmax = [extents = std::span {near.begin(), far.end()}](auto && comp) {
- const auto mm = std::minmax_element(extents.begin(), extents.end(), comp);
- return std::make_pair(comp.get(*mm.first), comp.get(*mm.second));
- };
-
- const auto lightProjection = [](const auto & x, const auto & y, const auto & z) {
- return glm::ortho(x.first, x.second, y.first, y.second, -z.second, -z.first);
- }(extents_minmax(CompareBy {0}), extents_minmax(CompareBy {1}), extents_minmax(CompareBy {2}));
-
+ Sizes sizes;
+ using ExtentsBoundingBox = AxisAlignedBoundingBox<RelativeDistance>;
+ std::ranges::transform(bandViewExtents | std::views::pairwise, std::back_inserter(out),
+ [&lightViewDir, &sizes](const auto & band) mutable {
+ const auto & [near, far] = band;
+ auto extents = ExtentsBoundingBox::fromPoints(std::span {near.begin(), far.end()});
+ extents.min.z -= 10'000.F;
+ extents.max.z += 10'000.F;
+ const auto lightProjection = glm::ortho(
+ extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z);
+ sizes.emplace_back(extents.max - extents.min);
return lightProjection * lightViewDir;
});
for (const auto p : std::initializer_list<const ShadowProgram *> {
- &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures}) {
- p->setView(out, lightViewPoint);
+ &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures, &stencilShadowProgram}) {
+ p->setView(out, sizes, lightViewPoint);
+ }
+ ExtentsBoundingBox extents {lightViewPoint, lightViewPoint};
+ for (const auto & point : bandViewExtents.back()) {
+ extents += point;
}
- scene.shadows(*this);
+ const auto lightProjection
+ = glm::ortho(extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z);
+ Frustum frustum {lightViewPoint, lightViewDir, lightProjection};
+ scene.shadows(*this, frustum);
glCullFace(GL_BACK);
@@ -116,12 +140,15 @@ ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs, const Shader & gs,
}
void
-ShadowMapper::ShadowProgram::setView(
- const std::span<const glm::mat4> viewProjection, const GlobalPosition3D viewPoint) const
+ShadowMapper::ShadowProgram::setView(const std::span<const glm::mat4x4> viewProjection,
+ const std::span<const RelativePosition3D> sizes, const GlobalPosition3D viewPoint) const
{
use();
glUniform(viewPointLoc, viewPoint);
glUniform(viewProjectionLoc, viewProjection);
+ if (sizesLoc) {
+ glUniform(sizesLoc, sizes);
+ }
glUniform(viewProjectionsLoc, static_cast<GLint>(viewProjection.size()));
}
@@ -146,3 +173,16 @@ ShadowMapper::DynamicPoint::setModel(const Location & location) const
glUniform(modelLoc, location.getRotationTransform());
glUniform(modelPosLoc, location.pos);
}
+
+ShadowMapper::StencilShadowProgram::StencilShadowProgram() :
+ ShadowProgram {shadowDynamicPointStencil_vs, shadowDynamicPointStencil_gs, shadowDynamicPointStencil_fs}
+{
+}
+
+void
+ShadowMapper::StencilShadowProgram::use(const RelativePosition3D & centre, const float size) const
+{
+ Program::use();
+ glUniform(centreLoc, centre);
+ glUniform(sizeLoc, size);
+}
diff --git a/gfx/gl/shadowMapper.h b/gfx/gl/shadowMapper.h
index 73dadd0..951e29c 100644
--- a/gfx/gl/shadowMapper.h
+++ b/gfx/gl/shadowMapper.h
@@ -1,6 +1,7 @@
#pragma once
#include "config/types.h"
+#include "gfx/gl/shadowStenciller.h"
#include "lib/glArrays.h"
#include "program.h"
#include <gfx/models/texture.h>
@@ -10,6 +11,7 @@
class SceneProvider;
class Camera;
+class LightDirection;
class ShadowMapper {
public:
@@ -18,20 +20,23 @@ public:
static constexpr std::size_t SHADOW_BANDS {4};
using Definitions = std::vector<glm::mat4x4>;
+ using Sizes = std::vector<RelativePosition3D>;
- [[nodiscard]] Definitions update(const SceneProvider &, const Direction3D & direction, const Camera &) const;
+ [[nodiscard]] Definitions update(const SceneProvider &, const LightDirection & direction, const Camera &) const;
class ShadowProgram : public Program {
public:
explicit ShadowProgram(const Shader & vs);
explicit ShadowProgram(const Shader & vs, const Shader & gs, const Shader & fs);
- void setView(const std::span<const glm::mat4>, const GlobalPosition3D) const;
+ void setView(const std::span<const glm::mat4x4>, const std::span<const RelativePosition3D>,
+ const GlobalPosition3D) const;
void use() const;
private:
RequiredUniformLocation viewProjectionLoc {*this, "viewProjection"};
RequiredUniformLocation viewProjectionsLoc {*this, "viewProjections"};
+ UniformLocation sizesLoc {*this, "sizes"};
RequiredUniformLocation viewPointLoc {*this, "viewPoint"};
};
@@ -46,8 +51,19 @@ public:
RequiredUniformLocation modelPosLoc {*this, "modelPos"};
};
+ class StencilShadowProgram : public ShadowProgram {
+ public:
+ StencilShadowProgram();
+ void use(const RelativePosition3D & centre, const float size) const;
+
+ private:
+ RequiredUniformLocation centreLoc {*this, "centre"};
+ RequiredUniformLocation sizeLoc {*this, "size"};
+ };
+
ShadowProgram landmess, dynamicPointInst, dynamicPointInstWithTextures;
DynamicPoint dynamicPoint;
+ StencilShadowProgram stencilShadowProgram;
// NOLINTNEXTLINE(hicpp-explicit-conversions)
operator GLuint() const
@@ -61,4 +77,5 @@ private:
glFrameBuffer depthMapFBO;
glTexture depthMap;
TextureAbsCoord size;
+ mutable ShadowStenciller shadowStenciller;
};
diff --git a/gfx/gl/shadowStenciller.cpp b/gfx/gl/shadowStenciller.cpp
new file mode 100644
index 0000000..86b77e4
--- /dev/null
+++ b/gfx/gl/shadowStenciller.cpp
@@ -0,0 +1,74 @@
+#include "shadowStenciller.h"
+#include "gfx/gl/program.h"
+#include "gfx/gl/shaders/fs-shadowStencil.h"
+#include "gfx/gl/shaders/gs-shadowStencil.h"
+#include "gfx/gl/shaders/vs-shadowStencil.h"
+#include "gfx/lightDirection.h"
+#include "gfx/models/mesh.h"
+#include "glArrays.h"
+#include "gl_traits.h"
+#include "maths.h"
+#include <stdexcept>
+
+ShadowStenciller::ShadowStenciller() :
+ shadowCaster {shadowStencil_vs, shadowStencil_gs, shadowStencil_fs}, viewProjections {}
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void
+ShadowStenciller::setLightDirection(const LightDirection & lightDir)
+{
+ viewProjections = [&lightDir]<GLint... Ep>(std::integer_sequence<GLint, Ep...>) {
+ constexpr float STEP = two_pi / STENCIL_ANGLES<decltype(two_pi)>;
+ return std::array {rotate_pitch<4>(half_pi - lightDir.position().y)
+ * rotate_yaw<4>((Ep * STEP) - lightDir.position().x)...};
+ }(std::make_integer_sequence<GLint, STENCIL_ANGLES<GLint>>());
+}
+
+glTexture
+ShadowStenciller::createStencilTexture(GLsizei width, GLsizei height)
+{
+ glTexture stencil;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, stencil);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, width, height, STENCIL_ANGLES<GLint>, 0,
+ GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr);
+
+ return stencil;
+}
+
+void
+ShadowStenciller::renderStencil(const glTexture & stencil, const MeshBase & mesh, const Texture::AnyPtr texture) const
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, stencil, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ throw std::runtime_error("Stencil framebuffer not complete!");
+ }
+ if (texture) {
+ texture->bind();
+ }
+ glUseProgram(shadowCaster);
+ glClear(GL_DEPTH_BUFFER_BIT);
+ const auto stencilSize = Texture::getSize(stencil);
+ glViewport(0, 0, stencilSize.x, stencilSize.y);
+ const auto & centre = mesh.getDimensions().centre;
+ const auto & size = mesh.getDimensions().size;
+ glUniform(viewProjectionLoc,
+ std::span<const glm::mat4> {viewProjections *
+ [extentsMat = glm::translate(glm::ortho(-size, size, -size, size, -size, size), -centre)](
+ const auto & viewProjection) {
+ return viewProjection * extentsMat;
+ }});
+ mesh.Draw();
+}
diff --git a/gfx/gl/shadowStenciller.h b/gfx/gl/shadowStenciller.h
new file mode 100644
index 0000000..f774ac7
--- /dev/null
+++ b/gfx/gl/shadowStenciller.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "gfx/gl/program.h"
+#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
+#include "glArrays.h"
+
+class LightDirection;
+
+class ShadowStenciller {
+public:
+ template<typename T> static constexpr T STENCIL_ANGLES = 8;
+
+ ShadowStenciller();
+
+ [[nodiscard]]
+ static glTexture createStencilTexture(GLsizei width, GLsizei height);
+ void setLightDirection(const LightDirection & lightDir);
+ void renderStencil(const glTexture &, const MeshBase &, Texture::AnyPtr texture) const;
+
+private:
+ glFrameBuffer fbo;
+ Program shadowCaster;
+ Program::RequiredUniformLocation viewProjectionLoc {shadowCaster, "viewProjection"};
+
+ std::array<glm::mat4, STENCIL_ANGLES<size_t>> viewProjections;
+};
diff --git a/gfx/gl/uiShader.cpp b/gfx/gl/uiShader.cpp
deleted file mode 100644
index 23da9dc..0000000
--- a/gfx/gl/uiShader.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "uiShader.h"
-#include "gl_traits.h"
-#include <gfx/gl/program.h>
-#include <gfx/gl/shader.h>
-#include <gfx/gl/shaders/fs-uiShader.h>
-#include <gfx/gl/shaders/fs-uiShaderFont.h>
-#include <gfx/gl/shaders/vs-uiShader.h>
-#include <glm/glm.hpp>
-#include <glm/gtc/type_ptr.hpp>
-
-UIShader::IconProgram::IconProgram(const glm::mat4 & vp) : UIProgram {vp, uiShader_vs, uiShader_fs} { }
-
-UIShader::TextProgram::TextProgram(const glm::mat4 & vp) : UIProgram {vp, uiShader_vs, uiShaderFont_fs} { }
-
-UIShader::UIShader(size_t width, size_t height) :
- UIShader {glm::ortho<float>(0, static_cast<float>(width), 0, static_cast<float>(height))}
-{
-}
-
-UIShader::UIShader(const glm::mat4 & viewProjection) : icon {viewProjection}, text {viewProjection} { }
-
-void
-UIShader::TextProgram::use(const RGB & colour) const
-{
- Program::use();
- glUniform(colorLoc, colour);
-}
diff --git a/gfx/gl/uiShader.h b/gfx/gl/uiShader.h
deleted file mode 100644
index 6d00166..0000000
--- a/gfx/gl/uiShader.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-#include "config/types.h"
-#include "gl_traits.h"
-#include "program.h"
-#include <cstddef>
-#include <glad/gl.h>
-#include <glm/glm.hpp>
-#include <glm/gtc/type_ptr.hpp>
-
-class UIShader {
-public:
- UIShader(std::size_t width, std::size_t height);
-
-private:
- explicit UIShader(const glm::mat4 & viewProjection);
-
- class UIProgram : public Program {
- public:
- template<typename... S>
- explicit UIProgram(const glm::mat4 & vp, S &&... srcs) : Program {std::forward<S>(srcs)...}
- {
- const RequiredUniformLocation uiProjectionLoc {*this, "uiProjection"};
- glUseProgram(*this);
- glUniform(uiProjectionLoc, vp);
- }
- };
-
- class IconProgram : public UIProgram {
- public:
- explicit IconProgram(const glm::mat4 & vp);
- using Program::use;
- };
-
- class TextProgram : public UIProgram {
- public:
- explicit TextProgram(const glm::mat4 & vp);
- void use(const RGB & colour) const;
-
- private:
- RequiredUniformLocation colorLoc {*this, "colour"};
- };
-
-public:
- IconProgram icon;
- TextProgram text;
-};
diff --git a/gfx/gl/vertexArrayObject.h b/gfx/gl/vertexArrayObject.h
index 57daaf3..d008897 100644
--- a/gfx/gl/vertexArrayObject.h
+++ b/gfx/gl/vertexArrayObject.h
@@ -21,14 +21,15 @@ public:
NO_COPY(VertexArrayObject);
template<typename m, typename T> struct MP {
- constexpr MP(m T::*p) : P {p} { }
+ constexpr MP(m T::* p) : P {p} { }
+ constexpr
operator void *() const
{
return &(static_cast<T *>(nullptr)->*P);
}
- m T::*P;
+ m T::* P;
using value_type = m;
};
@@ -69,6 +70,13 @@ public:
return *this;
}
+ VertexArrayObject &
+ data(const GLuint arrayBuffer, GLenum target)
+ {
+ glBindBuffer(target, arrayBuffer);
+ return *this;
+ }
+
template<typename Data>
static void
data(const Data & data, const GLuint arrayBuffer, GLenum target)
diff --git a/gfx/lightDirection.cpp b/gfx/lightDirection.cpp
new file mode 100644
index 0000000..3932872
--- /dev/null
+++ b/gfx/lightDirection.cpp
@@ -0,0 +1,12 @@
+#include "lightDirection.h"
+#include "maths.h"
+
+constexpr auto ASTRONOMICAL_TWILIGHT = 18.0_degrees;
+constexpr auto SUN_ANGLUAR_SIZE = 0.5_degrees;
+
+LightDirection::LightDirection(const Direction2D sunPos) :
+ pos {sunPos}, vec {glm::mat3 {rotate_yp(pi + sunPos.x, -sunPos.y)} * north},
+ amb {glm::clamp(sunPos.y + ASTRONOMICAL_TWILIGHT, 0.F, 1.F)},
+ dir {glm::clamp(sunPos.y + SUN_ANGLUAR_SIZE, 0.F, 1.F)}
+{
+}
diff --git a/gfx/lightDirection.h b/gfx/lightDirection.h
new file mode 100644
index 0000000..789830b
--- /dev/null
+++ b/gfx/lightDirection.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "config/types.h"
+
+class LightDirection {
+public:
+ // NOLINTNEXTLINE(hicpp-explicit-conversions) deliberately a helper
+ LightDirection(Direction2D sunPos);
+
+ [[nodiscard]] Direction2D
+ position() const noexcept
+ {
+ return pos;
+ }
+
+ [[nodiscard]] Direction3D
+ vector() const noexcept
+ {
+ return vec;
+ }
+
+ [[nodiscard]] float
+ ambient() const noexcept
+ {
+ return amb;
+ }
+
+ [[nodiscard]] float
+ directional() const noexcept
+ {
+ return dir;
+ }
+
+private:
+ Direction2D pos;
+ Direction3D vec;
+ float amb;
+ float dir;
+};
diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp
index e7474ca..2eae160 100644
--- a/gfx/models/mesh.cpp
+++ b/gfx/models/mesh.cpp
@@ -1,6 +1,32 @@
#include "mesh.h"
-MeshBase::MeshBase(GLsizei m_numIndices, GLenum mode) : m_numIndices {m_numIndices}, mode {mode} { }
+MeshBase::MeshBase(GLsizei m_numIndices, GLenum mode, const std::vector<RelativePosition3D> & positions) :
+ m_numIndices {m_numIndices}, mode {mode}, dimensions {positions}
+{
+}
+
+MeshBase::Dimensions::Dimensions(const std::span<const RelativePosition3D> positions) :
+ Dimensions {positions, {extents(positions, 0), extents(positions, 1), extents(positions, 2)}}
+{
+}
+
+MeshBase::Dimensions::Dimensions(
+ const std::span<const RelativePosition3D> positions, const std::array<Extents1D, 3> & extents1ds) :
+ minExtent(extents1ds[0].min, extents1ds[1].min, extents1ds[2].min),
+ maxExtent(extents1ds[0].max, extents1ds[1].max, extents1ds[2].max), centre {(minExtent + maxExtent) / 2.0F},
+ size {std::ranges::max(positions | std::views::transform([this](const auto & v) {
+ return glm::distance(v, centre);
+ }))}
+{
+}
+
+MeshBase::Dimensions::Extents1D
+MeshBase::Dimensions::extents(const std::span<const RelativePosition3D> positions, glm::length_t D)
+{
+ return std::ranges::minmax(positions | std::views::transform([D](const auto & v) {
+ return v[D];
+ }));
+}
void
MeshBase::Draw() const
diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h
index 248cb8f..8791aed 100644
--- a/gfx/models/mesh.h
+++ b/gfx/models/mesh.h
@@ -1,8 +1,10 @@
#pragma once
+#include "config/types.h"
#include "gfx/gl/vertexArrayObject.h"
#include <glArrays.h>
#include <glad/gl.h>
+#include <ranges>
#include <span>
#include <stdTypeDefs.h>
@@ -10,22 +12,46 @@ class Vertex;
class MeshBase {
public:
+ class Dimensions {
+ public:
+ using Extents1D = std::ranges::minmax_result<RelativeDistance>;
+ explicit Dimensions(const std::span<const RelativePosition3D>);
+
+ RelativePosition3D minExtent, maxExtent;
+ RelativePosition3D centre;
+ RelativeDistance size;
+
+ private:
+ Dimensions(const std::span<const RelativePosition3D>, const std::array<Extents1D, 3> &);
+ static Extents1D extents(const std::span<const RelativePosition3D>, glm::length_t D);
+ };
+
void Draw() const;
void DrawInstanced(GLuint vao, GLsizei count, GLuint base = 0) const;
+ [[nodiscard]] const Dimensions &
+ getDimensions() const
+ {
+ return dimensions;
+ }
+
protected:
- MeshBase(GLsizei m_numIndices, GLenum mode);
+ MeshBase(GLsizei m_numIndices, GLenum mode, const std::vector<RelativePosition3D> &);
glVertexArray m_vertexArrayObject;
glBuffers<2> m_vertexArrayBuffers;
GLsizei m_numIndices;
GLenum mode;
+ Dimensions dimensions;
};
template<typename V> class MeshT : public MeshBase, public ConstTypeDefs<MeshT<V>> {
public:
MeshT(const std::span<const V> vertices, const std::span<const unsigned int> indices, GLenum mode = GL_TRIANGLES) :
- MeshBase {static_cast<GLsizei>(indices.size()), mode}
+ MeshBase {static_cast<GLsizei>(indices.size()), mode,
+ materializeRange(vertices | std::views::transform([](const auto & v) {
+ return static_cast<RelativePosition3D>(v.pos);
+ }))}
{
VertexArrayObject::data(vertices, m_vertexArrayBuffers[0], GL_ARRAY_BUFFER);
VertexArrayObject::data(indices, m_vertexArrayBuffers[1], GL_ARRAY_BUFFER);
diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp
index 51223aa..3457fb5 100644
--- a/gfx/models/texture.cpp
+++ b/gfx/models/texture.cpp
@@ -50,6 +50,16 @@ Texture::Texture(GLsizei width, GLsizei height, const void * data, TextureOption
glTexParameter(type, GL_TEXTURE_MIN_FILTER, to.minFilter);
glTexParameter(type, GL_TEXTURE_MAG_FILTER, to.magFilter);
glTexImage2D(type, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ auto isMimmap = [](auto value) {
+ auto eqAnyOf = [value](auto... test) {
+ return (... || (value == test));
+ };
+ return eqAnyOf(
+ GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR);
+ };
+ if (isMimmap(to.minFilter) || isMimmap(to.magFilter)) {
+ glGenerateMipmap(type);
+ }
}
void
@@ -59,12 +69,13 @@ Texture::bind(GLenum unit) const
glBindTexture(type, m_texture);
}
-TextureAbsCoord
+TextureDimensions
Texture::getSize(const glTexture & texture)
{
- TextureAbsCoord size;
+ TextureDimensions size {};
glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_WIDTH, &size.x);
glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_HEIGHT, &size.y);
+ glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_DEPTH, &size.z);
return size;
}
@@ -73,7 +84,7 @@ 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 dataSize = (static_cast<size_t>(size.x * size.y * size.z * channels));
const size_t fileSize = dataSize + sizeof(TGAHead);
filesystem::fh out {path, O_RDWR | O_CREAT, 0660};
@@ -81,7 +92,7 @@ Texture::save(
auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED);
*tga.get<TGAHead>() = {
.format = tgaFormat,
- .size = size,
+ .size = {size.x, size.y * size.z},
.pixelDepth = static_cast<uint8_t>(8 * channels),
};
glPixelStorei(GL_PACK_ALIGNMENT, 1);
diff --git a/gfx/models/texture.h b/gfx/models/texture.h
index 689d378..d8c3b29 100644
--- a/gfx/models/texture.h
+++ b/gfx/models/texture.h
@@ -1,8 +1,9 @@
#pragma once
#include "config/types.h"
+#include "glArrays.h"
+#include "stdTypeDefs.h"
#include <filesystem>
-#include <glArrays.h>
#include <glm/fwd.hpp>
class Image;
@@ -20,7 +21,7 @@ struct TextureOptions {
static GLint glMapMode(MapMode);
};
-class Texture {
+class Texture : public StdTypeDefs<Texture> {
public:
virtual ~Texture() = default;
DEFAULT_MOVE_NO_COPY(Texture);
@@ -37,10 +38,10 @@ public:
static void saveDepth(const glTexture &, const char * path);
static void saveNormal(const glTexture &, const char * path);
static void savePosition(const glTexture &, const char * path);
+ static TextureDimensions getSize(const glTexture &);
protected:
static void save(const glTexture &, GLenum, GLenum, uint8_t channels, const char * path, uint8_t tgaFormat);
- static TextureAbsCoord getSize(const glTexture &);
glTexture m_texture;
GLenum type;
diff --git a/gfx/renderable.cpp b/gfx/renderable.cpp
index 0340189..27f2459 100644
--- a/gfx/renderable.cpp
+++ b/gfx/renderable.cpp
@@ -6,6 +6,11 @@ Renderable::lights(const SceneShader &) const
}
void
-Renderable::shadows(const ShadowMapper &) const
+Renderable::shadows(const ShadowMapper &, const Frustum &) const
+{
+}
+
+void
+Renderable::updateStencil(const ShadowStenciller &) const
{
}
diff --git a/gfx/renderable.h b/gfx/renderable.h
index e126fff..140c570 100644
--- a/gfx/renderable.h
+++ b/gfx/renderable.h
@@ -3,7 +3,9 @@
#include <special_members.h>
class SceneShader;
+class Frustum;
class ShadowMapper;
+class ShadowStenciller;
class Renderable {
public:
@@ -11,7 +13,9 @@ public:
virtual ~Renderable() = default;
DEFAULT_MOVE_COPY(Renderable);
- virtual void render(const SceneShader & shader) const = 0;
+ virtual void render(const SceneShader & shader, const Frustum &) const = 0;
virtual void lights(const SceneShader & shader) const;
- virtual void shadows(const ShadowMapper & shadowMapper) const;
+ virtual void shadows(const ShadowMapper & shadowMapper, const Frustum &) const;
+
+ virtual void updateStencil(const ShadowStenciller & lightDir) const;
};
diff --git a/glsl.jam b/glsl.jam
index eeeb340..a2a08c6 100644
--- a/glsl.jam
+++ b/glsl.jam
@@ -8,12 +8,13 @@ type.register GL_TESS_CONTROL_SHADER : tcs ;
type.register GL_TESS_EVALUATION_SHADER : tes ;
type.register GL_GEOMETRY_SHADER : gs ;
type.register GL_FRAGMENT_SHADER : fs ;
+type.register GL_GENERIC_SHADER : glsl ;
-generators.register-standard glsl.embed : GL_VERTEX_SHADER : CPP(vs-%) H(vs-%) ;
-generators.register-standard glsl.embed : GL_TESS_CONTROL_SHADER : CPP(tcs-%) H(tcs-%) ;
-generators.register-standard glsl.embed : GL_TESS_EVALUATION_SHADER : CPP(tes-%) H(tes-%) ;
-generators.register-standard glsl.embed : GL_GEOMETRY_SHADER : CPP(gs-%) H(gs-%) ;
-generators.register-standard glsl.embed : GL_FRAGMENT_SHADER : CPP(fs-%) H(fs-%) ;
+generators.register-standard glsl.embed : GL_VERTEX_SHADER : CPP(vs-%) H(vs-%) GL_GENERIC_SHADER(vs-%) ;
+generators.register-standard glsl.embed : GL_TESS_CONTROL_SHADER : CPP(tcs-%) H(tcs-%) GL_GENERIC_SHADER(tcs-%) ;
+generators.register-standard glsl.embed : GL_TESS_EVALUATION_SHADER : CPP(tes-%) H(tes-%) GL_GENERIC_SHADER(tes-%) ;
+generators.register-standard glsl.embed : GL_GEOMETRY_SHADER : CPP(gs-%) H(gs-%) GL_GENERIC_SHADER(gs-%) ;
+generators.register-standard glsl.embed : GL_FRAGMENT_SHADER : CPP(fs-%) H(fs-%) GL_GENERIC_SHADER(fs-%) ;
class m4-scanner : common-scanner {
rule pattern ( ) {
@@ -31,12 +32,17 @@ 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
+ m4 -I$(2:D) -DSOURCE=$(2) -DOUTPUT=$(1[3]) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(OPTIONS) $(2) > $(1[3])
+ clang-format -i $(1[3])
+ m4 -I$(2:D) -DSOURCE=$(1[3]) -DOUTPUT=$(1[3]) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(OPTIONS) lib/embed-glsl.h.m4 > $(1[2])
+ m4 -I$(2:D) -DSOURCE=$(1[3]) -DOUTPUT=$(1[3]) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(OPTIONS) lib/embed-glsl.cpp.m4 > $(1[1])
+ clang-format -i $(1[1]) $(1[2])
}
rule glsl.embed ( targets * : sources * : properties * )
{
- DEPENDS $(targets) : lib/embed-glsl.h.m4 lib/embed-glsl.cpp.m4 ;
+ NOUPDATE $(targets[2]) ;
+ DEPENDS $(targets[2]) : lib/embed-glsl.h.m4 ;
+ DEPENDS $(targets[1]) $(targets[3]) : lib/embed-glsl.cpp.m4 ;
OPTIONS on $(targets) = [ type.type $(sources) ] ;
}
diff --git a/lib/chronology.cpp b/lib/chronology.cpp
new file mode 100644
index 0000000..8707bba
--- /dev/null
+++ b/lib/chronology.cpp
@@ -0,0 +1,12 @@
+#include "chronology.h"
+
+time_t
+operator""_time_t(const char * iso, size_t)
+{
+ struct tm tm {};
+
+ if (const auto end = strptime(iso, "%FT%T", &tm); !end || *end) {
+ throw std::invalid_argument("Invalid date");
+ }
+ return mktime(&tm);
+}
diff --git a/lib/chronology.h b/lib/chronology.h
index 1980116..688a1f7 100644
--- a/lib/chronology.h
+++ b/lib/chronology.h
@@ -1,5 +1,7 @@
#pragma once
#include <chrono>
+#include <ctime>
using TickDuration = std::chrono::duration<float, std::chrono::seconds::period>;
+time_t operator""_time_t(const char * iso, size_t);
diff --git a/lib/collection.h b/lib/collection.h
index 6802bcb..1c77e1c 100644
--- a/lib/collection.h
+++ b/lib/collection.h
@@ -3,29 +3,62 @@
#include <algorithm>
#include <functional>
#include <memory>
+#include <special_members.h>
#include <type_traits>
#include <vector>
-template<typename Object, bool shared = true> class Collection {
+template<typename Ptr, typename... Others> class Collection {
public:
+ Collection() = default;
virtual ~Collection() = default;
- using Ptr = std::conditional_t<shared, std::shared_ptr<Object>, std::unique_ptr<Object>>;
+ DEFAULT_MOVE_NO_COPY(Collection);
+
+ using Object = Ptr::element_type;
using Objects = std::vector<Ptr>;
- Objects objects;
+ template<typename T> using OtherObjects = std::vector<T *>;
+
+ Collection &
+ operator=(Objects && other)
+ {
+ objects = std::move(other);
+ ((std::get<OtherObjects<Others>>(otherObjects).clear()), ...);
+ for (const auto & other : objects) {
+ addOthersPtr(other.get());
+ }
+ return *this;
+ }
+
+ const Ptr &
+ operator[](size_t idx) const
+ {
+ return objects[idx];
+ }
+
+ template<typename T = Object>
+ requires(std::is_same_v<T, Object> || (std::is_base_of_v<Others, T> || ...))
+ [[nodiscard]] auto
+ size() const noexcept
+ {
+ return containerFor<T>().size();
+ }
template<typename T = Object, typename... Params>
auto
create(Params &&... params)
requires std::is_base_of_v<Object, T>
{
- if constexpr (shared) {
+ if constexpr (requires(Ptr ptr) { ptr = std::make_shared<T>(std::forward<Params>(params)...); }) {
auto obj = std::make_shared<T>(std::forward<Params>(params)...);
objects.emplace_back(obj);
+ addOthersType<T>(obj.get());
return obj;
}
else {
- return static_cast<T *>(objects.emplace_back(std::make_unique<T>(std::forward<Params>(params)...)).get());
+ auto obj = static_cast<T *>(
+ objects.emplace_back(std::make_unique<T>(std::forward<Params>(params)...)).get());
+ addOthersType<T>(obj);
+ return obj;
}
}
@@ -33,12 +66,19 @@ public:
T *
find()
{
- if (auto i = std::find_if(objects.begin(), objects.end(),
- [](auto && o) {
- return (dynamic_cast<T *>(o.get()));
- });
- i != objects.end()) {
- return static_cast<T *>(i->get());
+ const auto & srcObjects = containerFor<T>();
+ if constexpr (std::is_convertible_v<typename std::remove_reference_t<decltype(srcObjects)>::value_type, T *>) {
+ if (srcObjects.empty()) {
+ return nullptr;
+ }
+ return srcObjects.front();
+ }
+ else if (auto i = std::find_if(srcObjects.begin(), srcObjects.end(),
+ [](auto && o) {
+ return dynamic_cast<T *>(std::to_address(o)) != nullptr;
+ });
+ i != srcObjects.end()) {
+ return static_cast<T *>(std::to_address(*i));
}
return nullptr;
}
@@ -58,74 +98,229 @@ public:
auto
apply(const auto & m, Params &&... params) const
{
- return apply_internal<T>(objects.begin(), objects.end(), m, std::forward<Params>(params)...);
+ const auto & srcObjects = containerFor<T>();
+ return apply_internal<T>(srcObjects.begin(), srcObjects.end(), m, std::forward<Params>(params)...);
}
template<typename T = Object, typename... Params>
auto
rapply(const auto & m, Params &&... params) const
{
- return apply_internal<T>(objects.rbegin(), objects.rend(), m, std::forward<Params>(params)...);
+ const auto & srcObjects = containerFor<T>();
+ return apply_internal<T>(srcObjects.rbegin(), srcObjects.rend(), m, std::forward<Params>(params)...);
}
template<typename T = Object, typename... Params>
auto
applyOne(const auto & m, Params &&... params) const
{
- return applyOne_internal<T>(objects.begin(), objects.end(), m, std::forward<Params>(params)...);
+ const auto & srcObjects = containerFor<T>();
+ return applyOne_internal<T>(srcObjects.begin(), srcObjects.end(), m, std::forward<Params>(params)...);
}
template<typename T = Object, typename... Params>
auto
rapplyOne(const auto & m, Params &&... params) const
{
- return applyOne_internal<T>(objects.rbegin(), objects.rend(), m, std::forward<Params>(params)...);
+ const auto & srcObjects = containerFor<T>();
+ return applyOne_internal<T>(srcObjects.rbegin(), srcObjects.rend(), m, std::forward<Params>(params)...);
}
- template<typename T = Object>
+ template<typename T>
+ requires std::is_base_of_v<Object, T>
auto
removeAll()
{
- return std::erase_if(objects, [](auto && op) {
- return dynamic_cast<T *>(op.get());
- });
+ auto removeAllFrom = [](auto & container) {
+ if constexpr (std::is_base_of_v<T, std::decay_t<decltype(*container.front())>>) {
+ const auto size = container.size();
+ container.clear();
+ return size;
+ }
+ else {
+ return std::erase_if(container, [](auto && objPtr) -> bool {
+ return dynamic_cast<const T *>(std::to_address(objPtr));
+ });
+ }
+ };
+ (removeAllFrom(std::get<OtherObjects<Others>>(otherObjects)), ...);
+ return removeAllFrom(objects);
+ }
+
+ void
+ clear()
+ {
+ ((std::get<OtherObjects<Others>>(otherObjects).clear()), ...);
+ objects.clear();
}
- auto
+ [[nodiscard]] auto
+ begin() const
+ {
+ return objects.begin();
+ }
+
+ [[nodiscard]] auto
end() const
{
return objects.end();
}
- auto
+ [[nodiscard]] auto
+ rbegin() const
+ {
+ return objects.rbegin();
+ }
+
+ [[nodiscard]] auto
rend() const
{
return objects.rend();
}
+ [[nodiscard]] bool
+ empty() const
+ {
+ return objects.empty();
+ }
+
+ decltype(auto)
+ emplace(Ptr && ptr)
+ {
+ const auto & object = objects.emplace_back(std::move(ptr));
+ addOthersPtr(object.get());
+ return object;
+ }
+
protected:
+ Objects objects;
+ std::tuple<OtherObjects<Others>...> otherObjects;
+
+ template<typename T>
+ void
+ addOthersType(T * obj)
+ {
+ applyToOthersType<T>(
+ [](auto & others, auto ptr) {
+ others.emplace_back(ptr);
+ },
+ obj);
+ }
+
+ void
+ addOthersPtr(Object * obj)
+ {
+ applyToOthersPtr(
+ [](auto & others, auto ptr) {
+ others.emplace_back(ptr);
+ },
+ obj);
+ }
+
+ template<typename T, typename... Params>
+ requires(sizeof...(Others) == 0)
+ void
+ applyToOthersType(const auto &, Params...)
+ {
+ }
+
+ void
+ applyToOthersPtr(const auto &, Object *)
+ requires(sizeof...(Others) == 0)
+ {
+ }
+
+ template<typename T, typename... Params>
+ requires(sizeof...(Others) > 0)
+ void
+ applyToOthersType(const auto & func, Params &&... params)
+ {
+ (
+ [&]() {
+ if constexpr (std::is_convertible_v<T *, Others *>) {
+ std::invoke(
+ func, std::get<OtherObjects<Others>>(otherObjects), std::forward<Params>(params)...);
+ }
+ }(),
+ ...);
+ }
+
+ void
+ applyToOthersPtr(const auto & func, Object * obj)
+ requires(sizeof...(Others) > 0)
+ {
+ (
+ [&]() {
+ if (auto ptr = dynamic_cast<Others *>(obj)) {
+ std::invoke(func, std::get<OtherObjects<Others>>(otherObjects), ptr);
+ }
+ }(),
+ ...);
+ }
+
+ template<typename T>
+ requires((std::is_base_of_v<Others, T> || ...))
+ [[nodiscard]] consteval static size_t
+ idx()
+ {
+ size_t typeIdx = 0;
+ auto found = ((++typeIdx && std::is_base_of_v<Others, T>) || ...);
+ return typeIdx - found;
+ }
+
+ template<typename T>
+ [[nodiscard]]
+ constexpr const auto &
+ containerFor() const
+ {
+ if constexpr ((std::is_base_of_v<Others, T> || ...)) {
+ return std::get<idx<T>()>(otherObjects);
+ }
+ else {
+ return objects;
+ }
+ }
+
template<typename T = Object, typename... Params>
auto
apply_internal(const auto begin, const auto end, const auto & m, Params &&... params) const
{
- return std::count_if(begin, end, [&m, &params...](auto && op) {
- if (auto o = dynamic_cast<T *>(op.get())) {
- std::invoke(m, o, std::forward<Params>(params)...);
- return true;
- }
- return false;
- });
+ if constexpr (std::is_convertible_v<decltype(std::to_address(*begin)), T *>) {
+ std::for_each(begin, end, [&m, &params...](auto && op) {
+ std::invoke(m, op, std::forward<Params>(params)...);
+ });
+ return std::distance(begin, end);
+ }
+ else {
+ return std::count_if(begin, end, [&m, &params...](auto && op) {
+ if (auto o = dynamic_cast<T *>(std::to_address(op))) {
+ std::invoke(m, o, std::forward<Params>(params)...);
+ return true;
+ }
+ return false;
+ });
+ }
}
template<typename T = Object, typename... Params>
auto
applyOne_internal(const auto begin, const auto end, const auto & m, Params &&... params) const
{
- return std::find_if(begin, end, [&m, &params...](auto && op) {
- if (auto o = dynamic_cast<T *>(op.get())) {
- return std::invoke(m, o, std::forward<Params>(params)...);
- }
- return false;
- });
+ if constexpr (std::is_convertible_v<decltype(std::to_address(*begin)), T *>) {
+ return std::find_if(begin, end, [&m, &params...](auto && op) {
+ return std::invoke(m, op, std::forward<Params>(params)...);
+ });
+ }
+ else {
+ return std::find_if(begin, end, [&m, &params...](auto && op) {
+ if (auto o = dynamic_cast<T *>(std::to_address(op))) {
+ return std::invoke(m, o, std::forward<Params>(params)...);
+ }
+ return false;
+ });
+ }
}
};
+
+template<typename T, typename... Others> using SharedCollection = Collection<std::shared_ptr<T>, Others...>;
+template<typename T, typename... Others> using UniqueCollection = Collection<std::unique_ptr<T>, Others...>;
diff --git a/lib/collections.h b/lib/collections.h
index ea5d5dc..e182af5 100644
--- a/lib/collections.h
+++ b/lib/collections.h
@@ -3,6 +3,7 @@
#include <algorithm>
#include <array>
#include <cstdint>
+#include <ranges>
#include <span>
#include <tuple>
#include <utility>
@@ -44,6 +45,7 @@ operator*(const std::array<T, first> & a, const std::array<V, second> & b)
template<typename T, std::size_t N>
[[nodiscard]] constexpr auto
operator*(const std::array<T, N> & in, auto && f)
+ requires requires { f(in.front()); }
{
std::array<decltype(f(in[0])), N> out;
@@ -106,6 +108,15 @@ operator+=(std::vector<T...> & in, std::vector<T...> && src)
return in;
}
+template<typename... T>
+constexpr auto
+operator+(std::vector<T...> in1, std::vector<T...> in2)
+{
+ in1.reserve(in1.size() + in2.size());
+ std::move(in2.begin(), in2.end(), std::back_inserter(in1));
+ return in1;
+}
+
template<typename... T, typename Vn>
[[nodiscard]] constexpr auto
operator+(const std::vector<T...> & in, Vn && vn)
@@ -129,16 +140,16 @@ vectorOfN(std::integral auto N, T start = {}, T step = 1)
template<template<typename...> typename Rtn = std::vector, typename In>
[[nodiscard]] auto
-materializeRange(const In begin, const In end)
+materializeRange(In && begin, In && end)
{
- return Rtn(begin, end);
+ return Rtn(std::forward<In>(begin), std::forward<In>(end));
}
template<template<typename...> typename Rtn = std::vector, IterableCollection In>
[[nodiscard]] auto
-materializeRange(const In & in)
+materializeRange(In && in)
{
- return materializeRange<Rtn>(in.begin(), in.end());
+ return materializeRange<Rtn>(std::forward<In>(in).begin(), std::forward<In>(in).end());
}
template<template<typename...> typename Rtn = std::vector, typename In>
@@ -187,6 +198,14 @@ template<typename iter> struct stripiter {
return *this;
}
+ constexpr stripiter
+ operator++(int)
+ {
+ auto out {*this};
+ ++*this;
+ return out;
+ }
+
constexpr stripiter &
operator--()
{
@@ -195,6 +214,14 @@ template<typename iter> struct stripiter {
return *this;
}
+ constexpr stripiter
+ operator--(int)
+ {
+ auto out {*this};
+ --*this;
+ return out;
+ }
+
constexpr auto
operator-(const stripiter & other) const
{
@@ -224,3 +251,54 @@ strip_end(IterableCollection auto & cont)
{
return stripiter {cont.end()};
}
+
+inline constexpr auto dereference = std::views::transform([](const auto & iter) -> decltype(auto) {
+ return *iter;
+});
+
+struct TriangleTriples : public std::ranges::range_adaptor_closure<TriangleTriples> {
+ decltype(auto)
+ operator()(const auto & triangleStrip) const
+ {
+ return std::views::iota(strip_begin(triangleStrip), strip_end(triangleStrip)) | dereference;
+ }
+};
+
+inline constexpr TriangleTriples triangleTriples;
+
+template<typename T, typename Dist, typename Merger>
+void
+mergeClose(std::vector<T> & range, const Dist & dist, const Merger & merger,
+ decltype(dist(range.front(), range.front())) tolerance)
+{
+ using DistanceType = decltype(tolerance);
+ std::vector<DistanceType> distances;
+ distances.reserve(range.size() - 1);
+ std::ranges::transform(range | std::views::pairwise, std::back_inserter(distances), [&dist](const auto & pair) {
+ return (std::apply(dist, pair));
+ });
+ while (distances.size() > 1) {
+ const auto closestPair = std::ranges::min_element(distances);
+ if (*closestPair > tolerance) {
+ return;
+ }
+ const auto offset = std::distance(distances.begin(), closestPair);
+ const auto idx = static_cast<std::size_t>(offset);
+ if (closestPair == distances.begin()) {
+ // Remove second element
+ range.erase(range.begin() + 1);
+ distances.erase(distances.begin());
+ }
+ else if (closestPair == --distances.end()) {
+ // Remove second from last element
+ range.erase(range.end() - 2);
+ distances.erase(distances.end() - 1);
+ }
+ else {
+ range[idx] = merger(range[idx], range[idx + 1]);
+ range.erase(range.begin() + offset + 1);
+ distances.erase(distances.begin() + offset);
+ }
+ distances[idx] = dist(range[idx], range[idx + 1]);
+ }
+}
diff --git a/lib/embed-glsl.cpp.m4 b/lib/embed-glsl.cpp.m4
index 9fe0b41..ecae004 100644
--- a/lib/embed-glsl.cpp.m4
+++ b/lib/embed-glsl.cpp.m4
@@ -5,5 +5,5 @@ changecom() dnl
#include <glad/gl.h>
constexpr Shader NAME`_'substr(TYPE,1) {
- R"GLSL-EMBED(dnl
+ R"GLSL-EMBED(// OUTPUT
include(SOURCE))GLSL-EMBED", GLTYPE };
diff --git a/lib/enumDetails.h b/lib/enumDetails.h
index dfae082..49906d4 100644
--- a/lib/enumDetails.h
+++ b/lib/enumDetails.h
@@ -9,11 +9,11 @@
/// EnumDetailsBase
// Shared helpers
struct EnumDetailsBase {
- template<size_t len>
+ template<size_t Len>
constexpr static auto
strArr(auto input, auto start, auto end)
{
- std::array<char, len> out;
+ std::array<char, Len> out;
input.copy(out.begin(), end - start, start);
return out;
}
@@ -33,22 +33,22 @@ protected:
return std::string_view {__PRETTY_FUNCTION__};
};
- constexpr static auto typeNameStart {typeraw().find(SEARCH_TYPE) + SEARCH_TYPE.length()};
- constexpr static auto typeNameEnd {typeraw().find_first_of("];", typeNameStart)};
- constexpr static auto typeNameLen {typeNameEnd - typeNameStart};
- constexpr static auto typeNameArr {strArr<typeNameLen>(typeraw(), typeNameStart, typeNameEnd)};
+ constexpr static auto TYPE_NAME_START {typeraw().find(SEARCH_TYPE) + SEARCH_TYPE.length()};
+ constexpr static auto TYPE_NAME_END {typeraw().find_first_of("];", TYPE_NAME_START)};
+ constexpr static auto TYPE_NAME_LEN {TYPE_NAME_END - TYPE_NAME_START};
+ constexpr static auto TYPE_NAME_ARR {strArr<TYPE_NAME_LEN>(typeraw(), TYPE_NAME_START, TYPE_NAME_END)};
public:
- constexpr static std::string_view typeName {typeNameArr.data(), typeNameArr.size()};
+ constexpr static std::string_view TYPE_NAME {TYPE_NAME_ARR.data(), TYPE_NAME_ARR.size()};
};
/// EnumValueDetails
// Extracts the value name and constructs string_views of the parts
-template<auto value> struct EnumValueDetails : public EnumTypeDetails<decltype(value)> {
+template<auto Value> struct EnumValueDetails : public EnumTypeDetails<decltype(Value)> {
#ifndef ENUM_PROBE
private:
#endif
- using T = EnumTypeDetails<decltype(value)>;
+ using T = EnumTypeDetails<decltype(Value)>;
constexpr static auto
raw()
@@ -56,26 +56,23 @@ private:
return std::string_view {__PRETTY_FUNCTION__};
};
- constexpr static auto nameStart {raw().find_last_of(": ") + 1};
- constexpr static auto nameEnd {raw().find_first_of("];", nameStart)};
- constexpr static auto nameLen {nameEnd - nameStart};
- constexpr static auto nameArr {EnumValueDetails::template strArr<nameLen>(raw(), nameStart, nameEnd)};
+ constexpr static auto NAME_START {raw().find_last_of(": ") + 1};
+ constexpr static auto NAME_END {raw().find_first_of("];", NAME_START)};
+ constexpr static auto NAME_LEN {NAME_END - NAME_START};
+ constexpr static auto NAME_ARR {EnumValueDetails::template strArr<NAME_LEN>(raw(), NAME_START, NAME_END)};
public:
- constexpr static std::string_view valueName {nameArr.data(), nameArr.size()};
- constexpr static auto valid {valueName.back() < '0' || valueName.back() > '9'};
-#pragma GCC diagnostic push
-#ifdef __clang__
-# pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
-#endif
- constexpr static auto raw_value {value};
-#pragma GCC diagnostic pop
+ constexpr static std::string_view VALUE_NAME {NAME_ARR.data(), NAME_ARR.size()};
+ constexpr static auto VALID {VALUE_NAME.back() < '0' || VALUE_NAME.back() > '9'};
+ constexpr static auto RAW_VALUE {Value};
};
/// EnumValueCollection
// Customisation point for specifying the range of underlying values your enum can have
template<typename E> struct EnumValueCollection {
- using Vs = std::make_integer_sequence<int, 256>;
+ using Underlying = std::underlying_type_t<E>;
+ static constexpr Underlying UPPER = std::numeric_limits<Underlying>::max();
+ using Vs = std::make_integer_sequence<Underlying, UPPER>;
};
/// EnumDetails
@@ -84,45 +81,36 @@ template<typename E> struct EnumDetails {
#ifndef ENUM_PROBE
private:
#endif
- template<auto... n>
+ using Underlying = std::underlying_type_t<E>;
+
+ template<auto... N>
constexpr static auto
- get_valids(std::integer_sequence<int, n...>)
+ getValids(std::integer_sequence<Underlying, N...>)
{
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wconversion"
-#ifdef __clang__
-# pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
-#endif
- return std::array {EnumValueDetails<static_cast<E>(n)>::valid...};
-#pragma GCC diagnostic pop
+ return std::array {EnumValueDetails<static_cast<E>(N)>::VALID...};
}
- template<auto... n>
+ template<auto... N>
constexpr static auto
- get_values(std::integer_sequence<int, n...>)
+ getValues(std::integer_sequence<Underlying, N...>)
{
-#pragma GCC diagnostic push
-#ifdef __clang__
-# pragma GCC diagnostic ignored "-Wenum-constexpr-conversion"
-#endif
- return std::array {EnumValueDetails<static_cast<E>(n)>::raw_value...};
-#pragma GCC diagnostic pop
+ return std::array {EnumValueDetails<static_cast<E>(N)>::RAW_VALUE...};
}
- template<auto... n>
+ template<auto... N>
constexpr static auto
- get_valueNames(std::integer_sequence<int, n...>)
+ getValueNames(std::integer_sequence<int, N...>)
{
- return std::array {EnumValueDetails<values[n]>::valueName...};
+ return std::array {EnumValueDetails<VALUES[N]>::VALUE_NAME...};
}
using EVC = EnumValueCollection<E>;
- constexpr static auto valid_flags {get_valids(typename EVC::Vs {})};
- constexpr static auto valid_count {std::count_if(valid_flags.begin(), valid_flags.end(), std::identity {})};
+ constexpr static auto VALID_FLAGS {getValids(typename EVC::Vs {})};
+ constexpr static auto VALID_COUNT {std::count_if(VALID_FLAGS.begin(), VALID_FLAGS.end(), std::identity {})};
constexpr static auto
- lookup(const auto key, const auto & search,
- const auto & out) -> std::optional<typename std::decay_t<decltype(out)>::value_type>
+ lookup(const auto key, const auto & search, const auto & out)
+ -> std::optional<typename std::decay_t<decltype(out)>::value_type>
{
if (const auto itr = std::find(search.begin(), search.end(), key); itr != search.end()) {
return out[static_cast<std::size_t>(std::distance(search.begin(), itr))];
@@ -131,32 +119,32 @@ private:
}
public:
- constexpr static auto values {[]() {
- constexpr auto values {get_values(typename EVC::Vs {})};
- static_assert(std::is_sorted(values.begin(), values.end()), "Candidate values must be sorted");
- std::array<E, valid_count> out;
- std::copy_if(values.begin(), values.end(), out.begin(), [valid = valid_flags.begin()](auto) mutable {
+ constexpr static auto VALUES {[]() {
+ constexpr auto VALUES {getValues(typename EVC::Vs {})};
+ static_assert(std::ranges::is_sorted(VALUES), "Candidate values must be sorted");
+ std::array<E, VALID_COUNT> out;
+ std::copy_if(VALUES.begin(), VALUES.end(), out.begin(), [valid = VALID_FLAGS.begin()](auto) mutable {
return *valid++;
});
return out;
}()};
- constexpr static auto names {get_valueNames(std::make_integer_sequence<int, valid_count> {})};
+ constexpr static auto NAMES {getValueNames(std::make_integer_sequence<int, VALID_COUNT> {})};
constexpr static bool
- is_valid(E value) noexcept
+ isValid(E value) noexcept
{
- return std::binary_search(values.begin(), values.end(), value);
+ return std::binary_search(VALUES.begin(), VALUES.end(), value);
}
constexpr static std::optional<E>
parse(std::string_view name) noexcept
{
- return lookup(name, names, values);
+ return lookup(name, NAMES, VALUES);
}
constexpr static std::optional<std::string_view>
- to_string(E value) noexcept
+ toString(E value) noexcept
{
- return lookup(value, values, names);
+ return lookup(value, VALUES, NAMES);
}
};
diff --git a/lib/filesystem.cpp b/lib/filesystem.cpp
index 7e8ab9c..5c0c6f8 100644
--- a/lib/filesystem.cpp
+++ b/lib/filesystem.cpp
@@ -37,7 +37,7 @@ namespace filesystem {
}
// NOLINTNEXTLINE(hicpp-vararg)
- fh::fh(const char * path, int flags, int mode) : h {open(path, flags, mode)}
+ fh::fh(const char * path, int flags, mode_t mode) : h {open(path, flags, mode)}
{
if (h == -1) {
throw_filesystem_error("open", errno, path);
diff --git a/lib/filesystem.h b/lib/filesystem.h
index b076f43..92dc08d 100644
--- a/lib/filesystem.h
+++ b/lib/filesystem.h
@@ -30,7 +30,7 @@ namespace filesystem {
class [[nodiscard]] fh final {
public:
- fh(const char * path, int flags, int mode);
+ fh(const char * path, int flags, mode_t mode);
~fh();
NO_MOVE(fh);
NO_COPY(fh);
diff --git a/lib/glArrays.h b/lib/glArrays.h
index 787ea17..842a593 100644
--- a/lib/glArrays.h
+++ b/lib/glArrays.h
@@ -6,6 +6,7 @@
#include <glad/gl.h>
#include <special_members.h>
+// NOLINTNEXTLINE(readability-identifier-naming)
template<size_t N> class glArraysBase {
static_assert(N > 0);
@@ -15,20 +16,30 @@ public:
CUSTOM_MOVE(glArraysBase);
// NOLINTNEXTLINE(hicpp-explicit-conversions)
- inline
operator GLuint() const
+ requires(N == 1)
{
- static_assert(N == 1, "Implicit cast only if N == 1");
return ids.front();
}
- inline auto
+ GLuint
+ operator*() const
+ requires(N == 1)
+ {
+ return ids.front();
+ }
+
+ const auto &
operator[](size_t n) const
{
return ids[n];
}
- constexpr static auto size {N};
+ constexpr static auto
+ size()
+ {
+ return N;
+ }
protected:
glArraysBase() noexcept = default;
@@ -49,6 +60,7 @@ glArraysBase<N>::operator=(glArraysBase<N> && src) noexcept
return *this;
}
+// NOLINTNEXTLINE(readability-identifier-naming)
template<size_t N, auto Gen, auto Del> class glArrays : public glArraysBase<N> {
public:
using glArraysBase<N>::glArraysBase;
@@ -56,12 +68,12 @@ public:
DEFAULT_MOVE_COPY(glArrays);
- inline glArrays() noexcept
+ glArrays() noexcept
{
(*Gen)(N, this->ids.data());
}
- inline ~glArrays() noexcept
+ ~glArrays() noexcept
{
if (this->ids.front()) {
(*Del)(N, this->ids.data());
@@ -69,6 +81,7 @@ public:
}
};
+// NOLINTBEGIN(readability-identifier-naming)
template<size_t N> using glVertexArrays = glArrays<N, &glGenVertexArrays, &glDeleteVertexArrays>;
using glVertexArray = glVertexArrays<1>;
template<size_t N> using glBuffers = glArrays<N, &glGenBuffers, &glDeleteBuffers>;
@@ -79,3 +92,4 @@ template<size_t N> using glFrameBuffers = glArrays<N, &glGenFramebuffers, &glDel
using glFrameBuffer = glFrameBuffers<1>;
template<size_t N> using glRenderBuffers = glArrays<N, &glGenRenderbuffers, &glDeleteRenderbuffers>;
using glRenderBuffer = glRenderBuffers<1>;
+// NOLINTEND(readability-identifier-naming)
diff --git a/lib/glMappedBufferWriter.cpp b/lib/glMappedBufferWriter.cpp
new file mode 100644
index 0000000..cc3c413
--- /dev/null
+++ b/lib/glMappedBufferWriter.cpp
@@ -0,0 +1,4 @@
+#include "glMappedBufferWriter.h"
+#include <iterator>
+
+static_assert(std::weakly_incrementable<glMappedBufferWriter<int>>);
diff --git a/lib/glMappedBufferWriter.h b/lib/glMappedBufferWriter.h
new file mode 100644
index 0000000..f97d7e1
--- /dev/null
+++ b/lib/glMappedBufferWriter.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "special_members.h"
+#include <cstddef>
+#include <glad/gl.h>
+#include <utility>
+
+template<typename T> class glMappedBufferWriter {
+public:
+ using difference_type = std::ptrdiff_t;
+
+ glMappedBufferWriter(GLenum target, GLuint buffer, size_t count, GLenum usage = GL_STATIC_DRAW) :
+ target {target}, data {[&]() {
+ glBindBuffer(target, buffer);
+ glBufferData(target, static_cast<GLsizeiptr>(sizeof(T) * count), nullptr, usage);
+ return static_cast<T *>(glMapBuffer(target, GL_WRITE_ONLY));
+ }()}
+ {
+ }
+
+ ~glMappedBufferWriter()
+ {
+ if (target) {
+ glUnmapBuffer(target);
+ }
+ }
+
+ glMappedBufferWriter(glMappedBufferWriter && other) noexcept :
+ target {std::exchange(other.target, 0)}, data {std::exchange(other.data, nullptr)}
+ {
+ }
+
+ glMappedBufferWriter &
+ operator=(glMappedBufferWriter && other) noexcept
+ {
+ if (target) {
+ glUnmapBuffer(target);
+ }
+ target = std::exchange(other.target, 0);
+ data = std::exchange(other.data, nullptr);
+ return *this;
+ }
+
+ NO_COPY(glMappedBufferWriter);
+
+ glMappedBufferWriter &
+ operator++()
+ {
+ data++;
+ return *this;
+ }
+
+ glMappedBufferWriter &
+ operator++(int)
+ {
+ glMappedBufferWriter rtn {data};
+ data++;
+ return rtn;
+ }
+
+ T &
+ operator*()
+ {
+ return *data;
+ }
+
+ T *
+ operator->() const
+ {
+ return data;
+ }
+
+private:
+ explicit glMappedBufferWriter(T * data) : target {0}, data {data} { }
+
+ GLenum target;
+ T * data;
+};
diff --git a/lib/jsonParse-persistence.h b/lib/jsonParse-persistence.h
index 6edebc7..e4e64c0 100644
--- a/lib/jsonParse-persistence.h
+++ b/lib/jsonParse-persistence.h
@@ -15,6 +15,9 @@ namespace Persistence {
inline T
loadState(std::istream & in)
{
+ if (!in.good()) {
+ throw std::runtime_error("Input stream not in good state");
+ }
T t {};
stk.push(std::make_unique<SelectionT<T>>(std::ref(t)));
loadState(in);
diff --git a/lib/jsonParse.ll b/lib/jsonParse.ll
index 100bc46..abcd070 100644
--- a/lib/jsonParse.ll
+++ b/lib/jsonParse.ll
@@ -149,7 +149,10 @@ text [^\\\"]*
<*>. {
LexerError("Unexpected input");
- // Make iwyu think unistd.h is required
- [[maybe_unused]]static constexpr auto x=getpid;
- [[maybe_unused]]static constexpr auto y=printf;
}
+
+%%
+
+// Make iwyu think unistd.h is required
+[[maybe_unused]]static constexpr auto x=getpid;
+[[maybe_unused]]static constexpr auto y=printf;
diff --git a/lib/manyPtr.h b/lib/manyPtr.h
new file mode 100644
index 0000000..9e08452
--- /dev/null
+++ b/lib/manyPtr.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include <memory>
+#include <tuple>
+
+template<typename Primary, typename... Others> class ManyPtr : Primary {
+public:
+ using element_type = typename Primary::element_type;
+
+ template<typename... Params> ManyPtr(Params &&... params) : Primary {std::forward<Params>(params)...}
+ {
+ updatePtrs();
+ }
+
+ using Primary::operator->;
+ using Primary::operator*;
+ using Primary::operator bool;
+ using Primary::get;
+
+ template<typename... Params>
+ void
+ reset(Params &&... params)
+ {
+ Primary::reset(std::forward<Params>(params)...);
+ updatePtrs();
+ }
+
+ template<typename Other>
+ [[nodiscard]] consteval static bool
+ couldBe()
+ {
+ return (std::is_convertible_v<Others *, Other *> || ...);
+ }
+
+ template<typename Other>
+ requires(couldBe<Other>())
+ [[nodiscard]] auto
+ getAs() const
+ {
+ return std::get<idx<Other>()>(others);
+ }
+
+ template<typename Other>
+ requires(!couldBe<Other>() && requires { std::dynamic_pointer_cast<Other>(std::declval<Primary>()); })
+ [[nodiscard]] auto
+ dynamicCast() const
+ {
+ return std::dynamic_pointer_cast<Other>(*this);
+ }
+
+ template<typename Other>
+ requires(!couldBe<Other>() && !requires { std::dynamic_pointer_cast<Other>(std::declval<Primary>()); })
+ [[nodiscard]] auto
+ dynamicCast() const
+ {
+ return dynamic_cast<Other *>(get());
+ }
+
+private:
+ using OtherPtrs = std::tuple<Others *...>;
+
+ template<typename Other>
+ requires(couldBe<Other>())
+ [[nodiscard]] consteval static bool
+ idx()
+ {
+ size_t typeIdx = 0;
+ return ((typeIdx++ && std::is_convertible_v<Others *, Other *>) || ...);
+ }
+
+ void
+ updatePtrs()
+ {
+ if (*this) {
+ others = {dynamic_cast<Others *>(get())...};
+ }
+ else {
+ others = {};
+ }
+ }
+
+ OtherPtrs others;
+};
+
+template<typename Primary, typename... Others> using ManySharedPtr = ManyPtr<std::shared_ptr<Primary>, Others...>;
+template<typename Primary, typename... Others> using ManyUniquePtr = ManyPtr<std::unique_ptr<Primary>, Others...>;
diff --git a/lib/maths.cpp b/lib/maths.cpp
index 51e27fe..12e0681 100644
--- a/lib/maths.cpp
+++ b/lib/maths.cpp
@@ -19,105 +19,26 @@ flat_orientation(const Direction3D & diff)
return (std::isnan(e[0][0])) ? oneeighty : e;
}
-// Helper to lookup into a matrix given an xy vector coordinate
-template<typename M, typename I>
-inline auto &
-operator^(M & m, glm::vec<2, I> xy)
-{
- return m[xy.x][xy.y];
-}
-
-// Create a matrix for the angle, given the targets into the matrix
-template<typename M, typename I>
-inline auto
-rotation(typename M::value_type a, glm::vec<2, I> c1, glm::vec<2, I> s1, glm::vec<2, I> c2, glm::vec<2, I> ms2)
-{
- M m(1);
- sincosf(a, m ^ s1, m ^ c1);
- m ^ c2 = m ^ c1;
- m ^ ms2 = -(m ^ s1);
- return m;
-}
-
-// Create a flat (2D) transformation matrix
-glm::mat2
-rotate_flat(float a)
-{
- return rotation<glm::mat2, glm::length_t>(a, {0, 0}, {0, 1}, {1, 1}, {1, 0});
-}
-
-// Create a yaw transformation matrix
-glm::mat4
-rotate_yaw(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {0, 0}, {1, 0}, {1, 1}, {0, 1});
-}
-
-// Create a roll transformation matrix
-glm::mat4
-rotate_roll(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {0, 0}, {2, 0}, {2, 2}, {0, 2});
-}
-
-// Create a pitch transformation matrix
-glm::mat4
-rotate_pitch(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {1, 1}, {1, 2}, {2, 2}, {2, 1});
-}
-
-// Create a combined yaw, pitch, roll transformation matrix
-glm::mat4
-rotate_ypr(Rotation3D a)
-{
- return rotate_yaw(a.y) * rotate_pitch(a.x) * rotate_roll(a.z);
-}
-
-glm::mat4
-rotate_yp(Rotation2D a)
-{
- return rotate_yaw(a.y) * rotate_pitch(a.x);
-}
-
-float
-vector_yaw(const Direction2D & diff)
-{
- return std::atan2(diff.x, diff.y);
-}
-
-float
-vector_pitch(const Direction3D & diff)
-{
- return std::atan(diff.z);
-}
-
-float
-round_frac(const float & v, const float & frac)
-{
- return std::round(v / frac) * frac;
-}
-
-float
-normalize(float ang)
-{
- while (ang > pi) {
- ang -= two_pi;
- }
- while (ang <= -pi) {
- ang += two_pi;
- }
- return ang;
-}
+static_assert(pow(1, 0) == 1);
+static_assert(pow(1, 1) == 1);
+static_assert(pow(1, 2) == 1);
+static_assert(pow(2, 0) == 1);
+static_assert(pow(2, 1) == 2);
+static_assert(pow(2, 2) == 4);
+static_assert(pow(2, 3) == 8);
+static_assert(pow(3, 0) == 1);
+static_assert(pow(3, 1) == 3);
+static_assert(pow(3, 2) == 9);
+static_assert(pow(pi, 3) == 31.006278991699219F);
float
-operator"" _mph(const long double v)
+operator""_mph(const long double v)
{
return static_cast<float>(mph_to_ms(v));
}
float
-operator"" _kph(const long double v)
+operator""_kph(const long double v)
{
return static_cast<float>(kph_to_ms(v));
}
diff --git a/lib/maths.h b/lib/maths.h
index 018ef0e..43d6dcd 100644
--- a/lib/maths.h
+++ b/lib/maths.h
@@ -1,15 +1,24 @@
#pragma once
#include "config/types.h"
+#include <algorithm>
+#include <array>
#include <cmath>
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <numeric>
+#include <optional>
#include <stdexcept>
#include <utility>
+template<typename T>
+concept Arithmetic = std::is_arithmetic_v<T>;
+
+template<Arithmetic T> using CalcType = std::conditional_t<std::is_floating_point_v<T>, T, int64_t>;
+template<Arithmetic T> using DifferenceType = std::conditional_t<std::is_floating_point_v<T>, T, float>;
+
struct Arc : public std::pair<Angle, Angle> {
- template<glm::length_t Lc, glm::length_t Le, typename T, glm::qualifier Q>
+ template<glm::length_t Lc, glm::length_t Le, Arithmetic T, glm::qualifier Q = glm::defaultp>
requires(Lc >= 2, Le >= 2)
Arc(const glm::vec<Lc, T, Q> & centre, const glm::vec<Le, T, Q> & e0p, const glm::vec<Le, T, Q> & e1p) :
Arc {RelativePosition2D {e0p.xy() - centre.xy()}, RelativePosition2D {e1p.xy() - centre.xy()}}
@@ -17,22 +26,42 @@ struct Arc : public std::pair<Angle, Angle> {
}
Arc(const RelativePosition2D & dir0, const RelativePosition2D & dir1);
- Arc(const Angle angb, const Angle anga);
+ Arc(Angle anga, Angle angb);
auto
- operator[](bool i) const
+ operator[](bool getSecond) const
{
- return i ? second : first;
+ return getSecond ? second : first;
}
- [[nodiscard]] constexpr inline float
+ [[nodiscard]] constexpr float
length() const
{
return second - first;
}
};
-constexpr const RelativePosition3D up {0, 0, 1};
+template<typename T, glm::qualifier Q = glm::defaultp> struct ArcSegment : public Arc {
+ using PointType = glm::vec<2, T, Q>;
+
+ constexpr ArcSegment(PointType centre, PointType ep0, PointType ep1);
+
+ PointType centre;
+ PointType ep0;
+ PointType ep1;
+ RelativeDistance radius;
+
+ [[nodiscard]] constexpr std::optional<std::pair<glm::vec<2, T, Q>, Angle>> crossesLineAt(
+ const glm::vec<2, T, Q> & lineStart, const glm::vec<2, T, Q> & lineEnd) const;
+
+ [[nodiscard]] constexpr bool
+ angleWithinArc(Angle angle) const
+ {
+ return first <= angle && angle <= second;
+ }
+};
+
+constexpr const RelativePosition3D up {0, 0, 1}; // NOLINT(readability-identifier-length)
constexpr const RelativePosition3D down {0, 0, -1};
constexpr const RelativePosition3D north {0, 1, 0};
constexpr const RelativePosition3D south {0, -1, 0};
@@ -40,158 +69,341 @@ constexpr const RelativePosition3D east {1, 0, 0};
constexpr const RelativePosition3D west {-1, 0, 0};
constexpr auto half_pi {glm::half_pi<float>()};
constexpr auto quarter_pi {half_pi / 2};
-constexpr auto pi {glm::pi<float>()};
+constexpr auto pi {glm::pi<float>()}; // NOLINT(readability-identifier-length)
constexpr auto two_pi {glm::two_pi<float>()};
+constexpr auto degreesToRads = pi / 180.F;
+
+constexpr auto earthMeanRadius = 6371.01F; // In km
+constexpr auto astronomicalUnit = 149597890.F; // In km
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g + GlobalPosition<D>(glm::round(r));
+ return global + GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
{
- return g + GlobalPosition<D>(r);
+ return global + GlobalPosition<D>(relative);
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g - GlobalPosition<D>(glm::round(r));
+ return global - GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
{
- return g - GlobalPosition<D>(r);
+ return global - GlobalPosition<D>(relative);
+}
+
+template<glm::length_t D, Arithmetic T, glm::qualifier Q = glm::defaultp>
+using DifferenceVector = glm::vec<D, DifferenceType<T>, Q>;
+
+template<glm::length_t D, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr DifferenceVector<D, T, Q>
+difference(const glm::vec<D, T, Q> & globalA, const glm::vec<D, T, Q> & globalB)
+{
+ return globalA - globalB;
+}
+
+template<glm::length_t D, Arithmetic T, glm::qualifier Q = glm::defaultp>
+using CalcVector = glm::vec<D, CalcType<T>, Q>;
+
+template<glm::length_t D, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr CalcVector<D, T, Q>
+calcDifference(const glm::vec<D, T, Q> & globalA, const glm::vec<D, T, Q> & globalB)
+{
+ return globalA - globalB;
+}
+
+template<glm::length_t D, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr auto
+distance(const glm::vec<D, T, Q> & pointA, const glm::vec<D, T, Q> & pointB)
+{
+ return glm::length(difference(pointA, pointB));
}
glm::mat4 flat_orientation(const Rotation3D & diff);
-// C++ wrapper for C's sincosf, but with references, not pointers
-inline auto
-sincosf(float a, float & s, float & c)
+namespace {
+ // Helpers
+ // C++ wrapper for C's sincosf, but with references, not pointers
+ template<std::floating_point T>
+ constexpr void
+ sincos(T angle, T & sinOut, T & cosOut)
+ {
+ if consteval {
+ sinOut = std::sin(angle);
+ cosOut = std::cos(angle);
+ }
+ else {
+ if constexpr (std::is_same_v<T, float>) {
+ ::sincosf(angle, &sinOut, &cosOut);
+ }
+ else if constexpr (std::is_same_v<T, double>) {
+ ::sincos(angle, &sinOut, &cosOut);
+ }
+ else if constexpr (std::is_same_v<T, long double>) {
+ ::sincosl(angle, &sinOut, &cosOut);
+ }
+ }
+ }
+
+ template<std::floating_point T, glm::qualifier Q = glm::qualifier::defaultp>
+ constexpr auto
+ sincos(const T angle)
+ {
+ glm::vec<2, T, Q> sincosOut {};
+ sincos(angle, sincosOut.x, sincosOut.y);
+ return sincosOut;
+ }
+
+ // Helper to lookup into a matrix given an xy vector coordinate
+ template<glm::length_t C, glm::length_t R, Arithmetic T, glm::qualifier Q = glm::defaultp,
+ std::integral I = glm::length_t>
+ constexpr auto &
+ operator^(glm::mat<C, R, T, Q> & matrix, const glm::vec<2, I> rowCol)
+ {
+ return matrix[rowCol.x][rowCol.y];
+ }
+
+ // Create a matrix for the angle, given the targets into the matrix
+ template<glm::length_t D, std::floating_point T, glm::qualifier Q = glm::defaultp, std::integral I = glm::length_t>
+ constexpr auto
+ rotation(const T angle, const glm::vec<2, I> cos1, const glm::vec<2, I> sin1, const glm::vec<2, I> cos2,
+ const glm::vec<2, I> negSin1)
+ {
+ glm::mat<D, D, T, Q> out(1);
+ sincos(angle, out ^ sin1, out ^ cos1);
+ out ^ cos2 = out ^ cos1;
+ out ^ negSin1 = -(out ^ sin1);
+ return out;
+ }
+}
+
+// Create a flat transformation matrix
+template<glm::length_t D = 2, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+rotate_flat(const T angle)
{
- return sincosf(a, &s, &c);
+ return rotation<D, T, Q>(angle, {0, 0}, {0, 1}, {1, 1}, {1, 0});
}
-inline Rotation2D
-sincosf(float a)
+// Create a yaw transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+rotate_yaw(const T angle)
{
- Rotation2D sc;
- sincosf(a, sc.x, sc.y);
- return sc;
+ return rotation<D, T, Q>(angle, {0, 0}, {1, 0}, {1, 1}, {0, 1});
}
-glm::mat2 rotate_flat(float);
-glm::mat4 rotate_roll(float);
-glm::mat4 rotate_yaw(float);
-glm::mat4 rotate_pitch(float);
-glm::mat4 rotate_yp(Rotation2D);
-glm::mat4 rotate_ypr(Rotation3D);
+// Create a roll transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_roll(const T angle)
+{
+ return rotation<D, T, Q>(angle, {0, 0}, {2, 0}, {2, 2}, {0, 2});
+}
-float vector_yaw(const Direction2D & diff);
-float vector_pitch(const Direction3D & diff);
+// Create a pitch transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_pitch(const T angle)
+{
+ return rotation<D, T, Q>(angle, {1, 1}, {1, 2}, {2, 2}, {2, 1});
+}
-template<typename T, glm::qualifier Q>
-glm::vec<2, T, Q>
-vector_normal(const glm::vec<2, T, Q> & v)
+// Create a combined yaw, pitch, roll transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_ypr(const glm::vec<3, T, Q> & angles)
{
- return {-v.y, v.x};
+ return rotate_yaw<D>(angles.y) * rotate_pitch<D>(angles.x) * rotate_roll<D>(angles.z);
+}
+
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_yp(const T yaw, const T pitch)
+{
+ return rotate_yaw<D>(yaw) * rotate_pitch<D>(pitch);
+}
+
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_yp(const glm::vec<2, T, Q> & angles)
+{
+ return rotate_yp<D>(angles.y, angles.x);
+}
+
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+vector_yaw(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan2(diff.x, diff.y);
+}
+
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+vector_pitch(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan(diff.z);
+}
+
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<2, T, Q>
+vector_normal(const glm::vec<2, T, Q> & vector)
+{
+ return {-vector.y, vector.x};
};
-float round_frac(const float & v, const float & frac);
+template<std::floating_point T>
+constexpr auto
+round_frac(const T value, const T frac)
+{
+ return std::round(value / frac) * frac;
+}
-template<typename T>
-inline constexpr auto
-sq(T v)
+template<Arithmetic T>
+ requires requires(T value) { value * value; }
+constexpr auto
+sq(T value)
{
- return v * v;
+ return value * value;
}
-template<glm::qualifier Q>
-inline constexpr glm::vec<3, int64_t, Q>
-crossProduct(const glm::vec<3, int64_t, Q> a, const glm::vec<3, int64_t, Q> b)
+template<glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<3, int64_t, Q>
+crossProduct(const glm::vec<3, int64_t, Q> & valueA, const glm::vec<3, int64_t, Q> & valueB)
{
return {
- (a.y * b.z) - (a.z * b.y),
- (a.z * b.x) - (a.x * b.z),
- (a.x * b.y) - (a.y * b.x),
+ (valueA.y * valueB.z) - (valueA.z * valueB.y),
+ (valueA.z * valueB.x) - (valueA.x * valueB.z),
+ (valueA.x * valueB.y) - (valueA.y * valueB.x),
};
}
-template<std::integral T, glm::qualifier Q>
-inline constexpr glm::vec<3, T, Q>
-crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+template<std::integral T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> & valueA, const glm::vec<3, T, Q> & valueB)
+{
+ return crossProduct<Q>(valueA, valueB);
+}
+
+template<std::floating_point T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> & valueA, const glm::vec<3, T, Q> & valueB)
{
- return crossProduct<Q>(a, b);
+ return glm::cross(valueA, valueB);
}
-template<std::floating_point T, glm::qualifier Q>
-inline constexpr glm::vec<3, T, Q>
-crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+template<Arithmetic R = float, Arithmetic Ta, Arithmetic Tb>
+constexpr auto
+ratio(const Ta valueA, const Tb valueB)
{
- return glm::cross(a, b);
+ using Common = std::common_type_t<Ta, Ta, R>;
+ return static_cast<R>((static_cast<Common>(valueA) / static_cast<Common>(valueB)));
}
-template<typename R = float, typename Ta, typename Tb>
-inline constexpr auto
-ratio(Ta a, Tb b)
+template<Arithmetic R = float, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr auto
+ratio(const glm::vec<2, T, Q> & value)
{
- return (static_cast<R>(a) / static_cast<R>(b));
+ return ratio<R>(value.x, value.y);
}
-template<typename R = float, typename T, glm::qualifier Q>
-inline constexpr auto
-ratio(glm::vec<2, T, Q> v)
+template<glm::length_t L = 3, std::floating_point T, glm::qualifier Q = glm::defaultp>
+constexpr auto
+perspective_divide(const glm::vec<4, T, Q> & value)
{
- return ratio<R>(v.x, v.y);
+ return value / value.w;
}
-template<glm::length_t L = 3, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspective_divide(glm::vec<4, T, Q> v)
+template<glm::length_t L1, glm::length_t L2, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<L1 + L2, T, Q>
+operator||(const glm::vec<L1, T, Q> valueA, const glm::vec<L2, T, Q> valueB)
{
- return v / v.w;
+ return {valueA, valueB};
}
-template<glm::length_t L1, glm::length_t L2, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L1 + L2, T, Q>
-operator||(const glm::vec<L1, T, Q> v1, const glm::vec<L2, T, Q> v2)
+template<glm::length_t L, Arithmetic T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<L + 1, T, Q>
+operator||(const glm::vec<L, T, Q> valueA, const T valueB)
{
- return {v1, v2};
+ return {valueA, valueB};
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L + 1, T, Q>
-operator||(const glm::vec<L, T, Q> v1, const T v2)
+template<glm::length_t L, std::floating_point T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<L, T, Q>
+perspectiveMultiply(const glm::vec<L, T, Q> & base, const glm::mat<L + 1, L + 1, T, Q> & mutation)
{
- return {v1, v2};
+ const auto mutated = mutation * (base || T(1));
+ return mutated / mutated.w;
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspectiveMultiply(const glm::vec<L, T, Q> & p, const glm::mat<L + 1, L + 1, T, Q> & mutation)
+template<glm::length_t L, std::floating_point T, glm::qualifier Q = glm::defaultp>
+constexpr glm::vec<L, T, Q>
+perspectiveApply(glm::vec<L, T, Q> & base, const glm::mat<L + 1, L + 1, T, Q> & mutation)
{
- const auto p2 = mutation * (p || T(1));
- return p2 / p2.w;
+ return base = perspectiveMultiply(base, mutation);
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspectiveApply(glm::vec<L, T, Q> & p, const glm::mat<L + 1, L + 1, T, Q> & mutation)
+template<std::floating_point T>
+constexpr T
+normalize(T ang)
{
- return p = perspectiveMultiply(p, mutation);
+ while (ang > glm::pi<T>()) {
+ ang -= glm::two_pi<T>();
+ }
+ while (ang <= -glm::pi<T>()) {
+ ang += glm::two_pi<T>();
+ }
+ return ang;
}
-float normalize(float ang);
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
+[[nodiscard]] constexpr std::optional<glm::vec<2, T, Q>>
+linesIntersectAt(const glm::vec<2, T, Q> Aabs, const glm::vec<2, T, Q> Babs, const glm::vec<2, T, Q> Cabs,
+ const glm::vec<2, T, Q> Dabs)
+{
+ using CT = CalcType<T>;
+ using CVec = glm::vec<2, CT, Q>;
+ // Line AB represented as a1x + b1y = c1
+ const CVec Brel = Babs - Aabs;
+ const CT a1 = Brel.y;
+ const CT b1 = -Brel.x;
+
+ // Line CD represented as a2x + b2y = c2
+ const CVec Crel = Cabs - Aabs, Del = Dabs - Aabs;
+ const CT a2 = Del.y - Crel.y;
+ const CT b2 = Crel.x - Del.x;
+ const CT c2 = (a2 * Crel.x) + (b2 * Crel.y);
+
+ const auto determinant = (a1 * b2) - (a2 * b1);
+
+ if (determinant == 0) {
+ return std::nullopt;
+ }
+ return Aabs + CVec {(b1 * c2) / -determinant, (a1 * c2) / determinant};
+}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
std::pair<glm::vec<2, T, Q>, bool>
find_arc_centre(glm::vec<2, T, Q> start, Rotation2D startDir, glm::vec<2, T, Q> end, Rotation2D endDir)
{
@@ -204,17 +416,17 @@ find_arc_centre(glm::vec<2, T, Q> start, Rotation2D startDir, glm::vec<2, T, Q>
throw std::runtime_error("no intersection");
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
std::pair<glm::vec<2, T, Q>, bool>
find_arc_centre(glm::vec<2, T, Q> start, Angle entrys, glm::vec<2, T, Q> end, Angle entrye)
{
if (start == end) {
return {start, false};
}
- return find_arc_centre(start, sincosf(entrys + half_pi), end, sincosf(entrye - half_pi));
+ return find_arc_centre(start, sincos(entrys + half_pi), end, sincos(entrye - half_pi));
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
Angle
find_arcs_radius(glm::vec<2, T, Q> start, Rotation2D ad, glm::vec<2, T, Q> end, Rotation2D bd)
{
@@ -239,38 +451,139 @@ find_arcs_radius(glm::vec<2, T, Q> start, Rotation2D ad, glm::vec<2, T, Q> end,
/ (2 * (sq(X) - 2 * X * Z + sq(Z) + sq(Y) - 2 * Y * W + sq(W) - 4));
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
std::pair<Angle, Angle>
find_arcs_radius(glm::vec<2, T, Q> start, Angle entrys, glm::vec<2, T, Q> end, Angle entrye)
{
const auto getrad = [&](auto leftOrRight) {
- return find_arcs_radius(start, sincosf(entrys + leftOrRight), end, sincosf(entrye + leftOrRight));
+ return find_arcs_radius(start, sincos(entrys + leftOrRight), end, sincos(entrye + leftOrRight));
};
return {getrad(-half_pi), getrad(half_pi)};
}
-template<typename T>
+template<Arithmetic T>
auto
midpoint(const std::pair<T, T> & v)
{
return std::midpoint(v.first, v.second);
}
+template<glm::length_t D, std::integral T, glm::qualifier Q = glm::defaultp>
+auto
+midpoint(const glm::vec<D, T, Q> & valueA, const glm::vec<D, T, Q> & valueB)
+{
+ return valueA + (valueB - valueA) / 2;
+}
+
+// std::pow is not constexpr
+template<Arithmetic T>
+ requires requires(T n) { n *= n; }
+constexpr T
+pow(const T base, std::integral auto exp)
+{
+ T res {1};
+ while (exp--) {
+ res *= base;
+ }
+ return res;
+}
+
// Conversions
-template<typename T>
-inline constexpr auto
+template<Arithmetic T>
+constexpr auto
mph_to_ms(T v)
{
return v / 2.237L;
}
-template<typename T>
-inline constexpr auto
+template<Arithmetic T>
+constexpr auto
kph_to_ms(T v)
{
return v / 3.6L;
}
// ... literals are handy for now, probably go away when we load stuff externally
-float operator"" _mph(const long double v);
-float operator"" _kph(const long double v);
+float operator""_mph(const long double v);
+float operator""_kph(const long double v);
+
+constexpr float
+operator""_degrees(long double degrees)
+{
+ return static_cast<float>(degrees) * degreesToRads;
+}
+
+// Late implementations due to dependencies
+template<typename T, glm::qualifier Q>
+constexpr ArcSegment<T, Q>::ArcSegment(PointType centre, PointType ep0, PointType ep1) :
+ Arc {centre, ep0, ep1}, centre {centre}, ep0 {ep0}, ep1 {ep1}, radius {::distance(centre, ep0)}
+{
+}
+
+template<typename T, glm::qualifier Q>
+[[nodiscard]] constexpr std::optional<std::pair<glm::vec<2, T, Q>, Angle>>
+ArcSegment<T, Q>::crossesLineAt(const glm::vec<2, T, Q> & lineStart, const glm::vec<2, T, Q> & lineEnd) const
+{
+ // Based on formulas from https://mathworld.wolfram.com/Circle-LineIntersection.html
+ const auto lineDiff = difference(lineEnd, lineStart);
+ const auto lineLen = glm::length(lineDiff);
+ const auto lineRelStart = difference(lineStart, centre);
+ const auto lineRelEnd = difference(lineEnd, centre);
+ const auto determinant = (lineRelStart.x * lineRelEnd.y) - (lineRelEnd.x * lineRelStart.y);
+ const auto discriminant = (radius * radius * lineLen * lineLen) - (determinant * determinant);
+ if (discriminant < 0) {
+ return std::nullopt;
+ }
+
+ const auto rootDiscriminant = std::sqrt(discriminant);
+ const auto drdr = lineLen * lineLen;
+ const RelativeDistance sgn = (lineDiff.y < 0 ? -1 : 1);
+ std::array<std::pair<RelativePosition2D, Angle>, 2> points;
+ std::ranges::transform(std::initializer_list {1, -1}, points.begin(), [&](RelativeDistance N) {
+ const auto point = RelativePosition2D {((determinant * lineDiff.y) + sgn * lineDiff.x * rootDiscriminant * N),
+ ((-determinant * lineDiff.x) + std::abs(lineDiff.y) * rootDiscriminant * N)}
+ / drdr;
+ return std::make_pair(point, vector_yaw(point));
+ });
+ const auto end
+ = std::remove_if(points.begin(), points.end(), [this, lineRelStart, lineDiff, drdr](const auto point) {
+ const auto dot = glm::dot(lineDiff, point.first - lineRelStart);
+ return !angleWithinArc(point.second) || dot < 0 || dot > drdr;
+ });
+ if (points.begin() == end) {
+ return std::nullopt;
+ }
+ const auto first = *std::ranges::min_element(points.begin(), end, {}, [lineRelStart](const auto point) {
+ return glm::distance(lineRelStart, point.first);
+ });
+ return std::make_pair(centre + first.first, first.second);
+}
+
+namespace {
+ template<template<typename> typename Op>
+ [[nodiscard]] constexpr auto
+ pointLineOp(const GlobalPosition2D point, const GlobalPosition2D end1, const GlobalPosition2D end2)
+ {
+ return Op {}(CalcDistance(end2.x - end1.x) * CalcDistance(point.y - end1.y),
+ CalcDistance(end2.y - end1.y) * CalcDistance(point.x - end1.x));
+ }
+}
+
+constexpr auto pointLeftOfLine = pointLineOp<std::greater>;
+constexpr auto pointLeftOfOrOnLine = pointLineOp<std::greater_equal>;
+
+[[nodiscard]] constexpr bool
+linesCross(const GlobalPosition2D lineAend1, const GlobalPosition2D lineAend2, const GlobalPosition2D lineBend1,
+ const GlobalPosition2D lineBend2)
+{
+ return (pointLeftOfLine(lineAend2, lineBend1, lineBend2) == pointLeftOfLine(lineAend1, lineBend2, lineBend1))
+ && (pointLeftOfLine(lineBend1, lineAend1, lineAend2) == pointLeftOfLine(lineBend2, lineAend2, lineAend1));
+}
+
+[[nodiscard]] constexpr bool
+linesCrossLtR(const GlobalPosition2D lineAend1, const GlobalPosition2D lineAend2, const GlobalPosition2D lineBend1,
+ const GlobalPosition2D lineBend2)
+{
+ return pointLeftOfLine(lineAend2, lineBend1, lineBend2) && pointLeftOfLine(lineAend1, lineBend2, lineBend1)
+ && pointLeftOfLine(lineBend1, lineAend1, lineAend2) && pointLeftOfLine(lineBend2, lineAend2, lineAend1);
+}
diff --git a/lib/msgException.h b/lib/msgException.h
new file mode 100644
index 0000000..660738d
--- /dev/null
+++ b/lib/msgException.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <optional>
+#include <string>
+
+template<typename BaseException> class MsgException : public BaseException {
+protected:
+ using BaseException::BaseException;
+ [[nodiscard]] virtual std::string getMsg() const noexcept = 0;
+
+public:
+ [[nodiscard]] const char *
+ what() const noexcept override
+ {
+ if (!msg) {
+ msg.emplace(getMsg());
+ }
+ return msg->c_str();
+ }
+
+private:
+ mutable std::optional<std::string> msg;
+};
diff --git a/lib/persistence.h b/lib/persistence.h
index e385b20..75bcea6 100644
--- a/lib/persistence.h
+++ b/lib/persistence.h
@@ -1,5 +1,6 @@
#pragma once
+#include "manyPtr.h"
#include <charconv>
#include <format>
#include <functional>
@@ -200,7 +201,7 @@ namespace Persistence {
}
[[nodiscard]] virtual NameActionSelection setName(const std::string_view key, SelectionFactory &&) = 0;
- virtual void selHandler() {};
+ virtual void selHandler() { };
virtual void setType(const std::string_view, const Persistable *) = 0;
SelectionPtr sel {};
diff --git a/lib/ray.h b/lib/ray.h
index a831270..642cd4d 100644
--- a/lib/ray.h
+++ b/lib/ray.h
@@ -27,8 +27,7 @@ public:
const auto n2 = crossProduct(direction, n);
const auto c1 = p1 + PositionType((glm::dot(RelativePosition3D(start - p1), n2) / glm::dot(d1, n2)) * d1);
const auto difflength = glm::length(diff);
- if (glm::length(RelativePosition3D(c1 - p1)) > difflength
- || glm::length(RelativePosition3D(c1 - e1)) > difflength) {
+ if (::distance(c1, p1) > difflength || ::distance(c1, e1) > difflength) {
return std::numeric_limits<typename PositionType::value_type>::infinity();
}
return static_cast<PositionType::value_type>(glm::abs(glm::dot(n, RelativePosition3D(p1 - start))));
@@ -54,34 +53,55 @@ public:
}
}
- bool
- intersectTriangle(const PositionType t0, const PositionType t1, const PositionType t2, BaryPosition & bary,
- RelativeDistance & distance) const
+ struct IntersectTriangleResult {
+ BaryPosition bary;
+ RelativeDistance distance;
+ };
+
+ std::optional<IntersectTriangleResult>
+ intersectTriangle(const PositionType t0, const PositionType t1, const PositionType t2) const
{
+ IntersectTriangleResult out;
if constexpr (std::is_floating_point_v<typename PositionType::value_type>) {
- return glm::intersectRayTriangle(start, direction, t0, t1, t2, bary, distance) && distance >= 0.F;
+ if (glm::intersectRayTriangle(start, direction, t0, t1, t2, out.bary, out.distance)
+ && out.distance >= 0.F) {
+ return out;
+ }
}
else {
const RelativePosition3D t0r = t0 - start, t1r = t1 - start, t2r = t2 - start;
- return glm::intersectRayTriangle({}, direction, t0r, t1r, t2r, bary, distance) && distance >= 0.F;
+ if (glm::intersectRayTriangle({}, direction, t0r, t1r, t2r, out.bary, out.distance)
+ && out.distance >= 0.F) {
+ return out;
+ }
}
+ return std::nullopt;
}
- bool
- intersectSphere(const PositionType centre, const PositionType::value_type size, PositionType & position,
- Normal3D & normal) const
+ struct IntersectSphereResult {
+ PositionType position;
+ Normal3D normal;
+ };
+
+ std::optional<IntersectSphereResult>
+ intersectSphere(const PositionType centre, const PositionType::value_type size) const
{
+ IntersectSphereResult out;
if constexpr (std::is_floating_point_v<typename PositionType::value_type>) {
- return glm::intersectRaySphere(start, direction, centre, size, position, normal);
+ if (glm::intersectRaySphere(start, direction, centre, size, out.position, out.normal)) {
+ return out;
+ }
}
else {
const RelativePosition3D cr = centre - start;
RelativePosition3D positionF {};
- const auto r = glm::intersectRaySphere(
- {}, direction, cr, static_cast<RelativeDistance>(size), positionF, normal);
- position = GlobalPosition3D(positionF) + start;
- return r;
+ if (glm::intersectRaySphere(
+ {}, direction, cr, static_cast<RelativeDistance>(size), positionF, out.normal)) {
+ out.position = GlobalPosition3D(positionF) + start;
+ return out;
+ }
}
+ return std::nullopt;
}
};
diff --git a/lib/sorting.h b/lib/sorting.h
index 777de00..be5a7e2 100644
--- a/lib/sorting.h
+++ b/lib/sorting.h
@@ -1,5 +1,6 @@
#pragma once
+#include <functional>
#include <glm/fwd.hpp>
#include <type_traits>
@@ -30,6 +31,14 @@ template<typename T, auto M> struct PtrMemberSorter : public PtrSorter<T> {
}
};
+template<auto Proj> struct SortedBy {
+ auto
+ operator()(const auto & left, const auto & right) const
+ {
+ return (std::invoke(Proj, left) < std::invoke(Proj, right));
+ }
+};
+
struct CompareBy {
glm::length_t index;
diff --git a/lib/stdTypeDefs.h b/lib/stdTypeDefs.h
index beab630..38ebe0b 100644
--- a/lib/stdTypeDefs.h
+++ b/lib/stdTypeDefs.h
@@ -28,6 +28,18 @@ template<typename T> struct AnyPtr {
return *ptr;
}
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ operator bool() const
+ {
+ return ptr != nullptr;
+ }
+
+ bool
+ operator!() const
+ {
+ return ptr == nullptr;
+ }
+
private:
T * ptr;
};
diff --git a/lib/stream_support.h b/lib/stream_support.h
index 57d82a1..7f1df96 100644
--- a/lib/stream_support.h
+++ b/lib/stream_support.h
@@ -4,8 +4,11 @@
#include <glm/glm.hpp>
#include <iostream>
#include <maths.h>
+#include <optional>
+#include <source_location>
#include <span>
#include <sstream>
+#include <tuple>
#include <type_traits>
template<typename S>
@@ -16,14 +19,14 @@ concept NonStringIterableCollection
namespace std {
std::ostream &
- operator<<(std::ostream & s, const NonStringIterableCollection auto & v)
+ operator<<(std::ostream & s, const NonStringIterableCollection auto & collection)
{
s << '(';
- for (const auto & i : v) {
- if (&i != &*v.begin()) {
+ for (size_t nth {}; const auto & element : collection) {
+ if (nth++) {
s << ", ";
}
- s << i;
+ s << element;
}
return s << ')';
}
@@ -49,6 +52,22 @@ namespace std {
return (s << '(' << v.first << ", " << v.second << ')');
}
+ namespace {
+ template<typename... T, size_t... Idx>
+ std::ostream &
+ printTuple(std::ostream & s, const std::tuple<T...> & v, std::integer_sequence<size_t, Idx...>)
+ {
+ return ((s << (Idx ? ", " : "") << std::get<Idx>(v)), ...);
+ }
+ }
+
+ template<typename... T>
+ std::ostream &
+ operator<<(std::ostream & s, const std::tuple<T...> & v)
+ {
+ return printTuple(s << '{', v, std::make_index_sequence<sizeof...(T)>()) << '}';
+ }
+
inline std::ostream &
operator<<(std::ostream & s, const Arc & arc)
{
@@ -62,7 +81,17 @@ namespace std {
inline std::ostream &
operator<<(std::ostream & s, const E & e)
{
- return s << EnumTypeDetails<E>::typeName << "::" << EnumDetails<E>::to_string(e).value();
+ return s << EnumTypeDetails<E>::TYPE_NAME << "::" << EnumDetails<E>::toString(e).value();
+ }
+
+ template<typename T>
+ inline std::ostream &
+ operator<<(std::ostream & s, const std::optional<T> & v)
+ {
+ if (v) {
+ return s << *v;
+ }
+ return s << "nullopt";
}
}
@@ -75,4 +104,15 @@ streamed_string(const T & v)
return std::move(ss).str();
}
-#define CLOG(x) std::cerr << __LINE__ << " : " #x " : " << x << "\n";
+namespace {
+ template<typename T>
+ void
+ clogImpl(const T & value, const std::string_view name,
+ const std::source_location loc = std::source_location::current())
+ {
+ std::cerr << loc.line() << " : " << name << " : " << value << "\n";
+ }
+}
+
+#define CLOG(x) clogImpl(x, #x)
+#define CLOGf(...) clogImpl(std::format(__VA_ARGS__), "msg")
diff --git a/lib/triangle.h b/lib/triangle.h
new file mode 100644
index 0000000..abd697c
--- /dev/null
+++ b/lib/triangle.h
@@ -0,0 +1,156 @@
+#pragma once
+
+#include "config/types.h"
+#include "maths.h"
+#include <glm/glm.hpp>
+
+template<glm::length_t Dim, Arithmetic T, glm::qualifier Q = glm::defaultp>
+struct Triangle : public glm::vec<3, glm::vec<Dim, T, Q>> {
+ using Point = glm::vec<Dim, T, Q>;
+ using Base = glm::vec<3, glm::vec<Dim, T, Q>>;
+ using Base::Base;
+
+ [[nodiscard]] constexpr Point
+ operator*(BaryPosition bari) const
+ {
+ return p(0) + (sideDifference(1) * bari.x) + (sideDifference(2) * bari.y);
+ }
+
+ [[nodiscard]] constexpr Point
+ centroid() const
+ {
+ return [this]<glm::length_t... Axis>(std::integer_sequence<glm::length_t, Axis...>) {
+ return Point {(p(0)[Axis] + p(1)[Axis] + p(2)[Axis]) / 3 ...};
+ }(std::make_integer_sequence<glm::length_t, Dim>());
+ }
+
+ [[nodiscard]] constexpr auto
+ area() const
+ requires(Dim == 3)
+ {
+ return glm::length(crossProduct(sideDifference(1), sideDifference(2))) / T {2};
+ }
+
+ [[nodiscard]] constexpr auto
+ area() const
+ requires(Dim == 2)
+ {
+ return std::abs((sideDifference(1).x * sideDifference(2).y) - (sideDifference(2).x * sideDifference(1).y)) / 2;
+ }
+
+ [[nodiscard]] constexpr Normal3D
+ normal() const
+ requires(Dim == 3)
+ {
+ return crossProduct(sideDifference(1), sideDifference(2));
+ }
+
+ [[nodiscard]] constexpr auto
+ height()
+ {
+ return (area() * 2) / ::distance(p(0), p(1));
+ }
+
+ [[nodiscard]] constexpr Normal3D
+ nnormal() const
+ requires(Dim == 3)
+ {
+ return glm::normalize(normal());
+ }
+
+ [[nodiscard]] constexpr auto
+ sideDifference(glm::length_t side) const
+ {
+ return difference(p(side), p(0));
+ }
+
+ [[nodiscard]] constexpr auto
+ calcSideDifference(glm::length_t side) const
+ {
+ return calcDifference(p(side), p(0));
+ }
+
+ [[nodiscard]] constexpr auto
+ angle(glm::length_t corner) const
+ {
+ return Arc {P(corner), P(corner + 2), P(corner + 1)}.length();
+ }
+
+ template<glm::length_t D = Dim>
+ [[nodiscard]] constexpr auto
+ angleAt(const glm::vec<D, T, Q> pos) const
+ requires(D <= Dim)
+ {
+ for (glm::length_t i {}; i < 3; ++i) {
+ if (glm::vec<D, T, Q> {p(i)} == pos) {
+ return angle(i);
+ }
+ }
+ return 0.F;
+ }
+
+ [[nodiscard]] constexpr auto
+ isUp() const
+ {
+ const auto edgeAB = sideDifference(1);
+ const auto edgeAC = sideDifference(2);
+ return edgeAB.x * edgeAC.y >= edgeAB.y * edgeAC.x;
+ }
+
+ [[nodiscard]] constexpr auto
+ p(const glm::length_t idx) const
+ {
+ return Base::operator[](idx);
+ }
+
+ [[nodiscard]] constexpr auto
+ P(const glm::length_t idx) const
+ {
+ return Base::operator[](idx % 3);
+ }
+
+ [[nodiscard]] constexpr Point *
+ begin()
+ {
+ return &(Base::x);
+ }
+
+ [[nodiscard]] constexpr const Point *
+ begin() const
+ {
+ return &(Base::x);
+ }
+
+ [[nodiscard]] constexpr Point *
+ end()
+ {
+ return begin() + 3;
+ }
+
+ [[nodiscard]] constexpr const Point *
+ end() const
+ {
+ return begin() + 3;
+ }
+
+ [[nodiscard]]
+ constexpr auto
+ positionOnPlane(const glm::vec<2, T, Q> coord2d) const
+ requires(Dim == 3)
+ {
+ const auto edgeCrossProduct = crossProduct(calcSideDifference(1), calcSideDifference(2));
+ return coord2d
+ || static_cast<T>(
+ ((edgeCrossProduct.x * p(0).x) + (edgeCrossProduct.y * p(0).y) + (edgeCrossProduct.z * p(0).z)
+ - (edgeCrossProduct.x * coord2d.x) - (edgeCrossProduct.y * coord2d.y))
+ / edgeCrossProduct.z);
+ }
+
+ [[nodiscard]]
+ constexpr bool
+ containsPoint(const GlobalPosition2D coord) const
+ {
+ return pointLeftOfOrOnLine(coord, p(0), p(1)) && pointLeftOfOrOnLine(coord, p(1), p(2))
+ && pointLeftOfOrOnLine(coord, p(2), p(0));
+ }
+};
diff --git a/lib/util.h b/lib/util.h
index 290492f..cd7971b 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -3,6 +3,7 @@
#include <algorithm> // IWYU pragma: keep
#include <array>
#include <cstddef>
+#include <tuple>
template<typename T, std::size_t N>
constexpr auto
@@ -12,3 +13,23 @@ transform_array(const std::array<T, N> & in, auto && transform)
std::transform(in.begin(), in.end(), out.begin(), transform);
return out;
}
+
+namespace {
+ template<size_t... N> struct GetNth {
+ decltype(auto)
+ operator()(const auto & tup) const
+ {
+ if constexpr (sizeof...(N) == 1) {
+ return std::get<N...>(tup);
+ }
+ else {
+ return std::tie(std::get<N>(tup)...);
+ }
+ }
+ };
+}
+
+template<size_t... N> inline constexpr auto Nth = GetNth<N...> {};
+inline constexpr auto GetFirst = Nth<0>;
+inline constexpr auto GetSecond = Nth<1>;
+inline constexpr auto GetSwapped = Nth<0, 1>;
diff --git a/lib/worker.cpp b/lib/worker.cpp
index 45fb6df..9b2b83c 100644
--- a/lib/worker.cpp
+++ b/lib/worker.cpp
@@ -10,6 +10,11 @@ Worker::Worker() : todoLen {0}
std::generate_n(std::back_inserter(threads), std::thread::hardware_concurrency(), [this]() {
return std::jthread {&Worker::worker, this};
});
+ if constexpr (requires { pthread_setname_np(std::declval<std::jthread>().native_handle(), ""); }) {
+ for (auto & thread : threads) {
+ pthread_setname_np(thread.native_handle(), "ilt-worker");
+ }
+ }
}
Worker::~Worker()
diff --git a/res/shapespark-low-poly-plants-kit.fbx b/res/shapespark-low-poly-plants-kit.fbx
index fe87c0c..7457adb 100644
--- a/res/shapespark-low-poly-plants-kit.fbx
+++ b/res/shapespark-low-poly-plants-kit.fbx
Binary files differ
diff --git a/res/ui/icon/magnifier.svg b/res/ui/icon/magnifier.svg
new file mode 100644
index 0000000..97abe9f
--- /dev/null
+++ b/res/ui/icon/magnifier.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Creator: CorelDRAW -->
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1.70666in" height="1.70666in" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+viewBox="0 0 1707 1707"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ .fil3 {fill:url(#id0)}
+ .fil0 {fill:url(#id1)}
+ .fil2 {fill:url(#id2)}
+ .fil4 {fill:url(#id3)}
+ .fil1 {fill:url(#id4)}
+ .fil5 {fill:url(#id5)}
+ ]]>
+ </style>
+ <linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="647.772" y1="1185.78" x2="647.772" y2="109.768">
+ <stop offset="0" style="stop-opacity:1; stop-color:#AFAFAF"/>
+ <stop offset="1" style="stop-opacity:1; stop-color:#9C9C9C"/>
+ </linearGradient>
+ <linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="1138.83" y1="1294.33" x2="1138.83" y2="983.327">
+ <stop offset="0" style="stop-opacity:1; stop-color:#949494"/>
+ <stop offset="1" style="stop-opacity:1; stop-color:#858585"/>
+ </linearGradient>
+ <linearGradient id="id2" gradientUnits="userSpaceOnUse" xlink:href="#id1" x1="647.772" y1="1295.55" x2="647.772" y2="0.00393701">
+ </linearGradient>
+ <linearGradient id="id3" gradientUnits="userSpaceOnUse" x1="647.772" y1="1143.81" x2="647.772" y2="151.736">
+ <stop offset="0" style="stop-opacity:1; stop-color:#D2D2D2"/>
+ <stop offset="1" style="stop-opacity:1; stop-color:#BBBBBB"/>
+ </linearGradient>
+ <linearGradient id="id4" gradientUnits="userSpaceOnUse" x1="1411.49" y1="1116.31" x2="1411.49" y2="1706.66">
+ <stop offset="0" style="stop-opacity:1; stop-color:#FF9911"/>
+ <stop offset="1" style="stop-opacity:1; stop-color:#FF6600"/>
+ </linearGradient>
+ <linearGradient id="id5" gradientUnits="userSpaceOnUse" x1="657.22" y1="496.535" x2="657.22" y2="322.63">
+ <stop offset="0" style="stop-opacity:1; stop-color:#E6E6E6"/>
+ <stop offset="1" style="stop-opacity:1; stop-color:#CCCCCC"/>
+ </linearGradient>
+ </defs>
+ <g id="Layer_x0020_1">
+ <metadata id="CorelCorpID_0Corel-Layer"/>
+ <path class="fil0" d="M1161 983l133 133 -178 178 -133 -133c36,-21 69,-47 100,-78 31,-31 57,-64 78,-100z"/>
+ <path class="fil1" d="M1116 1294l178 -178 376 376c49,49 49,129 0,178l0 0c-49,49 -129,49 -178,0l-376 -376z"/>
+ <path class="fil2" d="M190 190c253,-253 663,-253 916,0 253,253 253,663 0,916 -253,253 -663,253 -916,0 -253,-253 -253,-663 0,-916z"/>
+ <path class="fil3" d="M267 267c210,-210 551,-210 761,0 210,210 210,551 0,761 -210,210 -551,210 -761,0 -210,-210 -210,-551 0,-761z"/>
+ <path class="fil4" d="M297 297c194,-194 508,-194 702,0 193,194 193,508 0,702 -194,193 -508,193 -702,0 -194,-194 -194,-508 0,-702z"/>
+ <path class="fil5" d="M440 486c-14,14 -37,14 -52,0 -14,-15 -14,-38 0,-52 75,-74 172,-111 269,-111 97,0 195,37 269,111 14,14 14,38 0,52 -14,14 -37,14 -52,0 -60,-60 -138,-90 -217,-90 -79,0 -157,30 -217,90z"/>
+ </g>
+</svg>
diff --git a/res/ui/icon/network.png b/res/ui/icon/network.png
deleted file mode 100644
index 7a091f3..0000000
--- a/res/ui/icon/network.png
+++ /dev/null
Binary files differ
diff --git a/res/ui/icon/rails.svg b/res/ui/icon/rails.svg
new file mode 100644
index 0000000..81e9b94
--- /dev/null
+++ b/res/ui/icon/rails.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Creator: CorelDRAW -->
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1.99999in" height="1.99999in" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+viewBox="0 0 2000 2000"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ .fil0 {fill:none}
+ .fil1 {fill:#333333}
+ .fil2 {fill:#C06000}
+ .fil3 {fill:#CCCCCC}
+ ]]>
+ </style>
+ </defs>
+ <rect class="fil0" width="2000" height="2000"/>
+ <g id="Layer_x0020_1">
+ <metadata id="CorelCorpID_0Corel-Layer"/>
+ <path class="fil1" d="M1448 515l-61 0 29 126 108 0c30,0 55,23 63,59l38 168c8,37 -28,69 -63,69l-80 0 28 126 127 0c31,0 55,23 63,59l38 168c9,37 -27,69 -63,69l-98 0 28 126 146 0c31,0 55,22 63,58l38 169c8,37 -27,68 -63,68l-126 0c-8,20 -30,34 -51,34l-81 0c-21,0 -38,-13 -48,-34l-966 0c-10,21 -27,34 -48,34l-81 0c-21,0 -43,-14 -51,-34l-126 0c-36,0 -71,-31 -63,-68l38 -169c8,-36 32,-58 63,-58l146 0 29 -126 -99 0c-36,0 -72,-32 -63,-69l38 -168c8,-36 32,-59 63,-59l127 0 28 -126 -80 0c-35,0 -71,-32 -63,-69l38 -168c8,-36 32,-59 63,-59l109 0 28 -126c-40,0 -87,8 -113,-25 -10,-13 -14,-28 -11,-44 56,-248 29,-226 192,-226 10,-21 27,-34 49,-34l80 0c21,0 43,14 51,34l278 0c8,-20 30,-34 51,-34l81 0c21,0 39,13 48,34 163,0 136,-21 192,226 8,37 -28,69 -63,69zm-971 1239l341 -1516 0 -1c-1,-2 -5,-4 -8,-4l-80 0c-5,0 -8,10 -9,15l-338 1506c-2,7 -5,13 5,13l81 0c4,0 7,-8 8,-13zm108 -269l830 0 -28 -126 -773 0 -29 126zm95 -422l640 0 -28 -126 -584 0 -28 126zm95 -422l450 0 -28 -126 -394 0 -28 126zm-436 1093l46 -202 -136 0 0 0c-12,0 -16,17 -18,22l-37 168c-2,6 12,12 17,12l128 0zm95 -422l45 -202 -116 0 0 0c-12,0 -17,17 -18,22l-38 168c-1,7 13,12 18,12l109 0zm425 -1046l-45 202 373 0 -46 -202 -282 0zm-95 422l-45 202 562 0 -45 -202 -472 0zm-94 422l-46 202 752 0 -45 -202 -661 0zm-95 422l-46 202 942 0 -46 -202 -850 0zm760 -1035l-56 -251c-1,-5 -4,-13 -9,-13l-80 0c-3,0 -6,2 -8,4l341 1517c1,5 5,13 8,13l81 0c3,0 7,-2 8,-4l-285 -1266zm-4 -231l46 202 71 0c5,0 19,-5 17,-11l-37 -169c-2,-5 -6,-22 -18,-22l-79 0zm95 422l45 202 91 0c4,0 19,-5 17,-12l-38 -168c-1,-5 -6,-22 -17,-22l-98 0zm95 422l45 202 109 0c6,0 19,-5 18,-12l-38 -168c-1,-5 -6,-22 -17,-22l-117 0zm95 422l45 202 128 0c6,0 18,-6 18,-12l-38 -168c-1,-5 -6,-22 -18,-22l-135 0zm-1140 -844c-11,0 -16,17 -17,22l-38 168c-2,7 13,12 17,12l91 0 45 -202 -98 0zm114 -422c-12,0 -16,17 -17,22l-38 169c-2,6 12,11 17,11l72 0 45 -202 -79 0z"/>
+ <path class="fil2" d="M339 1734l46 -202 -136 0 0 0c-12,0 -16,17 -18,22l-37 168c-2,6 12,12 17,12l128 0z"/>
+ <path class="fil3" d="M721 248l-338 1506c-2,7 -5,13 5,13l81 0c4,0 7,-8 8,-13l341 -1516 0 -1c-1,-2 -5,-4 -8,-4l-80 0c-5,0 -8,10 -9,15z"/>
+ <polygon class="fil2" points="1425,1532 575,1532 529,1734 1471,1734 "/>
+ <path class="fil3" d="M1182 237l341 1517c1,5 5,13 8,13l81 0c3,0 7,-2 8,-4l-285 -1266 -56 -251c-1,-5 -4,-13 -9,-13l-80 0c-3,0 -6,2 -8,4z"/>
+ <path class="fil2" d="M1751 1532l-135 0 45 202 128 0c6,0 18,-6 18,-12l-38 -168c-1,-5 -6,-22 -18,-22z"/>
+ <path class="fil2" d="M1566 1312l109 0c6,0 19,-5 18,-12l-38 -168c-1,-5 -6,-22 -17,-22l-117 0 45 202z"/>
+ <polygon class="fil2" points="1376,1312 1331,1110 670,1110 624,1312 "/>
+ <path class="fil2" d="M434 1312l45 -202 -116 0 0 0c-12,0 -17,17 -18,22l-38 168c-1,7 13,12 18,12l109 0z"/>
+ <path class="fil2" d="M438 890l91 0 45 -202 -98 0c-11,0 -16,17 -17,22l-38 168c-2,7 13,12 17,12z"/>
+ <polygon class="fil2" points="764,688 719,890 1281,890 1236,688 "/>
+ <path class="fil2" d="M1426 688l45 202 91 0c4,0 19,-5 17,-12l-38 -168c-1,-5 -6,-22 -17,-22l-98 0z"/>
+ <path class="fil2" d="M1377 468l71 0c5,0 19,-5 17,-11l-37 -169c-2,-5 -6,-22 -18,-22l-79 0 46 202z"/>
+ <polygon class="fil2" points="1187,468 1141,266 859,266 814,468 "/>
+ <path class="fil2" d="M624 468l45 -202 -79 0c-12,0 -16,17 -17,22l-38 169c-2,6 12,11 17,11l72 0z"/>
+ </g>
+</svg>
diff --git a/res/ui/icon/road.svg b/res/ui/icon/road.svg
new file mode 100644
index 0000000..3b9ae77
--- /dev/null
+++ b/res/ui/icon/road.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Creator: CorelDRAW -->
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="512px" height="512px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
+viewBox="0 0 512 512"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ .fil0 {fill:#4D4D4D}
+ .fil1 {fill:#99A4AC}
+ .fil2 {fill:#D9F2F2}
+ ]]>
+ </style>
+ </defs>
+ <g id="Layer_x0020_1">
+ <metadata id="CorelCorpID_0Corel-Layer"/>
+ <path class="fil0" d="M491 512l-470 0c-5,0 -8,-4 -7,-8l82 -498c1,-3 4,-6 8,-6l308 0c4,0 7,2 7,6l79 498c1,3 -2,8 -7,8zm-406 -14l56 -484 -31 0 -81 484 56 0zm288 -484l54 484 56 0 -77 -484 -33 0zm40 484l-54 -484 -64 0 0 86c-1,4 -4,7 -8,7l-47 0c-4,0 -7,-3 -7,-7l0 -86 -77 0 -57 484 134 0 0 -86c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8l0 86 118 0zm-133 0l0 -79 -33 0 0 79 33 0zm7 -121l-47 0c-4,0 -7,-3 -7,-7l0 -93c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8l0 93c-1,4 -4,7 -8,7zm-40 -14l33 0 0 -79 -33 0 0 79zm40 -121l-47 0c-4,0 -7,-3 -7,-7l0 -93c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8l0 93c-1,4 -4,7 -8,7zm-40 -14l33 0 0 -79 -33 0 0 79zm0 -135l33 0 0 -79 -33 0 0 79z"/>
+ <path class="fil1" d="M295 498l118 0 -54 -484 -64 0 0 86c-1,4 -4,7 -8,7l-47 0c-4,0 -7,-3 -7,-7l0 -86 -77 0 -57 484 134 0 0 -86c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8l0 86zm0 -221l0 93c-1,4 -4,7 -8,7l-47 0c-4,0 -7,-3 -7,-7l0 -93c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8zm-8 -35l-47 0c-4,0 -7,-3 -7,-7l0 -93c0,-4 3,-8 7,-8l47 0c4,0 8,4 8,8l0 93c-1,4 -4,7 -8,7z"/>
+ <polygon class="fil2" points="247,228 280,228 280,149 247,149 "/>
+ <polygon class="fil2" points="280,284 247,284 247,363 280,363 "/>
+ <polygon class="fil2" points="280,419 247,419 247,498 280,498 "/>
+ <polygon class="fil2" points="247,93 280,93 280,14 247,14 "/>
+ <polygon class="fil1" points="373,14 427,498 483,498 406,14 "/>
+ <polygon class="fil1" points="85,498 141,14 110,14 29,498 "/>
+ </g>
+</svg>
diff --git a/test/.clang-tidy b/test/.clang-tidy
new file mode 100644
index 0000000..6e9ebae
--- /dev/null
+++ b/test/.clang-tidy
@@ -0,0 +1,3 @@
+InheritParentConfig: true
+Checks: '-readability-magic-numbers'
+
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index 0b830a8..bdaaa45 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -43,11 +43,21 @@ project : requirements
<toolset>tidy:<librarydef>boost
;
lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ;
+rule perfrun ( main + : extra-requirements * : runtime-dependency * : command-args * ) {
+ local name = $(main[0]:S=) ;
+ local benchmark = $(main[0]:S=.benchmark) ;
+ explicit $(name) ;
+ exe $(benchmark) : $(main) :
+ <library>benchmark
+ <library>test
+ $(extra-requirements) ;
+ run $(benchmark) : $(command-args) : : <dependency>$(runtime-dependency) : $(name) ;
+}
run test-collection.cpp ;
run test-maths.cpp ;
run test-lib.cpp ;
-run test-geoData.cpp : -- : [ sequence.insertion-sort [ glob-tree $(fixtures)/geoData : *.json ] fixtures/height/SD19.asc ] : <library>test ;
+run [ glob test-geoData*.cpp ] : -- : [ sequence.insertion-sort [ glob-tree $(fixtures)/geoData : *.json ] fixtures/height/SD19.asc ] : <library>test ;
run test-network.cpp : : : <library>test ;
run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob-tree $(fixtures)/json : *.json ] ] : <library>test ;
run test-text.cpp : -- : test-glContainer : <library>test ;
@@ -55,19 +65,18 @@ run test-enumDetails.cpp ;
run test-render.cpp : -- : test-assetFactory : <library>test ;
run test-glContextBhvr.cpp ;
run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.* ] fixtures/rgb.txt test-instancing ] : <library>test ;
-run perf-assetFactory.cpp : \< : test-assetFactory : <library>benchmark <library>test ;
-run perf-geoData.cpp : \< : test-geoData : <library>test <library>benchmark ;
-run perf-persistence.cpp : \< : test-persistence : <library>benchmark <library>test ;
+perfrun perf-assetFactory.cpp : : test-assetFactory ;
+perfrun perf-geoData.cpp : : test-geoData ;
+perfrun perf-terrain.cpp : : test-geoData ;
+perfrun perf-persistence.cpp : : test-persistence ;
run test-worker.cpp ;
run test-instancing.cpp : -- : test-glContainer : <library>test ;
-run perf-instancing.cpp : \< : test-instancing : <library>benchmark <library>test ;
+perfrun perf-instancing.cpp : : test-instancing ;
run test-glContainer.cpp : : : <library>test ;
run test-pack.cpp : : : <library>test ;
+run test-environment.cpp : : : <library>test ;
+run test-ui.cpp : : : <library>test ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
-explicit perf-assetFactory ;
-explicit perf-persistence ;
-explicit perf-geoData ;
-explicit perf-instancing ;
-alias perf : perf-assetFactory perf-persistence perf-geoData perf-instancing ;
+alias perf : perf-assetFactory perf-persistence perf-geoData perf-instancing perf-terrain ;
explicit perf ;
diff --git a/test/enumDetailsData.h b/test/enumDetailsData.h
index b7bd601..6383838 100644
--- a/test/enumDetailsData.h
+++ b/test/enumDetailsData.h
@@ -1,23 +1,24 @@
#pragma once
+#include <cstdint>
#include <enumDetails.h>
-enum GlobalUnscoped { aa, b, c };
-enum class GlobalScoped { aa, b, c };
+enum GlobalUnscoped : uint8_t { Aa, B, C };
+enum class GlobalScoped : int8_t { Aa, B, C };
namespace ns {
- enum Unscoped { aa, b, c };
- enum class Scoped { aa, b, c };
+ enum Unscoped : int8_t { Aa, B, C };
+ enum class Scoped : int8_t { Aa, B, C };
}
namespace test1 {
- enum class DefaultDense { a, bee, ci, de };
+ enum class DefaultDense : int8_t { A, Bee, Ci, De };
}
namespace test2 {
- enum class NumberedSparse { a = 0, bee = 3, ci = -20, de = 100 };
+ enum class NumberedSparse : int8_t { A = 0, Bee = 3, Ci = -20, De = 100 };
}
template<> struct EnumValueCollection<test2::NumberedSparse> {
// Any ordered integer_sequence which includes all enumeration values
- using Vs = std::integer_sequence<int, -100, -20, 0, 3, 10, 100, 1000>;
+ using Vs = std::integer_sequence<int8_t, -100, -20, 0, 3, 10, 100, 110>;
};
diff --git a/test/fixtures/geoData/deform/multi1.json b/test/fixtures/geoData/deform/multi1.json
new file mode 100644
index 0000000..c7456b6
--- /dev/null
+++ b/test/fixtures/geoData/deform/multi1.json
@@ -0,0 +1,21 @@
+[
+ [
+ [
+ [
+ 100,
+ 100,
+ 100
+ ],
+ [
+ 150,
+ 100,
+ 100
+ ],
+ [
+ 100,
+ 150,
+ 100
+ ]
+ ]
+ ]
+]
diff --git a/test/perf-assetFactory.cpp b/test/perf-assetFactory.cpp
index 671713c..0f7895b 100644
--- a/test/perf-assetFactory.cpp
+++ b/test/perf-assetFactory.cpp
@@ -2,27 +2,29 @@
#include "testMainWindow.h"
#include <benchmark/benchmark.h>
-static void
-brush47xml_load(benchmark::State & state)
-{
- TestMainWindowAppBase window;
+namespace {
+ void
+ brush47xmlLoad(benchmark::State & state)
+ {
+ TestMainWindowAppBase window;
- for (auto _ : state) {
- benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/brush47.xml"));
+ for (auto loop : state) {
+ benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/brush47.xml"));
+ }
}
-}
-static void
-foliagexml_load(benchmark::State & state)
-{
- TestMainWindowAppBase window;
+ void
+ foliagexmlLoad(benchmark::State & state)
+ {
+ TestMainWindowAppBase window;
- for (auto _ : state) {
- benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/foliage.xml"));
+ for (auto loop : state) {
+ benchmark::DoNotOptimize(AssetFactory::loadXML(RESDIR "/foliage.xml"));
+ }
}
}
-BENCHMARK(brush47xml_load);
-BENCHMARK(foliagexml_load);
+BENCHMARK(brush47xmlLoad);
+BENCHMARK(foliagexmlLoad);
BENCHMARK_MAIN();
diff --git a/test/perf-geoData.cpp b/test/perf-geoData.cpp
index 4d4505e..679f19f 100644
--- a/test/perf-geoData.cpp
+++ b/test/perf-geoData.cpp
@@ -2,41 +2,56 @@
#include <game/geoData.h>
namespace {
- const GeoData tm {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")};
+ const GeoData GEO_DATA_FIXTURE {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")};
void
- terrain_findPoint(benchmark::State & state)
+ terrainFindPoint(benchmark::State & state)
{
- for (auto _ : state) {
- benchmark::DoNotOptimize(tm.findPoint({315555000, 495556000}));
+ for (auto loop : state) {
+ benchmark::DoNotOptimize(GEO_DATA_FIXTURE.findPoint({315555000, 495556000}));
}
}
void
- terrain_walk(benchmark::State & state)
+ terrainWalk(benchmark::State & state)
{
const glm::vec2 point {310001000, 490000000};
- const GeoData::PointFace start {point, tm.findPoint(point)};
- for (auto _ : state) {
- tm.walk(start, {319999000, 500000000}, [](auto f) {
- benchmark::DoNotOptimize(f);
+ const GeoData::PointFace start {point, GEO_DATA_FIXTURE.findPoint(point)};
+ for (auto loop : state) {
+ GEO_DATA_FIXTURE.walk(start, {319999000, 500000000}, [](auto step) {
+ benchmark::DoNotOptimize(step);
});
}
}
void
- terrain_walkBoundary(benchmark::State & state)
+ terrainWalkBoundary(benchmark::State & state)
{
- for (auto _ : state) {
- tm.boundaryWalk([](auto heh) {
+ for (auto loop : state) {
+ GEO_DATA_FIXTURE.boundaryWalk([](auto heh) {
benchmark::DoNotOptimize(heh);
});
}
}
+
+ void
+ terrainDeform(benchmark::State & state)
+ {
+ std::array<GlobalPosition3D, 3> points {{
+ {315555000, 495556000, 0},
+ {315655000, 495556000, 0},
+ {315655000, 495557000, 0},
+ }};
+ for (auto loop : state) {
+ auto geoData {GEO_DATA_FIXTURE};
+ benchmark::DoNotOptimize(geoData.setHeights(points, GeoData::SetHeightsOpts {.surface = nullptr}));
+ }
+ }
}
-BENCHMARK(terrain_findPoint);
-BENCHMARK(terrain_walk);
-BENCHMARK(terrain_walkBoundary);
+BENCHMARK(terrainFindPoint);
+BENCHMARK(terrainWalk);
+BENCHMARK(terrainWalkBoundary);
+BENCHMARK(terrainDeform);
BENCHMARK_MAIN();
diff --git a/test/perf-instancing.cpp b/test/perf-instancing.cpp
index 3638111..a56d60e 100644
--- a/test/perf-instancing.cpp
+++ b/test/perf-instancing.cpp
@@ -3,38 +3,41 @@
#include <benchmark/benchmark.h>
#include <random>
-struct Instance {
- GlobalPosition3D pos;
- glm::mat3 rot;
-};
+namespace {
+ struct Instance {
+ GlobalPosition3D pos;
+ glm::mat3 rot;
+ };
-struct data {
- explicit data(size_t n)
- {
- std::mt19937 gen(std::random_device {}());
- std::uniform_int_distribution<GlobalDistance> xy(0, 1000000);
- std::uniform_int_distribution<GlobalDistance> z(0, 10000);
- while (n--) {
- proxies.emplace_back(instances.acquire(GlobalPosition3D {xy(gen), xy(gen), z(gen)}, glm::mat3 {}));
+ struct Data {
+ explicit Data(size_t n)
+ {
+ std::mt19937 gen(std::random_device {}());
+ std::uniform_int_distribution<GlobalDistance> xyDistrib(0, 1000000);
+ std::uniform_int_distribution<GlobalDistance> zDistrib(0, 10000);
+ while (n--) {
+ proxies.emplace_back(instances.acquire(
+ GlobalPosition3D {xyDistrib(gen), xyDistrib(gen), zDistrib(gen)}, glm::mat3 {}));
+ }
}
- }
- InstanceVertices<Instance> instances;
- std::vector<InstanceVertices<Instance>::InstanceProxy> proxies;
-};
+ InstanceVertices<Instance> instances;
+ std::vector<InstanceVertices<Instance>::InstanceProxy> proxies;
+ };
-static void
-partition(benchmark::State & state)
-{
- TestMainWindowAppBase window;
- data d(static_cast<size_t>(state.range()));
- GlobalPosition2D pos {};
- for (auto _ : state) {
- d.instances.partition([&pos](const auto & i) {
- return std::abs(i.pos.x - pos.x) < 5 && std::abs(i.pos.y - pos.y) < 5;
- });
- pos += GlobalPosition2D {33, 17};
- pos %= 1000000;
+ void
+ partition(benchmark::State & state)
+ {
+ TestMainWindowAppBase window;
+ Data data(static_cast<size_t>(state.range()));
+ GlobalPosition2D pos {};
+ for (auto loop : state) {
+ data.instances.partition([&pos](const auto & instance) {
+ return std::abs(instance.pos.x - pos.x) < 5 && std::abs(instance.pos.y - pos.y) < 5;
+ });
+ pos += GlobalPosition2D {33, 17};
+ pos %= 1000000;
+ }
}
}
diff --git a/test/perf-persistence.cpp b/test/perf-persistence.cpp
index 2e099bf..a39b0ba 100644
--- a/test/perf-persistence.cpp
+++ b/test/perf-persistence.cpp
@@ -1,23 +1,24 @@
#include "lib/jsonParse-persistence.h"
-#include "testMainWindow.h"
#include "testStructures.h"
#include <benchmark/benchmark.h>
-template<typename T>
-static void
-parse_load_object(benchmark::State & state, T &&, const char * path)
-{
- for (auto _ : state) {
- std::ifstream in {path};
- benchmark::DoNotOptimize(Persistence::JsonParsePersistence {}.loadState<T>(in));
+namespace {
+ template<typename T>
+ void
+ parseLoadObject(benchmark::State & state, T &&, const char * path)
+ {
+ for (auto loop : state) {
+ std::ifstream inStrm {path};
+ benchmark::DoNotOptimize(Persistence::JsonParsePersistence {}.loadState<T>(inStrm));
+ }
}
}
-BENCHMARK_CAPTURE(parse_load_object, load_object, std::unique_ptr<TestObject> {}, FIXTURESDIR "json/load_object.json");
-BENCHMARK_CAPTURE(parse_load_object, nested, std::unique_ptr<TestObject> {}, FIXTURESDIR "json/nested.json");
-BENCHMARK_CAPTURE(parse_load_object, shared_ptr_diff, std::unique_ptr<SharedTestObject> {},
+BENCHMARK_CAPTURE(parseLoadObject, load_object, std::unique_ptr<TestObject> {}, FIXTURESDIR "json/load_object.json");
+BENCHMARK_CAPTURE(parseLoadObject, nested, std::unique_ptr<TestObject> {}, FIXTURESDIR "json/nested.json");
+BENCHMARK_CAPTURE(parseLoadObject, shared_ptr_diff, std::unique_ptr<SharedTestObject> {},
FIXTURESDIR "json/shared_ptr_diff.json");
-BENCHMARK_CAPTURE(parse_load_object, shared_ptr_same, std::unique_ptr<SharedTestObject> {},
+BENCHMARK_CAPTURE(parseLoadObject, shared_ptr_same, std::unique_ptr<SharedTestObject> {},
FIXTURESDIR "json/shared_ptr_same.json");
BENCHMARK_MAIN();
diff --git a/test/perf-terrain.cpp b/test/perf-terrain.cpp
new file mode 100644
index 0000000..ed6a200
--- /dev/null
+++ b/test/perf-terrain.cpp
@@ -0,0 +1,37 @@
+#include "game/terrain.h"
+#include "gfx/camera.h"
+#include "gfx/gl/sceneShader.h"
+#include "testMainWindow.h"
+#include <benchmark/benchmark.h>
+
+namespace {
+ const TestMainWindowAppBase WINDOW;
+
+ void
+ terrainMeshgen(benchmark::State & state)
+ {
+ Terrain terrain {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")};
+
+ for (auto loop : state) {
+ terrain.generateMeshes();
+ }
+ }
+
+ void
+ terrainRender(benchmark::State & state)
+ {
+ Terrain terrain {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")};
+ SceneShader shader;
+ Camera cam {terrain.getExtents().min + GlobalPosition3D {0, 0, 10000}, 45.F, 1.F, 1, 10000};
+ cam.setForward(::north + ::east);
+
+ for (auto loop : state) {
+ terrain.render(shader, cam);
+ }
+ }
+}
+
+BENCHMARK(terrainMeshgen);
+BENCHMARK(terrainRender);
+
+BENCHMARK_MAIN();
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
index 1c2c417..ac7e04c 100644
--- a/test/test-assetFactory.cpp
+++ b/test/test-assetFactory.cpp
@@ -19,72 +19,77 @@
#include "gfx/renderable.h"
#include "lib/collection.h"
#include "lib/location.h"
-#include "lib/stream_support.h"
+#include "lib/stream_support.h" // IWYU pragma: keep
#include "testMainWindow.h"
+#include <special_members.h>
BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
-const std::filesystem::path TMP {"/tmp"};
-
-class FactoryFixture : public TestRenderOutputSize<TextureAbsCoord {2048, 1024}>, public SceneProvider {
-public:
- FactoryFixture() : sceneRenderer {size, output} { }
-
- ~FactoryFixture()
- {
- 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, outpath.c_str());
- }
-
- void
- content(const SceneShader & shader) const override
- {
- shader.basic.use(Location {{0, 0, 0}, {0, 0, 0}});
- objects.apply(&Renderable::render, shader);
- }
-
- void
- lights(const SceneShader & shader) const override
- {
- objects.apply(&Renderable::lights, shader);
- }
-
- void
- environment(const SceneShader &, const SceneRenderer & sceneRenderer) const override
- {
- sceneRenderer.setAmbientLight({.4, .4, .4});
- sceneRenderer.setDirectionalLight({.6, .6, .6}, east + south + south + down, *this);
- }
-
- void
- shadows(const ShadowMapper & mapper) const override
- {
- mapper.dynamicPoint.use(Location {{0, 0, 0}, {0, 0, 0}});
- objects.apply(&Renderable::shadows, mapper);
- }
-
- void
- render(float dist)
- {
- sceneRenderer.camera.setView({-dist, dist * 1.2f, dist * 1.2f}, south + east + down);
- sceneRenderer.render(*this);
- }
-
- Collection<const Renderable> objects;
-
-private:
- SceneRenderer sceneRenderer;
-};
-
-BOOST_AUTO_TEST_CASE(surfaces, *boost::unit_test::timeout(5))
+namespace {
+ class FactoryFixture : public TestRenderOutputSize<TextureAbsCoord {2048, 1024}>, public SceneProvider {
+ public:
+ FactoryFixture() : sceneRenderer {size, output} { }
+
+ NO_COPY(FactoryFixture);
+ NO_MOVE(FactoryFixture);
+
+ ~FactoryFixture() override
+ {
+ auto outpath = (ANALYSIS_DIRECTORY / boost::unit_test::framework::current_test_case().full_name())
+ .replace_extension(".tga");
+ std::filesystem::create_directories(outpath.parent_path());
+ Texture::save(outImage, outpath.c_str());
+ }
+
+ void
+ content(const SceneShader & shader, const Frustum & frustum) const override
+ {
+ shader.basic.use(Location {.pos = {0, 0, 0}, .rot = {0, 0, 0}});
+ objects.apply(&Renderable::render, shader, frustum);
+ }
+
+ void
+ lights(const SceneShader & shader) const override
+ {
+ objects.apply(&Renderable::lights, shader);
+ }
+
+ void
+ environment(const SceneShader &, const SceneRenderer & sceneRenderer) const override
+ {
+ sceneRenderer.setAmbientLight({.4, .4, .4});
+ sceneRenderer.setDirectionalLight({.6, .6, .6}, {{0.9, 0.5}}, *this);
+ }
+
+ void
+ shadows(const ShadowMapper & mapper, const Frustum & frustum) const override
+ {
+ mapper.dynamicPoint.use(Location {.pos = {0, 0, 0}, .rot = {0, 0, 0}});
+ objects.apply(&Renderable::shadows, mapper, frustum);
+ }
+
+ void
+ render(float dist)
+ {
+ sceneRenderer.camera.setView({-dist, dist * 1.2F, dist * 1.2F}, south + east + down);
+ sceneRenderer.render(*this);
+ }
+
+ SharedCollection<const Renderable> objects;
+
+ private:
+ SceneRenderer sceneRenderer;
+ };
+}
+
+BOOST_AUTO_TEST_CASE(Surfaces, *boost::unit_test::timeout(5))
{
- auto mf = AssetFactory::loadXML(RESDIR "/surfaces.xml");
- BOOST_REQUIRE(mf);
- BOOST_CHECK_EQUAL(4, mf->assets.size());
- auto gravelAsset = mf->assets.at("terrain.surface.gravel");
+ auto factory = AssetFactory::loadXML(RESDIR "/surfaces.xml");
+ BOOST_REQUIRE(factory);
+ BOOST_CHECK_EQUAL(4, factory->assets.size());
+ auto gravelAsset = factory->assets.at("terrain.surface.gravel");
BOOST_REQUIRE(gravelAsset);
- auto gravel = std::dynamic_pointer_cast<Surface>(gravelAsset);
+ auto gravel = gravelAsset.dynamicCast<Surface>();
BOOST_REQUIRE(gravel);
BOOST_REQUIRE_EQUAL(gravel->name, "Gravel");
BOOST_REQUIRE_EQUAL(gravel->colorBias, RGB {.9F});
@@ -93,84 +98,87 @@ BOOST_AUTO_TEST_CASE(surfaces, *boost::unit_test::timeout(5))
BOOST_FIXTURE_TEST_SUITE(m, FactoryFixture);
-BOOST_AUTO_TEST_CASE(brush47xml, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(Brush47xml, *boost::unit_test::timeout(5))
{
- auto mf = AssetFactory::loadXML(RESDIR "/brush47.xml");
- BOOST_REQUIRE(mf);
- BOOST_REQUIRE_GE(mf->shapes.size(), 6);
- BOOST_CHECK(mf->shapes.at("plane"));
- BOOST_CHECK(mf->shapes.at("cylinder"));
- BOOST_CHECK(mf->shapes.at("cuboid"));
- BOOST_CHECK(mf->shapes.at("wheel"));
- BOOST_CHECK(mf->shapes.at("axel"));
- auto bogie = mf->shapes.at("bogie");
+ auto factory = AssetFactory::loadXML(RESDIR "/brush47.xml");
+ BOOST_REQUIRE(factory);
+ gameState.assets = factory->assets;
+ BOOST_REQUIRE_GE(factory->shapes.size(), 6);
+ BOOST_CHECK(factory->shapes.at("plane"));
+ BOOST_CHECK(factory->shapes.at("cylinder"));
+ BOOST_CHECK(factory->shapes.at("cuboid"));
+ BOOST_CHECK(factory->shapes.at("wheel"));
+ BOOST_CHECK(factory->shapes.at("axel"));
+ auto bogie = factory->shapes.at("bogie");
BOOST_REQUIRE(bogie);
auto bogieObj = std::dynamic_pointer_cast<const Object>(bogie);
BOOST_CHECK_GE(bogieObj->uses.size(), 3);
- BOOST_CHECK_EQUAL(1, mf->assets.size());
- auto brush47 = mf->assets.at("brush-47");
+ BOOST_CHECK_EQUAL(1, factory->assets.size());
+ auto brush47 = factory->assets.at("brush-47");
BOOST_REQUIRE(brush47);
- auto brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(brush47);
+ auto brush47rvc = brush47.dynamicCast<RailVehicleClass>();
BOOST_REQUIRE(brush47rvc);
BOOST_REQUIRE(brush47rvc->bodyMesh);
BOOST_REQUIRE(brush47rvc->bogies.front());
BOOST_REQUIRE(brush47rvc->bogies.back());
auto railVehicle = std::make_shared<RailVehicle>(brush47rvc);
- objects.objects.push_back(brush47rvc);
+ objects.emplace(brush47rvc);
render(10000);
}
-BOOST_AUTO_TEST_CASE(foliage, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(Tree, *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 plant1 = std::make_shared<Plant>(tree_01_1_f, Location {{-2000, 2000, 0}, {0, 0, 0}});
- auto plant2 = std::make_shared<Plant>(tree_01_1_f, Location {{3000, -4000, 0}, {0, 1, 0}});
- auto plant3 = std::make_shared<Plant>(tree_01_1_f, Location {{-2000, -4000, 0}, {0, 2, 0}});
- auto plant4 = std::make_shared<Plant>(tree_01_1_f, Location {{3000, 2000, 0}, {0, 3, 0}});
- objects.objects.push_back(tree_01_1_f);
+ auto factory = AssetFactory::loadXML(RESDIR "/foliage.xml");
+ BOOST_REQUIRE(factory);
+ gameState.assets = factory->assets;
+ auto tree011Asset = factory->assets.at("Tree-01-1");
+ BOOST_REQUIRE(tree011Asset);
+ auto tree011 = tree011Asset.dynamicCast<Foliage>();
+ BOOST_REQUIRE(tree011);
+
+ auto plant1 = std::make_shared<Plant>(tree011, Location {.pos = {-2000, 2000, 0}, .rot = {0, 0, 0}});
+ auto plant2 = std::make_shared<Plant>(tree011, Location {.pos = {3000, -4000, 0}, .rot = {0, 1, 0}});
+ auto plant3 = std::make_shared<Plant>(tree011, Location {.pos = {-2000, -4000, 0}, .rot = {0, 2, 0}});
+ auto plant4 = std::make_shared<Plant>(tree011, Location {.pos = {3000, 2000, 0}, .rot = {0, 3, 0}});
+ objects.emplace(tree011);
render(6000);
}
-BOOST_AUTO_TEST_CASE(lights, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(Lights, *boost::unit_test::timeout(5))
{
- auto mf = AssetFactory::loadXML(RESDIR "/lights.xml");
- BOOST_REQUIRE(mf);
- auto rlight = mf->assets.at("r-light");
+ auto factory = AssetFactory::loadXML(RESDIR "/lights.xml");
+ BOOST_REQUIRE(factory);
+ gameState.assets = factory->assets;
+ auto rlightAsset = factory->assets.at("r-light");
+ BOOST_REQUIRE(rlightAsset);
+ auto oldlampAsset = factory->assets.at("old-lamp");
+ BOOST_REQUIRE(oldlampAsset);
+ auto rlight = rlightAsset.dynamicCast<Illuminator>();
BOOST_REQUIRE(rlight);
- auto oldlamp = mf->assets.at("old-lamp");
+ auto oldlamp = oldlampAsset.dynamicCast<Illuminator>();
BOOST_REQUIRE(oldlamp);
- auto rlight_f = std::dynamic_pointer_cast<Illuminator>(rlight);
- BOOST_REQUIRE(rlight_f);
- auto oldlamp_f = std::dynamic_pointer_cast<Illuminator>(oldlamp);
- BOOST_REQUIRE(oldlamp_f);
-
- auto light1 = std::make_shared<Light>(oldlamp_f, Location {{0, 0, 0}, {0, 0, 0}});
- auto light2 = std::make_shared<Light>(rlight_f, Location {{-4000, 0, 0}, {0, 2, 0}});
- auto light3 = std::make_shared<Light>(rlight_f, Location {{-4000, -4000, 0}, {0, 1, 0}});
- auto light4 = std::make_shared<Light>(oldlamp_f, Location {{3000, 4600, 0}, {0, 2, 0}});
- objects.objects.push_back(rlight_f);
- objects.objects.push_back(oldlamp_f);
+
+ auto light1 = std::make_shared<Light>(oldlamp, Location {.pos = {0, 0, 0}, .rot = {0, 0, 0}});
+ auto light2 = std::make_shared<Light>(rlight, Location {.pos = {-4000, 0, 0}, .rot = {0, 2, 0}});
+ auto light3 = std::make_shared<Light>(rlight, Location {.pos = {-4000, -4000, 0}, .rot = {0, 1, 0}});
+ auto light4 = std::make_shared<Light>(oldlamp, Location {.pos = {3000, 4600, 0}, .rot = {0, 2, 0}});
+ objects.emplace(rlight);
+ objects.emplace(oldlamp);
// yes I'm hacking some floor to light up as though its a bush
- auto floorf = std::dynamic_pointer_cast<Foliage>(mf->assets.at("floor"));
+ auto floorf = factory->assets.at("floor").dynamicCast<Foliage>();
auto floor = std::make_shared<Plant>(floorf, Location {});
- objects.objects.push_back(floorf);
+ objects.emplace(floorf);
render(6000);
}
BOOST_AUTO_TEST_SUITE_END();
-BOOST_AUTO_TEST_CASE(loadall)
+BOOST_AUTO_TEST_CASE(Loadall)
{
const auto assets = AssetFactory::loadAll(RESDIR);
BOOST_CHECK(assets.at("brush-47"));
@@ -179,7 +187,7 @@ BOOST_AUTO_TEST_CASE(loadall)
template<typename T> using InOut = std::tuple<T, T>;
-BOOST_DATA_TEST_CASE(normalizeColourName,
+BOOST_DATA_TEST_CASE(NormalizeColourName,
boost::unit_test::data::make<InOut<std::string>>({
{"", ""},
{"black", "black"},
@@ -191,14 +199,14 @@ BOOST_DATA_TEST_CASE(normalizeColourName,
{"BlAck ", "black"},
{"Bl Ack ", "black"},
}),
- in_, exp)
+ input, exp)
{
- auto in {in_};
- BOOST_CHECK_NO_THROW(AssetFactory::normalizeColourName(in));
- BOOST_CHECK_EQUAL(in, exp);
+ auto toNormalize {input};
+ BOOST_CHECK_NO_THROW(AssetFactory::normalizeColourName(toNormalize));
+ BOOST_CHECK_EQUAL(toNormalize, exp);
}
-BOOST_AUTO_TEST_CASE(parseX11RGB, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(ParseX11RGB, *boost::unit_test::timeout(5))
{
const auto parsedColours = AssetFactory::parseX11RGB(FIXTURESDIR "rgb.txt");
BOOST_REQUIRE_EQUAL(parsedColours.size(), 20);
@@ -207,7 +215,7 @@ BOOST_AUTO_TEST_CASE(parseX11RGB, *boost::unit_test::timeout(5))
BOOST_CHECK_CLOSE_VEC(parsedColours.at("lightsteelblue1"), AssetFactory::Colour(0.79, 0.88, 1));
}
-BOOST_AUTO_TEST_CASE(texturePacker, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(TexturePackerSimple, *boost::unit_test::timeout(5))
{
std::vector<TexturePacker::Image> input {
{10, 10},
@@ -217,28 +225,28 @@ BOOST_AUTO_TEST_CASE(texturePacker, *boost::unit_test::timeout(5))
{10, 200},
{5, 5},
};
- TexturePacker tp {input};
- BOOST_CHECK_EQUAL(TexturePacker::Size(128, 256), tp.minSize());
- const auto result = tp.pack();
+ TexturePacker packer {input};
+ BOOST_CHECK_EQUAL(TexturePacker::Size(128, 256), packer.minSize());
+ const auto result = packer.pack();
}
-BOOST_AUTO_TEST_CASE(texturePacker_many, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(TexturePackerMany, *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(), 0, [](auto t, const auto & i) {
- return t + TexturePacker::area(i);
+ const auto totalSize = std::ranges::fold_left(images, 0, [](auto total, const auto & image) {
+ return total + TexturePacker::area(image);
});
- TexturePacker tp {images};
- BOOST_CHECK_EQUAL(TexturePacker::Size(32, 32), tp.minSize());
- const auto result = tp.pack();
+ TexturePacker packer {images};
+ BOOST_CHECK_EQUAL(TexturePacker::Size(32, 32), packer.minSize());
+ const auto result = packer.pack();
BOOST_CHECK_EQUAL(result.first.size(), images.size());
BOOST_CHECK_GE(TexturePacker::area(result.second),
TexturePacker::area(images.front()) * static_cast<GLsizei>(images.size()));
BOOST_CHECK_EQUAL(totalSize, TexturePacker::area(result.second));
}
-BOOST_AUTO_TEST_CASE(texturePacker_many_random, *boost::unit_test::timeout(15))
+BOOST_AUTO_TEST_CASE(TexturePackerManyRandom, *boost::unit_test::timeout(15))
{
std::vector<TexturePacker::Image> images(2048);
std::mt19937 gen(std::random_device {}());
@@ -246,7 +254,7 @@ BOOST_AUTO_TEST_CASE(texturePacker_many_random, *boost::unit_test::timeout(15))
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();
+ TexturePacker packer {images};
+ const auto result = packer.pack();
BOOST_CHECK_EQUAL(result.first.size(), images.size());
}
diff --git a/test/test-collection.cpp b/test/test-collection.cpp
index 00204fc..45086a9 100644
--- a/test/test-collection.cpp
+++ b/test/test-collection.cpp
@@ -8,109 +8,394 @@
#include <special_members.h>
#include <vector>
-class Base {
-public:
- Base() = default;
- virtual ~Base() = default;
- DEFAULT_MOVE_COPY(Base);
-
- virtual bool
- add()
- {
- total += 1;
- return false;
- }
-
- unsigned int total {0};
-};
-
-class Sub : public Base {
-public:
- bool
- add() override
- {
- total += 2;
- return true;
- }
-};
-
-using TestCollection = Collection<Base>;
-
-BOOST_TEST_DONT_PRINT_LOG_VALUE(Collection<Base>::Objects::const_iterator)
-BOOST_TEST_DONT_PRINT_LOG_VALUE(Collection<Base>::Objects::const_reverse_iterator)
+namespace {
+ class Base {
+ public:
+ Base() = default;
+ virtual ~Base() = default;
+ DEFAULT_MOVE_COPY(Base);
+
+ virtual bool
+ add()
+ {
+ total += 1;
+ return false;
+ }
+
+ [[nodiscard]] virtual bool
+ yes() const
+ {
+ return true;
+ }
+
+ unsigned int total {0};
+ };
+
+ class Sub : public Base {
+ public:
+ bool
+ add() override
+ {
+ total += 2;
+ return true;
+ }
+ };
+
+ class Sub1 : public Sub { };
+
+ class Sub2 : public Sub { };
+
+ class Base2 {
+ public:
+ Base2() = default;
+ virtual ~Base2() = default;
+ DEFAULT_MOVE_COPY(Base2);
+ };
+
+ class Multi : public Sub1, public Base2 { };
+
+ using TestCollection = SharedCollection<Base, Sub>;
+}
+
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestCollection::Objects::iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestCollection::Objects::const_iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestCollection::Objects::const_reverse_iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestCollection::OtherObjects<Sub>::iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestCollection::OtherObjects<Sub>::const_iterator)
BOOST_FIXTURE_TEST_SUITE(tc, TestCollection)
-BOOST_AUTO_TEST_CASE(empty)
+BOOST_AUTO_TEST_CASE(Empty)
{
+ BOOST_CHECK(TestCollection::empty());
BOOST_REQUIRE(!apply(&Base::add));
- const auto i = applyOne(&Base::add);
- BOOST_CHECK_EQUAL(i, end());
+ const auto appliedTo = applyOne(&Base::add);
+ BOOST_CHECK_EQUAL(appliedTo, end());
+ BOOST_CHECK(!find<Base>());
+ BOOST_CHECK(!find<Sub>());
+ BOOST_CHECK(!find<Sub1>());
}
-BOOST_AUTO_TEST_CASE(a_base)
+BOOST_AUTO_TEST_CASE(ABaseApply)
{
- auto b = create<Base>();
+ auto base = create<Base>();
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
BOOST_REQUIRE(apply(&Base::add));
- BOOST_CHECK_EQUAL(b->total, 1);
- const auto i = applyOne(&Base::add);
- BOOST_CHECK_EQUAL(i, end());
+ BOOST_CHECK_EQUAL(base->total, 1);
+ const auto appliedTo = applyOne(&Base::add);
+ BOOST_CHECK_EQUAL(appliedTo, end());
+ BOOST_CHECK_EQUAL(base.get(), find<Base>());
+ BOOST_CHECK(!find<Sub>());
+ BOOST_CHECK(!find<Sub1>());
}
-BOOST_AUTO_TEST_CASE(a_rbase)
+BOOST_AUTO_TEST_CASE(EmplaceOthers)
{
- auto b = create<Base>();
+ auto base = emplace(std::make_shared<Base>());
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
+ auto sub = emplace(std::make_shared<Sub>());
+ BOOST_CHECK_EQUAL(objects.size(), 2);
+ BOOST_CHECK_EQUAL(std::get<OtherObjects<Sub>>(otherObjects).size(), 1);
+ BOOST_CHECK_EQUAL(base.get(), find<Base>());
+ BOOST_CHECK_EQUAL(sub.get(), find<Sub>());
+ BOOST_CHECK(!find<Sub1>());
+}
+
+BOOST_AUTO_TEST_CASE(ABaseRApply)
+{
+ auto base = create<Base>();
BOOST_REQUIRE(rapply(&Base::add));
- BOOST_CHECK_EQUAL(b->total, 1);
- const auto i = rapplyOne(&Base::add);
- BOOST_CHECK_EQUAL(i, rend());
+ BOOST_CHECK_EQUAL(base->total, 1);
+ const auto appliedTo = rapplyOne(&Base::add);
+ BOOST_CHECK_EQUAL(appliedTo, rend());
}
-BOOST_AUTO_TEST_CASE(a_sub)
+BOOST_AUTO_TEST_CASE(ASubApply)
{
- auto s = create<Sub>();
+ auto sub = create<Sub>();
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK_EQUAL(std::get<OtherObjects<Sub>>(otherObjects).size(), 1);
BOOST_REQUIRE(apply(&Base::add));
- BOOST_CHECK_EQUAL(s->total, 2);
- const auto i = applyOne(&Base::add);
- BOOST_CHECK_NE(i, end());
- BOOST_CHECK_EQUAL(*i, s);
+ BOOST_CHECK_EQUAL(sub->total, 2);
+ const auto appliedTo = applyOne(&Base::add);
+ BOOST_CHECK_NE(appliedTo, end());
+ BOOST_CHECK_EQUAL(*appliedTo, sub);
+}
+
+BOOST_AUTO_TEST_CASE(Filter)
+{
+ static_assert(TestCollection::idx<Sub>() == 0);
+ static_assert(TestCollection::idx<const Sub>() == 0);
+ static_assert(TestCollection::idx<Sub1>() == 0);
+ static_assert(TestCollection::idx<const Sub1>() == 0);
+ create<Base>();
+ BOOST_CHECK_EQUAL(1, apply<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(0, apply<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(0, apply<Sub1>(&Base::yes));
+ BOOST_CHECK_EQUAL(objects.begin(), applyOne<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).end(), applyOne<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).end(), applyOne<Sub1>(&Base::yes));
+ create<Sub>();
+ BOOST_CHECK_EQUAL(2, apply<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(1, apply<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(0, apply<Sub1>(&Base::yes));
+ BOOST_CHECK_EQUAL(objects.begin(), applyOne<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).begin(), applyOne<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).end(), applyOne<Sub1>(&Base::yes));
+ create<Sub1>();
+ BOOST_CHECK_EQUAL(3, apply<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(2, apply<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(1, apply<Sub1>(&Base::yes));
+ BOOST_CHECK_EQUAL(objects.begin(), applyOne<Base>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).begin(), applyOne<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).begin() + 1, applyOne<Sub1>(&Base::yes));
+
+ BOOST_CHECK_EQUAL(std::get<idx<Sub>()>(otherObjects).size(), 2);
+ BOOST_CHECK_EQUAL(std::get<idx<Sub1>()>(otherObjects).size(), 2);
+
+ BOOST_CHECK_EQUAL(&objects, &containerFor<Base>());
+ BOOST_CHECK_EQUAL(&std::get<0>(otherObjects), &containerFor<Sub>());
+ BOOST_CHECK_EQUAL(&std::get<0>(otherObjects), &containerFor<Sub1>());
+}
+
+BOOST_AUTO_TEST_CASE(BeginEnd)
+{
+ BOOST_CHECK_EQUAL(0, std::distance(begin(), end()));
+ create<Sub>();
+ create<Base>();
+ BOOST_CHECK_EQUAL(2, std::distance(begin(), end()));
+}
+
+BOOST_AUTO_TEST_CASE(RBeginREnd)
+{
+ BOOST_CHECK_EQUAL(0, std::distance(rbegin(), rend()));
+ create<Sub>();
+ create<Base>();
+ BOOST_CHECK_EQUAL(2, std::distance(rbegin(), rend()));
+}
+
+BOOST_AUTO_TEST_CASE(CreateCreate)
+{
+ auto base1 = findOrCreate<Base>();
+ BOOST_CHECK(base1);
+ auto base2 = findOrCreate<Base>();
+ BOOST_CHECK_EQUAL(base1, base2);
+ auto sub1 = findOrCreate<Sub>();
+ BOOST_CHECK_NE(sub1, base1);
+ auto sub2 = findOrCreate<Sub>();
+ BOOST_CHECK_EQUAL(sub1, sub2);
+}
+
+BOOST_AUTO_TEST_CASE(CreateCreateSub)
+{
+ auto sub = findOrCreate<Sub>();
+ auto base = findOrCreate<Base>();
+ BOOST_CHECK_EQUAL(sub, base);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+using TestUniqueCollection = UniqueCollection<Base, Sub>;
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestUniqueCollection::Objects::const_iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestUniqueCollection::Objects::const_reverse_iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestUniqueCollection::Objects::iterator)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(TestUniqueCollection::Objects::reverse_iterator)
+
+BOOST_FIXTURE_TEST_SUITE(utc, TestUniqueCollection)
+
+BOOST_AUTO_TEST_CASE(UniqueCreate)
+{
+ create<Base>();
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
+ create<Sub>();
+ BOOST_CHECK_EQUAL(objects.size(), 2);
+ BOOST_CHECK_EQUAL(std::get<OtherObjects<Sub>>(otherObjects).size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(MoveAssign)
+{
+ create<Base>();
+ create<Sub>();
+
+ TestUniqueCollection::Objects other;
+ TestUniqueCollection::operator=(std::move(other));
+ BOOST_CHECK(objects.empty());
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
+
+ other.push_back(std::make_unique<Sub>());
+ other.push_back(std::make_unique<Base>());
+ TestUniqueCollection::operator=(std::move(other));
+ BOOST_CHECK_EQUAL(objects.size(), 2);
+ BOOST_CHECK_EQUAL(std::get<OtherObjects<Sub>>(otherObjects).size(), 1);
+ BOOST_CHECK(other.empty());
+}
+
+BOOST_AUTO_TEST_CASE(ClearAll)
+{
+ create<Base>();
+ create<Sub>();
+ emplace(std::make_unique<Base>());
+ emplace(std::make_unique<Sub>());
+
+ clear();
+ BOOST_CHECK(objects.empty());
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
+}
+
+BOOST_AUTO_TEST_CASE(RemoveAllOfSub)
+{
+ create<Base>();
+ create<Sub>();
+ emplace(std::make_unique<Base>());
+ emplace(std::make_unique<Sub>());
+ emplace(std::make_unique<Sub1>());
+
+ BOOST_CHECK_EQUAL(removeAll<Sub>(), 3);
+ BOOST_CHECK_EQUAL(objects.size(), 2);
+ BOOST_CHECK(std::get<OtherObjects<Sub>>(otherObjects).empty());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_FIXTURE_TEST_SUITE(btc, UniqueCollection<Base>)
+
+BOOST_AUTO_TEST_CASE(NoOthers)
+{
+ create<Base>();
+ create<Sub>();
+ emplace(std::make_unique<Base>());
+ emplace(std::make_unique<Sub>());
+}
+
+BOOST_AUTO_TEST_CASE(ApplyAll)
+{
+ create<Base>();
+ BOOST_CHECK_EQUAL(0, apply<Sub>(&Base::add));
+ BOOST_CHECK_EQUAL(1, apply<Base>(&Base::add));
+ create<Sub>();
+ BOOST_CHECK_EQUAL(1, apply<Sub>(&Base::add));
+ BOOST_CHECK_EQUAL(2, apply<Base>(&Base::add));
+}
+
+BOOST_AUTO_TEST_CASE(ApplyOneType)
+{
+ create<Base>();
+ BOOST_CHECK_EQUAL(objects.end(), applyOne<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(objects.begin(), applyOne<Base>(&Base::yes));
+ create<Sub>();
+ BOOST_CHECK_EQUAL(objects.begin() + 1, applyOne<Sub>(&Base::yes));
+ BOOST_CHECK_EQUAL(objects.begin(), applyOne<Base>(&Base::yes));
+ create<Sub1>();
+ BOOST_CHECK_EQUAL(objects.begin() + 2, applyOne<Sub1>(&Base::yes));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+using MultiCollection = Collection<std::unique_ptr<Base>, Multi, Sub, Base2>;
+
+BOOST_FIXTURE_TEST_SUITE(multi, MultiCollection)
+
+BOOST_AUTO_TEST_CASE(AddMulti)
+{
+ static_assert(MultiCollection::idx<Multi>() == 0);
+ static_assert(MultiCollection::idx<Sub>() == 1);
+ static_assert(MultiCollection::idx<Base2>() == 2);
+ create<Base>();
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+ create<Sub>();
+ BOOST_CHECK_EQUAL(objects.size(), 2);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 1);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+ create<Sub1>();
+ BOOST_CHECK_EQUAL(objects.size(), 3);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 2);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+ create<Sub2>();
+ BOOST_CHECK_EQUAL(objects.size(), 4);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 3);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+ create<Multi>();
+ BOOST_CHECK_EQUAL(size(), 5);
+ BOOST_CHECK_EQUAL(size<Multi>(), 1);
+ BOOST_CHECK_EQUAL(size<Sub>(), 4);
+ BOOST_CHECK_EQUAL(size<Base2>(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(RemoveMulti)
+{
+ create<Base>();
+ create<Sub>();
+ create<Sub1>();
+ create<Sub2>();
+ create<Multi>();
+ BOOST_CHECK_EQUAL(objects.size(), 5);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 1);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 4);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 1);
+
+ BOOST_CHECK_EQUAL(removeAll<Multi>(), 1);
+ BOOST_CHECK_EQUAL(objects.size(), 4);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 3);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+
+ BOOST_CHECK_EQUAL(removeAll<Sub>(), 3);
+ BOOST_CHECK_EQUAL(objects.size(), 1);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
+
+ BOOST_CHECK_EQUAL(removeAll<Base>(), 1);
+ BOOST_CHECK_EQUAL(objects.size(), 0);
+ BOOST_CHECK_EQUAL(std::get<0>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<1>(otherObjects).size(), 0);
+ BOOST_CHECK_EQUAL(std::get<2>(otherObjects).size(), 0);
}
BOOST_AUTO_TEST_SUITE_END()
-BOOST_AUTO_TEST_CASE(wrapped_ptr_file_cons)
+BOOST_AUTO_TEST_CASE(WrappedPtrFileCons)
{
using FilePtr = wrapped_ptr<FILE, &fclose>;
- const FilePtr fp {fopen, "/dev/null", "r"};
- BOOST_REQUIRE(fp);
- BOOST_CHECK_NO_THROW(fflush(fp));
+ const FilePtr file {fopen, "/dev/null", "r"};
+ BOOST_REQUIRE(file);
+ BOOST_CHECK_NO_THROW(fflush(file));
- BOOST_CHECK_EQUAL(fp.get(), fp.operator->());
- BOOST_CHECK_EQUAL(fp.get(), fp.operator FILE *());
+ BOOST_CHECK_EQUAL(file.get(), file.operator->());
+ BOOST_CHECK_EQUAL(file.get(), file.operator FILE *());
}
-BOOST_AUTO_TEST_CASE(wrapped_ptr_file_move)
+BOOST_AUTO_TEST_CASE(WrappedPtrFileMove)
{
using FilePtr = wrapped_ptr<FILE, &fclose>;
- FilePtr fp {fopen, "/dev/null", "r"};
- BOOST_REQUIRE(fp);
+ FilePtr file {fopen, "/dev/null", "r"};
+ BOOST_REQUIRE(file);
- FilePtr fp2 {std::move(fp)};
- BOOST_REQUIRE(!fp);
+ FilePtr fp2 {std::move(file)};
+ BOOST_REQUIRE(!file);
BOOST_REQUIRE(fp2);
- fp = std::move(fp2);
- BOOST_REQUIRE(fp);
+ file = std::move(fp2);
+ BOOST_REQUIRE(file);
BOOST_REQUIRE(!fp2);
FilePtr fp3 {fopen, "/dev/null", "r"};
- fp = std::move(fp3);
+ file = std::move(fp3);
}
-BOOST_AUTO_TEST_CASE(wrapped_ptr_file_typed)
+BOOST_AUTO_TEST_CASE(WrappedPtrFileTyped)
{
using FilePtr = wrapped_ptrt<FILE, &fopen, &fclose>;
- const FilePtr fp {"/dev/null", "r"};
- BOOST_REQUIRE(fp);
- BOOST_CHECK_NO_THROW(fflush(fp));
+ const FilePtr file {"/dev/null", "r"};
+ BOOST_REQUIRE(file);
+ BOOST_CHECK_NO_THROW(fflush(file));
}
diff --git a/test/test-enumDetails.cpp b/test/test-enumDetails.cpp
index 0e759e5..d4f4d2f 100644
--- a/test/test-enumDetails.cpp
+++ b/test/test-enumDetails.cpp
@@ -7,47 +7,47 @@
#include <enumDetails.h>
#include <stream_support.h>
-constexpr std::array INVALID_NAMES {"", "missing", "GlobalScoped::aa", "GlobalScoped", "ns::aa", "a", "bb"};
-constexpr std::array VALID_NAMES {"aa", "b", "c"};
-template<typename E> constexpr std::array VALID_VALUES {E::aa, E::b, E::c};
+constexpr std::array INVALID_NAMES {"", "missing", "GlobalScoped::Aa", "GlobalScoped", "ns::Aa", "A", "Bb"};
+constexpr std::array VALID_NAMES {"Aa", "B", "C"};
+template<typename E> constexpr std::array VALID_VALUES {E::Aa, E::B, E::C};
// Not a template, else Boost test framework throws printing the context
constexpr std::array INVALID_VALUES {-1, 3, 20};
#define TESTS_FOR_TYPE(TYPE) \
- BOOST_DATA_TEST_CASE(invalid_check_##TYPE, INVALID_VALUES, in) \
+ BOOST_DATA_TEST_CASE(InvalidCheck##TYPE, INVALID_VALUES, input) \
{ \
- BOOST_CHECK(!EnumDetails<TYPE>::is_valid(static_cast<TYPE>(in))); \
+ BOOST_CHECK(!EnumDetails<TYPE>::isValid(static_cast<TYPE>(input))); \
} \
- BOOST_DATA_TEST_CASE(invalid_parse_##TYPE, INVALID_NAMES, in) \
+ BOOST_DATA_TEST_CASE(InvalidParse##TYPE, INVALID_NAMES, input) \
{ \
- BOOST_CHECK(!EnumDetails<TYPE>::parse(in).has_value()); \
+ BOOST_CHECK(!EnumDetails<TYPE>::parse(input).has_value()); \
} \
- BOOST_DATA_TEST_CASE(invalid_to_string_##TYPE, INVALID_VALUES, in) \
+ BOOST_DATA_TEST_CASE(InvalidToString##TYPE, INVALID_VALUES, input) \
{ \
- BOOST_CHECK(!EnumDetails<TYPE>::to_string(static_cast<TYPE>(in)).has_value()); \
+ BOOST_CHECK(!EnumDetails<TYPE>::toString(static_cast<TYPE>(input)).has_value()); \
} \
- BOOST_DATA_TEST_CASE(valid_check_##TYPE, VALID_VALUES<TYPE>, in) \
+ BOOST_DATA_TEST_CASE(ValidCheck##TYPE, VALID_VALUES<TYPE>, input) \
{ \
- BOOST_CHECK(EnumDetails<TYPE>::is_valid(in)); \
+ BOOST_CHECK(EnumDetails<TYPE>::isValid(input)); \
} \
- BOOST_DATA_TEST_CASE(valid_parse_##TYPE, VALID_NAMES ^ VALID_VALUES<TYPE>, in, out) \
+ BOOST_DATA_TEST_CASE(ValidParse##TYPE, VALID_NAMES ^ VALID_VALUES<TYPE>, input, output) \
{ \
- const auto v = EnumDetails<TYPE>::parse(in); \
- BOOST_CHECK_IF(vo, v.has_value()) { \
- BOOST_CHECK_EQUAL(v.value(), out); \
+ const auto parsed = EnumDetails<TYPE>::parse(input); \
+ BOOST_CHECK_IF(parsedOK, parsed.has_value()) { \
+ BOOST_CHECK_EQUAL(parsed.value(), output); \
} \
} \
- BOOST_DATA_TEST_CASE(valid_to_string_##TYPE, VALID_VALUES<TYPE> ^ VALID_NAMES, in, out) \
+ BOOST_DATA_TEST_CASE(ValidToString##TYPE, VALID_VALUES<TYPE> ^ VALID_NAMES, input, output) \
{ \
- const auto v = EnumDetails<TYPE>::to_string(in); \
- BOOST_CHECK_IF(vo, v.has_value()) { \
- BOOST_CHECK_EQUAL(v.value(), out); \
+ const auto parsed = EnumDetails<TYPE>::toString(input); \
+ BOOST_CHECK_IF(parsedOK, parsed.has_value()) { \
+ BOOST_CHECK_EQUAL(parsed.value(), output); \
} \
}
TESTS_FOR_TYPE(GlobalScoped)
TESTS_FOR_TYPE(GlobalUnscoped)
-using ns_unscoped = ns::Unscoped;
-using ns_scoped = ns::Scoped;
-TESTS_FOR_TYPE(ns_unscoped)
-TESTS_FOR_TYPE(ns_scoped)
+using NsUnscoped = ns::Unscoped;
+using NsScoped = ns::Scoped;
+TESTS_FOR_TYPE(NsUnscoped)
+TESTS_FOR_TYPE(NsScoped)
diff --git a/test/test-environment.cpp b/test/test-environment.cpp
new file mode 100644
index 0000000..a249192
--- /dev/null
+++ b/test/test-environment.cpp
@@ -0,0 +1,64 @@
+#define BOOST_TEST_MODULE environment
+#include "testHelpers.h"
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+#include <stream_support.h>
+
+#include <chronology.h>
+#include <config/types.h>
+#include <game/environment.h>
+#include <gfx/lightDirection.h>
+#include <maths.h>
+
+namespace {
+ using SunPosTestData = std::tuple<Direction2D, time_t, Direction2D>;
+ using SunDirTestData = std::tuple<Direction2D, Direction3D, float, float>;
+ constexpr Direction2D DONCASTER = {-1.1, 53.5};
+ constexpr Direction2D NEW_YORK = {74.0, 40.7};
+ constexpr Direction2D SYNDEY = {-151.2, -33.9};
+ constexpr Direction2D EQ_GM = {};
+}
+
+BOOST_DATA_TEST_CASE(SunPosition,
+ boost::unit_test::data::make<SunPosTestData>({
+ {EQ_GM, "2024-01-02T00:00:00"_time_t, {181.52F, -66.86F}},
+ {EQ_GM, "2024-01-02T06:00:00"_time_t, {113.12F, -0.85F}},
+ {EQ_GM, "2024-01-02T06:30:00"_time_t, {113.12F, 6.05F}},
+ {EQ_GM, "2024-01-02T12:00:00"_time_t, {177.82F, 66.97F}},
+ {EQ_GM, "2024-01-02T18:00:00"_time_t, {246.99F, 0.90F}},
+ {EQ_GM, "2024-01-03T00:00:00"_time_t, {181.52F, -67.04F}},
+ {EQ_GM, "2024-06-29T12:00:00"_time_t, {2.1F, 66.80F}},
+ {DONCASTER, "2024-06-29T12:00:00"_time_t, {176.34F, 59.64F}},
+ {NEW_YORK, "2024-06-29T12:00:00"_time_t, {278.04F, 27.34F}},
+ {SYNDEY, "2024-06-29T12:00:00"_time_t, {106.13F, -63.29F}},
+ }),
+ position, timeOfYear, expSunPos)
+{
+ const auto sunPos = Environment::getSunPos(position * degreesToRads, timeOfYear) / degreesToRads;
+ BOOST_CHECK_CLOSE(sunPos.x, expSunPos.x, 1.F);
+ BOOST_CHECK_CLOSE(sunPos.y, expSunPos.y, 1.F);
+}
+
+BOOST_DATA_TEST_CASE(SunDirection,
+ boost::unit_test::data::make<SunDirTestData>({
+ {{0.F, 0.F}, south, 0.314F, 0.0087F},
+ {{90.F, 0.F}, west, 0.314F, 0.0087F},
+ {{-90.F, 0.F}, east, 0.314F, 0.0087F},
+ // From above
+ // EqGM midnight, sun below horizon, shining upwards
+ {{181.52F, -66.86F}, {-0.01F, 0.39F, 0.919F}, 0, 0.F},
+ // EqGM just before sunrise, mostly west, north a bit, up a bit
+ {{113.12F, -0.85F}, {-0.92F, 0.39F, 0.015F}, 0.299F, 0.F},
+ // EqGM just after sunrise, mostly west, north a bit, down a bit
+ {{113.12F, 6.05F}, {-0.92F, 0.39F, -0.015F}, 0.42F, 0.114F},
+ // Doncaster noon, roughly from south to north, high in the sky, downward
+ {{176.34F, 59.64F}, {-0.03F, 0.5F, -0.86F}, 1, 1},
+ }),
+ position, direction, amb, dir)
+{
+ const LightDirection lightDir {position * degreesToRads};
+ BOOST_CHECK_CLOSE_VEC(lightDir.vector(), direction);
+ BOOST_CHECK_CLOSE(glm::length(lightDir.vector()), 1.F, 1);
+ BOOST_CHECK_CLOSE(lightDir.ambient(), amb, 5);
+ BOOST_CHECK_CLOSE(lightDir.directional(), dir, 5);
+}
diff --git a/test/test-geoData-counts.cpp b/test/test-geoData-counts.cpp
new file mode 100644
index 0000000..89f9633
--- /dev/null
+++ b/test/test-geoData-counts.cpp
@@ -0,0 +1,65 @@
+#include <OpenMesh/Core/IO/MeshIO.hh>
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/test_tools.hpp>
+#include <boost/test/unit_test_suite.hpp>
+
+#include "game/geoData.h"
+
+using GeoMutation = std::function<void(GeoData &)>;
+using Something = std::tuple<const char *, GeoMutation, size_t, size_t, size_t>;
+BOOST_TEST_DONT_PRINT_LOG_VALUE(GeoMutation);
+
+BOOST_TEST_DECORATOR(*boost::unit_test::timeout(2));
+
+BOOST_DATA_TEST_CASE(DeformLogical,
+ boost::unit_test::data::make<Something>({
+ {"nochange", [](GeoData &) {}, 16, 33, 18}, // No change base case
+ {"simple",
+ [](GeoData & geoData) {
+ Surface surface;
+ // Basic triangle, no crossing, simple case
+ geoData.setHeights(std::array<GlobalPosition3D, 3> {{
+ {2000, 8000, 1000},
+ {2000, 4000, 1000},
+ {6000, 8000, 1000},
+ }},
+ {.surface = &surface, .nearNodeTolerance = 0});
+ },
+ 19, 42, 24},
+ {"simple-cross",
+ [](GeoData & geoData) {
+ Surface surface;
+ // Basic triangle, with crossing, reasonably simple case
+ geoData.setHeights(std::array<GlobalPosition3D, 3> {{
+ {2000, 8000, 1000},
+ {3000, 4000, 1000},
+ {4000, 9000, 1000},
+ }},
+ {.surface = &surface, .nearNodeTolerance = 0});
+ },
+ 19, 42, 24},
+ {"quad-multi-cross",
+ [](GeoData & geoData) {
+ Surface surface;
+ // Basic quad, with crossing, spans into adjacent original triangle, should remove that edge
+ geoData.setHeights(std::array<GlobalPosition3D, 4> {{
+ {2000, 8000, 1000},
+ {3000, 4000, 1000},
+ {4000, 9000, 1000},
+ {8000, 2000, 1000},
+ }},
+ {.surface = &surface, .nearNodeTolerance = 0});
+ },
+ 20, 45, 26},
+ }),
+ name, func, expVertices, expEdges, expFaces)
+{
+ auto geoData = GeoData::createFlat({0, 0}, {30'000, 30'000}, 1000);
+
+ BOOST_REQUIRE_NO_THROW(func(geoData));
+ OpenMesh::IO::write_mesh(geoData, std::format("/tmp/mesh-{}.obj", name));
+
+ BOOST_CHECK_EQUAL(geoData.n_vertices(), expVertices);
+ BOOST_CHECK_EQUAL(geoData.n_edges(), expEdges);
+ BOOST_CHECK_EQUAL(geoData.n_faces(), expFaces);
+}
diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp
index 11d634d..a697578 100644
--- a/test/test-geoData.cpp
+++ b/test/test-geoData.cpp
@@ -1,96 +1,82 @@
#define BOOST_TEST_MODULE terrain
-#include "game/terrain.h"
-#include "test/testMainWindow.h"
-#include "test/testRenderOutput.h"
#include "testHelpers.h"
-#include "ui/applicationBase.h"
+#include "testMainWindow.h"
+#include "testRenderOutput.h"
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
-#include <gfx/gl/sceneRenderer.h>
#include <stream_support.h>
+#include <game/terrain.h>
+#include <gfx/gl/sceneRenderer.h>
+
#include <game/geoData.h>
-class TestTerrainMesh : public GeoData {
-public:
- TestTerrainMesh() : GeoData {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")} { }
-};
+namespace {
+ class TestTerrainMesh : public GeoData {
+ public:
+ TestTerrainMesh() : GeoData {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")} { }
+ };
-constexpr size_t ncols = 200, nrows = 200, xllcorner = 310000000, yllcorner = 490000000, cellsize = 50000;
+ constexpr size_t NCOLS = 200, NROWS = 200, XLLCORNER = 310000000, YLLCORNER = 490000000, CELLSIZE = 50000;
+ const TestTerrainMesh FIXED_TERRTAIN;
+}
+
+BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
BOOST_FIXTURE_TEST_SUITE(ttm, TestTerrainMesh);
-BOOST_AUTO_TEST_CASE(loadSuccess)
+BOOST_AUTO_TEST_CASE(LoadSuccess)
{
- BOOST_CHECK_EQUAL(ncols * nrows, n_vertices());
- BOOST_CHECK_EQUAL(2 * (ncols - 1) * (nrows - 1), n_faces());
+ BOOST_CHECK_EQUAL(NCOLS * NROWS, n_vertices());
+ BOOST_CHECK_EQUAL(2 * (NCOLS - 1) * (NROWS - 1), n_faces());
const auto [lower, upper] = getExtents();
BOOST_CHECK_EQUAL(lower, GlobalPosition3D(310000000, 490000000, -2600));
BOOST_CHECK_EQUAL(upper, GlobalPosition3D(319950000, 499950000, 571600));
}
-BOOST_AUTO_TEST_CASE(normalsAllPointUp)
+#ifndef NDEBUG
+BOOST_AUTO_TEST_CASE(SanityCheck)
{
- BOOST_CHECK_EQUAL(std::count_if(vertices_begin(), vertices_end(),
- [this](auto && vh) {
- return normal(vh).z > 0;
- }),
- n_vertices());
-}
-
-BOOST_AUTO_TEST_CASE(trianglesContainsPoints)
-{
- const auto face = face_handle(0);
-
- BOOST_TEST_CONTEXT(GeoData::Triangle<2>(this, fv_range(face))) {
- BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner}, face));
- BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + cellsize, yllcorner + cellsize}, face));
- BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner + cellsize}, face));
- BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + 1, yllcorner + 1}, face));
- BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + 1, yllcorner + 2}, face));
- BOOST_CHECK(!triangleContainsPoint(GlobalPosition2D {xllcorner + 3, yllcorner + 2}, face));
- BOOST_CHECK(!triangleContainsPoint(GlobalPosition2D {xllcorner + cellsize, yllcorner}, face));
- }
+ BOOST_CHECK_NO_THROW(sanityCheck());
}
+#endif
BOOST_AUTO_TEST_SUITE_END();
-static const TestTerrainMesh fixedTerrtain;
-
-BOOST_AUTO_TEST_CASE(locatePointFace)
+BOOST_AUTO_TEST_CASE(LocatePointFace)
{
- const GeoData::PointFace pf {{310002000, 490003000}};
- BOOST_CHECK(!pf.isLocated());
- BOOST_CHECK(pf.face(&fixedTerrtain).is_valid());
- BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), 0);
+ const GeoData::PointFace pointFace {{310002000, 490003000}};
+ BOOST_CHECK(!pointFace.isLocated());
+ BOOST_CHECK(pointFace.face(&FIXED_TERRTAIN).is_valid());
+ BOOST_CHECK_EQUAL(pointFace.face(&FIXED_TERRTAIN).idx(), 0);
}
-BOOST_AUTO_TEST_CASE(preLocatePointFace)
+BOOST_AUTO_TEST_CASE(PreLocatePointFace)
{
- const GeoData::PointFace pf {{310002000, 490003000}, &fixedTerrtain};
- BOOST_CHECK(pf.isLocated());
- BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), 0);
+ const GeoData::PointFace pointFace {{310002000, 490003000}, &FIXED_TERRTAIN};
+ BOOST_CHECK(pointFace.isLocated());
+ BOOST_CHECK_EQUAL(pointFace.face(&FIXED_TERRTAIN).idx(), 0);
}
using FindPointData = std::tuple<GlobalPosition2D, int>;
// No boundary cases as these can produce different valid results depending on starting point
-BOOST_DATA_TEST_CASE(findPointOnTerrain,
+BOOST_DATA_TEST_CASE(FindPointOnTerrain,
boost::unit_test::data::make<FindPointData>({
- {{0, 0}, -1}, {{xllcorner, 0}, -1}, {{0, yllcorner}, -1}, {{xllcorner + 1, yllcorner + 2}, 0},
- {{xllcorner + (cellsize * (nrows - 1)) - 2, yllcorner + (cellsize * (ncols - 1)) - 1}, 79200},
+ {{0, 0}, -1}, {{XLLCORNER, 0}, -1}, {{0, YLLCORNER}, -1}, {{XLLCORNER + 1, YLLCORNER + 2}, 0},
+ {{XLLCORNER + (CELLSIZE * (NROWS - 1)) - 2, YLLCORNER + (CELLSIZE * (NCOLS - 1)) - 1}, 79200},
{{315555000, 495556000}, 44400}, // perf test target
})
* boost::unit_test::data::make<int>(
{0, 1, 2, 3, 4, 5, 6, 10, 100, 150, 200, 1000, 1234, 17439, 79201, 79200, 79199}),
- p, fh, start)
+ point, exp, start)
{
- BOOST_CHECK_EQUAL(fh, fixedTerrtain.findPoint(p, GeoData::FaceHandle(start)).idx());
+ BOOST_CHECK_EQUAL(exp, FIXED_TERRTAIN.findPoint(point, GeoData::FaceHandle(start)).idx());
}
using FindPositionData = std::tuple<GlobalPosition2D, GlobalDistance>;
-BOOST_DATA_TEST_CASE(findPositionAt,
+BOOST_DATA_TEST_CASE(FindPositionAt,
boost::unit_test::data::make<FindPositionData>({
// corners
{{310000000, 490000000}, 32800},
@@ -105,36 +91,36 @@ BOOST_DATA_TEST_CASE(findPositionAt,
// other
{{310751000, 490152000}, 58326},
}),
- p, h)
+ point, height)
{
- BOOST_CHECK_EQUAL(fixedTerrtain.positionAt(p), GlobalPosition3D(p, h));
+ BOOST_CHECK_EQUAL(FIXED_TERRTAIN.positionAt(point), point || height);
}
using FindRayIntersectData = std::tuple<GlobalPosition3D, Direction3D, GlobalPosition3D>;
-BOOST_DATA_TEST_CASE(findRayIntersect,
+BOOST_DATA_TEST_CASE(FindRayIntersect,
boost::unit_test::data::make<FindRayIntersectData>({
{{310000000, 490000000, 50000}, {1, 1, -2}, {310008583, 490008583, 32834}},
{{310000000, 490000000, 50000}, {1, 1, -1}, {310017131, 490017131, 32869}},
}),
- p, d, i)
+ point, direction, intersectionPoint)
{
- BOOST_CHECK_EQUAL(fixedTerrtain.intersectRay({p, d})->first, i);
+ BOOST_CHECK_EQUAL(FIXED_TERRTAIN.intersectRay({point, direction})->first, intersectionPoint);
}
-BOOST_AUTO_TEST_CASE(boundaryWalk)
+BOOST_AUTO_TEST_CASE(BoundaryWalk)
{
size_t count {};
- fixedTerrtain.boundaryWalk([&count](auto heh) {
- BOOST_CHECK(fixedTerrtain.is_boundary(heh));
+ FIXED_TERRTAIN.boundaryWalk([&count](auto heh) {
+ BOOST_CHECK(FIXED_TERRTAIN.is_boundary(heh));
count++;
});
- BOOST_CHECK_EQUAL(count, 2 * (ncols + nrows - 2));
+ BOOST_CHECK_EQUAL(count, 2 * (NCOLS + NROWS - 2));
}
using WalkTerrainData = std::tuple<GlobalPosition2D, GlobalPosition2D, std::vector<int>>;
-BOOST_DATA_TEST_CASE(walkTerrain,
+BOOST_DATA_TEST_CASE(WalkTerrain,
boost::unit_test::data::make<WalkTerrainData>({
{{310002000, 490003000}, {310002000, 490003000}, {0}},
{{310003000, 490002000}, {310003000, 490002000}, {1}},
@@ -149,30 +135,33 @@ BOOST_DATA_TEST_CASE(walkTerrain,
{{309999000, 490003000}, {310004000, 489997000}, {0, 1}},
{{310004000, 489997000}, {309999000, 490003000}, {1, 0}},
}),
- from, to, visits)
+ fromPoint, toPoint, visits)
{
std::vector<int> visited;
- BOOST_CHECK_NO_THROW(fixedTerrtain.walk(from, to, [&visited](auto fh) {
- visited.emplace_back(fh.idx());
+ BOOST_CHECK_NO_THROW(FIXED_TERRTAIN.walk(fromPoint, toPoint, [&visited](auto step) {
+ if (!visited.empty()) {
+ BOOST_CHECK_EQUAL(step.previous.idx(), visited.back());
+ }
+ visited.emplace_back(step.current.idx());
}));
BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end());
}
-BOOST_DATA_TEST_CASE(walkTerrainSetsFromFace,
+BOOST_DATA_TEST_CASE(WalkTerrainSetsFromFace,
boost::unit_test::data::make<WalkTerrainData>({
{{310002000, 490003000}, {310002000, 490003000}, {0}},
{{310003000, 490002000}, {310003000, 490002000}, {1}},
{{310002000, 490003000}, {310003000, 490002000}, {0, 1}},
{{310003000, 490002000}, {310002000, 490003000}, {1, 0}},
}),
- from, to, visits)
+ fromPoint, toPoint, visits)
{
- GeoData::PointFace pf {from};
- BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) {}));
- BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), visits.front());
+ GeoData::PointFace pointFace {fromPoint};
+ BOOST_CHECK_NO_THROW(FIXED_TERRTAIN.walk(pointFace, toPoint, [](auto) { }));
+ BOOST_CHECK_EQUAL(pointFace.face(&FIXED_TERRTAIN).idx(), visits.front());
}
-BOOST_DATA_TEST_CASE(walkTerrainUntil,
+BOOST_DATA_TEST_CASE(WalkTerrainUntil,
boost::unit_test::data::make<WalkTerrainData>({
{{310002000, 490003000}, {310002000, 490003000}, {0}},
{{310003000, 490002000}, {310003000, 490002000}, {1}},
@@ -182,44 +171,73 @@ BOOST_DATA_TEST_CASE(walkTerrainUntil,
{{310202000, 490003000}, {310002000, 490003000}, {8, 7, 6, 5, 4}},
{{310002000, 490003000}, {310002000, 490203000}, {0, 399, 398, 797, 796}},
}),
- from, to, visits)
+ fromPoint, toPoint, visits)
{
std::vector<int> visited;
- BOOST_CHECK_NO_THROW(fixedTerrtain.walkUntil(from, to, [&visited](auto fh) {
- visited.emplace_back(fh.idx());
+ BOOST_CHECK_NO_THROW(FIXED_TERRTAIN.walkUntil(fromPoint, toPoint, [&visited](const auto & step) {
+ visited.emplace_back(step.current.idx());
return visited.size() >= 5;
}));
BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end());
}
-BOOST_AUTO_TEST_CASE(triangle_helpers)
+using WalkTerrainCurveData = std::tuple<GlobalPosition2D, GlobalPosition2D, GlobalPosition2D, std::vector<int>,
+ std::vector<GlobalPosition2D>>;
+
+BOOST_TEST_DECORATOR(*boost::unit_test::timeout(1))
+
+BOOST_DATA_TEST_CASE(WalkTerrainCurveSetsFromFace,
+ boost::unit_test::data::make<WalkTerrainCurveData>({
+ {{310002000, 490003000}, {310002000, 490003000}, {310002000, 490003000}, {0}, {}},
+ {{310003000, 490002000}, {310003000, 490002000}, {310003000, 490002000}, {1}, {}},
+ {{310202000, 490203000}, {310002000, 490003000}, {310002000, 490203000},
+ {1600, 1601, 1202, 1201, 802, 803, 404, 403, 4, 3, 2, 1, 0},
+ {
+ {310201997, 490201997},
+ {310201977, 490200000},
+ {310200000, 490174787},
+ {310194850, 490150000},
+ {310192690, 490142690},
+ {310173438, 490100000},
+ {310150000, 490068479},
+ {310130806, 490050000},
+ {310100000, 490028656},
+ {310062310, 490012310},
+ {310050000, 490008845},
+ {310003003, 490003003},
+ }},
+ {{310999999, 490205000}, {310999999, 490203000}, {310999000, 490204000}, {1631, 1632, 1631},
+ {
+ {311000000, 490204999},
+ {311000000, 490203001},
+ }},
+ }),
+ fromPoint, toPoint, centre, visits, exits)
{
- constexpr static GeoData::Triangle<3> t {{0, 0, 0}, {5, 0, 0}, {5, 5, 0}};
+ BOOST_REQUIRE_EQUAL(visits.size(), exits.size() + 1);
- BOOST_CHECK_EQUAL(t.nnormal(), up);
- BOOST_CHECK_CLOSE(t.angle(0), quarter_pi, 0.01F);
- BOOST_CHECK_CLOSE(t.angleAt({0, 0, 0}), quarter_pi, 0.01F);
- BOOST_CHECK_CLOSE(t.angle(1), half_pi, 0.01F);
- BOOST_CHECK_CLOSE(t.angleAt({5, 0, 0}), half_pi, 0.01F);
- BOOST_CHECK_CLOSE(t.angle(2), quarter_pi, 0.01F);
- BOOST_CHECK_CLOSE(t.angleAt({5, 5, 0}), quarter_pi, 0.01F);
-
- BOOST_CHECK_CLOSE(t.angleAt({0, 1, 0}), 0.F, 0.01F);
-
- BOOST_CHECK_CLOSE(t.area(), 12.5F, 0.01F);
+ std::vector<int> visited;
+ std::vector<GlobalPosition2D> exited;
+ BOOST_CHECK_NO_THROW(FIXED_TERRTAIN.walk(fromPoint, toPoint, centre, [&](const auto & step) {
+ visited.emplace_back(step.current.idx());
+ BOOST_REQUIRE(!std::ranges::contains(exited, step.exitPosition));
+ exited.emplace_back(step.exitPosition);
+ }));
+ BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end());
+ BOOST_CHECK_EQUAL_COLLECTIONS(exited.begin() + 1, exited.end(), exits.begin(), exits.end());
}
using FindEntiesData = std::tuple<GlobalPosition2D, GlobalPosition2D, int>;
-BOOST_DATA_TEST_CASE(findEntries,
+BOOST_DATA_TEST_CASE(FindEntryHalfEdge,
boost::unit_test::data::make<FindEntiesData>({
{{307739360, 494851616}, {314056992, 500079744}, 160667},
{{308597952, 498417056}, {315154144, 504671456}, 233623},
{{302690592, 502270912}, {311585184, 497868064}, 207311},
}),
- from, to, heh)
+ fromPoint, toPoint, heh)
{
- BOOST_CHECK_EQUAL(fixedTerrtain.findEntry(from, to).idx(), heh);
+ BOOST_CHECK_EQUAL(FIXED_TERRTAIN.findEntry(fromPoint, toPoint).idx(), heh);
}
using DeformTerrainData = std::tuple<std::vector<GlobalPosition3D>,
@@ -227,33 +245,29 @@ using DeformTerrainData = std::tuple<std::vector<GlobalPosition3D>,
BOOST_TEST_DECORATOR(*boost::unit_test::timeout(2));
-BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/1.json"), points, cams)
+BOOST_DATA_TEST_CASE(Deform, loadFixtureJson<DeformTerrainData>("geoData/deform/1.json"), points, cams)
{
Surface surface;
surface.colorBias = RGB {0, 0, 1};
- auto gd = std::make_shared<GeoData>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100));
- BOOST_CHECK_NO_THROW(gd->setHeights(points, surface));
- ApplicationBase ab;
- TestMainWindow tmw;
TestRenderOutput tro {{640, 480}};
struct TestTerrain : public SceneProvider {
- explicit TestTerrain(std::shared_ptr<GeoData> gd) : terrain(std::move(gd)) { }
+ explicit TestTerrain(GeoData geoData) : terrain(std::move(geoData)) { }
const Terrain terrain;
void
- content(const SceneShader & shader) const override
+ content(const SceneShader & shader, const Frustum & frustum) const override
{
- terrain.render(shader);
+ terrain.render(shader, frustum);
}
void
- environment(const SceneShader &, const SceneRenderer & sr) const override
+ environment(const SceneShader &, const SceneRenderer & renderer) const override
{
- sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ renderer.setAmbientLight({0.1, 0.1, 0.1});
+ renderer.setDirectionalLight({1, 1, 1}, {{quarter_pi, -3 * half_pi}}, *this);
}
void
@@ -262,17 +276,35 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/
}
void
- shadows(const ShadowMapper & shadowMapper) const override
+ shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override
{
- terrain.shadows(shadowMapper);
+ terrain.shadows(shadowMapper, frustum);
}
};
- TestTerrain t {gd};
- SceneRenderer ss {tro.size, tro.output};
- std::for_each(cams.begin(), cams.end(), [&ss, &t, &tro](const auto & cam) {
- ss.camera.setView(cam.first.first, glm::normalize(cam.first.second));
- BOOST_CHECK_NO_THROW(ss.render(t));
+ TestTerrain terrain {[&points, &surface]() {
+ auto geoData = GeoData::createFlat({0, 0}, {1000000, 1000000}, 100);
+ BOOST_CHECK_NO_THROW(geoData.setHeights(points, {.surface = &surface}));
+ return geoData;
+ }()};
+ SceneRenderer renderer {tro.size, tro.output};
+ std::for_each(cams.begin(), cams.end(), [&renderer, &terrain, &tro](const auto & cam) {
+ renderer.camera.setView(cam.first.first, glm::normalize(cam.first.second));
+ BOOST_CHECK_NO_THROW(renderer.render(terrain));
Texture::save(tro.outImage, cam.second.c_str());
});
}
+
+BOOST_TEST_DECORATOR(*boost::unit_test::timeout(2));
+
+BOOST_DATA_TEST_CASE(
+ deformMulti, loadFixtureJson<std::vector<std::vector<GlobalPosition3D>>>("geoData/deform/multi1.json"), points)
+{
+ BOOST_REQUIRE(!points.empty());
+ Surface surface;
+ auto geoData = std::make_shared<GeoData>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100));
+ for (const auto & strip : points) {
+ BOOST_REQUIRE_GE(strip.size(), 3);
+ BOOST_CHECK_NO_THROW(geoData->setHeights(strip, {.surface = &surface, .nearNodeTolerance = 50}));
+ }
+}
diff --git a/test/test-glContainer.cpp b/test/test-glContainer.cpp
index 332d440..7f82e13 100644
--- a/test/test-glContainer.cpp
+++ b/test/test-glContainer.cpp
@@ -18,7 +18,7 @@ BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
BOOST_FIXTURE_TEST_SUITE(i, glContainer<int>)
-BOOST_AUTO_TEST_CASE(createDestroy, *boost::unit_test::timeout(1))
+BOOST_AUTO_TEST_CASE(CreateDestroy, *boost::unit_test::timeout(1))
{
// Unmapped
BOOST_CHECK(!data_);
@@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE(createDestroy, *boost::unit_test::timeout(1))
BOOST_CHECK(!access_);
}
-BOOST_AUTO_TEST_CASE(mapModes)
+BOOST_AUTO_TEST_CASE(MapModes)
{
BOOST_CHECK_EQUAL(std::accumulate(begin(), end(), 0), 0);
BOOST_CHECK(!data_);
@@ -78,14 +78,13 @@ BOOST_AUTO_TEST_CASE(mapModes)
BOOST_CHECK_EQUAL(access_, GL_READ_WRITE);
BOOST_CHECK_NO_THROW(unmap());
- const auto & c = *this;
- BOOST_CHECK_EQUAL(std::accumulate(c.begin(), c.end(), 0), 10);
+ BOOST_CHECK_EQUAL(std::ranges::fold_left(std::as_const(*this), 0, std::plus {}), 10);
BOOST_CHECK(data_);
BOOST_CHECK_EQUAL(access_, GL_READ_ONLY);
BOOST_CHECK_NO_THROW(unmap());
}
-BOOST_AUTO_TEST_CASE(push_back_test, *boost::unit_test::timeout(1))
+BOOST_AUTO_TEST_CASE(PushBackTest, *boost::unit_test::timeout(1))
{
BOOST_CHECK_EQUAL(capacity_, 1);
BOOST_CHECK_EQUAL(size_, 0);
@@ -103,7 +102,7 @@ BOOST_AUTO_TEST_CASE(push_back_test, *boost::unit_test::timeout(1))
}
}
-BOOST_AUTO_TEST_CASE(emplace_back_test, *boost::unit_test::timeout(1))
+BOOST_AUTO_TEST_CASE(EmplaceBackTest, *boost::unit_test::timeout(1))
{
BOOST_CHECK_EQUAL(capacity_, 1);
BOOST_CHECK_EQUAL(size_, 0);
@@ -146,7 +145,7 @@ BOOST_AUTO_TEST_CASE(emplace_back_test, *boost::unit_test::timeout(1))
BOOST_CHECK_EQUAL(7, rend() - rbegin());
}
-BOOST_AUTO_TEST_CASE(resize_test)
+BOOST_AUTO_TEST_CASE(ResizeTest)
{
BOOST_CHECK_NO_THROW(push_back(1));
BOOST_CHECK_NO_THROW(emplace_back(2));
@@ -177,7 +176,7 @@ BOOST_AUTO_TEST_CASE(resize_test)
BOOST_CHECK_EQUAL(rbegin(), rend());
}
-BOOST_AUTO_TEST_CASE(shrink_to_fit_test)
+BOOST_AUTO_TEST_CASE(ShrinkToFitTest)
{
BOOST_CHECK_NO_THROW(reserve(4));
BOOST_CHECK_NO_THROW(emplace_back(1));
@@ -205,7 +204,7 @@ BOOST_AUTO_TEST_CASE(shrink_to_fit_test)
BOOST_CHECK_EQUAL(size(), 0);
}
-BOOST_AUTO_TEST_CASE(getters)
+BOOST_AUTO_TEST_CASE(Getters)
{
BOOST_CHECK(empty());
BOOST_CHECK_NO_THROW(emplace_back(1));
@@ -238,24 +237,24 @@ BOOST_AUTO_TEST_CASE(getters)
BOOST_CHECK_THROW(std::ignore = constCont.at(2), std::out_of_range);
}
-BOOST_AUTO_TEST_CASE(random_access)
+BOOST_AUTO_TEST_CASE(RandomAccess)
{
BOOST_CHECK_NO_THROW(emplace_back(1));
BOOST_CHECK_NO_THROW(emplace_back(2));
BOOST_CHECK_NO_THROW(emplace_back(3));
- auto i = begin();
- BOOST_CHECK_EQUAL(1, *i);
- BOOST_CHECK_EQUAL(2, *++i);
- BOOST_CHECK_EQUAL(2, *i++);
- BOOST_CHECK_EQUAL(3, *i);
- BOOST_CHECK_EQUAL(3, *i--);
- BOOST_CHECK_EQUAL(2, *i);
- BOOST_CHECK_EQUAL(1, *--i);
- BOOST_CHECK_EQUAL(1, *i);
+ auto iterator = begin();
+ BOOST_CHECK_EQUAL(1, *iterator);
+ BOOST_CHECK_EQUAL(2, *++iterator);
+ BOOST_CHECK_EQUAL(2, *iterator++);
+ BOOST_CHECK_EQUAL(3, *iterator);
+ BOOST_CHECK_EQUAL(3, *iterator--);
+ BOOST_CHECK_EQUAL(2, *iterator);
+ BOOST_CHECK_EQUAL(1, *--iterator);
+ BOOST_CHECK_EQUAL(1, *iterator);
}
-BOOST_AUTO_TEST_CASE(random_write)
+BOOST_AUTO_TEST_CASE(RandomWrite)
{
BOOST_CHECK_NO_THROW(resize(3));
BOOST_CHECK_EQUAL(size(), 3);
@@ -278,7 +277,7 @@ BOOST_AUTO_TEST_CASE(random_write)
BOOST_CHECK_THROW(at(4, 0), std::out_of_range);
}
-BOOST_AUTO_TEST_CASE(insert_remove_test)
+BOOST_AUTO_TEST_CASE(InsertRemoveTest)
{
BOOST_CHECK_NO_THROW(emplace_back(1));
BOOST_CHECK_NO_THROW(emplace_back(2));
@@ -323,19 +322,19 @@ BOOST_AUTO_TEST_CASE(insert_remove_test)
}
}
-BOOST_AUTO_TEST_CASE(stl)
+BOOST_AUTO_TEST_CASE(StlCompatilibty)
{
BOOST_CHECK_NO_THROW(resize(10));
- BOOST_CHECK_NO_THROW(std::generate(begin(), end(), [x = 0]() mutable {
- return x++;
+ BOOST_CHECK_NO_THROW(std::generate(begin(), end(), [value = 0]() mutable {
+ return value++;
}));
BOOST_CHECK_NO_THROW(std::sort(rbegin(), rend()));
{
std::array expected1 {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end());
}
- const auto newend = std::remove_if(begin(), end(), [](const auto & x) {
- return x % 2 == 0;
+ const auto newend = std::remove_if(begin(), end(), [](const auto & value) {
+ return value % 2 == 0;
});
{
std::array expected1 {9, 7, 5, 3, 1};
@@ -343,7 +342,7 @@ BOOST_AUTO_TEST_CASE(stl)
}
}
-BOOST_AUTO_TEST_CASE(iter_compare)
+BOOST_AUTO_TEST_CASE(IterCompare)
{
BOOST_CHECK_EQUAL(begin(), end());
BOOST_CHECK_EQUAL(rbegin(), rend());
@@ -356,7 +355,7 @@ BOOST_AUTO_TEST_CASE(iter_compare)
BOOST_AUTO_TEST_SUITE_END();
-BOOST_AUTO_TEST_CASE(create_copy_source, *boost::unit_test::timeout(1))
+BOOST_AUTO_TEST_CASE(CreateCopySource, *boost::unit_test::timeout(1))
{
const std::vector src {4, 6, 2, 4, 6, 0};
glContainer dst {src};
@@ -365,16 +364,36 @@ BOOST_AUTO_TEST_CASE(create_copy_source, *boost::unit_test::timeout(1))
BOOST_CHECK_EQUAL_COLLECTIONS(src.begin(), src.end(), dst.begin(), dst.end());
}
-struct C {
- int x;
- float y;
-};
+namespace {
+ struct C {
+ int x;
+ float y;
+ };
-static_assert(std::is_trivially_destructible_v<C>);
+ static_assert(std::is_trivially_destructible_v<C>);
+
+ struct CC {
+ CC() = default;
+
+ CC(int intValue, float floatValue) noexcept : x {intValue}, y {floatValue} { }
+
+ ~CC()
+ {
+ ++x;
+ }
+
+ DEFAULT_MOVE_COPY(CC);
+
+ int x;
+ float y;
+ };
+
+ static_assert(!std::is_trivially_destructible_v<CC>);
+}
BOOST_FIXTURE_TEST_SUITE(c, glContainer<C>)
-BOOST_AUTO_TEST_CASE(basic)
+BOOST_AUTO_TEST_CASE(Basic)
{
BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
BOOST_CHECK_EQUAL(1, begin()->x);
@@ -391,27 +410,9 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_AUTO_TEST_SUITE_END();
-struct CC {
- CC() = default;
-
- CC(int a, float b) noexcept : x {a}, y {b} { }
-
- ~CC()
- {
- ++x;
- }
-
- DEFAULT_MOVE_COPY(CC);
-
- int x;
- float y;
-};
-
-static_assert(!std::is_trivially_destructible_v<CC>);
-
BOOST_FIXTURE_TEST_SUITE(cc, glContainer<CC>)
-BOOST_AUTO_TEST_CASE(basic)
+BOOST_AUTO_TEST_CASE(Basic)
{
BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
BOOST_CHECK_EQUAL(1, begin()->x);
@@ -435,7 +436,7 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_CHECK_EQUAL(capacity(), 1);
}
-BOOST_AUTO_TEST_CASE(insert_remove_test)
+BOOST_AUTO_TEST_CASE(InsertRemoveTest)
{
BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
BOOST_CHECK_NO_THROW(emplace_back(3, 4.F));
diff --git a/test/test-glContextBhvr.cpp b/test/test-glContextBhvr.cpp
index ec5cc21..e4b20e1 100644
--- a/test/test-glContextBhvr.cpp
+++ b/test/test-glContextBhvr.cpp
@@ -11,35 +11,39 @@ BOOST_GLOBAL_FIXTURE(ApplicationBase);
#define TEST_WINDOW_PARAMS __FILE__, 0, 0, 640, 480, static_cast<Uint32>(SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN)
-static void
-CreateProgramTest()
-{
- const ProgramRef p;
- BOOST_REQUIRE(p);
+namespace {
+ void
+ createProgramTest()
+ {
+ const ProgramRef prog;
+ BOOST_REQUIRE(prog);
+ }
}
-BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour1)
+BOOST_AUTO_TEST_CASE(WindowContextThingsBehaviour1)
{
BOOST_REQUIRE(!glCreateProgram); // Init not called yet
{
const SDL_WindowPtr window {TEST_WINDOW_PARAMS};
BOOST_REQUIRE(window);
BOOST_REQUIRE(!glCreateProgram);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
BOOST_REQUIRE_EQUAL(gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress)), 0); // No context yet
{
const SDL_GLContextPtr context {window};
BOOST_REQUIRE(context);
BOOST_REQUIRE(!glCreateProgram);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
BOOST_REQUIRE_GT(gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress)), 0);
BOOST_REQUIRE(glCreateProgram);
- CreateProgramTest();
+ createProgramTest();
} // Context destroyed
BOOST_REQUIRE(glCreateProgram); // Functions still set
- BOOST_REQUIRE_THROW({ const ProgramRef p; }, std::exception); // Get fails with no context
+ BOOST_REQUIRE_THROW({ const ProgramRef prog; }, std::exception); // Get fails with no context
{
const SDL_GLContextPtr context {window};
BOOST_REQUIRE(context);
- CreateProgramTest();
+ createProgramTest();
}
}
{
@@ -47,11 +51,11 @@ BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour1)
BOOST_REQUIRE(window);
const SDL_GLContextPtr context {window};
BOOST_REQUIRE(context);
- CreateProgramTest();
+ createProgramTest();
}
}
-BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour2)
+BOOST_AUTO_TEST_CASE(WindowContextThingsBehaviour2)
{
const SDL_WindowPtr window1 {TEST_WINDOW_PARAMS};
BOOST_REQUIRE(window1);
@@ -60,12 +64,12 @@ BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour2)
BOOST_REQUIRE(window2);
const SDL_GLContextPtr context {window2};
BOOST_REQUIRE(context);
- CreateProgramTest();
+ createProgramTest();
}
- BOOST_REQUIRE_THROW({ const ProgramRef p; }, std::exception); // Get fails with no context
+ BOOST_REQUIRE_THROW({ const ProgramRef prog; }, std::exception); // Get fails with no context
}
-BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour3)
+BOOST_AUTO_TEST_CASE(WindowContextThingsBehaviour3)
{
std::optional<SDL_WindowPtr> window1 {std::in_place, TEST_WINDOW_PARAMS};
const std::optional<SDL_WindowPtr> window2 {std::in_place, TEST_WINDOW_PARAMS};
@@ -73,11 +77,11 @@ BOOST_AUTO_TEST_CASE(windowContextThingsBehaviour3)
BOOST_REQUIRE(window1.value());
const SDL_GLContextPtr context {window1.value()};
BOOST_REQUIRE(context);
- CreateProgramTest();
+ createProgramTest();
window1.reset();
- BOOST_REQUIRE_THROW({ const ProgramRef p; }, std::exception); // Get fails with context's window gone
+ BOOST_REQUIRE_THROW({ const ProgramRef prog; }, std::exception); // Get fails with context's window gone
window1.emplace(TEST_WINDOW_PARAMS);
BOOST_REQUIRE(window1);
BOOST_REQUIRE(window1.value());
- BOOST_REQUIRE_THROW({ const ProgramRef p; }, std::exception); // Get still fails with context's window gone
+ BOOST_REQUIRE_THROW({ const ProgramRef prog; }, std::exception); // Get still fails with context's window gone
}
diff --git a/test/test-instancing.cpp b/test/test-instancing.cpp
index 1dd4cea..21d78e1 100644
--- a/test/test-instancing.cpp
+++ b/test/test-instancing.cpp
@@ -11,36 +11,38 @@
BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
-struct TestInstanceVertices : public InstanceVertices<int> {
- void
- checkReverseIndex(std::source_location ctx = std::source_location::current())
- {
- BOOST_TEST_CONTEXT(ctx.function_name() << ":" << ctx.line()) {
- std::vector<size_t> genIndexIndex(size(), npos);
- for (size_t i {}; i < index.size(); i++) {
- if (index[i] != npos) {
- BOOST_REQUIRE_EQUAL(genIndexIndex.at(index[i]), npos);
- genIndexIndex.at(index[i]) = i;
+namespace {
+ struct TestInstanceVertices : public InstanceVertices<int> {
+ void
+ checkReverseIndex(std::source_location ctx = std::source_location::current())
+ {
+ BOOST_TEST_CONTEXT(ctx.function_name() << ":" << ctx.line()) {
+ std::vector<size_t> genIndexIndex(size(), npos);
+ for (size_t i {}; i < index.size(); i++) {
+ if (index[i] != npos) {
+ BOOST_REQUIRE_EQUAL(genIndexIndex.at(index[i]), npos);
+ genIndexIndex.at(index[i]) = i;
+ }
}
- }
- BOOST_TEST_CONTEXT(reverseIndex << genIndexIndex) {
- BOOST_CHECK_EQUAL_COLCOL(reverseIndex, genIndexIndex);
+ BOOST_TEST_CONTEXT(reverseIndex << genIndexIndex) {
+ BOOST_CHECK_EQUAL_COLCOL(reverseIndex, genIndexIndex);
+ }
}
}
- }
-};
+ };
+}
BOOST_FIXTURE_TEST_SUITE(i, TestInstanceVertices)
-BOOST_AUTO_TEST_CASE(createDestroy)
+BOOST_AUTO_TEST_CASE(CreateDestroy)
{
BOOST_CHECK(unused.empty());
BOOST_CHECK(index.empty());
BOOST_CHECK(reverseIndex.empty());
}
-BOOST_AUTO_TEST_CASE(acquireRelease)
+BOOST_AUTO_TEST_CASE(AcquireRelease)
{
{
auto proxy = acquire();
@@ -58,7 +60,7 @@ BOOST_AUTO_TEST_CASE(acquireRelease)
BOOST_CHECK(reverseIndex.empty());
}
-BOOST_AUTO_TEST_CASE(acquireReleaseMove)
+BOOST_AUTO_TEST_CASE(AcquireReleaseMove)
{
{
auto proxy1 = acquire();
@@ -75,7 +77,7 @@ BOOST_AUTO_TEST_CASE(acquireReleaseMove)
BOOST_CHECK(reverseIndex.empty());
}
-BOOST_AUTO_TEST_CASE(autoMapUnmap)
+BOOST_AUTO_TEST_CASE(AutoMapUnmap)
{
{
auto proxy = acquire();
@@ -88,7 +90,7 @@ BOOST_AUTO_TEST_CASE(autoMapUnmap)
BOOST_CHECK_EQUAL(0, size());
}
-BOOST_AUTO_TEST_CASE(initialize)
+BOOST_AUTO_TEST_CASE(Initialize)
{
auto proxy = acquire(5);
const auto & constProxy = proxy;
@@ -97,20 +99,20 @@ BOOST_AUTO_TEST_CASE(initialize)
BOOST_CHECK_EQUAL(constProxy.get(), constProxy.get());
}
-BOOST_AUTO_TEST_CASE(resize)
+BOOST_AUTO_TEST_CASE(Resize)
{
constexpr auto COUNT = 500;
std::vector<decltype(acquire())> proxies;
std::vector<int> expected;
- for (auto n = 0; n < COUNT; n++) {
- proxies.push_back(acquire(n));
- expected.emplace_back(n);
+ for (auto value = 0; value < COUNT; value++) {
+ proxies.push_back(acquire(value));
+ expected.emplace_back(value);
}
BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), data(), data() + COUNT);
BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), proxies.begin(), proxies.end());
}
-BOOST_AUTO_TEST_CASE(shuffle)
+BOOST_AUTO_TEST_CASE(Shuffle)
{
std::vector<decltype(acquire())> proxies;
BOOST_CHECK_EQUAL(0, proxies.emplace_back(acquire(0)));
@@ -176,30 +178,33 @@ BOOST_AUTO_TEST_CASE(shuffle)
checkReverseIndex();
}
-BOOST_DATA_TEST_CASE(shuffle_random, boost::unit_test::data::xrange(0, 10), x)
+BOOST_DATA_TEST_CASE(ShuffleRandom, boost::unit_test::data::xrange(0, 10), iteration)
{
- std::ignore = x;
+ std::ignore = iteration;
std::mt19937 gen(std::random_device {}());
std::map<int, InstanceVertices<int>::InstanceProxy> proxies;
- const std::string_view actions = "aaaaaaaarararrraarrrararararaarrrarararararararararraarrrraaaarararaararar";
- int n {};
- for (const auto action : actions) {
+ static constexpr std::string_view ACTIONS
+ = "aaaaaaaarararrraarrrararararaarrrarararararararararraarrrraaaarararaararar";
+ int count {};
+ for (const auto action : ACTIONS) {
switch (action) {
+ default:
+ std::unreachable();
case 'a':
- BOOST_REQUIRE_EQUAL(n, proxies.emplace(n, acquire(n)).first->second);
- n++;
+ BOOST_REQUIRE_EQUAL(count, proxies.emplace(count, acquire(count)).first->second);
+ count++;
break;
case 'r':
BOOST_REQUIRE(!proxies.empty());
- auto e = std::next(proxies.begin(),
+ auto toErase = std::next(proxies.begin(),
std::uniform_int_distribution<> {0, static_cast<int>(proxies.size() - 1)}(gen));
- proxies.erase(e);
+ proxies.erase(toErase);
break;
}
BOOST_REQUIRE_EQUAL(size(), proxies.size());
- for (const auto & [n, p] : proxies) {
- BOOST_REQUIRE_EQUAL(n, p);
+ for (const auto & [value, proxy] : proxies) {
+ BOOST_REQUIRE_EQUAL(value, proxy);
}
std::set<size_t> iused;
for (size_t i {}; i < index.size(); i++) {
@@ -219,25 +224,25 @@ BOOST_DATA_TEST_CASE(shuffle_random, boost::unit_test::data::xrange(0, 10), x)
}
}
-BOOST_AUTO_TEST_CASE(partition_by, *boost::unit_test::timeout(1))
+BOOST_AUTO_TEST_CASE(PartitionBy, *boost::unit_test::timeout(1))
{
std::mt19937 gen(std::random_device {}());
std::uniform_int_distribution<int> dist(0, 100000);
- static constexpr auto N = 1000;
- reserve(N);
+ static constexpr auto COUNT = 1000;
+ reserve(COUNT);
std::vector<decltype(acquire(0))> instances;
- instances.reserve(N);
+ instances.reserve(COUNT);
// At least one of each
instances.push_back(acquire(1));
instances.push_back(acquire(3));
- while (instances.size() < N) {
+ while (instances.size() < COUNT) {
instances.push_back(acquire(dist(gen)));
}
const std::vector<int> values(instances.begin(), instances.end());
- BOOST_REQUIRE_EQUAL(size(), N);
+ BOOST_REQUIRE_EQUAL(size(), COUNT);
- const auto pred = [](auto x) {
- return (x % 3) == 0;
+ const auto pred = [](auto value) {
+ return (value % 3) == 0;
};
auto matchedEnd = partition(pred);
// The underlying data is partitioned...
diff --git a/test/test-lib.cpp b/test/test-lib.cpp
index 5f0b5e5..c79ef6e 100644
--- a/test/test-lib.cpp
+++ b/test/test-lib.cpp
@@ -3,50 +3,54 @@
#include "testHelpers.h"
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
+#include <stream_support.h>
#include <collections.h>
+#include <gfx/aabb.h>
#include <glArrays.h>
#include <glad/gl.h>
#include <set>
-std::set<GLuint> active;
+namespace {
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - tracker
+ std::set<GLuint> active;
-void
-generator(GLsizei n, GLuint * out)
-{
- static GLuint next {1};
+ void
+ generator(GLsizei count, GLuint * out)
+ {
+ static GLuint next {1};
- while (n--) {
- active.insert(next);
- *out++ = next++;
+ std::generate_n(out, count, []() {
+ return *active.emplace(next++).first;
+ });
}
-}
-void
-deleter(GLsizei n, GLuint * in)
-{
- BOOST_CHECK(std::all_of(in, in + n, [](GLuint id) {
- return active.erase(id) > 0;
- }));
-}
+ void
+ deleter(GLsizei n, GLuint * input)
+ {
+ BOOST_CHECK(std::ranges::all_of(std::span(input, static_cast<size_t>(n)), [](GLuint name) {
+ return active.erase(name) > 0;
+ }));
+ }
-using TestArray = glArrays<5, &generator, &deleter>;
+ using TestArray = glArrays<5, &generator, &deleter>;
+}
-BOOST_AUTO_TEST_CASE(generate_and_delete)
+BOOST_AUTO_TEST_CASE(GenerateAndDelete)
{
{
- const TestArray a;
+ const TestArray arr;
}
BOOST_CHECK(active.empty());
}
-BOOST_AUTO_TEST_CASE(generate_move_and_delete)
+BOOST_AUTO_TEST_CASE(GenerateMoveAndDelete)
{
{
- TestArray a;
- BOOST_CHECK_EQUAL(TestArray::size, active.size());
- const TestArray b {std::move(a)};
- BOOST_CHECK_EQUAL(TestArray::size, active.size());
+ TestArray arr1;
+ BOOST_CHECK_EQUAL(TestArray::size(), active.size());
+ const TestArray arr2 {std::move(arr1)};
+ BOOST_CHECK_EQUAL(TestArray::size(), active.size());
}
BOOST_CHECK(active.empty());
}
@@ -54,18 +58,69 @@ BOOST_AUTO_TEST_CASE(generate_move_and_delete)
constexpr std::array TRIANGLE_STRIP_IN {0, 1, 2, 3, 4, 5};
static_assert(std::distance(strip_begin(TRIANGLE_STRIP_IN), strip_end(TRIANGLE_STRIP_IN)) == 4);
-BOOST_AUTO_TEST_CASE(triangle_strip_iter)
+BOOST_AUTO_TEST_CASE(TriangleStripIter)
{
constexpr std::array TRIANGLE_STRIP_EXPECTED {0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5};
std::vector<int> out;
- std::for_each(strip_begin(TRIANGLE_STRIP_IN), strip_end(TRIANGLE_STRIP_IN), [&out](const auto & t) {
- const auto [a, b, c] = t;
- out.push_back(a);
- out.push_back(b);
- out.push_back(c);
+ std::for_each(strip_begin(TRIANGLE_STRIP_IN), strip_end(TRIANGLE_STRIP_IN), [&out](const auto & triangle) {
+ const auto [corner1, corner2, corner3] = triangle;
+ out.push_back(corner1);
+ out.push_back(corner2);
+ out.push_back(corner3);
});
BOOST_REQUIRE_EQUAL(out.size(), (TRIANGLE_STRIP_IN.size() - 2) * 3);
- BOOST_CHECK_EQUAL_COLLECTIONS(
- out.begin(), out.end(), TRIANGLE_STRIP_EXPECTED.begin(), TRIANGLE_STRIP_EXPECTED.end());
+ BOOST_CHECK_EQUAL_COLCOL(out, TRIANGLE_STRIP_EXPECTED);
+}
+
+BOOST_AUTO_TEST_CASE(TriangleStripRangeAdapter)
+{
+ using TriTuple = std::tuple<int, int, int>;
+ std::vector<TriTuple> outRange;
+ std::ranges::copy(TRIANGLE_STRIP_IN | triangleTriples, std::back_inserter(outRange));
+ constexpr std::array<TriTuple, 4> TRIANGLE_STRIP_EXPECTED_TUPLES {{{0, 1, 2}, {2, 1, 3}, {2, 3, 4}, {4, 3, 5}}};
+ BOOST_CHECK_EQUAL_COLCOL(outRange, TRIANGLE_STRIP_EXPECTED_TUPLES);
+}
+
+using MergeCloseData = std::tuple<std::vector<int>, int, std::vector<int>>;
+
+BOOST_DATA_TEST_CASE(MergeCloseInts,
+ boost::unit_test::data::make<MergeCloseData>({
+ {{0}, 0, {0}},
+ {{0, 1}, 0, {0, 1}},
+ {{0, 1}, 2, {0, 1}},
+ {{0, 1, 2}, 2, {0, 2}},
+ {{0, 1, 4}, 2, {0, 4}},
+ {{0, 1, 2}, 4, {0, 2}},
+ {{0, 4, 8}, 4, {0, 8}},
+ {{0, 4, 10, 14}, 4, {0, 14}},
+ {{0, 3, 6}, 2, {0, 3, 6}},
+ {{0, 3, 4}, 2, {0, 4}},
+ {{0, 5, 7, 12}, 4, {0, 6, 12}},
+ {{0, 3, 4, 5, 10, 17, 18, 19}, 2, {0, 4, 10, 19}},
+ }),
+ collection, tolerance, expected)
+{
+ auto mutableCollection {collection};
+ BOOST_REQUIRE_NO_THROW((mergeClose(
+ mutableCollection,
+ [](int left, int right) {
+ return std::abs(left - right);
+ },
+ [](int left, int right) {
+ return (left + right) / 2;
+ },
+ tolerance)));
+ BOOST_CHECK_EQUAL_COLCOL(mutableCollection, expected);
+}
+
+BOOST_AUTO_TEST_CASE(AABBFromPoints)
+{
+ const auto aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints(std::vector<GlobalPosition3D> {
+ {1, 2, 3},
+ {4, 2, 1},
+ {9, 1, 7},
+ });
+ BOOST_CHECK_EQUAL(aabb.min, GlobalPosition3D(1, 1, 1));
+ BOOST_CHECK_EQUAL(aabb.max, GlobalPosition3D(9, 2, 7));
}
diff --git a/test/test-maths.cpp b/test/test-maths.cpp
index ccfb113..c181686 100644
--- a/test/test-maths.cpp
+++ b/test/test-maths.cpp
@@ -6,16 +6,16 @@
#include <glm/gtx/transform.hpp>
#include <stream_support.h>
#include <string_view>
-#include <type_traits>
#include <game/network/link.h>
-#include <gfx/gl/camera.h>
+#include <gfx/camera.h>
#include <glm/glm.hpp>
#include <maths.h>
+#include <triangle.h>
#include <tuple>
-using vecter_and_angle = std::tuple<glm::vec3, float>;
-using angle_pair = std::tuple<float, float>;
+using VecterAndAngle = std::tuple<glm::vec3, Angle>;
+using AnglePair = std::tuple<Angle, Angle>;
//
// STANDARD DEFINITIONS
//
@@ -47,17 +47,17 @@ static_assert(west.x < 0);
// Positive rotation on the XY plane (y member, yaw, around the down axis, as would be expected for vehicle or building
// on flat land) shall be clockwise, in radians. Cycles shall be considered equal; 0 == 2pi, pi == -pi, 1/4pi == -3/4pi.
-BOOST_DATA_TEST_CASE(test_vector_yaw,
- boost::unit_test::data::make<vecter_and_angle>(
+BOOST_DATA_TEST_CASE(TestVectorYaw,
+ boost::unit_test::data::make<VecterAndAngle>(
{{up, 0}, {north, 0}, {south, pi}, {west, -half_pi}, {east, half_pi}, {north + east, quarter_pi},
{south + east, quarter_pi * 3}, {north + west, -quarter_pi}, {south + west, -quarter_pi * 3}}),
- v, a)
+ vec, exp)
{
- BOOST_CHECK_CLOSE(vector_yaw(v), a, 1.F);
+ BOOST_CHECK_CLOSE(vector_yaw(vec), exp, 1.F);
}
-BOOST_DATA_TEST_CASE(test_angle_normalize,
- boost::unit_test::data::make<angle_pair>({
+BOOST_DATA_TEST_CASE(TestAngleNormalize,
+ boost::unit_test::data::make<AnglePair>({
{0, 0},
{two_pi, 0},
{-two_pi, 0},
@@ -66,17 +66,17 @@ BOOST_DATA_TEST_CASE(test_angle_normalize,
{half_pi * 3, -half_pi},
{-half_pi * 3, half_pi},
}),
- in, exp)
+ angle, exp)
{
- BOOST_CHECK_CLOSE(normalize(in), exp, 1);
+ BOOST_CHECK_CLOSE(normalize(angle), exp, 1);
}
// Positive rotation on the YZ plane (x member, pitch, around the east axis relative to its yaw, as would be expected
// for a vehicle travelling forward uphill), in radians. Cycles can be considered non-sense as even in the worst/best
// cases pitch beyond +/- 1/2pi would be crashing.
-BOOST_DATA_TEST_CASE(test_vector_pitch,
- boost::unit_test::data::make<vecter_and_angle>({
+BOOST_DATA_TEST_CASE(TestVectorPitch,
+ boost::unit_test::data::make<VecterAndAngle>({
{north, 0},
{east, 0},
{south, 0},
@@ -92,9 +92,9 @@ BOOST_DATA_TEST_CASE(test_vector_pitch,
{north + west - up, -quarter_pi},
{north + west + up, quarter_pi},
}),
- v, a)
+ vec, exp)
{
- BOOST_CHECK_CLOSE(vector_pitch(v), a, 1.F);
+ BOOST_CHECK_CLOSE(vector_pitch(vec), exp, 1.F);
}
// Positive rotation on the ZX plane (z member, roll, around Y axis relative to its yaw and pitch, as would be expected
@@ -103,26 +103,26 @@ BOOST_DATA_TEST_CASE(test_vector_pitch,
// The ILT functions rotate_yaw, rotate_pitch and rotate_roll provide a simple equivelent to glm::rotate around the
// stated axis.
-const auto angs = boost::unit_test::data::make({pi, half_pi, two_pi, quarter_pi, -pi, -half_pi, -quarter_pi, 0.F})
+const auto ANGLES = boost::unit_test::data::make({pi, half_pi, two_pi, quarter_pi, -pi, -half_pi, -quarter_pi, 0.F})
* boost::unit_test::data::make(0);
-const auto random_angs = boost::unit_test::data::random(-two_pi, two_pi) ^ boost::unit_test::data::xrange(1000);
-const auto rots = boost::unit_test::data::make<std::tuple<glm::vec3, glm::mat4 (*)(float), std::string_view>>({
- {down, rotate_yaw, "yaw"},
- {east, rotate_pitch, "pitch"},
- {north, rotate_roll, "roll"},
+const auto RANDOM_ANGLES = boost::unit_test::data::random(-two_pi, two_pi) ^ boost::unit_test::data::xrange(1000);
+const auto ROTATIONS = boost::unit_test::data::make<std::tuple<glm::vec3, glm::mat4 (*)(float), std::string_view>>({
+ {down, rotate_yaw<4>, "yaw"},
+ {east, rotate_pitch<4>, "pitch"},
+ {north, rotate_roll<4>, "roll"},
});
-BOOST_DATA_TEST_CASE(test_rotations, (angs + random_angs) * rots, angle, ai, axis, ilt_func, name)
+BOOST_DATA_TEST_CASE(TestRotations, (ANGLES + RANDOM_ANGLES) * ROTATIONS, angle, index, axis, iltFunc, name)
{
- (void)ai;
+ (void)index;
BOOST_TEST_CONTEXT(name) {
- const auto g {glm::rotate(angle, axis)}, ilt {ilt_func(angle)};
- for (glm::length_t c = 0; c < 4; c++) {
- BOOST_TEST_CONTEXT(c) {
- for (glm::length_t r = 0; r < 4; r++) {
- BOOST_TEST_CONTEXT(r) {
- BOOST_CHECK_CLOSE(g[c][r], ilt[c][r], 0.0001);
+ const auto glmVal {glm::rotate(angle, axis)}, iltVal {iltFunc(angle)};
+ for (glm::length_t col = 0; col < 4; col++) {
+ BOOST_TEST_CONTEXT(col) {
+ for (glm::length_t row = 0; row < 4; row++) {
+ BOOST_TEST_CONTEXT(row) {
+ BOOST_CHECK_CLOSE(glmVal[col][row], iltVal[col][row], 0.0001);
}
}
}
@@ -132,10 +132,10 @@ BOOST_DATA_TEST_CASE(test_rotations, (angs + random_angs) * rots, angle, ai, axi
// An arc shall be defined as a centre point, start point and end point. The arc shall progress positively from start to
// end in a clockwise manner. Arc start shall be the yaw from centre to start, arc end shall be greater than arc start.
-using pos3_to_arc = std::tuple<glm::vec3, glm::vec3, glm::vec3, Arc>;
+using Pos3ToArc = std::tuple<glm::vec3, glm::vec3, glm::vec3, Arc>;
-BOOST_DATA_TEST_CASE(test_create_arc,
- boost::unit_test::data::make<pos3_to_arc>({
+BOOST_DATA_TEST_CASE(TestCreateArc,
+ boost::unit_test::data::make<Pos3ToArc>({
{{0, 0, 0}, north, east, {0, half_pi}},
{{0, 0, 0}, west, east, {-half_pi, half_pi}},
{{0, 0, 0}, south, east, {pi, half_pi * 5}},
@@ -143,193 +143,301 @@ BOOST_DATA_TEST_CASE(test_create_arc,
{{0, 0, 0}, south, north, {pi, two_pi}},
{{0, 0, 0}, east, south, {half_pi, pi}},
}),
- c, s, e, a)
+ centre, start, end, expAngles)
{
- const Arc arc {c, s, e};
+ const Arc arc {centre, start, end};
BOOST_REQUIRE_LT(arc.first, arc.second);
- BOOST_CHECK_CLOSE(arc.first, a.first, 1.F);
- BOOST_CHECK_CLOSE(arc.second, a.second, 1.F);
+ BOOST_CHECK_CLOSE(arc.first, expAngles.first, 1.F);
+ BOOST_CHECK_CLOSE(arc.second, expAngles.second, 1.F);
}
-using fac = std::tuple<glm::vec2, float, glm::vec2, float, glm::vec2, bool>;
+using FindArcCentreData = std::tuple<glm::vec2, float, glm::vec2, float, glm::vec2, bool>;
-BOOST_DATA_TEST_CASE(test_find_arc_centre,
- boost::unit_test::data::make<fac>({
+BOOST_DATA_TEST_CASE(TestFindArcCentre,
+ boost::unit_test::data::make<FindArcCentreData>({
{{2, 2}, pi, {3, 3}, half_pi, {3, 2}, true},
{{2, 2}, pi, {1, 3}, -half_pi, {1, 2}, false},
{{-1100, -1000}, pi, {-900, -800}, half_pi, {-900, -1000}, true},
{{1100, 1000}, 0, {1050, 900}, pi + 0.92F, {973, 1000}, true},
{{1050, 900}, 0.92F, {1000, 800}, pi, {1127, 800}, false},
}),
- s, es, e, ee, exp, lr)
+ startPoint, startEntryAngle, endPoint, endEntryAngle, expCentre, leftRight)
{
- const auto c = find_arc_centre(s, es, e, ee);
- BOOST_CHECK_CLOSE(exp.x, c.first.x, 1);
- BOOST_CHECK_CLOSE(exp.y, c.first.y, 1);
- BOOST_CHECK_EQUAL(lr, c.second);
+ const auto centre = find_arc_centre(startPoint, startEntryAngle, endPoint, endEntryAngle);
+ BOOST_CHECK_CLOSE(expCentre.x, centre.first.x, 1);
+ BOOST_CHECK_CLOSE(expCentre.y, centre.first.y, 1);
+ BOOST_CHECK_EQUAL(leftRight, centre.second);
}
-BOOST_AUTO_TEST_CASE(test_find_arcs_radius)
+BOOST_AUTO_TEST_CASE(TestFindArcsRadius)
{
BOOST_CHECK_CLOSE(
find_arcs_radius(RelativePosition2D {10.32, 26.71}, {0.4, .92}, {2.92, 22.41}, {-0.89, -0.45}), 2.29, 1);
}
-struct TestLinkStraight : public LinkStraight {
- explicit TestLinkStraight(glm::vec3 v) :
- Link {{std::make_shared<Node>(GlobalPosition3D {}), vector_yaw(v)}, {std::make_shared<Node>(v), vector_yaw(-v)},
- glm::length(v)}
+namespace {
+ struct TestLinkStraight : public LinkStraight {
+ explicit TestLinkStraight(glm::vec3 entryVector) :
+ Link {{.node = std::make_shared<Node>(GlobalPosition3D {}), .dir = vector_yaw(entryVector)},
+ {.node = std::make_shared<Node>(entryVector), .dir = vector_yaw(-entryVector)},
+ glm::length(entryVector)}
+ {
+ }
+ };
+
+ using StraightsData = std::tuple<glm::vec3, float /*angFor*/, float /* angBack*/>;
+
+ struct TestLinkCurve : public LinkCurve {
+ explicit TestLinkCurve(glm::vec3 startPos, glm::vec3 endPos, glm::vec3 ctr) :
+ Link {{.node = std::make_shared<Node>(startPos), .dir = normalize(vector_yaw(ctr - startPos) - half_pi)},
+ {.node = std::make_shared<Node>(endPos), .dir = normalize(vector_yaw(ctr - endPos) - half_pi)},
+ glm::length(endPos - startPos)},
+ LinkCurve(ctr, glm::length(startPos - ctr), {ctr, startPos, endPos})
+ {
+ }
+ };
+
+ using CurvesData
+ = std::tuple<GlobalPosition3D /*e1*/, GlobalPosition3D /*ctr*/, Angle /*angFor*/, Angle /* angBack*/>;
+
+ template<typename T = float>
+ auto
+ nTestPointsBetween(std::size_t n = 2, T min = -100.F, T max = 100.F)
{
+ return boost::unit_test::data::xrange(n) ^ boost::unit_test::data::random(min, max);
}
-};
-
-using StraightsData = std::tuple<glm::vec3, float /*angFor*/, float /* angBack*/>;
+}
-BOOST_DATA_TEST_CASE(straight1,
+BOOST_DATA_TEST_CASE(Straight1,
boost::unit_test::data::make<StraightsData>({
{north, 0, pi},
{south, pi, 0},
{east, half_pi, -half_pi},
{west, -half_pi, half_pi},
}),
- v, angFor, angBack)
+ direction, angFor, angBack)
{
- const TestLinkStraight l(v);
+ const TestLinkStraight link(direction);
{
- const auto p = l.positionAt(0, 0);
- BOOST_CHECK_EQUAL(p.pos, GlobalPosition3D {});
- BOOST_CHECK_EQUAL(p.rot, glm::vec3(0, angFor, 0));
+ const auto position = link.positionAt(0, 0);
+ BOOST_CHECK_EQUAL(position.pos, GlobalPosition3D {});
+ BOOST_CHECK_EQUAL(position.rot, glm::vec3(0, angFor, 0));
}
{
- const auto p = l.positionAt(0, 1);
- BOOST_CHECK_EQUAL(p.pos, GlobalPosition3D {v});
- BOOST_CHECK_EQUAL(p.rot, glm::vec3(0, angBack, 0));
+ const auto position = link.positionAt(0, 1);
+ BOOST_CHECK_EQUAL(position.pos, GlobalPosition3D {direction});
+ BOOST_CHECK_EQUAL(position.rot, glm::vec3(0, angBack, 0));
}
}
-struct TestLinkCurve : public LinkCurve {
- explicit TestLinkCurve(glm::vec3 e0, glm::vec3 e1, glm::vec3 ctr) :
- Link {{std::make_shared<Node>(e0), normalize(vector_yaw(ctr - e0) - half_pi)},
- {std::make_shared<Node>(e1), normalize(vector_yaw(ctr - e1) - half_pi)}, glm::length(e1 - e0)},
- LinkCurve(ctr, glm::length(e0 - ctr), {ctr, e0, e1})
- {
- }
-};
-
-using CurvesData = std::tuple<GlobalPosition3D /*e1*/, GlobalPosition3D /*ctr*/, Angle /*angFor*/, Angle /* angBack*/>;
-
-BOOST_DATA_TEST_CASE(curve1,
+BOOST_DATA_TEST_CASE(Curve1,
boost::unit_test::data::make<CurvesData>({
{north + east, east, 0, -half_pi},
{east * 2.F, east, 0, 0},
{south + east, east, 0, half_pi},
{south + west, west, pi, half_pi},
}),
- e1, ctr, angFor, angBack)
+ endPos, ctr, angFor, angBack)
{
{ // One-way...
- const TestLinkCurve l({}, e1, ctr);
- BOOST_CHECK_EQUAL(l.radius, 1.F);
+ const TestLinkCurve link({}, endPos, ctr);
+ BOOST_CHECK_EQUAL(link.radius, 1.F);
{
- const auto p = l.positionAt(0, 0);
- BOOST_CHECK_CLOSE_VECI(p.pos, GlobalPosition3D {});
- BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angFor, 0));
+ const auto position = link.positionAt(0, 0);
+ BOOST_CHECK_CLOSE_VECI(position.pos, GlobalPosition3D {});
+ BOOST_CHECK_CLOSE_VEC(position.rot, glm::vec3(0, angFor, 0));
}
{
- const auto p = l.positionAt(0, 1);
- BOOST_CHECK_CLOSE_VECI(p.pos, e1);
- BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angBack, 0));
+ const auto position = link.positionAt(0, 1);
+ BOOST_CHECK_CLOSE_VECI(position.pos, endPos);
+ BOOST_CHECK_CLOSE_VEC(position.rot, glm::vec3(0, angBack, 0));
}
}
{ // The other way...
- const TestLinkCurve l(e1, {}, ctr);
- BOOST_CHECK_EQUAL(l.radius, 1.F);
+ const TestLinkCurve link(endPos, {}, ctr);
+ BOOST_CHECK_EQUAL(link.radius, 1.F);
{
- const auto p = l.positionAt(0, 0);
- const auto angForReversed = normalize(vector_yaw(-e1) * 2 - angFor);
- BOOST_CHECK_CLOSE_VECI(p.pos, e1);
- BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angForReversed, 0));
+ const auto position = link.positionAt(0, 0);
+ const auto angForReversed = normalize(vector_yaw(difference({}, endPos)) * 2 - angFor);
+ BOOST_CHECK_CLOSE_VECI(position.pos, endPos);
+ BOOST_CHECK_CLOSE_VEC(position.rot, glm::vec3(0, angForReversed, 0));
}
{
- const auto p = l.positionAt(0, 1);
- const auto angBackReversed = normalize(vector_yaw(e1) * 2 - angBack);
- BOOST_CHECK_CLOSE_VECI(p.pos, GlobalPosition3D {});
- BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angBackReversed, 0));
+ const auto position = link.positionAt(0, 1);
+ const auto angBackReversed = normalize(vector_yaw(difference(endPos, {})) * 2 - angBack);
+ BOOST_CHECK_CLOSE_VECI(position.pos, GlobalPosition3D {});
+ BOOST_CHECK_CLOSE_VEC(position.rot, glm::vec3(0, angBackReversed, 0));
}
}
}
-BOOST_AUTO_TEST_CASE(camera_clicks)
+BOOST_AUTO_TEST_CASE(CameraClicks)
{
Camera camera {{}, ::half_pi, 1.25F, 1000, 10000000};
- constexpr float centre {0.5F}, right {0.9F}, left {0.1F}, top {1.F}, bottom {0.F};
+ constexpr float CENTRE {0.5F}, RIGHT {0.9F}, LEFT {0.1F}, TOP {1.F}, BOTTOM {0.F};
camera.setForward(::north);
- BOOST_CHECK_EQUAL(camera.unProject({centre, centre}).start, GlobalPosition3D {});
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, ::north);
- BOOST_CHECK_CLOSE_VEC(camera.unProject({left, centre}).direction, glm::normalize(::north + ::west));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({right, centre}).direction, glm::normalize(::north + ::east));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, top}).direction, glm::normalize(::north + ::up));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, bottom}).direction, glm::normalize(::north + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({left, top}).direction, glm::normalize(::north + ::west + ::up));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({right, top}).direction, glm::normalize(::north + ::east + ::up));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({left, bottom}).direction, glm::normalize(::north + ::west + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({right, bottom}).direction, glm::normalize(::north + ::east + ::down));
+ BOOST_CHECK_EQUAL(camera.unProject({CENTRE, CENTRE}).start, GlobalPosition3D {});
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, CENTRE}).direction, ::north);
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({LEFT, CENTRE}).direction, glm::normalize(::north + ::west));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({RIGHT, CENTRE}).direction, glm::normalize(::north + ::east));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, TOP}).direction, glm::normalize(::north + ::up));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, BOTTOM}).direction, glm::normalize(::north + ::down));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({LEFT, TOP}).direction, glm::normalize(::north + ::west + ::up));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({RIGHT, TOP}).direction, glm::normalize(::north + ::east + ::up));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({LEFT, BOTTOM}).direction, glm::normalize(::north + ::west + ::down));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({RIGHT, BOTTOM}).direction, glm::normalize(::north + ::east + ::down));
camera.setForward(::east);
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, ::east);
- BOOST_CHECK_CLOSE_VEC(camera.unProject({left, centre}).direction, glm::normalize(::north + ::east));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({right, centre}).direction, glm::normalize(::south + ::east));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, CENTRE}).direction, ::east);
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({LEFT, CENTRE}).direction, glm::normalize(::north + ::east));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({RIGHT, CENTRE}).direction, glm::normalize(::south + ::east));
camera.setForward(glm::normalize(::north + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, glm::normalize(::north + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, top}).direction, glm::normalize(::north));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, CENTRE}).direction, glm::normalize(::north + ::down));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, TOP}).direction, glm::normalize(::north));
camera.setForward(glm::normalize(::north + ::west + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, glm::normalize(::north + ::west + ::down));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, top}).direction, glm::normalize(::north + ::west + ::up * 0.2F));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, CENTRE}).direction, glm::normalize(::north + ::west + ::down));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, TOP}).direction, glm::normalize(::north + ::west + ::up * 0.2F));
camera.setForward(glm::normalize(::north + ::west));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, centre}).direction, glm::normalize(::north + ::west));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({centre, top}).direction, glm::normalize(::north + ::west + ::up * 1.2F));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({right, centre}).direction, glm::normalize(::north));
- BOOST_CHECK_CLOSE_VEC(camera.unProject({left, centre}).direction, glm::normalize(::west));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, CENTRE}).direction, glm::normalize(::north + ::west));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({CENTRE, TOP}).direction, glm::normalize(::north + ::west + ::up * 1.2F));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({RIGHT, CENTRE}).direction, glm::normalize(::north));
+ BOOST_CHECK_CLOSE_VEC(camera.unProject({LEFT, CENTRE}).direction, glm::normalize(::west));
+}
+
+BOOST_DATA_TEST_CASE(RayLineDistance,
+ nTestPointsBetween() * // n1x
+ nTestPointsBetween() * // n1y
+ nTestPointsBetween() * // n1z
+ nTestPointsBetween() * // n2x
+ nTestPointsBetween() * // n2y
+ nTestPointsBetween() * // n2z
+ nTestPointsBetween() * // cx
+ nTestPointsBetween() * // cy
+ nTestPointsBetween(), // cz
+ idx1, n1x, idx2, n1y, idx3, n1z, idx4, n2x, idx5, n2y, idx6, n2z, idx7, originx, idx8, originy, idx9, originz)
+{
+ (void)idx1;
+ (void)idx2;
+ (void)idx3;
+ (void)idx4;
+ (void)idx5;
+ (void)idx6;
+ (void)idx7;
+ (void)idx8;
+ (void)idx9;
+ const glm::vec3 node1 {n1x, n1y, n1z}, node2 {n2x, n2y, n2z}, origin {originx, originy, originz};
+
+ const auto nstep = node2 - node1;
+ for (float along = 0.2F; along <= 0.8F; along += 0.1F) {
+ const auto target = node1 + (along * nstep);
+ const auto direction = glm::normalize(target - origin);
+ BOOST_CHECK_LE(Ray<RelativePosition3D>(origin, direction).distanceToLine(node1, node2), 0.01F);
+ }
}
-template<typename T = float>
-auto
-n_test_points_between(std::size_t n = 2, T min = -100.F, T max = 100.F)
+static_assert(linesIntersectAt(glm::ivec2 {10, 10}, {40, 40}, {10, 80}, {20, 40}).value().x == 24);
+static_assert(linesIntersectAt(glm::vec2 {10, 10}, {40, 40}, {10, 80}, {20, 40}).value().y == 24);
+static_assert(linesIntersectAt(GlobalPosition2D {311000100, 491100100}, {311050000, 491150000}, {312000100, 491200100},
+ {311000100, 491100100})
+ .value()
+ == GlobalPosition2D {311000100, 491100100});
+static_assert(!linesIntersectAt(glm::dvec2 {0, 1}, {0, 4}, {1, 8}, {1, 4}).has_value());
+
+BOOST_AUTO_TEST_CASE(Triangle2dHelpers)
{
- return boost::unit_test::data::xrange(n) ^ boost::unit_test::data::random(min, max);
+ constexpr static Triangle<2, float> TRIANGLE {{0, 0}, {5, 0}, {5, 5}};
+
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(0), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({0, 0}), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(1), half_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({5, 0}), half_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(2), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({5, 5}), quarter_pi, 0.01F);
+
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({0, 1}), 0.F, 0.01F);
+
+ BOOST_CHECK_CLOSE(TRIANGLE.area(), 12.5F, 0.01F);
}
-BOOST_DATA_TEST_CASE(rayLineDistance,
- n_test_points_between() * // n1x
- n_test_points_between() * // n1y
- n_test_points_between() * // n1z
- n_test_points_between() * // n2x
- n_test_points_between() * // n2y
- n_test_points_between() * // n2z
- n_test_points_between() * // cx
- n_test_points_between() * // cy
- n_test_points_between(), // cz
- i1, n1x, i2, n1y, i3, n1z, i4, n2x, i5, n2y, i6, n2z, i7, cx, i8, cy, i9, cz)
+BOOST_AUTO_TEST_CASE(Triangle3dHelpers)
{
- (void)i1;
- (void)i2;
- (void)i3;
- (void)i4;
- (void)i5;
- (void)i6;
- (void)i7;
- (void)i8;
- (void)i9;
- const glm::vec3 n1 {n1x, n1y, n1z}, n2 {n2x, n2y, n2z}, c {cx, cy, cz};
-
- const auto nstep = n2 - n1;
- for (float along = 0.2F; along <= 0.8F; along += 0.1F) {
- const auto target = n1 + (along * nstep);
- const auto direction = glm::normalize(target - c);
- BOOST_CHECK_LE(Ray<RelativePosition3D>(c, direction).distanceToLine(n1, n2), 0.01F);
+ constexpr static Triangle<3, float> TRIANGLE {{0, 0, 0}, {5, 0, 0}, {5, 5, 0}};
+
+ BOOST_CHECK_EQUAL(TRIANGLE.nnormal(), up);
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(0), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({0, 0, 0}), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(1), half_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({5, 0, 0}), half_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angle(2), quarter_pi, 0.01F);
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({5, 5, 0}), quarter_pi, 0.01F);
+
+ BOOST_CHECK_CLOSE(TRIANGLE.angleAt({0, 1, 0}), 0.F, 0.01F);
+
+ BOOST_CHECK_CLOSE(TRIANGLE.area(), 12.5F, 0.01F);
+}
+
+using ArcLineIntersectExp = std::pair<GlobalPosition2D, Angle>;
+using ArcLineIntersectData = std::tuple<GlobalPosition2D, GlobalPosition2D, GlobalPosition2D, GlobalPosition2D,
+ GlobalPosition2D, std::optional<ArcLineIntersectExp>>;
+
+BOOST_DATA_TEST_CASE(ArclineIntersection,
+ boost::unit_test::data::make<ArcLineIntersectData>({
+ {{0, 0}, {0, 100}, {100, 0}, {200, 0}, {0, 200}, std::nullopt},
+ {{0, 0}, {0, 100}, {100, 0}, {0, 0}, {10, 10}, std::nullopt},
+ {{0, 0}, {0, 100}, {100, 0}, {0, 0}, {100, 100}, ArcLineIntersectExp {{71, 71}, quarter_pi}},
+ {{15, 27}, {15, 127}, {115, 27}, {15, 27}, {115, 127}, ArcLineIntersectExp {{86, 98}, quarter_pi}},
+ {{0, 0}, {0, 100}, {100, 0}, {0, 0}, {-100, -100}, std::nullopt},
+ {{0, 0}, {0, 100}, {100, 0}, {-10, 125}, {125, -10}, ArcLineIntersectExp {{16, 99}, 0.164F}},
+ {{0, 0}, {0, 100}, {100, 0}, {125, -10}, {-10, 125}, ArcLineIntersectExp {{99, 16}, 1.407F}},
+ {{0, 0}, {0, 100}, {100, 0}, {10, 125}, {125, -10}, ArcLineIntersectExp {{38, 93}, 0.385F}},
+ {{0, 0}, {0, 100}, {100, 0}, {12, 80}, {125, -10}, ArcLineIntersectExp {{99, 10}, 1.467F}},
+ {{0, 0}, {0, 100}, {100, 0}, {40, 80}, {125, -10}, ArcLineIntersectExp {{98, 18}, 1.387F}},
+ {{0, 0}, {0, 100}, {100, 0}, {40, 80}, {80, 20}, std::nullopt},
+ {{0, 0}, {0, 100}, {100, 0}, {40, 80}, {80, 80}, ArcLineIntersectExp {{60, 80}, 0.6435F}},
+ {{0, 0}, {0, 100}, {100, 0}, {80, 40}, {80, 80}, ArcLineIntersectExp {{80, 60}, 0.9273F}},
+ {{310002000, 490203000}, {310202000, 490203000}, {310002000, 490003000}, {310200000, 490150000},
+ {310150000, 490150000}, ArcLineIntersectExp {{310194850, 490150000}, 1.839F}},
+ }),
+ centre, arcStart, arcEnd, lineStart, lineEnd, expected)
+{
+ const ArcSegment arc {centre, arcStart, arcEnd};
+ BOOST_TEST_INFO(arc.first);
+ BOOST_TEST_INFO(arc.second);
+ BOOST_TEST_INFO(arc.length());
+
+ const auto intersection = arc.crossesLineAt(lineStart, lineEnd);
+ BOOST_REQUIRE_EQUAL(expected.has_value(), intersection.has_value());
+ if (expected.has_value()) {
+ BOOST_CHECK_EQUAL(expected->first, intersection->first);
+ BOOST_CHECK_CLOSE(expected->second, intersection->second, 1.F);
}
}
+
+static_assert(pointLeftOfLine({1, 2}, {1, 1}, {2, 2}));
+static_assert(pointLeftOfLine({2, 1}, {2, 2}, {1, 1}));
+static_assert(pointLeftOfLine({2, 2}, {1, 2}, {2, 1}));
+static_assert(pointLeftOfLine({1, 1}, {2, 1}, {1, 2}));
+static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490000000}, {310050000, 490050000}));
+static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310050000, 490050000}, {310000000, 490050000}));
+static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490050000}, {310000000, 490000000}));
+
+static_assert(linesCross({1, 1}, {2, 2}, {1, 2}, {2, 1}));
+static_assert(linesCross({2, 2}, {1, 1}, {1, 2}, {2, 1}));
+
+static_assert(linesCrossLtR({1, 1}, {2, 2}, {1, 2}, {2, 1}));
+static_assert(!linesCrossLtR({2, 2}, {1, 1}, {1, 2}, {2, 1}));
+
+static_assert(Triangle<3, GlobalDistance> {{1, 2, 3}, {1, 0, 1}, {-2, 1, 0}}.positionOnPlane({7, -2})
+ == GlobalPosition3D {7, -2, 3});
+static_assert(Triangle<3, GlobalDistance> {
+ {310000000, 490000000, 32800}, {310050000, 490050000, 33000}, {310000000, 490050000, 32700}}
+ .positionOnPlane({310000000, 490000000})
+ == GlobalPosition3D {310000000, 490000000, 32800});
+static_assert(Triangle<3, GlobalDistance> {
+ {310750000, 490150000, 58400}, {310800000, 490200000, 55500}, {310750000, 490200000, 57600}}
+ .positionOnPlane({310751000, 490152000})
+ == GlobalPosition3D {310751000, 490152000, 58326});
diff --git a/test/test-network.cpp b/test/test-network.cpp
index 59eebae..1c91981 100644
--- a/test/test-network.cpp
+++ b/test/test-network.cpp
@@ -21,194 +21,210 @@
BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
-struct TestLinkS;
-
-struct TestLink : public virtual Link {
- using StraightLink = TestLinkS;
- using CurveLink = TestLinkS;
-};
-
-struct TestLinkS : public TestLink, public LinkStraight {
- TestLinkS(NetworkLinkHolder<TestLinkS> & network, const Node::Ptr & a, const Node::Ptr & b) :
- TestLinkS {network, a, b, (a->pos - b->pos)}
- {
- }
-
- TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr a, Node::Ptr b, RelativePosition2D l) :
- Link {{std::move(a), 0}, {std::move(b), pi}, glm::length(l)}
- {
- }
-
- struct Vertex { };
-
- TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr a, Node::Ptr b, float l) :
- Link {{std::move(a), 0}, {std::move(b), pi}, l}
- {
- }
-};
-
-constexpr GlobalPosition3D p000 {0, 0, 0}, p100 {10000, 0, 0}, p200 {20000, 0, 0}, p300 {30000, 0, 0};
-constexpr GlobalPosition3D p110 {10000, 10000, 0};
+namespace {
+ struct TestLinkS;
+
+ struct TestLink : public virtual Link {
+ using StraightLink = TestLinkS;
+ using CurveLink = TestLinkS;
+ };
+
+ struct TestLinkS : public TestLink, public LinkStraight {
+ TestLinkS(NetworkLinkHolder<TestLinkS> & network, const Node::Ptr & nodeA, const Node::Ptr & nodeB) :
+ TestLinkS {network, nodeA, nodeB, (nodeA->pos - nodeB->pos)}
+ {
+ }
+
+ TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr nodeA, Node::Ptr nodeB, RelativePosition2D difference) :
+ Link {{.node = std::move(nodeA), .dir = 0}, {.node = std::move(nodeB), .dir = pi}, glm::length(difference)}
+ {
+ }
+
+ struct Vertex { };
+
+ TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr nodeA, Node::Ptr nodeB, float length) :
+ Link {{.node = std::move(nodeA), .dir = 0}, {.node = std::move(nodeB), .dir = pi}, length}
+ {
+ }
+ };
+
+ constexpr GlobalPosition3D P000 {0, 0, 500}, P100 {10500, 0, 1000}, P200 {20100, 0, 2000}, P300 {30700, 0, 3000};
+ constexpr GlobalPosition3D P110 {10300, 10400, 4000};
+}
template<> NetworkLinkHolder<TestLinkS>::NetworkLinkHolder() = default;
-struct TestNetwork : public NetworkOf<TestLink, TestLinkS> {
- TestNetwork() : NetworkOf<TestLink, TestLinkS> {RESDIR "rails.jpg"}
- {
- // 0 1 2
- // p000 <-> p100 <-> p200 <-> p300
- // \ | /
- // \ 5 /
- // 3 | 4
- // \-> p110 <-/
- addLink<TestLinkS>(p000, p100, 1.F);
- addLink<TestLinkS>(p100, p200, 1.F);
- addLink<TestLinkS>(p200, p300, 1.F);
- addLink<TestLinkS>(p000, p110, 2.F);
- addLink<TestLinkS>(p200, p110, 2.F);
- addLink<TestLinkS>(p100, p110, 1.F);
- }
-
- void
- render(const SceneShader &) const override
- {
- }
-};
-
-const auto VALID_NODES = boost::unit_test::data::make<GlobalPosition3D>({
- p000,
- p100,
- p200,
- p300,
-});
-const auto INVALID_NODES = boost::unit_test::data::make<GlobalPosition3D>({
- {1000, 0, 0},
- {0, 1000, 0},
- {0, 0, 1000},
-});
+namespace {
+ struct TestNetwork : public NetworkOf<TestLink, TestLinkS> {
+ TestNetwork() : NetworkOf<TestLink, TestLinkS> {RESDIR "rails.jpg"}
+ {
+ // 0 1 2
+ // p000 <-> p100 <-> p200 <-> p300
+ // \ | /
+ // \ 5 /
+ // 3 | 4
+ // \-> p110 <-/
+ addLink<TestLinkS>(P000, P100, 1.F);
+ addLink<TestLinkS>(P100, P200, 1.F);
+ addLink<TestLinkS>(P200, P300, 1.F);
+ addLink<TestLinkS>(P000, P110, 2.F);
+ addLink<TestLinkS>(P200, P110, 2.F);
+ addLink<TestLinkS>(P100, P110, 1.F);
+ }
+
+ void
+ render(const SceneShader &, const Frustum &) const override
+ {
+ }
+
+ const Surface *
+ getBaseSurface() const override
+ {
+ return nullptr;
+ }
+
+ RelativeDistance
+ getBaseWidth() const override
+ {
+ return 5'700;
+ }
+ };
+
+ constexpr auto VALID_NODES = std::array<GlobalPosition3D, 4>({
+ P000,
+ P100,
+ P200,
+ P300,
+ });
+ constexpr auto INVALID_NODES = std::array<GlobalPosition3D, 3>({
+ {1000, 0, 0},
+ {0, 1000, 0},
+ {0, 0, 1000},
+ });
+}
BOOST_FIXTURE_TEST_SUITE(tn, TestNetwork)
-BOOST_DATA_TEST_CASE(findNodeAt_valid, VALID_NODES, p)
+BOOST_DATA_TEST_CASE(FindNodeAtValid, VALID_NODES, point)
{
- auto n = findNodeAt(p);
- BOOST_REQUIRE(n);
- BOOST_CHECK_EQUAL(n->pos, p);
+ auto node = findNodeAt(point);
+ BOOST_REQUIRE(node);
+ BOOST_CHECK_EQUAL(node->pos, point);
}
-BOOST_DATA_TEST_CASE(findNodeAt_invalid, INVALID_NODES, p)
+BOOST_DATA_TEST_CASE(FindNodeAtInvalid, INVALID_NODES, point)
{
- BOOST_REQUIRE(!findNodeAt(p));
+ BOOST_REQUIRE(!findNodeAt(point));
}
-BOOST_DATA_TEST_CASE(nodeAt, VALID_NODES + INVALID_NODES, p)
+BOOST_DATA_TEST_CASE(NodeAt, VALID_NODES + INVALID_NODES, point)
{
- auto n = nodeAt(p);
- BOOST_REQUIRE(n);
- BOOST_CHECK_EQUAL(n->pos, p);
+ auto node = nodeAt(point);
+ BOOST_REQUIRE(node);
+ BOOST_CHECK_EQUAL(node->pos, point);
}
-BOOST_DATA_TEST_CASE(newNodeAt_existing, VALID_NODES, p)
+BOOST_DATA_TEST_CASE(NewNodeAtExisting, VALID_NODES, point)
{
- auto n = newNodeAt(p);
- BOOST_CHECK_EQUAL(n.second, Network::NodeIs::InNetwork);
- BOOST_REQUIRE(n.first);
- BOOST_CHECK_EQUAL(n.first->pos, p);
+ auto node = newNodeAt(point);
+ BOOST_CHECK_EQUAL(node.second, Network::NodeIs::InNetwork);
+ BOOST_REQUIRE(node.first);
+ BOOST_CHECK_EQUAL(node.first->pos, point);
}
-BOOST_DATA_TEST_CASE(newNodeAt_new, INVALID_NODES, p)
+BOOST_DATA_TEST_CASE(NewNodeAtNew, INVALID_NODES, point)
{
- auto n = newNodeAt(p);
- BOOST_CHECK_EQUAL(n.second, Network::NodeIs::NotInNetwork);
- BOOST_REQUIRE(n.first);
- BOOST_CHECK_EQUAL(n.first->pos, p);
+ auto node = newNodeAt(point);
+ BOOST_CHECK_EQUAL(node.second, Network::NodeIs::NotInNetwork);
+ BOOST_REQUIRE(node.first);
+ BOOST_CHECK_EQUAL(node.first->pos, point);
}
-BOOST_AUTO_TEST_CASE(network_joins)
+BOOST_AUTO_TEST_CASE(NetworkJoins)
{
// Ends
- BOOST_CHECK(links.objects[2]->ends[1].nexts.empty());
+ BOOST_CHECK(links[2]->ends[1].nexts.empty());
// Join 0 <-> 1
- BOOST_REQUIRE_EQUAL(links.objects[0]->ends[1].nexts.size(), 2);
- BOOST_CHECK_EQUAL(links.objects[0]->ends[1].nexts[0].first.lock().get(), links.objects[1].get());
- BOOST_CHECK_EQUAL(links.objects[0]->ends[1].nexts[0].second, 0);
- BOOST_CHECK_EQUAL(links.objects[0]->ends[1].nexts[1].first.lock().get(), links.objects[5].get());
- BOOST_CHECK_EQUAL(links.objects[0]->ends[1].nexts[1].second, 0);
- BOOST_REQUIRE_EQUAL(links.objects[1]->ends[0].nexts.size(), 2);
- BOOST_CHECK_EQUAL(links.objects[1]->ends[0].nexts[0].first.lock().get(), links.objects[0].get());
- BOOST_CHECK_EQUAL(links.objects[1]->ends[0].nexts[0].second, 1);
- BOOST_CHECK_EQUAL(links.objects[1]->ends[0].nexts[1].first.lock().get(), links.objects[5].get());
- BOOST_CHECK_EQUAL(links.objects[1]->ends[0].nexts[1].second, 0);
+ BOOST_REQUIRE_EQUAL(links[0]->ends[1].nexts.size(), 2);
+ BOOST_CHECK_EQUAL(links[0]->ends[1].nexts[0].first.lock().get(), links[1].get());
+ BOOST_CHECK_EQUAL(links[0]->ends[1].nexts[0].second, 0);
+ BOOST_CHECK_EQUAL(links[0]->ends[1].nexts[1].first.lock().get(), links[5].get());
+ BOOST_CHECK_EQUAL(links[0]->ends[1].nexts[1].second, 0);
+ BOOST_REQUIRE_EQUAL(links[1]->ends[0].nexts.size(), 2);
+ BOOST_CHECK_EQUAL(links[1]->ends[0].nexts[0].first.lock().get(), links[0].get());
+ BOOST_CHECK_EQUAL(links[1]->ends[0].nexts[0].second, 1);
+ BOOST_CHECK_EQUAL(links[1]->ends[0].nexts[1].first.lock().get(), links[5].get());
+ BOOST_CHECK_EQUAL(links[1]->ends[0].nexts[1].second, 0);
// Join 1 <-> 2
- BOOST_REQUIRE_EQUAL(links.objects[1]->ends[1].nexts.size(), 2);
- BOOST_CHECK_EQUAL(links.objects[1]->ends[1].nexts[0].first.lock().get(), links.objects[2].get());
- BOOST_CHECK_EQUAL(links.objects[1]->ends[1].nexts[0].second, 0);
- BOOST_REQUIRE_EQUAL(links.objects[2]->ends[0].nexts.size(), 2);
- BOOST_CHECK_EQUAL(links.objects[2]->ends[0].nexts[0].first.lock().get(), links.objects[1].get());
- BOOST_CHECK_EQUAL(links.objects[2]->ends[0].nexts[0].second, 1);
+ BOOST_REQUIRE_EQUAL(links[1]->ends[1].nexts.size(), 2);
+ BOOST_CHECK_EQUAL(links[1]->ends[1].nexts[0].first.lock().get(), links[2].get());
+ BOOST_CHECK_EQUAL(links[1]->ends[1].nexts[0].second, 0);
+ BOOST_REQUIRE_EQUAL(links[2]->ends[0].nexts.size(), 2);
+ BOOST_CHECK_EQUAL(links[2]->ends[0].nexts[0].first.lock().get(), links[1].get());
+ BOOST_CHECK_EQUAL(links[2]->ends[0].nexts[0].second, 1);
}
-BOOST_DATA_TEST_CASE(routeTo_nodeNotInNetwork, INVALID_NODES, dest)
+BOOST_DATA_TEST_CASE(RouteToNodeNotInNetwork, INVALID_NODES, dest)
{
- const auto & start = links.objects.front()->ends[1];
+ const auto & start = links[0]->ends[1];
BOOST_CHECK_THROW((void)routeFromTo(start, dest), std::out_of_range);
}
-BOOST_AUTO_TEST_CASE(routeTo_noSteps)
+BOOST_AUTO_TEST_CASE(RouteToNoSteps)
{
- const auto & start = links.objects.front()->ends[1];
- auto r = this->routeFromTo(start, p100);
- BOOST_CHECK(r.empty());
+ const auto & start = links[0]->ends[1];
+ auto route = this->routeFromTo(start, P100);
+ BOOST_CHECK(route.empty());
}
-BOOST_AUTO_TEST_CASE(routeTo_upStream_to2)
+BOOST_AUTO_TEST_CASE(RouteToUpStreamTo2)
{
- const auto & start = links.objects.front()->ends[1];
- auto r = this->routeFromTo(start, p200);
- BOOST_REQUIRE_EQUAL(r.size(), 1);
- BOOST_CHECK_EQUAL(r[0].first.lock().get(), links.objects[1].get());
+ const auto & start = links[0]->ends[1];
+ auto route = this->routeFromTo(start, P200);
+ BOOST_REQUIRE_EQUAL(route.size(), 1);
+ BOOST_CHECK_EQUAL(route[0].first.lock().get(), links[1].get());
}
-BOOST_AUTO_TEST_CASE(routeTo_upStream_to3)
+BOOST_AUTO_TEST_CASE(RouteToUpStreamTo3)
{
- const auto & start = links.objects[0]->ends[1];
- auto r = this->routeFromTo(start, p300);
- BOOST_REQUIRE_EQUAL(r.size(), 2);
- BOOST_CHECK_EQUAL(r[0].first.lock().get(), links.objects[1].get());
- BOOST_CHECK_EQUAL(r[1].first.lock().get(), links.objects[2].get());
+ const auto & start = links[0]->ends[1];
+ auto route = this->routeFromTo(start, P300);
+ BOOST_REQUIRE_EQUAL(route.size(), 2);
+ BOOST_CHECK_EQUAL(route[0].first.lock().get(), links[1].get());
+ BOOST_CHECK_EQUAL(route[1].first.lock().get(), links[2].get());
}
-BOOST_AUTO_TEST_CASE(routeTo_downStream_to0)
+BOOST_AUTO_TEST_CASE(RouteToDownStreamTo0)
{
- const auto & start = links.objects[2]->ends[0];
- auto r = this->routeFromTo(start, p000);
- BOOST_REQUIRE_EQUAL(r.size(), 2);
- BOOST_CHECK_EQUAL(r[0].first.lock().get(), links.objects[1].get());
- BOOST_CHECK_EQUAL(r[1].first.lock().get(), links.objects[0].get());
+ const auto & start = links[2]->ends[0];
+ auto route = this->routeFromTo(start, P000);
+ BOOST_REQUIRE_EQUAL(route.size(), 2);
+ BOOST_CHECK_EQUAL(route[0].first.lock().get(), links[1].get());
+ BOOST_CHECK_EQUAL(route[1].first.lock().get(), links[0].get());
}
-BOOST_AUTO_TEST_CASE(routeTo_upStream_3to300)
+BOOST_AUTO_TEST_CASE(RouteToUpStream3to300)
{
- const auto & start = links.objects[3]->ends[1];
- auto r = this->routeFromTo(start, p300);
- BOOST_REQUIRE_EQUAL(r.size(), 2);
- BOOST_CHECK_EQUAL(r[0].first.lock().get(), links.objects[4].get());
- BOOST_CHECK_EQUAL(r[1].first.lock().get(), links.objects[2].get());
+ const auto & start = links[3]->ends[1];
+ auto route = this->routeFromTo(start, P300);
+ BOOST_REQUIRE_EQUAL(route.size(), 2);
+ BOOST_CHECK_EQUAL(route[0].first.lock().get(), links[4].get());
+ BOOST_CHECK_EQUAL(route[1].first.lock().get(), links[2].get());
}
-BOOST_AUTO_TEST_CASE(routeTo_downStream_3to300)
+BOOST_AUTO_TEST_CASE(RouteToDownStream3to300)
{
- const auto & start = links.objects[3]->ends[0];
- auto r = this->routeFromTo(start, p300);
- BOOST_REQUIRE_EQUAL(r.size(), 3);
- BOOST_CHECK_EQUAL(r[0].first.lock().get(), links.objects[0].get());
- BOOST_CHECK_EQUAL(r[1].first.lock().get(), links.objects[1].get());
- BOOST_CHECK_EQUAL(r[2].first.lock().get(), links.objects[2].get());
+ const auto & start = links[3]->ends[0];
+ auto route = this->routeFromTo(start, P300);
+ BOOST_REQUIRE_EQUAL(route.size(), 3);
+ BOOST_CHECK_EQUAL(route[0].first.lock().get(), links[0].get());
+ BOOST_CHECK_EQUAL(route[1].first.lock().get(), links[1].get());
+ BOOST_CHECK_EQUAL(route[2].first.lock().get(), links[2].get());
}
BOOST_AUTO_TEST_SUITE_END()
-BOOST_FIXTURE_TEST_CASE(test_rail_network, RailLinks)
+BOOST_FIXTURE_TEST_CASE(TestRailNetwork, RailLinks)
{
// 0 1 2
// --p000 <-> p100 <-> p200 <-> p300 \ x
@@ -219,51 +235,60 @@ BOOST_FIXTURE_TEST_CASE(test_rail_network, RailLinks)
// \ | \ /
// \ / ------/
// --------
- auto l0 = addLinksBetween(p000, p100);
- BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l0.get()));
- BOOST_CHECK_EQUAL(l0->length, 10000);
- BOOST_CHECK_CLOSE(l0->ends[0].dir, half_pi, 0.1F);
- BOOST_CHECK_CLOSE(l0->ends[1].dir, -half_pi, 0.1F);
- BOOST_CHECK(l0->ends[0].nexts.empty());
- BOOST_CHECK(l0->ends[1].nexts.empty());
-
- auto l1 = addLinksBetween(p200, p100);
- BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l1.get()));
- BOOST_CHECK_EQUAL(l1->length, 10000);
- BOOST_CHECK_CLOSE(l1->ends[0].dir, half_pi, 0.1F);
- BOOST_CHECK_CLOSE(l1->ends[1].dir, -half_pi, 0.1F);
- BOOST_CHECK(l0->ends[0].nexts.empty());
- BOOST_CHECK_EQUAL(l0->ends[1].nexts.at(0).first.lock(), l1);
- BOOST_CHECK_EQUAL(l0->ends[1].nexts.at(0).second, 0);
- BOOST_CHECK_EQUAL(l1->ends[0].nexts.at(0).first.lock(), l0);
- BOOST_CHECK_EQUAL(l1->ends[0].nexts.at(0).second, 1);
- BOOST_CHECK(l1->ends[1].nexts.empty());
-
- auto l2 = addLinksBetween(p200, p300);
- BOOST_CHECK(dynamic_cast<RailLinkStraight *>(l2.get()));
- BOOST_CHECK_EQUAL(l2->length, 10000);
- BOOST_CHECK_CLOSE(l2->ends[0].dir, half_pi, 0.1F);
- BOOST_CHECK_CLOSE(l2->ends[1].dir, -half_pi, 0.1F);
- BOOST_CHECK(l0->ends[0].nexts.empty());
- BOOST_CHECK_EQUAL(l1->ends[1].nexts.at(0).first.lock(), l2);
- BOOST_CHECK_EQUAL(l1->ends[1].nexts.at(0).second, 0);
- BOOST_CHECK_EQUAL(l2->ends[0].nexts.at(0).first.lock(), l1);
- BOOST_CHECK_EQUAL(l2->ends[0].nexts.at(0).second, 1);
- BOOST_CHECK(l2->ends[1].nexts.empty());
-
- auto l3 = addLinksBetween(p000, p110);
- BOOST_CHECK(dynamic_cast<RailLinkCurve *>(l3.get()));
- BOOST_CHECK_CLOSE(l3->length, (pi + half_pi) * 10000.F, 0.1F);
- BOOST_CHECK_CLOSE(l3->ends[0].dir, -half_pi, 0.1F);
- BOOST_CHECK_CLOSE(l3->ends[1].dir, 0, 0.1F);
- BOOST_CHECK_EQUAL(l0->ends[0].nexts.at(0).first.lock(), l3);
- BOOST_CHECK_EQUAL(l0->ends[0].nexts.at(0).second, 0);
- BOOST_CHECK_EQUAL(l3->ends[0].nexts.at(0).first.lock(), l0);
- BOOST_CHECK_EQUAL(l3->ends[0].nexts.at(0).second, 0);
- BOOST_CHECK(l3->ends[1].nexts.empty());
-
- auto l4 = addLinksBetween(p110, p300);
- BOOST_CHECK_CLOSE(l4->length, 30400.F, 0.1F);
- BOOST_CHECK_BETWEEN(l4->ends[0].dir, .23F, .24F);
- BOOST_CHECK_CLOSE(l4->ends[1].dir, half_pi, 0.1F);
+ auto link0 = addLinksBetween(P000, P100);
+ BOOST_CHECK(dynamic_cast<RailLinkStraight *>(link0.get()));
+ BOOST_CHECK_EQUAL(link0->length, ::distance(P000, P100));
+ BOOST_CHECK_CLOSE(link0->ends[0].dir, half_pi, 0.1F);
+ BOOST_CHECK_CLOSE(link0->ends[1].dir, -half_pi, 0.1F);
+ BOOST_CHECK(link0->ends[0].nexts.empty());
+ BOOST_CHECK(link0->ends[1].nexts.empty());
+
+ auto link1 = addLinksBetween(P200, P100);
+ BOOST_CHECK(dynamic_cast<RailLinkStraight *>(link1.get()));
+ BOOST_CHECK_EQUAL(link1->length, ::distance(P200, P100));
+ BOOST_CHECK_CLOSE(link1->ends[0].dir, half_pi, 0.1F);
+ BOOST_CHECK_CLOSE(link1->ends[1].dir, -half_pi, 0.1F);
+ BOOST_CHECK(link0->ends[0].nexts.empty());
+ BOOST_CHECK_EQUAL(link0->ends[1].nexts.at(0).first.lock(), link1);
+ BOOST_CHECK_EQUAL(link0->ends[1].nexts.at(0).second, 0);
+ BOOST_CHECK_EQUAL(link1->ends[0].nexts.at(0).first.lock(), link0);
+ BOOST_CHECK_EQUAL(link1->ends[0].nexts.at(0).second, 1);
+ BOOST_CHECK(link1->ends[1].nexts.empty());
+
+ auto link2 = addLinksBetween(P200, P300);
+ BOOST_CHECK(dynamic_cast<RailLinkStraight *>(link2.get()));
+ BOOST_CHECK_EQUAL(link2->length, ::distance(P200, P300));
+ BOOST_CHECK_CLOSE(link2->ends[0].dir, half_pi, 0.1F);
+ BOOST_CHECK_CLOSE(link2->ends[1].dir, -half_pi, 0.1F);
+ BOOST_CHECK(link0->ends[0].nexts.empty());
+ BOOST_CHECK_EQUAL(link1->ends[1].nexts.at(0).first.lock(), link2);
+ BOOST_CHECK_EQUAL(link1->ends[1].nexts.at(0).second, 0);
+ BOOST_CHECK_EQUAL(link2->ends[0].nexts.at(0).first.lock(), link1);
+ BOOST_CHECK_EQUAL(link2->ends[0].nexts.at(0).second, 1);
+ BOOST_CHECK(link2->ends[1].nexts.empty());
+
+ BOOST_CHECK_IF(link3, addLinksBetween(P000, P110)) {
+ BOOST_CHECK_IF(link3c, dynamic_cast<RailLinkCurve *>(link3.get())) {
+ BOOST_CHECK_CLOSE(link3c->radius, 10'300.F, 0.1F);
+ BOOST_CHECK_CLOSE(link3c->arc.length(), pi + half_pi, 0.5F);
+ BOOST_CHECK_CLOSE(link3->length, 48'563.F, 0.1F);
+ BOOST_CHECK_CLOSE(link3->ends[0].dir, -half_pi, 0.5F);
+ BOOST_CHECK_CLOSE(link3->ends[1].dir, -0.0097F, 0.5F);
+ BOOST_CHECK_EQUAL(link0->ends[0].nexts.at(0).first.lock(), link3);
+ BOOST_CHECK_EQUAL(link0->ends[0].nexts.at(0).second, 0);
+ BOOST_CHECK_EQUAL(link3->ends[0].nexts.at(0).first.lock(), link0);
+ BOOST_CHECK_EQUAL(link3->ends[0].nexts.at(0).second, 0);
+ BOOST_CHECK(link3->ends[1].nexts.empty());
+ }
+ }
+
+ BOOST_CHECK_IF(link4, addLinksBetween(P110, P300)) {
+ BOOST_CHECK_IF(link4c, dynamic_cast<RailLinkCurve *>(link4.get())) {
+ BOOST_CHECK_CLOSE(link4c->radius, 6950.F, 0.1F);
+ BOOST_CHECK_CLOSE(link4c->arc.length(), 4.456F, 0.1F);
+ BOOST_CHECK_CLOSE(link4->length, 30'981.F, 0.1F);
+ BOOST_CHECK_BETWEEN(link4->ends[0].dir, .25F, .26F);
+ BOOST_CHECK_CLOSE(link4->ends[1].dir, half_pi, 0.1F);
+ }
+ }
}
diff --git a/test/test-pack.cpp b/test/test-pack.cpp
index 1f9f061..1e5848c 100644
--- a/test/test-pack.cpp
+++ b/test/test-pack.cpp
@@ -10,7 +10,7 @@ using IntegerVectorPack = pack<int, std::vector>;
BOOST_FIXTURE_TEST_SUITE(pint, IntegerVectorPack)
-BOOST_AUTO_TEST_CASE(basics)
+BOOST_AUTO_TEST_CASE(Basics)
{
BOOST_CHECK_EQUAL(size(), 0);
BOOST_CHECK_NO_THROW(emplace(1));
diff --git a/test/test-persistence.cpp b/test/test-persistence.cpp
index ce53f72..1385424 100644
--- a/test/test-persistence.cpp
+++ b/test/test-persistence.cpp
@@ -15,191 +15,193 @@
#include <tuple>
#include <vector>
-struct JPP {
- template<typename T>
- T
- load_json(const std::filesystem::path & path)
- {
- BOOST_TEST_CONTEXT(path) {
- std::ifstream ss {path};
- auto to = Persistence::JsonParsePersistence {}.loadState<T>(ss);
- BOOST_REQUIRE(to);
- return to;
+namespace {
+ struct JPP {
+ template<typename T>
+ T
+ loadJson(const std::filesystem::path & path)
+ {
+ BOOST_TEST_CONTEXT(path) {
+ std::ifstream inputStream {path};
+ auto object = Persistence::JsonParsePersistence {}.loadState<T>(inputStream);
+ BOOST_REQUIRE(object);
+ return object;
+ }
+
+ // Presumably BOOST_TEST_CONTEXT is implemented as an if (...) { }
+ std::unreachable();
}
+ };
- // Presumably BOOST_TEST_CONTEXT is implemented as an if (...) { }
- throw std::logic_error("We shouldn't ever get here, but apparently we can!");
+ std::vector<std::filesystem::path>
+ fixturesIn(const std::filesystem::path & root)
+ {
+ return {std::filesystem::directory_iterator {root}, {}};
}
-};
-
-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);
- BOOST_CHECK_CLOSE(to->pos[0], 3.14, 0.01);
- BOOST_CHECK_CLOSE(to->pos[1], 6.28, 0.01);
- BOOST_CHECK_CLOSE(to->pos[2], 1.57, 0.01);
- BOOST_CHECK_EQUAL(to->gpos[0], 2147483647);
- BOOST_CHECK_EQUAL(to->gpos[1], 2147483646);
- BOOST_CHECK_EQUAL(to->gpos[2], -2147483648);
- BOOST_REQUIRE_EQUAL(to->flts.size(), 6);
- BOOST_CHECK_CLOSE(to->flts[0], 3.14, 0.01);
- BOOST_CHECK_CLOSE(to->flts[1], 6.28, 0.01);
- BOOST_CHECK_CLOSE(to->flts[2], 1.57, 0.01);
- BOOST_CHECK_CLOSE(to->flts[3], 0, 0.01);
- BOOST_CHECK_CLOSE(to->flts[4], -1, 0.01);
- BOOST_CHECK_CLOSE(to->flts[5], -3.14, 0.01);
- BOOST_REQUIRE_EQUAL(to->poss.size(), 2);
- BOOST_CHECK_CLOSE(to->poss[0][0], 3.14, 0.01);
- BOOST_CHECK_CLOSE(to->poss[0][1], 6.28, 0.01);
- BOOST_CHECK_CLOSE(to->poss[0][2], 1.57, 0.01);
- BOOST_CHECK_CLOSE(to->poss[1][0], 0, 0.01);
- BOOST_CHECK_CLOSE(to->poss[1][1], -1, 0.01);
- BOOST_CHECK_CLOSE(to->poss[1][2], -3.14, 0.01);
- BOOST_REQUIRE_EQUAL(to->nest.size(), 3);
- BOOST_REQUIRE_EQUAL(to->nest.at(0).size(), 2);
- BOOST_REQUIRE_EQUAL(to->nest.at(0).at(0).size(), 2);
- BOOST_REQUIRE_EQUAL(to->nest.at(0).at(1).size(), 3);
- BOOST_REQUIRE_EQUAL(to->nest.at(1).size(), 1);
- BOOST_REQUIRE_EQUAL(to->nest.at(1).at(0).size(), 1);
- BOOST_REQUIRE_EQUAL(to->nest.at(2).size(), 0);
- BOOST_REQUIRE(to->ptr);
- BOOST_CHECK_CLOSE(to->ptr->flt, 3.14, 0.01);
- BOOST_CHECK_EQUAL(to->ptr->str, "Lovely string");
}
-BOOST_FIXTURE_TEST_CASE(load_nested_object, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadObject, 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);
- BOOST_CHECK_EQUAL(to->ptr->flt, 2.F);
- BOOST_CHECK_EQUAL(to->ptr->str, "two");
- BOOST_REQUIRE(to->ptr->ptr);
- BOOST_CHECK_EQUAL(to->ptr->ptr->flt, 3.F);
- BOOST_CHECK_EQUAL(to->ptr->ptr->str, "three");
- BOOST_REQUIRE(to->ptr->ptr->ptr);
- BOOST_CHECK_EQUAL(to->ptr->ptr->ptr->flt, 4.F);
- BOOST_CHECK_EQUAL(to->ptr->ptr->ptr->str, "four");
- BOOST_REQUIRE(!to->ptr->ptr->ptr->ptr);
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/load_object.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_CHECK_CLOSE(object->flt, 3.14, 0.01);
+ BOOST_CHECK_EQUAL(object->str, "Lovely string");
+ BOOST_CHECK_EQUAL(object->bl, true);
+ BOOST_CHECK_CLOSE(object->pos[0], 3.14, 0.01);
+ BOOST_CHECK_CLOSE(object->pos[1], 6.28, 0.01);
+ BOOST_CHECK_CLOSE(object->pos[2], 1.57, 0.01);
+ BOOST_CHECK_EQUAL(object->gpos[0], 2147483647);
+ BOOST_CHECK_EQUAL(object->gpos[1], 2147483646);
+ BOOST_CHECK_EQUAL(object->gpos[2], -2147483648);
+ BOOST_REQUIRE_EQUAL(object->flts.size(), 6);
+ BOOST_CHECK_CLOSE(object->flts[0], 3.14, 0.01);
+ BOOST_CHECK_CLOSE(object->flts[1], 6.28, 0.01);
+ BOOST_CHECK_CLOSE(object->flts[2], 1.57, 0.01);
+ BOOST_CHECK_CLOSE(object->flts[3], 0, 0.01);
+ BOOST_CHECK_CLOSE(object->flts[4], -1, 0.01);
+ BOOST_CHECK_CLOSE(object->flts[5], -3.14, 0.01);
+ BOOST_REQUIRE_EQUAL(object->poss.size(), 2);
+ BOOST_CHECK_CLOSE(object->poss[0][0], 3.14, 0.01);
+ BOOST_CHECK_CLOSE(object->poss[0][1], 6.28, 0.01);
+ BOOST_CHECK_CLOSE(object->poss[0][2], 1.57, 0.01);
+ BOOST_CHECK_CLOSE(object->poss[1][0], 0, 0.01);
+ BOOST_CHECK_CLOSE(object->poss[1][1], -1, 0.01);
+ BOOST_CHECK_CLOSE(object->poss[1][2], -3.14, 0.01);
+ BOOST_REQUIRE_EQUAL(object->nest.size(), 3);
+ BOOST_REQUIRE_EQUAL(object->nest.at(0).size(), 2);
+ BOOST_REQUIRE_EQUAL(object->nest.at(0).at(0).size(), 2);
+ BOOST_REQUIRE_EQUAL(object->nest.at(0).at(1).size(), 3);
+ BOOST_REQUIRE_EQUAL(object->nest.at(1).size(), 1);
+ BOOST_REQUIRE_EQUAL(object->nest.at(1).at(0).size(), 1);
+ BOOST_REQUIRE_EQUAL(object->nest.at(2).size(), 0);
+ BOOST_REQUIRE(object->ptr);
+ BOOST_CHECK_CLOSE(object->ptr->flt, 3.14, 0.01);
+ BOOST_CHECK_EQUAL(object->ptr->str, "Lovely string");
}
-BOOST_FIXTURE_TEST_CASE(load_implicit_object, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadNestedObject, 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");
- BOOST_CHECK_EQUAL(to->str, "after");
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/nested.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_CHECK_EQUAL(object->flt, 1.F);
+ BOOST_CHECK_EQUAL(object->str, "one");
+ BOOST_REQUIRE(object->ptr);
+ BOOST_CHECK_EQUAL(object->ptr->flt, 2.F);
+ BOOST_CHECK_EQUAL(object->ptr->str, "two");
+ BOOST_REQUIRE(object->ptr->ptr);
+ BOOST_CHECK_EQUAL(object->ptr->ptr->flt, 3.F);
+ BOOST_CHECK_EQUAL(object->ptr->ptr->str, "three");
+ BOOST_REQUIRE(object->ptr->ptr->ptr);
+ BOOST_CHECK_EQUAL(object->ptr->ptr->ptr->flt, 4.F);
+ BOOST_CHECK_EQUAL(object->ptr->ptr->ptr->str, "four");
+ BOOST_REQUIRE(!object->ptr->ptr->ptr->ptr);
}
-BOOST_FIXTURE_TEST_CASE(load_empty_object, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadImplicitObject, 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");
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/implicit.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_CHECK(object->ptr);
+ BOOST_CHECK_EQUAL(object->flt, 1.F);
+ BOOST_CHECK_EQUAL(object->ptr->str, "trigger");
+ BOOST_CHECK_EQUAL(object->str, "after");
}
-static std::vector<std::filesystem::path>
-fixtures_in(const std::filesystem::path & root)
+BOOST_FIXTURE_TEST_CASE(LoadEmptyObject, JPP)
{
- return {std::filesystem::directory_iterator {root}, {}};
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/empty.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_CHECK_EQUAL(object->flt, 1.F);
+ BOOST_CHECK(object->ptr);
+ BOOST_CHECK_EQUAL(object->str, "after");
}
-BOOST_DATA_TEST_CASE_F(JPP, various_parse_failures, fixtures_in(FIXTURESDIR "json/bad"), path)
+BOOST_DATA_TEST_CASE_F(JPP, various_parse_failures, fixturesIn(FIXTURESDIR "json/bad"), path)
{
- BOOST_CHECK_THROW(load_json<std::unique_ptr<TestObject>>(path), std::runtime_error);
+ BOOST_CHECK_THROW(loadJson<std::unique_ptr<TestObject>>(path), std::runtime_error);
}
-BOOST_FIXTURE_TEST_CASE(load_obj_no_such_type, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadObjNoSuchType, JPP)
{
- BOOST_CHECK_THROW(load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/bad_type.json"), std::out_of_range);
+ BOOST_CHECK_THROW(loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/bad_type.json"), std::out_of_range);
}
-BOOST_FIXTURE_TEST_CASE(load_abs_object, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadAbsObject, 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");
- auto s = dynamic_cast<SubObject *>(to->aptr.get());
- BOOST_REQUIRE(s);
- BOOST_CHECK_EQUAL(s->sub, "set sub");
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/abs.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_REQUIRE(object->aptr);
+ BOOST_CHECK_NO_THROW(object->aptr->dummy());
+ BOOST_CHECK_EQUAL(object->aptr->base, "set base");
+ auto subObject = dynamic_cast<SubObject *>(object->aptr.get());
+ BOOST_REQUIRE(subObject);
+ BOOST_CHECK_EQUAL(subObject->sub, "set sub");
}
-BOOST_FIXTURE_TEST_CASE(load_vector_ptr, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadVectorPtr, 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");
- BOOST_CHECK_CLOSE(to->vptr.at(1)->flt, 3.14, .01);
- BOOST_CHECK(!to->vptr.at(2));
- BOOST_CHECK(to->vptr.at(3)->str.empty());
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/vector_ptr.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_CHECK(object->str.empty());
+ BOOST_CHECK_EQUAL(object->vptr.size(), 4);
+ BOOST_CHECK_EQUAL(object->vptr.at(0)->str, "type");
+ BOOST_CHECK_CLOSE(object->vptr.at(1)->flt, 3.14, .01);
+ BOOST_CHECK(!object->vptr.at(2));
+ BOOST_CHECK(object->vptr.at(3)->str.empty());
}
-BOOST_FIXTURE_TEST_CASE(test_conversion, JPP)
+BOOST_FIXTURE_TEST_CASE(TestConversion, 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);
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/conv.json");
+ BOOST_CHECK_EQUAL(object->postLoadCalled, 1);
+ BOOST_REQUIRE(object);
+ BOOST_CHECK_EQUAL(object->bl, true);
+ BOOST_CHECK_EQUAL(object->flt, 3.14F);
}
-BOOST_FIXTURE_TEST_CASE(load_shared_object_diff, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadSharedObjectDiff, JPP)
{
- auto to = load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_diff.json");
- BOOST_CHECK(to->sptr);
- BOOST_CHECK(to->ssptr);
- BOOST_CHECK_NE(to->sptr, to->ssptr);
- BOOST_CHECK_EQUAL(to->sptr.use_count(), 1);
- BOOST_CHECK_EQUAL(to->ssptr.use_count(), 1);
+ auto object = loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_diff.json");
+ BOOST_CHECK(object->sptr);
+ BOOST_CHECK(object->ssptr);
+ BOOST_CHECK_NE(object->sptr, object->ssptr);
+ BOOST_CHECK_EQUAL(object->sptr.use_count(), 1);
+ BOOST_CHECK_EQUAL(object->ssptr.use_count(), 1);
}
-BOOST_FIXTURE_TEST_CASE(load_shared_object_same, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadSharedObjectSame, JPP)
{
- auto to = load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_same.json");
- BOOST_CHECK(to->sptr);
- BOOST_CHECK(to->ssptr);
- BOOST_CHECK_EQUAL(to->sptr, to->ssptr);
- BOOST_CHECK_EQUAL(to->sptr.use_count(), 2);
- BOOST_CHECK_EQUAL(to->ssptr.use_count(), 2);
+ auto object = loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_same.json");
+ BOOST_CHECK(object->sptr);
+ BOOST_CHECK(object->ssptr);
+ BOOST_CHECK_EQUAL(object->sptr, object->ssptr);
+ BOOST_CHECK_EQUAL(object->sptr.use_count(), 2);
+ BOOST_CHECK_EQUAL(object->ssptr.use_count(), 2);
}
-BOOST_FIXTURE_TEST_CASE(load_shared_object_diff_default, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadSharedObjectDiffDefault, JPP)
{
- auto to = load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_diff_default.json");
- BOOST_CHECK(to->sptr);
- BOOST_CHECK(to->ssptr);
- BOOST_CHECK_NE(to->sptr, to->ssptr);
+ auto object = loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_diff_default.json");
+ BOOST_CHECK(object->sptr);
+ BOOST_CHECK(object->ssptr);
+ BOOST_CHECK_NE(object->sptr, object->ssptr);
}
-BOOST_FIXTURE_TEST_CASE(load_shared_object_wrong_type, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadSharedObjectWrongType, JPP)
{
- BOOST_CHECK_THROW(load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_wrong_type.json"),
+ BOOST_CHECK_THROW(loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_wrong_type.json"),
std::runtime_error);
}
-BOOST_FIXTURE_TEST_CASE(load_shared_object_null, JPP)
+BOOST_FIXTURE_TEST_CASE(LoadSharedObjectNull, JPP)
{
- auto to = load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_null.json");
- BOOST_CHECK(to->sptr);
- BOOST_CHECK(!to->ssptr);
+ auto object = loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_null.json");
+ BOOST_CHECK(object->sptr);
+ BOOST_CHECK(!object->ssptr);
}
-using svs = std::tuple<const char * const, std::string_view>;
-auto const TEST_STRINGS = boost::unit_test::data::make<svs>({
+using InputStringAndExpected = std::tuple<const char * const, std::string_view>;
+auto const TEST_STRINGS = boost::unit_test::data::make<InputStringAndExpected>({
{R"J("")J", ""},
{R"J("non empty")J", "non empty"},
{R"J("new\nline")J", "new\nline"},
@@ -209,7 +211,7 @@ auto const TEST_STRINGS = boost::unit_test::data::make<svs>({
{R"J("form\ffeed?")J", "form\ffeed?"},
{R"J("a \u0007 bell")J", "a \a bell"},
});
-auto const TEST_STRINGS_DECODE_ONLY = boost::unit_test::data::make<svs>({
+auto const TEST_STRINGS_DECODE_ONLY = boost::unit_test::data::make<InputStringAndExpected>({
{R"J("forward\/slash")J", "forward/slash"},
{R"J("\u00a5 yen")J", "¥ yen"},
{R"J("gbp \u00a3")J", "gbp £"},
@@ -218,93 +220,94 @@ auto const TEST_STRINGS_DECODE_ONLY = boost::unit_test::data::make<svs>({
{R"J("\u0833 SAMARITAN PUNCTUATION BAU")J", "࠳ SAMARITAN PUNCTUATION BAU"},
});
-BOOST_DATA_TEST_CASE(load_strings, TEST_STRINGS + TEST_STRINGS_DECODE_ONLY, in, exp)
+BOOST_DATA_TEST_CASE(LoadStrings, TEST_STRINGS + TEST_STRINGS_DECODE_ONLY, input, exp)
{
- std::stringstream str {in};
+ std::stringstream str {input};
BOOST_CHECK_EQUAL(Persistence::JsonParsePersistence {}.loadState<std::string>(str), exp);
}
-using cpstr = std::tuple<unsigned long, std::string_view>;
+using CodePointAndString = std::tuple<unsigned long, std::string_view>;
-BOOST_DATA_TEST_CASE(utf8_decode,
- boost::unit_test::data::make<cpstr>({
+BOOST_DATA_TEST_CASE(Utf8Decode,
+ boost::unit_test::data::make<CodePointAndString>({
{9, "\t"},
{0x00010000, "𐀀"},
}),
- cp, str)
+ codePoint, str)
{
std::string out;
- BOOST_CHECK_NO_THROW(json::jsonParser::appendEscape(cp, out));
+ BOOST_CHECK_NO_THROW(json::jsonParser::appendEscape(codePoint, out));
BOOST_CHECK_EQUAL(out, str);
}
-BOOST_DATA_TEST_CASE(utf8_decode_bad, boost::unit_test::data::make<unsigned long>({0xd800, 0xdfff, 0x110000}), cp)
+BOOST_DATA_TEST_CASE(Utf8DecodeBad, boost::unit_test::data::make<unsigned long>({0xd800, 0xdfff, 0x110000}), codePoint)
{
std::string out;
- BOOST_CHECK_THROW(json::jsonParser::appendEscape(cp, out), std::runtime_error);
+ BOOST_CHECK_THROW(json::jsonParser::appendEscape(codePoint, out), std::runtime_error);
}
-BOOST_AUTO_TEST_CASE(write_test_null)
+BOOST_AUTO_TEST_CASE(WriteTestNull)
{
- std::unique_ptr<TestObject> to {};
- std::stringstream ss;
- Persistence::JsonWritePersistence {ss}.saveState(to);
- BOOST_CHECK_EQUAL(ss.str(), "null");
+ std::unique_ptr<TestObject> object {};
+ std::stringstream outStream;
+ Persistence::JsonWritePersistence {outStream}.saveState(object);
+ BOOST_CHECK_EQUAL(outStream.view(), "null");
}
-BOOST_AUTO_TEST_CASE(write_test_dfl)
+BOOST_AUTO_TEST_CASE(WriteTestDfl)
{
- auto to = std::make_unique<TestObject>();
- std::stringstream ss;
- Persistence::JsonWritePersistence {ss}.saveState(to);
- BOOST_CHECK_EQUAL(ss.str(),
+ auto object = std::make_unique<TestObject>();
+ std::stringstream outStream;
+ Persistence::JsonWritePersistence {outStream}.saveState(object);
+ BOOST_CHECK_EQUAL(outStream.view(),
R"({"p.typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"gpos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]})");
}
-BOOST_FIXTURE_TEST_CASE(write_test_loaded, JPP)
+BOOST_FIXTURE_TEST_CASE(WriteTestLoaded, JPP)
{
- auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/load_object.json");
- std::stringstream ss;
- Persistence::JsonWritePersistence {ss}.saveState(to);
- BOOST_CHECK_EQUAL(ss.str(),
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/load_object.json");
+ std::stringstream outStream;
+ Persistence::JsonWritePersistence {outStream}.saveState(object);
+ BOOST_CHECK_EQUAL(outStream.view(),
R"({"p.typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":true,"pos":[3.14,6.28,1.57],"gpos":[2147483647,2147483646,-2147483648],"flts":[3.14,6.28,1.57,0,-1,-3.14],"poss":[[3.14,6.28,1.57],[0,-1,-3.14]],"nest":[[["a","b"],["c","d","e"]],[["f"]],[]],"ptr":{"p.typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":false,"pos":[0,0,0],"gpos":[0,0,0],"flts":[],"poss":[],"nest":[],"vptr":[]},"vptr":[]})");
}
-BOOST_FIXTURE_TEST_CASE(write_test_loaded_abs, JPP)
+BOOST_FIXTURE_TEST_CASE(WriteTestLoadedAbs, JPP)
{
- auto to = load_json<std::unique_ptr<TestObject>>(FIXTURESDIR "json/abs.json");
- std::stringstream ss;
- Persistence::JsonWritePersistence {ss}.saveState(to);
- BOOST_CHECK_EQUAL(ss.str(),
+ auto object = loadJson<std::unique_ptr<TestObject>>(FIXTURESDIR "json/abs.json");
+ std::stringstream outStream;
+ Persistence::JsonWritePersistence {outStream}.saveState(object);
+ BOOST_CHECK_EQUAL(outStream.view(),
R"({"p.typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"gpos":[0,0,0],"flts":[],"poss":[],"nest":[],"aptr":{"p.typeid":"SubObject","base":"set base","sub":"set sub"},"vptr":[]})");
}
-BOOST_FIXTURE_TEST_CASE(write_test_loaded_shared, JPP)
+BOOST_FIXTURE_TEST_CASE(WriteTestLoadedShared, JPP)
{
- auto to = load_json<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_same.json");
- std::stringstream ss;
+ auto object = loadJson<std::unique_ptr<SharedTestObject>>(FIXTURESDIR "json/shared_ptr_same.json");
+ std::stringstream outStream;
Persistence::seenSharedObjects.clear();
- Persistence::JsonWritePersistence {ss}.saveState(to);
+ Persistence::JsonWritePersistence {outStream}.saveState(object);
BOOST_CHECK_EQUAL(Persistence::seenSharedObjects.size(), 1);
- BOOST_CHECK_EQUAL(ss.str(),
+ BOOST_CHECK_EQUAL(outStream.view(),
R"({"p.typeid":"SharedTestObject","sptr":{"p.typeid":"SubObject","p.id":"someid","base":"","sub":""},"ssptr":"someid"})");
}
-BOOST_DATA_TEST_CASE(write_special_strings, TEST_STRINGS, exp, in)
+BOOST_DATA_TEST_CASE(WriteSpecialStrings, TEST_STRINGS, exp, input)
{
- std::stringstream ss;
- std::string copy(in);
- Persistence::JsonWritePersistence {ss}.saveState(copy);
- BOOST_CHECK_EQUAL(ss.str(), exp);
+ std::stringstream outStream;
+ std::string copy(input);
+ Persistence::JsonWritePersistence {outStream}.saveState(copy);
+ BOOST_CHECK_EQUAL(outStream.view(), exp);
}
-BOOST_AUTO_TEST_CASE(get_default_id)
+BOOST_AUTO_TEST_CASE(GetDefaultId)
{
- SubObject2 so;
- const auto id {so.getId()};
+ SubObject2 subObject;
+ const auto subObjectId {subObject.getId()};
- BOOST_TEST_CONTEXT(id) {
- auto ptr = std::stoul(id, nullptr, 16);
- BOOST_CHECK_EQUAL(ptr, reinterpret_cast<decltype(ptr)>(&so));
+ BOOST_TEST_CONTEXT(subObjectId) {
+ auto ptr = std::stoul(subObjectId, nullptr, 16);
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ BOOST_CHECK_EQUAL(ptr, reinterpret_cast<decltype(ptr)>(&subObject));
}
}
diff --git a/test/test-render.cpp b/test/test-render.cpp
index 2c4efea..90fa894 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -7,8 +7,12 @@
#include <boost/test/unit_test.hpp>
#include <assetFactory/assetFactory.h>
+#include <game/environment.h>
+#include <game/gamestate.h>
#include <game/geoData.h>
#include <game/network/rail.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>
@@ -22,86 +26,123 @@
#include <ui/applicationBase.h>
#include <ui/window.h>
-class TestScene : public SceneProvider {
- const RailVehicleClassPtr brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(
- AssetFactory::loadXML(RESDIR "/brush47.xml")->assets.at("brush-47"));
- std::shared_ptr<RailVehicle> train1, train2;
- RailLinks rail;
- std::shared_ptr<GeoData> gd = std::make_shared<GeoData>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 1));
-
- Terrain terrain {gd};
- Water water {gd};
-
-public:
- TestScene()
- {
- train1 = std::make_shared<RailVehicle>(brush47rvc);
- train1->location.setPosition({52000, 50000, 2000});
- train1->bogies.front().setPosition(train1->bogies.front().position() + train1->location.position());
- train1->bogies.back().setPosition(train1->bogies.back().position() + train1->location.position());
- train2 = std::make_shared<RailVehicle>(brush47rvc);
- train2->location.setPosition({52000, 30000, 2000});
- train2->bogies.front().setPosition(train2->bogies.front().position() + train2->location.position());
- train2->bogies.back().setPosition(train2->bogies.back().position() + train2->location.position());
- rail.addLinksBetween({42000, 50000, 1000}, {65000, 50000, 1000});
- rail.addLinksBetween({65000, 50000, 1000}, {75000, 45000, 2000});
- }
-
- void
- content(const SceneShader & shader) const override
- {
- terrain.render(shader);
- water.render(shader);
- brush47rvc->render(shader);
- rail.render(shader);
- }
-
- void
- lights(const SceneShader &) const override
- {
- }
-
- void
- shadows(const ShadowMapper & shadowMapper) const override
- {
- terrain.shadows(shadowMapper);
- brush47rvc->shadows(shadowMapper);
- }
-};
+namespace {
+ class TestScene : public SceneProvider {
+ RailVehicleClassPtr brush47rvc;
+ std::shared_ptr<RailVehicle> train1, train2;
+ RailLinks rail;
+ std::shared_ptr<Environment> env = std::make_shared<Environment>();
+
+ std::shared_ptr<Terrain> terrain
+ = std::make_shared<Terrain>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 1));
+ Water water {terrain};
+
+ public:
+ TestScene()
+ {
+ terrain->point(GeoData::VertexHandle {517}).z = 100'000;
+ terrain->generateMeshes();
+ gameState->assets = AssetFactory::loadAll(RESDIR);
+ brush47rvc = gameState->assets.at("brush-47").dynamicCast<RailVehicleClass>();
+ std::random_device randomdev {};
+ std::uniform_real_distribution<Angle> rotationDistribution {0, two_pi};
+ std::uniform_int_distribution<GlobalDistance> positionOffsetDistribution {-1500, +1500};
+ std::uniform_int_distribution<int> treeDistribution {1, 3};
+ std::uniform_int_distribution<int> treeVariantDistribution {1, 4};
+ train1 = std::make_shared<RailVehicle>(brush47rvc);
+ train1->location.setPosition({52000, 50000, 2000});
+ train1->bogies.front().setPosition(train1->bogies.front().position() + train1->location.position());
+ train1->bogies.back().setPosition(train1->bogies.back().position() + train1->location.position());
+ train2 = std::make_shared<RailVehicle>(brush47rvc);
+ train2->location.setPosition({52000, 30000, 2000});
+ train2->bogies.front().setPosition(train2->bogies.front().position() + train2->location.position());
+ train2->bogies.back().setPosition(train2->bogies.back().position() + train2->location.position());
+ for (auto posX = 40000; posX < 100000; posX += 5000) {
+ for (auto posY = 65000; posY < 125000; posY += 5000) {
+ gameState->world.create<Plant>(
+ gameState->assets
+ .at(std::format("Tree-{:#02}-{}", treeDistribution(randomdev),
+ treeVariantDistribution(randomdev)))
+ .dynamicCast<Foliage>(),
+ Location {.pos = {posX + positionOffsetDistribution(randomdev),
+ posY + positionOffsetDistribution(randomdev), 1},
+ .rot = {0, rotationDistribution(randomdev), 0}});
+ }
+ }
+ rail.addLinksBetween({42000, 50000, 1000}, {65000, 50000, 1000});
+ rail.addLinksBetween({65000, 50000, 1000}, {75000, 45000, 2000});
+ }
+
+ void
+ content(const SceneShader & shader, const Frustum & frustum) const override
+ {
+ terrain->render(shader, frustum);
+ water.render(shader, frustum);
+ rail.render(shader, frustum);
+ std::ranges::for_each(gameState->assets, [&shader, &frustum](const auto & asset) {
+ if (const auto renderable = asset.second.template getAs<const Renderable>()) {
+ renderable->render(shader, frustum);
+ }
+ });
+ }
+
+ void
+ lights(const SceneShader &) const override
+ {
+ }
+
+ void
+ environment(const SceneShader &, const SceneRenderer & renderer) const override
+ {
+ env->render(renderer, *this);
+ }
+
+ void
+ shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override
+ {
+ terrain->shadows(shadowMapper, frustum);
+ std::ranges::for_each(gameState->assets, [&shadowMapper, &frustum](const auto & asset) {
+ if (const auto renderable = asset.second.template getAs<const Renderable>()) {
+ renderable->shadows(shadowMapper, frustum);
+ }
+ });
+ }
+ };
+}
BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
-BOOST_DATA_TEST_CASE(cam,
+BOOST_DATA_TEST_CASE(Cam,
boost::unit_test::data::xrange(500, 30000, 1300) * boost::unit_test::data::xrange(500, 10000, 300)
* boost::unit_test::data::xrange(50000, 500000, 70000),
dist, near, far)
{
- static constexpr GlobalPosition4D pos {-10, -10, 60000, 0};
- const Camera cam {pos, half_pi, 1.F, near, far};
+ static constexpr GlobalPosition4D POS {-10, -10, 60000, 0};
+ const Camera cam {POS, half_pi, 1.F, near, far};
- const auto e = cam.extentsAtDist(dist);
+ const auto extents = cam.extentsAtDist(dist);
- BOOST_CHECK_CLOSE_VECI(e[0], pos + GlobalPosition4D(-dist, dist, -dist, dist));
- BOOST_CHECK_CLOSE_VECI(e[1], pos + GlobalPosition4D(-dist, dist, dist, dist));
- BOOST_CHECK_CLOSE_VECI(e[2], pos + GlobalPosition4D(dist, dist, -dist, dist));
- BOOST_CHECK_CLOSE_VECI(e[3], pos + GlobalPosition4D(dist, dist, dist, dist));
+ BOOST_CHECK_CLOSE_VECI(extents[0], POS + GlobalPosition4D(-dist, dist, -dist, dist));
+ BOOST_CHECK_CLOSE_VECI(extents[1], POS + GlobalPosition4D(-dist, dist, dist, dist));
+ BOOST_CHECK_CLOSE_VECI(extents[2], POS + GlobalPosition4D(dist, dist, -dist, dist));
+ BOOST_CHECK_CLOSE_VECI(extents[3], POS + GlobalPosition4D(dist, dist, dist, dist));
}
-BOOST_AUTO_TEST_CASE(camSeaFloor)
+BOOST_AUTO_TEST_CASE(CamSeaFloor)
{
const Camera cam {{100, 200, 300}, half_pi, 1.F, 100, 2000};
- const auto e = cam.extentsAtDist(2000);
+ const auto extents = cam.extentsAtDist(2000);
- BOOST_CHECK_CLOSE_VECI(e[0], GlobalPosition4D(-1700, 2000, -1500, 1800));
- BOOST_CHECK_CLOSE_VECI(e[1], GlobalPosition4D(-1900, 2200, 2300, 2000));
- BOOST_CHECK_CLOSE_VECI(e[2], GlobalPosition4D(1900, 2000, -1500, 1800));
- BOOST_CHECK_CLOSE_VECI(e[3], GlobalPosition4D(2100, 2200, 2300, 2000));
+ BOOST_CHECK_CLOSE_VECI(extents[0], GlobalPosition4D(-1700, 2000, -1500, 1800));
+ BOOST_CHECK_CLOSE_VECI(extents[1], GlobalPosition4D(-1900, 2200, 2300, 2000));
+ BOOST_CHECK_CLOSE_VECI(extents[2], GlobalPosition4D(1900, 2000, -1500, 1800));
+ BOOST_CHECK_CLOSE_VECI(extents[3], GlobalPosition4D(2100, 2200, 2300, 2000));
}
BOOST_FIXTURE_TEST_SUITE(w, TestRenderOutput);
-BOOST_AUTO_TEST_CASE(basic)
+BOOST_AUTO_TEST_CASE(Basic)
{
class TestSceneRenderer : public SceneRenderer {
using SceneRenderer::SceneRenderer;
@@ -118,37 +159,36 @@ BOOST_AUTO_TEST_CASE(basic)
}
};
- TestSceneRenderer ss {size, output};
- ss.camera.setView({-10000, -10000, 60000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
+ TestSceneRenderer renderer {size, output};
+ renderer.camera.setView({-10000, -10000, 60000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
const TestScene scene;
- ss.render(scene);
- ss.saveBuffers("/tmp/basic");
+ renderer.render(scene);
+ renderer.saveBuffers("/tmp/basic");
Texture::save(outImage, "/tmp/basic/final.tga");
}
-BOOST_AUTO_TEST_CASE(terrain)
+BOOST_AUTO_TEST_CASE(TerrainSD19)
{
- SceneRenderer ss {size, output};
- ss.camera.setView({310000000, 490000000, 600000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
+ SceneRenderer renderer {size, output};
+ renderer.camera.setView({310000000, 490000000, 600000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
class TestTerrain : public SceneProvider {
- std::shared_ptr<GeoData> gd
- = std::make_shared<GeoData>(GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc"));
- Terrain terrain {gd};
- Water water {gd};
+ std::shared_ptr<Terrain> terrain
+ = std::make_shared<Terrain>(GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc"));
+ Water water {terrain};
void
- content(const SceneShader & shader) const override
+ content(const SceneShader & shader, const Frustum & frustum) const override
{
- terrain.render(shader);
- water.render(shader);
+ terrain->render(shader, frustum);
+ water.render(shader, frustum);
}
void
- environment(const SceneShader &, const SceneRenderer & sr) const override
+ environment(const SceneShader &, const SceneRenderer & renderer) const override
{
- sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ renderer.setAmbientLight({0.1, 0.1, 0.1});
+ renderer.setDirectionalLight({1, 1, 1}, {{0, quarter_pi}}, *this);
}
void
@@ -157,20 +197,20 @@ BOOST_AUTO_TEST_CASE(terrain)
}
void
- shadows(const ShadowMapper & shadowMapper) const override
+ shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const override
{
- terrain.shadows(shadowMapper);
+ terrain->shadows(shadowMapper, frustum);
}
};
- ss.render(TestTerrain {});
+ renderer.render(TestTerrain {});
Texture::save(outImage, "/tmp/terrain.tga");
}
-BOOST_AUTO_TEST_CASE(railnet)
+BOOST_AUTO_TEST_CASE(RailNetwork)
{
- SceneRenderer ss {size, output};
- ss.camera.setView({0, 0, 10000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
+ SceneRenderer renderer {size, output};
+ renderer.camera.setView({0, 0, 10000}, glm::normalize(glm::vec3 {1, 1, -0.5F}));
class TestRail : public SceneProvider {
RailLinks net;
@@ -186,16 +226,16 @@ BOOST_AUTO_TEST_CASE(railnet)
}
void
- content(const SceneShader & shader) const override
+ content(const SceneShader & shader, const Frustum & frustum) const override
{
- net.render(shader);
+ net.render(shader, frustum);
}
void
- environment(const SceneShader &, const SceneRenderer & sr) const override
+ environment(const SceneShader &, const SceneRenderer & renderer) const override
{
- sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ renderer.setAmbientLight({0.1, 0.1, 0.1});
+ renderer.setDirectionalLight({1, 1, 1}, {{0, quarter_pi}}, *this);
}
void
@@ -204,12 +244,12 @@ BOOST_AUTO_TEST_CASE(railnet)
}
void
- shadows(const ShadowMapper &) const override
+ shadows(const ShadowMapper &, const Frustum &) const override
{
}
};
- ss.render(TestRail {});
+ renderer.render(TestRail {});
Texture::save(outImage, "/tmp/railnet.tga");
}
diff --git a/test/test-static-enumDetails.cpp b/test/test-static-enumDetails.cpp
index 5f81753..932a2af 100644
--- a/test/test-static-enumDetails.cpp
+++ b/test/test-static-enumDetails.cpp
@@ -3,76 +3,76 @@
#include <enumDetails.h>
// Test type name
-static_assert(EnumTypeDetails<GlobalUnscoped>::typeName == "GlobalUnscoped");
-static_assert(EnumTypeDetails<GlobalScoped>::typeName == "GlobalScoped");
-static_assert(EnumTypeDetails<ns::Unscoped>::typeName == "ns::Unscoped");
-static_assert(EnumTypeDetails<ns::Scoped>::typeName == "ns::Scoped");
+static_assert(EnumTypeDetails<GlobalUnscoped>::TYPE_NAME == "GlobalUnscoped");
+static_assert(EnumTypeDetails<GlobalScoped>::TYPE_NAME == "GlobalScoped");
+static_assert(EnumTypeDetails<ns::Unscoped>::TYPE_NAME == "ns::Unscoped");
+static_assert(EnumTypeDetails<ns::Scoped>::TYPE_NAME == "ns::Scoped");
-static_assert(EnumValueDetails<GlobalUnscoped::aa>::valueName == "aa");
-static_assert(EnumValueDetails<GlobalScoped::aa>::valueName == "aa");
-static_assert(EnumValueDetails<ns::Unscoped::aa>::valueName == "aa");
-static_assert(EnumValueDetails<ns::Scoped::aa>::valueName == "aa");
+static_assert(EnumValueDetails<GlobalUnscoped::Aa>::VALUE_NAME == "Aa");
+static_assert(EnumValueDetails<GlobalScoped::Aa>::VALUE_NAME == "Aa");
+static_assert(EnumValueDetails<ns::Unscoped::Aa>::VALUE_NAME == "Aa");
+static_assert(EnumValueDetails<ns::Scoped::Aa>::VALUE_NAME == "Aa");
namespace test1 {
- static_assert(EnumValueDetails<DefaultDense::a>::valid);
- static_assert(EnumValueDetails<DefaultDense::de>::valid);
- static_assert(EnumValueDetails<static_cast<DefaultDense>(0)>::valid);
- static_assert(EnumValueDetails<static_cast<DefaultDense>(3)>::valid);
- static_assert(!EnumValueDetails<static_cast<DefaultDense>(-1)>::valid);
- static_assert(!EnumValueDetails<static_cast<DefaultDense>(4)>::valid);
- static_assert(EnumValueDetails<DefaultDense::a>::valueName == "a");
- static_assert(EnumValueDetails<DefaultDense::de>::valueName == "de");
- using ED_DD = EnumDetails<DefaultDense>;
- static_assert(EnumValueCollection<DefaultDense>::Vs::size() == 256);
- static_assert(ED_DD::valid_flags.size() == 256);
- static_assert(ED_DD::values.size() == 4);
- static_assert(std::is_sorted(ED_DD::values.begin(), ED_DD::values.end()));
- static_assert(ED_DD::values.at(0) == DefaultDense::a);
- static_assert(ED_DD::values.at(3) == DefaultDense::de);
- static_assert(ED_DD::names.at(0) == "a");
- static_assert(ED_DD::names.at(3) == "de");
+ static_assert(EnumValueDetails<DefaultDense::A>::VALID);
+ static_assert(EnumValueDetails<DefaultDense::De>::VALID);
+ static_assert(EnumValueDetails<static_cast<DefaultDense>(0)>::VALID);
+ static_assert(EnumValueDetails<static_cast<DefaultDense>(3)>::VALID);
+ static_assert(!EnumValueDetails<static_cast<DefaultDense>(-1)>::VALID);
+ static_assert(!EnumValueDetails<static_cast<DefaultDense>(4)>::VALID);
+ static_assert(EnumValueDetails<DefaultDense::A>::VALUE_NAME == "A");
+ static_assert(EnumValueDetails<DefaultDense::De>::VALUE_NAME == "De");
+ using EdDd = EnumDetails<DefaultDense>;
+ static_assert(EnumValueCollection<DefaultDense>::Vs::size() == 127);
+ static_assert(EdDd::VALID_FLAGS.size() == 127);
+ static_assert(EdDd::VALUES.size() == 4);
+ static_assert(std::ranges::is_sorted(EdDd::VALUES));
+ static_assert(EdDd::VALUES.at(0) == DefaultDense::A);
+ static_assert(EdDd::VALUES.at(3) == DefaultDense::De);
+ static_assert(EdDd::NAMES.at(0) == "A");
+ static_assert(EdDd::NAMES.at(3) == "De");
- static_assert(ED_DD::is_valid(DefaultDense::a));
- static_assert(ED_DD::is_valid(DefaultDense::de));
- static_assert(!ED_DD::is_valid(DefaultDense(-1)));
- static_assert(!ED_DD::parse("").has_value());
- static_assert(!ED_DD::parse("nonsense").has_value());
- static_assert(ED_DD::parse("bee").value() == DefaultDense::bee);
- static_assert(ED_DD::parse("ci").value() == DefaultDense::ci);
- static_assert(ED_DD::to_string(DefaultDense::de).value() == "de");
- static_assert(!ED_DD::to_string(static_cast<DefaultDense>(10)).has_value());
+ static_assert(EdDd::isValid(DefaultDense::A));
+ static_assert(EdDd::isValid(DefaultDense::De));
+ static_assert(!EdDd::isValid(DefaultDense(-1)));
+ static_assert(!EdDd::parse("").has_value());
+ static_assert(!EdDd::parse("nonsense").has_value());
+ static_assert(EdDd::parse("Bee").value() == DefaultDense::Bee);
+ static_assert(EdDd::parse("Ci").value() == DefaultDense::Ci);
+ static_assert(EdDd::toString(DefaultDense::De).value() == "De");
+ static_assert(!EdDd::toString(static_cast<DefaultDense>(10)).has_value());
}
namespace test2 {
- static_assert(EnumValueDetails<NumberedSparse::bee>::valid);
- static_assert(EnumValueDetails<static_cast<NumberedSparse>(0)>::valid);
- static_assert(EnumValueDetails<static_cast<NumberedSparse>(3)>::valid);
- static_assert(EnumValueDetails<static_cast<NumberedSparse>(-20)>::valid);
- static_assert(EnumValueDetails<static_cast<NumberedSparse>(100)>::valid);
- static_assert(!EnumValueDetails<static_cast<NumberedSparse>(2)>::valid);
- static_assert(EnumValueDetails<NumberedSparse::a>::valueName == "a");
- static_assert(EnumValueDetails<NumberedSparse::de>::valueName == "de");
- using ED_NS = EnumDetails<NumberedSparse>;
+ static_assert(EnumValueDetails<NumberedSparse::Bee>::VALID);
+ static_assert(EnumValueDetails<static_cast<NumberedSparse>(0)>::VALID);
+ static_assert(EnumValueDetails<static_cast<NumberedSparse>(3)>::VALID);
+ static_assert(EnumValueDetails<static_cast<NumberedSparse>(-20)>::VALID);
+ static_assert(EnumValueDetails<static_cast<NumberedSparse>(100)>::VALID);
+ static_assert(!EnumValueDetails<static_cast<NumberedSparse>(2)>::VALID);
+ static_assert(EnumValueDetails<NumberedSparse::A>::VALUE_NAME == "A");
+ static_assert(EnumValueDetails<NumberedSparse::De>::VALUE_NAME == "De");
+ using EdNs = EnumDetails<NumberedSparse>;
static_assert(EnumValueCollection<NumberedSparse>::Vs::size() == 7);
- static_assert(ED_NS::values.size() == 4);
- static_assert(ED_NS::valid_flags.size() == 7);
- static_assert(std::is_sorted(ED_NS::values.begin(), ED_NS::values.end()));
- static_assert(ED_NS::values.at(0) == NumberedSparse::ci);
- static_assert(ED_NS::values.at(1) == NumberedSparse::a);
- static_assert(ED_NS::values.at(2) == NumberedSparse::bee);
- static_assert(ED_NS::values.at(3) == NumberedSparse::de);
- static_assert(ED_NS::names.at(0) == "ci");
- static_assert(ED_NS::names.at(1) == "a");
- static_assert(ED_NS::names.at(2) == "bee");
- static_assert(ED_NS::names.at(3) == "de");
+ static_assert(EdNs::VALUES.size() == 4);
+ static_assert(EdNs::VALID_FLAGS.size() == 7);
+ static_assert(std::ranges::is_sorted(EdNs::VALUES));
+ static_assert(EdNs::VALUES.at(0) == NumberedSparse::Ci);
+ static_assert(EdNs::VALUES.at(1) == NumberedSparse::A);
+ static_assert(EdNs::VALUES.at(2) == NumberedSparse::Bee);
+ static_assert(EdNs::VALUES.at(3) == NumberedSparse::De);
+ static_assert(EdNs::NAMES.at(0) == "Ci");
+ static_assert(EdNs::NAMES.at(1) == "A");
+ static_assert(EdNs::NAMES.at(2) == "Bee");
+ static_assert(EdNs::NAMES.at(3) == "De");
- static_assert(ED_NS::is_valid(NumberedSparse::a));
- static_assert(ED_NS::is_valid(NumberedSparse::de));
- static_assert(!ED_NS::is_valid(NumberedSparse(-1)));
- static_assert(!ED_NS::parse("").has_value());
- static_assert(!ED_NS::parse("nonsense").has_value());
- static_assert(ED_NS::parse("bee").value() == NumberedSparse::bee);
- static_assert(ED_NS::parse("ci").value() == NumberedSparse::ci);
- static_assert(ED_NS::to_string(NumberedSparse::ci).value() == "ci");
- static_assert(!ED_NS::to_string(static_cast<NumberedSparse>(10)).has_value());
+ static_assert(EdNs::isValid(NumberedSparse::A));
+ static_assert(EdNs::isValid(NumberedSparse::De));
+ static_assert(!EdNs::isValid(NumberedSparse(-1)));
+ static_assert(!EdNs::parse("").has_value());
+ static_assert(!EdNs::parse("nonsense").has_value());
+ static_assert(EdNs::parse("Bee").value() == NumberedSparse::Bee);
+ static_assert(EdNs::parse("Ci").value() == NumberedSparse::Ci);
+ static_assert(EdNs::toString(NumberedSparse::Ci).value() == "Ci");
+ static_assert(!EdNs::toString(static_cast<NumberedSparse>(10)).has_value());
}
diff --git a/test/test-static-stream_support.cpp b/test/test-static-stream_support.cpp
index 6bf9ea4..ec99f52 100644
--- a/test/test-static-stream_support.cpp
+++ b/test/test-static-stream_support.cpp
@@ -14,7 +14,7 @@ static_assert(NonStringIterableCollection<std::array<char, 1>>);
static_assert(!NonStringIterableCollection<std::string>);
static_assert(!NonStringIterableCollection<std::string_view>);
-static_assert(requires(std::vector<int> i, std::ostream & o) { o << i; });
-static_assert(requires(std::array<int, 10> i, std::ostream & o) { o << i; });
-static_assert(requires(std::set<int> i, std::ostream & o) { o << i; });
-static_assert(requires(std::map<int, int> i, std::ostream & o) { o << i; });
+static_assert(requires(std::vector<int> input, std::ostream & strm) { strm << input; });
+static_assert(requires(std::array<int, 10> input, std::ostream & strm) { strm << input; });
+static_assert(requires(std::set<int> input, std::ostream & strm) { strm << input; });
+static_assert(requires(std::map<int, int> input, std::ostream & strm) { strm << input; });
diff --git a/test/test-text.cpp b/test/test-text.cpp
index b0a9503..a68718e 100644
--- a/test/test-text.cpp
+++ b/test/test-text.cpp
@@ -6,8 +6,6 @@
#include <stream_support.h>
#include "testMainWindow.h"
-#include "testRenderOutput.h"
-#include "ui/text.h"
#include <array>
#include <gfx/models/texture.h>
#include <glm/glm.hpp>
@@ -17,16 +15,16 @@
BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
-BOOST_AUTO_TEST_CASE(utf8_string_view_iter)
+BOOST_AUTO_TEST_CASE(Utf8StringViewIter)
{
- static constexpr utf8_string_view text {"Some UTF-8 €£²¹ text."};
- static constexpr std::array codepoints {
+ static constexpr utf8_string_view TEXT {"Some UTF-8 €£²¹ text."};
+ static constexpr std::array CODEPOINTS {
83, 111, 109, 101, 32, 85, 84, 70, 45, 56, 32, 8364, 163, 178, 185, 32, 116, 101, 120, 116, 46};
- BOOST_CHECK_EQUAL(std::count_if(text.begin(), text.end(), isspace), 3);
- BOOST_CHECK_EQUAL(text.length(), 21);
+ BOOST_CHECK_EQUAL(std::count_if(TEXT.begin(), TEXT.end(), isspace), 3);
+ BOOST_CHECK_EQUAL(TEXT.length(), 21);
std::vector<uint32_t> codepointsOut;
- std::copy(text.begin(), text.end(), std::back_inserter(codepointsOut));
- BOOST_CHECK_EQUAL_COLLECTIONS(codepoints.begin(), codepoints.end(), codepointsOut.begin(), codepointsOut.end());
+ std::copy(TEXT.begin(), TEXT.end(), std::back_inserter(codepointsOut));
+ BOOST_CHECK_EQUAL_COLLECTIONS(CODEPOINTS.begin(), CODEPOINTS.end(), codepointsOut.begin(), codepointsOut.end());
}
struct FontTest : public Font {
@@ -37,10 +35,10 @@ BOOST_TEST_DONT_PRINT_LOG_VALUE(Font::CharData);
using TextureSizeTestData = std::tuple<unsigned, unsigned, unsigned>;
-BOOST_DATA_TEST_CASE(fontTextureSize, boost::unit_test::data::make<unsigned>({2, 3, 10, 50, 250}), fontHeight)
+BOOST_DATA_TEST_CASE(FontTextureSize, boost::unit_test::data::make<unsigned>({2, 3, 10, 50, 250}), fontHeight)
{
- auto isPowerOfTwo = [](auto x) {
- return (x & (x - 1)) == 0;
+ auto isPowerOfTwo = [](std::integral auto number) {
+ return (number & (number - 1)) == 0;
};
const auto res = Font::getTextureSize(fontHeight);
// Power of 2 dimensions...
@@ -58,7 +56,7 @@ BOOST_DATA_TEST_CASE(fontTextureSize, boost::unit_test::data::make<unsigned>({2,
BOOST_FIXTURE_TEST_SUITE(ft, FontTest);
-BOOST_AUTO_TEST_CASE(initialize_chardata)
+BOOST_AUTO_TEST_CASE(InitializeCharData)
{
BOOST_CHECK_GE(charsData.size(), 72);
BOOST_CHECK_EQUAL(fontTextures.size(), 2);
@@ -66,7 +64,7 @@ BOOST_AUTO_TEST_CASE(initialize_chardata)
using CharDataTest = std::tuple<decltype(get_codepoint(nullptr)), Font::CharData>;
-BOOST_DATA_TEST_CASE(initialize_chardata_A,
+BOOST_DATA_TEST_CASE(InitializeCharDataA,
boost::unit_test::data::make<CharDataTest>({
{'A', {0, {34, 35}, {712, 0}, {-1, 35}, 32}},
{'I', {0, {6, 35}, {947, 0}, {4, 35}, 13}},
@@ -75,74 +73,61 @@ BOOST_DATA_TEST_CASE(initialize_chardata_A,
}),
character, expected)
{
- const auto & cd = charsData.at(character);
- BOOST_CHECK_EQUAL(cd.textureIdx, expected.textureIdx);
- BOOST_CHECK_EQUAL(cd.size, expected.size);
- BOOST_CHECK_EQUAL(cd.position, expected.position);
- BOOST_CHECK_EQUAL(cd.bearing, expected.bearing);
- BOOST_CHECK_EQUAL(cd.advance, expected.advance);
+ const auto & charData = charsData.at(character);
+ BOOST_CHECK_EQUAL(charData.textureIdx, expected.textureIdx);
+ BOOST_CHECK_EQUAL(charData.size, expected.size);
+ BOOST_CHECK_EQUAL(charData.position, expected.position);
+ BOOST_CHECK_EQUAL(charData.bearing, expected.bearing);
+ BOOST_CHECK_EQUAL(charData.advance, expected.advance);
}
static_assert(glm::vec2 {862, 0} / glm::vec2 {2048, 64} == glm::vec2 {0.4208984375, 0});
static_assert(glm::vec2 {866, 35} / glm::vec2 {2048, 64} == glm::vec2 {0.4228515625, 0.546875});
-BOOST_AUTO_TEST_CASE(render_font)
+BOOST_AUTO_TEST_CASE(RenderFont)
{
- constexpr std::string_view text {"I Like Trains"};
- const auto spaces = static_cast<std::size_t>(std::count_if(text.begin(), text.end(), isspace));
- const auto tqs = render(text);
- BOOST_REQUIRE_EQUAL(tqs.size(), 1);
- const auto & t1 = tqs.begin();
- BOOST_CHECK_EQUAL(t1->first, fontTextures.front().texture);
- const auto & v = t1->second;
- BOOST_CHECK_EQUAL(v.size(), text.size() - spaces);
+ constexpr std::string_view TEXT {"I Like Trains"};
+ const auto spaces = static_cast<std::size_t>(std::ranges::count_if(TEXT, isspace));
+ const auto textureQuads = render(TEXT);
+ BOOST_REQUIRE_EQUAL(textureQuads.size(), 1);
+ const auto & textureQuad = textureQuads.begin();
+ BOOST_CHECK_EQUAL(textureQuad->first, fontTextures.front().texture);
+ const auto & vertices = textureQuad->second;
+ BOOST_CHECK_EQUAL(vertices.size(), TEXT.size() - spaces);
BOOST_TEST_CONTEXT(size) {
// I
- BOOST_CHECK_CLOSE_VEC(v[0][0], glm::vec4(4, 0, 0.42, 0.54));
- BOOST_CHECK_CLOSE_VEC(v[0][1], glm::vec4(10, 0, 0.42, 0.54));
- BOOST_CHECK_CLOSE_VEC(v[0][2], glm::vec4(10, 35, 0.42, 0));
- BOOST_CHECK_CLOSE_VEC(v[0][3], glm::vec4(4, 35, 0.42, 0));
+ BOOST_CHECK_CLOSE_VEC(vertices[0][0], glm::vec4(4, 0, 0.42, 0.54));
+ BOOST_CHECK_CLOSE_VEC(vertices[0][1], glm::vec4(10, 0, 0.42, 0.54));
+ BOOST_CHECK_CLOSE_VEC(vertices[0][2], glm::vec4(10, 35, 0.42, 0));
+ BOOST_CHECK_CLOSE_VEC(vertices[0][3], glm::vec4(4, 35, 0.42, 0));
// (space, no glyph)
// L
- BOOST_CHECK_CLOSE_VEC(v[1][0], glm::vec4(32, 0, 0.42, 0.54));
- BOOST_CHECK_CLOSE_VEC(v[1][1], glm::vec4(54, 0, 0.42, 0.54));
- BOOST_CHECK_CLOSE_VEC(v[1][2], glm::vec4(54, 35, 0.42, 0));
- BOOST_CHECK_CLOSE_VEC(v[1][3], glm::vec4(32, 35, 0.42, 0));
+ BOOST_CHECK_CLOSE_VEC(vertices[1][0], glm::vec4(32, 0, 0.42, 0.54));
+ BOOST_CHECK_CLOSE_VEC(vertices[1][1], glm::vec4(54, 0, 0.42, 0.54));
+ BOOST_CHECK_CLOSE_VEC(vertices[1][2], glm::vec4(54, 35, 0.42, 0));
+ BOOST_CHECK_CLOSE_VEC(vertices[1][3], glm::vec4(32, 35, 0.42, 0));
}
}
-BOOST_AUTO_TEST_CASE(render_text)
-{
- TestRenderOutput output;
- glBindFramebuffer(GL_FRAMEBUFFER, output.output);
- glViewport(0, 0, 640, 480);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- Text t {"I Like Trains", *this, {{10, 10}, {200, 40}}, {1, 1, 1}};
- UIShader s {640, 480};
- t.render(s, {});
- Texture::save(output.outImage, "/tmp/text.tga");
-}
-
BOOST_AUTO_TEST_SUITE_END()
-BOOST_AUTO_TEST_CASE(stream_vec)
+BOOST_AUTO_TEST_CASE(StreamVec)
{
BOOST_CHECK_EQUAL(streamed_string(glm::vec3 {1.2, 2.3, 3.4}), "(1.2, 2.3, 3.4)");
}
-BOOST_AUTO_TEST_CASE(stream_array)
+BOOST_AUTO_TEST_CASE(StreamArray)
{
BOOST_CHECK_EQUAL(streamed_string(std::array {1.2, 2.3, 3.4}), "(1.2, 2.3, 3.4)");
}
-BOOST_AUTO_TEST_CASE(stream_vector)
+BOOST_AUTO_TEST_CASE(StreamVector)
{
BOOST_CHECK_EQUAL(streamed_string(std::vector {1.2, 2.3, 3.4}), "(1.2, 2.3, 3.4)");
}
-BOOST_AUTO_TEST_CASE(stream_mat)
+BOOST_AUTO_TEST_CASE(StreamMat)
{
BOOST_CHECK_EQUAL(streamed_string(glm::mat2 {1.2, 2.3, 3.4, 4.5}), "((1.2, 2.3), (3.4, 4.5))");
}
diff --git a/test/test-ui.cpp b/test/test-ui.cpp
new file mode 100644
index 0000000..2810cda
--- /dev/null
+++ b/test/test-ui.cpp
@@ -0,0 +1,20 @@
+#define BOOST_TEST_MODULE UI
+#include <boost/test/unit_test.hpp>
+#include <stream_support.h>
+
+#include "testMainWindow.h"
+#include <gfx/models/texture.h>
+#include <resource.h>
+#include <ui/svgIcon.h>
+
+constexpr GLsizei RENDER_SIZE = 64;
+
+BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase);
+
+BOOST_AUTO_TEST_CASE(LoadFromFile)
+{
+ SvgIcon svg(ImageDimensions {RENDER_SIZE}, Resource::mapPath("ui/icon/rails.svg"));
+ const auto size = Texture::getSize(svg.texture);
+ BOOST_CHECK_EQUAL(size, TextureDimensions(RENDER_SIZE, RENDER_SIZE, 1));
+ Texture::save(svg.texture, "/tmp/rails.tga");
+}
diff --git a/test/test-worker.cpp b/test/test-worker.cpp
index cadc366..c259079 100644
--- a/test/test-worker.cpp
+++ b/test/test-worker.cpp
@@ -1,104 +1,101 @@
#define BOOST_TEST_MODULE test_worker
-#include "testHelpers.h"
#include <boost/test/unit_test.hpp>
#include <set>
#include <stream_support.h>
#include <worker.h>
-uint32_t
-workCounter()
-{
- static std::atomic_uint32_t n;
- usleep(1000);
- return n++;
-}
+namespace {
+ uint32_t
+ workCounter()
+ {
+ static std::atomic_uint32_t counter;
+ usleep(1000);
+ return counter++;
+ }
-void
-workVoid()
-{
- usleep(1000);
-}
+ void
+ workVoid()
+ {
+ usleep(1000);
+ }
-void
-workFail()
-{
- usleep(1000);
- throw std::runtime_error {"test"};
+ void
+ workFail()
+ {
+ usleep(1000);
+ throw std::runtime_error {"test"};
+ }
}
-BOOST_AUTO_TEST_CASE(basic_slow_counter)
+BOOST_AUTO_TEST_CASE(BasicSlowCounter)
{
- std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ std::vector<Worker::WorkPtrT<uint32_t>> jobs;
for (int i {}; i < 30; ++i) {
- ps.push_back(Worker::addWork(workCounter));
+ jobs.emplace_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());
+ std::ranges::transform(jobs, std::inserter(out, out.end()), &Worker::WorkItemT<uint32_t>::get);
+ BOOST_REQUIRE_EQUAL(out.size(), jobs.size());
BOOST_CHECK_EQUAL(*out.begin(), 0);
- BOOST_CHECK_EQUAL(*out.rbegin(), ps.size() - 1);
+ BOOST_CHECK_EQUAL(*out.rbegin(), jobs.size() - 1);
}
-BOOST_AUTO_TEST_CASE(basic_error_handler)
+BOOST_AUTO_TEST_CASE(BasicErrorHandler)
{
auto workitem = Worker::addWork(workFail);
BOOST_CHECK_THROW(workitem->get(), std::runtime_error);
}
-BOOST_AUTO_TEST_CASE(basic_void_work)
+BOOST_AUTO_TEST_CASE(BasicVoidWork)
{
auto workitem = Worker::addWork(workVoid);
BOOST_CHECK_NO_THROW(workitem->get());
}
-BOOST_AUTO_TEST_CASE(lambda_void)
+BOOST_AUTO_TEST_CASE(LambdaVoid)
{
- 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_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_AUTO_TEST_CASE(LambdaValue)
{
BOOST_CHECK_EQUAL(1, Worker::addWork([]() {
return 1;
})->get());
BOOST_CHECK_EQUAL(2,
Worker::addWork(
- [](int i) {
- return i;
+ [](int value) {
+ return value;
},
2)
->get());
BOOST_CHECK_EQUAL(3,
Worker::addWork(
- [](int i, int j) {
- return i + j;
+ [](int valueA, int valueB) {
+ return valueA + valueB;
},
1, 2)
->get());
}
-BOOST_AUTO_TEST_CASE(recursive, *boost::unit_test::timeout(5))
+BOOST_AUTO_TEST_CASE(Recursive, *boost::unit_test::timeout(5))
{
auto recurse = []() {
- std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ std::vector<Worker::WorkPtrT<uint32_t>> jobs;
for (int i {}; i < 30; ++i) {
- ps.push_back(Worker::addWork(workCounter));
+ jobs.emplace_back(Worker::addWork(workCounter));
}
- return std::accumulate(ps.begin(), ps.end(), 0U, [](auto && out, auto && p) {
- return out += p->get();
+ return std::ranges::fold_left(jobs, 0U, [](auto && out, auto && job) {
+ return out += job->get();
});
};
- std::vector<Worker::WorkPtrT<uint32_t>> ps;
+ std::vector<Worker::WorkPtrT<uint32_t>> jobs;
for (int i {}; i < 30; ++i) {
- ps.push_back(Worker::addWork(recurse));
+ jobs.emplace_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();
- });
+ std::ranges::transform(jobs, std::inserter(out, out.end()), &Worker::WorkItemT<uint32_t>::get);
}
diff --git a/test/testHelpers.cpp b/test/testHelpers.cpp
new file mode 100644
index 0000000..7d78cd2
--- /dev/null
+++ b/test/testHelpers.cpp
@@ -0,0 +1,6 @@
+#include "testHelpers.h"
+
+const std::filesystem::path ANALYSIS_DIRECTORY = []() {
+ auto xdgRuntimeDir = getenv("XDG_RUNTIME_DIR");
+ return std::filesystem::path {xdgRuntimeDir ? xdgRuntimeDir : "/tmp"} / "ilt-output";
+}();
diff --git a/test/testHelpers.h b/test/testHelpers.h
index a261b3d..e1fed97 100644
--- a/test/testHelpers.h
+++ b/test/testHelpers.h
@@ -11,34 +11,36 @@ template<typename T>
decltype(auto)
loadFixtureJson(const std::filesystem::path & path)
{
- std::ifstream in {FIXTURESDIR / path};
- return Persistence::JsonParsePersistence {}.loadState<std::vector<T>>(in);
+ std::ifstream inputStream {FIXTURESDIR / path};
+ return Persistence::JsonParsePersistence {}.loadState<std::vector<T>>(inputStream);
}
+extern const std::filesystem::path ANALYSIS_DIRECTORY;
+
#define BOOST_CHECK_CLOSE_VEC(a_, b_) \
{ \
- const auto a {a_}, b {b_}; \
- BOOST_TEST_CONTEXT("BOOST_CHECK_CLOSE_VEC(" << std::setprecision(8) << a << ", " << b << ")") { \
- BOOST_CHECK_LT(glm::length(a - b), 0.1F); \
+ const auto left_ {a_}, right_ {b_}; \
+ BOOST_TEST_CONTEXT("BOOST_CHECK_CLOSE_VEC(" << std::setprecision(8) << left_ << ", " << right_ << ")") { \
+ BOOST_CHECK_LT(glm::length(left_ - right_), 0.1F); \
} \
}
#define BOOST_CHECK_CLOSE_VECI(a_, b_) \
{ \
- const auto a {a_}, b {b_}; \
- BOOST_TEST_CONTEXT("BOOST_CHECK_CLOSE_VEC(" << std::setprecision(8) << a << ", " << b << ")") { \
- BOOST_CHECK_LE(std::abs(a.x - b.x), 1); \
- BOOST_CHECK_LE(std::abs(a.y - b.y), 1); \
- BOOST_CHECK_LE(std::abs(a.z - b.z), 1); \
+ const auto left_ {a_}, right_ {b_}; \
+ BOOST_TEST_CONTEXT("BOOST_CHECK_CLOSE_VEC(" << std::setprecision(8) << left_ << ", " << right_ << ")") { \
+ BOOST_CHECK_LE(std::abs(left_.x - right_.x), 1); \
+ BOOST_CHECK_LE(std::abs(left_.y - right_.y), 1); \
+ BOOST_CHECK_LE(std::abs(left_.z - right_.z), 1); \
} \
}
#define BOOST_CHECK_BETWEEN(a_, b_, c_) \
{ \
- const auto a {a_}, b {b_}, c {c_}; \
- BOOST_TEST_CONTEXT("BOOST_CHECK_BETWEEN(" << a << ", " << b << ", " << c << ")") { \
- BOOST_CHECK_LE(b, a); \
- BOOST_CHECK_GE(c, a); \
+ const auto value_ {a_}, min_ {b_}, max_ {c_}; \
+ BOOST_TEST_CONTEXT("BOOST_CHECK_BETWEEN(" << min_ << " <= " << value_ << " <= " << max_ << ")") { \
+ BOOST_CHECK_LE(min_, value_); \
+ BOOST_CHECK_GE(max_, value_); \
} \
}
#define BOOST_REQUIRE_THEN(VAR, EXPR) \
diff --git a/test/testMainWindow.cpp b/test/testMainWindow.cpp
index 4a76044..d048682 100644
--- a/test/testMainWindow.cpp
+++ b/test/testMainWindow.cpp
@@ -2,7 +2,7 @@
#include <boost/test/test_tools.hpp>
#include <format>
-TestMainWindow::TestMainWindow() : MainWindow {1, 1, __FILE__, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN}
+TestMainWindow::TestMainWindow() : MainWindow {{1, 1}, __FILE__, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN}
{
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
diff --git a/test/testRenderOutput.cpp b/test/testRenderOutput.cpp
index 68b46f6..2c74ceb 100644
--- a/test/testRenderOutput.cpp
+++ b/test/testRenderOutput.cpp
@@ -2,7 +2,7 @@
#include <gl_traits.h>
#include <stdexcept>
-TestRenderOutput::TestRenderOutput(TextureAbsCoord s) : size {s}
+TestRenderOutput::TestRenderOutput(TextureAbsCoord outputSize) : size {outputSize}
{
glBindFramebuffer(GL_FRAMEBUFFER, output);
const auto configuregdata
diff --git a/test/testRenderOutput.h b/test/testRenderOutput.h
index 056d029..79908b1 100644
--- a/test/testRenderOutput.h
+++ b/test/testRenderOutput.h
@@ -1,6 +1,7 @@
#pragma once
#include "config/types.h"
+#include "game/gamestate.h"
#include "glArrays.h"
#include <glm/vec2.hpp>
#include <special_members.h>
@@ -17,6 +18,7 @@ public:
glFrameBuffer output;
glRenderBuffer depth;
glTexture outImage;
+ GameState gameState;
};
template<TextureAbsCoord Size> class TestRenderOutputSize : public TestRenderOutput {
diff --git a/test/testStructures.h b/test/testStructures.h
index 4eb4764..61bef68 100644
--- a/test/testStructures.h
+++ b/test/testStructures.h
@@ -30,7 +30,7 @@ struct TestObject : public Persistence::Persistable {
TestObject() = default;
float flt {};
- std::string str {};
+ std::string str;
bool bl {};
RelativePosition3D pos {};
GlobalPosition3D gpos {};
diff --git a/thirdparty/Jamfile.jam b/thirdparty/Jamfile.jam
index 7a47589..26497c9 100644
--- a/thirdparty/Jamfile.jam
+++ b/thirdparty/Jamfile.jam
@@ -14,12 +14,13 @@ lib stb : stb_image.c :
;
lib imguisdl2 :
- [ glob imgui/imgui*.cpp : imgui/imgui_demo.cpp ]
+ [ glob imgui/imgui*.cpp imgui/misc/cpp/*.cpp : imgui/imgui_demo.cpp ]
imgui/backends/imgui_impl_sdl2.cpp
imgui/backends/imgui_impl_opengl3.cpp
:
<link>static
<include>imgui
+ <include>imgui/misc/cpp
<use>..//sdl2
<cflags>-fPIC
<warnings>off
@@ -27,3 +28,15 @@ lib imguisdl2 :
: :
<include>imgui
;
+
+lib lunasvg :
+ [ glob lunasvg/source/*.cpp lunasvg/plutovg/source/*.c ]
+ :
+ <link>static
+ <include>lunasvg/include
+ <include>lunasvg/plutovg/include
+ <warnings>off
+ <cflags>-fPIC
+ : :
+ <include>lunasvg/include
+ ;
diff --git a/thirdparty/ctre b/thirdparty/ctre
-Subproject b3d7788b559e34d985c8530c3e0e7260b67505a
+Subproject acb2f4de2e24a06280088377e47534137c0bc75
diff --git a/thirdparty/imgui b/thirdparty/imgui
-Subproject 2db79d0868f7b02d26f7557a72504a0b6f84493
+Subproject b4c96355c9b51b54c4deb52e7d7cdfc7bf79bc2
diff --git a/thirdparty/lunasvg b/thirdparty/lunasvg
new file mode 160000
+Subproject f8aabfb444bb37f69df7290790f57e4a27730a9
diff --git a/thirdparty/openmesh/helpers.h b/thirdparty/openmesh/helpers.h
new file mode 100644
index 0000000..0e29261
--- /dev/null
+++ b/thirdparty/openmesh/helpers.h
@@ -0,0 +1,60 @@
+#pragma once
+#include <OpenMesh/Core/Mesh/BaseKernel.hh>
+#include <OpenMesh/Core/Mesh/PolyConnectivity.hh>
+#include <ranges>
+
+namespace OpenMesh {
+ template<typename Iter, typename... IterParams>
+ using IteratorFunction = Iter (OpenMesh::PolyConnectivity::*)(IterParams...) const;
+
+#if OM_GET_VER < 8
+ template<typename Iter, typename CenterEntityHandle, IteratorFunction<Iter, CenterEntityHandle> BeginFunc,
+ IteratorFunction<Iter, CenterEntityHandle> EndFunc, typename Adaptor>
+ auto
+ operator|(const OpenMesh::PolyConnectivity::CirculatorRange<OpenMesh::PolyConnectivity, Iter, CenterEntityHandle,
+ BeginFunc, EndFunc> & range,
+ Adaptor && adaptor)
+ {
+ return std::views::iota(range.begin(), range.end()) | std::forward<Adaptor>(adaptor);
+ }
+
+ template<typename Iter, IteratorFunction<Iter> BeginFunc, IteratorFunction<Iter> EndFunc, typename Adaptor>
+ auto
+ operator|(
+ const OpenMesh::PolyConnectivity::EntityRange<const OpenMesh::PolyConnectivity, Iter, BeginFunc, EndFunc> &
+ range,
+ Adaptor && adaptor)
+ {
+ return std::views::iota(range.begin(), range.end()) | std::forward<Adaptor>(adaptor);
+ }
+#else
+ template<typename Iter, typename CenterEntityHandle, typename ToEntityHandle,
+ IteratorFunction<Iter, CenterEntityHandle> BeginFunc, IteratorFunction<Iter, CenterEntityHandle> EndFunc,
+ typename Adaptor>
+ auto
+ operator|(const CirculatorRange<CirculatorRangeTraitT<OpenMesh::PolyConnectivity, Iter, CenterEntityHandle,
+ ToEntityHandle, BeginFunc, EndFunc>> & range,
+ Adaptor && adaptor)
+ {
+ return std::views::iota(range.begin(), range.end()) | std::forward<Adaptor>(adaptor);
+ }
+
+ template<typename Iter, IteratorFunction<Iter> BeginFunc, IteratorFunction<Iter> EndFunc, typename Adaptor>
+ auto
+ operator|(const EntityRange<RangeTraitT<const OpenMesh::PolyConnectivity, Iter, BeginFunc, EndFunc>> & range,
+ Adaptor && adaptor)
+ {
+ return std::views::iota(range.begin(), range.end()) | std::forward<Adaptor>(adaptor);
+ }
+
+#endif
+
+ namespace Helpers {
+ template<typename Type, template<typename> typename PropertyT> struct Property : public PropertyT<Type> {
+ template<typename... Params> explicit Property(OpenMesh::BaseKernel * kernel, Params &&... params)
+ {
+ kernel->add_property(*this, std::forward<Params>(params)...);
+ }
+ };
+ }
+}
diff --git a/ui/builders/freeExtend.cpp b/ui/builders/freeExtend.cpp
index db127e6..aff7cd7 100644
--- a/ui/builders/freeExtend.cpp
+++ b/ui/builders/freeExtend.cpp
@@ -16,17 +16,17 @@ BuilderFreeExtend::move(
{
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- candidateLinks.objects = network->candidateJoins(*p1, p->pos);
+ candidateLinks = network->candidateJoins(*p1, p->pos);
}
else if (const auto p = geoData->intersectRay(ray)) {
- candidateLinks.objects = network->candidateExtend(*p1, p->first);
+ candidateLinks = network->candidateExtend(*p1, p->first);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
@@ -38,11 +38,11 @@ BuilderFreeExtend::click(
case SDL_BUTTON_LEFT:
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- network->addJoins(*p1, p->pos);
+ createJoin(network, geoData, *p1, p->pos);
p1 = p->pos;
}
else if (const auto p = geoData->intersectRay(ray)) {
- network->addExtend(*p1, p->first);
+ createExtend(network, geoData, *p1, p->first);
p1 = p->first;
}
}
@@ -57,3 +57,21 @@ BuilderFreeExtend::click(
return;
}
}
+
+Link::CCollection
+BuilderFreeExtend::createJoin(
+ Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
+{
+ const auto links = network->addJoins(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
+}
+
+Link::CCollection
+BuilderFreeExtend::createExtend(
+ Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
+{
+ const auto links = network->addExtend(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
+}
diff --git a/ui/builders/freeExtend.h b/ui/builders/freeExtend.h
index 0d5f327..6f28493 100644
--- a/ui/builders/freeExtend.h
+++ b/ui/builders/freeExtend.h
@@ -5,11 +5,17 @@ class Network;
class GeoData;
class BuilderFreeExtend : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
+public:
+ Link::CCollection createJoin(Network * network, const GeoData *, GlobalPosition3D, GlobalPosition3D) const;
+ Link::CCollection createExtend(Network * network, const GeoData *, GlobalPosition3D, GlobalPosition3D) const;
+
+private:
std::optional<GlobalPosition3D> p1;
};
diff --git a/ui/builders/join.cpp b/ui/builders/join.cpp
index 7474c5b..f6cbce5 100644
--- a/ui/builders/join.cpp
+++ b/ui/builders/join.cpp
@@ -15,25 +15,25 @@ BuilderJoin::move(Network * network, const GeoData *, const SDL_MouseMotionEvent
{
if (p1) {
if (const auto p = network->intersectRayNodes(ray)) {
- candidateLinks.objects = network->candidateJoins(p1->pos, p->pos);
+ candidateLinks = network->candidateJoins(p1->pos, p->pos);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
}
void
BuilderJoin::click(
- Network * network, const GeoData *, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
+ Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
{
switch (e.button) {
case SDL_BUTTON_LEFT:
if (const auto p = network->intersectRayNodes(ray)) {
if (p1) {
- create(network, p1, p);
+ create(network, geoData, p1, p);
p1.reset();
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
else {
p1 = p;
@@ -46,8 +46,10 @@ BuilderJoin::click(
}
}
-void
-BuilderJoin::create(Network * network, const Node::Ptr & p1, const Node::Ptr & p2) const
+Link::CCollection
+BuilderJoin::create(Network * network, const GeoData * geoData, const Node::Ptr & p1, const Node::Ptr & p2) const
{
- network->addJoins(p1->pos, p2->pos);
+ const auto links = network->addJoins(geoData, p1->pos, p2->pos);
+ setHeightsFor(network, links);
+ return links;
}
diff --git a/ui/builders/join.h b/ui/builders/join.h
index dd57895..326d23d 100644
--- a/ui/builders/join.h
+++ b/ui/builders/join.h
@@ -5,13 +5,14 @@ class Network;
class GeoData;
class BuilderJoin : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
- void create(Network * network, const Node::Ptr & p1, const Node::Ptr & p2) const;
+ Link::CCollection create(Network * network, const GeoData *, const Node::Ptr & p1, const Node::Ptr & p2) const;
Node::Ptr p1;
};
diff --git a/ui/builders/straight.cpp b/ui/builders/straight.cpp
index 43f5ec8..e7d83b5 100644
--- a/ui/builders/straight.cpp
+++ b/ui/builders/straight.cpp
@@ -1,4 +1,5 @@
#include "straight.h"
+#include "stream_support.h"
#include <game/geoData.h>
std::string
@@ -16,10 +17,10 @@ BuilderStraight::move(
{
if (p1) {
if (const auto p = geoData->intersectRay(ray)) {
- candidateLinks.objects = network->candidateStraight(*p1, p->first);
+ candidateLinks = network->candidateStraight(*p1, p->first);
}
else {
- candidateLinks.removeAll();
+ candidateLinks.clear();
}
}
}
@@ -32,8 +33,8 @@ BuilderStraight::click(
case SDL_BUTTON_LEFT:
if (const auto p = geoData->intersectRay(ray)) {
if (p1) {
- create(network, *p1, p->first);
- candidateLinks.removeAll();
+ create(network, geoData, *p1, p->first);
+ candidateLinks.clear();
p1.reset();
}
else {
@@ -43,13 +44,15 @@ BuilderStraight::click(
return;
case SDL_BUTTON_MIDDLE:
p1.reset();
- candidateLinks.removeAll();
+ candidateLinks.clear();
return;
}
}
-void
-BuilderStraight::create(Network * network, GlobalPosition3D p1, GlobalPosition3D p2) const
+Link::CCollection
+BuilderStraight::create(Network * network, const GeoData * geoData, GlobalPosition3D p1, GlobalPosition3D p2) const
{
- network->addStraight(p1, p2);
+ const auto links = network->addStraight(geoData, p1, p2);
+ setHeightsFor(network, links);
+ return links;
}
diff --git a/ui/builders/straight.h b/ui/builders/straight.h
index 28eb66e..0a6f290 100644
--- a/ui/builders/straight.h
+++ b/ui/builders/straight.h
@@ -5,13 +5,16 @@ class Network;
class GeoData;
class BuilderStraight : public EditNetwork::Builder {
+private:
std::string hint() const override;
void click(Network * network, const GeoData * geoData, const SDL_MouseButtonEvent & e,
const Ray<GlobalPosition3D> & ray) override;
void move(Network * network, const GeoData * geoData, const SDL_MouseMotionEvent & e,
const Ray<GlobalPosition3D> & ray) override;
- void create(Network * network, GlobalPosition3D p1, GlobalPosition3D p2) const;
+public:
+ Link::CCollection create(Network * network, const GeoData *, GlobalPosition3D p1, GlobalPosition3D p2) const;
+private:
std::optional<GlobalPosition3D> p1;
};
diff --git a/ui/editNetwork.cpp b/ui/editNetwork.cpp
index ac2d93d..c900191 100644
--- a/ui/editNetwork.cpp
+++ b/ui/editNetwork.cpp
@@ -2,31 +2,21 @@
#include "builders/freeExtend.h"
#include "builders/join.h"
#include "builders/straight.h"
-#include "text.h"
+#include "imgui_wrap.h"
#include <game/gamestate.h>
-#include <game/geoData.h>
+#include <game/terrain.h>
#include <gfx/gl/sceneShader.h>
#include <gfx/models/texture.h>
-const std::filesystem::path fontpath {"/usr/share/fonts/hack/Hack-Regular.ttf"};
constexpr const glm::u8vec4 TRANSPARENT_BLUE {30, 50, 255, 200};
-EditNetwork::EditNetwork(Network * n) :
- network {n},
- builderToolbar {
- {"ui/icon/network.png", mode.toggle<BuilderStraight>()},
- {"ui/icon/network.png", mode.toggle<BuilderJoin>()},
- {"ui/icon/network.png", mode.toggle<BuilderFreeExtend>()},
- },
- blue {1, 1, &TRANSPARENT_BLUE}, font {fontpath, 15}
-{
-}
+EditNetwork::EditNetwork(Network * n) : network {n}, blue {1, 1, &TRANSPARENT_BLUE} { }
bool
EditNetwork::click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> & ray)
{
if (builder && (e.button == SDL_BUTTON_LEFT || e.button == SDL_BUTTON_MIDDLE)) {
- builder->click(network, gameState->geoData.get(), e, ray);
+ builder->click(network, gameState->terrain.get(), e, ray);
return true;
}
return false;
@@ -36,38 +26,59 @@ bool
EditNetwork::move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> & ray)
{
if (builder) {
- builder->move(network, gameState->geoData.get(), e, ray);
+ builder->move(network, gameState->terrain.get(), e, ray);
}
return false;
}
bool
-EditNetwork::handleInput(const SDL_Event & e, const UIComponent::Position & parentPos)
+EditNetwork::handleInput(const SDL_Event &)
{
- return builderToolbar.handleInput(e, parentPos);
+ return false;
}
void
-EditNetwork::render(const SceneShader & shader) const
+EditNetwork::render(const SceneShader & shader, const Frustum & frustum) const
{
if (builder) {
blue.bind();
shader.absolute.use();
- builder->render(shader);
+ builder->render(shader, frustum);
}
}
void
-EditNetwork::Builder::render(const SceneShader & shader) const
+EditNetwork::Builder::render(const SceneShader & shader, const Frustum & frustum) const
{
- candidateLinks.apply<const Renderable>(&Renderable::render, shader);
+ candidateLinks.apply<const Renderable>(&Renderable::render, shader, frustum);
}
void
-EditNetwork::render(const UIShader & shader, const UIComponent::Position & parentPos) const
+EditNetwork::Builder::setHeightsFor(Network * network, const Link::CCollection & links, GeoData::SetHeightsOpts opts)
{
- if (builder) {
- Text {builder->hint(), font, {{50, 10}, {0, 15}}, {1, 1, 0}}.render(shader, parentPos);
+ opts.surface = network->getBaseSurface();
+ const auto width = network->getBaseWidth();
+
+ for (const auto & link : links) {
+ gameState->terrain->setHeights(link->getBase(width), opts);
}
- builderToolbar.render(shader, parentPos);
+}
+
+void
+EditNetwork::render(bool & open)
+{
+ ImGui::SetNextWindowSize({-1, -1});
+ ImGui::Begin("Edit Network", &open);
+
+ auto builderChoice = [this]<typename Impl>(const char * name) {
+ if (ImGui::RadioButton(name, dynamic_cast<Impl *>(builder.get()))) {
+ builder = std::make_unique<Impl>();
+ }
+ };
+ builderChoice.operator()<BuilderStraight>("Straight");
+ builderChoice.operator()<BuilderJoin>("Join");
+ builderChoice.operator()<BuilderFreeExtend>("Free Extend");
+ ImGui::TextUnformatted(builder ? builder->hint().c_str() : "Select a build mode");
+
+ ImGui::End();
}
diff --git a/ui/editNetwork.h b/ui/editNetwork.h
index ec06fa7..4155534 100644
--- a/ui/editNetwork.h
+++ b/ui/editNetwork.h
@@ -1,8 +1,7 @@
#pragma once
+#include "game/geoData.h"
#include "gameMainSelector.h"
-#include "modeHelper.h"
-#include "toolbar.h"
#include "worldOverlay.h"
#include <game/gamestate.h>
#include <game/network/network.h>
@@ -16,33 +15,32 @@ public:
bool click(const SDL_MouseButtonEvent & e, const Ray<GlobalPosition3D> &) override;
bool move(const SDL_MouseMotionEvent & e, const Ray<GlobalPosition3D> &) override;
- bool handleInput(const SDL_Event & e, const UIComponent::Position &) override;
- void render(const SceneShader &) const override;
- void render(const UIShader & shader, const UIComponent::Position & pos) const override;
+ bool handleInput(const SDL_Event & e) override;
+ void render(const SceneShader &, const Frustum &) const override;
+ void render(bool & open) override;
using NetworkClickPos = std::variant<GlobalPosition3D, Node::Ptr>;
class Builder {
public:
virtual ~Builder() = default;
- virtual void render(const SceneShader & shader) const;
+ virtual void render(const SceneShader & shader, const Frustum &) const;
virtual std::string hint() const = 0;
virtual void click(Network *, const GeoData *, const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) = 0;
virtual void move(Network *, const GeoData *, const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &) = 0;
+ static void setHeightsFor(Network *, const Link::CCollection &, GeoData::SetHeightsOpts = {});
+
using Ptr = std::unique_ptr<Builder>;
protected:
- Collection<const Link> candidateLinks;
+ SharedCollection<const Link> candidateLinks;
};
private:
Network * network;
Builder::Ptr builder;
- Mode<Builder::Ptr, ModeSecondClick::NoAction> mode {builder};
- Toolbar builderToolbar;
Texture blue;
- const Font font;
};
template<typename T> class EditNetworkOf : public EditNetwork {
diff --git a/ui/gameMainSelector.cpp b/ui/gameMainSelector.cpp
index 5bef48d..0c40abc 100644
--- a/ui/gameMainSelector.cpp
+++ b/ui/gameMainSelector.cpp
@@ -1,50 +1,44 @@
#include "gameMainSelector.h"
-#include "collection.h"
-#include "text.h"
#include "ui/uiComponent.h"
#include <SDL2/SDL.h>
#include <game/gamestate.h>
-#include <game/geoData.h>
#include <game/selectable.h>
+#include <game/terrain.h>
#include <game/worldobject.h> // IWYU pragma: keep
-#include <gfx/gl/camera.h>
-#include <optional>
+#include <gfx/camera.h>
#include <stream_support.h>
-#include <typeinfo>
-const std::filesystem::path fontpath {"/usr/share/fonts/hack/Hack-Regular.ttf"};
-
-GameMainSelector::GameMainSelector(const Camera * c, ScreenAbsCoord size) :
- UIComponent {{{}, size}}, camera {c}, font {fontpath, 15}
-{
-}
+GameMainSelector::GameMainSelector(const Camera * c) : camera {c} { }
constexpr ScreenAbsCoord TargetPos {5, 45};
void
-GameMainSelector::render(const UIShader & shader, const Position & parentPos) const
+GameMainSelector::render()
{
if (target) {
- target->render(shader, parentPos + position + TargetPos);
- }
- if (!clicked.empty()) {
- Text {clicked, font, {{50, 10}, {0, 15}}, {1, 1, 0}}.render(shader, parentPos);
+ bool open = true;
+ target->render(open);
+ if (!open) {
+ target.reset();
+ }
}
}
void
-GameMainSelector::render(const SceneShader & shader) const
+GameMainSelector::render(const SceneShader & shader, const Frustum & frustum) const
{
if (target) {
- target->render(shader);
+ target->render(shader, frustum);
}
}
bool
-GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
+GameMainSelector::handleInput(const SDL_Event & e)
{
- const auto getRay = [this](const auto & e) {
- const auto mouse = ScreenRelCoord {e.x, e.y} / position.size;
+ const auto getRay = [this, &window = e.window](const auto & e) {
+ glm::ivec2 size {};
+ SDL_GetWindowSizeInPixels(SDL_GetWindowFromID(window.windowID), &size.x, &size.y);
+ const auto mouse = ScreenRelCoord {e.x, e.y} / ScreenRelCoord {size};
return camera->unProject(mouse);
};
if (target) {
@@ -60,7 +54,7 @@ GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
}
break;
}
- return target->handleInput(e, parentPos + position + TargetPos);
+ return target->handleInput(e);
}
else {
switch (e.type) {
@@ -73,22 +67,8 @@ GameMainSelector::handleInput(const SDL_Event & e, const Position & parentPos)
}
void
-GameMainSelector::defaultClick(const Ray<GlobalPosition3D> & ray)
+GameMainSelector::defaultClick(const Ray<GlobalPosition3D> &)
{
- BaryPosition baryPos {};
- RelativeDistance distance {};
-
- if (const auto selected = gameState->world.applyOne<Selectable>(&Selectable::intersectRay, ray, baryPos, distance);
- selected != gameState->world.end()) {
- const auto & ref = *selected.base()->get();
- clicked = typeid(ref).name();
- }
- else if (const auto pos = gameState->geoData->intersectRay(ray)) {
- clicked = streamed_string(*pos);
- }
- else {
- clicked.clear();
- }
}
bool
@@ -104,17 +84,17 @@ GameMainSelector::Component::move(const SDL_MouseMotionEvent &, const Ray<Global
}
bool
-GameMainSelector::Component::handleInput(const SDL_Event &, const Position &)
+GameMainSelector::Component::handleInput(const SDL_Event &)
{
return false;
}
void
-GameMainSelector::Component::render(const UIShader &, const UIComponent::Position &) const
+GameMainSelector::Component::render(bool &)
{
}
void
-GameMainSelector::Component::render(const SceneShader &) const
+GameMainSelector::Component::render(const SceneShader &, const Frustum &) const
{
}
diff --git a/ui/gameMainSelector.h b/ui/gameMainSelector.h
index ccf0fa0..8c2be4b 100644
--- a/ui/gameMainSelector.h
+++ b/ui/gameMainSelector.h
@@ -2,16 +2,13 @@
#include "SDL_events.h"
#include "config/types.h"
-#include "font.h"
#include "uiComponent.h"
#include "worldOverlay.h"
#include <glm/glm.hpp>
#include <memory>
-#include <string>
class SceneShader;
template<typename> class Ray;
-class UIShader;
class Camera;
class GameMainSelector : public UIComponent, public WorldOverlay {
@@ -22,17 +19,17 @@ public:
virtual bool click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &);
virtual bool move(const SDL_MouseMotionEvent &, const Ray<GlobalPosition3D> &);
- virtual bool handleInput(const SDL_Event &, const Position & pos);
- virtual void render(const UIShader & shader, const Position & pos) const;
- virtual void render(const SceneShader &) const;
+ virtual bool handleInput(const SDL_Event &);
+ virtual void render(bool & open);
+ virtual void render(const SceneShader &, const Frustum &) const;
};
- GameMainSelector(const Camera * c, ScreenAbsCoord size);
+ GameMainSelector(const Camera * c);
- void render(const UIShader & shader, const Position & pos) const override;
- void render(const SceneShader & shader) const override;
+ void render() override;
+ void render(const SceneShader & shader, const Frustum &) const override;
- bool handleInput(const SDL_Event & e, const Position &) override;
+ bool handleInput(const SDL_Event & e) override;
void defaultClick(const Ray<GlobalPosition3D> & ray);
@@ -40,6 +37,4 @@ public:
private:
const Camera * camera;
- const Font font;
- std::string clicked;
};
diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp
index 6168504..dbbf8a7 100644
--- a/ui/gameMainWindow.cpp
+++ b/ui/gameMainWindow.cpp
@@ -1,89 +1,128 @@
#include "gameMainWindow.h"
#include "editNetwork.h"
#include "gameMainSelector.h"
-#include "gfx/camera_controller.h"
+#include "imgui_extras.h"
#include "manualCameraController.h"
-#include "modeHelper.h"
-#include "toolbar.h"
-#include "window.h"
+#include "queryTool.h"
+#include "svgIcon.h"
#include <SDL2/SDL.h>
#include <collection.h>
+#include <game/environment.h>
#include <game/gamestate.h>
#include <game/network/rail.h>
#include <game/worldobject.h> // IWYU pragma: keep
+#include <gfx/camera_controller.h>
#include <gfx/renderable.h>
#include <glad/gl.h>
#include <glm/glm.hpp>
#include <memory>
-class GameMainToolbar : Mode<decltype(GameMainSelector::target)>, public Toolbar {
+class GameMainToolbar : public UIComponent {
public:
- explicit GameMainToolbar(GameMainSelector * gms_) :
- Mode<decltype(GameMainSelector::target)> {gms_->target},
- Toolbar {
- {"ui/icon/network.png", toggle<EditNetworkOf<RailLinks>>()},
+ static constexpr auto TOOLBAR_HEIGHT = 54.F;
+ template<typename T> static constexpr T TOOLBAR_ICON_SIZE {32, 32};
+
+ explicit GameMainToolbar(GameMainSelector * gms) : gms {gms} { }
+
+ void
+ render() override
+ {
+ if (IltGui::BeginToolbar("bottomBar", ImGuiDir_Down, TOOLBAR_HEIGHT)) {
+ if (ImGui::ImageButton("Build rails", *buildRailsIcon, TOOLBAR_ICON_SIZE<ImVec2>)) {
+ gms->target = std::make_unique<EditNetworkOf<RailLinks>>();
+ }
+ if (ImGui::ImageButton("Query", *queryToolIcon, TOOLBAR_ICON_SIZE<ImVec2>)) {
+ gms->target = std::make_unique<QueryTool>();
+ }
+ IltGui::EndToolbar();
}
+ }
+
+ bool
+ handleInput(const SDL_Event &) override
{
+ return false;
}
+
+private:
+ SvgIcon buildRailsIcon {TOOLBAR_ICON_SIZE<ImageDimensions>, "ui/icon/rails.svg"};
+ SvgIcon queryToolIcon {TOOLBAR_ICON_SIZE<ImageDimensions>, "ui/icon/magnifier.svg"};
+ GameMainSelector * gms;
};
-GameMainWindow::GameMainWindow(size_t w, size_t h) : WindowContent {w, h}, SceneRenderer {{w, h}, 0}
+GameMainWindow::GameMainWindow(ScreenAbsCoord size) : SceneRenderer {size, 0}
{
uiComponents.create<ManualCameraController>(glm::vec2 {310'727'624, 494'018'810});
- auto gms = uiComponents.create<GameMainSelector>(&camera, ScreenAbsCoord {w, h});
- uiComponents.create<GameMainToolbar>(gms.get());
+ auto gms = uiComponents.create<GameMainSelector>(&camera);
+ uiComponents.create<GameMainToolbar>(gms);
}
-GameMainWindow::~GameMainWindow() { }
-
void
GameMainWindow::tick(TickDuration)
{
uiComponents.apply<CameraController>(&CameraController::updateCamera, &camera);
}
+bool
+GameMainWindow::handleInput(const SDL_Event & event)
+{
+ switch (event.type) {
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ SceneRenderer::resize({event.window.data1, event.window.data2});
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return WindowContent::handleInput(event);
+}
+
void
-GameMainWindow::render() const
+GameMainWindow::render()
{
SceneRenderer::render(*this);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
- uiComponents.apply(&UIComponent::render, uiShader, UIComponent::Position {});
+ uiComponents.apply(&UIComponent::render);
}
void
-GameMainWindow::content(const SceneShader & shader) const
+GameMainWindow::content(const SceneShader & shader, const Frustum & frustum) const
{
- for (const auto & [id, asset] : gameState->assets) {
- if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
- r->render(shader);
+ for (const auto & [assetId, asset] : gameState->assets) {
+ if (const auto renderable = asset.getAs<const Renderable>()) {
+ renderable->render(shader, frustum);
}
}
- gameState->world.apply<Renderable>(&Renderable::render, shader);
- uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader);
+ gameState->world.apply<const Renderable>(&Renderable::render, shader, frustum);
+ uiComponents.apply<WorldOverlay>(&WorldOverlay::render, shader, frustum);
}
void
-GameMainWindow::environment(const SceneShader & s, const SceneRenderer & r) const
+GameMainWindow::environment(const SceneShader &, const SceneRenderer & r) const
{
- // default for now
- SceneProvider::environment(s, r);
+ gameState->environment->render(r, *this);
}
void
GameMainWindow::lights(const SceneShader & shader) const
{
- gameState->world.apply<Renderable>(&Renderable::lights, shader);
+ gameState->world.apply<const Renderable>(&Renderable::lights, shader);
}
void
-GameMainWindow::shadows(const ShadowMapper & shadowMapper) const
+GameMainWindow::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const
{
- for (const auto & [id, asset] : gameState->assets) {
- if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
- r->shadows(shadowMapper);
+ for (const auto & [assetId, asset] : gameState->assets) {
+ if (const auto renderable = asset.getAs<const Renderable>()) {
+ renderable->shadows(shadowMapper, frustum);
}
}
- gameState->world.apply<Renderable>(&Renderable::shadows, shadowMapper);
+ gameState->world.apply<const Renderable>(&Renderable::shadows, shadowMapper, frustum);
}
diff --git a/ui/gameMainWindow.h b/ui/gameMainWindow.h
index fcbd135..71b6314 100644
--- a/ui/gameMainWindow.h
+++ b/ui/gameMainWindow.h
@@ -3,22 +3,19 @@
#include "chronology.h"
#include "gfx/gl/sceneRenderer.h"
#include "windowContent.h"
-#include <cstddef>
class GameMainWindow : public WindowContent, SceneRenderer, public SceneProvider {
public:
- GameMainWindow(size_t w, size_t h);
- ~GameMainWindow() override;
-
- NO_MOVE(GameMainWindow);
- NO_COPY(GameMainWindow);
+ GameMainWindow(ScreenAbsCoord size);
void tick(TickDuration) override;
- void render() const override;
+ void render() override;
private:
- void content(const SceneShader &) const override;
+ bool handleInput(const SDL_Event &) override;
+
+ void content(const SceneShader &, const Frustum &) const override;
void environment(const SceneShader &, const SceneRenderer &) const override;
void lights(const SceneShader &) const override;
- void shadows(const ShadowMapper &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
};
diff --git a/ui/icon.cpp b/ui/icon.cpp
index c3b5078..0bdc91a 100644
--- a/ui/icon.cpp
+++ b/ui/icon.cpp
@@ -22,9 +22,10 @@ Icon::Icon(const Image & tex) : size {tex.width, tex.height}
GL_RGBA, GL_UNSIGNED_BYTE, tex.data.data());
}
-void
-Icon::Bind() const
+ImTextureID
+Icon::operator*() const
{
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, m_texture);
+ static_assert(sizeof(m_texture) <= sizeof(ImTextureID));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr) This is how ImGui works
+ return reinterpret_cast<ImTextureID>(*m_texture);
}
diff --git a/ui/icon.h b/ui/icon.h
index 76cd3ae..3d0788a 100644
--- a/ui/icon.h
+++ b/ui/icon.h
@@ -1,5 +1,6 @@
#pragma once
+#include "imgui_wrap.h"
#include <filesystem>
#include <glArrays.h>
#include <glm/glm.hpp>
@@ -11,8 +12,8 @@ public:
explicit Icon(const std::filesystem::path & fileName);
explicit Icon(const Image & image);
- void Bind() const;
const glm::vec2 size;
+ ImTextureID operator*() const;
private:
glTexture m_texture;
diff --git a/ui/iconButton.cpp b/ui/iconButton.cpp
deleted file mode 100644
index fe8c817..0000000
--- a/ui/iconButton.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "iconButton.h"
-#include "glArrays.h"
-#include "ui/icon.h"
-#include "ui/uiComponent.h"
-#include <SDL2/SDL.h>
-#include <array>
-#include <filesystem>
-#include <functional>
-#include <glad/gl.h>
-#include <glm/gtc/type_ptr.hpp>
-#include <utility>
-
-IconButton::IconButton(const std::string & icon_, glm::vec2 position_, UIEvent click_) :
- UIComponent {{position_, ICON_SIZE}}, icon {icon_}, click {std::move(click_)}
-{
- glBindVertexArray(m_vertexArrayObject);
-
- glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffer);
- glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec4) * 4), nullptr, GL_DYNAMIC_DRAW);
-
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), nullptr);
-
- glBindVertexArray(0);
-}
-
-void
-IconButton::render(const UIShader &, const Position & parentPos) const
-{
- icon.Bind();
- glBindVertexArray(m_vertexArrayObject);
- glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffer);
- const auto abs = parentPos.origin + position.origin;
- const auto limit = abs + ICON_SIZE;
- std::array<glm::vec4, 4> vertices {{
- {abs.x, abs.y, 0, 0},
- {limit.x, abs.y, 1, 0},
- {limit.x, limit.y, 1, 1},
- {abs.x, limit.y, 0, 1},
- }};
- glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), glm::value_ptr(vertices.front()));
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindVertexArray(0);
-}
-
-bool
-IconButton::handleInput(const SDL_Event & e, const Position & parentPos)
-{
- const auto absPos = position + parentPos;
- if (absPos & e.button) {
- if (e.button.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT) {
- click(e);
- }
- }
- return false;
-}
diff --git a/ui/iconButton.h b/ui/iconButton.h
deleted file mode 100644
index 0afe92d..0000000
--- a/ui/iconButton.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "icon.h"
-#include "uiComponent.h"
-#include <glArrays.h>
-#include <glm/glm.hpp>
-#include <string>
-
-class UIShader;
-union SDL_Event;
-
-static const constexpr glm::vec2 ICON_SIZE {32.F, 32.F};
-
-class IconButton : public UIComponent {
-public:
- IconButton(const std::string & icon, glm::vec2 position, UIEvent click);
-
- void render(const UIShader &, const Position & parentPos) const override;
-
- bool handleInput(const SDL_Event & e, const Position & parentPos) override;
-
- Icon icon;
- UIEvent click;
- glVertexArray m_vertexArrayObject;
- glBuffer m_vertexArrayBuffer;
-};
diff --git a/ui/imgui_extras.cpp b/ui/imgui_extras.cpp
new file mode 100644
index 0000000..1643f4f
--- /dev/null
+++ b/ui/imgui_extras.cpp
@@ -0,0 +1,33 @@
+#define IMGUI_INTERNAL
+#include "imgui_extras.h"
+
+namespace IltGui {
+ bool
+ BeginToolbar(const char * name, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags)
+ {
+ return BeginToolbar(name, ImGui::GetMainViewport(), dir, axisSize, windowFlags);
+ }
+
+ bool
+ BeginToolbar(
+ const char * name, ImGuiViewport * viewport, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags)
+ {
+ bool isOpen = ImGui::BeginViewportSideBar(name, viewport, dir, axisSize,
+ windowFlags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings);
+ if (isOpen) {
+ if (dir == ImGuiDir_Up || dir == ImGuiDir_Down) {
+ ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ }
+ }
+ else {
+ ImGui::End();
+ }
+ return isOpen;
+ }
+
+ void
+ EndToolbar()
+ {
+ ImGui::End();
+ }
+}
diff --git a/ui/imgui_extras.h b/ui/imgui_extras.h
new file mode 100644
index 0000000..0babaa3
--- /dev/null
+++ b/ui/imgui_extras.h
@@ -0,0 +1,10 @@
+#include "imgui_wrap.h"
+
+namespace IltGui {
+ // NOLINTBEGIN(readability-identifier-naming)
+ bool BeginToolbar(const char * name, ImGuiViewport * viewport, ImGuiDir dir, float axisSize,
+ ImGuiWindowFlags windowFlags = 0);
+ bool BeginToolbar(const char * name, ImGuiDir dir, float axisSize, ImGuiWindowFlags windowFlags = 0);
+ void EndToolbar();
+ // NOLINTEND(readability-identifier-naming)
+}
diff --git a/ui/imgui_wrap.h b/ui/imgui_wrap.h
index 1d619a4..520d8b8 100644
--- a/ui/imgui_wrap.h
+++ b/ui/imgui_wrap.h
@@ -3,4 +3,11 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#include "imgui.h" // IWYU pragma: export
+#ifdef IMGUI_INTERNAL
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+# pragma GCC diagnostic ignored "-Wsign-conversion"
+# include "imgui_internal.h" // IWYU pragma: export
+# pragma GCC diagnostic pop
+#endif
#pragma GCC diagnostic pop
diff --git a/ui/mainApplication.h b/ui/mainApplication.h
index a6cb126..1489587 100644
--- a/ui/mainApplication.h
+++ b/ui/mainApplication.h
@@ -6,7 +6,7 @@
class MainApplication : public ApplicationBase {
public:
- using Windows = Collection<Window>;
+ using Windows = SharedCollection<Window>;
void mainLoop();
protected:
diff --git a/ui/mainWindow.cpp b/ui/mainWindow.cpp
index 38d6a6e..57dabc0 100644
--- a/ui/mainWindow.cpp
+++ b/ui/mainWindow.cpp
@@ -8,9 +8,7 @@
#include "backends/imgui_impl_sdl2.h"
#pragma GCC diagnostic pop
-MainWindow::MainWindow(size_t w, size_t h) : MainWindow {w, h, "I Like Trains", SDL_WINDOW_OPENGL} { }
-
-MainWindow::MainWindow(size_t w, size_t h, const std::string & title, Uint32 flags) : Window {w, h, title, flags}
+MainWindow::MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags) : Window {size, title, flags}
{
if (const auto version = gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress)); version < 30003) {
throw std::runtime_error {std::format("Insufficient OpenGL version: {}", version)};
diff --git a/ui/mainWindow.h b/ui/mainWindow.h
index fe26c5c..d2de9b3 100644
--- a/ui/mainWindow.h
+++ b/ui/mainWindow.h
@@ -5,12 +5,9 @@
class MainWindow : public Window {
public:
- MainWindow(size_t w, size_t h);
+ MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
~MainWindow() override;
NO_MOVE(MainWindow);
NO_COPY(MainWindow);
-
-protected:
- MainWindow(size_t width, size_t height, const std::string & title, Uint32 flags);
};
diff --git a/ui/manualCameraController.cpp b/ui/manualCameraController.cpp
index 065e1d8..553afc1 100644
--- a/ui/manualCameraController.cpp
+++ b/ui/manualCameraController.cpp
@@ -1,11 +1,11 @@
#include "manualCameraController.h"
#include <algorithm>
#include <cmath>
-#include <gfx/gl/camera.h>
+#include <gfx/camera.h>
#include <maths.h>
bool
-ManualCameraController::handleInput(const SDL_Event & e, const Position &)
+ManualCameraController::handleInput(const SDL_Event & e)
{
switch (e.type) {
case SDL_KEYDOWN:
@@ -72,13 +72,13 @@ ManualCameraController::handleInput(const SDL_Event & e, const Position &)
}
void
-ManualCameraController::render(const UIShader &, const Position &) const
+ManualCameraController::render()
{
}
void
ManualCameraController::updateCamera(Camera * camera) const
{
- const auto forward = glm::normalize(sincosf(direction) || -sin(pitch));
+ const auto forward = glm::normalize(sincos(direction) || -sin(pitch));
camera->setView((focus || 0) - (forward * 3.F * std::pow(dist, 1.3F)), forward);
}
diff --git a/ui/manualCameraController.h b/ui/manualCameraController.h
index 2f955e7..6501762 100644
--- a/ui/manualCameraController.h
+++ b/ui/manualCameraController.h
@@ -6,15 +6,14 @@
#include <glm/glm.hpp>
#include <maths.h>
-class UIShader;
class Camera;
class ManualCameraController : public CameraController, public UIComponent {
public:
- explicit ManualCameraController(GlobalPosition2D f) : UIComponent {{}}, focus {f} { }
+ explicit ManualCameraController(GlobalPosition2D f) : focus {f} { }
- bool handleInput(const SDL_Event & e, const Position &) override;
- void render(const UIShader &, const Position & parentPos) const override;
+ bool handleInput(const SDL_Event & e) override;
+ void render() override;
void updateCamera(Camera * camera) const override;
diff --git a/ui/modeHelper.h b/ui/modeHelper.h
deleted file mode 100644
index d20f2db..0000000
--- a/ui/modeHelper.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include <memory>
-union SDL_Event;
-
-enum ModeSecondClick { Unset, Reset, NoAction };
-
-template<typename Target, ModeSecondClick msc = ModeSecondClick::Unset> class Mode {
-public:
- explicit Mode(Target & t) : target {t} { }
-
- Target & target;
-
- template<typename Mode, typename... Params>
- auto
- toggle(Params &&... params)
- {
- return [params..., this](const SDL_Event &) {
- toggleSetMode<Mode>(std::forward<Params>(params)...);
- };
- }
-
-private:
- template<typename Mode, typename... Params>
- void
- toggleSetMode(Params &&... params)
- {
- if (dynamic_cast<Mode *>(target.get())) {
- if constexpr (msc == ModeSecondClick::Unset) {
- target.reset();
- }
- if constexpr (msc == ModeSecondClick::Reset) {
- target = std::make_unique<Mode>(std::forward<Params>(params)...);
- }
- }
- else {
- target = std::make_unique<Mode>(std::forward<Params>(params)...);
- }
- }
-};
diff --git a/ui/queryTool.cpp b/ui/queryTool.cpp
new file mode 100644
index 0000000..549bd9e
--- /dev/null
+++ b/ui/queryTool.cpp
@@ -0,0 +1,42 @@
+#include "queryTool.h"
+#include "imgui_wrap.h"
+#include <game/gamestate.h>
+#include <game/selectable.h>
+#include <game/terrain.h>
+#include <game/worldobject.h>
+#include <ray.h>
+#include <stream_support.h>
+
+QueryTool::QueryTool() : clicked {"Click something for details"} { }
+
+bool
+QueryTool::click(const SDL_MouseButtonEvent & event, const Ray<GlobalPosition3D> & ray)
+{
+ if (event.button != SDL_BUTTON_LEFT) {
+ return false;
+ }
+ BaryPosition baryPos {};
+ RelativeDistance distance {};
+
+ if (const auto selected = gameState->world.applyOne<Selectable>(&Selectable::intersectRay, ray, baryPos, distance);
+ selected != gameState->world.end()) {
+ const auto & ref = *selected.base()->get();
+ clicked = typeid(ref).name();
+ }
+ else if (const auto pos = gameState->terrain->intersectRay(ray)) {
+ clicked = streamed_string(*pos);
+ }
+ else {
+ clicked.clear();
+ }
+ return true;
+}
+
+void
+QueryTool::render(bool & open)
+{
+ ImGui::SetNextWindowSize({-1, -1});
+ ImGui::Begin("Query Tool", &open);
+ ImGui::TextUnformatted(clicked.c_str());
+ ImGui::End();
+}
diff --git a/ui/queryTool.h b/ui/queryTool.h
new file mode 100644
index 0000000..74c5380
--- /dev/null
+++ b/ui/queryTool.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "gameMainSelector.h"
+
+class QueryTool : public GameMainSelector::Component {
+public:
+ QueryTool();
+
+protected:
+ using GameMainSelector::Component::render;
+
+ bool click(const SDL_MouseButtonEvent &, const Ray<GlobalPosition3D> &) override;
+ void render(bool & open) override;
+
+private:
+ std::string clicked;
+};
diff --git a/ui/svgIcon.cpp b/ui/svgIcon.cpp
new file mode 100644
index 0000000..499d9cc
--- /dev/null
+++ b/ui/svgIcon.cpp
@@ -0,0 +1,34 @@
+#include "svgIcon.h"
+#include "gl_traits.h"
+#include <resource.h>
+
+SvgIcon::SvgIcon(ImageDimensions dim, const std::filesystem::path & path)
+{
+ const auto svgDoc = lunasvg::Document::loadFromFile(Resource::mapPath(path).native());
+ if (!svgDoc) {
+ throw std::runtime_error("Failed to load SVG from " + path.string());
+ }
+
+ auto bitmap = svgDoc->renderToBitmap(dim.x, dim.y);
+ if (bitmap.isNull()) {
+ throw std::runtime_error("Failed to render SVG " + path.string());
+ }
+ bitmap.convertToRGBA();
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim.x, dim.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.data());
+}
+
+ImTextureID
+SvgIcon::operator*() const
+{
+ static_assert(sizeof(glTexture) <= sizeof(ImTextureID));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr) This is how ImGui works
+ return reinterpret_cast<ImTextureID>(*texture);
+}
diff --git a/ui/svgIcon.h b/ui/svgIcon.h
new file mode 100644
index 0000000..106f97c
--- /dev/null
+++ b/ui/svgIcon.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "glArrays.h"
+#include "imgui_wrap.h"
+#include <config/types.h>
+#include <filesystem>
+#include <lunasvg.h>
+
+class SvgIcon {
+public:
+ SvgIcon(ImageDimensions, const std::filesystem::path &);
+
+ ImTextureID operator*() const;
+
+private:
+ friend class LoadFromFile; // Test case verifying size/content
+ glTexture texture;
+};
diff --git a/ui/text.cpp b/ui/text.cpp
deleted file mode 100644
index bdaaba5..0000000
--- a/ui/text.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#include "text.h"
-#include "font.h"
-#include "gfx/gl/uiShader.h"
-#include "gfx/gl/vertexArrayObject.h"
-#include "uiComponent.h"
-#include <array>
-#include <collections.h>
-#include <glArrays.h>
-#include <glm/gtc/type_ptr.hpp>
-#include <maths.h>
-#include <numeric>
-#include <utility>
-
-Text::Text(std::string_view s, const Font & font, Position pos, glm::vec3 c) :
- UIComponent {pos}, colour {c}, font {font}
-{
- VertexArrayObject {vao}.addAttribs<Font::Quad::value_type>(quads.bufferName(), 0);
- operator=(s);
-}
-
-Text &
-Text::operator=(const std::string_view s)
-{
- auto tquads = font.render(s);
- models.resize(tquads.size());
- const auto glyphCount = std::accumulate(tquads.begin(), tquads.end(), size_t {}, [](auto && init, const auto & q) {
- return init += q.second.size();
- });
- quads.resize(glyphCount);
- GLushort current = 0;
- auto model = models.begin();
- auto quad = quads.begin();
- for (const auto & [texture, fquads] : tquads) {
- model->textureId = texture;
- model->range.resize(fquads.size() * 6);
- for (auto out = model->range.begin(); const auto & q [[maybe_unused]] : fquads) {
- static constexpr std::array<GLushort, 6> quadIndices {0, 1, 2, 2, 3, 0};
- std::transform(quadIndices.begin(), quadIndices.end(), out, [current](auto x) {
- return current + x;
- });
- current += 4;
- out += 6;
- }
- model++;
- quad = std::transform(fquads.begin(), fquads.end(), quad, [this](const Font::Quad & q) {
- return q * [this](const glm::vec4 & corner) {
- return corner + glm::vec4 {this->position.origin, 0, 0};
- };
- });
- }
- quads.unmap();
- return *this;
-}
-
-void
-Text::render(const UIShader & shader, const Position &) const
-{
- shader.text.use(colour);
- glActiveTexture(GL_TEXTURE0);
- glBindVertexArray(vao);
- for (const auto & m : models) {
- glBindTexture(GL_TEXTURE_2D, m.textureId);
- glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m.range.size()), GL_UNSIGNED_SHORT, m.range.data());
- }
- glBindVertexArray(0);
- glBindTexture(GL_TEXTURE_2D, 0);
-}
-
-bool
-Text::handleInput(const SDL_Event &, const Position &)
-{
- return false;
-}
diff --git a/ui/text.h b/ui/text.h
deleted file mode 100644
index a367456..0000000
--- a/ui/text.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include "font.h"
-#include "glContainer.h"
-#include "uiComponent.h"
-#include <glArrays.h>
-#include <glad/gl.h>
-#include <glm/glm.hpp>
-#include <string_view>
-
-class UIShader;
-union SDL_Event;
-
-class Text : public UIComponent {
-public:
- Text(std::string_view s, const Font &, Position, glm::vec3 colour);
-
- void render(const UIShader &, const Position & parentPos) const override;
- bool handleInput(const SDL_Event &, const Position & parentPos) override;
-
- Text & operator=(const std::string_view s);
-
-private:
- struct TextData {
- GLuint textureId;
- std::vector<unsigned short> range;
- };
-
- std::vector<TextData> models;
- glContainer<Font::Quad> quads;
- glVertexArray vao;
- glm::vec3 colour;
- const Font & font;
-};
diff --git a/ui/toolbar.cpp b/ui/toolbar.cpp
deleted file mode 100644
index 31d87dc..0000000
--- a/ui/toolbar.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "toolbar.h"
-#include "gfx/gl/uiShader.h"
-#include "ui/iconButton.h"
-#include "ui/uiComponent.h"
-#include "uiComponentPlacer.h"
-#include <SDL2/SDL.h>
-#include <glm/glm.hpp>
-
-Toolbar::Toolbar(const std::initializer_list<InitInfo> & initInfo) : UIComponent {{{}, {}}}
-{
- UIComponentPlacer placer {{10, 10}, 5, 1};
- for (const auto & ii : initInfo) {
- icons.create(ii.first, placer.next(ICON_SIZE), ii.second);
- }
- this->position.size = placer.getLimit();
-}
-
-void
-Toolbar::render(const UIShader & uiShader, const Position & parentPos) const
-{
- uiShader.icon.use();
- const auto absPos = this->position + parentPos;
- icons.apply(&UIComponent::render, uiShader, absPos);
-}
-
-bool
-Toolbar::handleInput(const SDL_Event & e, const Position & parentPos)
-{
- const auto absPos = this->position + parentPos;
- if (absPos & e.button) {
- icons.applyOne(&UIComponent::handleInput, e, absPos);
- return true;
- }
- return false;
-}
diff --git a/ui/toolbar.h b/ui/toolbar.h
deleted file mode 100644
index ea560f5..0000000
--- a/ui/toolbar.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include "collection.h"
-#include "iconButton.h"
-#include "uiComponent.h"
-#include <initializer_list>
-#include <string>
-#include <utility>
-
-class UIShader;
-union SDL_Event;
-
-class Toolbar : public UIComponent {
-public:
- using InitInfo = std::pair<std::string, UIEvent>;
- explicit Toolbar(const std::initializer_list<InitInfo> & initInfo);
-
- void render(const UIShader & uiShader, const Position & parentPos) const override;
-
- bool handleInput(const SDL_Event & e, const Position & parentPos) override;
-
- Collection<IconButton, false> icons;
-};
diff --git a/ui/uiComponent.cpp b/ui/uiComponent.cpp
deleted file mode 100644
index aa4838d..0000000
--- a/ui/uiComponent.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "uiComponent.h"
-#include <SDL2/SDL.h>
-
-UIComponent::UIComponent(Position position) : position {position} { }
-
-UIComponent::Position
-UIComponent::Position::operator+(const Position & parentPos) const
-{
- return *this + parentPos.origin;
-}
-
-UIComponent::Position
-UIComponent::Position::operator+(const glm::vec2 & parentPos) const
-{
- return {origin + parentPos, size};
-}
-
-bool
-UIComponent::Position::operator&(const glm::vec2 & pos) const
-{
- return (pos.x >= origin.x && pos.y >= origin.y && pos.x < origin.x + size.x && pos.y < origin.y + size.y);
-}
-
-bool
-UIComponent::Position::operator&(const SDL_MouseButtonEvent & pos) const
-{
- switch (pos.type) {
- case SDL_MOUSEBUTTONUP:
- case SDL_MOUSEBUTTONDOWN:
- return *this & glm::vec2 {pos.x, pos.y};
- }
- return false;
-}
diff --git a/ui/uiComponent.h b/ui/uiComponent.h
index 71d2659..b2c1a8f 100644
--- a/ui/uiComponent.h
+++ b/ui/uiComponent.h
@@ -1,32 +1,18 @@
#pragma once
-#include <functional>
#include <glm/glm.hpp>
#include <special_members.h>
-class UIShader;
union SDL_Event;
-struct SDL_MouseButtonEvent;
-using UIEvent = std::function<void(const SDL_Event &)>;
class UIComponent {
public:
- struct Position {
- glm::vec2 origin, size;
- Position operator+(const Position &) const;
- Position operator+(const glm::vec2 &) const;
- bool operator&(const SDL_MouseButtonEvent &) const;
- bool operator&(const glm::vec2 &) const;
- };
-
- explicit UIComponent(Position);
+ UIComponent() = default;
virtual ~UIComponent() = default;
NO_MOVE(UIComponent);
NO_COPY(UIComponent);
- virtual void render(const UIShader &, const Position & parentPos) const = 0;
- virtual bool handleInput(const SDL_Event &, const Position & parentPos) = 0;
-
- Position position;
+ virtual void render() = 0;
+ virtual bool handleInput(const SDL_Event &) = 0;
};
diff --git a/ui/uiComponentPlacer.cpp b/ui/uiComponentPlacer.cpp
deleted file mode 100644
index 5e645d8..0000000
--- a/ui/uiComponentPlacer.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "uiComponentPlacer.h"
-#include <algorithm>
-
-UIComponentPlacer::UIComponentPlacer(glm::vec2 padding, float spacing, glm::length_t axis) :
- padding {padding}, spacing {spacing}, axis {axis}, current {padding[axis]}
-{
-}
-
-glm::vec2
-UIComponentPlacer::next(glm::vec2 size)
-{
- glm::vec2 n {};
- n[axis] = current;
- n[1 - axis] = padding[1 - axis];
- current += spacing + size[axis];
- max = std::max(max, size[1 - axis]);
- return n;
-}
-
-glm::vec2
-UIComponentPlacer::getLimit() const
-{
- glm::vec2 n {};
- n[axis] = current + padding[axis];
- n[1 - axis] = max + padding[1 - axis];
- return n;
-}
diff --git a/ui/uiComponentPlacer.h b/ui/uiComponentPlacer.h
deleted file mode 100644
index 1e64f78..0000000
--- a/ui/uiComponentPlacer.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include <glm/glm.hpp>
-
-class UIComponentPlacer {
-public:
- UIComponentPlacer(glm::vec2 padding, float spacing, glm::length_t axis = 0);
-
- glm::vec2 next(glm::vec2 size);
- glm::vec2 getLimit() const;
-
-private:
- const glm::vec2 padding;
- const float spacing;
- const glm::length_t axis;
-
- float current {};
- float max {};
-};
diff --git a/ui/window.cpp b/ui/window.cpp
index 732e9ef..06857b2 100644
--- a/ui/window.cpp
+++ b/ui/window.cpp
@@ -7,10 +7,9 @@
#include "backends/imgui_impl_sdl2.h"
#pragma GCC diagnostic pop
-Window::Window(size_t width, size_t height, const std::string & title, Uint32 flags) :
- size {static_cast<int>(width), static_cast<int>(height)},
- m_window {title.c_str(), static_cast<int>(SDL_WINDOWPOS_CENTERED), static_cast<int>(SDL_WINDOWPOS_CENTERED), size.x,
- size.y, flags},
+Window::Window(ScreenAbsCoord size, const char * title, Uint32 flags) :
+ m_window {title, static_cast<int>(SDL_WINDOWPOS_CENTERED), static_cast<int>(SDL_WINDOWPOS_CENTERED), size.x, size.y,
+ flags},
glContext {m_window}
{
}
diff --git a/ui/window.h b/ui/window.h
index 62c34de..99a977f 100644
--- a/ui/window.h
+++ b/ui/window.h
@@ -15,7 +15,7 @@ using SDL_GLContextPtr = wrapped_ptrt<GL_Context, SDL_GL_CreateContext, SDL_GL_D
class Window {
public:
- Window(size_t width, size_t height, const std::string & title, Uint32 flags);
+ Window(ScreenAbsCoord size, const char * title, Uint32 flags);
virtual ~Window() = default;
NO_COPY(Window);
@@ -27,7 +27,7 @@ public:
{
glm::ivec2 size {};
SDL_GetWindowSizeInPixels(m_window, &size.x, &size.y);
- content = std::make_unique<C>(size.x, size.y, std::forward<P>(p)...);
+ content = std::make_unique<C>(ScreenAbsCoord {size.x, size.y}, std::forward<P>(p)...);
}
void tick(TickDuration elapsed);
@@ -39,7 +39,6 @@ public:
protected:
void clear(float r, float g, float b, float a) const;
- const ScreenAbsCoord size;
SDL_WindowPtr m_window;
SDL_GLContextPtr glContext;
WindowContent::Ptr content;
diff --git a/ui/windowContent.cpp b/ui/windowContent.cpp
index 91732a7..0f6dc04 100644
--- a/ui/windowContent.cpp
+++ b/ui/windowContent.cpp
@@ -1,8 +1,6 @@
#include "windowContent.h"
#include "SDL_events.h"
-WindowContent::WindowContent(size_t width, size_t height) : uiShader {width, height} { }
-
void
WindowContent::tick(TickDuration)
{
@@ -27,6 +25,6 @@ WindowContent::handleInput(const SDL_Event & e)
eAdjusted.motion.y = size.y - e.motion.y;
break;
}
- uiComponents.rapplyOne(&UIComponent::handleInput, eAdjusted, UIComponent::Position {{}, size});
+ uiComponents.rapplyOne(&UIComponent::handleInput, eAdjusted);
return true;
}
diff --git a/ui/windowContent.h b/ui/windowContent.h
index 474445a..34cbea3 100644
--- a/ui/windowContent.h
+++ b/ui/windowContent.h
@@ -2,25 +2,21 @@
#include "chronology.h"
#include "collection.h"
-#include "gfx/gl/uiShader.h"
#include "special_members.h"
#include "stdTypeDefs.h"
#include "uiComponent.h" // IWYU pragma: keep
-#include <functional>
class WindowContent : public StdTypeDefs<WindowContent> {
public:
- using Factory = std::function<Ptr(size_t width, size_t height)>;
- WindowContent(size_t width, size_t height);
+ WindowContent() = default;
virtual ~WindowContent() = default;
NO_MOVE(WindowContent);
NO_COPY(WindowContent);
virtual void tick(TickDuration);
- virtual void render() const = 0;
+ virtual void render() = 0;
virtual bool handleInput(const SDL_Event & e);
protected:
- ::Collection<UIComponent> uiComponents;
- UIShader uiShader;
+ UniqueCollection<UIComponent> uiComponents;
};
diff --git a/ui/worldOverlay.h b/ui/worldOverlay.h
index 18fab3f..a0f3b65 100644
--- a/ui/worldOverlay.h
+++ b/ui/worldOverlay.h
@@ -1,9 +1,10 @@
#pragma once
class SceneShader;
+class Frustum;
class WorldOverlay {
public:
virtual ~WorldOverlay() = default;
- virtual void render(const SceneShader &) const = 0;
+ virtual void render(const SceneShader &, const Frustum &) const = 0;
};