Hey @d_schmidt yes this is expected, openvdb is sparse with 4 level tile options. Since our volume tool use openVDB we inherit from that.
In the context of OpenVDB and similar data structures, "sparse" refers to a way of efficiently representing and storing data in which only the non-empty or non-default parts are explicitly kept in memory. This is in contrast to dense representations, where every element is stored regardless of whether it holds meaningful information or not.
In a sparse representation like OpenVDB, the data structure is designed to allocate memory only for the parts of the voxel grid that contain relevant data (i.e., active voxels), while ignoring the regions that are empty or hold similar values. This allows for significant memory savings and performance improvements, especially when dealing with large grids where most of the space is empty/similar.
So with that's said the way to retrieve what you expected is with the next code. Note that I've used Multi-Instance for speed reason and not polluting too much the viewport.
constexpr inline Int ConvertVoxelLevelToAmount(TREEVOXELLEVEL level) { switch (level) { case TREEVOXELLEVEL::ROOT: { return 32; } case TREEVOXELLEVEL::TWO: { return 16; } case TREEVOXELLEVEL::THREE: { return 8; } case TREEVOXELLEVEL::LEAF: { return 1; } } static_assert(true, "Unsupported voxel depth"); return 0; } class Command : public CommandData { public: virtual Bool Execute(BaseDocument* doc, GeDialog* parentManager) { iferr_scope_handler { return true; }; BaseObject* object = doc->GetFirstObject(); if (!object) return true; if (!object->IsInstanceOf(Ovolumebuilder)) return true; VolumeBuilder* const volumeBuilder = static_cast<VolumeBuilder*>(object); // get cache BaseObject* const cache = volumeBuilder->GetCache(); if (cache == nullptr) return true; // check for volume object if (!cache->IsInstanceOf(Ovolume)) return true; const VolumeObject* const volumeObject = static_cast<VolumeObject*>(cache); maxon::Volume volume = volumeObject->GetVolume(); maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON> iterator = maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON>::Create() iferr_return; iterator.Init(volume) iferr_return; maxon::Matrix transform = volume.GetGridTransform(); BaseObject* sphere = BaseObject::Alloc(Osphere); BaseContainer* con = sphere->GetDataInstance(); con->SetFloat(PRIM_SPHERE_RAD, 5); doc->InsertObject(sphere, nullptr, nullptr); // create instance object InstanceObject* const instance = InstanceObject::Alloc(); if (instance == nullptr) return true; doc->InsertObject(instance, nullptr, nullptr); instance->SetReferenceObject(sphere) iferr_return; if (!instance->SetParameter(ConstDescID(DescLevel(INSTANCEOBJECT_RENDERINSTANCE_MODE)), INSTANCEOBJECT_RENDERINSTANCE_MODE_MULTIINSTANCE, DESCFLAGS_SET::NONE)) return true; maxon::BaseArray<Matrix> matrices; for (; iterator.IsNotAtEnd(); iterator.StepNext()) { if (iterator.GetValue() < 0.0) continue; const Int voxelAmount = ConvertVoxelLevelToAmount(iterator.GetVoxelLevel()); for (Int32 voxelIndexX = 0; voxelIndexX < voxelAmount; voxelIndexX++) { for (Int32 voxelIndexY = 0; voxelIndexY < voxelAmount; voxelIndexY++) { for (Int32 voxelIndexZ = 0; voxelIndexZ < voxelAmount; voxelIndexZ++) { const maxon::IntVector32 voxelCoord = iterator.GetCoords(); Matrix m; m.off = transform * Vector(voxelCoord.x + voxelIndexX, voxelCoord.y + voxelIndexY, voxelCoord.z + voxelIndexZ); matrices.Append(std::move(m)) iferr_return; } } } } instance->SetInstanceMatrices(matrices) iferr_return; EventAdd(); return dlg.Open(DLG_TYPE::ASYNC, ID_ACTIVEOBJECT, -1, -1, 500, 300); } virtual Int32 GetState(BaseDocument* doc, GeDialog* parentManager) { return CMD_ENABLED; } };Cheers,
Maxime.