diff --git a/scripts/icosahedron_tables.py b/scripts/icosahedron_tables.py index 0ee05b25..63cf74a9 100644 --- a/scripts/icosahedron_tables.py +++ b/scripts/icosahedron_tables.py @@ -126,9 +126,9 @@ def distance(a, b): # Edge lengths vs subdiv level -edge_length_min = [2.0*sin(0.5*acot(0.5) * 0.5**x) for x in range(0, 10)] +edge_length_min = [2.0*sin(0.5*acot(0.5) * 0.5**x) for x in range(0, 24)] -print("inline constexpr std::array const gc_icoMinEdgeVsSubdiv\n" +print("inline constexpr std::array const gc_icoMinEdgeVsSubdiv\n" + "{\n" + " " + ", ".join(nstr_float(edge_length) for edge_length in edge_length_min) + "\n" + "};\n") @@ -143,7 +143,7 @@ def distance(a, b): ( cxa, -sya, hei) ) -for _ in range(0, 10): +for _ in range(0, 24): edge_length_max.append(distance(tri[0], tri[1])) @@ -154,7 +154,7 @@ def distance(a, b): norm(mid(tri[2], tri[0])) ) -print("inline constexpr std::array const gc_icoMaxEdgeVsSubdiv\n" +print("inline constexpr std::array const gc_icoMaxEdgeVsSubdiv\n" + "{\n" + " " + ", ".join(nstr_float(edge_length) for edge_length in edge_length_max) + "\n" + "};\n") @@ -165,9 +165,9 @@ def distance(a, b): # If identical towers were built on the two vertices spanning an edge, this is how high each tower # needs to be in order to see each other over the horizon. # = exsec(0.5*acot(0.5) * levels) -tower_heights = [sec(0.5*acot(0.5) * 0.5**x ) - 1 for x in range(0, 10)] +tower_heights = [sec(0.5*acot(0.5) * 0.5**x ) - 1 for x in range(0, 24)] -print("inline constexpr std::array const gc_icoTowerOverHorizonVsSubdiv\n" +print("inline constexpr std::array const gc_icoTowerOverHorizonVsSubdiv\n" + "{\n" + " " + ", ".join(nstr_float(edge_length) for edge_length in tower_heights) + "\n" + "};\n") diff --git a/src/osp/core/array_view.h b/src/osp/core/array_view.h index 0c66d4c4..2de7429c 100644 --- a/src/osp/core/array_view.h +++ b/src/osp/core/array_view.h @@ -26,6 +26,8 @@ // IWYU pragma: begin_exports #include +#include +#include // IWYU pragma: end_exports #include @@ -36,34 +38,54 @@ namespace osp using Corrade::Containers::ArrayView; using Corrade::Containers::arrayView; +using Corrade::Containers::arrayCast; + +/** + * @brief Wraps a Corrade ArrayView or StridedArrayView to use as a 2D array of equally sized rows + */ template struct ArrayView2DWrapper { - constexpr ArrayView row(std::size_t const index) const noexcept + // yes, I know that StridedArrayView2D exists, but ".row(...)" is more explicit + + constexpr T row(std::size_t const columnIndex) const noexcept { - return view.sliceSize(index * columns, columns); + return view.sliceSize(columnIndex * rowSize, rowSize); } - ArrayView view; - std::size_t columns; + T view; + std::size_t rowSize; ///< Size of each row, same as the number of columns }; +/** + * @brief Returns an interface that treats an ArrayView as a 2D array of equally sized rows. + * + * This overload auto-converts ArrayView-compatible types. + */ +template + requires requires (T &view) { osp::arrayView(view); } +constexpr decltype(auto) as_2d(T &view, std::size_t rowSize) noexcept +{ + return ArrayView2DWrapper{ .view = osp::arrayView(view), .rowSize = rowSize }; +} + +/** + * @brief Returns an interface that treats an ArrayView as a 2D array of equally sized rows. + */ template -constexpr ArrayView2DWrapper as_2d(ArrayView view, std::size_t columns) noexcept +constexpr ArrayView2DWrapper< ArrayView > as_2d(ArrayView view, std::size_t rowSize) noexcept { - return { .view = view, .columns = columns }; + return { .view = view, .rowSize = rowSize }; } /** - * @brief slice_2d_row - * @param view - * @param index - * @param size + * @brief Returns an interface that treats an ArrayView as a 2D array of equally sized rows. */ template -ArrayView slice_2d_row(ArrayView const& view, std::size_t index, std::size_t size) noexcept +constexpr ArrayView2DWrapper< Corrade::Containers::StridedArrayView1D > + as_2d(Corrade::Containers::StridedArrayView1D view, std::size_t rowSize) noexcept { - return view.sliceSize(index, size); + return { .view = view, .rowSize = rowSize }; } /** diff --git a/src/osp/core/buffer_format.h b/src/osp/core/buffer_format.h new file mode 100644 index 00000000..b0c49986 --- /dev/null +++ b/src/osp/core/buffer_format.h @@ -0,0 +1,139 @@ +/** + * Open Space Program + * Copyright © 2019-2024 Open Space Program Project + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#pragma once + +#include +#include + +namespace osp +{ + +/** + * @brief Buffer Attribute Format. Describes how to access element attribute data within a buffer. + * + * This is useful for SIMD, GPU, and serialization. A SIMD n-body simulation may prefer [XXXYYYZZZ] + * to store positions, but GPU mesh vertex positions prefer [XYZXYZXYZ...]. + */ +template +struct BufAttribFormat +{ + using View_t = Corrade::Containers::StridedArrayView1D; + using ViewConst_t = Corrade::Containers::StridedArrayView1D; + using Data_t = Corrade::Containers::ArrayView; + using DataConst_t = Corrade::Containers::ArrayView; + + constexpr View_t view(Data_t data, std::size_t count) const noexcept + { + return stridedArrayView(data, reinterpret_cast(&data[offset]), count, stride); + } + + constexpr ViewConst_t view_const(DataConst_t data, std::size_t count) const noexcept + { + return stridedArrayView(data, reinterpret_cast(&data[offset]), count, stride); + } + + constexpr bool is_not_used() const noexcept { return stride == 0; } + + std::size_t offset{}; + std::ptrdiff_t stride{}; +}; + +/** + * @brief Builder to more easily create BufAttribFormats + */ +class BufferFormatBuilder +{ +public: + + /** + * @brief Insert a single contiguous block of attribute data + * + * To make the buffer format [XXXX... YYYYY... ZZZZZ...] for example, use: + * + * @code{.cpp} + * builder.insert_block(count); // X + * builder.insert_block(count); // Y + * builder.insert_block(count); // Z + * @endcode + * + * @param count [in] Number of elements + */ + template + constexpr BufAttribFormat insert_block(std::size_t const count) + { + auto const prevbytesUsed = m_totalSize; + m_totalSize += sizeof(T) * count; + + return { .offset = prevbytesUsed, .stride = sizeof(T) }; + } + + /** + * @brief Insert interleaved attribute data + * + * To make the buffer format [XYZXYZXYZXYZ...] for example, use: + * + * @code{.cpp} + * BufAttribFormat attribX; + * BufAttribFormat attribY; + * BufAttribFormat attribZ; + * builder.insert_interleave(count, attribX, attribY, attribZ); + * @endcode + * + * @param count [in] Number of elements + */ + template + constexpr void insert_interleave(std::size_t count, BufAttribFormat& ... rInterleave) + { + constexpr std::size_t stride = (sizeof(T) + ...); + + (rInterleave.m_stride = ... = stride); + + interleave_aux(m_totalSize, rInterleave ...); + + m_totalSize += stride * count; + } + + constexpr std::size_t total_size() const noexcept + { + return m_totalSize; + } + +private: + + template + constexpr void interleave_aux(std::size_t const pos, BufAttribFormat& rInterleaveFirst, BufAttribFormat& ... rInterleave) + { + rInterleaveFirst.m_offset = pos; + + if constexpr (sizeof...(T) != 0) + { + interleave_aux(pos + sizeof(FIRST_T), rInterleave ...); + } + } + + std::size_t m_totalSize{0}; +}; + +} // namespace osp diff --git a/src/osp/core/math_int64.h b/src/osp/core/math_int64.h index 8c2b30d0..ce3bdb4c 100644 --- a/src/osp/core/math_int64.h +++ b/src/osp/core/math_int64.h @@ -32,7 +32,7 @@ namespace osp /** * @brief int64 abs(lhs - rhs) with no risk of overflow */ -constexpr std::uint64_t absdelta(std::int64_t lhs, std::int64_t rhs) noexcept +constexpr std::uint64_t abs_difference(std::int64_t lhs, std::int64_t rhs) noexcept { // TODO: maybe use int128 for compilers that support it? @@ -53,26 +53,14 @@ constexpr std::uint64_t absdelta(std::int64_t lhs, std::int64_t rhs) noexcept /** * @brief (distance between a and b) > threshold - * - * This function is quick and dirty, max threshold is limited to 1,431,655,765 */ -constexpr bool is_distance_near(Vector3l const a, Vector3l const b, std::uint64_t const threshold) noexcept +constexpr bool is_distance_near(Vector3l const a, Vector3l const b, double const threshold) noexcept { - std::uint64_t const dx = absdelta(a.x(), b.x()); - std::uint64_t const dy = absdelta(a.y(), b.y()); - std::uint64_t const dz = absdelta(a.z(), b.z()); - - // 1431655765 = sqrt(2^64)/3 = max distance without risk of overflow - constexpr std::uint64_t dmax = 1431655765ul; - - if (dx > dmax || dy > dmax || dz > dmax) - { - return false; - } - - std::uint64_t const magnitudeSqr = (dx*dx + dy*dy + dz*dz); + double const dx = abs_difference(a.x(), b.x()); + double const dy = abs_difference(a.y(), b.y()); + double const dz = abs_difference(a.z(), b.z()); - return magnitudeSqr < threshold*threshold; + return (dx*dx + dy*dy + dz*dz) < threshold*threshold; } } // namespace osp diff --git a/src/osp/drawing_gl/rendergl.h b/src/osp/drawing_gl/rendergl.h index b6f4f3d6..89cbeb93 100644 --- a/src/osp/drawing_gl/rendergl.h +++ b/src/osp/drawing_gl/rendergl.h @@ -26,6 +26,7 @@ #include "FullscreenTriShader.h" +#include "../core/strong_id.h" #include "../drawing/drawing_fn.h" #include @@ -41,8 +42,8 @@ namespace osp::draw { -enum class TexGlId : uint32_t { }; -enum class MeshGlId : uint32_t { }; +using TexGlId = osp::StrongId; +using MeshGlId = osp::StrongId; using TexGlStorage_t = Storage_t; using MeshGlStorage_t = Storage_t; diff --git a/src/planet-a/chunk_generate.cpp b/src/planet-a/chunk_generate.cpp index e00e28f9..1fadb11f 100644 --- a/src/planet-a/chunk_generate.cpp +++ b/src/planet-a/chunk_generate.cpp @@ -31,6 +31,7 @@ using osp::ArrayView; using osp::Vector3; using osp::Vector3u; +using osp::Vector3l; using osp::ZeroInit; using osp::arrayView; using osp::as_2d; @@ -41,9 +42,12 @@ namespace planeta void ChunkScratchpad::resize(ChunkSkeleton const& rChSk) { auto const maxSharedVrtx = rChSk.m_sharedIds.capacity(); + auto const maxChunks = rChSk.m_chunkIds.capacity(); - edgeVertices .resize((rChSk.m_chunkEdgeVrtxCount - 1) * 3); - stitchCmds .resize(rChSk.m_chunkIds.capacity(), {}); + edgeVertices .resize(std::size_t(rChSk.m_chunkEdgeVrtxCount - 1) * 3); + stitchCmds .resize(maxChunks, {}); + chunksAdded .resize(maxChunks); + chunksRemoved .resize(maxChunks); sharedAdded .resize(maxSharedVrtx); sharedRemoved .resize(maxSharedVrtx); sharedNormalsDirty .resize(maxSharedVrtx); @@ -153,18 +157,22 @@ void restitch_check( ChunkId const parentNeighborChunk = rSkCh.m_triToChunk[parentNeighbor]; int const neighborEdge = rSkel.tri_at(parentNeighbor).find_neighbor_index(parent); - ChunkStitch const desiredStitch = { + if (parentNeighborChunk.has_value()) + { + ChunkStitch const desiredStitch = { .enabled = true, .detailX2 = true, .x2ownEdge = static_cast(neighborEdge), .x2neighborEdge = static_cast(selfEdgeIdx) - }; + }; - ChunkStitch &rStitchCmd = rChSP.stitchCmds[parentNeighborChunk]; - LGRN_ASSERT( (rStitchCmd.enabled == false) - || (rStitchCmd.detailX2 == false) - || (rStitchCmd == desiredStitch)); - rStitchCmd = desiredStitch; + ChunkStitch &rStitchCmd = rChSP.stitchCmds[parentNeighborChunk]; + LGRN_ASSERT( (rStitchCmd.enabled == false) + || (rStitchCmd.detailX2 == false) + || (rStitchCmd == desiredStitch)); + rStitchCmd = desiredStitch; + } + // else, hole in terrain } } @@ -190,14 +198,15 @@ void update_faces( return; // Nothing to do } - auto const ibufSlice = as_2d(arrayView(rGeom.chunkIbuf), rChInfo.chunkMaxFaceCount).row(chunkId.value); - auto const fanNormalContrib = as_2d(arrayView(rGeom.chunkFanNormalContrib), rChInfo.fanMaxSharedCount).row(chunkId.value); - auto const fillNormalContrib = as_2d(arrayView(rGeom.chunkFillSharedNormals), rSkCh.m_chunkSharedCount) .row(chunkId.value); + auto const vbufNormalsView = rGeom.vbufNormals.view(rGeom.vrtxBuffer, rChInfo.vrtxTotal); + auto const ibufSlice = as_2d(rGeom.indxBuffer, rChInfo.chunkMaxFaceCount).row(chunkId.value); + auto const fanNormalContrib = as_2d(rGeom.chunkFanNormalContrib, rChInfo.fanMaxSharedCount).row(chunkId.value); + auto const fillNormalContrib = as_2d(rGeom.chunkFillSharedNormals, rSkCh.m_chunkSharedCount) .row(chunkId.value); auto const sharedUsed = rSkCh.shared_vertices_used(chunkId); TerrainFaceWriter writer{ - .vbufPos = rGeom.chunkVbufPos, - .vbufNrm = rGeom.chunkVbufNrm, + .vbufPos = rGeom.vbufPositions.view_const(rGeom.vrtxBuffer, rChInfo.vrtxTotal), + .vbufNrm = vbufNormalsView, .sharedNormalSum = rGeom.sharedNormalSum.base(), .fillNormalContrib = fillNormalContrib, .fanNormalContrib = fanNormalContrib, @@ -211,7 +220,7 @@ void update_faces( if (newlyAdded) { // Reset fill normals to zero, as values are left over from a previously deleted chunk - auto const chunkVbufFillNormals2D = as_2d(arrayView(rGeom.chunkVbufNrm).exceptPrefix(rChInfo.vbufFillOffset), rChInfo.fillVrtxCount); + auto const chunkVbufFillNormals2D = as_2d(vbufNormalsView.exceptPrefix(rChInfo.vbufFillOffset), rChInfo.fillVrtxCount); auto const vbufFillNormals = chunkVbufFillNormals2D.row(chunkId.value); // These aren't cleaned up by the previous chunk that used them @@ -371,27 +380,29 @@ void debug_check_invariants( ChunkMeshBufferInfo const &rChInfo, ChunkSkeleton const &rSkCh) { + auto const vbufNormalsView = rGeom.vbufNormals.view_const(rGeom.vrtxBuffer, rChInfo.vrtxTotal); + auto const check_vertex = [&] (VertexIdx vertex, SharedVrtxId sharedId, ChunkId chunkId) { - osp::Vector3 const normal = rGeom.chunkVbufNrm[vertex]; + osp::Vector3 const normal = vbufNormalsView[vertex]; float const length = normal.length(); LGRN_ASSERTMV(std::abs(length - 1.0f) < 0.05f, "Normal isn't normalized", length, vertex, sharedId.value, chunkId.value); }; - for (SharedVrtxId const sharedId : rSkCh.m_sharedIds) + for (std::size_t const sharedInt : rSkCh.m_sharedIds.bitview().zeros()) { - check_vertex(rChInfo.vbufSharedOffset + std::size_t(sharedId), sharedId, {}); + check_vertex(rChInfo.vbufSharedOffset + sharedInt, SharedVrtxId(sharedInt), {}); } - for (ChunkId const chunkId : rSkCh.m_chunkIds) + for (std::size_t const chunkInt : rSkCh.m_chunkIds.bitview().zeros()) { - VertexIdx const first = rChInfo.vbufFillOffset + std::size_t(chunkId) * rChInfo.fillVrtxCount; + VertexIdx const first = rChInfo.vbufFillOffset + chunkInt * rChInfo.fillVrtxCount; VertexIdx const last = first + rChInfo.fillVrtxCount; for (VertexIdx vertex = first; vertex < last; ++vertex) { - check_vertex(vertex, {}, chunkId); + check_vertex(vertex, {}, ChunkId(chunkInt)); } } } @@ -402,29 +413,32 @@ void write_obj( ChunkMeshBufferInfo const &rChInfo, ChunkSkeleton const &rSkCh) { - auto const chunkCount = rSkCh.m_chunkIds.size(); + auto const chunkCount = rSkCh.m_chunkIds.size(); auto const sharedCount = rSkCh.m_sharedIds.size(); + auto const vbufPosView = rGeom.vbufPositions.view_const(rGeom.vrtxBuffer, rChInfo.vrtxTotal); + auto const vbufNrmView = rGeom.vbufNormals .view_const(rGeom.vrtxBuffer, rChInfo.vrtxTotal); + + rStream << "# Terrain mesh debug output\n" << "# Chunks: " << chunkCount << "/" << rSkCh.m_chunkIds.capacity() << "\n" << "# Shared Vertices: " << sharedCount << "/" << rSkCh.m_sharedIds.capacity() << "\n"; rStream << "o Planet\n"; - for (osp::Vector3 v : rGeom.chunkVbufPos) + for (osp::Vector3 v : vbufPosView) { rStream << "v " << v.x() << " " << v.y() << " " << v.z() << "\n"; } - for (osp::Vector3 v : rGeom.chunkVbufNrm) + for (osp::Vector3 v : vbufNrmView) { rStream << "vn " << v.x() << " " << v.y() << " " << v.z() << "\n"; } - for (ChunkId const chunkId : rSkCh.m_chunkIds) + for (std::size_t const chunkIdInt : rSkCh.m_chunkIds.bitview().zeros()) { - std::size_t const chunkIdInt = std::size_t(chunkId); - auto const view = osp::as_2d(osp::arrayView(rGeom.chunkIbuf), rChInfo.chunkMaxFaceCount).row(chunkIdInt); + auto const view = osp::as_2d(rGeom.indxBuffer, rChInfo.chunkMaxFaceCount).row(chunkIdInt); for (osp::Vector3u const faceIdx : view) { diff --git a/src/planet-a/chunk_generate.h b/src/planet-a/chunk_generate.h index ae0ad221..64a2b9b1 100644 --- a/src/planet-a/chunk_generate.h +++ b/src/planet-a/chunk_generate.h @@ -47,14 +47,11 @@ struct ChunkScratchpad /// New stitches to apply to currently existing chunks osp::KeyedVec stitchCmds; - /// Newly added chunks - std::vector chunksAdded; + lgrn::IdSetStl chunksAdded; ///< Recently added chunks + lgrn::IdSetStl chunksRemoved; ///< Recently removed chunks - /// Recently added shared vertices, position needs to be copied from skeleton - lgrn::IdSetStl sharedAdded; - - /// Recently added shared vertices - lgrn::IdSetStl sharedRemoved; + lgrn::IdSetStl sharedAdded; ///< Recently added shared vertices + lgrn::IdSetStl sharedRemoved; ///< Recently removed shared vertices /// Shared vertices that need to recalculate normals lgrn::IdSetStl sharedNormalsDirty; diff --git a/src/planet-a/chunk_utils.cpp b/src/planet-a/chunk_utils.cpp index 91953846..19459263 100644 --- a/src/planet-a/chunk_utils.cpp +++ b/src/planet-a/chunk_utils.cpp @@ -56,9 +56,17 @@ void ChunkFillSubdivLUT::subdiv_line_recurse( { Vector2us const mid = (a + b) / 2; - ChunkLocalSharedId const out = ChunkLocalSharedId(xy_to_triangular(mid.x() - 1, mid.y() - 2)); - - m_data.emplace_back(ToSubdiv{id_at(a), id_at(b), out}); + std::uint32_t const out = xy_to_triangular(mid.x() - 1, mid.y() - 2); + ChunkLocalSharedId const sharedA = coord_to_shared(a.x(), a.y(), m_edgeVrtxCount); + ChunkLocalSharedId const sharedB = coord_to_shared(b.x(), b.y(), m_edgeVrtxCount); + + m_data.emplace_back(ToSubdiv{ + .vrtxA = std::uint32_t(sharedA.has_value() ? sharedA.value : xy_to_triangular(a.x() - 1, a.y() - 2)), + .vrtxB = std::uint32_t(sharedB.has_value() ? sharedB.value : xy_to_triangular(b.x() - 1, b.y() - 2)), + .fillOut = out, + .aIsShared = sharedA.has_value(), + .bIsShared = sharedB.has_value() + }); if (level > 1) { diff --git a/src/planet-a/chunk_utils.h b/src/planet-a/chunk_utils.h index 083b27eb..e13f7fa2 100644 --- a/src/planet-a/chunk_utils.h +++ b/src/planet-a/chunk_utils.h @@ -79,13 +79,16 @@ struct ChunkMeshBufferInfo /// Max total faces per chunk. fillFaceCount + fanMaxFaceCount std::uint32_t chunkMaxFaceCount; + /// Total number of faces + std::uint32_t faceTotal; + /// Index of first fill vertex within in vertex buffer std::uint32_t vbufFillOffset; /// Index of first shared vertex within in vertex buffer std::uint32_t vbufSharedOffset; - /// Total size of vertex buffer - std::uint32_t vbufSize; + /// Total number of vertices + std::uint32_t vrtxTotal; }; constexpr ChunkMeshBufferInfo make_chunk_mesh_buffer_info(ChunkSkeleton const &skChunks) @@ -99,6 +102,7 @@ constexpr ChunkMeshBufferInfo make_chunk_mesh_buffer_info(ChunkSkeleton const &s std::uint32_t const fanFaceCount = ChunkMeshBufferInfo::smc_fanFacesVsSubdivLevel[skChunks.m_chunkSubdivLevel]; std::uint32_t const fillFaceCount = chunkWidth*chunkWidth - fanFaceCount; std::uint32_t const fanMaxFaceCount = fanFaceCount + fanFaceCount/3 + 1; + std::uint32_t const chunkMaxFaceCount = fillFaceCount + fanMaxFaceCount; std::uint32_t const fanMaxSharedCount = fanMaxFaceCount + 4; return @@ -107,10 +111,11 @@ constexpr ChunkMeshBufferInfo make_chunk_mesh_buffer_info(ChunkSkeleton const &s .fillFaceCount = fillFaceCount, .fanMaxFaceCount = fanMaxFaceCount, .fanMaxSharedCount = fanMaxSharedCount, - .chunkMaxFaceCount = fillFaceCount + fanMaxFaceCount, + .chunkMaxFaceCount = chunkMaxFaceCount, + .faceTotal = maxChunks * chunkMaxFaceCount, .vbufFillOffset = 0, .vbufSharedOffset = fillTotal, - .vbufSize = std::uint32_t(fillTotal + maxSharedVrtx) + .vrtxTotal = std::uint32_t(fillTotal + maxSharedVrtx) }; } @@ -131,7 +136,7 @@ constexpr ChunkMeshBufferInfo make_chunk_mesh_buffer_info(ChunkSkeleton const &s * @param y [in] * @return triangular number */ -constexpr int xy_to_triangular(int const x, int const y) noexcept +constexpr std::uint32_t xy_to_triangular(std::uint32_t const x, std::uint32_t const y) noexcept { return ( y * (y + 1) ) / 2 + x; }; @@ -159,7 +164,7 @@ constexpr ChunkLocalSharedId coord_to_shared( } } -constexpr VertexIdx fill_to_vrtx(ChunkMeshBufferInfo const& info, ChunkId const chunkId, int const triangular) +constexpr VertexIdx fill_to_vrtx(ChunkMeshBufferInfo const& info, ChunkId const chunkId, std::uint32_t const triangular) { return info.vbufFillOffset + info.fillVrtxCount * chunkId.value + triangular; } @@ -197,31 +202,18 @@ class ChunkFillSubdivLUT public: using Vector2us = Magnum::Math::Vector2; - // Can either be a shared vertex or fill vertex - // Fill vertex if (0 to m_chunkVrtxCount-1) - // Shared vertex if m_chunkVrtxCount to (m_edgeVrtxCount*3-1) - enum class LUTVrtx : std::uint16_t {}; struct ToSubdiv { - LUTVrtx m_vrtxA; - LUTVrtx m_vrtxB; - - ChunkLocalSharedId m_fillOut; - }; + // Both of these can either be a Fill vertex or ChunkLocalSharedId, depending on *IsShared + std::uint32_t vrtxA; + std::uint32_t vrtxB; - constexpr VertexIdx index( - osp::ArrayView sharedUsed, - std::uint32_t const fillOffset, - std::uint32_t const sharedOffset, - LUTVrtx const lutVrtx ) const noexcept - { - auto const lutVrtxInt = std::uint16_t(lutVrtx); - bool const isShared = lutVrtxInt > m_fillVrtxCount; + std::uint32_t fillOut; - return isShared ? sharedOffset + sharedUsed[lutVrtxInt-m_fillVrtxCount].value().value - : fillOffset + lutVrtxInt; - } + bool aIsShared; + bool bIsShared; + }; constexpr std::vector const& data() const noexcept { return m_data; } @@ -229,17 +221,6 @@ class ChunkFillSubdivLUT private: - constexpr LUTVrtx id_at(Vector2us const pos) const noexcept - { - ChunkLocalSharedId const shared = coord_to_shared(pos.x(), pos.y(), m_edgeVrtxCount); - if (shared.has_value()) - { - return LUTVrtx(m_fillVrtxCount + std::uint16_t(shared)); - } - - return LUTVrtx(xy_to_triangular(pos.x() - 1, pos.y() - 2)); - }; - /** * @brief subdiv_edge_recurse * @@ -251,10 +232,10 @@ class ChunkFillSubdivLUT void fill_tri_recurse(Vector2us top, Vector2us lft, Vector2us rte, std::uint8_t level); - //std::unique_ptr m_data; std::vector m_data; - std::uint16_t m_fillVrtxCount{0}; - std::uint16_t m_edgeVrtxCount{0}; + + std::uint16_t m_fillVrtxCount{}; + std::uint16_t m_edgeVrtxCount{}; }; // class ChunkFillSubdivLUT diff --git a/src/planet-a/geometry.cpp b/src/planet-a/geometry.cpp index 8e2aeb7b..675dc679 100644 --- a/src/planet-a/geometry.cpp +++ b/src/planet-a/geometry.cpp @@ -31,15 +31,22 @@ namespace planeta void BasicChunkMeshGeometry::resize(ChunkSkeleton const& skCh, ChunkMeshBufferInfo const& info) { + using Corrade::Containers::Array; + auto const maxChunks = skCh.m_chunkIds.capacity(); auto const maxSharedVrtx = skCh.m_sharedIds.capacity(); - chunkVbufPos .resize(info.vbufSize); - chunkVbufNrm .resize(info.vbufSize); - chunkIbuf .resize(maxChunks * info.chunkMaxFaceCount); + osp::BufferFormatBuilder formatBuilder; + vbufPositions = formatBuilder.insert_block(info.vrtxTotal); + vbufNormals = formatBuilder.insert_block(info.vrtxTotal); + + vrtxBuffer = Array (Corrade::ValueInit, formatBuilder.total_size()); + indxBuffer = Array(Corrade::ValueInit, info.faceTotal); + chunkFanNormalContrib .resize(maxChunks * info.fanMaxSharedCount); chunkFillSharedNormals .resize(maxChunks * skCh.m_chunkSharedCount, osp::Vector3{osp::ZeroInit}); sharedNormalSum .resize(maxSharedVrtx, osp::Vector3{osp::ZeroInit}); + sharedPosNoHeightmap .resize(maxSharedVrtx, osp::Vector3{osp::ZeroInit}); } } // namespace planeta diff --git a/src/planet-a/geometry.h b/src/planet-a/geometry.h index 21e63583..b9bf4caf 100644 --- a/src/planet-a/geometry.h +++ b/src/planet-a/geometry.h @@ -33,6 +33,7 @@ #include "chunk_utils.h" #include +#include namespace planeta { @@ -83,9 +84,14 @@ struct BasicChunkMeshGeometry { void resize(ChunkSkeleton const& skCh, ChunkMeshBufferInfo const& info); - std::vector chunkVbufPos; - std::vector chunkVbufNrm; - std::vector chunkIbuf; + Corrade::Containers::Array vrtxBuffer; ///< Output vertex buffer + Corrade::Containers::Array indxBuffer; ///< Output index buffer + + osp::BufAttribFormat vbufPositions; ///< Describes Position data in vrtxBuffer + osp::BufAttribFormat vbufNormals; ///< Describes Normal data in vrtxBuffer + + /// Shared vertex positions copied from the skeleton and offsetted with no heightmap applied + osp::KeyedVec sharedPosNoHeightmap; /// See \c FanNormalContrib; 2D, each row is \c ChunkMeshBufferInfo::fanMaxSharedCount std::vector chunkFanNormalContrib; @@ -95,6 +101,11 @@ struct BasicChunkMeshGeometry /// Non-normalized sum of face normals of connected faces osp::KeyedVec sharedNormalSum; + + /// Offset of vertex positions relative to the skeleton positions they were copied from + /// "Chunk Mesh Vertex positions = to_float(skeleton positions + skelOffset)". This is intended + /// to move the mesh's origin closer to the viewer, preventing floating point imprecision. + osp::Vector3l originSkelPos{osp::ZeroInit}; }; /** @@ -202,8 +213,9 @@ struct TerrainFaceWriter selectedFaceNormal = Magnum::Math::cross(u, v).normalized(); } - osp::ArrayView vbufPos; - osp::ArrayView vbufNrm; + osp::BufAttribFormat::ViewConst_t vbufPos; + osp::BufAttribFormat::View_t vbufNrm; + osp::ArrayView sharedNormalSum; osp::ArrayView fillNormalContrib; osp::ArrayView fanNormalContrib; diff --git a/src/planet-a/icosahedron.cpp b/src/planet-a/icosahedron.cpp index 22ed3760..f0154131 100644 --- a/src/planet-a/icosahedron.cpp +++ b/src/planet-a/icosahedron.cpp @@ -48,7 +48,7 @@ SubdivTriangleSkeleton create_skeleton_icosahedron( rSkData.positions.resize(skeleton.vrtx_ids().capacity()); rSkData.normals.resize (skeleton.vrtx_ids().capacity()); - double const totalScale = radius * std::pow(2.0, rSkData.precision); + double const totalScale = radius * std::exp2(rSkData.precision); for (int i = 0; i < gc_icoVrtxCount; i ++) { rSkData.positions[vrtxIds[i]] = osp::Vector3l(gc_icoVrtxPos[i] * totalScale); @@ -116,7 +116,7 @@ static void calc_midpoint_spherical( double const curvature = radius - midLen; rSkData.normals[mid] = osp::Vector3(midPosDbl / midLen); - rSkData.positions[mid] = midPos + osp::Vector3l(rSkData.normals[mid] * curvature * scale); + rSkData.positions[mid] = midPos + osp::Vector3l(osp::Vector3d(rSkData.normals[mid]) * (curvature * scale)); } @@ -126,7 +126,7 @@ void ico_calc_middles( std::array, 3> const vrtxMid, SkeletonVertexData &rSkData) { - float const scale = std::pow(2.0f, rSkData.precision); + double const scale = std::exp2(double(rSkData.precision)); if (vrtxMid[0].isNew) { @@ -157,7 +157,7 @@ void ico_calc_chunk_edge( return; } - float const scale = std::pow(2.0f, rSkData.precision); + float const scale = std::exp2(float(rSkData.precision)); auto const recurse = [scale, radius, &rSkData] (auto &&self, SkVrtxId const a, SkVrtxId const b, int const currentLevel, ChunkEdgeView_t const view) noexcept -> void { @@ -182,14 +182,16 @@ void ico_calc_chunk_edge( void ico_calc_sphere_tri_center( SkTriGroupId const groupId, - float const maxRadius, - float const height, + double const maxRadius, + double const height, SubdivTriangleSkeleton const &rSkel, SkeletonVertexData& rSkData) { - using osp::math::int_2pow; - SkTriGroup const &group = rSkel.tri_group_at(groupId); + LGRN_ASSERT(group.depth < gc_icoTowerOverHorizonVsLevel.size()); + + double const terrainMaxHeight = height + maxRadius * gc_icoTowerOverHorizonVsLevel[group.depth]; + float const scale = std::exp2(float(rSkData.precision)); for (int i = 0; i < 4; ++i) { @@ -200,24 +202,18 @@ void ico_calc_sphere_tri_center( SkVrtxId const vb = tri.vertices[1].value(); SkVrtxId const vc = tri.vertices[2].value(); - // average without overflow - osp::Vector3l const posAvg = rSkData.positions[va] / 3 - + rSkData.positions[vb] / 3 - + rSkData.positions[vc] / 3; - - osp::Vector3 const nrmSum = rSkData.normals[va] - + rSkData.normals[vb] - + rSkData.normals[vc]; + // Divide components individually to prevent potential overflow + osp::Vector3l const posAverage = rSkData.positions[va] / 3 + + rSkData.positions[vb] / 3 + + rSkData.positions[vc] / 3; - LGRN_ASSERT(group.depth < gc_icoTowerOverHorizonVsLevel.size()); - float const terrainMaxHeight = height + maxRadius * gc_icoTowerOverHorizonVsLevel[group.depth]; + osp::Vector3 const nrmAverage = ( rSkData.normals[va] + + rSkData.normals[vb] + + rSkData.normals[vc]) / 3.0; - // 0.5 * terrainMaxHeight : halve for middle - // int_2pow(rTerrain.scale) : Vector3l conversion factor - // / 3.0f : average from sum of 3 values - osp::Vector3l const riseToMid = osp::Vector3l(nrmSum * (0.5f * terrainMaxHeight * osp::math::int_2pow(rSkData.precision) / 3.0f)); + osp::Vector3l const highestPoint = osp::Vector3l(nrmAverage * float(terrainMaxHeight * scale)); - rSkData.centers[sktriId] = posAvg + riseToMid; + rSkData.centers[sktriId] = posAverage + highestPoint; } } diff --git a/src/planet-a/icosahedron.h b/src/planet-a/icosahedron.h index 0c9d46f4..2a0816d8 100644 --- a/src/planet-a/icosahedron.h +++ b/src/planet-a/icosahedron.h @@ -91,20 +91,29 @@ inline constexpr std::array, 20> gc_icoNeighbors /** * @brief Icosahedron Minimum Edge Length vs Subdiv Levels, radius = 1.0 */ -inline constexpr std::array const gc_icoMinEdgeVsLevel +inline constexpr std::array const gc_icoMinEdgeVsLevel { - 1.05146222e+0f, 5.46533058e-1f, 2.75904484e-1f, 1.38283174e-1f, 6.91829904e-2f, - 3.45966718e-2f, 1.72989830e-2f, 8.64957239e-3f, 4.32479631e-3f, 2.16239942e-3f + 1.05146222e+0f, 5.46533058e-1f, 2.75904484e-1f, 1.38283174e-1f, + 6.91829904e-2f, 3.45966718e-2f, 1.72989830e-2f, 8.64957239e-3f, + 4.32479631e-3f, 2.16239942e-3f, 1.08119987e-3f, 5.40599953e-4f, + 2.70299979e-4f, 1.35149990e-4f, 6.75749950e-5f, 3.37874975e-5f, + 1.68937487e-5f, 8.44687437e-6f, 4.22343719e-6f, 2.11171859e-6f, + 1.05585930e-6f, 5.27929648e-7f, 2.63964824e-7f, 1.31982412e-7f }; /** * @brief Icosahedron Maximum Edge Length vs Subdiv Levels, radius = 1.0 */ -inline constexpr std::array const gc_icoMaxEdgeVsLevel -{ - 1.05146222e+0f, 6.18033989e-1f, 3.24919696e-1f, 1.64647160e-1f, 8.26039665e-2f, - 4.13372560e-2f, 2.06730441e-2f, 1.03370743e-2f, 5.16860619e-3f, 2.58431173e-3f -}; +inline constexpr std::array const gc_icoMaxEdgeVsLevel +{{ + 1.05146222e+0f, 6.18033989e-1f, 3.24919696e-1f, 1.64647160e-1f, + 8.26039665e-2f, 4.13372560e-2f, 2.06730441e-2f, 1.03370743e-2f, + 5.16860619e-3f, 2.58431173e-3f, 1.29215694e-3f, 6.46078606e-4f, + 3.23039320e-4f, 1.61519662e-4f, 8.07598312e-5f, 4.03799157e-5f, + 2.01899578e-5f, 1.00949789e-5f, 5.04748946e-6f, 2.52374473e-6f, + 1.26187236e-6f, 6.30936182e-7f, 3.15468091e-7f, 1.57734046e-7f +}}; + /** * @brief Tower height required to clear the horizon over an edge vs Subdiv levels, radius = 1.0 @@ -112,10 +121,14 @@ inline constexpr std::array const gc_icoMaxEdgeVsLevel * If identical towers were built on the two vertices spanning an edge, this is how high each tower * needs to be in order to see each other over the horizon. */ -inline constexpr std::array const gc_icoTowerOverHorizonVsLevel +inline constexpr std::array const gc_icoTowerOverHorizonVsLevel { - 1.75570505e-1f, 3.95676520e-2f, 9.65341549e-3f, 2.39888395e-3f, 5.98823224e-4f, - 1.49649798e-4f, 3.74089507e-5f, 9.35201901e-6f, 2.33799109e-6f, 5.84496918e-7f + 1.75570505e-1f, 3.95676520e-2f, 9.65341549e-3f, 2.39888395e-3f, + 5.98823224e-4f, 1.49649798e-4f, 3.74089507e-5f, 9.35201901e-6f, + 2.33799109e-6f, 5.84496918e-7f, 1.46124176e-7f, 3.65310407e-8f, + 9.13275996e-9f, 2.28318998e-9f, 5.70797494e-10f, 1.42699373e-10f, + 3.56748433e-11f, 8.91871083e-12f, 2.22967771e-12f, 5.57419427e-13f, + 1.39354857e-13f, 3.48387142e-14f, 8.70967855e-15f, 2.17741964e-15f }; /** @@ -179,8 +192,8 @@ void ico_calc_chunk_edge( */ void ico_calc_sphere_tri_center( SkTriGroupId groupId, - float maxRadius, - float height, + double maxRadius, + double height, SubdivTriangleSkeleton const &rSkel, SkeletonVertexData &rSkData); diff --git a/src/planet-a/skeleton.h b/src/planet-a/skeleton.h index c037f165..253e70df 100644 --- a/src/planet-a/skeleton.h +++ b/src/planet-a/skeleton.h @@ -46,7 +46,7 @@ namespace planeta { -inline constexpr std::size_t gc_maxSubdivLevels = 10; +inline constexpr std::size_t gc_maxSubdivLevels = 24; struct SkeletonTriangle { diff --git a/src/planet-a/skeleton_subdiv.cpp b/src/planet-a/skeleton_subdiv.cpp index 309db949..463cc98a 100644 --- a/src/planet-a/skeleton_subdiv.cpp +++ b/src/planet-a/skeleton_subdiv.cpp @@ -106,9 +106,11 @@ void unsubdivide_select_by_distance( // Floodfill by checking neighbors next SkeletonTriangle const& sktri = rSkel.tri_at(sktriId); for (SkTriId const neighbor : sktri.neighbors) - if (neighbor.has_value()) { - maybe_distance_check(neighbor); + if (neighbor.has_value()) + { + maybe_distance_check(neighbor); + } } } } @@ -128,8 +130,12 @@ void unsubdivide_deselect_invariant_violations( { int subdivedNeighbors = 0; for (SkTriId const neighbor : sktri.neighbors) - if (neighbor.has_value()) { + if (!neighbor.has_value()) + { + continue; + } + SkeletonTriangle const &rNeighbor = rSkel.tri_at(neighbor); // Pretend neighbor is unsubdivided when it's in tryUnsubdiv, overrided // by cantUnsubdiv @@ -147,17 +153,19 @@ void unsubdivide_deselect_invariant_violations( switch (neighborEdge) { case 0: - if (neighborGroup.triangles[0].children.has_value()) return true; - if (neighborGroup.triangles[1].children.has_value()) return true; + if (neighborGroup.triangles[0].children.has_value()) { return true; } + if (neighborGroup.triangles[1].children.has_value()) { return true; } break; case 1: - if (neighborGroup.triangles[1].children.has_value()) return true; - if (neighborGroup.triangles[2].children.has_value()) return true; + if (neighborGroup.triangles[1].children.has_value()) { return true; } + if (neighborGroup.triangles[2].children.has_value()) { return true; } break; case 2: - if (neighborGroup.triangles[2].children.has_value()) return true; - if (neighborGroup.triangles[0].children.has_value()) return true; + if (neighborGroup.triangles[2].children.has_value()) { return true; } + if (neighborGroup.triangles[0].children.has_value()) { return true; } break; + default: + LGRN_ASSERTM(false, "This should never happen. Triangles only have 3 sides!"); } } } @@ -175,12 +183,16 @@ void unsubdivide_deselect_invariant_violations( { SkeletonTriangle const& sktri = rSkel.tri_at(sktriId); - if (violates_invariants(sktriId, sktri)) + if ( ! violates_invariants(sktriId, sktri) ) { - rSP.cantUnsubdiv.insert(sktriId); + return; + } - // Recurse into neighbors if they're also tryUnsubdiv - for (SkTriId const neighbor : sktri.neighbors) + rSP.cantUnsubdiv.insert(sktriId); + + // Recurse into neighbors if they're also tryUnsubdiv + for (SkTriId const neighbor : sktri.neighbors) + { if (rSP.tryUnsubdiv.contains(neighbor) && ! rSP.cantUnsubdiv.contains(neighbor)) { self(self, neighbor); @@ -212,15 +224,23 @@ void unsubdivide_level( SubdivScratchpadLevel &rLvlSP = rSP .levels[lvl]; for (SkTriId const sktriId : rSP.tryUnsubdiv) - if ( ! rSP.cantUnsubdiv.contains(sktriId) ) { + if ( rSP.cantUnsubdiv.contains(sktriId) ) + { + continue; + } + // All checks passed, 100% confirmed sktri will be unsubdivided SkeletonTriangle &rTri = rSkel.tri_at(sktriId); LGRN_ASSERT(!rLvl.hasSubdivedNeighbor.contains(sktriId)); for (SkTriId const neighborId : rTri.neighbors) - if ( neighborId.has_value() && wont_unsubdivide(neighborId) ) { + if ( ! ( neighborId.has_value() && wont_unsubdivide(neighborId) ) ) + { + continue; + } + SkeletonTriangle const& rNeighborTri = rSkel.tri_at(neighborId); if ( rNeighborTri.children.has_value() ) { @@ -229,18 +249,18 @@ void unsubdivide_level( } else { - bool neighborHasSubdivedNeighbor = false; - for (SkTriId const neighborNeighborId : rNeighborTri.neighbors) - if ( neighborNeighborId.has_value() - && neighborNeighborId != sktriId - && wont_unsubdivide(neighborNeighborId) - && rSkel.is_tri_subdivided(neighborNeighborId) ) + auto const neighbor_has_subdivided_neighbor = [sktriId, &wont_unsubdivide, &rSkel] + (SkTriId const neighborNeighborId) { - neighborHasSubdivedNeighbor = true; - break; - } - - if (neighborHasSubdivedNeighbor) + return neighborNeighborId.has_value() + && neighborNeighborId != sktriId + && wont_unsubdivide(neighborNeighborId) + && rSkel.is_tri_subdivided(neighborNeighborId); + }; + + if ( neighbor_has_subdivided_neighbor(rNeighborTri.neighbors[0]) + || neighbor_has_subdivided_neighbor(rNeighborTri.neighbors[1]) + || neighbor_has_subdivided_neighbor(rNeighborTri.neighbors[2]) ) { rLvl.hasSubdivedNeighbor.insert(neighborId); } @@ -362,9 +382,13 @@ SkTriGroupId subdivide( // Check neighbours along all 3 edges for (int selfEdgeIdx = 0; selfEdgeIdx < 3; ++selfEdgeIdx) - if ( SkTriId const neighborId = neighbors[selfEdgeIdx]; - neighborId.has_value() ) { + SkTriId const neighborId = neighbors[selfEdgeIdx]; + if ( ! neighborId.has_value() ) + { + continue; // Neighbor does not exist + } + SkeletonTriangle& rNeighbor = rSkel.tri_at(neighborId); if (rNeighbor.children.has_value()) { @@ -394,17 +418,17 @@ SkTriGroupId subdivide( } } - bool neighborHasNonSubdivedNeighbor = false; - for (SkTriId const neighborNeighborId : rNeighbor.neighbors) - if ( neighborNeighborId.has_value() - && neighborNeighborId != sktriId - && ! rSkel.is_tri_subdivided(neighborNeighborId) ) + auto const neighbor_has_subdivided_neighbor = [sktriId, &rSkel] + (SkTriId const neighborNeighborId) { - neighborHasNonSubdivedNeighbor = true; - break; - } + return neighborNeighborId.has_value() + && neighborNeighborId != sktriId + && ! rSkel.is_tri_subdivided(neighborNeighborId); + }; - if (neighborHasNonSubdivedNeighbor) + if ( neighbor_has_subdivided_neighbor(rNeighbor.neighbors[0]) + || neighbor_has_subdivided_neighbor(rNeighbor.neighbors[1]) + || neighbor_has_subdivided_neighbor(rNeighbor.neighbors[2])) { rLvl.hasNonSubdivedNeighbor.insert(neighborId); } diff --git a/src/planet-a/skeleton_subdiv.h b/src/planet-a/skeleton_subdiv.h index 3f16bba1..2a034e2c 100644 --- a/src/planet-a/skeleton_subdiv.h +++ b/src/planet-a/skeleton_subdiv.h @@ -55,8 +55,8 @@ struct SkeletonSubdivScratchpad void resize(SubdivTriangleSkeleton &rSkel); - std::array distanceThresholdSubdiv{{}}; - std::array distanceThresholdUnsubdiv{{}}; + std::array distanceThresholdSubdiv{{}}; + std::array distanceThresholdUnsubdiv{{}}; std::array levels; @@ -82,6 +82,8 @@ struct SkeletonSubdivScratchpad OnUnsubdivideFunc_t onUnsubdiv {nullptr}; UserData_t onUnsubdivUserData {{nullptr, nullptr, nullptr, nullptr}}; + osp::Vector3l viewerPosition; + std::uint32_t distanceCheckCount{}; }; diff --git a/src/testapp/identifiers.h b/src/testapp/identifiers.h index ae572593..4eba0af3 100644 --- a/src/testapp/identifiers.h +++ b/src/testapp/identifiers.h @@ -314,6 +314,7 @@ struct PlTerrain { PipelineDef skeleton {"skeleton"}; PipelineDef surfaceChanges {"surfaceChanges"}; + PipelineDef chunkMesh {"chunkMesh"}; PipelineDef terrainFrame {"terrainFrame"}; }; @@ -426,21 +427,6 @@ struct PlCameraCtrl }; -#define TESTAPP_DATA_SHADER_VISUALIZER 1, \ - idDrawShVisual - - - -#define TESTAPP_DATA_SHADER_PHONG 1, \ - idDrawShPhong - - - -#define TESTAPP_DATA_SHADER_FLAT 1, \ - idDrawShFlat - - - #define TESTAPP_DATA_INDICATOR 1, \ idIndicator diff --git a/src/testapp/scenarios.cpp b/src/testapp/scenarios.cpp index 7797f433..1e4e132d 100644 --- a/src/testapp/scenarios.cpp +++ b/src/testapp/scenarios.cpp @@ -299,11 +299,11 @@ static ScenarioMap_t make_scenarios() return setup_renderer; }); - add_scenario("terrain", "Planet terrain mesh test", + add_scenario("terrain", "Planet terrain mesh test (Earth-sized planet)", [] (TestApp& rTestApp) -> RendererSetupFunc_t { #define SCENE_SESSIONS scene, commonScene, physics, physShapes, terrain, terrainIco, terrainSubdiv - #define RENDERER_SESSIONS sceneRenderer, magnumScene, cameraCtrl, cameraFree, shVisual, shFlat, shPhong, camThrow, shapeDraw, cursor, terrainDraw + #define RENDERER_SESSIONS sceneRenderer, magnumScene, cameraCtrl, shVisual, shFlat, shPhong, camThrow, shapeDraw, cursor, terrainDraw, terrainDrawGL using namespace testapp::scenes; @@ -319,18 +319,109 @@ static ScenarioMap_t make_scenarios() commonScene = setup_common_scene (builder, rTopData, scene, application, defaultPkg); physics = setup_physics (builder, rTopData, scene, commonScene); physShapes = setup_phys_shapes (builder, rTopData, scene, commonScene, physics, sc_matPhong); - terrain = setup_terrain (builder, rTopData, scene); + terrain = setup_terrain (builder, rTopData, scene, commonScene); terrainIco = setup_terrain_icosahedron (builder, rTopData, terrain); terrainSubdiv = setup_terrain_subdiv_dist (builder, rTopData, scene, terrain, terrainIco); + OSP_DECLARE_GET_DATA_IDS(terrain, TESTAPP_DATA_TERRAIN); + auto &rTerrain = top_get(rTopData, idTerrain); + auto &rTerrainFrame = top_get(rTopData, idTerrainFrame); + + constexpr std::uint64_t c_earthRadius = 6371000; + + initialize_ico_terrain(rTopData, terrain, terrainIco, { + .radius = double(c_earthRadius), + .height = 20000.0, // Height between Mariana Trench and Mount Everest + .skelPrecision = 10, // 2^10 units = 1024 units = 1 meter + .skelMaxSubdivLevels = 19, + .chunkSubdivLevels = 4 + }); + + // Set scene position relative to planet to be just on the surface + rTerrainFrame.position = Vector3l{0,0,c_earthRadius} * 1024; + + RendererSetupFunc_t const setup_renderer = [] (TestApp& rTestApp) -> void + { + auto const application = rTestApp.m_application; + auto const windowApp = rTestApp.m_windowApp; + auto const magnum = rTestApp.m_magnum; + auto const defaultPkg = rTestApp.m_defaultPkg; + auto & rTopData = rTestApp.m_topData; + + TopTaskBuilder builder{rTestApp.m_tasks, rTestApp.m_renderer.m_edges, rTestApp.m_taskData}; + + auto & [SCENE_SESSIONS] = unpack<7>(rTestApp.m_scene.m_sessions); + auto & [RENDERER_SESSIONS] = resize_then_unpack<11>(rTestApp.m_renderer.m_sessions); + + sceneRenderer = setup_scene_renderer (builder, rTopData, application, windowApp, commonScene); + create_materials(rTopData, sceneRenderer, sc_materialCount); + + magnumScene = setup_magnum_scene (builder, rTopData, application, windowApp, sceneRenderer, magnum, scene, commonScene); + cameraCtrl = setup_camera_ctrl (builder, rTopData, windowApp, sceneRenderer, magnumScene); + shVisual = setup_shader_visualizer (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matVisualizer); + shFlat = setup_shader_flat (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matFlat); + shPhong = setup_shader_phong (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matPhong); + shapeDraw = setup_phys_shapes_draw (builder, rTopData, windowApp, sceneRenderer, commonScene, physics, physShapes); + cursor = setup_cursor (builder, rTopData, application, sceneRenderer, cameraCtrl, commonScene, sc_matFlat, rTestApp.m_defaultPkg); + terrainDraw = setup_terrain_debug_draw (builder, rTopData, scene, sceneRenderer, cameraCtrl, commonScene, terrain, terrainIco, sc_matVisualizer); + terrainDrawGL = setup_terrain_draw_magnum (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, terrain); + + OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); + + auto &rCamCtrl = top_get(rTopData, idCamCtrl); + rCamCtrl.m_target = Vector3(0.0f, 0.0f, 0.0f); + rCamCtrl.m_orbitDistanceMin = 1.0f; + rCamCtrl.m_moveSpeed = 0.5f; + + setup_magnum_draw(rTestApp, scene, sceneRenderer, magnumScene); + }; + + #undef SCENE_SESSIONS + #undef RENDERER_SESSIONS + + return setup_renderer; + }); + + + add_scenario("terrain_small", "Planet terrain mesh test (100m radius planet)", + [] (TestApp& rTestApp) -> RendererSetupFunc_t + { + #define SCENE_SESSIONS scene, commonScene, physics, physShapes, terrain, terrainIco, terrainSubdiv + #define RENDERER_SESSIONS sceneRenderer, magnumScene, cameraCtrl, shVisual, shFlat, shPhong, camThrow, shapeDraw, cursor, terrainDraw, terrainDrawGL + + using namespace testapp::scenes; + + auto const defaultPkg = rTestApp.m_defaultPkg; + auto const application = rTestApp.m_application; + auto & rTopData = rTestApp.m_topData; + + TopTaskBuilder builder{rTestApp.m_tasks, rTestApp.m_scene.m_edges, rTestApp.m_taskData}; + + auto & [SCENE_SESSIONS] = resize_then_unpack<7>(rTestApp.m_scene.m_sessions); + + scene = setup_scene (builder, rTopData, application); + commonScene = setup_common_scene (builder, rTopData, scene, application, defaultPkg); + physics = setup_physics (builder, rTopData, scene, commonScene); + physShapes = setup_phys_shapes (builder, rTopData, scene, commonScene, physics, sc_matPhong); + terrain = setup_terrain (builder, rTopData, scene, commonScene); + terrainIco = setup_terrain_icosahedron (builder, rTopData, terrain); + terrainSubdiv = setup_terrain_subdiv_dist (builder, rTopData, scene, terrain, terrainIco); + + OSP_DECLARE_GET_DATA_IDS(terrain, TESTAPP_DATA_TERRAIN); + auto &rTerrain = top_get(rTopData, idTerrain); + auto &rTerrainFrame = top_get(rTopData, idTerrainFrame); + initialize_ico_terrain(rTopData, terrain, terrainIco, { - .radius = 50.0, - .height = 5.0, + .radius = 100.0, + .height = 2.0, .skelPrecision = 10, // 2^10 units = 1024 units = 1 meter - .skelMaxSubdivLevels = 7, + .skelMaxSubdivLevels = 5, .chunkSubdivLevels = 4 }); + // Position on surface + rTerrainFrame.position = Vector3l{0,0,100} * 1024; + RendererSetupFunc_t const setup_renderer = [] (TestApp& rTestApp) -> void { auto const application = rTestApp.m_application; @@ -349,18 +440,18 @@ static ScenarioMap_t make_scenarios() magnumScene = setup_magnum_scene (builder, rTopData, application, windowApp, sceneRenderer, magnum, scene, commonScene); cameraCtrl = setup_camera_ctrl (builder, rTopData, windowApp, sceneRenderer, magnumScene); - cameraFree = setup_camera_free (builder, rTopData, windowApp, scene, cameraCtrl); shVisual = setup_shader_visualizer (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matVisualizer); shFlat = setup_shader_flat (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matFlat); shPhong = setup_shader_phong (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, sc_matPhong); shapeDraw = setup_phys_shapes_draw (builder, rTopData, windowApp, sceneRenderer, commonScene, physics, physShapes); cursor = setup_cursor (builder, rTopData, application, sceneRenderer, cameraCtrl, commonScene, sc_matFlat, rTestApp.m_defaultPkg); - terrainDraw = setup_terrain_debug_draw (builder, rTopData, windowApp, sceneRenderer, cameraCtrl, commonScene, terrain, terrainIco, sc_matFlat); + terrainDraw = setup_terrain_debug_draw (builder, rTopData, scene, sceneRenderer, cameraCtrl, commonScene, terrain, terrainIco, sc_matVisualizer); + terrainDrawGL = setup_terrain_draw_magnum (builder, rTopData, windowApp, sceneRenderer, magnum, magnumScene, terrain); - OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); + OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); auto &rCamCtrl = top_get(rTopData, idCamCtrl); - rCamCtrl.m_target = Vector3(0.0f, 0.0f, 50.0f); + rCamCtrl.m_target = Vector3(0.0f, 0.0f, 0.0f); rCamCtrl.m_orbitDistanceMin = 1.0f; rCamCtrl.m_moveSpeed = 0.5f; diff --git a/src/testapp/sessions/magnum.cpp b/src/testapp/sessions/magnum.cpp index 01d1722f..61b5ad47 100644 --- a/src/testapp/sessions/magnum.cpp +++ b/src/testapp/sessions/magnum.cpp @@ -23,6 +23,7 @@ * SOFTWARE. */ #include "magnum.h" +#include "terrain.h" #include "common.h" #include "../MagnumApplication.h" @@ -42,6 +43,7 @@ #include +#include // for the 0xrrggbb_rgbf and angle literals using namespace Magnum::Math::Literals; @@ -139,7 +141,7 @@ Session setup_magnum_scene( auto &rCamera = top_emplace< Camera >(topData, idCamera); - rCamera.m_far = 1u << 24; + rCamera.m_far = 100000000.0f; rCamera.m_near = 1.0f; rCamera.m_fov = Magnum::Deg(45.0f); @@ -328,7 +330,7 @@ Session setup_shader_visualizer( auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); Session out; - OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SHADER_VISUALIZER) + auto const [idDrawShVisual] = out.acquire_data<1>(topData); auto &rDrawVisual = top_emplace< ACtxDrawMeshVisualizer >(topData, idDrawShVisual); @@ -399,7 +401,8 @@ Session setup_shader_flat( auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); Session out; - OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SHADER_FLAT) + auto const [idDrawShFlat] = out.acquire_data<1>(topData); + auto &rDrawFlat = top_emplace< ACtxDrawFlat >(topData, idDrawShFlat); rDrawFlat.shaderDiffuse = FlatGL3D{FlatGL3D::Configuration{}.setFlags(FlatGL3D::Flag::Textured)}; @@ -484,7 +487,7 @@ Session setup_shader_phong( auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); Session out; - OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_SHADER_PHONG) + auto const [idDrawShPhong] = out.acquire_data<1>(topData); auto &rDrawPhong = top_emplace< ACtxDrawPhong >(topData, idDrawShPhong); auto const texturedFlags = PhongGL::Flag::DiffuseTexture | PhongGL::Flag::AlphaMask | PhongGL::Flag::AmbientTexture; @@ -546,4 +549,133 @@ Session setup_shader_phong( } // setup_shader_phong + + +struct ACtxDrawTerrainGL +{ + Magnum::GL::Buffer vrtxBufGL{Corrade::NoCreate}; + Magnum::GL::Buffer indxBufGL{Corrade::NoCreate}; + MeshGlId terrainMeshGl; + bool enabled{false}; +}; + +Session setup_terrain_draw_magnum( + TopTaskBuilder &rBuilder, + ArrayView const topData, + Session const &windowApp, + Session const &sceneRenderer, + Session const &magnum, + Session const &magnumScene, + Session const &terrain) +{ + OSP_DECLARE_GET_DATA_IDS(sceneRenderer, TESTAPP_DATA_SCENE_RENDERER); + OSP_DECLARE_GET_DATA_IDS(magnumScene, TESTAPP_DATA_MAGNUM_SCENE); + OSP_DECLARE_GET_DATA_IDS(magnum, TESTAPP_DATA_MAGNUM); + OSP_DECLARE_GET_DATA_IDS(terrain, TESTAPP_DATA_TERRAIN); + auto const tgWin = windowApp .get_pipelines< PlWindowApp >(); + auto const tgScnRdr = sceneRenderer .get_pipelines< PlSceneRenderer >(); + auto const tgMgn = magnum .get_pipelines< PlMagnum >(); + auto const tgTrn = terrain .get_pipelines(); + + auto &rScnRender = top_get< ACtxSceneRender > (topData, idScnRender); + auto &rScnRenderGl = top_get< ACtxSceneRenderGL > (topData, idScnRenderGl); + auto &rRenderGl = top_get< RenderGL > (topData, idRenderGl); + + Session out; + auto const [idDrawTerrainGl] = out.acquire_data<1>(topData); + auto &rDrawTerrainGl = top_emplace< ACtxDrawTerrainGL >(topData, idDrawTerrainGl); + + rDrawTerrainGl.terrainMeshGl = rRenderGl.m_meshIds.create(); + rRenderGl.m_meshGl.emplace(rDrawTerrainGl.terrainMeshGl, Magnum::GL::Mesh{Corrade::NoCreate}); + + rBuilder.task() + .name ("Sync terrainMeshGl to entities with terrainMesh") + .run_on ({tgScnRdr.entMeshDirty(UseOrRun)}) + .sync_with ({tgScnRdr.mesh(Ready), tgScnRdr.entMesh(Ready), tgMgn.meshGL(Ready), tgMgn.entMeshGL(Modify), tgScnRdr.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawTerrainGl, idTerrain, idScnRender, idScnRenderGl, idRenderGl }) + .func([] (ACtxDrawTerrainGL& rDrawTerrainGl, ACtxTerrain& rTerrain, ACtxSceneRender& rScnRender, ACtxSceneRenderGL& rScnRenderGl, RenderGL& rRenderGl) noexcept + { + for (DrawEnt const drawEnt : rScnRender.m_meshDirty) + { + ACompMeshGl &rEntMeshGl = rScnRenderGl.m_meshId[drawEnt]; + MeshIdOwner_t const &entMeshScnId = rScnRender.m_mesh[drawEnt]; + + if (entMeshScnId == rTerrain.terrainMesh) + { + rScnRenderGl.m_meshId[drawEnt] = ACompMeshGl{ + .m_scnId = rTerrain.terrainMesh, + .m_glId = rDrawTerrainGl.terrainMeshGl }; + } + } + }); + + rBuilder.task() + .name ("Resync terrainMeshGl to entities with terrainMesh") + .run_on ({tgWin.resync(Run)}) + .sync_with ({tgScnRdr.mesh(Ready), tgMgn.meshGL(Ready), tgMgn.entMeshGL(Modify), tgScnRdr.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idDrawTerrainGl, idTerrain, idScnRender, idScnRenderGl, idRenderGl }) + .func([] (ACtxDrawTerrainGL& rDrawTerrainGl, ACtxTerrain& rTerrain, ACtxSceneRender& rScnRender, ACtxSceneRenderGL& rScnRenderGl, RenderGL& rRenderGl) noexcept + { + for (DrawEnt const drawEnt : rScnRender.m_drawIds) + { + MeshIdOwner_t const &entMeshScnId = rScnRender.m_mesh[drawEnt]; + + if (entMeshScnId == rTerrain.terrainMesh) + { + rScnRenderGl.m_meshId[drawEnt] = ACompMeshGl{ + .m_scnId = rTerrain.terrainMesh, + .m_glId = rDrawTerrainGl.terrainMeshGl }; + } + } + }); + + rBuilder.task() + .name ("Update terrain mesh GPU buffer data") + .run_on ({tgWin.sync(Run)}) + .sync_with ({tgTrn.chunkMesh(Ready)}) + .push_to (out.m_tasks) + .args ({ idScnRender, idGroupFwd, idScnRenderGl, idRenderGl, idDrawTerrainGl, idTerrain}) + .func([] (ACtxSceneRender& rScnRender, RenderGroup& rGroupFwd, ACtxSceneRenderGL const& rScnRenderGl, RenderGL& rRenderGl, ACtxDrawTerrainGL& rDrawTerrainGl, ACtxTerrain& rTerrain) noexcept + { + if ( ! rDrawTerrainGl.enabled ) + { + rDrawTerrainGl.enabled = true; + + rDrawTerrainGl.indxBufGL = Magnum::GL::Buffer{}; + rDrawTerrainGl.vrtxBufGL = Magnum::GL::Buffer{}; + + Magnum::GL::Mesh &rMesh = rRenderGl.m_meshGl.get(rDrawTerrainGl.terrainMeshGl); + + rMesh = Mesh{Magnum::GL::MeshPrimitive::Triangles}; + + auto const &posFormat = rTerrain.chunkGeom.vbufPositions; + auto const &nrmFormat = rTerrain.chunkGeom.vbufNormals; + + + rMesh.addVertexBuffer(rDrawTerrainGl.vrtxBufGL, GLintptr(posFormat.offset), GLsizei(posFormat.stride - sizeof(Vector3u)), Magnum::Shaders::GenericGL3D::Position{}) + .addVertexBuffer(rDrawTerrainGl.vrtxBufGL, GLintptr(nrmFormat.offset), GLsizei(nrmFormat.stride - sizeof(Vector3u)), Magnum::Shaders::GenericGL3D::Normal{}) + .setIndexBuffer(rDrawTerrainGl.indxBufGL, 0, Magnum::MeshIndexType::UnsignedInt) + .setCount(Magnum::Int(3*rTerrain.chunkInfo.faceTotal)); // 3 vertices in each triangle + } + + auto const indxBuffer = arrayCast(rTerrain.chunkGeom.indxBuffer); + auto const vrtxBuffer = arrayView(rTerrain.chunkGeom.vrtxBuffer); + + // There's faster ways to sync the buffer, but keeping it simple for now + + // see "Buffer re-specification" in + // https://www.khronos.org/opengl/wiki/Buffer_Object_Streaming + + rDrawTerrainGl.indxBufGL.setData({nullptr, indxBuffer.size()}); + rDrawTerrainGl.indxBufGL.setData(indxBuffer); + + rDrawTerrainGl.vrtxBufGL.setData({nullptr, vrtxBuffer.size()}); + rDrawTerrainGl.vrtxBufGL.setData(vrtxBuffer); + }); + + return out; +} // setup_terrain_draw_magnum + } // namespace testapp::scenes diff --git a/src/testapp/sessions/magnum.h b/src/testapp/sessions/magnum.h index 197cbfcd..f942da69 100644 --- a/src/testapp/sessions/magnum.h +++ b/src/testapp/sessions/magnum.h @@ -89,4 +89,13 @@ osp::Session setup_shader_phong( osp::Session const& magnumScene, osp::draw::MaterialId materialId = lgrn::id_null()); + +osp::Session setup_terrain_draw_magnum( + osp::TopTaskBuilder &rBuilder, + osp::ArrayView topData, + osp::Session const &windowApp, + osp::Session const &sceneRenderer, + osp::Session const &magnum, + osp::Session const &magnumScene, + osp::Session const &terrain); } diff --git a/src/testapp/sessions/newton.cpp b/src/testapp/sessions/newton.cpp index 3cc0ba9a..28185640 100644 --- a/src/testapp/sessions/newton.cpp +++ b/src/testapp/sessions/newton.cpp @@ -599,7 +599,7 @@ static void rocket_thrust_force(NewtonBody const* pBody, BodyId const body, ACtx auto &rBodyRockets = rRocketsNwt.m_bodyRockets[body]; - if (rBodyRockets.empty()) + if (rBodyRockets.size() == 0) { return; } diff --git a/src/testapp/sessions/terrain.cpp b/src/testapp/sessions/terrain.cpp index eea97774..e515ad54 100644 --- a/src/testapp/sessions/terrain.cpp +++ b/src/testapp/sessions/terrain.cpp @@ -40,8 +40,7 @@ #include #include -using adera::ACtxCameraController; - +using namespace adera; using namespace planeta; using namespace osp; using namespace osp::math; @@ -53,10 +52,14 @@ namespace testapp::scenes Session setup_terrain( TopTaskBuilder& rBuilder, ArrayView const topData, - Session const &scene) + Session const &scene, + Session const &commonScene) { + OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); auto const tgScn = scene.get_pipelines(); + auto &rDrawing = top_get(topData, idDrawing); + Session out; OSP_DECLARE_CREATE_DATA_IDS(out, topData, TESTAPP_DATA_TERRAIN); auto const tgTrn = out.create_pipelines(rBuilder); @@ -64,10 +67,13 @@ Session setup_terrain( rBuilder.pipeline(tgTrn.skeleton) .parent(tgScn.update); rBuilder.pipeline(tgTrn.surfaceChanges) .parent(tgScn.update); rBuilder.pipeline(tgTrn.terrainFrame) .parent(tgScn.update); + rBuilder.pipeline(tgTrn.chunkMesh) .parent(tgScn.update); auto &rTerrainFrame = top_emplace< ACtxTerrainFrame >(topData, idTerrainFrame); auto &rTerrain = top_emplace< ACtxTerrain > (topData, idTerrain); + rTerrain.terrainMesh = rDrawing.m_meshRefCounts.ref_add(rDrawing.m_meshIds.create()); + rBuilder.task() .name ("Clear surfaceAdded & surfaceRemoved once we're done with it") .run_on ({tgTrn.surfaceChanges(Clear)}) @@ -83,8 +89,8 @@ Session setup_terrain( .name ("Clean up terrain-related IdOwners") .run_on ({tgScn.cleanup(Run_)}) .push_to (out.m_tasks) - .args ({ idTerrain }) - .func([] (ACtxTerrain &rTerrain) noexcept + .args ({ idTerrain, idDrawing}) + .func([] (ACtxTerrain &rTerrain, ACtxDrawing &rDrawing) noexcept { // rTerrain.skChunks has owners referring to rTerrain.skeleton. A reference to // rTerrain.skeleton can't be obtained during destruction, we must clear it separately. @@ -92,6 +98,8 @@ Session setup_terrain( // rTerrain.skeleton will clean itself up in its destructor, since it only holds owners // referring to itself. + + rDrawing.m_meshRefCounts.ref_release(std::move(rTerrain.terrainMesh)); }); return out; @@ -141,6 +149,8 @@ Session setup_terrain_subdiv_dist( BasicChunkMeshGeometry &rChGeo = rTerrain.chunkGeom; SkeletonSubdivScratchpad &rSkSP = rTerrain.scratchpad; + Vector3l const& viewerPos = rTerrain.scratchpad.viewerPosition; + // ## Unsubdivide triangles that are too far away // Unsubdivide is performed first, since it's better to remove stuff before adding new @@ -152,7 +162,7 @@ Session setup_terrain_subdiv_dist( for (int level = rSkel.levelMax-1; level >= 0; --level) { // Select and deselect only modifies rSkSP - unsubdivide_select_by_distance(level, rTerrainFrame.position, rSkel, rSkData, rSkSP); + unsubdivide_select_by_distance(level, viewerPos, rSkel, rSkData, rSkSP); unsubdivide_deselect_invariant_violations(level, rSkel, rSkData, rSkSP); // Perform changes on skeleton, delete selected triangles @@ -178,7 +188,7 @@ Session setup_terrain_subdiv_dist( // Do the subdivide for real for (int level = 0; level < rSkel.levelMax; ++level) { - subdivide_level_by_distance(rTerrainFrame.position, level, rSkel, rSkData, rSkSP); + subdivide_level_by_distance(viewerPos, level, rSkel, rSkData, rSkSP); } rSkSP.distanceTestDone.clear(); @@ -189,7 +199,7 @@ Session setup_terrain_subdiv_dist( rBuilder.task() .name ("Update Terrain Chunks") .run_on ({tgScn.update(Run)}) - .sync_with ({tgTrn.terrainFrame(Ready), tgTrn.skeleton(New), tgTrn.surfaceChanges(UseOrRun)}) + .sync_with ({tgTrn.terrainFrame(Ready), tgTrn.skeleton(Ready), tgTrn.surfaceChanges(UseOrRun), tgTrn.chunkMesh(Modify)}) .push_to (out.m_tasks) .args({ idTerrainFrame, idTerrain, idTerrainIco }) .func([] (ACtxTerrainFrame &rTerrainFrame, ACtxTerrain &rTerrain, ACtxTerrainIco &rTerrainIco) noexcept @@ -208,6 +218,7 @@ Session setup_terrain_subdiv_dist( SkeletonSubdivScratchpad &rSkSP = rTerrain.scratchpad; rChSP.chunksAdded .clear(); + rChSP.chunksRemoved .clear(); rChSP.sharedNormalsDirty.clear(); rChSP.sharedAdded .clear(); rChSP.sharedRemoved .clear(); @@ -216,10 +227,12 @@ Session setup_terrain_subdiv_dist( for (SkTriId const sktriId : rSkSP.surfaceRemoved) { ChunkId const chunkId = rSkCh.m_triToChunk[sktriId]; - - subtract_normal_contrib(chunkId, false, rChGeo, rChInfo, rChSP, rSkCh); - - rSkCh.chunk_remove(chunkId, sktriId, rChSP.sharedRemoved, rSkel); + if (chunkId.has_value()) + { + subtract_normal_contrib(chunkId, false, rChGeo, rChInfo, rChSP, rSkCh); + rSkCh.chunk_remove(chunkId, sktriId, rChSP.sharedRemoved, rSkel); + rChSP.chunksRemoved.insert(chunkId); + } } auto const chLevel = rSkCh.m_chunkSubdivLevel; @@ -232,16 +245,17 @@ Session setup_terrain_subdiv_dist( auto const &corners = rSkel.tri_at(sktriId).vertices; ArrayView< MaybeNewId > const edgeVrtxView = rChSP.edgeVertices; - ArrayView< MaybeNewId > const edgeLft = edgeVrtxView.sliceSize(edgeSize * 0, edgeSize); - ArrayView< MaybeNewId > const edgeBtm = edgeVrtxView.sliceSize(edgeSize * 1, edgeSize); - ArrayView< MaybeNewId > const edgeRte = edgeVrtxView.sliceSize(edgeSize * 2, edgeSize); + ArrayView< MaybeNewId > const edgeLft = edgeVrtxView.sliceSize(edgeSize * 0ul, edgeSize); + ArrayView< MaybeNewId > const edgeBtm = edgeVrtxView.sliceSize(edgeSize * 1ul, edgeSize); + ArrayView< MaybeNewId > const edgeRte = edgeVrtxView.sliceSize(edgeSize * 2ul, edgeSize); rSkel.vrtx_create_chunk_edge_recurse(chLevel, corners[0], corners[1], edgeLft); rSkel.vrtx_create_chunk_edge_recurse(chLevel, corners[1], corners[2], edgeBtm); rSkel.vrtx_create_chunk_edge_recurse(chLevel, corners[2], corners[0], edgeRte); ChunkId const chunkId = rSkCh.chunk_create(sktriId, rSkel, rChSP.sharedAdded, edgeLft, edgeBtm, edgeRte); - rChSP.chunksAdded.push_back(chunkId); + + rChSP.chunksAdded.insert(chunkId); // chunk_create creates new Skeleton Vertices. Resize is needed after each call rSkData.resize(rSkel); @@ -253,56 +267,135 @@ Session setup_terrain_subdiv_dist( ico_calc_chunk_edge(rTerrainIco.radius, chLevel, corners[2], corners[0], edgeRte, rSkData); } - for (SkTriId const sktriId : rSkSP.surfaceAdded) + for (ChunkId const chunkId : rChSP.chunksAdded) { - auto const chunkId = rSkCh.m_triToChunk[sktriId]; - restitch_check(chunkId, sktriId, rSkCh, rSkel, rSkData, rChSP); + restitch_check(chunkId, rSkCh.m_chunkToTri[chunkId], rSkCh, rSkel, rSkData, rChSP); } - // Calculate positions for newly added shared vertex - float const scalepow = std::pow(2.0f, -rSkData.precision); - for (SharedVrtxId const sharedVrtxId : rChSP.sharedAdded) + float const scale = std::exp2(float(-rSkData.precision)); + + auto const vbufPosView = rChGeo.vbufPositions.view(rChGeo.vrtxBuffer, rChInfo.vrtxTotal); + auto const vbufNrmView = rChGeo.vbufNormals .view(rChGeo.vrtxBuffer, rChInfo.vrtxTotal); + + // TODO: temporary code of course + auto const heightmap = [scale, h = rTerrainIco.height] (Vector3l posl) -> float { - SkVrtxId const skelVrtx = rSkCh.m_sharedToSkVrtx[sharedVrtxId]; + return h * std::clamp( 0.1*(0.5 - 0.5*std::cos(0.000050*posl.x()*scale*2.0*3.14159)) + + 0.9*(0.5 - 0.5*std::cos(0.000005*posl.y()*scale*2.0*3.14159)) , 0.0, 1.0 ); + }; - // Normal is not cleaned up by the previous user. Normal is initially set to zero, and - // face normals added in update_faces(...) will accumulate here. - rChGeo.sharedNormalSum[sharedVrtxId] = Vector3{ZeroInit}; + auto const update_shared_vrtx_position + = [&vbufPosView, &heightmap, scale, &rSkCh, &rChInfo, &rSkData, &rChGeo, &rTerrainIco] + (SharedVrtxId const sharedVrtxId) + { + SkVrtxId const skelVrtx = rSkCh.m_sharedToSkVrtx[sharedVrtxId]; + VertexIdx const vbufVertex = rChInfo.vbufSharedOffset + sharedVrtxId.value; + Vector3l const skPos = rSkData.positions[skelVrtx]; + Vector3 const posOut = Vector3{skPos - rChGeo.originSkelPos} * scale; + Vector3 const radialDir = Vector3{Vector3d(skPos) * scale / rTerrainIco.radius}; + + rChGeo.sharedPosNoHeightmap[sharedVrtxId] = posOut; + vbufPosView[vbufVertex] = posOut + radialDir * heightmap(skPos); + }; + + // TODO: Limit rChGeo.originSkelPos to always be near the surface. There isn't a point in + // translating the mesh when moving away from the terrain. + // Also add a threshold to only translate if the two positions diverge too far. Vary + // the threshold by the maximum present subdivision level, so less translations are + // needed when moving across low-detail terrain. + + if (rChGeo.originSkelPos == rTerrainFrame.position) + { + // Copy offsetted positions from the skeleton for newly added shared vertices - //Vector3l const translated = positions[size_t(skelId)] + translaton; - Vector3d const scaled = Vector3d(rSkData.positions[skelVrtx]) * scalepow; - VertexIdx const vertex = rChInfo.vbufSharedOffset + sharedVrtxId.value; + for (SharedVrtxId const sharedVrtxId : rChSP.sharedAdded) + { + update_shared_vrtx_position(sharedVrtxId); + } + } + else + { + // The scene position relative to planet origin has changed. + OSP_LOG_INFO("Translating Terrain Mesh"); + + Vector3l const deltaOffset = rChGeo.originSkelPos - rTerrainFrame.position; + Vector3 const deltaOffsetF = Vector3(deltaOffset) * scale; + rChGeo.originSkelPos = rTerrainFrame.position; + + // Refresh all shared vertex positions + for (SharedVrtxId const sharedVrtxId : rSkCh.m_sharedIds) + { + update_shared_vrtx_position(sharedVrtxId); + } - // Heightmap goes here (1) - rChGeo.chunkVbufPos[vertex] = Vector3(scaled); + // Translate all existing chunk fill vertices + for (ChunkId const chunkId : rSkCh.m_chunkIds) + { + if (rChSP.chunksAdded.contains(chunkId)) + { + continue; // Not added yet, no need to translate + } + + std::size_t const fillOffset = rChInfo.vbufFillOffset + chunkId.value*rChInfo.fillVrtxCount; + for (Vector3 &rPos : vbufPosView.sliceSize(fillOffset, rChInfo.fillVrtxCount)) + { + rPos += deltaOffsetF; + } + } } - // Calculate fill vertex positions - for (SkTriId const sktriId : rSkSP.surfaceAdded) - { - auto const chunk = rSkCh.m_triToChunk[sktriId]; - auto const chunkIdInt = std::size_t(chunk); - std::size_t const fillOffset = rChInfo.vbufFillOffset + chunkIdInt*rChInfo.fillVrtxCount; + Vector3d const center = -Vector3d(rChGeo.originSkelPos) * scale; - osp::ArrayView sharedUsed = rSkCh.shared_vertices_used(chunk); + // Calculate new fill vertex positions + for (ChunkId const chunkId : rChSP.chunksAdded) + { + std::size_t const fillOffset = rChInfo.vbufFillOffset + chunkId.value*rChInfo.fillVrtxCount; + osp::ArrayView sharedUsed = rSkCh.shared_vertices_used(chunkId); + // Use ChunkFillSubdivLUT to generate a spherically curved triangle fill through + // building up and subdividing pairs of vertices. Don't apply heightmap yet, as this + // will interfere with middle position and curvature calculations. for (ChunkFillSubdivLUT::ToSubdiv const& toSubdiv : rChSP.lut.data()) { - std::size_t const vrtxA = rChSP.lut.index(sharedUsed, fillOffset, rChInfo.vbufSharedOffset, toSubdiv.m_vrtxA); - std::size_t const vrtxB = rChSP.lut.index(sharedUsed, fillOffset, rChInfo.vbufSharedOffset, toSubdiv.m_vrtxB); - std::size_t const vrtxC = rChInfo.vbufFillOffset + rChInfo.fillVrtxCount*chunkIdInt + toSubdiv.m_fillOut.value; + Vector3 const vrtxAPos = toSubdiv.aIsShared + ? rChGeo.sharedPosNoHeightmap[sharedUsed[toSubdiv.vrtxA]] + : vbufPosView[fillOffset + toSubdiv.vrtxA]; + Vector3 const vrtxBPos = toSubdiv.bIsShared + ? rChGeo.sharedPosNoHeightmap[sharedUsed[toSubdiv.vrtxB]] + : vbufPosView[fillOffset + toSubdiv.vrtxB]; + + Vector3d const middle = 0.5*( Vector3d(vrtxAPos) + Vector3d(vrtxBPos) ); + Vector3d const centerDiff = Vector3d(middle) - center; + double const centerDist = centerDiff.length(); + Vector3d const radialDir = centerDiff / centerDist; + double const roundness = rTerrainIco.radius - centerDiff.length(); + Vector3d const posOut = middle + radialDir * roundness; + + vbufPosView[fillOffset + toSubdiv.fillOut] = Vector3(posOut); + } - Vector3 &rPosC = rChGeo.chunkVbufPos[vrtxC]; + // Apply heightmap afterwards + for (Vector3 &rPos : vbufPosView.sliceSize(fillOffset, rChInfo.fillVrtxCount)) + { + Vector3d const centerDiff = Vector3d(rPos) - center; + double const centerDist = centerDiff.length(); + Vector3 const radialDir = Vector3{centerDiff / centerDist}; - // Heightmap goes here (2) - Vector3 const avg = (rChGeo.chunkVbufPos[vrtxA] + rChGeo.chunkVbufPos[vrtxB]) / 2.0f; - float const avgLen = avg.length(); - float const roundness = rTerrainIco.radius - avgLen; + Vector3l const bigpos = Vector3l(rPos / scale) + rChGeo.originSkelPos; - rPosC = avg + (avg / avgLen) * roundness; + rPos += radialDir * heightmap(bigpos); } } + // Normal is not cleaned up by the previous user; Initially set them to zero. + // Face normals added in update_faces(...) will accumulate here. + for (SharedVrtxId const sharedVrtxId : rChSP.sharedAdded) + { + rChGeo.sharedNormalSum[sharedVrtxId] = Vector3{ZeroInit}; + } + + // Update Index buffer + // Add or remove faces according to chunk changes. This also calculates normals. // Vertex normals are calculated from a weighted sum of face normals of connected faces. // For shared vertices, we add or subtract face normals from rChGeo.sharedNormalSum. @@ -315,23 +408,49 @@ Session setup_terrain_subdiv_dist( } std::fill(rChSP.stitchCmds.begin(), rChSP.stitchCmds.end(), ChunkStitch{}); + // Fill unused parts of the index buffer with zeros. This includes chunks that are deleted + // but not (yet) reused. + auto const ibuf2d = as_2d(rChGeo.indxBuffer, rChInfo.chunkMaxFaceCount); + for (ChunkId const chunkId : rChSP.chunksRemoved) + { + if ( ! rSkCh.m_chunkIds.exists(chunkId) ) + { + auto const indicesView = ibuf2d.row(chunkId.value); + std::fill(indicesView.begin(), indicesView.end(), Vector3u{0, 0, 0}); + } + } + // Update vertex buffer normals of shared vertices, as rChGeo.sharedNormalSum was modified. for (SharedVrtxId const sharedId : rChSP.sharedNormalsDirty) { Vector3 const normalSum = rChGeo.sharedNormalSum[sharedId]; - rChGeo.chunkVbufNrm[rChInfo.vbufSharedOffset + sharedId.value] = normalSum.normalized(); + vbufNrmView[rChInfo.vbufSharedOffset + sharedId.value] = normalSum.normalized(); } // Uncomment these if some new change breaks something //debug_check_invariants(rChGeo, rChInfo, rSkCh); - // TODO: temporary, write debug obj file every ~10 seconds - static int fish = 0; + + static unsigned int fish = 1; ++fish; - if (fish == 60*10) + + // TODO: temporary, write statistics about every second + if (fish % 60 == 0) { - fish = 0; + OSP_LOG_INFO("Terrain stats: \n" + "* Skeleton Triangles: {}\n" + "* Skeleton Vertices: {}\n" + "* Chunks: {}/{}\n" + "* Shared Vertices: {}/{}\n", + rSkel.tri_group_ids().size()*4, rSkel.vrtx_ids().size(), + rSkCh.m_chunkIds.size(), rSkCh.m_chunkIds.capacity(), + rSkCh.m_sharedIds.size(), rSkCh.m_sharedIds.capacity()); + } + /* + // TODO: temporary, write debug obj file every ~10 seconds + if (fish % (60*10) == 0) + { auto const time = std::chrono::system_clock::now().time_since_epoch().count(); std::string const filename = fmt::format("planetdebug_{}.obj", time); @@ -346,6 +465,7 @@ Session setup_terrain_subdiv_dist( objfile.open(filename); write_obj(objfile, rTerrain.chunkGeom, rChInfo, rSkCh); } + */ }); return out; @@ -384,7 +504,7 @@ void initialize_ico_terrain( rTerrain.skData.resize(rTerrain.skeleton); - double const scale = std::pow(2.0, rTerrain.skData.precision); + double const scale = std::exp2(double(rTerrain.skData.precision)); double const maxRadius = rTerrainIco.radius + rTerrainIco.height; for (SkTriGroupId const groupId : rTerrainIco.icoGroups) @@ -439,16 +559,16 @@ void initialize_ico_terrain( for (int level = 0; level < gc_maxSubdivLevels; ++level) { // Good-enough bounding sphere is ~75% of the edge length (determined using Blender) - float const edgeLength = gc_icoMaxEdgeVsLevel[level] * rTerrainIco.radius * scale; - float const subdivRadius = 0.75f * edgeLength; + double const edgeLength = gc_icoMaxEdgeVsLevel[level] * rTerrainIco.radius * scale; + double const subdivRadius = 0.75 * edgeLength; // TODO: Pick thresholds based on the angular diameter (size on screen) of the // chunk triangle mesh that will actually be rendered. - rSP.distanceThresholdSubdiv[level] = std::uint64_t(subdivRadius); + rSP.distanceThresholdSubdiv[level] = subdivRadius; // Unsubdivide thresholds should be slightly larger (arbitrary x2) to avoid rapid // terrain changes when moving back and forth quickly - rSP.distanceThresholdUnsubdiv[level] = std::uint64_t(2.0f * subdivRadius); + rSP.distanceThresholdUnsubdiv[level] = 2.0f * subdivRadius; } // ## Prepare Chunk Skeleton @@ -463,7 +583,7 @@ void initialize_ico_terrain( // Approximate max number of shared vertices. Determined experimentally, roughly 60% of all // vertices end up being shared. Margin is inherited from maxChunksApprox. std::uint32_t const maxVrtxApprox = maxChunksApprox * rTerrain.skChunks.m_chunkSharedCount; - std::uint32_t const maxSharedVrtxApprox = 0.6f * maxVrtxApprox; + std::uint32_t const maxSharedVrtxApprox = std::uint32_t(0.6f * float(maxVrtxApprox)); rTerrain.skChunks.chunk_reserve(std::uint16_t(maxChunksApprox)); rTerrain.skChunks.shared_reserve(maxSharedVrtxApprox); @@ -491,9 +611,8 @@ void initialize_ico_terrain( rTerrain.skChunks.m_chunkSharedCount, rTerrain.chunkInfo.chunkMaxFaceCount, rTerrain.skChunks.m_sharedIds.capacity(), - fmt::group_digits(rTerrain.chunkGeom.chunkIbuf.size() * sizeof(Vector3u)), - fmt::group_digits( rTerrain.chunkGeom.chunkVbufNrm.size() * sizeof(Vector3) - + rTerrain.chunkGeom.chunkVbufPos.size() * sizeof(Vector3))); + fmt::group_digits(rTerrain.chunkGeom.vrtxBuffer.size()), + fmt::group_digits(rTerrain.chunkGeom.indxBuffer.size() * sizeof(Vector3u))); } @@ -502,12 +621,13 @@ struct TerrainDebugDraw { KeyedVec verts; MaterialId mat; + DrawEnt surface; }; Session setup_terrain_debug_draw( TopTaskBuilder& rBuilder, ArrayView const topData, - Session const &windowApp, + Session const &scene, Session const &sceneRenderer, Session const &cameraCtrl, Session const &commonScene, @@ -515,13 +635,14 @@ Session setup_terrain_debug_draw( Session const &terrainIco, MaterialId const mat) { + OSP_DECLARE_GET_DATA_IDS(scene, TESTAPP_DATA_SCENE); OSP_DECLARE_GET_DATA_IDS(commonScene, TESTAPP_DATA_COMMON_SCENE); OSP_DECLARE_GET_DATA_IDS(sceneRenderer, TESTAPP_DATA_SCENE_RENDERER); OSP_DECLARE_GET_DATA_IDS(cameraCtrl, TESTAPP_DATA_CAMERA_CTRL); OSP_DECLARE_GET_DATA_IDS(terrain, TESTAPP_DATA_TERRAIN); OSP_DECLARE_GET_DATA_IDS(terrainIco, TESTAPP_DATA_TERRAIN_ICO); - auto const tgWin = windowApp .get_pipelines(); + auto const tgScn = scene .get_pipelines(); auto const tgScnRdr = sceneRenderer .get_pipelines(); auto const tgCmCt = cameraCtrl .get_pipelines(); auto const tgTrn = terrain .get_pipelines(); @@ -532,30 +653,116 @@ Session setup_terrain_debug_draw( auto &rTrnDbgDraw = top_emplace< TerrainDebugDraw > (topData, idTrnDbgDraw, TerrainDebugDraw{.mat = mat}); + auto &rDrawing = top_get< ACtxDrawing > (topData, idDrawing); + auto &rScnRender = top_get< ACtxSceneRender > (topData, idScnRender); + auto &rTerrain = top_get< ACtxTerrain > (topData, idTerrain); + + rTrnDbgDraw.surface = rScnRender.m_drawIds.create(); + rScnRender.resize_draw(); + + rScnRender.m_visible.insert(rTrnDbgDraw.surface); + rScnRender.m_opaque .insert(rTrnDbgDraw.surface); + rScnRender.m_materials[mat].m_ents.insert(rTrnDbgDraw.surface); + rScnRender.m_materials[mat].m_dirty.push_back(rTrnDbgDraw.surface); + rScnRender.m_mesh[rTrnDbgDraw.surface] = rDrawing.m_meshRefCounts.ref_add(rTerrain.terrainMesh); + rBuilder.task() - .name ("Position SceneFrame center to Camera Controller target") - .run_on ({tgWin.inputs(Run)}) - .sync_with ({tgCmCt.camCtrl(Ready), tgTrn.terrainFrame(Modify)}) + .name ("Handle Scene<-->Terrain positioning and floating origin") + .run_on ({tgScn.update(Run)}) + .sync_with ({tgCmCt.camCtrl(Modify), tgTrn.terrainFrame(Modify)}) .push_to (out.m_tasks) - .args ({ idCamCtrl, idTerrainFrame, idTerrain, idTerrainIco }) - .func([] (ACtxCameraController& rCamCtrl, ACtxTerrainFrame &rTerrainFrame, ACtxTerrain &rTerrain, ACtxTerrainIco &rTerrainIco) noexcept + .args ({ idCamCtrl, idDeltaTimeIn, idTerrainFrame, idTerrain, idTerrainIco }) + .func([] (ACtxCameraController& rCamCtrl, float const deltaTimeIn, ACtxTerrainFrame &rTerrainFrame, ACtxTerrain &rTerrain, ACtxTerrainIco &rTerrainIco) noexcept { - if ( ! rCamCtrl.m_target.has_value()) + using Magnum::Math::abs; + using Magnum::Math::cross; + using Magnum::Math::dot; + using Magnum::Math::floor; + using Magnum::Math::sign; + using Magnum::Math::sqrt; + + if ( ! rCamCtrl.m_target.has_value() ) { return; } - Vector3 camPos = rCamCtrl.m_target.value(); - float const len = camPos.length(); - float const midHeightRadius = rTerrainIco.radius + 0.5f*rTerrainIco.height; - if (len < midHeightRadius) + // Camera translation with controls + SysCameraController::update_move(rCamCtrl, deltaTimeIn, true); + + int const scale = int_2pow(rTerrain.skData.precision); + + Vector3 &rCamPos = rCamCtrl.m_target.value(); + + constexpr float maxDist = 65565.0f; + + // Do a floating origin translation if required + + // Determine if x, y, or z in rCamPos goes further than maxDist, and by how much. + // Round up/down towards zero to the closest multiple of maxDist. + // Zero if rCamPos is (maxDist) meters away. + Vector3 const translateOrigin = sign(rCamPos) * floor(abs(rCamPos) / maxDist) * maxDist; + if ( ! translateOrigin.isZero() ) { - camPos *= midHeightRadius / len; + // Origin translation involves translating everything in the scene, but in this case + // it's just the camera. Terrain will respond accordingly to changes in rTerrainFrame. + rCamPos -= translateOrigin; + + // Scene has moved relative to terrain + rTerrainFrame.position += Vector3l{translateOrigin} * scale; } - rTerrainFrame.position = Vector3l(camPos * int_2pow(rTerrain.skData.precision)); + // Set position of camera target relative to terrain, used for LOD distance checking + rTerrain.scratchpad.viewerPosition = rTerrainFrame.position + Vector3l(rCamPos * float(scale)); + + Vector3d const viewerPosD = Vector3d{rTerrain.scratchpad.viewerPosition}; + double const distanceToCenter = viewerPosD.length(); + double const minDistanceToCenter = (rTerrainIco.radius + rTerrainIco.height) * scale; + + // Enforce minimum distance to center for viewerPosition. This makes it so that moving the + // camera below the surface will still show the highest level of detail. + if (distanceToCenter < minDistanceToCenter) + { + rTerrain.scratchpad.viewerPosition *= minDistanceToCenter / distanceToCenter; + } + + // Set camera controller's 'up' direction to gravity direction + + Vector3 const upOld = rCamCtrl.m_up; + Vector3 const upNew = Vector3{viewerPosD / distanceToCenter}; + rCamCtrl.m_up = upNew; + + // A bit hacky: Rotate around the target to account for change in 'up' to prevent weird + // behaviour with fast (zoomed-out) camera movement. + // return; // Hard to explain, uncomment this return to see what I mean :> + + // Rotation required to rotate upOld into upNew + float const w = sqrt(upNew.dot() * upNew.dot()) + dot(upOld, upNew); + auto const rotation = Quaternion{cross(upOld, upNew), w}.normalized(); + + Vector3 const pivot = rCamCtrl.m_target.value(); + rCamCtrl.m_transform.translation() -= pivot; + rCamCtrl.m_transform = Matrix4{rotation.toMatrix()} * rCamCtrl.m_transform; + rCamCtrl.m_transform.translation() += pivot; + + SysCameraController::update_view(rCamCtrl, deltaTimeIn); + }); + + rBuilder.task() + .name ("Reposition terrain surface mesh") + .run_on ({tgScnRdr.render(Run)}) + .sync_with ({tgTrn.terrainFrame(Ready), tgTrn.chunkMesh(Ready), tgScnRdr.drawEnt(Ready), tgScnRdr.drawTransforms(Modify_), tgScnRdr.drawEntResized(Done)}) + .push_to (out.m_tasks) + .args ({ idTrnDbgDraw, idTerrainFrame, idTerrain, idScnRender }) + .func([] (TerrainDebugDraw& rTrnDbgDraw, ACtxTerrainFrame &rTerrainFrame, ACtxTerrain &rTerrain, ACtxSceneRender &rScnRender) noexcept + { + float const scale = std::exp2(float(rTerrain.skData.precision)); + + Vector3 const pos = Vector3(rTerrain.chunkGeom.originSkelPos-rTerrainFrame.position) / scale; + + rScnRender.m_drawTransform[rTrnDbgDraw.surface] = Matrix4::translation(pos); }); +#if 0 // Setup skeleton vertex visualizer rBuilder.task() @@ -640,6 +847,7 @@ Session setup_terrain_debug_draw( * Matrix4::scaling({0.05f, 0.05f, 0.05f}); } }); +#endif return out; } diff --git a/src/testapp/sessions/terrain.h b/src/testapp/sessions/terrain.h index 13a61fde..e4e2a023 100644 --- a/src/testapp/sessions/terrain.h +++ b/src/testapp/sessions/terrain.h @@ -27,8 +27,8 @@ #include "../scenarios.h" #include "planet-a/chunk_utils.h" +#include #include -#include #include #include @@ -38,13 +38,14 @@ namespace testapp::scenes { /** - * @brief Scene orientation relative to planet center + * @brief Scene orientation relative to terrain * * This is intended to be modified by a system responsible for handling floating origin and * the Scene's position within a universe. */ struct ACtxTerrainFrame { + /// Position of scene's (0, 0, 0) origin point from the terrain's frame of reference. osp::Vector3l position; osp::Quaterniond rotation; bool active {false}; @@ -63,14 +64,16 @@ struct ACtxTerrain planeta::ChunkScratchpad chunkSP; planeta::SkeletonSubdivScratchpad scratchpad; + + osp::draw::MeshIdOwner_t terrainMesh; }; struct ACtxTerrainIco { - /// Planet lowest ground level in meters + /// Planet lowest ground level in meters. Lowest valley. double radius{}; - /// Planet max ground height in meters + /// Planet max ground height in meters. Highest mountain. double height{}; std::array icoVrtx; @@ -85,7 +88,8 @@ struct ACtxTerrainIco osp::Session setup_terrain( osp::TopTaskBuilder &rBuilder, osp::ArrayView topData, - osp::Session const &scene); + osp::Session const &scene, + osp::Session const &commonScene); /** * @brief Icosahedron-specific data for spherical planet terrains @@ -117,7 +121,7 @@ struct TerrainTestPlanetSpecs /// Skeleton Vector3l precision (2^precision units = 1 meter) int skelPrecision {}; - /// Skeleton max subdivision levels. 0 for no subdivision. Max is 9. + /// Skeleton max subdivision levels. 0 for no subdivision. Max is 23. std::uint8_t skelMaxSubdivLevels {}; /// Number of times an initial triangle is subdivided to form a chunk. @@ -133,7 +137,7 @@ void initialize_ico_terrain( osp::ArrayView topData, osp::Session const &terrain, osp::Session const &terrainIco, - TerrainTestPlanetSpecs params); + TerrainTestPlanetSpecs specs); /** @@ -142,7 +146,7 @@ void initialize_ico_terrain( osp::Session setup_terrain_debug_draw( osp::TopTaskBuilder& rBuilder, osp::ArrayView topData, - osp::Session const &windowApp, + osp::Session const &scene, osp::Session const &sceneRenderer, osp::Session const &cameraCtrl, osp::Session const &commonScene,