Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
efa37a7
Remove wage and store weighted normal instead
Sep 18, 2025
cc4a219
Move getAngleWeight to hlsl/shapes/triangle.hlsl
Oct 4, 2025
c018762
Refactor erase to resize
Oct 4, 2025
42ccf48
Remove parentTriangleFaceNormal from SSNGVertexData
Oct 4, 2025
05ee8ed
Refactor getNeighboringCells
Oct 4, 2025
f4b8ac6
Slight improvement for readability
Oct 4, 2025
0dd02e8
Refactor Radix Sorter and use histogram as skip list
Oct 6, 2025
ce99287
Move VertexHashMap into its own file
Oct 7, 2025
28f73f5
Refactor smooth normal generator to use VertexHashGrid and separate w…
Oct 18, 2025
5acb40d
Fix normal comparison to use dot instead of value by value comparison
Oct 18, 2025
0feb4e9
Merge branch 'master' into smooth_normal_calculation
Oct 21, 2025
7424828
Fix triangle.hlsl
Oct 26, 2025
d078b2c
Rename LSBSorter to RadixLSBSorter
Oct 26, 2025
e72e0ef
Add comment for future task
Oct 26, 2025
e91aba1
Refactor CVertexHashGrid
Oct 27, 2025
509b359
Make radix sort more efficient
Oct 27, 2025
a30ef6f
Change type of index in SSNGVertexData
Oct 27, 2025
c580d72
Move CanJoinVertices to CVertexWelder
Oct 27, 2025
5af4a9b
Move SSNGVertexData and VxCmpFunction to CSmoothNormalGenerator
Oct 28, 2025
39e288c
Add comment
Oct 28, 2025
c49d8d1
Add overload for CVertexHashGrid::forEachBroadphaseNeighborCandidates…
Nov 2, 2025
c688f7b
Add inline specifier to a bunch of method
Nov 2, 2025
c521f95
use 0 base indexing for edge and vertex in compInternalAngle
Nov 2, 2025
a7aae4d
Iterate backward when gatherin histogram frequency for better cache l…
Nov 2, 2025
4a5c490
Refactor CVertexWelder to use abstract class for WeldPredicate instea…
Nov 2, 2025
82f9820
Add concept for Vertex Welder AccelerationStructure
Nov 3, 2025
401f1bb
Fix virtual destructor for WeldPredicate
Nov 3, 2025
9b007d2
Return nullptr if vertex contain INVALID_INDEX
Nov 3, 2025
7d31750
Reindent CVertexHashGrid to use tabs
Nov 3, 2025
4699173
Reindent CPolygonGeometryManipulator to use TABS
Nov 3, 2025
e65297b
Add diagram to explain why we choose cellSize to be twice as epsilon
Nov 3, 2025
dbffea5
Improve Diagram
Nov 3, 2025
ad0646e
Improve the explanation of VertexHashGrid
Nov 3, 2025
60ec970
Improve comment
Nov 3, 2025
7ebd7d0
Fix wrong iteration order
Nov 3, 2025
087beb8
Fix compInternalAngle
Nov 3, 2025
6d4b794
Fix method specifier in radix_sort.h
Nov 3, 2025
0cdf7e8
Fix variable and function specifier
Nov 3, 2025
4dbcbeb
Fix the patchedEpsilon
Nov 3, 2025
ff42165
Remove inline specifier for function that is defined in cpp
Nov 3, 2025
94c65b6
Remove forEachBroadphaseNeighborCandidates overload that takes vertex…
Nov 3, 2025
38ef627
Add inline specifier for method in CVertexWelder
Nov 3, 2025
bfba9fa
Refactor class name for SSNGVertexData
Nov 3, 2025
f3d9a19
Add inline specifier for DefaultWeldPredicate constructor and destructor
Nov 3, 2025
f5d38f2
Remove outdated TODO
Nov 3, 2025
cc39901
Slight refactor for SSNGVertexData alias
Nov 3, 2025
1257d4d
Add comment to implement a class template of CVertexHashGrid and CSmo…
Nov 6, 2025
d2503af
Add some assert in isAttributeValEqual and isAttributeDirEqual
Nov 6, 2025
1386fba
Cache channelCount and byte size in init
Nov 6, 2025
ac2ed54
Fix weldVertices
Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions include/nbl/asset/utils/CPolygonGeometryManipulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "nbl/asset/ICPUPolygonGeometry.h"
#include "nbl/asset/utils/CGeometryManipulator.h"
#include "nbl/asset/utils/CVertexHashGrid.h"

