VariableTag, Resize, and CopyTo
-
Hello, I was looking to return a VertexColorTag along with a generator plugin I'm making. I'm able to do that easily enough when I'm initially creating the tag. Once the tag the exists on my plugin I don't seem to be able to modify the size of the VertexColorTag.
I saw in this: https://developers.maxon.net/forum/topic/13335/is-it-possible-to-resize-a-c4d-variabletag that while a VariableTag can't be resized, I could copy a new larger data set from a new VertexColorTag to my existing one. Is that understanding correct?
I've been struggling to actually get the new(larger) data to be successfully copied over. Would it just be C4DAtom->CopyTo() or a different methodology?
Shortened version of my existing code.
VertexColorTag* existingVertexColor = static_cast<VertexColorTag*>(op->GetTag(Tvertexcolor)); if(vertexColorTag == nullptr) { existingVertexColor = VertexColorTag::Alloc(newCount); op->InsertTag(existingVertexColor); } if(newCount != oldCount) { VertexColorTag* largerVertexColor = VertexColorTag::Alloc(count); //fill in color data largerVertexColor->CopyTo(existingVertexColor, COPYFLAGS::NONE, nullptr); }
Dan
-
Hi Dan,
Could you please elaborate on your use case in a more macroscopic way? What is the situation when you need to adjust the size of the vertex color tag?
Normally, variable tags (vertex color tag in particular) are tied to the geometry data, because otherwise this doesn't make too much sense. (Imagine you have 3 vertices of triangle and vertex color tag containing 5 values). Hence, I assume you're changing the polygon object before doing the the vertex color tag resizing. If that's the case, then you should probably use the Modeling kernel approach for modifying polygon objects, since it properly handles tag dependencies by sending the MSG_TRANSLATE_ messages.
Cheers,
Ilia -
Hi Ilia,
Looking over the Modeling kernel, that seems to be suited for taking in user provided geometry and modifying it, is that right?
In my case I'm creating original geometry to return. I'm building geometry based on attributes on my plugin. The easiest version would be that I'm creating a plane from scratch, with each polygon being made by me, using PolygonObject, GetPolygonW(), and GetPointW(). I'm manually setting all the points and polygons. If I don't return the cache(when it's not dirty) then I'm rebuilding the output from scratch.
I wanted to add color data to my points. Each update I'm creating a new VertexColorTag with the new proper size and using CopyTo() to move the data onto the existing VertexColorTag on my plugin. At the end I'm using CopyTagsTo() to copy the exposed VertexColorTag from the plugin to my returning geometry.
PolygonObject* myGeometry = PolygonObject::Alloc(polygonCount,pointCount); //create geometry VertexColorTag* existingVertexColor = static_cast<VertexColorTag*>(op->GetTag(Tvertexcolor)); if(vertexColorTag == nullptr) { existingVertexColor = VertexColorTag::Alloc(pointCount); op->InsertTag(existingVertexColor); } if(pointCount != oldPointCount) { VertexColorTag* largerVertexColor = VertexColorTag::Alloc(pointCount); largerVertexColor->CopyTo(existingVertexColor, COPYFLAGS::NONE, nullptr); } else { //fill in color data for existingVertexColor } op->CopyTagsTo(myGeometry, true, true, false, nullptr);
-
Hi @d_schmidt, few releases ago Maxon changed the way how vertex maps are applies to the generators.
I think nowadays generators tag data is not taken in respect.
Cinema automatically propagates generators tags onto cache geometry, and evaluates fields from origin tag to each geometry.
If there are no fields — your maps will have values of zero on your cache geo.Maybe you'll need to utilize
AddToExecution()
method to build geometry, return it as result ofGetVirtualObjects()
, then Cinema will propagates origin tags onto cache, and then you'll do your tags logic at priority of "Generators" 0+. -
Hi @baca
So I should be creating my VertexColorTag inside of Execute() and applying it to my Generator plugin rather than the returned geometry? With that change would resizing VertexColorTag no longer be an issue?
Dan
-
@d_schmidt
If this vertex data is going to be utilized by shaders, or deformers, or fields — you don't need to store any data there, your tag will be just a reference, so you'll need to fined derived tag on the cache.If you want to grab data exactly from the generator's tag, then this approach doesn't suit you.
-
Hi Dan,
You can use Modeling kernel for working with geometry in cinema, this includes both adjusting already existing objects and creating them from scratch too, e.g.
BaseObject* ret = BaseObject::Alloc(Opolygon); CheckState(ret); AutoAlloc<Modeling> pKernel; CheckState(pKernel); CheckState(pKernel->InitObject(ret)); maxon::BaseArray<Int32> pointIndices; // Create 3 points for (const Vector& p : { Vector(0, 0, 0), Vector(200, 0, 0), Vector(0, 0, 200) }) { Int32 pIdx = pKernel->AddPoint(ret, p); CheckState(pIdx); pointIndices.Append(pIdx) iferr_return; } // Create a single Ngon consisting of all points we've added Int32 ngonIdx = pKernel->CreateNgon(ret, pointIndices.GetFirst(), (Int32)pointIndices.GetCount()); CheckState(ngonIdx); CheckState(pKernel->Commit());
Regarding the VertexColorTag. You're creating VC tag on the op (the parent object of your cache), then you're creating the VC tag that corresponds to your cache and copy it over to the op, and then you copy everything back to the cache. I'm a little bit missing the point why you need this and what you're actually trying to finally achieve. You're saying
I wanted to add color data to my points
How are you going to use this color data? VertexColorTag works with polygon objects, so having it on the op (the actual object that user sees in c4d) doesn't make too much sense, as it can neither be modified with the painting tool (because it sits on your plugin object, not on polygon object) nor can it be displayed (the same reason).
You can add the VC tag to your cache object (the returning BaseObject* from the GVO function). In this case, once the user runs the "Make Editable" command, he would get your cache object together with your VC tag that he can actually make use of, but in this case I see no reason to insert the VC tag to you op object.
Additionally, as @baca already mentioned, in case you're using vertex map tag it would be zeroed out unless you're using fields, which in turn anyways won't give you any effect until you use them with polygon object. This is why the question is again the same, why do you need it or in other words what are you trying to achieve by using VC tag like this?
Regarding the execution passes. @baca, thank you for mentioning this. You should use GVO (GetVirtualObjects) function for creating your cache. You can also specify additional execution pass (and not only a single one) using AddToExecution() function. More information about priorities:
How do priorities in scenes work?
Scene Execution Pipeline and ThreadCheers,
Ilia -
Hi Ilia, baca
Thanks for the reply.Thanks for the example code! Is there a manual for the Modeling kernel? I took a look in the SDK but didn't see one. Would it always be better to use that approach for creating geometry?
Here's my current setup.
For more context on what I'm trying to achieve, this is my hierarchy:
"My Point Generator" is returning a series of points, a polygon object with only points, no polygons. I'm adding a Vertex Color Tag to the plugin, and copying it to my cache. Right now with the colors being random.
Then "My Point Generator" is being given to the Cloner, for the point locations. The Vertex Color Tag is put in the Field list for the Plain effector and then the Plain Effector is given to the Cloner to pass the color data on.
Right now that is working and resulting in this:Dan
-
Does this provide the needed context or would more details be helpful?
Dan
-
I'm also confused by what you meant here.
@i_mazlov said in VariableTag, Resize, and CopyTo:
How are you going to use this color data? VertexColorTag works with polygon objects, so having it on the op (the actual object that user sees in c4d) doesn't make too much sense, as it can neither be modified with the painting tool (because it sits on your plugin object, not on polygon object) nor can it be displayed (the same reason).
The VertexColorTag data can be displayed in this case. If I have the tag selected the black to white gradient is being displayed in the viewport.
-
Hi @d_schmidt ,
Please excuse the delay, there's currently a deadline I need to meet. Your thread is not forgotten, I'll reply asap.
Cheers,
Ilia -
Hi @d_schmidt ,
Regarding the Modeling kernel, it is up to you what approach better suits your needs. I personally find it easier to work with ngons or when you need to modify your geometry. If you work plainly with tris/quads and do not make topological operations on your geometry, it might be just easier to use the other approach with the GetPolygonR/W() / GetPointR/W().
Regarding documentation, you can find the reference page on our documentation Modeling. You can also check the Edge Cut Tool example in our C++ github repo.
I'm also confused by what you meant here.
I mean there's not much sense of having the vertex color tag only on your geometry cache, because you cannot actually use it. (The only use case I see is to expose it on make editable command). Hence, you need to have vertex color tag on both your cache object as well as on the connected BaseObject object. If your geometry has changed inside the GVO function, this means your vc tags are not in sync anymore (namely, using CopyTagsTo() function wouldn't be enough, because of the point mapping mismatch). This means that you need to rebuild your vertex color tag data.
I see your intention to reuse the already existing data instead of rebuilding everything from scratch. However, you cannot just copy the data from previous vc tag to the new one, as you need to take into account the mapping between point data in the old vc tag and new one. For example, your points changed from [0, 1, 2] to [0, 3, 4, 1, 2]. If you copy over the old vc tag data to a new one, you'd end up in wrong results for all the points except the first one.
In general, you should just create new vertex color tag and populate it the way you need. Plus, you need to have this tag on both cache object (what's returned by GVO) and the connected BaseObject (what's appears in the cinema). I'd suggest doing similar thing to what you showed in the very first posting:
VertexColorTag* opVertexColorTag = static_cast<VertexColorTag*>(op->GetTag(Tvertexcolor)); Int oldCount = -1; if (opVertexColorTag) oldCount = opVertexColorTag->GetDataCount(); if (!opVertexColorTag || newCount != oldCount) { op->KillTag(Tvertexcolor); // how do you handle user-created VC tags? opVertexColorTag = VertexColorTag::Alloc(newCount); op->InsertTag(opVertexColorTag); } // fill in your data to opVertexColorTag op->CopyTagsTo(myGeometry, true, true, false, nullptr);
Cheers,
Ilia -
Hi @i_mazlov
I'm currently using CopyTagsTo(), to copy the tag from the object to the cache. The problem with the methodology is that if I'm linking to the VertexColorTag externally, that deleting it and creating a new tag would break that link.That's where I was left at this point:
PolygonObject* myGeometry = PolygonObject::Alloc(polygonCount,pointCount); //create geometry VertexColorTag* existingVertexColor = static_cast<VertexColorTag*>(op->GetTag(Tvertexcolor)); if(vertexColorTag == nullptr) { existingVertexColor = VertexColorTag::Alloc(pointCount); op->InsertTag(existingVertexColor); } if(pointCount != oldPointCount) { VertexColorTag* largerVertexColor = VertexColorTag::Alloc(pointCount); //fill in color data for the new tag, then copy it to the existing tag largerVertexColor->CopyTo(existingVertexColor, COPYFLAGS::NONE, nullptr); } else { //fill in color data for existingVertexColor } op->CopyTagsTo(myGeometry, true, true, false, nullptr);
The problem I was encountering here is that CopyTo() doesn't seem to be copying the data from the new correct larger tag to the smaller existing one.
Should CopyTo() work here?
Dan
-
From my current testing it seems like CopyTo() 'work', in so far as the data is copied, but it seems to break all existing Links to the existingVertexColor VertexColorTag. Would there be a way to preserve the links?
Dan
-
Hi Dan,
Please excuse the delayed answer.
If the tag is deleted, the corresponding links are updated, there's nothing you can do inside the objectdata plugin to prevent this, this is simply not the plugin's responsibility to manage these links.
The more I look to your use case the more it seems to be something wrong, so I suggest we make a step back and see what's the stumbling block here. Your plugin wants to control the data of the vertex color tag, but not only that, you also need to expose this tag to the c4d, otherwise you won't be able to use it in the field list. Moreover, eventually your plugin changes the underlying geometry and would like to adjust the vertex color data accordingly without breaking the links that were made by the user outside of your cache geometry.
There's no (easy) way to keep the links of the tag to other parts of the scene if you create new tag (unless you want to fall in the rabbit hole of tracking these links manualy using FiledLayer methods), so I checked the ways to adjust the existing data of the variable tag. There's VariableChanged struct that surves for adjusting variable tag data to the new geometry (this is what's happening internally, when the geometry is changed and these changes are propagated to the tags by the modeling kernel).
I assume in your scenario (when you need to keep vc tags in sync: the cache one and the exposed one), you can do the mapping manually. Please check the code snippet below.
Cheers,
IliaHere is the entire GVO function based on RoundedTube example plugin:
BaseObject* RoundedTube::GetVirtualObjects(BaseObject* op, const HierarchyHelp* hh) { BaseObject* opResult = nullptr; iferr_scope_handler { BaseObject::Free(opResult); return BaseObject::Alloc(Onull); }; Bool isDirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS::DATA); if (!isDirty) { op->TouchDependenceList(); DiagnosticOutput("Returning CACHE"); return op->GetCache(); } DiagnosticOutput("Calculating NEW_OBJECT"); BaseContainer* data = op->GetDataInstance(); Float dataRad = ClampValue(data->GetFloat(TUBEOBJECT_RAD, 50.0), 1.0, LIMIT<Float>::MAX); Int32 dataPtsCount = ClampValue(data->GetInt32(TUBEOBJECT_SEG, 3), 3, LIMIT<Int32>::MAX); opResult = BaseObject::Alloc(Opolygon); CheckState(opResult); MAXON_SCOPE // Create geometry { AutoAlloc<Modeling> pKernel; CheckState(pKernel); CheckState(pKernel->InitObject(opResult)); maxon::BaseArray<Int32> pointIndices; // Create points Float radStep = PI2 / dataPtsCount; for (Int idx = 0; idx < dataPtsCount; ++idx) { Vector point(Cos(radStep * idx) * dataRad, Sin(radStep * idx) * dataRad, 0); Int32 pIdx = pKernel->AddPoint(opResult, point); CheckState(pIdx); pointIndices.Append(pIdx) iferr_return; } // Create a single Ngon consisting of all points we've added Int32 ngonIdx = pKernel->CreateNgon(opResult, pointIndices.GetFirst(), (Int32)pointIndices.GetCount()); CheckState(ngonIdx); CheckState(pKernel->Commit()); }; VertexColorTag* existingVCTag = static_cast<VertexColorTag*>(op->GetTag(Tvertexcolor)); // Create VC tag if (!existingVCTag) { existingVCTag = VertexColorTag::Alloc(dataPtsCount); CheckState(existingVCTag); op->InsertTag(existingVCTag); existingVCTag->SetPerPointMode(true); const Float clrStep = 1.0 / dataPtsCount; const VertexColorHandle handle = existingVCTag->GetDataAddressW(); for (Int32 idx = 0; idx < dataPtsCount; ++idx) { const Vector clr = HSVToRGB(Vector(clrStep * idx, 1.0, 1.0)); VertexColorTag::Set(handle, nullptr, nullptr, idx, maxon::ColorA32(clr.GetColor(), 1.0)); } } // VC tag needs adjustments if (existingVCTag->GetDataCount() != dataPtsCount) { DiagnosticOutput("Points count mismatch"); Random rnd; rnd.Init(123); const Int32 oldDataPtsCount = existingVCTag->GetDataCount(); maxon::BaseArray<Int32> map; map.Resize(oldDataPtsCount) iferr_return; for (Int32 idx = 0; idx < oldDataPtsCount; ++idx) map[idx] = idx; // We just keep all the previous points stays as is VariableChanged vc; vc.old_cnt = oldDataPtsCount; vc.new_cnt = dataPtsCount; vc.map = map.GetFirst(); // Notify object tags about the geometry change if (op->Message(MSG_POINTS_CHANGED, &vc)) { // Tags adjusted its data structures, so now we fill the new data in const VertexColorHandle handle = existingVCTag->GetDataAddressW(); for (Int32 idx = vc.old_cnt; idx < vc.new_cnt; ++idx) { const Vector clr = HSVToRGB(Vector(rnd.Get01(), 1.0, 1.0)); VertexColorTag::Set(handle, nullptr, nullptr, idx, maxon::ColorA32(clr.GetColor(), 1.0)); } } } CheckState(op->CopyTagsTo(opResult, true, true, false, nullptr)); return opResult; }
-
Hello, @i_mazlov
You're exactly right about what I'm trying to achieve. Thanks again for the answers.
With your code, placed in RoundedTube, seems to work in that the VertrexColorTag is preserved between updates and I can see the tag changes as it's updated.
I am encountering an issue where if I increasing and decreasing Rotation Segments I'll occasionally get a crash. Sometimes the crash is pointing to this line: "CheckState(pKernel->Commit());" but other times I'm not getting a particular crash location. Do you encounter this issue?
Then, when I implement your fix into my code, everything seems to be working well, until I shrink down the number of points I have. When increasing the number of points it seems like the VertexColorTag is increasing up correctly but when I scale decrease the number of points it will crash occasionally. This isn't tied to the above crash, since I'm not using Modeling in my code. Would any tweaks need to be done to the code to handle shrinking point counts?
Dan
-
Hi Dan,
which C4D version are you using and which platform? I haven't encountered any crashes, but I only checked the functionality itself (without hard testing)
Cheers,
Ilia -
Hi Ilia,
I'm on Mac 13.3 and C4D 2024.0.0 at the moment.
Dan