summaryrefslogtreecommitdiff
path: root/game
diff options
context:
space:
mode:
Diffstat (limited to 'game')
-rw-r--r--game/environment.cpp93
-rw-r--r--game/environment.h18
-rw-r--r--game/gamestate.cpp3
-rw-r--r--game/gamestate.h9
-rw-r--r--game/geoData.cpp821
-rw-r--r--game/geoData.h204
-rw-r--r--game/geoDataMesh.cpp150
-rw-r--r--game/geoDataMesh.h116
-rw-r--r--game/network/link.cpp100
-rw-r--r--game/network/link.h14
-rw-r--r--game/network/network.cpp89
-rw-r--r--game/network/network.h59
-rw-r--r--game/network/network.impl.h90
-rw-r--r--game/network/rail.cpp156
-rw-r--r--game/network/rail.h9
-rw-r--r--game/orders.h2
-rw-r--r--game/scenary/foliage.cpp41
-rw-r--r--game/scenary/foliage.h21
-rw-r--r--game/scenary/illuminator.cpp12
-rw-r--r--game/scenary/illuminator.h8
-rw-r--r--game/scenary/plant.cpp3
-rw-r--r--game/terrain.cpp166
-rw-r--r--game/terrain.h51
-rw-r--r--game/vehicles/linkHistory.cpp18
-rw-r--r--game/vehicles/railVehicle.cpp9
-rw-r--r--game/vehicles/railVehicleClass.cpp17
-rw-r--r--game/vehicles/railVehicleClass.h11
-rw-r--r--game/vehicles/train.cpp10
-rw-r--r--game/vehicles/train.h4
-rw-r--r--game/vehicles/vehicle.cpp2
-rw-r--r--game/vehicles/vehicle.h2
-rw-r--r--game/water.cpp4
-rw-r--r--game/water.h12
33 files changed, 1414 insertions, 910 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 5725360..4f5d2b0 100644
--- a/game/network/network.h
+++ b/game/network/network.h
@@ -1,23 +1,24 @@
#pragma once
+#include "collection.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
#include "link.h"
-#include <collection.h>
-#include <gfx/renderable.h>
+#include "sorting.h"
+#include "special_members.h"
#include <glm/glm.hpp>
#include <memory>
#include <set>
-#include <sorting.h>
-#include <special_members.h>
#include <string>
#include <utility>
-#include <variant>
-class Texture;
class SceneShader;
+struct Surface;
+class GeoData;
template<typename> class Ray;
-template<size_t... n> using GenDef = std::tuple<glm::vec<n, Distance>...>;
+template<size_t... N> using GenDef = std::tuple<glm::vec<N, GlobalDistance>...>;
using GenCurveDef = GenDef<3, 3, 2>;
class Network {
@@ -29,7 +30,7 @@ public:
[[nodiscard]] Node::Ptr findNodeAt(GlobalPosition3D) const;
[[nodiscard]] Node::Ptr nodeAt(GlobalPosition3D);
- enum class NodeIs { InNetwork, NotInNetwork };
+ enum class NodeIs : uint8_t { InNetwork, NotInNetwork };
using NodeInsertion = std::pair<Node::Ptr, NodeIs>;
[[nodiscard]] NodeInsertion newNodeAt(GlobalPosition3D);
[[nodiscard]] NodeInsertion candidateNodeAt(GlobalPosition3D) const;
@@ -37,26 +38,29 @@ public:
[[nodiscard]] virtual Node::Ptr intersectRayNodes(const Ray<GlobalPosition3D> &) const;
[[nodiscard]] Link::Nexts routeFromTo(const Link::End &, GlobalPosition3D) const;
- [[nodiscard]] Link::Nexts routeFromTo(const Link::End &, const Node::Ptr &) const;
+ [[nodiscard]] static Link::Nexts routeFromTo(const Link::End &, const Node::Ptr &);
virtual Link::CCollection candidateStraight(GlobalPosition3D, GlobalPosition3D) = 0;
virtual Link::CCollection candidateJoins(GlobalPosition3D, GlobalPosition3D) = 0;
virtual Link::CCollection candidateExtend(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addStraight(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addJoins(GlobalPosition3D, GlobalPosition3D) = 0;
- virtual Link::CCollection addExtend(GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addStraight(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addJoins(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
+ virtual Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) = 0;
[[nodiscard]] virtual float findNodeDirection(Node::AnyCPtr) const = 0;
+ [[nodiscard]] virtual const Surface * getBaseSurface() const = 0;
+ [[nodiscard]] virtual RelativeDistance getBaseWidth() const = 0;
+
protected:
- static void joinLinks(const Link::Ptr & l, const Link::Ptr & ol);
+ static void joinLinks(const Link::Ptr & link, const Link::Ptr & oldLink);
static GenCurveDef genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir);
static std::pair<GenCurveDef, GenCurveDef> genCurveDef(
const GlobalPosition3D & start, const GlobalPosition3D & end, float startDir, float endDir);
using Nodes = std::set<Node::Ptr, PtrMemberSorter<Node::Ptr, &Node::pos>>;
Nodes nodes;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
};
template<typename LinkType> class NetworkLinkHolder {
@@ -73,42 +77,41 @@ class NetworkOf : public Network, public Renderable, public NetworkLinkHolder<Li
protected:
using Network::Network;
- Collection<T> links;
+ SharedCollection<T> links;
void joinLinks(const Link::Ptr &) const;
-protected:
[[nodiscard]] Link::Ptr intersectRayLinks(const Ray<GlobalPosition3D> &) const override;
public:
template<typename L, typename... Params>
std::shared_ptr<L>
- candidateLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params)
+ candidateLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params)
requires std::is_base_of_v<T, L>
{
- const auto node1 = candidateNodeAt(a).first, node2 = candidateNodeAt(b).first;
+ const auto node1 = candidateNodeAt(positionA).first, node2 = candidateNodeAt(positionB).first;
return std::make_shared<L>(*this, node1, node2, std::forward<Params>(params)...);
}
template<typename L, typename... Params>
std::shared_ptr<L>
- addLink(GlobalPosition3D a, GlobalPosition3D b, Params &&... params)
+ addLink(GlobalPosition3D positionA, GlobalPosition3D positionB, Params &&... params)
requires std::is_base_of_v<T, L>
{
- const auto node1 = nodeAt(a), node2 = nodeAt(b);
- auto l {links.template create<L>(*this, node1, node2, std::forward<Params>(params)...)};
- joinLinks(l);
- return l;
+ const auto node1 = nodeAt(positionA), node2 = nodeAt(positionB);
+ auto newLink = links.template create<L>(*this, node1, node2, std::forward<Params>(params)...);
+ joinLinks(newLink);
+ return std::move(newLink);
}
- Link::CCollection candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2) override;
+ Link::CCollection candidateStraight(GlobalPosition3D, GlobalPosition3D) override;
Link::CCollection candidateJoins(GlobalPosition3D, GlobalPosition3D) override;
Link::CCollection candidateExtend(GlobalPosition3D, GlobalPosition3D) override;
- Link::CCollection addStraight(GlobalPosition3D n1, GlobalPosition3D n2) override;
- Link::CCollection addJoins(GlobalPosition3D, GlobalPosition3D) override;
- Link::CCollection addExtend(GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addStraight(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addJoins(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
+ Link::CCollection addExtend(const GeoData *, GlobalPosition3D, GlobalPosition3D) override;
[[nodiscard]] float findNodeDirection(Node::AnyCPtr) const override;
protected:
- Link::CCollection addJoins();
+ Link::CCollection addCurve(const GeoData *, const GenCurveDef &);
};
diff --git a/game/network/network.impl.h b/game/network/network.impl.h
index ff29088..e339922 100644
--- a/game/network/network.impl.h
+++ b/game/network/network.impl.h
@@ -1,13 +1,15 @@
+#include "collections.h"
#include "network.h"
+#include <game/geoData.h>
#include <gfx/gl/sceneShader.h>
#include <gfx/models/texture.h>
template<typename T, typename... Links>
void
-NetworkOf<T, Links...>::joinLinks(const Link::Ptr & l) const
+NetworkOf<T, Links...>::joinLinks(const Link::Ptr & link) const
{
- for (const auto & ol : links.objects) {
- Network::joinLinks(l, ol);
+ for (const auto & oldLink : links) {
+ Network::joinLinks(link, oldLink);
}
}
@@ -16,11 +18,11 @@ Link::Ptr
NetworkOf<T, Links...>::intersectRayLinks(const Ray<GlobalPosition3D> & ray) const
{
// Click link
- if (const auto link = std::find_if(links.objects.begin(), links.objects.end(),
+ if (const auto link = std::find_if(links.begin(), links.end(),
[&ray](const std::shared_ptr<T> & link) {
return link->intersectRay(ray);
});
- link != links.objects.end()) {
+ link != links.end()) {
return *link;
}
return {};
@@ -30,11 +32,11 @@ template<typename T, typename... Links>
float
NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const
{
- for (const auto & l : links.objects) {
- for (const auto & e : l->ends) {
+ for (const auto & link : links) {
+ for (const auto & end : link->ends) {
// cppcheck-suppress useStlAlgorithm
- if (e.node.get() == n.get()) {
- return e.dir;
+ if (end.node.get() == n.get()) {
+ return end.dir;
}
}
}
@@ -43,16 +45,17 @@ NetworkOf<T, Links...>::findNodeDirection(Node::AnyCPtr n) const
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D n1, GlobalPosition3D n2)
+NetworkOf<T, Links...>::candidateStraight(GlobalPosition3D positionA, GlobalPosition3D positionB)
{
- return {candidateLink<typename T::StraightLink>(n1, n2)};
+ return {candidateLink<typename T::StraightLink>(positionA, positionB)};
}
template<typename T, typename... Links>
Link::CCollection
NetworkOf<T, Links...>::candidateJoins(GlobalPosition3D start, GlobalPosition3D end)
{
- if (glm::length(RelativePosition3D(start - end)) < 2000.F) {
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ if (::distance(start, end) < MIN_DISTANCE) {
return {};
}
const auto defs = genCurveDef(
@@ -72,28 +75,71 @@ NetworkOf<T, Links...>::candidateExtend(GlobalPosition3D start, GlobalPosition3D
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addStraight(GlobalPosition3D n1, GlobalPosition3D n2)
+NetworkOf<T, Links...>::addStraight(const GeoData * geoData, GlobalPosition3D positionA, GlobalPosition3D positionB)
{
- return {addLink<typename T::StraightLink>(n1, n2)};
+ Link::CCollection out;
+ geoData->walk(positionA.xy(), positionB, [geoData, &out, this, &positionA](const GeoData::WalkStep & step) {
+ if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) {
+ const auto surfaceEdgePosition = geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current));
+ out.emplace_back(addLink<typename T::StraightLink>(positionA, surfaceEdgePosition));
+ positionA = surfaceEdgePosition;
+ }
+ });
+ out.emplace_back(addLink<typename T::StraightLink>(positionA, positionB));
+ return out;
+}
+
+template<typename T, typename... Links>
+Link::CCollection
+NetworkOf<T, Links...>::addCurve(const GeoData * geoData, const GenCurveDef & curve)
+{
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ auto [cstart, cend, centre] = curve;
+ Link::CCollection out;
+ std::set<GeoData::WalkStepCurve, SortedBy<&GeoData::WalkStepCurve::angle>> breaks;
+ const auto radiusMid = ::distance(cstart.xy(), centre);
+ for (const auto radiusOffset : {-getBaseWidth() / 2.F, 0.F, getBaseWidth() / 2.F}) {
+ const auto radius = radiusOffset + radiusMid;
+ const auto start = centre + (difference(cstart.xy(), centre) * radius) / radiusMid;
+ const auto end = centre + (difference(cend.xy(), centre) * radius) / radiusMid;
+ geoData->walk(start, end, centre, [geoData, &breaks](const GeoData::WalkStepCurve & step) {
+ if (step.previous.is_valid() && geoData->getSurface(step.current) != geoData->getSurface(step.previous)) {
+ breaks.insert(step);
+ }
+ });
+ }
+ std::vector<GlobalPosition3D> points;
+ points.reserve(breaks.size() + 2);
+ points.push_back(cstart);
+ std::ranges::transform(
+ breaks, std::back_inserter(points), [geoData, centre, radiusMid](const GeoData::WalkStepCurve & step) {
+ return (centre + (sincos(step.angle) * radiusMid))
+ || geoData->positionAt(GeoData::PointFace(step.exitPosition, step.current)).z;
+ });
+ points.push_back(cend);
+ mergeClose(points, ::distance<3, GlobalDistance>, ::midpoint<3, GlobalDistance>, MIN_DISTANCE);
+ std::ranges::transform(points | std::views::pairwise, std::back_inserter(out), [this, centre](const auto pair) {
+ const auto [a, b] = pair;
+ return this->addLink<typename T::CurveLink>(a, b, centre);
+ });
+ return out;
}
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addJoins(GlobalPosition3D start, GlobalPosition3D end)
+NetworkOf<T, Links...>::addJoins(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end)
{
- if (glm::length(RelativePosition3D(start - end)) < 2000.F) {
+ static constexpr auto MIN_DISTANCE = 2000.F;
+ if (::distance(start, end) < MIN_DISTANCE) {
return {};
}
const auto defs = genCurveDef(start, end, findNodeDirection(nodeAt(start)), findNodeDirection(nodeAt(end)));
- const auto & [c1s, c1e, c1c] = defs.first;
- const auto & [c2s, c2e, c2c] = defs.second;
- return {addLink<typename T::CurveLink>(c1s, c1e, c1c), addLink<typename T::CurveLink>(c2s, c2e, c2c)};
+ return addCurve(geoData, defs.first) + addCurve(geoData, defs.second);
}
template<typename T, typename... Links>
Link::CCollection
-NetworkOf<T, Links...>::addExtend(GlobalPosition3D start, GlobalPosition3D end)
+NetworkOf<T, Links...>::addExtend(const GeoData * geoData, GlobalPosition3D start, GlobalPosition3D end)
{
- const auto [cstart, cend, centre] = genCurveDef(start, end, findNodeDirection(nodeAt(start)));
- return {addLink<typename T::CurveLink>(cstart, cend, centre)};
+ return addCurve(geoData, genCurveDef(start, end, findNodeDirection(nodeAt(start))));
}
diff --git a/game/network/rail.cpp b/game/network/rail.cpp
index fd07ace..c0e597d 100644
--- a/game/network/rail.cpp
+++ b/game/network/rail.cpp
@@ -1,4 +1,5 @@
#include "rail.h"
+#include "game/gamestate.h"
#include "network.h"
#include <game/network/network.impl.h> // IWYU pragma: keep
#include <gfx/gl/sceneShader.h>
@@ -8,7 +9,7 @@
template class NetworkOf<RailLink, RailLinkStraight, RailLinkCurve>;
constexpr auto RAIL_CROSSSECTION_VERTICES {5U};
-constexpr Size3D RAIL_HEIGHT {0, 0, 250.F};
+constexpr Size3D RAIL_HEIGHT {0, 0, 50.F};
RailLinks::RailLinks() : NetworkOf<RailLink, RailLinkStraight, RailLinkCurve> {"rails.jpg"} { }
@@ -32,40 +33,39 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
}
// Find start link/end - opposite entry dir to existing link; so pi +...
const Angle dir = pi + findNodeDirection(node1ins.first);
- if (dir == vector_yaw(end - start)) {
+ if (dir == vector_yaw(difference(end, start))) {
return addLink<RailLinkStraight>(start, end);
}
const auto flatStart {start.xy()}, flatEnd {end.xy()};
if (node2ins.second == NodeIs::InNetwork) {
auto midheight = [&](auto mid) {
- const auto sm = glm::length(RelativePosition2D(flatStart - mid)),
- em = glm::length(RelativePosition2D(flatEnd - mid));
- return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (sm / (sm + em)));
+ const auto startToMid = ::distance<2>(flatStart, mid);
+ const auto endToMid = ::distance<2>(flatEnd, mid);
+ return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid)));
};
const float dir2 = pi + findNodeDirection(node2ins.first);
- if (const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); radii.first < radii.second) {
- const auto radius {radii.first};
- const auto c1 = flatStart + (sincosf(dir + half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(dir2 + half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
+ const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2);
+ if (radii.first < radii.second) {
+ const auto radius = radii.first;
+ const auto centre1 = flatStart + (sincos(dir + half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(dir2 + half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
const auto midh = mid || midheight(mid);
- addLink<RailLinkCurve>(start, midh, c1);
- return addLink<RailLinkCurve>(end, midh, c2);
- }
- else {
- const auto radius {radii.second};
- const auto c1 = flatStart + (sincosf(dir - half_pi) * radius);
- const auto c2 = flatEnd + (sincosf(dir2 - half_pi) * radius);
- const auto mid = (c1 + c2) / 2;
- const auto midh = mid || midheight(mid);
- addLink<RailLinkCurve>(midh, start, c1);
- return addLink<RailLinkCurve>(midh, end, c2);
+ addLink<RailLinkCurve>(start, midh, centre1);
+ return addLink<RailLinkCurve>(end, midh, centre2);
}
+ const auto radius = radii.second;
+ const auto centre1 = flatStart + (sincos(dir - half_pi) * radius);
+ const auto centre2 = flatEnd + (sincos(dir2 - half_pi) * radius);
+ const auto mid = (centre1 + centre2) / 2;
+ const auto midh = mid || midheight(mid);
+ addLink<RailLinkCurve>(midh, start, centre1);
+ return addLink<RailLinkCurve>(midh, end, centre2);
}
- const auto diff {end - start};
- const auto vy {vector_yaw(diff)};
- const auto n2ed {(vy * 2) - dir - pi};
- const auto centre {find_arc_centre(flatStart, dir, flatEnd, n2ed)};
+ const auto diff = difference(end, start);
+ const auto yaw = vector_yaw(diff);
+ const auto n2ed = (yaw * 2) - dir - pi;
+ const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed);
if (centre.second) { // right hand arc
std::swap(start, end);
@@ -73,53 +73,61 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
return addLink<RailLinkCurve>(start, end, centre.first);
}
-constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> railCrossSection {{
- {-1900.F, 0.F, 0.F},
- {-608.F, 0.F, RAIL_HEIGHT.z},
- {0, 0.F, RAIL_HEIGHT.z * .7F},
- {608.F, 0.F, RAIL_HEIGHT.z},
- {1900.F, 0.F, 0.F},
-}};
-constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> railTexturePos {
- 0.F,
- .34F,
- .5F,
- .66F,
- 1.F,
-};
-constexpr auto sleepers {5.F}; // There are 5 repetitions of sleepers in the texture
-
-inline auto
-round_sleepers(const float v)
-{
- return round_frac(v, sleepers);
+namespace {
+ constexpr const std::array<RelativePosition3D, RAIL_CROSSSECTION_VERTICES> RAIL_CROSS_SECTION {{
+ {-1330.F, 0.F, 0},
+ {-608.F, 0.F, RAIL_HEIGHT.z},
+ {0, 0.F, RAIL_HEIGHT.z / 2},
+ {608.F, 0.F, RAIL_HEIGHT.z},
+ {1330.F, 0.F, 0},
+ }};
+ constexpr const std::array<float, RAIL_CROSSSECTION_VERTICES> RAIL_TEXTURE_POS {
+ 0.15F,
+ .34F,
+ .5F,
+ .66F,
+ 0.85F,
+ };
+ template<std::floating_point T> constexpr T SLEEPERS_PER_TEXTURE {5};
+ template<std::floating_point T> constexpr T TEXTURE_LENGTH {2'000};
+ template<std::floating_point T> constexpr T SLEEPER_LENGTH {T {1} / SLEEPERS_PER_TEXTURE<T>};
+
+ template<std::floating_point T>
+ constexpr auto
+ roundSleepers(const T length)
+ {
+ return round_frac(length / TEXTURE_LENGTH<T>, SLEEPER_LENGTH<T>);
+ }
}
-RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & a,
- const Node::Ptr & b) : RailLinkStraight(instances, a, b, b->pos - a->pos)
+RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB) : RailLinkStraight(instances, nodeA, nodeB, nodeB->pos - nodeA->pos)
{
}
-RailLinkStraight::RailLinkStraight(
- NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr a, Node::Ptr b, const RelativePosition3D & diff) :
- Link({std::move(a), vector_yaw(diff)}, {std::move(b), vector_yaw(-diff)}, glm::length(diff)),
+RailLinkStraight::RailLinkStraight(NetworkLinkHolder<RailLinkStraight> & instances, Node::Ptr nodeA, Node::Ptr nodeB,
+ const RelativePosition3D & diff) :
+ Link({.node = std::move(nodeA), .dir = vector_yaw(diff)}, {.node = std::move(nodeB), .dir = vector_yaw(-diff)},
+ glm::length(diff)),
instance {instances.vertices.acquire(
- ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), round_sleepers(length / 2000.F))}
+ ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), roundSleepers(length))}
{
}
-RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b,
- GlobalPosition2D c) : RailLinkCurve(instances, a, b, c || a->pos.z, {c, a->pos, b->pos})
+RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB, GlobalPosition2D centre) :
+ RailLinkCurve(instances, nodeA, nodeB, centre || nodeA->pos.z, ::distance<2>(nodeA->pos.xy(), centre),
+ {centre, nodeA->pos, nodeB->pos})
{
}
-RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & a, const Node::Ptr & b,
- GlobalPosition3D c, const Arc arc) :
- Link({a, normalize(arc.first + half_pi)}, {b, normalize(arc.second - half_pi)},
- glm::length(RelativePosition3D(a->pos - c)) * arc.length()),
- LinkCurve {c, glm::length(RelativePosition3D(ends[0].node->pos - c)), arc},
- instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, c, round_sleepers(length / 2000.F),
- half_pi - arc.first, half_pi - arc.second, radius)}
+RailLinkCurve::RailLinkCurve(NetworkLinkHolder<RailLinkCurve> & instances, const Node::Ptr & nodeA,
+ const Node::Ptr & nodeB, GlobalPosition3D centre, RelativeDistance radius, const Arc arc) :
+ Link({.node = nodeA, .dir = normalize(arc.first + half_pi)},
+ {.node = nodeB, .dir = normalize(arc.second - half_pi)},
+ glm::length(RelativePosition2D {radius * arc.length(), difference(nodeA->pos, nodeB->pos).z})),
+ LinkCurve {centre, radius, arc}, instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, centre,
+ roundSleepers(length), half_pi - arc.first, half_pi - arc.second, radius)}
{
}
@@ -148,23 +156,39 @@ template<> NetworkLinkHolder<RailLinkCurve>::NetworkLinkHolder()
namespace {
template<typename LinkType>
void
- renderType(const NetworkLinkHolder<LinkType> & n, auto & s)
+ renderType(const NetworkLinkHolder<LinkType> & networkLinks, auto & shader)
{
- if (auto count = n.vertices.size()) {
- s.use(railCrossSection, railTexturePos);
- glBindVertexArray(n.vao);
+ if (auto count = networkLinks.vertices.size()) {
+ shader.use(RAIL_CROSS_SECTION, RAIL_TEXTURE_POS);
+ glBindVertexArray(networkLinks.vao);
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count));
}
};
}
void
-RailLinks::render(const SceneShader & shader) const
+RailLinks::render(const SceneShader & shader, const Frustum &) const
{
- if (!links.objects.empty()) {
+ if (!links.empty()) {
texture->bind();
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(-1, 0);
renderType<RailLinkStraight>(*this, shader.networkStraight);
renderType<RailLinkCurve>(*this, shader.networkCurve);
+ glDisable(GL_POLYGON_OFFSET_FILL);
glBindVertexArray(0);
}
}
+
+const Surface *
+RailLinks::getBaseSurface() const
+{
+ return gameState->assets.at("terrain.surface.gravel").dynamicCast<const Surface>().get();
+}
+
+RelativeDistance
+RailLinks::getBaseWidth() const
+{
+ static constexpr auto BASE_WIDTH = 5'700;
+ return BASE_WIDTH;
+}
diff --git a/game/network/rail.h b/game/network/rail.h
index c8effef..4aef9e3 100644
--- a/game/network/rail.h
+++ b/game/network/rail.h
@@ -62,8 +62,8 @@ public:
};
private:
- RailLinkCurve(
- NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D, const Arc);
+ RailLinkCurve(NetworkLinkHolder<RailLinkCurve> &, const Node::Ptr &, const Node::Ptr &, GlobalPosition3D centreBase,
+ RelativeDistance radius, Arc);
InstanceVertices<Vertex>::InstanceProxy instance;
};
@@ -75,7 +75,10 @@ public:
RailLinks();
std::shared_ptr<RailLink> addLinksBetween(GlobalPosition3D start, GlobalPosition3D end);
- void render(const SceneShader &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
+
+ [[nodiscard]] const Surface * getBaseSurface() const override;
+ [[nodiscard]] RelativeDistance getBaseWidth() const override;
private:
void tick(TickDuration elapsed) override;
diff --git a/game/orders.h b/game/orders.h
index ca5cfdb..840aa3c 100644
--- a/game/orders.h
+++ b/game/orders.h
@@ -5,7 +5,7 @@
class Objective;
-class Orders : public Collection<Objective> {
+class Orders : public SharedCollection<Objective> {
public:
[[nodiscard]] Objective * current() const;
Objective * next();
diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp
index 73d285f..140c4e5 100644
--- a/game/scenary/foliage.cpp
+++ b/game/scenary/foliage.cpp
@@ -2,8 +2,16 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/shadowMapper.h"
#include "gfx/gl/vertexArrayObject.h"
-#include "gfx/models/texture.h"
-#include "location.h"
+#include <location.h>
+
+static_assert(std::is_constructible_v<Foliage>);
+
+std::any
+Foliage::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(
+ instances.acquire(position.getRotationTransform(), position.rot.y, position.pos));
+}
bool
Foliage::persist(Persistence::PersistenceStore & store)
@@ -16,11 +24,22 @@ Foliage::postLoad()
{
texture = getTexture();
bodyMesh->configureVAO(instanceVAO)
- .addAttribs<LocationVertex, &LocationVertex::first, &LocationVertex::second>(instances.bufferName(), 1);
+ .addAttribs<LocationVertex, &LocationVertex::rotation, &LocationVertex::position>(
+ instances.bufferName(), 1);
+ VertexArrayObject {instancePointVAO}.addAttribs<LocationVertex, &LocationVertex::position, &LocationVertex::yaw>(
+ instances.bufferName());
+}
+
+void
+Foliage::updateStencil(const ShadowStenciller & ss) const
+{
+ if (instances.size() > 0) {
+ ss.renderStencil(shadowStencil, *bodyMesh, texture);
+ }
}
void
-Foliage::render(const SceneShader & shader) const
+Foliage::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = instances.size()) {
shader.basicInst.use();
@@ -32,13 +51,15 @@ Foliage::render(const SceneShader & shader) const
}
void
-Foliage::shadows(const ShadowMapper & mapper) const
+Foliage::shadows(const ShadowMapper & mapper, const Frustum &) const
{
if (const auto count = instances.size()) {
- mapper.dynamicPointInstWithTextures.use();
- if (texture) {
- texture->bind(GL_TEXTURE3);
- }
- bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count));
+ const auto dimensions = bodyMesh->getDimensions();
+ mapper.stencilShadowProgram.use(dimensions.centre, dimensions.size);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowStencil);
+ glBindVertexArray(instancePointVAO);
+ glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count));
+ glBindVertexArray(0);
}
}
diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h
index 3beda89..d15a8b0 100644
--- a/game/scenary/foliage.h
+++ b/game/scenary/foliage.h
@@ -2,23 +2,34 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/gl/shadowStenciller.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
class SceneShader;
class ShadowMapper;
class Location;
-class Texture;
class Foliage : public Asset, public Renderable, public StdTypeDefs<Foliage> {
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
glVertexArray instanceVAO;
+ glVertexArray instancePointVAO;
public:
- using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>;
+ [[nodiscard]] std::any createAt(const Location &) const override;
+
+ struct LocationVertex {
+ glm::mat3 rotation;
+ float yaw;
+ GlobalPosition3D position;
+ };
+
mutable InstanceVertices<LocationVertex> instances;
- void render(const SceneShader &) const override;
- void shadows(const ShadowMapper &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
+ void updateStencil(const ShadowStenciller &) const override;
+ glTexture shadowStencil = ShadowStenciller::createStencilTexture(256, 256);
protected:
friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>;
diff --git a/game/scenary/illuminator.cpp b/game/scenary/illuminator.cpp
index e3810ec..d8e4c4e 100644
--- a/game/scenary/illuminator.cpp
+++ b/game/scenary/illuminator.cpp
@@ -2,6 +2,16 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/vertexArrayObject.h"
#include "gfx/models/texture.h" // IWYU pragma: keep
+#include <location.h>
+
+static_assert(std::is_constructible_v<Illuminator>);
+
+std::any
+Illuminator::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(
+ instances.acquire(position.getRotationTransform(), position.pos));
+}
bool
Illuminator::SpotLight::persist(Persistence::PersistenceStore & store)
@@ -59,7 +69,7 @@ Illuminator::postLoad()
}
void
-Illuminator::render(const SceneShader & shader) const
+Illuminator::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = instances.size()) {
shader.basicInst.use();
diff --git a/game/scenary/illuminator.h b/game/scenary/illuminator.h
index cd6073c..200ba40 100644
--- a/game/scenary/illuminator.h
+++ b/game/scenary/illuminator.h
@@ -2,19 +2,21 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
class SceneShader;
class Location;
-class Texture;
class Illuminator : public Asset, public Renderable, public StdTypeDefs<Illuminator> {
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
glVertexArray instanceVAO;
std::optional<glVertexArray> instancesSpotLightVAO, instancesPointLightVAO;
public:
+ [[nodiscard]] std::any createAt(const Location &) const override;
+
struct LightCommonVertex {
RelativePosition3D position;
RGB colour;
@@ -45,7 +47,7 @@ public:
mutable InstanceVertices<LocationVertex> instances;
mutable InstanceVertices<SpotLightVertex> instancesSpotLight;
mutable InstanceVertices<PointLightVertex> instancesPointLight;
- void render(const SceneShader &) const override;
+ void render(const SceneShader &, const Frustum &) const override;
void lights(const SceneShader &) const override;
protected:
diff --git a/game/scenary/plant.cpp b/game/scenary/plant.cpp
index b39c28b..2006225 100644
--- a/game/scenary/plant.cpp
+++ b/game/scenary/plant.cpp
@@ -2,6 +2,7 @@
#include "location.h"
Plant::Plant(std::shared_ptr<const Foliage> type, const Location & position) :
- type {std::move(type)}, location {this->type->instances.acquire(position.getRotationTransform(), position.pos)}
+ type {std::move(type)},
+ location {this->type->instances.acquire(position.getRotationTransform(), position.rot.y, position.pos)}
{
}
diff --git a/game/terrain.cpp b/game/terrain.cpp
index 3b16e79..f10aac6 100644
--- a/game/terrain.cpp
+++ b/game/terrain.cpp
@@ -1,65 +1,118 @@
#include "terrain.h"
-#include "game/geoData.h"
-#include "gfx/models/texture.h"
+#include "gfx/frustum.h"
#include <algorithm>
-#include <cstddef>
#include <gfx/gl/sceneShader.h>
#include <gfx/gl/shadowMapper.h>
#include <gfx/image.h>
#include <gfx/models/mesh.h>
#include <gfx/models/vertex.h>
+#include <glMappedBufferWriter.h>
#include <glm/glm.hpp>
-#include <iterator>
#include <location.h>
#include <maths.h>
#include <utility>
#include <vector>
-static constexpr RGB openSurface {-1};
-
-Terrain::Terrain(std::shared_ptr<GeoData> tm) : geoData {std::move(tm)}, grass {std::make_shared<Texture>("grass.png")}
-{
- generateMeshes();
-}
+static constexpr RGB OPEN_SURFACE {-1};
+static constexpr GlobalDistance TILE_SIZE = 1024 * 1024; // ~1km, power of 2, fast divide
template<>
VertexArrayObject &
VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, const GLuint divisor)
{
- return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal, &Terrain::Vertex::colourBias>(
- arrayBuffer, divisor);
+ return addAttribs<Terrain::Vertex, &Terrain::Vertex::pos, &Terrain::Vertex::normal>(arrayBuffer, divisor);
}
-void
-Terrain::generateMeshes()
+bool
+Terrain::SurfaceKey::operator<(const SurfaceKey & other) const
{
- std::vector<unsigned int> indices;
- indices.reserve(geoData->n_faces() * 3);
- std::vector<Vertex> vertices;
- vertices.reserve(geoData->n_vertices());
- std::map<std::pair<GeoData::VertexHandle, const Surface *>, size_t> vertexIndex;
- std::for_each(geoData->vertices_sbegin(), geoData->vertices_end(),
- [this, &vertexIndex, &vertices](const GeoData::VertexHandle v) {
- std::for_each(geoData->vf_begin(v), geoData->vf_end(v),
- [&vertexIndex, v, this, &vertices](const GeoData::FaceHandle f) {
- const auto surface = geoData->get_surface(f);
- if (const auto vertexIndexRef = vertexIndex.emplace(std::make_pair(v, surface), 0);
- vertexIndexRef.second) {
- vertexIndexRef.first->second = vertices.size();
+ return std::tie(surface, basePosition.x, basePosition.y)
+ < std::tie(other.surface, other.basePosition.x, other.basePosition.y);
+}
- vertices.emplace_back(geoData->point(v), geoData->normal(v),
- surface ? surface->colorBias : openSurface);
- }
- });
- });
- std::for_each(
- geoData->faces_sbegin(), geoData->faces_end(), [this, &vertexIndex, &indices](const GeoData::FaceHandle f) {
- std::transform(geoData->fv_begin(f), geoData->fv_end(f), std::back_inserter(indices),
- [&vertexIndex, f, this](const GeoData::VertexHandle v) {
- return vertexIndex[std::make_pair(v, geoData->get_surface(f))];
- });
+inline void
+Terrain::copyVerticesToBuffer() const
+{
+ std::ranges::transform(all_vertices(), glMappedBufferWriter<Vertex> {GL_ARRAY_BUFFER, verticesBuffer, n_vertices()},
+ [this](const auto & vertex) {
+ return Vertex {point(vertex), normal(vertex)};
});
- meshes.create<MeshT<Vertex>>(vertices, indices);
+}
+
+inline GlobalPosition2D
+Terrain::getTile(const FaceHandle & face) const
+{
+ return point(*cfv_begin(face)).xy() / TILE_SIZE;
+};
+
+Terrain::SurfaceIndices
+Terrain::mapSurfaceFacesToIndices() const
+{
+ SurfaceIndices surfaceIndices;
+ const auto indexBySurfaceAndTile = std::views::transform([this](const auto & faceItr) {
+ return std::pair<SurfaceKey, FaceHandle> {{getSurface(*faceItr), getTile(*faceItr)}, *faceItr};
+ });
+ const auto chunkBySurfaceAndTile = std::views::chunk_by([](const auto & face1, const auto & face2) {
+ return face1.first.surface == face2.first.surface && face1.first.basePosition == face2.first.basePosition;
+ });
+ for (const auto & faceRange : faces() | indexBySurfaceAndTile | chunkBySurfaceAndTile) {
+ const SurfaceKey & surfaceKey = faceRange.front().first;
+ auto indexItr = surfaceIndices.find(surfaceKey);
+ if (indexItr == surfaceIndices.end()) {
+ indexItr = surfaceIndices.emplace(surfaceKey, std::vector<GLuint> {}).first;
+ if (auto existing = meshes.find(surfaceKey); existing != meshes.end()) {
+ indexItr->second.reserve(static_cast<size_t>(existing->second.count));
+ }
+ }
+ for (auto push = std::back_inserter(indexItr->second); const auto & [_, face] : faceRange) {
+ std::ranges::transform(fv_range(face), push, &OpenMesh::VertexHandle::idx);
+ }
+ }
+ return surfaceIndices;
+}
+
+void
+Terrain::copyIndicesToBuffers(const SurfaceIndices & surfaceIndices)
+{
+ for (const auto & [surfaceKey, indices] : surfaceIndices) {
+ auto meshItr = meshes.find(surfaceKey);
+ if (meshItr == meshes.end()) {
+ meshItr = meshes.emplace(surfaceKey, SurfaceArrayBuffer {}).first;
+ VertexArrayObject {meshItr->second.vertexArray}
+ .addAttribsFor<Vertex>(verticesBuffer)
+ .addIndices(meshItr->second.indicesBuffer, indices)
+ .data(verticesBuffer, GL_ARRAY_BUFFER);
+ }
+ else {
+ VertexArrayObject {meshItr->second.vertexArray}
+ .addIndices(meshItr->second.indicesBuffer, indices)
+ .data(verticesBuffer, GL_ARRAY_BUFFER);
+ }
+ meshItr->second.count = static_cast<GLsizei>(indices.size());
+ meshItr->second.aabb = AxisAlignedBoundingBox<GlobalDistance>::fromPoints(
+ indices | std::views::transform([this](const auto vertex) {
+ return this->point(VertexHandle {static_cast<int>(vertex)});
+ }));
+ }
+}
+
+void
+Terrain::pruneOrphanMeshes(const SurfaceIndices & surfaceIndices)
+{
+ if (meshes.size() > surfaceIndices.size()) {
+ std::erase_if(meshes, [&surfaceIndices](const auto & mesh) {
+ return !surfaceIndices.contains(mesh.first);
+ });
+ }
+}
+
+void
+Terrain::generateMeshes()
+{
+ copyVerticesToBuffer();
+ const auto surfaceIndices = mapSurfaceFacesToIndices();
+ copyIndicesToBuffers(surfaceIndices);
+ pruneOrphanMeshes(surfaceIndices);
}
void
@@ -68,16 +121,41 @@ Terrain::tick(TickDuration)
}
void
-Terrain::render(const SceneShader & shader) const
+Terrain::afterChange()
+{
+ generateMeshes();
+}
+
+void
+Terrain::render(const SceneShader & shader, const Frustum & frustum) const
{
- shader.landmass.use();
grass->bind();
- meshes.apply(&Mesh::Draw);
+
+ const auto chunkBySurface = std::views::chunk_by([](const auto & itr1, const auto & itr2) {
+ return itr1.first.surface == itr2.first.surface;
+ });
+ for (const auto & surfaceRange : meshes | chunkBySurface) {
+ const auto surface = surfaceRange.front().first.surface;
+ shader.landmass.use(surface ? surface->colorBias : OPEN_SURFACE);
+ for (const auto & sab : surfaceRange) {
+ if (frustum.contains(sab.second.aabb)) {
+ glBindVertexArray(sab.second.vertexArray);
+ glDrawElements(GL_TRIANGLES, sab.second.count, GL_UNSIGNED_INT, nullptr);
+ }
+ }
+ }
+ glBindVertexArray(0);
}
void
-Terrain::shadows(const ShadowMapper & shadowMapper) const
+Terrain::shadows(const ShadowMapper & shadowMapper, const Frustum & frustum) const
{
shadowMapper.landmess.use();
- meshes.apply(&Mesh::Draw);
+ for (const auto & [surface, sab] : meshes) {
+ if (frustum.shadedBy(sab.aabb)) {
+ glBindVertexArray(sab.vertexArray);
+ glDrawElements(GL_TRIANGLES, sab.count, GL_UNSIGNED_INT, nullptr);
+ }
+ }
+ glBindVertexArray(0);
}
diff --git a/game/terrain.h b/game/terrain.h
index d088f89..1a63296 100644
--- a/game/terrain.h
+++ b/game/terrain.h
@@ -1,36 +1,57 @@
#pragma once
#include "chronology.h"
-#include "collection.h"
#include "config/types.h"
#include "game/worldobject.h"
-#include <gfx/models/mesh.h>
-#include <gfx/renderable.h>
-#include <memory>
+#include "geoData.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
class SceneShader;
-class Texture;
-class GeoData;
-class Terrain : public WorldObject, public Renderable {
+class Terrain : public GeoData, public WorldObject, public Renderable {
public:
- explicit Terrain(std::shared_ptr<GeoData>);
+ template<typename... P> explicit Terrain(P &&... params) : GeoData {std::forward<P>(params)...}
+ {
+ generateMeshes();
+ }
- void render(const SceneShader & shader) const override;
- void shadows(const ShadowMapper &) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
+ void shadows(const ShadowMapper &, const Frustum &) const override;
void tick(TickDuration) override;
struct Vertex {
GlobalPosition3D pos;
Normal3D normal;
- RGB colourBias;
};
-private:
void generateMeshes();
- std::shared_ptr<GeoData> geoData;
- Collection<MeshT<Vertex>, false> meshes;
- std::shared_ptr<Texture> grass;
+private:
+ void afterChange() override;
+
+ struct SurfaceArrayBuffer {
+ glVertexArray vertexArray;
+ glBuffer indicesBuffer;
+ GLsizei count;
+ AxisAlignedBoundingBox<GlobalDistance> aabb;
+ };
+
+ struct SurfaceKey {
+ const Surface * surface;
+ GlobalPosition2D basePosition;
+ inline bool operator<(const SurfaceKey &) const;
+ };
+
+ using SurfaceIndices = std::map<SurfaceKey, std::vector<GLuint>>;
+ void copyVerticesToBuffer() const;
+ [[nodiscard]] SurfaceIndices mapSurfaceFacesToIndices() const;
+ void copyIndicesToBuffers(const SurfaceIndices &);
+ void pruneOrphanMeshes(const SurfaceIndices &);
+ [[nodiscard]] inline GlobalPosition2D getTile(const FaceHandle &) const;
+
+ glBuffer verticesBuffer;
+ std::map<SurfaceKey, SurfaceArrayBuffer> meshes;
+ Texture::Ptr grass = std::make_shared<Texture>("grass.png");
};
diff --git a/game/vehicles/linkHistory.cpp b/game/vehicles/linkHistory.cpp
index e6bab36..77840ed 100644
--- a/game/vehicles/linkHistory.cpp
+++ b/game/vehicles/linkHistory.cpp
@@ -1,17 +1,27 @@
#include "linkHistory.h"
#include "game/network/link.h"
#include <memory>
+#include <optional>
LinkHistory::Entry
LinkHistory::add(const Link::WPtr & l, unsigned char d)
{
+ constexpr auto HISTORY_KEEP_LENGTH = 500'000.F;
+ while (const auto newLength = [this]() -> std::optional<decltype(totalLen)> {
+ if (!links.empty()) {
+ const auto newLength = totalLen - links.back().first.lock()->length;
+ if (newLength >= HISTORY_KEEP_LENGTH) {
+ return newLength;
+ }
+ }
+ return std::nullopt;
+ }()) {
+ totalLen = newLength.value();
+ links.pop_back();
+ }
links.insert(links.begin(), {l, d});
const auto lp = l.lock();
totalLen += lp->length;
- while (totalLen >= 1000000.F && !links.empty()) {
- totalLen -= links.back().first.lock()->length;
- links.pop_back();
- }
return {lp, d};
}
diff --git a/game/vehicles/railVehicle.cpp b/game/vehicles/railVehicle.cpp
index 59d1e83..4a0a22d 100644
--- a/game/vehicles/railVehicle.cpp
+++ b/game/vehicles/railVehicle.cpp
@@ -72,7 +72,12 @@ RailVehicle::intersectRay(const Ray<GlobalPosition3D> & ray, BaryPosition & bary
}};
return std::any_of(
triangles.begin(), triangles.end(), [&cornerVertices, &ray, &baryPos, &distance](const auto & idx) {
- return ray.intersectTriangle(
- cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]], baryPos, distance);
+ if (const auto inter = ray.intersectTriangle(
+ cornerVertices[idx[0]], cornerVertices[idx[1]], cornerVertices[idx[2]])) {
+ baryPos = inter->bary;
+ distance = inter->distance;
+ return true;
+ };
+ return false;
});
}
diff --git a/game/vehicles/railVehicleClass.cpp b/game/vehicles/railVehicleClass.cpp
index 34c1359..21f01c8 100644
--- a/game/vehicles/railVehicleClass.cpp
+++ b/game/vehicles/railVehicleClass.cpp
@@ -17,6 +17,19 @@ RailVehicleClass::persist(Persistence::PersistenceStore & store)
&& STORE_HELPER(bodyMesh, Asset::MeshConstruct) && Asset::persist(store);
}
+std::any
+RailVehicleClass::createAt(const Location & position) const
+{
+ return std::make_shared<InstanceVertices<LocationVertex>::InstanceProxy>(instances.acquire(LocationVertex {
+ .body = position.getRotationTransform(),
+ .front = position.getRotationTransform(),
+ .back = position.getRotationTransform(),
+ .bodyPos = position.pos,
+ .frontPos = {sincos(position.rot.x) * wheelBase * 0.5F, position.pos.z},
+ .backPos = {sincos(position.rot.x) * wheelBase * -0.5F, position.pos.z},
+ }));
+}
+
void
RailVehicleClass::postLoad()
{
@@ -33,7 +46,7 @@ RailVehicleClass::postLoad()
}
void
-RailVehicleClass::render(const SceneShader & shader) const
+RailVehicleClass::render(const SceneShader & shader, const Frustum &) const
{
if (const auto count = static_cast<GLsizei>(instances.size())) {
if (texture) {
@@ -47,7 +60,7 @@ RailVehicleClass::render(const SceneShader & shader) const
}
void
-RailVehicleClass::shadows(const ShadowMapper & mapper) const
+RailVehicleClass::shadows(const ShadowMapper & mapper, const Frustum &) const
{
if (const auto count = static_cast<GLsizei>(instances.size())) {
mapper.dynamicPointInst.use();
diff --git a/game/vehicles/railVehicleClass.h b/game/vehicles/railVehicleClass.h
index 9d9d4c2..ccff3e2 100644
--- a/game/vehicles/railVehicleClass.h
+++ b/game/vehicles/railVehicleClass.h
@@ -3,20 +3,21 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
#include "gfx/renderable.h"
#include <array>
#include <memory>
-#include <string>
class SceneShader;
class ShadowMapper;
-class Texture;
class Location;
class RailVehicleClass : public Renderable, public Asset {
public:
- void render(const SceneShader & shader) const override;
- void shadows(const ShadowMapper & shadowMapper) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
+ void shadows(const ShadowMapper & shadowMapper, const Frustum &) const override;
+
+ [[nodiscard]] std::any createAt(const Location &) const override;
struct LocationVertex {
glm::mat3 body, front, back;
@@ -25,7 +26,7 @@ public:
std::array<Mesh::Ptr, 2> bogies;
Mesh::Ptr bodyMesh;
- std::shared_ptr<Texture> texture;
+ Texture::Ptr texture;
float wheelBase;
float length;
float maxSpeed;
diff --git a/game/vehicles/train.cpp b/game/vehicles/train.cpp
index 5bddd61..2461d9c 100644
--- a/game/vehicles/train.cpp
+++ b/game/vehicles/train.cpp
@@ -2,7 +2,6 @@
#include "game/vehicles/linkHistory.h"
#include "game/vehicles/railVehicle.h"
#include "game/vehicles/railVehicleClass.h"
-#include "gfx/renderable.h"
#include "location.h"
#include <algorithm>
#include <functional>
@@ -11,6 +10,9 @@
template<typename> class Ray;
+constexpr auto DECELERATION_RATE = 60000.F;
+constexpr auto ACCELERATIONS_RATE = 30000.F;
+
Location
Train::getBogiePosition(float linkDist, float dist) const
{
@@ -41,8 +43,8 @@ Train::doActivity(Go * go, TickDuration dur)
const auto maxSpeed = objects.front()->rvClass->maxSpeed;
if (go->dist) {
*go->dist -= speed * dur.count();
- if (*go->dist < (speed * speed) / 60.F) {
- speed -= std::min(speed, 30.F * dur.count());
+ if (*go->dist < (speed * speed) / DECELERATION_RATE) {
+ speed -= std::min(speed, ACCELERATIONS_RATE * dur.count());
}
else {
if (speed != maxSpeed) {
@@ -61,6 +63,6 @@ void
Train::doActivity(Idle *, TickDuration dur)
{
if (speed != 0.F) {
- speed -= std::min(speed, 30.F * dur.count());
+ speed -= std::min(speed, DECELERATION_RATE * dur.count());
}
}
diff --git a/game/vehicles/train.h b/game/vehicles/train.h
index 4320103..88e30f9 100644
--- a/game/vehicles/train.h
+++ b/game/vehicles/train.h
@@ -15,9 +15,9 @@ class SceneShader;
class ShadowMapper;
template<typename> class Ray;
-class Train : public Vehicle, public Collection<RailVehicle, false>, public Can<Go>, public Can<Idle> {
+class Train : public Vehicle, public UniqueCollection<RailVehicle>, public Can<Go>, public Can<Idle> {
public:
- explicit Train(const Link::Ptr & link, float linkDist = 0) : Vehicle {link, linkDist} { }
+ explicit Train(const Link::CPtr & link, float linkDist = 0) : Vehicle {link, linkDist} { }
[[nodiscard]] const Location &
getLocation() const override
diff --git a/game/vehicles/vehicle.cpp b/game/vehicles/vehicle.cpp
index 0d46017..dd652bc 100644
--- a/game/vehicles/vehicle.cpp
+++ b/game/vehicles/vehicle.cpp
@@ -15,7 +15,7 @@
#include <utility>
#include <vector>
-Vehicle::Vehicle(const Link::Ptr & l, float ld) : linkDist {ld}
+Vehicle::Vehicle(const Link::CPtr & l, float ld) : linkDist {ld}
{
linkHist.add(l, 0);
currentActivity = std::make_unique<Idle>();
diff --git a/game/vehicles/vehicle.h b/game/vehicles/vehicle.h
index 354f904..c3b35b7 100644
--- a/game/vehicles/vehicle.h
+++ b/game/vehicles/vehicle.h
@@ -14,7 +14,7 @@ class Location;
class Vehicle : public WorldObject, public Selectable {
public:
- explicit Vehicle(const Link::Ptr & link, float linkDist = 0);
+ explicit Vehicle(const Link::CPtr & link, float linkDist = 0);
float linkDist; // distance along current link
float speed {}; // speed in m/s (~75 km/h)
diff --git a/game/water.cpp b/game/water.cpp
index f720e3e..527e85a 100644
--- a/game/water.cpp
+++ b/game/water.cpp
@@ -82,7 +82,7 @@ Water::generateMeshes()
const auto pos = (p * TILE_SIZE) + GlobalPosition2D {x, y};
const auto v = vertexIndex.emplace(pos, vertices.size());
if (v.second) {
- const auto cpos = glm::clamp(pos, std::get<0>(extents).xy(), std::get<1>(extents).xy());
+ const auto cpos = glm::clamp(pos, extents.min.xy(), extents.max.xy());
vertices.emplace_back(geoData->positionAt(cpos));
}
*out++ = static_cast<unsigned int>(v.first->second);
@@ -102,7 +102,7 @@ Water::tick(TickDuration dur)
}
void
-Water::render(const SceneShader & shader) const
+Water::render(const SceneShader & shader, const Frustum &) const
{
shader.water.use(waveCycle);
water->bind();
diff --git a/game/water.h b/game/water.h
index ceb7bd2..07d9ae1 100644
--- a/game/water.h
+++ b/game/water.h
@@ -4,19 +4,19 @@
#include "collection.h"
#include "config/types.h"
#include "game/worldobject.h"
-#include <gfx/models/mesh.h>
-#include <gfx/renderable.h>
+#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
+#include "gfx/renderable.h"
#include <memory>
class SceneShader;
-class Texture;
class GeoData;
class Water : public WorldObject, public Renderable {
public:
explicit Water(std::shared_ptr<GeoData>);
- void render(const SceneShader & shader) const override;
+ void render(const SceneShader & shader, const Frustum &) const override;
void tick(TickDuration) override;
float waveCycle {0.F};
@@ -29,6 +29,6 @@ private:
void generateMeshes();
std::shared_ptr<GeoData> geoData;
- Collection<MeshT<Vertex>, false> meshes;
- std::shared_ptr<Texture> water;
+ UniqueCollection<MeshT<Vertex>> meshes;
+ Texture::Ptr water;
};