Skip to content

Commit 2caed72

Browse files
authored
Merge pull request #1386 from Idclip/feature/replicate
Added PointReplicate for duplicating points and attributes
2 parents a4d4c5f + 3e59ff8 commit 2caed72

File tree

4 files changed

+683
-0
lines changed

4 files changed

+683
-0
lines changed
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
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+
typename PointDataGridT::Ptr
39+
replicate(const PointDataGridT& source,
40+
const Index 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+
typename PointDataGridT::Ptr
60+
replicate(const PointDataGridT& source,
61+
const Index multiplier,
62+
const std::string& scaleAttribute = "",
63+
const std::string& replicationIndex = "");
64+
65+
66+
////////////////////////////////////////
67+
68+
69+
template <typename PointDataGridT>
70+
typename PointDataGridT::Ptr
71+
replicate(const PointDataGridT& source,
72+
const Index 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+
#ifdef __clang__
82+
// Silence incorrect clang warning
83+
_Pragma("clang diagnostic push")
84+
_Pragma("clang diagnostic ignored \"-Wunused-local-typedef\"")
85+
using GetIncrementCB = std::function<Index(const Index)>;
86+
_Pragma("clang diagnostic pop")
87+
#else
88+
using GetIncrementCB = std::function<Index(const Index)>;
89+
#endif
90+
91+
CopyIter(const Index end, const GetIncrementCB& cb)
92+
: mIt(0), mEnd(0), mSource(0), mSourceEnd(end), mCallback(cb) {
93+
mEnd = mCallback(mSource);
94+
}
95+
96+
operator bool() const { return mSource < mSourceEnd; }
97+
98+
CopyIter& operator++()
99+
{
100+
++mIt;
101+
// If we have hit the end for current source idx, increment the source idx
102+
// moving end to the new position for the next source with a non zero
103+
// number of new values
104+
while (mIt >= mEnd) {
105+
++mSource;
106+
if (*this) mEnd += mCallback(mSource);
107+
else return *this;
108+
}
109+
110+
return *this;
111+
}
112+
113+
Index sourceIndex() const { assert(*this); return mSource; }
114+
Index targetIndex() const { assert(*this); return mIt; }
115+
116+
private:
117+
Index mIt, mEnd, mSource;
118+
const Index mSourceEnd;
119+
const GetIncrementCB mCallback;
120+
}; // struct CopyIter
121+
122+
123+
// We want the topology and index values of the leaf nodes, but we
124+
// DON'T want to copy the arrays. This should only shallow copy the
125+
// descriptor and arrays
126+
PointDataGrid::Ptr points = source.deepCopy();
127+
128+
auto iter = source.tree().cbeginLeaf();
129+
if (!iter) return points;
130+
131+
const AttributeSet::Descriptor& sourceDescriptor =
132+
iter->attributeSet().descriptor();
133+
134+
// verify position
135+
136+
const size_t ppos = sourceDescriptor.find("P");
137+
assert(ppos != AttributeSet::INVALID_POS);
138+
139+
// build new dummy attribute set
140+
141+
AttributeSet::Ptr set;
142+
std::vector<size_t> attribsToDrop;
143+
if (!attributes.empty()) {
144+
for (const auto& nameIdxPair : sourceDescriptor.map()) {
145+
if (std::find(attributes.begin(), attributes.end(), nameIdxPair.first) != attributes.end()) continue;
146+
if (nameIdxPair.first == "P") continue;
147+
attribsToDrop.emplace_back(nameIdxPair.second);
148+
}
149+
set.reset(new AttributeSet(iter->attributeSet(), 1));
150+
}
151+
else {
152+
set.reset(new AttributeSet(AttributeSet::Descriptor::create(sourceDescriptor.type(ppos))));
153+
}
154+
155+
if (!replicationIndex.empty()) {
156+
// don't copy replicationIndex attribute if it exists
157+
// as it'll be overwritten and we create it after
158+
const size_t replIdxIdx = sourceDescriptor.find(replicationIndex);
159+
if (replIdxIdx != AttributeSet::INVALID_POS) attribsToDrop.emplace_back(replIdxIdx);
160+
}
161+
set->dropAttributes(attribsToDrop);
162+
163+
// validate replication attributes
164+
165+
size_t replicationIdx = AttributeSet::INVALID_POS;
166+
if (!replicationIndex.empty()) {
167+
set->appendAttribute(replicationIndex, TypedAttributeArray<int32_t>::attributeType());
168+
replicationIdx = set->descriptor().find(replicationIndex);
169+
}
170+
171+
AttributeSet::DescriptorPtr descriptor = set->descriptorPtr();
172+
173+
const size_t scaleIdx = !scaleAttribute.empty() ?
174+
sourceDescriptor.find(scaleAttribute) : AttributeSet::INVALID_POS;
175+
176+
openvdb::tree::LeafManager<const PointDataTree> sourceManager(source.tree());
177+
openvdb::tree::LeafManager<openvdb::points::PointDataTree> manager(points->tree());
178+
179+
manager.foreach(
180+
[&](PointDataTree::LeafNodeType& leaf, size_t pos)
181+
{
182+
using ValueType = PointDataTree::ValueType;
183+
184+
const auto& sourceLeaf = sourceManager.leaf(pos);
185+
// @note This really shoudn't return uint64_t as AttributeArray's size is
186+
// limited to the max of a uint32_t...
187+
assert(sourceLeaf.pointCount() < Index64(std::numeric_limits<Index>::max()));
188+
const Index sourceCount = static_cast<Index>(sourceLeaf.pointCount());
189+
190+
Index uniformMultiplier = multiplier;
191+
AttributeHandle<float>::UniquePtr scaleHandle(nullptr);
192+
bool useScale = scaleIdx != AttributeSet::INVALID_POS;
193+
if (useScale) {
194+
scaleHandle = std::make_unique<AttributeHandle<float>>
195+
(sourceLeaf.constAttributeArray(scaleIdx));
196+
}
197+
// small lambda that returns the amount of points to generate
198+
// based on a scale. Should only be called if useScale is true,
199+
// otherwise the scaleHandle will be reset or null
200+
auto getPointsToGenerate = [&](const Index index) -> Index {
201+
const float scale = std::max(0.0f, scaleHandle->get(index));
202+
return static_cast<Index>
203+
(math::Round(static_cast<float>(multiplier) * scale));
204+
};
205+
206+
// if uniform, update the multiplier and don't bother using the scale attribute
207+
208+
if (useScale && scaleHandle->isUniform()) {
209+
uniformMultiplier = getPointsToGenerate(0);
210+
scaleHandle.reset();
211+
useScale = false;
212+
}
213+
214+
// get the new count and build the new offsets - do this in this loop so we
215+
// don't have to cache the offset vector. Note that the leaf offsets become
216+
// invalid until leaf.replaceAttributeSet is called and should not be used
217+
218+
Index total = 0;
219+
220+
if (useScale) {
221+
for (auto iter = sourceLeaf.cbeginValueAll(); iter; ++iter) {
222+
for (auto piter = sourceLeaf.beginIndexVoxel(iter.getCoord());
223+
piter; ++piter) { total += getPointsToGenerate(*piter); }
224+
leaf.setOffsetOnly(iter.pos(), total);
225+
}
226+
}
227+
else {
228+
total = uniformMultiplier * sourceCount;
229+
230+
// with a uniform multiplier, just multiply each voxel value
231+
auto* data = leaf.buffer().data();
232+
for (size_t i = 0; i < leaf.buffer().size(); ++i) {
233+
const ValueType::IntType value = data[i];
234+
data[i] = value * uniformMultiplier;
235+
}
236+
}
237+
238+
// turn voxels off if no points
239+
leaf.updateValueMask();
240+
const AttributeSet& sourceSet = sourceLeaf.attributeSet();
241+
242+
std::unique_ptr<openvdb::points::AttributeSet> newSet
243+
(new AttributeSet(*set, total));
244+
245+
auto copy = [&](const std::string& name)
246+
{
247+
const auto* sourceArray = sourceSet.getConst(name);
248+
assert(sourceArray);
249+
250+
// manually expand so that copyValues() doesn't expand and fill the array -
251+
// we don't want to unnecessarily zero initialize the target values as we know
252+
// we're going to write to all of them.
253+
auto* array = newSet->get(name);
254+
assert(array);
255+
array->expand(/*fill*/false);
256+
257+
if (useScale) {
258+
const CopyIter iter(sourceCount, [&](const Index i) { return getPointsToGenerate(i); });
259+
array->copyValues(*sourceArray, iter);
260+
}
261+
else {
262+
const CopyIter iter(sourceCount, [&](const Index) { return uniformMultiplier; });
263+
array->copyValues(*sourceArray, iter);
264+
}
265+
};
266+
267+
copy("P");
268+
for (const auto& iter : descriptor->map()) {
269+
if (iter.first == "P") continue;
270+
if (iter.first == replicationIndex) continue;
271+
copy(iter.first);
272+
}
273+
274+
// assign the replication idx if requested
275+
276+
if (replicationIdx != AttributeSet::INVALID_POS && total > 0) {
277+
AttributeWriteHandle<int32_t>
278+
idxHandle(*newSet->get(replicationIdx), /*expand*/false);
279+
idxHandle.expand(/*fill*/false);
280+
assert(idxHandle.size() == total);
281+
282+
283+
Index offset = 0;
284+
285+
if (useScale) {
286+
for (Index i = 0; i < sourceCount; ++i) {
287+
const Index pointRepCount = getPointsToGenerate(i);
288+
for (Index j = 0; j < pointRepCount; ++j) {
289+
idxHandle.set(offset++, j);
290+
}
291+
}
292+
}
293+
else {
294+
while (offset < total) {
295+
for (Index j = 0; j < uniformMultiplier; ++j) {
296+
idxHandle.set(offset++, j);
297+
}
298+
}
299+
}
300+
}
301+
302+
leaf.replaceAttributeSet(newSet.release(), /*mismatch*/true);
303+
});
304+
305+
if (!scaleAttribute.empty()) {
306+
tools::pruneInactive(points->tree());
307+
}
308+
309+
return points;
310+
}
311+
312+
template <typename PointDataGridT>
313+
typename PointDataGridT::Ptr
314+
replicate(const PointDataGridT& source,
315+
const Index multiplier,
316+
const std::string& scaleAttribute,
317+
const std::string& replicationIndex)
318+
{
319+
auto iter = source.tree().cbeginLeaf();
320+
if (!iter) return source.deepCopy();
321+
322+
const openvdb::points::AttributeSet::Descriptor& sourceDescriptor =
323+
iter->attributeSet().descriptor();
324+
325+
std::vector<std::string> attribs;
326+
attribs.reserve(sourceDescriptor.map().size());
327+
for (const auto& namepos : sourceDescriptor.map()) {
328+
attribs.emplace_back(namepos.first);
329+
}
330+
331+
return replicate(source, multiplier, attribs, scaleAttribute, replicationIndex);
332+
}
333+
334+
335+
} // namespace points
336+
} // namespace OPENVDB_VERSION_NAME
337+
} // namespace openvdb
338+
339+
#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
@@ -149,6 +149,7 @@ else()
149149
TestPointRasterizeFrustum.cc
150150
TestPointRasterizeSDF.cc
151151
TestPointRasterizeTrilinear.cc
152+
TestPointReplicate.cc
152153
TestPointSample.cc
153154
TestPointScatter.cc
154155
TestPointStatistics.cc

0 commit comments

Comments
 (0)