Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    VariableTag, Resize, and CopyTo

    Cinema 4D SDK
    2024 c++
    3
    18
    2.8k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • D
      d_schmidt
      last edited by

      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

      i_mazlovI 1 Reply Last reply Reply Quote 0
      • i_mazlovI
        i_mazlov @d_schmidt
        last edited by

        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

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • D
          d_schmidt
          last edited by

          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);
          
          bacaB 1 Reply Last reply Reply Quote 0
          • bacaB
            baca @d_schmidt
            last edited by

            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 of GetVirtualObjects(), then Cinema will propagates origin tags onto cache, and then you'll do your tags logic at priority of "Generators" 0+.

            1 Reply Last reply Reply Quote 0
            • D
              d_schmidt
              last edited by

              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

              bacaB 1 Reply Last reply Reply Quote 0
              • bacaB
                baca @d_schmidt
                last edited by

                @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.

                i_mazlovI 1 Reply Last reply Reply Quote 0
                • i_mazlovI
                  i_mazlov @baca
                  last edited by

                  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 Thread

                  Cheers,
                  Ilia

                  MAXON SDK Specialist
                  developers.maxon.net

                  D 1 Reply Last reply Reply Quote 0
                  • D
                    d_schmidt
                    last edited by

                    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: Screenshot 2024-01-11 at 11.56.04 AM.png

                    "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:

                    Screenshot 2024-01-11 at 11.54.49 AM.png

                    Dan

                    1 Reply Last reply Reply Quote 0
                    • D
                      d_schmidt
                      last edited by

                      Does this provide the needed context or would more details be helpful?

                      Dan

                      1 Reply Last reply Reply Quote 0
                      • D
                        d_schmidt @i_mazlov
                        last edited by

                        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).

                        Screenshot 2024-02-05 at 12.09.50 PM.png
                        Screenshot 2024-02-05 at 12.09.55 PM.png

                        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.

                        1 Reply Last reply Reply Quote 0
                        • i_mazlovI
                          i_mazlov
                          last edited by

                          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

                          MAXON SDK Specialist
                          developers.maxon.net

                          i_mazlovI 1 Reply Last reply Reply Quote 0
                          • i_mazlovI
                            i_mazlov @i_mazlov
                            last edited by

                            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

                            MAXON SDK Specialist
                            developers.maxon.net

                            1 Reply Last reply Reply Quote 0
                            • D
                              d_schmidt
                              last edited by

                              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

                              1 Reply Last reply Reply Quote 0
                              • D
                                d_schmidt
                                last edited by

                                @i_mazlov

                                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

                                i_mazlovI 1 Reply Last reply Reply Quote 0
                                • i_mazlovI
                                  i_mazlov @d_schmidt
                                  last edited by

                                  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,
                                  Ilia

                                  Here 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;
                                  }
                                  

                                  MAXON SDK Specialist
                                  developers.maxon.net

                                  1 Reply Last reply Reply Quote 0
                                  • D
                                    d_schmidt
                                    last edited by d_schmidt

                                    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

                                    1 Reply Last reply Reply Quote 0
                                    • i_mazlovI
                                      i_mazlov
                                      last edited by

                                      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

                                      MAXON SDK Specialist
                                      developers.maxon.net

                                      1 Reply Last reply Reply Quote 0
                                      • D
                                        d_schmidt
                                        last edited by

                                        Hi Ilia,

                                        I'm on Mac 13.3 and C4D 2024.0.0 at the moment.

                                        Dan

                                        1 Reply Last reply Reply Quote 0
                                        • First post
                                          Last post