Solved VoxelizationInterface

Hi,

I continue my tests on the C4D API with the VoxelizationInterface, and I'm already stucked at the first method :(

MAXON_METHOD Result<Bool> Init (PolygonObject*	polyObject, Int32 voxelResolution, Int32 minResolution, BaseArray< Range< Vector >> & 	polyRanges,
const Vector * 	pointOverride = nullptr, Boolprecise = false )		

// Initializes the voxelization with a polygon object.

Parameters
[in]	polyObject	Pointer to the polygon object to be added to the voxelization.
[in]	voxelResolution	The voxel resolution of the largest dimension.
[in]	minResolution	The minimal resolution of either dimension.
[out]	polyRanges	Calculated bounding boxes for every polygon.
[in]	pointOverride	Override option for the polygon points. If not nullptr, these points will be used for the polygon vertices.
[in]	precise	If true, only the voxels touching a polygon will hold the specific polygon index. If false, all voxels touching the polygon AABB will hold the polygon index.
Returns
True if the voxelization was properly initialized, false otherwise.

Even after reading the description, I can't figure out what are exactly voxelResolution and minResolution, and how to interpret the maxon::range values (i.e min and max values) in an AABB context ?

Can somebody help me to understand this please ?

Thank you !!

hi,

I've used this snippet to transform a mesh to a volume.

And this snippet allow to iterate trough voxel

the part that you have to be careful of are the parameter that you pass to MeshToVolume
specially gridSize, bandWidthInterior, bandWidthExterior.
for the conversionSettings you should not use DISABLE_INTERSECTING_VOXEL_REMOVAL

I've tried to use GetActiveVoxelDim that is an openVDB's function but it doesn't really work as i was expecting even for a cube.

	iferr_scope;

	if (doc == nullptr)
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "document is nullptr"_s);

	// Creates a cube to insert 
	BaseObject* cube = BaseObject::Alloc(Ocube);
	if (cube == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "not able to create a cube"_s);
	
	ModelingCommandData mcd;
	mcd.doc = doc;
	mcd.op = cube;

	// execute the "Current State to Object" modeling command
	if (!SendModelingCommand(MCOMMAND_CURRENTSTATETOOBJECT, mcd))
		return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

	C4DAtom* atom = mcd.result->GetIndex(0);
	BaseObject* const op = static_cast<BaseObject*>(atom);

	PolygonObject* const polyObject = static_cast<PolygonObject*>(op);
	doc->InsertObject(polyObject, nullptr, nullptr);

	

	

	auto PolyToVolume = [](PolygonObject* const poly) -> maxon::Result<maxon::Volume>
	{
		iferr_scope;

		// check polygon object data

		const Vector*   const points = poly->GetPointR();
		const CPolygon* const polys = poly->GetPolygonR();

		const maxon::Bool noPoints = points == nullptr;
		const maxon::Bool noPolys = polys == nullptr;
		if (noPoints || noPolys)
			return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

		// get polygon object data

		const Int32  pointCount = poly->GetPointCount();
		const Int32  polyCount = poly->GetPolygonCount();
		const Matrix objectMatrix = poly->GetMg();


		const maxon::COLLECTION_RESIZE_FLAGS flags = maxon::COLLECTION_RESIZE_FLAGS::ON_GROW_UNINITIALIZED;

		// point array
		maxon::BaseArray<maxon::Vector> volPoints;
		volPoints.Resize(pointCount, flags) iferr_return;

		// copy point data
		for (Int32 pointIndex = 0; pointIndex < pointCount; ++pointIndex)
			volPoints[pointIndex] = objectMatrix * points[pointIndex];

		// polygon array
		maxon::BaseArray<maxon::VolumeConversionPolygon> volPolys;
		volPolys.Resize(polyCount, flags) iferr_return;

		// copy polygon data
		for (Int32 polyIndex = 0; polyIndex < polyCount; polyIndex++)
		{
			volPolys[polyIndex] = *reinterpret_cast<const maxon::VolumeConversionPolygon*>(&polys[polyIndex]);

			if (polys[polyIndex].IsTriangle())
				volPolys[polyIndex].SetTriangle();
		}

		const maxon::ThreadInterface* const thread = maxon::ThreadRef::GetCurrentThread().GetPointer();
		if (thread == nullptr)
			return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

		const maxon::ThreadRef threadRef = const_cast<maxon::ThreadInterface*>(thread);
		maxon::POLYGONCONVERSIONFLAGS conversionSettings = maxon::POLYGONCONVERSIONFLAGS::NONE;
		conversionSettings |= maxon::POLYGONCONVERSIONFLAGS::DISABLE_RENORMALIZATION | maxon::POLYGONCONVERSIONFLAGS::DISABLE_NARROW_BAND_TRIMMING;


		return maxon::VolumeToolsInterface::MeshToVolume(volPoints, volPolys, objectMatrix, 50.0, 0, 0, threadRef, conversionSettings);
	};

	// Use the lambda to transform a mesh to a volume
	const maxon::Volume volumeA = PolyToVolume(polyObject) iferr_return;

	

	// get matrix
	const maxon::Matrix transform = volumeA.GetGridTransform();

	// create iterator
	maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON> iterator = maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON>::Create() iferr_return;
	iterator.Init(volumeA) iferr_return;

	maxon::IntVector32 dim = volumeA.GetActiveVoxelDim();

	
	ApplicationOutput("dimension of voxels @", dim);
	
	// create instance object
	InstanceObject* const instance = InstanceObject::Alloc();
	if (instance == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);

	// insert object into the scene
	doc->InsertObject(instance, nullptr, nullptr);
	instance->SetParameter(INSTANCEOBJECT_RENDERINSTANCE_MODE, INSTANCEOBJECT_RENDERINSTANCE_MODE_MULTIINSTANCE, DESCFLAGS_SET::NONE);

	// prepare matrices and colors
	maxon::BaseArray<Matrix>				 matrices;
	maxon::BaseArray<maxon::Color64> colors;

	for (; iterator.IsNotAtEnd(); iterator.StepNext())
	{
		Float32 value = iterator.GetValue();
		maxon::IntVector32 coord = iterator.GetCoords();

		
		// set position

		Vector pos;
		pos.x = coord.x;
		pos.y = coord.y;
		pos.z = coord.z;
		pos = transform * pos;

		matrices.Append(MatrixMove(pos)) iferr_return;

		
		Vector rgb = Vector(255, 0, 0);
		if (value < 0.1)
			rgb = Vector(0, 255, 0);

		
		colors.Append(maxon::Color64(rgb)) iferr_return;
	}

	// store data in the instance object
	instance->SetInstanceMatrices(matrices) iferr_return;
	instance->SetInstanceColors(colors) iferr_return;
	instance->SetParameter(INSTANCEOBJECT_DRAW_MODE, INSTANCEOBJECT_DRAW_MODE_POINTS, DESCFLAGS_SET::NONE);

	
	

	EventAdd();

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

