diff options
305 files changed, 9634 insertions, 5767 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..d8d195e 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -1,4 +1,3 @@ -using gcc ; using pkg-config ; import pkg-config ; import testing ; @@ -13,14 +12,14 @@ 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 <cxxstd>23 <linkflags>-Wl,-z,defs + <variant>debug:<define>GLDEBUG=1 <variant>release:<lto>on-thin <variant>profile:<lto>on-thin - <variant>coverage:<coverage>on <toolset>tidy:<enable>all <toolset>tidy:<exclude>bin/link-static/lib/jsonParse.cpp <toolset>tidy:<checkxx>boost-* @@ -47,18 +46,18 @@ 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 ] + [ glob-tree *.cpp *.vert *.frag *.geom *.tesc *.tese : application bin test thirdparty ] [ lib generated : [ glob-tree *.ll *.c : bin thirdparty ] : <include>. <include>lib @@ -93,7 +92,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..a1396bc 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -1,3 +1,4 @@ +#include "game/scenary/light.h" #include "ui/mainApplication.h" #include "ui/mainWindow.h" #include <array> @@ -11,6 +12,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 +26,99 @@ #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 { +class DummyMainApplication : public MainApplication, public GameState { 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}}); } } + + world.create<Light>(assets.at("old-lamp").dynamicCast<Illuminator>(), + Location {.pos = terrain->positionAt({{311000000, 491100000}})}); + world.create<Light>(assets.at("r-light").dynamicCast<Illuminator>(), + Location {.pos = terrain->positionAt({{311000000, 491096000}})}); } mainLoop(); - world.objects.clear(); + world.clear(); return 0; } }; diff --git a/application/resviewer.cpp b/application/resviewer.cpp new file mode 100644 index 0000000..68c2316 --- /dev/null +++ b/application/resviewer.cpp @@ -0,0 +1,242 @@ +#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::preFrame(*this, gameState->environment->getSunPos()); + 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 + forEachRenderable(const RenderableProcessor & func) const override + { + if (selectedAsset) { + func(selectedAsset); + } + } + + 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); + } + } + + void + environment(const SceneShader &, const SceneRenderer & renderer) const override + { + gameState->environment->render(renderer, *this); + } + + std::span<const std::filesystem::path> fileList; + std::filesystem::file_time_type fileTime; + const std::filesystem::path * selectedFile {}; + std::string selectedAssetId; + 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..d8b42f6 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, 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..a8d6036 100644 --- a/assetFactory/assetFactory.cpp +++ b/assetFactory/assetFactory.cpp @@ -3,8 +3,10 @@ #include "cuboid.h" #include "cylinder.h" #include "filesystem.h" +#include "gfx/gl/gldebug.h" #include "gfx/image.h" #include "gfx/models/texture.h" +#include "gfx/renderable.h" #include "object.h" #include "plane.h" #include "saxParse-persistence.h" @@ -25,6 +27,7 @@ AssetFactory::AssetFactory() : std::shared_ptr<AssetFactory> AssetFactory::loadXML(const std::filesystem::path & filename) { + glDebugScope _ {0, filename.native()}; filesystem::FileStar file {filename.c_str(), "r"}; return Persistence::SAXParsePersistence {}.loadState<std::shared_ptr<AssetFactory>>(file); } @@ -32,6 +35,7 @@ AssetFactory::loadXML(const std::filesystem::path & filename) AssetFactory::Assets AssetFactory::loadAll(const std::filesystem::path & root) { + glDebugScope _ {0}; return std::accumulate(std::filesystem::recursive_directory_iterator {root}, std::filesystem::recursive_directory_iterator {}, Assets {}, [](auto out, const auto & path) { if (path.path().extension() == ".xml") { @@ -114,6 +118,7 @@ void AssetFactory::createTexutre() const { if (!textureFragments.empty() && !texture) { + glDebugScope _ {0}; // * layout images std::map<const TextureFragment *, std::unique_ptr<const Image>> images; std::transform( @@ -139,6 +144,7 @@ AssetFactory::createTexutre() const size++; return decltype(textureFragmentPositions)::value_type {i.first->id, m}; }); + texture->complete(); } } @@ -146,7 +152,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/lights.cpp b/assetFactory/lights.cpp new file mode 100644 index 0000000..ec8e17e --- /dev/null +++ b/assetFactory/lights.cpp @@ -0,0 +1,14 @@ +#include "lights.h" + +bool +SpotLight::persist(Persistence::PersistenceStore & store) +{ + return STORE_TYPE && STORE_MEMBER(position) && STORE_MEMBER(direction) && STORE_MEMBER(colour) && STORE_MEMBER(kq) + && STORE_MEMBER(arc); +} + +bool +PointLight::persist(Persistence::PersistenceStore & store) +{ + return STORE_TYPE && STORE_MEMBER(position) && STORE_MEMBER(colour) && STORE_MEMBER(kq); +} diff --git a/assetFactory/lights.h b/assetFactory/lights.h new file mode 100644 index 0000000..8657d85 --- /dev/null +++ b/assetFactory/lights.h @@ -0,0 +1,18 @@ +#pragma once + +#include "gfx/models/lights.h" +#include "persistence.h" +#include "stdTypeDefs.h" + +struct SpotLight : Persistence::Persistable, SpotLightDef, StdTypeDefs<SpotLight> { +private: + friend Persistence::SelectionPtrBase<std::shared_ptr<SpotLight>>; + bool persist(Persistence::PersistenceStore & store) override; +}; + +struct PointLight : Persistence::Persistable, PointLightDef, StdTypeDefs<PointLight> { +private: + friend Persistence::SelectionPtrBase<std::shared_ptr<PointLight>>; + bool persist(Persistence::PersistenceStore & store) override; +}; + 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..58a5b53 --- /dev/null +++ b/game/environment.cpp @@ -0,0 +1,111 @@ +#include "environment.h" +#include "gfx/lightDirection.h" +#include <chronology.h> +#include <gfx/gl/sceneRenderer.h> + +constexpr Direction2D DONCASTER = {-1.1_degrees, 53.5_degrees}; + +Environment::Environment() : worldTime {"2026-06-01T12:00:00"_seconds}, gameTimeScaleFactor {1440}, earthPos {DONCASTER} +{ +} + +void +Environment::tick(TickDuration elapsed) +{ + worldTime += std::chrono::duration_cast<WorldTime::duration>(elapsed * gameTimeScaleFactor); +} + +Environment::WorldTime +Environment::getWorldTime() const +{ + return worldTime; +} + +Direction2D +Environment::getSunPos() const +{ + return getSunPos(earthPos, worldTime.time_since_epoch().count()); +} + +void +Environment::render(const SceneRenderer & renderer, const SceneProvider & scene) const +{ + constexpr RGB SUN_LIGHT {1, 1, .878F}; + constexpr RGB SKY_BLUE {.529F, .808F, .922F}; + constexpr RGB BASE_AMBIENT_LIGHT {0.1F}; + + const LightDirection sunPos {getSunPos()}; + const auto scattered = SKY_BLUE * sunPos.atmosphericScattering() * sunPos.ambient(); + const auto ambient = BASE_AMBIENT_LIGHT + scattered; + const auto directional = (SUN_LIGHT - BASE_AMBIENT_LIGHT - scattered) * 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 dSinEclipticLongitude = sin(dEclipticLongitude); + const auto decY = cos(dEclipticObliquity) * dSinEclipticLongitude; + const auto decX = cos(dEclipticLongitude); + auto dRightAscension = atan2(decY, decX); + if (dRightAscension < 0) { + dRightAscension = dRightAscension + two_pi; + } + const auto dDeclination = asin(sin(dEclipticObliquity) * dSinEclipticLongitude); + + // 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 dCosLatitude = cos(dLatitudeInRadians); + const auto dSinLatitude = sin(dLatitudeInRadians); + const auto dCosHourAngle = cos(dHourAngle); + Direction2D udtSunCoordinates; + udtSunCoordinates.y + = (acos((dCosLatitude * dCosHourAngle * cos(dDeclination)) + (sin(dDeclination) * dSinLatitude))); + udtSunCoordinates.x = atan2(-sin(dHourAngle), (tan(dDeclination) * dCosLatitude) - (dSinLatitude * dCosHourAngle)); + 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..94211bc --- /dev/null +++ b/game/environment.h @@ -0,0 +1,25 @@ +#pragma once + +#include "config/types.h" +#include "worldobject.h" +#include <chrono> + +class SceneRenderer; +class SceneProvider; + +class Environment : public WorldObject { +public: + using WorldTime = std::chrono::utc_time<std::chrono::seconds>; + + Environment(); + void tick(TickDuration elapsed) override; + void render(const SceneRenderer &, const SceneProvider &) const; + [[nodiscard]] Direction2D getSunPos() const; + [[nodiscard]] WorldTime getWorldTime() const; + [[nodiscard]] static Direction2D getSunPos(Direction2D position, time_t time); + +private: + WorldTime worldTime; + uint16_t gameTimeScaleFactor; + glm::vec<2, Angle> earthPos; +}; 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 { - auto f = from.face(this); - if (!f.is_valid()) { + WalkStep step { + .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; + 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; } } - f.reset(); + step.current.reset(); } } } void -GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op) const +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 +{ + WalkStepCurve step {WalkStep {.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); + } + 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; + } + } + step.current.reset(); + } + } +} + +void +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 {}; } - - const auto initialVertexCount = static_cast<unsigned int>(n_vertices()); - - // 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); - } + for (const auto & vertex : triangleStrip) { + extents += vertex; } - 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; - }; + 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); + }))} + { + } - 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))); + 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; + } - 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); + 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; + } - 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); + } - return extrusionDir; - }; + using HeightSetTodo = std::multimap<VertexHandle, VertexHandle>; - 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())); + else if (previousVertex.is_valid()) { + out.emplace(vertex, previousVertex); + } + }; + for (const auto vertex : newVerts) { + setSurfaceVertexHeight(setSurfaceVertexHeight, vertex, VertexHandle {}); + } + return out; + } - // 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); + 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; + }); - 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); + 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; + } + + 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(); - delete_face(face, false); + 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; - } + struct WalkStepCurve : public WalkStep { + Angle angle {}; + }; - [[nodiscard]] Normal3D - normal() const - requires(Dim == 3) - { - return crossProduct(difference(p(0), p(1)), difference(p(0), p(2))); - } + template<typename T> using Consumer = const std::function<void(const T &)> &; + template<typename T> using Tester = const std::function<bool(const T &)> &; - [[nodiscard]] Normal3D - nnormal() const - requires(Dim == 3) - { - return glm::normalize(normal()); - } + 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; - [[nodiscard]] auto - angle(glm::length_t c) const - { - return Arc {P(c), P(c + 2), P(c + 1)}.length(); - } + void boundaryWalk(Consumer<HalfedgeHandle>) const; + void boundaryWalk(Consumer<HalfedgeHandle>, HalfedgeHandle start) const; + void boundaryWalkUntil(Tester<HalfedgeHandle>) const; + void boundaryWalkUntil(Tester<HalfedgeHandle>, HalfedgeHandle start) const; - 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]] HalfedgeHandle findEntry(GlobalPosition2D from, GlobalPosition2D to) const; - [[nodiscard]] inline auto - p(const glm::length_t i) const - { - return base::operator[](i); - } + struct SetHeightsOpts { + static constexpr auto DEFAULT_NEAR_NODE_TOLERANACE = 500.F; + static constexpr auto DEFAULT_MAX_SLOPE = 0.5F; - [[nodiscard]] inline auto - P(const glm::length_t i) const - { - return base::operator[](i % 3); - } + const Surface * surface = nullptr; + RelativeDistance nearNodeTolerance = DEFAULT_NEAR_NODE_TOLERANACE; + RelativeDistance maxSlope = DEFAULT_MAX_SLOPE; }; - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const; - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const; - - [[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, 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; - - 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; + std::set<FaceHandle> setHeights(std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts &); - [[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const; - - void setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface &); - - [[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/mixins/lights.cpp b/game/mixins/lights.cpp new file mode 100644 index 0000000..6829bbb --- /dev/null +++ b/game/mixins/lights.cpp @@ -0,0 +1,30 @@ +#include "lights.h" +#include "gfx/renderable.h" + +bool +AssetLights::persist(Persistence::PersistenceStore & store) +{ + return STORE_HELPER(pointLight, Persistence::Appender<decltype(pointLight)>) + && STORE_HELPER(spotLight, Persistence::Appender<decltype(spotLight)>); +} + +void +InstanceLights::lightsEnable(AnyPtr<const AssetLights> asset, uint32_t owner) +{ + auto createLights = [owner](const auto & assetLights, auto & lightInstances, auto commonLights) { + std::ranges::transform(assetLights | std::views::enumerate, std::inserter(lightInstances, lightInstances.end()), + [&commonLights, owner](const auto & idxAndLight) { + const auto & [idx, light] = idxAndLight; + return std::make_pair(idx, commonLights->acquire(*light, owner)); + }); + }; + createLights(asset->spotLight, spotLightInstances, Renderable::commonSpotLights.lock()); + createLights(asset->pointLight, pointLightInstances, Renderable::commonPointLights.lock()); +} + +void +InstanceLights::lightsDisable() +{ + spotLightInstances.clear(); + pointLightInstances.clear(); +} diff --git a/game/mixins/lights.h b/game/mixins/lights.h new file mode 100644 index 0000000..c2207a5 --- /dev/null +++ b/game/mixins/lights.h @@ -0,0 +1,26 @@ +#pragma once + +#include "assetFactory/lights.h" +#include "gfx/gl/instanceVertices.h" +#include <flat_map> + +class AssetLights { +protected: + bool persist(Persistence::PersistenceStore & store); + template<typename T> using LightVec = std::vector<typename T::Ptr>; + + LightVec<SpotLight> spotLight; + LightVec<PointLight> pointLight; + + friend class InstanceLights; +}; + +class InstanceLights { +protected: + template<typename V> using LightInstanceMap = std::flat_map<size_t, typename InstanceVertices<V>::InstanceProxy>; + LightInstanceMap<SpotLightVertex> spotLightInstances; + LightInstanceMap<PointLightVertex> pointLightInstances; + + void lightsEnable(AnyPtr<const AssetLights>, uint32_t); + void lightsDisable(); +}; 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..46b84d4 100644 --- a/game/network/network.h +++ b/game/network/network.h @@ -1,23 +1,25 @@ #pragma once +#include "collection.h" +#include "gfx/gl/glVertexArray.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 +31,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 +39,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 +78,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..e8cc1b6 100644 --- a/game/network/rail.cpp +++ b/game/network/rail.cpp @@ -1,14 +1,14 @@ #include "rail.h" +#include "game/gamestate.h" #include "network.h" #include <game/network/network.impl.h> // IWYU pragma: keep #include <gfx/gl/sceneShader.h> -#include <gfx/gl/vertexArrayObject.h> #include <gfx/models/texture.h> 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 +32,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 +72,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 +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>}; -inline auto -round_sleepers(const float v) -{ - return round_frac(v, sleepers); + 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)} { } @@ -131,40 +138,61 @@ RailLink::vehiclePositionOffset() const template<> NetworkLinkHolder<RailLinkStraight>::NetworkLinkHolder() { - VertexArrayObject {vao} + glDebugScope _ {0}; + vao.configure() .addAttribs<RailLinkStraight::Vertex, &RailLinkStraight::Vertex::a, &RailLinkStraight::Vertex::b, - &RailLinkStraight::Vertex::rotation, &RailLinkStraight::Vertex::textureRepeats>( - vertices.bufferName()); + &RailLinkStraight::Vertex::rotation, &RailLinkStraight::Vertex::textureRepeats>(0); } template<> NetworkLinkHolder<RailLinkCurve>::NetworkLinkHolder() { - VertexArrayObject {vao} + glDebugScope _ {0}; + vao.configure() .addAttribs<RailLinkCurve::Vertex, &RailLinkCurve::Vertex::a, &RailLinkCurve::Vertex::b, &RailLinkCurve::Vertex::c, &RailLinkCurve::Vertex::textureRepeats, &RailLinkCurve::Vertex::aangle, - &RailLinkCurve::Vertex::bangle, &RailLinkCurve::Vertex::radius>(vertices.bufferName()); + &RailLinkCurve::Vertex::bangle, &RailLinkCurve::Vertex::radius>(0); } namespace { template<typename LinkType> void - renderType(const NetworkLinkHolder<LinkType> & n, auto & s) + renderType(const NetworkLinkHolder<LinkType> & networkLinks, auto & shader, GLenum mode) { - if (auto count = n.vertices.size()) { - s.use(railCrossSection, railTexturePos); - glBindVertexArray(n.vao); - glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count)); + if (auto count = networkLinks.vertices.size()) { + auto _ = glDebugScope(networkLinks.vao); + shader.use(RAIL_CROSS_SECTION, RAIL_TEXTURE_POS); + glBindVertexArray(networkLinks.vao); + networkLinks.vao.useBuffer(0, networkLinks.vertices); + glDrawArrays(mode, 0, static_cast<GLsizei>(count)); } }; } void -RailLinks::render(const SceneShader & shader) const +RailLinks::render(const SceneShader & shader, const Frustum &) const { - if (!links.objects.empty()) { - texture->bind(); - renderType<RailLinkStraight>(*this, shader.networkStraight); - renderType<RailLinkCurve>(*this, shader.networkCurve); + if (!links.empty()) { + glDebugScope _ {0}; + texture->bind(0); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1, 0); + renderType<RailLinkStraight>(*this, shader.networkStraight, GL_POINTS); + glPatchParameteri(GL_PATCH_VERTICES, 1); + renderType<RailLinkCurve>(*this, shader.networkCurve, GL_PATCHES); + 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..f27ac26 100644 --- a/game/scenary/foliage.cpp +++ b/game/scenary/foliage.cpp @@ -1,9 +1,34 @@ #include "foliage.h" +#include "gfx/frustum.h" +#include "gfx/gl/billboardPainter.h" #include "gfx/gl/sceneShader.h" #include "gfx/gl/shadowMapper.h" -#include "gfx/gl/vertexArrayObject.h" -#include "gfx/models/texture.h" -#include "location.h" +#include "gfx/gl/shadowStenciller.h" +#include "util.h" +#include <location.h> + +static_assert(std::is_constructible_v<Foliage>); +constexpr float OBJECT_BILLBOARD_DIVISOR = 64; +constexpr float BILLBOARD_ANGLE_TOLERANCE = 250.F; // Radians per mm size +constexpr float ASSUMED_VIEWPORT = 1440; +constexpr float OVER_SAMPLE_MULTIPLIER = 2; // Use mesh until billboard 1/2 of rendered size + +namespace { + GLsizei + billboardTextureSizeForObject(RelativeDistance objectSize) + { + return static_cast<GLsizei>(std::pow(2, std::ceil(std::log2(objectSize / OBJECT_BILLBOARD_DIVISOR)))); + } +} + +std::weak_ptr<glVertexArray> Foliage::commonInstanceVAO, Foliage::commonInstancePointVAO; + +std::any +Foliage::createAt(const Location & position) const +{ + return std::make_shared<InstanceVertices<InstanceVertex>::InstanceProxy>( + instances.acquire(locationData->acquire(position))); +} bool Foliage::persist(Persistence::PersistenceStore & store) @@ -15,30 +40,117 @@ void Foliage::postLoad() { texture = getTexture(); - bodyMesh->configureVAO(instanceVAO) - .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1); + glDebugScope _ {0}; + if (createIfRequired(instanceVAO, commonInstanceVAO)) { + bodyMesh->configureVAO(*instanceVAO, 0).addAttribs<InstanceVertex, &InstanceVertex::location>(1); + } + if (createIfRequired(instancePointVAO, commonInstancePointVAO)) { + instancePointVAO->configure().addAttribs<InstanceVertex, &InstanceVertex::location>(0); + } + const auto & size = bodyMesh->getDimensions().size; + billboardSize = billboardTextureSizeForObject(size); + ShadowStenciller::configureStencilTexture(shadowStencil, {billboardSize, billboardSize}); + BillboardPainter::configureBillBoardTextures(billboard, {billboardSize, billboardSize}); + useMeshClipDist = (ASSUMED_VIEWPORT * OVER_SAMPLE_MULTIPLIER * size) / static_cast<RelativeDistance>(billboardSize); } void -Foliage::render(const SceneShader & shader) const +Foliage::updateStencil(const ShadowStenciller & shadowStenciller) const { - if (const auto count = instances.size()) { - shader.basicInst.use(); - if (texture) { - texture->bind(); + if (instancePartitions.second.second != instancePartitions.second.first + && glm::distance(shadowStenciller.getLightDirection(), shadowStencilDir) + > BILLBOARD_ANGLE_TOLERANCE / bodyMesh->getDimensions().size) { + shadowStenciller.renderStencil(shadowStencil, *bodyMesh, texture); + } +} + +void +Foliage::updateBillboard(const BillboardPainter & bbp) const +{ + if (instancePartitions.first != instancePartitions.second.first + && std::abs(bbp.getAngle() - billboardAngle) > BILLBOARD_ANGLE_TOLERANCE / bodyMesh->getDimensions().size) { + bbp.renderBillBoard(billboard, *bodyMesh, texture); + billboardAngle = bbp.getAngle(); + } +} + +void +Foliage::preFrame(const Frustum & frustum, const Frustum & lighting) +{ + if (instances.size() > 0) { + const auto & dims = bodyMesh->getDimensions(); + instancePartitions = instances.partition( + [&frustum, &dims](const auto & instance) { + return frustum.contains(instance.location->position.xyz() + dims.centre, dims.size); + }, + [&frustum, this](const auto & instance) { + return distance(frustum.getPosition(), instance.location->position.xyz()) < useMeshClipDist; + }, + [&lighting, &dims](const auto & instance) { + return lighting.contains(instance.location->position.xyz() + dims.centre, dims.size); + }); + // In view frustum / Outside view frustum / + // Close to view / Far from view / Casts shadow into view / No shadow in view / + } +} + +void +Foliage::render(const SceneShader & shader, const Frustum &) const +{ + if (instancePartitions.first) { + glDebugScope _ {*instanceVAO}; + std::ignore = instances.size(); + if (const auto count = instancePartitions.first - instancePartitions.second.first) { + glDebugScope _ {0, "Billboard"}; + const auto dimensions = bodyMesh->getDimensions(); + shader.billboard.use(dimensions.size, dimensions.centre); + billboard[0].bind(0); + billboard[1].bind(1); + billboard[2].bind(2); + glBindVertexArray(*instancePointVAO); + instancePointVAO->useBuffer(0, instances); + glDrawArrays(GL_POINTS, static_cast<GLint>(instancePartitions.second.first), static_cast<GLsizei>(count)); + glBindVertexArray(0); + } + if (const auto count = instancePartitions.second.first) { + glDebugScope _ {0, "Mesh"}; + shader.basicInst.use(); + if (texture) { + texture->bind(0); + } + instanceVAO->useBuffer(1, instances); + bodyMesh->drawInstanced(*instanceVAO, static_cast<GLsizei>(count)); } - bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count)); } } 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); + if (instancePartitions.second.second) { + glDebugScope _ {*instanceVAO}; + std::ignore = instances.size(); + if (const auto count = instancePartitions.second.second - instancePartitions.second.first) { + glDebugScope _ {0, "Billboard"}; + const auto dimensions = bodyMesh->getDimensions(); + mapper.stencilShadowProgram.use(dimensions.centre, dimensions.size); + shadowStencil.bind(0); + glBindVertexArray(*instancePointVAO); + instancePointVAO->useBuffer(0, instances); + glDrawArrays(GL_POINTS, static_cast<GLint>(instancePartitions.second.first), static_cast<GLsizei>(count)); + glBindVertexArray(0); + } + if (const auto count = instancePartitions.second.first) { + glDebugScope _ {0, "Mesh"}; + if (texture) { + texture->bind(3); + mapper.dynamicPointInstWithTextures.use(); + } + else { + mapper.dynamicPointInst.use(); + } + instanceVAO->useBuffer(1, instances); + bodyMesh->drawInstanced(*instanceVAO, static_cast<GLsizei>(count)); } - bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count)); } } diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h index 3beda89..f424ffc 100644 --- a/game/scenary/foliage.h +++ b/game/scenary/foliage.h @@ -2,26 +2,46 @@ #include "assetFactory/asset.h" #include "gfx/gl/instanceVertices.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; - glVertexArray instanceVAO; + Texture::Ptr texture; + std::shared_ptr<glVertexArray> instanceVAO, instancePointVAO; + static std::weak_ptr<glVertexArray> commonInstanceVAO, commonInstancePointVAO; public: - using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>; - mutable InstanceVertices<LocationVertex> instances; - void render(const SceneShader &) const override; - void shadows(const ShadowMapper &) const override; + [[nodiscard]] std::any createAt(const Location &) const override; + + struct InstanceVertex { + CommonLocationInstance location; + // float scale; + // something colorBias; + }; + + mutable InstanceVertices<InstanceVertex> instances; + void preFrame(const Frustum &, const Frustum &) override; + void render(const SceneShader &, const Frustum &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; + void updateStencil(const ShadowStenciller &) const override; + void updateBillboard(const BillboardPainter &) const override; protected: friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>; bool persist(Persistence::PersistenceStore & store) override; void postLoad() override; + GLsizei billboardSize {}; + RelativeDistance useMeshClipDist {}; + mutable Direction2D shadowStencilDir {std::numeric_limits<Direction2D::value_type>::infinity()}; + glTexture<GL_TEXTURE_2D_ARRAY> shadowStencil; + mutable Angle billboardAngle = std::numeric_limits<Angle>::infinity(); + glTextures<GL_TEXTURE_2D_ARRAY, 3> billboard; + +private: + InstanceVertices<InstanceVertex>::PartitionResult instancePartitions; }; diff --git a/game/scenary/illuminator.cpp b/game/scenary/illuminator.cpp index e3810ec..7f0c7c2 100644 --- a/game/scenary/illuminator.cpp +++ b/game/scenary/illuminator.cpp @@ -1,19 +1,18 @@ #include "illuminator.h" #include "gfx/gl/sceneShader.h" -#include "gfx/gl/vertexArrayObject.h" #include "gfx/models/texture.h" // IWYU pragma: keep +#include "util.h" +#include <location.h> -bool -Illuminator::SpotLight::persist(Persistence::PersistenceStore & store) -{ - return STORE_TYPE && STORE_MEMBER(position) && STORE_MEMBER(direction) && STORE_MEMBER(colour) && STORE_MEMBER(kq) - && STORE_MEMBER(arc); -} +static_assert(std::is_constructible_v<Illuminator>); -bool -Illuminator::PointLight::persist(Persistence::PersistenceStore & store) +std::weak_ptr<glVertexArray> Illuminator::commonInstanceVAO; + +std::any +Illuminator::createAt(const Location & position) const { - return STORE_TYPE && STORE_MEMBER(position) && STORE_MEMBER(colour) && STORE_MEMBER(kq); + return std::make_shared<InstanceVertices<InstanceVertex>::InstanceProxy>( + instances.acquire(locationData->acquire(position))); } bool @@ -31,60 +30,22 @@ Illuminator::postLoad() throw std::logic_error {"Illuminator has no lights"}; } texture = getTexture(); - bodyMesh->configureVAO(instanceVAO) - .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1); - if (!spotLight.empty()) { - instancesSpotLightVAO.emplace(); - VertexArrayObject {*instancesSpotLightVAO} - .addAttribs<SpotLightVertex, &SpotLightVertex::position, &SpotLightVertex::direction, - &SpotLightVertex::colour, &SpotLightVertex::kq, &SpotLightVertex::arc>( - instancesSpotLight.bufferName(), 0) - .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1); - std::transform( - spotLight.begin(), spotLight.end(), std::back_inserter(spotLightInstances), [this](const auto & s) { - return instancesSpotLight.acquire(*s); - }); - } - if (!pointLight.empty()) { - instancesPointLightVAO.emplace(); - VertexArrayObject {*instancesPointLightVAO} - .addAttribs<PointLightVertex, &PointLightVertex::position, &PointLightVertex::colour, - &PointLightVertex::kq>(instancesPointLight.bufferName(), 0) - .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1); - std::transform( - pointLight.begin(), pointLight.end(), std::back_inserter(pointLightInstances), [this](const auto & s) { - return instancesPointLight.acquire(*s); - }); + glDebugScope _ {0}; + if (createIfRequired(instanceVAO, commonInstanceVAO)) { + bodyMesh->configureVAO(*instanceVAO, 0).addAttribs<InstanceVertex, &InstanceVertex::location>(1); } } void -Illuminator::render(const SceneShader & shader) const +Illuminator::render(const SceneShader & shader, const Frustum &) const { if (const auto count = instances.size()) { + glDebugScope _ {*instanceVAO}; shader.basicInst.use(); if (texture) { - texture->bind(); + texture->bind(0); } - bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count)); - } -} - -void -Illuminator::lights(const SceneShader & shader) const -{ - if (const auto count = instances.size()) { - if (const auto scount = instancesSpotLight.size()) { - shader.spotLightInst.use(); - glBindVertexArray(*instancesSpotLightVAO); - glDrawArraysInstanced(GL_POINTS, 0, static_cast<GLsizei>(scount), static_cast<GLsizei>(count)); - } - if (const auto pcount = instancesPointLight.size()) { - shader.pointLightInst.use(); - glBindVertexArray(*instancesPointLightVAO); - glDrawArraysInstanced(GL_POINTS, 0, static_cast<GLsizei>(pcount), static_cast<GLsizei>(count)); - } - - glBindVertexArray(0); + instanceVAO->useBuffer(1, instances); + bodyMesh->drawInstanced(*instanceVAO, static_cast<GLsizei>(count)); } } diff --git a/game/scenary/illuminator.h b/game/scenary/illuminator.h index cd6073c..2373812 100644 --- a/game/scenary/illuminator.h +++ b/game/scenary/illuminator.h @@ -1,60 +1,32 @@ #pragma once #include "assetFactory/asset.h" +#include "game/mixins/lights.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> { +class Illuminator : public Asset, public Renderable, public AssetLights, public StdTypeDefs<Illuminator> { Mesh::Ptr bodyMesh; - std::shared_ptr<Texture> texture; - glVertexArray instanceVAO; - std::optional<glVertexArray> instancesSpotLightVAO, instancesPointLightVAO; + Texture::Ptr texture; + std::shared_ptr<glVertexArray> instanceVAO; + static std::weak_ptr<glVertexArray> commonInstanceVAO; public: - struct LightCommonVertex { - RelativePosition3D position; - RGB colour; - RelativeDistance kq; - }; - - struct SpotLightVertex : LightCommonVertex { - Direction3D direction; - Angle arc; - }; - - struct PointLightVertex : LightCommonVertex { }; + [[nodiscard]] std::any createAt(const Location &) const override; - struct SpotLight : Persistence::Persistable, SpotLightVertex, StdTypeDefs<SpotLight> { - private: - friend Persistence::SelectionPtrBase<std::shared_ptr<SpotLight>>; - bool persist(Persistence::PersistenceStore & store) override; + struct InstanceVertex { + CommonLocationInstance location; }; - struct PointLight : Persistence::Persistable, PointLightVertex, StdTypeDefs<PointLight> { - private: - friend Persistence::SelectionPtrBase<std::shared_ptr<PointLight>>; - bool persist(Persistence::PersistenceStore & store) override; - }; - -public: - using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>; - mutable InstanceVertices<LocationVertex> instances; - mutable InstanceVertices<SpotLightVertex> instancesSpotLight; - mutable InstanceVertices<PointLightVertex> instancesPointLight; - void render(const SceneShader &) const override; - void lights(const SceneShader &) const override; + mutable InstanceVertices<InstanceVertex> instances; + void render(const SceneShader &, const Frustum &) const override; protected: friend Persistence::SelectionPtrBase<std::shared_ptr<Illuminator>>; bool persist(Persistence::PersistenceStore & store) override; void postLoad() override; - - std::vector<SpotLight::Ptr> spotLight; - std::vector<PointLight::Ptr> pointLight; - std::vector<InstanceVertices<SpotLightVertex>::InstanceProxy> spotLightInstances; - std::vector<InstanceVertices<PointLightVertex>::InstanceProxy> pointLightInstances; }; diff --git a/game/scenary/light.cpp b/game/scenary/light.cpp index 6207497..455d5b5 100644 --- a/game/scenary/light.cpp +++ b/game/scenary/light.cpp @@ -2,6 +2,8 @@ #include "location.h" Light::Light(std::shared_ptr<const Illuminator> type, const Location & position) : - type {std::move(type)}, location {this->type->instances.acquire(position.getRotationTransform(), position.pos)} + type {std::move(type)}, + instance {this->type->instances.acquire(Renderable::commonLocationData.lock()->acquire(position))} { + lightsEnable(this->type, instance->location.index); } diff --git a/game/scenary/light.h b/game/scenary/light.h index 0b19535..0b9320c 100644 --- a/game/scenary/light.h +++ b/game/scenary/light.h @@ -5,15 +5,18 @@ class Location; -class Light : public WorldObject { +class Light : public WorldObject, public InstanceLights { std::shared_ptr<const Illuminator> type; - InstanceVertices<Illuminator::LocationVertex>::InstanceProxy location; + InstanceVertices<Illuminator::InstanceVertex>::InstanceProxy instance; void tick(TickDuration) override { } + std::vector<InstanceVertices<SpotLightVertex>::InstanceProxy> spotLightInstances; + std::vector<InstanceVertices<PointLightVertex>::InstanceProxy> pointLightInstances; + public: Light(std::shared_ptr<const Illuminator> type, const Location & position); }; diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp index b39c28b..b0e7d16 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)}, + instance {this->type->instances.acquire(Renderable::commonLocationData.lock()->acquire(position))} { } diff --git a/game/scenary/plant.h b/game/scenary/plant.h index 77c9ff7..cc690c5 100644 --- a/game/scenary/plant.h +++ b/game/scenary/plant.h @@ -7,7 +7,7 @@ class Location; class Plant : public WorldObject { std::shared_ptr<const Foliage> type; - InstanceVertices<Foliage::LocationVertex>::InstanceProxy location; + InstanceVertices<Foliage::InstanceVertex>::InstanceProxy instance; void tick(TickDuration) override diff --git a/game/terrain.cpp b/game/terrain.cpp index 3b16e79..f3d7a7d 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -1,65 +1,111 @@ #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 <glMappedBufferSpan.h> #include <glm/glm.hpp> -#include <iterator> #include <location.h> #include <maths.h> #include <utility> #include <vector> -static constexpr RGB openSurface {-1}; +static constexpr RGB OPEN_SURFACE {-1}; +static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide -Terrain::Terrain(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, grass {std::make_shared<Texture>("grass.png")} +void +Terrain::initialise() { + glDebugScope _ {0}; + vertexArray.configure().addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal>( + 0, verticesBuffer); generateMeshes(); } -template<> -VertexArrayObject & -VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, const GLuint divisor) +bool +Terrain::SurfaceKey::operator<(const SurfaceKey & other) const +{ + return std::tie(surface, basePosition.x, basePosition.y) + < std::tie(other.surface, other.basePosition.x, other.basePosition.y); +} + +inline void +Terrain::copyVerticesToBuffer() const +{ + std::ranges::transform(all_vertices(), + glMappedBufferSpan<Vertex> {verticesBuffer, n_vertices(), GL_WRITE_ONLY, true}.begin(), + [this](const auto & vertex) { + return Vertex {point(vertex), normal(vertex)}; + }); +} + +inline GlobalPosition2D +Terrain::getTile(const FaceHandle & face) const +{ + return point(*cfv_begin(face)).xy() / TILE_SIZE; +}; + +Terrain::SurfaceIndices +Terrain::mapSurfaceFacesToIndices() const { - return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal, &Terrain::Vertex::colourBias>( - arrayBuffer, divisor); + 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::generateMeshes() +Terrain::copyIndicesToBuffers(const SurfaceIndices & surfaceIndices) { - 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(); + for (const auto & [surfaceKey, indices] : surfaceIndices) { + auto & mesh = meshes[surfaceKey]; + mesh.indicesBuffer.data(indices, GL_DYNAMIC_DRAW); + mesh.count = static_cast<GLsizei>(indices.size()); + mesh.aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints( + indices | std::views::transform([this](const auto vertex) { + return this->point(VertexHandle {static_cast<int>(vertex)}); + })); + } +} - 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))]; - }); - }); - meshes.create<MeshT<Vertex>>(vertices, indices); +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() +{ + glDebugScope _ {0}; + copyVerticesToBuffer(); + const auto surfaceIndices = mapSurfaceFacesToIndices(); + copyIndicesToBuffers(surfaceIndices); + pruneOrphanMeshes(surfaceIndices); } void @@ -68,16 +114,45 @@ Terrain::tick(TickDuration) } void -Terrain::render(const SceneShader & shader) const +Terrain::afterChange() { - shader.landmass.use(); - grass->bind(); - meshes.apply(&Mesh::Draw); + generateMeshes(); +} + +void +Terrain::render(const SceneShader & shader, const Frustum & frustum) const +{ + glDebugScope _ {0}; + glBindVertexArray(vertexArray); + grass->bind(0); + + 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)) { + glVertexArrayElementBuffer(vertexArray, sab.second.indicesBuffer); + 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 { + glDebugScope _ {0}; + glBindVertexArray(vertexArray); shadowMapper.landmess.use(); - meshes.apply(&Mesh::Draw); + for (const auto & [surface, sab] : meshes) { + if (frustum.contains(sab.aabb)) { + glVertexArrayElementBuffer(vertexArray, sab.indicesBuffer); + glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr); + } + } + glBindVertexArray(0); } diff --git a/game/terrain.h b/game/terrain.h index d088f89..f1170f2 100644 --- a/game/terrain.h +++ b/game/terrain.h @@ -1,36 +1,59 @@ #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)...} + { + initialise(); + } - 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 initialise(); + void afterChange() override; + + struct SurfaceArrayBuffer { + glBuffer indicesBuffer; + GLsizei count; + AxisAlignedBoundingBox<GlobalDistance> aabb; + }; + + glVertexArray vertexArray; + + 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..b5de833 100644 --- a/game/vehicles/railVehicle.cpp +++ b/game/vehicles/railVehicle.cpp @@ -11,48 +11,46 @@ #include <maths.h> #include <ray.h> -RailVehicle::RailVehicle(RailVehicleClassPtr rvc) : - RailVehicleClass::Instance {rvc->instances.acquire()}, rvClass {std::move(rvc)}, - location {[this](const BufferedLocation * l) { - this->get()->body = l->getRotationTransform(); - this->get()->bodyPos = l->position(); - }}, - bogies {{ - {[this](const BufferedLocation * l) { - this->get()->front = l->getRotationTransform(); - this->get()->frontPos = l->position(); - }, - GlobalPosition3D {0, rvClass->wheelBase / 2.F, 0}}, - {[this](const BufferedLocation * l) { - this->get()->back = l->getRotationTransform(); - this->get()->backPos = l->position(); - }, - GlobalPosition3D {0, -rvClass->wheelBase / 2.F, 0}}, - }} +RailVehicle::RailVehicle(RailVehicleClassPtr rvc, GlobalPosition3D position) : + RailVehicleClass::Instance {rvc->instances.acquire( + RailVehicleClass::commonLocationData.lock()->acquire(Location {.pos = position, .rot = {}}), + RailVehicleClass::commonLocationData.lock()->acquire( + Location {.pos = position + RelativePosition3D {0, rvc->wheelBase / 2.F, 0}, .rot = {}}), + RailVehicleClass::commonLocationData.lock()->acquire( + Location {.pos = position + RelativePosition3D {0, -rvc->wheelBase / 2.F, 0}, .rot = {}}))}, + rvClass {std::move(rvc)} { + lightsEnable(rvClass, get()->body.index); } void RailVehicle::move(const Train * t, float & trailBy) { const auto overhang {(rvClass->length - rvClass->wheelBase) / 2}; - const auto & b1Pos = bogies[0] = t->getBogiePosition(t->linkDist, trailBy += overhang); - const auto & b2Pos = bogies[1] = t->getBogiePosition(t->linkDist, trailBy += rvClass->wheelBase); - const auto diff = glm::normalize(RelativePosition3D(b2Pos.position() - b1Pos.position())); - location.setLocation((b1Pos.position() + b2Pos.position()) / 2, {vector_pitch(diff), vector_yaw(diff), 0}); + const auto & b1Pos = *(get()->front = t->getBogiePosition(t->linkDist, trailBy += overhang)); + const auto & b2Pos = *(get()->back = t->getBogiePosition(t->linkDist, trailBy += rvClass->wheelBase)); + const auto diff = glm::normalize(difference(b1Pos.position, b2Pos.position)); + get()->body = Location { + .pos = midpoint(b1Pos.position, b2Pos.position), .rot = {vector_pitch(diff), vector_yaw(diff), 0}}; trailBy += 600.F + overhang; } +Location +RailVehicle::getLocation() const +{ + return {.pos = get()->body->position, .rot = get()->body->rotation}; +} + bool RailVehicle::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & baryPos, RelativeDistance & distance) const { constexpr const auto X = 1350.F; const auto Y = this->rvClass->length / 2.F; constexpr const auto Z = 3900.F; - const glm::mat3 moveBy = location.getRotationTransform(); - const auto cornerVertices = cuboidCorners(-X, X, -Y, Y, 0.F, Z) * [&moveBy, this](const auto & corner) { - return location.position() + (moveBy * corner); - }; + const auto cornerVertices + = cuboidCorners(-X, X, -Y, Y, 0.F, Z) * [body = this->get()->body.get()](const auto & corner) { + return body->position + (body->rotationMatrix * corner); + }; static constexpr const std::array<glm::vec<3, uint8_t>, 10> triangles {{ // Front {0, 1, 2}, @@ -72,7 +70,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/railVehicle.h b/game/vehicles/railVehicle.h index bf1e782..c02c19e 100644 --- a/game/vehicles/railVehicle.h +++ b/game/vehicles/railVehicle.h @@ -1,8 +1,6 @@ #pragma once -#include "gfx/gl/bufferedLocation.h" #include "railVehicleClass.h" -#include <array> #include <game/selectable.h> #include <glm/glm.hpp> #include <memory> @@ -10,18 +8,16 @@ template<typename> class Ray; class Train; -class RailVehicle : Selectable, RailVehicleClass::Instance { +class RailVehicle : Selectable, RailVehicleClass::Instance, public InstanceLights { public: - explicit RailVehicle(RailVehicleClassPtr rvc); + explicit RailVehicle(RailVehicleClassPtr rvc, GlobalPosition3D = {}); void move(const Train *, float & trailBy); + [[nodiscard]] Location getLocation() const; [[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &, BaryPosition &, RelativeDistance &) const override; RailVehicleClassPtr rvClass; - using LV = RailVehicleClass::LocationVertex; - BufferedLocationUpdater location; - std::array<BufferedLocationUpdater, 2> bogies; }; using RailVehiclePtr = std::unique_ptr<RailVehicle>; diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp index 34c1359..cfdc52d 100644 --- a/game/vehicles/railVehicleClass.cpp +++ b/game/vehicles/railVehicleClass.cpp @@ -1,7 +1,6 @@ #include "railVehicleClass.h" #include "gfx/gl/sceneShader.h" #include "gfx/gl/shadowMapper.h" -#include "gfx/gl/vertexArrayObject.h" #include <array> #include <glm/glm.hpp> #include <lib/resource.h> @@ -13,46 +12,62 @@ bool RailVehicleClass::persist(Persistence::PersistenceStore & store) { return STORE_TYPE && STORE_MEMBER(length) && STORE_MEMBER(wheelBase) && STORE_MEMBER(maxSpeed) - && STORE_NAME_HELPER("bogie", bogies, Asset::MeshArrayConstruct) + && STORE_NAME_HELPER("bogie", bogies, Asset::MeshArrayConstruct) && AssetLights::persist(store) && STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store); } +std::any +RailVehicleClass::createAt(const Location & position) const +{ + return std::make_shared<InstanceVertices<InstanceVertex>::InstanceProxy>(instances.acquire(InstanceVertex { + .body = locationData->acquire(position), + .front = locationData->acquire(position + ((sincos(position.rot.x) * wheelBase * 0.5F) || 0.F)), + .back = locationData->acquire(position + ((sincos(position.rot.x) * wheelBase * -0.5F) || 0.F)), + })); +} + void RailVehicleClass::postLoad() { texture = getTexture(); - bodyMesh->configureVAO(instanceVAO) - .addAttribs<LocationVertex, &LocationVertex::body, &LocationVertex::bodyPos>(instances.bufferName(), 1); - bogies.front() - ->configureVAO(instancesBogiesVAO.front()) - .addAttribs<LocationVertex, &LocationVertex::front, &LocationVertex::frontPos>(instances.bufferName(), 1); - bogies.back() - ->configureVAO(instancesBogiesVAO.back()) - .addAttribs<LocationVertex, &LocationVertex::back, &LocationVertex::backPos>(instances.bufferName(), 1); - static_assert(sizeof(LocationVertex) == 144UL); + glDebugScope _ {0}; + bodyMesh->configureVAO(instanceVAO, 0).addAttribs<InstanceVertex, &InstanceVertex::body>(1); +} + +void +RailVehicleClass::renderAllParts(const size_t count) const +{ + using PartPair = std::pair<Mesh::Ptr, CommonLocationInstance InstanceVertex::*>; + const auto bufferName = instances.bufferName(); + for (const auto & [mesh, part] : { + PartPair {bodyMesh, &InstanceVertex::body}, + PartPair {bogies.front(), &InstanceVertex::front}, + PartPair {bogies.back(), &InstanceVertex::back}, + }) { + instanceVAO.useBuffer<InstanceVertex>(1, bufferName, part); + mesh->drawInstanced(instanceVAO, static_cast<GLsizei>(count)); + } } 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 (const auto count = (instances.size())) { + glDebugScope _ {instanceVAO}; if (texture) { - texture->bind(); + texture->bind(0); } shader.basicInst.use(); - bodyMesh->DrawInstanced(instanceVAO, count); - bogies.front()->DrawInstanced(instancesBogiesVAO.front(), count); - bogies.back()->DrawInstanced(instancesBogiesVAO.back(), count); + renderAllParts(count); } } void -RailVehicleClass::shadows(const ShadowMapper & mapper) const +RailVehicleClass::shadows(const ShadowMapper & mapper, const Frustum &) const { - if (const auto count = static_cast<GLsizei>(instances.size())) { + if (const auto count = instances.size()) { + glDebugScope _ {instanceVAO}; mapper.dynamicPointInst.use(); - bodyMesh->DrawInstanced(instanceVAO, count); - bogies.front()->DrawInstanced(instancesBogiesVAO.front(), count); - bogies.back()->DrawInstanced(instancesBogiesVAO.back(), count); + renderAllParts(count); } } diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h index 9d9d4c2..1ea87cd 100644 --- a/game/vehicles/railVehicleClass.h +++ b/game/vehicles/railVehicleClass.h @@ -1,46 +1,47 @@ #pragma once #include "assetFactory/asset.h" +#include "game/mixins/lights.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 { +class RailVehicleClass : public Renderable, public Asset, public AssetLights { 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; - struct LocationVertex { - glm::mat3 body, front, back; - GlobalPosition3D bodyPos, frontPos, backPos; + [[nodiscard]] std::any createAt(const Location &) const override; + + struct InstanceVertex { + CommonLocationInstance body, front, back; }; std::array<Mesh::Ptr, 2> bogies; Mesh::Ptr bodyMesh; - std::shared_ptr<Texture> texture; + Texture::Ptr texture; float wheelBase; float length; float maxSpeed; - mutable InstanceVertices<LocationVertex> instances; + mutable InstanceVertices<InstanceVertex> instances; using Instance = decltype(instances)::InstanceProxy; protected: friend Persistence::SelectionPtrBase<std::shared_ptr<RailVehicleClass>>; bool persist(Persistence::PersistenceStore & store) override; void postLoad() override; + void renderAllParts(size_t count) const; private: glVertexArray instanceVAO; - std::array<glVertexArray, 2> instancesBogiesVAO; }; using RailVehicleClassPtr = std::shared_ptr<RailVehicleClass>; diff --git a/game/vehicles/train.cpp b/game/vehicles/train.cpp index 5bddd61..c79fd17 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 { @@ -19,6 +21,12 @@ Train::getBogiePosition(float linkDist, float dist) const return b2Link.first->positionAt(b2linkDist, b2Link.second); } +Location +Train::getLocation() const +{ + return objects.front()->getLocation(); +} + bool Train::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & baryPos, RelativeDistance & distance) const { @@ -41,8 +49,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 +69,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..9ca53a8 100644 --- a/game/vehicles/train.h +++ b/game/vehicles/train.h @@ -15,16 +15,11 @@ 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} { } - - [[nodiscard]] const Location & - getLocation() const override - { - return objects.front()->location; - } + explicit Train(const Link::CPtr & link, float linkDist = 0) : Vehicle {link, linkDist} { } + [[nodiscard]] Location getLocation() const override; [[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &, BaryPosition &, RelativeDistance &) const override; void tick(TickDuration elapsed) 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..cca8ff0 100644 --- a/game/vehicles/vehicle.h +++ b/game/vehicles/vehicle.h @@ -14,11 +14,11 @@ 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) - [[nodiscard]] virtual const Location & getLocation() const = 0; + [[nodiscard]] virtual Location getLocation() const = 0; Orders orders; ActivityPtr currentActivity; diff --git a/game/water.cpp b/game/water.cpp index f720e3e..96f35cf 100644 --- a/game/water.cpp +++ b/game/water.cpp @@ -1,5 +1,6 @@ #include "water.h" #include "game/geoData.h" +#include "gfx/gl/gldebug.h" #include "gfx/models/texture.h" #include <algorithm> #include <cstddef> @@ -24,10 +25,10 @@ namespace glm { } template<> -VertexArrayObject & -VertexArrayObject::addAttribsFor<Water::Vertex>(const GLuint arrayBuffer, const GLuint divisor) +Impl::VertexArrayConfigurator & +Impl::VertexArrayConfigurator::addAttribsFor<Water::Vertex>(const GLuint divisor) { - return addAttribs<Water::Vertex, &Water::Vertex::pos>(arrayBuffer, divisor); + return addAttribs<Water::Vertex, &Water::Vertex::pos>(divisor); } Water::Water(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, water {std::make_shared<Texture>("water.png")} @@ -42,6 +43,7 @@ static constexpr GlobalDistance BORDER = TILE_SIZE / 2; void Water::generateMeshes() { + glDebugScope _ {0}; // Map out where a water square needs to exist to cover all terrain faces with a low vertex std::set<GlobalPosition2D> waterPositions; std::for_each(geoData->vertices_sbegin(), geoData->vertices_end(), [this, &waterPositions](const auto vh) { @@ -82,7 +84,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,9 +104,10 @@ Water::tick(TickDuration dur) } void -Water::render(const SceneShader & shader) const +Water::render(const SceneShader & shader, const Frustum &) const { + glDebugScope _ {0}; shader.water.use(waveCycle); - water->bind(); - meshes.apply(&MeshT<GlobalPosition3D>::Draw); + water->bind(0); + meshes.apply(&MeshT<GlobalPosition3D>::draw); } 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..4d46810 100644 --- a/gfx/gl/camera.cpp +++ b/gfx/camera.cpp @@ -4,28 +4,45 @@ #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, RelativeDistance near, RelativeDistance 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, RelativeDistance near, RelativeDistance 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(); +} + +Angle +Camera::getAspect() const +{ + return aspect; } 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 @@ -47,9 +64,8 @@ Camera::extentsAtDist(const GlobalDistance dist) const } return {target, dist}; }; - const auto depth = -(2.F * (static_cast<float>(dist - near)) * static_cast<float>(far)) - / (static_cast<float>(dist) * (static_cast<float>(near - far))) - - 1.F; + const auto depth + = -(2.F * (static_cast<float>(dist) - near) * far) / (static_cast<float>(dist) * (near - far)) - 1.F; static constexpr const std::array extents {-1.F, 1.F}; static constexpr const auto cartesianExtents = extents * extents; return cartesianExtents * [&depth, this, &clampToSeaFloor](const auto & extent) { diff --git a/gfx/gl/camera.h b/gfx/camera.h index 8d53261..1be012b 100644 --- a/gfx/gl/camera.h +++ b/gfx/camera.h @@ -1,22 +1,20 @@ #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, RelativeDistance near, RelativeDistance far); [[nodiscard]] Ray<GlobalPosition3D> unProject(const ScreenRelCoord &) const; + void setAspect(Angle aspect); + Angle getAspect() const; + void setPosition(const GlobalPosition3D & p) { @@ -65,9 +63,21 @@ public: } [[nodiscard]] auto - getPosition() const + getNear() const { - return position; + return near; + } + + [[nodiscard]] auto + getFar() const + { + return far; + } + + [[nodiscard]] auto + getDepth() const + { + return far - near; } [[nodiscard]] std::array<GlobalPosition4D, 4> extentsAtDist(GlobalDistance) const; @@ -75,13 +85,12 @@ public: [[nodiscard]] static Direction3D upFromForward(const Direction3D & forward); private: + Camera(GlobalPosition3D position, Angle fov, Angle aspect, RelativeDistance near, RelativeDistance 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; + RelativeDistance near, far; }; diff --git a/gfx/followCameraController.cpp b/gfx/followCameraController.cpp index 52dfb35..c3a5d08 100644 --- a/gfx/followCameraController.cpp +++ b/gfx/followCameraController.cpp @@ -1,10 +1,9 @@ #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> -#include <memory> #include <tuple> #include <utility> @@ -15,7 +14,7 @@ FollowCameraController::updateCamera(Camera * camera) const { const auto [pos, rot] = [this]() { const auto t {target.lock()}; - return std::tie(t->getLocation().pos, t->getLocation().rot); + return std::make_pair(t->getLocation().pos, t->getLocation().rot); }(); switch (mode) { @@ -24,7 +23,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..294fe5d --- /dev/null +++ b/gfx/frustum.cpp @@ -0,0 +1,71 @@ +#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 +{ + 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 contains(corners, 0); +} + +bool +Frustum::contains(GlobalPosition3D point, RelativeDistance size) const +{ + return contains(std::array {RelativePosition4D {(point - position), 1.F}}, size); +} + +bool +Frustum::contains(const std::span<const RelativePosition4D> points, RelativeDistance size) const +{ + return std::ranges::none_of(planes, [&points, size](const auto & frustumPlane) { + return (std::ranges::all_of(points, [&frustumPlane, size](const auto & point) { + const auto distanceFromPlane = glm::dot(frustumPlane, point); + return distanceFromPlane < -size; + })); + }); +} + +void +Frustum::updateCache() +{ + viewProjection = projection * view; + inverseViewProjection = glm::inverse(viewProjection); + std::ranges::transform(PLANES | std::views::take(planes.size()), planes.begin(), + [vpt = glm::transpose(viewProjection)](const auto & idxs) { + const auto [idx, sgn] = idxs; + const auto plane = vpt[3] + (vpt[idx] * sgn); + const auto mag = glm::length(plane.xyz()); + return plane / mag; + }); +} diff --git a/gfx/frustum.h b/gfx/frustum.h new file mode 100644 index 0000000..5b7947b --- /dev/null +++ b/gfx/frustum.h @@ -0,0 +1,52 @@ +#pragma once + +#include "aabb.h" +#include "config/types.h" +#include <array> +#include <glm/mat4x4.hpp> +#include <span> + +class Frustum { +public: + Frustum(const GlobalPosition3D & pos, const glm::mat4 & view, const glm::mat4 & projection); + + [[nodiscard]] auto & + getFrustumPlanes() const + { + return planes; + } + + [[nodiscard]] auto & + getView() const + { + return view; + } + + [[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 contains(GlobalPosition3D, RelativeDistance size = 0) const; + +protected: + static constexpr size_t FACES = 5; + void updateCache(); + [[nodiscard]] bool contains(std::span<const RelativePosition4D>, RelativeDistance) const; + + GlobalPosition3D position; + glm::mat4 view, projection; + glm::mat4 viewProjection, inverseViewProjection; + std::array<glm::vec4, FACES> planes; +}; diff --git a/gfx/gl/billboardPainter.cpp b/gfx/gl/billboardPainter.cpp new file mode 100644 index 0000000..878e950 --- /dev/null +++ b/gfx/gl/billboardPainter.cpp @@ -0,0 +1,90 @@ +#include "billboardPainter.h" +#include "gldebug.h" +#include "maths.h" +#include <gfx/gl/shaders/billboardPainter-frag.h> +#include <gfx/gl/shaders/billboardPainter-geom.h> +#include <gfx/gl/shaders/billboardPainter-vert.h> +#include <stdexcept> + +const auto VIEWS = []<GLint... Ep>(std::integer_sequence<GLint, Ep...>) { + constexpr float STEP = two_pi / BillboardPainter::VIEW_ANGLES<decltype(two_pi)>; + return std::array {rotate_yaw<4>(Ep * STEP)...}; +}(std::make_integer_sequence<GLint, BillboardPainter::VIEW_ANGLES<GLint>>()); + +BillboardPainter::BillboardPainter() : + program {billboardPainter_vert, billboardPainter_geom, billboardPainter_frag}, angle {}, view {} +{ + glDebugScope _ {fbo}; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + static constexpr std::array<GLenum, 2> ATTACHMENTS {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; + glDrawBuffers(ATTACHMENTS.size(), ATTACHMENTS.data()); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glUseProgram(program); + glUniform(viewLoc, std::span<const glm::mat4> {VIEWS}); +} + +void +BillboardPainter::setView(const Angle newAngle, const glm::mat4 & newView) +{ + angle = newAngle; + view = newView; +} + +Angle +BillboardPainter::getAngle() const +{ + return angle; +} + +void +BillboardPainter::configureBillBoardTextures(glTextures<GL_TEXTURE_2D_ARRAY, 3> & textures, ImageDimensions size) +{ + glDebugScope _ {0}; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + const auto configuregdata = [size](Impl::glTexture<GL_TEXTURE_2D_ARRAY> & texture, const GLenum iformat) { + texture.storage(1, iformat, size || VIEW_ANGLES<GLsizei>); + texture.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + texture.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + texture.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + texture.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + }; + configuregdata(textures[0], GL_DEPTH_COMPONENT16); + configuregdata(textures[1], GL_RGB8_SNORM); + configuregdata(textures[2], GL_RGB5_A1); +} + +void +BillboardPainter::renderBillBoard(const glTextures<GL_TEXTURE_2D_ARRAY, 3> & billboard, const MeshBase & mesh, + const Texture::AnyPtr texture) const +{ + glDebugScope _ {fbo}; + glEnable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glCullFace(GL_BACK); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClearColor(0, 0, 0, 0); + fbo.texture(GL_DEPTH_ATTACHMENT, billboard[0]); + fbo.texture(GL_COLOR_ATTACHMENT0, billboard[1]); + fbo.texture(GL_COLOR_ATTACHMENT1, billboard[2]); + fbo.assertComplete(); + if (texture) { + texture->bind(0); + } + glUseProgram(program); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + const TextureDimensions billboardSize = billboard[0].getSize(); + glViewport(0, 0, billboardSize.x, billboardSize.y); + const auto & centre = mesh.getDimensions().centre; + const auto & size = mesh.getDimensions().size; + glUniform(viewProjectionLoc, + std::span<const glm::mat4> {VIEWS * + [extentsMat = glm::translate(glm::ortho(-size, size, -size, size, -size, size), -centre), this]( + const auto & view) { + return this->view * view * extentsMat; + }}); + mesh.draw(); +} diff --git a/gfx/gl/billboardPainter.h b/gfx/gl/billboardPainter.h new file mode 100644 index 0000000..617f431 --- /dev/null +++ b/gfx/gl/billboardPainter.h @@ -0,0 +1,29 @@ +#pragma once + +#include "gfx/models/mesh.h" +#include "gfx/models/texture.h" +#include "glFramebuffer.h" +#include "program.h" + +class LightDirection; + +class BillboardPainter { +public: + template<typename T> static constexpr T VIEW_ANGLES = 8; + + BillboardPainter(); + + static void configureBillBoardTextures(glTextures<GL_TEXTURE_2D_ARRAY, 3> &, ImageDimensions); + void setView(Angle angle, const glm::mat4 &); + [[nodiscard]] Angle getAngle() const; + void renderBillBoard(const glTextures<GL_TEXTURE_2D_ARRAY, 3> &, const MeshBase &, Texture::AnyPtr texture) const; + +private: + mutable glFramebuffer fbo; + Program program; + Program::RequiredUniformLocation viewProjectionLoc {program, "viewProjection"}; + Program::RequiredUniformLocation viewLoc {program, "view"}; + + Angle angle; + glm::mat4 view; +}; diff --git a/gfx/gl/bufferedLocation.cpp b/gfx/gl/bufferedLocation.cpp deleted file mode 100644 index f1bedfe..0000000 --- a/gfx/gl/bufferedLocation.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "bufferedLocation.h" -#include "location.h" -#include <glm/gtx/transform.hpp> - -BufferedLocation::BufferedLocation(GlobalPosition3D p, Rotation3D r) : BufferedLocation {Location {p, r}} { } - -BufferedLocation::BufferedLocation(const Location & l) : loc {l} { } - -BufferedLocation::operator const Location &() const -{ - return loc; -} - -BufferedLocation & -BufferedLocation::operator=(const Location & l) -{ - loc = l; - updateBuffer(); - return *this; -} - -GlobalPosition3D -BufferedLocation::position() const -{ - return loc.pos; -} - -Rotation3D -BufferedLocation::rotation() const -{ - return loc.rot; -} - -void -BufferedLocation::setPosition(GlobalPosition3D p, bool update) -{ - loc.pos = p; - if (update) { - updateBuffer(); - } -} - -void -BufferedLocation::setRotation(Rotation3D r, bool update) -{ - loc.rot = r; - if (update) { - updateBuffer(); - } -} - -void -BufferedLocation::setLocation(GlobalPosition3D p, Rotation3D r) -{ - loc.pos = p; - loc.rot = r; - updateBuffer(); -} - -glm::mat4 -BufferedLocation::getRotationTransform() const -{ - return loc.getRotationTransform(); -} - -void -BufferedLocationUpdater::updateBuffer() const -{ - onUpdate(this); -} diff --git a/gfx/gl/bufferedLocation.h b/gfx/gl/bufferedLocation.h deleted file mode 100644 index 87b957f..0000000 --- a/gfx/gl/bufferedLocation.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "location.h" -#include <functional> -#include <glm/mat4x4.hpp> -#include <glm/vec3.hpp> -#include <utility> - -class BufferedLocation { -public: - BufferedLocation(GlobalPosition3D = {}, Rotation3D = {}); - BufferedLocation(const Location &); - virtual ~BufferedLocation() = default; - - BufferedLocation & operator=(const Location &); - - operator const Location &() const; - - [[nodiscard]] GlobalPosition3D position() const; - [[nodiscard]] Rotation3D rotation() const; - void setPosition(GlobalPosition3D, bool update = true); - void setRotation(Rotation3D, bool update = true); - void setLocation(GlobalPosition3D, Rotation3D); - - [[nodiscard]] glm::mat4 getRotationTransform() const; - -private: - virtual void updateBuffer() const = 0; - - Location loc; -}; - -class BufferedLocationUpdater : public BufferedLocation { -public: - template<typename... LocationArgs> - BufferedLocationUpdater(std::function<void(const BufferedLocation *)> onUpdate, LocationArgs &&... t) : - BufferedLocation {std::forward<LocationArgs>(t)...}, onUpdate {std::move(onUpdate)} - { - updateBuffer(); - } - - using BufferedLocation::operator=; - -private: - void updateBuffer() const override; - - std::function<void(const BufferedLocation *)> onUpdate; -}; diff --git a/gfx/gl/glBuffer.h b/gfx/gl/glBuffer.h new file mode 100644 index 0000000..275276f --- /dev/null +++ b/gfx/gl/glBuffer.h @@ -0,0 +1,24 @@ +#pragma once + +#include "glArrays.h" + +namespace Impl { + struct glBuffer : Detail::glNamed { + void + storage(const std::ranges::contiguous_range auto & data, GLenum flags) + { + glNamedBufferStorage( + name, static_cast<GLsizeiptr>(data.size() * sizeof(decltype(*data.data()))), data.data(), flags); + } + + void + data(const std::ranges::contiguous_range auto & data, GLenum flags) + { + glNamedBufferData( + name, static_cast<GLsizeiptr>(data.size() * sizeof(decltype(*data.data()))), data.data(), flags); + } + }; +} + +template<size_t N> using glBuffers = glManagedArray<Impl::glBuffer, N, &glCreateBuffers, &glDeleteBuffers>; +using glBuffer = glManagedSingle<Impl::glBuffer, &glCreateBuffers, &glDeleteBuffers>; diff --git a/gfx/gl/glFramebuffer.cpp b/gfx/gl/glFramebuffer.cpp new file mode 100644 index 0000000..fb3290f --- /dev/null +++ b/gfx/gl/glFramebuffer.cpp @@ -0,0 +1,28 @@ +#include "glFramebuffer.h" +#include <stdexcept> + +void +Impl::glRenderbuffer::storage(const GLenum iformat, const ImageDimensions dims) +{ + glNamedRenderbufferStorage(name, iformat, dims.x, dims.y); +} + +void +Impl::glFramebuffer::buffer(const GLenum attachment, const Impl::glRenderbuffer & buffer) +{ + glNamedFramebufferRenderbuffer(name, attachment, GL_RENDERBUFFER, buffer); +} + +void +Impl::glFramebuffer::drawBuffers(const std::span<const GLenum> buffers) +{ + glNamedFramebufferDrawBuffers(name, static_cast<GLsizei>(buffers.size()), buffers.data()); +} + +void +Impl::glFramebuffer::assertComplete() const +{ + if (glCheckNamedFramebufferStatus(name, GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw std::runtime_error("Framebuffer not complete!"); + } +} diff --git a/gfx/gl/glFramebuffer.h b/gfx/gl/glFramebuffer.h new file mode 100644 index 0000000..0a4ec5a --- /dev/null +++ b/gfx/gl/glFramebuffer.h @@ -0,0 +1,40 @@ +#pragma once + +#include "config/types.h" +#include "glArrays.h" +#include "glTexture.h" + +namespace Impl { + struct glRenderbuffer : Detail::glNamed { + void storage(GLenum iformat, ImageDimensions); + }; + + struct glFramebuffer : Detail::glNamed { + template<GLenum Target> + void + texture(GLenum attachment, const glTexture<Target> & texture) + { + glNamedFramebufferTexture(name, attachment, texture, 0); + } + + void drawBuffers(std::span<const GLenum> buffers); + + template<std::convertible_to<GLenum>... Buffers> + void + drawBuffers(Buffers... buffers) + { + drawBuffers(std::array {static_cast<GLenum>(buffers)...}); + } + + void buffer(GLenum attachment, const glRenderbuffer &); + void assertComplete() const; + }; +} + +template<size_t N> +using glFramebuffers = glManagedArray<Impl::glFramebuffer, N, &glCreateFramebuffers, &glDeleteFramebuffers>; +using glFramebuffer = glManagedSingle<Impl::glFramebuffer, &glCreateFramebuffers, &glDeleteFramebuffers>; + +template<size_t N> +using glRenderbuffers = glManagedArray<Impl::glRenderbuffer, N, &glCreateRenderbuffers, &glDeleteRenderbuffers>; +using glRenderbuffer = glManagedSingle<Impl::glRenderbuffer, &glCreateRenderbuffers, &glDeleteRenderbuffers>; diff --git a/gfx/gl/glTexture.cpp b/gfx/gl/glTexture.cpp new file mode 100644 index 0000000..51a27ed --- /dev/null +++ b/gfx/gl/glTexture.cpp @@ -0,0 +1,195 @@ +#include "glTexture.h" +#include "config/types.h" +#include "filesystem.h" +#include "gfx/models/tga.h" +#include "maths.h" +#include <fcntl.h> +#include <ranges> +#include <sys/mman.h> + +void +Impl::glTextureBase::bind(const GLuint unit) const +{ + glBindTextureUnit(unit, name); +} + +void +Impl::glTextureBase::generateMipmap() +{ + glGenerateTextureMipmap(name); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +glm::vec<Dims, GLsizei> +Impl::glTextureDims<Dims>::getSize() const +{ + static constexpr std::array<GLenum, 3> PARAMS {GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_DEPTH}; + glm::vec<Dims, GLsizei> size {}; + for (auto [dim, param] : std::views::enumerate(PARAMS) | std::views::take(Dims)) { + glGetTextureLevelParameteriv(name, 0, param, &size[static_cast<glm::length_t>(dim)]); + } + return size; +} + +template<> +void +Impl::glTextureDims<1>::storage(const GLsizei levels, const GLenum internalformat, glm::vec<1, GLsizei> dims) +{ + glTextureStorage1D(name, levels, internalformat, dims.x); +} + +template<> +void +Impl::glTextureDims<2>::storage(const GLsizei levels, const GLenum internalformat, glm::vec<2, GLsizei> dims) +{ + glTextureStorage2D(name, levels, internalformat, dims.x, dims.y); +} + +template<> +void +Impl::glTextureDims<3>::storage(const GLsizei levels, const GLenum internalformat, glm::vec<3, GLsizei> dims) +{ + glTextureStorage3D(name, levels, internalformat, dims.x, dims.y, dims.z); +} + +template<> +void +Impl::glTextureDims<1>::subImage( + glm::vec<1, GLint> offset, glm::vec<1, GLint> size, const GLenum format, const GLenum type, const void * pixels) +{ + glTextureSubImage1D(name, 0, offset.x, size.x, format, type, pixels); +} + +template<> +void +Impl::glTextureDims<2>::subImage( + glm::vec<2, GLint> offset, glm::vec<2, GLint> size, const GLenum format, const GLenum type, const void * pixels) +{ + glTextureSubImage2D(name, 0, offset.x, offset.y, size.x, size.y, format, type, pixels); +} + +template<> +void +Impl::glTextureDims<3>::subImage( + glm::vec<3, GLint> offset, glm::vec<3, GLint> size, const GLenum format, const GLenum type, const void * pixels) +{ + glTextureSubImage3D(name, 0, offset.x, offset.y, offset.z, size.x, size.y, size.z, format, type, pixels); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::image( + glm::vec<Dims, GLint> size, const GLenum format, const GLenum type, const void * pixels) +{ + subImage({}, size, format, type, pixels); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::image(const GLenum format, const GLenum type, const void * pixels) +{ + image(getSize(), format, type, pixels); +} + +namespace { + template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) + size_t + areaOf(glm::vec<Dims, GLsizei> size) + { + size_t area = 1; + for (auto dim = 0; dim < Dims; ++dim) { + area *= static_cast<size_t>(size[dim]); + } + return area; + } + + template<glm::length_t Dims, glm::length_t channels> + requires(Dims >= 1 && Dims <= 3) + void + save(const Impl::glTextureDims<Dims> & texture, const GLenum format, const GLenum type, const char * path, + uint8_t tgaFormat) + { + const auto size = texture.getSize(); + const auto area = areaOf(size); + size_t dataSize = area * channels; + const size_t fileSize = dataSize + sizeof(TGAHead<channels>); + + filesystem::fh out {path, O_RDWR | O_CREAT, 0660}; + out.truncate(fileSize); + auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED); + auto outTga = tga.get<TGAHead<channels>>(); + *outTga = { + .format = tgaFormat, + .size = {size.x, (area / static_cast<size_t>(size.x))}, + }; + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glGetTextureImage(texture, 0, format, type, static_cast<GLsizei>(dataSize), outTga->data); + tga.msync(MS_ASYNC); + } +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::saveColour(const char * path) const +{ + save<Dims, 3>(*this, GL_BGR, GL_UNSIGNED_BYTE, path, 2); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::savePosition(const char * path) const +{ + const auto size = getSize(); + const auto area = areaOf(size); + size_t dataSize = area * sizeof(TGAHead<3>::PixelType); + const size_t fileSize = dataSize + sizeof(TGAHead<3>); + + filesystem::fh out {path, O_RDWR | O_CREAT, 0660}; + out.truncate(fileSize); + auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED); + auto outTga = tga.get<TGAHead<3>>(); + *outTga = { + .format = 2, + .size = {size.x, (area / static_cast<size_t>(size.x))}, + }; + glPixelStorei(GL_PACK_ALIGNMENT, 1); + std::vector<RelativePosition3D> raw {area}; + glGetTextureImage(name, 0, GL_BGR, GL_FLOAT, static_cast<GLsizei>(sizeof(GlobalPosition3D) * area), raw.data()); + using Comp = RelativePosition3D (*)(const RelativePosition3D &, const RelativePosition3D &); + auto notZero = std::views::filter([](const RelativePosition3D & pos) { + return pos != RelativePosition3D {}; + }); + const auto minPos = *std::ranges::fold_left_first(raw | notZero, static_cast<Comp>(&glm::min)); + const auto maxPos = *std::ranges::fold_left_first(raw | notZero, static_cast<Comp>(&glm::max)); + const auto rangePos = difference(maxPos, minPos); + std::ranges::transform(raw, outTga->data, [minPos, rangePos](const auto & pos) { + return 255.F * ((pos - minPos) / rangePos); + }); + tga.msync(MS_ASYNC); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::saveDepth(const char * path) const +{ + save<Dims, 1>(*this, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, path, 3); +} + +template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) +void +Impl::glTextureDims<Dims>::saveNormal(const char * path) const +{ + save<Dims, 3>(*this, GL_BGR, GL_BYTE, path, 2); +} + +template struct Impl::glTextureDims<1>; +template struct Impl::glTextureDims<2>; +template struct Impl::glTextureDims<3>; diff --git a/gfx/gl/glTexture.h b/gfx/gl/glTexture.h new file mode 100644 index 0000000..3472c4a --- /dev/null +++ b/gfx/gl/glTexture.h @@ -0,0 +1,72 @@ +#pragma once + +#include "glArrays.h" +#include "gl_traits.h" + +namespace Impl { + template<GLenum> struct TextureTargetTraits; + + template<GLenum Target> struct TextureTargetTraitsCommon { + static void + create(GLsizei count, GLuint * textures) + { + glCreateTextures(Target, count, textures); + } + }; + + template<> struct TextureTargetTraits<GL_TEXTURE_2D> : TextureTargetTraitsCommon<GL_TEXTURE_2D> { + constexpr static glm::length_t dims = 2; + }; + + template<> struct TextureTargetTraits<GL_TEXTURE_2D_ARRAY> : TextureTargetTraitsCommon<GL_TEXTURE_2D_ARRAY> { + constexpr static glm::length_t dims = 3; + }; + + template<> struct TextureTargetTraits<GL_TEXTURE_RECTANGLE> : TextureTargetTraitsCommon<GL_TEXTURE_RECTANGLE> { + constexpr static glm::length_t dims = 2; + }; + + struct glTextureBase : Detail::glNamed { + void bind(GLuint unit) const; + void generateMipmap(); + + template<has_glTextureParameter T> + void + parameter(GLenum pname, T param) + { + (*gl_traits<T>::glTextureParameterFunc)(name, pname, param); + } + + template<glm::length_t L, has_glTextureParameterv T, glm::qualifier Q> + void + parameter(GLenum pname, const glm::vec<L, T, Q> & param) + { + (*gl_traits<T>::glTextureParametervFunc)(name, pname, glm::value_ptr(param)); + } + }; + + template<glm::length_t Dims> + requires(Dims >= 1 && Dims <= 3) + struct glTextureDims : glTextureBase { + [[nodiscard]] glm::vec<Dims, GLsizei> getSize() const; + void storage(GLsizei levels, GLenum internalformat, glm::vec<Dims, GLsizei> dims); + void image(GLenum format, GLenum type, const void * pixels); + void image(glm::vec<Dims, GLint> size, GLenum format, GLenum type, const void * pixels); + void subImage(glm::vec<Dims, GLint> offset, glm::vec<Dims, GLint> size, GLenum format, GLenum type, + const void * pixels); + + void saveColour(const char * path) const; + void saveDepth(const char * path) const; + void saveNormal(const char * path) const; + void savePosition(const char * path) const; + }; + + template<GLenum Target> struct glTexture : glTextureDims<TextureTargetTraits<Target>::dims> { }; +} + +template<GLenum Target, size_t N> +using glTextures + = glManagedArray<Impl::glTexture<Target>, N, &Impl::TextureTargetTraits<Target>::create, &glDeleteTextures>; +template<GLenum Target> +using glTexture + = glManagedSingle<Impl::glTexture<Target>, &Impl::TextureTargetTraits<Target>::create, &glDeleteTextures>; diff --git a/gfx/gl/glVertexArray.h b/gfx/gl/glVertexArray.h new file mode 100644 index 0000000..4e8113f --- /dev/null +++ b/gfx/gl/glVertexArray.h @@ -0,0 +1,162 @@ +#pragma once + +#include "collections.h" +#include "glArrays.h" +#include "glBuffer.h" +#include "gl_traits.h" +#include "util.h" + +namespace Impl { + class VertexArrayConfigurator { + public: + template<typename M, typename T> struct MP { + constexpr MP(M T::* ptr) : ptr {ptr} { } + + constexpr + operator GLuint() const + { + constexpr static char dummy {}; + return static_cast<GLuint>(reinterpret_cast<const char *>(&(reinterpret_cast<const T *>(&dummy)->*ptr)) + - reinterpret_cast<const char *>(&dummy)); + } + + M T::* ptr; + }; + + explicit VertexArrayConfigurator(GLuint name) : name {name} { } + + VertexArrayConfigurator & + addIndices(const glBuffer & buffer) + { + glVertexArrayElementBuffer(name, buffer); + return *this; + } + + VertexArrayConfigurator & + addIndices(glBuffer & buffer, const SequentialCollection<GLuint> auto & indices) + { + buffer.storage(indices, 0); + return addIndices(buffer); + } + + // Customisation point + template<typename VertexT> VertexArrayConfigurator & addAttribsFor(GLuint divisor); + + template<typename VertexT> + VertexArrayConfigurator & + addAttribsFor(GLuint divisor, const glBuffer & buffer) + { + glVertexArrayVertexBuffer(name, binding, buffer, 0, sizeof(VertexT)); + return addAttribsFor<VertexT>(divisor); + } + + template<typename VertexT, auto... Attribs> + VertexArrayConfigurator & + addAttribs(const GLuint divisor) + { + configureAttribs<VertexT, Attribs...>(divisor); + return *this; + } + + template<typename VertexT, auto... Attribs> + VertexArrayConfigurator & + addAttribs(const GLuint divisor, const glBuffer & buffer) + { + glVertexArrayVertexBuffer(name, binding, buffer, 0, sizeof(VertexT)); + return addAttribs<VertexT, Attribs...>(divisor); + } + + template<typename VertexT, auto... Attribs> + VertexArrayConfigurator & + addAttribs(const GLuint divisor, glBuffer & buffer, const SequentialCollection<VertexT> auto & data) + { + buffer.storage(data, 0); + return addAttribs<VertexT, Attribs...>(divisor, buffer); + } + + private: + void + setPointerMeta(const GLuint usedAttribs) + { + while (attrib < usedAttribs) { + glEnableVertexArrayAttrib(name, attrib); + glVertexArrayAttribBinding(name, attrib++, binding); + } + } + + template<typename T> + void + setPointer(const GLuint offset) + { + setPointerMeta(attrib + gl_traits<T>::vertexArrayAttribFormat(name, attrib, offset)); + } + + template<typename VertexT, auto Attrib> + void + setPointer() + { + using Mbr = MemberValueType<Attrib>; + setPointer<Mbr>(MP<Mbr, VertexT> {Attrib}); + } + + template<typename VertexT, auto... Attribs> + void + configureAttribs(const GLuint divisor) + { + if constexpr (sizeof...(Attribs) == 0) { + setPointer<VertexT>(0); + } + else { + ((setPointer<VertexT, Attribs>()), ...); + } + glVertexArrayBindingDivisor(name, binding++, divisor); + } + + GLuint name; + GLuint binding = 0; + GLuint attrib = 0; + }; + + struct glVertexArray : Detail::glNamed { + VertexArrayConfigurator + configure() + { + return VertexArrayConfigurator {name}; + } + + template<typename glAllocated> + void + useBuffer(GLuint binding, const glAllocated & buffer, GLsizei offset = 0) const + requires requires { + { buffer.bufferName() } -> std::same_as<GLuint>; + } + { + using T = typename glAllocated::value_type; + useBuffer(binding, buffer.bufferName(), sizeof(T), offset); + } + + template<typename V> + void + useBuffer(GLuint binding, GLuint bufferName, auto V::* mbr) const + { + useBuffer(binding, bufferName, sizeof(V), VertexArrayConfigurator::MP {mbr}); + } + + template<typename V> + void + useBuffer(GLuint binding, GLuint bufferName, GLintptr offset = 0) const + { + useBuffer(binding, bufferName, sizeof(V), offset); + } + + void + useBuffer(GLuint binding, GLuint bufferName, GLsizei stride, GLintptr offset = 0) const + { + glVertexArrayVertexBuffer(name, binding, bufferName, offset, stride); + } + }; +} + +template<size_t N> +using glVertexArrays = glManagedArray<Impl::glVertexArray, N, &glCreateVertexArrays, &glDeleteVertexArrays>; +using glVertexArray = glManagedSingle<Impl::glVertexArray, &glCreateVertexArrays, &glDeleteVertexArrays>; diff --git a/gfx/gl/gldebug.cpp b/gfx/gl/gldebug.cpp new file mode 100644 index 0000000..518d4fb --- /dev/null +++ b/gfx/gl/gldebug.cpp @@ -0,0 +1,20 @@ +#if GLDEBUG == 2 +// Level 2 is out of line because its "complex" + +# include "gldebug.h" +# include <format> + +glDebugScope::glDebugScope(GLuint id, const std::source_location & location) +{ + const auto fullMsg = std::format("{} ({}:{})", location.function_name(), location.file_name(), location.line()); + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, id, static_cast<GLsizei>(fullMsg.length()), fullMsg.c_str()); +} + +glDebugScope::glDebugScope(GLuint id, const std::string_view msg, const std::source_location & location) +{ + const auto fullMsg + = std::format("{} @ {} ({}:{})", msg, location.function_name(), location.file_name(), location.line()); + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, id, static_cast<GLsizei>(fullMsg.length()), fullMsg.c_str()); +} + +#endif diff --git a/gfx/gl/gldebug.h b/gfx/gl/gldebug.h new file mode 100644 index 0000000..5cfd099 --- /dev/null +++ b/gfx/gl/gldebug.h @@ -0,0 +1,52 @@ +#pragma once +#ifndef GLDEBUG +# define GLDEBUG 0 +#endif + +#include "special_members.h" +#include <glad/gl.h> +#include <source_location> +#include <string_view> + +class [[nodiscard]] glDebugScope { +public: + explicit glDebugScope(GLuint id, const std::source_location & = std::source_location::current()); + explicit glDebugScope( + GLuint id, std::string_view msg, const std::source_location & = std::source_location::current()); + + ~glDebugScope(); + + constexpr + operator bool() const + { + return true; + } + + NO_MOVE(glDebugScope); + NO_COPY(glDebugScope); +}; + +#if GLDEBUG > 0 +inline glDebugScope::~glDebugScope() +{ + glPopDebugGroup(); +} +# if GLDEBUG == 1 +// Level 1 is inlined for performance because they're thin wrappers +inline glDebugScope::glDebugScope(GLuint id, const std::source_location & location) : + glDebugScope {id, location.function_name()} +{ +} + +inline glDebugScope::glDebugScope(GLuint id, const std::string_view msg, const std::source_location &) +{ + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, id, static_cast<GLsizei>(msg.length()), msg.data()); +} +# endif +#else +inline glDebugScope::glDebugScope(GLuint, const std::source_location &) { } + +inline glDebugScope::glDebugScope(GLuint, const std::string_view, const std::source_location &) { } + +inline glDebugScope::~glDebugScope() = default; +#endif diff --git a/gfx/gl/instanceVertices.h b/gfx/gl/instanceVertices.h index 28e11ee..9963a96 100644 --- a/gfx/gl/instanceVertices.h +++ b/gfx/gl/instanceVertices.h @@ -1,17 +1,20 @@ #pragma once -#include "glContainer.h" +#include "glAllocator.h" +#include <algorithm> #include <cassert> +#include <functional> #include <special_members.h> #include <utility> -template<typename T> class InstanceVertices : protected glContainer<T> { - using base = glContainer<T>; +template<typename T> class InstanceVertices : protected glVector<T> { + using base = glVector<T>; + using IndexT = uint32_t; public: class [[nodiscard]] InstanceProxy { public: - InstanceProxy(InstanceVertices * iv, std::size_t idx) : instances {iv}, index {idx} { } + InstanceProxy(InstanceVertices * iv, IndexT idx) : instances {iv}, index {idx} { } InstanceProxy(InstanceProxy && other) noexcept : instances {std::exchange(other.instances, nullptr)}, index {other.index} @@ -47,13 +50,15 @@ public: } // NOLINTNEXTLINE)hicpp-explicit-conversions - [[nodiscard]] operator T &() + [[nodiscard]] + operator T &() { return instances->lookup(index); } // NOLINTNEXTLINE)hicpp-explicit-conversions - [[nodiscard]] operator const T &() const + [[nodiscard]] + operator const T &() const { return instances->lookup(index); } @@ -94,9 +99,9 @@ public: return instances->lookup(index); } - private: + // private: InstanceVertices<T> * instances; - std::size_t index; + IndexT index; }; template<typename... Params> @@ -106,41 +111,91 @@ public: if (!unused.empty()) { auto idx = unused.back(); unused.pop_back(); - index[idx] = base::size(); + index[idx] = static_cast<IndexT>(base::size()); reverseIndex.emplace_back(idx); base::emplace_back(std::forward<Params>(params)...); return InstanceProxy {this, idx}; } - index.emplace_back(base::size()); - reverseIndex.push_back(base::size()); + index.emplace_back(static_cast<IndexT>(base::size())); + reverseIndex.push_back(static_cast<IndexT>(base::size())); base::emplace_back(std::forward<Params>(params)...); - return InstanceProxy {this, index.size() - 1}; + return InstanceProxy {this, static_cast<IndexT>(index.size() - 1)}; } - using base::bufferName; + [[nodiscard]] GLuint + bufferName() const + { + return base::begin().base().bufferName(); + } - [[nodiscard]] auto - size() const + [[nodiscard]] GLuint + indexBufferName() const { - base::unmap(); - return base::size(); + return index.begin().base().bufferName(); } + using typename base::value_type; + + using base::at; + using base::begin; + using base::cbegin; + using base::cend; + using base::crbegin; + using base::crend; + using base::end; + using base::rbegin; + using base::rend; + using base::size; + using base::operator[]; + using base::back; + using base::capacity; + using base::data; + using base::empty; + using base::front; + using base::reserve; + using base::shrink_to_fit; + template<typename Pred> - glContainer<T>::iterator + base::size_type partition(Pred pred) { - return partition(base::begin(), base::end(), pred); + return indexOf(partition(base::begin(), base::end(), pred)); + } + + using PartitionResult + = std::pair<typename base::size_type, std::pair<typename base::size_type, typename base::size_type>>; + + template<typename Pred1, typename Pred2> + PartitionResult + partition(Pred1 pred1, Pred2 pred2) + { + return partition(pred1, std::not_fn(pred2), pred2); + } + + template<typename Pred1, typename Pred2, typename Pred3> + PartitionResult + partition(Pred1 pred1, Pred2 pred2, Pred3 pred3) + { + auto boundary1 = partition(base::begin(), base::end(), pred1); + auto begin2 = partition(base::begin(), boundary1, pred2); + auto end2 = partition(boundary1, base::end(), pred3); + return {indexOf(boundary1), {indexOf(begin2), indexOf(end2)}}; } protected: - static constexpr auto npos = static_cast<size_t>(-1); + static constexpr auto npos = static_cast<IndexT>(-1); friend InstanceProxy; + base::size_type + indexOf(base::iterator iter) + { + return static_cast<base::size_type>(iter - base::begin()); + } + void - release(const size_t pidx) + release(const IndexT pidx) { - if (const size_t last = base::size() - 1; last != index[pidx]) { + if (const auto last = static_cast<IndexT>(base::size() - 1); last != index[pidx]) { lookup(pidx) = std::move(base::back()); const auto movedKey = reverseIndex[last]; index[movedKey] = std::exchange(index[pidx], npos); @@ -159,14 +214,14 @@ protected: } [[nodiscard]] T & - lookup(size_t pindex) + lookup(IndexT pindex) { return base::data()[index[pindex]]; } template<typename Pred> - glContainer<T>::iterator - partition(glContainer<T>::iterator first, glContainer<T>::iterator last, Pred pred) + base::iterator + partition(base::iterator first, base::iterator last, Pred pred) { while (first < last) { first = std::find_if_not(first, last, pred); @@ -183,8 +238,8 @@ protected: } // Index into buffer given to nth proxy - std::vector<size_t> index; - std::vector<size_t> reverseIndex; + glVector<IndexT> index; + std::vector<IndexT> reverseIndex; // List of free spaces in index - std::vector<size_t> unused; + std::vector<IndexT> unused; }; diff --git a/gfx/gl/program.cpp b/gfx/gl/program.cpp index 7287fde..da85456 100644 --- a/gfx/gl/program.cpp +++ b/gfx/gl/program.cpp @@ -1,5 +1,5 @@ #include "program.h" -#include "shader.h" +#include "gldebug.h" #include <format> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/transform.hpp> @@ -9,11 +9,12 @@ void Program::linkAndValidate() const { + glDebugScope _ {m_program}; 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 +23,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..2b06d2d 100644 --- a/gfx/gl/program.h +++ b/gfx/gl/program.h @@ -1,6 +1,7 @@ #pragma once -#include "shader.h" +#include "gldebug.h" +#include "shader.h" // IWYU pragma: export #include <glRef.h> #include <glad/gl.h> #include <glm/mat4x4.hpp> @@ -12,7 +13,11 @@ using ProgramRef = glRef<GLuint, &glCreateProgram, &glDeleteProgram>; class Program { public: - template<typename... S> explicit Program(const S &... srcs) + Program() = delete; + + template<typename... S> explicit Program(const S &... srcs) : Program {glDebugScope {0}, srcs...} { } + + template<typename... S> explicit Program(glDebugScope, const S &... srcs) { (glAttachShader(m_program, srcs.compile()), ...); linkAndValidate(); @@ -31,6 +36,12 @@ public: return location; } + explicit + operator bool() const + { + return location >= 0; + } + protected: GLint location; }; @@ -47,6 +58,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..e69885a 100644 --- a/gfx/gl/sceneProvider.h +++ b/gfx/gl/sceneProvider.h @@ -1,10 +1,13 @@ #pragma once +#include <functional> #include <special_members.h> class SceneRenderer; class ShadowMapper; class SceneShader; +class Frustum; +class Renderable; class SceneProvider { public: @@ -12,8 +15,10 @@ public: virtual ~SceneProvider() = default; DEFAULT_MOVE_COPY(SceneProvider); - virtual void content(const SceneShader &) const = 0; + using RenderableProcessor = std::function<void(Renderable *)>; + virtual void forEachRenderable(const RenderableProcessor &) 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..a809ed5 100644 --- a/gfx/gl/sceneRenderer.cpp +++ b/gfx/gl/sceneRenderer.cpp @@ -1,9 +1,10 @@ #include "sceneRenderer.h" #include "maths.h" -#include "vertexArrayObject.h" -#include <gfx/gl/shaders/fs-directionalLight.h> -#include <gfx/gl/shaders/fs-lighting.h> -#include <gfx/gl/shaders/vs-lighting.h> +#include "stream_support.h" +#include <gfx/gl/shaders/directionalLight-frag.h> +#include <gfx/gl/shaders/lighting-frag.h> +#include <gfx/gl/shaders/lighting-vert.h> +#include <gfx/renderable.h> #include <glm/gtc/type_ptr.hpp> static constexpr const std::array<const glm::i8vec4, 4> displayVAOdata {{ @@ -14,109 +15,154 @@ static constexpr const std::array<const glm::i8vec4, 4> displayVAOdata {{ {1, -1, 1, 0}, }}; -SceneRenderer::SceneRenderer(ScreenAbsCoord s, GLuint o) : +SceneRenderer::SceneRenderer(ScreenAbsCoord s, GLuint o) : SceneRenderer {s, o, glDebugScope {o}} { } + +SceneRenderer::SceneRenderer(ScreenAbsCoord s, GLuint o, glDebugScope) : camera {{-1250000, -1250000, 35.0F}, quarter_pi, ratio(s), 100, 10000000}, size {s}, output {o}, - lighting {lighting_vs, lighting_fs}, shadowMapper {{2048, 2048}} + lighting {lighting_vert, lighting_frag}, shadowMapper {{2048, 2048}} { shader.setViewPort({0, 0, size.x, size.y}); - VertexArrayObject {displayVAO}.addAttribs<glm::i8vec4>(displayVBO, displayVAOdata); + displayVAO.configure().addAttribs<glm::i8vec4>(0, displayVBO, displayVAOdata); - const auto configuregdata = [this](const GLuint data, const std::initializer_list<GLint> iformats, - const GLenum format, const GLenum attachment) { - glBindTexture(GL_TEXTURE_2D, data); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - for (const auto iformat : iformats) { - glTexImage2D(GL_TEXTURE_2D, 0, iformat, size.x, size.y, 0, format, GL_BYTE, nullptr); + gBuffer.drawBuffers(GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2); + gBufferIll.drawBuffers(GL_COLOR_ATTACHMENT0); + configureBuffers(); +} - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, data, 0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - return iformat; - } - } - throw std::runtime_error("Framebuffer could not be completed!"); +void +SceneRenderer::resize(ScreenAbsCoord newSize) +{ + glDebugScope _ {output}; + size = newSize; + shader.setViewPort({0, 0, size.x, size.y}); + camera.setAspect(ratio(size)); + + depth = {}; + gPosition = {}; + gNormal = {}; + gAlbedoSpec = {}; + gIllumination = {}; + configureBuffers(); +} + +void +SceneRenderer::configureBuffers() +{ + const auto configureAttachment = [this](glFramebuffer & fbo, glTexture<GL_TEXTURE_2D> & data, const GLenum iformat, + const GLenum attachment) { + data.storage(1, iformat, size); + data.parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + data.parameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + fbo.texture(attachment, data); }; - 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); - 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}; - glDrawBuffers(attachments.size(), attachments.data()); + configureAttachment(gBuffer, gPosition, GL_RGB32F, GL_COLOR_ATTACHMENT0); + configureAttachment(gBuffer, gNormal, GL_RGB8_SNORM, GL_COLOR_ATTACHMENT1); + configureAttachment(gBuffer, gAlbedoSpec, GL_RGB8, GL_COLOR_ATTACHMENT2); - glBindRenderbuffer(GL_RENDERBUFFER, depth); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.x, size.y); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); + depth.storage(GL_DEPTH_COMPONENT, size); + gBuffer.buffer(GL_DEPTH_ATTACHMENT, depth); + gBuffer.assertComplete(); - glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll); - configuregdata(gIllumination, {GL_RGB8}, GL_RGB, GL_COLOR_ATTACHMENT0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); + configureAttachment(gBufferIll, gIllumination, GL_RGB8, GL_COLOR_ATTACHMENT0); + gBufferIll.assertComplete(); +} - glBindFramebuffer(GL_FRAMEBUFFER, output); +void +SceneRenderer::preFrame(const SceneProvider & scene, const LightDirection lightDirection) +{ + glDebugScope _ {output}; + const auto lightView = shadowMapper.preFrame(lightDirection, camera); + billboardPainter.setView(std::asin(camera.getForward().z), camera.getView()); + scene.forEachRenderable([&lightView, this](Renderable * renderable) { + renderable->preFrame(camera, lightView); + renderable->updateBillboard(billboardPainter); + }); + if (auto cld = Renderable::commonLocationData.lock()) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cld->bufferName()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, cld->indexBufferName()); + } } void SceneRenderer::render(const SceneProvider & scene) const { + glDebugScope _ {output}; shader.setViewProjection(camera.getPosition(), camera.getViewProjection()); glViewport(0, 0, size.x, size.y); - // Geometry pass - glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); - glEnable(GL_BLEND); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_DEPTH_TEST); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - scene.content(shader); + if (glDebugScope _ {gBuffer, "Geometry/colour pass"}) { + // Geometry/colour pass - writes albedo, normal and position textures + glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); + glEnable(GL_BLEND); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_DEPTH_TEST); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_PROGRAM_POINT_SIZE); + scene.content(shader, camera); + } - // Illumination pass - 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); - scene.environment(shader, *this); - glDisable(GL_DEPTH_TEST); - scene.lights(shader); + if (glDebugScope _ {gBufferIll, "Environment pass"}) { + // Environment pass - + // * ambient - clears illumination texture - see setAmbientLight + // * directional - updates shadowMapper, reads normal and position, writes illumination - see + // setDirectionalLight + scene.environment(shader, *this); + } - // Lighting pass - glBindFramebuffer(GL_FRAMEBUFFER, output); - glViewport(0, 0, size.x, size.y); - glCullFace(GL_BACK); - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); - glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, gIllumination); - lighting.use(); - renderQuad(); + if (glDebugScope _ {gBufferIll, "Scene lighting pass"}) { + // Scene lights pass - + // * per light - reads normal and position, writes illumination + glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll); + glBlendFunc(GL_ONE, GL_ONE); + gPosition.bind(0); + gNormal.bind(1); + shadowMapper.bind(2); + glDisable(GL_DEPTH_TEST); + scene.lights(shader); + } + + if (glDebugScope _ {output, "Composition pass"}) { + // Composition pass - reads albedo and illumination, writes output + glBindFramebuffer(GL_FRAMEBUFFER, output); + glViewport(0, 0, size.x, size.y); + glCullFace(GL_BACK); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + gAlbedoSpec.bind(2); + gIllumination.bind(3); + lighting.use(); + renderQuad(); + } } void SceneRenderer::setAmbientLight(const RGB & colour) const { + glDebugScope _ {output}; glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll); glClearColor(colour.r, colour.g, colour.b, 1.0F); glClear(GL_COLOR_BUFFER_BIT); } 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) { + glDebugScope _ {output}; const auto lvp = shadowMapper.update(scene, direction, camera); glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll); + glBlendFunc(GL_ONE, GL_ONE); + gPosition.bind(0); + gNormal.bind(1); + shadowMapper.bind(2); glViewport(0, 0, size.x, size.y); dirLight.use(); - dirLight.setDirectionalLight(colour, direction, camera.getPosition(), lvp); + dirLight.setDirectionalLight(colour, direction.vector(), lvp); renderQuad(); } } @@ -129,22 +175,20 @@ SceneRenderer::renderQuad() const glBindVertexArray(0); } -SceneRenderer::DirectionalLightProgram::DirectionalLightProgram() : Program {lighting_vs, directionalLight_fs} { } +SceneRenderer::DirectionalLightProgram::DirectionalLightProgram() : Program {lighting_vert, directionalLight_frag} { } const auto toTextureSpaceMat = glm::translate(glm::identity<glm::mat4>(), glm::vec3 {0.5F}) * glm::scale(glm::identity<glm::mat4>(), glm::vec3 {0.5F}); void SceneRenderer::DirectionalLightProgram::setDirectionalLight( - const RGB & c, const Direction3D & d, const GlobalPosition3D & p, const std::span<const glm::mat4x4> lvp) const + const RGB & c, const Direction3D & d, const std::span<const glm::mat4x4> lvp) const { const auto toTextureSpace = [](const glm::mat4 & m) { return toTextureSpaceMat * m; }; glUniform(colourLoc, c); - const auto nd = glm::normalize(d); - glUniform(directionLoc, nd); - glUniform(lightPointLoc, p); + glUniform(directionLoc, d); 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..2901dea 100644 --- a/gfx/gl/sceneRenderer.h +++ b/gfx/gl/sceneRenderer.h @@ -1,31 +1,39 @@ #pragma once -#include "camera.h" -#include "glArrays.h" +#include "billboardPainter.h" +#include "gfx/gl/glFramebuffer.h" +#include "gfx/lightDirection.h" +#include "gldebug.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); + SceneRenderer(ScreenAbsCoord size, GLuint output); + SceneRenderer(ScreenAbsCoord size, GLuint output, glDebugScope); + void resize(ScreenAbsCoord size); + + void preFrame(const SceneProvider & scene, LightDirection lightDirection); 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; protected: void renderQuad() const; + void configureBuffers(); ScreenAbsCoord size; GLuint output; - glFrameBuffer gBuffer, gBufferIll; - glTexture gPosition, gNormal, gAlbedoSpec, gIllumination; - glRenderBuffer depth; + glFramebuffer gBuffer, gBufferIll; + glTexture<GL_TEXTURE_2D> gPosition, gNormal, gAlbedoSpec, gIllumination; + glRenderbuffer depth; class DeferredLightProgram : public Program { public: @@ -38,13 +46,11 @@ protected: DirectionalLightProgram(); using Program::use; - void setDirectionalLight( - const RGB &, const Direction3D &, const GlobalPosition3D &, const std::span<const glm::mat4x4>) const; + void setDirectionalLight(const RGB &, const Direction3D &, const std::span<const glm::mat4x4>) const; private: RequiredUniformLocation directionLoc {*this, "lightDirection"}; RequiredUniformLocation colourLoc {*this, "lightColour"}; - RequiredUniformLocation lightPointLoc {*this, "lightPoint"}; RequiredUniformLocation lightViewProjectionLoc {*this, "lightViewProjection"}; RequiredUniformLocation lightViewProjectionCountLoc {*this, "lightViewProjectionCount"}; }; @@ -55,4 +61,5 @@ protected: glBuffer displayVBO; SceneShader shader; ShadowMapper shadowMapper; + BillboardPainter billboardPainter; }; diff --git a/gfx/gl/sceneShader.cpp b/gfx/gl/sceneShader.cpp index 4cbccb3..302edda 100644 --- a/gfx/gl/sceneShader.cpp +++ b/gfx/gl/sceneShader.cpp @@ -1,24 +1,28 @@ #include "sceneShader.h" -#include <gfx/gl/shaders/fs-landmass.h> -#include <gfx/gl/shaders/fs-material.h> -#include <gfx/gl/shaders/fs-network.h> -#include <gfx/gl/shaders/fs-pointLight.h> -#include <gfx/gl/shaders/fs-spotLight.h> -#include <gfx/gl/shaders/fs-water.h> -#include <gfx/gl/shaders/gs-networkCurve.h> -#include <gfx/gl/shaders/gs-networkStraight.h> -#include <gfx/gl/shaders/gs-pointLight.h> -#include <gfx/gl/shaders/gs-spotLight.h> -#include <gfx/gl/shaders/vs-dynamicPoint.h> -#include <gfx/gl/shaders/vs-dynamicPointInst.h> -#include <gfx/gl/shaders/vs-fixedPoint.h> -#include <gfx/gl/shaders/vs-landmass.h> -#include <gfx/gl/shaders/vs-networkCurve.h> -#include <gfx/gl/shaders/vs-networkStraight.h> -#include <gfx/gl/shaders/vs-pointLight.h> -#include <gfx/gl/shaders/vs-spotLight.h> -#include <gfx/gl/shaders/vs-water.h> -#include <gfx/gl/vertexArrayObject.h> +#include <gfx/gl/shaders/billboard-frag.h> +#include <gfx/gl/shaders/billboard-vert.h> +#include <gfx/gl/shaders/dynamicPoint-vert.h> +#include <gfx/gl/shaders/dynamicPointInst-vert.h> +#include <gfx/gl/shaders/fixedPoint-vert.h> +#include <gfx/gl/shaders/landmass-frag.h> +#include <gfx/gl/shaders/landmass-vert.h> +#include <gfx/gl/shaders/material-frag.h> +#include <gfx/gl/shaders/network-frag.h> +#include <gfx/gl/shaders/networkCurve-geom.h> +#include <gfx/gl/shaders/networkCurve-tesc.h> +#include <gfx/gl/shaders/networkCurve-tese.h> +#include <gfx/gl/shaders/networkCurve-vert.h> +#include <gfx/gl/shaders/networkStraight-geom.h> +#include <gfx/gl/shaders/networkStraight-vert.h> +#include <gfx/gl/shaders/pointLight-frag.h> +#include <gfx/gl/shaders/pointLight-geom.h> +#include <gfx/gl/shaders/pointLight-vert.h> +#include <gfx/gl/shaders/spotLight-frag.h> +#include <gfx/gl/shaders/spotLight-geom.h> +#include <gfx/gl/shaders/spotLight-vert.h> +#include <gfx/gl/shaders/water-frag.h> +#include <gfx/gl/shaders/water-vert.h> +#include <gl_traits.h> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/transform.hpp> #include <location.h> @@ -28,17 +32,17 @@ inline void SceneShader::allPrograms(auto member, auto &&... ps) const { for (const auto & prog : std::initializer_list<const SceneProgram *> {&basic, &basicInst, &water, &landmass, - &absolute, &pointLightInst, &spotLightInst, &networkStraight, &networkCurve}) { + &absolute, &pointLightInst, &spotLightInst, &networkStraight, &networkCurve, &billboard}) { (prog->*member)(std::forward<decltype(ps)>(ps)...); } } 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}, - networkStraight {networkStraight_vs, networkStraight_gs, network_fs}, - networkCurve {networkCurve_vs, networkCurve_gs, network_fs} + basicInst {dynamicPointInst_vert, material_frag}, absolute {fixedPoint_vert, material_frag}, + spotLightInst {spotLight_vert, spotLight_geom, spotLight_frag}, + pointLightInst {pointLight_vert, pointLight_geom, pointLight_frag}, landmass {landmass_vert, landmass_frag}, + networkStraight {networkStraight_vert, networkStraight_geom, network_frag}, + networkCurve {networkCurve_vert, networkCurve_tesc, networkCurve_tese, networkCurve_geom, network_frag} { } @@ -65,13 +69,13 @@ SceneShader::SceneProgram::setViewProjection(const GlobalPosition3D & viewPoint, void SceneShader::SceneProgram::setViewPort(const ViewPort & viewPort) const { - if (viewPortLoc >= 0) { + if (viewPortLoc) { glUseProgram(*this); glUniform(viewPortLoc, viewPort); } } -SceneShader::BasicProgram::BasicProgram() : SceneProgram {dynamicPoint_vs, material_fs} { } +SceneShader::BasicProgram::BasicProgram() : SceneProgram {dynamicPoint_vert, material_frag} { } void SceneShader::BasicProgram::setModel(Location const & location) const @@ -87,6 +91,23 @@ SceneShader::BasicProgram::use(Location const & location) const setModel(location); } +SceneShader::BillboardProgram::BillboardProgram() : SceneProgram {billboard_vert, billboard_frag} { } + +void +SceneShader::BillboardProgram::use(RelativeDistance size, RelativePosition3D centre) const +{ + Program::use(); + glUniform(sizeLoc, size); + glUniform(centreLoc, centre); +} + +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 @@ -97,7 +118,7 @@ SceneShader::NetworkProgram::use( glUniform(profileLengthLoc, static_cast<GLuint>(profile.size())); } -SceneShader::WaterProgram::WaterProgram() : SceneProgram {water_vs, water_fs} { } +SceneShader::WaterProgram::WaterProgram() : SceneProgram {water_vert, water_frag} { } void SceneShader::WaterProgram::use(float waveCycle) const diff --git a/gfx/gl/sceneShader.h b/gfx/gl/sceneShader.h index 51f0e21..faba4a4 100644 --- a/gfx/gl/sceneShader.h +++ b/gfx/gl/sceneShader.h @@ -32,6 +32,17 @@ class SceneShader { RequiredUniformLocation modelPosLoc {*this, "modelPos"}; }; + class BillboardProgram : public SceneProgram { + public: + BillboardProgram(); + + void use(RelativeDistance size, RelativePosition3D centre) const; + + private: + RequiredUniformLocation sizeLoc {*this, "size"}; + RequiredUniformLocation centreLoc {*this, "centre"}; + }; + class AbsolutePosProgram : public SceneProgram { public: using Program::use; @@ -50,6 +61,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 +84,9 @@ public: BasicProgram basic; WaterProgram water; - AbsolutePosProgram basicInst, landmass, absolute, spotLightInst, pointLightInst; + AbsolutePosProgram basicInst, absolute, spotLightInst, pointLightInst; + BillboardProgram billboard; + 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..319726f 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> @@ -7,17 +8,51 @@ namespace { auto - getInt(GLenum e) + getInt(GLenum pname) { - GLint i {}; - glGetIntegerv(e, &i); - return std::to_string(i); + GLint data {}; + glGetIntegerv(pname, &data); + return std::to_string(data); } using LookUpFunction = std::string (*)(GLenum); - constexpr std::array<std::tuple<std::string_view, GLenum, LookUpFunction>, 1> LOOKUPS {{ + constexpr auto LOOKUPS = std::to_array<std::tuple<std::string_view, GLenum, LookUpFunction>>({ {"GL_MAX_GEOMETRY_OUTPUT_VERTICES", GL_MAX_GEOMETRY_OUTPUT_VERTICES, getInt}, - }}; + {"GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS", GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, 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 +64,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(), + while (const auto match = ctre::search<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; }); @@ -38,6 +73,9 @@ Shader::compile() const const auto & [name, pname, getFunction] = *lookup; textMod.replace(match.begin(), match.end(), getFunction(pname)); } + else { + throw std::domain_error(std::format("Unknown shader constant: {}", match.view())); + } } source(textMod.c_str(), static_cast<GLint>(textMod.length())); } @@ -46,31 +84,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); - } - else { - glGetShaderiv(shader, flag, &success); + if (getShaderParam(shader, GL_COMPILE_STATUS) == GL_FALSE) { + throw ShaderCompileError {shader, text}; } +} - 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/billboard.frag b/gfx/gl/shaders/billboard.frag new file mode 100644 index 0000000..d5610de --- /dev/null +++ b/gfx/gl/shaders/billboard.frag @@ -0,0 +1,29 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +const float tau = 6.28318531; + +layout(binding = 0) uniform sampler2DArray billboardDepth; +layout(binding = 1) uniform sampler2DArray billboardNormal; +layout(binding = 2) uniform sampler2DArray billboardAlbedo; +uniform mat4 viewProjection; +uniform float size; + +#include "materialOut.glsl" + +flat in vec3 ModelPos; +flat in float Yaw; +flat in float Depth; + +void +main() +{ + int viewAngle = int(round(8 * Yaw / tau)) % 8; + vec3 texel = vec3(gl_PointCoord * vec2(-1, 1) + vec2(1, 0), viewAngle); + gAlbedoSpec = texture(billboardAlbedo, texel); + if (gAlbedoSpec.a < 0.5) { + discard; + } + gPosition = ivec4(ModelPos + vec3(0, 0, size * 2 * (1 - gl_PointCoord.y)), 1); + gNormal = texture(billboardNormal, texel) * vec4(-1, -1, 1, 1); +} diff --git a/gfx/gl/shaders/billboard.vert b/gfx/gl/shaders/billboard.vert new file mode 100644 index 0000000..d6d3869 --- /dev/null +++ b/gfx/gl/shaders/billboard.vert @@ -0,0 +1,27 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "commonLocationData.glsl" + +uniform mat4 viewProjection; +uniform ivec4 viewPort; +uniform ivec3 viewPoint; +uniform vec3 centre; +uniform float size; + +layout(location = 0) in uint index; + +flat out vec3 ModelPos; +flat out float Yaw; +flat out float Depth; + +void +main() +{ + const ivec3 modelPos = locations[cldIndex[index]].position.xyz; + ModelPos = modelPos - viewPoint; + Yaw = locations[cldIndex[index]].rotation.x; + gl_Position = viewProjection * vec4(ModelPos + centre, 1); + Depth = gl_Position.w; + gl_PointSize = (viewPort.w * size * 2) / gl_Position.w; +} diff --git a/gfx/gl/shaders/billboardPainter.frag b/gfx/gl/shaders/billboardPainter.frag new file mode 100644 index 0000000..cba294f --- /dev/null +++ b/gfx/gl/shaders/billboardPainter.frag @@ -0,0 +1,23 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 0) uniform sampler2D textureAlbedo; +layout(location = 0) out vec4 bNormal; +layout(location = 1) out vec4 bAlbedoSpec; + +#include "materialCommon.glsl" +#include "materialDetail.glsl" +in vec2 gTexCoords; +in vec3 gNormal; +in vec4 gColour; +flat in MaterialDetail gMaterial; + +void +main() +{ + vec4 textureColour = getTextureColour(gMaterial, gTexCoords); + float opaque = step(0.5, mix(textureColour.a, 1, gColour.a)); + bNormal = vec4(gNormal, opaque); + gl_FragDepth = mix(1.0, gl_FragCoord.z, opaque); + bAlbedoSpec = mix(textureColour, vec4(gColour.rgb, 1), gColour.a); +} diff --git a/gfx/gl/shaders/billboardPainter.geom b/gfx/gl/shaders/billboardPainter.geom new file mode 100644 index 0000000..aa4ad81 --- /dev/null +++ b/gfx/gl/shaders/billboardPainter.geom @@ -0,0 +1,35 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "materialDetail.glsl" + +layout(triangles) in; +layout(triangle_strip, max_vertices = 24) out; + +uniform mat4 viewProjection[8]; +uniform mat4 view[8]; +in vec3 FragPos[]; +in vec2 TexCoords[]; +flat in MaterialDetail Material[]; +in vec3 Normal[]; +in vec4 Colour[]; +out vec2 gTexCoords; +out vec3 gNormal; +out vec4 gColour; +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); + gNormal = (view[gl_Layer] * vec4(Normal[v], 1)).xyz; + gTexCoords = TexCoords[v]; + gMaterial = Material[v]; + gColour = Colour[v]; + EmitVertex(); + } + EndPrimitive(); + } +} diff --git a/gfx/gl/shaders/billboardPainter.vert b/gfx/gl/shaders/billboardPainter.vert new file mode 100644 index 0000000..a2d0b2e --- /dev/null +++ b/gfx/gl/shaders/billboardPainter.vert @@ -0,0 +1,24 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 1) uniform usampler2DRect materialData; + +#include "getMaterialDetail.glsl" +#include "materialDetail.glsl" +#include "meshIn.glsl" + +out vec3 FragPos; +out vec2 TexCoords; +flat out MaterialDetail Material; +out vec3 Normal; +out vec4 Colour; + +void +main() +{ + TexCoords = texCoord; + Material = getMaterialDetail(material); + FragPos = position; + Colour = colour; + Normal = normal; +} diff --git a/gfx/gl/shaders/commonLocationData.glsl b/gfx/gl/shaders/commonLocationData.glsl new file mode 100644 index 0000000..4939b9b --- /dev/null +++ b/gfx/gl/shaders/commonLocationData.glsl @@ -0,0 +1,20 @@ +#ifndef COMMON_LOCATION_DATA_INCLUDED +#define COMMON_LOCATION_DATA_INCLUDED + +struct CommonLocationData { + ivec4 position; + vec4 rotation; + mat3x4 rotationMatrix; +}; + +layout(binding = 0, std430) restrict readonly buffer commonLocationData +{ + CommonLocationData locations[]; +}; + +layout(binding = 1, std430) restrict readonly buffer commonLocationDataIndex +{ + uint cldIndex[]; +}; + +#endif diff --git a/gfx/gl/shaders/commonPoint.glsl b/gfx/gl/shaders/commonPoint.glsl index 2d9e388..7c1a521 100644 --- a/gfx/gl/shaders/commonPoint.glsl +++ b/gfx/gl/shaders/commonPoint.glsl @@ -1,26 +1,18 @@ -layout(binding = 1) uniform usampler2DRect materialData; +#ifndef COMMON_POINT_INCLUDED +#define COMMON_POINT_INCLUDED -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() { - FragPos = (model * position) + modelPos; + FragPos = (model * position) + modelPos - viewPoint; TexCoords = texCoord; Normal = (model * normal); Colour = colour; Material = getMaterialDetail(material); - gl_Position = viewProjection * vec4(FragPos - viewPoint, 1); + gl_Position = viewProjection * vec4(FragPos, 1); } + +#endif diff --git a/gfx/gl/shaders/commonShadowPoint.gs b/gfx/gl/shaders/commonShadowPoint-geom.glsl index b99bd20..f373834 100644 --- a/gfx/gl/shaders/commonShadowPoint.gs +++ b/gfx/gl/shaders/commonShadowPoint-geom.glsl @@ -1,5 +1,5 @@ -#version 330 core -#extension GL_ARB_viewport_array : enable +#ifndef COMMON_SHADOW_POINT_GEOM_INCLUDED +#define COMMON_SHADOW_POINT_GEOM_INCLUDED uniform mat4 viewProjection[4]; uniform int viewProjections; @@ -7,7 +7,14 @@ in vec4 vworldPos[]; layout(triangles) in; layout(triangle_strip, max_vertices = 12) out; -ifdef(`TEXTURES', in vec2 vtexCoord[]; out vec2 texCoord;); +#ifdef TEXTURES +# include "materialDetail.glsl" + +in vec2 TexCoords[]; +out vec2 texCoord; +flat in MaterialDetail Material[]; +flat out MaterialDetail material; +#endif void main() @@ -17,9 +24,14 @@ 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]; + material = Material[v]; +#endif EmitVertex(); } EndPrimitive(); } } + +#endif diff --git a/gfx/gl/shaders/commonShadowPoint.geom b/gfx/gl/shaders/commonShadowPoint.geom new file mode 100644 index 0000000..519dc62 --- /dev/null +++ b/gfx/gl/shaders/commonShadowPoint.geom @@ -0,0 +1,4 @@ +#version 460 +#extension GL_ARB_shading_language_include : enable + +#include "commonShadowPoint-geom.glsl" diff --git a/gfx/gl/shaders/commonShadowPoint.glsl b/gfx/gl/shaders/commonShadowPoint.glsl index c4ea827..e0b71a8 100644 --- a/gfx/gl/shaders/commonShadowPoint.glsl +++ b/gfx/gl/shaders/commonShadowPoint.glsl @@ -1,11 +1,17 @@ -out vec4 vworldPos; +#ifndef COMMON_SHADOW_POINT_INCLUDED +#define COMMON_SHADOW_POINT_INCLUDED -ifdef(`TEXTURES', out vec2 vtexCoord;); +out vec4 vworldPos; void main() { vec3 worldPos = model * position; vworldPos = vec4(worldPos - viewPoint + modelPos, 1); - ifdef(`TEXTURES', vtexCoord = texCoord;); +#ifdef TEXTURES + TexCoords = texCoord; + Material = getMaterialDetail(material); +#endif } + +#endif diff --git a/gfx/gl/shaders/directionalLight.frag b/gfx/gl/shaders/directionalLight.frag new file mode 100644 index 0000000..5da1acd --- /dev/null +++ b/gfx/gl/shaders/directionalLight.frag @@ -0,0 +1,59 @@ +#version 460 core + +const int MAX_MAPS = 4; + +out vec3 FragColor; + +in vec2 TexCoords; + +layout(binding = 0) uniform sampler2D gPosition; +layout(binding = 1) uniform sampler2D gNormal; +layout(binding = 2) uniform sampler2DArray shadowMap; + +uniform vec3 lightDirection; +uniform vec3 lightColour; +uniform mat4 lightViewProjection[MAX_MAPS]; +uniform uint lightViewProjectionCount; + +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, vec2 texelSize) +{ + 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, texelSize); + if (inside > 0) { + return getShadow(positionInLightSpace, m, texelSize); + } + } + return 1.0; +} + +void +main() +{ + const vec4 Position = vec4(texture(gPosition, TexCoords).xyz, 1); + const vec3 Normal = texture(gNormal, TexCoords).rgb; + const float shaded = isShaded(Position); + FragColor = shaded * max(dot(-lightDirection, Normal) * lightColour, 0); +} diff --git a/gfx/gl/shaders/directionalLight.fs b/gfx/gl/shaders/directionalLight.fs deleted file mode 100644 index 24457b8..0000000 --- a/gfx/gl/shaders/directionalLight.fs +++ /dev/null @@ -1,50 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -const int MAX_MAPS = 4; - -out vec3 FragColor; - -in vec2 TexCoords; - -layout(binding = 0) uniform isampler2D gPosition; -layout(binding = 1) uniform sampler2D gNormal; -layout(binding = 2) uniform sampler2DArray shadowMap; - -uniform vec3 lightDirection; -uniform vec3 lightColour; -uniform ivec3 lightPoint; -uniform mat4 lightViewProjection[MAX_MAPS]; -uniform uint lightViewProjectionCount; - -const vec3 e1 = vec3(0, 0, 0), e2 = vec3(1, 1, 1); - -float -insideShadowCube(vec3 v) -{ - const vec3 s = step(e1, v) - step(e2, v); - return s.x * s.y * s.z; -} - -float -isShaded(vec4 Position) -{ - for (uint m = 0u; m < lightViewProjectionCount; m++) { - const vec3 PositionInLightSpace = (lightViewProjection[m] * Position).xyz; - const float inside = insideShadowCube(PositionInLightSpace); - if (inside > 0) { - const float lightSpaceDepth = texture(shadowMap, vec3(PositionInLightSpace.xy, m)).r; - return step(lightSpaceDepth, PositionInLightSpace.z); - } - } - return 0; -} - -void -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); -} diff --git a/gfx/gl/shaders/dynamicPoint.vert b/gfx/gl/shaders/dynamicPoint.vert new file mode 100644 index 0000000..fb4c7fd --- /dev/null +++ b/gfx/gl/shaders/dynamicPoint.vert @@ -0,0 +1,14 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 1) uniform usampler2DRect materialData; + +#include "materialInterface.glsl" +#include "meshIn.glsl" + +uniform mat4 viewProjection; +uniform ivec3 viewPoint; +uniform mat3 model; +uniform ivec3 modelPos; + +#include "commonPoint.glsl" diff --git a/gfx/gl/shaders/dynamicPoint.vs b/gfx/gl/shaders/dynamicPoint.vs deleted file mode 100644 index 7551688..0000000 --- a/gfx/gl/shaders/dynamicPoint.vs +++ /dev/null @@ -1,12 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -include(`meshIn.glsl') -include(`materialInterface.glsl') - -uniform mat4 viewProjection; -uniform ivec3 viewPoint; -uniform mat3 model; -uniform ivec3 modelPos; - -include(`commonPoint.glsl') diff --git a/gfx/gl/shaders/dynamicPointInst.vert b/gfx/gl/shaders/dynamicPointInst.vert new file mode 100644 index 0000000..7c50706 --- /dev/null +++ b/gfx/gl/shaders/dynamicPointInst.vert @@ -0,0 +1,16 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 1) uniform usampler2DRect materialData; + +#include "commonLocationData.glsl" +#include "materialInterface.glsl" +#include "meshIn.glsl" + +uniform mat4 viewProjection; +uniform ivec3 viewPoint; +layout(location = 5) in uint index; +mat3 model = mat3(locations[cldIndex[index]].rotationMatrix); +ivec3 modelPos = locations[cldIndex[index]].position.xyz; + +#include "commonPoint.glsl" diff --git a/gfx/gl/shaders/dynamicPointInst.vs b/gfx/gl/shaders/dynamicPointInst.vs deleted file mode 100644 index 69eab0c..0000000 --- a/gfx/gl/shaders/dynamicPointInst.vs +++ /dev/null @@ -1,12 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -include(`meshIn.glsl') -include(`materialInterface.glsl') - -uniform mat4 viewProjection; -uniform ivec3 viewPoint; -layout(location = 5) in mat3 model; -layout(location = 8) in ivec3 modelPos; - -include(`commonPoint.glsl') diff --git a/gfx/gl/shaders/fixedPoint.vert b/gfx/gl/shaders/fixedPoint.vert new file mode 100644 index 0000000..fa9cf67 --- /dev/null +++ b/gfx/gl/shaders/fixedPoint.vert @@ -0,0 +1,14 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 1) uniform usampler2DRect materialData; + +#include "materialInterface.glsl" +#include "meshIn.glsl" + +uniform mat4 viewProjection; +uniform ivec3 viewPoint; +const mat3 model = mat3(1); +const vec3 modelPos = ivec3(0); + +#include "commonPoint.glsl" diff --git a/gfx/gl/shaders/fixedPoint.vs b/gfx/gl/shaders/fixedPoint.vs deleted file mode 100644 index 5cfe9b3..0000000 --- a/gfx/gl/shaders/fixedPoint.vs +++ /dev/null @@ -1,12 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -include(`meshIn.glsl') -include(`materialInterface.glsl') - -uniform mat4 viewProjection; -uniform ivec3 viewPoint; -const mat3 model = mat3(1); -const vec3 modelPos = ivec3(0); - -include(`commonPoint.glsl') diff --git a/gfx/gl/shaders/getMaterialDetail.glsl b/gfx/gl/shaders/getMaterialDetail.glsl new file mode 100644 index 0000000..0169e4e --- /dev/null +++ b/gfx/gl/shaders/getMaterialDetail.glsl @@ -0,0 +1,19 @@ +#ifndef GET_MATERIAL_DETAIL_INCLUDED +#define GET_MATERIAL_DETAIL_INCLUDED + +#include "materialDetail.glsl" + +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)); +} + +#endif diff --git a/gfx/gl/shaders/landmass.fs b/gfx/gl/shaders/landmass.frag index 55e3c24..d0a2ce3 100644 --- a/gfx/gl/shaders/landmass.fs +++ b/gfx/gl/shaders/landmass.frag @@ -1,12 +1,14 @@ -#version 330 core +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "materialOut.glsl" -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 +37,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; @@ -67,7 +69,7 @@ main() } } - gPosition = ivec4(position, 1); + gPosition = vec4(FragPos, 1); gNormal = vec4(Normal, 1); gAlbedoSpec = vec4(color, 1); } diff --git a/gfx/gl/shaders/landmass.vs b/gfx/gl/shaders/landmass.vert index 9617cb9..91fc7f8 100644 --- a/gfx/gl/shaders/landmass.vs +++ b/gfx/gl/shaders/landmass.vert @@ -1,13 +1,10 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable +#version 460 core 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 +14,6 @@ main() { FragPos = position - viewPoint; Normal = normal; - ColourBias = colourBias; gl_Position = viewProjection * vec4(FragPos, 1); } diff --git a/gfx/gl/shaders/lighting.fs b/gfx/gl/shaders/lighting.frag index 4646b75..b5c6c8b 100644 --- a/gfx/gl/shaders/lighting.fs +++ b/gfx/gl/shaders/lighting.frag @@ -1,5 +1,4 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable +#version 460 core out vec3 FragColor; diff --git a/gfx/gl/shaders/lighting.vs b/gfx/gl/shaders/lighting.vert index e07cd0a..1046379 100644 --- a/gfx/gl/shaders/lighting.vs +++ b/gfx/gl/shaders/lighting.vert @@ -1,4 +1,4 @@ -#version 330 core +#version 460 core in ivec4 position; diff --git a/gfx/gl/shaders/material.frag b/gfx/gl/shaders/material.frag new file mode 100644 index 0000000..16ea6d1 --- /dev/null +++ b/gfx/gl/shaders/material.frag @@ -0,0 +1,19 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 0) uniform sampler2D textureAlbedo; + +#include "materialCommon.glsl" +#include "materialInterface.glsl" +#include "materialOut.glsl" + +void +main() +{ + vec4 textureColour = getTextureColour(Material, TexCoords); + float opaque = step(0.5, mix(textureColour.a, 1, Colour.a)); + gPosition = ivec4(FragPos, opaque); + gNormal = vec4(Normal, opaque); + gl_FragDepth = mix(1.0, gl_FragCoord.z, opaque); + gAlbedoSpec = mix(textureColour, vec4(Colour.rgb, 1), Colour.a); +} diff --git a/gfx/gl/shaders/material.fs b/gfx/gl/shaders/material.fs deleted file mode 100644 index 5b93707..0000000 --- a/gfx/gl/shaders/material.fs +++ /dev/null @@ -1,49 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -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); -} - -void -main() -{ - vec4 textureColour = getTextureColour(Material, TexCoords); - float opaque = step(0.5, mix(textureColour.a, 1, Colour.a)); - gPosition = ivec4(FragPos, opaque); - gNormal = vec4(Normal, opaque); - gl_FragDepth = mix(1.0, gl_FragCoord.z, opaque); - gAlbedoSpec = mix(textureColour, vec4(Colour.rgb, 1), Colour.a); -} diff --git a/gfx/gl/shaders/materialCommon.glsl b/gfx/gl/shaders/materialCommon.glsl new file mode 100644 index 0000000..f905e76 --- /dev/null +++ b/gfx/gl/shaders/materialCommon.glsl @@ -0,0 +1,40 @@ +#ifndef MATERIAL_COMMON_INCLUDED +#define MATERIAL_COMMON_INCLUDED + +#include "materialDetail.glsl" + +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.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); +} + +#endif diff --git a/gfx/gl/shaders/materialDetail.glsl b/gfx/gl/shaders/materialDetail.glsl new file mode 100644 index 0000000..873b343 --- /dev/null +++ b/gfx/gl/shaders/materialDetail.glsl @@ -0,0 +1,10 @@ +#ifndef MATERIAL_DETAIL_INCLUDED +#define MATERIAL_DETAIL_INCLUDED + +struct MaterialDetail { + vec2 textureOrigin; + vec2 textureSize; + uvec2 mapmode; +}; + +#endif diff --git a/gfx/gl/shaders/materialInterface.glsl b/gfx/gl/shaders/materialInterface.glsl index 3a4796b..926bd6c 100644 --- a/gfx/gl/shaders/materialInterface.glsl +++ b/gfx/gl/shaders/materialInterface.glsl @@ -1,13 +1,18 @@ -struct MaterialDetail { - vec2 textureOrigin; - vec2 textureSize; - uvec2 mapmode; -}; +#ifndef MATERIAL_INTERFACE_INCLUDED +#define MATERIAL_INTERFACE_INCLUDED -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; +#include "materialDetail.glsl" + +#ifdef GL_FRAGMENT_SHADER +# define INOUT in +#else +# define INOUT out +#endif + +INOUT vec3 FragPos; +INOUT vec2 TexCoords; +INOUT vec3 Normal; +INOUT vec4 Colour; +flat INOUT MaterialDetail Material; + +#endif diff --git a/gfx/gl/shaders/materialOut.glsl b/gfx/gl/shaders/materialOut.glsl index 846825e..928edf0 100644 --- a/gfx/gl/shaders/materialOut.glsl +++ b/gfx/gl/shaders/materialOut.glsl @@ -1,3 +1,8 @@ -layout(location = 0) out ivec4 gPosition; +#ifndef MATERIAL_OUT_INCLUDED +#define MATERIAL_OUT_INCLUDED + +layout(location = 0) out vec4 gPosition; layout(location = 1) out vec4 gNormal; layout(location = 2) out vec4 gAlbedoSpec; + +#endif diff --git a/gfx/gl/shaders/meshIn.glsl b/gfx/gl/shaders/meshIn.glsl index dd84a10..2a69391 100644 --- a/gfx/gl/shaders/meshIn.glsl +++ b/gfx/gl/shaders/meshIn.glsl @@ -1,5 +1,10 @@ +#ifndef MESH_OUT_INCLUDED +#define MESH_OUT_INCLUDED + layout(location = 0) in vec3 position; layout(location = 1) in vec2 texCoord; layout(location = 2) in vec3 normal; layout(location = 3) in vec4 colour; layout(location = 4) in uint material; + +#endif diff --git a/gfx/gl/shaders/network.fs b/gfx/gl/shaders/network.frag index 4e347b4..b7e24d2 100644 --- a/gfx/gl/shaders/network.fs +++ b/gfx/gl/shaders/network.frag @@ -1,17 +1,17 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "materialOut.glsl" -include(`materialOut.glsl') in vec3 rposition; in vec2 texCoord; layout(binding = 0) uniform sampler2D texture0; -uniform ivec3 viewPoint; void main() { - gPosition = ivec4(viewPoint + rposition, 1); + gPosition = vec4(rposition, 1); gNormal = vec4(0, 0, 1, 1); gAlbedoSpec = texture(texture0, texCoord); } diff --git a/gfx/gl/shaders/networkCommon.glsl b/gfx/gl/shaders/networkCommon.glsl index 0bc3c1c..faa95ec 100644 --- a/gfx/gl/shaders/networkCommon.glsl +++ b/gfx/gl/shaders/networkCommon.glsl @@ -1,3 +1,6 @@ +#ifndef NETWORK_COMMON_INCLUDED +#define NETWORK_COMMON_INCLUDED + uniform vec3[10] profile; uniform float[10] texturePos; uniform uint profileLength; @@ -12,32 +15,43 @@ out vec2 texCoord; out vec3 rposition; float +viewPointDist(const ivec3 position) +{ + return length(vec3(viewPoint - position)); +} + +float segDist(const ivec3 a, const ivec3 b) { - return min(distance(viewPoint, a), distance(viewPoint, b)); + return min(viewPointDist(a), viewPointDist(b)); } -ifelse( - TYPE, .gs, - // Begin: Geometry shader only function - void doVertex(const ivec3 end, const uint v, const float texY, const mat2 rot) { - ivec3 vpos = end + ivec3(rot * profile[v].xy, profile[v].z); - rposition = vpos - viewPoint; - gl_Position = viewProjection * vec4(rposition, 1); - texCoord = vec2(texturePos[v], texY); - EmitVertex(); - } +#ifdef GL_GEOMETRY_SHADER // Begin: Geometry shader only function - void doSeg(const float dist, const ivec3 apos, const ivec3 bpos, const float atexY, const float btexY, - const mat2 arot, const mat2 brot) { - if (dist < clipDistance) { - uint vstep = (dist < flatDistance) ? 1u : profileLength - 1u; - for (uint v = 0u; v < profileLength; v += vstep) { - doVertex(bpos, v, btexY, brot); - doVertex(apos, v, atexY, arot); - } - EndPrimitive(); - } +void +doVertex(const ivec3 end, const uint v, const float texY, const mat2 rot) +{ + ivec3 vpos = end + ivec3(rot * profile[v].xy, profile[v].z); + rposition = vpos - viewPoint; + gl_Position = viewProjection * vec4(rposition, 1); + texCoord = vec2(texturePos[v], texY); + EmitVertex(); +} + +void +doSeg(const float dist, const ivec3 apos, const ivec3 bpos, const float atexY, const float btexY, const mat2 arot, + const mat2 brot) +{ + if (dist < clipDistance) { + uint vstep = (dist < flatDistance) ? 1u : profileLength - 1u; + for (uint v = 0u; v < profileLength; v += vstep) { + doVertex(bpos, v, btexY, brot); + doVertex(apos, v, atexY, arot); } - // End: Geometry shader only function -) + EndPrimitive(); + } +} + +#endif // End: Geometry shader only function + +#endif diff --git a/gfx/gl/shaders/networkCurve.geom b/gfx/gl/shaders/networkCurve.geom new file mode 100644 index 0000000..a6bbd13 --- /dev/null +++ b/gfx/gl/shaders/networkCurve.geom @@ -0,0 +1,18 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(lines) in; +layout(triangle_strip, max_vertices = 10) out; + +flat in ivec3 pos[]; +flat in mat2 rot[]; +flat in float tpos[]; +flat in float dist[]; + +#include "networkCommon.glsl" + +void +main() +{ + doSeg(min(dist[0], dist[1]), pos[0], pos[1], tpos[0], tpos[1], rot[0], rot[1]); +} diff --git a/gfx/gl/shaders/networkCurve.gs b/gfx/gl/shaders/networkCurve.gs deleted file mode 100644 index 7cb6c42..0000000 --- a/gfx/gl/shaders/networkCurve.gs +++ /dev/null @@ -1,47 +0,0 @@ -#version 330 core - -flat in ivec3 apos[]; -flat in ivec3 bpos[]; -flat in ivec3 cpos[]; -flat in float reps[]; -flat in float aangle[]; -flat in float bangle[]; -flat in float radius[]; - -layout(points) in; -layout(triangle_strip, max_vertices = GL_MAX_GEOMETRY_OUTPUT_VERTICES) out; - -const mat2 rot = mat2(1); - -include(`networkCommon.glsl') - -mat2 -getRot(float angle) -{ - return mat2(cos(angle), sin(angle), -sin(angle), cos(angle)); -} - -void -main() -{ - float segs = clamp( - round(reps[0] * radius[0] / 1000), 4, floor(uint(GL_MAX_GEOMETRY_OUTPUT_VERTICES) / (profileLength * 2u))); - vec3 arcstep = vec3((bangle[0] - aangle[0]), // angle - reps[0], // texture - (bpos[0].z - apos[0].z)) // height - / segs; - - ivec3 prevPos = apos[0]; - mat2 prevRot = getRot(aangle[0]); - float prevTex = 0; - for (vec3 arc = arcstep; arc.y < reps[0] - 0.01; arc += arcstep) { - mat2 rot = getRot(arc.x + aangle[0]); - ivec3 pos = cpos[0] + ivec3(rot * vec2(radius[0], 0), arc.z); - float tex = arc.y; - doSeg(segDist(prevPos, pos), pos, prevPos, tex, prevTex, rot, prevRot); - prevPos = pos; - prevRot = rot; - prevTex = tex; - } - doSeg(segDist(prevPos, bpos[0]), bpos[0], prevPos, reps[0], prevTex, getRot(bangle[0]), prevRot); -} diff --git a/gfx/gl/shaders/networkCurve.tesc b/gfx/gl/shaders/networkCurve.tesc new file mode 100644 index 0000000..5a6e449 --- /dev/null +++ b/gfx/gl/shaders/networkCurve.tesc @@ -0,0 +1,37 @@ +#version 460 core + +layout(vertices = 1) out; + +flat in ivec3 pos[][2]; +flat in ivec2 cpos[]; +flat in float reps[]; +flat in float angles[][2]; +flat in float radius[]; + +flat out ivec3 c_pos[][2]; +flat out ivec2 c_cpos[]; +flat out float c_reps[]; +flat out float c_angles[][2]; +flat out float c_radius[]; + +float +segments() +{ + const float arc = angles[gl_InvocationID][0] - angles[gl_InvocationID][1]; + const float error = 100.; + const float diff = acos(1.f - (error / radius[gl_InvocationID])); + return clamp(arc / diff, arc, 180); +} + +void +main() +{ + c_pos[gl_InvocationID] = pos[gl_InvocationID]; + c_cpos[gl_InvocationID] = cpos[gl_InvocationID]; + c_reps[gl_InvocationID] = reps[gl_InvocationID]; + c_angles[gl_InvocationID] = angles[gl_InvocationID]; + c_radius[gl_InvocationID] = radius[gl_InvocationID]; + + gl_TessLevelOuter[0] = 1; + gl_TessLevelOuter[1] = segments(); +} diff --git a/gfx/gl/shaders/networkCurve.tese b/gfx/gl/shaders/networkCurve.tese new file mode 100644 index 0000000..1331776 --- /dev/null +++ b/gfx/gl/shaders/networkCurve.tese @@ -0,0 +1,46 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(isolines, equal_spacing, cw) in; + +flat in ivec3 c_pos[][2]; +flat in ivec2 c_cpos[]; +flat in float c_reps[]; +flat in float c_angles[][2]; +flat in float c_radius[]; + +flat out ivec3 pos; +flat out mat2 rot; +flat out float tpos; +flat out float dist; + +const float startTolerance = 1. / 200.; +const float endTolerance = 1. - startTolerance; + +#include "networkCommon.glsl" + +mat2 +getRot(float angle) +{ + return mat2(cos(angle), sin(angle), -sin(angle), cos(angle)); +} + +void +main() +{ + const float angle = mix(c_angles[0][1], c_angles[0][0], gl_TessCoord.x); + rot = getRot(angle); + if (gl_TessCoord.x < startTolerance) { + pos = c_pos[0][1]; + } + else if (gl_TessCoord.x > endTolerance) { + pos = c_pos[0][0]; + } + else { + const int height = int(mix(c_pos[0][1].z, c_pos[0][0].z, gl_TessCoord.x)); + pos = ivec3(c_cpos[0] + ivec2(rot * vec2(c_radius[0], 0)), height); + } + + tpos = c_reps[0] * gl_TessCoord.x; + dist = viewPointDist(pos); +} diff --git a/gfx/gl/shaders/networkCurve.vert b/gfx/gl/shaders/networkCurve.vert new file mode 100644 index 0000000..7e363d3 --- /dev/null +++ b/gfx/gl/shaders/networkCurve.vert @@ -0,0 +1,23 @@ +#version 460 core + +layout(location = 0) in ivec3 v_pos[2]; +layout(location = 2) in ivec3 v_centre; +layout(location = 3) in float v_reps; +layout(location = 4) in float v_angles[2]; +layout(location = 6) in float v_radius; + +flat out ivec3 pos[2]; +flat out ivec2 cpos; +flat out float reps; +flat out float angles[2]; +flat out float radius; + +void +main() +{ + pos = v_pos; + cpos = v_centre.xy; + reps = v_reps; + angles = v_angles; + radius = v_radius; +} diff --git a/gfx/gl/shaders/networkCurve.vs b/gfx/gl/shaders/networkCurve.vs deleted file mode 100644 index f51bb87..0000000 --- a/gfx/gl/shaders/networkCurve.vs +++ /dev/null @@ -1,29 +0,0 @@ -#version 330 core - -layout(location = 0) in ivec3 v_apos; -layout(location = 1) in ivec3 v_bpos; -layout(location = 2) in ivec3 v_centre; -layout(location = 3) in float v_reps; -layout(location = 4) in float v_aangle; -layout(location = 5) in float v_bangle; -layout(location = 6) in float v_radius; - -flat out ivec3 apos; -flat out ivec3 bpos; -flat out ivec3 cpos; -flat out float reps; -flat out float aangle; -flat out float bangle; -flat out float radius; - -void -main() -{ - apos = v_apos; - bpos = v_bpos; - cpos = v_centre; - reps = v_reps; - aangle = v_aangle; - bangle = v_bangle; - radius = v_radius; -} diff --git a/gfx/gl/shaders/networkStraight.geom b/gfx/gl/shaders/networkStraight.geom new file mode 100644 index 0000000..0aa029a --- /dev/null +++ b/gfx/gl/shaders/networkStraight.geom @@ -0,0 +1,18 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(points) in; +layout(triangle_strip, max_vertices = 10) out; + +flat in ivec3 pos[][2]; +flat in mat2 rot[]; +flat in float reps[]; +flat in float dist[]; + +#include "networkCommon.glsl" + +void +main() +{ + doSeg(dist[0], pos[0][0], pos[0][1], 0.f, reps[0], rot[0], rot[0]); +} diff --git a/gfx/gl/shaders/networkStraight.gs b/gfx/gl/shaders/networkStraight.gs deleted file mode 100644 index 51df5fb..0000000 --- a/gfx/gl/shaders/networkStraight.gs +++ /dev/null @@ -1,17 +0,0 @@ -#version 330 core - -flat in ivec3 apos[]; -flat in ivec3 bpos[]; -flat in mat2 rot[]; -flat in float reps[]; -flat in float dist[]; - -layout(points) in; -layout(triangle_strip, max_vertices = 10) out; -include(`networkCommon.glsl') - -void -main() -{ - doSeg(dist[0], apos[0], bpos[0], 0.f, reps[0], rot[0], rot[0]); -} diff --git a/gfx/gl/shaders/networkStraight.vert b/gfx/gl/shaders/networkStraight.vert new file mode 100644 index 0000000..ffd7deb --- /dev/null +++ b/gfx/gl/shaders/networkStraight.vert @@ -0,0 +1,22 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(location = 0) in ivec3 v_pos[2]; +layout(location = 2) in mat2 v_rot; +layout(location = 4) in float v_reps; + +flat out ivec3 pos[2]; +flat out mat2 rot; +flat out float reps; +flat out float dist; + +#include "networkCommon.glsl" + +void +main() +{ + pos = v_pos; + rot = v_rot; + reps = v_reps; + dist = segDist(v_pos[0], v_pos[1]); +} diff --git a/gfx/gl/shaders/networkStraight.vs b/gfx/gl/shaders/networkStraight.vs deleted file mode 100644 index 55f9c4f..0000000 --- a/gfx/gl/shaders/networkStraight.vs +++ /dev/null @@ -1,24 +0,0 @@ -#version 330 core - -layout(location = 0) in ivec3 v_apos; -layout(location = 1) in ivec3 v_bpos; -layout(location = 2) in mat2 v_rot; -layout(location = 4) in float v_reps; - -flat out ivec3 apos; -flat out ivec3 bpos; -flat out mat2 rot; -flat out float reps; -flat out float dist; - -include(`networkCommon.glsl') - -void -main() -{ - apos = v_apos; - bpos = v_bpos; - rot = v_rot; - reps = v_reps; - dist = segDist(v_apos, v_bpos); -} diff --git a/gfx/gl/shaders/pointLight.fs b/gfx/gl/shaders/pointLight.frag index 7531d3e..a141592 100644 --- a/gfx/gl/shaders/pointLight.fs +++ b/gfx/gl/shaders/pointLight.frag @@ -1,9 +1,8 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable +#version 460 core out vec3 FragColor; -layout(binding = 0) uniform isampler2D gPosition; +layout(binding = 0) uniform sampler2D gPosition; layout(binding = 1) uniform sampler2D gNormal; uniform ivec4 viewPort; flat in vec4 geo_centre; diff --git a/gfx/gl/shaders/pointLight.gs b/gfx/gl/shaders/pointLight.geom index fc1d7c3..1ee7e06 100644 --- a/gfx/gl/shaders/pointLight.gs +++ b/gfx/gl/shaders/pointLight.geom @@ -1,6 +1,4 @@ -#version 330 core -#extension GL_ARB_enhanced_layouts : enable -#extension GL_ARB_shading_language_420pack : enable +#version 460 core const vec3[] cube = vec3[]( // http://www.cs.umd.edu/gvil/papers/av_ts.pdf vec3(-1, 1, 1), // Front-top-left diff --git a/gfx/gl/shaders/pointLight.vert b/gfx/gl/shaders/pointLight.vert new file mode 100644 index 0000000..61953ad --- /dev/null +++ b/gfx/gl/shaders/pointLight.vert @@ -0,0 +1,28 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "commonLocationData.glsl" + +layout(location = 0) in vec3 v_position; +layout(location = 1) in vec3 v_colour; +layout(location = 2) in float v_kq; +layout(location = 3) in uint index; +mat3 model = mat3(locations[cldIndex[index]].rotationMatrix); +ivec3 modelPos = locations[cldIndex[index]].position.xyz; + +uniform ivec3 viewPoint; + +flat out vec3 position; +flat out vec3 colour; +flat out float size; +flat out float kq; + +void +main() +{ + position = (modelPos - viewPoint) + ivec3(mat3(model) * v_position); + kq = v_kq; + size = (8000 * sqrt(max(max(v_colour.r, v_colour.g), v_colour.b))) / sqrt(v_kq); + colour = v_colour; + gl_Position = vec4(position, 0); +} diff --git a/gfx/gl/shaders/pointLight.vs b/gfx/gl/shaders/pointLight.vs deleted file mode 100644 index fbd031c..0000000 --- a/gfx/gl/shaders/pointLight.vs +++ /dev/null @@ -1,24 +0,0 @@ -#version 330 core - -layout(location = 0) in vec3 v_position; -layout(location = 1) in vec3 v_colour; -layout(location = 2) in float v_kq; -layout(location = 3) in mat3 model; -layout(location = 6) in ivec3 modelPos; - -uniform ivec3 viewPoint; - -flat out vec3 position; -flat out vec3 colour; -flat out float size; -flat out float kq; - -void -main() -{ - position = modelPos + ivec3(mat3(model) * v_position); - kq = v_kq; - size = (8000 * sqrt(max(max(v_colour.r, v_colour.g), v_colour.b))) / sqrt(v_kq); - colour = v_colour; - gl_Position = vec4(position - viewPoint, 0); -} diff --git a/gfx/gl/shaders/shadowDynamicPoint.vert b/gfx/gl/shaders/shadowDynamicPoint.vert new file mode 100644 index 0000000..248b7b0 --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPoint.vert @@ -0,0 +1,10 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "meshIn.glsl" + +uniform ivec3 viewPoint; +uniform mat3 model; +uniform ivec3 modelPos; + +#include "commonShadowPoint.glsl" diff --git a/gfx/gl/shaders/shadowDynamicPoint.vs b/gfx/gl/shaders/shadowDynamicPoint.vs deleted file mode 100644 index 7335b9a..0000000 --- a/gfx/gl/shaders/shadowDynamicPoint.vs +++ /dev/null @@ -1,9 +0,0 @@ -#version 330 core - -include(`meshIn.glsl') - -uniform ivec3 viewPoint; -uniform mat3 model; -uniform ivec3 modelPos; - -include(`commonShadowPoint.glsl') diff --git a/gfx/gl/shaders/shadowDynamicPointInst.vert b/gfx/gl/shaders/shadowDynamicPointInst.vert new file mode 100644 index 0000000..b978e4a --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointInst.vert @@ -0,0 +1,12 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "commonLocationData.glsl" +#include "meshIn.glsl" + +uniform ivec3 viewPoint; +layout(location = 5) in uint index; +mat3 model = mat3(locations[cldIndex[index]].rotationMatrix); +ivec3 modelPos = locations[cldIndex[index]].position.xyz; + +#include "commonShadowPoint.glsl" diff --git a/gfx/gl/shaders/shadowDynamicPointInst.vs b/gfx/gl/shaders/shadowDynamicPointInst.vs deleted file mode 100644 index d0eb649..0000000 --- a/gfx/gl/shaders/shadowDynamicPointInst.vs +++ /dev/null @@ -1,9 +0,0 @@ -#version 330 core - -include(`meshIn.glsl') - -uniform ivec3 viewPoint; -layout(location = 5) in mat3 model; -layout(location = 8) in ivec3 modelPos; - -include(`commonShadowPoint.glsl') diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.frag b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.frag new file mode 100644 index 0000000..e9d83d2 --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.frag @@ -0,0 +1,19 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 3) uniform sampler2D textureAlbedo; + +#include "materialCommon.glsl" +#include "materialDetail.glsl" + +in vec2 texCoord; +flat in MaterialDetail material; + +void +main() +{ + if (getTextureColour(material, texCoord).a < 0.5) { + discard; + } + gl_FragDepth = gl_FragCoord.z; +} diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs deleted file mode 100644 index 90519e3..0000000 --- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.fs +++ /dev/null @@ -1,15 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -layout(binding = 3) uniform sampler2D texture0; - -in vec2 texCoord; - -void -main() -{ - if (texture(texture0, texCoord).a < 0.5) { - discard; - } - gl_FragDepth = gl_FragCoord.z; -} diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.geom b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.geom new file mode 100644 index 0000000..72e4075 --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.geom @@ -0,0 +1,5 @@ +#version 460 +#extension GL_ARB_shading_language_include : enable +#define TEXTURES + +#include "commonShadowPoint-geom.glsl" diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.gs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.gs deleted file mode 100644 index e6e213e..0000000 --- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.gs +++ /dev/null @@ -1,3 +0,0 @@ -define(`TEXTURES', 1) - -include(`commonShadowPoint.gs') diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vert b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vert new file mode 100644 index 0000000..a5e8245 --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vert @@ -0,0 +1,17 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable +#define TEXTURES + +layout(binding = 4) uniform usampler2DRect materialData; + +#include "commonLocationData.glsl" +#include "getMaterialDetail.glsl" +#include "materialInterface.glsl" +#include "meshIn.glsl" + +uniform ivec3 viewPoint; +layout(location = 5) in uint index; +mat3 model = mat3(locations[cldIndex[index]].rotationMatrix); +ivec3 modelPos = locations[cldIndex[index]].position.xyz; + +#include "commonShadowPoint.glsl" diff --git a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs b/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs deleted file mode 100644 index 27ad9d7..0000000 --- a/gfx/gl/shaders/shadowDynamicPointInstWithTextures.vs +++ /dev/null @@ -1,3 +0,0 @@ -define(`TEXTURES', 1) - -include(`shadowDynamicPointInst.vs') diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.frag b/gfx/gl/shaders/shadowDynamicPointStencil.frag new file mode 100644 index 0000000..d6b8a0e --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointStencil.frag @@ -0,0 +1,15 @@ +#version 460 core + +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.geom b/gfx/gl/shaders/shadowDynamicPointStencil.geom new file mode 100644 index 0000000..df8be8d --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointStencil.geom @@ -0,0 +1,35 @@ +#version 460 core + +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.vert b/gfx/gl/shaders/shadowDynamicPointStencil.vert new file mode 100644 index 0000000..0a41143 --- /dev/null +++ b/gfx/gl/shaders/shadowDynamicPointStencil.vert @@ -0,0 +1,18 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "commonLocationData.glsl" + +layout(location = 0) in uint index; +uniform ivec3 viewPoint; +uniform vec3 centre; + +out float vmodelYaw; +out ivec3 vworldPos; + +void +main() +{ + vmodelYaw = locations[cldIndex[index]].rotation.x; + vworldPos = locations[cldIndex[index]].position.xyz - viewPoint + ivec3(centre); +} diff --git a/gfx/gl/shaders/shadowLandmass.vs b/gfx/gl/shaders/shadowLandmass.vert index becf142..cf68fe5 100644 --- a/gfx/gl/shaders/shadowLandmass.vs +++ b/gfx/gl/shaders/shadowLandmass.vert @@ -1,4 +1,4 @@ -#version 330 core +#version 460 core layout(location = 0) in ivec3 position; diff --git a/gfx/gl/shaders/shadowStencil.frag b/gfx/gl/shaders/shadowStencil.frag new file mode 100644 index 0000000..35cdf6e --- /dev/null +++ b/gfx/gl/shaders/shadowStencil.frag @@ -0,0 +1,19 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 0) uniform sampler2D textureAlbedo; + +#include "materialCommon.glsl" +#include "materialDetail.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.geom b/gfx/gl/shaders/shadowStencil.geom new file mode 100644 index 0000000..9c5ba48 --- /dev/null +++ b/gfx/gl/shaders/shadowStencil.geom @@ -0,0 +1,28 @@ +#version 460 core +#extension GL_ARB_shading_language_include : 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.vert b/gfx/gl/shaders/shadowStencil.vert new file mode 100644 index 0000000..98e8434 --- /dev/null +++ b/gfx/gl/shaders/shadowStencil.vert @@ -0,0 +1,20 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +layout(binding = 1) uniform usampler2DRect materialData; + +#include "getMaterialDetail.glsl" +#include "materialDetail.glsl" +#include "meshIn.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/spotLight.fs b/gfx/gl/shaders/spotLight.frag index ad33458..0241e88 100644 --- a/gfx/gl/shaders/spotLight.fs +++ b/gfx/gl/shaders/spotLight.frag @@ -1,9 +1,8 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable +#version 460 core out vec3 FragColor; -layout(binding = 0) uniform isampler2D gPosition; +layout(binding = 0) uniform sampler2D gPosition; layout(binding = 1) uniform sampler2D gNormal; uniform ivec4 viewPort; flat in vec4 geo_centre; diff --git a/gfx/gl/shaders/spotLight.gs b/gfx/gl/shaders/spotLight.geom index 194812a..fec191e 100644 --- a/gfx/gl/shaders/spotLight.gs +++ b/gfx/gl/shaders/spotLight.geom @@ -1,6 +1,4 @@ -#version 330 core -#extension GL_ARB_enhanced_layouts : enable -#extension GL_ARB_shading_language_420pack : enable +#version 460 core const vec3[] pyramid = vec3[]( // four-sided vec3(0, 0, 0), // Apex diff --git a/gfx/gl/shaders/spotLight.vs b/gfx/gl/shaders/spotLight.vert index e0196c3..6f72a63 100644 --- a/gfx/gl/shaders/spotLight.vs +++ b/gfx/gl/shaders/spotLight.vert @@ -1,12 +1,16 @@ -#version 330 core +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "commonLocationData.glsl" layout(location = 0) in vec3 v_position; layout(location = 1) in vec3 v_direction; layout(location = 2) in vec3 v_colour; layout(location = 3) in float v_kq; layout(location = 4) in float v_arc; -layout(location = 5) in mat3 model; -layout(location = 8) in ivec3 modelPos; +layout(location = 5) in uint index; +mat3 model = mat3(locations[cldIndex[index]].rotationMatrix); +ivec3 modelPos = locations[cldIndex[index]].position.xyz; uniform ivec3 viewPoint; @@ -20,11 +24,11 @@ flat out float kq; void main() { - position = modelPos + ivec3(mat3(model) * v_position); + position = (modelPos - viewPoint) + ivec3(mat3(model) * v_position); direction = normalize(mat3(model) * v_direction); colour = v_colour; kq = v_kq; size = (8000 * sqrt(max(max(colour.r, colour.g), colour.b))) / sqrt(kq); arc = vec2(cos(v_arc / 2), tan(v_arc / 2)); - gl_Position = vec4(position - viewPoint, 0); + gl_Position = vec4(position, 0); } 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/shaders/water.frag b/gfx/gl/shaders/water.frag new file mode 100644 index 0000000..0c57bfb --- /dev/null +++ b/gfx/gl/shaders/water.frag @@ -0,0 +1,18 @@ +#version 460 core +#extension GL_ARB_shading_language_include : enable + +#include "materialOut.glsl" + +in vec4 FragPos; +in vec2 TexCoords; + +uniform sampler2D texture0; + +void +main() +{ + gPosition = vec4(FragPos.xyz, 1); + gNormal = vec4(0, 0, 1, 1); + gAlbedoSpec = texture(texture0, TexCoords); + gAlbedoSpec.a *= clamp(-FragPos.w * .0007, .1, 1.0); +} diff --git a/gfx/gl/shaders/water.fs b/gfx/gl/shaders/water.fs deleted file mode 100644 index 0918d9f..0000000 --- a/gfx/gl/shaders/water.fs +++ /dev/null @@ -1,17 +0,0 @@ -#version 330 core -#extension GL_ARB_shading_language_420pack : enable - -in vec3 FragPos; -in vec2 TexCoords; -include(`materialOut.glsl') - -uniform sampler2D texture0; - -void -main() -{ - gPosition = ivec4(FragPos, 1); - gNormal = vec4(0, 0, 1, 1); - gAlbedoSpec = texture(texture0, TexCoords); - gAlbedoSpec.a *= clamp(-FragPos.z * .0007, .1, 1.0); -} diff --git a/gfx/gl/shaders/water.vs b/gfx/gl/shaders/water.vert index 58bf7b6..bb056b8 100644 --- a/gfx/gl/shaders/water.vs +++ b/gfx/gl/shaders/water.vert @@ -1,7 +1,7 @@ -#version 330 core +#version 460 core layout(location = 0) in ivec3 position; -out vec3 FragPos; +out vec4 FragPos; out vec2 TexCoords; uniform mat4 viewProjection; @@ -14,8 +14,8 @@ main() vec3 wpos = vec3(position.x + (cos(waves) * 1000.0), position.y + (cos(waves * 1.4) * 1000.0), cos(waves + (position.x / 1000000) + (position.y / 8000)) * 300.0); - FragPos = vec3(wpos.xy, position.z); + FragPos = vec4(wpos - viewPoint, position.z); TexCoords = (position.xy / 8192) - (viewPoint.xy / 8192); - gl_Position = viewProjection * vec4(wpos - viewPoint, 1.0); + gl_Position = viewProjection * vec4(FragPos.xyz, 1.0); } diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp index a846a3d..e01fd20 100644 --- a/gfx/gl/shadowMapper.cpp +++ b/gfx/gl/shadowMapper.cpp @@ -1,114 +1,138 @@ #include "shadowMapper.h" -#include "camera.h" #include "collections.h" -#include "gfx/gl/shaders/fs-shadowDynamicPointInstWithTextures.h" -#include "gfx/gl/shaders/gs-commonShadowPoint.h" -#include "gfx/gl/shaders/gs-shadowDynamicPointInstWithTextures.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-shadowLandmass.h" +#include "game/gamestate.h" +#include "gfx/aabb.h" +#include "gfx/gl/shadowStenciller.h" +#include "gfx/lightDirection.h" +#include "gfx/renderable.h" #include "gl_traits.h" +#include "gldebug.h" #include "location.h" -#include "maths.h" #include "sceneProvider.h" #include "sceneShader.h" -#include "sorting.h" +#include <gfx/camera.h> +#include <gfx/gl/shaders/commonShadowPoint-geom.h> +#include <gfx/gl/shaders/shadowDynamicPoint-vert.h> +#include <gfx/gl/shaders/shadowDynamicPointInst-vert.h> +#include <gfx/gl/shaders/shadowDynamicPointInstWithTextures-frag.h> +#include <gfx/gl/shaders/shadowDynamicPointInstWithTextures-geom.h> +#include <gfx/gl/shaders/shadowDynamicPointInstWithTextures-vert.h> +#include <gfx/gl/shaders/shadowDynamicPointStencil-frag.h> +#include <gfx/gl/shaders/shadowDynamicPointStencil-geom.h> +#include <gfx/gl/shaders/shadowDynamicPointStencil-vert.h> +#include <gfx/gl/shaders/shadowLandmass-vert.h> #include <glm/gtc/type_ptr.hpp> #include <glm/gtx/transform.hpp> #include <glm/matrix.hpp> -#include <vector> +#include <maths.h> ShadowMapper::ShadowMapper(const TextureAbsCoord & s) : - landmess {shadowLandmass_vs}, dynamicPointInst {shadowDynamicPointInst_vs}, - dynamicPointInstWithTextures {shadowDynamicPointInstWithTextures_vs, shadowDynamicPointInstWithTextures_gs, - shadowDynamicPointInstWithTextures_fs}, - size {s} + landmess {shadowLandmass_vert}, dynamicPointInst {shadowDynamicPointInst_vert}, + dynamicPointInstWithTextures {shadowDynamicPointInstWithTextures_vert, shadowDynamicPointInstWithTextures_geom, + shadowDynamicPointInstWithTextures_frag}, + size {s}, frustum {{}, {}, {}} { - glBindTexture(GL_TEXTURE_2D_ARRAY, depthMap); - glTexImage3D( - GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, size.x, size.y, 4, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); - glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glDebugScope _ {depthMap}; + depthMap.storage(1, GL_DEPTH_COMPONENT16, size || static_cast<GLsizei>(SHADOW_BANDS)); + depthMap.parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + depthMap.parameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + depthMap.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + depthMap.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); static constexpr RGBA border {std::numeric_limits<RGBA::value_type>::infinity()}; - glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, border); + depthMap.parameter(GL_TEXTURE_BORDER_COLOR, border); - glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthMap, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - throw std::runtime_error("Framebuffer not complete!"); - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); + depthMapFBO.texture(GL_DEPTH_ATTACHMENT, depthMap); + depthMapFBO.drawBuffers(GL_NONE); + depthMapFBO.assertComplete(); } -constexpr std::array<GlobalDistance, ShadowMapper::SHADOW_BANDS + 1> shadowBands { - 1000, - 250000, - 750000, - 2500000, - 10000000, -}; +constexpr GlobalDistance SHADOW_NEAR = 1; +constexpr GlobalDistance SHADOW_FAR = 10'000'000; +constexpr auto SHADOW_BANDS_DISTS + = []<GlobalDistance... Ints>(const float scaleFactor, std::integer_sequence<GlobalDistance, Ints...>) { + const auto base = SHADOW_FAR / pow(scaleFactor, sizeof...(Ints) - 1); + return std::array {SHADOW_NEAR, static_cast<GlobalDistance>((base * pow(scaleFactor, Ints)))...}; + }(4.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>()); -std::vector<std::array<RelativePosition3D, 4>> -ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightViewDir) +static_assert(SHADOW_BANDS_DISTS.front() == 1); +static_assert(SHADOW_BANDS_DISTS.back() == SHADOW_FAR); +static_assert(SHADOW_BANDS_DISTS.size() == ShadowMapper::SHADOW_BANDS + 1); + +size_t +ShadowMapper::getBandViewExtents( + BandViewExtents & bandViewExtents, const Camera & camera, const glm::mat4 & lightViewDir) { - std::vector<std::array<RelativePosition3D, 4>> bandViewExtents; - for (const auto dist : shadowBands) { + size_t band = 0; + for (const auto dist : SHADOW_BANDS_DISTS) { const auto extents = camera.extentsAtDist(dist); - bandViewExtents.emplace_back(extents * [&lightViewDir, cameraPos = camera.getPosition()](const auto & e) { - return glm::mat3(lightViewDir) * (e.xyz() - cameraPos); - }); - if (std::none_of(extents.begin(), extents.end(), [targetDist = dist - 1](const auto & e) { - return e.w > targetDist; + bandViewExtents[band++] = extents * [&lightViewDir, cameraPos = camera.getPosition()](const auto & extent) { + return glm::mat3(lightViewDir) * (extent.xyz() - cameraPos); + }; + if (std::ranges::none_of(extents, [dist](const auto & extent) { + return extent.w >= dist; })) { break; } } - return bandViewExtents; + return band; } -ShadowMapper::Definitions -ShadowMapper::update(const SceneProvider & scene, const Direction3D & dir, const Camera & camera) const +const Frustum & +ShadowMapper::preFrame(const LightDirection & dir, const Camera & camera) { + const auto lightViewDir = glm::lookAt({}, dir.vector(), Camera::upFromForward(dir.vector())); + const auto lightViewPoint = camera.getPosition(); + const auto bandViewExtentCount = getBandViewExtents(bandViewExtents, camera, lightViewDir); + const auto activeBandViewExtents = std::span(bandViewExtents).subspan(0, bandViewExtentCount); + + using ExtentsBoundingBox = AxisAlignedBoundingBox<RelativeDistance>; + for (auto out = std::make_pair(sizes.begin(), definitions.begin()); + const auto & [near, far] : activeBandViewExtents | std::views::pairwise) { + const auto extents = ExtentsBoundingBox::fromPoints(std::span {near.begin(), far.end()}); + const auto lightProjection = glm::ortho( + extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z); + *out.first++ = extents.max - extents.min; + *out.second++ = lightProjection * lightViewDir; + } + + const auto extents = ExtentsBoundingBox::fromPoints(activeBandViewExtents.back()) += {}; + const auto lightProjection + = glm::ortho(extents.min.x, extents.max.x, extents.min.y, extents.max.y, -extents.max.z, -extents.min.z); + frustum = {lightViewPoint, lightViewDir, lightProjection}; + return frustum; +} + +std::span<const glm::mat4> +ShadowMapper::update(const SceneProvider & scene, const LightDirection & dir, const Camera & camera) const +{ + glDebugScope _ {depthMap}; + 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 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})); - - 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(definitions, sizes, lightViewPoint); } - scene.shadows(*this); + scene.shadows(*this, frustum); glCullFace(GL_BACK); - return out; + return definitions; } -ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs) : Program {vs, commonShadowPoint_gs} { } +ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs) : Program {vs, commonShadowPoint_geom} { } ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs, const Shader & gs, const Shader & fs) : Program {vs, gs, fs} @@ -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())); } @@ -131,7 +158,7 @@ ShadowMapper::ShadowProgram::use() const glUseProgram(*this); } -ShadowMapper::DynamicPoint::DynamicPoint() : ShadowProgram {shadowDynamicPoint_vs} { } +ShadowMapper::DynamicPoint::DynamicPoint() : ShadowProgram {shadowDynamicPoint_vert} { } void ShadowMapper::DynamicPoint::use(const Location & location) const @@ -146,3 +173,16 @@ ShadowMapper::DynamicPoint::setModel(const Location & location) const glUniform(modelLoc, location.getRotationTransform()); glUniform(modelPosLoc, location.pos); } + +ShadowMapper::StencilShadowProgram::StencilShadowProgram() : + ShadowProgram {shadowDynamicPointStencil_vert, shadowDynamicPointStencil_geom, shadowDynamicPointStencil_frag} +{ +} + +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..f0356b9 100644 --- a/gfx/gl/shadowMapper.h +++ b/gfx/gl/shadowMapper.h @@ -1,15 +1,18 @@ #pragma once #include "config/types.h" +#include "gfx/frustum.h" +#include "gfx/gl/shadowStenciller.h" #include "lib/glArrays.h" #include "program.h" +#include <array> #include <gfx/models/texture.h> #include <glm/vec2.hpp> #include <span> -#include <vector> class SceneProvider; class Camera; +class LightDirection; class ShadowMapper { public: @@ -17,21 +20,26 @@ public: static constexpr std::size_t SHADOW_BANDS {4}; - using Definitions = std::vector<glm::mat4x4>; + using Definitions = std::array<glm::mat4, SHADOW_BANDS>; + using Sizes = std::array<RelativePosition3D, SHADOW_BANDS>; - [[nodiscard]] Definitions update(const SceneProvider &, const Direction3D & direction, const Camera &) const; + const Frustum & preFrame(const LightDirection & direction, const Camera &); + [[nodiscard]] std::span<const glm::mat4> 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,19 +54,37 @@ 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 + void + bind(GLenum unit) const { - return depthMap; + depthMap.bind(unit); } private: - [[nodiscard]] static std::vector<std::array<RelativePosition3D, 4>> getBandViewExtents( - const Camera &, const glm::mat4 & lightView); - glFrameBuffer depthMapFBO; - glTexture depthMap; + using BandViewExtents = std::array<std::array<RelativePosition3D, 4>, SHADOW_BANDS + 1>; + [[nodiscard]] static size_t getBandViewExtents(BandViewExtents &, const Camera &, const glm::mat4 & lightView); + glFramebuffer depthMapFBO; + glTexture<GL_TEXTURE_2D_ARRAY> depthMap; TextureAbsCoord size; + + BandViewExtents bandViewExtents; + Definitions definitions; + Sizes sizes; + Frustum frustum; + + mutable ShadowStenciller shadowStenciller; }; diff --git a/gfx/gl/shadowStenciller.cpp b/gfx/gl/shadowStenciller.cpp new file mode 100644 index 0000000..77ce309 --- /dev/null +++ b/gfx/gl/shadowStenciller.cpp @@ -0,0 +1,75 @@ +#include "shadowStenciller.h" +#include "gfx/lightDirection.h" +#include "gfx/models/mesh.h" +#include "gl_traits.h" +#include "gldebug.h" +#include "maths.h" +#include <gfx/gl/shaders/shadowStencil-frag.h> +#include <gfx/gl/shaders/shadowStencil-geom.h> +#include <gfx/gl/shaders/shadowStencil-vert.h> +#include <stdexcept> + +ShadowStenciller::ShadowStenciller() : + shadowCaster {shadowStencil_vert, shadowStencil_geom, shadowStencil_frag}, lightDir {}, viewProjections {} +{ + glDebugScope _ {fbo}; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void +ShadowStenciller::setLightDirection(const LightDirection & lightDir) +{ + this->lightDir = lightDir.position(); + 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>>()); +} + +Direction2D +ShadowStenciller::getLightDirection() const +{ + return lightDir; +} + +void +ShadowStenciller::configureStencilTexture(glTexture<GL_TEXTURE_2D_ARRAY> & stencil, ImageDimensions size) +{ + glDebugScope _ {0}; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + stencil.storage(1, GL_DEPTH_COMPONENT16, size || STENCIL_ANGLES<GLsizei>); + stencil.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + stencil.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + stencil.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + stencil.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void +ShadowStenciller::renderStencil( + const glTexture<GL_TEXTURE_2D_ARRAY> & stencil, const MeshBase & mesh, const Texture::AnyPtr texture) const +{ + glDebugScope _ {fbo}; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + fbo.texture(GL_DEPTH_ATTACHMENT, stencil); + fbo.assertComplete(); + if (texture) { + texture->bind(0); + } + glUseProgram(shadowCaster); + glClear(GL_DEPTH_BUFFER_BIT); + const auto stencilSize = stencil.getSize(); + 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..ff88ff7 --- /dev/null +++ b/gfx/gl/shadowStenciller.h @@ -0,0 +1,28 @@ +#pragma once + +#include "gfx/models/mesh.h" +#include "gfx/models/texture.h" +#include "glFramebuffer.h" +#include "program.h" + +class LightDirection; + +class ShadowStenciller { +public: + template<typename T> static constexpr T STENCIL_ANGLES = 8; + + ShadowStenciller(); + + static void configureStencilTexture(glTexture<GL_TEXTURE_2D_ARRAY> &, ImageDimensions); + void setLightDirection(const LightDirection & lightDir); + [[nodiscard]] Direction2D getLightDirection() const; + void renderStencil(const glTexture<GL_TEXTURE_2D_ARRAY> &, const MeshBase &, Texture::AnyPtr texture) const; + +private: + mutable glFramebuffer fbo; + Program shadowCaster; + Program::RequiredUniformLocation viewProjectionLoc {shadowCaster, "viewProjection"}; + + Direction2D lightDir; + 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 deleted file mode 100644 index 57daaf3..0000000 --- a/gfx/gl/vertexArrayObject.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include "collections.h" -#include "gl_traits.h" -#include "special_members.h" -#include <glad/gl.h> - -class VertexArrayObject { -public: - template<typename T> [[nodiscard]] VertexArrayObject(const T & arrayObject) - { - glBindVertexArray(arrayObject); - } - - ~VertexArrayObject() - { - glBindVertexArray(0); - } - - NO_MOVE(VertexArrayObject); - NO_COPY(VertexArrayObject); - - template<typename m, typename T> struct MP { - constexpr MP(m T::*p) : P {p} { } - - operator void *() const - { - return &(static_cast<T *>(nullptr)->*P); - } - - m T::*P; - using value_type = m; - }; - - template<typename m, typename T> MP(m T::*) -> MP<m, T>; - - template<typename VertexT, MP... attribs> - VertexArrayObject & - addAttribs(const GLuint arrayBuffer, const SequentialCollection<VertexT> auto & vertices, const GLuint divisor = 0) - { - addAttribs<VertexT, attribs...>(arrayBuffer, divisor); - data(vertices, arrayBuffer, GL_ARRAY_BUFFER); - return *this; - } - - template<typename VertexT, MP... attribs> - VertexArrayObject & - addAttribs(const GLuint arrayBuffer, const GLuint divisor = 0) - { - configure_attribs<VertexT, attribs...>(arrayBuffer, divisor); - return *this; - } - - // Customisation point - template<typename VertexT> VertexArrayObject & addAttribsFor(const GLuint arrayBuffer, const GLuint divisor = 0); - - template<typename Indices> - VertexArrayObject & - addIndices(const GLuint arrayBuffer, const Indices & indices) - { - data(indices, arrayBuffer, GL_ELEMENT_ARRAY_BUFFER); - return *this; - } - - VertexArrayObject & - addIndices(const GLuint arrayBuffer) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrayBuffer); - return *this; - } - - template<typename Data> - static void - data(const Data & data, const GLuint arrayBuffer, GLenum target) - { - using Value = typename Data::value_type; - glBindBuffer(target, arrayBuffer); - glBufferData(target, static_cast<GLsizeiptr>(sizeof(Value) * data.size()), data.data(), GL_STATIC_DRAW); - } - -private: - template<typename VertexT, typename T> - static auto - set_pointer(const GLuint vertexArrayId, const void * ptr, const GLuint divisor) - { - using traits = gl_traits<T>; - const auto usedAttribs - = traits::vertexAttribFunc(vertexArrayId, traits::size, traits::type, sizeof(VertexT), ptr); - for (GLuint i {}; i < usedAttribs; i++) { - glEnableVertexAttribArray(vertexArrayId + i); - glVertexAttribDivisor(vertexArrayId + i, divisor); - } - return usedAttribs; - } - - template<typename VertexT, MP attrib> - static auto - set_pointer(const GLuint vertexArrayId, const GLuint divisor) - { - return set_pointer<VertexT, typename decltype(attrib)::value_type>(vertexArrayId, attrib, divisor); - } - - template<typename VertexT, MP... attribs> - void - configure_attribs(const GLuint arrayBuffer, const GLuint divisor) - { - glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer); - if constexpr (sizeof...(attribs) == 0) { - vertexArrayId += set_pointer<VertexT, VertexT>(vertexArrayId, nullptr, divisor); - } - else { - ((vertexArrayId += set_pointer<VertexT, attribs>(vertexArrayId, divisor)), ...); - } - } - - GLuint vertexArrayId {}; -}; diff --git a/gfx/lightDirection.cpp b/gfx/lightDirection.cpp new file mode 100644 index 0000000..5198bdf --- /dev/null +++ b/gfx/lightDirection.cpp @@ -0,0 +1,25 @@ +#include "lightDirection.h" +#include "maths.h" + +constexpr auto TWILIGHT_START = .0_degrees; +constexpr auto TWILIGHT_END = -18.0_degrees; +constexpr auto TWILIGHT_RANGE = TWILIGHT_START - TWILIGHT_END; + +constexpr auto SUN_ANGLUAR_SIZE = 0.5_degrees; +constexpr auto SUN_ANGLUAR_RADIUS = SUN_ANGLUAR_SIZE / 2; +constexpr auto SUN_ELEVATION_REFRACTION_OFFSET = 0.5_degrees; +constexpr auto SUN_ANGLUAR_OFFSET = SUN_ANGLUAR_RADIUS + SUN_ELEVATION_REFRACTION_OFFSET; + +constexpr auto ATMOSPHERE_SCATTER_MIN = .4F; +constexpr auto ATMOSPHERE_SCATTER_MAX = .7F; +constexpr auto ATMOSPHERE_SCATTER_RANGE = ATMOSPHERE_SCATTER_MAX - ATMOSPHERE_SCATTER_MIN; + +constexpr auto NORM = 0.5F; + +LightDirection::LightDirection(const Direction2D sunPos) : + pos {sunPos}, vec {glm::mat3 {rotate_yp(pi + sunPos.x, -sunPos.y)} * north}, + amb {glm::clamp((sunPos.y - TWILIGHT_END) / TWILIGHT_RANGE, 0.F, 1.F)}, + dir {(-std::cos(std::clamp((sunPos.y + SUN_ANGLUAR_OFFSET) / SUN_ANGLUAR_SIZE, 0.F, 1.F) * pi) * NORM) + NORM}, + atmosScatter {((half_pi - sunPos.y) / half_pi * ATMOSPHERE_SCATTER_RANGE) + ATMOSPHERE_SCATTER_MIN} +{ +} diff --git a/gfx/lightDirection.h b/gfx/lightDirection.h new file mode 100644 index 0000000..296f497 --- /dev/null +++ b/gfx/lightDirection.h @@ -0,0 +1,46 @@ +#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; + } + + [[nodiscard]] float + atmosphericScattering() const noexcept + { + return atmosScatter; + } + +private: + Direction2D pos; + Direction3D vec; + float amb; + float dir; + float atmosScatter; +}; diff --git a/gfx/models/lights.cpp b/gfx/models/lights.cpp new file mode 100644 index 0000000..8c0e9e6 --- /dev/null +++ b/gfx/models/lights.cpp @@ -0,0 +1,11 @@ +#include "lights.h" + +SpotLightVertex::SpotLightVertex(const SpotLightDef & light, uint32_t parentObjectIdx) : + SpotLightDef {light}, LightCommonVertex {parentObjectIdx} +{ +} + +PointLightVertex::PointLightVertex(const PointLightDef & light, uint32_t parentObjectIdx) : + PointLightDef {light}, LightCommonVertex {parentObjectIdx} +{ +} diff --git a/gfx/models/lights.h b/gfx/models/lights.h new file mode 100644 index 0000000..586b3ef --- /dev/null +++ b/gfx/models/lights.h @@ -0,0 +1,29 @@ +#pragma once + +#include "config/types.h" + +struct LightCommon { + RelativePosition3D position; + RGB colour; + RelativeDistance kq; +}; + +struct LightCommonVertex { + uint32_t parentObject; +}; + +struct SpotLightDef : LightCommon { + Direction3D direction; + Angle arc; +}; + +struct PointLightDef : LightCommon { }; + +struct SpotLightVertex : SpotLightDef, LightCommonVertex { + SpotLightVertex(const SpotLightDef &, uint32_t); +}; + +struct PointLightVertex : PointLightDef, LightCommonVertex { + PointLightVertex(const PointLightDef &, uint32_t); +}; + diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp index e7474ca..6a53f52 100644 --- a/gfx/models/mesh.cpp +++ b/gfx/models/mesh.cpp @@ -1,23 +1,54 @@ #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, GLsizei vertexStride) : + vertexStride {vertexStride}, 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 +MeshBase::draw() const { - glBindVertexArray(m_vertexArrayObject); + glBindVertexArray(*vertexArrayObject); + glVertexArrayVertexBuffer(*vertexArrayObject, 0, vertexArrayBuffers[0], 0, vertexStride); + glVertexArrayElementBuffer(*vertexArrayObject, vertexArrayBuffers[1]); - glDrawElements(mode, m_numIndices, GL_UNSIGNED_INT, nullptr); + glDrawElements(mode, numIndices, GL_UNSIGNED_INT, nullptr); glBindVertexArray(0); } void -MeshBase::DrawInstanced(GLuint vao, GLsizei count, GLuint base) const +MeshBase::drawInstanced(GLuint vao, GLsizei count, GLuint base) const { glBindVertexArray(vao); + glVertexArrayVertexBuffer(vao, 0, vertexArrayBuffers[0], 0, vertexStride); + glVertexArrayElementBuffer(vao, vertexArrayBuffers[1]); - glDrawElementsInstancedBaseInstance(mode, m_numIndices, GL_UNSIGNED_INT, nullptr, count, base); + glDrawElementsInstancedBaseInstance(mode, numIndices, GL_UNSIGNED_INT, nullptr, count, base); glBindVertexArray(0); } diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h index 248cb8f..fa12ae8 100644 --- a/gfx/models/mesh.h +++ b/gfx/models/mesh.h @@ -1,8 +1,11 @@ #pragma once -#include "gfx/gl/vertexArrayObject.h" -#include <glArrays.h> +#include "config/types.h" +#include "gfx/gl/glBuffer.h" +#include "gfx/gl/gldebug.h" +#include <gfx/gl/glVertexArray.h> #include <glad/gl.h> +#include <ranges> #include <span> #include <stdTypeDefs.h> @@ -10,33 +13,69 @@ class Vertex; class MeshBase { public: - void Draw() const; - void DrawInstanced(GLuint vao, GLsizei count, GLuint base = 0) const; + class Dimensions { + public: + using Extents1D = std::ranges::minmax_result<RelativeDistance>; + explicit Dimensions(std::span<const RelativePosition3D>); + + RelativePosition3D minExtent, maxExtent; + RelativePosition3D centre; + RelativeDistance size; + + private: + Dimensions(std::span<const RelativePosition3D>, const std::array<Extents1D, 3> &); + static Extents1D extents(std::span<const RelativePosition3D>, glm::length_t); + }; + + 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 numIndices, GLenum mode, const std::vector<RelativePosition3D> &, GLsizei vertexStride); - glVertexArray m_vertexArrayObject; - glBuffers<2> m_vertexArrayBuffers; - GLsizei m_numIndices; + std::shared_ptr<glVertexArray> vertexArrayObject; + glBuffers<2> vertexArrayBuffers; + GLsizei vertexStride; + GLsizei 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 & vertex) { + return static_cast<RelativePosition3D>(vertex.pos); + })), + sizeof(V)} { - VertexArrayObject::data(vertices, m_vertexArrayBuffers[0], GL_ARRAY_BUFFER); - VertexArrayObject::data(indices, m_vertexArrayBuffers[1], GL_ARRAY_BUFFER); - configureVAO(m_vertexArrayObject); + glDebugScope _ {0}; + vertexArrayBuffers[0].storage(vertices, 0); + vertexArrayBuffers[1].storage(indices, 0); + if (!(vertexArrayObject = commonVertexArrayObject.lock())) { + commonVertexArrayObject = vertexArrayObject = std::make_shared<glVertexArray>(); + configureVAO(*vertexArrayObject, 0); + } } - VertexArrayObject & - configureVAO(VertexArrayObject && vao) const + auto + configureVAO(glVertexArray & vao, GLuint divisor) const { - return vao.addAttribsFor<V>(m_vertexArrayBuffers[0]).addIndices(m_vertexArrayBuffers[1]); + glDebugScope _ {0}; + return vao.configure().addAttribsFor<V>(divisor); } + +protected: + static std::weak_ptr<glVertexArray> commonVertexArrayObject; }; +template<typename T> std::weak_ptr<glVertexArray> MeshT<T>::commonVertexArrayObject; + using Mesh = MeshT<Vertex>; diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp index 51223aa..5a104be 100644 --- a/gfx/models/texture.cpp +++ b/gfx/models/texture.cpp @@ -1,7 +1,5 @@ #include "texture.h" #include "config/types.h" -#include "glArrays.h" -#include "tga.h" #include <fcntl.h> #include <filesystem.h> #include <gfx/image.h> @@ -12,6 +10,8 @@ #include <stb/stb_image.h> #include <sys/mman.h> +using std::ceil; + GLint TextureOptions::glMapMode(TextureOptions::MapMode mm) { @@ -33,117 +33,66 @@ Texture::Texture(const std::filesystem::path & fileName, TextureOptions to) : } Texture::Texture(const Image & tex, TextureOptions to) : - Texture {static_cast<GLsizei>(tex.width), static_cast<GLsizei>(tex.height), tex.data.data(), to} -{ -} - -Texture::Texture(GLsizei width, GLsizei height, TextureOptions to) : Texture {width, height, nullptr, to} { } - -Texture::Texture(GLsizei width, GLsizei height, const void * data, TextureOptions to) : type {to.type} -{ - glBindTexture(type, m_texture); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glTexParameter(type, GL_TEXTURE_WRAP_S, TextureOptions::glMapMode(to.wrapU)); - glTexParameter(type, GL_TEXTURE_WRAP_T, TextureOptions::glMapMode(to.wrapV)); - - 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); -} - -void -Texture::bind(GLenum unit) const + Texture {static_cast<GLsizei>(tex.width), static_cast<GLsizei>(tex.height), GL_RGBA, GL_UNSIGNED_BYTE, + tex.data.data(), to} { - glActiveTexture(unit); - glBindTexture(type, m_texture); } -TextureAbsCoord -Texture::getSize(const glTexture & texture) +Texture::Texture(GLsizei width, GLsizei height, TextureOptions to) { - TextureAbsCoord size; - glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_WIDTH, &size.x); - glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_HEIGHT, &size.y); - return size; + const auto levels = static_cast<GLsizei>(ceil(std::log2(std::max(width, height)))); + m_texture.storage(levels, GL_RGBA8, {width, height}); + m_texture.parameter(GL_TEXTURE_WRAP_S, TextureOptions::glMapMode(to.wrapU)); + m_texture.parameter(GL_TEXTURE_WRAP_T, TextureOptions::glMapMode(to.wrapV)); + m_texture.parameter(GL_TEXTURE_MIN_FILTER, to.minFilter); + m_texture.parameter(GL_TEXTURE_MAG_FILTER, to.magFilter); } -void -Texture::save( - const glTexture & texture, GLenum format, GLenum type, uint8_t channels, const char * path, uint8_t tgaFormat) +Texture::Texture(GLsizei width, GLsizei height, GLenum pixelFormat, GLenum PixelType, const void * pixels, + TextureOptions to) : Texture {width, height, to} { - const auto size = getSize(texture); - const size_t dataSize = (static_cast<size_t>(size.x * size.y * channels)); - const size_t fileSize = dataSize + sizeof(TGAHead); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - filesystem::fh out {path, O_RDWR | O_CREAT, 0660}; - out.truncate(fileSize); - auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED); - *tga.get<TGAHead>() = { - .format = tgaFormat, - .size = size, - .pixelDepth = static_cast<uint8_t>(8 * channels), + m_texture.image({width, height}, pixelFormat, PixelType, pixels); + 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); }; - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glGetTextureImage(texture, 0, format, type, static_cast<GLsizei>(dataSize), tga.get<TGAHead>() + 1); - tga.msync(MS_ASYNC); -} - -void -Texture::save(const char * path) const -{ - save(m_texture, GL_BGR, GL_UNSIGNED_BYTE, 3, path, 2); -} - -void -Texture::save(const glTexture & texture, const char * path) -{ - save(texture, GL_BGR, GL_UNSIGNED_BYTE, 3, path, 2); -} - -void -Texture::savePosition(const glTexture & texture, const char * path) -{ - save(texture, GL_BGR_INTEGER, GL_UNSIGNED_BYTE, 3, path, 2); -} - -void -Texture::saveDepth(const glTexture & texture, const char * path) -{ - save(texture, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 1, path, 3); + const auto levels = static_cast<GLsizei>(ceil(std::log2(std::max(width, height)))); + if (levels > 1 && (isMimmap(to.minFilter) || isMimmap(to.magFilter))) { + m_texture.generateMipmap(); + } } void -Texture::saveNormal(const glTexture & texture, const char * path) +Texture::bind(GLenum unit) const { - save(texture, GL_BGR, GL_BYTE, 3, path, 2); + m_texture.bind(unit); } -TextureAtlas::TextureAtlas(GLsizei width, GLsizei height, GLuint count) : Texture(width, height, nullptr, {}) +TextureAtlas::TextureAtlas(GLsizei width, GLsizei height, GLuint count) : Texture(width, height) { - glBindTexture(GL_TEXTURE_RECTANGLE, m_atlas); - - glTexParameter(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameter(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexParameter(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameter(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA16UI, 2, static_cast<GLsizei>(count), 0, GL_RGBA_INTEGER, - GL_UNSIGNED_BYTE, nullptr); + m_atlas.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + m_atlas.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_atlas.parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + m_atlas.parameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + m_atlas.storage(1, GL_RGBA16UI, {2, count}); } void TextureAtlas::bind(GLenum unit) const { Texture::bind(unit); - glActiveTexture(unit + 1); - glBindTexture(GL_TEXTURE_RECTANGLE, m_atlas); + m_atlas.bind(unit + 1); } GLuint TextureAtlas::add(TextureAbsCoord position, TextureAbsCoord size, void * data, TextureOptions to) { - glTextureSubImage2D(m_texture, 0, position.x, position.y, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, data); + m_texture.subImage(position, size, GL_RGBA, GL_UNSIGNED_BYTE, data); struct Material { glm::vec<2, uint16_t> position, size; @@ -152,6 +101,12 @@ TextureAtlas::add(TextureAbsCoord position, TextureAbsCoord size, void * data, T } material {position, size, to.wrapU, to.wrapV}; static_assert(sizeof(Material) <= 32); - glTextureSubImage2D(m_atlas, 0, 0, static_cast<GLsizei>(used), 2, 1, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, &material); + m_atlas.subImage({0, used}, {2, 1}, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, &material); return ++used; } + +void +TextureAtlas::complete() +{ + m_texture.generateMipmap(); +} diff --git a/gfx/models/texture.h b/gfx/models/texture.h index 689d378..9c67ae1 100644 --- a/gfx/models/texture.h +++ b/gfx/models/texture.h @@ -1,8 +1,9 @@ #pragma once #include "config/types.h" +#include "gfx/gl/glTexture.h" +#include "stdTypeDefs.h" #include <filesystem> -#include <glArrays.h> #include <glm/fwd.hpp> class Image; @@ -16,11 +17,10 @@ struct TextureOptions { }; MapMode wrapU {MapMode::Repeat}, wrapV {MapMode::Repeat}; GLint minFilter {GL_LINEAR}, magFilter {GL_LINEAR}; - GLenum type {GL_TEXTURE_2D}; static GLint glMapMode(MapMode); }; -class Texture { +class Texture : public StdTypeDefs<Texture> { public: virtual ~Texture() = default; DEFAULT_MOVE_NO_COPY(Texture); @@ -28,32 +28,24 @@ public: explicit Texture(const std::filesystem::path & fileName, TextureOptions = {}); explicit Texture(const Image & image, TextureOptions = {}); explicit Texture(GLsizei width, GLsizei height, TextureOptions = {}); - explicit Texture(GLsizei width, GLsizei height, const void * data, TextureOptions = {}); + explicit Texture(GLsizei width, GLsizei height, GLenum pixelFormat, GLenum PixelType, const void * pixels, + TextureOptions = {}); - virtual void bind(GLenum unit = GL_TEXTURE0) const; - - void save(const char * path) const; - static void save(const glTexture &, const char * path); - static void saveDepth(const glTexture &, const char * path); - static void saveNormal(const glTexture &, const char * path); - static void savePosition(const glTexture &, const char * path); + virtual void bind(GLuint unit) const; 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; + glTexture<GL_TEXTURE_2D> m_texture; }; class TextureAtlas : public Texture { public: TextureAtlas(GLsizei width, GLsizei height, GLuint count); - void bind(GLenum unit = GL_TEXTURE0) const override; + void bind(GLuint unit) const override; GLuint add(TextureAbsCoord position, TextureAbsCoord size, void * data, TextureOptions = {}); + void complete(); private: - glTexture m_atlas; + glTexture<GL_TEXTURE_RECTANGLE> m_atlas; GLuint used {}; }; diff --git a/gfx/models/tga.h b/gfx/models/tga.h index 3d072fb..955e5e1 100644 --- a/gfx/models/tga.h +++ b/gfx/models/tga.h @@ -3,14 +3,15 @@ #include <cstdint> #include <glm/vec2.hpp> -struct TGAHead { +template<glm::length_t Channels> struct TGAHead { using XY = glm::vec<2, uint16_t>; + using PixelType = glm::vec<Channels, uint8_t>; + uint8_t idLength {}, colorMapType {}, format {}; uint16_t __attribute__((packed)) colorMapFirst {}, colorMapLength {}; uint8_t colorMapEntrySize {}; XY origin {}, size {}; - uint8_t pixelDepth {}; + uint8_t pixelDepth {8 * Channels}; uint8_t descriptor {}; + PixelType data[1] {}; }; - -static_assert(sizeof(TGAHead) == 18); diff --git a/gfx/models/vertex.cpp b/gfx/models/vertex.cpp index c144db3..4b5ce54 100644 --- a/gfx/models/vertex.cpp +++ b/gfx/models/vertex.cpp @@ -1,10 +1,10 @@ #include "vertex.h" -#include "gfx/gl/vertexArrayObject.h" +#include "gfx/gl/glVertexArray.h" template<> -VertexArrayObject & -VertexArrayObject::addAttribsFor<Vertex>(const GLuint arrayBuffer, const GLuint divisor) +Impl::VertexArrayConfigurator & +Impl::VertexArrayConfigurator::addAttribsFor<Vertex>(const GLuint divisor) { return addAttribs<Vertex, &Vertex::pos, &Vertex::texCoord, &Vertex::normal, &Vertex::colour, &Vertex::material>( - arrayBuffer, divisor); + divisor); } diff --git a/gfx/renderable.cpp b/gfx/renderable.cpp index 0340189..8523118 100644 --- a/gfx/renderable.cpp +++ b/gfx/renderable.cpp @@ -1,11 +1,100 @@ #include "renderable.h" +#include "gfx/gl/sceneShader.h" +#include "gl_traits.h" +#include "location.h" +#include "maths.h" +#include "util.h" + +std::weak_ptr<Renderable::CommonLocationData> Renderable::commonLocationData; +std::weak_ptr<Renderable::CommonSpotLights> Renderable::commonSpotLights; +std::weak_ptr<Renderable::CommonPointLights> Renderable::commonPointLights; +std::weak_ptr<glVertexArray> Renderable::commonInstancesSpotLightVAO, Renderable::commonInstancesPointLightVAO; + +Renderable::CommonLocation::CommonLocation(Location const & location) : + position {location.pos, 0}, rotation {location.rot, 0}, rotationMatrix {location.getRotationTransform()} +{ +} + +Renderable::CommonLocation & +Renderable::CommonLocation ::operator=(Location const & location) +{ + position = location.pos || 0; + rotation = location.rot || 0.F; + rotationMatrix = location.getRotationTransform(); + return *this; +} + +Renderable::Renderable() +{ + createIfRequired(locationData, commonLocationData); + createIfRequired(spotLights, commonSpotLights); + createIfRequired(pointLights, commonPointLights); + if (createIfRequired(instancesSpotLightVAO, commonInstancesSpotLightVAO)) { + instancesSpotLightVAO->configure() + .addAttribs<SpotLightVertex, &SpotLightVertex::position, &SpotLightVertex::direction, + &SpotLightVertex::colour, &SpotLightVertex::kq, &SpotLightVertex::arc, + &SpotLightVertex::parentObject>(0); + } + if (createIfRequired(instancesPointLightVAO, commonInstancesPointLightVAO)) { + instancesPointLightVAO->configure() + .addAttribs<PointLightVertex, &PointLightVertex::position, &PointLightVertex::colour, + &PointLightVertex::kq, &PointLightVertex::parentObject>(0); + } +} + +GLuint +gl_traits<InstanceVertices<Renderable::CommonLocation>::InstanceProxy>::vertexArrayAttribFormat( + GLuint vao, GLuint index, GLuint offset) +{ + return gl_traits< + decltype(InstanceVertices<Renderable::CommonLocation>::InstanceProxy::index)>::vertexArrayAttribFormat(vao, + index, offset + offsetof(InstanceVertices<Renderable::CommonLocation>::InstanceProxy, index)); +}; + +void +Renderable::preFrame(const Frustum &, const Frustum &) +{ +} + +void +Renderable::lights(const SceneShader & shader) +{ + glDebugScope _ {0}; + if (const auto instancesSpotLight = commonSpotLights.lock()) { + if (const auto scount = instancesSpotLight->size()) { + if (const auto instancesSpotLightVAO = commonInstancesSpotLightVAO.lock()) { + glDebugScope _ {*instancesSpotLightVAO, "Spot lights"}; + shader.spotLightInst.use(); + glBindVertexArray(*instancesSpotLightVAO); + instancesSpotLightVAO->useBuffer(0, *instancesSpotLight); + glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(scount)); + } + } + } + if (const auto instancesPointLight = commonPointLights.lock()) { + if (const auto pcount = instancesPointLight->size()) { + if (const auto instancesPointLightVAO = commonInstancesPointLightVAO.lock()) { + glDebugScope _ {*instancesPointLightVAO, "Point lights"}; + shader.pointLightInst.use(); + glBindVertexArray(*instancesPointLightVAO); + instancesPointLightVAO->useBuffer(0, *instancesPointLight); + glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(pcount)); + } + } + } +} + +void +Renderable::shadows(const ShadowMapper &, const Frustum &) const +{ +} void -Renderable::lights(const SceneShader &) const +Renderable::updateStencil(const ShadowStenciller &) const { } void -Renderable::shadows(const ShadowMapper &) const +Renderable::updateBillboard(const BillboardPainter &) const { } diff --git a/gfx/renderable.h b/gfx/renderable.h index e126fff..7f4f52e 100644 --- a/gfx/renderable.h +++ b/gfx/renderable.h @@ -1,17 +1,58 @@ #pragma once +#include "gfx/gl/glVertexArray.h" +#include "gfx/gl/instanceVertices.h" +#include "gfx/models/lights.h" +#include "gl_traits.h" +#include <glm/mat3x3.hpp> #include <special_members.h> class SceneShader; +class Frustum; class ShadowMapper; +class ShadowStenciller; +class BillboardPainter; +class Location; class Renderable { public: - Renderable() = default; + Renderable(); virtual ~Renderable() = default; DEFAULT_MOVE_COPY(Renderable); - virtual void render(const SceneShader & shader) const = 0; - virtual void lights(const SceneShader & shader) const; - virtual void shadows(const ShadowMapper & shadowMapper) const; + virtual void preFrame(const Frustum &, const Frustum &); + virtual void render(const SceneShader & shader, const Frustum &) const = 0; + static void lights(const SceneShader & shader); + virtual void shadows(const ShadowMapper & shadowMapper, const Frustum &) const; + + virtual void updateStencil(const ShadowStenciller & lightDir) const; + virtual void updateBillboard(const BillboardPainter &) const; + + struct CommonLocation { + CommonLocation(const Location &); + CommonLocation & operator=(const Location &); + + glm::ivec4 position; + glm::vec4 rotation; + glm::mat3x4 rotationMatrix; + }; + + using CommonLocationData = InstanceVertices<CommonLocation>; + using CommonLocationInstance = CommonLocationData::InstanceProxy; + std::shared_ptr<CommonLocationData> locationData; + static std::weak_ptr<CommonLocationData> commonLocationData; + + using CommonSpotLights = InstanceVertices<SpotLightVertex>; + std::shared_ptr<CommonSpotLights> spotLights; + static std::weak_ptr<CommonSpotLights> commonSpotLights; + using CommonPointLights = InstanceVertices<PointLightVertex>; + std::shared_ptr<CommonPointLights> pointLights; + static std::weak_ptr<CommonPointLights> commonPointLights; + std::shared_ptr<glVertexArray> instancesSpotLightVAO, instancesPointLightVAO; + static std::weak_ptr<glVertexArray> commonInstancesSpotLightVAO, commonInstancesPointLightVAO; +}; + +template<> struct gl_traits<InstanceVertices<Renderable::CommonLocation>::InstanceProxy> { + static GLuint vertexArrayAttribFormat(GLuint vao, GLuint index, GLuint offset); }; + @@ -3,21 +3,22 @@ import type : type ; import generators : register-standard ; import scanner : register ; -type.register GL_VERTEX_SHADER : vs ; -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_VERTEX_SHADER : vert ; +type.register GL_TESS_CONTROL_SHADER : tesc ; +type.register GL_TESS_EVALUATION_SHADER : tese ; +type.register GL_GEOMETRY_SHADER : geom ; +type.register GL_FRAGMENT_SHADER : frag ; +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(%-vert) H(%-vert) GL_GENERIC_SHADER(%-vert) ; +generators.register-standard glsl.embed : GL_TESS_CONTROL_SHADER : CPP(%-tesc) H(%-tesc) GL_GENERIC_SHADER(%-tesc) ; +generators.register-standard glsl.embed : GL_TESS_EVALUATION_SHADER : CPP(%-tese) H(%-tese) GL_GENERIC_SHADER(%-tese) ; +generators.register-standard glsl.embed : GL_GEOMETRY_SHADER : CPP(%-geom) H(%-geom) GL_GENERIC_SHADER(%-geom) ; +generators.register-standard glsl.embed : GL_FRAGMENT_SHADER : CPP(%-frag) H(%-frag) GL_GENERIC_SHADER(%-frag) ; class m4-scanner : common-scanner { rule pattern ( ) { - return "s?include\\(`([^']*)'\\)" ; + return "#include *\"([^\"]*)\"" ; } } @@ -28,15 +29,24 @@ type.set-scanner GL_TESS_CONTROL_SHADER : m4-scanner ; type.set-scanner GL_TESS_EVALUATION_SHADER : m4-scanner ; type.set-scanner GL_GEOMETRY_SHADER : m4-scanner ; type.set-scanner GL_FRAGMENT_SHADER : m4-scanner ; +type.set-scanner GL_GENERIC_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 + set -e + glslangValidator -E $(2) | grep -v '#line' > $(1[3])$(2:S) + glslangValidator -l $(1[3])$(2:S) + mv $(1[3])$(2:S) $(1[3]) + clang-format -i $(1[3]) + m4 -I$(2:D) -DSOURCE=$(1[3]) -DOUTPUT=$(1[3]) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(SHADER_TYPE) lib/embed-glsl.h.m4 > $(1[2]) + m4 -I$(2:D) -DSOURCE=$(1[3]) -DOUTPUT=$(1[3]) -DNAME=$(2:B) -DTYPE=$(2:S) -DGLTYPE=$(SHADER_TYPE) 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 ; - OPTIONS on $(targets) = [ type.type $(sources) ] ; + NOUPDATE $(targets[2]) ; + DEPENDS $(targets[2]) : lib/embed-glsl.h.m4 ; + DEPENDS $(targets[1]) $(targets[3]) : lib/embed-glsl.cpp.m4 ; + SHADER_TYPE on $(targets) = [ type.type $(sources) ] ; } diff --git a/lib/chronology.cpp b/lib/chronology.cpp new file mode 100644 index 0000000..49f6df2 --- /dev/null +++ b/lib/chronology.cpp @@ -0,0 +1,18 @@ +#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); +} + +std::chrono::seconds +operator""_seconds(const char * iso, size_t) +{ + return std::chrono::seconds(operator""_time_t(iso, 0)); +} diff --git a/lib/chronology.h b/lib/chronology.h index 1980116..886424d 100644 --- a/lib/chronology.h +++ b/lib/chronology.h @@ -1,5 +1,8 @@ #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); +std::chrono::seconds operator""_seconds(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, ¶ms...](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, ¶ms...](auto && op) { + std::invoke(m, op, std::forward<Params>(params)...); + }); + return std::distance(begin, end); + } + else { + return std::count_if(begin, end, [&m, ¶ms...](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, ¶ms...](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, ¶ms...](auto && op) { + return std::invoke(m, op, std::forward<Params>(params)...); + }); + } + else { + return std::find_if(begin, end, [&m, ¶ms...](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..3c80125 100644 --- a/lib/collections.h +++ b/lib/collections.h @@ -3,16 +3,15 @@ #include <algorithm> #include <array> #include <cstdint> +#include <ranges> #include <span> #include <tuple> #include <utility> #include <vector> template<typename T, typename E> -concept SequentialCollection = requires(T c) { - { c.size() } -> std::integral; - { c.data() } -> std::same_as<const E *>; -}; +concept SequentialCollection + = std::ranges::contiguous_range<T> && std::is_same_v<const E, const typename T::value_type>; template<typename T> concept IterableCollection = std::is_same_v<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>; @@ -44,6 +43,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 +106,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 +138,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 +196,14 @@ template<typename iter> struct stripiter { return *this; } + constexpr stripiter + operator++(int) + { + auto out {*this}; + ++*this; + return out; + } + constexpr stripiter & operator--() { @@ -195,6 +212,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 +249,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..e8d285c 100644 --- a/lib/embed-glsl.cpp.m4 +++ b/lib/embed-glsl.cpp.m4 @@ -1,9 +1,9 @@ changecom() dnl // NAME #include "gfx/gl/shader.h" -#include "substr(TYPE,1)-NAME.h" +#include "NAME-substr(TYPE,1).h" #include <glad/gl.h> constexpr Shader NAME`_'substr(TYPE,1) { - R"GLSL-EMBED(dnl - include(SOURCE))GLSL-EMBED", GLTYPE }; + R"GLSL-EMBED(// SOURCE +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/glAllocator.cpp b/lib/glAllocator.cpp new file mode 100644 index 0000000..633f7ab --- /dev/null +++ b/lib/glAllocator.cpp @@ -0,0 +1,28 @@ +#include "glAllocator.h" + +namespace Detail { + std::pair<void *, GLuint> + allocateBuffer(size_t count, size_t objSize) + { + constexpr static GLbitfield MAPPING_FLAGS + = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; + constexpr static GLbitfield STORAGE_FLAGS = GL_DYNAMIC_STORAGE_BIT | MAPPING_FLAGS; + GLuint name = 0; + glCreateBuffers(1, &name); + const auto size = static_cast<GLsizeiptr>(count * objSize); + glNamedBufferStorage(name, size, nullptr, STORAGE_FLAGS); + const auto data = (glMapNamedBufferRange(name, 0, size, MAPPING_FLAGS)); + if (!data) { + glDeleteBuffers(1, &name); + throw std::bad_alloc(); + } + return {data, name}; + } + + void + deallocateBuffer(GLuint name) + { + glUnmapNamedBuffer(name); + glDeleteBuffers(1, &name); + } +} diff --git a/lib/glAllocator.h b/lib/glAllocator.h new file mode 100644 index 0000000..b592ecb --- /dev/null +++ b/lib/glAllocator.h @@ -0,0 +1,169 @@ +#pragma once + +#include "special_members.h" +#include <glad/gl.h> +#include <iterator> +#include <memory> +#include <vector> + +namespace Detail { + template<typename T> class glPointer { + public: + constexpr glPointer(const glPointer<std::remove_const_t<T>> & other) + requires(std::is_const_v<T>) + : ptr {other.get()}, name {other.bufferName()} + { + } + + ~glPointer() noexcept = default; + + DEFAULT_MOVE_COPY(glPointer); + + constexpr glPointer() : ptr {nullptr}, name {0} { } + + constexpr glPointer(T * ptr, GLuint name) : ptr {ptr}, name {name} { } + + auto operator<=>(const glPointer &) const noexcept = default; + + operator T *() const noexcept + { + return ptr; + } + + operator bool() const noexcept + { + return ptr; + } + + std::ptrdiff_t + operator-(const glPointer & other) const noexcept + { + return ptr - other.ptr; + } + + T * + get() const noexcept + { + return ptr; + } + + T & + operator*() const noexcept + { + return *ptr; + } + + [[nodiscard]] + T & + operator[](std::unsigned_integral auto index) const noexcept + { + return ptr[index]; + } + + T * + operator->() const noexcept + { + return ptr; + } + + glPointer<T> & + operator++() noexcept + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ++ptr; + return *this; + } + + glPointer<T> & + operator--() noexcept + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + --ptr; + return *this; + } + + [[nodiscard]] glPointer<T> + operator+(std::integral auto offset) const noexcept + { + return {ptr + offset, name}; + } + + [[nodiscard]] glPointer<T> + operator-(std::integral auto offset) const noexcept + { + return {ptr - offset, name}; + } + + [[nodiscard]] glPointer<T> + operator+=(std::integral auto offset) const noexcept + { + return {ptr += offset, name}; + } + + [[nodiscard]] glPointer<T> + operator-=(std::integral auto offset) const noexcept + { + return {ptr -= offset, name}; + } + + [[nodiscard]] GLuint + bufferName() const noexcept + { + return name; + } + + private: + T * ptr; + GLuint name; + }; + + std::pair<void *, GLuint> allocateBuffer(size_t count, size_t objSize); + void deallocateBuffer(GLuint name); + + template<typename T> class glAllocator { + public: + // NOLINTBEGIN(readability-identifier-naming) - STL like + using pointer = glPointer<T>; + using const_pointer = glPointer<const T>; + using value_type = T; + using is_always_equal = std::true_type; + + // NOLINTEND(readability-identifier-naming) + + pointer + allocate(size_t count) + { + auto allocated = allocateBuffer(count, sizeof(T)); + return {static_cast<T *>(allocated.first), allocated.second}; + } + +#if (__cpp_lib_allocate_at_least >= 202302L) + std::allocation_result<pointer> + allocate_at_least(size_t count) + { + count = std::min(count, 32ZU); + return {allocate(count), count}; + } +#endif + + void + deallocate(pointer ptr, size_t) + { + deallocateBuffer(ptr.bufferName()); + } + }; +} + +template<typename T> struct std::iterator_traits<Detail::glPointer<T>> { + // NOLINTBEGIN(readability-identifier-naming) - STL like + using iterator_category = std::random_access_iterator_tag; + using iterator_concept = std::contiguous_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using reference = T &; + using pointer = T *; + // NOLINTEND(readability-identifier-naming) - STL like +}; + +template<typename T> +using glVector = std::vector<T, typename std::allocator_traits<Detail::glAllocator<T>>::allocator_type>; diff --git a/lib/glArrays.cpp b/lib/glArrays.cpp deleted file mode 100644 index 7c5b2ea..0000000 --- a/lib/glArrays.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "glArrays.h" -#include <type_traits> - -// Base -static_assert(!std::is_default_constructible_v<glArraysBase<1>>); -static_assert(!std::is_copy_constructible_v<glArraysBase<1>>); -static_assert(!std::is_copy_assignable_v<glArraysBase<1>>); -static_assert(std::is_nothrow_move_constructible_v<glArraysBase<1>>); -static_assert(std::is_nothrow_move_assignable_v<glArraysBase<1>>); - -// Specialisations (glBuffer is an example of the typedef) -static_assert(std::is_nothrow_default_constructible_v<glBuffer>); -static_assert(!std::is_trivially_default_constructible_v<glBuffer>); -static_assert(std::is_nothrow_destructible_v<glBuffer>); -static_assert(!std::is_trivially_destructible_v<glBuffer>); -static_assert(std::is_default_constructible_v<glBuffer>); -static_assert(!std::is_copy_constructible_v<glBuffer>); -static_assert(!std::is_copy_assignable_v<glBuffer>); -static_assert(std::is_nothrow_move_constructible_v<glBuffer>); -static_assert(std::is_nothrow_move_assignable_v<glBuffer>); -static_assert(sizeof(glBuffer) == sizeof(GLuint)); diff --git a/lib/glArrays.h b/lib/glArrays.h index 787ea17..7f5a10b 100644 --- a/lib/glArrays.h +++ b/lib/glArrays.h @@ -5,77 +5,108 @@ #include <cstddef> #include <glad/gl.h> #include <special_members.h> +#include <utility> -template<size_t N> class glArraysBase { - static_assert(N > 0); +namespace Detail { + class glNamed; -public: - ~glArraysBase() = default; - NO_COPY(glArraysBase); - CUSTOM_MOVE(glArraysBase); + template<typename Named> + concept IsglNamed = sizeof(Named) == sizeof(GLuint) && std::is_base_of_v<Detail::glNamed, Named>; +} + +template<Detail::IsglNamed, size_t, auto, auto> struct glManagedArray; + +namespace Detail { + class glNamed { + public: + glNamed() = default; + ~glNamed() = default; + DEFAULT_MOVE_NO_COPY(glNamed); + + GLuint + operator*() const noexcept + { + return name; + } + + // NOLINTNEXTLINE(hicpp-explicit-conversions) + operator GLuint() const noexcept + { + return name; + } - // NOLINTNEXTLINE(hicpp-explicit-conversions) - inline - operator GLuint() const + protected: + GLuint name {}; + template<Detail::IsglNamed, size_t, auto, auto> friend struct ::glManagedArray; + }; +} + +template<Detail::IsglNamed Named, auto Gen, auto Del> struct glManagedSingle : public Named { + glManagedSingle() noexcept { - static_assert(N == 1, "Implicit cast only if N == 1"); - return ids.front(); + (*Gen)(1, &this->name); } - inline auto - operator[](size_t n) const + glManagedSingle(glManagedSingle && src) noexcept { - return ids[n]; + this->name = std::exchange(src.name, 0); } - constexpr static auto size {N}; + ~glManagedSingle() noexcept + { + if (this->name) { + (*Del)(1, &this->name); + } + } -protected: - glArraysBase() noexcept = default; - std::array<GLuint, N> ids {}; -}; + NO_COPY(glManagedSingle); -template<size_t N> inline glArraysBase<N>::glArraysBase(glArraysBase<N> && src) noexcept : ids {src.ids} -{ - std::fill(src.ids.begin(), src.ids.end(), 0); -} + glManagedSingle & + operator=(glManagedSingle && src) noexcept + { + if (this->name) { + (*Del)(1, &this->name); + } + this->name = std::exchange(src.name, 0); + return *this; + } +}; -template<size_t N> -inline glArraysBase<N> & -glArraysBase<N>::operator=(glArraysBase<N> && src) noexcept -{ - ids = src.ids; - std::fill(src.ids.begin(), src.ids.end(), 0); - return *this; -} +template<Detail::IsglNamed Named, size_t N, auto Gen, auto Del> struct glManagedArray : public std::array<Named, N> { + using Arr = std::array<Named, N>; -template<size_t N, auto Gen, auto Del> class glArrays : public glArraysBase<N> { -public: - using glArraysBase<N>::glArraysBase; - using glArraysBase<N>::operator=; + glManagedArray() noexcept + { + (*Gen)(N, &Arr::front().name); + } - DEFAULT_MOVE_COPY(glArrays); + glManagedArray(glManagedArray && src) noexcept + { + Arr::operator=(std::exchange(static_cast<Arr &>(src), {})); + } - inline glArrays() noexcept + ~glManagedArray() noexcept { - (*Gen)(N, this->ids.data()); + if (Arr::front().name) { + (*Del)(N, &Arr::front().name); + } } - inline ~glArrays() noexcept + NO_COPY(glManagedArray); + + glManagedArray & + operator=(glManagedArray && src) noexcept { - if (this->ids.front()) { - (*Del)(N, this->ids.data()); + if (Arr::front().name) { + (*Del)(N, &Arr::front().name); } + Arr::operator=(std::exchange(static_cast<Arr &>(src), {})); + return *this; } -}; -template<size_t N> using glVertexArrays = glArrays<N, &glGenVertexArrays, &glDeleteVertexArrays>; -using glVertexArray = glVertexArrays<1>; -template<size_t N> using glBuffers = glArrays<N, &glGenBuffers, &glDeleteBuffers>; -using glBuffer = glBuffers<1>; -template<size_t N> using glTextures = glArrays<N, &glGenTextures, &glDeleteTextures>; -using glTexture = glTextures<1>; -template<size_t N> using glFrameBuffers = glArrays<N, &glGenFramebuffers, &glDeleteFramebuffers>; -using glFrameBuffer = glFrameBuffers<1>; -template<size_t N> using glRenderBuffers = glArrays<N, &glGenRenderbuffers, &glDeleteRenderbuffers>; -using glRenderBuffer = glRenderBuffers<1>; + [[nodiscard]] static constexpr size_t + size() noexcept + { + return N; + } +}; diff --git a/lib/glContainer.h b/lib/glContainer.h deleted file mode 100644 index 5cbb038..0000000 --- a/lib/glContainer.h +++ /dev/null @@ -1,497 +0,0 @@ -#pragma once - -#include "glArrays.h" -#include <cassert> -#include <span> -#include <stdexcept> -#include <utility> -#include <vector> - -static_assert(GL_READ_ONLY < GL_READ_WRITE); -static_assert(GL_WRITE_ONLY < GL_READ_WRITE); - -template<typename T> class glContainer { -public: - using span = std::span<T>; - using const_span = std::span<const T>; - using value_type = T; - using reference_type = T &; - using const_reference_type = const T &; - using pointer_type = T *; - using const_pointer_type = const T *; - using size_type = std::size_t; - using iterator = span::iterator; - using const_iterator = const_span::iterator; - using reverse_iterator = span::reverse_iterator; - using const_reverse_iterator = const_span::reverse_iterator; - static constexpr bool is_trivial_dest = std::is_trivially_destructible_v<T>; - - explicit glContainer(GLenum target = GL_ARRAY_BUFFER) : target_ {target} - { - allocBuffer(1); - } - - ~glContainer() - requires(is_trivial_dest) - = default; - - ~glContainer() - requires(!is_trivial_dest) - { - clear(); - } - - template<template<typename, typename...> typename C> - explicit glContainer(const C<T> & src, GLenum target = GL_ARRAY_BUFFER) : target_ {target} - { - reserve(src.size()); - std::copy(src.begin(), src.end(), std::back_inserter(*this)); - } - - DEFAULT_MOVE_NO_COPY(glContainer); - - [[nodiscard]] iterator - begin() - { - map(GL_READ_WRITE); - return mkspan().begin(); - } - - [[nodiscard]] iterator - end() - { - map(GL_READ_WRITE); - return mkspan().end(); - } - - [[nodiscard]] const_iterator - begin() const - { - map(GL_READ_ONLY); - return mkcspan().begin(); - } - - [[nodiscard]] const_iterator - end() const - { - map(GL_READ_ONLY); - return mkcspan().end(); - } - - [[nodiscard]] const_iterator - cbegin() const - { - map(GL_READ_ONLY); - return mkcspan().begin(); - } - - [[nodiscard]] const_iterator - cend() const - { - map(GL_READ_ONLY); - return mkcspan().end(); - } - - [[nodiscard]] reverse_iterator - rbegin() - { - map(GL_READ_WRITE); - return mkspan().rbegin(); - } - - [[nodiscard]] reverse_iterator - rend() - { - map(GL_READ_WRITE); - return mkspan().rend(); - } - - [[nodiscard]] const_reverse_iterator - rbegin() const - { - map(GL_READ_ONLY); - return mkcspan().rbegin(); - } - - [[nodiscard]] const_reverse_iterator - rend() const - { - map(GL_READ_ONLY); - return mkcspan().rend(); - } - - [[nodiscard]] const_reverse_iterator - crbegin() const - { - map(GL_READ_ONLY); - return mkcspan().rbegin(); - } - - [[nodiscard]] const_reverse_iterator - crend() const - { - map(GL_READ_ONLY); - return mkcspan().rend(); - } - - [[nodiscard]] const auto & - bufferName() const - { - return buffer_; - } - - [[nodiscard]] size_type - size() const - { - return size_; - } - - void - at(size_type pos, const T & value) - { - if (pos >= size()) { - throw std::out_of_range {__FUNCTION__}; - } - if (data_) { - mkspan()[pos] = value; - } - else { - glBindBuffer(target_, buffer_); - glBufferSubData(target_, static_cast<GLintptr>(pos * sizeof(T)), sizeof(value), &value); - glBindBuffer(target_, 0); - } - } - - [[nodiscard]] reference_type - at(size_type pos) - { - if (pos >= size()) { - throw std::out_of_range {__FUNCTION__}; - } - map(GL_READ_WRITE); - return mkspan()[pos]; - } - - [[nodiscard]] const_reference_type - at(size_type pos) const - { - if (pos >= size()) { - throw std::out_of_range {__FUNCTION__}; - } - map(GL_READ_ONLY); - return mkcspan()[pos]; - } - - [[nodiscard]] reference_type - operator[](size_type pos) - { - map(GL_READ_WRITE); - return mkspan()[pos]; - } - - [[nodiscard]] const_reference_type - operator[](size_type pos) const - { - map(GL_READ_ONLY); - return mkcspan()[pos]; - } - - [[nodiscard]] pointer_type - data() - { - map(GL_READ_WRITE); - return data_; - } - - [[nodiscard]] const_pointer_type - data() const - { - map(GL_READ_ONLY); - return data_; - } - - [[nodiscard]] reference_type - front() - { - map(GL_READ_WRITE); - return mkspan().front(); - } - - [[nodiscard]] reference_type - back() - { - map(GL_READ_WRITE); - return mkspan().back(); - } - - [[nodiscard]] const_reference_type - front() const - { - map(GL_READ_ONLY); - return mkcspan().front(); - } - - [[nodiscard]] const_reference_type - back() const - { - map(GL_READ_ONLY); - return mkcspan().back(); - } - - [[nodiscard]] bool - empty() const - { - return !size(); - } - - [[nodiscard]] size_type - capacity() const - { - return capacity_; - } - - void - unmap() const - { - if (data_) { - glBindBuffer(target_, buffer_); - glUnmapBuffer(target_); - glBindBuffer(target_, 0); - data_ = {}; - access_ = {}; - } - } - - void - reserve(size_type newCapacity) - { - if (newCapacity <= capacity_) { - return; - } - newCapacity = std::max(newCapacity, capacity_ * 2); - - std::vector<T> existing; - existing.reserve(size_); - std::move(begin(), end(), std::back_inserter(existing)); - allocBuffer(newCapacity); - std::move(existing.begin(), existing.end(), begin()); - } - - void - resize(size_type newSize) - requires std::is_default_constructible_v<T> - { - if (newSize == size_) { - return; - } - - if (const auto maintain = std::min(newSize, size_)) { - std::vector<T> existing; - const auto maintaind = static_cast<typename decltype(existing)::difference_type>(maintain); - existing.reserve(maintain); - std::move(begin(), end(), std::back_inserter(existing)); - allocBuffer(newSize); - mapForAdd(); - std::move(existing.begin(), existing.begin() + maintaind, begin()); - } - else { - allocBuffer(newSize); - mapForAdd(); - } - if (newSize > size_) { - for (auto & uninitialised : mkspan().subspan(size_, newSize - size_)) { - new (&uninitialised) T {}; - } - } - setSize(newSize); - } - - void - shrink_to_fit() - { - if (capacity_ <= size_) { - return; - } - - std::vector<T> existing; - existing.reserve(size_); - map(is_trivial_dest ? GL_READ_ONLY : GL_READ_WRITE); - std::move(begin(), end(), std::back_inserter(existing)); - allocBuffer(size_); - map(GL_READ_WRITE); - std::move(existing.begin(), existing.end(), begin()); - } - - void - clear() noexcept(is_trivial_dest) - { - if constexpr (!is_trivial_dest) { - map(GL_READ_WRITE); - std::for_each(begin(), end(), [](auto && v) { - v.~T(); - }); - } - setSize(0); - } - - template<typename... P> - reference_type - emplace_back(P &&... ps) - requires std::is_constructible_v<T, P...> - { - auto newSize = size_ + 1; - reserve(newSize); - mapForAdd(); - new (&*end()) T {std::forward<P>(ps)...}; - setSize(newSize); - return back(); - } - - template<typename... P> - iterator - emplace(iterator pos, P &&... ps) - requires std::is_nothrow_constructible_v<T, P...> - { - auto newSize = size_ + 1; - const auto idx = pos - begin(); - reserve(newSize); - mapForAdd(); - pos = begin() + idx; - std::move_backward(pos, end(), end() + 1); - pos->~T(); - new (&*pos) T {std::forward<P>(ps)...}; - setSize(newSize); - return pos; - } - - reference_type - push_back(T p) - requires std::is_move_constructible_v<T> - { - auto newSize = size_ + 1; - reserve(newSize); - mapForAdd(); - new (&*end()) T {std::move(p)}; - setSize(newSize); - return back(); - } - - iterator - insert(iterator pos, T p) - requires std::is_nothrow_move_constructible_v<T> - { - auto newSize = size_ + 1; - const auto idx = pos - begin(); - reserve(newSize); - mapForAdd(); - pos = begin() + idx; - std::move_backward(pos, end(), end() + 1); - pos->~T(); - new (&*pos) T {std::move(p)}; - setSize(newSize); - return pos; - } - - void - pop_back() - { - if constexpr (!is_trivial_dest) { - map(GL_READ_WRITE); - back().~T(); - } - --size_; - } - - void - erase(iterator pos) - { - erase(pos, pos + 1); - } - - void - erase(iterator pos, iterator to) - { - const auto eraseSize = to - pos; - map(GL_READ_WRITE); - std::move(to, end(), pos); - if constexpr (!is_trivial_dest) { - std::for_each(end() - eraseSize, end(), [](auto && v) { - v.~T(); - }); - } - setSize(size_ - static_cast<size_type>(eraseSize)); - } - -protected: - void - setSize(size_type s) - { - size_ = s; - } - - void - allocBuffer(size_type newCapacity) - { - if (newCapacity == 0) { - return allocBuffer(1); - } - glBindBuffer(target_, buffer_); - glBufferData(target_, static_cast<GLsizeiptr>(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW); - glBindBuffer(target_, 0); - capacity_ = newCapacity; - data_ = {}; - access_ = {}; - } - - void - map(GLenum access) const - { - if (size_ > 0) { - mapMode(access); - } - } - - void - mapForAdd() const - { - if (!data_) { - mapMode(GL_READ_WRITE); - } - } - - void - mapMode(GLenum access) const - { - if (data_ && access_ < access) { - unmap(); - } - if (!data_) { - glBindBuffer(target_, buffer_); - data_ = static_cast<T *>(glMapBuffer(target_, access)); - glBindBuffer(target_, 0); - assert(data_); - access_ = access; - } - } - - span - mkspan() const - { - assert(!size_ || data_); - return span {data_, size_}; - } - - const_span - mkcspan() const - { - assert(!size_ || data_); - return const_span {data_, size_}; - } - - glBuffer buffer_; - GLenum target_; - std::size_t capacity_ {}; - std::size_t size_ {}; - mutable T * data_; - mutable GLenum access_ {}; -}; diff --git a/lib/glMappedBufferSpan.h b/lib/glMappedBufferSpan.h new file mode 100644 index 0000000..d30c911 --- /dev/null +++ b/lib/glMappedBufferSpan.h @@ -0,0 +1,51 @@ +#pragma once + +#include "special_members.h" +#include <cstddef> +#include <glad/gl.h> +#include <span> +#include <utility> + +template<typename T> class glMappedBufferSpan : public std::span<T> { +public: + glMappedBufferSpan(GLuint buffer, size_t count, GLenum access, bool reinit) : + std::span<T> {[&]() { + if (reinit) { + glNamedBufferData( + buffer, static_cast<GLsizeiptr>(sizeof(T) * count), nullptr, GL_DYNAMIC_DRAW); + } + return static_cast<T *>(glMapNamedBuffer(buffer, access)); + }(), + count}, + buffer {buffer} + { + } + + ~glMappedBufferSpan() + { + if (buffer) { + glUnmapNamedBuffer(buffer); + } + } + + glMappedBufferSpan(glMappedBufferSpan && other) noexcept : + std::span<T> {other}, buffer {std::exchange(other.buffer, 0)} + { + } + + glMappedBufferSpan & + operator=(glMappedBufferSpan && other) noexcept + { + std::span<T>::span = other; + if (buffer) { + glUnmapBuffer(buffer); + } + buffer = std::exchange(other.buffer, 0); + return *this; + } + + NO_COPY(glMappedBufferSpan); + +private: + GLuint buffer; +}; diff --git a/lib/gl_traits.h b/lib/gl_traits.h index b3c6909..eff9304 100644 --- a/lib/gl_traits.h +++ b/lib/gl_traits.h @@ -14,27 +14,33 @@ struct gl_traits_base { }; struct gl_traits_float : public gl_traits_base { - static constexpr auto vertexAttribFunc { - [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint { - glVertexAttribPointer(index, size, type, GL_FALSE, stride, pointer); - return 1; - }}; + template<GLenum type, GLint size> + static GLuint + vertexAttribFormatFunc(GLuint vao, GLuint index, GLuint offset) + { + glVertexArrayAttribFormat(vao, index, size, type, GL_FALSE, offset); + return 1; + } }; struct gl_traits_longfloat : public gl_traits_base { - static constexpr auto vertexAttribFunc { - [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint { - glVertexAttribLPointer(index, size, type, stride, pointer); - return 1; - }}; + template<GLenum type, GLint size> + static GLuint + vertexAttribFormatFunc(GLuint vao, GLuint index, GLuint offset) + { + glVertexArrayAttribLFormat(vao, index, size, type, offset); + return 1; + } }; struct gl_traits_integer : public gl_traits_base { - static constexpr auto vertexAttribFunc { - [](GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) -> GLuint { - glVertexAttribIPointer(index, size, type, stride, pointer); - return 1; - }}; + template<GLenum type, GLint size> + static GLuint + vertexAttribFormatFunc(GLuint vao, GLuint index, GLuint offset) + { + glVertexArrayAttribIFormat(vao, index, size, type, offset); + return 1; + } }; template<> struct gl_traits<glm::f32> : public gl_traits_float { @@ -42,36 +48,43 @@ template<> struct gl_traits<glm::f32> : public gl_traits_float { static constexpr auto glUniformFunc {&glUniform1f}; static constexpr std::array glUniformvFunc {&glUniform1fv, &glUniform2fv, &glUniform3fv, &glUniform4fv}; static constexpr std::array glUniformmFunc {&glUniformMatrix2fv, &glUniformMatrix3fv, &glUniformMatrix4fv}; - static constexpr auto glTexParameterFunc {&glTexParameterf}; - static constexpr auto glTexParameterfFunc {&glTexParameterfv}; + static constexpr auto glTextureParameterFunc {&glTextureParameterf}; + static constexpr auto glTextureParametervFunc {&glTextureParameterfv}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::f64> : public gl_traits_longfloat { static constexpr GLenum type {GL_DOUBLE}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::int8> : public gl_traits_integer { static constexpr GLenum type {GL_BYTE}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::int16> : public gl_traits_integer { static constexpr GLenum type {GL_SHORT}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::int32> : public gl_traits_integer { static constexpr GLenum type {GL_INT}; static constexpr auto glUniformFunc {&glUniform1i}; static constexpr std::array glUniformvFunc {&glUniform1iv, &glUniform2iv, &glUniform3iv, &glUniform4iv}; - static constexpr auto glTexParameterFunc {&glTexParameteri}; - static constexpr auto glTexParameterfFunc {&glTexParameteriv}; + static constexpr auto glTextureParameterFunc {&glTextureParameteri}; + static constexpr auto glTextureParametervFunc {&glTextureParameteriv}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::uint8> : public gl_traits_integer { static constexpr GLenum type {GL_UNSIGNED_BYTE}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::uint16> : public gl_traits_integer { static constexpr GLenum type {GL_UNSIGNED_SHORT}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<> struct gl_traits<glm::uint32> : public gl_traits_integer { @@ -79,35 +92,44 @@ template<> struct gl_traits<glm::uint32> : public gl_traits_integer { static constexpr auto glUniformFunc {&glUniform1ui}; static constexpr std::array<decltype(&glUniform1uiv), 5> glUniformvFunc { &glUniform1uiv, &glUniform2uiv, &glUniform3uiv, &glUniform4uiv}; + static constexpr auto vertexArrayAttribFormat {&vertexAttribFormatFunc<type, 1>}; }; template<typename T, std::size_t S> struct gl_traits<std::array<T, S>> : public gl_traits<T> { static constexpr GLint size {S * gl_traits<T>::size}; - static constexpr auto vertexAttribFunc { - [](GLuint index, GLint, GLenum type, GLsizei stride, const void * pointer) -> GLuint { - const auto base = static_cast<const T *>(pointer); - for (GLuint e = 0; e < S; e++) { - glVertexAttribPointer(index + e, gl_traits<T>::size, type, GL_FALSE, stride, base + e); - } - return S; - }}; + static constexpr auto vertexArrayAttribFormat {[](GLuint vao, GLuint index, GLuint offset) { + if constexpr (std::is_pod_v<T>) { + return gl_traits<T>::template vertexAttribFormatFunc<gl_traits<T>::type, S>(vao, index, offset); + } + else { + GLuint used = 0; + for (GLuint e = 0; e < S; e++) { + used += gl_traits<T>::template vertexAttribFormatFunc<gl_traits<T>::type, 1>( + vao, index + e, offset + (e * sizeof(T))); + } + return used; + } + }}; }; template<glm::length_t L, typename T, glm::qualifier Q> struct gl_traits<glm::vec<L, T, Q>> : public gl_traits<T> { static constexpr GLint size {L}; + static constexpr auto vertexArrayAttribFormat {[](GLuint vao, GLuint index, GLuint offset) { + return gl_traits<T>::template vertexAttribFormatFunc<gl_traits<T>::type, L>(vao, index, offset); + }}; }; template<glm::length_t C, glm::length_t R, typename T, glm::qualifier Q> struct gl_traits<glm::mat<C, R, T, Q>> : public gl_traits<T> { static constexpr GLint size {C * R}; - static constexpr auto vertexAttribFunc { - [](GLuint index, GLint, GLenum type, GLsizei stride, const void * pointer) -> GLuint { - const auto base = static_cast<const T *>(pointer); - for (GLuint r = 0; r < R; r++) { - glVertexAttribPointer(index + r, C, type, GL_FALSE, stride, base + (r * C)); - } - return R; - }}; + static constexpr auto vertexArrayAttribFormat {[](GLuint vao, GLuint index, GLuint offset) { + GLuint used = 0; + for (GLuint row = 0; row < R; row++) { + used += gl_traits<T>::template vertexAttribFormatFunc<gl_traits<T>::type, C>( + vao, index + row, offset + (C * row * static_cast<GLuint>(sizeof(T)))); + } + return used; + }}; }; template<typename T> @@ -117,9 +139,9 @@ concept has_glUniformNv = requires { gl_traits<T>::glUniformvFunc; }; template<typename T> concept has_glUniformMatrixNv = requires { gl_traits<T>::glUniformmFunc; }; template<typename T> -concept has_glTexParameter = requires { gl_traits<T>::glTexParameterFunc; }; +concept has_glTextureParameter = requires { gl_traits<T>::glTextureParameterFunc; }; template<typename T> -concept has_glTexParameterf = requires { gl_traits<T>::glTexParameterfFunc; }; +concept has_glTextureParameterv = requires { gl_traits<T>::glTextureParametervFunc; }; template<has_glUniform1 T> void @@ -163,17 +185,3 @@ glUniform(GLint location, std::span<const glm::mat<L, L, T, Q>> v) (*gl_traits<T>::glUniformmFunc[L - 2])( location, static_cast<GLsizei>(v.size()), GL_FALSE, glm::value_ptr(v.front())); } - -template<has_glTexParameter T> -void -glTexParameter(GLenum target, GLenum pname, T param) -{ - (*gl_traits<T>::glTexParameterFunc)(target, pname, param); -} - -template<glm::length_t L, has_glTexParameterf T, glm::qualifier Q> -void -glTexParameter(GLenum target, GLenum pname, const glm::vec<L, T, Q> & param) -{ - (*gl_traits<T>::glTexParameterfFunc)(target, pname, glm::value_ptr(param)); -} 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/location.cpp b/lib/location.cpp index 13acfde..2138f0a 100644 --- a/lib/location.cpp +++ b/lib/location.cpp @@ -2,6 +2,14 @@ #include "maths.h" #include <glm/gtx/transform.hpp> +Location +Location::operator+(RelativePosition3D offset) const +{ + Location ret {*this}; + ret.pos += offset; + return ret; +} + glm::mat3 Location::getRotationTransform() const { diff --git a/lib/location.h b/lib/location.h index 016aee7..e642b41 100644 --- a/lib/location.h +++ b/lib/location.h @@ -11,6 +11,8 @@ public: [[nodiscard]] glm::mat3 getRotationTransform() const; + Location operator+(RelativePosition3D) const; + GlobalPosition3D pos; Rotation3D rot; }; 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..4a835d3 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,353 @@ 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 + +// GLM round is not constexpr :( And we can use lround to convert at the same time +template<glm::length_t D, std::floating_point T, glm::qualifier Q> +[[nodiscard]] constexpr glm::vec<D, long, Q> +lround(const glm::vec<D, T, Q> & value) +{ + glm::vec<D, long, Q> out {}; + for (glm::length_t axis = 0; axis < D; ++axis) { + out[axis] = std::lround(value[axis]); + } + return out; +} 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 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 g - GlobalPosition<D>(r); + 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 rotation<D, T, Q>(angle, {0, 0}, {0, 1}, {1, 1}, {1, 0}); +} + +// 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) { - return sincosf(a, &s, &c); + return rotation<D, T, Q>(angle, {0, 0}, {1, 0}, {1, 1}, {0, 1}); } -inline Rotation2D -sincosf(float a) +// 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) { - Rotation2D sc; - sincosf(a, sc.x, sc.y); - return sc; + return rotation<D, T, Q>(angle, {0, 0}, {2, 0}, {2, 2}, {0, 2}); } -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 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}); +} -float vector_yaw(const Direction2D & diff); -float vector_pitch(const Direction3D & diff); +// 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 rotate_yaw<D>(angles.y) * rotate_pitch<D>(angles.x) * rotate_roll<D>(angles.z); +} -template<typename T, glm::qualifier Q> -glm::vec<2, T, Q> -vector_normal(const glm::vec<2, T, Q> & v) +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 {-v.y, v.x}; + 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>(a, b); + return crossProduct<Q>(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<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 glm::cross(a, b); + return glm::cross(valueA, valueB); } -template<typename R = float, typename Ta, typename Tb> -inline constexpr auto -ratio(Ta a, Tb b) +template<Arithmetic R = float, Arithmetic Ta, Arithmetic Tb> +constexpr auto +ratio(const Ta valueA, const Tb valueB) { - return (static_cast<R>(a) / static_cast<R>(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 T, glm::qualifier Q> -inline constexpr auto -ratio(glm::vec<2, T, Q> v) +template<Arithmetic R = float, Arithmetic T, glm::qualifier Q = glm::defaultp> +constexpr auto +ratio(const glm::vec<2, T, Q> & value) { - return ratio<R>(v.x, v.y); + return ratio<R>(value.x, value.y); } -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 L = 3, std::floating_point T, glm::qualifier Q = glm::defaultp> +constexpr auto +perspective_divide(const glm::vec<4, T, Q> & value) { - return v / v.w; + return value / value.w; } -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 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 {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, 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, 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> +perspectiveMultiply(const 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; + 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> -perspectiveApply(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) { - return p = perspectiveMultiply(p, mutation); + return base = perspectiveMultiply(base, mutation); } -float normalize(float ang); +template<std::floating_point T> +constexpr T +normalize(T ang) +{ + while (ang > glm::pi<T>()) { + ang -= glm::two_pi<T>(); + } + while (ang <= -glm::pi<T>()) { + ang += glm::two_pi<T>(); + } + return ang; +} -template<typename T, glm::qualifier Q> +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<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 +428,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 +463,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 {}; @@ -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.cpp b/lib/util.cpp deleted file mode 100644 index 408a76a..0000000 --- a/lib/util.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "util.h" @@ -3,6 +3,8 @@ #include <algorithm> // IWYU pragma: keep #include <array> #include <cstddef> +#include <memory> +#include <tuple> template<typename T, std::size_t N> constexpr auto @@ -12,3 +14,44 @@ 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>; + +template<typename T, typename M> struct Decompose { + consteval Decompose(M T::*) { } + + using ValueType = M; + using ContainerType = T; +}; + +template<auto MbrPtr> using MemberValueType = typename decltype(Decompose {MbrPtr})::ValueType; +template<auto MbrPtr> using ContainerType = typename decltype(Decompose {MbrPtr})::ContainerType; + +template<typename T> +bool +createIfRequired(std::shared_ptr<T> & instance, std::weak_ptr<T> & common) +{ + if (!instance && !(instance = common.lock())) { + common = instance = std::make_shared<T>(); + return true; + } + return false; +} 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/brush47.xml b/res/brush47.xml index dc33282..2271073 100644 --- a/res/brush47.xml +++ b/res/brush47.xml @@ -96,6 +96,8 @@ <use type="buffers" position="0,9.69,1.2" colour="grey20"/> <use type="buffers" position="0,-9.69,1.2" colour="grey20" rotation="0,3.14159,0"/> </bodyMesh> + <spotLight position="1000,969,1300" direction="0,1,0" colour=".9,.9,.9" kq="0.001" arc=".3"/> + <spotLight position="-1000,969,1300" direction="0,1,0" colour=".9,.9,.9" kq="0.001" arc=".3"/> <bogie id="bogie1"> <use type="bogie" position="0,-1.85,0"/> </bogie> diff --git a/res/shapespark-low-poly-plants-kit.fbx b/res/shapespark-low-poly-plants-kit.fbx Binary files differindex fe87c0c..7457adb 100644 --- a/res/shapespark-low-poly-plants-kit.fbx +++ b/res/shapespark-low-poly-plants-kit.fbx 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 Binary files differdeleted file mode 100644 index 7a091f3..0000000 --- a/res/ui/icon/network.png +++ /dev/null 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..baf0db4 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -4,6 +4,7 @@ import path : glob-tree ; lib boost_unit_test_framework ; lib benchmark ; +lib stdc++exp ; path-constant res : ../res ; path-constant fixtures : fixtures ; @@ -42,32 +43,42 @@ project : requirements <toolset>tidy:<xcheckxx>hicpp-vararg <toolset>tidy:<librarydef>boost ; -lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ; +lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] : <library>stdc++exp ; +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 ; +run test-text.cpp : -- : test-glAllocator : <library>test ; run test-enumDetails.cpp ; -run test-render.cpp : -- : test-assetFactory : <library>test ; +run test-render.cpp : -- : : <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 ; -run test-glContainer.cpp : : : <library>test ; +run test-instancing.cpp : -- : test-glAllocator : <library>test ; +perfrun perf-instancing.cpp : : test-instancing ; +run test-glAllocator.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 ; +compile test-static-util.cpp ; +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/1.json b/test/fixtures/geoData/deform/1.json index 6930238..06d97c4 100644 --- a/test/fixtures/geoData/deform/1.json +++ b/test/fixtures/geoData/deform/1.json @@ -36,7 +36,7 @@ -1.5 ] ], - "/tmp/geoData0.tga" + "geoData0.tga" ], [ [ @@ -51,7 +51,7 @@ -1.5 ] ], - "/tmp/geoData1.tga" + "geoData1.tga" ], [ [ @@ -66,7 +66,7 @@ -1.5 ] ], - "/tmp/geoData2.tga" + "geoData2.tga" ] ] ], @@ -102,7 +102,7 @@ -1.5 ] ], - "/tmp/geoData3.tga" + "geoData3.tga" ], [ [ @@ -117,7 +117,7 @@ -0.5 ] ], - "/tmp/geoData4.tga" + "geoData4.tga" ] ] ], @@ -153,7 +153,7 @@ -1.5 ] ], - "/tmp/geoData5.tga" + "geoData5.tga" ] ] ], @@ -194,7 +194,7 @@ -1.5 ] ], - "/tmp/geoData6.tga" + "geoData6.tga" ] ] ] 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..4373e5a 100644 --- a/test/perf-instancing.cpp +++ b/test/perf-instancing.cpp @@ -3,41 +3,72 @@ #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) + const TestMainWindowAppBase _; + + class Data : public benchmark::Fixture { + public: + void + SetUp(::benchmark::State & state) override + { + auto count = static_cast<size_t>(state.range()); + std::mt19937 gen(std::random_device {}()); + std::uniform_int_distribution<GlobalDistance> xyDistrib(0, 1000000); + std::uniform_int_distribution<GlobalDistance> zDistrib(0, 10000); + proxies.reserve(count); + instances.reserve(count); + + while (count--) { + proxies.emplace_back(instances.acquire( + GlobalPosition3D {xyDistrib(gen), xyDistrib(gen), zDistrib(gen)}, glm::mat3 {})); + } + } + + void + TearDown(::benchmark::State &) override + { + proxies.clear(); + } + + InstanceVertices<Instance> instances; + std::vector<InstanceVertices<Instance>::InstanceProxy> proxies; + }; + + BENCHMARK_DEFINE_F(Data, partition1)(benchmark::State & state) { - 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 {})); + GlobalPosition2D pos {}; + for (auto loop : state) { + 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; } } - 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; + BENCHMARK_DEFINE_F(Data, partition2)(benchmark::State & state) + { + GlobalPosition2D pos {}; + for (auto loop : state) { + instances.partition( + [&pos](const auto & instance) { + return std::abs(instance.pos.x - pos.x) < 5; + }, + [&pos](const auto & instance) { + return std::abs(instance.pos.y - pos.y) < 5; + }); + pos += GlobalPosition2D {33, 17}; + pos %= 1000000; + } } } -BENCHMARK(partition)->Range(0, 1 << 20); +BENCHMARK_REGISTER_F(Data, partition1)->Range(0, 1 << 20); +BENCHMARK_REGISTER_F(Data, partition2)->Range(0, 1 << 20); BENCHMARK_MAIN(); 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..b1cd822 100644 --- a/test/test-assetFactory.cpp +++ b/test/test-assetFactory.cpp @@ -19,72 +19,89 @@ #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"}; +namespace { + class FactoryFixture : public TestRenderOutputSize<TextureAbsCoord {2048, 1024}>, public SceneProvider { + public: + FactoryFixture() : sceneRenderer {size, output} { } -class FactoryFixture : public TestRenderOutputSize<TextureAbsCoord {2048, 1024}>, public SceneProvider { -public: - FactoryFixture() : sceneRenderer {size, output} { } + NO_COPY(FactoryFixture); + NO_MOVE(FactoryFixture); - ~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()); - } + ~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()); + outImage.saveColour(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 + 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 + lights(const SceneShader & shader) const override + { + 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 + 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 override - { - mapper.dynamicPoint.use(Location {{0, 0, 0}, {0, 0, 0}}); - objects.apply(&Renderable::shadows, mapper); - } + 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); - } + void + forEachRenderable(const RenderableProcessor & func) const override + { + for (const auto & [assetId, asset] : gameState.assets) { + if (const auto renderable = asset.getAs<Renderable>()) { + func(renderable); + } + } + gameState.world.apply<Renderable>(func); + } - Collection<const Renderable> objects; + void + render(float dist) + { + sceneRenderer.camera.setView({-dist, dist * 1.2F, dist * 1.2F}, south + east + down); + sceneRenderer.preFrame(*this, {{0.9, 0.5}}); + sceneRenderer.render(*this); + } -private: - SceneRenderer sceneRenderer; -}; + SharedCollection<const Renderable> objects; -BOOST_AUTO_TEST_CASE(surfaces, *boost::unit_test::timeout(5)) + 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 +110,88 @@ 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 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>(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 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}}); + auto plant5 = std::make_shared<Plant>(tree011, Location {.pos = {500'000, -500'000, -400'000}, .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 +200,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 +212,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 +228,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 +238,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 +267,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); +namespace { + class Base { + public: + Base() = default; + virtual ~Base() = default; + DEFAULT_MOVE_COPY(Base); - virtual bool - add() - { - total += 1; - return false; - } + virtual bool + add() + { + total += 1; + return false; + } - unsigned int total {0}; -}; + [[nodiscard]] virtual bool + yes() const + { + return true; + } -class Sub : public Base { -public: - bool - add() override - { - total += 2; - return true; - } -}; + unsigned int total {0}; + }; -using TestCollection = Collection<Base>; + class Sub : public Base { + public: + bool + add() override + { + total += 2; + return true; + } + }; -BOOST_TEST_DONT_PRINT_LOG_VALUE(Collection<Base>::Objects::const_iterator) -BOOST_TEST_DONT_PRINT_LOG_VALUE(Collection<Base>::Objects::const_reverse_iterator) + 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(EmplaceOthers) +{ + 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(a_rbase) +BOOST_AUTO_TEST_CASE(ABaseRApply) { - auto b = create<Base>(); + 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..4fb5d13 --- /dev/null +++ b/test/test-environment.cpp @@ -0,0 +1,95 @@ +#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>; + using SunAmtTestData = std::tuple<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}, + {{90.F, 0.F}, west}, + {{-90.F, 0.F}, east}, + // From above + // EqGM midnight, sun below horizon, shining upwards + {{181.52F, -66.86F}, {-0.01F, 0.39F, 0.919F}}, + // EqGM just before sunrise, mostly west, north a bit, up a bit + {{113.12F, -0.85F}, {-0.92F, 0.39F, 0.015F}}, + // EqGM just after sunrise, mostly west, north a bit, down a bit + {{113.12F, 6.05F}, {-0.92F, 0.39F, -0.015F}}, + // Doncaster noon, roughly from south to north, high in the sky, downward + {{176.34F, 59.64F}, {-0.03F, 0.5F, -0.86F}}, + }), + position, direction) +{ + const LightDirection lightDir {position * degreesToRads}; + BOOST_CHECK_CLOSE_VEC(lightDir.vector(), direction); + BOOST_CHECK_CLOSE(glm::length(lightDir.vector()), 1.F, 1); +} + +BOOST_DATA_TEST_CASE(SunDirectionalAmount, + boost::unit_test::data::make<SunAmtTestData>({ + {5._degrees, 1.F}, + {1._degrees, 1.F}, + {0._degrees, 1.F}, + {-0.25_degrees, 1.F}, + {-0.5_degrees, 0.5F}, + {-0.75_degrees, 0.F}, + {-1._degrees, 0.F}, + {-5._degrees, 0.F}, + }), + elevation, amount) +{ + const LightDirection lightDir {{0, elevation}}; + BOOST_CHECK_CLOSE(lightDir.directional(), amount, 1.F); +} + +BOOST_DATA_TEST_CASE(SunAmbientAmount, + boost::unit_test::data::make<SunAmtTestData>({ + {20._degrees, 1.F}, + {1._degrees, 1.F}, + {-9._degrees, 0.5F}, + {-18._degrees, 0.F}, + {-25._degrees, 0.F}, + {-50._degrees, 0.F}, + }), + elevation, amount) +{ + const LightDirection lightDir {{0, elevation}}; + BOOST_CHECK_CLOSE(lightDir.ambient(), amount, 1.F); +} diff --git a/test/test-geoData-counts.cpp b/test/test-geoData-counts.cpp new file mode 100644 index 0000000..f67c5e9 --- /dev/null +++ b/test/test-geoData-counts.cpp @@ -0,0 +1,66 @@ +#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" +#include "test/testHelpers.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, ANALYSIS_DIRECTORY / std::format("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..e489925 100644 --- a/test/test-geoData.cpp +++ b/test/test-geoData.cpp @@ -1,96 +1,85 @@ #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; + const TestTerrainMesh FIXED_TERRTAIN; +} -constexpr size_t ncols = 200, nrows = 200, xllcorner = 310000000, yllcorner = 490000000, cellsize = 50000; +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 +94,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 +138,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 +174,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) -{ - constexpr static GeoData::Triangle<3> t {{0, 0, 0}, {5, 0, 0}, {5, 5, 0}}; +using WalkTerrainCurveData = std::tuple<GlobalPosition2D, GlobalPosition2D, GlobalPosition2D, std::vector<int>, + std::vector<GlobalPosition2D>>; - 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_TEST_DECORATOR(*boost::unit_test::timeout(1)) - BOOST_CHECK_CLOSE(t.angleAt({0, 1, 0}), 0.F, 0.01F); +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) +{ + BOOST_REQUIRE_EQUAL(visits.size(), exits.size() + 1); - 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 +248,35 @@ 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::make_shared<Terrain>(std::move(geoData))) { } - const Terrain terrain; + std::shared_ptr<Terrain> terrain; + + void + forEachRenderable(const RenderableProcessor & func) const override + { + func(terrain.get()); + } 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 +285,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)); - Texture::save(tro.outImage, cam.second.c_str()); + 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)); + tro.outImage.saveColour((ANALYSIS_DIRECTORY / 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-glAllocator.cpp b/test/test-glAllocator.cpp new file mode 100644 index 0000000..baf658c --- /dev/null +++ b/test/test-glAllocator.cpp @@ -0,0 +1,29 @@ +#define BOOST_TEST_MODULE glAllocator + +#include "testMainWindow.h" +#include <boost/test/data/test_case.hpp> +#include <boost/test/unit_test.hpp> + +#include "glAllocator.h" +#include <ranges> + +BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase); + +namespace { + BOOST_AUTO_TEST_CASE(Simple) + { + GLuint name = 0; + { + glVector<long double> list; + BOOST_REQUIRE_EQUAL(list.begin().base().bufferName(), 0); + list.reserve(5); + name = list.begin().base().bufferName(); + BOOST_REQUIRE_GT(name, 0); + std::ranges::copy(std::views::iota(0, 10), std::back_inserter(list)); + BOOST_REQUIRE_EQUAL(10, list.size()); + BOOST_CHECK_EQUAL(0, list.front()); + BOOST_CHECK_EQUAL(9, list.back()); + } + BOOST_CHECK(!glIsBuffer(name)); + } +} diff --git a/test/test-glContainer.cpp b/test/test-glContainer.cpp deleted file mode 100644 index 332d440..0000000 --- a/test/test-glContainer.cpp +++ /dev/null @@ -1,452 +0,0 @@ -#define BOOST_TEST_MODULE glContainer - -#include "testMainWindow.h" -#include <boost/test/data/test_case.hpp> -#include <boost/test/unit_test.hpp> - -#include "glContainer.h" - -// Force generation of all functions -template class glContainer<int>; - -BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::iterator); -BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::const_iterator); -BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::reverse_iterator); -BOOST_TEST_DONT_PRINT_LOG_VALUE(glContainer<int>::const_reverse_iterator); - -BOOST_GLOBAL_FIXTURE(TestMainWindowAppBase); - -BOOST_FIXTURE_TEST_SUITE(i, glContainer<int>) - -BOOST_AUTO_TEST_CASE(createDestroy, *boost::unit_test::timeout(1)) -{ - // Unmapped - BOOST_CHECK(!data_); - BOOST_CHECK(!access_); - // Request map, but empty - BOOST_CHECK_NO_THROW(map(GL_READ_ONLY)); - BOOST_REQUIRE(!data_); - BOOST_REQUIRE(!access_); - BOOST_CHECK_NO_THROW(map(GL_READ_WRITE)); - BOOST_REQUIRE(!data_); - BOOST_REQUIRE(!access_); - // Add something - BOOST_CHECK_NO_THROW(emplace_back(0)); - BOOST_REQUIRE(data_); - BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE); - // Unmap - BOOST_CHECK_NO_THROW(unmap()); - BOOST_REQUIRE(!data_); - BOOST_REQUIRE(!access_); - // Map RO - BOOST_CHECK_NO_THROW(map(GL_READ_ONLY)); - BOOST_REQUIRE(data_); - BOOST_REQUIRE_EQUAL(access_, GL_READ_ONLY); - // Map RW upgradde - BOOST_CHECK_NO_THROW(map(GL_READ_WRITE)); - BOOST_REQUIRE(data_); - BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE); - // Map RO downgradde, no change - BOOST_CHECK_NO_THROW(map(GL_READ_ONLY)); - BOOST_REQUIRE(data_); - BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE); - // Unmap - BOOST_CHECK_NO_THROW(unmap()); - BOOST_CHECK(!data_); - BOOST_CHECK(!access_); -} - -BOOST_AUTO_TEST_CASE(mapModes) -{ - BOOST_CHECK_EQUAL(std::accumulate(begin(), end(), 0), 0); - BOOST_CHECK(!data_); - BOOST_CHECK(!access_); - - BOOST_CHECK_NO_THROW(push_back(1)); - BOOST_CHECK_NO_THROW(push_back(2)); - BOOST_CHECK_NO_THROW(push_back(3)); - BOOST_CHECK_NO_THROW(push_back(4)); - BOOST_CHECK_NO_THROW(unmap()); - - BOOST_CHECK_EQUAL(std::accumulate(cbegin(), cend(), 0), 10); - BOOST_CHECK(data_); - BOOST_CHECK_EQUAL(access_, GL_READ_ONLY); - BOOST_CHECK_NO_THROW(unmap()); - - BOOST_CHECK_EQUAL(std::accumulate(begin(), end(), 0), 10); - BOOST_CHECK(data_); - 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(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_CHECK_EQUAL(capacity_, 1); - BOOST_CHECK_EQUAL(size_, 0); - BOOST_CHECK_NO_THROW(push_back(1)); - BOOST_CHECK_NO_THROW(push_back(2)); - BOOST_CHECK_NO_THROW(push_back(3)); - BOOST_CHECK_NO_THROW(push_back(4)); - BOOST_CHECK_NO_THROW(push_back(5)); - BOOST_CHECK_EQUAL(capacity_, 8); - BOOST_CHECK_EQUAL(size_, 5); - { - std::array expected1 {1, 2, 3, 4, 5}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected1.rbegin(), expected1.rend()); - } -} - -BOOST_AUTO_TEST_CASE(emplace_back_test, *boost::unit_test::timeout(1)) -{ - BOOST_CHECK_EQUAL(capacity_, 1); - BOOST_CHECK_EQUAL(size_, 0); - - BOOST_CHECK_NO_THROW(emplace_back(1)); - BOOST_CHECK_NO_THROW(emplace_back(2)); - BOOST_CHECK_EQUAL(capacity_, 2); - BOOST_CHECK_EQUAL(size_, 2); - - BOOST_CHECK_NO_THROW(reserve(5)); - BOOST_CHECK_EQUAL(capacity_, 5); - BOOST_CHECK_EQUAL(size_, 2); - - BOOST_CHECK_NO_THROW(emplace_back(3)); - BOOST_CHECK_NO_THROW(emplace_back(4)); - BOOST_CHECK_EQUAL(capacity_, 5); - BOOST_CHECK_EQUAL(size_, 4); - - { - std::array expected1 {1, 2, 3, 4}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected1.rbegin(), expected1.rend()); - } - - BOOST_CHECK_NO_THROW(emplace_back(5)); - BOOST_CHECK_EQUAL(capacity_, 5); - BOOST_CHECK_EQUAL(size_, 5); - BOOST_CHECK_NO_THROW(emplace_back(6)); - BOOST_CHECK_NO_THROW(emplace_back(7)); - BOOST_CHECK_EQUAL(capacity_, 10); - BOOST_CHECK_EQUAL(size_, 7); - - { - std::array expected2 {1, 2, 3, 4, 5, 6, 7}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected2.begin(), expected2.end()); - BOOST_CHECK_EQUAL_COLLECTIONS(rbegin(), rend(), expected2.rbegin(), expected2.rend()); - } - - BOOST_CHECK_EQUAL(7, end() - begin()); - BOOST_CHECK_EQUAL(7, rend() - rbegin()); -} - -BOOST_AUTO_TEST_CASE(resize_test) -{ - BOOST_CHECK_NO_THROW(push_back(1)); - BOOST_CHECK_NO_THROW(emplace_back(2)); - BOOST_CHECK_NO_THROW(resize(4)); - BOOST_CHECK_EQUAL(capacity_, 4); - BOOST_CHECK_EQUAL(size_, 4); - { - std::array expected1 {1, 2, 0, 0}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - - BOOST_CHECK_NO_THROW(resize(1)); - BOOST_CHECK_EQUAL(capacity_, 1); - BOOST_CHECK_EQUAL(size_, 1); - { - std::array expected2 {1}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected2.begin(), expected2.end()); - } - - BOOST_CHECK_NO_THROW(resize(1)); - BOOST_CHECK_EQUAL(capacity_, 1); - BOOST_CHECK_EQUAL(size_, 1); - - BOOST_CHECK_NO_THROW(resize(0)); - BOOST_CHECK_EQUAL(capacity_, 1); - BOOST_CHECK_EQUAL(size_, 0); - BOOST_CHECK_EQUAL(begin(), end()); - BOOST_CHECK_EQUAL(rbegin(), rend()); -} - -BOOST_AUTO_TEST_CASE(shrink_to_fit_test) -{ - BOOST_CHECK_NO_THROW(reserve(4)); - BOOST_CHECK_NO_THROW(emplace_back(1)); - BOOST_CHECK_NO_THROW(emplace_back(2)); - BOOST_CHECK_EQUAL(capacity_, 4); - BOOST_CHECK_EQUAL(size_, 2); - { - std::array expected1 {1, 2}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - BOOST_CHECK_NO_THROW(shrink_to_fit()); - BOOST_CHECK_EQUAL(capacity_, 2); - BOOST_CHECK_EQUAL(size_, 2); - { - std::array expected1 {1, 2}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - - BOOST_CHECK_NO_THROW(shrink_to_fit()); - BOOST_CHECK_EQUAL(capacity(), 2); - BOOST_CHECK_EQUAL(size(), 2); - - BOOST_CHECK_NO_THROW(clear()); - BOOST_CHECK_EQUAL(capacity(), 2); - BOOST_CHECK_EQUAL(size(), 0); -} - -BOOST_AUTO_TEST_CASE(getters) -{ - BOOST_CHECK(empty()); - BOOST_CHECK_NO_THROW(emplace_back(1)); - BOOST_CHECK(!empty()); - BOOST_CHECK_NO_THROW(emplace_back(2)); - BOOST_CHECK_EQUAL(1, front()); - BOOST_CHECK_EQUAL(2, back()); - BOOST_CHECK_EQUAL(1, at(0)); - BOOST_CHECK_EQUAL(2, at(1)); - BOOST_CHECK_EQUAL(1, (*this)[0]); - BOOST_CHECK_EQUAL(2, (*this)[1]); - BOOST_CHECK_EQUAL(data_, data()); - BOOST_CHECK_THROW(std::ignore = at(2), std::out_of_range); - - const auto & constCont {*this}; - BOOST_CHECK_EQUAL(1, constCont.front()); - BOOST_CHECK_EQUAL(2, constCont.back()); - { - std::array expected1 {1, 2}; - BOOST_CHECK_EQUAL_COLLECTIONS(constCont.begin(), constCont.end(), expected1.begin(), expected1.end()); - BOOST_CHECK_EQUAL_COLLECTIONS(constCont.rbegin(), constCont.rend(), expected1.rbegin(), expected1.rend()); - BOOST_CHECK_EQUAL_COLLECTIONS(constCont.cbegin(), constCont.cend(), expected1.cbegin(), expected1.cend()); - BOOST_CHECK_EQUAL_COLLECTIONS(constCont.crbegin(), constCont.crend(), expected1.crbegin(), expected1.crend()); - } - BOOST_CHECK_EQUAL(1, constCont.at(0)); - BOOST_CHECK_EQUAL(2, constCont.at(1)); - BOOST_CHECK_EQUAL(1, constCont[0]); - BOOST_CHECK_EQUAL(2, constCont[1]); - BOOST_CHECK_EQUAL(data_, constCont.data()); - BOOST_CHECK_THROW(std::ignore = constCont.at(2), std::out_of_range); -} - -BOOST_AUTO_TEST_CASE(random_access) -{ - 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); -} - -BOOST_AUTO_TEST_CASE(random_write) -{ - BOOST_CHECK_NO_THROW(resize(3)); - BOOST_CHECK_EQUAL(size(), 3); - BOOST_CHECK_NO_THROW(unmap()); - BOOST_REQUIRE(!data_); - BOOST_CHECK_NO_THROW(at(0, 10)); - BOOST_CHECK_NO_THROW(at(1, 20)); - BOOST_CHECK_NO_THROW(at(2, 30)); - BOOST_CHECK(!data_); - { - std::array expected1 {10, 20, 30}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - BOOST_CHECK(data_); - BOOST_CHECK_NO_THROW(at(1, 40)); - { - std::array expected1 {10, 40, 30}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - BOOST_CHECK_THROW(at(4, 0), std::out_of_range); -} - -BOOST_AUTO_TEST_CASE(insert_remove_test) -{ - BOOST_CHECK_NO_THROW(emplace_back(1)); - BOOST_CHECK_NO_THROW(emplace_back(2)); - BOOST_CHECK_NO_THROW(emplace_back(3)); - BOOST_CHECK_NO_THROW(emplace_back(4)); - BOOST_CHECK_NO_THROW(pop_back()); - BOOST_CHECK_EQUAL(size_, 3); - BOOST_CHECK_EQUAL(capacity_, 4); - - BOOST_CHECK_NO_THROW(emplace(begin(), 5)); - BOOST_CHECK_EQUAL(size_, 4); - BOOST_CHECK_EQUAL(capacity_, 4); - { - std::array expected1 {5, 1, 2, 3}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - - { - std::array expected1 {2, 3}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin() + 2, end(), expected1.begin(), expected1.end()); - } - BOOST_CHECK_NO_THROW(insert(begin() + 2, 6)); - BOOST_CHECK_EQUAL(size_, 5); - BOOST_CHECK_EQUAL(capacity_, 8); - { - std::array expected1 {5, 1, 6, 2, 3}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - erase(begin() + 1); - BOOST_CHECK_EQUAL(size_, 4); - BOOST_CHECK_EQUAL(capacity_, 8); - { - std::array expected1 {5, 6, 2, 3}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } - erase(begin() + 1, end() - 1); - BOOST_CHECK_EQUAL(size_, 2); - BOOST_CHECK_EQUAL(capacity_, 8); - { - std::array expected1 {5, 3}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end()); - } -} - -BOOST_AUTO_TEST_CASE(stl) -{ - BOOST_CHECK_NO_THROW(resize(10)); - BOOST_CHECK_NO_THROW(std::generate(begin(), end(), [x = 0]() mutable { - return x++; - })); - 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; - }); - { - std::array expected1 {9, 7, 5, 3, 1}; - BOOST_CHECK_EQUAL_COLLECTIONS(begin(), newend, expected1.begin(), expected1.end()); - } -} - -BOOST_AUTO_TEST_CASE(iter_compare) -{ - BOOST_CHECK_EQUAL(begin(), end()); - BOOST_CHECK_EQUAL(rbegin(), rend()); - emplace_back(); - BOOST_CHECK_LT(begin(), end()); - BOOST_CHECK_LT(rbegin(), rend()); - BOOST_CHECK_LT(cbegin(), cend()); - BOOST_CHECK_LT(crbegin(), crend()); -} - -BOOST_AUTO_TEST_SUITE_END(); - -BOOST_AUTO_TEST_CASE(create_copy_source, *boost::unit_test::timeout(1)) -{ - const std::vector src {4, 6, 2, 4, 6, 0}; - glContainer dst {src}; - static_assert(std::is_same_v<decltype(src)::value_type, decltype(dst)::value_type>); - dst.unmap(); - BOOST_CHECK_EQUAL_COLLECTIONS(src.begin(), src.end(), dst.begin(), dst.end()); -} - -struct C { - int x; - float y; -}; - -static_assert(std::is_trivially_destructible_v<C>); - -BOOST_FIXTURE_TEST_SUITE(c, glContainer<C>) - -BOOST_AUTO_TEST_CASE(basic) -{ - BOOST_CHECK_NO_THROW(emplace_back(1, 2.F)); - BOOST_CHECK_EQUAL(1, begin()->x); - BOOST_CHECK_EQUAL(2.F, begin()->y); - BOOST_CHECK_NO_THROW(begin()->x = 3); - BOOST_CHECK_EQUAL(3, begin()->x); - - BOOST_CHECK_NO_THROW(push_back(C {4, 5.F})); - BOOST_CHECK_EQUAL(3, begin()->x); - BOOST_CHECK_EQUAL(2.F, begin()->y); - BOOST_CHECK_EQUAL(4, rbegin()->x); - BOOST_CHECK_EQUAL(5.F, rbegin()->y); -} - -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_CHECK_NO_THROW(emplace_back(1, 2.F)); - BOOST_CHECK_EQUAL(1, begin()->x); - BOOST_CHECK_EQUAL(2.F, begin()->y); - BOOST_CHECK_NO_THROW(begin()->x = 3); - BOOST_CHECK_EQUAL(3, begin()->x); - - BOOST_CHECK_NO_THROW(push_back(CC {4, 5.F})); - BOOST_CHECK_EQUAL(3, begin()->x); - BOOST_CHECK_EQUAL(2.F, begin()->y); - BOOST_CHECK_EQUAL(4, rbegin()->x); - BOOST_CHECK_EQUAL(5.F, rbegin()->y); - BOOST_CHECK_NO_THROW(pop_back()); - BOOST_CHECK_EQUAL(size(), 1); - BOOST_CHECK_EQUAL(capacity(), 2); - BOOST_CHECK_NO_THROW(resize(3)); - BOOST_CHECK_EQUAL(size(), 3); - BOOST_CHECK_EQUAL(capacity(), 3); - BOOST_CHECK_NO_THROW(resize(1)); - BOOST_CHECK_EQUAL(size(), 1); - BOOST_CHECK_EQUAL(capacity(), 1); -} - -BOOST_AUTO_TEST_CASE(insert_remove_test) -{ - BOOST_CHECK_NO_THROW(emplace_back(1, 2.F)); - BOOST_CHECK_NO_THROW(emplace_back(3, 4.F)); - BOOST_CHECK_NO_THROW(emplace(begin(), 5, 6.F)); - BOOST_CHECK_NO_THROW(emplace(begin() + 1, 7, 8.F)); - BOOST_CHECK_NO_THROW(emplace(begin() + 2, 9, 10.F)); - BOOST_CHECK_EQUAL(capacity(), 8); - BOOST_CHECK_EQUAL(size(), 5); - BOOST_CHECK_NO_THROW(shrink_to_fit()); - BOOST_CHECK_EQUAL(capacity(), 5); - BOOST_CHECK_EQUAL(size(), 5); -} - -BOOST_AUTO_TEST_SUITE_END(); 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..399a84c 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,20 +77,7 @@ BOOST_AUTO_TEST_CASE(acquireReleaseMove) BOOST_CHECK(reverseIndex.empty()); } -BOOST_AUTO_TEST_CASE(autoMapUnmap) -{ - { - auto proxy = acquire(); - BOOST_CHECK(data_); - std::ignore = bufferName(); - BOOST_CHECK(data_); - BOOST_CHECK_EQUAL(1, size()); - BOOST_CHECK(!data_); - } - 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 +86,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 +165,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,34 +211,87 @@ 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... - BOOST_REQUIRE(std::is_partitioned(mkcspan().cbegin(), mkcspan().cend(), pred)); + BOOST_REQUIRE(std::is_partitioned(cbegin(), cend(), pred)); // The external view of the data is unchanged... BOOST_CHECK_EQUAL_COLLECTIONS(values.cbegin(), values.cend(), instances.cbegin(), instances.cend()); // The partition point is right... - BOOST_CHECK(!pred(*matchedEnd)); - BOOST_CHECK(pred(*--matchedEnd)); + BOOST_CHECK(!pred(at(matchedEnd))); + BOOST_CHECK(pred(at(matchedEnd - 1))); + checkReverseIndex(); +} + +BOOST_AUTO_TEST_CASE(PartitionBy2, *boost::unit_test::timeout(1)) +{ + std::mt19937 gen(std::random_device {}()); + std::uniform_int_distribution<int> dist(0, 100000); + static constexpr auto COUNT = 20; + reserve(COUNT); + std::vector<decltype(acquire(0))> instances; + instances.reserve(COUNT); + // At least one of each + instances.push_back(acquire(1)); + instances.push_back(acquire(3)); + while (instances.size() < COUNT) { + instances.push_back(acquire(dist(gen))); + } + const std::vector<int> values(instances.begin(), instances.end()); + BOOST_REQUIRE_EQUAL(size(), COUNT); + + const auto pred3 = [](auto value) { + return (value % 3) == 0; + }; + const auto pred5 = [](auto value) { + return (value % 5) == 0; + }; + auto matchedBoundaries = partition(pred3, pred5); + // As PartitionBy... primary partition is normal layout + // The underlying data is partitioned... + BOOST_REQUIRE(std::is_partitioned(cbegin(), cend(), pred3)); + // The external view of the data is unchanged... + BOOST_CHECK_EQUAL_COLLECTIONS(values.cbegin(), values.cend(), instances.cbegin(), instances.cend()); + // The partition point is right... + BOOST_CHECK(!pred3(at(matchedBoundaries.first))); + BOOST_CHECK(pred3(at(matchedBoundaries.first - 1))); + + // Secondary partition lives contiguous in the middle somewhere, with two falsy blocks at each end + const auto p2bndry = matchedBoundaries.second; + + BOOST_TEST_CONTEXT(std::span(cbegin(), cend())) { + BOOST_TEST_CONTEXT(matchedBoundaries.first) { + BOOST_CHECK(std::all_of(cbegin(), cbegin() + static_cast<int>(matchedBoundaries.first), pred3)); + BOOST_CHECK(std::none_of(cbegin() + static_cast<int>(matchedBoundaries.first), cend(), pred3)); + } + + BOOST_TEST_CONTEXT(p2bndry) { + BOOST_CHECK(std::all_of( + cbegin() + static_cast<int>(p2bndry.first), cbegin() + static_cast<int>(p2bndry.second), pred5)); + BOOST_CHECK(std::none_of(cbegin(), cbegin() + static_cast<int>(p2bndry.first), pred5)); + BOOST_CHECK(std::none_of(cbegin() + static_cast<int>(p2bndry.second), cend(), pred5)); + } + } + checkReverseIndex(); } diff --git a/test/test-lib.cpp b/test/test-lib.cpp index 5f0b5e5..0fccbb8 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 = glManagedArray<Detail::glNamed, 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)); } -template<typename T = float> -auto -n_test_points_between(std::size_t n = 2, T min = -100.F, T max = 100.F) +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) { - return boost::unit_test::data::xrange(n) ^ boost::unit_test::data::random(min, max); + (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); + } } -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) +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) { - (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}; + constexpr static Triangle<2, float> TRIANGLE {{0, 0}, {5, 0}, {5, 5}}; - 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); + 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_AUTO_TEST_CASE(Triangle3dHelpers) +{ + 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; +namespace { + struct TestLinkS; -struct TestLink : public virtual Link { - using StraightLink = TestLinkS; - using CurveLink = 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)} - { - } + 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 a, Node::Ptr b, RelativePosition2D l) : - Link {{std::move(a), 0}, {std::move(b), pi}, glm::length(l)} - { - } + 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 { }; + struct Vertex { }; - TestLinkS(NetworkLinkHolder<TestLinkS> &, Node::Ptr a, Node::Ptr b, float l) : - Link {{std::move(a), 0}, {std::move(b), pi}, l} - { - } -}; + 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, 0}, p100 {10000, 0, 0}, p200 {20000, 0, 0}, p300 {30000, 0, 0}; -constexpr GlobalPosition3D p110 {10000, 10000, 0}; + 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); - } +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 override - { - } -}; + void + render(const SceneShader &, const Frustum &) 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}, -}); + 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 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 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 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 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 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()); - 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()); + 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()); + } + } - 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); + 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..688275c 100644 --- a/test/test-render.cpp +++ b/test/test-render.cpp @@ -1,3 +1,4 @@ +#include "game/scenary/light.h" #define BOOST_TEST_MODULE test_render #include "testHelpers.h" @@ -7,8 +8,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 +27,135 @@ #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)); +namespace { + class TestScene : public SceneProvider { + RailVehicleClassPtr brush47rvc; + std::shared_ptr<RailVehicle> train1, train2; + std::shared_ptr<RailLinks> rail = std::make_shared<RailLinks>(); + std::shared_ptr<Environment> env = std::make_shared<Environment>(); - Terrain terrain {gd}; - Water water {gd}; + std::shared_ptr<Terrain> terrain + = std::make_shared<Terrain>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 1)); + std::shared_ptr<Water> water = std::make_shared<Water>(terrain); -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}); - } + public: + TestScene() + { + terrain->point(GeoData::VertexHandle {517}).z = 40'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, GlobalPosition3D {52000, 50000, 2000}); + train2 = std::make_shared<RailVehicle>(brush47rvc, GlobalPosition3D {52000, 30000, 2000}); + 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}); + gameState->world.create<Light>(gameState->assets.at("old-lamp").dynamicCast<Illuminator>(), + Location {.pos = {25000, 52000, 1}, .rot = {}}); + gameState->world.create<Light>(gameState->assets.at("r-light").dynamicCast<Illuminator>(), + Location {.pos = {20000, 57000, 1}, .rot = {}}); + } - void - content(const SceneShader & shader) const override - { - terrain.render(shader); - water.render(shader); - brush47rvc->render(shader); - rail.render(shader); - } + void + forEachRenderable(const RenderableProcessor & func) const override + { + func(terrain.get()); + func(water.get()); + func(rail.get()); + std::ranges::for_each(gameState->assets, [&func](const auto & asset) { + if (const auto renderable = asset.second.template getAs<const Renderable>()) { + func(renderable); + } + }); + } - void - lights(const SceneShader &) const override - { - } + 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 & shader) const override + { + Renderable::lights(shader); + } + + void + environment(const SceneShader &, const SceneRenderer & renderer) const override + { + env->render(renderer, *this); + } - void - shadows(const ShadowMapper & shadowMapper) const override - { - terrain.shadows(shadowMapper); - brush47rvc->shadows(shadowMapper); - } -}; + 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::unit_test::data::xrange(500, 30000, 1300) * boost::unit_test::data::xrange(500, 10000, 300) - * boost::unit_test::data::xrange(50000, 500000, 70000), +BOOST_DATA_TEST_CASE(Cam, + boost::unit_test::data::xrange(500, 30000, 1300) * boost::unit_test::data::xrange(500.F, 10000.F, 300.F) + * boost::unit_test::data::xrange(50000.F, 500000.F, 70000.F), 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; @@ -111,44 +165,51 @@ BOOST_AUTO_TEST_CASE(basic) saveBuffers(const std::filesystem::path & prefix) const { std::filesystem::create_directories(prefix); - Texture::save(gAlbedoSpec, (prefix / "albedo.tga").c_str()); - Texture::savePosition(gPosition, (prefix / "position.tga").c_str()); - Texture::saveNormal(gNormal, (prefix / "normal.tga").c_str()); - Texture::save(gIllumination, (prefix / "illumination.tga").c_str()); + gAlbedoSpec.saveColour((prefix / "albedo.tga").c_str()); + gPosition.savePosition((prefix / "position.tga").c_str()); + gNormal.saveNormal((prefix / "normal.tga").c_str()); + gIllumination.saveColour((prefix / "illumination.tga").c_str()); } }; - 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"); - Texture::save(outImage, "/tmp/basic/final.tga"); + renderer.preFrame(scene, gameState.environment->getSunPos()); + renderer.render(scene); + renderer.saveBuffers(ANALYSIS_DIRECTORY / "basic"); + outImage.saveColour((ANALYSIS_DIRECTORY / "basic/final.tga").c_str()); } -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")); + std::shared_ptr<Water> water = std::make_shared<Water>(terrain); void - content(const SceneShader & shader) const override + forEachRenderable(const RenderableProcessor & func) const override { - terrain.render(shader); - water.render(shader); + func(terrain.get()); + func(water.get()); } void - environment(const SceneShader &, const SceneRenderer & sr) const override + content(const SceneShader & shader, const Frustum & frustum) const override { - sr.setAmbientLight({0.1, 0.1, 0.1}); - sr.setDirectionalLight({1, 1, 1}, south + down, *this); + terrain->render(shader, frustum); + water->render(shader, frustum); + } + + void + environment(const SceneShader &, const SceneRenderer & renderer) const override + { + renderer.setAmbientLight({0.1, 0.1, 0.1}); + renderer.setDirectionalLight({1, 1, 1}, {{0, quarter_pi}}, *this); } void @@ -157,45 +218,53 @@ 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 {}); - Texture::save(outImage, "/tmp/terrain.tga"); + TestTerrain testTerrain; + renderer.preFrame(testTerrain, gameState.environment->getSunPos()); + renderer.render(testTerrain); + outImage.saveColour((ANALYSIS_DIRECTORY / "terrain.tga").c_str()); } -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; + std::shared_ptr<RailLinks> net = std::make_shared<RailLinks>(); public: TestRail() { - net.addLinksBetween({20000, 10000, 0}, {100000, 100000, 0}); - net.addLinksBetween({20000, 10000, 0}, {10000, 10000, 0}); - net.addLinksBetween({10000, 20000, 0}, {100000, 120000, 0}); - net.addLinksBetween({10000, 20000, 0}, {10000, 10000, 0}); - net.addLinksBetween({100000, 100000, 0}, {100000, 120000, 0}); + net->addLinksBetween({20000, 10000, 0}, {100000, 100000, 0}); + net->addLinksBetween({20000, 10000, 0}, {10000, 10000, 0}); + net->addLinksBetween({10000, 20000, 0}, {100000, 120000, 0}); + net->addLinksBetween({10000, 20000, 0}, {10000, 10000, 0}); + net->addLinksBetween({100000, 100000, 0}, {100000, 120000, 0}); + } + + void + forEachRenderable(const RenderableProcessor & func) const override + { + func(net.get()); } 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,13 +273,13 @@ BOOST_AUTO_TEST_CASE(railnet) } void - shadows(const ShadowMapper &) const override + shadows(const ShadowMapper &, const Frustum &) const override { } }; - ss.render(TestRail {}); - Texture::save(outImage, "/tmp/railnet.tga"); + renderer.render(TestRail {}); + outImage.saveColour((ANALYSIS_DIRECTORY / "railnet.tga").c_str()); } BOOST_AUTO_TEST_SUITE_END(); 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-static-util.cpp b/test/test-static-util.cpp new file mode 100644 index 0000000..2a8aa81 --- /dev/null +++ b/test/test-static-util.cpp @@ -0,0 +1,29 @@ +#include "util.h" + +namespace { + struct Base1 { + int a; + float b; + }; + + struct Base2 { + int x; + float y; + }; + + struct Sub : Base1, Base2 { + double value; + }; + + static_assert(std::is_same_v<MemberValueType<&Base1::a>, int>); + static_assert(std::is_same_v<MemberValueType<&Base2::y>, float>); + static_assert(std::is_same_v<MemberValueType<&Sub::a>, int>); + static_assert(std::is_same_v<MemberValueType<&Sub::y>, float>); + static_assert(std::is_same_v<MemberValueType<&Sub::value>, double>); + + static_assert(std::is_same_v<ContainerType<&Base1::a>, Base1>); + static_assert(std::is_same_v<ContainerType<&Base2::y>, Base2>); + static_assert(std::is_same_v<ContainerType<&Sub::a>, Base1>); + static_assert(std::is_same_v<ContainerType<&Sub::y>, Base2>); + static_assert(std::is_same_v<ContainerType<&Sub::value>, Sub>); +} 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..74953b3 --- /dev/null +++ b/test/test-ui.cpp @@ -0,0 +1,21 @@ +#define BOOST_TEST_MODULE UI +#include <boost/test/unit_test.hpp> +#include <stream_support.h> + +#include "testHelpers.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 = svg.texture.getSize(); + BOOST_CHECK_EQUAL(size, ImageDimensions(RENDER_SIZE, RENDER_SIZE)); + svg.texture.saveColour((ANALYSIS_DIRECTORY / "rails.tga").c_str()); +} 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..c769d32 --- /dev/null +++ b/test/testHelpers.cpp @@ -0,0 +1,8 @@ +#include "testHelpers.h" + +const std::filesystem::path ANALYSIS_DIRECTORY = []() { + auto xdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); + auto out = std::filesystem::path {xdgRuntimeDir ? xdgRuntimeDir : "/tmp"} / "ilt-output"; + std::filesystem::create_directories(out); + return out; +}(); 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..40f5567 100644 --- a/test/testMainWindow.cpp +++ b/test/testMainWindow.cpp @@ -1,15 +1,19 @@ #include "testMainWindow.h" +#include <boost/test/framework.hpp> #include <boost/test/test_tools.hpp> #include <format> +#include <stacktrace> -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); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback( [](GLenum /*source*/, GLenum type, GLuint /*id*/, GLenum severity, GLsizei /*length*/, const GLchar * message, const void *) { const auto msg = std::format("GL CALLBACK: {} type = 0x{:x}, severity = 0x{:x}, message = {}", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message); + BOOST_TEST_INFO(std::stacktrace::current()); switch (type) { case GL_DEBUG_TYPE_ERROR: case GL_DEBUG_TYPE_PORTABILITY: diff --git a/test/testRenderOutput.cpp b/test/testRenderOutput.cpp index 68b46f6..e2cc437 100644 --- a/test/testRenderOutput.cpp +++ b/test/testRenderOutput.cpp @@ -1,27 +1,20 @@ #include "testRenderOutput.h" #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 - = [this](const GLuint data, const GLint format, const GLenum type, const GLenum attachment) { - glBindTexture(GL_TEXTURE_2D, data); - glTexImage2D(GL_TEXTURE_2D, 0, format, size.x, size.y, 0, GL_RGBA, type, nullptr); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, data, 0); - }; - configuregdata(outImage, GL_RGBA, GL_UNSIGNED_BYTE, GL_COLOR_ATTACHMENT0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); + const auto configureAttachment = [this](glFramebuffer & fbo, glTexture<GL_TEXTURE_2D> & data, const GLenum iformat, + const GLenum attachment) { + data.storage(1, iformat, size); + data.parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + data.parameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + fbo.texture(attachment, data); + }; + configureAttachment(output, outImage, GL_RGBA8, GL_COLOR_ATTACHMENT0); + output.drawBuffers(GL_COLOR_ATTACHMENT0); - glBindRenderbuffer(GL_RENDERBUFFER, depth); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.x, size.y); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); + depth.storage(GL_DEPTH_COMPONENT, size); + output.buffer(GL_DEPTH_ATTACHMENT, depth); - // finally check if framebuffer is complete - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - throw std::runtime_error("Framebuffer not complete!"); - } + output.assertComplete(); } diff --git a/test/testRenderOutput.h b/test/testRenderOutput.h index 056d029..e68d1f3 100644 --- a/test/testRenderOutput.h +++ b/test/testRenderOutput.h @@ -1,7 +1,8 @@ #pragma once #include "config/types.h" -#include "glArrays.h" +#include "game/gamestate.h" +#include "gfx/gl/glFramebuffer.h" #include <glm/vec2.hpp> #include <special_members.h> @@ -14,9 +15,10 @@ public: NO_COPY(TestRenderOutput); const TextureAbsCoord size; - glFrameBuffer output; - glRenderBuffer depth; - glTexture outImage; + glFramebuffer output; + glRenderbuffer depth; + glTexture<GL_TEXTURE_2D> 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..468e6a0 100644 --- a/thirdparty/Jamfile.jam +++ b/thirdparty/Jamfile.jam @@ -1,29 +1,36 @@ -import glad ; -lib glad : gl.xml : +project ilt.thirdparty : requirements <link>static <cflags>-fPIC <warnings>off <warnings-as-errors>off - <glad.version>3.3 ; -lib stb : stb_image.c : - <link>static - <cflags>-fPIC - <warnings>off - <warnings-as-errors>off + +import glad ; +lib glad : gl.xml : + <glad.version>4.6 ; +lib stb : stb_image.c ; + +path-constant imgui : imgui ; lib imguisdl2 : - [ glob imgui/imgui*.cpp : imgui/imgui_demo.cpp ] - imgui/backends/imgui_impl_sdl2.cpp - imgui/backends/imgui_impl_opengl3.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) + <include>$(imgui)/misc/cpp <use>..//sdl2 - <cflags>-fPIC - <warnings>off - <warnings-as-errors>off : : - <include>imgui + <cflags>-isystem\ $(imgui) + ; + +path-constant lunasvg : lunasvg ; +lib lunasvg : + [ glob $(lunasvg)/source/*.cpp $(lunasvg)/plutovg/source/*.c ] + : + <include>$(lunasvg)/include + <include>$(lunasvg)/plutovg/include + : : + <cflags>-isystem\ $(lunasvg)/include ; diff --git a/thirdparty/ctre b/thirdparty/ctre -Subproject b3d7788b559e34d985c8530c3e0e7260b67505a +Subproject e34c26ba149b9fd9c34aa0f678e39739641a0d1 diff --git a/thirdparty/glad b/thirdparty/glad -Subproject 2348b07c1ab4504d60398713781d8a57880234f +Subproject 73db193f853e2ee079bf3ca8a64aa2eaf645904 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/applicationBase.cpp b/ui/applicationBase.cpp index 961007b..c9748e7 100644 --- a/ui/applicationBase.cpp +++ b/ui/applicationBase.cpp @@ -1,6 +1,6 @@ #include "applicationBase.h" -#include "imgui_wrap.h" #include <SDL2/SDL.h> +#include <imgui.h> #include <stdexcept> ApplicationBase::ApplicationBase() @@ -29,8 +29,8 @@ ApplicationBase::initSDL() const setGlAttribute(SDL_GL_DEPTH_SIZE, 16); setGlAttribute(SDL_GL_DOUBLEBUFFER, 1); - setGlAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - setGlAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + setGlAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + setGlAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); setGlAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); setGlAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); } 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..a213ccd 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 <game/gamestate.h> -#include <game/geoData.h> +#include <game/terrain.h> #include <gfx/gl/sceneShader.h> #include <gfx/models/texture.h> +#include <imgui.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, GL_RGBA, GL_UNSIGNED_BYTE, &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(); + blue.bind(0); 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/font.cpp b/ui/font.cpp index ebd29d0..9c3d770 100644 --- a/ui/font.cpp +++ b/ui/font.cpp @@ -4,7 +4,6 @@ #include <format> #include <ft2build.h> #include FT_FREETYPE_H -#include "gl_traits.h" #include <glRef.h> #include <maths.h> #include <optional> @@ -93,8 +92,7 @@ Font::generateChars(const utf8_string_view chars) const const auto textureIdx = getTextureWithSpace(glyph->bitmap.width); auto & texture = fontTextures[textureIdx]; - glTexSubImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(texture.used), 0, - static_cast<GLsizei>(glyph->bitmap.width), static_cast<GLsizei>(glyph->bitmap.rows), GL_RED, + texture.texture.subImage({texture.used, 0}, {glyph->bitmap.width, glyph->bitmap.rows}, GL_RED, GL_UNSIGNED_BYTE, glyph->bitmap.buffer); const auto & cd = charsData @@ -116,18 +114,15 @@ Font::getTextureWithSpace(unsigned int adv) const return (ft.used + adv) < size.x; }); itr != fontTextures.end()) { - glBindTexture(GL_TEXTURE_2D, itr->texture); return static_cast<std::size_t>(itr - fontTextures.begin()); } auto & texture = fontTextures.emplace_back(); - glBindTexture(GL_TEXTURE_2D, texture.texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, static_cast<GLsizei>(size.x), static_cast<GLsizei>(size.y), 0, GL_RED, - GL_UNSIGNED_BYTE, nullptr); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + texture.texture.storage(1, GL_R8, size); + texture.texture.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + texture.texture.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texture.texture.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + texture.texture.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); return fontTextures.size() - 1; } @@ -1,9 +1,9 @@ #pragma once +#include "gfx/gl/glTexture.h" #include <array> #include <cstddef> #include <filesystem> -#include <glArrays.h> #include <glad/gl.h> #include <glm/glm.hpp> #include <map> @@ -28,7 +28,7 @@ public: }; struct FontTexture { - glTexture texture; + glTexture<GL_TEXTURE_2D> texture; unsigned int used; }; diff --git a/ui/gameMainSelector.cpp b/ui/gameMainSelector.cpp index 5bef48d..098c8db 100644 --- a/ui/gameMainSelector.cpp +++ b/ui/gameMainSelector.cpp @@ -1,50 +1,41 @@ #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} -{ -} - -constexpr ScreenAbsCoord TargetPos {5, 45}; +GameMainSelector::GameMainSelector(const Camera * c) : camera {c} { } 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 +51,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 +64,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 +81,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..01b93bb 100644 --- a/ui/gameMainWindow.cpp +++ b/ui/gameMainWindow.cpp @@ -1,89 +1,141 @@ #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)) { + IltGui::Text(std::format("{:%a, %H:%M\n%d %m %Y}", gameState->environment->getWorldTime())); + 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::forEachRenderable(const RenderableProcessor & func) const +{ + for (const auto & [assetId, asset] : gameState->assets) { + if (const auto renderable = asset.getAs<Renderable>()) { + func(renderable); + } + } + gameState->world.apply<Renderable>(func); +} + void -GameMainWindow::render() const +GameMainWindow::render() { + SceneRenderer::preFrame(*this, gameState->environment->getSunPos()); 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 +GameMainWindow::lights(const SceneShader & sceneShader) const { - gameState->world.apply<Renderable>(&Renderable::lights, shader); + Renderable::lights(sceneShader); } 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..015bb2b 100644 --- a/ui/gameMainWindow.h +++ b/ui/gameMainWindow.h @@ -3,22 +3,20 @@ #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 forEachRenderable(const RenderableProcessor & func) const 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..e0399e5 100644 --- a/ui/icon.cpp +++ b/ui/icon.cpp @@ -1,5 +1,4 @@ #include "icon.h" -#include "gl_traits.h" #include <gfx/image.h> #include <glad/gl.h> #include <resource.h> @@ -11,20 +10,18 @@ Icon::Icon(const std::filesystem::path & fileName) : Icon {Image {Resource::mapP Icon::Icon(const Image & tex) : size {tex.width, tex.height} { - glBindTexture(GL_TEXTURE_2D, m_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, static_cast<GLsizei>(tex.width), static_cast<GLsizei>(tex.height), 0, - GL_RGBA, GL_UNSIGNED_BYTE, tex.data.data()); + m_texture.storage(1, GL_RGBA8, size); + m_texture.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + m_texture.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + m_texture.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_texture.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + m_texture.image(size, 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); } @@ -1,8 +1,9 @@ #pragma once +#include "gfx/gl/glTexture.h" #include <filesystem> -#include <glArrays.h> #include <glm/glm.hpp> +#include <imgui.h> class Image; @@ -11,9 +12,9 @@ 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; + glTexture<GL_TEXTURE_2D> 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..f2ae8ba --- /dev/null +++ b/ui/imgui_extras.cpp @@ -0,0 +1,33 @@ +#include "imgui_extras.h" +#include <imgui_internal.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..8b87b28 --- /dev/null +++ b/ui/imgui_extras.h @@ -0,0 +1,24 @@ +#include <imgui.h> +#include <imgui_internal.h> +#include <memory> +#include <ranges> + +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(); + + void + Text(std::ranges::contiguous_range auto text, ImGuiTextFlags flags = ImGuiTextFlags_None) + requires requires { + { std::to_address(text.begin()) } -> std::convertible_to<const char *>; + { std::to_address(text.end()) } -> std::convertible_to<const char *>; + } + { + ImGui::TextEx(std::to_address(text.begin()), std::to_address(text.end()), flags); + } + + // NOLINTEND(readability-identifier-naming) +} diff --git a/ui/imgui_wrap.h b/ui/imgui_wrap.h deleted file mode 100644 index 1d619a4..0000000 --- a/ui/imgui_wrap.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#include "imgui.h" // IWYU pragma: export -#pragma GCC diagnostic pop diff --git a/ui/mainApplication.cpp b/ui/mainApplication.cpp index 6cb1037..bb91c21 100644 --- a/ui/mainApplication.cpp +++ b/ui/mainApplication.cpp @@ -1,10 +1,8 @@ #include "mainApplication.h" +#include "backends/imgui_impl_sdl2.h" #include "game/gamestate.h" #include "game/worldobject.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#include "backends/imgui_impl_sdl2.h" -#pragma GCC diagnostic pop +#include "gfx/gl/gldebug.h" void MainApplication::mainLoop() @@ -16,10 +14,13 @@ MainApplication::mainLoop() const auto t_passed = std::chrono::duration_cast<TickDuration>(t_end - t_start); if (gameState) { + glDebugScope _ {0, "Tick all game state world objects"}; gameState->world.apply(&WorldObject::tick, t_passed); } windows.apply(&Window::tick, t_passed); - windows.apply(&Window::refresh); + if (glDebugScope _ {0, "Refresh all windows"}) { + windows.apply(&Window::refresh); + } ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); 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..1834a33 100644 --- a/ui/mainWindow.cpp +++ b/ui/mainWindow.cpp @@ -1,16 +1,10 @@ #include "mainWindow.h" -#include <format> -#include <stdexcept> - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" #include "backends/imgui_impl_opengl3.h" #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} { } +#include <format> +#include <stdexcept> -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..56eee9a --- /dev/null +++ b/ui/queryTool.cpp @@ -0,0 +1,42 @@ +#include "queryTool.h" +#include <game/gamestate.h> +#include <game/selectable.h> +#include <game/terrain.h> +#include <game/worldobject.h> +#include <imgui.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..2c73b5d --- /dev/null +++ b/ui/svgIcon.cpp @@ -0,0 +1,31 @@ +#include "svgIcon.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(); + + texture.storage(1, GL_RGBA8, dim); + texture.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + texture.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + texture.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + texture.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + texture.image(dim, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.data()); +} + +ImTextureID +SvgIcon::operator*() const +{ + static_assert(sizeof(glTexture<GL_TEXTURE_2D>) <= 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..94948bd --- /dev/null +++ b/ui/svgIcon.h @@ -0,0 +1,18 @@ +#pragma once + +#include "gfx/gl/glTexture.h" +#include <config/types.h> +#include <filesystem> +#include <imgui.h> +#include <lunasvg.h> + +class SvgIcon { +public: + SvgIcon(ImageDimensions, const std::filesystem::path &); + + ImTextureID operator*() const; + +private: + friend struct LoadFromFile; // Test case verifying size/content + glTexture<GL_TEXTURE_2D> 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..9d8dc1a 100644 --- a/ui/window.cpp +++ b/ui/window.cpp @@ -1,16 +1,13 @@ #include "window.h" -#include <glad/gl.h> -#include <glm/glm.hpp> -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" #include "backends/imgui_impl_opengl3.h" #include "backends/imgui_impl_sdl2.h" -#pragma GCC diagnostic pop +#include "gfx/gl/gldebug.h" +#include <glad/gl.h> +#include <glm/glm.hpp> -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} { } @@ -18,6 +15,7 @@ Window::Window(size_t width, size_t height, const std::string & title, Uint32 fl void Window::clear(float r, float g, float b, float a) const { + glDebugScope _ {0}; glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } @@ -60,8 +58,10 @@ Window::refresh() const content->render(); // Render UI stuff here } - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + if (glDebugScope _ {0, "ImGui post"}) { + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } swapBuffers(); } 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; }; |
