From ec29d7bb5e786549eaa960016ddf511fad010cc5 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 9 Feb 2025 15:23:15 +0000 Subject: Split GeoData mesh basics into a subclass Declutters the class for terrain related things --- game/geoData.cpp | 160 +++------------------------------------------------ game/geoData.h | 74 +----------------------- game/geoDataMesh.cpp | 118 +++++++++++++++++++++++++++++++++++++ game/geoDataMesh.h | 83 ++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 222 deletions(-) create mode 100644 game/geoDataMesh.cpp create mode 100644 game/geoDataMesh.h (limited to 'game') diff --git a/game/geoData.cpp b/game/geoData.cpp index 3b94564..a1d9762 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -110,74 +110,6 @@ GeoData::createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistan return mesh; } -OpenMesh::FaceHandle -GeoData::findPoint(GlobalPosition2D p) const -{ - return findPoint(p, *faces_sbegin()); -} - -GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh) : - PointFace {p, mesh, *mesh->faces_sbegin()} -{ -} - -GeoData::PointFace::PointFace(const GlobalPosition2D p, const GeoData * mesh, FaceHandle start) : - PointFace {p, mesh->findPoint(p, start)} -{ -} - -GeoData::FaceHandle -GeoData::PointFace::face(const GeoData * mesh, FaceHandle start) const -{ - if (_face.is_valid() && mesh->triangleContainsPoint(point, _face)) { - return _face; - } - return (_face = mesh->findPoint(point, start)); -} - -GeoData::FaceHandle -GeoData::PointFace::face(const GeoData * mesh) const -{ - return face(mesh, *mesh->faces_sbegin()); -} - -namespace { - constexpr GlobalPosition3D - positionOnTriangle(const GlobalPosition2D point, const GeoData::Triangle<3> & t) - { - const CalcPosition3D a = t[1] - t[0], b = t[2] - t[0]; - const auto n = crossProduct(a, b); - return {point, ((n.x * t[0].x) + (n.y * t[0].y) + (n.z * t[0].z) - (n.x * point.x) - (n.y * point.y)) / n.z}; - } - - static_assert(positionOnTriangle({7, -2}, {{1, 2, 3}, {1, 0, 1}, {-2, 1, 0}}) == GlobalPosition3D {7, -2, 3}); -} - -OpenMesh::FaceHandle -GeoData::findPoint(GlobalPosition2D p, OpenMesh::FaceHandle f) const -{ - while (f.is_valid() && !triangleContainsPoint(p, triangle<2>(f))) { - for (auto next = cfh_iter(f); next.is_valid(); ++next) { - f = opposite_face_handle(*next); - if (f.is_valid()) { - const auto e1 = point(to_vertex_handle(*next)); - const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next))); - if (pointLeftOfLine(p, e1, e2)) { - break; - } - } - f.reset(); - } - } - return f; -} - -GlobalPosition3D -GeoData::positionAt(const PointFace & p) const -{ - return positionOnTriangle(p.point, triangle<3>(p.face(this))); -} - [[nodiscard]] GeoData::IntersectionResult GeoData::intersectRay(const Ray & ray) const { @@ -230,11 +162,12 @@ GeoData::walkUntil(const PointFace & from, const GlobalPosition2D to, Tester & t) -{ - return pointLeftOfOrOnLine(p, t[0], t[1]) && pointLeftOfOrOnLine(p, t[1], t[2]) - && pointLeftOfOrOnLine(p, t[2], t[0]); -} - -bool -GeoData::triangleContainsPoint(const GlobalPosition2D p, FaceHandle face) const -{ - return triangleContainsPoint(p, triangle<2>(face)); -} - -GeoData::HalfedgeHandle -GeoData::findBoundaryStart() const -{ - return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) { - return is_boundary(heh); - }); -} - -[[nodiscard]] RelativePosition3D -GeoData::difference(const HalfedgeHandle heh) const -{ - return ::difference(point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); -} - -template -[[nodiscard]] RelativeDistance -GeoData::length(const HalfedgeHandle heh) const -{ - return ::distance(point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); -} - -[[nodiscard]] GlobalPosition3D -GeoData::centre(const HalfedgeHandle heh) const -{ - return point(from_vertex_handle(heh)) + (difference(heh) / 2.F); -} - void GeoData::updateAllVertexNormals() { @@ -400,22 +293,6 @@ GeoData::updateVertexNormal(VertexHandle vertex) set_normal(vertex, glm::normalize(n)); } -bool -GeoData::triangleOverlapsTriangle(const Triangle<2> & a, const Triangle<2> & b) -{ - return triangleContainsPoint(a.x, b) || triangleContainsPoint(a.y, b) || triangleContainsPoint(a.z, b) - || triangleContainsPoint(b.x, a) || triangleContainsPoint(b.y, a) || triangleContainsPoint(b.z, a) - || linesCross(a.x, a.y, b.x, b.y) || linesCross(a.x, a.y, b.y, b.z) || linesCross(a.x, a.y, b.z, b.x) - || linesCross(a.y, a.z, b.x, b.y) || linesCross(a.y, a.z, b.y, b.z) || linesCross(a.y, a.z, b.z, b.x) - || linesCross(a.z, a.x, b.x, b.y) || linesCross(a.z, a.x, b.y, b.z) || linesCross(a.z, a.x, b.z, b.x); -} - -bool -GeoData::triangleContainsTriangle(const Triangle<2> & a, const Triangle<2> & b) -{ - return triangleContainsPoint(a.x, b) && triangleContainsPoint(a.y, b) && triangleContainsPoint(a.z, b); -} - std::vector GeoData::setHeights(const std::span triangleStrip, const SetHeightsOpts & opts) { @@ -489,7 +366,7 @@ GeoData::setHeights(const std::span triangleStrip, const 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); + return triangle.containsPoint(point); }); t != strip.end()) { return &*t; @@ -534,7 +411,7 @@ GeoData::setHeights(const std::span triangleStrip, const / distance(startPoint.xy(), endPoint.xy())) < opts.nearNodeTolerance) { start = adjVertex; - point(start).z = positionOnTriangle(adjPoint, triangle).z; + point(start).z = triangle.positionOnPlane(adjPoint).z; return true; } return false; @@ -554,7 +431,7 @@ GeoData::setHeights(const std::span triangleStrip, const flip(*nextEdge); return true; } - start = split_copy(edge_handle(next), positionOnTriangle(*intersection, triangle)); + start = split_copy(edge_handle(next), triangle.positionOnPlane(*intersection)); addVertexForNormalUpdate(start); boundaryTriangles.emplace(start, &triangle); return true; @@ -610,7 +487,7 @@ GeoData::setHeights(const std::span triangleStrip, const } } if (toTriangle) { // point within the new strip, adjust vertically by triangle - toPoint.z = positionOnTriangle(toPoint, *toTriangle).z; + toPoint.z = toTriangle->positionOnPlane(toPoint).z; addVertexForNormalUpdate(toVertex); todoOutHalfEdges(toVertex); } @@ -658,22 +535,3 @@ GeoData::getGeneration() const { return generation; } - -void -GeoData::sanityCheck(const std::source_location & loc) const -{ - if (const auto upSideDown = std::ranges::count_if(faces(), [this](const auto face) { - if (!triangle<2>(face).isUp()) { -#ifndef NDEBUG - for (const auto v : fv_range(face)) { - CLOG(point(v)); - } -#endif - return true; - } - return false; - }) > 0) { - throw std::logic_error(std::format( - "{} upside down faces detected - checked from {}:{}", upSideDown, loc.function_name(), loc.line())); - } -} diff --git a/game/geoData.h b/game/geoData.h index 03a2b3a..d486f22 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -1,28 +1,12 @@ #pragma once #include "collections.h" // IWYU pragma: keep IterableCollection -#include "config/types.h" -#include "ray.h" +#include "geoDataMesh.h" #include "surface.h" -#include "triangle.h" -#include #include #include -#include -#include -#include -#include - -struct GeoDataTraits : public OpenMesh::DefaultTraits { - FaceAttributes(OpenMesh::Attributes::Status); - EdgeAttributes(OpenMesh::Attributes::Status); - VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); - HalfedgeAttributes(OpenMesh::Attributes::Status); - using Point = GlobalPosition3D; - using Normal = Normal3D; -}; -class GeoData : public OpenMesh::TriMesh_ArrayKernelT { +class GeoData : public GeoDataMesh { private: const OpenMesh::Helpers::Property surface {this}; @@ -30,35 +14,6 @@ public: static GeoData loadFromAsciiGrid(const std::filesystem::path &); static GeoData createFlat(GlobalPosition2D lower, GlobalPosition2D upper, GlobalDistance h); - struct PointFace { - // NOLINTNEXTLINE(hicpp-explicit-conversions) - PointFace(const GlobalPosition2D p) : point {p} { } - - PointFace(const GlobalPosition2D p, FaceHandle face) : point {p}, _face {face} { } - - PointFace(const GlobalPosition2D p, const GeoData *); - PointFace(const GlobalPosition2D p, const GeoData *, FaceHandle start); - - const GlobalPosition2D point; - [[nodiscard]] FaceHandle face(const GeoData *) const; - [[nodiscard]] FaceHandle face(const GeoData *, FaceHandle start) const; - - [[nodiscard]] bool - isLocated() const - { - return _face.is_valid(); - } - - private: - mutable FaceHandle _face {}; - }; - - template using Triangle = ::Triangle; - - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const; - [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle start) const; - - [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const; using IntersectionLocation = std::pair; using IntersectionResult = std::optional; [[nodiscard]] IntersectionResult intersectRay(const Ray &) const; @@ -89,7 +44,7 @@ public: void boundaryWalkUntil(Tester) const; void boundaryWalkUntil(Tester, HalfedgeHandle start) const; - [[nodiscard]] HalfedgeHandle findEntry(const GlobalPosition2D from, const GlobalPosition2D to) const; + [[nodiscard]] HalfedgeHandle findEntry(GlobalPosition2D from, GlobalPosition2D to) const; struct SetHeightsOpts { static constexpr auto DEFAULT_NEAR_NODE_TOLERANACE = 500.F; @@ -118,30 +73,7 @@ public: return property(surface, handle); } - void sanityCheck(const std::source_location & = std::source_location::current()) const; - protected: - template - [[nodiscard]] Triangle - triangle(FaceHandle face) const - { - Triangle 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> &); - [[nodiscard]] bool triangleContainsPoint(const GlobalPosition2D, FaceHandle) const; - [[nodiscard]] static bool triangleOverlapsTriangle(const Triangle<2> &, const Triangle<2> &); - [[nodiscard]] static bool triangleContainsTriangle(const Triangle<2> &, const Triangle<2> &); - [[nodiscard]] HalfedgeHandle findBoundaryStart() const; - [[nodiscard]] RelativePosition3D difference(const HalfedgeHandle) const; - - template [[nodiscard]] RelativeDistance length(const HalfedgeHandle) const; - [[nodiscard]] GlobalPosition3D centre(const HalfedgeHandle) const; - void updateAllVertexNormals(); template void updateAllVertexNormals(const R &); void updateVertexNormal(VertexHandle); diff --git a/game/geoDataMesh.cpp b/game/geoDataMesh.cpp new file mode 100644 index 0000000..aaa8c9c --- /dev/null +++ b/game/geoDataMesh.cpp @@ -0,0 +1,118 @@ +#include "geoDataMesh.h" +#include +#ifndef NDEBUG +# include +#endif + +OpenMesh::FaceHandle +GeoDataMesh::findPoint(GlobalPosition2D coord) const +{ + return findPoint(coord, *faces_sbegin()); +} + +GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh) : + PointFace {coord, mesh, *mesh->faces_sbegin()} +{ +} + +GeoDataMesh::PointFace::PointFace(const GlobalPosition2D coord, const GeoDataMesh * mesh, FaceHandle start) : + PointFace {coord, mesh->findPoint(coord, start)} +{ +} + +OpenMesh::FaceHandle +GeoDataMesh::PointFace::face(const GeoDataMesh * mesh, FaceHandle start) const +{ + if (faceCache.is_valid() && mesh->faceContainsPoint(point, faceCache)) { + return faceCache; + } + return (faceCache = mesh->findPoint(point, start)); +} + +OpenMesh::FaceHandle +GeoDataMesh::PointFace::face(const GeoDataMesh * mesh) const +{ + return face(mesh, *mesh->faces_sbegin()); +} + +GeoDataMesh::HalfEdgeVertices +GeoDataMesh::toVertexHandles(HalfedgeHandle halfEdge) const +{ + return {from_vertex_handle(halfEdge), to_vertex_handle(halfEdge)}; +} + +GeoDataMesh::HalfEdgePoints +GeoDataMesh::points(HalfEdgeVertices vertices) const +{ + return {point(vertices.first), point(vertices.second)}; +} + +OpenMesh::FaceHandle +GeoDataMesh::findPoint(const GlobalPosition2D coord, OpenMesh::FaceHandle face) const +{ + while (face.is_valid() && !triangle<2>(face).containsPoint(coord)) { + for (auto next = cfh_iter(face); next.is_valid(); ++next) { + face = opposite_face_handle(*next); + if (face.is_valid()) { + const auto nextPoints = points(toVertexHandles(*next)); + if (pointLeftOfLine(coord, nextPoints.second, nextPoints.first)) { + break; + } + } + face.reset(); + } + } + return face; +} + +GlobalPosition3D +GeoDataMesh::positionAt(const PointFace & coord) const +{ + return triangle<3>(coord.face(this)).positionOnPlane(coord.point); +} + +bool +GeoDataMesh::faceContainsPoint(const GlobalPosition2D coord, FaceHandle face) const +{ + return triangle<2>(face).containsPoint(coord); +} + +OpenMesh::HalfedgeHandle +GeoDataMesh::findBoundaryStart() const +{ + return *std::find_if(halfedges_sbegin(), halfedges_end(), [this](const auto heh) { + return is_boundary(heh); + }); +} + +[[nodiscard]] RelativePosition3D +GeoDataMesh::difference(const HalfedgeHandle heh) const +{ + return ::difference(point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); +} + +[[nodiscard]] GlobalPosition3D +GeoDataMesh::centre(const HalfedgeHandle heh) const +{ + const auto hehPoints = points(toVertexHandles(heh)); + return midpoint(hehPoints.first, hehPoints.second); +} + +void +GeoDataMesh::sanityCheck(const std::source_location & loc) const +{ + if (const auto upSideDown = std::ranges::count_if(faces(), [this](const auto face) { + if (!triangle<2>(face).isUp()) { +#ifndef NDEBUG + for (const auto vertex : fv_range(face)) { + CLOG(point(vertex)); + } +#endif + return true; + } + return false; + }) > 0) { + throw std::logic_error(std::format( + "{} upside down faces detected - checked from {}:{}", upSideDown, loc.function_name(), loc.line())); + } +} diff --git a/game/geoDataMesh.h b/game/geoDataMesh.h new file mode 100644 index 0000000..00db67c --- /dev/null +++ b/game/geoDataMesh.h @@ -0,0 +1,83 @@ +#pragma once + +#include "config/types.h" +#include "ray.h" +#include "triangle.h" +#include +#include +#include +#include + +struct GeoDataTraits : public OpenMesh::DefaultTraits { + FaceAttributes(OpenMesh::Attributes::Status); + EdgeAttributes(OpenMesh::Attributes::Status); + VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); + HalfedgeAttributes(OpenMesh::Attributes::Status); + using Point = GlobalPosition3D; + using Normal = Normal3D; +}; + +class GeoDataMesh : public OpenMesh::TriMesh_ArrayKernelT { +public: + struct PointFace { + // NOLINTNEXTLINE(hicpp-explicit-conversions) + PointFace(GlobalPosition2D coord) : point {coord} { } + + PointFace(GlobalPosition2D coord, FaceHandle face) : point {coord}, faceCache {face} { } + + PointFace(GlobalPosition2D coord, const GeoDataMesh *); + PointFace(GlobalPosition2D coord, GeoDataMesh const *, FaceHandle start); + + const GlobalPosition2D point; + [[nodiscard]] FaceHandle face(const GeoDataMesh *) const; + [[nodiscard]] FaceHandle face(const GeoDataMesh *, FaceHandle start) const; + + [[nodiscard]] bool + isLocated() const + { + return faceCache.is_valid(); + } + + private: + mutable FaceHandle faceCache; + }; + + template using Triangle = ::Triangle; + + [[nodiscard]] FaceHandle findPoint(GlobalPosition2D) const; + [[nodiscard]] FaceHandle findPoint(GlobalPosition2D, FaceHandle) const; + + [[nodiscard]] GlobalPosition3D positionAt(const PointFace &) const; + +protected: + void sanityCheck(const std::source_location & = std::source_location::current()) const; + + [[nodiscard]] bool faceContainsPoint(GlobalPosition2D, FaceHandle) const; + [[nodiscard]] HalfedgeHandle findBoundaryStart() const; + [[nodiscard]] RelativePosition3D difference(HalfedgeHandle) const; + using HalfEdgeVertices = std::pair; + [[nodiscard]] HalfEdgeVertices toVertexHandles(HalfedgeHandle) const; + using HalfEdgePoints = std::pair; + [[nodiscard]] HalfEdgePoints points(HalfEdgeVertices) const; + + template + [[nodiscard]] RelativeDistance + length(HalfedgeHandle heh) const + { + return ::distance( + point(to_vertex_handle(heh)), point(from_vertex_handle(heh))); + } + + [[nodiscard]] GlobalPosition3D centre(HalfedgeHandle) const; + + template + [[nodiscard]] Triangle + triangle(FaceHandle face) const + { + Triangle triangle; + std::ranges::transform(fv_range(face), triangle.begin(), [this](auto vertex) { + return point(vertex); + }); + return triangle; + } +}; -- cgit v1.2.3