Skip to content

Commit f26b2a0

Browse files
committed
Added point replication tools
Signed-off-by: Nick Avramoussis <[email protected]>
1 parent 57bd50e commit f26b2a0

File tree

4 files changed

+669
-0
lines changed

4 files changed

+669
-0
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
// Copyright Contributors to the OpenVDB Project
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
/// @author Nick Avramoussis
5+
///
6+
/// @file PointReplicate.h
7+
///
8+
/// @brief Algorithms to duplicate points in PointDataGrids.
9+
10+
#ifndef OPENVDB_POINTS_POINT_REPLICATE_HAS_BEEN_INCLUDED
11+
#define OPENVDB_POINTS_POINT_REPLICATE_HAS_BEEN_INCLUDED
12+
13+
#include <openvdb/points/PointDataGrid.h>
14+
#include <openvdb/tools/Prune.h>
15+
16+
namespace openvdb {
17+
OPENVDB_USE_VERSION_NAMESPACE
18+
namespace OPENVDB_VERSION_NAME {
19+
namespace points {
20+
21+
22+
/// @brief Replicates points provided in a source grid into a new grid,
23+
/// transfering and creating attributes found in a provided
24+
/// attribute vector. If an attribute doesn't exist, it is ignored.
25+
/// Position is always replicated, leaving the new points exactly
26+
/// over the top of the source points.
27+
/// @note The position attribute must exist
28+
/// @param source The source grid to replicate points from
29+
/// @param multiplier The base number of points to replicate per point
30+
/// @param attributes Attributes to transfer to the new grid
31+
/// @param scaleAttribute A scale float attribute which multiplies the base
32+
/// multiplier to vary the point count per point.
33+
/// @param replicationIndex When provided, creates a replication attribute
34+
/// of the given name which holds the replication
35+
/// index. This can be subsequently used to modify
36+
/// the replicated points as a post process.
37+
template <typename PointDataGridT>
38+
inline typename PointDataGridT::Ptr
39+
replicate(const PointDataGridT& source,
40+
const size_t multiplier,
41+
const std::vector<std::string>& attributes,
42+
const std::string& scaleAttribute = "",
43+
const std::string& replicationIndex = "");
44+
45+
/// @brief Replicates points provided in a source grid into a new grid,
46+
/// transfering and creating all attributes from the source grid.
47+
/// Position is always replicated, leaving the new points exactly
48+
/// over the top of the source points.
49+
/// @note The position attribute must exist
50+
/// @param source The source grid to replicate points from
51+
/// @param multiplier The base number of points to replicate per point
52+
/// @param scaleAttribute A scale float attribute which multiplies the base
53+
/// multiplier to vary the point count per point.
54+
/// @param replicationIndex When provided, creates a replication attribute
55+
/// of the given name which holds the replication
56+
/// index. This can be subsequently used to modify
57+
/// the replicated points as a post process.
58+
template <typename PointDataGridT>
59+
inline typename PointDataGridT::Ptr
60+
replicate(const PointDataGridT& source,
61+
const size_t multiplier,
62+
const std::string& scaleAttribute = "",
63+
const std::string& replicationIndex = "");
64+
65+
66+
////////////////////////////////////////
67+
68+
69+
template <typename PointDataGridT>
70+
inline typename PointDataGridT::Ptr
71+
replicate(const PointDataGridT& source,
72+
const size_t multiplier,
73+
const std::vector<std::string>& attributes,
74+
const std::string& scaleAttribute,
75+
const std::string& replicationIndex)
76+
{
77+
// The copy iterator, used to transfer array values from the source grid
78+
// to the target (replicated grid).
79+
struct CopyIter
80+
{
81+
using GetIncrementCB = std::function<Index(const Index)>;
82+
83+
CopyIter(const Index end, const GetIncrementCB& cb)
84+
: mIt(0), mEnd(0), mSource(0), mSourceEnd(end), mCallback(cb) {
85+
mEnd = mCallback(mSource);
86+
}
87+
88+
operator bool() const { return mSource < mSourceEnd; }
89+
90+
CopyIter& operator++()
91+
{
92+
++mIt;
93+
// If we have hit the end for current source idx, increment the source idx
94+
// moving end to the new position for the next source with a non zero
95+
// number of new values
96+
while (mIt >= mEnd) {
97+
++mSource;
98+
if (*this) mEnd += mCallback(mSource);
99+
else return *this;
100+
}
101+
102+
return *this;
103+
}
104+
105+
Index sourceIndex() const { assert(*this); return mSource; }
106+
Index targetIndex() const { assert(*this); return mIt; }
107+
108+
private:
109+
Index mIt, mEnd, mSource;
110+
const Index mSourceEnd;
111+
const GetIncrementCB& mCallback;
112+
}; // struct CopyIter
113+
114+
115+
// We want the topology and index values of the leaf nodes, but we
116+
// DON'T want to copy the arrays. This should only shallow copy the
117+
// descriptor and arrays
118+
PointDataGrid::Ptr points = source.deepCopy();
119+
120+
auto iter = source.tree().cbeginLeaf();
121+
if (!iter) return points;
122+
123+
const AttributeSet::Descriptor& sourceDescriptor =
124+
iter->attributeSet().descriptor();
125+
126+
// verify position
127+
128+
const size_t ppos = sourceDescriptor.find("P");
129+
assert(ppos != AttributeSet::INVALID_POS);
130+
131+
// build new dummy attribute set
132+
133+
AttributeSet::Ptr set;
134+
std::vector<size_t> attribsToDrop;
135+
if (!attributes.empty()) {
136+
for (const auto& nameIdxPair : sourceDescriptor.map()) {
137+
if (std::find(attributes.begin(), attributes.end(), nameIdxPair.first) != attributes.end()) continue;
138+
if (nameIdxPair.first == "P") continue;
139+
attribsToDrop.emplace_back(nameIdxPair.second);
140+
}
141+
set.reset(new AttributeSet(iter->attributeSet(), 1));
142+
}
143+
else {
144+
set.reset(new AttributeSet(AttributeSet::Descriptor::create(sourceDescriptor.type(ppos))));
145+
}
146+
147+
if (!replicationIndex.empty()) {
148+
// don't copy replicationIndex attribute if it exists
149+
// as it'll be overwritten and we create it after
150+
const size_t replIdxIdx = sourceDescriptor.find(replicationIndex);
151+
if (replIdxIdx != AttributeSet::INVALID_POS) attribsToDrop.emplace_back(replIdxIdx);
152+
}
153+
set->dropAttributes(attribsToDrop);
154+
155+
// validate replication attributes
156+
157+
size_t replicationIdx = AttributeSet::INVALID_POS;
158+
if (!replicationIndex.empty()) {
159+
set->appendAttribute(replicationIndex, TypedAttributeArray<int32_t>::attributeType());
160+
replicationIdx = set->descriptor().find(replicationIndex);
161+
}
162+
163+
AttributeSet::DescriptorPtr descriptor = set->descriptorPtr();
164+
165+
const size_t scaleIdx = !scaleAttribute.empty() ?
166+
sourceDescriptor.find(scaleAttribute) : AttributeSet::INVALID_POS;
167+
168+
openvdb::tree::LeafManager<const PointDataTree> sourceManager(source.tree());
169+
openvdb::tree::LeafManager<openvdb::points::PointDataTree> manager(points->tree());
170+
171+
manager.foreach(
172+
[&](PointDataTree::LeafNodeType& leaf, size_t pos)
173+
{
174+
using ValueType = PointDataTree::ValueType;
175+
176+
const auto& sourceLeaf = sourceManager.leaf(pos);
177+
const openvdb::Index64 sourceCount = sourceLeaf.pointCount();
178+
size_t uniformMultiplier = multiplier;
179+
AttributeHandle<float>::UniquePtr scaleHandle(nullptr);
180+
bool useScale = scaleIdx != AttributeSet::INVALID_POS;
181+
if (useScale) {
182+
scaleHandle = std::make_unique<AttributeHandle<float>>
183+
(sourceLeaf.constAttributeArray(scaleIdx));
184+
}
185+
// small lambda that returns the amount of points to generate
186+
// based on a scale. Should only be called if useScale is true,
187+
// otherwise the scaleHandle will be reset or null
188+
auto getPointsToGenerate = [&](const size_t index) -> size_t {
189+
const float scale = std::max(0.0f, scaleHandle->get(index));
190+
return static_cast<size_t>
191+
(math::Round(static_cast<float>(multiplier) * scale));
192+
};
193+
194+
// if uniform, update the multiplier and don't bother using the scale attribute
195+
196+
if (useScale && scaleHandle->isUniform()) {
197+
uniformMultiplier = getPointsToGenerate(0);
198+
scaleHandle.reset();
199+
useScale = false;
200+
}
201+
202+
// get the new count and build the new offsets - do this in this loop so we
203+
// don't have to cache the offset vector. Note that the leaf offsets become
204+
// invalid until leaf.replaceAttributeSet is called and should not be used
205+
206+
openvdb::Index64 total = 0;
207+
208+
if (useScale) {
209+
for (auto iter = sourceLeaf.cbeginValueAll(); iter; ++iter) {
210+
for (auto piter = sourceLeaf.beginIndexVoxel(iter.getCoord());
211+
piter; ++piter) { total += getPointsToGenerate(*piter); }
212+
leaf.setOffsetOnly(iter.pos(), total);
213+
}
214+
}
215+
else {
216+
total = uniformMultiplier * sourceCount;
217+
218+
// with a uniform multiplier, just multiply each voxel value
219+
auto* data = leaf.buffer().data();
220+
for (size_t i = 0; i < leaf.buffer().size(); ++i) {
221+
const ValueType::IntType value = data[i];
222+
data[i] = value * uniformMultiplier;
223+
}
224+
}
225+
226+
// turn voxels off if no points
227+
leaf.updateValueMask();
228+
const AttributeSet& sourceSet = sourceLeaf.attributeSet();
229+
230+
std::unique_ptr<openvdb::points::AttributeSet> newSet
231+
(new AttributeSet(*set, total));
232+
233+
auto copy = [&](const std::string& name)
234+
{
235+
const auto* sourceArray = sourceSet.getConst(name);
236+
assert(sourceArray);
237+
238+
// manually expand so that copyValues() doesn't expand and fill the array -
239+
// we don't want to unnecessarily zero initialize the target values as we know
240+
// we're going to write to all of them.
241+
auto* array = newSet->get(name);
242+
assert(array);
243+
array->expand(/*fill*/false);
244+
245+
if (useScale) {
246+
const CopyIter iter(sourceCount, [&](const Index i) { return getPointsToGenerate(i); });
247+
array->copyValues(*sourceArray, iter);
248+
}
249+
else {
250+
const CopyIter iter(sourceCount, [&](const Index) { return uniformMultiplier; });
251+
array->copyValues(*sourceArray, iter);
252+
}
253+
};
254+
255+
copy("P");
256+
for (const auto& iter : descriptor->map()) {
257+
if (iter.first == "P") continue;
258+
if (iter.first == replicationIndex) continue;
259+
copy(iter.first);
260+
}
261+
262+
// assign the replication idx if requested
263+
264+
if (replicationIdx != AttributeSet::INVALID_POS && total > 0) {
265+
AttributeWriteHandle<int32_t>
266+
idxHandle(*newSet->get(replicationIdx), /*expand*/false);
267+
idxHandle.expand(/*fill*/false);
268+
assert(idxHandle.size() == total);
269+
270+
if (useScale) {
271+
size_t offset = 0;
272+
for (size_t i = 0; i < sourceCount; ++i) {
273+
const size_t pointRepCount = getPointsToGenerate(i);
274+
for (size_t j = 0; j < pointRepCount; ++j, ++offset) {
275+
idxHandle.set(offset, j);
276+
}
277+
}
278+
}
279+
else {
280+
for (size_t i = 0; i < total;) {
281+
for (size_t j = 0; j < uniformMultiplier; ++j, ++i) {
282+
idxHandle.set(i, j);
283+
}
284+
}
285+
}
286+
}
287+
288+
leaf.replaceAttributeSet(newSet.release(), /*mismatch*/true);
289+
});
290+
291+
if (!scaleAttribute.empty()) {
292+
tools::pruneInactive(points->tree());
293+
}
294+
295+
return points;
296+
}
297+
298+
template <typename PointDataGridT>
299+
inline typename PointDataGridT::Ptr
300+
replicate(const PointDataGridT& source,
301+
const size_t multiplier,
302+
const std::string& scaleAttribute,
303+
const std::string& replicationIndex)
304+
{
305+
auto iter = source.tree().cbeginLeaf();
306+
if (!iter) return source.deepCopy();
307+
308+
const openvdb::points::AttributeSet::Descriptor& sourceDescriptor =
309+
iter->attributeSet().descriptor();
310+
311+
std::vector<std::string> attribs;
312+
attribs.reserve(sourceDescriptor.map().size());
313+
for (const auto& namepos : sourceDescriptor.map()) {
314+
attribs.emplace_back(namepos.first);
315+
}
316+
317+
return replicate(source, multiplier, attribs, scaleAttribute, replicationIndex);
318+
}
319+
320+
321+
} // namespace points
322+
} // namespace OPENVDB_VERSION_NAME
323+
} // namespace openvdb
324+
325+
#endif // OPENVDB_POINTS_POINT_REPLICATE_HAS_BEEN_INCLUDED

openvdb/openvdb/unittest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ else()
148148
TestPointPartitioner.cc
149149
TestPointRasterizeSDF.cc
150150
TestPointRasterizeTrilinear.cc
151+
TestPointReplicate.cc
151152
TestPointSample.cc
152153
TestPointScatter.cc
153154
TestPointStatistics.cc

0 commit comments

Comments
 (0)