diff options
Diffstat (limited to 'game')
-rw-r--r-- | game/environment.cpp | 11 | ||||
-rw-r--r-- | game/geoData.cpp | 411 | ||||
-rw-r--r-- | game/geoData.h | 106 | ||||
-rw-r--r-- | game/network/link.cpp | 6 | ||||
-rw-r--r-- | game/network/network.cpp | 10 | ||||
-rw-r--r-- | game/network/network.h | 2 | ||||
-rw-r--r-- | game/network/rail.cpp | 12 | ||||
-rw-r--r-- | game/scenary/foliage.cpp | 27 | ||||
-rw-r--r-- | game/scenary/foliage.h | 11 | ||||
-rw-r--r-- | game/scenary/plant.cpp | 3 | ||||
-rw-r--r-- | game/terrain.cpp | 1 | ||||
-rw-r--r-- | game/terrain.h | 2 | ||||
-rw-r--r-- | game/vehicles/linkHistory.cpp | 18 | ||||
-rw-r--r-- | game/vehicles/train.cpp | 10 | ||||
-rw-r--r-- | game/vehicles/train.h | 2 | ||||
-rw-r--r-- | game/vehicles/vehicle.cpp | 2 | ||||
-rw-r--r-- | game/vehicles/vehicle.h | 2 |
17 files changed, 253 insertions, 383 deletions
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) |