diff options
63 files changed, 1281 insertions, 664 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/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..acb4f21 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.ambient(); + const auto directional = baseDirectional + relativeDirectional * sunPos.directional(); 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..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/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 3127d3c..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,7 +44,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,154 +52,314 @@ 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 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 sincosf(a, &s, &c); + return rotate_yaw<D>(angles.y) * rotate_pitch<D>(angles.x) * rotate_roll<D>(angles.z); } -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 T yaw, const T pitch) { - Rotation2D sc; - sincosf(a, sc.x, sc.y); - return sc; + return rotate_yaw<D>(yaw) * rotate_pitch<D>(pitch); } -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 = 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); +} -float vector_yaw(const Direction2D & diff); -float vector_pitch(const Direction3D & diff); +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<typename T, glm::qualifier Q> -glm::vec<2, T, Q> -vector_normal(const glm::vec<2, T, Q> & v) +template<std::floating_point T, glm::qualifier Q> +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) +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) { @@ -208,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) { @@ -243,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; @@ -278,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/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..8bd64be 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, 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,27 @@ 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.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 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/thirdparty/ctre b/thirdparty/ctre -Subproject b3d7788b559e34d985c8530c3e0e7260b67505a +Subproject acb2f4de2e24a06280088377e47534137c0bc75 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); } |