What do you actually want to achieve?

If you want to convert a polygon object into a volume, there is the VolumeToolsInterface, which has its own manual: Volume Tools Manual.

I don't know anything about VoxelizationInterface.

First of all, thank you for your answer!

Just like I said in my previous post, I want to get more details on the VoxelResolution, and minResolution args.

Here an example of what I did to get bounding boxes of all polygons:

…
// voxelResolution    The voxel resolution of the largest dimension.
// minResolution      The minimal resolution of either dimension.

maxon::BaseArray<maxon::Range< Vector >> polyRanges;
auto result = voxRef.Init(currentObj, 10, 1, polyRanges);
…

This code works fine and polyRange is correctly populated.

As you can see, I set VoxelResultion to 10, it’s an arbitrary choice (Why 10 and not 100 or 1 ? … meh..). In the beginning I thought that this parameter is to define the voxel size, but since It’s an Integer parameter and the calculation time increases by increasing this number, I suppose that VoxelResultion is reacting more like a subdivision or a number of iterations, and in that case, what “Resolution and Dimension” the documentation is referring to?

And the same thing with the minResolution parameter…

On the other hand, the Init method returns a list of maxon::Range elements, each one represents the bounding box of a polygon. But I don’t know how to exploit it.

Example:
If we have a polygon with 3 points A, B and C:

The maxon::range seems to store something like this:

_minValue: { min(A.x, B.x, C.x), min(A.y, B.y, C.y), min(A.z, B.z, C.z) }
_maxValue: { max(A.x, B.x, C.x), max(A.y, B.y, C.y), max(A.z, B.z, C.z) }