namespace nbl::asset
{
Expand All @@ -17,15 +18,31 @@ namespace nbl::asset
class NBL_API2 CPolygonGeometryManipulator
{
public:
//vertex data needed for CSmoothNormalGenerator
struct SSNGVertexData
{
uint32_t index; //offset of the vertex into index buffer
uint32_t hash; //
float wage; //angle wage of the vertex
hlsl::float32_t3 position; //position of the vertex in 3D space
hlsl::float32_t3 parentTriangleFaceNormal; //
};

struct SSNGVertexData
{
uint64_t index; //offset of the vertex into index buffer
uint32_t hash;
hlsl::float32_t3 weightedNormal;
// TODO(kevinyu): Should we separate this from SSNGVertexData, and store it in its own vector in VertexHashGrid? Similar like how hashmap work. Or keep it intrusive?
hlsl::float32_t3 position; //position of the vertex in 3D space
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing #921 (comment)

Is it okay If I just use 64 bit? So we always work with 64 bit normal and position even for 32 bit views? Since the format of position view is known at runtime, there will be a lot of branch down the line if we keep 32 bit and 64 bit hash grid. I think it is reasonable, we store index with 64 bit to reduce branches, so why not do the same for position?

Approved because we use a grid thats tiny, compute a hash function in uint32_t, epsilon is approximate, request approve

you should write a TODO in the comment to make the CVertexHashGrid and CSmoothNormalGenerator templated on the position floating point type (float32_t or float64_t) and we'll make some intern or recruitment candidate implement a dispatch depending on position view precision and implement 64bit hash grids

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should write a TODO in the comment to make the CVertexHashGrid and CSmoothNormalGenerator templated on the position floating point type (float32_t or float64_t) and we'll make some intern or recruitment candidate implement a dispatch depending on position view precision and implement 64bit hash grids

write it all down

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not written down


hlsl::float32_t3 getPosition() const
{
return position;
}

void setHash(uint32_t hash)
{
this->hash = hash;
}

uint32_t getHash() const
{
return hash;
};

};

using VxCmpFunction = std::function<bool(const SSNGVertexData&, const SSNGVertexData&, const ICPUPolygonGeometry*)>;

Expand Down Expand Up @@ -247,7 +264,7 @@ class NBL_API2 CPolygonGeometryManipulator
VxCmpFunction vxcmp = [](const CPolygonGeometryManipulator::SSNGVertexData& v0, const CPolygonGeometryManipulator::SSNGVertexData& v1, const ICPUPolygonGeometry* buffer)
{
static constexpr float cosOf45Deg = 0.70710678118f;
return dot(v0.parentTriangleFaceNormal,v1.parentTriangleFaceNormal) > cosOf45Deg;
return dot(normalize(v0.weightedNormal),normalize(v1.weightedNormal)) > cosOf45Deg;
});

#if 0 // TODO: REDO
Expand Down
212 changes: 212 additions & 0 deletions include/nbl/asset/utils/CVertexHashGrid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#ifndef _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_
#define _NBL_ASSET_C_VERTEX_HASH_MAP_H_INCLUDED_

#include "nbl/core/declarations.h"

namespace nbl::asset
{

template <typename T>
concept HashGridVertexData = requires(T obj, T const cobj, uint32_t hash) {
{ cobj.getHash() } -> std::same_as<uint32_t>;
{ obj.setHash(hash) } -> std::same_as<void>;
{ cobj.getPosition() } -> std::same_as<hlsl::float32_t3>;
};

template <HashGridVertexData VertexData>
class CVertexHashGrid
{
public:

using vertex_data_t = VertexData;
using collection_t = core::vector<VertexData>;
struct BucketBounds
{
collection_t::const_iterator begin;
collection_t::const_iterator end;
};

CVertexHashGrid(size_t _vertexCount, uint32_t _hashTableMaxSize, float _cellSize) :
m_sorter(createSorter(_vertexCount)),
m_hashTableMaxSize(_hashTableMaxSize),
m_cellSize(_cellSize)
{
assert((core::isPoT(m_hashTableMaxSize)));

m_vertices.reserve(_vertexCount);
}

//inserts vertex into hash table
void add(VertexData&& vertex)
{
vertex.setHash(hash(vertex));
m_vertices.push_back(vertex);
}

void validate()
{
const auto oldSize = m_vertices.size();
m_vertices.resize(oldSize*2u);
auto finalSortedOutput = std::visit( [&](auto& sorter) { return sorter(m_vertices.data(), m_vertices.data() + oldSize, oldSize, KeyAccessor()); },m_sorter );

if (finalSortedOutput != m_vertices.data())
m_vertices.erase(m_vertices.begin(), m_vertices.begin() + oldSize);
else
m_vertices.resize(oldSize);
}

const collection_t& vertices() const { return m_vertices; }

collection_t& vertices(){ return m_vertices; }

inline uint32_t getVertexCount() const { return m_vertices.size(); }

template <typename Fn>
void iterateBroadphaseCandidates(const VertexData& vertex, Fn fn) const
{
std::array<uint32_t, 8> neighboringCells;
const auto cellCount = getNeighboringCellHashes(neighboringCells.data(), vertex);

//iterate among all neighboring cells
for (uint8_t i = 0; i < cellCount; i++)
{
const auto& neighborCell = neighboringCells[i];
BucketBounds bounds = getBucketBoundsByHash(neighborCell);
for (; bounds.begin != bounds.end; bounds.begin++)
{
const vertex_data_t& neighborVertex = *bounds.begin;
if (&vertex != &neighborVertex)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho the "not checking against oneself" thing could be done in the fn if we change the iterateBroadphaseCandidates to take a position and not VertexData

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also its fragile because you compare based on pointer address, if you take a copy by accident and you pass the reference to the stack copy it will never trigger

furthermore I don't like the difference in behaviour between the two overloads const VertexData& and const hlsl::float32_t3&

if (!fn(neighborVertex)) break;
}
}

};

private:
struct KeyAccessor
{
_NBL_STATIC_INLINE_CONSTEXPR size_t key_bit_count = 32ull;

template<auto bit_offset, auto radix_mask>
inline decltype(radix_mask) operator()(const VertexData& item) const
{
return static_cast<decltype(radix_mask)>(item.getHash() >> static_cast<uint32_t>(bit_offset)) & radix_mask;
}
};

static constexpr uint32_t primeNumber1 = 73856093;
static constexpr uint32_t primeNumber2 = 19349663;
static constexpr uint32_t primeNumber3 = 83492791;

static constexpr uint32_t invalidHash = 0xFFFFFFFF;

using sorter_t = std::variant<
core::LSBSorter<KeyAccessor::key_bit_count, uint16_t>,
core::LSBSorter<KeyAccessor::key_bit_count, uint32_t>,
core::LSBSorter<KeyAccessor::key_bit_count, size_t>>;
sorter_t m_sorter;

static sorter_t createSorter(size_t vertexCount)
{
if (vertexCount < (0x1ull << 16ull))
return core::LSBSorter<KeyAccessor::key_bit_count, uint16_t>();
if (vertexCount < (0x1ull << 32ull))
return core::LSBSorter<KeyAccessor::key_bit_count, uint32_t>();
return core::LSBSorter<KeyAccessor::key_bit_count, size_t>();
}

collection_t m_vertices;
const uint32_t m_hashTableMaxSize;
const float m_cellSize;

uint32_t hash(const VertexData& vertex) const
{
const hlsl::float32_t3 position = floor(vertex.getPosition() / m_cellSize);

return ((static_cast<uint32_t>(position.x) * primeNumber1) ^
(static_cast<uint32_t>(position.y) * primeNumber2) ^
(static_cast<uint32_t>(position.z) * primeNumber3))& (m_hashTableMaxSize - 1);
}

uint32_t hash(const hlsl::uint32_t3& position) const
{
return ((position.x * primeNumber1) ^
(position.y * primeNumber2) ^
(position.z * primeNumber3))& (m_hashTableMaxSize - 1);
}

uint8_t getNeighboringCellHashes(uint32_t* outNeighbors, const VertexData& vertex) const
{
hlsl::float32_t3 cellfloatcoord = floor(vertex.getPosition() / m_cellSize - hlsl::float32_t3(0.5));
hlsl::uint32_t3 baseCoord = hlsl::uint32_t3(static_cast<uint32_t>(cellfloatcoord.x), static_cast<uint32_t>(cellfloatcoord.y), static_cast<uint32_t>(cellfloatcoord.z));

uint8_t neighborCount = 0;

outNeighbors[neighborCount] = hash(baseCoord);
neighborCount++;

auto addUniqueNeighbor = [&neighborCount, outNeighbors](uint32_t hashval)
{
if (std::find(outNeighbors, outNeighbors + neighborCount, hashval) == outNeighbors + neighborCount)
{
outNeighbors[neighborCount] = hashval;
neighborCount++;
}
};

addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 0, 1)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 1, 0)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 0, 0)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 1, 0)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 0, 1)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(0, 1, 1)));
addUniqueNeighbor(hash(baseCoord + hlsl::uint32_t3(1, 1, 1)));

return neighborCount;
}

BucketBounds getBucketBoundsByHash(uint32_t hash) const
{
if (hash == invalidHash)
return { m_vertices.end(), m_vertices.end() };

const auto skipListBound = std::visit([&](auto& sorter)
{
auto hashBound = sorter.getHashBound(hash);
return std::pair<collection_t::const_iterator, collection_t::const_iterator>(m_vertices.begin() + hashBound.first, m_vertices.begin() + hashBound.second);
}, m_sorter);

auto begin = std::lower_bound(
skipListBound.first,
skipListBound.second,
hash,
[](const VertexData& vertex, uint32_t hash)
{
return vertex.hash < hash;
});

auto end = std::upper_bound(
skipListBound.first,
skipListBound.second,
hash,
[](uint32_t hash, const VertexData& vertex)
{
return hash < vertex.hash;
});

const auto beginIx = begin - m_vertices.begin();
const auto endIx = end - m_vertices.begin();
//bucket missing
if (begin == end)
return { m_vertices.end(), m_vertices.end() };

//bucket missing
if (begin->hash != hash)
return { m_vertices.end(), m_vertices.end() };

return { begin, end };
}
};

}
#endif
Loading
Loading