summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2024-10-22 01:01:02 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2024-10-22 01:01:02 +0100
commit9edf8711471db08427c5441ed37b6dfe3dd7f3b4 (patch)
tree4356058e9fd85e44c4404c5db8d5d3322a64aa29
parentBump to CTRE to v3.9.0-1-gacb2f4d to fix compilation with clang 19 (diff)
parentFurther template maths functions (diff)
downloadilt-9edf8711471db08427c5441ed37b6dfe3dd7f3b4.tar.bz2
ilt-9edf8711471db08427c5441ed37b6dfe3dd7f3b4.tar.xz
ilt-9edf8711471db08427c5441ed37b6dfe3dd7f3b4.zip
Merge branch 'billboard-shadows'
-rw-r--r--application/main.cpp13
-rw-r--r--assetFactory/cylinder.cpp2
-rw-r--r--assetFactory/mutation.cpp5
-rw-r--r--config/types.h1
-rw-r--r--game/environment.cpp11
-rw-r--r--game/geoData.cpp2
-rw-r--r--game/network/link.cpp6
-rw-r--r--game/network/network.cpp10
-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--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/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.cpp8
-rw-r--r--gfx/lightDirection.h32
-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/maths.cpp101
-rw-r--r--lib/maths.h310
-rw-r--r--test/test-assetFactory.cpp5
-rw-r--r--test/test-environment.cpp27
-rw-r--r--test/test-geoData.cpp4
-rw-r--r--test/test-maths.cpp10
-rw-r--r--test/test-render.cpp45
-rw-r--r--test/testRenderOutput.h2
-rw-r--r--ui/manualCameraController.cpp2
45 files changed, 832 insertions, 274 deletions
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/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
index 19aad84..5aa1f09 100644
--- a/game/environment.cpp
+++ b/game/environment.cpp
@@ -1,4 +1,5 @@
#include "environment.h"
+#include "gfx/lightDirection.h"
#include <chronology.h>
#include <gfx/gl/sceneRenderer.h>
@@ -16,14 +17,12 @@ Environment::render(const SceneRenderer & renderer, const SceneProvider & scene)
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 auto sunPos = getSunPos({}, worldTime);
- const auto sunDir = (glm::mat3 {rotate_yp({sunPos.y + pi, sunPos.x})} * north);
- const auto vertical = -std::min(0.F, sunDir.z - 0.1F);
- const auto ambient = baseAmbient + relativeAmbient * vertical;
- const auto directional = baseDirectional + relativeDirectional * vertical;
+ const LightDirection sunPos = getSunPos({}, worldTime);
+ const auto ambient = baseAmbient + relativeAmbient * sunPos.vertical();
+ const auto directional = baseDirectional + relativeDirectional * sunPos.vertical();
renderer.setAmbientLight(ambient);
- renderer.setDirectionalLight(directional, sunDir, scene);
+ renderer.setDirectionalLight(directional, sunPos, scene);
}
// Based on the C++ code published at https://www.psa.es/sdg/sunpos.htm
diff --git a/game/geoData.cpp b/game/geoData.cpp
index 72aa056..d212651 100644
--- a/game/geoData.cpp
+++ b/game/geoData.cpp
@@ -559,7 +559,7 @@ GeoData::setHeights(const std::span<const GlobalPosition3D> triangleStrip, const
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)));
+ addExtrusionFor(sincos(arc.first + (step * inc)));
}
}
else {
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/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/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/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..6d7d1ea
--- /dev/null
+++ b/gfx/lightDirection.cpp
@@ -0,0 +1,8 @@
+#include "lightDirection.h"
+#include "maths.h"
+
+LightDirection::LightDirection(const Direction2D sunPos) :
+ pos {sunPos}, vec {glm::mat3 {rotate_yp(pi + sunPos.x, -sunPos.y)} * north},
+ vert {-glm::clamp(-1.F, 0.F, vec.z - 0.1F)}
+{
+}
diff --git a/gfx/lightDirection.h b/gfx/lightDirection.h
new file mode 100644
index 0000000..f1e1cc4
--- /dev/null
+++ b/gfx/lightDirection.h
@@ -0,0 +1,32 @@
+#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
+ vertical() const noexcept
+ {
+ return vert;
+ }
+
+private:
+ Direction2D pos;
+ Direction3D vec;
+ float vert;
+};
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/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 3127d3c..90ddb69 100644
--- a/lib/maths.h
+++ b/lib/maths.h
@@ -17,22 +17,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,7 +40,7 @@ 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;
@@ -48,152 +48,283 @@ constexpr auto earthMeanRadius = 6371.01F; // In km
constexpr auto astronomicalUnit = 149597890.F; // In km
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g + GlobalPosition<D>(glm::round(r));
+ return global + GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator+(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator+(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
{
- return g + GlobalPosition<D>(r);
+ return global + GlobalPosition<D>(relative);
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const RelativePosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const RelativePosition<D> & relative)
{
- return g - GlobalPosition<D>(glm::round(r));
+ return global - GlobalPosition<D>(glm::round(relative));
}
template<glm::length_t D>
-constexpr inline GlobalPosition<D>
-operator-(const GlobalPosition<D> & g, const CalcPosition<D> & r)
+constexpr GlobalPosition<D>
+operator-(const GlobalPosition<D> & global, const CalcPosition<D> & relative)
{
- return g - GlobalPosition<D>(r);
+ return global - GlobalPosition<D>(relative);
+}
+
+template<glm::length_t D, 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 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, typename 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 rotation<D, T, Q>(angle, {0, 0}, {0, 1}, {1, 1}, {1, 0});
+}
+
+// Create a yaw transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 2)
+constexpr auto
+rotate_yaw(const T angle)
+{
+ return rotation<D, T, Q>(angle, {0, 0}, {1, 0}, {1, 1}, {0, 1});
+}
+
+// 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});
+}
+
+// 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});
+}
+
+// Create a combined yaw, pitch, roll transformation matrix
+template<glm::length_t D = 3, glm::qualifier Q = glm::qualifier::defaultp, std::floating_point T>
+ requires(D >= 3)
+constexpr auto
+rotate_ypr(const glm::vec<3, T, Q> & angles)
+{
+ return rotate_yaw<D>(angles.y) * rotate_pitch<D>(angles.x) * rotate_roll<D>(angles.z);
+}
+
+template<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 sincosf(a, &s, &c);
+ return rotate_yaw<D>(yaw) * rotate_pitch<D>(pitch);
}
-inline Rotation2D
-sincosf(float a)
+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)
{
- Rotation2D sc;
- sincosf(a, sc.x, sc.y);
- return sc;
+ return rotate_yp<D>(angles.y, angles.x);
}
-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);
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, typename T>
+ requires(D >= 2)
+constexpr auto
+vector_yaw(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan2(diff.x, diff.y);
+}
-float vector_yaw(const Direction2D & diff);
-float vector_pitch(const Direction3D & diff);
+template<glm::length_t D, glm::qualifier Q = glm::qualifier::defaultp, typename T>
+ requires(D >= 3)
+constexpr auto
+vector_pitch(const glm::vec<D, T, Q> & diff)
+{
+ return std::atan(diff.z);
+}
template<typename T, glm::qualifier Q>
-glm::vec<2, T, Q>
-vector_normal(const glm::vec<2, T, Q> & v)
+constexpr glm::vec<2, T, Q>
+vector_normal(const glm::vec<2, T, Q> & vector)
{
- return {-v.y, v.x};
+ 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)
+ 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)
+constexpr auto
+ratio(const Ta valueA, const Tb valueB)
{
- return (static_cast<R>(a) / static_cast<R>(b));
+ return (static_cast<R>(valueA) / static_cast<R>(valueB));
}
template<typename R = float, typename T, glm::qualifier Q>
-inline constexpr auto
-ratio(glm::vec<2, T, Q> v)
+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)
+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)
+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);
}
-float normalize(float ang);
+template<std::floating_point T>
+constexpr T
+normalize(T ang)
+{
+ while (ang > glm::pi<T>()) {
+ ang -= glm::two_pi<T>();
+ }
+ while (ang <= -glm::pi<T>()) {
+ ang += glm::two_pi<T>();
+ }
+ return ang;
+}
template<typename T, glm::qualifier Q>
std::pair<glm::vec<2, T, Q>, bool>
@@ -215,7 +346,7 @@ find_arc_centre(glm::vec<2, T, Q> start, Angle entrys, glm::vec<2, T, Q> end, An
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>
@@ -248,7 +379,7 @@ 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)};
}
@@ -260,16 +391,29 @@ midpoint(const std::pair<T, T> & v)
return std::midpoint(v.first, v.second);
}
+// std::pow is not constexpr
+template<typename 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
+constexpr auto
mph_to_ms(T v)
{
return v / 2.237L;
}
template<typename T>
-inline constexpr auto
+constexpr auto
kph_to_ms(T v)
{
return v / 3.6L;
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
index b6e0e4f..76144a4 100644
--- a/test/test-environment.cpp
+++ b/test/test-environment.cpp
@@ -1,4 +1,5 @@
#define BOOST_TEST_MODULE environment
+#include "testHelpers.h"
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
#include <cmath>
@@ -7,9 +8,11 @@
#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>;
constexpr Direction2D Doncaster = {-1.1, 53.5};
constexpr Direction2D NewYork = {74.0, 40.7};
constexpr Direction2D Syndey = {-151.2, -33.9};
@@ -19,6 +22,7 @@ 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}},
@@ -33,3 +37,26 @@ BOOST_DATA_TEST_CASE(sun_position,
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.1F},
+ {{90.F, 0.F}, west, 0.1F},
+ {{-90.F, 0.F}, east, 0.1F},
+ // From above
+ // EqGM midnight, sun below horizon, shining upwards
+ {{181.52F, -66.86F}, {-0.01F, 0.39F, 0.919F}, 0},
+ // EqGM just before sunrise, mostly west, north a bit, up a bit
+ {{113.12F, -0.85F}, {-0.92F, 0.39F, 0.015F}, 0.085F},
+ // EqGM just after sunrise, mostly west, north a bit, down a bit
+ {{113.12F, 6.05F}, {-0.92F, 0.39F, -0.015F}, 0.205F},
+ // Doncaster noon, roughly from south to north, high in the sky, downward
+ {{176.34F, 59.64F}, {-0.03F, 0.5F, -0.86F}, 0.96F},
+ }),
+ position, direction, vert)
+{
+ 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.vertical(), vert, 5);
+}
diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp
index 11d634d..35d6bae 100644
--- a/test/test-geoData.cpp
+++ b/test/test-geoData.cpp
@@ -168,7 +168,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());
}
@@ -253,7 +253,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..f7f34b3 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));
}
diff --git a/test/test-render.cpp b/test/test-render.cpp
index ea53708..3966f28 100644
--- a/test/test-render.cpp
+++ b/test/test-render.cpp
@@ -1,4 +1,3 @@
-#include "game/environment.h"
#define BOOST_TEST_MODULE test_render
#include "testHelpers.h"
@@ -8,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>
@@ -26,12 +27,8 @@
#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>();
@@ -42,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());
@@ -50,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});
}
@@ -60,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
@@ -80,8 +96,11 @@ public:
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);
+ }
+ });
}
};
@@ -164,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
@@ -211,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/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);
}