summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jamroot.jam1
-rw-r--r--application/main.cpp13
-rw-r--r--assetFactory/cylinder.cpp2
-rw-r--r--assetFactory/factoryMesh.cpp11
-rw-r--r--assetFactory/mutation.cpp5
-rw-r--r--config/types.h1
-rw-r--r--game/environment.cpp93
-rw-r--r--game/environment.h18
-rw-r--r--game/gamestate.cpp3
-rw-r--r--game/gamestate.h2
-rw-r--r--game/geoData.cpp411
-rw-r--r--game/geoData.h106
-rw-r--r--game/network/link.cpp6
-rw-r--r--game/network/network.cpp10
-rw-r--r--game/network/network.h2
-rw-r--r--game/network/rail.cpp12
-rw-r--r--game/scenary/foliage.cpp27
-rw-r--r--game/scenary/foliage.h11
-rw-r--r--game/scenary/plant.cpp3
-rw-r--r--game/terrain.cpp1
-rw-r--r--game/terrain.h2
-rw-r--r--game/vehicles/linkHistory.cpp18
-rw-r--r--game/vehicles/train.cpp10
-rw-r--r--game/vehicles/train.h2
-rw-r--r--game/vehicles/vehicle.cpp2
-rw-r--r--game/vehicles/vehicle.h2
-rw-r--r--gfx/followCameraController.cpp2
-rw-r--r--gfx/gl/program.h6
-rw-r--r--gfx/gl/sceneProvider.cpp2
-rw-r--r--gfx/gl/sceneRenderer.cpp28
-rw-r--r--gfx/gl/sceneRenderer.h3
-rw-r--r--gfx/gl/sceneShader.cpp2
-rw-r--r--gfx/gl/shader.cpp4
-rw-r--r--gfx/gl/shaders/directionalLight.fs25
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.fs16
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.gs36
-rw-r--r--gfx/gl/shaders/shadowDynamicPointStencil.vs17
-rw-r--r--gfx/gl/shaders/shadowStencil.fs18
-rw-r--r--gfx/gl/shaders/shadowStencil.gs28
-rw-r--r--gfx/gl/shaders/shadowStencil.vs20
-rw-r--r--gfx/gl/shadowMapper.cpp80
-rw-r--r--gfx/gl/shadowMapper.h21
-rw-r--r--gfx/gl/shadowStenciller.cpp74
-rw-r--r--gfx/gl/shadowStenciller.h27
-rw-r--r--gfx/lightDirection.cpp12
-rw-r--r--gfx/lightDirection.h39
-rw-r--r--gfx/models/mesh.cpp28
-rw-r--r--gfx/models/mesh.h30
-rw-r--r--gfx/models/texture.cpp9
-rw-r--r--gfx/models/texture.h2
-rw-r--r--gfx/renderable.cpp5
-rw-r--r--gfx/renderable.h3
-rw-r--r--lib/chronology.cpp12
-rw-r--r--lib/chronology.h2
-rw-r--r--lib/filesystem.cpp2
-rw-r--r--lib/filesystem.h2
-rw-r--r--lib/jsonParse-persistence.h3
-rw-r--r--lib/maths.cpp101
-rw-r--r--lib/maths.h381
-rw-r--r--lib/stream_support.h38
-rw-r--r--lib/triangle.h108
-rw-r--r--test/Jamfile.jam1
-rw-r--r--test/test-assetFactory.cpp5
-rw-r--r--test/test-environment.cpp63
-rw-r--r--test/test-geoData.cpp19
-rw-r--r--test/test-maths.cpp18
-rw-r--r--test/test-render.cpp51
-rw-r--r--test/testRenderOutput.h2
m---------thirdparty/ctre0
-rw-r--r--ui/gameMainWindow.cpp8
-rw-r--r--ui/manualCameraController.cpp2
71 files changed, 1463 insertions, 666 deletions
diff --git a/Jamroot.jam b/Jamroot.jam
index b07dfdd..8587aaa 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -1,4 +1,3 @@
-using gcc ;
using pkg-config ;
import pkg-config ;
import testing ;
diff --git a/application/main.cpp b/application/main.cpp
index d58cf6d..db42a63 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -24,6 +24,7 @@
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp> // IWYU pragma: keep
#include <memory>
+#include <random>
#include <special_members.h>
#include <ui/applicationBase.h>
#include <ui/gameMainWindow.h>
@@ -79,10 +80,18 @@ public:
&train->orders, l3->ends[1], l3->length, rl->findNodeAt({-1100000, -450000, 15000}));
train->currentActivity = train->orders.current()->createActivity();
- auto foliage = std::dynamic_pointer_cast<Foliage>(assets.at("Tree-01-1"));
+ std::random_device randomdev {};
+ std::uniform_real_distribution<Angle> rotationDistribution {0, two_pi};
+ std::uniform_int_distribution<GlobalDistance> positionOffsetDistribution {-1500, +1500};
+ std::uniform_int_distribution<int> treeDistribution {1, 3};
+ std::uniform_int_distribution<int> treeVariantDistribution {1, 4};
for (auto x = 311000000; x < 311830000; x += 5000) {
for (auto y = 491100000; y < 491130000; y += 5000) {
- world.create<Plant>(foliage, Location {geoData->positionAt({{x, y}})});
+ world.create<Plant>(std::dynamic_pointer_cast<Foliage>(assets.at(std::format("Tree-{:#02}-{}",
+ treeDistribution(randomdev), treeVariantDistribution(randomdev)))),
+ Location {geoData->positionAt({{x + positionOffsetDistribution(randomdev),
+ y + positionOffsetDistribution(randomdev)}}),
+ {0, rotationDistribution(randomdev), 0}});
}
}
}
diff --git a/assetFactory/cylinder.cpp b/assetFactory/cylinder.cpp
index f41bfd4..432fb16 100644
--- a/assetFactory/cylinder.cpp
+++ b/assetFactory/cylinder.cpp
@@ -12,7 +12,7 @@ Cylinder::createMesh(ModelFactoryMesh & mesh, Scale3D lodf) const
// Generate 2D circumference points
std::vector<RelativePosition2D> circumference(P);
std::generate(circumference.begin(), circumference.end(), [a = 0.F, step]() mutable {
- return sincosf(a += step) * .5F;
+ return sincos(a += step) * .5F;
});
CreatedFaces surface;
diff --git a/assetFactory/factoryMesh.cpp b/assetFactory/factoryMesh.cpp
index bf4706e..eb9d525 100644
--- a/assetFactory/factoryMesh.cpp
+++ b/assetFactory/factoryMesh.cpp
@@ -25,11 +25,12 @@ FactoryMesh::createMesh() const
std::vector<unsigned int> faceIndices;
for (const auto & heh : mesh.fh_range(face)) {
const auto & vertex = mesh.to_vertex_handle(heh);
- const auto & textureUV = mesh.texcoord2D(heh);
- const auto & point = mesh.point(vertex);
- const auto & normal = useVertexNormals ? mesh.property(mesh.vertex_normals_pph(), vertex)
- : mesh.property(mesh.face_normals_pph(), face);
- Vertex outVertex {point * 1000.F, textureUV, normal, colour, material};
+ Vertex outVertex {.pos = mesh.point(vertex) * 1000.F,
+ .texCoord = mesh.texcoord2D(heh),
+ .normal = useVertexNormals ? mesh.property(mesh.vertex_normals_pph(), vertex)
+ : mesh.property(mesh.face_normals_pph(), face),
+ .colour = colour,
+ .material = material};
if (const auto existingItr = std::find(vertices.rbegin(), vertices.rend(), outVertex);
existingItr != vertices.rend()) {
faceIndices.push_back(static_cast<unsigned int>(std::distance(existingItr, vertices.rend()) - 1));
diff --git a/assetFactory/mutation.cpp b/assetFactory/mutation.cpp
index 6695dff..2ab2a76 100644
--- a/assetFactory/mutation.cpp
+++ b/assetFactory/mutation.cpp
@@ -1,12 +1,11 @@
#include "mutation.h"
-#include <algorithm>
#include <glm/gtx/transform.hpp>
#include <maths.h>
Mutation::Matrix
Mutation::getMatrix() const
{
- return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr(rotation)
+ return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr<4>(rotation)
* glm::scale(glm::identity<Matrix>(), scale);
}
@@ -19,7 +18,7 @@ Mutation::getDeformationMatrix() const
Mutation::Matrix
Mutation::getLocationMatrix() const
{
- return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr(rotation);
+ return glm::translate(glm::identity<Matrix>(), position) * rotate_ypr<4>(rotation);
}
bool
diff --git a/config/types.h b/config/types.h
index 081530d..c501f41 100644
--- a/config/types.h
+++ b/config/types.h
@@ -42,6 +42,7 @@ using Normal3D = Normal<3>;
using Rotation2D = Rotation<2>;
using Rotation3D = Rotation<3>;
using TextureRelCoord = glm::vec<2, float>;
+using TextureDimensions = glm::vec<3, GLsizei>;
using TextureRelRegion = glm::vec<4, float>;
using TextureAbsCoord = glm::vec<2, GLsizei>;
using TextureAbsRegion = glm::vec<4, GLsizei>;
diff --git a/game/environment.cpp b/game/environment.cpp
new file mode 100644
index 0000000..acb4f21
--- /dev/null
+++ b/game/environment.cpp
@@ -0,0 +1,93 @@
+#include "environment.h"
+#include "gfx/lightDirection.h"
+#include <chronology.h>
+#include <gfx/gl/sceneRenderer.h>
+
+Environment::Environment() : worldTime {"2024-01-01T12:00:00"_time_t} { }
+
+void
+Environment::tick(TickDuration)
+{
+ worldTime += 50;
+}
+
+void
+Environment::render(const SceneRenderer & renderer, const SceneProvider & scene) const
+{
+ constexpr RGB baseAmbient {0.1F}, baseDirectional {0.0F};
+ constexpr RGB relativeAmbient {0.3F, 0.3F, 0.4F}, relativeDirectional {0.6F, 0.6F, 0.5F};
+
+ const LightDirection sunPos = getSunPos({}, worldTime);
+ const auto ambient = baseAmbient + relativeAmbient * sunPos.ambient();
+ const auto directional = baseDirectional + relativeDirectional * sunPos.directional();
+
+ renderer.setAmbientLight(ambient);
+ renderer.setDirectionalLight(directional, sunPos, scene);
+}
+
+// Based on the C++ code published at https://www.psa.es/sdg/sunpos.htm
+// Linked from https://www.pveducation.org/pvcdrom/properties-of-sunlight/suns-position-to-high-accuracy
+Direction2D
+Environment::getSunPos(const Direction2D position, const time_t time)
+{
+ auto & longitude = position.x;
+ auto & latitude = position.y;
+ using std::acos;
+ using std::asin;
+ using std::atan2;
+ using std::cos;
+ using std::floor;
+ using std::sin;
+ using std::tan;
+ static const auto JD2451545 = "2000-01-01T12:00:00"_time_t;
+
+ // Calculate difference in days between the current Julian Day
+ // and JD 2451545.0, which is noon 1 January 2000 Universal Time
+ // Calculate time of the day in UT decimal hours
+ const auto dDecimalHours = static_cast<float>(time % 86400) / 3600.F;
+ const auto dElapsedJulianDays = static_cast<float>(time - JD2451545) / 86400.F;
+
+ // Calculate ecliptic coordinates (ecliptic longitude and obliquity of the
+ // ecliptic in radians but without limiting the angle to be less than 2*Pi
+ // (i.e., the result may be greater than 2*Pi)
+ const auto dOmega = 2.1429F - 0.0010394594F * dElapsedJulianDays;
+ const auto dMeanLongitude = 4.8950630F + 0.017202791698F * dElapsedJulianDays; // Radians
+ const auto dMeanAnomaly = 6.2400600F + 0.0172019699F * dElapsedJulianDays;
+ const auto dEclipticLongitude = dMeanLongitude + 0.03341607F * sin(dMeanAnomaly)
+ + 0.00034894F * sin(2 * dMeanAnomaly) - 0.0001134F - 0.0000203F * sin(dOmega);
+ const auto dEclipticObliquity = 0.4090928F - 6.2140e-9F * dElapsedJulianDays + 0.0000396F * cos(dOmega);
+
+ // Calculate celestial coordinates ( right ascension and declination ) in radians
+ // but without limiting the angle to be less than 2*Pi (i.e., the result may be
+ // greater than 2*Pi)
+ const auto dSin_EclipticLongitude = sin(dEclipticLongitude);
+ const auto dY = cos(dEclipticObliquity) * dSin_EclipticLongitude;
+ const auto dX = cos(dEclipticLongitude);
+ auto dRightAscension = atan2(dY, dX);
+ if (dRightAscension < 0) {
+ dRightAscension = dRightAscension + two_pi;
+ }
+ const auto dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude);
+
+ // Calculate local coordinates ( azimuth and zenith angle ) in degrees
+ const auto dGreenwichMeanSiderealTime = 6.6974243242F + 0.0657098283F * dElapsedJulianDays + dDecimalHours;
+ const auto dLocalMeanSiderealTime
+ = (dGreenwichMeanSiderealTime * 15.0F + (longitude / degreesToRads)) * degreesToRads;
+ const auto dHourAngle = dLocalMeanSiderealTime - dRightAscension;
+ const auto dLatitudeInRadians = latitude;
+ const auto dCos_Latitude = cos(dLatitudeInRadians);
+ const auto dSin_Latitude = sin(dLatitudeInRadians);
+ const auto dCos_HourAngle = cos(dHourAngle);
+ Direction2D udtSunCoordinates;
+ udtSunCoordinates.y
+ = (acos(dCos_Latitude * dCos_HourAngle * cos(dDeclination) + sin(dDeclination) * dSin_Latitude));
+ udtSunCoordinates.x = atan2(-sin(dHourAngle), tan(dDeclination) * dCos_Latitude - dSin_Latitude * dCos_HourAngle);
+ if (udtSunCoordinates.x < 0) {
+ udtSunCoordinates.x = udtSunCoordinates.x + two_pi;
+ }
+ // Parallax Correction
+ const auto dParallax = (earthMeanRadius / astronomicalUnit) * sin(udtSunCoordinates.y);
+ udtSunCoordinates.y = half_pi - (udtSunCoordinates.y + dParallax);
+
+ return udtSunCoordinates;
+}
diff --git a/game/environment.h b/game/environment.h
new file mode 100644
index 0000000..a6f3036
--- /dev/null
+++ b/game/environment.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "config/types.h"
+#include "worldobject.h"
+
+class SceneRenderer;
+class SceneProvider;
+
+class Environment : public WorldObject {
+public:
+ Environment();
+ void tick(TickDuration elapsed) override;
+ void render(const SceneRenderer &, const SceneProvider &) const;
+ static Direction2D getSunPos(const Direction2D position, const time_t time);
+
+private:
+ time_t worldTime;
+};
diff --git a/game/gamestate.cpp b/game/gamestate.cpp
index fcd4248..910e8a7 100644
--- a/game/gamestate.cpp
+++ b/game/gamestate.cpp
@@ -1,4 +1,5 @@
#include "gamestate.h"
+#include "environment.h"
#include <cassert>
GameState * gameState {nullptr};
@@ -7,6 +8,8 @@ GameState::GameState()
{
assert(!gameState);
gameState = this;
+
+ environment = world.create<Environment>();
}
GameState::~GameState()
diff --git a/game/gamestate.h b/game/gamestate.h
index f07f844..892aa69 100644
--- a/game/gamestate.h
+++ b/game/gamestate.h
@@ -7,6 +7,7 @@
class WorldObject;
class GeoData;
+class Environment;
class GameState {
public:
@@ -17,6 +18,7 @@ public:
Collection<WorldObject> world;
std::shared_ptr<GeoData> geoData;
+ std::shared_ptr<Environment> environment;
AssetFactory::Assets assets;
};
diff --git a/game/geoData.cpp b/game/geoData.cpp
index 72aa056..a5fc4ef 100644
--- a/game/geoData.cpp
+++ b/game/geoData.cpp
@@ -66,7 +66,7 @@ GeoData::loadFromAsciiGrid(const std::filesystem::path & input)
});
}
}
- mesh.update_vertex_normals_only();
+ mesh.updateAllVertexNormals();
return mesh;
};
@@ -105,7 +105,7 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan
}
}
- mesh.update_vertex_normals_only();
+ mesh.updateAllVertexNormals();
return mesh;
}
@@ -361,7 +361,7 @@ GeoData::findBoundaryStart() const
[[nodiscard]] RelativePosition3D
GeoData::difference(const HalfedgeHandle heh) const
{
- return point(to_vertex_handle(heh)) - point(from_vertex_handle(heh));
+ return ::difference(point(to_vertex_handle(heh)), point(from_vertex_handle(heh)));
}
[[nodiscard]] RelativeDistance
@@ -377,23 +377,28 @@ GeoData::centre(const HalfedgeHandle heh) const
}
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);
});
}
+void
+GeoData::updateVertexNormal(VertexHandle vertex)
+{
+ Normal3D n;
+ calc_vertex_normal_correct(vertex, n);
+ set_normal(vertex, glm::normalize(n));
+}
+
bool
GeoData::triangleOverlapsTriangle(const Triangle<2> & a, const Triangle<2> & b)
{
@@ -411,265 +416,161 @@ GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b)
}
void
-GeoData::split(FaceHandle _fh)
-{
- // 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);
- }
-
- if (split2) {
- split(eh2, v2);
- }
-
- // 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)
+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;
}
+ const auto stripMinMax = std::ranges::minmax(triangleStrip, {}, &GlobalPosition3D::z);
+ lowerExtent.z = std::min(upperExtent.z, stripMinMax.min.z);
+ upperExtent.z = std::max(upperExtent.z, stripMinMax.max.z);
- const auto initialVertexCount = static_cast<unsigned int>(n_vertices());
+ const auto vertexDistFrom = [this](GlobalPosition2D p) {
+ return [p, this](const VertexHandle v) {
+ return std::make_pair(v, glm::length(::difference(p, this->point(v).xy())));
+ };
+ };
+
+ std::set<VertexHandle> newOrChangedVerts;
+ auto addVertexForNormalUpdate = [this, &newOrChangedVerts](const VertexHandle vertex) {
+ newOrChangedVerts.emplace(vertex);
+ std::ranges::copy(vv_range(vertex), std::inserter(newOrChangedVerts, newOrChangedVerts.end()));
+ };
- // Create new vertices
+ // New vertices for each vertex in triangleStrip
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<FaceHandle> newFaces;
- std::copy_if(FaceIter {*this, FaceHandle {initialFaceCount}, true}, faces_end(), std::back_inserter(newFaces),
- [this](FaceHandle fh) {
- return !this->status(fh).deleted();
+ std::transform(triangleStrip.begin(), triangleStrip.end(), std::back_inserter(newVerts),
+ [this, &newVerts, &vertexDistFrom, &opts](const auto tsPoint) {
+ const auto face = findPoint(tsPoint);
+ if (const auto nearest = std::ranges::min(std::views::iota(fv_begin(face), fv_end(face))
+ | std::views::transform(vertexDistFrom(tsPoint)),
+ {}, &std::pair<VertexHandle, float>::second);
+ nearest.second < opts.nearNodeTolerance && !std::ranges::contains(newVerts, nearest.first)) {
+ point(nearest.first) = tsPoint;
+ return nearest.first;
+ }
+ return split(face, tsPoint);
});
-
- // Extrude corners
- struct Extrusion {
- VertexHandle boundaryVertex, extrusionVertex;
- Direction3D lowerLimit, upperLimit;
+ std::ranges::for_each(newVerts, addVertexForNormalUpdate);
+
+ // Create temporary triangles from triangleStrip
+ std::vector<Triangle<3>> strip;
+ std::transform(
+ strip_begin(triangleStrip), strip_end(triangleStrip), std::back_inserter(strip), [](const auto & newVert) {
+ const auto [a, b, c] = newVert;
+ return Triangle<3> {a, b, c};
+ });
+ auto getTriangle = [&strip](const auto point) -> const Triangle<3> * {
+ if (const auto t = std::ranges::find_if(strip,
+ [point](const auto & triangle) {
+ return triangleContainsPoint(point, triangle);
+ });
+ 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;
- }
- }
-
- 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));
- }
- 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)));
- }
- }
- else {
- // Single tangent bisecting the difference
- addExtrusionFor(normalize(e0 + e1) / sinf((arc.length() - pi) / 2.F));
- }
- },
- *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);
+ // Cut along each edge of triangleStrip AB, AC, BC, BD, CD, CE etc
+ std::map<VertexHandle, const Triangle<3> *> boundaryTriangles;
+ auto doBoundaryPart = [this, &boundaryTriangles, &newVerts, &vertexDistFrom, &opts, &addVertexForNormalUpdate](
+ VertexHandle start, VertexHandle end, const Triangle<3> & triangle) {
+ boundaryTriangles.emplace(start, &triangle);
+ const auto endPoint = point(end);
+ while (!std::ranges::contains(vv_range(start), end)
+ && std::ranges::any_of(voh_range(start), [&](const auto & outHalf) {
+ const auto next = next_halfedge_handle(outHalf);
+ const auto startPoint = point(start);
+ const auto nexts = std::array {from_vertex_handle(next), to_vertex_handle(next)};
+ const auto nextPoints = nexts | std::views::transform([this](const auto v) {
+ return std::make_pair(v, this->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 nextDist
+ = std::ranges::min(nexts | std::views::transform(vertexDistFrom(*intersection)),
+ {}, &std::pair<VertexHandle, float>::second);
+ nextDist.second < opts.nearNodeTolerance
+ && !boundaryTriangles.contains(nextDist.first)
+ && !std::ranges::contains(newVerts, nextDist.first)) {
+ start = nextDist.first;
+ point(start) = positionOnTriangle(*intersection, triangle);
+ }
+ else {
+ start = split(edge_handle(next), positionOnTriangle(*intersection, triangle));
+ }
+ addVertexForNormalUpdate(start);
+ boundaryTriangles.emplace(start, &triangle);
+ return true;
+ }
+ }
+ return false;
+ })) { }
+ };
+ auto doBoundary = [&doBoundaryPart, triangle = strip.begin()](const auto & verts) mutable {
+ const auto & [a, b, c] = verts;
+ doBoundaryPart(a, b, *triangle);
+ doBoundaryPart(a, c, *triangle);
+ triangle++;
+ };
+ std::ranges::for_each(newVerts | std::views::adjacent<3>, doBoundary);
+ doBoundaryPart(*++newVerts.rbegin(), newVerts.back(), *strip.rbegin());
+
+ std::set<HalfedgeHandle> done;
+ std::set<HalfedgeHandle> todo;
+ auto todoOutHalfEdges = [&todo, &done, this](const VertexHandle v) {
+ std::copy_if(voh_begin(v), voh_end(v), std::inserter(todo, todo.end()), [&done](const auto & h) {
+ return !done.contains(h);
+ });
+ };
+ std::ranges::for_each(newVerts, todoOutHalfEdges);
+ while (!todo.empty()) {
+ const auto heh = todo.extract(todo.begin()).value();
+ const auto fromVertex = from_vertex_handle(heh);
+ const auto toVertex = to_vertex_handle(heh);
+ const auto & fromPoint = point(fromVertex);
+ auto & toPoint = point(toVertex);
+ auto toTriangle = getTriangle(toPoint);
+ if (!toTriangle) {
+ if (const auto boundaryVertex = boundaryTriangles.find(toVertex);
+ boundaryVertex != boundaryTriangles.end()) {
+ toTriangle = boundaryVertex->second;
+ }
}
- out.emplace_back(second.extrusionVertex);
- if (first.boundaryVertex != second.boundaryVertex) {
- out.emplace_back(second.boundaryVertex);
+ if (toTriangle) { // point within the new strip, adjust vertically by triangle
+ toPoint.z = positionOnTriangle(toPoint, *toTriangle).z;
+ addVertexForNormalUpdate(toVertex);
+ todoOutHalfEdges(toVertex);
}
+ else if (!toTriangle) { // point without the new strip, adjust vertically by limit
+ const auto maxOffset = static_cast<GlobalDistance>(opts.maxSlope * glm::length(difference(heh).xy()));
+ const auto newHeight = std::clamp(toPoint.z, fromPoint.z - maxOffset, fromPoint.z + maxOffset);
+ if (newHeight != toPoint.z) {
+ toPoint.z = newHeight;
+ addVertexForNormalUpdate(toVertex);
+ std::copy_if(voh_begin(toVertex), voh_end(toVertex), std::inserter(todo, todo.end()),
+ [this, &boundaryTriangles](const auto & heh) {
+ return !boundaryTriangles.contains(to_vertex_handle(heh));
+ });
+ }
+ }
+ done.insert(heh);
}
- // 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));
- }
- });
-
- delete_face(face, false);
+ auto surfaceStripWalk = [this, &getTriangle, &opts](const auto & surfaceStripWalk, const auto & face) -> void {
+ if (!property(surface, face)) {
+ property(surface, face) = &opts.surface;
+ std::ranges::for_each(
+ ff_range(face), [this, &getTriangle, &surfaceStripWalk](const auto & adjacentFaceHandle) {
+ if (getTriangle(this->triangle<2>(adjacentFaceHandle).centroid())) {
+ surfaceStripWalk(surfaceStripWalk, adjacentFaceHandle);
+ }
+ });
}
};
- removeOld(removeOld, findPoint(triangleStrip.front()));
-
- std::for_each(boundaryFaces.begin(), boundaryFaces.end(), [&](auto & boundaryFace) {
- std::reverse(boundaryFace.begin(), boundaryFace.end());
- add_face(boundaryFace);
- });
-
- // Tidy up
- update_vertex_normals_only(VertexIter {*this, vertex_handle(initialVertexCount), true});
+ surfaceStripWalk(surfaceStripWalk, findPoint(strip.front().centroid()));
- std::for_each(newFaces.begin(), newFaces.end(), [&newFaceSurface, this](const auto fh) {
- property(surface, fh) = &newFaceSurface;
- });
+ updateAllVertexNormals(newOrChangedVerts);
}
diff --git a/game/geoData.h b/game/geoData.h
index ed1734c..79924d3 100644
--- a/game/geoData.h
+++ b/game/geoData.h
@@ -4,6 +4,7 @@
#include "config/types.h"
#include "ray.h"
#include "surface.h"
+#include "triangle.h"
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <filesystem>
#include <glm/vec2.hpp>
@@ -52,76 +53,7 @@ public:
mutable FaceHandle _face {};
};
- 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);
- }
- };
+ template<glm::length_t Dim> using Triangle = ::Triangle<Dim, GlobalDistance>;
[[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const;
[[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const;
@@ -142,7 +74,16 @@ public:
[[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const;
- void setHeights(const std::span<const GlobalPosition3D> triangleStrip, const Surface &);
+ struct SetHeightsOpts {
+ static constexpr auto DEFAULT_NEAR_NODE_TOLERANACE = 500.F;
+ static constexpr auto DEFAULT_MAX_SLOPE = 0.5F;
+
+ const Surface & surface;
+ RelativeDistance nearNodeTolerance = DEFAULT_NEAR_NODE_TOLERANACE;
+ RelativeDistance maxSlope = DEFAULT_MAX_SLOPE;
+ };
+
+ void setHeights(std::span<const GlobalPosition3D> triangleStrip, const SetHeightsOpts &);
[[nodiscard]] auto
getExtents() const
@@ -160,9 +101,13 @@ public:
protected:
template<glm::length_t Dim>
[[nodiscard]] Triangle<Dim>
- triangle(FaceHandle f) const
+ triangle(FaceHandle face) const
{
- return {this, fv_range(f)};
+ Triangle<Dim> triangle;
+ std::ranges::transform(fv_range(face), triangle.begin(), [this](auto vertex) {
+ return point(vertex);
+ });
+ return triangle;
}
[[nodiscard]] static bool triangleContainsPoint(const GlobalPosition2D, const Triangle<2> &);
@@ -172,21 +117,12 @@ protected:
[[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);
+ void updateAllVertexNormals();
+ template<std::ranges::range R> void updateAllVertexNormals(const R &);
+ void updateVertexNormal(VertexHandle);
private:
GlobalPosition3D lowerExtent {}, upperExtent {};
diff --git a/game/network/link.cpp b/game/network/link.cpp
index 248fe7d..79af92a 100644
--- a/game/network/link.cpp
+++ b/game/network/link.cpp
@@ -46,12 +46,12 @@ LinkCurve::positionAt(float dist, unsigned char start) const
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 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})};
+ const auto pitch {vector_pitch(difference(es.second->pos, es.first->pos) / length)};
return Location {GlobalPosition3D(relPos + relClimb) + centreBase, {pitch, normalize(ang + dirOffset[start]), 0}};
}
@@ -69,7 +69,7 @@ LinkCurve::intersectRay(const Ray<GlobalPosition3D> & ray) const
points.reserve(segCount);
for (std::remove_const_t<decltype(step)> swing = {arc.first, centreBase.z - e0p.z}; 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);
}
diff --git a/game/network/network.cpp b/game/network/network.cpp
index 65b2a62..6ba3ed6 100644
--- a/game/network/network.cpp
+++ b/game/network/network.cpp
@@ -95,7 +95,7 @@ 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 diff = difference(end, start);
const auto vy {vector_yaw(diff)};
const auto dir = pi + startDir;
const auto flatStart {start.xy()}, flatEnd {end.xy()};
@@ -121,16 +121,16 @@ Network::genCurveDef(const GlobalPosition3D & start, const GlobalPosition3D & en
};
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 c1 = flatStart + (sincos(startDir + half_pi) * radius);
+ const auto c2 = flatEnd + (sincos(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 c1 = flatStart + (sincos(startDir - half_pi) * radius);
+ const auto c2 = flatEnd + (sincos(endDir - half_pi) * radius);
const auto mid = (c1 + c2) / 2;
const auto midh = mid || midheight(mid);
return {{midh, start, c1}, {midh, end, c2}};
diff --git a/game/network/network.h b/game/network/network.h
index ca17581..be0900b 100644
--- a/game/network/network.h
+++ b/game/network/network.h
@@ -16,7 +16,7 @@
class SceneShader;
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 {
diff --git a/game/network/rail.cpp b/game/network/rail.cpp
index fd07ace..6f04070 100644
--- a/game/network/rail.cpp
+++ b/game/network/rail.cpp
@@ -32,7 +32,7 @@ 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()};
@@ -45,8 +45,8 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
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 c1 = flatStart + (sincos(dir + half_pi) * radius);
+ const auto c2 = flatEnd + (sincos(dir2 + half_pi) * radius);
const auto mid = (c1 + c2) / 2;
const auto midh = mid || midheight(mid);
addLink<RailLinkCurve>(start, midh, c1);
@@ -54,15 +54,15 @@ RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end)
}
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 c1 = flatStart + (sincos(dir - half_pi) * radius);
+ const auto c2 = flatEnd + (sincos(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);
}
}
- const auto diff {end - start};
+ const auto diff = difference(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)};
diff --git a/game/scenary/foliage.cpp b/game/scenary/foliage.cpp
index 73d285f..a0ec576 100644
--- a/game/scenary/foliage.cpp
+++ b/game/scenary/foliage.cpp
@@ -2,8 +2,6 @@
#include "gfx/gl/sceneShader.h"
#include "gfx/gl/shadowMapper.h"
#include "gfx/gl/vertexArrayObject.h"
-#include "gfx/models/texture.h"
-#include "location.h"
bool
Foliage::persist(Persistence::PersistenceStore & store)
@@ -16,7 +14,18 @@ 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
@@ -35,10 +44,12 @@ void
Foliage::shadows(const ShadowMapper & mapper) const
{
if (const auto count = instances.size()) {
- mapper.dynamicPointInstWithTextures.use();
- if (texture) {
- texture->bind(GL_TEXTURE3);
- }
- bodyMesh->DrawInstanced(instanceVAO, static_cast<GLsizei>(count));
+ const auto dimensions = bodyMesh->getDimensions();
+ mapper.stencilShadowProgram.use(dimensions.centre, dimensions.size);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowStencil);
+ glBindVertexArray(instancePointVAO);
+ glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(count));
+ glBindVertexArray(0);
}
}
diff --git a/game/scenary/foliage.h b/game/scenary/foliage.h
index 0a4261c..5da63f0 100644
--- a/game/scenary/foliage.h
+++ b/game/scenary/foliage.h
@@ -2,6 +2,7 @@
#include "assetFactory/asset.h"
#include "gfx/gl/instanceVertices.h"
+#include "gfx/gl/shadowStenciller.h"
#include "gfx/models/texture.h"
#include "gfx/renderable.h"
@@ -13,12 +14,20 @@ class Foliage : public Asset, public Renderable, public StdTypeDefs<Foliage> {
Mesh::Ptr bodyMesh;
Texture::Ptr texture;
glVertexArray instanceVAO;
+ glVertexArray instancePointVAO;
public:
- using LocationVertex = std::pair<glm::mat3, GlobalPosition3D>;
+ 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 updateStencil(const ShadowStenciller &) const override;
+ glTexture shadowStencil = ShadowStenciller::createStencilTexture(256, 256);
protected:
friend Persistence::SelectionPtrBase<std::shared_ptr<Foliage>>;
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..bb8e3ce 100644
--- a/game/terrain.cpp
+++ b/game/terrain.cpp
@@ -33,6 +33,7 @@ VertexArrayObject::addAttribsFor<Terrain::Vertex>(const GLuint arrayBuffer, cons
void
Terrain::generateMeshes()
{
+ meshes.removeAll();
std::vector<unsigned int> indices;
indices.reserve(geoData->n_faces() * 3);
std::vector<Vertex> vertices;
diff --git a/game/terrain.h b/game/terrain.h
index 1c79d19..7d074cf 100644
--- a/game/terrain.h
+++ b/game/terrain.h
@@ -27,9 +27,9 @@ public:
RGB colourBias;
};
-private:
void generateMeshes();
+private:
std::shared_ptr<GeoData> geoData;
Collection<MeshT<Vertex>, false> meshes;
Texture::Ptr grass;
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/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..4933347 100644
--- a/game/vehicles/train.h
+++ b/game/vehicles/train.h
@@ -17,7 +17,7 @@ template<typename> class Ray;
class Train : public Vehicle, public Collection<RailVehicle, false>, 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/gfx/followCameraController.cpp b/gfx/followCameraController.cpp
index 52dfb35..cf6da34 100644
--- a/gfx/followCameraController.cpp
+++ b/gfx/followCameraController.cpp
@@ -24,7 +24,7 @@ FollowCameraController::updateCamera(Camera * camera) const
break;
case Mode::Ride:
- camera->setView(pos + (up * 4.8F), -sincosf(rot.y) || 0.F);
+ camera->setView(pos + (up * 4.8F), -sincos(rot.y) || 0.F);
break;
case Mode::ISO:
diff --git a/gfx/gl/program.h b/gfx/gl/program.h
index c89a128..20be1aa 100644
--- a/gfx/gl/program.h
+++ b/gfx/gl/program.h
@@ -33,6 +33,12 @@ public:
return location;
}
+ explicit
+ operator bool() const
+ {
+ return location >= 0;
+ }
+
protected:
GLint location;
};
diff --git a/gfx/gl/sceneProvider.cpp b/gfx/gl/sceneProvider.cpp
index 2e8604c..4e271db 100644
--- a/gfx/gl/sceneProvider.cpp
+++ b/gfx/gl/sceneProvider.cpp
@@ -5,7 +5,7 @@ void
SceneProvider::environment(const SceneShader &, const SceneRenderer & renderer) const
{
renderer.setAmbientLight({0.5F, 0.5F, 0.5F});
- renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {-1, 1, -1}, *this);
+ renderer.setDirectionalLight({0.6F, 0.6F, 0.6F}, {{-quarter_pi, -quarter_pi}}, *this);
}
void
diff --git a/gfx/gl/sceneRenderer.cpp b/gfx/gl/sceneRenderer.cpp
index e0938f2..b2a7d78 100644
--- a/gfx/gl/sceneRenderer.cpp
+++ b/gfx/gl/sceneRenderer.cpp
@@ -62,7 +62,7 @@ SceneRenderer::render(const SceneProvider & scene) const
shader.setViewProjection(camera.getPosition(), camera.getViewProjection());
glViewport(0, 0, size.x, size.y);
- // Geometry pass
+ // Geometry/colour pass - writes albedo, normal and position textures
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
@@ -73,7 +73,13 @@ SceneRenderer::render(const SceneProvider & scene) const
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
scene.content(shader);
- // Illumination pass
+ // Environment pass -
+ // * ambient - clears illumination texture - see setAmbientLight
+ // * directional - updates shadowMapper, reads normal and position, writes illumination - see setDirectionalLight
+ scene.environment(shader, *this);
+
+ // Scene lights pass -
+ // * per light - reads normal and position, writes illumination
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
glBlendFunc(GL_ONE, GL_ONE);
glActiveTexture(GL_TEXTURE0);
@@ -82,11 +88,10 @@ SceneRenderer::render(const SceneProvider & scene) const
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
- scene.environment(shader, *this);
glDisable(GL_DEPTH_TEST);
scene.lights(shader);
- // Lighting pass
+ // Composition pass - reads albedo and illumination, writes output
glBindFramebuffer(GL_FRAMEBUFFER, output);
glViewport(0, 0, size.x, size.y);
glCullFace(GL_BACK);
@@ -109,14 +114,22 @@ SceneRenderer::setAmbientLight(const RGB & colour) const
}
void
-SceneRenderer::setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider & scene) const
+SceneRenderer::setDirectionalLight(
+ const RGB & colour, const LightDirection & direction, const SceneProvider & scene) const
{
if (colour.r > 0 || colour.g > 0 || colour.b > 0) {
const auto lvp = shadowMapper.update(scene, direction, camera);
glBindFramebuffer(GL_FRAMEBUFFER, gBufferIll);
+ glBlendFunc(GL_ONE, GL_ONE);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, gPosition);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, gNormal);
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, shadowMapper);
glViewport(0, 0, size.x, size.y);
dirLight.use();
- dirLight.setDirectionalLight(colour, direction, camera.getPosition(), lvp);
+ dirLight.setDirectionalLight(colour, direction.vector(), camera.getPosition(), lvp);
renderQuad();
}
}
@@ -142,8 +155,7 @@ SceneRenderer::DirectionalLightProgram::setDirectionalLight(
return toTextureSpaceMat * m;
};
glUniform(colourLoc, c);
- const auto nd = glm::normalize(d);
- glUniform(directionLoc, nd);
+ glUniform(directionLoc, d);
glUniform(lightPointLoc, p);
glUniform(lightViewProjectionCountLoc, static_cast<GLuint>(lvp.size()));
glUniform(lightViewProjectionLoc, std::span<const glm::mat4> {lvp * toTextureSpace});
diff --git a/gfx/gl/sceneRenderer.h b/gfx/gl/sceneRenderer.h
index 4195bcf..93470f5 100644
--- a/gfx/gl/sceneRenderer.h
+++ b/gfx/gl/sceneRenderer.h
@@ -1,6 +1,7 @@
#pragma once
#include "camera.h"
+#include "gfx/lightDirection.h"
#include "glArrays.h"
#include "program.h"
#include "sceneProvider.h"
@@ -14,7 +15,7 @@ public:
void render(const SceneProvider &) const;
void setAmbientLight(const RGB & colour) const;
- void setDirectionalLight(const RGB & colour, const Direction3D & direction, const SceneProvider &) const;
+ void setDirectionalLight(const RGB & colour, const LightDirection & direction, const SceneProvider &) const;
Camera camera;
diff --git a/gfx/gl/sceneShader.cpp b/gfx/gl/sceneShader.cpp
index 4cbccb3..571538a 100644
--- a/gfx/gl/sceneShader.cpp
+++ b/gfx/gl/sceneShader.cpp
@@ -65,7 +65,7 @@ SceneShader::SceneProgram::setViewProjection(const GlobalPosition3D & viewPoint,
void
SceneShader::SceneProgram::setViewPort(const ViewPort & viewPort) const
{
- if (viewPortLoc >= 0) {
+ if (viewPortLoc) {
glUseProgram(*this);
glUniform(viewPortLoc, viewPort);
}
diff --git a/gfx/gl/shader.cpp b/gfx/gl/shader.cpp
index 931c372..9a4c270 100644
--- a/gfx/gl/shader.cpp
+++ b/gfx/gl/shader.cpp
@@ -63,8 +63,8 @@ Shader::compile() const
};
if (lookups) {
std::basic_string<GLchar> textMod {text};
- for (const auto & match : ctre::range<R"(\bGL_[A-Z_]+\b)">(textMod)) {
- if (const auto lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
+ for (const auto & match : ctre::search_all<R"(\bGL_[A-Z_]+\b)">(textMod)) {
+ if (const auto * const lookup = std::find_if(LOOKUPS.begin(), LOOKUPS.end(),
[&match](const auto & lookup) {
return std::get<std::string_view>(lookup) == match;
});
diff --git a/gfx/gl/shaders/directionalLight.fs b/gfx/gl/shaders/directionalLight.fs
index 86447ec..cdf0389 100644
--- a/gfx/gl/shaders/directionalLight.fs
+++ b/gfx/gl/shaders/directionalLight.fs
@@ -17,24 +17,35 @@ uniform ivec3 lightPoint;
uniform mat4 lightViewProjection[MAX_MAPS];
uniform uint lightViewProjectionCount;
-const vec3 e1 = vec3(0, 0, 0), e2 = vec3(1, 1, 1);
+float
+getShadow(vec3 positionInLightSpace, float m, vec2 texelSize)
+{
+ float shadow = 0.0;
+ for (float x = -texelSize.x; x <= texelSize.x; x += texelSize.x) {
+ for (float y = -texelSize.y; y <= texelSize.y; y += texelSize.y) {
+ const float lightSpaceDepth = texture(shadowMap, vec3(positionInLightSpace.xy + vec2(x, y), m)).r;
+ shadow += step(positionInLightSpace.z, lightSpaceDepth + 0.001);
+ }
+ }
+ return shadow / 9.0;
+}
float
-insideShadowCube(vec3 v)
+insideShadowCube(vec3 v, vec2 texelSize)
{
- const vec3 s = step(e1, v) - step(e2, v);
+ const vec3 s = step(vec3(texelSize, 0), v) - step(vec3(1 - texelSize, 1), v);
return s.x * s.y * s.z;
}
float
isShaded(vec4 Position)
{
+ const vec2 texelSize = 1.0 / textureSize(shadowMap, 0).xy;
for (uint m = 0u; m < lightViewProjectionCount; m++) {
- const vec3 PositionInLightSpace = (lightViewProjection[m] * Position).xyz;
- const float inside = insideShadowCube(PositionInLightSpace);
+ const vec3 positionInLightSpace = (lightViewProjection[m] * Position).xyz;
+ const float inside = insideShadowCube(positionInLightSpace, texelSize);
if (inside > 0) {
- const float lightSpaceDepth = texture(shadowMap, vec3(PositionInLightSpace.xy, m)).r;
- return step(PositionInLightSpace.z, lightSpaceDepth + 0.001);
+ return getShadow(positionInLightSpace, m, texelSize);
}
}
return 1;
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.fs b/gfx/gl/shaders/shadowDynamicPointStencil.fs
new file mode 100644
index 0000000..fe91b07
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.fs
@@ -0,0 +1,16 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2DArray stencilDepth;
+flat in vec3 scale;
+in vec3 texCoord;
+
+void
+main()
+{
+ float stDepth = texture(stencilDepth, texCoord).r;
+ if (stDepth >= 1) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z + ((stDepth - 0.5) * scale.z);
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.gs b/gfx/gl/shaders/shadowDynamicPointStencil.gs
new file mode 100644
index 0000000..7e81d97
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.gs
@@ -0,0 +1,36 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+const vec2[] corners = vec2[4](vec2(-1, -1), vec2(-1, 1), vec2(1, -1), vec2(1, 1));
+const float tau = 6.28318531;
+
+uniform mat4 viewProjection[4];
+uniform int viewProjections;
+uniform vec3 sizes[4];
+uniform float size;
+
+in float vmodelYaw[];
+in ivec3 vworldPos[];
+
+flat out vec3 scale;
+out vec3 texCoord;
+
+layout(points) in;
+layout(triangle_strip, max_vertices = 16) out;
+
+void
+main()
+{
+ int viewAngle = int(round(4.0 + (vmodelYaw[0] / tau))) % 8;
+ for (gl_Layer = 0; gl_Layer < viewProjections; ++gl_Layer) {
+ scale = 2.0 * size / sizes[gl_Layer];
+ vec4 pos = viewProjection[gl_Layer] * vec4(vworldPos[0], 1);
+ for (int c = 0; c < corners.length(); ++c) {
+ gl_Position = pos + vec4(scale.xy * corners[c], 0, 0);
+ gl_Position.z = max(gl_Position.z, -1);
+ texCoord = vec3((corners[c] * 0.5) + 0.5, viewAngle);
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowDynamicPointStencil.vs b/gfx/gl/shaders/shadowDynamicPointStencil.vs
new file mode 100644
index 0000000..0dd2d79
--- /dev/null
+++ b/gfx/gl/shaders/shadowDynamicPointStencil.vs
@@ -0,0 +1,17 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(location = 0) in ivec3 worldPos;
+layout(location = 1) in float modelYaw;
+uniform ivec3 viewPoint;
+uniform vec3 centre;
+
+out float vmodelYaw;
+out ivec3 vworldPos;
+
+void
+main()
+{
+ vmodelYaw = modelYaw;
+ vworldPos = worldPos - viewPoint + ivec3(centre);
+}
diff --git a/gfx/gl/shaders/shadowStencil.fs b/gfx/gl/shaders/shadowStencil.fs
new file mode 100644
index 0000000..1164cc9
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.fs
@@ -0,0 +1,18 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 0) uniform sampler2D textureAlbedo;
+
+include(`materialDetail.glsl')
+include(`materialCommon.glsl')
+in vec2 gTexCoords;
+flat in MaterialDetail gMaterial;
+
+void
+main()
+{
+ if (getTextureColour(gMaterial, gTexCoords).a < 0.5) {
+ discard;
+ }
+ gl_FragDepth = gl_FragCoord.z;
+}
diff --git a/gfx/gl/shaders/shadowStencil.gs b/gfx/gl/shaders/shadowStencil.gs
new file mode 100644
index 0000000..2c3f9bd
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.gs
@@ -0,0 +1,28 @@
+#version 330 core
+#extension GL_ARB_viewport_array : enable
+
+include(`materialDetail.glsl')
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = 24) out;
+
+uniform mat4 viewProjection[8];
+in vec3 FragPos[];
+in vec2 TexCoords[];
+flat in MaterialDetail Material[];
+out vec2 gTexCoords;
+flat out MaterialDetail gMaterial;
+
+void
+main()
+{
+ for (gl_Layer = 0; gl_Layer < viewProjection.length(); ++gl_Layer) {
+ for (int v = 0; v < FragPos.length(); ++v) {
+ gl_Position = viewProjection[gl_Layer] * vec4(FragPos[v], 1);
+ gTexCoords = TexCoords[v];
+ gMaterial = Material[v];
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+}
diff --git a/gfx/gl/shaders/shadowStencil.vs b/gfx/gl/shaders/shadowStencil.vs
new file mode 100644
index 0000000..a15c4fb
--- /dev/null
+++ b/gfx/gl/shaders/shadowStencil.vs
@@ -0,0 +1,20 @@
+#version 330 core
+#extension GL_ARB_shading_language_420pack : enable
+
+layout(binding = 1) uniform usampler2DRect materialData;
+
+include(`meshIn.glsl')
+include(`materialDetail.glsl')
+include(`getMaterialDetail.glsl')
+
+out vec3 FragPos;
+out vec2 TexCoords;
+flat out MaterialDetail Material;
+
+void
+main()
+{
+ TexCoords = texCoord;
+ Material = getMaterialDetail(material);
+ FragPos = position;
+}
diff --git a/gfx/gl/shadowMapper.cpp b/gfx/gl/shadowMapper.cpp
index a846a3d..1b95aa3 100644
--- a/gfx/gl/shadowMapper.cpp
+++ b/gfx/gl/shadowMapper.cpp
@@ -1,13 +1,20 @@
#include "shadowMapper.h"
#include "camera.h"
#include "collections.h"
+#include "game/gamestate.h"
#include "gfx/gl/shaders/fs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/fs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/gs-commonShadowPoint.h"
#include "gfx/gl/shaders/gs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/gs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowDynamicPoint.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInst.h"
#include "gfx/gl/shaders/vs-shadowDynamicPointInstWithTextures.h"
+#include "gfx/gl/shaders/vs-shadowDynamicPointStencil.h"
#include "gfx/gl/shaders/vs-shadowLandmass.h"
+#include "gfx/gl/shadowStenciller.h"
+#include "gfx/lightDirection.h"
+#include "gfx/renderable.h"
#include "gl_traits.h"
#include "location.h"
#include "maths.h"
@@ -45,13 +52,15 @@ ShadowMapper::ShadowMapper(const TextureAbsCoord & s) :
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
-constexpr std::array<GlobalDistance, ShadowMapper::SHADOW_BANDS + 1> shadowBands {
- 1000,
- 250000,
- 750000,
- 2500000,
- 10000000,
-};
+constexpr auto shadowBands
+ = []<GlobalDistance... ints>(const float scaleFactor, std::integer_sequence<GlobalDistance, ints...>) {
+ const auto base = 10'000'000 / pow(scaleFactor, sizeof...(ints) - 1);
+ return std::array {1, static_cast<GlobalDistance>((base * pow(scaleFactor, ints)))...};
+ }(6.6F, std::make_integer_sequence<GlobalDistance, ShadowMapper::SHADOW_BANDS>());
+
+static_assert(shadowBands.front() == 1);
+static_assert(shadowBands.back() == 10'000'000);
+static_assert(shadowBands.size() == ShadowMapper::SHADOW_BANDS + 1);
std::vector<std::array<RelativePosition3D, 4>>
ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightViewDir)
@@ -72,34 +81,49 @@ ShadowMapper::getBandViewExtents(const Camera & camera, const glm::mat4 & lightV
}
ShadowMapper::Definitions
-ShadowMapper::update(const SceneProvider & scene, const Direction3D & dir, const Camera & camera) const
+ShadowMapper::update(const SceneProvider & scene, const LightDirection & dir, const Camera & camera) const
{
+ glCullFace(GL_FRONT);
+ glEnable(GL_DEPTH_TEST);
+
+ shadowStenciller.setLightDirection(dir);
+ for (const auto & [id, asset] : gameState->assets) {
+ if (const auto r = std::dynamic_pointer_cast<const Renderable>(asset)) {
+ r->updateStencil(shadowStenciller);
+ }
+ }
+
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
- glCullFace(GL_FRONT);
glViewport(0, 0, size.x, size.y);
- const auto lightViewDir = glm::lookAt({}, dir, up);
+ const auto lightViewDir = glm::lookAt({}, dir.vector(), up);
const auto lightViewPoint = camera.getPosition();
const auto bandViewExtents = getBandViewExtents(camera, lightViewDir);
Definitions out;
+ Sizes sizes;
std::transform(bandViewExtents.begin(), std::prev(bandViewExtents.end()), std::next(bandViewExtents.begin()),
std::back_inserter(out),
- [bands = bandViewExtents.size() - 2, &lightViewDir](const auto & near, const auto & far) mutable {
- const auto extents_minmax = [extents = std::span {near.begin(), far.end()}](auto && comp) {
- const auto mm = std::minmax_element(extents.begin(), extents.end(), comp);
- return std::make_pair(comp.get(*mm.first), comp.get(*mm.second));
- };
+ [bands = bandViewExtents.size() - 2, &lightViewDir, &sizes](const auto & near, const auto & far) mutable {
+ const auto extents_minmax
+ = [extents = std::span {near.begin(), far.end()}](auto && comp, RelativeDistance extra) {
+ const auto mm = std::minmax_element(extents.begin(), extents.end(), comp);
+ return std::make_pair(comp.get(*mm.first) - extra, comp.get(*mm.second) + extra);
+ };
+ const std::array extents = {extents_minmax(CompareBy {0}, 0), extents_minmax(CompareBy {1}, 0),
+ extents_minmax(CompareBy {2}, 10'000)};
const auto lightProjection = [](const auto & x, const auto & y, const auto & z) {
return glm::ortho(x.first, x.second, y.first, y.second, -z.second, -z.first);
- }(extents_minmax(CompareBy {0}), extents_minmax(CompareBy {1}), extents_minmax(CompareBy {2}));
+ }(extents[0], extents[1], extents[2]);
+ sizes.emplace_back(extents[0].second - extents[0].first, extents[1].second - extents[1].first,
+ extents[2].second - extents[2].first);
return lightProjection * lightViewDir;
});
for (const auto p : std::initializer_list<const ShadowProgram *> {
- &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures}) {
- p->setView(out, lightViewPoint);
+ &landmess, &dynamicPoint, &dynamicPointInst, &dynamicPointInstWithTextures, &stencilShadowProgram}) {
+ p->setView(out, sizes, lightViewPoint);
}
scene.shadows(*this);
@@ -116,12 +140,15 @@ ShadowMapper::ShadowProgram::ShadowProgram(const Shader & vs, const Shader & gs,
}
void
-ShadowMapper::ShadowProgram::setView(
- const std::span<const glm::mat4> viewProjection, const GlobalPosition3D viewPoint) const
+ShadowMapper::ShadowProgram::setView(const std::span<const glm::mat4x4> viewProjection,
+ const std::span<const RelativePosition3D> sizes, const GlobalPosition3D viewPoint) const
{
use();
glUniform(viewPointLoc, viewPoint);
glUniform(viewProjectionLoc, viewProjection);
+ if (sizesLoc) {
+ glUniform(sizesLoc, sizes);
+ }
glUniform(viewProjectionsLoc, static_cast<GLint>(viewProjection.size()));
}
@@ -146,3 +173,16 @@ ShadowMapper::DynamicPoint::setModel(const Location & location) const
glUniform(modelLoc, location.getRotationTransform());
glUniform(modelPosLoc, location.pos);
}
+
+ShadowMapper::StencilShadowProgram::StencilShadowProgram() :
+ ShadowProgram {shadowDynamicPointStencil_vs, shadowDynamicPointStencil_gs, shadowDynamicPointStencil_fs}
+{
+}
+
+void
+ShadowMapper::StencilShadowProgram::use(const RelativePosition3D & centre, const float size) const
+{
+ Program::use();
+ glUniform(centreLoc, centre);
+ glUniform(sizeLoc, size);
+}
diff --git a/gfx/gl/shadowMapper.h b/gfx/gl/shadowMapper.h
index 73dadd0..951e29c 100644
--- a/gfx/gl/shadowMapper.h
+++ b/gfx/gl/shadowMapper.h
@@ -1,6 +1,7 @@
#pragma once
#include "config/types.h"
+#include "gfx/gl/shadowStenciller.h"
#include "lib/glArrays.h"
#include "program.h"
#include <gfx/models/texture.h>
@@ -10,6 +11,7 @@
class SceneProvider;
class Camera;
+class LightDirection;
class ShadowMapper {
public:
@@ -18,20 +20,23 @@ public:
static constexpr std::size_t SHADOW_BANDS {4};
using Definitions = std::vector<glm::mat4x4>;
+ using Sizes = std::vector<RelativePosition3D>;
- [[nodiscard]] Definitions update(const SceneProvider &, const Direction3D & direction, const Camera &) const;
+ [[nodiscard]] Definitions update(const SceneProvider &, const LightDirection & direction, const Camera &) const;
class ShadowProgram : public Program {
public:
explicit ShadowProgram(const Shader & vs);
explicit ShadowProgram(const Shader & vs, const Shader & gs, const Shader & fs);
- void setView(const std::span<const glm::mat4>, const GlobalPosition3D) const;
+ void setView(const std::span<const glm::mat4x4>, const std::span<const RelativePosition3D>,
+ const GlobalPosition3D) const;
void use() const;
private:
RequiredUniformLocation viewProjectionLoc {*this, "viewProjection"};
RequiredUniformLocation viewProjectionsLoc {*this, "viewProjections"};
+ UniformLocation sizesLoc {*this, "sizes"};
RequiredUniformLocation viewPointLoc {*this, "viewPoint"};
};
@@ -46,8 +51,19 @@ public:
RequiredUniformLocation modelPosLoc {*this, "modelPos"};
};
+ class StencilShadowProgram : public ShadowProgram {
+ public:
+ StencilShadowProgram();
+ void use(const RelativePosition3D & centre, const float size) const;
+
+ private:
+ RequiredUniformLocation centreLoc {*this, "centre"};
+ RequiredUniformLocation sizeLoc {*this, "size"};
+ };
+
ShadowProgram landmess, dynamicPointInst, dynamicPointInstWithTextures;
DynamicPoint dynamicPoint;
+ StencilShadowProgram stencilShadowProgram;
// NOLINTNEXTLINE(hicpp-explicit-conversions)
operator GLuint() const
@@ -61,4 +77,5 @@ private:
glFrameBuffer depthMapFBO;
glTexture depthMap;
TextureAbsCoord size;
+ mutable ShadowStenciller shadowStenciller;
};
diff --git a/gfx/gl/shadowStenciller.cpp b/gfx/gl/shadowStenciller.cpp
new file mode 100644
index 0000000..86b77e4
--- /dev/null
+++ b/gfx/gl/shadowStenciller.cpp
@@ -0,0 +1,74 @@
+#include "shadowStenciller.h"
+#include "gfx/gl/program.h"
+#include "gfx/gl/shaders/fs-shadowStencil.h"
+#include "gfx/gl/shaders/gs-shadowStencil.h"
+#include "gfx/gl/shaders/vs-shadowStencil.h"
+#include "gfx/lightDirection.h"
+#include "gfx/models/mesh.h"
+#include "glArrays.h"
+#include "gl_traits.h"
+#include "maths.h"
+#include <stdexcept>
+
+ShadowStenciller::ShadowStenciller() :
+ shadowCaster {shadowStencil_vs, shadowStencil_gs, shadowStencil_fs}, viewProjections {}
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void
+ShadowStenciller::setLightDirection(const LightDirection & lightDir)
+{
+ viewProjections = [&lightDir]<GLint... Ep>(std::integer_sequence<GLint, Ep...>) {
+ constexpr float STEP = two_pi / STENCIL_ANGLES<decltype(two_pi)>;
+ return std::array {rotate_pitch<4>(half_pi - lightDir.position().y)
+ * rotate_yaw<4>((Ep * STEP) - lightDir.position().x)...};
+ }(std::make_integer_sequence<GLint, STENCIL_ANGLES<GLint>>());
+}
+
+glTexture
+ShadowStenciller::createStencilTexture(GLsizei width, GLsizei height)
+{
+ glTexture stencil;
+ glBindTexture(GL_TEXTURE_2D_ARRAY, stencil);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameter(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, width, height, STENCIL_ANGLES<GLint>, 0,
+ GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr);
+
+ return stencil;
+}
+
+void
+ShadowStenciller::renderStencil(const glTexture & stencil, const MeshBase & mesh, const Texture::AnyPtr texture) const
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, stencil, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ throw std::runtime_error("Stencil framebuffer not complete!");
+ }
+ if (texture) {
+ texture->bind();
+ }
+ glUseProgram(shadowCaster);
+ glClear(GL_DEPTH_BUFFER_BIT);
+ const auto stencilSize = Texture::getSize(stencil);
+ glViewport(0, 0, stencilSize.x, stencilSize.y);
+ const auto & centre = mesh.getDimensions().centre;
+ const auto & size = mesh.getDimensions().size;
+ glUniform(viewProjectionLoc,
+ std::span<const glm::mat4> {viewProjections *
+ [extentsMat = glm::translate(glm::ortho(-size, size, -size, size, -size, size), -centre)](
+ const auto & viewProjection) {
+ return viewProjection * extentsMat;
+ }});
+ mesh.Draw();
+}
diff --git a/gfx/gl/shadowStenciller.h b/gfx/gl/shadowStenciller.h
new file mode 100644
index 0000000..f774ac7
--- /dev/null
+++ b/gfx/gl/shadowStenciller.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "gfx/gl/program.h"
+#include "gfx/models/mesh.h"
+#include "gfx/models/texture.h"
+#include "glArrays.h"
+
+class LightDirection;
+
+class ShadowStenciller {
+public:
+ template<typename T> static constexpr T STENCIL_ANGLES = 8;
+
+ ShadowStenciller();
+
+ [[nodiscard]]
+ static glTexture createStencilTexture(GLsizei width, GLsizei height);
+ void setLightDirection(const LightDirection & lightDir);
+ void renderStencil(const glTexture &, const MeshBase &, Texture::AnyPtr texture) const;
+
+private:
+ glFrameBuffer fbo;
+ Program shadowCaster;
+ Program::RequiredUniformLocation viewProjectionLoc {shadowCaster, "viewProjection"};
+
+ std::array<glm::mat4, STENCIL_ANGLES<size_t>> viewProjections;
+};
diff --git a/gfx/lightDirection.cpp b/gfx/lightDirection.cpp
new file mode 100644
index 0000000..3932872
--- /dev/null
+++ b/gfx/lightDirection.cpp
@@ -0,0 +1,12 @@
+#include "lightDirection.h"
+#include "maths.h"
+
+constexpr auto ASTRONOMICAL_TWILIGHT = 18.0_degrees;
+constexpr auto SUN_ANGLUAR_SIZE = 0.5_degrees;
+
+LightDirection::LightDirection(const Direction2D sunPos) :
+ pos {sunPos}, vec {glm::mat3 {rotate_yp(pi + sunPos.x, -sunPos.y)} * north},
+ amb {glm::clamp(sunPos.y + ASTRONOMICAL_TWILIGHT, 0.F, 1.F)},
+ dir {glm::clamp(sunPos.y + SUN_ANGLUAR_SIZE, 0.F, 1.F)}
+{
+}
diff --git a/gfx/lightDirection.h b/gfx/lightDirection.h
new file mode 100644
index 0000000..789830b
--- /dev/null
+++ b/gfx/lightDirection.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "config/types.h"
+
+class LightDirection {
+public:
+ // NOLINTNEXTLINE(hicpp-explicit-conversions) deliberately a helper
+ LightDirection(Direction2D sunPos);
+
+ [[nodiscard]] Direction2D
+ position() const noexcept
+ {
+ return pos;
+ }
+
+ [[nodiscard]] Direction3D
+ vector() const noexcept
+ {
+ return vec;
+ }
+
+ [[nodiscard]] float
+ ambient() const noexcept
+ {
+ return amb;
+ }
+
+ [[nodiscard]] float
+ directional() const noexcept
+ {
+ return dir;
+ }
+
+private:
+ Direction2D pos;
+ Direction3D vec;
+ float amb;
+ float dir;
+};
diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp
index e7474ca..2eae160 100644
--- a/gfx/models/mesh.cpp
+++ b/gfx/models/mesh.cpp
@@ -1,6 +1,32 @@
#include "mesh.h"
-MeshBase::MeshBase(GLsizei m_numIndices, GLenum mode) : m_numIndices {m_numIndices}, mode {mode} { }
+MeshBase::MeshBase(GLsizei m_numIndices, GLenum mode, const std::vector<RelativePosition3D> & positions) :
+ m_numIndices {m_numIndices}, mode {mode}, dimensions {positions}
+{
+}
+
+MeshBase::Dimensions::Dimensions(const std::span<const RelativePosition3D> positions) :
+ Dimensions {positions, {extents(positions, 0), extents(positions, 1), extents(positions, 2)}}
+{
+}
+
+MeshBase::Dimensions::Dimensions(
+ const std::span<const RelativePosition3D> positions, const std::array<Extents1D, 3> & extents1ds) :
+ minExtent(extents1ds[0].min, extents1ds[1].min, extents1ds[2].min),
+ maxExtent(extents1ds[0].max, extents1ds[1].max, extents1ds[2].max), centre {(minExtent + maxExtent) / 2.0F},
+ size {std::ranges::max(positions | std::views::transform([this](const auto & v) {
+ return glm::distance(v, centre);
+ }))}
+{
+}
+
+MeshBase::Dimensions::Extents1D
+MeshBase::Dimensions::extents(const std::span<const RelativePosition3D> positions, glm::length_t D)
+{
+ return std::ranges::minmax(positions | std::views::transform([D](const auto & v) {
+ return v[D];
+ }));
+}
void
MeshBase::Draw() const
diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h
index 248cb8f..8791aed 100644
--- a/gfx/models/mesh.h
+++ b/gfx/models/mesh.h
@@ -1,8 +1,10 @@
#pragma once
+#include "config/types.h"
#include "gfx/gl/vertexArrayObject.h"
#include <glArrays.h>
#include <glad/gl.h>
+#include <ranges>
#include <span>
#include <stdTypeDefs.h>
@@ -10,22 +12,46 @@ class Vertex;
class MeshBase {
public:
+ class Dimensions {
+ public:
+ using Extents1D = std::ranges::minmax_result<RelativeDistance>;
+ explicit Dimensions(const std::span<const RelativePosition3D>);
+
+ RelativePosition3D minExtent, maxExtent;
+ RelativePosition3D centre;
+ RelativeDistance size;
+
+ private:
+ Dimensions(const std::span<const RelativePosition3D>, const std::array<Extents1D, 3> &);
+ static Extents1D extents(const std::span<const RelativePosition3D>, glm::length_t D);
+ };
+
void Draw() const;
void DrawInstanced(GLuint vao, GLsizei count, GLuint base = 0) const;
+ [[nodiscard]] const Dimensions &
+ getDimensions() const
+ {
+ return dimensions;
+ }
+
protected:
- MeshBase(GLsizei m_numIndices, GLenum mode);
+ MeshBase(GLsizei m_numIndices, GLenum mode, const std::vector<RelativePosition3D> &);
glVertexArray m_vertexArrayObject;
glBuffers<2> m_vertexArrayBuffers;
GLsizei m_numIndices;
GLenum mode;
+ Dimensions dimensions;
};
template<typename V> class MeshT : public MeshBase, public ConstTypeDefs<MeshT<V>> {
public:
MeshT(const std::span<const V> vertices, const std::span<const unsigned int> indices, GLenum mode = GL_TRIANGLES) :
- MeshBase {static_cast<GLsizei>(indices.size()), mode}
+ MeshBase {static_cast<GLsizei>(indices.size()), mode,
+ materializeRange(vertices | std::views::transform([](const auto & v) {
+ return static_cast<RelativePosition3D>(v.pos);
+ }))}
{
VertexArrayObject::data(vertices, m_vertexArrayBuffers[0], GL_ARRAY_BUFFER);
VertexArrayObject::data(indices, m_vertexArrayBuffers[1], GL_ARRAY_BUFFER);
diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp
index 51223aa..a508421 100644
--- a/gfx/models/texture.cpp
+++ b/gfx/models/texture.cpp
@@ -59,12 +59,13 @@ Texture::bind(GLenum unit) const
glBindTexture(type, m_texture);
}
-TextureAbsCoord
+TextureDimensions
Texture::getSize(const glTexture & texture)
{
- TextureAbsCoord size;
+ TextureDimensions size {};
glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_WIDTH, &size.x);
glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_HEIGHT, &size.y);
+ glGetTextureLevelParameteriv(texture, 0, GL_TEXTURE_DEPTH, &size.z);
return size;
}
@@ -73,7 +74,7 @@ Texture::save(
const glTexture & texture, GLenum format, GLenum type, uint8_t channels, const char * path, uint8_t tgaFormat)
{
const auto size = getSize(texture);
- const size_t dataSize = (static_cast<size_t>(size.x * size.y * channels));
+ const size_t dataSize = (static_cast<size_t>(size.x * size.y * size.z * channels));
const size_t fileSize = dataSize + sizeof(TGAHead);
filesystem::fh out {path, O_RDWR | O_CREAT, 0660};
@@ -81,7 +82,7 @@ Texture::save(
auto tga = out.mmap(fileSize, 0, PROT_WRITE, MAP_SHARED);
*tga.get<TGAHead>() = {
.format = tgaFormat,
- .size = size,
+ .size = {size.x, size.y * size.z},
.pixelDepth = static_cast<uint8_t>(8 * channels),
};
glPixelStorei(GL_PACK_ALIGNMENT, 1);
diff --git a/gfx/models/texture.h b/gfx/models/texture.h
index 8cb8128..d8c3b29 100644
--- a/gfx/models/texture.h
+++ b/gfx/models/texture.h
@@ -38,10 +38,10 @@ public:
static void saveDepth(const glTexture &, const char * path);
static void saveNormal(const glTexture &, const char * path);
static void savePosition(const glTexture &, const char * path);
+ static TextureDimensions getSize(const glTexture &);
protected:
static void save(const glTexture &, GLenum, GLenum, uint8_t channels, const char * path, uint8_t tgaFormat);
- static TextureAbsCoord getSize(const glTexture &);
glTexture m_texture;
GLenum type;
diff --git a/gfx/renderable.cpp b/gfx/renderable.cpp
index 0340189..3594968 100644
--- a/gfx/renderable.cpp
+++ b/gfx/renderable.cpp
@@ -9,3 +9,8 @@ void
Renderable::shadows(const ShadowMapper &) const
{
}
+
+void
+Renderable::updateStencil(const ShadowStenciller &) const
+{
+}
diff --git a/gfx/renderable.h b/gfx/renderable.h
index e126fff..83522e3 100644
--- a/gfx/renderable.h
+++ b/gfx/renderable.h
@@ -4,6 +4,7 @@
class SceneShader;
class ShadowMapper;
+class ShadowStenciller;
class Renderable {
public:
@@ -14,4 +15,6 @@ public:
virtual void render(const SceneShader & shader) const = 0;
virtual void lights(const SceneShader & shader) const;
virtual void shadows(const ShadowMapper & shadowMapper) const;
+
+ virtual void updateStencil(const ShadowStenciller & lightDir) const;
};
diff --git a/lib/chronology.cpp b/lib/chronology.cpp
new file mode 100644
index 0000000..8707bba
--- /dev/null
+++ b/lib/chronology.cpp
@@ -0,0 +1,12 @@
+#include "chronology.h"
+
+time_t
+operator""_time_t(const char * iso, size_t)
+{
+ struct tm tm {};
+
+ if (const auto end = strptime(iso, "%FT%T", &tm); !end || *end) {
+ throw std::invalid_argument("Invalid date");
+ }
+ return mktime(&tm);
+}
diff --git a/lib/chronology.h b/lib/chronology.h
index 1980116..688a1f7 100644
--- a/lib/chronology.h
+++ b/lib/chronology.h
@@ -1,5 +1,7 @@
#pragma once
#include <chrono>
+#include <ctime>
using TickDuration = std::chrono::duration<float, std::chrono::seconds::period>;
+time_t operator""_time_t(const char * iso, size_t);
diff --git a/lib/filesystem.cpp b/lib/filesystem.cpp
index 7e8ab9c..5c0c6f8 100644
--- a/lib/filesystem.cpp
+++ b/lib/filesystem.cpp
@@ -37,7 +37,7 @@ namespace filesystem {
}
// NOLINTNEXTLINE(hicpp-vararg)
- fh::fh(const char * path, int flags, int mode) : h {open(path, flags, mode)}
+ fh::fh(const char * path, int flags, mode_t mode) : h {open(path, flags, mode)}
{
if (h == -1) {
throw_filesystem_error("open", errno, path);
diff --git a/lib/filesystem.h b/lib/filesystem.h
index b076f43..92dc08d 100644
--- a/lib/filesystem.h
+++ b/lib/filesystem.h
@@ -30,7 +30,7 @@ namespace filesystem {
class [[nodiscard]] fh final {
public:
- fh(const char * path, int flags, int mode);
+ fh(const char * path, int flags, mode_t mode);
~fh();
NO_MOVE(fh);
NO_COPY(fh);
diff --git a/lib/jsonParse-persistence.h b/lib/jsonParse-persistence.h
index 6edebc7..e4e64c0 100644
--- a/lib/jsonParse-persistence.h
+++ b/lib/jsonParse-persistence.h
@@ -15,6 +15,9 @@ namespace Persistence {
inline T
loadState(std::istream & in)
{
+ if (!in.good()) {
+ throw std::runtime_error("Input stream not in good state");
+ }
T t {};
stk.push(std::make_unique<SelectionT<T>>(std::ref(t)));
loadState(in);
diff --git a/lib/maths.cpp b/lib/maths.cpp
index 51e27fe..3a9bf9b 100644
--- a/lib/maths.cpp
+++ b/lib/maths.cpp
@@ -19,96 +19,17 @@ flat_orientation(const Direction3D & diff)
return (std::isnan(e[0][0])) ? oneeighty : e;
}
-// Helper to lookup into a matrix given an xy vector coordinate
-template<typename M, typename I>
-inline auto &
-operator^(M & m, glm::vec<2, I> xy)
-{
- return m[xy.x][xy.y];
-}
-
-// Create a matrix for the angle, given the targets into the matrix
-template<typename M, typename I>
-inline auto
-rotation(typename M::value_type a, glm::vec<2, I> c1, glm::vec<2, I> s1, glm::vec<2, I> c2, glm::vec<2, I> ms2)
-{
- M m(1);
- sincosf(a, m ^ s1, m ^ c1);
- m ^ c2 = m ^ c1;
- m ^ ms2 = -(m ^ s1);
- return m;
-}
-
-// Create a flat (2D) transformation matrix
-glm::mat2
-rotate_flat(float a)
-{
- return rotation<glm::mat2, glm::length_t>(a, {0, 0}, {0, 1}, {1, 1}, {1, 0});
-}
-
-// Create a yaw transformation matrix
-glm::mat4
-rotate_yaw(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {0, 0}, {1, 0}, {1, 1}, {0, 1});
-}
-
-// Create a roll transformation matrix
-glm::mat4
-rotate_roll(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {0, 0}, {2, 0}, {2, 2}, {0, 2});
-}
-
-// Create a pitch transformation matrix
-glm::mat4
-rotate_pitch(float a)
-{
- return rotation<glm::mat4, glm::length_t>(a, {1, 1}, {1, 2}, {2, 2}, {2, 1});
-}
-
-// Create a combined yaw, pitch, roll transformation matrix
-glm::mat4
-rotate_ypr(Rotation3D a)
-{
- return rotate_yaw(a.y) * rotate_pitch(a.x) * rotate_roll(a.z);
-}
-
-glm::mat4
-rotate_yp(Rotation2D a)
-{
- return rotate_yaw(a.y) * rotate_pitch(a.x);
-}
-
-float
-vector_yaw(const Direction2D & diff)
-{
- return std::atan2(diff.x, diff.y);
-}
-
-float
-vector_pitch(const Direction3D & diff)
-{
- return std::atan(diff.z);
-}
-
-float
-round_frac(const float & v, const float & frac)
-{
- return std::round(v / frac) * frac;
-}
-
-float
-normalize(float ang)
-{
- while (ang > pi) {
- ang -= two_pi;
- }
- while (ang <= -pi) {
- ang += two_pi;
- }
- return ang;
-}
+static_assert(pow(1, 0) == 1);
+static_assert(pow(1, 1) == 1);
+static_assert(pow(1, 2) == 1);
+static_assert(pow(2, 0) == 1);
+static_assert(pow(2, 1) == 2);
+static_assert(pow(2, 2) == 4);
+static_assert(pow(2, 3) == 8);
+static_assert(pow(3, 0) == 1);
+static_assert(pow(3, 1) == 3);
+static_assert(pow(3, 2) == 9);
+static_assert(pow(pi, 3) == 31.006278991699219F);
float
operator"" _mph(const long double v)
diff --git a/lib/maths.h b/lib/maths.h
index 018ef0e..3959896 100644
--- a/lib/maths.h
+++ b/lib/maths.h
@@ -5,11 +5,15 @@
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <numeric>
+#include <optional>
#include <stdexcept>
#include <utility>
+template<typename T>
+concept Arithmetic = std::is_arithmetic_v<T>;
+
struct Arc : public std::pair<Angle, Angle> {
- template<glm::length_t Lc, glm::length_t Le, typename T, glm::qualifier Q>
+ template<glm::length_t Lc, glm::length_t Le, Arithmetic T, glm::qualifier Q>
requires(Lc >= 2, Le >= 2)
Arc(const glm::vec<Lc, T, Q> & centre, const glm::vec<Le, T, Q> & e0p, const glm::vec<Le, T, Q> & e1p) :
Arc {RelativePosition2D {e0p.xy() - centre.xy()}, RelativePosition2D {e1p.xy() - centre.xy()}}
@@ -17,22 +21,22 @@ struct Arc : public std::pair<Angle, Angle> {
}
Arc(const RelativePosition2D & dir0, const RelativePosition2D & dir1);
- Arc(const Angle angb, const Angle anga);
+ Arc(Angle anga, Angle angb);
auto
- operator[](bool i) const
+ operator[](bool getSecond) const
{
- return i ? second : first;
+ return getSecond ? second : first;
}
- [[nodiscard]] constexpr inline float
+ [[nodiscard]] constexpr float
length() const
{
return second - first;
}
};
-constexpr const RelativePosition3D up {0, 0, 1};
+constexpr const RelativePosition3D up {0, 0, 1}; // NOLINT(readability-identifier-length)
constexpr const RelativePosition3D down {0, 0, -1};
constexpr const RelativePosition3D north {0, 1, 0};
constexpr const RelativePosition3D south {0, -1, 0};
@@ -40,158 +44,322 @@ constexpr const RelativePosition3D east {1, 0, 0};
constexpr const RelativePosition3D west {-1, 0, 0};
constexpr auto half_pi {glm::half_pi<float>()};
constexpr auto quarter_pi {half_pi / 2};
-constexpr auto pi {glm::pi<float>()};
+constexpr auto pi {glm::pi<float>()}; // NOLINT(readability-identifier-length)
constexpr auto two_pi {glm::two_pi<float>()};
+constexpr auto degreesToRads = pi / 180.F;
+
+constexpr auto earthMeanRadius = 6371.01F; // In km
+constexpr auto astronomicalUnit = 149597890.F; // In km
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g + GlobalPosition<D>(glm::round(r));
+ return global + GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
{
- return g + GlobalPosition<D>(r);
+ return global + GlobalPosition<D>(relative);
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g - GlobalPosition<D>(glm::round(r));
+ return global - GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
+{
+ return global - GlobalPosition<D>(relative);
+}
+
+template<glm::length_t D, std::integral T, glm::qualifier Q>
+constexpr RelativePosition<D>
+difference(const glm::vec<D, T, Q> & globalA, const glm::vec<D, T, Q> & globalB)
{
- return g - GlobalPosition<D>(r);
+ return globalA - globalB;
}
glm::mat4 flat_orientation(const Rotation3D & diff);
-// C++ wrapper for C's sincosf, but with references, not pointers
-inline auto
-sincosf(float a, float & s, float & c)
+namespace {
+ // Helpers
+ // C++ wrapper for C's sincosf, but with references, not pointers
+ template<std::floating_point T>
+ constexpr void
+ sincos(T angle, T & sinOut, T & cosOut)
+ {
+ if consteval {
+ sinOut = std::sin(angle);
+ cosOut = std::cos(angle);
+ }
+ else {
+ if constexpr (std::is_same_v<T, float>) {
+ ::sincosf(angle, &sinOut, &cosOut);
+ }
+ else if constexpr (std::is_same_v<T, double>) {
+ ::sincos(angle, &sinOut, &cosOut);
+ }
+ else if constexpr (std::is_same_v<T, long double>) {
+ ::sincosl(angle, &sinOut, &cosOut);
+ }
+ }
+ }
+
+ template<std::floating_point T, glm::qualifier Q = glm::qualifier::defaultp>
+ constexpr auto
+ sincos(const T angle)
+ {
+ glm::vec<2, T, Q> sincosOut {};
+ sincos(angle, sincosOut.x, sincosOut.y);
+ return sincosOut;
+ }
+
+ // Helper to lookup into a matrix given an xy vector coordinate
+ template<glm::length_t C, glm::length_t R, Arithmetic T, glm::qualifier Q, std::integral I = glm::length_t>
+ constexpr auto &
+ operator^(glm::mat<C, R, T, Q> & matrix, const glm::vec<2, I> rowCol)
+ {
+ return matrix[rowCol.x][rowCol.y];
+ }
+
+ // Create a matrix for the angle, given the targets into the matrix
+ template<glm::length_t D, std::floating_point T, glm::qualifier Q, std::integral I = glm::length_t>
+ constexpr auto
+ rotation(const T angle, const glm::vec<2, I> cos1, const glm::vec<2, I> sin1, const glm::vec<2, I> cos2,
+ const glm::vec<2, I> negSin1)
+ {
+ glm::mat<D, D, T, Q> out(1);
+ sincos(angle, out ^ sin1, out ^ cos1);
+ out ^ cos2 = out ^ cos1;
+ out ^ negSin1 = -(out ^ sin1);
+ return out;
+ }
+}
+
+// Create a flat transformation matrix
+template<glm::length_t D = 2, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+rotate_flat(const T angle)
{
- return sincosf(a, &s, &c);
+ return rotation<D, T, Q>(angle, {0, 0}, {0, 1}, {1, 1}, {1, 0});
}
-inline Rotation2D
-sincosf(float a)
+// Create a yaw transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+rotate_yaw(const T angle)
{
- Rotation2D sc;
- sincosf(a, sc.x, sc.y);
- return sc;
+ return rotation<D, T, Q>(angle, {0, 0}, {1, 0}, {1, 1}, {0, 1});
}
-glm::mat2 rotate_flat(float);
-glm::mat4 rotate_roll(float);
-glm::mat4 rotate_yaw(float);
-glm::mat4 rotate_pitch(float);
-glm::mat4 rotate_yp(Rotation2D);
-glm::mat4 rotate_ypr(Rotation3D);
+// Create a roll transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_roll(const T angle)
+{
+ return rotation<D, T, Q>(angle, {0, 0}, {2, 0}, {2, 2}, {0, 2});
+}
-float vector_yaw(const Direction2D & diff);
-float vector_pitch(const Direction3D & diff);
+// Create a pitch transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_pitch(const T angle)
+{
+ return rotation<D, T, Q>(angle, {1, 1}, {1, 2}, {2, 2}, {2, 1});
+}
-template<typename T, glm::qualifier Q>
-glm::vec<2, T, Q>
-vector_normal(const glm::vec<2, T, Q> & v)
+// Create a combined yaw, pitch, roll transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_ypr(const glm::vec<3, T, Q> & angles)
{
- return {-v.y, v.x};
+ return rotate_yaw<D>(angles.y) * rotate_pitch<D>(angles.x) * rotate_roll<D>(angles.z);
+}
+
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_yp(const T yaw, const T pitch)
+{
+ return rotate_yaw<D>(yaw) * rotate_pitch<D>(pitch);
+}
+
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_yp(const glm::vec<2, T, Q> & angles)
+{
+ return rotate_yp<D>(angles.y, angles.x);
+}
+
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+vector_yaw(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan2(diff.x, diff.y);
+}
+
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+vector_pitch(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan(diff.z);
+}
+
+template<std::floating_point T, glm::qualifier Q>
+constexpr glm::vec<2, T, Q>
+vector_normal(const glm::vec<2, T, Q> & vector)
+{
+ return {-vector.y, vector.x};
};
-float round_frac(const float & v, const float & frac);
+template<std::floating_point T>
+constexpr auto
+round_frac(const T value, const T frac)
+{
+ return std::round(value / frac) * frac;
+}
-template<typename T>
-inline constexpr auto
-sq(T v)
+template<Arithmetic T>
+ requires requires(T value) { value * value; }
+constexpr auto
+sq(T value)
{
- return v * v;
+ return value * value;
}
template<glm::qualifier Q>
-inline constexpr glm::vec<3, int64_t, Q>
-crossProduct(const glm::vec<3, int64_t, Q> a, const glm::vec<3, int64_t, Q> b)
+constexpr glm::vec<3, int64_t, Q>
+crossProduct(const glm::vec<3, int64_t, Q> & valueA, const glm::vec<3, int64_t, Q> & valueB)
{
return {
- (a.y * b.z) - (a.z * b.y),
- (a.z * b.x) - (a.x * b.z),
- (a.x * b.y) - (a.y * b.x),
+ (valueA.y * valueB.z) - (valueA.z * valueB.y),
+ (valueA.z * valueB.x) - (valueA.x * valueB.z),
+ (valueA.x * valueB.y) - (valueA.y * valueB.x),
};
}
template<std::integral T, glm::qualifier Q>
-inline constexpr glm::vec<3, T, Q>
-crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> & valueA, const glm::vec<3, T, Q> & valueB)
{
- return crossProduct<Q>(a, b);
+ return crossProduct<Q>(valueA, valueB);
}
template<std::floating_point T, glm::qualifier Q>
-inline constexpr glm::vec<3, T, Q>
-crossProduct(const glm::vec<3, T, Q> a, const glm::vec<3, T, Q> b)
+constexpr glm::vec<3, T, Q>
+crossProduct(const glm::vec<3, T, Q> & valueA, const glm::vec<3, T, Q> & valueB)
{
- return glm::cross(a, b);
+ return glm::cross(valueA, valueB);
}
-template<typename R = float, typename Ta, typename Tb>
-inline constexpr auto
-ratio(Ta a, Tb b)
+template<Arithmetic R = float, Arithmetic Ta, Arithmetic Tb>
+constexpr auto
+ratio(const Ta valueA, const Tb valueB)
{
- return (static_cast<R>(a) / static_cast<R>(b));
+ using Common = std::common_type_t<Ta, Ta>;
+ return static_cast<R>((static_cast<Common>(valueA) / static_cast<Common>(valueB)));
}
-template<typename R = float, typename T, glm::qualifier Q>
-inline constexpr auto
-ratio(glm::vec<2, T, Q> v)
+template<Arithmetic R = float, Arithmetic T, glm::qualifier Q>
+constexpr auto
+ratio(const glm::vec<2, T, Q> & value)
{
- return ratio<R>(v.x, v.y);
+ return ratio<R>(value.x, value.y);
}
-template<glm::length_t L = 3, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspective_divide(glm::vec<4, T, Q> v)
+template<glm::length_t L = 3, std::floating_point T, glm::qualifier Q>
+constexpr auto
+perspective_divide(const glm::vec<4, T, Q> & value)
{
- return v / v.w;
+ return value / value.w;
}
-template<glm::length_t L1, glm::length_t L2, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L1 + L2, T, Q>
-operator||(const glm::vec<L1, T, Q> v1, const glm::vec<L2, T, Q> v2)
+template<glm::length_t L1, glm::length_t L2, Arithmetic T, glm::qualifier Q>
+constexpr glm::vec<L1 + L2, T, Q>
+operator||(const glm::vec<L1, T, Q> valueA, const glm::vec<L2, T, Q> valueB)
{
- return {v1, v2};
+ return {valueA, valueB};
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L + 1, T, Q>
-operator||(const glm::vec<L, T, Q> v1, const T v2)
+template<glm::length_t L, Arithmetic T, glm::qualifier Q>
+constexpr glm::vec<L + 1, T, Q>
+operator||(const glm::vec<L, T, Q> valueA, const T valueB)
{
- return {v1, v2};
+ return {valueA, valueB};
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspectiveMultiply(const glm::vec<L, T, Q> & p, const glm::mat<L + 1, L + 1, T, Q> & mutation)
+template<glm::length_t L, std::floating_point T, glm::qualifier Q>
+constexpr glm::vec<L, T, Q>
+perspectiveMultiply(const glm::vec<L, T, Q> & base, const glm::mat<L + 1, L + 1, T, Q> & mutation)
{
- const auto p2 = mutation * (p || T(1));
- return p2 / p2.w;
+ const auto mutated = mutation * (base || T(1));
+ return mutated / mutated.w;
}
-template<glm::length_t L, typename T, glm::qualifier Q>
-inline constexpr glm::vec<L, T, Q>
-perspectiveApply(glm::vec<L, T, Q> & p, const glm::mat<L + 1, L + 1, T, Q> & mutation)
+template<glm::length_t L, std::floating_point T, glm::qualifier Q>
+constexpr glm::vec<L, T, Q>
+perspectiveApply(glm::vec<L, T, Q> & base, const glm::mat<L + 1, L + 1, T, Q> & mutation)
{
- return p = perspectiveMultiply(p, mutation);
+ return base = perspectiveMultiply(base, mutation);
+}
+
+template<std::floating_point T>
+constexpr T
+normalize(T ang)
+{
+ while (ang > glm::pi<T>()) {
+ ang -= glm::two_pi<T>();
+ }
+ while (ang <= -glm::pi<T>()) {
+ ang += glm::two_pi<T>();
+ }
+ return ang;
}
-float normalize(float ang);
+template<Arithmetic T> using CalcType = std::conditional_t<std::is_floating_point_v<T>, T, int64_t>;
+
+template<Arithmetic T, glm::qualifier Q = glm::defaultp>
+[[nodiscard]] constexpr std::optional<glm::vec<2, T, Q>>
+linesIntersectAt(const glm::vec<2, T, Q> Aabs, const glm::vec<2, T, Q> Babs, const glm::vec<2, T, Q> Cabs,
+ const glm::vec<2, T, Q> Dabs)
+{
+ using CT = CalcType<T>;
+ using CVec = glm::vec<2, CT, Q>;
+ // Line AB represented as a1x + b1y = c1
+ const CVec Brel = Babs - Aabs;
+ const CT a1 = Brel.y;
+ const CT b1 = -Brel.x;
+
+ // Line CD represented as a2x + b2y = c2
+ const CVec Crel = Cabs - Aabs, Del = Dabs - Aabs;
+ const CT a2 = Del.y - Crel.y;
+ const CT b2 = Crel.x - Del.x;
+ const CT c2 = (a2 * Crel.x) + (b2 * Crel.y);
+
+ const auto determinant = (a1 * b2) - (a2 * b1);
+
+ if (determinant == 0) {
+ return std::nullopt;
+ }
+ return Aabs + CVec {(b1 * c2) / -determinant, (a1 * c2) / determinant};
+}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q>
std::pair<glm::vec<2, T, Q>, bool>
find_arc_centre(glm::vec<2, T, Q> start, Rotation2D startDir, glm::vec<2, T, Q> end, Rotation2D endDir)
{
@@ -204,17 +372,17 @@ find_arc_centre(glm::vec<2, T, Q> start, Rotation2D startDir, glm::vec<2, T, Q>
throw std::runtime_error("no intersection");
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q>
std::pair<glm::vec<2, T, Q>, bool>
find_arc_centre(glm::vec<2, T, Q> start, Angle entrys, glm::vec<2, T, Q> end, Angle entrye)
{
if (start == end) {
return {start, false};
}
- return find_arc_centre(start, sincosf(entrys + half_pi), end, sincosf(entrye - half_pi));
+ return find_arc_centre(start, sincos(entrys + half_pi), end, sincos(entrye - half_pi));
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q>
Angle
find_arcs_radius(glm::vec<2, T, Q> start, Rotation2D ad, glm::vec<2, T, Q> end, Rotation2D bd)
{
@@ -239,33 +407,46 @@ find_arcs_radius(glm::vec<2, T, Q> start, Rotation2D ad, glm::vec<2, T, Q> end,
/ (2 * (sq(X) - 2 * X * Z + sq(Z) + sq(Y) - 2 * Y * W + sq(W) - 4));
}
-template<typename T, glm::qualifier Q>
+template<Arithmetic T, glm::qualifier Q>
std::pair<Angle, Angle>
find_arcs_radius(glm::vec<2, T, Q> start, Angle entrys, glm::vec<2, T, Q> end, Angle entrye)
{
const auto getrad = [&](auto leftOrRight) {
- return find_arcs_radius(start, sincosf(entrys + leftOrRight), end, sincosf(entrye + leftOrRight));
+ return find_arcs_radius(start, sincos(entrys + leftOrRight), end, sincos(entrye + leftOrRight));
};
return {getrad(-half_pi), getrad(half_pi)};
}
-template<typename T>
+template<Arithmetic T>
auto
midpoint(const std::pair<T, T> & v)
{
return std::midpoint(v.first, v.second);
}
+// std::pow is not constexpr
+template<Arithmetic T>
+ requires requires(T n) { n *= n; }
+constexpr T
+pow(const T base, std::integral auto exp)
+{
+ T res {1};
+ while (exp--) {
+ res *= base;
+ }
+ return res;
+}
+
// Conversions
-template<typename T>
-inline constexpr auto
+template<Arithmetic T>
+constexpr auto
mph_to_ms(T v)
{
return v / 2.237L;
}
-template<typename T>
-inline constexpr auto
+template<Arithmetic T>
+constexpr auto
kph_to_ms(T v)
{
return v / 3.6L;
@@ -274,3 +455,9 @@ kph_to_ms(T v)
// ... literals are handy for now, probably go away when we load stuff externally
float operator"" _mph(const long double v);
float operator"" _kph(const long double v);
+
+constexpr float
+operator"" _degrees(long double degrees)
+{
+ return static_cast<float>(degrees) * degreesToRads;
+}
diff --git a/lib/stream_support.h b/lib/stream_support.h
index 57d82a1..f21622a 100644
--- a/lib/stream_support.h
+++ b/lib/stream_support.h
@@ -4,8 +4,10 @@
#include <glm/glm.hpp>
#include <iostream>
#include <maths.h>
+#include <source_location>
#include <span>
#include <sstream>
+#include <tuple>
#include <type_traits>
template<typename S>
@@ -16,14 +18,14 @@ concept NonStringIterableCollection
namespace std {
std::ostream &
- operator<<(std::ostream & s, const NonStringIterableCollection auto & v)
+ operator<<(std::ostream & s, const NonStringIterableCollection auto & collection)
{
s << '(';
- for (const auto & i : v) {
- if (&i != &*v.begin()) {
+ for (size_t nth {}; const auto & element : collection) {
+ if (nth++) {
s << ", ";
}
- s << i;
+ s << element;
}
return s << ')';
}
@@ -49,6 +51,22 @@ namespace std {
return (s << '(' << v.first << ", " << v.second << ')');
}
+ namespace {
+ template<typename... T, size_t... Idx>
+ std::ostream &
+ printTuple(std::ostream & s, const std::tuple<T...> & v, std::integer_sequence<size_t, Idx...>)
+ {
+ return ((s << (Idx ? ", " : "") << std::get<Idx>(v)), ...);
+ }
+ }
+
+ template<typename... T>
+ std::ostream &
+ operator<<(std::ostream & s, const std::tuple<T...> & v)
+ {
+ return printTuple(s << '{', v, std::make_index_sequence<sizeof...(T)>()) << '}';
+ }
+
inline std::ostream &
operator<<(std::ostream & s, const Arc & arc)
{
@@ -75,4 +93,14 @@ streamed_string(const T & v)
return std::move(ss).str();
}
-#define CLOG(x) std::cerr << __LINE__ << " : " #x " : " << x << "\n";
+namespace {
+ template<typename T>
+ void
+ clogImpl(const T & value, const std::string_view name,
+ const std::source_location loc = std::source_location::current())
+ {
+ std::cerr << loc.line() << " : " << name << " : " << value << "\n";
+ }
+}
+
+#define CLOG(x) clogImpl(x, #x)
diff --git a/lib/triangle.h b/lib/triangle.h
new file mode 100644
index 0000000..812bfab
--- /dev/null
+++ b/lib/triangle.h
@@ -0,0 +1,108 @@
+#pragma once
+
+#include "config/types.h"
+#include "maths.h"
+#include <glm/glm.hpp>
+
+template<glm::length_t Dim, Arithmetic T, glm::qualifier Q = glm::defaultp>
+struct Triangle : public glm::vec<3, glm::vec<Dim, T, Q>> {
+ using Point = glm::vec<Dim, T, Q>;
+ using Base = glm::vec<3, glm::vec<Dim, T, Q>>;
+ using Base::Base;
+
+ [[nodiscard]] constexpr Point
+ operator*(BaryPosition bari) const
+ {
+ return p(0) + (sideDifference(1) * bari.x) + (sideDifference(2) * bari.y);
+ }
+
+ [[nodiscard]] constexpr Point
+ centroid() const
+ {
+ return [this]<glm::length_t... Axis>(std::integer_sequence<glm::length_t, Axis...>) {
+ return Point {(p(0)[Axis] + p(1)[Axis] + p(2)[Axis]) / 3 ...};
+ }(std::make_integer_sequence<glm::length_t, Dim>());
+ }
+
+ [[nodiscard]] constexpr auto
+ area() const
+ requires(Dim == 3)
+ {
+ return glm::length(crossProduct(sideDifference(1), sideDifference(2))) / T {2};
+ }
+
+ [[nodiscard]] constexpr Normal3D
+ normal() const
+ requires(Dim == 3)
+ {
+ return crossProduct(sideDifference(1), sideDifference(2));
+ }
+
+ [[nodiscard]] constexpr Normal3D
+ nnormal() const
+ requires(Dim == 3)
+ {
+ return glm::normalize(normal());
+ }
+
+ [[nodiscard]] constexpr auto
+ sideDifference(glm::length_t side) const
+ {
+ return difference(p(side), p(0));
+ }
+
+ [[nodiscard]] constexpr auto
+ angle(glm::length_t corner) const
+ {
+ return Arc {P(corner), P(corner + 2), P(corner + 1)}.length();
+ }
+
+ template<glm::length_t D = Dim>
+ [[nodiscard]] constexpr auto
+ angleAt(const glm::vec<D, T, Q> pos) const
+ requires(D <= Dim)
+ {
+ for (glm::length_t i {}; i < 3; ++i) {
+ if (glm::vec<D, T, Q> {p(i)} == pos) {
+ return angle(i);
+ }
+ }
+ return 0.F;
+ }
+
+ [[nodiscard]] constexpr auto
+ p(const glm::length_t idx) const
+ {
+ return Base::operator[](idx);
+ }
+
+ [[nodiscard]] constexpr auto
+ P(const glm::length_t idx) const
+ {
+ return Base::operator[](idx % 3);
+ }
+
+ [[nodiscard]] constexpr Point *
+ begin()
+ {
+ return &(Base::x);
+ }
+
+ [[nodiscard]] constexpr const Point *
+ begin() const
+ {
+ return &(Base::x);
+ }
+
+ [[nodiscard]] constexpr Point *
+ end()
+ {
+ return begin() + 3;
+ }
+
+ [[nodiscard]] constexpr const Point *
+ end() const
+ {
+ return begin() + 3;
+ }
+};
diff --git a/test/Jamfile.jam b/test/Jamfile.jam
index 0b830a8..3ab4c4c 100644
--- a/test/Jamfile.jam
+++ b/test/Jamfile.jam
@@ -63,6 +63,7 @@ run test-instancing.cpp : -- : test-glContainer : <library>test ;
run perf-instancing.cpp : \< : test-instancing : <library>benchmark <library>test ;
run test-glContainer.cpp : : : <library>test ;
run test-pack.cpp : : : <library>test ;
+run test-environment.cpp : : : <library>test ;
compile test-static-enumDetails.cpp ;
compile test-static-stream_support.cpp ;
explicit perf-assetFactory ;
diff --git a/test/test-assetFactory.cpp b/test/test-assetFactory.cpp
index 1c2c417..6036721 100644
--- a/test/test-assetFactory.cpp
+++ b/test/test-assetFactory.cpp
@@ -54,7 +54,7 @@ public:
environment(const SceneShader &, const SceneRenderer & sceneRenderer) const override
{
sceneRenderer.setAmbientLight({.4, .4, .4});
- sceneRenderer.setDirectionalLight({.6, .6, .6}, east + south + south + down, *this);
+ sceneRenderer.setDirectionalLight({.6, .6, .6}, {{0.9, 0.5}}, *this);
}
void
@@ -97,6 +97,7 @@ BOOST_AUTO_TEST_CASE(brush47xml, *boost::unit_test::timeout(5))
{
auto mf = AssetFactory::loadXML(RESDIR "/brush47.xml");
BOOST_REQUIRE(mf);
+ gameState.assets = mf->assets;
BOOST_REQUIRE_GE(mf->shapes.size(), 6);
BOOST_CHECK(mf->shapes.at("plane"));
BOOST_CHECK(mf->shapes.at("cylinder"));
@@ -126,6 +127,7 @@ BOOST_AUTO_TEST_CASE(foliage, *boost::unit_test::timeout(5))
{
auto mf = AssetFactory::loadXML(RESDIR "/foliage.xml");
BOOST_REQUIRE(mf);
+ gameState.assets = mf->assets;
auto tree_01_1 = mf->assets.at("Tree-01-1");
BOOST_REQUIRE(tree_01_1);
auto tree_01_1_f = std::dynamic_pointer_cast<Foliage>(tree_01_1);
@@ -144,6 +146,7 @@ BOOST_AUTO_TEST_CASE(lights, *boost::unit_test::timeout(5))
{
auto mf = AssetFactory::loadXML(RESDIR "/lights.xml");
BOOST_REQUIRE(mf);
+ gameState.assets = mf->assets;
auto rlight = mf->assets.at("r-light");
BOOST_REQUIRE(rlight);
auto oldlamp = mf->assets.at("old-lamp");
diff --git a/test/test-environment.cpp b/test/test-environment.cpp
new file mode 100644
index 0000000..8bd64be
--- /dev/null
+++ b/test/test-environment.cpp
@@ -0,0 +1,63 @@
+#define BOOST_TEST_MODULE environment
+#include "testHelpers.h"
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/unit_test.hpp>
+#include <cmath>
+#include <stream_support.h>
+
+#include <chronology.h>
+#include <config/types.h>
+#include <game/environment.h>
+#include <gfx/lightDirection.h>
+#include <maths.h>
+
+using sunPosTestData = std::tuple<Direction2D, time_t, Direction2D>;
+using sunDirTestData = std::tuple<Direction2D, Direction3D, float, float>;
+constexpr Direction2D Doncaster = {-1.1, 53.5};
+constexpr Direction2D NewYork = {74.0, 40.7};
+constexpr Direction2D Syndey = {-151.2, -33.9};
+constexpr Direction2D EqGM = {};
+
+BOOST_DATA_TEST_CASE(sun_position,
+ boost::unit_test::data::make<sunPosTestData>({
+ {EqGM, "2024-01-02T00:00:00"_time_t, {181.52F, -66.86F}},
+ {EqGM, "2024-01-02T06:00:00"_time_t, {113.12F, -0.85F}},
+ {EqGM, "2024-01-02T06:30:00"_time_t, {113.12F, 6.05F}},
+ {EqGM, "2024-01-02T12:00:00"_time_t, {177.82F, 66.97F}},
+ {EqGM, "2024-01-02T18:00:00"_time_t, {246.99F, 0.90F}},
+ {EqGM, "2024-01-03T00:00:00"_time_t, {181.52F, -67.04F}},
+ {EqGM, "2024-06-29T12:00:00"_time_t, {2.1F, 66.80F}},
+ {Doncaster, "2024-06-29T12:00:00"_time_t, {176.34F, 59.64F}},
+ {NewYork, "2024-06-29T12:00:00"_time_t, {278.04F, 27.34F}},
+ {Syndey, "2024-06-29T12:00:00"_time_t, {106.13F, -63.29F}},
+ }),
+ position, timeOfYear, expSunPos)
+{
+ const auto sunPos = Environment::getSunPos(position * degreesToRads, timeOfYear) / degreesToRads;
+ BOOST_CHECK_CLOSE(sunPos.x, expSunPos.x, 1.F);
+ BOOST_CHECK_CLOSE(sunPos.y, expSunPos.y, 1.F);
+}
+
+BOOST_DATA_TEST_CASE(sun_direction,
+ boost::unit_test::data::make<sunDirTestData>({
+ {{0.F, 0.F}, south, 0.314F, 0.0087F},
+ {{90.F, 0.F}, west, 0.314F, 0.0087F},
+ {{-90.F, 0.F}, east, 0.314F, 0.0087F},
+ // From above
+ // EqGM midnight, sun below horizon, shining upwards
+ {{181.52F, -66.86F}, {-0.01F, 0.39F, 0.919F}, 0, 0.F},
+ // EqGM just before sunrise, mostly west, north a bit, up a bit
+ {{113.12F, -0.85F}, {-0.92F, 0.39F, 0.015F}, 0.299F, 0.F},
+ // EqGM just after sunrise, mostly west, north a bit, down a bit
+ {{113.12F, 6.05F}, {-0.92F, 0.39F, -0.015F}, 0.42F, 0.114F},
+ // Doncaster noon, roughly from south to north, high in the sky, downward
+ {{176.34F, 59.64F}, {-0.03F, 0.5F, -0.86F}, 1, 1},
+ }),
+ position, direction, amb, dir)
+{
+ const LightDirection ld {position * degreesToRads};
+ BOOST_CHECK_CLOSE_VEC(ld.vector(), direction);
+ BOOST_CHECK_CLOSE(glm::length(ld.vector()), 1.F, 1);
+ BOOST_CHECK_CLOSE(ld.ambient(), amb, 5);
+ BOOST_CHECK_CLOSE(ld.directional(), dir, 5);
+}
diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp
index 11d634d..bd1ff87 100644
--- a/test/test-geoData.cpp
+++ b/test/test-geoData.cpp
@@ -31,18 +31,16 @@ BOOST_AUTO_TEST_CASE(loadSuccess)
BOOST_AUTO_TEST_CASE(normalsAllPointUp)
{
- BOOST_CHECK_EQUAL(std::count_if(vertices_begin(), vertices_end(),
- [this](auto && vh) {
- return normal(vh).z > 0;
- }),
- n_vertices());
+ BOOST_CHECK(std::ranges::all_of(vertices(), [this](auto && vertex) {
+ return normal(vertex).z > 0;
+ }));
}
BOOST_AUTO_TEST_CASE(trianglesContainsPoints)
{
const auto face = face_handle(0);
- BOOST_TEST_CONTEXT(GeoData::Triangle<2>(this, fv_range(face))) {
+ BOOST_TEST_CONTEXT(this->triangle<2>(face)) {
BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner}, face));
BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner + cellsize, yllcorner + cellsize}, face));
BOOST_CHECK(triangleContainsPoint(GlobalPosition2D {xllcorner, yllcorner + cellsize}, face));
@@ -168,7 +166,7 @@ BOOST_DATA_TEST_CASE(walkTerrainSetsFromFace,
from, to, visits)
{
GeoData::PointFace pf {from};
- BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) {}));
+ BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) { }));
BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), visits.front());
}
@@ -232,7 +230,10 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/
Surface surface;
surface.colorBias = RGB {0, 0, 1};
auto gd = std::make_shared<GeoData>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 100));
- BOOST_CHECK_NO_THROW(gd->setHeights(points, surface));
+ BOOST_CHECK_NO_THROW(gd->setHeights(points, {.surface = surface}));
+ BOOST_CHECK(std::ranges::all_of(gd->vertices(), [&gd](auto && vertex) {
+ return gd->normal(vertex).z > 0;
+ }));
ApplicationBase ab;
TestMainWindow tmw;
@@ -253,7 +254,7 @@ BOOST_DATA_TEST_CASE(deform, loadFixtureJson<DeformTerrainData>("geoData/deform/
environment(const SceneShader &, const SceneRenderer & sr) const override
{
sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ sr.setDirectionalLight({1, 1, 1}, {{quarter_pi, -3 * half_pi}}, *this);
}
void
diff --git a/test/test-maths.cpp b/test/test-maths.cpp
index ccfb113..b9d08bb 100644
--- a/test/test-maths.cpp
+++ b/test/test-maths.cpp
@@ -107,9 +107,9 @@ const auto angs = boost::unit_test::data::make({pi, half_pi, two_pi, quarter_pi,
* boost::unit_test::data::make(0);
const auto random_angs = boost::unit_test::data::random(-two_pi, two_pi) ^ boost::unit_test::data::xrange(1000);
const auto rots = boost::unit_test::data::make<std::tuple<glm::vec3, glm::mat4 (*)(float), std::string_view>>({
- {down, rotate_yaw, "yaw"},
- {east, rotate_pitch, "pitch"},
- {north, rotate_roll, "roll"},
+ {down, rotate_yaw<4>, "yaw"},
+ {east, rotate_pitch<4>, "pitch"},
+ {north, rotate_roll<4>, "roll"},
});
BOOST_DATA_TEST_CASE(test_rotations, (angs + random_angs) * rots, angle, ai, axis, ilt_func, name)
@@ -247,13 +247,13 @@ BOOST_DATA_TEST_CASE(curve1,
BOOST_CHECK_EQUAL(l.radius, 1.F);
{
const auto p = l.positionAt(0, 0);
- const auto angForReversed = normalize(vector_yaw(-e1) * 2 - angFor);
+ const auto angForReversed = normalize(vector_yaw(difference({}, e1)) * 2 - angFor);
BOOST_CHECK_CLOSE_VECI(p.pos, e1);
BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angForReversed, 0));
}
{
const auto p = l.positionAt(0, 1);
- const auto angBackReversed = normalize(vector_yaw(e1) * 2 - angBack);
+ const auto angBackReversed = normalize(vector_yaw(difference(e1, {})) * 2 - angBack);
BOOST_CHECK_CLOSE_VECI(p.pos, GlobalPosition3D {});
BOOST_CHECK_CLOSE_VEC(p.rot, glm::vec3(0, angBackReversed, 0));
}
@@ -333,3 +333,11 @@ BOOST_DATA_TEST_CASE(rayLineDistance,
BOOST_CHECK_LE(Ray<RelativePosition3D>(c, direction).distanceToLine(n1, n2), 0.01F);
}
}
+
+static_assert(linesIntersectAt(glm::ivec2 {10, 10}, {40, 40}, {10, 80}, {20, 40}).value().x == 24);
+static_assert(linesIntersectAt(glm::vec2 {10, 10}, {40, 40}, {10, 80}, {20, 40}).value().y == 24);
+static_assert(linesIntersectAt(GlobalPosition2D {311000100, 491100100}, {311050000, 491150000}, {312000100, 491200100},
+ {311000100, 491100100})
+ .value()
+ == GlobalPosition2D {311000100, 491100100});
+static_assert(!linesIntersectAt(glm::dvec2 {0, 1}, {0, 4}, {1, 8}, {1, 4}).has_value());
diff --git a/test/test-render.cpp b/test/test-render.cpp
index b9a809e..3966f28 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -7,6 +7,8 @@
#include <boost/test/unit_test.hpp>
#include <assetFactory/assetFactory.h>
+#include <game/environment.h>
+#include <game/gamestate.h>
#include <game/geoData.h>
#include <game/network/rail.h>
#include <game/scenary/foliage.h>
@@ -25,14 +27,11 @@
#include <ui/window.h>
class TestScene : public SceneProvider {
- const RailVehicleClassPtr brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(
- AssetFactory::loadXML(RESDIR "/brush47.xml")->assets.at("brush-47"));
- const std::shared_ptr<Foliage> tree021f
- = std::dynamic_pointer_cast<Foliage>(AssetFactory::loadXML(RESDIR "/foliage.xml")->assets.at("Tree-02-1"));
+ RailVehicleClassPtr brush47rvc;
std::shared_ptr<RailVehicle> train1, train2;
- std::shared_ptr<Plant> plant1;
RailLinks rail;
std::shared_ptr<GeoData> gd = std::make_shared<GeoData>(GeoData::createFlat({0, 0}, {1000000, 1000000}, 1));
+ std::shared_ptr<Environment> env = std::make_shared<Environment>();
Terrain terrain {gd};
Water water {gd};
@@ -40,6 +39,13 @@ class TestScene : public SceneProvider {
public:
TestScene()
{
+ gameState->assets = AssetFactory::loadAll(RESDIR);
+ brush47rvc = std::dynamic_pointer_cast<RailVehicleClass>(gameState->assets.at("brush-47"));
+ std::random_device randomdev {};
+ std::uniform_real_distribution<Angle> rotationDistribution {0, two_pi};
+ std::uniform_int_distribution<GlobalDistance> positionOffsetDistribution {-1500, +1500};
+ std::uniform_int_distribution<int> treeDistribution {1, 3};
+ std::uniform_int_distribution<int> treeVariantDistribution {1, 4};
train1 = std::make_shared<RailVehicle>(brush47rvc);
train1->location.setPosition({52000, 50000, 2000});
train1->bogies.front().setPosition(train1->bogies.front().position() + train1->location.position());
@@ -48,7 +54,16 @@ public:
train2->location.setPosition({52000, 30000, 2000});
train2->bogies.front().setPosition(train2->bogies.front().position() + train2->location.position());
train2->bogies.back().setPosition(train2->bogies.back().position() + train2->location.position());
- plant1 = std::make_shared<Plant>(tree021f, Location {{40000, 60000, 1}, {}});
+ for (auto x = 40000; x < 100000; x += 5000) {
+ for (auto y = 65000; y < 125000; y += 5000) {
+ gameState->world.create<Plant>(
+ std::dynamic_pointer_cast<Foliage>(gameState->assets.at(std::format(
+ "Tree-{:#02}-{}", treeDistribution(randomdev), treeVariantDistribution(randomdev)))),
+ Location {{x + positionOffsetDistribution(randomdev), y + positionOffsetDistribution(randomdev),
+ 1},
+ {0, rotationDistribution(randomdev), 0}});
+ }
+ }
rail.addLinksBetween({42000, 50000, 1000}, {65000, 50000, 1000});
rail.addLinksBetween({65000, 50000, 1000}, {75000, 45000, 2000});
}
@@ -58,9 +73,12 @@ public:
{
terrain.render(shader);
water.render(shader);
- brush47rvc->render(shader);
- tree021f->render(shader);
rail.render(shader);
+ std::ranges::for_each(gameState->assets, [&shader](const auto & asset) {
+ if (const auto renderable = std::dynamic_pointer_cast<const Renderable>(asset.second)) {
+ renderable->render(shader);
+ }
+ });
}
void
@@ -69,11 +87,20 @@ public:
}
void
+ environment(const SceneShader &, const SceneRenderer & r) const override
+ {
+ env->render(r, *this);
+ }
+
+ void
shadows(const ShadowMapper & shadowMapper) const override
{
terrain.shadows(shadowMapper);
- brush47rvc->shadows(shadowMapper);
- tree021f->shadows(shadowMapper);
+ std::ranges::for_each(gameState->assets, [&shadowMapper](const auto & asset) {
+ if (const auto renderable = std::dynamic_pointer_cast<const Renderable>(asset.second)) {
+ renderable->shadows(shadowMapper);
+ }
+ });
}
};
@@ -156,7 +183,7 @@ BOOST_AUTO_TEST_CASE(terrain)
environment(const SceneShader &, const SceneRenderer & sr) const override
{
sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ sr.setDirectionalLight({1, 1, 1}, {{0, quarter_pi}}, *this);
}
void
@@ -203,7 +230,7 @@ BOOST_AUTO_TEST_CASE(railnet)
environment(const SceneShader &, const SceneRenderer & sr) const override
{
sr.setAmbientLight({0.1, 0.1, 0.1});
- sr.setDirectionalLight({1, 1, 1}, south + down, *this);
+ sr.setDirectionalLight({1, 1, 1}, {{0, quarter_pi}}, *this);
}
void
diff --git a/test/testRenderOutput.h b/test/testRenderOutput.h
index 056d029..79908b1 100644
--- a/test/testRenderOutput.h
+++ b/test/testRenderOutput.h
@@ -1,6 +1,7 @@
#pragma once
#include "config/types.h"
+#include "game/gamestate.h"
#include "glArrays.h"
#include <glm/vec2.hpp>
#include <special_members.h>
@@ -17,6 +18,7 @@ public:
glFrameBuffer output;
glRenderBuffer depth;
glTexture outImage;
+ GameState gameState;
};
template<TextureAbsCoord Size> class TestRenderOutputSize : public TestRenderOutput {
diff --git a/thirdparty/ctre b/thirdparty/ctre
-Subproject b3d7788b559e34d985c8530c3e0e7260b67505a
+Subproject acb2f4de2e24a06280088377e47534137c0bc75
diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp
index 6168504..c53300b 100644
--- a/ui/gameMainWindow.cpp
+++ b/ui/gameMainWindow.cpp
@@ -1,16 +1,17 @@
#include "gameMainWindow.h"
#include "editNetwork.h"
#include "gameMainSelector.h"
-#include "gfx/camera_controller.h"
#include "manualCameraController.h"
#include "modeHelper.h"
#include "toolbar.h"
#include "window.h"
#include <SDL2/SDL.h>
#include <collection.h>
+#include <game/environment.h>
#include <game/gamestate.h>
#include <game/network/rail.h>
#include <game/worldobject.h> // IWYU pragma: keep
+#include <gfx/camera_controller.h>
#include <gfx/renderable.h>
#include <glad/gl.h>
#include <glm/glm.hpp>
@@ -65,10 +66,9 @@ GameMainWindow::content(const SceneShader & shader) const
}
void
-GameMainWindow::environment(const SceneShader & s, const SceneRenderer & r) const
+GameMainWindow::environment(const SceneShader &, const SceneRenderer & r) const
{
- // default for now
- SceneProvider::environment(s, r);
+ gameState->environment->render(r, *this);
}
void
diff --git a/ui/manualCameraController.cpp b/ui/manualCameraController.cpp
index 065e1d8..53905eb 100644
--- a/ui/manualCameraController.cpp
+++ b/ui/manualCameraController.cpp
@@ -79,6 +79,6 @@ ManualCameraController::render(const UIShader &, const Position &) const
void
ManualCameraController::updateCamera(Camera * camera) const
{
- const auto forward = glm::normalize(sincosf(direction) || -sin(pitch));
+ const auto forward = glm::normalize(sincos(direction) || -sin(pitch));
camera->setView((focus || 0) - (forward * 3.F * std::pow(dist, 1.3F)), forward);
}