|
| 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 |
0 commit comments