From 582ac127f763f512c45f35e17b768487e3b51796 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sat, 4 Nov 2023 17:07:58 +0000 Subject: Rename TerrainMesh to GeoData to drop inplace --- Jamroot.jam | 1 + application/main.cpp | 16 +-- game/geoData.cpp | 340 +++++++++++++++++++++++++++----------------------- game/geoData.h | 115 +++++++++++------ game/terrain.cpp | 65 +++------- game/terrain2.cpp | 221 -------------------------------- game/terrain2.h | 93 -------------- test/Jamfile.jam | 5 +- test/perf-geoData.cpp | 18 +++ test/perf-terrain.cpp | 18 --- test/test-geo.cpp | 225 --------------------------------- test/test-geoData.cpp | 175 ++++++++++++++++++++++++++ test/test-render.cpp | 3 +- test/test-terrain.cpp | 175 -------------------------- ui/gameMainWindow.cpp | 2 +- 15 files changed, 480 insertions(+), 992 deletions(-) delete mode 100644 game/terrain2.cpp delete mode 100644 game/terrain2.h create mode 100644 test/perf-geoData.cpp delete mode 100644 test/perf-terrain.cpp delete mode 100644 test/test-geo.cpp create mode 100644 test/test-geoData.cpp delete mode 100644 test/test-terrain.cpp diff --git a/Jamroot.jam b/Jamroot.jam index 8a111ee..ef2f4c2 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -101,6 +101,7 @@ lib ilt : . lib thirdparty//glad + OpenMeshCore ; build-project test ; diff --git a/application/main.cpp b/application/main.cpp index 532c23d..9e4ca33 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -34,11 +34,11 @@ static const int DISPLAY_HEIGHT = 1024; class MainApplication : public GameState, public ApplicationBase { public: using Windows = Collection; + int run() { - geoData = std::make_shared(GeoData::Limits {{-120, -120}, {120, 120}}, 10.F); - geoData->generateRandom(); + geoData = std::make_shared(GeoData::loadFromAsciiGrid("test/fixtures/height/SD19.asc")); Windows windows; windows.create(DISPLAY_WIDTH, DISPLAY_HEIGHT); @@ -78,12 +78,12 @@ public: train->orders.create(&train->orders, l3->ends[1], l3->length, rl->findNodeAt({-1100, -450, 15})); train->currentActivity = train->orders.current()->createActivity(); - auto foliage = std::dynamic_pointer_cast(assets.at("Tree-01-1")); - for (float x = 900; x < 1100; x += 3) { - for (float y = 900; y < 1100; y += 3) { - world.create(foliage, Location {geoData->positionAt({-x, -y})}); - } - } + // auto foliage = std::dynamic_pointer_cast(assets.at("Tree-01-1")); + // for (float x = 900; x < 1100; x += 3) { + // for (float y = 900; y < 1100; y += 3) { + // world.create(foliage, Location {terrainMesh->positionAt(glm::vec2 {-x, -y})}); + //} + //} } auto t_start = std::chrono::high_resolution_clock::now(); diff --git a/game/geoData.cpp b/game/geoData.cpp index 34a1030..d32bf8c 100644 --- a/game/geoData.cpp +++ b/game/geoData.cpp @@ -1,214 +1,238 @@ #include "geoData.h" -#include "gfx/image.h" -#include -#include -#include -#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include - -GeoData::GeoData(Limits l, float s) : - limit {std::move(l)}, size {(limit.second - limit.first) + 1}, scale {s}, nodes {[this]() { - return (static_cast(size.x * size.y)); - }()} -{ -} -void -GeoData::generateRandom() +GeoData +GeoData::loadFromAsciiGrid(const std::filesystem::path & input) { - // We acknowledge this is terrible :) - - // Add hills - std::mt19937 gen(std::random_device {}()); - std::uniform_int_distribution<> rxpos(limit.first.x + 2, limit.second.x - 2), - rypos(limit.first.y + 2, limit.second.y - 2); - std::uniform_int_distribution<> rsize(10, 30); - std::uniform_real_distribution rheight(1, 3); - for (int h = 0; h < 500;) { - const glm::ivec2 hpos {rxpos(gen), rypos(gen)}; - const glm::ivec2 hsize {rsize(gen), rsize(gen)}; - if (const auto lim1 = hpos - hsize; lim1.x > limit.first.x && lim1.y > limit.first.y) { - if (const auto lim2 = hpos + hsize; lim2.x < limit.second.x && lim2.y < limit.second.y) { - const auto height = rheight(gen); - const glm::ivec2 hsizesqrd {hsize.x * hsize.x, hsize.y * hsize.y}; - for (auto y = lim1.y; y < lim2.y; y += 1) { - for (auto x = lim1.x; x < lim2.x; x += 1) { - const auto dist {hpos - glm::ivec2 {x, y}}; - const glm::ivec2 distsqrd {dist.x * dist.x, dist.y * dist.y}; - const auto out {ratio(sq(x - hpos.x), sq(hsize.x)) + ratio(sq(y - hpos.y), sq(hsize.y))}; - if (out <= 1.0F) { - auto & node {nodes[at({x, y})]}; - const auto m {1.F / (7.F * out - 8.F) + 1.F}; - node.height += height * m; - } - } - } - h += 1; - } + size_t ncols = 0, nrows = 0, xllcorner = 0, yllcorner = 0, cellsize = 0; + std::map properties { + {"ncols", &ncols}, + {"nrows", &nrows}, + {"xllcorner", &xllcorner}, + {"yllcorner", &yllcorner}, + {"cellsize", &cellsize}, + }; + std::ifstream f {input}; + while (!properties.empty()) { + std::string property; + f >> property; + f >> *properties.at(property); + properties.erase(property); + } + std::vector vertices; + vertices.reserve(ncols * nrows); + GeoData mesh; + mesh.lowerExtent = {xllcorner, yllcorner, std::numeric_limits::max()}; + mesh.upperExtent + = {xllcorner + (cellsize * ncols), yllcorner + (cellsize * nrows), std::numeric_limits::min()}; + for (size_t row = 0; row < nrows; ++row) { + for (size_t col = 0; col < ncols; ++col) { + float height = 0; + f >> height; + mesh.upperExtent.z = std::max(mesh.upperExtent.z, height); + mesh.lowerExtent.z = std::min(mesh.lowerExtent.z, height); + vertices.push_back(mesh.add_vertex({xllcorner + (col * cellsize), yllcorner + (row * cellsize), height})); } } -} + if (!f.good()) { + throw std::runtime_error("Couldn't read terrain file"); + } + for (size_t row = 1; row < nrows; ++row) { + for (size_t col = 1; col < ncols; ++col) { + mesh.add_face({ + vertices[ncols * (row - 1) + (col - 1)], + vertices[ncols * (row - 0) + (col - 0)], + vertices[ncols * (row - 0) + (col - 1)], + }); + mesh.add_face({ + vertices[ncols * (row - 1) + (col - 1)], + vertices[ncols * (row - 1) + (col - 0)], + vertices[ncols * (row - 0) + (col - 0)], + }); + } + } + mesh.update_face_normals(); + mesh.update_vertex_normals(); -void -GeoData::loadFromImages(const std::filesystem::path & fileName, float scale_) -{ - const Image map {fileName.c_str(), STBI_grey}; - size = {map.width, map.height}; - limit = {{0, 0}, size - glm::uvec2 {1, 1}}; - const auto points {size.x * size.y}; - scale = scale_; - nodes.resize(points); - - std::transform(map.data.data(), map.data.data() + points, nodes.begin(), [](auto d) { - return Node {(d * 0.1F) - 1.5F}; - }); -} + return mesh; +}; -GeoData::Quad -GeoData::quad(glm::vec2 wcoord) const +GeoData +GeoData::createFlat(glm::vec2 lower, glm::vec2 upper, float h) { - constexpr static const std::array corners {{{0, 0}, {0, 1}, {1, 0}, {1, 1}}}; - return transform_array(transform_array(corners, - [coord = (wcoord / scale)](const auto c) { - return glm::vec2 {std::floor(coord.x), std::floor(coord.y)} + c; - }), - [this](const auto c) { - return (c * scale) || nodes[at(c)].height; - }); -} + GeoData mesh; -glm::vec3 -GeoData::positionAt(const glm::vec2 wcoord) const -{ - const auto point {quad(wcoord)}; - const glm::vec2 frac = (wcoord - !point.front()) / scale; - auto edge = [&point, &frac](auto offset) { - return point[offset].z + ((point[offset + 2].z - point[offset].z) * frac.x); - }; - const auto heightFloor = edge(0U), heightCeil = edge(1U), - heightMid = heightFloor + ((heightCeil - heightFloor) * frac.y); + mesh.lowerExtent = lower ^ h; + mesh.upperExtent = upper ^ h; + + const auto ll = mesh.add_vertex({lower.x, lower.y, h}), lu = mesh.add_vertex({lower.x, upper.y, h}), + ul = mesh.add_vertex({upper.x, lower.y, h}), uu = mesh.add_vertex({upper.x, upper.y, h}); + + mesh.add_face(ll, uu, lu); + mesh.add_face(ll, ul, uu); - return wcoord || heightMid; + mesh.update_face_normals(); + mesh.update_vertex_normals(); + + return mesh; } -GeoData::RayTracer::RayTracer(glm::vec2 p0, glm::vec2 p1) : RayTracer {p0, p1, glm::abs(p1)} { } -GeoData::RayTracer::RayTracer(glm::vec2 p0, glm::vec2 p1, glm::vec2 d) : - RayTracer {p0, d, byAxis(p0, p1, d, 0), byAxis(p0, p1, d, 1)} +OpenMesh::FaceHandle +GeoData::findPoint(glm::vec2 p) const { + return findPoint(p, *faces_begin()); } -GeoData::RayTracer::RayTracer( - glm::vec2 p0, glm::vec2 d_, std::pair xdata, std::pair ydata) : - p {glm::floor(p0)}, - d {d_}, error {xdata.second - ydata.second}, inc {xdata.first, ydata.first} +GeoData::PointFace::PointFace(const glm::vec2 p, const GeoData * mesh) : PointFace {p, mesh, *mesh->faces_begin()} { } + +GeoData::PointFace::PointFace(const glm::vec2 p, const GeoData * mesh, FaceHandle start) : + PointFace {p, mesh->findPoint(p, start)} { } -std::pair -GeoData::RayTracer::byAxis(glm::vec2 p0, glm::vec2 p1, glm::vec2 d, glm::length_t axis) +GeoData::FaceHandle +GeoData::PointFace::face(const GeoData * mesh, FaceHandle start) const { - using Limits = std::numeric_limits; - static_assert(Limits::has_infinity); - if (d[axis] == 0) { - return {0, Limits::infinity()}; - } - else if (p1[axis] > 0) { - return {1, (std::floor(p0[axis]) + 1.F - p0[axis]) * d[1 - axis]}; + if (_face.is_valid()) { + assert(mesh->triangleContainsPoint(point, _face)); + return _face; } else { - return {-1, (p0[axis] - std::floor(p0[axis])) * d[1 - axis]}; + return (_face = mesh->findPoint(point, start)); } } -glm::vec2 -GeoData::RayTracer::next() +GeoData::FaceHandle +GeoData::PointFace::face(const GeoData * mesh) const { - const glm::vec2 cur {p}; + return face(mesh, *mesh->faces_begin()); +} + +namespace { + [[nodiscard]] constexpr inline bool + pointLeftOfLine(const glm::vec2 p, const glm::vec2 e1, const glm::vec2 e2) + { + return (e2.x - e1.x) * (p.y - e1.y) > (e2.y - e1.y) * (p.x - e1.x); + } - static constexpr const glm::vec2 m {1, -1}; - const int axis = (error > 0) ? 1 : 0; - p[axis] += inc[axis]; - error += d[1 - axis] * m[axis]; + static_assert(pointLeftOfLine({1, 2}, {1, 1}, {2, 2})); + static_assert(pointLeftOfLine({2, 1}, {2, 2}, {1, 1})); + static_assert(pointLeftOfLine({2, 2}, {1, 2}, {2, 1})); + static_assert(pointLeftOfLine({1, 1}, {2, 1}, {1, 2})); - return cur; + [[nodiscard]] constexpr inline bool + linesCross(const glm::vec2 a1, const glm::vec2 a2, const glm::vec2 b1, const glm::vec2 b2) + { + return pointLeftOfLine(a2, b1, b2) && pointLeftOfLine(a1, b2, b1) && pointLeftOfLine(b1, a1, a2) + && pointLeftOfLine(b2, a2, a1); + } + + static_assert(linesCross({1, 1}, {2, 2}, {1, 2}, {2, 1})); } -std::optional -GeoData::intersectRay(const Ray & ray) const +OpenMesh::FaceHandle +GeoData::findPoint(glm::vec2 p, OpenMesh::FaceHandle f) const { - if (glm::length(!ray.direction) <= 0) { - return {}; - } - RayTracer rt {ray.start / scale, ray.direction}; - while (true) { - const auto n {rt.next() * scale}; - try { - const auto point = quad(n); - for (auto offset : {0U, 1U}) { - glm::vec2 bary; - float distance; - if (glm::intersectRayTriangle(ray.start, ray.direction, point[offset], point[offset + 1], - point[offset + 2], bary, distance)) { - return point[offset] + ((point[offset + 1] - point[offset]) * bary[0]) - + ((point[offset + 2] - point[offset]) * bary[1]); + ConstFaceVertexIter vertices; + while (f.is_valid() && !triangleContainsPoint(p, vertices = cfv_iter(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; } } - } - catch (std::range_error &) { - const auto rel = n / !ray.direction; - if (rel.x > 0 && rel.y > 0) { - return {}; - } + f.reset(); } } + return f; +} - return {}; +glm::vec3 +GeoData::positionAt(const PointFace & p) const +{ + glm::vec3 out {}; + Triangle<3> t {this, fv_range(p.face(this))}; + glm::intersectLineTriangle(p.point ^ 0.F, up, t[0], t[1], t[2], out); + return p.point ^ out[0]; } -unsigned int -GeoData::at(glm::ivec2 coord) const +[[nodiscard]] std::optional +GeoData::intersectRay(const Ray & ray) const { - if (coord.x < limit.first.x || coord.x > limit.second.x || coord.y < limit.first.y || coord.y > limit.second.y) { - throw std::range_error {"Coordinates outside GeoData limits"}; - } - const glm::uvec2 offset = coord - limit.first; - return offset.x + (offset.y * size.x); + return intersectRay(ray, findPoint(ray.start)); } -unsigned int -GeoData::at(int x, int y) const +[[nodiscard]] std::optional +GeoData::intersectRay(const Ray & ray, FaceHandle face) const { - return at({x, y}); + std::optional out; + walkUntil(PointFace {ray.start, face}, ray.start + (ray.direction * 10000.F), [&out, &ray, this](FaceHandle face) { + glm::vec2 bari {}; + float dist {}; + Triangle<3> t {this, fv_range(face)}; + if (glm::intersectRayTriangle(ray.start, ray.direction, t[0], t[1], t[2], bari, dist)) { + out = t * bari; + return true; + } + return false; + }); + return out; } -GeoData::Limits -GeoData::getLimit() const +void +GeoData::walk(const PointFace & from, const glm::vec2 to, const std::function & op) const { - return limit; + walkUntil(from, to, [&op](const auto & fh) { + op(fh); + return false; + }); } -float -GeoData::getScale() const +void +GeoData::walkUntil(const PointFace & from, const glm::vec2 to, const std::function & op) const +{ + assert(from.face(this).is_valid()); // TODO replace with a boundary search + auto f = from.face(this); + FaceHandle previousFace; + while (f.is_valid() && !op(f)) { + for (auto next = cfh_iter(f); next.is_valid(); ++next) { + f = opposite_face_handle(*next); + if (f.is_valid() && f != previousFace) { + const auto e1 = point(to_vertex_handle(*next)); + const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next))); + if (linesCross(from.point, to, e1, e2)) { + previousFace = f; + break; + } + } + f.reset(); + } + } +} + +bool +GeoData::triangleContainsPoint(const glm::vec2 p, const glm::vec2 a, const glm::vec2 b, const glm::vec2 c) { - return scale; + const auto det = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + + return det * ((b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)) >= 0 + && det * ((c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x)) >= 0 + && det * ((a.x - c.x) * (p.y - c.y) - (a.y - c.y) * (p.x - c.x)) >= 0; } -glm::uvec2 -GeoData::getSize() const +bool +GeoData::triangleContainsPoint(const glm::vec2 p, FaceHandle face) const { - return size; + return triangleContainsPoint(p, cfv_iter(face)); } -std::span -GeoData::getNodes() const +bool +GeoData::triangleContainsPoint(const glm::vec2 p, ConstFaceVertexIter vertices) const { - return nodes; + return triangleContainsPoint(p, point(*vertices++), point(*vertices++), point(*vertices++)); } diff --git a/game/geoData.h b/game/geoData.h index f433a5c..3a4f98e 100644 --- a/game/geoData.h +++ b/game/geoData.h @@ -1,61 +1,94 @@ #pragma once -#include +#include "collections.h" // IWYU pragma: keep IterableCollection +#include "ray.h" +#include #include -#include +#include #include -#include -#include -#include +#include -class Ray; +struct GeoDataTraits : public OpenMesh::DefaultTraits { + FaceAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); + EdgeAttributes(OpenMesh::Attributes::Status); + VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); + HalfedgeAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); + using Point = glm::vec3; + using Normal = glm::vec3; +}; + +class GeoData : public OpenMesh::TriMesh_ArrayKernelT { +private: + GeoData() = default; -class GeoData { public: - struct Node { - float height {-1.5F}; - }; - using Quad = std::array; + static GeoData loadFromAsciiGrid(const std::filesystem::path &); + static GeoData createFlat(glm::vec2 lower, glm::vec2, float h); - using Limits = std::pair; + struct PointFace { + // NOLINTNEXTLINE(hicpp-explicit-conversions) + PointFace(const glm::vec2 p) : point {p} { } - explicit GeoData(Limits limit, float scale = 10.F); + PointFace(const glm::vec2 p, FaceHandle face) : point {p}, _face {face} { } - void generateRandom(); - void loadFromImages(const std::filesystem::path &, float scale); + PointFace(const glm::vec2 p, const GeoData *); + PointFace(const glm::vec2 p, const GeoData *, FaceHandle start); - [[nodiscard]] glm::vec3 positionAt(glm::vec2) const; - [[nodiscard]] std::optional intersectRay(const Ray &) const; + const glm::vec2 point; + [[nodiscard]] FaceHandle face(const GeoData *) const; + [[nodiscard]] FaceHandle face(const GeoData *, FaceHandle start) const; - [[nodiscard]] unsigned int at(glm::ivec2) const; - [[nodiscard]] unsigned int at(int x, int y) const; - [[nodiscard]] Quad quad(glm::vec2) const; + [[nodiscard]] bool + isLocated() const + { + return _face.is_valid(); + } - [[nodiscard]] Limits getLimit() const; - [[nodiscard]] glm::uvec2 getSize() const; - [[nodiscard]] float getScale() const; - [[nodiscard]] std::span getNodes() const; + private: + mutable FaceHandle _face {}; + }; - class RayTracer { - public: - RayTracer(glm::vec2 p0, glm::vec2 p1); + template struct Triangle : public glm::vec<3, glm::vec> { + using base = glm::vec<3, glm::vec>; + using base::base; - glm::vec2 next(); + template 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); + }); + } - private: - RayTracer(glm::vec2 p0, glm::vec2 p1, glm::vec2 d); - RayTracer(glm::vec2 p0, glm::vec2 d, std::pair, std::pair); - static std::pair byAxis(glm::vec2 p0, glm::vec2 p1, glm::vec2 d, glm::length_t); - - glm::vec2 p; - const glm::vec2 d; - float error; - glm::vec2 inc; + glm::vec + operator*(glm::vec2 bari) const + { + const auto & t {*this}; + return t[0] + ((t[1] - t[0]) * bari.x) + ((t[2] - t[1]) * bari.y); + } }; + [[nodiscard]] FaceHandle findPoint(glm::vec2) const; + [[nodiscard]] FaceHandle findPoint(glm::vec2, FaceHandle start) const; + + [[nodiscard]] glm::vec3 positionAt(const PointFace &) const; + [[nodiscard]] std::optional intersectRay(const Ray &) const; + [[nodiscard]] std::optional intersectRay(const Ray &, FaceHandle start) const; + + void walk(const PointFace & from, const glm::vec2 to, const std::function & op) const; + void walkUntil(const PointFace & from, const glm::vec2 to, const std::function & op) const; + + [[nodiscard]] auto + getExtents() const + { + return std::tie(lowerExtent, upperExtent); + } + protected: - Limits limit {}; // Base grid limits first(x,y) -> second(x,y) - glm::uvec2 size {}; - float scale {1}; - std::vector nodes; + [[nodiscard]] static bool triangleContainsPoint(const glm::vec2, const glm::vec2, const glm::vec2, const glm::vec2); + [[nodiscard]] bool triangleContainsPoint(const glm::vec2, FaceHandle) const; + [[nodiscard]] bool triangleContainsPoint(const glm::vec2, ConstFaceVertexIter) const; + +private: + glm::vec3 lowerExtent {}, upperExtent {}; }; diff --git a/game/terrain.cpp b/game/terrain.cpp index f3bec1e..5bb8abd 100644 --- a/game/terrain.cpp +++ b/game/terrain.cpp @@ -2,7 +2,6 @@ #include "game/geoData.h" #include "gfx/models/texture.h" #include -#include #include #include #include @@ -18,8 +17,8 @@ #include #include -Terrain::Terrain(std::shared_ptr gd) : - geoData {std::move(gd)}, grass {Texture::cachedTexture.get("grass.png")}, +Terrain::Terrain(std::shared_ptr tm) : + geoData {std::move(tm)}, grass {Texture::cachedTexture.get("grass.png")}, water {Texture::cachedTexture.get("water.png")} { generateMeshes(); @@ -29,51 +28,23 @@ void Terrain::generateMeshes() { std::vector indices; - const auto isize = geoData->getSize() - glm::uvec2 {1, 1}; - indices.reserve(static_cast(isize.x * isize.y) * 6); - - const auto limit = geoData->getLimit(); - // Indices - constexpr std::array indices_offsets {{ - {0, 0}, - {1, 0}, - {1, 1}, - {0, 0}, - {1, 1}, - {0, 1}, - }}; - for (auto y = limit.first.y; y < limit.second.y; y += 1) { - for (auto x = limit.first.x; x < limit.second.x; x += 1) { - std::transform(indices_offsets.begin(), indices_offsets.end(), std::back_inserter(indices), - [this, x, y](const auto off) { - return geoData->at(x + off.x, y + off.y); - }); - } - } - - const auto nodes = geoData->getNodes(); - const auto scale = geoData->getScale(); + indices.reserve(geoData->n_faces() * 3); std::vector vertices; - vertices.reserve(nodes.size()); - // Positions - for (auto y = limit.first.y; y <= limit.second.y; y += 1) { - for (auto x = limit.first.x; x <= limit.second.x; x += 1) { - const glm::vec2 xy {x, y}; - vertices.emplace_back((xy * scale) ^ nodes[geoData->at(x, y)].height, xy, ::up); - } - } - // Normals - const glm::uvec2 size = geoData->getSize(); - for (auto y = limit.first.y + 1; y < limit.second.y; y += 1) { - for (auto x = limit.first.x + 1; x < limit.second.x; x += 1) { - const auto n {geoData->at(x, y)}; - const auto a = vertices[n - 1].pos; - const auto b = vertices[n - size.x].pos; - const auto c = vertices[n + 1].pos; - const auto d = vertices[n + size.x].pos; - vertices[n].normal = -glm::normalize(glm::cross(b - d, a - c)); - } - } + vertices.reserve(geoData->n_vertices()); + std::map vertexIndex; + std::transform(geoData->vertices_begin(), geoData->vertices_end(), std::back_inserter(vertices), + [this, &vertexIndex](const GeoData::VertexHandle v) { + vertexIndex.emplace(v, vertexIndex.size()); + const glm::vec3 p = geoData->point(v); + return Vertex {p, p / 10.F, geoData->normal(v)}; + }); + std::for_each( + geoData->faces_begin(), geoData->faces_end(), [this, &vertexIndex, &indices](const GeoData::FaceHandle f) { + std::transform(geoData->fv_begin(f), geoData->fv_end(f), std::back_inserter(indices), + [&vertexIndex](const GeoData::VertexHandle v) { + return vertexIndex[v]; + }); + }); meshes.create(vertices, indices); } diff --git a/game/terrain2.cpp b/game/terrain2.cpp deleted file mode 100644 index 0e8b78e..0000000 --- a/game/terrain2.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "terrain2.h" -#include -#include -#include - -TerrainMesh -TerrainMesh::loadFromAsciiGrid(const std::filesystem::path & input) -{ - size_t ncols = 0, nrows = 0, xllcorner = 0, yllcorner = 0, cellsize = 0; - std::map properties { - {"ncols", &ncols}, - {"nrows", &nrows}, - {"xllcorner", &xllcorner}, - {"yllcorner", &yllcorner}, - {"cellsize", &cellsize}, - }; - std::ifstream f {input}; - while (!properties.empty()) { - std::string property; - f >> property; - f >> *properties.at(property); - properties.erase(property); - } - std::vector vertices; - vertices.reserve(ncols * nrows); - TerrainMesh mesh; - mesh.lowerExtent = {xllcorner, yllcorner, std::numeric_limits::max()}; - mesh.upperExtent - = {xllcorner + (cellsize * ncols), yllcorner + (cellsize * nrows), std::numeric_limits::min()}; - for (size_t row = 0; row < nrows; ++row) { - for (size_t col = 0; col < ncols; ++col) { - float height = 0; - f >> height; - mesh.upperExtent.z = std::max(mesh.upperExtent.z, height); - mesh.lowerExtent.z = std::min(mesh.lowerExtent.z, height); - vertices.push_back(mesh.add_vertex({xllcorner + (col * cellsize), yllcorner + (row * cellsize), height})); - } - } - if (!f.good()) { - throw std::runtime_error("Couldn't read terrain file"); - } - for (size_t row = 1; row < nrows; ++row) { - for (size_t col = 1; col < ncols; ++col) { - mesh.add_face({ - vertices[ncols * (row - 1) + (col - 1)], - vertices[ncols * (row - 0) + (col - 0)], - vertices[ncols * (row - 0) + (col - 1)], - }); - mesh.add_face({ - vertices[ncols * (row - 1) + (col - 1)], - vertices[ncols * (row - 1) + (col - 0)], - vertices[ncols * (row - 0) + (col - 0)], - }); - } - } - mesh.update_face_normals(); - mesh.update_vertex_normals(); - - return mesh; -}; - -OpenMesh::FaceHandle -TerrainMesh::findPoint(glm::vec2 p) const -{ - return findPoint(p, *faces_begin()); -} - -TerrainMesh::PointFace::PointFace(const glm::vec2 p, const TerrainMesh * mesh) : - PointFace {p, mesh, *mesh->faces_begin()} -{ -} - -TerrainMesh::PointFace::PointFace(const glm::vec2 p, const TerrainMesh * mesh, FaceHandle start) : - PointFace {p, mesh->findPoint(p, start)} -{ -} - -TerrainMesh::FaceHandle -TerrainMesh::PointFace::face(const TerrainMesh * mesh, FaceHandle start) const -{ - if (_face.is_valid()) { - assert(mesh->triangleContainsPoint(point, _face)); - return _face; - } - else { - return (_face = mesh->findPoint(point, start)); - } -} - -TerrainMesh::FaceHandle -TerrainMesh::PointFace::face(const TerrainMesh * mesh) const -{ - return face(mesh, *mesh->faces_begin()); -} - -namespace { - [[nodiscard]] constexpr inline bool - pointLeftOfLine(const glm::vec2 p, const glm::vec2 e1, const glm::vec2 e2) - { - return (e2.x - e1.x) * (p.y - e1.y) > (e2.y - e1.y) * (p.x - e1.x); - } - - static_assert(pointLeftOfLine({1, 2}, {1, 1}, {2, 2})); - static_assert(pointLeftOfLine({2, 1}, {2, 2}, {1, 1})); - static_assert(pointLeftOfLine({2, 2}, {1, 2}, {2, 1})); - static_assert(pointLeftOfLine({1, 1}, {2, 1}, {1, 2})); - - [[nodiscard]] constexpr inline bool - linesCross(const glm::vec2 a1, const glm::vec2 a2, const glm::vec2 b1, const glm::vec2 b2) - { - return pointLeftOfLine(a2, b1, b2) && pointLeftOfLine(a1, b2, b1) && pointLeftOfLine(b1, a1, a2) - && pointLeftOfLine(b2, a2, a1); - } - - static_assert(linesCross({1, 1}, {2, 2}, {1, 2}, {2, 1})); -} - -OpenMesh::FaceHandle -TerrainMesh::findPoint(glm::vec2 p, OpenMesh::FaceHandle f) const -{ - ConstFaceVertexIter vertices; - while (f.is_valid() && !triangleContainsPoint(p, vertices = cfv_iter(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; -} - -glm::vec3 -TerrainMesh::positionAt(const PointFace & p) const -{ - glm::vec3 out {}; - Triangle<3> t {this, fv_range(p.face(this))}; - glm::intersectLineTriangle(p.point ^ 0.F, up, t[0], t[1], t[2], out); - return p.point ^ out[0]; -} - -[[nodiscard]] std::optional -TerrainMesh::intersectRay(const Ray & ray) const -{ - return intersectRay(ray, findPoint(ray.start)); -} - -[[nodiscard]] std::optional -TerrainMesh::intersectRay(const Ray & ray, FaceHandle face) const -{ - std::optional out; - walkUntil(PointFace {ray.start, face}, ray.start + (ray.direction * 10000.F), [&out, &ray, this](FaceHandle face) { - glm::vec2 bari {}; - float dist {}; - Triangle<3> t {this, fv_range(face)}; - if (glm::intersectRayTriangle(ray.start, ray.direction, t[0], t[1], t[2], bari, dist)) { - out = t * bari; - return true; - } - return false; - }); - return out; -} - -void -TerrainMesh::walk(const PointFace & from, const glm::vec2 to, const std::function & op) const -{ - walkUntil(from, to, [&op](const auto & fh) { - op(fh); - return false; - }); -} - -void -TerrainMesh::walkUntil(const PointFace & from, const glm::vec2 to, const std::function & op) const -{ - assert(from.face(this).is_valid()); // TODO replace with a boundary search - auto f = from.face(this); - FaceHandle previousFace; - while (f.is_valid() && !op(f)) { - for (auto next = cfh_iter(f); next.is_valid(); ++next) { - f = opposite_face_handle(*next); - if (f.is_valid() && f != previousFace) { - const auto e1 = point(to_vertex_handle(*next)); - const auto e2 = point(to_vertex_handle(opposite_halfedge_handle(*next))); - if (linesCross(from.point, to, e1, e2)) { - previousFace = f; - break; - } - } - f.reset(); - } - } -} - -bool -TerrainMesh::triangleContainsPoint(const glm::vec2 p, const glm::vec2 a, const glm::vec2 b, const glm::vec2 c) -{ - const auto det = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); - - return det * ((b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)) >= 0 - && det * ((c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x)) >= 0 - && det * ((a.x - c.x) * (p.y - c.y) - (a.y - c.y) * (p.x - c.x)) >= 0; -} - -bool -TerrainMesh::triangleContainsPoint(const glm::vec2 p, FaceHandle face) const -{ - return triangleContainsPoint(p, cfv_iter(face)); -} - -bool -TerrainMesh::triangleContainsPoint(const glm::vec2 p, ConstFaceVertexIter vertices) const -{ - return triangleContainsPoint(p, point(*vertices++), point(*vertices++), point(*vertices++)); -} diff --git a/game/terrain2.h b/game/terrain2.h deleted file mode 100644 index eda56d0..0000000 --- a/game/terrain2.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include "collections.h" // IWYU pragma: keep IterableCollection -#include "ray.h" -#include -#include -#include -#include -#include - -struct TerrainTraits : public OpenMesh::DefaultTraits { - FaceAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); - EdgeAttributes(OpenMesh::Attributes::Status); - VertexAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); - HalfedgeAttributes(OpenMesh::Attributes::Normal | OpenMesh::Attributes::Status); - using Point = glm::vec3; - using Normal = glm::vec3; -}; - -class TerrainMesh : public OpenMesh::TriMesh_ArrayKernelT { -private: - TerrainMesh() = default; - -public: - static TerrainMesh loadFromAsciiGrid(const std::filesystem::path &); - - struct PointFace { - // NOLINTNEXTLINE(hicpp-explicit-conversions) - PointFace(const glm::vec2 p) : point {p} { } - - PointFace(const glm::vec2 p, FaceHandle face) : point {p}, _face {face} { } - - PointFace(const glm::vec2 p, const TerrainMesh *); - PointFace(const glm::vec2 p, const TerrainMesh *, FaceHandle start); - - const glm::vec2 point; - [[nodiscard]] FaceHandle face(const TerrainMesh *) const; - [[nodiscard]] FaceHandle face(const TerrainMesh *, FaceHandle start) const; - - [[nodiscard]] bool - isLocated() const - { - return _face.is_valid(); - } - - private: - mutable FaceHandle _face {}; - }; - - template struct Triangle : public glm::vec<3, glm::vec> { - using base = glm::vec<3, glm::vec>; - using base::base; - - template Triangle(const TerrainMesh * 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); - }); - } - - glm::vec - operator*(glm::vec2 bari) const - { - const auto & t {*this}; - return t[0] + ((t[1] - t[0]) * bari.x) + ((t[2] - t[1]) * bari.y); - } - }; - - [[nodiscard]] FaceHandle findPoint(glm::vec2) const; - [[nodiscard]] FaceHandle findPoint(glm::vec2, FaceHandle start) const; - - [[nodiscard]] glm::vec3 positionAt(const PointFace &) const; - [[nodiscard]] std::optional intersectRay(const Ray &) const; - [[nodiscard]] std::optional intersectRay(const Ray &, FaceHandle start) const; - - void walk(const PointFace & from, const glm::vec2 to, const std::function & op) const; - void walkUntil(const PointFace & from, const glm::vec2 to, const std::function & op) const; - - [[nodiscard]] auto - getExtents() const - { - return std::tie(lowerExtent, upperExtent); - } - -protected: - [[nodiscard]] static bool triangleContainsPoint(const glm::vec2, const glm::vec2, const glm::vec2, const glm::vec2); - [[nodiscard]] bool triangleContainsPoint(const glm::vec2, FaceHandle) const; - [[nodiscard]] bool triangleContainsPoint(const glm::vec2, ConstFaceVertexIter) const; - -private: - glm::vec3 lowerExtent {}, upperExtent {}; -}; diff --git a/test/Jamfile.jam b/test/Jamfile.jam index d88a238..6f62056 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -46,7 +46,7 @@ lib test : [ glob *.cpp : test-*.cpp perf-*.cpp ] ; run test-collection.cpp ; run test-maths.cpp ; run test-lib.cpp ; -run test-geo.cpp ; +run test-geoData.cpp : -- : fixtures/height/SD19.asc : test ; run test-network.cpp : : : test ; run test-persistence.cpp : -- : [ sequence.insertion-sort [ glob-tree fixtures : *.json ] ] : test ; run test-text.cpp : : : test ; @@ -55,13 +55,12 @@ run test-render.cpp : -- : test-assetFactory : test ; run test-glContextBhvr.cpp ; run test-assetFactory.cpp : -- : [ sequence.insertion-sort [ glob-tree $(res) : *.* ] fixtures/rgb.txt test-instancing ] : test ; run perf-assetFactory.cpp : -- : test-assetFactory : benchmark test ; +run perf-geoData.cpp : : : test benchmark ; run perf-persistence.cpp : -- : test-persistence : benchmark test ; run test-worker.cpp ; run test-instancing.cpp : : : test ; run test-glContainer.cpp : : : test ; run test-pack.cpp : : : test ; -run test-terrain.cpp : -- : fixtures/height/SD19.asc : test ..//OpenMeshCore ; -run perf-terrain.cpp : : : test ..//OpenMeshCore benchmark ; compile test-static-enumDetails.cpp ; compile test-static-stream_support.cpp ; explicit perf-assetFactory ; diff --git a/test/perf-geoData.cpp b/test/perf-geoData.cpp new file mode 100644 index 0000000..e1546c1 --- /dev/null +++ b/test/perf-geoData.cpp @@ -0,0 +1,18 @@ +#include +#include + +namespace { + const GeoData tm {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")}; + + void + terrain_findPoint(benchmark::State & state) + { + for (auto _ : state) { + benchmark::DoNotOptimize(tm.findPoint({315555, 495556})); + } + } +} + +BENCHMARK(terrain_findPoint); + +BENCHMARK_MAIN(); diff --git a/test/perf-terrain.cpp b/test/perf-terrain.cpp deleted file mode 100644 index e998f60..0000000 --- a/test/perf-terrain.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -namespace { - const TerrainMesh tm {FIXTURESDIR "height/SD19.asc"}; - - void - terrain_findPoint(benchmark::State & state) - { - for (auto _ : state) { - benchmark::DoNotOptimize(tm.findPoint({315555, 495556})); - } - } -} - -BENCHMARK(terrain_findPoint); - -BENCHMARK_MAIN(); diff --git a/test/test-geo.cpp b/test/test-geo.cpp deleted file mode 100644 index 9874fb7..0000000 --- a/test/test-geo.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#define BOOST_TEST_MODULE test_geo - -#include "testHelpers.h" -#include -#include -#include - -#include -#include - -struct TestGeoData : public GeoData { - TestGeoData() : GeoData {{{-10, -5}, {30, 40}}, 5.F} { } -}; - -namespace std { - std::ostream & - operator<<(std::ostream & s, const Ray & r) - { - return (s << r.start << "->" << r.direction); - } -} - -BOOST_FIXTURE_TEST_SUITE(tgd, TestGeoData) - -BOOST_AUTO_TEST_CASE(initialize) -{ - BOOST_CHECK_EQUAL(limit.first, glm::ivec2(-10, -5)); - BOOST_CHECK_EQUAL(limit.second, glm::ivec2(30, 40)); - BOOST_CHECK_EQUAL(scale, 5.F); - BOOST_CHECK_EQUAL(size, glm::uvec2(41, 46)); - BOOST_CHECK_EQUAL(nodes.size(), 1886); - BOOST_CHECK(std::all_of(nodes.begin(), nodes.end(), [](const auto & n) { - return n.height == -1.5F; - })); -} - -BOOST_AUTO_TEST_CASE(coords) -{ - BOOST_CHECK_EQUAL(at(-10, -5), 0); - BOOST_CHECK_EQUAL(at(-9, -5), 1); - BOOST_CHECK_EQUAL(at(0, -5), 10); - BOOST_CHECK_EQUAL(at(30, -5), 40); - BOOST_CHECK_EQUAL(at(30, 40), 1885); -} - -BOOST_AUTO_TEST_CASE(coords_bad) -{ - BOOST_CHECK_THROW(std::ignore = at(-11, -5), std::range_error); - BOOST_CHECK_THROW(std::ignore = at(-10, -6), std::range_error); - BOOST_CHECK_THROW(std::ignore = at(-11, -6), std::range_error); - BOOST_CHECK_THROW(std::ignore = at(31, 40), std::range_error); - BOOST_CHECK_THROW(std::ignore = at(30, 41), std::range_error); - BOOST_CHECK_THROW(std::ignore = at(31, 41), std::range_error); -} - -BOOST_AUTO_TEST_CASE(gen_random) -{ - // Can only really its sanity - generateRandom(); - // Some terrain above sea level - BOOST_CHECK(std::any_of(nodes.begin(), nodes.end(), [](const auto & n) { - return n.height > 0; - })); - // Still an island - for (int x = limit.first.x; x <= limit.second.x; x += 1) { - BOOST_CHECK_EQUAL(nodes[at(x, limit.first.y)].height, -1.5F); - BOOST_CHECK_EQUAL(nodes[at(x, limit.second.y)].height, -1.5F); - } - for (int y = limit.first.y; y <= limit.second.y; y += 1) { - BOOST_CHECK_EQUAL(nodes[at(limit.first.x, y)].height, -1.5F); - BOOST_CHECK_EQUAL(nodes[at(limit.second.x, y)].height, -1.5F); - } -} - -BOOST_AUTO_TEST_CASE(load_uk_heightmap) -{ - loadFromImages(FIXTURESDIR "/height/V0txo.jpg", 100.F); - // Some terrain above sea level - BOOST_CHECK(std::any_of(nodes.begin(), nodes.end(), [](const auto & n) { - return n.height > 0; - })); -} - -BOOST_AUTO_TEST_CASE(get_height_at) -{ - // at(x,y) is index based - nodes[at(0, 0)].height = 1; - nodes[at(1, 0)].height = 2; - nodes[at(0, 1)].height = 3; - nodes[at(1, 1)].height = 4; - - // positionAt(x,y) is world based - // Corners - BOOST_CHECK_EQUAL(positionAt({0, 0}), glm::vec3(0, 0, 1)); - BOOST_CHECK_EQUAL(positionAt({5, 0}), glm::vec3(5, 0, 2)); - BOOST_CHECK_EQUAL(positionAt({0, 5}), glm::vec3(0, 5, 3)); - BOOST_CHECK_EQUAL(positionAt({5, 5}), glm::vec3(5, 5, 4)); - - // Edge midpoints - BOOST_CHECK_EQUAL(positionAt({0, 2.5F}), glm::vec3(0, 2.5F, 2)); - BOOST_CHECK_EQUAL(positionAt({5, 2.5F}), glm::vec3(5, 2.5F, 3)); - BOOST_CHECK_EQUAL(positionAt({2.5F, 0}), glm::vec3(2.5F, 0, 1.5F)); - BOOST_CHECK_EQUAL(positionAt({2.5F, 5}), glm::vec3(2.5F, 5, 3.5F)); - - // Centre - BOOST_CHECK_EQUAL(positionAt({2.5F, 2.5F}), glm::vec3(2.5F, 2.5F, 2.5F)); -} - -using TestRayTracerData = std::tuple>; -BOOST_TEST_DECORATOR(*boost::unit_test::timeout(1)) - -BOOST_DATA_TEST_CASE(raytracer, - boost::unit_test::data::make({ - {{1, 2}, {4, 5}, 4, - { - {0, 0}, - {0, 4}, - {4, 4}, - {4, 8}, - {8, 8}, - {8, 12}, - {12, 12}, - {12, 16}, - {12, 20}, - {16, 20}, - }}, - {{-1, -1}, {-4, -5}, 5, - { - {-5, -5}, - {-5, -10}, - {-10, -10}, - {-10, -15}, - {-15, -15}, - {-15, -20}, - {-20, -20}, - {-20, -25}, - }}, - }), - start, dir, scale, points) -{ - GeoData::RayTracer rt {start / scale, glm::normalize(dir)}; - for (const auto & point : points) { - BOOST_CHECK_CLOSE_VEC(point, rt.next() * scale); - } -} - -using TestRayData = std::tuple; -BOOST_TEST_DECORATOR(*boost::unit_test::timeout(1)) - -BOOST_DATA_TEST_CASE(intersect_ray, - boost::unit_test::data::make({ - {{-1, -1, 1.0}, {1, 1, 0}, {0, 0, 1}}, - {{-1, -1, 2.5}, {1, 1, 0}, {2.5F, 2.5F, 2.5F}}, - {{-20, -20, 2.5}, {1, 1, 0}, {2.5F, 2.5F, 2.5F}}, - // outside the map looking in - {{-205, -205, 4}, {1, 1, 0}, {5, 5, 4}}, - {{-205, 5, 4}, {1, 0, 0}, {5, 5, 4}}, - {{-205, 215, 4}, {1, -1, 0}, {5, 5, 4}}, - {{215, -205, 4}, {-1, 1, 0}, {5, 5, 4}}, - {{215, 5, 4}, {-1, 0, 0}, {5, 5, 4}}, - {{215, 215, 4}, {-1, -1, 0}, {5, 5, 4}}, - {{5, 215, 4}, {0, -1, 0}, {5, 5, 4}}, - {{5, -205, 4}, {0, 1, 0}, {5, 5, 4}}, - }), - start, dir, pos) -{ - // at(x,y) is index based - nodes[at(0, 0)].height = 1; - nodes[at(1, 0)].height = 2; - nodes[at(0, 1)].height = 3; - nodes[at(1, 1)].height = 4; - - const auto intersect = intersectRay({start, glm::normalize(dir)}); - - BOOST_CHECK_IF(has_intersect, intersect) { - BOOST_CHECK_CLOSE_VEC(*intersect, pos); - } -} - -auto xs = boost::unit_test::data::xrange(-20.F, 0.F, 0.6F), ys = boost::unit_test::data::xrange(-20.F, 0.F, 0.7F); -auto targetsx = boost::unit_test::data::xrange(0.2F, 4.9F, 1.3F), - targetsy = boost::unit_test::data::xrange(0.3F, 4.9F, 1.3F); -BOOST_TEST_DECORATOR(*boost::unit_test::timeout(1)) - -BOOST_DATA_TEST_CASE(intersect_ray_many, xs * ys * targetsx * targetsy, x, y, targetx, targety) -{ - // at(x,y) is index based - nodes[at(0, 0)].height = 1; - nodes[at(1, 0)].height = 2; - nodes[at(0, 1)].height = 3; - nodes[at(1, 1)].height = 4; - - const glm::vec3 start {x, y, 10}; - const auto target {this->positionAt({targetx, targety})}; - const Ray ray {start, glm::normalize(target - start)}; - - BOOST_TEST_CONTEXT(ray) { - const auto intersect = intersectRay(ray); - - BOOST_CHECK_IF(has_intersect, intersect) { - BOOST_CHECK_CLOSE_VEC(*intersect, target); - } - } -} - -BOOST_TEST_DECORATOR(*boost::unit_test::timeout(1)) - -BOOST_DATA_TEST_CASE(intersect_ray_miss, - boost::unit_test::data::make({ - {{3, 3, 5}, {-1, -1, 0}}, - {{0, 0, 5}, {0, 0, 1}}, - {{0, 0, 5}, {0, 0, -1}}, - }), - ray) -{ - // at(x,y) is index based - nodes[at(0, 0)].height = 1; - nodes[at(1, 0)].height = 2; - nodes[at(0, 1)].height = 3; - nodes[at(1, 1)].height = 4; - - BOOST_REQUIRE(!intersectRay(ray)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/test/test-geoData.cpp b/test/test-geoData.cpp new file mode 100644 index 0000000..d79f5b8 --- /dev/null +++ b/test/test-geoData.cpp @@ -0,0 +1,175 @@ +#define BOOST_TEST_MODULE terrain +#include "testHelpers.h" +#include +#include +#include + +#include + +class TestTerrainMesh : public GeoData { +public: + TestTerrainMesh() : GeoData {GeoData::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")} { } +}; + +constexpr size_t ncols = 200, nrows = 200, xllcorner = 310000, yllcorner = 490000, cellsize = 50; + +BOOST_FIXTURE_TEST_SUITE(ttm, TestTerrainMesh); + +BOOST_AUTO_TEST_CASE(loadSuccess) +{ + BOOST_CHECK_EQUAL(ncols * nrows, n_vertices()); + BOOST_CHECK_EQUAL(2 * (ncols - 1) * (nrows - 1), n_faces()); + const auto [lower, upper] = getExtents(); + BOOST_CHECK_EQUAL(lower, glm::vec3(310000, 490000, -2.6)); + BOOST_CHECK_EQUAL(upper, glm::vec3(320000, 500000, 571.6)); +} + +BOOST_AUTO_TEST_CASE(normalsAllPointUp) +{ + BOOST_CHECK(std::all_of(faces_begin(), faces_end(), [this](auto && vh) { + return normal(vh).z > 0; + })); + BOOST_CHECK(std::all_of(vertices_begin(), vertices_end(), [this](auto && vh) { + return normal(vh).z > 0; + })); +} + +BOOST_AUTO_TEST_CASE(trianglesContainsPoints) +{ + const auto face = face_handle(0); + auto vertices = cfv_iter(face); + + BOOST_TEST_CONTEXT(point(*vertices++) << point(*vertices++) << point(*vertices++)) { + BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner, yllcorner}, face)); + BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + cellsize, yllcorner + cellsize}, face)); + BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner, yllcorner + cellsize}, face)); + BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + 1, yllcorner + 1}, face)); + BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + 1, yllcorner + 2}, face)); + BOOST_CHECK(!triangleContainsPoint(glm::vec2 {xllcorner + 3, yllcorner + 2}, face)); + BOOST_CHECK(!triangleContainsPoint(glm::vec2 {xllcorner + cellsize, yllcorner}, face)); + } +} + +BOOST_AUTO_TEST_CASE(locatePointFace) +{ + const PointFace pf {{310002, 490003}}; + BOOST_CHECK(!pf.isLocated()); + BOOST_CHECK(pf.face(this).is_valid()); + BOOST_CHECK_EQUAL(pf.face(this).idx(), 0); +} + +BOOST_AUTO_TEST_CASE(preLocatePointFace) +{ + const PointFace pf {{310002, 490003}, this}; + BOOST_CHECK(pf.isLocated()); + BOOST_CHECK_EQUAL(pf.face(this).idx(), 0); +} + +BOOST_AUTO_TEST_SUITE_END(); + +using FindPointData = std::tuple; + +static const TestTerrainMesh fixedTerrtain; + +// No boundary cases as these can produce different valid results depending on starting point +BOOST_DATA_TEST_CASE(findPointOnTerrain, + boost::unit_test::data::make({ + {{0, 0}, -1}, {{xllcorner, 0}, -1}, {{0, yllcorner}, -1}, {{xllcorner + 1, yllcorner + 2}, 0}, + {{xllcorner + (cellsize * (nrows - 1)) - 2, yllcorner + (cellsize * (ncols - 1)) - 1}, 79200}, + {{315555, 495556}, 44400}, // perf test target + }) + * boost::unit_test::data::make( + {0, 1, 2, 3, 4, 5, 6, 10, 100, 150, 200, 1000, 1234, 17439, 79201, 79200, 79199}), + p, fh, start) +{ + BOOST_CHECK_EQUAL(fh, fixedTerrtain.findPoint(p, GeoData::FaceHandle(start)).idx()); +} + +using FindPositionData = std::tuple; + +BOOST_DATA_TEST_CASE(findPositionAt, + boost::unit_test::data::make({ + // corners + {{310000, 490000}, 32.8F}, + {{310050, 490050}, 33.0F}, + {{310000, 490050}, 32.7F}, + {{310050, 490000}, 33.2F}, + {{310750, 490150}, 58.4F}, + // midpoints + {{310025, 490025}, 32.9F}, + {{310025, 490050}, 32.85F}, + {{310000, 490025}, 32.75F}, + // other + {{310751, 490152}, 58.326F}, + }), + p, h) +{ + BOOST_CHECK_CLOSE_VEC(fixedTerrtain.positionAt(p), p ^ h); +} + +using FindRayIntersectData = std::tuple; + +BOOST_DATA_TEST_CASE(findRayIntersect, + boost::unit_test::data::make({ + {{310000, 490000, 50}, {1, 1, -2}, {310008.59, 490008.59, 32.834301}}, + {{310000, 490000, 50}, {1, 1, -1}, {310017.12, 490017.12, 32.868526}}, + }), + p, d, i) +{ + BOOST_CHECK_CLOSE_VEC(fixedTerrtain.intersectRay({p, d}).value(), i); +} + +using WalkTerrainData = std::tuple>; + +BOOST_DATA_TEST_CASE(walkTerrain, + boost::unit_test::data::make({ + {{310002, 490003}, {310002, 490003}, {0}}, + {{310003, 490002}, {310003, 490002}, {1}}, + {{310002, 490003}, {310003, 490002}, {0, 1}}, + {{310003, 490002}, {310002, 490003}, {1, 0}}, + {{310002, 490003}, {310202, 490003}, {0, 1, 2, 3, 4, 5, 6, 7, 8}}, + {{310202, 490003}, {310002, 490003}, {8, 7, 6, 5, 4, 3, 2, 1, 0}}, + {{310002, 490003}, {310002, 490203}, {0, 399, 398, 797, 796, 1195, 1194, 1593, 1592}}, + }), + from, to, visits) +{ + std::vector visited; + BOOST_CHECK_NO_THROW(fixedTerrtain.walk(from, to, [&visited](auto fh) { + visited.emplace_back(fh.idx()); + })); + BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); +} + +BOOST_DATA_TEST_CASE(walkTerrainSetsFromFace, + boost::unit_test::data::make({ + {{310002, 490003}, {310002, 490003}, {0}}, + {{310003, 490002}, {310003, 490002}, {1}}, + {{310002, 490003}, {310003, 490002}, {0, 1}}, + {{310003, 490002}, {310002, 490003}, {1, 0}}, + }), + from, to, visits) +{ + GeoData::PointFace pf {from}; + BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) {})); + BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), visits.front()); +} + +BOOST_DATA_TEST_CASE(walkTerrainUntil, + boost::unit_test::data::make({ + {{310002, 490003}, {310002, 490003}, {0}}, + {{310003, 490002}, {310003, 490002}, {1}}, + {{310002, 490003}, {310003, 490002}, {0, 1}}, + {{310003, 490002}, {310002, 490003}, {1, 0}}, + {{310002, 490003}, {310202, 490003}, {0, 1, 2, 3, 4}}, + {{310202, 490003}, {310002, 490003}, {8, 7, 6, 5, 4}}, + {{310002, 490003}, {310002, 490203}, {0, 399, 398, 797, 796}}, + }), + from, to, visits) +{ + std::vector visited; + BOOST_CHECK_NO_THROW(fixedTerrtain.walkUntil(from, to, [&visited](auto fh) { + visited.emplace_back(fh.idx()); + return visited.size() >= 5; + })); + BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); +} diff --git a/test/test-render.cpp b/test/test-render.cpp index b16f241..fb2a71b 100644 --- a/test/test-render.cpp +++ b/test/test-render.cpp @@ -26,8 +26,7 @@ class TestScene : public SceneProvider { std::shared_ptr train1, train2; Terrain terrain {[]() { - auto gd = std::make_shared(GeoData::Limits {{0, 0}, {100, 100}}); - gd->generateRandom(); + auto gd = std::make_shared(GeoData::createFlat({0, 0}, {1000, 1000}, 1)); return gd; }()}; diff --git a/test/test-terrain.cpp b/test/test-terrain.cpp deleted file mode 100644 index 71fc1ec..0000000 --- a/test/test-terrain.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#define BOOST_TEST_MODULE terrain -#include "testHelpers.h" -#include -#include -#include - -#include - -class TestTerrainMesh : public TerrainMesh { -public: - TestTerrainMesh() : TerrainMesh {TerrainMesh::loadFromAsciiGrid(FIXTURESDIR "height/SD19.asc")} { } -}; - -constexpr size_t ncols = 200, nrows = 200, xllcorner = 310000, yllcorner = 490000, cellsize = 50; - -BOOST_FIXTURE_TEST_SUITE(ttm, TestTerrainMesh); - -BOOST_AUTO_TEST_CASE(loadSuccess) -{ - BOOST_CHECK_EQUAL(ncols * nrows, n_vertices()); - BOOST_CHECK_EQUAL(2 * (ncols - 1) * (nrows - 1), n_faces()); - const auto [lower, upper] = getExtents(); - BOOST_CHECK_EQUAL(lower, glm::vec3(310000, 490000, -2.6)); - BOOST_CHECK_EQUAL(upper, glm::vec3(320000, 500000, 571.6)); -} - -BOOST_AUTO_TEST_CASE(normalsAllPointUp) -{ - BOOST_CHECK(std::all_of(faces_begin(), faces_end(), [this](auto && vh) { - return normal(vh).z > 0; - })); - BOOST_CHECK(std::all_of(vertices_begin(), vertices_end(), [this](auto && vh) { - return normal(vh).z > 0; - })); -} - -BOOST_AUTO_TEST_CASE(trianglesContainsPoints) -{ - const auto face = face_handle(0); - auto vertices = cfv_iter(face); - - BOOST_TEST_CONTEXT(point(*vertices++) << point(*vertices++) << point(*vertices++)) { - BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner, yllcorner}, face)); - BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + cellsize, yllcorner + cellsize}, face)); - BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner, yllcorner + cellsize}, face)); - BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + 1, yllcorner + 1}, face)); - BOOST_CHECK(triangleContainsPoint(glm::vec2 {xllcorner + 1, yllcorner + 2}, face)); - BOOST_CHECK(!triangleContainsPoint(glm::vec2 {xllcorner + 3, yllcorner + 2}, face)); - BOOST_CHECK(!triangleContainsPoint(glm::vec2 {xllcorner + cellsize, yllcorner}, face)); - } -} - -BOOST_AUTO_TEST_CASE(locatePointFace) -{ - const PointFace pf {{310002, 490003}}; - BOOST_CHECK(!pf.isLocated()); - BOOST_CHECK(pf.face(this).is_valid()); - BOOST_CHECK_EQUAL(pf.face(this).idx(), 0); -} - -BOOST_AUTO_TEST_CASE(preLocatePointFace) -{ - const PointFace pf {{310002, 490003}, this}; - BOOST_CHECK(pf.isLocated()); - BOOST_CHECK_EQUAL(pf.face(this).idx(), 0); -} - -BOOST_AUTO_TEST_SUITE_END(); - -using FindPointData = std::tuple; - -static const TestTerrainMesh fixedTerrtain; - -// No boundary cases as these can produce different valid results depending on starting point -BOOST_DATA_TEST_CASE(findPointOnTerrain, - boost::unit_test::data::make({ - {{0, 0}, -1}, {{xllcorner, 0}, -1}, {{0, yllcorner}, -1}, {{xllcorner + 1, yllcorner + 2}, 0}, - {{xllcorner + (cellsize * (nrows - 1)) - 2, yllcorner + (cellsize * (ncols - 1)) - 1}, 79200}, - {{315555, 495556}, 44400}, // perf test target - }) - * boost::unit_test::data::make( - {0, 1, 2, 3, 4, 5, 6, 10, 100, 150, 200, 1000, 1234, 17439, 79201, 79200, 79199}), - p, fh, start) -{ - BOOST_CHECK_EQUAL(fh, fixedTerrtain.findPoint(p, TerrainMesh::FaceHandle(start)).idx()); -} - -using FindPositionData = std::tuple; - -BOOST_DATA_TEST_CASE(findPositionAt, - boost::unit_test::data::make({ - // corners - {{310000, 490000}, 32.8F}, - {{310050, 490050}, 33.0F}, - {{310000, 490050}, 32.7F}, - {{310050, 490000}, 33.2F}, - {{310750, 490150}, 58.4F}, - // midpoints - {{310025, 490025}, 32.9F}, - {{310025, 490050}, 32.85F}, - {{310000, 490025}, 32.75F}, - // other - {{310751, 490152}, 58.326F}, - }), - p, h) -{ - BOOST_CHECK_CLOSE_VEC(fixedTerrtain.positionAt(p), p ^ h); -} - -using FindRayIntersectData = std::tuple; - -BOOST_DATA_TEST_CASE(findRayIntersect, - boost::unit_test::data::make({ - {{310000, 490000, 50}, {1, 1, -2}, {310008.59, 490008.59, 32.834301}}, - {{310000, 490000, 50}, {1, 1, -1}, {310017.12, 490017.12, 32.868526}}, - }), - p, d, i) -{ - BOOST_CHECK_CLOSE_VEC(fixedTerrtain.intersectRay({p, d}).value(), i); -} - -using WalkTerrainData = std::tuple>; - -BOOST_DATA_TEST_CASE(walkTerrain, - boost::unit_test::data::make({ - {{310002, 490003}, {310002, 490003}, {0}}, - {{310003, 490002}, {310003, 490002}, {1}}, - {{310002, 490003}, {310003, 490002}, {0, 1}}, - {{310003, 490002}, {310002, 490003}, {1, 0}}, - {{310002, 490003}, {310202, 490003}, {0, 1, 2, 3, 4, 5, 6, 7, 8}}, - {{310202, 490003}, {310002, 490003}, {8, 7, 6, 5, 4, 3, 2, 1, 0}}, - {{310002, 490003}, {310002, 490203}, {0, 399, 398, 797, 796, 1195, 1194, 1593, 1592}}, - }), - from, to, visits) -{ - std::vector visited; - BOOST_CHECK_NO_THROW(fixedTerrtain.walk(from, to, [&visited](auto fh) { - visited.emplace_back(fh.idx()); - })); - BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); -} - -BOOST_DATA_TEST_CASE(walkTerrainSetsFromFace, - boost::unit_test::data::make({ - {{310002, 490003}, {310002, 490003}, {0}}, - {{310003, 490002}, {310003, 490002}, {1}}, - {{310002, 490003}, {310003, 490002}, {0, 1}}, - {{310003, 490002}, {310002, 490003}, {1, 0}}, - }), - from, to, visits) -{ - TerrainMesh::PointFace pf {from}; - BOOST_CHECK_NO_THROW(fixedTerrtain.walk(pf, to, [](auto) {})); - BOOST_CHECK_EQUAL(pf.face(&fixedTerrtain).idx(), visits.front()); -} - -BOOST_DATA_TEST_CASE(walkTerrainUntil, - boost::unit_test::data::make({ - {{310002, 490003}, {310002, 490003}, {0}}, - {{310003, 490002}, {310003, 490002}, {1}}, - {{310002, 490003}, {310003, 490002}, {0, 1}}, - {{310003, 490002}, {310002, 490003}, {1, 0}}, - {{310002, 490003}, {310202, 490003}, {0, 1, 2, 3, 4}}, - {{310202, 490003}, {310002, 490003}, {8, 7, 6, 5, 4}}, - {{310002, 490003}, {310002, 490203}, {0, 399, 398, 797, 796}}, - }), - from, to, visits) -{ - std::vector visited; - BOOST_CHECK_NO_THROW(fixedTerrtain.walkUntil(from, to, [&visited](auto fh) { - visited.emplace_back(fh.idx()); - return visited.size() >= 5; - })); - BOOST_CHECK_EQUAL_COLLECTIONS(visited.begin(), visited.end(), visits.begin(), visits.end()); -} diff --git a/ui/gameMainWindow.cpp b/ui/gameMainWindow.cpp index c35c9c6..6981c54 100644 --- a/ui/gameMainWindow.cpp +++ b/ui/gameMainWindow.cpp @@ -31,7 +31,7 @@ public: GameMainWindow::GameMainWindow(size_t w, size_t h) : Window {w, h, "I Like Trains", SDL_WINDOW_OPENGL}, SceneRenderer {Window::size, 0} { - uiComponents.create(glm::vec2 {-1150, -1150}); + uiComponents.create(glm::vec2 {315000, 495000}); auto gms = uiComponents.create(&camera, glm::vec2 {w, h}); uiComponents.create(gms.get()); } -- cgit v1.2.3