So, how to convert this to a classic Bounding Box data ( i.e a center + a radius) just like with GetMp() and GetRad() for an object...or maybe… I misunderstand something :/

Thank you.

Hi,

@NesNes said in VoxelizationInterface:

Just like I said in my previous post, I want to get more details on the VoxelResolution, and minResolution args.

From the wording of the description I would assume that VoxelResolution determines the edge lengths of your voxels, while minResolution is fallback value that overwrites this value. So let's say you have a bounding box of 200 * 100 * 60 and you set the VoxelResolution to 20. Which should give you 10 * 5 * 3 cells. Setting minResolution to 3 would not change anything, but setting it to 5 would give you 10 * 5 * 5 cells and cells that do not have uniform edge lengths.

As you can see, I set VoxelResultion to 10, it’s an arbitrary choice (Why 10 and not 100 or 1 ? … meh..). In the beginning I thought that this parameter is to define the voxel size, but since It’s an Integer parameter and the calculation time increases ...

I do not quite understand your confusion. When you increase the number of bins / cells in a mesh access data structure, it will obviously increase the initialisation costs, but for heavier geometry this will be compensated by faster query times (via GetClosestPoly here).

So, how to convert this to a classic Bounding Box data ( i.e a center + a radius) just like with GetMp() and GetRad() for an object...or maybe… I misunderstand something :/

Range has a GetCenter method. Alternatively you could also calculate the center from the bounds yourself. Your bounding box vector is then the absolute of the difference between a boundary vector and the center vector (e.g. abs(upper - center)). Or, when you do not need the center vector, you could also just calculate (upper - lower) *.5.

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

hi,

without going too much in detail, the resolution will be used like so :

divide the biggest size of the object by the resolution.
for each axis, it does multiply the size by this scalar.
If that is smaller than the minimum resolution, the minimum resolution will be picked.

About the Ranges, i don't see what's the issue, you have a baseArray of ranges for each polygons.

	voxelRef.Init(polyObject, 200, 1, polyRanges, polyObject->GetPointR(), true) iferr_return;

	for (maxon::Range<Vector>& value : polyRanges)
	{
		ApplicationOutput("value of the range is @, center is @, dimension are @", value, value.GetCenter(), value.GetDimension());
	}

Maybe i didn't got what was your issue.

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Thanks to both of you for your answers!

voxelResolution, minResolution : :+1:
maxon::Range: seems I have a lack of sleep :cold_sweat:

Last question (I hope),
Since the "object space" is divided into cells/voxels (based on the voxelResolution), is there any way to loop through all cells or get access to a specific one ?

Thanks again

hello,

None that i know. What was your though behind that question ?

you have GetVoxelRangesFromBoundingBox that can help (and other functions in VoxelizationInterface )

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Hi Manuel,

for instance, I want to create spheres at the voxels' locations and make the ones touching the polygon red and the others gray.

hi,

I've used this snippet to transform a mesh to a volume.

And this snippet allow to iterate trough voxel

the part that you have to be careful of are the parameter that you pass to MeshToVolume
specially gridSize, bandWidthInterior, bandWidthExterior.
for the conversionSettings you should not use DISABLE_INTERSECTING_VOXEL_REMOVAL

