diff options
Diffstat (limited to 'game')
33 files changed, 1396 insertions, 890 deletions
diff --git a/game/environment.cpp b/game/environment.cpp new file mode 100644 index 0000000..acb4f21 --- /dev/null +++ b/game/environment.cpp @@ -0,0 +1,93 @@ +#include "environment.h" +#include "gfx/lightDirection.h" +#include <chronology.h> +#include <gfx/gl/sceneRenderer.h> + +Environment::Environment() : worldTime {"2024-01-01T12:00:00"_time_t} { } + +void +Environment::tick(TickDuration) +{ + worldTime += 50; +} + +void +Environment::render(const SceneRenderer & renderer, const SceneProvider & scene) const +{ + constexpr RGB baseAmbient {0.1F}, baseDirectional {0.0F}; + constexpr RGB relativeAmbient {0.3F, 0.3F, 0.4F}, relativeDirectional {0.6F, 0.6F, 0.5F}; + + const LightDirection sunPos = getSunPos({}, worldTime); + const auto ambient = baseAmbient + relativeAmbient * sunPos.ambient(); + const auto directional = baseDirectional + relativeDirectional * sunPos.directional(); + + renderer.setAmbientLight(ambient); + renderer.setDirectionalLight(directional, sunPos, scene); +} + +// Based on the C++ code published at https://www.psa.es/sdg/sunpos.htm +// Linked from https://www.pveducation.org/pvcdrom/properties-of-sunlight/suns-position-to-high-accuracy +Direction2D +Environment::getSunPos(const Direction2D position, const time_t time) +{ + auto & longitude = position.x; + auto & latitude = position.y; + using std::acos; + using std::asin; + using std::atan2; + using std::cos; + using std::floor; + using std::sin; + using std::tan; + static const auto JD2451545 = "2000-01-01T12:00:00"_time_t; + + // Calculate difference in days between the current Julian Day + // and JD 2451545.0, which is noon 1 January 2000 Universal Time + // Calculate time of the day in UT decimal hours + const auto dDecimalHours = static_cast<float>(time % 86400) / 3600.F; + const auto dElapsedJulianDays = static_cast<float>(time - JD2451545) / 86400.F; + + // Calculate ecliptic coordinates (ecliptic longitude and obliquity of the + // ecliptic in radians but without limiting the angle to be less than 2*Pi + // (i.e., the result may be greater than 2*Pi) + const auto dOmega = 2.1429F - 0.0010394594F * dElapsedJulianDays; + const auto dMeanLongitude = 4.8950630F + 0.017202791698F * dElapsedJulianDays; // Radians + const auto dMeanAnomaly = 6.2400600F + 0.0172019699F * dElapsedJulianDays; + const auto dEclipticLongitude = dMeanLongitude + 0.03341607F * sin(dMeanAnomaly) + + 0.00034894F * sin(2 * dMeanAnomaly) - 0.0001134F - 0.0000203F * sin(dOmega); + const auto dEclipticObliquity = 0.4090928F - 6.2140e-9F * dElapsedJulianDays + 0.0000396F * cos(dOmega); + + // Calculate celestial coordinates ( right ascension and declination ) in radians + // but without limiting the angle to be less than 2*Pi (i.e., the result may be + // greater than 2*Pi) + const auto dSin_EclipticLongitude = sin(dEclipticLongitude); + const auto dY = cos(dEclipticObliquity) * dSin_EclipticLongitude; + const auto dX = cos(dEclipticLongitude); + auto dRightAscension = atan2(dY, dX); + if (dRightAscension < 0) { + dRightAscension = dRightAscension + two_pi; + } + const auto dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude); + + // Calculate local coordinates ( azimuth and zenith angle ) in degrees + const auto dGreenwichMeanSiderealTime = 6.6974243242F + 0.0657098283F * dElapsedJulianDays + dDecimalHours; + const auto dLocalMeanSiderealTime + = (dGreenwichMeanSiderealTime * 15.0F + (longitude / degreesToRads)) * degreesToRads; + const auto dHourAngle = dLocalMeanSiderealTime - dRightAscension; + const auto dLatitudeInRadians = latitude; + const auto dCos_Latitude = cos(dLatitudeInRadians); + const auto dSin_Latitude = sin(dLatitudeInRadians); + const auto dCos_HourAngle = cos(dHourAngle); + Direction2D udtSunCoordinates; + udtSunCoordinates.y + = (acos(dCos_Latitude * dCos_HourAngle * cos(dDeclination) + sin(dDeclination) * dSin_Latitude)); + udtSunCoordinates.x = atan2(-sin(dHourAngle), tan(dDeclination) * dCos_Latitude - dSin_Latitude * dCos_HourAngle); + if (udtSunCoordinates.x < 0) { + udtSunCoordinates.x = udtSunCoordinates.x + two_pi; + } + // Parallax Correction + const auto dParallax = (earthMeanRadius / astronomicalUnit) * sin(udtSunCoordinates.y); + udtSunCoordinates.y = half_pi - (udtSunCoordinates.y + dParallax); + + return udtSunCoordinates; +} diff --git a/game/environment.h b/game/environment.h new file mode 100644 index 0000000..a6f3036 --- /dev/null +++ b/game/environment.h @@ -0,0 +1,18 @@ +#pragma once + +#include "config/types.h" +#include "worldobject.h" + +class SceneRenderer; +class SceneProvider; + +class Environment : public WorldObject { +public: + Environment(); + void tick(TickDuration elapsed) override; + void render(const SceneRenderer &, const SceneProvider &) const; + static Direction2D getSunPos(const Direction2D position, const time_t time); + +private: + time_t worldTime; +}; diff --git a/game/gamestate.cpp b/game/gamestate.cpp index fcd4248..910e8a7 100644 --- a/game/gamestate.cpp +++ b/game/gamestate.cpp @@ -1,4 +1,5 @@ #include "gamestate.h" +#include "environment.h" #include <cassert> GameState * gameState {nullptr}; @@ -7,6 +8,8 @@ GameState::GameState() { assert(!gameState); gameState = this; + + environment = world.create<Environment>(); } GameState::~GameState() diff --git a/game/gamestate.h b/game/gamestate.h index f07f844..85cb5db 100644 --- a/game/gamestate.h +++ b/game/gamestate.h @@ -6,7 +6,9 @@ #include <special_members.h> class WorldObject; -class GeoData; +class Terrain; +class Environment; +class Renderable; class GameState { public: @@ -15,8 +17,9 @@ public: NO_MOVE(GameState); NO_COPY(GameState); - Collection<WorldObject> world; - std::shared_ptr<GeoData> geoData; + SharedCollection<WorldObject, Renderable> world; + std::shared_ptr<Terrain> terrain; + std::shared_ptr<Environment> environment; AssetFactory::Assets assets; }; diff --git a/game/geoData.cpp b/game/geoData.cpp index 72aa056..b886efd 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -1,16 +1,15 @@ #include "geoData.h" #include "collections.h" #include "geometricPlane.h" +#include "util.h" #include <fstream> #include <glm/gtx/intersect.hpp> #include <maths.h> #include <ranges> #include <set> - -GeoData::GeoData() -{ - add_property(surface); -} +#ifndef NDEBUG +# include <stream_support.h> +#endif GeoData GeoData::loadFromAsciiGrid(const std::filesystem::path & input) @@ -36,16 +35,16 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input) std::vector<VertexHandle> vertices; vertices.reserve(ncols * nrows); GeoData mesh; - mesh.lowerExtent = {xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()}; - mesh.upperExtent = {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)), - std::numeric_limits<GlobalDistance>::min()}; + mesh.extents = {{xllcorner, yllcorner, std::numeric_limits<GlobalDistance>::max()}, + {xllcorner + (cellsize * (ncols - 1)), yllcorner + (cellsize * (nrows - 1)), + std::numeric_limits<GlobalDistance>::min()}}; for (size_t row = 0; row < nrows; ++row) { for (size_t col = 0; col < ncols; ++col) { float heightf = 0; f >> heightf; const auto height = static_cast<GlobalDistance>(std::round(heightf * 1000.F)); - mesh.upperExtent.z = std::max(mesh.upperExtent.z, height); - mesh.lowerExtent.z = std::min(mesh.lowerExtent.z, height); + mesh.extents.max.z = std::max(mesh.extents.max.z, height); + mesh.extents.min.z = std::min(mesh.extents.min.z, height); vertices.push_back(mesh.add_vertex({xllcorner + (col * cellsize), yllcorner + (row * cellsize), height})); } } @@ -66,7 +65,7 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input) }); } } - mesh.update_vertex_normals_only(); + mesh.updateAllVertexNormals(); return mesh; }; @@ -79,8 +78,7 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan assert((upper - lower) % GRID_SIZE == GlobalPosition2D {}); GeoData mesh; - mesh.lowerExtent = {lower, h}; - mesh.upperExtent = {upper, h}; + mesh.extents = {{lower, h}, {upper, h}}; std::vector<VertexHandle> vertices; for (GlobalDistance row = lower.x; row <= upper.x; row += GRID_SIZE) { @@ -105,123 +103,11 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan } } - mesh.update_vertex_normals_only(); + mesh.updateAllVertexNormals(); return mesh; } -OpenMesh::FaceHandle -GeoData::findPoint(GlobalPosition2D p) const -{ - return findPoint(p, *faces_sbegin()); -} - -GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh) : - PointFace {p, mesh, *mesh->faces_sbegin()} -{ -} - -GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh, FaceHandle start) : - PointFace {p, mesh->findPoint(p, start)} -{ -} - -GeoData::FaceHandle -GeoData::PointFace::face(const GeoData * mesh, FaceHandle start) const -{ - if (_face.is_valid()) { - assert(mesh->triangleContainsPoint(point, _face)); - return _face; - } - else { - return (_face = mesh->findPoint(point, start)); - } -} - -GeoData::FaceHandle -GeoData::PointFace::face(const GeoData * mesh) const -{ - return face(mesh, *mesh->faces_sbegin()); -} - -namespace { - template<template<typename> typename Op> - [[nodiscard]] constexpr inline auto - pointLineOp(const GlobalPosition2D p, const GlobalPosition2D e1, const GlobalPosition2D e2) - { - return Op {}(CalcDistance(e2.x - e1.x) * CalcDistance(p.y - e1.y), - CalcDistance(e2.y - e1.y) * CalcDistance(p.x - e1.x)); - } - - constexpr auto pointLeftOfLine = pointLineOp<std::greater>; - constexpr auto pointLeftOfOrOnLine = pointLineOp<std::greater_equal>; - - static_assert(pointLeftOfLine({1, 2}, {1, 1}, {2, 2})); - static_assert(pointLeftOfLine({2, 1}, {2, 2}, {1, 1})); - static_assert(pointLeftOfLine({2, 2}, {1, 2}, {2, 1})); - static_assert(pointLeftOfLine({1, 1}, {2, 1}, {1, 2})); - static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490000000}, {310050000, 490050000})); - static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310050000, 490050000}, {310000000, 490050000})); - static_assert(pointLeftOfOrOnLine({310000000, 490000000}, {310000000, 490050000}, {310000000, 490000000})); - - [[nodiscard]] constexpr inline bool - linesCross( - const GlobalPosition2D a1, const GlobalPosition2D a2, const GlobalPosition2D b1, const GlobalPosition2D b2) - { - return (pointLeftOfLine(a2, b1, b2) == pointLeftOfLine(a1, b2, b1)) - && (pointLeftOfLine(b1, a1, a2) == pointLeftOfLine(b2, a2, a1)); - } - - static_assert(linesCross({1, 1}, {2, 2}, {1, 2}, {2, 1})); - static_assert(linesCross({2, 2}, {1, 1}, {1, 2}, {2, 1})); - - [[nodiscard]] constexpr inline bool - linesCrossLtR( - const GlobalPosition2D a1, const GlobalPosition2D a2, const GlobalPosition2D b1, const GlobalPosition2D b2) - { - return pointLeftOfLine(a2, b1, b2) && pointLeftOfLine(a1, b2, b1) && pointLeftOfLine(b1, a1, a2) - && pointLeftOfLine(b2, a2, a1); - } - - static_assert(linesCrossLtR({1, 1}, {2, 2}, {1, 2}, {2, 1})); - static_assert(!linesCrossLtR({2, 2}, {1, 1}, {1, 2}, {2, 1})); - - constexpr GlobalPosition3D - positionOnTriangle(const GlobalPosition2D point, const GeoData::Triangle<3> & t) - { - const CalcPosition3D a = t[1] - t[0], b = t[2] - t[0]; - const auto n = crossProduct(a, b); - return {point, ((n.x * t[0].x) + (n.y * t[0].y) + (n.z * t[0].z) - (n.x * point.x) - (n.y * point.y)) / n.z}; - } - - static_assert(positionOnTriangle({7, -2}, {{1, 2, 3}, {1, 0, 1}, {-2, 1, 0}}) == GlobalPosition3D {7, -2, 3}); -} - -OpenMesh::FaceHandle -GeoData::findPoint(GlobalPosition2D p, OpenMesh::FaceHandle f) const -{ - while (f.is_valid() && !triangleContainsPoint(p, triangle<2>(f))) { - for (auto next = cfh_iter(f); next.is_valid(); ++next) { - f = opposite_face_handle(*next); - if (f.is_valid()) { - const auto e1 = point(to_vertex_handle(*next)); - const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next))); - if (pointLeftOfLine(p, e1, e2)) { - break; - } - } - f.reset(); - } - } - return f; -} - -GlobalPosition3D -GeoData::positionAt(const PointFace & p) const -{ - return positionOnTriangle(p.point, triangle<3>(p.face(this))); -} - [[nodiscard]] GeoData::IntersectionResult GeoData::intersectRay(const Ray<GlobalPosition3D> & ray) const { @@ -233,13 +119,11 @@ GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const { GeoData::IntersectionResult out; walkUntil(PointFace {ray.start, face}, - ray.start.xy() + (ray.direction.xy() * RelativePosition2D(upperExtent.xy() - lowerExtent.xy())), - [&out, &ray, this](FaceHandle face) { - BaryPosition bari {}; - RelativeDistance dist {}; - const auto t = triangle<3>(face); - if (ray.intersectTriangle(t.x, t.y, t.z, bari, dist)) { - out.emplace(t * bari, face); + ray.start.xy() + (ray.direction.xy() * ::difference(extents.max.xy(), extents.min.xy())), + [&out, &ray, this](const auto & step) { + const auto t = triangle<3>(step.current); + if (const auto inter = ray.intersectTriangle(t.x, t.y, t.z)) { + out.emplace(t * inter->bary, step.current); return true; } return false; @@ -248,7 +132,7 @@ GeoData::intersectRay(const Ray<GlobalPosition3D> & ray, FaceHandle face) const } void -GeoData::walk(const PointFace & from, const GlobalPosition2D to, const std::function<void(FaceHandle)> & op) const +GeoData::walk(const PointFace & from, const GlobalPosition2D to, Consumer<WalkStep> op) const { walkUntil(from, to, [&op](const auto & fh) { op(fh); @@ -257,41 +141,86 @@ GeoData::walk(const PointFace & from, const GlobalPosition2D to, const std::func } void -GeoData::walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function<bool(FaceHandle)> & op) const +GeoData::walkUntil(const PointFace & from, const GlobalPosition2D to, Tester<WalkStep> op) const +{ + WalkStep step { + .current = from.face(this), + }; + if (!step.current.is_valid()) { + const auto entryEdge = findEntry(from.point, to); + if (!entryEdge.is_valid()) { + return; + } + step.current = opposite_face_handle(entryEdge); + } + while (step.current.is_valid() && !op(step)) { + step.previous = step.current; + for (const auto next : fh_range(step.current)) { + step.current = opposite_face_handle(next); + if (step.current.is_valid() && step.current != step.previous) { + const auto nextPoints = points(toVertexHandles(next)); + if (linesCrossLtR(from.point, to, nextPoints.second, nextPoints.first)) { + step.exitHalfedge = next; + step.exitPosition + = linesIntersectAt(from.point.xy(), to.xy(), nextPoints.second.xy(), nextPoints.first.xy()) + .value(); + break; + } + } + step.current.reset(); + } + } +} + +void +GeoData::walk(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Consumer<WalkStepCurve> op) const +{ + walkUntil(from, to, centre, [&op](const auto & fh) { + op(fh); + return false; + }); +} + +void +GeoData::walkUntil(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Tester<WalkStepCurve> op) const { - auto f = from.face(this); - if (!f.is_valid()) { + WalkStepCurve step {WalkStep {.current = from.face(this)}}; + if (!step.current.is_valid()) { const auto entryEdge = findEntry(from.point, to); if (!entryEdge.is_valid()) { return; } - f = opposite_face_handle(entryEdge); + step.current = opposite_face_handle(entryEdge); } - FaceHandle previousFace; - while (f.is_valid() && !op(f)) { - for (auto next = cfh_iter(f); next.is_valid(); ++next) { - f = opposite_face_handle(*next); - if (f.is_valid() && f != previousFace) { - const auto e1 = point(to_vertex_handle(*next)); - const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next))); - if (linesCrossLtR(from.point, to, e1, e2)) { - previousFace = f; + ArcSegment arc {centre, from.point, to}; + step.angle = arc.first; + while (step.current.is_valid() && !op(step)) { + step.previous = step.current; + for (const auto next : fh_range(step.current)) { + step.current = opposite_face_handle(next); + if (step.current.is_valid()) { + const auto e1 = point(to_vertex_handle(next)); + const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(next))); + if (const auto intersect = arc.crossesLineAt(e1, e2)) { + step.exitHalfedge = next; + arc.ep0 = step.exitPosition = intersect.value().first; + arc.first = std::nextafter(step.angle = intersect.value().second, INFINITY); break; } } - f.reset(); + step.current.reset(); } } } void -GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op) const +GeoData::boundaryWalk(Consumer<HalfedgeHandle> op) const { boundaryWalk(op, findBoundaryStart()); } void -GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op, HalfedgeHandle start) const +GeoData::boundaryWalk(Consumer<HalfedgeHandle> op, HalfedgeHandle start) const { assert(is_boundary(start)); boundaryWalkUntil( @@ -303,13 +232,13 @@ GeoData::boundaryWalk(const std::function<void(HalfedgeHandle)> & op, HalfedgeHa } void -GeoData::boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> & op) const +GeoData::boundaryWalkUntil(Tester<HalfedgeHandle> op) const { boundaryWalkUntil(op, findBoundaryStart()); } void -GeoData::boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> & op, HalfedgeHandle start) const +GeoData::boundaryWalkUntil(Tester<HalfedgeHandle> op, HalfedgeHandle start) const { assert(is_boundary(start)); if (!op(start)) { @@ -337,339 +266,337 @@ GeoData::findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const return entry; } -bool -GeoData::triangleContainsPoint(const GlobalPosition2D p, const Triangle<2> & t) -{ - return pointLeftOfOrOnLine(p, t[0], t[1]) && pointLeftOfOrOnLine(p, t[1], t[2]) - && pointLeftOfOrOnLine(p, t[2], t[0]); -} - -bool -GeoData::triangleContainsPoint(const GlobalPosition2D p, FaceHandle face) const -{ - return triangleContainsPoint(p, triangle<2>(face)); -} - -GeoData::HalfedgeHandle -GeoData::findBoundaryStart() const -{ - return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) { - return is_boundary(heh); - }); -} - -[[nodiscard]] RelativePosition3D -GeoData::difference(const HalfedgeHandle heh) const -{ - return point(to_vertex_handle(heh)) - point(from_vertex_handle(heh)); -} - -[[nodiscard]] RelativeDistance -GeoData::length(const HalfedgeHandle heh) const -{ - return glm::length(difference(heh)); -} - -[[nodiscard]] GlobalPosition3D -GeoData::centre(const HalfedgeHandle heh) const -{ - return point(from_vertex_handle(heh)) + (difference(heh) / 2.F); -} - void -GeoData::update_vertex_normals_only() +GeoData::updateAllVertexNormals() { - update_vertex_normals_only(vertices_sbegin()); + updateAllVertexNormals(vertices()); } +template<std::ranges::range R> void -GeoData::update_vertex_normals_only(VertexIter start) +GeoData::updateAllVertexNormals(const R & range) { - std::for_each(start, vertices_end(), [this](const auto vh) { - if (normal(vh) == Normal3D {}) { - Normal3D n; - calc_vertex_normal_correct(vh, n); - this->set_normal(vh, glm::normalize(n)); - } + std::ranges::for_each(range, [this](const auto vertex) { + updateVertexNormal(vertex); }); } -bool -GeoData::triangleOverlapsTriangle(const Triangle<2> & a, const Triangle<2> & b) -{ - return triangleContainsPoint(a.x, b) || triangleContainsPoint(a.y, b) || triangleContainsPoint(a.z, b) - || triangleContainsPoint(b.x, a) || triangleContainsPoint(b.y, a) || triangleContainsPoint(b.z, a) - || linesCross(a.x, a.y, b.x, b.y) || linesCross(a.x, a.y, b.y, b.z) || linesCross(a.x, a.y, b.z, b.x) - || linesCross(a.y, a.z, b.x, b.y) || linesCross(a.y, a.z, b.y, b.z) || linesCross(a.y, a.z, b.z, b.x) - || linesCross(a.z, a.x, b.x, b.y) || linesCross(a.z, a.x, b.y, b.z) || linesCross(a.z, a.x, b.z, b.x); -} - -bool -GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b) +void +GeoData::updateVertexNormal(VertexHandle vertex) { - return triangleContainsPoint(a.x, b) && triangleContainsPoint(a.y, b) && triangleContainsPoint(a.z, b); + Normal3D normal {}; + { // Lifted from calc_vertex_normal_correct in PolyMeshT_impl.hh but doesn't use Scalar to normalise + ConstVertexIHalfedgeIter cvih_it = this->cvih_iter(vertex); + if (!cvih_it.is_valid()) { // don't crash on isolated vertices + return; + } + Normal in_he_vec; + calc_edge_vector(*cvih_it, in_he_vec); + for (; cvih_it.is_valid(); ++cvih_it) { // calculates the sector normal defined by cvih_it and adds it to normal + if (this->is_boundary(*cvih_it)) { + continue; + } + HalfedgeHandle out_heh(this->next_halfedge_handle(*cvih_it)); + Normal out_he_vec; + calc_edge_vector(out_heh, out_he_vec); + normal += cross(in_he_vec, out_he_vec); // sector area is taken into account + in_he_vec = out_he_vec; + in_he_vec *= -1; // change the orientation + } + } // End lift + set_normal(vertex, glm::normalize(normal)); } -void -GeoData::split(FaceHandle _fh) +OpenMesh::VertexHandle +GeoData::setPoint(GlobalPosition3D tsPoint, const RelativeDistance nearNodeTolerance) { - // Collect halfedges of face - const HalfedgeHandle he0 = halfedge_handle(_fh); - const HalfedgeHandle he1 = next_halfedge_handle(he0); - const HalfedgeHandle he2 = next_halfedge_handle(he1); - - const EdgeHandle eh0 = edge_handle(he0); - const EdgeHandle eh1 = edge_handle(he1); - const EdgeHandle eh2 = edge_handle(he2); - - // Collect points of face - const VertexHandle p0 = to_vertex_handle(he0); - const VertexHandle p1 = to_vertex_handle(he1); - const VertexHandle p2 = to_vertex_handle(he2); - - // Calculate midpoint coordinates - const Point new0 = centre(he0); - const Point new1 = centre(he1); - const Point new2 = centre(he2); - - // Add vertices at midpoint coordinates - const VertexHandle v0 = add_vertex(new0); - const VertexHandle v1 = add_vertex(new1); - const VertexHandle v2 = add_vertex(new2); - - const bool split0 = !is_boundary(eh0); - const bool split1 = !is_boundary(eh1); - const bool split2 = !is_boundary(eh2); - - // delete original face - delete_face(_fh, false); - - // split boundary edges of deleted face ( if not boundary ) - if (split0) { - split(eh0, v0); - } - - if (split1) { - split(eh1, v1); + const auto face = findPoint(tsPoint); + const auto distFromTsPoint = vertexDistanceFunction<2>(tsPoint); + // Check vertices + if (const auto nearest = std::ranges::min(fv_range(face) | std::views::transform(distFromTsPoint), {}, GetSecond); + nearest.second < nearNodeTolerance) { + point(nearest.first).z = tsPoint.z; + return nearest.first; } - - if (split2) { - split(eh2, v2); + // Check edges + if (const auto nearest = std::ranges::min(fh_range(face) | std::views::transform(distFromTsPoint), {}, GetSecond); + nearest.second < nearNodeTolerance) { + const auto from = point(from_vertex_handle(nearest.first)).xy(); + const auto to = point(to_vertex_handle(nearest.first)).xy(); + const auto v = vector_normal(from - to); + const auto inter = linesIntersectAt(from, to, tsPoint.xy(), tsPoint.xy() + v); + if (!inter) [[unlikely]] { + throw std::runtime_error("Perpendicular lines do not cross"); + } + return split_copy(edge_handle(nearest.first), *inter || tsPoint.z); } + // Nothing close, split face + return split_copy(face, tsPoint); +}; - // Retriangulate - add_face(v0, p0, v1); - add_face(p2, v0, v2); - add_face(v2, v1, p1); - add_face(v2, v0, v1); -} - -void -GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface & newFaceSurface) +std::set<GeoData::FaceHandle> +GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts & opts) { - static const RelativeDistance MAX_SLOPE = 1.5F; - static const RelativeDistance MIN_ARC = 0.01F; - if (triangleStrip.size() < 3) { - return; + return {}; + } + for (const auto & vertex : triangleStrip) { + extents += vertex; } - const auto initialVertexCount = static_cast<unsigned int>(n_vertices()); + class SetHeights { + public: + SetHeights(GeoData * geoData, const std::span<const GlobalPosition3D> triangleStrip) : + geoData(geoData), triangleStrip {triangleStrip}, + strip {materializeRange(triangleStrip | triangleTriples | std::views::transform([](const auto & newVert) { + return std::make_from_tuple<Triangle<3>>(newVert); + }))} + { + } - // Create new vertices - std::vector<VertexHandle> newVerts; - newVerts.reserve(newVerts.size()); - std::transform(triangleStrip.begin(), triangleStrip.end(), std::back_inserter(newVerts), [this](const auto tsVert) { - return add_vertex(tsVert); - }); - // Create new faces - const auto initialFaceCount = static_cast<int>(n_faces()); - std::for_each(strip_begin(newVerts), strip_end(newVerts), [this](const auto & newVert) { - const auto [a, b, c] = newVert; - add_face(a, b, c); - }); - for (auto fhi = FaceIter {*this, FaceHandle {initialFaceCount}, true}; fhi != faces_end(); fhi++) { - static constexpr auto MAX_FACE_AREA = 100'000'000.F; - const auto fh = *fhi; - if (triangle<3>(fh).area() > MAX_FACE_AREA) { - split(fh); + std::vector<VertexHandle> + createVerticesForStrip(RelativeDistance nearNodeTolerance) + { + // New vertices for each vertex in triangleStrip + const auto newVerts + = materializeRange(triangleStrip | std::views::transform([this, nearNodeTolerance](auto v) { + return geoData->setPoint(v, nearNodeTolerance); + })); + std::ranges::copy(newVerts, std::inserter(newOrChangedVerts, newOrChangedVerts.end())); +#ifndef NDEBUG + geoData->sanityCheck(); +#endif + return newVerts; } - } - std::vector<FaceHandle> newFaces; - std::copy_if(FaceIter {*this, FaceHandle {initialFaceCount}, true}, faces_end(), std::back_inserter(newFaces), - [this](FaceHandle fh) { - return !this->status(fh).deleted(); - }); - // Extrude corners - struct Extrusion { - VertexHandle boundaryVertex, extrusionVertex; - Direction3D lowerLimit, upperLimit; - }; + const Triangle<3> * + getTriangle(const GlobalPosition2D point) const + { + if (const auto t = std::ranges::find_if(strip, + [point](const auto & triangle) { + return triangle.containsPoint(point); + }); + t != strip.end()) { + return &*t; + } + return nullptr; + } - std::vector<Extrusion> extrusionExtents; - boundaryWalk( - [this, &extrusionExtents](const auto boundaryHeh) { - const auto prevBoundaryHeh = prev_halfedge_handle(boundaryHeh); - const auto prevBoundaryVertex = from_vertex_handle(prevBoundaryHeh); - const auto boundaryVertex = from_vertex_handle(boundaryHeh); - const auto nextBoundaryVertex = to_vertex_handle(boundaryHeh); - const auto p0 = point(prevBoundaryVertex); - const auto p1 = point(boundaryVertex); - const auto p2 = point(nextBoundaryVertex); - const auto e0 = glm::normalize(vector_normal(RelativePosition2D(p1 - p0))); - const auto e1 = glm::normalize(vector_normal(RelativePosition2D(p2 - p1))); - - const auto addExtrusionFor = [this, &extrusionExtents, boundaryVertex, p1](Direction2D direction) { - const auto doExtrusion = [this](VertexHandle & extrusionVertex, Direction2D direction, - GlobalPosition3D boundaryVertex, RelativeDistance vert) { - const auto extrusionDir = glm::normalize(direction || vert); - - if (!extrusionVertex.is_valid()) { - if (const auto intersect = intersectRay({boundaryVertex, extrusionDir})) { - auto splitVertex = split(intersect->second, intersect->first); - extrusionVertex = splitVertex; - } - else if (const auto intersect - = intersectRay({boundaryVertex + GlobalPosition3D {1, 1, 0}, extrusionDir})) { - auto splitVertex = split(intersect->second, intersect->first); - extrusionVertex = splitVertex; - } - else if (const auto intersect - = intersectRay({boundaryVertex + GlobalPosition3D {1, 0, 0}, extrusionDir})) { - auto splitVertex = split(intersect->second, intersect->first); - extrusionVertex = splitVertex; + void + doBoundaryPart(VertexHandle start, VertexHandle end, const Triangle<3> & triangle, + const RelativeDistance nearNodeTolerance) + { + boundaryTriangles.emplace(start, &triangle); + const auto endPoint = geoData->point(end); + while (!std::ranges::contains(geoData->vv_range(start), end)) { + const auto startPoint = geoData->point(start); + const auto distanceToEndPoint = distance(startPoint.xy(), endPoint.xy()); + if (std::ranges::any_of(geoData->vv_range(start), [&](const auto & adjVertex) { + const auto adjPoint = geoData->point(adjVertex); + if (distance(adjPoint.xy(), endPoint.xy()) < distanceToEndPoint + && (Triangle<2> {startPoint, endPoint, adjPoint}.area() + / distance(startPoint.xy(), endPoint.xy())) + < nearNodeTolerance) { + start = adjVertex; + newOrChangedVerts.emplace(start); + boundaryTriangles.emplace(start, &triangle); + geoData->point(start).z = triangle.positionOnPlane(adjPoint).z; + return true; + } + return false; + })) { + continue; + } + if (std::ranges::any_of(geoData->voh_range(start), [&](const auto & outHalf) { + const auto next = geoData->next_halfedge_handle(outHalf); + const auto nexts + = std::array {geoData->from_vertex_handle(next), geoData->to_vertex_handle(next)}; + const auto nextPoints = nexts | std::views::transform([this](const auto v) { + return std::make_pair(v, geoData->point(v)); + }); + if (linesCross(startPoint, endPoint, nextPoints.front().second, nextPoints.back().second)) { + if (const auto intersection = linesIntersectAt(startPoint.xy(), endPoint.xy(), + nextPoints.front().second.xy(), nextPoints.back().second.xy())) { + if (const auto nextEdge = geoData->shouldFlip(next, startPoint)) { + geoData->flip(*nextEdge); + return true; + } + start = geoData->split_copy( + geoData->edge_handle(next), triangle.positionOnPlane(*intersection)); + newOrChangedVerts.emplace(start); + boundaryTriangles.emplace(start, &triangle); + return true; } + throw std::runtime_error("Crossing lines don't intersect"); } + return false; + })) { + continue; + } +#ifndef NDEBUG + CLOG(start); + CLOG(startPoint); + CLOG(end); + CLOG(endPoint); + for (const auto v : geoData->vv_range(start)) { + CLOG(geoData->point(v)); + } + geoData->sanityCheck(); +#endif + throw std::runtime_error( + std::format("Could not navigate to ({}, {}, {})", endPoint.x, endPoint.y, endPoint.z)); + } + } + + void + cutBoundary(const std::vector<VertexHandle> & newVerts, RelativeDistance nearNodeTolerance) + { + // Cut along each edge of triangleStrip AB, AC, BC, BD, CD, CE etc + std::ranges::for_each(newVerts | std::views::adjacent<3>, + [this, nearNodeTolerance, triangle = strip.begin()](const auto & verts) mutable { + const auto & [a, _, c] = verts; + doBoundaryPart(a, c, *triangle, nearNodeTolerance); + triangle++; + }); + doBoundaryPart(*++newVerts.begin(), newVerts.front(), strip.front(), nearNodeTolerance); + doBoundaryPart(*++newVerts.rbegin(), newVerts.back(), strip.back(), nearNodeTolerance); + } + + using HeightSetTodo = std::multimap<VertexHandle, VertexHandle>; - return extrusionDir; - }; - - VertexHandle extrusionVertex; - extrusionExtents.emplace_back(boundaryVertex, extrusionVertex, - doExtrusion(extrusionVertex, direction, p1, -MAX_SLOPE), - doExtrusion(extrusionVertex, direction, p1, MAX_SLOPE)); - assert(extrusionVertex.is_valid()); - }; - if (const Arc arc {e0, e1}; arc.length() < MIN_ARC) { - addExtrusionFor(normalize(e0 + e1) / cosf(arc.length() / 2.F)); + HeightSetTodo + setSurfaceHeights(const std::span<const VertexHandle> newVerts) + { + HeightSetTodo out; + auto setSurfaceVertexHeight = [this, &out](const auto & setSurfaceVertexHeight, const VertexHandle vertex, + const VertexHandle previousVertex) -> void { + if (surfaceVerts.contains(vertex)) { + return; } - else if (arc.length() < pi) { - // Previous half edge end to current half end start arc tangents - const auto limit = std::ceil(arc.length() * 5.F / pi); - const auto inc = arc.length() / limit; - for (float step = 0; step <= limit; step += 1.F) { - addExtrusionFor(sincosf(arc.first + (step * inc))); + auto & point = geoData->point(vertex); + auto triangle = getTriangle(point); + if (!triangle) { + if (const auto boundaryVertex = boundaryTriangles.find(vertex); + boundaryVertex != boundaryTriangles.end()) { + triangle = boundaryVertex->second; } } - else { - // Single tangent bisecting the difference - addExtrusionFor(normalize(e0 + e1) / sinf((arc.length() - pi) / 2.F)); + if (triangle) { // point within the new strip, adjust vertically by triangle + point.z = triangle->positionOnPlane(point).z; + newOrChangedVerts.emplace(vertex); + surfaceVerts.emplace(vertex); + for (const auto nextVertex : geoData->vv_range(vertex)) { + setSurfaceVertexHeight(setSurfaceVertexHeight, nextVertex, vertex); + } } - }, - *voh_begin(newVerts.front())); - - // Cut existing terrain - extrusionExtents.emplace_back(extrusionExtents.front()); // Circular next - std::vector<std::vector<VertexHandle>> boundaryFaces; - for (const auto & [first, second] : extrusionExtents | std::views::adjacent<2>) { - const auto p0 = point(first.boundaryVertex); - const auto p1 = point(second.boundaryVertex); - const auto bdir = RelativePosition3D(p1 - p0); - const auto make_plane = [p0](auto y, auto z) { - return GeometricPlaneT<GlobalPosition3D> {p0, crossProduct(y, z)}; - }; - const auto planes = ((first.boundaryVertex == second.boundaryVertex) - ? std::array {make_plane(second.lowerLimit, first.lowerLimit), - make_plane(second.upperLimit, first.upperLimit), - } - : std::array { - make_plane(bdir, second.lowerLimit), - make_plane(bdir, second.upperLimit), - }); - assert(planes.front().normal.z > 0.F); - assert(planes.back().normal.z > 0.F); - - auto & out = boundaryFaces.emplace_back(); - out.emplace_back(first.boundaryVertex); - out.emplace_back(first.extrusionVertex); - for (auto currentVertex = first.extrusionVertex; - !find_halfedge(currentVertex, second.extrusionVertex).is_valid();) { - [[maybe_unused]] const auto n - = std::any_of(voh_begin(currentVertex), voh_end(currentVertex), [&](const auto currentVertexOut) { - const auto next = next_halfedge_handle(currentVertexOut); - const auto nextVertex = to_vertex_handle(next); - const auto startVertex = from_vertex_handle(next); - if (nextVertex == *++out.rbegin()) { - // This half edge goes back to the previous vertex - return false; - } - const auto edge = edge_handle(next); - const auto ep0 = point(startVertex); - const auto ep1 = point(nextVertex); - if (planes.front().getRelation(ep1) == GeometricPlane::PlaneRelation::Below - || planes.back().getRelation(ep1) == GeometricPlane::PlaneRelation::Above) { - return false; - } - const auto diff = RelativePosition3D(ep1 - ep0); - const auto length = glm::length(diff); - const auto dir = diff / length; - const Ray r {ep1, -dir}; - const auto dists = planes * [r](const auto & plane) { - RelativeDistance dist {}; - if (r.intersectPlane(plane.origin, plane.normal, dist)) { - return dist; - } - return INFINITY; - }; - const auto dist = *std::min_element(dists.begin(), dists.end()); - const auto splitPos = ep1 - (dir * dist); - if (dist <= length) { - currentVertex = split(edge, splitPos); - out.emplace_back(currentVertex); - return true; - } - return false; - }); - assert(n); + else if (previousVertex.is_valid()) { + out.emplace(vertex, previousVertex); + } + }; + for (const auto vertex : newVerts) { + setSurfaceVertexHeight(setSurfaceVertexHeight, vertex, VertexHandle {}); + } + return out; + } + + void + setHalfedgeToHeight(HeightSetTodo & nexts, HeightSetTodo::const_iterator verticesBegin, + HeightSetTodo::const_iterator verticesEnd, const RelativeDistance maxSlope) + { + const auto vertex = verticesBegin->first; + auto & point = geoData->point(vertex); + const auto minMaxHeight = std::accumulate(verticesBegin, verticesEnd, + std::pair<GlobalDistance, GlobalDistance> { + std::numeric_limits<GlobalDistance>::min(), + std::numeric_limits<GlobalDistance>::max(), + }, + [this, maxSlope, point](auto limit, auto previousVertexItr) { + const auto & fromPoint = geoData->point(previousVertexItr.second); + const auto maxOffset = static_cast<GlobalDistance>( + std::round(maxSlope * ::distance<2>(fromPoint.xy(), point.xy()))); + limit.first = std::max(limit.first, fromPoint.z - maxOffset); + limit.second = std::min(limit.second, fromPoint.z + maxOffset); + return limit; + }); + + const auto newHeight = std::clamp(point.z, minMaxHeight.first, minMaxHeight.second); + if (newHeight != point.z) { + point.z = newHeight; + newOrChangedVerts.emplace(vertex); + for (const auto nextVertex : geoData->vv_range(vertex)) { + if (!std::ranges::contains(verticesBegin, verticesEnd, nextVertex, GetSecond) + && !boundaryTriangles.contains(nextVertex)) { + nexts.emplace(nextVertex, vertex); + } + } + } } - out.emplace_back(second.extrusionVertex); - if (first.boundaryVertex != second.boundaryVertex) { - out.emplace_back(second.boundaryVertex); + + void + setHeightsAsRequired(HeightSetTodo starts, RelativeDistance maxSlope) + { + while (!starts.empty()) { + HeightSetTodo nexts; + + for (const auto chunk : starts | std::views::chunk_by([](const auto a, const auto b) { + return a.first == b.first; + })) { + setHalfedgeToHeight(nexts, chunk.begin(), chunk.end(), maxSlope); + } + starts = std::move(nexts); + } } - } - // Remove old faces - std::set<FaceHandle> visited; - auto removeOld = [&](auto & self, const auto face) -> void { - if (visited.insert(face).second) { - std::for_each(fh_begin(face), fh_end(face), [&](const auto fh) { - const auto b1 = to_vertex_handle(fh); - const auto b2 = from_vertex_handle(fh); - if (opposite_face_handle(fh).is_valid() - && std::none_of(boundaryFaces.begin(), boundaryFaces.end(), [b2, b1](const auto & bf) { - return std::adjacent_find(bf.begin(), bf.end(), [b2, b1](const auto v1, const auto v2) { - return b1 == v1 && b2 == v2; - }) != bf.end(); - })) { - self(self, opposite_face_handle(fh)); + std::set<FaceHandle> + setSurface(const Surface * surface) + { + std::set<FaceHandle> out; + auto surfaceStripWalk = [this, surface, &out](const auto & surfaceStripWalk, const auto & face) -> void { + if (!out.contains(face)) { + geoData->property(geoData->surface, face) = surface; + out.emplace(face); + std::ranges::for_each( + geoData->ff_range(face), [this, &surfaceStripWalk](const auto & adjacentFaceHandle) { + if (getTriangle(geoData->triangle<2>(adjacentFaceHandle).centroid())) { + surfaceStripWalk(surfaceStripWalk, adjacentFaceHandle); + } + }); } - }); + }; + for (const auto & triangle : strip) { + surfaceStripWalk(surfaceStripWalk, geoData->findPoint(triangle.centroid())); + } + return out; + } - delete_face(face, false); + std::set<GeoData::FaceHandle> + run(const SetHeightsOpts & opts) + { + const std::vector<VertexHandle> newVerts = createVerticesForStrip(opts.nearNodeTolerance); + cutBoundary(newVerts, opts.nearNodeTolerance); + setHeightsAsRequired(setSurfaceHeights(newVerts), opts.maxSlope); + const auto out = setSurface(opts.surface); + + geoData->expandVerts(newOrChangedVerts); + geoData->updateAllVertexNormals(newOrChangedVerts); + geoData->afterChange(); + + return out; } - }; - removeOld(removeOld, findPoint(triangleStrip.front())); - std::for_each(boundaryFaces.begin(), boundaryFaces.end(), [&](auto & boundaryFace) { - std::reverse(boundaryFace.begin(), boundaryFace.end()); - add_face(boundaryFace); - }); + private: + GeoData * geoData; + const std::span<const GlobalPosition3D> triangleStrip; + const std::vector<Triangle<3>> strip; + std::set<VertexHandle> newOrChangedVerts; + std::set<VertexHandle> surfaceVerts; + std::map<VertexHandle, const Triangle<3> *> boundaryTriangles; + }; - // Tidy up - update_vertex_normals_only(VertexIter {*this, vertex_handle(initialVertexCount), true}); + return SetHeights {this, triangleStrip}.run(opts); +} - std::for_each(newFaces.begin(), newFaces.end(), [&newFaceSurface, this](const auto fh) { - property(surface, fh) = &newFaceSurface; - }); +void +GeoData::afterChange() +{ } diff --git a/game/geoData.h b/game/geoData.h index ed1734c..0ff0c42 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -1,193 +1,85 @@ #pragma once #include "collections.h" // IWYU pragma: keep IterableCollection -#include "config/types.h" -#include "ray.h" +#include "geoDataMesh.h" +#include "gfx/aabb.h" #include "surface.h" -#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> #include <filesystem> #include <glm/vec2.hpp> -#include <optional> -#include <thirdparty/openmesh/glmcompat.h> - -struct GeoDataTraits : public OpenMesh::DefaultTraits { - FaceAttributes(OpenMesh::Attributes::Status); - EdgeAttributes(OpenMesh::Attributes::Status); - VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); - HalfedgeAttributes(OpenMesh::Attributes::Status); - using Point = GlobalPosition3D; - using Normal = Normal3D; -}; -class GeoData : public OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits> { +class GeoData : public GeoDataMesh { private: - GeoData(); - - OpenMesh::FPropHandleT<const Surface *> surface; + const OpenMesh::Helpers::Property<const Surface *, OpenMesh::FPropHandleT> surface {this}; public: static GeoData loadFromAsciiGrid(const std::filesystem::path &); static GeoData createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistance h); - struct PointFace { - // NOLINTNEXTLINE(hicpp-explicit-conversions) - PointFace(const GlobalPosition2D p) : point {p} { } - - PointFace(const GlobalPosition2D p, FaceHandle face) : point {p}, _face {face} { } - - PointFace(const GlobalPosition2D p, const GeoData *); - PointFace(const GlobalPosition2D p, const GeoData *, FaceHandle start); - - const GlobalPosition2D point; - [[nodiscard]] FaceHandle face(const GeoData *) const; - [[nodiscard]] FaceHandle face(const GeoData *, FaceHandle start) const; - - [[nodiscard]] bool - isLocated() const - { - return _face.is_valid(); - } + using IntersectionLocation = std::pair<GlobalPosition3D, FaceHandle>; + using IntersectionResult = std::optional<IntersectionLocation>; + [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &) const; + [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &, FaceHandle start) const; - private: - mutable FaceHandle _face {}; + struct WalkStep { + FaceHandle current; + FaceHandle previous {}; + HalfedgeHandle exitHalfedge {}; + GlobalPosition2D exitPosition {}; }; - template<glm::length_t Dim> struct Triangle : public glm::vec<3, glm::vec<Dim, GlobalDistance>> { - using base = glm::vec<3, glm::vec<Dim, GlobalDistance>>; - using base::base; - - template<IterableCollection Range> Triangle(const GeoData * m, Range range) - { - assert(std::distance(range.begin(), range.end()) == 3); - std::transform(range.begin(), range.end(), &base::operator[](0), [m](auto vh) { - return m->point(vh); - }); - } - - [[nodiscard]] glm::vec<Dim, GlobalDistance> - operator*(BaryPosition bari) const - { - return p(0) + (difference(p(0), p(1)) * bari.x) + (difference(p(0), p(2)) * bari.y); - } - - [[nodiscard]] auto - area() const - requires(Dim == 3) - { - return glm::length(crossProduct(difference(p(0), p(1)), difference(p(0), p(2)))) / 2.F; - } - - [[nodiscard]] Normal3D - normal() const - requires(Dim == 3) - { - return crossProduct(difference(p(0), p(1)), difference(p(0), p(2))); - } - - [[nodiscard]] Normal3D - nnormal() const - requires(Dim == 3) - { - return glm::normalize(normal()); - } - - [[nodiscard]] auto - angle(glm::length_t c) const - { - return Arc {P(c), P(c + 2), P(c + 1)}.length(); - } - - template<glm::length_t D = Dim> - [[nodiscard]] auto - angleAt(const GlobalPosition<D> pos) const - requires(D <= Dim) - { - for (glm::length_t i {}; i < 3; ++i) { - if (GlobalPosition<D> {p(i)} == pos) { - return angle(i); - } - } - return 0.F; - } - - [[nodiscard]] inline auto - p(const glm::length_t i) const - { - return base::operator[](i); - } - - [[nodiscard]] inline auto - P(const glm::length_t i) const - { - return base::operator[](i % 3); - } + struct WalkStepCurve : public WalkStep { + Angle angle {}; }; - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const; - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const; + template<typename T> using Consumer = const std::function<void(const T &)> &; + template<typename T> using Tester = const std::function<bool(const T &)> &; - [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const; - using IntersectionLocation = std::pair<GlobalPosition3D, FaceHandle>; - using IntersectionResult = std::optional<IntersectionLocation>; - [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &) const; - [[nodiscard]] IntersectionResult intersectRay(const Ray<GlobalPosition3D> &, FaceHandle start) const; + void walk(const PointFace & from, GlobalPosition2D to, Consumer<WalkStep> op) const; + void walkUntil(const PointFace & from, GlobalPosition2D to, Tester<WalkStep> op) const; + void walk(const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Consumer<WalkStepCurve> op) const; + void walkUntil( + const PointFace & from, GlobalPosition2D to, GlobalPosition2D centre, Tester<WalkStepCurve> op) const; + + void boundaryWalk(Consumer<HalfedgeHandle>) const; + void boundaryWalk(Consumer<HalfedgeHandle>, HalfedgeHandle start) const; + void boundaryWalkUntil(Tester<HalfedgeHandle>) const; + void boundaryWalkUntil(Tester<HalfedgeHandle>, HalfedgeHandle start) const; - void walk(const PointFace & from, const GlobalPosition2D to, const std::function<void(FaceHandle)> & op) const; - void walkUntil(const PointFace & from, const GlobalPosition2D to, const std::function<bool(FaceHandle)> & op) const; + [[nodiscard]] HalfedgeHandle findEntry(GlobalPosition2D from, GlobalPosition2D to) const; - void boundaryWalk(const std::function<void(HalfedgeHandle)> &) const; - void boundaryWalk(const std::function<void(HalfedgeHandle)> &, HalfedgeHandle start) const; - void boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> &) const; - void boundaryWalkUntil(const std::function<bool(HalfedgeHandle)> &, HalfedgeHandle start) const; + struct SetHeightsOpts { + static constexpr auto DEFAULT_NEAR_NODE_TOLERANACE = 500.F; + static constexpr auto DEFAULT_MAX_SLOPE = 0.5F; - [[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const; + const Surface * surface = nullptr; + RelativeDistance nearNodeTolerance = DEFAULT_NEAR_NODE_TOLERANACE; + RelativeDistance maxSlope = DEFAULT_MAX_SLOPE; + }; - void setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface &); + std::set<FaceHandle> setHeights(std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts &); - [[nodiscard]] auto + [[nodiscard]] auto & getExtents() const { - return std::tie(lowerExtent, upperExtent); + return extents; } template<typename HandleT> + requires(std::derived_from<HandleT, OpenMesh::BaseHandle>) [[nodiscard]] auto - get_surface(const HandleT h) + getSurface(const HandleT handle) const { - return property(surface, h); + assert(handle.is_valid()); + return property(surface, handle); } protected: - template<glm::length_t Dim> - [[nodiscard]] Triangle<Dim> - triangle(FaceHandle f) const - { - return {this, fv_range(f)}; - } - - [[nodiscard]] static bool triangleContainsPoint(const GlobalPosition2D, const Triangle<2> &); - [[nodiscard]] bool triangleContainsPoint(const GlobalPosition2D, FaceHandle) const; - [[nodiscard]] static bool triangleOverlapsTriangle(const Triangle<2> &, const Triangle<2> &); - [[nodiscard]] static bool triangleContainsTriangle(const Triangle<2> &, const Triangle<2> &); - [[nodiscard]] HalfedgeHandle findBoundaryStart() const; - [[nodiscard]] RelativePosition3D difference(const HalfedgeHandle) const; - - template<glm::length_t D> - [[nodiscard]] static RelativePosition<D> - difference(const GlobalPosition<D> a, const GlobalPosition<D> b) - { - return b - a; - } - - [[nodiscard]] RelativeDistance length(const HalfedgeHandle) const; - [[nodiscard]] GlobalPosition3D centre(const HalfedgeHandle) const; - - void update_vertex_normals_only(); - void update_vertex_normals_only(VertexIter start); - - using OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits>::split; - void split(FaceHandle); + [[nodiscard]] VertexHandle setPoint(GlobalPosition3D point, RelativeDistance nearNodeTolerance); + void updateAllVertexNormals(); + template<std::ranges::range R> void updateAllVertexNormals(const R &); + void updateVertexNormal(VertexHandle); + virtual void afterChange(); private: - GlobalPosition3D lowerExtent {}, upperExtent {}; + AxisAlignedBoundingBox<GlobalDistance> extents; }; diff --git a/game/geoDataMesh.cpp b/game/geoDataMesh.cpp new file mode 100644 index 0000000..8107a5e --- /dev/null +++ b/game/geoDataMesh.cpp @@ -0,0 +1,150 @@ +#include "geoDataMesh.h" +#include <format> +#ifndef NDEBUG +# include <stream_support.h> +#endif + +OpenMesh::FaceHandle +GeoDataMesh::findPoint(GlobalPosition2D coord) const +{ + return findPoint(coord, *faces_sbegin()); +} + +GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh) : + PointFace {coord, mesh, *mesh->faces_sbegin()} +{ +} + +GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh, FaceHandle start) : + PointFace {coord, mesh->findPoint(coord, start)} +{ +} + +OpenMesh::FaceHandle +GeoDataMesh::PointFace::face(const GeoDataMesh * mesh, FaceHandle start) const +{ + if (faceCache.is_valid() && mesh->faceContainsPoint(point, faceCache)) { + return faceCache; + } + return (faceCache = mesh->findPoint(point, start)); +} + +OpenMesh::FaceHandle +GeoDataMesh::PointFace::face(const GeoDataMesh * mesh) const +{ + return face(mesh, *mesh->faces_sbegin()); +} + +GeoDataMesh::HalfEdgeVertices +GeoDataMesh::toVertexHandles(HalfedgeHandle halfEdge) const +{ + return {from_vertex_handle(halfEdge), to_vertex_handle(halfEdge)}; +} + +GeoDataMesh::HalfEdgePoints +GeoDataMesh::points(HalfEdgeVertices vertices) const +{ + return {point(vertices.first), point(vertices.second)}; +} + +OpenMesh::FaceHandle +GeoDataMesh::findPoint(const GlobalPosition2D coord, OpenMesh::FaceHandle face) const +{ + while (face.is_valid() && !triangle<2>(face).containsPoint(coord)) { + for (auto next = cfh_iter(face); next.is_valid(); ++next) { + face = opposite_face_handle(*next); + if (face.is_valid()) { + const auto nextPoints = points(toVertexHandles(*next)); + if (pointLeftOfLine(coord, nextPoints.second, nextPoints.first)) { + break; + } + } + face.reset(); + } + } + return face; +} + +GlobalPosition3D +GeoDataMesh::positionAt(const PointFace & coord) const +{ + return triangle<3>(coord.face(this)).positionOnPlane(coord.point); +} + +bool +GeoDataMesh::faceContainsPoint(const GlobalPosition2D coord, FaceHandle face) const +{ + return triangle<2>(face).containsPoint(coord); +} + +OpenMesh::HalfedgeHandle +GeoDataMesh::findBoundaryStart() const +{ + return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) { + return is_boundary(heh); + }); +} + +[[nodiscard]] RelativePosition3D +GeoDataMesh::difference(const HalfedgeHandle heh) const +{ + return ::difference(point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); +} + +[[nodiscard]] GlobalPosition3D +GeoDataMesh::centre(const HalfedgeHandle heh) const +{ + const auto hehPoints = points(toVertexHandles(heh)); + return midpoint(hehPoints.first, hehPoints.second); +} + +#ifndef NDEBUG +void +GeoDataMesh::sanityCheck(const std::source_location & loc) const +{ + if (const auto upSideDown = std::ranges::count_if(faces(), [this](const auto face) { + if (!triangle<2>(face).isUp()) { + for (const auto vertex : fv_range(face)) { + CLOG(point(vertex)); + } + return true; + } + return false; + }) > 0) { + throw std::logic_error(std::format( + "{} upside down faces detected - checked from {}:{}", upSideDown, loc.function_name(), loc.line())); + } +} +#endif + +bool +GeoDataMesh::canFlip(const HalfedgeHandle edge) const +{ + const auto opposite = opposite_halfedge_handle(edge); + const auto pointA = point(to_vertex_handle(edge)); + const auto pointB = point(to_vertex_handle(opposite)); + const auto pointC = point(to_vertex_handle(next_halfedge_handle(edge))); + const auto pointD = point(to_vertex_handle(next_halfedge_handle(opposite))); + + return Triangle<2> {pointC, pointB, pointD}.isUp() && Triangle<2> {pointA, pointC, pointD}.isUp(); +}; + +std::optional<OpenMesh::EdgeHandle> +GeoDataMesh::shouldFlip(const HalfedgeHandle next, const GlobalPosition2D startPoint) const +{ + if (const auto nextEdge = edge_handle(next); is_flip_ok(nextEdge) && canFlip(next)) { + const auto oppositePoint = point(to_vertex_handle(next_halfedge_handle(opposite_halfedge_handle(next)))).xy(); + if (distance<2>(startPoint, oppositePoint) < length<2>(next)) { + return nextEdge; + } + } + return std::nullopt; +}; + +void +GeoDataMesh::expandVerts(std::set<VertexHandle> & verts) const +{ + std::ranges::for_each(std::vector<VertexHandle>(verts.begin(), verts.end()), [&verts, this](auto vertex) { + std::ranges::copy(vv_range(vertex), std::inserter(verts, verts.end())); + }); +} diff --git a/game/geoDataMesh.h b/game/geoDataMesh.h new file mode 100644 index 0000000..c9da8f0 --- /dev/null +++ b/game/geoDataMesh.h @@ -0,0 +1,116 @@ +#pragma once + +#include "config/types.h" +#include "triangle.h" +#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh> +#include <set> +#include <source_location> +#include <thirdparty/openmesh/glmcompat.h> +#include <thirdparty/openmesh/helpers.h> + +struct GeoDataTraits : public OpenMesh::DefaultTraits { + FaceAttributes(OpenMesh::Attributes::Status); + EdgeAttributes(OpenMesh::Attributes::Status); + VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); + HalfedgeAttributes(OpenMesh::Attributes::Status); + using Point = GlobalPosition3D; + using Normal = Normal3D; +}; + +class GeoDataMesh : public OpenMesh::TriMesh_ArrayKernelT<GeoDataTraits> { +public: + struct PointFace { + // NOLINTNEXTLINE(hicpp-explicit-conversions) + PointFace(GlobalPosition2D coord) : point {coord} { } + + PointFace(GlobalPosition2D coord, FaceHandle face) : point {coord}, faceCache {face} { } + + PointFace(GlobalPosition2D coord, const GeoDataMesh *); + PointFace(GlobalPosition2D coord, GeoDataMesh const *, FaceHandle start); + + const GlobalPosition2D point; + [[nodiscard]] FaceHandle face(const GeoDataMesh *) const; + [[nodiscard]] FaceHandle face(const GeoDataMesh *, FaceHandle start) const; + + [[nodiscard]] bool + isLocated() const + { + return faceCache.is_valid(); + } + + private: + mutable FaceHandle faceCache; + }; + + template<glm::length_t Dim> using Triangle = ::Triangle<Dim, GlobalDistance>; + + [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const; + [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle) const; + + [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const; + +protected: +#ifndef NDEBUG + void sanityCheck(const std::source_location & = std::source_location::current()) const; +#endif + + [[nodiscard]] bool faceContainsPoint(GlobalPosition2D, FaceHandle) const; + [[nodiscard]] HalfedgeHandle findBoundaryStart() const; + [[nodiscard]] RelativePosition3D difference(HalfedgeHandle) const; + using HalfEdgeVertices = std::pair<VertexHandle, VertexHandle>; + [[nodiscard]] HalfEdgeVertices toVertexHandles(HalfedgeHandle) const; + using HalfEdgePoints = std::pair<GlobalPosition3D, GlobalPosition3D>; + [[nodiscard]] HalfEdgePoints points(HalfEdgeVertices) const; + + template<glm::length_t D> + [[nodiscard]] auto + vertexDistanceFunction(GlobalPosition<D> point) const + { + struct DistanceCalculator { + [[nodiscard]] std::pair<VertexHandle, float> + operator()(VertexHandle compVertex) const + { + return std::make_pair( + compVertex, ::distance<D, GlobalDistance, glm::defaultp>(point, mesh->point(compVertex))); + } + + [[nodiscard]] + std::pair<HalfedgeHandle, float> + operator()(const HalfedgeHandle compHalfedge) const + { + const auto edgePoints = mesh->points(mesh->toVertexHandles(compHalfedge)); + return std::make_pair(compHalfedge, Triangle<2> {edgePoints.second, edgePoints.first, point}.height()); + }; + + const GeoDataMesh * mesh; + GlobalPosition<D> point; + }; + + return DistanceCalculator {this, point}; + } + + [[nodiscard]] bool canFlip(HalfedgeHandle edge) const; + [[nodiscard]] std::optional<EdgeHandle> shouldFlip(HalfedgeHandle next, GlobalPosition2D startPoint) const; + void expandVerts(std::set<VertexHandle> & verts) const; + + template<glm::length_t D> + [[nodiscard]] RelativeDistance + length(HalfedgeHandle heh) const + { + return ::distance<D, GlobalDistance, glm::defaultp>( + point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); + } + + [[nodiscard]] GlobalPosition3D centre(HalfedgeHandle) const; + + template<glm::length_t Dim> + [[nodiscard]] Triangle<Dim> + triangle(FaceHandle face) const + { + Triangle<Dim> triangle; + std::ranges::transform(fv_range(face), triangle.begin(), [this](auto vertex) { + return this->point(vertex); + }); + return triangle; + } +}; diff --git a/game/network/link.cpp b/game/network/link.cpp index 248fe7d..b8ffee2 100644 --- a/game/network/link.cpp +++ b/game/network/link.cpp @@ -5,71 +5,115 @@ #include <ray.h> #include <tuple> -Link::Link(End a, End b, float l) : ends {{std::move(a), std::move(b)}}, length {l} { } +Link::Link(End endA, End endB, float len) : ends {{std::move(endA), std::move(endB)}}, length {len} { } -LinkCurve::LinkCurve(GlobalPosition3D c, RelativeDistance r, Arc a) : centreBase {c}, radius {r}, arc {std::move(a)} { } +LinkCurve::LinkCurve(GlobalPosition3D centre, RelativeDistance radius, Arc arc) : + centreBase {centre}, radius {radius}, arc {std::move(arc)} +{ +} bool -operator<(const GlobalPosition3D & a, const GlobalPosition3D & b) +operator<(const GlobalPosition3D & left, const GlobalPosition3D & right) { // NOLINTNEXTLINE(hicpp-use-nullptr,modernize-use-nullptr) - return std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z); + return std::tie(left.x, left.y, left.z) < std::tie(right.x, right.y, right.z); } bool -operator<(const Node & a, const Node & b) +operator<(const Node & left, const Node & right) { - return a.pos < b.pos; + return left.pos < right.pos; } Location LinkStraight::positionAt(RelativeDistance dist, unsigned char start) const { - const auto es {std::make_pair(ends[start].node.get(), ends[1 - start].node.get())}; - const RelativePosition3D diff {es.second->pos - es.first->pos}; - const auto dir {glm::normalize(diff)}; - return Location {es.first->pos + (vehiclePositionOffset() + dir * dist), {vector_pitch(dir), vector_yaw(dir), 0}}; + const auto endNodes = std::make_pair(ends[start].node.get(), ends[1 - start].node.get()); + const auto diff = ::difference(endNodes.second->pos, endNodes.first->pos); + const auto directionVector = glm::normalize(diff); + return Location { + .pos = endNodes.first->pos + (vehiclePositionOffset() + directionVector * dist), + .rot = {vector_pitch(directionVector), vector_yaw(directionVector), 0}, + }; } bool LinkStraight::intersectRay(const Ray<GlobalPosition3D> & ray) const { - return ray.passesCloseToEdges( - std::array {GlobalPosition3D {ends.front().node->pos}, GlobalPosition3D {ends.back().node->pos}}, 1000); + static constexpr auto PROXIMITY = 1'000; + return ray.passesCloseToEdges(std::array {ends.front().node->pos, ends.back().node->pos}, PROXIMITY); +} + +std::vector<GlobalPosition3D> +LinkStraight::getBase(RelativeDistance width) const +{ + const auto start = ends.front().node->pos; + const auto end = ends.back().node->pos; + const auto direction = (vector_normal(normalize(::difference(start, end).xy())) * width / 2.F) || 0.F; + return { + start - direction, + start + direction, + end - direction, + end + direction, + }; } Location LinkCurve::positionAt(float dist, unsigned char start) const { - static constexpr std::array<float, 2> dirOffset {half_pi, -half_pi}; - const auto frac {dist / length}; - const auto es {std::make_pair(ends[start].node.get(), ends[1 - start].node.get())}; - const auto as {std::make_pair(arc[start], arc[1 - start])}; - const auto ang {as.first + ((as.second - as.first) * frac)}; - const auto relPos {(sincosf(ang) || 0.F) * radius}; - const auto relClimb {vehiclePositionOffset() + static constexpr std::array DIR_OFFSET {half_pi, -half_pi}; + const auto frac = dist / length; + const auto endNodes = std::make_pair(ends[start].node.get(), ends[1 - start].node.get()); + const auto arcEndAngles = std::make_pair(arc[start], arc[1 - start]); + const auto ang = glm::mix(arcEndAngles.first, arcEndAngles.second, frac); + const auto relPos = (sincos(ang) || 0.F) * radius; + const auto relClimb = vehiclePositionOffset() + RelativePosition3D {0, 0, - static_cast<RelativeDistance>(es.first->pos.z - centreBase.z) - + (static_cast<RelativeDistance>(es.second->pos.z - es.first->pos.z) * frac)}}; - const auto pitch {vector_pitch({0, 0, static_cast<RelativeDistance>(es.second->pos.z - es.first->pos.z) / length})}; - return Location {GlobalPosition3D(relPos + relClimb) + centreBase, {pitch, normalize(ang + dirOffset[start]), 0}}; + static_cast<RelativeDistance>(endNodes.first->pos.z - centreBase.z) + + (static_cast<RelativeDistance>(endNodes.second->pos.z - endNodes.first->pos.z) * frac)}; + const auto pitch {vector_pitch(difference(endNodes.second->pos, endNodes.first->pos) / length)}; + return Location { + .pos = GlobalPosition3D(relPos + relClimb) + centreBase, + .rot = {pitch, normalize(ang + DIR_OFFSET[start]), 0}, + }; } bool LinkCurve::intersectRay(const Ray<GlobalPosition3D> & ray) const { - const auto & e0p {ends[0].node->pos}; - const auto & e1p {ends[1].node->pos}; + const auto e0p = ends[0].node->pos.z; + const auto e1p = ends[1].node->pos.z; const auto slength = round_frac(length / 2.F, 5.F); const auto segs = std::round(15.F * slength / std::pow(radius, 0.7F)); - const auto step {glm::vec<2, RelativeDistance> {arc.length(), e1p.z - e0p.z} / segs}; + const auto step {glm::vec<2, RelativeDistance> {arc.length(), e1p - e0p} / segs}; auto segCount = static_cast<std::size_t>(std::lround(segs)) + 1; std::vector<GlobalPosition3D> points; points.reserve(segCount); - for (std::remove_const_t<decltype(step)> swing = {arc.first, centreBase.z - e0p.z}; segCount; + for (std::remove_const_t<decltype(step)> swing = {arc.first, centreBase.z - e0p}; segCount; swing += step, --segCount) { - points.emplace_back(centreBase + ((sincosf(swing.x) * radius) || swing.y)); + points.emplace_back(centreBase + ((sincos(swing.x) * radius) || swing.y)); } return ray.passesCloseToEdges(points, 1.F); } + +std::vector<GlobalPosition3D> +LinkCurve::getBase(RelativeDistance width) const +{ + const auto start = ends.front().node->pos; + const auto end = ends.back().node->pos; + const auto segs = std::ceil(std::sqrt(radius) * 0.02F * arc.length()); + const auto step = glm::vec<2, RelativeDistance> {arc.length(), end.z - start.z} / segs; + + auto segCount = static_cast<size_t>(segs) + 1; + std::vector<GlobalPosition3D> out; + out.reserve(segCount); + for (RelativePosition2D swing = {arc.first, centreBase.z - start.z}; segCount != 0U; swing += step, --segCount) { + const auto direction = sincos(swing.x); + const auto linkCentre = centreBase + ((direction * radius) || swing.y); + const auto toEdge = (direction * width / 2.F) || 0.F; + out.emplace_back(linkCentre + toEdge); + out.emplace_back(linkCentre - toEdge); + } + return out; +} diff --git a/game/network/link.h b/game/network/link.h index 725e023..0b58558 100644 --- a/game/network/link.h +++ b/game/network/link.h @@ -16,7 +16,7 @@ template<typename> class Ray; // it has location class Node : public StdTypeDefs<Node> { public: - explicit Node(GlobalPosition3D p) noexcept : pos(p) {}; + explicit Node(GlobalPosition3D position) noexcept : pos(position) { }; virtual ~Node() noexcept = default; NO_COPY(Node); NO_MOVE(Node); @@ -35,16 +35,18 @@ public: struct End { Node::Ptr node; float dir; + // NOLINTNEXTLINE(readability-redundant-member-init) don't require client to empty initialise this Nexts nexts {}; }; - Link(End, End, float); + Link(End, End, RelativeDistance length); virtual ~Link() = default; NO_COPY(Link); NO_MOVE(Link); [[nodiscard]] virtual Location positionAt(RelativeDistance dist, unsigned char start) const = 0; [[nodiscard]] virtual bool intersectRay(const Ray<GlobalPosition3D> &) const = 0; + [[nodiscard]] virtual std::vector<GlobalPosition3D> getBase(RelativeDistance width) const = 0; std::array<End, 2> ends; float length; @@ -57,8 +59,8 @@ protected: } }; -bool operator<(const GlobalPosition3D & a, const GlobalPosition3D & b); -bool operator<(const Node & a, const Node & b); +bool operator<(const GlobalPosition3D &, const GlobalPosition3D &); +bool operator<(const Node &, const Node &); class LinkStraight : public virtual Link { public: @@ -69,6 +71,7 @@ public: [[nodiscard]] Location positionAt(RelativeDistance dist, unsigned char start) const override; [[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override; + [[nodiscard]] std::vector<GlobalPosition3D> getBase(RelativeDistance width) const override; }; LinkStraight::~LinkStraight() = default; @@ -76,12 +79,13 @@ LinkStraight::~LinkStraight() = default; class LinkCurve : public virtual Link { public: inline ~LinkCurve() override = 0; - LinkCurve(GlobalPosition3D, RelativeDistance, Arc); + LinkCurve(GlobalPosition3D centreBase, RelativeDistance radius, Arc); NO_COPY(LinkCurve); NO_MOVE(LinkCurve); [[nodiscard]] Location positionAt(RelativeDistance dist, unsigned char start) const override; [[nodiscard]] bool intersectRay(const Ray<GlobalPosition3D> &) const override; + [[nodiscard]] std::vector<GlobalPosition3D> getBase(RelativeDistance width) const override; GlobalPosition3D centreBase; RelativeDistance radius; diff --git a/game/network/network.cpp b/game/network/network.cpp index 65b2a62..c8482de 100644 --- a/game/network/network.cpp +++ b/game/network/network.cpp @@ -8,7 +8,13 @@ #include <stdexcept> #include <utility> -Network::Network(const std::string & tn) : texture {std::make_shared<Texture>(tn)} { } +Network::Network(const std::string & textureName) : + texture {std::make_shared<Texture>(textureName, + TextureOptions { + .minFilter = GL_NEAREST_MIPMAP_LINEAR, + })} +{ +} Node::Ptr Network::nodeAt(GlobalPosition3D pos) @@ -19,19 +25,18 @@ Network::nodeAt(GlobalPosition3D pos) Network::NodeInsertion Network::newNodeAt(GlobalPosition3D pos) { - if (auto [n, i] = candidateNodeAt(pos); i == NodeIs::NotInNetwork) { - return {*nodes.insert(std::move(n)).first, i}; - } - else { - return {std::move(n), NodeIs::InNetwork}; + auto [node, inNetwork] = candidateNodeAt(pos); + if (inNetwork == NodeIs::NotInNetwork) { + return {*nodes.insert(std::move(node)).first, inNetwork}; } + return {std::move(node), NodeIs::InNetwork}; } Node::Ptr Network::findNodeAt(GlobalPosition3D pos) const { - if (const auto n = nodes.find(pos); n != nodes.end()) { - return *n; + if (const auto node = nodes.find(pos); node != nodes.end()) { + return *node; } return {}; } @@ -39,8 +44,8 @@ Network::findNodeAt(GlobalPosition3D pos) const Network::NodeInsertion Network::candidateNodeAt(GlobalPosition3D pos) const { - if (const auto n = nodes.find(pos); n != nodes.end()) { - return {*n, NodeIs::InNetwork}; + if (const auto node = nodes.find(pos); node != nodes.end()) { + return {*node, NodeIs::InNetwork}; } return {std::make_shared<Node>(pos), NodeIs::NotInNetwork}; } @@ -48,12 +53,11 @@ Network::candidateNodeAt(GlobalPosition3D pos) const Node::Ptr Network::intersectRayNodes(const Ray<GlobalPosition3D> & ray) const { + static constexpr auto MIN_DISTANCE = 2000; // Click within 2m of a node if (const auto node = std::find_if(nodes.begin(), nodes.end(), [&ray](const Node::Ptr & node) { - GlobalPosition3D ipos; - Normal3D inorm; - return ray.intersectSphere(node->pos, 2000, ipos, inorm); + return ray.intersectSphere(node->pos, MIN_DISTANCE); }); node != nodes.end()) { return *node; @@ -62,14 +66,14 @@ Network::intersectRayNodes(const Ray<GlobalPosition3D> & ray) const } void -Network::joinLinks(const Link::Ptr & l, const Link::Ptr & ol) +Network::joinLinks(const Link::Ptr & link, const Link::Ptr & oldLink) { - if (l != ol) { - for (const auto oe : {0U, 1U}) { - for (const auto te : {0U, 1U}) { - if (l->ends[te].node == ol->ends[oe].node) { - l->ends[te].nexts.emplace_back(ol, oe); - ol->ends[oe].nexts.emplace_back(l, te); + if (link != oldLink) { + for (const auto oldLinkEnd : {0U, 1U}) { + for (const auto linkEnd : {0U, 1U}) { + if (link->ends[linkEnd].node == oldLink->ends[oldLinkEnd].node) { + link->ends[linkEnd].nexts.emplace_back(oldLink, oldLinkEnd); + oldLink->ends[oldLinkEnd].nexts.emplace_back(link, linkEnd); } } } @@ -87,7 +91,7 @@ Network::routeFromTo(const Link::End & start, GlobalPosition3D dest) const } Link::Nexts -Network::routeFromTo(const Link::End & end, const Node::Ptr & dest) const +Network::routeFromTo(const Link::End & end, const Node::Ptr & dest) { return RouteWalker().findRouteTo(end, dest); } @@ -95,12 +99,12 @@ Network::routeFromTo(const Link::End & end, const Node::Ptr & dest) const GenCurveDef Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir) { - const auto diff {end - start}; - const auto vy {vector_yaw(diff)}; + const auto diff = difference(end, start); + const auto yaw = vector_yaw(diff); const auto dir = pi + startDir; - const auto flatStart {start.xy()}, flatEnd {end.xy()}; - const auto n2ed {(vy * 2) - dir - pi}; - const auto centre {find_arc_centre(flatStart, dir, flatEnd, n2ed)}; + const auto flatStart = start.xy(), flatEnd = end.xy(); + const auto n2ed = (yaw * 2) - dir - pi; + const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed); if (centre.second) { // right hand arc return {end, start, centre.first}; @@ -115,24 +119,23 @@ Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & en endDir += pi; const auto flatStart {start.xy()}, flatEnd {end.xy()}; auto midheight = [&](auto mid) { - const auto sm = glm::length(RelativePosition2D(flatStart - mid)), - em = glm::length(RelativePosition2D(flatEnd - mid)); - return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (sm / (sm + em))); + const auto startToMid = ::distance<2>(flatStart, mid); + const auto endToMid = ::distance<2>(flatEnd, mid); + return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid))); }; - if (const auto radii = find_arcs_radius(flatStart, startDir, flatEnd, endDir); radii.first < radii.second) { - const auto radius {radii.first}; - const auto c1 = flatStart + (sincosf(startDir + half_pi) * radius); - const auto c2 = flatEnd + (sincosf(endDir + half_pi) * radius); - const auto mid = (c1 + c2) / 2; - const auto midh = mid || midheight(mid); - return {{start, midh, c1}, {end, midh, c2}}; - } - else { - const auto radius {radii.second}; - const auto c1 = flatStart + (sincosf(startDir - half_pi) * radius); - const auto c2 = flatEnd + (sincosf(endDir - half_pi) * radius); - const auto mid = (c1 + c2) / 2; + const auto radii = find_arcs_radius(flatStart, startDir, flatEnd, endDir); + if (radii.first < radii.second) { + const auto radius = radii.first; + const auto centre1 = flatStart + (sincos(startDir + half_pi) * radius); + const auto centre2 = flatEnd + (sincos(endDir + half_pi) * radius); + const auto mid = (centre1 + centre2) / 2; const auto midh = mid || midheight(mid); - return {{midh, start, c1}, {midh, end, c2}}; + return {{start, midh, centre1}, {end, midh, centre2}}; } + const auto radius = radii.second; + const auto centre1 = flatStart + (sincos(startDir - half_pi) * radius); + const auto centre2 = flatEnd + (sincos(endDir - half_pi) * radius); + const auto mid = (centre1 + centre2) / 2; + const auto midh = mid || midheight(mid); + return {{midh, start, centre1}, {midh, end, centre2}}; } diff --git a/game/network/network.h b/game/network/network.h index ca17581..4f5d2b0 100644 --- a/game/network/network.h +++ b/game/network/network.h @@ -14,9 +14,11 @@ #include <utility> 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 { @@ -28,7 +30,7 @@ public: [[nodiscard]] Node::Ptr findNodeAt(GlobalPosition3D) const; [[nodiscard]] Node::Ptr nodeAt(GlobalPosition3D); - enum class NodeIs { InNetwork, NotInNetwork }; + enum class NodeIs : uint8_t { InNetwork, NotInNetwork }; using NodeInsertion = std::pair<Node::Ptr, NodeIs>; [[nodiscard]] NodeInsertion newNodeAt(GlobalPosition3D); [[nodiscard]] NodeInsertion candidateNodeAt(GlobalPosition3D) const; @@ -36,19 +38,22 @@ 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); @@ -72,42 +77,41 @@ class NetworkOf : public Network, public Renderable, public NetworkLinkHolder<Li protected: using Network::Network; - Collection<T> links; + SharedCollection<T> links; void joinLinks(const Link::Ptr &) const; -protected: [[nodiscard]] Link::Ptr intersectRayLinks(const Ray<GlobalPosition3D> &) const override; public: template<typename L, typename... Params> std::shared_ptr<L> - candidateLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params) + candidateLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params) requires std::is_base_of_v<T, L> { - const auto node1 = candidateNodeAt(a).first, node2 = candidateNodeAt(b).first; + const auto node1 = candidateNodeAt(positionA).first, node2 = candidateNodeAt(positionB).first; return std::make_shared<L>(*this, node1, node2, std::forward<Params>(params)...); } template<typename L, typename... Params> std::shared_ptr<L> - addLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params) + addLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params) requires std::is_base_of_v<T, L> { - const auto node1 = nodeAt(a), node2 = nodeAt(b); - auto l {links.template create<L>(*this, node1, node2, std::forward<Params>(params)...)}; - joinLinks(l); - return l; + const auto node1 = nodeAt(positionA), node2 = nodeAt(positionB); + auto newLink = links.template create<L>(*this, node1, node2, std::forward<Params>(params)...); + joinLinks(newLink); + return std::move(newLink); } - Link::CCollection candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2) override; + Link::CCollection candidateStraight(GlobalPosition3D, GlobalPosition3D) override; Link::CCollection candidateJoins(GlobalPosition3D, GlobalPosition3D) override; Link::CCollection candidateExtend(GlobalPosition3D, GlobalPosition3D) override; - Link::CCollection addStraight(GlobalPosition3D n1, GlobalPosition3D n2) override; - Link::CCollection addJoins(GlobalPosition3D, GlobalPosition3D) override; - Link::CCollection addExtend(GlobalPosition3D, GlobalPosition3D) override; + Link::CCollection addStraight(const GeoData *, GlobalPosition3D, GlobalPosition3D) override; + Link::CCollection addJoins(const GeoData *, GlobalPosition3D, GlobalPosition3D) override; + Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) override; [[nodiscard]] float findNodeDirection(Node::AnyCPtr) const override; protected: - Link::CCollection addJoins(); + Link::CCollection addCurve(const GeoData *, const GenCurveDef &); }; diff --git a/game/network/network.impl.h b/game/network/network.impl.h index ff29088..e339922 100644 --- a/game/network/network.impl.h +++ b/game/network/network.impl.h @@ -1,13 +1,15 @@ +#include "collections.h" #include "network.h" +#include <game/geoData.h> #include <gfx/gl/sceneShader.h> #include <gfx/models/texture.h> template<typename T, typename... Links> void -NetworkOf<T, Links...>::joinLinks(const Link::Ptr & l) const +NetworkOf<T, Links...>::joinLinks(const Link::Ptr & link) const { - for (const auto & ol : links.objects) { - Network::joinLinks(l, ol); + for (const auto & oldLink : links) { + Network::joinLinks(link, oldLink); } } @@ -16,11 +18,11 @@ Link::Ptr NetworkOf<T, Links...>::intersectRayLinks(const Ray<GlobalPosition3D> & ray) const { // Click link - if (const auto link = std::find_if(links.objects.begin(), links.objects.end(), + if (const auto link = std::find_if(links.begin(), links.end(), [&ray](const std::shared_ptr<T> & link) { return link->intersectRay(ray); }); - link != links.objects.end()) { + link != links.end()) { return *link; } return {}; @@ -30,11 +32,11 @@ template<typename T, typename... Links> float NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const { - for (const auto & l : links.objects) { - for (const auto & e : l->ends) { + for (const auto & link : links) { + for (const auto & end : link->ends) { // cppcheck-suppress useStlAlgorithm - if (e.node.get() == n.get()) { - return e.dir; + if (end.node.get() == n.get()) { + return end.dir; } } } @@ -43,16 +45,17 @@ NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const template<typename T, typename... Links> Link::CCollection -NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2) +NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D positionA, GlobalPosition3D positionB) { - return {candidateLink<typename T::StraightLink>(n1, n2)}; + return {candidateLink<typename T::StraightLink>(positionA, positionB)}; } template<typename T, typename... Links> Link::CCollection NetworkOf<T, Links...>::candidateJoins(GlobalPosition3D start, GlobalPosition3D end) { - if (glm::length(RelativePosition3D(start - end)) < 2000.F) { + static constexpr auto MIN_DISTANCE = 2000.F; + if (::distance(start, end) < MIN_DISTANCE) { return {}; } const auto defs = genCurveDef( @@ -72,28 +75,71 @@ NetworkOf<T, Links...>::candidateExtend(GlobalPosition3D start, GlobalPosition3D template<typename T, typename... Links> Link::CCollection -NetworkOf<T, Links...>::addStraight(GlobalPosition3D n1, GlobalPosition3D n2) +NetworkOf<T, Links...>::addStraight(const GeoData * geoData, GlobalPosition3D positionA, GlobalPosition3D positionB) { - return {addLink<typename T::StraightLink>(n1, n2)}; + Link::CCollection out; + geoData->walk(positionA.xy(), positionB, [geoData, &out, this, &positionA](const GeoData::WalkStep & step) { + if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) { + const auto surfaceEdgePosition = geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current)); + out.emplace_back(addLink<typename T::StraightLink>(positionA, surfaceEdgePosition)); + positionA = surfaceEdgePosition; + } + }); + out.emplace_back(addLink<typename T::StraightLink>(positionA, positionB)); + return out; +} + +template<typename T, typename... Links> +Link::CCollection +NetworkOf<T, Links...>::addCurve(const GeoData * geoData, const GenCurveDef & curve) +{ + static constexpr auto MIN_DISTANCE = 2000.F; + auto [cstart, cend, centre] = curve; + Link::CCollection out; + std::set<GeoData::WalkStepCurve, SortedBy<&GeoData::WalkStepCurve::angle>> breaks; + const auto radiusMid = ::distance(cstart.xy(), centre); + for (const auto radiusOffset : {-getBaseWidth() / 2.F, 0.F, getBaseWidth() / 2.F}) { + const auto radius = radiusOffset + radiusMid; + const auto start = centre + (difference(cstart.xy(), centre) * radius) / radiusMid; + const auto end = centre + (difference(cend.xy(), centre) * radius) / radiusMid; + geoData->walk(start, end, centre, [geoData, &breaks](const GeoData::WalkStepCurve & step) { + if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) { + breaks.insert(step); + } + }); + } + std::vector<GlobalPosition3D> points; + points.reserve(breaks.size() + 2); + points.push_back(cstart); + std::ranges::transform( + breaks, std::back_inserter(points), [geoData, centre, radiusMid](const GeoData::WalkStepCurve & step) { + return (centre + (sincos(step.angle) * radiusMid)) + || geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current)).z; + }); + points.push_back(cend); + mergeClose(points, ::distance<3, GlobalDistance>, ::midpoint<3, GlobalDistance>, MIN_DISTANCE); + std::ranges::transform(points | std::views::pairwise, std::back_inserter(out), [this, centre](const auto pair) { + const auto [a, b] = pair; + return this->addLink<typename T::CurveLink>(a, b, centre); + }); + return out; } template<typename T, typename... Links> Link::CCollection -NetworkOf<T, Links...>::addJoins(GlobalPosition3D start, GlobalPosition3D end) +NetworkOf<T, Links...>::addJoins(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end) { - if (glm::length(RelativePosition3D(start - end)) < 2000.F) { + static constexpr auto MIN_DISTANCE = 2000.F; + if (::distance(start, end) < MIN_DISTANCE) { return {}; } const auto defs = genCurveDef(start, end, findNodeDirection(nodeAt(start)), findNodeDirection(nodeAt(end))); - const auto & [c1s, c1e, c1c] = defs.first; - const auto & [c2s, c2e, c2c] = defs.second; - return {addLink<typename T::CurveLink>(c1s, c1e, c1c), addLink<typename T::CurveLink>(c2s, c2e, c2c)}; + return addCurve(geoData, defs.first) + addCurve(geoData, defs.second); } template<typename T, typename... Links> Link::CCollection -NetworkOf<T, Links...>::addExtend(GlobalPosition3D start, GlobalPosition3D end) +NetworkOf<T, Links...>::addExtend(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end) { - const auto [cstart, cend, centre] = genCurveDef(start, end, findNodeDirection(nodeAt(start))); - return {addLink<typename T::CurveLink>(cstart, cend, centre)}; + return addCurve(geoData, genCurveDef(start, end, findNodeDirection(nodeAt(start)))); } diff --git a/game/network/rail.cpp b/game/network/rail.cpp index fd07ace..c0e597d 100644 --- a/game/network/rail.cpp +++ b/game/network/rail.cpp @@ -1,4 +1,5 @@ #include "rail.h" +#include "game/gamestate.h" #include "network.h" #include <game/network/network.impl.h> // IWYU pragma: keep #include <gfx/gl/sceneShader.h> @@ -8,7 +9,7 @@ template class NetworkOf<RailLink, RailLinkStraight, RailLinkCurve>; constexpr auto RAIL_CROSSSECTION_VERTICES {5U}; -constexpr Size3D RAIL_HEIGHT {0, 0, 250.F}; +constexpr Size3D RAIL_HEIGHT {0, 0, 50.F}; RailLinks::RailLinks() : NetworkOf<RailLink, RailLinkStraight, RailLinkCurve> {"rails.jpg"} { } @@ -32,40 +33,39 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end) } // Find start link/end - opposite entry dir to existing link; so pi +... const Angle dir = pi + findNodeDirection(node1ins.first); - if (dir == vector_yaw(end - start)) { + if (dir == vector_yaw(difference(end, start))) { return addLink<RailLinkStraight>(start, end); } const auto flatStart {start.xy()}, flatEnd {end.xy()}; if (node2ins.second == NodeIs::InNetwork) { auto midheight = [&](auto mid) { - const auto sm = glm::length(RelativePosition2D(flatStart - mid)), - em = glm::length(RelativePosition2D(flatEnd - mid)); - return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (sm / (sm + em))); + const auto startToMid = ::distance<2>(flatStart, mid); + const auto endToMid = ::distance<2>(flatEnd, mid); + return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid))); }; const float dir2 = pi + findNodeDirection(node2ins.first); - if (const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); radii.first < radii.second) { - const auto radius {radii.first}; - const auto c1 = flatStart + (sincosf(dir + half_pi) * radius); - const auto c2 = flatEnd + (sincosf(dir2 + half_pi) * radius); - const auto mid = (c1 + c2) / 2; + const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); + if (radii.first < radii.second) { + const auto radius = radii.first; + const auto centre1 = flatStart + (sincos(dir + half_pi) * radius); + const auto centre2 = flatEnd + (sincos(dir2 + half_pi) * radius); + const auto mid = (centre1 + centre2) / 2; const auto midh = mid || midheight(mid); - addLink<RailLinkCurve>(start, midh, c1); - return addLink<RailLinkCurve>(end, midh, c2); - } - else { - const auto radius {radii.second}; - const auto c1 = flatStart + (sincosf(dir - half_pi) * radius); - const auto c2 = flatEnd + (sincosf(dir2 - half_pi) * radius); - const auto mid = (c1 + c2) / 2; - const auto midh = mid || midheight(mid); - addLink<RailLinkCurve>(midh, start, c1); - return addLink<RailLinkCurve>(midh, end, c2); + addLink<RailLinkCurve>(start, midh, centre1); + return addLink<RailLinkCurve>(end, midh, centre2); } + const auto radius = radii.second; + const auto centre1 = flatStart + (sincos(dir - half_pi) * radius); + const auto centre2 = flatEnd + (sincos(dir2 - half_pi) * radius); + const auto mid = (centre1 + centre2) / 2; + const auto midh = mid || midheight(mid); + addLink<RailLinkCurve>(midh, start, centre1); + return addLink<RailLinkCurve>(midh, end, centre2); } - const auto diff {end - start}; - const auto vy {vector_yaw(diff)}; - const auto n2ed {(vy * 2) - dir - pi}; - const auto centre {find_arc_centre(flatStart, dir, flatEnd, n2ed)}; + const auto diff = difference(end, start); + const auto yaw = vector_yaw(diff); + const auto n2ed = (yaw * 2) - dir - pi; + const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed); if (centre.second) { // right hand arc std::swap(start, end); @@ -73,53 +73,61 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end) return addLink<RailLinkCurve>(start, end, centre.first); } -constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> railCrossSection {{ - {-1900.F, 0.F, 0.F}, - {-608.F, 0.F, RAIL_HEIGHT.z}, - {0, 0.F, RAIL_HEIGHT.z * .7F}, - {608.F, 0.F, RAIL_HEIGHT.z}, - {1900.F, 0.F, 0.F}, -}}; -constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> railTexturePos { - 0.F, - .34F, - .5F, - .66F, - 1.F, -}; -constexpr auto sleepers {5.F}; // There are 5 repetitions of sleepers in the texture - -inline auto -round_sleepers(const float v) -{ - return round_frac(v, sleepers); +namespace { + constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> RAIL_CROSS_SECTION {{ + {-1330.F, 0.F, 0}, + {-608.F, 0.F, RAIL_HEIGHT.z}, + {0, 0.F, RAIL_HEIGHT.z / 2}, + {608.F, 0.F, RAIL_HEIGHT.z}, + {1330.F, 0.F, 0}, + }}; + constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> RAIL_TEXTURE_POS { + 0.15F, + .34F, + .5F, + .66F, + 0.85F, + }; + template<std::floating_point T> constexpr T SLEEPERS_PER_TEXTURE {5}; + template<std::floating_point T> constexpr T TEXTURE_LENGTH {2'000}; + template<std::floating_point T> constexpr T SLEEPER_LENGTH {T {1} / SLEEPERS_PER_TEXTURE<T>}; + + template<std::floating_point T> + constexpr auto + roundSleepers(const T length) + { + return round_frac(length / TEXTURE_LENGTH<T>, SLEEPER_LENGTH<T>); + } } -RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & a, - const Node::Ptr & b) : RailLinkStraight(instances, a, b, b->pos - a->pos) +RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & nodeA, + const Node::Ptr & nodeB) : RailLinkStraight(instances, nodeA, nodeB, nodeB->pos - nodeA->pos) { } -RailLinkStraight::RailLinkStraight( - NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr a, Node::Ptr b, const RelativePosition3D & diff) : - Link({std::move(a), vector_yaw(diff)}, {std::move(b), vector_yaw(-diff)}, glm::length(diff)), +RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr nodeA, Node::Ptr nodeB, + const RelativePosition3D & diff) : + Link({.node = std::move(nodeA), .dir = vector_yaw(diff)}, {.node = std::move(nodeB), .dir = vector_yaw(-diff)}, + glm::length(diff)), instance {instances.vertices.acquire( - ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), round_sleepers(length / 2000.F))} + ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), roundSleepers(length))} { } -RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b, - GlobalPosition2D c) : RailLinkCurve(instances, a, b, c || a->pos.z, {c, a->pos, b->pos}) +RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA, + const Node::Ptr & nodeB, GlobalPosition2D centre) : + RailLinkCurve(instances, nodeA, nodeB, centre || nodeA->pos.z, ::distance<2>(nodeA->pos.xy(), centre), + {centre, nodeA->pos, nodeB->pos}) { } -RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b, - GlobalPosition3D c, const Arc arc) : - Link({a, normalize(arc.first + half_pi)}, {b, normalize(arc.second - half_pi)}, - glm::length(RelativePosition3D(a->pos - c)) * arc.length()), - LinkCurve {c, glm::length(RelativePosition3D(ends[0].node->pos - c)), arc}, - instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, c, round_sleepers(length / 2000.F), - half_pi - arc.first, half_pi - arc.second, radius)} +RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA, + const Node::Ptr & nodeB, GlobalPosition3D centre, RelativeDistance radius, const Arc arc) : + Link({.node = nodeA, .dir = normalize(arc.first + half_pi)}, + {.node = nodeB, .dir = normalize(arc.second - half_pi)}, + glm::length(RelativePosition2D {radius * arc.length(), difference(nodeA->pos, nodeB->pos).z})), + LinkCurve {centre, radius, arc}, instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, centre, + roundSleepers(length), half_pi - arc.first, half_pi - arc.second, radius)} { } @@ -148,23 +156,39 @@ template<> NetworkLinkHolder<RailLinkCurve>::NetworkLinkHolder() namespace { template<typename LinkType> void - renderType(const NetworkLinkHolder<LinkType> & n, auto & s) + renderType(const NetworkLinkHolder<LinkType> & networkLinks, auto & shader) { - if (auto count = n.vertices.size()) { - s.use(railCrossSection, railTexturePos); - glBindVertexArray(n.vao); + if (auto count = networkLinks.vertices.size()) { + shader.use(RAIL_CROSS_SECTION, RAIL_TEXTURE_POS); + glBindVertexArray(networkLinks.vao); glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count)); } }; } void -RailLinks::render(const SceneShader & shader) const +RailLinks::render(const SceneShader & shader, const Frustum &) const { - if (!links.objects.empty()) { + if (!links.empty()) { texture->bind(); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1, 0); renderType<RailLinkStraight>(*this, shader.networkStraight); renderType<RailLinkCurve>(*this, shader.networkCurve); + glDisable(GL_POLYGON_OFFSET_FILL); glBindVertexArray(0); } } + +const Surface * +RailLinks::getBaseSurface() const +{ + return gameState->assets.at("terrain.surface.gravel").dynamicCast<const Surface>().get(); +} + +RelativeDistance +RailLinks::getBaseWidth() const +{ + static constexpr auto BASE_WIDTH = 5'700; + return BASE_WIDTH; +} diff --git a/game/network/rail.h b/game/network/rail.h index c8effef..4aef9e3 100644 --- a/game/network/rail.h +++ b/game/network/rail.h @@ -62,8 +62,8 @@ public: }; private: - RailLinkCurve( - NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D, const Arc); + RailLinkCurve(NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D centreBase, + RelativeDistance radius, Arc); InstanceVertices<Vertex>::InstanceProxy instance; }; @@ -75,7 +75,10 @@ public: RailLinks(); std::shared_ptr<RailLink> addLinksBetween(GlobalPosition3D start, GlobalPosition3D end); - void render(const SceneShader &) const override; + void render(const SceneShader &, const Frustum &) const override; + + [[nodiscard]] const Surface * getBaseSurface() const override; + [[nodiscard]] RelativeDistance getBaseWidth() const override; private: void tick(TickDuration elapsed) override; diff --git a/game/orders.h b/game/orders.h index ca5cfdb..840aa3c 100644 --- a/game/orders.h +++ b/game/orders.h @@ -5,7 +5,7 @@ class Objective; -class Orders : public Collection<Objective> { +class Orders : public SharedCollection<Objective> { public: [[nodiscard]] Objective * current() const; Objective * next(); diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp index 73d285f..140c4e5 100644 --- a/game/scenary/foliage.cpp +++ b/game/scenary/foliage.cpp @@ -2,8 +2,16 @@ #include "gfx/gl/sceneShader.h" #include "gfx/gl/shadowMapper.h" #include "gfx/gl/vertexArrayObject.h" -#include "gfx/models/texture.h" -#include "location.h" +#include <location.h> + +static_assert(std::is_constructible_v<Foliage>); + +std::any +Foliage::createAt(const Location & position) const +{ + return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>( + instances.acquire(position.getRotationTransform(), position.rot.y, position.pos)); +} bool Foliage::persist(Persistence::PersistenceStore & store) @@ -16,11 +24,22 @@ Foliage::postLoad() { texture = getTexture(); bodyMesh->configureVAO(instanceVAO) - .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1); + .addAttribs<LocationVertex, &LocationVertex::rotation, &LocationVertex::position>( + instances.bufferName(), 1); + VertexArrayObject {instancePointVAO}.addAttribs<LocationVertex, &LocationVertex::position, &LocationVertex::yaw>( + instances.bufferName()); +} + +void +Foliage::updateStencil(const ShadowStenciller & ss) const +{ + if (instances.size() > 0) { + ss.renderStencil(shadowStencil, *bodyMesh, texture); + } } void -Foliage::render(const SceneShader & shader) const +Foliage::render(const SceneShader & shader, const Frustum &) const { if (const auto count = instances.size()) { shader.basicInst.use(); @@ -32,13 +51,15 @@ Foliage::render(const SceneShader & shader) const } void -Foliage::shadows(const ShadowMapper & mapper) const +Foliage::shadows(const ShadowMapper & mapper, const Frustum &) const { if (const auto count = instances.size()) { - mapper.dynamicPointInstWithTextures.use(); - if (texture) { - texture->bind(GL_TEXTURE3); - } - bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count)); + const auto dimensions = bodyMesh->getDimensions(); + mapper.stencilShadowProgram.use(dimensions.centre, dimensions.size); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, shadowStencil); + glBindVertexArray(instancePointVAO); + glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count)); + glBindVertexArray(0); } } diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h index 0a4261c..d15a8b0 100644 --- a/game/scenary/foliage.h +++ b/game/scenary/foliage.h @@ -2,6 +2,7 @@ #include "assetFactory/asset.h" #include "gfx/gl/instanceVertices.h" +#include "gfx/gl/shadowStenciller.h" #include "gfx/models/texture.h" #include "gfx/renderable.h" @@ -13,12 +14,22 @@ class Foliage : public Asset, public Renderable, public StdTypeDefs<Foliage> { Mesh::Ptr bodyMesh; Texture::Ptr texture; glVertexArray instanceVAO; + glVertexArray instancePointVAO; public: - using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>; + [[nodiscard]] std::any createAt(const Location &) const override; + + struct LocationVertex { + glm::mat3 rotation; + float yaw; + GlobalPosition3D position; + }; + mutable InstanceVertices<LocationVertex> instances; - void render(const SceneShader &) const override; - void shadows(const ShadowMapper &) const override; + void render(const SceneShader &, const Frustum &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; + void updateStencil(const ShadowStenciller &) const override; + glTexture shadowStencil = ShadowStenciller::createStencilTexture(256, 256); protected: friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>; diff --git a/game/scenary/illuminator.cpp b/game/scenary/illuminator.cpp index e3810ec..d8e4c4e 100644 --- a/game/scenary/illuminator.cpp +++ b/game/scenary/illuminator.cpp @@ -2,6 +2,16 @@ #include "gfx/gl/sceneShader.h" #include "gfx/gl/vertexArrayObject.h" #include "gfx/models/texture.h" // IWYU pragma: keep +#include <location.h> + +static_assert(std::is_constructible_v<Illuminator>); + +std::any +Illuminator::createAt(const Location & position) const +{ + return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>( + instances.acquire(position.getRotationTransform(), position.pos)); +} bool Illuminator::SpotLight::persist(Persistence::PersistenceStore & store) @@ -59,7 +69,7 @@ Illuminator::postLoad() } void -Illuminator::render(const SceneShader & shader) const +Illuminator::render(const SceneShader & shader, const Frustum &) const { if (const auto count = instances.size()) { shader.basicInst.use(); diff --git a/game/scenary/illuminator.h b/game/scenary/illuminator.h index 44bd583..200ba40 100644 --- a/game/scenary/illuminator.h +++ b/game/scenary/illuminator.h @@ -15,6 +15,8 @@ class Illuminator : public Asset, public Renderable, public StdTypeDefs<Illumina std::optional<glVertexArray> instancesSpotLightVAO, instancesPointLightVAO; public: + [[nodiscard]] std::any createAt(const Location &) const override; + struct LightCommonVertex { RelativePosition3D position; RGB colour; @@ -45,7 +47,7 @@ public: mutable InstanceVertices<LocationVertex> instances; mutable InstanceVertices<SpotLightVertex> instancesSpotLight; mutable InstanceVertices<PointLightVertex> instancesPointLight; - void render(const SceneShader &) const override; + void render(const SceneShader &, const Frustum &) const override; void lights(const SceneShader &) const override; protected: diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp index b39c28b..2006225 100644 --- a/game/scenary/plant.cpp +++ b/game/scenary/plant.cpp @@ -2,6 +2,7 @@ #include "location.h" Plant::Plant(std::shared_ptr<const Foliage> type, const Location & position) : - type {std::move(type)}, location {this->type->instances.acquire(position.getRotationTransform(), position.pos)} + type {std::move(type)}, + location {this->type->instances.acquire(position.getRotationTransform(), position.rot.y, position.pos)} { } diff --git a/game/terrain.cpp b/game/terrain.cpp index 3b16e79..f10aac6 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -1,65 +1,118 @@ #include "terrain.h" -#include "game/geoData.h" -#include "gfx/models/texture.h" +#include "gfx/frustum.h" #include <algorithm> -#include <cstddef> #include <gfx/gl/sceneShader.h> #include <gfx/gl/shadowMapper.h> #include <gfx/image.h> #include <gfx/models/mesh.h> #include <gfx/models/vertex.h> +#include <glMappedBufferWriter.h> #include <glm/glm.hpp> -#include <iterator> #include <location.h> #include <maths.h> #include <utility> #include <vector> -static constexpr RGB openSurface {-1}; - -Terrain::Terrain(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, grass {std::make_shared<Texture>("grass.png")} -{ - generateMeshes(); -} +static constexpr RGB OPEN_SURFACE {-1}; +static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide template<> VertexArrayObject & VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, const GLuint divisor) { - return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal, &Terrain::Vertex::colourBias>( - arrayBuffer, divisor); + return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal>(arrayBuffer, divisor); } -void -Terrain::generateMeshes() +bool +Terrain::SurfaceKey::operator<(const SurfaceKey & other) const { - std::vector<unsigned int> indices; - indices.reserve(geoData->n_faces() * 3); - std::vector<Vertex> vertices; - vertices.reserve(geoData->n_vertices()); - std::map<std::pair<GeoData::VertexHandle, const Surface *>, size_t> vertexIndex; - std::for_each(geoData->vertices_sbegin(), geoData->vertices_end(), - [this, &vertexIndex, &vertices](const GeoData::VertexHandle v) { - std::for_each(geoData->vf_begin(v), geoData->vf_end(v), - [&vertexIndex, v, this, &vertices](const GeoData::FaceHandle f) { - const auto surface = geoData->get_surface(f); - if (const auto vertexIndexRef = vertexIndex.emplace(std::make_pair(v, surface), 0); - vertexIndexRef.second) { - vertexIndexRef.first->second = vertices.size(); + return std::tie(surface, basePosition.x, basePosition.y) + < std::tie(other.surface, other.basePosition.x, other.basePosition.y); +} - vertices.emplace_back(geoData->point(v), geoData->normal(v), - surface ? surface->colorBias : openSurface); - } - }); - }); - std::for_each( - geoData->faces_sbegin(), geoData->faces_end(), [this, &vertexIndex, &indices](const GeoData::FaceHandle f) { - std::transform(geoData->fv_begin(f), geoData->fv_end(f), std::back_inserter(indices), - [&vertexIndex, f, this](const GeoData::VertexHandle v) { - return vertexIndex[std::make_pair(v, geoData->get_surface(f))]; - }); +inline void +Terrain::copyVerticesToBuffer() const +{ + std::ranges::transform(all_vertices(), glMappedBufferWriter<Vertex> {GL_ARRAY_BUFFER, verticesBuffer, n_vertices()}, + [this](const auto & vertex) { + return Vertex {point(vertex), normal(vertex)}; }); - meshes.create<MeshT<Vertex>>(vertices, indices); +} + +inline GlobalPosition2D +Terrain::getTile(const FaceHandle & face) const +{ + return point(*cfv_begin(face)).xy() / TILE_SIZE; +}; + +Terrain::SurfaceIndices +Terrain::mapSurfaceFacesToIndices() const +{ + SurfaceIndices surfaceIndices; + const auto indexBySurfaceAndTile = std::views::transform([this](const auto & faceItr) { + return std::pair<SurfaceKey, FaceHandle> {{getSurface(*faceItr), getTile(*faceItr)}, *faceItr}; + }); + const auto chunkBySurfaceAndTile = std::views::chunk_by([](const auto & face1, const auto & face2) { + return face1.first.surface == face2.first.surface && face1.first.basePosition == face2.first.basePosition; + }); + for (const auto & faceRange : faces() | indexBySurfaceAndTile | chunkBySurfaceAndTile) { + const SurfaceKey & surfaceKey = faceRange.front().first; + auto indexItr = surfaceIndices.find(surfaceKey); + if (indexItr == surfaceIndices.end()) { + indexItr = surfaceIndices.emplace(surfaceKey, std::vector<GLuint> {}).first; + if (auto existing = meshes.find(surfaceKey); existing != meshes.end()) { + indexItr->second.reserve(static_cast<size_t>(existing->second.count)); + } + } + for (auto push = std::back_inserter(indexItr->second); const auto & [_, face] : faceRange) { + std::ranges::transform(fv_range(face), push, &OpenMesh::VertexHandle::idx); + } + } + return surfaceIndices; +} + +void +Terrain::copyIndicesToBuffers(const SurfaceIndices & surfaceIndices) +{ + for (const auto & [surfaceKey, indices] : surfaceIndices) { + auto meshItr = meshes.find(surfaceKey); + if (meshItr == meshes.end()) { + meshItr = meshes.emplace(surfaceKey, SurfaceArrayBuffer {}).first; + VertexArrayObject {meshItr->second.vertexArray} + .addAttribsFor<Vertex>(verticesBuffer) + .addIndices(meshItr->second.indicesBuffer, indices) + .data(verticesBuffer, GL_ARRAY_BUFFER); + } + else { + VertexArrayObject {meshItr->second.vertexArray} + .addIndices(meshItr->second.indicesBuffer, indices) + .data(verticesBuffer, GL_ARRAY_BUFFER); + } + meshItr->second.count = static_cast<GLsizei>(indices.size()); + meshItr->second.aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints( + indices | std::views::transform([this](const auto vertex) { + return this->point(VertexHandle {static_cast<int>(vertex)}); + })); + } +} + +void +Terrain::pruneOrphanMeshes(const SurfaceIndices & surfaceIndices) +{ + if (meshes.size() > surfaceIndices.size()) { + std::erase_if(meshes, [&surfaceIndices](const auto & mesh) { + return !surfaceIndices.contains(mesh.first); + }); + } +} + +void +Terrain::generateMeshes() +{ + copyVerticesToBuffer(); + const auto surfaceIndices = mapSurfaceFacesToIndices(); + copyIndicesToBuffers(surfaceIndices); + pruneOrphanMeshes(surfaceIndices); } void @@ -68,16 +121,41 @@ Terrain::tick(TickDuration) } void -Terrain::render(const SceneShader & shader) const +Terrain::afterChange() +{ + generateMeshes(); +} + +void +Terrain::render(const SceneShader & shader, const Frustum & frustum) const { - shader.landmass.use(); grass->bind(); - meshes.apply(&Mesh::Draw); + + const auto chunkBySurface = std::views::chunk_by([](const auto & itr1, const auto & itr2) { + return itr1.first.surface == itr2.first.surface; + }); + for (const auto & surfaceRange : meshes | chunkBySurface) { + const auto surface = surfaceRange.front().first.surface; + shader.landmass.use(surface ? surface->colorBias : OPEN_SURFACE); + for (const auto & sab : surfaceRange) { + if (frustum.contains(sab.second.aabb)) { + glBindVertexArray(sab.second.vertexArray); + glDrawElements(GL_TRIANGLES, sab.second.count, GL_UNSIGNED_INT, nullptr); + } + } + } + glBindVertexArray(0); } void -Terrain::shadows(const ShadowMapper & shadowMapper) const +Terrain::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const { shadowMapper.landmess.use(); - meshes.apply(&Mesh::Draw); + for (const auto & [surface, sab] : meshes) { + if (frustum.shadedBy(sab.aabb)) { + glBindVertexArray(sab.vertexArray); + glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr); + } + } + glBindVertexArray(0); } diff --git a/game/terrain.h b/game/terrain.h index 1c79d19..1a63296 100644 --- a/game/terrain.h +++ b/game/terrain.h @@ -1,36 +1,57 @@ #pragma once #include "chronology.h" -#include "collection.h" #include "config/types.h" #include "game/worldobject.h" -#include "gfx/models/mesh.h" +#include "geoData.h" #include "gfx/models/texture.h" #include "gfx/renderable.h" -#include <memory> class SceneShader; -class GeoData; -class Terrain : public WorldObject, public Renderable { +class Terrain : public GeoData, public WorldObject, public Renderable { public: - explicit Terrain(std::shared_ptr<GeoData>); + template<typename... P> explicit Terrain(P &&... params) : GeoData {std::forward<P>(params)...} + { + generateMeshes(); + } - void render(const SceneShader & shader) const override; - void shadows(const ShadowMapper &) const override; + void render(const SceneShader & shader, const Frustum &) const override; + void shadows(const ShadowMapper &, const Frustum &) const override; void tick(TickDuration) override; struct Vertex { GlobalPosition3D pos; Normal3D normal; - RGB colourBias; }; -private: void generateMeshes(); - std::shared_ptr<GeoData> geoData; - Collection<MeshT<Vertex>, false> meshes; - Texture::Ptr grass; +private: + void afterChange() override; + + struct SurfaceArrayBuffer { + glVertexArray vertexArray; + glBuffer indicesBuffer; + GLsizei count; + AxisAlignedBoundingBox<GlobalDistance> aabb; + }; + + struct SurfaceKey { + const Surface * surface; + GlobalPosition2D basePosition; + inline bool operator<(const SurfaceKey &) const; + }; + + using SurfaceIndices = std::map<SurfaceKey, std::vector<GLuint>>; + void copyVerticesToBuffer() const; + [[nodiscard]] SurfaceIndices mapSurfaceFacesToIndices() const; + void copyIndicesToBuffers(const SurfaceIndices &); + void pruneOrphanMeshes(const SurfaceIndices &); + [[nodiscard]] inline GlobalPosition2D getTile(const FaceHandle &) const; + + glBuffer verticesBuffer; + std::map<SurfaceKey, SurfaceArrayBuffer> meshes; + Texture::Ptr grass = std::make_shared<Texture>("grass.png"); }; diff --git a/game/vehicles/linkHistory.cpp b/game/vehicles/linkHistory.cpp index e6bab36..77840ed 100644 --- a/game/vehicles/linkHistory.cpp +++ b/game/vehicles/linkHistory.cpp @@ -1,17 +1,27 @@ #include "linkHistory.h" #include "game/network/link.h" #include <memory> +#include <optional> LinkHistory::Entry LinkHistory::add(const Link::WPtr & l, unsigned char d) { + constexpr auto HISTORY_KEEP_LENGTH = 500'000.F; + while (const auto newLength = [this]() -> std::optional<decltype(totalLen)> { + if (!links.empty()) { + const auto newLength = totalLen - links.back().first.lock()->length; + if (newLength >= HISTORY_KEEP_LENGTH) { + return newLength; + } + } + return std::nullopt; + }()) { + totalLen = newLength.value(); + links.pop_back(); + } links.insert(links.begin(), {l, d}); const auto lp = l.lock(); totalLen += lp->length; - while (totalLen >= 1000000.F && !links.empty()) { - totalLen -= links.back().first.lock()->length; - links.pop_back(); - } return {lp, d}; } diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp index 59d1e83..4a0a22d 100644 --- a/game/vehicles/railVehicle.cpp +++ b/game/vehicles/railVehicle.cpp @@ -72,7 +72,12 @@ RailVehicle::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & bary }}; return std::any_of( triangles.begin(), triangles.end(), [&cornerVertices, &ray, &baryPos, &distance](const auto & idx) { - return ray.intersectTriangle( - cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]], baryPos, distance); + if (const auto inter = ray.intersectTriangle( + cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]])) { + baryPos = inter->bary; + distance = inter->distance; + return true; + }; + return false; }); } diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp index 34c1359..21f01c8 100644 --- a/game/vehicles/railVehicleClass.cpp +++ b/game/vehicles/railVehicleClass.cpp @@ -17,6 +17,19 @@ RailVehicleClass::persist(Persistence::PersistenceStore & store) && STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store); } +std::any +RailVehicleClass::createAt(const Location & position) const +{ + return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(instances.acquire(LocationVertex { + .body = position.getRotationTransform(), + .front = position.getRotationTransform(), + .back = position.getRotationTransform(), + .bodyPos = position.pos, + .frontPos = {sincos(position.rot.x) * wheelBase * 0.5F, position.pos.z}, + .backPos = {sincos(position.rot.x) * wheelBase * -0.5F, position.pos.z}, + })); +} + void RailVehicleClass::postLoad() { @@ -33,7 +46,7 @@ RailVehicleClass::postLoad() } void -RailVehicleClass::render(const SceneShader & shader) const +RailVehicleClass::render(const SceneShader & shader, const Frustum &) const { if (const auto count = static_cast<GLsizei>(instances.size())) { if (texture) { @@ -47,7 +60,7 @@ RailVehicleClass::render(const SceneShader & shader) const } void -RailVehicleClass::shadows(const ShadowMapper & mapper) const +RailVehicleClass::shadows(const ShadowMapper & mapper, const Frustum &) const { if (const auto count = static_cast<GLsizei>(instances.size())) { mapper.dynamicPointInst.use(); diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h index 88f08c5..ccff3e2 100644 --- a/game/vehicles/railVehicleClass.h +++ b/game/vehicles/railVehicleClass.h @@ -14,8 +14,10 @@ class Location; class RailVehicleClass : public Renderable, public Asset { public: - void render(const SceneShader & shader) const override; - void shadows(const ShadowMapper & shadowMapper) const override; + void render(const SceneShader & shader, const Frustum &) const override; + void shadows(const ShadowMapper & shadowMapper, const Frustum &) const override; + + [[nodiscard]] std::any createAt(const Location &) const override; struct LocationVertex { glm::mat3 body, front, back; diff --git a/game/vehicles/train.cpp b/game/vehicles/train.cpp index 5bddd61..2461d9c 100644 --- a/game/vehicles/train.cpp +++ b/game/vehicles/train.cpp @@ -2,7 +2,6 @@ #include "game/vehicles/linkHistory.h" #include "game/vehicles/railVehicle.h" #include "game/vehicles/railVehicleClass.h" -#include "gfx/renderable.h" #include "location.h" #include <algorithm> #include <functional> @@ -11,6 +10,9 @@ template<typename> class Ray; +constexpr auto DECELERATION_RATE = 60000.F; +constexpr auto ACCELERATIONS_RATE = 30000.F; + Location Train::getBogiePosition(float linkDist, float dist) const { @@ -41,8 +43,8 @@ Train::doActivity(Go * go, TickDuration dur) const auto maxSpeed = objects.front()->rvClass->maxSpeed; if (go->dist) { *go->dist -= speed * dur.count(); - if (*go->dist < (speed * speed) / 60.F) { - speed -= std::min(speed, 30.F * dur.count()); + if (*go->dist < (speed * speed) / DECELERATION_RATE) { + speed -= std::min(speed, ACCELERATIONS_RATE * dur.count()); } else { if (speed != maxSpeed) { @@ -61,6 +63,6 @@ void Train::doActivity(Idle *, TickDuration dur) { if (speed != 0.F) { - speed -= std::min(speed, 30.F * dur.count()); + speed -= std::min(speed, DECELERATION_RATE * dur.count()); } } diff --git a/game/vehicles/train.h b/game/vehicles/train.h index 4320103..88e30f9 100644 --- a/game/vehicles/train.h +++ b/game/vehicles/train.h @@ -15,9 +15,9 @@ class SceneShader; class ShadowMapper; template<typename> class Ray; -class Train : public Vehicle, public Collection<RailVehicle, false>, public Can<Go>, public Can<Idle> { +class Train : public Vehicle, public UniqueCollection<RailVehicle>, public Can<Go>, public Can<Idle> { public: - explicit Train(const Link::Ptr & link, float linkDist = 0) : Vehicle {link, linkDist} { } + explicit Train(const Link::CPtr & link, float linkDist = 0) : Vehicle {link, linkDist} { } [[nodiscard]] const Location & getLocation() const override diff --git a/game/vehicles/vehicle.cpp b/game/vehicles/vehicle.cpp index 0d46017..dd652bc 100644 --- a/game/vehicles/vehicle.cpp +++ b/game/vehicles/vehicle.cpp @@ -15,7 +15,7 @@ #include <utility> #include <vector> -Vehicle::Vehicle(const Link::Ptr & l, float ld) : linkDist {ld} +Vehicle::Vehicle(const Link::CPtr & l, float ld) : linkDist {ld} { linkHist.add(l, 0); currentActivity = std::make_unique<Idle>(); diff --git a/game/vehicles/vehicle.h b/game/vehicles/vehicle.h index 354f904..c3b35b7 100644 --- a/game/vehicles/vehicle.h +++ b/game/vehicles/vehicle.h @@ -14,7 +14,7 @@ class Location; class Vehicle : public WorldObject, public Selectable { public: - explicit Vehicle(const Link::Ptr & link, float linkDist = 0); + explicit Vehicle(const Link::CPtr & link, float linkDist = 0); float linkDist; // distance along current link float speed {}; // speed in m/s (~75 km/h) diff --git a/game/water.cpp b/game/water.cpp index f720e3e..527e85a 100644 --- a/game/water.cpp +++ b/game/water.cpp @@ -82,7 +82,7 @@ Water::generateMeshes() const auto pos = (p * TILE_SIZE) + GlobalPosition2D {x, y}; const auto v = vertexIndex.emplace(pos, vertices.size()); if (v.second) { - const auto cpos = glm::clamp(pos, std::get<0>(extents).xy(), std::get<1>(extents).xy()); + const auto cpos = glm::clamp(pos, extents.min.xy(), extents.max.xy()); vertices.emplace_back(geoData->positionAt(cpos)); } *out++ = static_cast<unsigned int>(v.first->second); @@ -102,7 +102,7 @@ Water::tick(TickDuration dur) } void -Water::render(const SceneShader & shader) const +Water::render(const SceneShader & shader, const Frustum &) const { shader.water.use(waveCycle); water->bind(); diff --git a/game/water.h b/game/water.h index ba46703..07d9ae1 100644 --- a/game/water.h +++ b/game/water.h @@ -16,7 +16,7 @@ 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; + UniqueCollection<MeshT<Vertex>> meshes; Texture::Ptr water; }; |