From 7aa33e215b5e3aea60f53638a0ba39798303e371 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Mon, 2 Feb 2026 17:43:19 -0800 Subject: [PATCH 1/9] BitMatrix bit safe, first parallel implementation --- src/vistree.cpp | 118 +++++++++++++++++++++++++++++++++++++++--------- src/vistree.h | 14 ++++-- 2 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/vistree.cpp b/src/vistree.cpp index d5891e6..b74aabf 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -5,33 +5,89 @@ #include #include +BitMatrix::BitMatrix(const BitMatrix& other) +{ + std::lock_guard lock(other.m_mutex); + m_width = other.m_width; + m_height = other.m_height; + m_data = other.m_data; +} + +BitMatrix& BitMatrix::operator=(const BitMatrix& other) +{ + if (this == &other) { return *this; } + std::scoped_lock lock(m_mutex, other.m_mutex); + m_width = other.m_width; + m_height = other.m_height; + m_data = other.m_data; + return *this; +} + +BitMatrix::BitMatrix(BitMatrix&& other) noexcept +{ + std::lock_guard lock(other.m_mutex); + m_width = other.m_width; + m_height = other.m_height; + m_data = std::move(other.m_data); + other.m_width = 0; + other.m_height = 0; +} + +BitMatrix& BitMatrix::operator=(BitMatrix&& other) noexcept +{ + if (this == &other) { return *this; } + std::scoped_lock lock(m_mutex, other.m_mutex); + m_width = other.m_width; + m_height = other.m_height; + m_data = std::move(other.m_data); + other.m_width = 0; + other.m_height = 0; + return *this; +} + bool BitMatrix::Get(size_t x, size_t y) const { - return m_data[(y * m_width) + x]; + std::lock_guard lock(m_mutex); + const size_t index = (y * m_width) + x; + return m_data[index] != 0; } size_t BitMatrix::GetWidth() const { + std::lock_guard lock(m_mutex); return m_width; } size_t BitMatrix::GetHeight() const { + std::lock_guard lock(m_mutex); return m_height; } void BitMatrix::Set(bool value, size_t x, size_t y) { - m_data[(y * m_width) + x] = value; + std::lock_guard lock(m_mutex); + const size_t index = (y * m_width) + x; + m_data[index] = value ? 1 : 0; +} + +void BitMatrix::SetRow(const std::vector& rowData, size_t y) +{ + std::lock_guard lock(m_mutex); + if (rowData.size() != m_width) { return; } + const size_t rowStart = y * m_width; + std::copy(rowData.begin(), rowData.end(), m_data.begin() + rowStart); } bool BitMatrix::IsEmpty() const { + std::lock_guard lock(m_mutex); return m_data.empty(); } void BitMatrix::Clear() { + std::lock_guard lock(m_mutex); m_width = 0; m_height = 0; m_data.clear(); @@ -39,9 +95,9 @@ void BitMatrix::Clear() static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const Vec3& worldSpaceRayDir, const std::array& tri, float& dist) { - //TODO : merge with renderer - constexpr float failsafe = 0.5f; - constexpr float barycentricTolerance = 0.5f; + //TODO : merge with renderer + constexpr float failsafe = 0.5f; + constexpr float barycentricTolerance = 0.5f; //moller-trumbore intersection test //https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm @@ -91,7 +147,7 @@ static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const static bool RayIntersectQuadblockTest(const Vec3& worldSpaceRayOrigin, const Vec3& worldSpaceRayDir, const Quadblock& qb, float& dist) { std::vector> triFacesID = qb.GetTriFacesIndexes(); - for (std::array ids : triFacesID) + for (std::array ids : triFacesID) { if (WorldspaceRayTriIntersection(worldSpaceRayOrigin, worldSpaceRayDir, qb.GetTriFace(ids[0], ids[1], ids[2]), dist)) { return true; } } @@ -128,7 +184,7 @@ static bool RayIntersectBoundingBox(const Vec3& rayOrigin, const Vec3& rayDir, c && bbox.min.z - failsafe < rayOrigin.z && rayOrigin.z < bbox.max.z + failsafe) { tmin = -1.0f; // We fake tmin negative with a true return to say we are inside - return true; + return true; } // No intersection if tmax < 0 (box is behind ray) or tmin > tmax (ray misses box) if (tmax < 0.0f || tmin > tmax) @@ -202,7 +258,7 @@ static std::vector GetPotentialQuadblockIndexes( { for (size_t quadID : leaf.quadIndexes) { - const Quadblock& quad = quadblocks[quadID]; + const Quadblock& quad = quadblocks[quadID]; if (quad.GetBSPID() == leafBID || !quad.GetVisTreeTransparent()) { // Ideally, a quad has 8 normal, but I just test 2 @@ -237,9 +293,9 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q }; // Helper to add point if not duplicate - auto addIfUnique = [&samples, &isDuplicate](const Vec3& point, bool end) + auto addIfUnique = [&samples, &isDuplicate](const Vec3& point, bool end) { - if (!isDuplicate(point)) + if (!isDuplicate(point)) { if (end) { samples.push_back(point); } else { samples.insert(samples.begin(),point); } @@ -298,6 +354,7 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r const float maxDistanceSquared = maxDistance * maxDistance; std::vector leaves = root->GetLeaves(); BitMatrix vizMatrix = BitMatrix(leaves.size(), leaves.size()); + const int leafCount = static_cast(leaves.size()); const float cameraHeight = 5.0f; const float failsafe = 0.5f; @@ -310,19 +367,35 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r for (size_t index : quadIndexes) { quadIndexesToLeaves[index] = i; } } - for (size_t leafA = 0; leafA < leaves.size(); leafA++) + std::vector> sourceSamples(leaves.size()); + std::vector> targetSamples(leaves.size()); + for (size_t i = 0; i < leaves.size(); i++) + { + sourceSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], cameraHeight, simpleVisTree); + targetSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], 0.0f, simpleVisTree); + } + + int progress = 0; +#pragma omp parallel for + for (int leafAIndex = 0; leafAIndex < leafCount; leafAIndex++) { - printf("Prog: %d/%d\n", static_cast(leafA + 1), static_cast(leaves.size())); - vizMatrix.Set(true, leafA, leafA); - const std::vector sampleA = GenerateSamplePointLeaf(quadblocks, *leaves[leafA], cameraHeight, simpleVisTree); + const size_t leafA = static_cast(leafAIndex); +#pragma omp critical(vistree_progress) + { + progress++; + if (progress % 16 == 0 || progress == leafCount) + { + printf("Prog: %d/%d\n", progress, leafCount); + } + } + + std::vector rowVisibility(leaves.size(), 0); + rowVisibility[leafA] = 1; + const std::vector& sampleA = sourceSamples[leafA]; for (size_t leafB = 0; leafB < leaves.size(); leafB++) { bool foundLeafABHit = false; - const std::vector sampleB = GenerateSamplePointLeaf(quadblocks, *leaves[leafB], 0.0f, simpleVisTree); - - // Pre-build sets of quadblock indices for leafA and leafB for quick lookup - const std::vector& quadIndexesA = leaves[leafA]->GetQuadblockIndexes(); - const std::vector& quadIndexesB = leaves[leafB]->GetQuadblockIndexes(); + const std::vector& sampleB = targetSamples[leafB]; float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); // If minDistance is positive, and bigger than distBbox @@ -353,7 +426,7 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r } if (tmin < 0.0f) { - // We are inside the Bbox. + // We are inside the Bbox. foundLeafABHit = true; break; } @@ -409,9 +482,10 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r } if (foundLeafABHit) { - vizMatrix.Set(true, leafA, leafB); + rowVisibility[leafB] = 1; } } + vizMatrix.SetRow(rowVisibility, leafA); } int count = 0; for (size_t leafA = 0; leafA < leaves.size(); leafA++) @@ -435,4 +509,4 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r printf("Runtime %lldmin %lldsec\n", mins, secs); return vizMatrix; -} \ No newline at end of file +} diff --git a/src/vistree.h b/src/vistree.h index b363b61..da468e3 100644 --- a/src/vistree.h +++ b/src/vistree.h @@ -5,24 +5,32 @@ #include "geo.h" #include +#include +#include class BitMatrix { public: - BitMatrix() {}; - BitMatrix(size_t width, size_t height) : m_width(width), m_height(height), m_data(width * height) {} + BitMatrix() : m_width(0), m_height(0) {}; + BitMatrix(size_t width, size_t height) : m_width(width), m_height(height), m_data(width * height, 0) {} + BitMatrix(const BitMatrix& other); + BitMatrix& operator=(const BitMatrix& other); + BitMatrix(BitMatrix&& other) noexcept; + BitMatrix& operator=(BitMatrix&& other) noexcept; bool Get(size_t x, size_t y) const; size_t GetWidth() const; size_t GetHeight() const; void Set(bool value, size_t x, size_t y); + void SetRow(const std::vector& rowData, size_t y); bool IsEmpty() const; void Clear(); private: size_t m_width; size_t m_height; - std::vector m_data; + std::vector m_data; + mutable std::mutex m_mutex; }; BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, bool simpleVisTree, float minDistance, float maxDistance); From c21dde62d7c38aa1eeb9da92bec24bcd9a33d4c0 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Mon, 2 Feb 2026 18:03:59 -0800 Subject: [PATCH 2/9] parallelize loops A and B --- src/vistree.cpp | 157 ++++++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 86 deletions(-) diff --git a/src/vistree.cpp b/src/vistree.cpp index b74aabf..ebc4353 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -333,7 +333,7 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q return samples; } -float GetLeafDistanceSquared(const BSP& leaf1, const BSP& leaf2) +static float GetLeafDistanceSquared(const BSP& leaf1, const BSP& leaf2) { // Return the closest distance between the BBox of leaf1 and leaf2. // Return 0.0f if they are intersecting, or one is included in the other @@ -375,118 +375,103 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r targetSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], 0.0f, simpleVisTree); } - int progress = 0; + std::vector> visibilityRows(leaves.size(), std::vector(leaves.size(), 0)); + const int64_t totalLeafPairs = static_cast(leafCount) * static_cast(leafCount); #pragma omp parallel for - for (int leafAIndex = 0; leafAIndex < leafCount; leafAIndex++) + for (int64_t pairIndex = 0; pairIndex < totalLeafPairs; pairIndex++) { - const size_t leafA = static_cast(leafAIndex); -#pragma omp critical(vistree_progress) - { - progress++; - if (progress % 16 == 0 || progress == leafCount) - { - printf("Prog: %d/%d\n", progress, leafCount); - } - } + const size_t leafA = static_cast(pairIndex / leafCount); + const size_t leafB = static_cast(pairIndex % leafCount); - std::vector rowVisibility(leaves.size(), 0); - rowVisibility[leafA] = 1; + bool foundLeafABHit = (leafA == leafB); const std::vector& sampleA = sourceSamples[leafA]; - for (size_t leafB = 0; leafB < leaves.size(); leafB++) + const std::vector& sampleB = targetSamples[leafB]; + + float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); + // If minDistance is positive, and bigger than distBbox + if (minDistance > -0.0001f && minDistance * minDistance >= distBboxsquared) { - bool foundLeafABHit = false; - const std::vector& sampleB = targetSamples[leafB]; + foundLeafABHit = true; + } - float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); - // If minDistance is positive, and bigger than distBbox - if (minDistance > -0.0001f && minDistance * minDistance >= distBboxsquared) - { - foundLeafABHit = true; - } + for (const Vec3& pointA : sampleA) + { + if (foundLeafABHit) { break; } - for (const Vec3& pointA : sampleA) + for (const Vec3& pointB : sampleB) { if (foundLeafABHit) { break; } + Vec3 directionVector = pointB - pointA; + if (directionVector.LengthSquared() > maxDistanceSquared) { continue; } + directionVector.Normalize(); + + // Calculate distance range to leafB's bounding box + float tmin, tmax; + const BoundingBox& bboxB = leaves[leafB]->GetBoundingBox(); + bool intersect = RayIntersectBoundingBox(pointA, directionVector, bboxB, tmin, tmax); + if (!intersect) + { + // Weird edge case that shouldn't happen, but still does... + continue; + } + if (tmin < 0.0f) + { + // We are inside the Bbox. + foundLeafABHit = true; + break; + } + + std::vector potentialQuads = GetPotentialQuadblockIndexes(quadblocks, root, pointA, directionVector, leaves[leafB]->GetId(), tmax); - for (const Vec3& pointB : sampleB) + float closestDist = std::numeric_limits::max(); + size_t closestLeaf = leafA; + bool foundBlockingQuad = false; + + // Single loop: test quads and check for blocking simultaneously + for (size_t i = 0; i < potentialQuads.size(); i++) { - if (foundLeafABHit) { break; } - Vec3 directionVector = pointB - pointA; - if (directionVector.LengthSquared() > maxDistanceSquared) { continue; } - directionVector.Normalize(); - - // Calculate distance range to leafB's bounding box - float tmin, tmax; - const BoundingBox& bboxB = leaves[leafB]->GetBoundingBox(); - bool intersect = RayIntersectBoundingBox(pointA, directionVector, bboxB, tmin, tmax); - if (!intersect) - { - // Weird edge case that shouldn't happen, but still does... - continue; - } - if (tmin < 0.0f) - { - // We are inside the Bbox. - foundLeafABHit = true; - break; - } + if (foundBlockingQuad) { break; } - std::vector potentialQuads = GetPotentialQuadblockIndexes(quadblocks, root, pointA, directionVector, leaves[leafB]->GetId() , tmax); + size_t testQuadIndex = potentialQuads[i]; + size_t quadLeaf = quadIndexesToLeaves[testQuadIndex]; - float closestDist = std::numeric_limits::max(); - size_t closestLeaf = leafA; - bool foundBlockingQuad = false; + float dist = 0.0f; + const Quadblock& testQuad = quadblocks[testQuadIndex]; - // Single loop: test quads and check for blocking simultaneously - for (size_t i = 0; i < potentialQuads.size(); i++) + if (RayIntersectQuadblockTest(pointA, directionVector, testQuad, dist)) { - if (foundBlockingQuad) + // Early exit check: if quad hits before tmin and is not from leafB, it's blocking + if (quadLeaf != leafB && (dist + failsafe < tmin)) { + foundBlockingQuad = true; break; } - size_t testQuadIndex = potentialQuads[i]; - size_t quadLeaf = quadIndexesToLeaves[testQuadIndex]; - - float dist = 0.0f; - const Quadblock& testQuad = quadblocks[testQuadIndex]; - - if (RayIntersectQuadblockTest(pointA, directionVector, testQuad, dist)) + if (dist - (quadLeaf == leafB ? failsafe : 0.0f) < closestDist - (closestLeaf == leafB ? failsafe : 0.0f)) { - // Early exit check: if quad hits before tmin and is not from leafB, it's blocking - if (quadLeaf != leafB && (dist + failsafe < tmin)) - { - foundBlockingQuad = true; - break; - } - - if (dist - (quadLeaf == leafB ? failsafe : 0.0f) < closestDist - (closestLeaf == leafB ? failsafe : 0.0f)) - { - closestDist = dist; - closestLeaf = quadLeaf; - } + closestDist = dist; + closestLeaf = quadLeaf; } } + } - // If we found a blocking quad, skip to next pointB - if (foundBlockingQuad) - { - continue; - } + // If we found a blocking quad, skip to next pointB + if (foundBlockingQuad) { continue; } - if (closestLeaf == leafB) - { - foundLeafABHit = true; - } - } - } - if (foundLeafABHit) - { - rowVisibility[leafB] = 1; + if (closestLeaf == leafB) { foundLeafABHit = true; } } } - vizMatrix.SetRow(rowVisibility, leafA); + if (foundLeafABHit) + { + visibilityRows[leafA][leafB] = 1; + } } + + for (size_t leafA = 0; leafA < leaves.size(); leafA++) + { + vizMatrix.SetRow(visibilityRows[leafA], leafA); + } + int count = 0; for (size_t leafA = 0; leafA < leaves.size(); leafA++) { From dea36b5d45fc05c63fea97ae2ba2fa035ff9f244 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 09:54:37 -0800 Subject: [PATCH 3/9] cleanup --- src/vistree.cpp | 106 +++++++++++------------------------------------- src/vistree.h | 6 --- 2 files changed, 23 insertions(+), 89 deletions(-) diff --git a/src/vistree.cpp b/src/vistree.cpp index ebc4353..4063f41 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -5,75 +5,28 @@ #include #include -BitMatrix::BitMatrix(const BitMatrix& other) -{ - std::lock_guard lock(other.m_mutex); - m_width = other.m_width; - m_height = other.m_height; - m_data = other.m_data; -} - -BitMatrix& BitMatrix::operator=(const BitMatrix& other) -{ - if (this == &other) { return *this; } - std::scoped_lock lock(m_mutex, other.m_mutex); - m_width = other.m_width; - m_height = other.m_height; - m_data = other.m_data; - return *this; -} - -BitMatrix::BitMatrix(BitMatrix&& other) noexcept -{ - std::lock_guard lock(other.m_mutex); - m_width = other.m_width; - m_height = other.m_height; - m_data = std::move(other.m_data); - other.m_width = 0; - other.m_height = 0; -} - -BitMatrix& BitMatrix::operator=(BitMatrix&& other) noexcept -{ - if (this == &other) { return *this; } - std::scoped_lock lock(m_mutex, other.m_mutex); - m_width = other.m_width; - m_height = other.m_height; - m_data = std::move(other.m_data); - other.m_width = 0; - other.m_height = 0; - return *this; -} - bool BitMatrix::Get(size_t x, size_t y) const { - std::lock_guard lock(m_mutex); - const size_t index = (y * m_width) + x; - return m_data[index] != 0; + return m_data[(y * m_width) + x] != 0; } size_t BitMatrix::GetWidth() const { - std::lock_guard lock(m_mutex); return m_width; } size_t BitMatrix::GetHeight() const { - std::lock_guard lock(m_mutex); return m_height; } void BitMatrix::Set(bool value, size_t x, size_t y) { - std::lock_guard lock(m_mutex); - const size_t index = (y * m_width) + x; - m_data[index] = value ? 1 : 0; + m_data[(y * m_width) + x] = value ? 1 : 0; } void BitMatrix::SetRow(const std::vector& rowData, size_t y) { - std::lock_guard lock(m_mutex); if (rowData.size() != m_width) { return; } const size_t rowStart = y * m_width; std::copy(rowData.begin(), rowData.end(), m_data.begin() + rowStart); @@ -81,13 +34,11 @@ void BitMatrix::SetRow(const std::vector& rowData, size_t y) bool BitMatrix::IsEmpty() const { - std::lock_guard lock(m_mutex); return m_data.empty(); } void BitMatrix::Clear() { - std::lock_guard lock(m_mutex); m_width = 0; m_height = 0; m_data.clear(); @@ -123,20 +74,17 @@ static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const float v = inv_det * worldSpaceRayDir.Dot(s_cross_e1); // Allow v to be slightly outside [0, 1] range - if (v < -barycentricTolerance || v > 1.0f + barycentricTolerance) { - return false; - } + if (v < -barycentricTolerance || v > 1.0f + barycentricTolerance) { return false; } // Check if u+v is within triangle (with tolerance) // u+v > 1 means outside the triangle on the hypotenuse edge - if (u + v > 1.0f + barycentricTolerance) { - return false; - } + if (u + v > 1.0f + barycentricTolerance) { return false; } float t = inv_det * edge_2.Dot(s_cross_e1); // time value (interpolant) // Allow hits slightly behind the origin (for edge cases where ray starts on surface) - if (t > -failsafe) { + if (t > -failsafe) + { dist = t; return true; } @@ -147,7 +95,7 @@ static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const static bool RayIntersectQuadblockTest(const Vec3& worldSpaceRayOrigin, const Vec3& worldSpaceRayDir, const Quadblock& qb, float& dist) { std::vector> triFacesID = qb.GetTriFacesIndexes(); - for (std::array ids : triFacesID) + for (const std::array&ids : triFacesID) { if (WorldspaceRayTriIntersection(worldSpaceRayOrigin, worldSpaceRayDir, qb.GetTriFace(ids[0], ids[1], ids[2]), dist)) { return true; } } @@ -187,10 +135,7 @@ static bool RayIntersectBoundingBox(const Vec3& rayOrigin, const Vec3& rayDir, c return true; } // No intersection if tmax < 0 (box is behind ray) or tmin > tmax (ray misses box) - if (tmax < 0.0f || tmin > tmax) - { - return false; - } + if (tmax < 0.0f || tmin > tmax) { return false; } return true; } @@ -220,7 +165,7 @@ static void GetPotentialLeavesRecursive( // If this is a leaf node, add it's LeafWithDistance if (!node->IsBranch()) { - result.push_back({ node->GetQuadblockIndexes(), tmax }); + result.push_back({node->GetQuadblockIndexes(), tmax}); return; } @@ -254,7 +199,7 @@ static std::vector GetPotentialQuadblockIndexes( [](const LeafWithDistance& a, const LeafWithDistance& b) { return a.tmax < b.tmax; }); std::vector result; - for (const auto& leaf : leavesWithDist) + for (const LeafWithDistance& leaf : leavesWithDist) { for (size_t quadID : leaf.quadIndexes) { @@ -284,12 +229,13 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q const float dedupeThresholdSquared = dedupeThreshold * dedupeThreshold; // Helper to check if a point already exists in samples - auto isDuplicate = [&samples, dedupeThresholdSquared](const Vec3& point) { - for (const Vec3& existing : samples) + auto isDuplicate = [&samples, dedupeThresholdSquared](const Vec3& point) { - if ((existing - point).LengthSquared() < dedupeThresholdSquared) {return true;} - } - return false; + for (const Vec3& existing : samples) + { + if ((existing - point).LengthSquared() < dedupeThresholdSquared) { return true; } + } + return false; }; // Helper to add point if not duplicate @@ -298,7 +244,7 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q if (!isDuplicate(point)) { if (end) { samples.push_back(point); } - else { samples.insert(samples.begin(),point); } + else { samples.insert(samples.begin(), point); } } }; @@ -306,10 +252,8 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q { Quadblock quad = quadblocks[quadID]; float up_dist = 0.0f; - if (quad.GetFlags() & QuadFlags::GROUND) - { - up_dist = camera_raise; - } + if (quad.GetFlags() & QuadFlags::GROUND) { up_dist = camera_raise; } + addIfUnique(quad.GetCenter() + (up * up_dist), false); if (!simpleVisTree) { @@ -340,10 +284,9 @@ static float GetLeafDistanceSquared(const BSP& leaf1, const BSP& leaf2) const BoundingBox& a = leaf1.GetBoundingBox(); const BoundingBox& b = leaf2.GetBoundingBox(); - float dx = std::max({ 0.0f, b.min.x - a.max.x, a.min.x - b.max.x }); - float dy = std::max({ 0.0f, b.min.y - a.max.y, a.min.y - b.max.y }); - float dz = std::max({ 0.0f, b.min.z - a.max.z, a.min.z - b.max.z }); - + float dx = std::max({0.0f, b.min.x - a.max.x, a.min.x - b.max.x}); + float dy = std::max({0.0f, b.min.y - a.max.y, a.min.y - b.max.y}); + float dz = std::max({0.0f, b.min.z - a.max.z, a.min.z - b.max.z}); return (dx * dx + dy * dy + dz * dz); } @@ -477,10 +420,7 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r { for (size_t leafB = 0; leafB < leaves.size(); leafB++) { - if (vizMatrix.Get(leafA, leafB)) - { - count++; - } + if (vizMatrix.Get(leafA, leafB)) { count++; } } } int max = static_cast(leaves.size() * leaves.size()); diff --git a/src/vistree.h b/src/vistree.h index da468e3..91d7d11 100644 --- a/src/vistree.h +++ b/src/vistree.h @@ -6,17 +6,12 @@ #include #include -#include class BitMatrix { public: BitMatrix() : m_width(0), m_height(0) {}; BitMatrix(size_t width, size_t height) : m_width(width), m_height(height), m_data(width * height, 0) {} - BitMatrix(const BitMatrix& other); - BitMatrix& operator=(const BitMatrix& other); - BitMatrix(BitMatrix&& other) noexcept; - BitMatrix& operator=(BitMatrix&& other) noexcept; bool Get(size_t x, size_t y) const; size_t GetWidth() const; @@ -30,7 +25,6 @@ class BitMatrix size_t m_width; size_t m_height; std::vector m_data; - mutable std::mutex m_mutex; }; BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, bool simpleVisTree, float minDistance, float maxDistance); From a037e5166fc9ec2a934f98eeed78b383f0e11e51 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 14:48:39 -0800 Subject: [PATCH 4/9] only calculate upper matrix for visibility --- src/vistree.cpp | 145 +++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 69 deletions(-) diff --git a/src/vistree.cpp b/src/vistree.cpp index 4063f41..75ba99e 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -319,94 +319,101 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r } std::vector> visibilityRows(leaves.size(), std::vector(leaves.size(), 0)); - const int64_t totalLeafPairs = static_cast(leafCount) * static_cast(leafCount); -#pragma omp parallel for - for (int64_t pairIndex = 0; pairIndex < totalLeafPairs; pairIndex++) +#pragma omp parallel for schedule(dynamic) + for (int leafAInt = 0; leafAInt < leafCount; leafAInt++) { - const size_t leafA = static_cast(pairIndex / leafCount); - const size_t leafB = static_cast(pairIndex % leafCount); - - bool foundLeafABHit = (leafA == leafB); - const std::vector& sampleA = sourceSamples[leafA]; - const std::vector& sampleB = targetSamples[leafB]; - - float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); - // If minDistance is positive, and bigger than distBbox - if (minDistance > -0.0001f && minDistance * minDistance >= distBboxsquared) + const size_t leafA = static_cast(leafAInt); + for (int leafBInt = leafAInt; leafBInt < leafCount; leafBInt++) { - foundLeafABHit = true; - } + const size_t leafB = static_cast(leafBInt); - for (const Vec3& pointA : sampleA) - { - if (foundLeafABHit) { break; } + bool foundLeafABHit = (leafA == leafB); + const std::vector& sampleA = sourceSamples[leafA]; + const std::vector& sampleB = targetSamples[leafB]; - for (const Vec3& pointB : sampleB) + float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); + // If minDistance is positive, and bigger than distBbox + if (minDistance > -0.0001f && minDistance * minDistance >= distBboxsquared) { - if (foundLeafABHit) { break; } - Vec3 directionVector = pointB - pointA; - if (directionVector.LengthSquared() > maxDistanceSquared) { continue; } - directionVector.Normalize(); - - // Calculate distance range to leafB's bounding box - float tmin, tmax; - const BoundingBox& bboxB = leaves[leafB]->GetBoundingBox(); - bool intersect = RayIntersectBoundingBox(pointA, directionVector, bboxB, tmin, tmax); - if (!intersect) - { - // Weird edge case that shouldn't happen, but still does... - continue; - } - if (tmin < 0.0f) - { - // We are inside the Bbox. - foundLeafABHit = true; - break; - } - - std::vector potentialQuads = GetPotentialQuadblockIndexes(quadblocks, root, pointA, directionVector, leaves[leafB]->GetId(), tmax); + foundLeafABHit = true; + } - float closestDist = std::numeric_limits::max(); - size_t closestLeaf = leafA; - bool foundBlockingQuad = false; + for (const Vec3& pointA : sampleA) + { + if (foundLeafABHit) { break; } - // Single loop: test quads and check for blocking simultaneously - for (size_t i = 0; i < potentialQuads.size(); i++) + for (const Vec3& pointB : sampleB) { - if (foundBlockingQuad) { break; } + if (foundLeafABHit) { break; } + Vec3 directionVector = pointB - pointA; + if (directionVector.LengthSquared() > maxDistanceSquared) { continue; } + directionVector.Normalize(); + + // Calculate distance range to leafB's bounding box + float tmin, tmax; + const BoundingBox& bboxB = leaves[leafB]->GetBoundingBox(); + bool intersect = RayIntersectBoundingBox(pointA, directionVector, bboxB, tmin, tmax); + if (!intersect) + { + // Weird edge case that shouldn't happen, but still does... + continue; + } + if (tmin < 0.0f) + { + // We are inside the Bbox. + foundLeafABHit = true; + break; + } - size_t testQuadIndex = potentialQuads[i]; - size_t quadLeaf = quadIndexesToLeaves[testQuadIndex]; + std::vector potentialQuads = GetPotentialQuadblockIndexes(quadblocks, root, pointA, directionVector, leaves[leafB]->GetId(), tmax); - float dist = 0.0f; - const Quadblock& testQuad = quadblocks[testQuadIndex]; + float closestDist = std::numeric_limits::max(); + size_t closestLeaf = leafA; + bool foundBlockingQuad = false; - if (RayIntersectQuadblockTest(pointA, directionVector, testQuad, dist)) + // Single loop: test quads and check for blocking simultaneously + for (size_t i = 0; i < potentialQuads.size(); i++) { - // Early exit check: if quad hits before tmin and is not from leafB, it's blocking - if (quadLeaf != leafB && (dist + failsafe < tmin)) - { - foundBlockingQuad = true; - break; - } + if (foundBlockingQuad) { break; } + + size_t testQuadIndex = potentialQuads[i]; + size_t quadLeaf = quadIndexesToLeaves[testQuadIndex]; + + float dist = 0.0f; + const Quadblock& testQuad = quadblocks[testQuadIndex]; - if (dist - (quadLeaf == leafB ? failsafe : 0.0f) < closestDist - (closestLeaf == leafB ? failsafe : 0.0f)) + if (RayIntersectQuadblockTest(pointA, directionVector, testQuad, dist)) { - closestDist = dist; - closestLeaf = quadLeaf; + // Early exit check: if quad hits before tmin and is not from leafB, it's blocking + if (quadLeaf != leafB && (dist + failsafe < tmin)) + { + foundBlockingQuad = true; + break; + } + + if (dist - (quadLeaf == leafB ? failsafe : 0.0f) < closestDist - (closestLeaf == leafB ? failsafe : 0.0f)) + { + closestDist = dist; + closestLeaf = quadLeaf; + } } } - } - // If we found a blocking quad, skip to next pointB - if (foundBlockingQuad) { continue; } + // If we found a blocking quad, skip to next pointB + if (foundBlockingQuad) { continue; } - if (closestLeaf == leafB) { foundLeafABHit = true; } + if (closestLeaf == leafB) { foundLeafABHit = true; } + } + } + if (foundLeafABHit) + { + visibilityRows[leafA][leafB] = 1; + /* + while this is not necessarily true, + in practice it barely affects visibility and speeds up generation by 2x~3x + */ + visibilityRows[leafB][leafA] = 1; } - } - if (foundLeafABHit) - { - visibilityRows[leafA][leafB] = 1; } } From 1b4fa5699355085ea25b2b22190d930eb837628e Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 16:33:06 -0800 Subject: [PATCH 5/9] refactor VisTree options --- src/level.cpp | 6 ++---- src/level.h | 4 +--- src/levelui.cpp | 37 +++++++++++++++++++++++++------------ src/vistree.cpp | 24 +++++++++++------------- src/vistree.h | 11 ++++++++++- 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/level.cpp b/src/level.cpp index a2326fe..2b7b92d 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -70,12 +70,10 @@ void Level::Clear(bool clearErrors) m_rendererQueryPoint = Vec3(); m_rendererSelectedQuadblockIndexes.clear(); m_genVisTree = false; - m_simpleVisTree = false; m_bspVis.Clear(); m_maxQuadPerLeaf = 31; m_maxLeafAxisLength = 64.0f; - m_distanceNearClip = -1.0f; - m_distanceFarClip = 1000.0f; + m_visTreeSettings = VisTreeSettings(); m_pythonConsole.clear(); m_saveScript = false; m_vrm.clear(); @@ -188,7 +186,7 @@ bool Level::GenerateBSP() if (m_bsp.IsValid()) { GenerateRenderBspData(); - if (m_genVisTree) { m_bspVis = GenerateVisTree(m_quadblocks, &m_bsp, m_simpleVisTree, m_distanceNearClip, m_distanceFarClip); } + if (m_genVisTree) { m_bspVis = GenerateVisTree(m_quadblocks, &m_bsp, m_visTreeSettings); } return true; } m_bsp.Clear(); diff --git a/src/level.h b/src/level.h index 36998ad..317d7b4 100644 --- a/src/level.h +++ b/src/level.h @@ -97,12 +97,10 @@ class Level bool m_showLogWindow; bool m_showHotReloadWindow; bool m_loaded; - bool m_simpleVisTree; bool m_genVisTree; int m_maxQuadPerLeaf; float m_maxLeafAxisLength; - float m_distanceNearClip; - float m_distanceFarClip; + VisTreeSettings m_visTreeSettings; std::vector> m_invalidQuadblocks; std::string m_logMessage; diff --git a/src/levelui.cpp b/src/levelui.cpp index c36d06d..bf56b2d 100644 --- a/src/levelui.cpp +++ b/src/levelui.cpp @@ -749,18 +749,31 @@ void Level::RenderUI(Renderer& renderer) static ButtonUI generateBSPButton = ButtonUI(); if (ImGui::TreeNode("Advanced")) { - if (ImGui::InputInt("Max Quad Per Leaf", &m_maxQuadPerLeaf)) { m_maxQuadPerLeaf = std::max(m_maxQuadPerLeaf, 1); } - ImGui::SetItemTooltip("Lower values improve rendering performance, but increases file size and slows down vis tree generation."); - if (ImGui::InputFloat("Max Leaf Axis Length", &m_maxLeafAxisLength)) { m_maxLeafAxisLength = std::max(m_maxLeafAxisLength, 0.0f); } - ImGui::SetItemTooltip("Lower values improve rendering performance, but increases file size and slows down vis tree generation."); - if (ImGui::InputFloat("Near Clip Distance", &m_distanceNearClip)) { m_distanceNearClip = std::max(m_distanceNearClip, -1.0f); } - ImGui::SetItemTooltip("Minimum drawing distance. Higher values decrease performance and speed up the vis tree generation."); - if (ImGui::InputFloat("Far Clip Distance", &m_distanceFarClip)) { m_distanceFarClip = std::max(m_distanceFarClip, 0.0f); } - ImGui::SetItemTooltip("Maximum drawing distance. Lower values improve performance and speed up the vis tree generation."); - ImGui::Checkbox("Simple Vis Tree", &m_simpleVisTree); - ImGui::SetItemTooltip("The vis tree will be generated faster, but will be less precise"); - ImGui::Checkbox("Generate Vis Tree", &m_genVisTree); - ImGui::SetItemTooltip("Generating the vis tree may take several minutes, but the gameplay will be more performant."); + if (ImGui::TreeNodeEx("BSP Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::InputInt("Max Quad Per Leaf", &m_maxQuadPerLeaf)) { m_maxQuadPerLeaf = std::max(m_maxQuadPerLeaf, 1); } + ImGui::SetItemTooltip("Lower values improve rendering performance, but increases file size and slows down vis tree generation."); + if (ImGui::InputFloat("Max Leaf Axis Length", &m_maxLeafAxisLength)) { m_maxLeafAxisLength = std::max(m_maxLeafAxisLength, 0.0f); } + ImGui::SetItemTooltip("Lower values improve rendering performance, but increases file size and slows down vis tree generation."); + + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Vis Tree Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Generate Vis Tree", &m_genVisTree); + ImGui::SetItemTooltip("Generating the vis tree may take several minutes, but the gameplay will be more performant."); + ImGui::BeginDisabled(!m_genVisTree); + if (ImGui::InputFloat("Near Clip Distance", &m_visTreeSettings.nearClipDistance)) { m_visTreeSettings.nearClipDistance = std::max(m_visTreeSettings.nearClipDistance, -1.0f); } + ImGui::SetItemTooltip("Minimum drawing distance. Higher values decrease performance and speed up the vis tree generation."); + if (ImGui::InputFloat("Far Clip Distance", &m_visTreeSettings.farClipDistance)) { m_visTreeSettings.farClipDistance = std::max(m_visTreeSettings.farClipDistance, 0.0f); } + ImGui::SetItemTooltip("Maximum drawing distance. Lower values improve performance and speed up the vis tree generation."); + ImGui::Checkbox("Assume Commutative Rays", &m_visTreeSettings.commutativeRays); + ImGui::SetItemTooltip("Speeds up VisTree generation by a factor of 2x to 3x with minimal loss of precision."); + ImGui::Checkbox("Center-Only Samples", &m_visTreeSettings.centerOnlySamples); + ImGui::SetItemTooltip("Only casts rays from each quad center (skips corner samples). Much faster, but may miss narrow visibility paths."); + ImGui::EndDisabled(); + ImGui::TreePop(); + } ImGui::TreePop(); } if (generateBSPButton.Show("Generate", buttonMessage, false)) diff --git a/src/vistree.cpp b/src/vistree.cpp index 75ba99e..78af337 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -219,7 +219,7 @@ static std::vector GetPotentialQuadblockIndexes( return result; } -static std::vector GenerateSamplePointLeaf(const std::vector& quadblocks, const BSP& leaf, float camera_raise, bool simpleVisTree) +static std::vector GenerateSamplePointLeaf(const std::vector& quadblocks, const BSP& leaf, float camera_raise, bool centerOnlySamples) { // For a leaf node, generate all the points for the vis ray test. std::vector samples; @@ -255,7 +255,7 @@ static std::vector GenerateSamplePointLeaf(const std::vector& q if (quad.GetFlags() & QuadFlags::GROUND) { up_dist = camera_raise; } addIfUnique(quad.GetCenter() + (up * up_dist), false); - if (!simpleVisTree) + if (!centerOnlySamples) { if (quad.IsQuadblock()) { @@ -290,11 +290,11 @@ static float GetLeafDistanceSquared(const BSP& leaf1, const BSP& leaf2) return (dx * dx + dy * dy + dz * dz); } -BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, bool simpleVisTree, float minDistance, float maxDistance) +BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, const VisTreeSettings& settings) { auto start_time = std::chrono::high_resolution_clock::now(); - const float maxDistanceSquared = maxDistance * maxDistance; + const float maxDistanceSquared = settings.farClipDistance * settings.farClipDistance; std::vector leaves = root->GetLeaves(); BitMatrix vizMatrix = BitMatrix(leaves.size(), leaves.size()); const int leafCount = static_cast(leaves.size()); @@ -314,8 +314,8 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r std::vector> targetSamples(leaves.size()); for (size_t i = 0; i < leaves.size(); i++) { - sourceSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], cameraHeight, simpleVisTree); - targetSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], 0.0f, simpleVisTree); + sourceSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], cameraHeight, settings.centerOnlySamples); + targetSamples[i] = GenerateSamplePointLeaf(quadblocks, *leaves[i], 0.0f, settings.centerOnlySamples); } std::vector> visibilityRows(leaves.size(), std::vector(leaves.size(), 0)); @@ -323,7 +323,8 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r for (int leafAInt = 0; leafAInt < leafCount; leafAInt++) { const size_t leafA = static_cast(leafAInt); - for (int leafBInt = leafAInt; leafBInt < leafCount; leafBInt++) + const int leafBStart = settings.commutativeRays ? leafAInt : 0; + for (int leafBInt = leafBStart; leafBInt < leafCount; leafBInt++) { const size_t leafB = static_cast(leafBInt); @@ -333,7 +334,7 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r float distBboxsquared = GetLeafDistanceSquared(*leaves[leafA], *leaves[leafB]); // If minDistance is positive, and bigger than distBbox - if (minDistance > -0.0001f && minDistance * minDistance >= distBboxsquared) + if ((settings.nearClipDistance > -EPSILON) && (settings.nearClipDistance * settings.nearClipDistance >= distBboxsquared)) { foundLeafABHit = true; } @@ -405,14 +406,11 @@ BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* r if (closestLeaf == leafB) { foundLeafABHit = true; } } } + if (foundLeafABHit) { visibilityRows[leafA][leafB] = 1; - /* - while this is not necessarily true, - in practice it barely affects visibility and speeds up generation by 2x~3x - */ - visibilityRows[leafB][leafA] = 1; + if (settings.commutativeRays) { visibilityRows[leafB][leafA] = 1; } } } } diff --git a/src/vistree.h b/src/vistree.h index 91d7d11..c794820 100644 --- a/src/vistree.h +++ b/src/vistree.h @@ -7,6 +7,15 @@ #include #include +struct VisTreeSettings +{ + bool centerOnlySamples; + bool commutativeRays; + float nearClipDistance; + float farClipDistance; + VisTreeSettings() : centerOnlySamples(false), commutativeRays(false), nearClipDistance(-1.0f), farClipDistance(1000.0f) {} +}; + class BitMatrix { public: @@ -27,4 +36,4 @@ class BitMatrix std::vector m_data; }; -BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, bool simpleVisTree, float minDistance, float maxDistance); +BitMatrix GenerateVisTree(const std::vector& quadblocks, const BSP* root, const VisTreeSettings& settings); From 59d113e9b0695ea8e62c903bf6396cee5537e9b5 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 17:01:46 -0800 Subject: [PATCH 6/9] collision triface optimization --- src/quadblock.cpp | 82 ++++++++++++++++++++++++++++++++++++----------- src/quadblock.h | 2 ++ src/vistree.cpp | 2 +- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/quadblock.cpp b/src/quadblock.cpp index 9e30b97..4375996 100644 --- a/src/quadblock.cpp +++ b/src/quadblock.cpp @@ -27,7 +27,7 @@ Quadblock::Quadblock(const std::string& name, Tri& t0, Tri& t1, Tri& t2, Tri& t3 { throw QuadException( ("Unique Vertices: " + std::to_string(uniqueCount) + "/3\n" + - "Shared Vertices: " + std::to_string(sharedCount) + "/3\n") + "Shared Vertices: " + std::to_string(sharedCount) + "/3\n") ); } @@ -193,11 +193,11 @@ Quadblock::Quadblock(const std::string& name, Tri& t0, Tri& t1, Tri& t2, Tri& t3 const Tri* uvt1 = FindTri(m_p[2].m_pos, t0, t1, t2, t3); const Tri* uvt2 = FindTri(m_p[6].m_pos, t0, t1, t2, t3); - m_uvs[0] = { GetUV(m_p[0].m_pos, *uvt0), GetUV(m_p[1].m_pos, *uvt0), GetUV(m_p[3].m_pos, *uvt0), GetUV(m_p[4].m_pos, *centerTri) }; - m_uvs[1] = { GetUV(m_p[1].m_pos, *uvt1), GetUV(m_p[2].m_pos, *uvt1), GetUV(m_p[4].m_pos, *uvt1), Vec2() }; - m_uvs[2] = { GetUV(m_p[3].m_pos, *uvt2), GetUV(m_p[4].m_pos, *uvt2), GetUV(m_p[6].m_pos, *uvt2), Vec2() }; - m_uvs[3] = { Vec2(), Vec2(), Vec2(), Vec2() }; - m_uvs[4] = { GetUV(m_p[0].m_pos, *uvt0), GetUV(m_p[2].m_pos, *uvt1), GetUV(m_p[6].m_pos, *uvt2), Vec2() }; + m_uvs[0] = {GetUV(m_p[0].m_pos, *uvt0), GetUV(m_p[1].m_pos, *uvt0), GetUV(m_p[3].m_pos, *uvt0), GetUV(m_p[4].m_pos, *centerTri)}; + m_uvs[1] = {GetUV(m_p[1].m_pos, *uvt1), GetUV(m_p[2].m_pos, *uvt1), GetUV(m_p[4].m_pos, *uvt1), Vec2()}; + m_uvs[2] = {GetUV(m_p[3].m_pos, *uvt2), GetUV(m_p[4].m_pos, *uvt2), GetUV(m_p[6].m_pos, *uvt2), Vec2()}; + m_uvs[3] = {Vec2(), Vec2(), Vec2(), Vec2()}; + m_uvs[4] = {GetUV(m_p[0].m_pos, *uvt0), GetUV(m_p[2].m_pos, *uvt1), GetUV(m_p[6].m_pos, *uvt2), Vec2()}; } else { ResetUVs(); } @@ -233,8 +233,8 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad { throw QuadException( ("Unique Vertices: " + std::to_string(uniqueCount) + "/4\n" + - "Shared Vertices: " + std::to_string(sharedCount) + "/4\n" + - "Center Vertices: " + std::to_string(centerCount) + "/1\n") + "Shared Vertices: " + std::to_string(sharedCount) + "/4\n" + + "Center Vertices: " + std::to_string(centerCount) + "/1\n") ); } @@ -306,7 +306,7 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad auto FindQuad = [](const Vec3& pos, const Quad& q0, const Quad& q1, const Quad& q2, const Quad& q3) -> const Quad* { - const Quad* quads[] = { &q0, &q1, &q2, &q3 }; + const Quad* quads[] = {&q0, &q1, &q2, &q3}; for (size_t i = 0; i < 4; i++) { const Quad* quad = quads[i]; @@ -334,10 +334,10 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad if (hasUV) { - m_uvs[0] = { GetUV(m_p[0].m_pos, *uvq0), GetUV(m_p[1].m_pos, *uvq0), GetUV(m_p[3].m_pos, *uvq0), GetUV(m_p[4].m_pos, *uvq0) }; - m_uvs[1] = { GetUV(m_p[1].m_pos, *uvq1), GetUV(m_p[2].m_pos, *uvq1), GetUV(m_p[4].m_pos, *uvq1), GetUV(m_p[5].m_pos, *uvq1) }; - m_uvs[2] = { GetUV(m_p[3].m_pos, *uvq2), GetUV(m_p[4].m_pos, *uvq2), GetUV(m_p[6].m_pos, *uvq2), GetUV(m_p[7].m_pos, *uvq2) }; - m_uvs[3] = { GetUV(m_p[4].m_pos, *uvq3), GetUV(m_p[5].m_pos, *uvq3), GetUV(m_p[7].m_pos, *uvq3), GetUV(m_p[8].m_pos, *uvq3) }; + m_uvs[0] = {GetUV(m_p[0].m_pos, *uvq0), GetUV(m_p[1].m_pos, *uvq0), GetUV(m_p[3].m_pos, *uvq0), GetUV(m_p[4].m_pos, *uvq0)}; + m_uvs[1] = {GetUV(m_p[1].m_pos, *uvq1), GetUV(m_p[2].m_pos, *uvq1), GetUV(m_p[4].m_pos, *uvq1), GetUV(m_p[5].m_pos, *uvq1)}; + m_uvs[2] = {GetUV(m_p[3].m_pos, *uvq2), GetUV(m_p[4].m_pos, *uvq2), GetUV(m_p[6].m_pos, *uvq2), GetUV(m_p[7].m_pos, *uvq2)}; + m_uvs[3] = {GetUV(m_p[4].m_pos, *uvq3), GetUV(m_p[5].m_pos, *uvq3), GetUV(m_p[7].m_pos, *uvq3), GetUV(m_p[8].m_pos, *uvq3)}; float uMin = std::numeric_limits::max(); float vMin = std::numeric_limits::max(); float uMax = -std::numeric_limits::max(); float vMax = -std::numeric_limits::max(); @@ -352,7 +352,7 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad bool indexPicked[4] = {false, false, false, false}; bool boundPicked[4] = {false, false, false, false}; - const QuadUV uvBounds = { Vec2(uMin, vMin), Vec2(uMax, vMin), Vec2(uMin, vMax), Vec2(uMax, vMax)}; + const QuadUV uvBounds = {Vec2(uMin, vMin), Vec2(uMax, vMin), Vec2(uMin, vMax), Vec2(uMax, vMax)}; for (size_t indexCount = 0; indexCount < 4; indexCount++) { @@ -390,7 +390,7 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad Quadblock::Quadblock(const PSX::Quadblock& quadblock, const std::vector& vertices, UpdateFilterCallback filterCallback) { - uint16_t reverseIndexMapping[NUM_VERTICES_QUADBLOCK] = { 0, 2, 6, 8, 1, 3, 4, 5, 7 }; + uint16_t reverseIndexMapping[NUM_VERTICES_QUADBLOCK] = {0, 2, 6, 8, 1, 3, 4, 5, 7}; for (size_t i = 0; i < NUM_VERTICES_QUADBLOCK; i++) { uint16_t index = quadblock.index[i]; @@ -449,13 +449,18 @@ Vec3 Quadblock::GetNormal() const return normal; } +std::vector> Quadblock::GetCollTriFacesIndexes() const +{ + return m_collTriFaces; +} + std::vector> Quadblock::GetTriFacesIndexes() const { // Return a list of (size_t, size_t, size_t) containing vertex ID // of every triface composing the quad, ordered clockwise std::vector> triFaces; - if (!m_triblock) + if (!m_triblock) { triFaces = { {0, 1, 3}, @@ -468,7 +473,7 @@ std::vector> Quadblock::GetTriFacesIndexes() const {5, 8, 7} }; } - else + else { triFaces = { {0, 1, 3}, @@ -756,7 +761,7 @@ std::vector Quadblock::ToGeometry(bool filterTriangles, const std::ar { 3, 4, 6 }, { 1, 4, 3 }, }; - constexpr int triblockQuadIndex[triCount] = { 0, 1, 2, 0 }; + constexpr int triblockQuadIndex[triCount] = {0, 1, 2, 0}; for (int triIndex = 0; triIndex < triCount; triIndex++) { const int quadIndex = triblockQuadIndex[triIndex]; @@ -783,7 +788,7 @@ std::vector Quadblock::ToGeometry(bool filterTriangles, const std::ar std::vector Quadblock::GetVertices() const { /* 0 1 2 3 4 5 6 7 8 */ - std::vector vertices = { m_p[0], m_p[2], m_p[6], m_p[8], m_p[1], m_p[3], m_p[4], m_p[5], m_p[7] }; + std::vector vertices = {m_p[0], m_p[2], m_p[6], m_p[8], m_p[1], m_p[3], m_p[4], m_p[5], m_p[7]}; return vertices; } @@ -894,6 +899,45 @@ void Quadblock::SetDefaultValues() m_faceDrawMode[i] = FaceDrawMode::DRAW_BOTH; m_faceRotateFlip[i] = FaceRotateFlip::NONE; } + + const bool equivalentDiagonal = ((m_p[2].m_pos - m_p[6].m_pos).Length() - ((m_p[2].m_pos - m_p[4].m_pos).Length() + (m_p[4].m_pos - m_p[6].m_pos).Length())) <= EPSILON; + if ( + equivalentDiagonal && + (((m_p[0].m_pos - m_p[2].m_pos).Length() - ((m_p[0].m_pos - m_p[1].m_pos).Length() + (m_p[1].m_pos - m_p[2].m_pos).Length())) <= EPSILON) && + (((m_p[0].m_pos - m_p[6].m_pos).Length() - ((m_p[0].m_pos - m_p[3].m_pos).Length() + (m_p[3].m_pos - m_p[6].m_pos).Length())) <= EPSILON) + ) + { + m_collTriFaces = {{0, 2, 6}}; + } + else + { + m_collTriFaces = { + {0, 1, 3}, + {1, 2, 4}, + {1, 4, 3}, + {4, 6, 3} + }; + } + + if (!m_triblock) + { + if ( + equivalentDiagonal && + (((m_p[2].m_pos - m_p[8].m_pos).Length() - ((m_p[2].m_pos - m_p[5].m_pos).Length() + (m_p[5].m_pos - m_p[8].m_pos).Length())) <= EPSILON) && + (((m_p[6].m_pos - m_p[8].m_pos).Length() - ((m_p[6].m_pos - m_p[7].m_pos).Length() + (m_p[7].m_pos - m_p[8].m_pos).Length())) <= EPSILON) + ) + { + m_collTriFaces.push_back({2, 8, 6}); + } + else + { + m_collTriFaces.push_back({2, 5, 4}); + m_collTriFaces.push_back({4, 7, 6}); + m_collTriFaces.push_back({4, 5, 7}); + m_collTriFaces.push_back({5, 8, 7}); + } + } + m_doubleSided = false; m_checkpointPathable = true; m_checkpointStatus = false; diff --git a/src/quadblock.h b/src/quadblock.h index 805938a..a3f5124 100644 --- a/src/quadblock.h +++ b/src/quadblock.h @@ -117,6 +117,7 @@ class Quadblock const std::string& GetName() const; Vec3 GetCenter() const; Vec3 GetNormal() const; + std::vector> GetCollTriFacesIndexes() const; std::vector> GetTriFacesIndexes() const; std::array GetTriFace(size_t id0, size_t id1, size_t id2) const; uint8_t GetTerrain() const; @@ -205,6 +206,7 @@ class Quadblock size_t m_turboPadIndex; Color m_filterColor; mutable size_t m_bspID; + std::vector> m_collTriFaces; std::array m_uvs; /* Last id is reserved for low tex */ std::array m_textureIDs = { 0, 0, 0, 0, 0 }; std::array m_animTexOffset = {0, 0, 0, 0, 0}; diff --git a/src/vistree.cpp b/src/vistree.cpp index 78af337..b1e402c 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -94,7 +94,7 @@ static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const static bool RayIntersectQuadblockTest(const Vec3& worldSpaceRayOrigin, const Vec3& worldSpaceRayDir, const Quadblock& qb, float& dist) { - std::vector> triFacesID = qb.GetTriFacesIndexes(); + std::vector> triFacesID = qb.GetCollTriFacesIndexes(); for (const std::array&ids : triFacesID) { if (WorldspaceRayTriIntersection(worldSpaceRayOrigin, worldSpaceRayDir, qb.GetTriFace(ids[0], ids[1], ids[2]), dist)) { return true; } From e8296a33568b7e7b71867b61e78a1aa809016f59 Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 17:15:20 -0800 Subject: [PATCH 7/9] const ref --- src/quadblock.cpp | 2 +- src/quadblock.h | 2 +- src/vistree.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/quadblock.cpp b/src/quadblock.cpp index 4375996..dedbd7f 100644 --- a/src/quadblock.cpp +++ b/src/quadblock.cpp @@ -449,7 +449,7 @@ Vec3 Quadblock::GetNormal() const return normal; } -std::vector> Quadblock::GetCollTriFacesIndexes() const +const std::vector>& Quadblock::GetCollTriFacesIndexes() const { return m_collTriFaces; } diff --git a/src/quadblock.h b/src/quadblock.h index a3f5124..fac4004 100644 --- a/src/quadblock.h +++ b/src/quadblock.h @@ -117,7 +117,7 @@ class Quadblock const std::string& GetName() const; Vec3 GetCenter() const; Vec3 GetNormal() const; - std::vector> GetCollTriFacesIndexes() const; + const std::vector>& GetCollTriFacesIndexes() const; std::vector> GetTriFacesIndexes() const; std::array GetTriFace(size_t id0, size_t id1, size_t id2) const; uint8_t GetTerrain() const; diff --git a/src/vistree.cpp b/src/vistree.cpp index b1e402c..8ceb50d 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -94,7 +94,7 @@ static bool WorldspaceRayTriIntersection(const Vec3& worldSpaceRayOrigin, const static bool RayIntersectQuadblockTest(const Vec3& worldSpaceRayOrigin, const Vec3& worldSpaceRayDir, const Quadblock& qb, float& dist) { - std::vector> triFacesID = qb.GetCollTriFacesIndexes(); + const std::vector>& triFacesID = qb.GetCollTriFacesIndexes(); for (const std::array&ids : triFacesID) { if (WorldspaceRayTriIntersection(worldSpaceRayOrigin, worldSpaceRayDir, qb.GetTriFace(ids[0], ids[1], ids[2]), dist)) { return true; } From aea3f64b316d23c6f83665614c37b8521803250c Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Tue, 3 Feb 2026 17:17:37 -0800 Subject: [PATCH 8/9] triblock fix --- src/vistree.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vistree.cpp b/src/vistree.cpp index 8ceb50d..09663b9 100644 --- a/src/vistree.cpp +++ b/src/vistree.cpp @@ -207,8 +207,7 @@ static std::vector GetPotentialQuadblockIndexes( if (quad.GetBSPID() == leafBID || !quad.GetVisTreeTransparent()) { // Ideally, a quad has 8 normal, but I just test 2 - // Fix for triblocks please - if (quad.GetDrawDoubleSided() || quad.ComputeNormalVector(0, 2, 6).Dot(rayDir) < 0 || quad.ComputeNormalVector(2, 8, 6).Dot(rayDir) < 0) + if (quad.GetDrawDoubleSided() || quad.ComputeNormalVector(0, 2, 6).Dot(rayDir) < 0 || (quad.IsQuadblock() && quad.ComputeNormalVector(2, 8, 6).Dot(rayDir) < 0)) { result.push_back(quadID); } From c31df877a0d757b8ba2a1fc19cf0c2d600f0742d Mon Sep 17 00:00:00 2001 From: mateusfavarin Date: Sun, 15 Feb 2026 20:41:38 -0800 Subject: [PATCH 9/9] Level render: ignore turbo pad invisible quadblock --- src/level.cpp | 4 +++- src/quadblock.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/level.cpp b/src/level.cpp index 2b7b92d..2bf813f 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -1855,8 +1855,10 @@ void Level::GenerateRenderLevData() size_t triangleOffset = 0; for (Quadblock& qb : m_quadblocks) { - qb.SetRenderPrimitiveIndex(triangleOffset); std::vector qbTriangles = qb.ToGeometry(false); + if (qbTriangles.empty()) { continue; } + + qb.SetRenderPrimitiveIndex(triangleOffset); std::vector qbFilterTriangles = qb.ToGeometry(true); levTriangles.insert(levTriangles.end(), qbTriangles.begin(), qbTriangles.end()); filterTriangles.insert(filterTriangles.end(), qbFilterTriangles.begin(), qbFilterTriangles.end()); diff --git a/src/quadblock.cpp b/src/quadblock.cpp index dedbd7f..c923c89 100644 --- a/src/quadblock.cpp +++ b/src/quadblock.cpp @@ -704,6 +704,8 @@ const BoundingBox& Quadblock::GetBoundingBox() const std::vector Quadblock::ToGeometry(bool filterTriangles, const std::array* overrideUvs, const std::filesystem::path* overrideTexturePath) const { + if (GetHide()) { return std::vector(); } /* Turbo Pads */ + constexpr int NUM_VERTICES_QUAD = 4; constexpr int uvVertInd[NUM_FACES_QUADBLOCK][NUM_VERTICES_QUAD] = {