Howto map polygon numbers before - after split



  • Hi,
    I have a PolygonObject and have selected polygons. I then perform a SendModelingCommand with parameter MC_COMMAND_SPLIT to split off the selected polygons as a separate object.
    How do I map the polygon numbers from the original object to the polygon numbers of the split-off object?

    Say, I had selected polygons 63, 65, 67 on the original object. The split-off object has polygons 0, 1, 2. How could I find out which of 0, 1, 2 did result from 63, 65, 67?
    Same for points numbers, how to map the original to the split-off ones?

    I am afraid the answer is: you cannot.
    But maybe someone has an idea I overlooked.



  • There is, of course, always the simplest solution. And that is by comparing the coordinates of points before and after the split to map them ... but is a route I would prefer not to take, if a more clever solution is available.



  • Hi,

    I am not quite sure what you are expecting, but I would propose these two assumptions:

    1. While parametric objects often exhibit regular qualities in the manner their points and polygons are ordered in regards to their topology, the vast majority of polygon objects will not. Meaning that splitting of a single patch/island of polygons might cause multiple consecutive sub-sequences of the point and polygon arrays to be removed. Which in turn renders any simple 'smart' remapping impossible.
    2. Although MAXON might be willing to answer this, the whole question also assumes that the split command uses a reversible - or better consecutive - pattern to place the new points and polygons.

    Aside from that, I do not see another way than the one you described. You could technically store some meaningful geometrical hash-value (like for example the mean value of all points in a polygon, although this would assume non-self-intersecting meshes) in some kind of BSP-Tree (like for example KDTree) and then use your split-result mesh with the same hash-function to retrieve the old polygon and point ids. But I cannot really think of a case where all this overhead could beat going through a few 1,000, 10,000 or even 100,000 polygons manually. So the question would be - how heavy are your meshes?

    Cheers,
    zipit



  • Hello,

    as you seems to know and as @zipit said, there's no solution for that.

    Maybe with a CustomDataTag but it's only available in the last release of Cinema4D (and a bit buggy)

    I'll try to finish my example and post it here.

    Cheers,
    Manuel.



  • @zipit
    Thanks for bringing up the two assumptions, which I agree to.
    I am not actually expecting anything, but I also had an assumption of my own.
    Allow me to explain:
    One selects polygons, creates a polygon selection tag from it. When splitting off part of the original mesh, one can expect that the split off polygons and points have different indexes.
    Yet, the polygon selection tags that are copied along when performing the split have been updated to reflect the new indexing.
    Obviously, the split command will update the tags since it knows the before and after states. As such, I would expect some smart logic need to exist in order to update the split-off tag.
    Hence the reason for my question.

    @m_magalhaes
    Thanks for pointing out the CustomDataTag. I'd love to see a completed example to learn from, but as you mentioned it isn't backwards compatible.

    However, bringing up the tag made me realize another potential solution ... although rather crazy and potentially overkill.
    Hear me out:
    Let's say we create a polygon selection tag for every single polygon of our mesh (hey, I warned you it might get crazy). Then, we name each of these tags with the polygon index they represent. Perform a split.
    The split-off part has all the polygon selection tags ... which have updated polygon indeces. But the name of the tag still refers to the original polygon index. And presto ... we have our mapping.
    Yeah, I know: crazy and overkill. But it's an idea, just like any other.



  • @C4DS

    Hi,

    you are right, technically it is possible that the split command uses a reversible algorithm, but if I would be given the task to write such method, I would not bother implementing it in such way, because it is unnecessarily complicated. Instead I would just create a hash map that maps the old point indices to the new point indices (and an analogous hash map for the polygon indices) and simply recreate / update all data with these hash maps for nodes attached to the new object.

    But since you and @m_magalhaes mentioned tags (and @m_magalhaes is probably going to show the same principle just with the new fancy data tags), I thought of this:

    1. Create a VertexMap tag on your object (you could also use a VertexColor tag to give you more address space).
    2. For each point hash the point index into the VertexMap, do the same for polygon indices. By using a VertexColor tag you could probably get away with just one tag.
    3. Your VariableTag(s) will be copied onto the new object, where you can unhash the data.

    For the hashing:

    1. For a few hundred or thousand point/polygon indices to hash you could probably get away with just dividing your index by the total amount of indices and store it directly as a float. But floating point precision will probably get quickly into your way. Unhasing the data would be a multiplication followed by a cast to an integer type.
    2. The safer approach would be to interpret your index as binary and then convert that binary into a float, which you could then save in your tag. Unhashing would be the opposite operation.

    edit : I overlooked that these tags are clamped to the closed interval [0, 1]. So you will probably need multiple tags to have enough address space, which makes the binary approach less attractive.

    edit2: An alternative could be to pack your data into 32 tags which are either high or low for each data point. You could then interpret these 32 tags as a binary32 integer for each data point. You could probably cut down the number of needed tags significantly by not just storing 1 bit in each data point, but lets say 4 or 8, without floating point precision getting into your way.

    Cheers,
    zipit



  • hi,

    As another workaround you could use the VertexColorTag in polygon mode.
    Transforming the polygonID to a color with alpha, that would lead to a 255x255x255x255 possibilities.

    The split command is able to transfert the VertexColorTag and keep those colors. (and that's available in R19)

    I didn't tried out yet but that should work.

    Cheers,
    Manuel



  • hi,

    sorry for the late reply.

    using a customDataTag works (at least on R22)

    the header for the custom tag (based on the SDK example)

    #ifndef _PC12498_H__
    #define _PC12498_H__
    
    #include "maxon/lib_math.h"
    #include "lib_customdatatag.h"
    
    
    #include "maxon/apibase.h"
    #include "maxon/datatype.h"
    #include "maxon/mesh_attribute_base.h"
    
    
    static const maxon::Int32 ID_POLYGGONID_TAG = 1054949;
    
    namespace maxon
    {
    	//----------------------------------------------------------------------------------------
    	/// Polygon Information
    	/// This class is used to store the polygon ID.
    	/// It have been created because of a bug in the customdatatag and because the data size must be 64bits
    	// This class provides a way to store in a custom data tag an int32 index for each component.
    	// Internally the modeling system uses some bits to define some special behaviours.
    	// For this reason we shift _polygonIndex by 4 bytes using _privateBuffer.
    	//----------------------------------------------------------------------------------------
    
    	class PolygonInformation
    	{
    	public:
    		PolygonInformation() = default;
    
    		MAXON_IMPLICIT PolygonInformation(Int32 index) : _privateBuffer(0), _polygonIndex(index) { }
    
    		PolygonInformation& operator =(const PolygonInformation& src)
    		{
    			_polygonIndex = src._polygonIndex;
    			_privateBuffer = src._privateBuffer;
    			return *this;
    		}
    
    		inline Bool operator ==(const PolygonInformation& other) const
    		{
    			return _polygonIndex == other._polygonIndex;
    		}
    
    		inline Bool operator <(const PolygonInformation& other) const
    		{
    			return _polygonIndex < other._polygonIndex;
    		}
    
    		MAXON_OPERATOR_COMPARISON(PolygonInformation);
    
    		inline String ToString(const FormatStatement* formatStatement) const
    		{
    			return String::IntToString(_polygonIndex);
    		}
    
    		Int32 _privateBuffer = 0; /// not used just here for the size of the class
    		Int32 _polygonIndex = NOTOK;
    
    	};
    
    
    	//----------------------------------------------------------------------------------------
    	/// Declaration of a new DataType.
    	/// This will be store inside the new mesh attribute
    	//----------------------------------------------------------------------------------------
    	MAXON_DATATYPE(PolygonInformation, "net.maxonexample.meshattribute.polygonid");
    	//----------------------------------------------------------------------------------------
    	/// Declaration of the new MeshAttribute
    	//----------------------------------------------------------------------------------------
    	MAXON_MESHATTRIBUTE(PolygonInformation, POLYGONID);
    	//----------------------------------------------------------------------------------------
    	/// Declaration of the new DataTape for the mesh attribute
    	//----------------------------------------------------------------------------------------
    	MAXON_DATATYPE(POLYGONID_MESHATTRIBUTE, "net.maxonexample.meshattribute.polygonidattribute");
    
    
    	namespace CustomDataTagClasses
    	{
    		//----------------------------------------------------------------------------------------
    		/// Declaration of the CustomDataTag class for the polygonID itself
    		//----------------------------------------------------------------------------------------
    		MAXON_DECLARATION(maxon::CustomDataTagClasses::EntryType, POLYGONID, "net.maxonexample.mesh_misc.customdatatagclass.polygonid");
    	} // namespace CustomDataTagClasses
    
    
    }; // namespace maxon
    #endif
    
    

    .cpp for the tag itself

    
    namespace maxon
    {       // Register the datatype
    	MAXON_DATATYPE_REGISTER(PolygonInformation);
    	MAXON_DATATYPE_REGISTER(POLYGONID_MESHATTRIBUTE);
    
            // Tag implementation
    	class PolygroupIDTagImpl : public Component<PolygroupIDTagImpl, CustomDataTagClassInterface>
    	{
    		MAXON_COMPONENT();
    		MAXON_CUSTOMDATATAG(ID_POLYGGONID_TAG, "polygon id"_s, ""_s, CustomDataTagClasses::POLYGONID.GetId(), 0, NOTOK, 0); // notice the last value is 0, this mean the tag is not VISIBLE and not multiple
    
    	public:
    
    		MAXON_METHOD const DataType& GetDataType() const
    		{
    			return maxon::GetDataType<POLYGONID_MESHATTRIBUTE>();
    		}
    
    		// There's no interpolation between the polygons
    		MAXON_METHOD void InterpolateLinear(void* data1, const void* data2, Float blendValue) const {	}
    
    		MAXON_METHOD void InterpolateInOutline(void* data, const Block<void*>& outline, const Block<Float>& weights) const { }
    
    		// default value is 0 (polygon not assigned)
    		MAXON_METHOD void GetDefaultValue(void* data) const
    		{
    			if (!data)
    				return;
    
    			PolygonInformation* v = static_cast<PolygonInformation*>(data);
    			if (v)
    			{
    				(*v)._polygonIndex = -1;
    			}
    		}
    		MAXON_METHOD Bool AtrLessThen(const void* data1, const void* data2) const
    		{
    			if (MAXON_UNLIKELY(data1 == nullptr || data2 == nullptr))
    				return false;
    
    			const PolygonInformation* a = static_cast<const PolygonInformation*>(data1);
    			const PolygonInformation* b = static_cast<const PolygonInformation*>(data2);
    			return a->_polygonIndex < b->_polygonIndex;
    		}
    
    		MAXON_METHOD Bool AtrIsEqual(const void* data1, const void* data2) const
    		{
    			if (MAXON_UNLIKELY(data1 == nullptr || data2 == nullptr))
    				return false;
    
    			const PolygonInformation* a = static_cast<const PolygonInformation*>(data1);
    			const PolygonInformation* b = static_cast<const PolygonInformation*>(data2);
    			return a->_polygonIndex == b->_polygonIndex;
    		}
    
    		MAXON_METHOD void AtrAdd(void* data1, const void* data2) const 
    		{
    			if (MAXON_UNLIKELY(data1 == nullptr || data2 == nullptr))
    				return;
    			PolygonInformation* a = static_cast<PolygonInformation*>(data1);
    			const PolygonInformation* b = static_cast<const PolygonInformation*>(data2);
    			a->_polygonIndex = 42;
    			
    		}
    
    		MAXON_METHOD void AtrSubstract(void* data1, const void* data2) const { }
    
    		MAXON_METHOD void AtrMultiply(void* data1, const void* data2) const { }
    
    		MAXON_METHOD void AtrMultiply(void* data, Float value) const { }
    
    		MAXON_METHOD void AtrDivide(void* data1, const void* data2) const {	}
    
    		MAXON_METHOD void AtrDivide(void* data, Float value) const { }
    
    		MAXON_METHOD String AtrToString(const void* data, const FormatStatement* formatStatement) const
    		{
    			const PolygonInformation* v = static_cast<const PolygonInformation*>(data);
    			return FormatString("(@)", v->_polygonIndex);
    		}
    
    		MAXON_METHOD Result<void> Read(void* data, HyperFile* hf, Int32 level) const
    		{
    			if (!hf || !data)
    				return IllegalArgumentError(MAXON_SOURCE_LOCATION);
    
    			PolygonInformation* dataPtr = static_cast<PolygonInformation*>(data);
    			if (!dataPtr)
    				return OutOfMemoryError(MAXON_SOURCE_LOCATION);
    
    			if (!hf->ReadInt32(&dataPtr->_polygonIndex))
    				return IllegalStateError(MAXON_SOURCE_LOCATION);
    
    			return OK;
    		}
    
    		MAXON_METHOD Bool Message(GeListNode* node, Int32 type, void* data) const
    		{
    			return true;
    		}
    
    		MAXON_METHOD maxon::Result<void> Write(const void* data, HyperFile* hf) const
    		{
    			if (!data || !hf)
    				return IllegalArgumentError(MAXON_SOURCE_LOCATION);
    
    			const PolygonInformation* dataPtr = static_cast<const PolygonInformation*>(data);
    			if (!dataPtr)
    				return OutOfMemoryError(MAXON_SOURCE_LOCATION);
    
    			if (!hf->WriteInt32(dataPtr->_polygonIndex))
    				return IllegalStateError(MAXON_SOURCE_LOCATION);
    
    
    
    			return OK;
    		}
    
    		MAXON_METHOD Int32 GetIcon(Bool perPolyVertex) const
    		{
    			return NOTOK;
    		}
    
    	};
    
    	MAXON_COMPONENT_OBJECT_REGISTER(PolygroupIDTagImpl, CustomDataTagClasses::POLYGONID);
    }// namespace maxon
    
    

    and inside your command you can do something like this. Just create the tag, use the Split command and read back the tag.

    // Creates the customDataTag on the passed object
    static maxon::Result<void> CreateCustomDataTag(BaseObject* op)
    {
    	iferr_scope;
    
    	if (MAXON_UNLIKELY(op == nullptr || !op->IsInstanceOf(Opolygon)))
    		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);
    
    	// Have a look at the SDK example for mor information and comments.
    	const maxon::Int32 polygonCount = ToPoly(op)->GetPolygonCount();
    
    	BaseTag* tagCreated = CustomDataTag::Alloc(ID_POLYGGONID_TAG);
    	if (tagCreated == nullptr)
    		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
    
    	CustomDataTag* polygroupTag = static_cast<CustomDataTag*>(tagCreated);
    	if (polygroupTag == nullptr)
    		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
    	op->InsertTag(polygroupTag);
    
    	polygroupTag->InitData() iferr_return;
    
    
    	polygroupTag->SetMode(CUSTOMDATATAG_MODE::POLYVERTEX);
    	polygroupTag->Resize(polygonCount) iferr_return;
    	polygroupTag->SetName("Polygon IDs"_s);
    
    	for (maxon::Int32 polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
    	{
    		for (maxon::Int32 pointIndex = 0; pointIndex < 4; pointIndex++)
    		{
    			polygroupTag->SetPolyVertexData<maxon::PolygonInformation>(polygonIndex, pointIndex, maxon::PolygonInformation(polygonIndex));
    		}
    	}
    	return maxon::OK;
    }
    
    static maxon::Result<void> pc12498(BaseDocument* doc)
    {
    	iferr_scope;
    
    	if (MAXON_UNLIKELY(doc == nullptr))
    		return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    
    	BaseObject* op = doc->GetActiveObject();
    	if (MAXON_UNLIKELY(op == nullptr || !op->IsInstanceOf(Opolygon)))
    	{
    		MessageDialog("select at least one polygon  object"_s);
    		return maxon::OK;
    	}
    	PolygonObject* obj{ nullptr };
    	if (op->IsInstanceOf(Opolygon))
    		obj = static_cast<PolygonObject*>(op);
    
    	if (MAXON_UNLIKELY(obj == nullptr))
    		return maxon::NullptrError(MAXON_SOURCE_LOCATION);
    
    
    	// Create the custom data tag on the object to store the actual polygon IDs
    	CreateCustomDataTag(op) iferr_return;
    
    
    	// Use the modeling command data to split the polygon, the customData will follow
    	ModelingCommandData mcd;
    	mcd.op = obj;
    	mcd.doc = doc;
    	mcd.mode = MODELINGCOMMANDMODE::POLYGONSELECTION;
    
    
    	if (!SendModelingCommand(MCOMMAND_SPLIT, mcd))
    		return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    
    	PolygonObject* result{ nullptr };
    	result = static_cast<PolygonObject*>(mcd.result->GetIndex(0));
    	if (MAXON_UNLIKELY(result == nullptr))
    		return maxon::NullptrError(MAXON_SOURCE_LOCATION);
    
    	doc->InsertObject(result, nullptr, nullptr);
    
    
    	// Read the customData tag information 
    	BaseTag* basePolygonIDTag = result->GetTag(ID_POLYGGONID_TAG);
    	if (basePolygonIDTag != nullptr)
    	{
    
    		CustomDataTag* polygonIDTag = static_cast<CustomDataTag*>(basePolygonIDTag);
    		if (MAXON_UNLIKELY(polygonIDTag == nullptr))
    			return maxon::UnknownError(MAXON_SOURCE_LOCATION);
    
    		const maxon::Int32 splittedPolyCnt{ result->GetPolygonCount() };
    
    		for (maxon::Int32 polygonIndex = 0; polygonIndex < splittedPolyCnt; polygonIndex++)
    		{
    			const maxon::PolygonInformation value = polygonIDTag->GetPolyVertexData<maxon::PolygonInformation>(polygonIndex, 1);
    			ApplicationOutput("the polygon @ was polygon @", polygonIndex, value);
    		}
    
    
    	}
    
    
    
    	return maxon::OK;
    

    Cheers,
    Manuel



  • @m_magalhaes
    Thanks for the example. I am sure this will be helpful in future, however for now I am still stuck with the classic API to remain backwards compatible.

    While there isn't a readily available solution to the problem, a few options have been mentioned how to handle this. As such I will set the topic as solved.