how to get vertex color tag's data for a specific frame (different with current frame) without modifying current document?
-
Hi,
could you please help me find the recommended way to read vertex color tag's color data at a specific frame (different with current frame) without modifying current document?
I think I should use BaseDocument::GetClone() and change the cloned document's time. However, I didn't find a good way to get the same vertex color tag and the object it belongs from the cloned document.
Could you please give me a help on this?
Thank you! -
Hey @BruceC,
Thank you for reaching out to us. Let's split these effectively two questions into two parts.
Evaluating a BaseDocument at time #t
When you want to know the state of a scene at a time, #t, you can do this by setting the time of the document (
BaseDocument::SetTime
) and then executing its passes (BaseDocument::ExecutePasses
), which is CInema 4D API slang for updating the scene state. This is configurable and can evaluate animations, build caches, and execute expressions, i.e., do the whole scene evaluation. But when you have a very heavy document, executing the passes is of course a not so cheap operation.A more clever solution can be to manually interpolate animations - when that is all you need - via
CCurve.GetValue
. But that only works for animations that defined by keyframes and are decomposable intofloat
values. The former is here not given for vertex colors, as you can only animate them procedurally.This is all further complicated in a real life scenario, as being at frame
t
, then jumping to framet + 10
, and then just executing the passes might not be enough. The reason is because what you to query might be dependent on simulations; and your output will then be wrong when you just execute framet
andt + 10
, instead oft
,t + 1
, ...,t + 10
.So, a preroll function could look like this:
using namespace cinema; static maxon::Result<void> PrerollToTime(BaseDocument* const doc, const BaseTime& time) { iferr_scope; CheckArgument(doc, "doc"_s, "Document is nullptr."_s); const Int32 fps = doc->GetFps(); const Int32 firstFrame = doc->GetTime().GetFrame(fps) + 1; const Int32 lastFrame = time.GetFrame(fps); if (lastFrame < firstFrame - 1) return maxon::IllegalArgumentError( MAXON_SOURCE_LOCATION, "The target time is before the current time."_s); // Loop over all frames we need to reach #t and execute the passes. What to execute for each pass // (caches, animations, expressions) depends on what you want to achieve, and will of course have // an impact on the performance. Note that things likes cache building can have an impact on // animations and expressions, and vice versa. So, when one needs the 'true' scene state, one must // often calculate everything. for (Int32 frame = firstFrame; frame <= lastFrame; ++frame) { doc->SetTime(BaseTime(frame, fps)); // Execute the passes for everything in the main thread (nullptr), use your thread instead when // you have one. if (!doc->ExecutePasses(nullptr, true, true, true, BUILDFLAGS::NONE)) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Failed to execute pass."_s); } // For the last frame we should execute the passes twice, so that the scene can settle. if (!doc->ExecutePasses(nullptr, true, true, true, BUILDFLAGS::NONE)) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Failed to execute pass."_s); // The document is now in the state of #time. return maxon::OK; }
Finding a scene element #e in two scenes
I am a bit surprised that you ask that, as this is sort of the bread and butter for what you are doing. So, I might be misunderstanding you here. If that is so, please clarify what you mean.
The identity of scene elements is expressed in Cinema 4D by GeMarker. Such markers are given when objects are allocated (and by default also when copied) and are a hash of the creating machine's mac address, a timestamp and some secret sauce. An alternative way to retrieve the same data is BaseList2D::FindUniqueID(MAXON_CREATOR_ID) which just returns the raw data of the
GeMarker
obtainable viaBaseList2D::GetMarker
. When you copy scene elements or whole scenes, by default they are given new markers. You can prevent that withCOPYFLAGS::PRIVATE_IDENTMARKER
.Never insert multiple nodes with the same marker into a scene.
An alternative could be to use links, i.e., a
BaseLinks
, for example in aBaseContainer
stored with the document. But that is also just built on top ofGeMarker
.Cheers,
Ferdinand -
Thank you for the reply, @ferdinand,
-
Thanks for the
PrerollToTime()
function! I didn't know that I need toExecutePasses()
for each frame until thelastFrame
.I'm writing a tag plugin that has a parameter which is a link to an existing vertex color tag, and a parameter to allow user specify a frame number. These two parameters work together, so the plugin will get the data from the vertex color tag at the specific frame, and use the data to do the rendering across all frames.
-
1.1 Because the tag plugin depends the linked vertex color tag, so whenever there is a change in the vertex color tag, the plugin needs to update its internal data based on the updated vertex color tag. Currently, I do this in the tag plugin's Execute() function. The tag plugin remembers the vertex color tag's pointer (initialized as
nullptr
) and dirty checksum (byC4DAtom::GetDirty()
, and initialized as 0). SoExecute()
checks if the vertex color tag read from the link parameter changes or the vertex color tag's dirty checksum changes, if it changes, the vertex color tag's data at the specified frame will be read again.However, I found
ExecutePasses()
called inPrerollToTime()
triggers the tag plugin'sExecute()
function (I think this is the cloned tag in the cloned document), andExecute()
function tries to read the linked vertex color tag at the speficied frame again, soPrerollToTime()
->ExecutePasses()
is called again, and triggers a new cloned tag in a new cloned document to run itsExecute()
, and so on.This makes me wonder if I'm not using the correct way to check if the linked vertex color tag's change. Could you please help find out what's C4D's recommended way to handle such case?
-
1.2 According to the sample code
PrerollToTime()
, thelastFrame
(the target frame) must not be earlier than the current scene's time. This means if the scene's current time is later than the frame number (say, user slides the timeline to frame 10, but the frame number parameter is set to 5), there will be no way to get the desired data from the vertex color tag, right? How does C4d handle this kind of situation? Or there is no such situation at all in native C4d?
-
-
To get the same tag from the cloned scene, I could use code like below, but I am wondering if there is an existing API to do this, as I think this is a basic need.
BaseObject* getObjectByMarker(const BaseDocument& doc, const GeMarker& objMarker, BaseObject* start = nullptr) { for (BaseObject* obj = start ? start : doc.GetFirstObject(); obj != nullptr; obj = obj->GetNext()) { if (obj->GetMarker().IsEqual(objMarker)) { return obj; } else { // check children BaseObject* childObj = obj->GetDown(); if (childObj) { BaseObject* found = getObjectByMarker(doc, objMarker, childObj); if (found) { return found; } } } } return nullptr; } BaseTag* getTagByMarker(const BaseDocument& doc, const GeMarker& objMarker, const GeMarker& tagMarker) { BaseObject* obj = getObjectByMarker(doc, objMarker); if (obj) { for (BaseTag* tag = obj->GetFirstTag(); tag; tag = tag->GetNext()) { if (tag->GetMarker().IsEqual(tagMarker)) { return tag; } } } return nullptr; }
-
I found I cannot read the cloned vertex color tag's data from the cloned document, because the owner of the cloned object is not an instance of
Opoint
.
According to the document, to read data from vertex color tag, I need to get the point count of thepolyObject
who owns the vertex color tag.To do that, the
BaseObject
returned fromBaseTag::GetObject()
will need to be converted toPointObject
, thenPointObject::GetPointCount()
will be used to to get the point count.
However, I found if the tag is from a cloned document, theBaseObject
returned fromBaseTag::GetObject()
is not an instance ofOpoint
, so it cannot be converted toPointObject
.I can confirm that the
BaseObject
got from the original tag in the original document can be converted toPointObject
.I try to use the object from the cloned document, because I think the point count might change in a different frame. (The cloned document is at a different time)
BTW, the cloned document is created by
doc->GetClone(COPYFLAGS::PRIVATE_IDENTMARKER, nullptr)
.
Sorry for the long list, please let me know if I need to split above questions to different topics.
Now, all these questions above make me wonder if the usage is designed appropriately.
Maybe from C4D design's point of view, it is not a proper design to let user specify the time (different with the current frame, and even can be a earlier frame) when the tag's data should be used to render all frames?Thank you!
-
-
Hey @BruceC,
uff, that is a lot of points. I'll try to comb through most of it, but I do not have that much time today. Feel free to ask for clarifications where I left out things.
Currently, I do this in the tag plugin's Execute() function. The tag plugin remembers the vertex color tag's pointer (initialized as
nullptr
) and dirty checksum (byC4DAtom::GetDirty()
, and initialized as 0).Never user pointers to long term keep track of scene elements. Something not being
nullptr
does not mean you point to valid data. The node you point to could be long deleted, and then you point to garbage and access attempts will then result in access violations and a crash. Either use aBaseLink
to store a reference to a scene element, or use a weak pointer. I.e., a pointer which becomes invalid when the pointed data is being deleted. You can for example see here how to use amaxon::WeakRawPtr
.However, I found
ExecutePasses()
called inPrerollToTime()
triggers the tag plugin'sExecute()
function (I think this is the cloned tag in the cloned document), andExecute()
function tries to read the linked vertex color tag at the speficied frame again, soPrerollToTime()
->ExecutePasses()
is called again, and triggers a new cloned tag in a new cloned document to run itsExecute()
, and so on.Yes, that is true and a common problem (not only in Cinema 4D). The superficial reason is that my preroll code example also passes
True
for the third argumentexpressions
(effectively API slang for tags). When you would pass therefalse
, your tag would not be executed again. But also all other tags would not be executed (which is likely not what you want). The solution to this can be registering a plugin ID, e.g.,ID_FOO_PLUGIN_IS_COMPUTE_PASS
. When you then clone your document, or execute the passes on an existing document, you write under that ID for example a bool into the data container of the document. Your tagsExecute(tag, ...)
then gets the document fromtag
and checks for that flag being present, to stop what it does that causes the infinite update loop. When operating on a document not owned by you, you would then delete that flag or set it to false after you manually invoked the passes, so that future updates made by Cinema 4D (which owns and operates the document) then take the 'normal' route.The better solution is to generally design things in a manner that such infinite update loops cannot happen in the first place. I do not understand your problem well enough to give more concrete advice here. These scenarios are also not always avoidable.
According to the sample code PrerollToTime(), the lastFrame (the target frame) must not be earlier than the current scene's time.
The preroll function was just an example, not something you must follow to the letter. There is also a little bug I just see now, it should be of course
if (lastFrame < firstFrame - 1)
. But the general idea is, that when you have a scene with simulations, i.e., stuff like Pyro or particles, where the state of the current frame depends on the last frame, you must basically cycle through all the correct scene states to get to that state. You can of course also jump to a previous point, but then you do not have to sim fromNOW
toFUTURE
but from0
toPREVIOUS
.Prerolling is not necessary when the scene does not contain any simulations, e.g., just key framed position animations, all simulations are cached, or you do not care about simulations and their impact on the rest of scene. Given what kind of plugins you do develop, you likely want to preroll.
Just to stress this again: Prerolling can be extremely expensive. When the user has a scene with 300 frames of Pyro simulations worth 30 minutes of computing, and you try to preroll half of it, your preroll function will run for 15 minutes.
What to do here, really depends on the exact case. Generally you try to avoid having to reach into the future or the past, as this is often a big no-no. The most common "pro" solution to that is caching. E.g., that you write yourself a vertex color cache in your case. When you only need to reach into the past, and this only applies in rendering, you could also effectively hide this from the user, as you must have passed and naturally calculated that value before (and therefore need no explicit cache recording).
Will answer the (2) portion on Monday, running a bit out of Friday time right now.
Cheers,
Ferdinand