I've tried to use GetActiveVoxelDim that is an openVDB's function but it doesn't really work as i was expecting even for a cube.

	iferr_scope;

	if (doc == nullptr)
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "document is nullptr"_s);

	// Creates a cube to insert 
	BaseObject* cube = BaseObject::Alloc(Ocube);
	if (cube == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "not able to create a cube"_s);
	
	ModelingCommandData mcd;
	mcd.doc = doc;
	mcd.op = cube;

	// execute the "Current State to Object" modeling command
	if (!SendModelingCommand(MCOMMAND_CURRENTSTATETOOBJECT, mcd))
		return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

	C4DAtom* atom = mcd.result->GetIndex(0);
	BaseObject* const op = static_cast<BaseObject*>(atom);

	PolygonObject* const polyObject = static_cast<PolygonObject*>(op);
	doc->InsertObject(polyObject, nullptr, nullptr);

	

	

	auto PolyToVolume = [](PolygonObject* const poly) -> maxon::Result<maxon::Volume>
	{
		iferr_scope;

		// check polygon object data

		const Vector*   const points = poly->GetPointR();
		const CPolygon* const polys = poly->GetPolygonR();

		const maxon::Bool noPoints = points == nullptr;
		const maxon::Bool noPolys = polys == nullptr;
		if (noPoints || noPolys)
			return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

		// get polygon object data

		const Int32  pointCount = poly->GetPointCount();
		const Int32  polyCount = poly->GetPolygonCount();
		const Matrix objectMatrix = poly->GetMg();


		const maxon::COLLECTION_RESIZE_FLAGS flags = maxon::COLLECTION_RESIZE_FLAGS::ON_GROW_UNINITIALIZED;

		// point array
		maxon::BaseArray<maxon::Vector> volPoints;
		volPoints.Resize(pointCount, flags) iferr_return;

		// copy point data
		for (Int32 pointIndex = 0; pointIndex < pointCount; ++pointIndex)
			volPoints[pointIndex] = objectMatrix * points[pointIndex];

		// polygon array
		maxon::BaseArray<maxon::VolumeConversionPolygon> volPolys;
		volPolys.Resize(polyCount, flags) iferr_return;

		// copy polygon data
		for (Int32 polyIndex = 0; polyIndex < polyCount; polyIndex++)
		{
			volPolys[polyIndex] = *reinterpret_cast<const maxon::VolumeConversionPolygon*>(&polys[polyIndex]);

			if (polys[polyIndex].IsTriangle())
				volPolys[polyIndex].SetTriangle();
		}

		const maxon::ThreadInterface* const thread = maxon::ThreadRef::GetCurrentThread().GetPointer();
		if (thread == nullptr)
			return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);

		const maxon::ThreadRef threadRef = const_cast<maxon::ThreadInterface*>(thread);
		maxon::POLYGONCONVERSIONFLAGS conversionSettings = maxon::POLYGONCONVERSIONFLAGS::NONE;
		conversionSettings |= maxon::POLYGONCONVERSIONFLAGS::DISABLE_RENORMALIZATION | maxon::POLYGONCONVERSIONFLAGS::DISABLE_NARROW_BAND_TRIMMING;


		return maxon::VolumeToolsInterface::MeshToVolume(volPoints, volPolys, objectMatrix, 50.0, 0, 0, threadRef, conversionSettings);
	};

	// Use the lambda to transform a mesh to a volume
	const maxon::Volume volumeA = PolyToVolume(polyObject) iferr_return;

	

	// get matrix
	const maxon::Matrix transform = volumeA.GetGridTransform();

	// create iterator
	maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON> iterator = maxon::GridIteratorRef<maxon::Float32, maxon::ITERATORTYPE::ON>::Create() iferr_return;
	iterator.Init(volumeA) iferr_return;

	maxon::IntVector32 dim = volumeA.GetActiveVoxelDim();

	
	ApplicationOutput("dimension of voxels @", dim);
	
	// create instance object
	InstanceObject* const instance = InstanceObject::Alloc();
	if (instance == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);

	// insert object into the scene
	doc->InsertObject(instance, nullptr, nullptr);
	instance->SetParameter(INSTANCEOBJECT_RENDERINSTANCE_MODE, INSTANCEOBJECT_RENDERINSTANCE_MODE_MULTIINSTANCE, DESCFLAGS_SET::NONE);

	// prepare matrices and colors
	maxon::BaseArray<Matrix>				 matrices;
	maxon::BaseArray<maxon::Color64> colors;

	for (; iterator.IsNotAtEnd(); iterator.StepNext())
	{
		Float32 value = iterator.GetValue();
		maxon::IntVector32 coord = iterator.GetCoords();

		
		// set position

		Vector pos;
		pos.x = coord.x;
		pos.y = coord.y;
		pos.z = coord.z;
		pos = transform * pos;

		matrices.Append(MatrixMove(pos)) iferr_return;

		
		Vector rgb = Vector(255, 0, 0);
		if (value < 0.1)
			rgb = Vector(0, 255, 0);

		
		colors.Append(maxon::Color64(rgb)) iferr_return;
	}

	// store data in the instance object
	instance->SetInstanceMatrices(matrices) iferr_return;
	instance->SetInstanceColors(colors) iferr_return;
	instance->SetParameter(INSTANCEOBJECT_DRAW_MODE, INSTANCEOBJECT_DRAW_MODE_POINTS, DESCFLAGS_SET::NONE);

	
	

	EventAdd();

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Thank you Manuel !! this is exactly what I was looking for :blush: