SceneHook and object caches

  • Hi,
    I have a SceneHook which I use to draw some information related to Opolygon objects in the scene.
    Up to now, I was only looking at the "original" objects in de scene, but am now looking how to add support for deformed objects.
    I have looked through the documentation and found BaseObject::GetCache, GetDeformedCache, and cannot really wrap my head around the explanation.
    Would there be any example available that demonstrates how to access the cache?

    There are different scenarios I have in mind:

    Scenario 1:
    I iterate over all objects in a scene, and want to collect all deformed objects' cache, ignoring any other object, including the original input object for the deformation/SDS

    	BaseObject* obj = doc->GetFirstObject();
    	while (obj)
    		if (obj->IsInstanceOf(Opolygon))
    			// ignore
    			maxon::BaseArray<BaseObject*> deformed;
    			DoRecursion(obj, deformed);
    			for (const auto& defobj : deformed)
    				// process these
    		// get next object
    		if (obj->GetDown())
    			obj = obj->GetDown();
    			while (!obj->GetNext() && obj->GetUp())
    				obj = obj->GetUp();
    			obj = obj->GetNext();
    void DoRecursion(BaseObject *op, maxon::BaseArray<BaseObject*>& deformed)
    	BaseObject* tp = op->GetDeformCache();
    	if (tp)
    		DoRecursion(tp, deformed);
    		tp = op->GetCache(nullptr);
    		if (tp)
    			DoRecursion(tp, deformed);
    			if (!op->GetBit(BIT_CONTROLOBJECT))
    				if (op->IsInstanceOf(Opolygon))
    					// --------------------------  
    					// --------------------------  
    					{ return; }
    	for (tp = op->GetDown(); tp; tp = tp->GetNext())
    		DoRecursion(tp, deformed);

    This seems to work, except that I ignore any polygon object which is not an input of a deform/SDS.
    So, how to distinguish between a polygon object which is an input and one which isn't an input for deform/SDS?

    Scenario 2:
    Assume I want to iterate over each object in the scene, and want to collect the pair of polygon object and it's deformed cache. This in order to draw a line form each of the deformed points to the original points of the object.
    How to find back the relation "original object" <-> "deformed object".

    Thanks in advance,

  • Hi @C4DS,

    thank you for reaching out to us. I had some trouble understanding the finer details of your question. Your DoRecursion method looks correct to me. One thing noteworthy pointing out for a SceneHook context is that one does not have the guarantee that the caches always have been built or are up-to-date, depending on where and when the SceneHook does intercept.

    What I do not quite understand is why you do bundle up deformer and SDS ("... including the original input object for the deformation/SDS ...", I assume SDS does stand for Subdivision Surfaces), since an SDS-object does not write its output to the deformed cache but the cache. Generally speaking, caches can be quite complex in Cinema, since you often use generators. And in this case the deformed caches can be buried deep within the cache of the generator. You can use either the C++ SDK examples ActiveObject plugin to get a better sense of the full complexity of Cinema's scene graph:


    or use a little script I did post here, which only does focus on printing the expanded BaseObject scene graph tree with caches to the console, which can be a bit easier to read on large caches.

    This seems to work, except that I ignore any polygon object which is not an input of a deform/SDS. So, how to distinguish between a polygon object which is an input and one which isn't an input for deform/SDS?

    Well, you use BIT_CONTROLOBJECT like you do in your code. It will be set by ObjectData nodes (registered with the correct flag) on the object they govern or are governed by. Generators will mark their input objects and themselves in this fashion and deformers will not mark themselves but the objects they deform. So for this scenario here:


    The expanded tree of the top null would look like this (the output is from my script):

    Null/Null (BIT_CONTROLOBJECT: False)
    	Null/Null returns 'None' for GetCache.
    	Null/Null returns 'None' for GetDeformCache.
    	children of Null/Null:
    		Polygon/Cube (BIT_CONTROLOBJECT: True)
    			Polygon/Cube returns 'None' for GetCache.
    			Polygon/Cube returns for GetDeformCache:
                                    # This is Polygon/Cube deformed by the deformer Bend/Bend.
    				Polygon/Cube (BIT_CONTROLOBJECT: False)
    					Polygon/Cube returns 'None' for GetCache.
    					Polygon/Cube returns 'None' for GetDeformCache.
    					Polygon/Cube has no children.
    			Polygon/Cube has no children.
    		Bend/Bend (BIT_CONTROLOBJECT: False)
    			Bend/Bend returns 'None' for GetCache.
    			Bend/Bend returns 'None' for GetDeformCache.
    			Bend/Bend has no children.

    This will get even more complex once you do include generators.

    How to find back the relation "original object" <-> "deformed object".

    It depends on what you would consider to be a "original object". Raw polygon objects, i.e. nodes of type Opolygon, will always directly hold their deformed cache, even if the deformer is not a direct child of them (see previous example) or when you have multiple consecutive levels of deformation. For generators this is not so easy, and I would say it is a bit a matter of opinion there what you would consider to be the original and what not. The important point here is that deformed caches then will be buried within the cache of the generator, since only editable point objects are processed for deformers (i.e., something that is hidden in the cache for a generator).

    As already stated, I am not quite sure what you are asking exactly for. So I hope this helps a bit. If I missed your point, I would kindly ask you to explain again what your goals are.


  • @ferdinand
    Thanks for the info.

    I didn't know what I was doing, and I didn't quite get what the documentation was saying.
    Now at least I understand that GetCache is related to generators, and GetDeformCache is related to deformers.
    That wasn't quite clear at all from the docs ... at least to me.

    Also, a big thank you for that script to walk the cache. Quite useful.

    I now think to have the basics to proceed.

  • @ferdinand
    I have been playing with your script to walk the cache of different hierarchy scenarios I have built.
    I now have a better understanding of what to expect from the GetCache and GetDeformCache.
    However, I am still missing one bit of information.

    For purpose of explanation, let's assume the main idea is to draw a line from the first point of an object to the first point of the "modified" object.
    Where modification is done via a deformer or subdivision surface, or combination of both.
    To draw that line I thus need the coordinates of the "original" object, and the coordinates of the modified one.


    + Sphere (active)
      + Twist

    Assume I have a polygon object "Sphere", and a Twist deformer as child.
    I can get the deformcache from Sphere, perform a DoRecursion to obtain the deformed polygon object, and access its points.


    + Subdivision Surface
       + Cube (active)

    Here I get the cache of the subdivision surface and perform a DoRecursion to get obtain the "deformed" polygon object. Using the term "deformed object" might be a bit misleading, so let's use "generated object" instead.

    In both scenarios I could then draw the line from the original coordinate to the modified coordinate.

    Now, let's assume I have an option (checkbox):

    • when checkbox on, I want to only draw the line when the object is active (=selected in OM)
    • when checkbox off, the line is always drawn, independent of the active state of the object

    In both scenarios the BIT_ACTIVE flag is only set on the original object. In both cache as in deformcache the flag is always false.
    However, in case of scenario 1, I can perform a GetCacheParent on the obtained modified object, which will give me the original object, from which I can check the BIT_ACTIVE.
    In case of scenario 2, performing a GetCacheParent will get me the Subdivision Surface.

    How to have a common solution to find out if the original object of a cached object (from GetCache or from GetDeformCache) is active?

  • Hi @C4DS,

    well, in scenario two the sds IS your cache parent, so this is kind of to be expected. I understand your line of thinking, that the Cube is somehow the meat of this of this generator/input object pair, but in the end it is just another scenario than a deformer deformed object pair. You said it yourself, deformation is not the right term for SDS.

    Anyways, we will talk about it on Monday and someone else or maybe I have a light-bulb moment then. But for now I do not see a built in functionality to do that (you can of course cobble your own logic together by fetching the control-objects, but this probably going to be a bit messy).

    Have a nice weekend and cheers,

  • @ferdinand said in SceneHook and object caches:

    well, in scenario two the sds IS your cache parent, so this is kind of to be expected

    Yes, indeed. I didn't want to imply that was not the expected cache parent.
    I only meant to say that going that route I wasn't able to reach the input object, to get it's active state.

    Correct, building my own logic will quickly turn into something messy, but I am afraid there isn't another way.
    I 'll think about it some more ... have a nice weekend.

  • I spent some time looking for a way to obtain the cache of a particular object.
    Not sure if this will work for every possible scenario, but for now I am trying following concept:

    1. start from the selected (input) object
    2. traverse the scene up, doing so I built up a "tree-address" for the object, and keep track of the parent-most Subdivision Surface.
    3. I get the cache of the parent-most SDS, and traverse the cache tree down, using the obtained tree-address to reach the cache object.

    This technique will probably fail when a generator, like cloner/array/... is located in the tree between SDS and input object. Since then the tree address of the input will not match with the tree of the cache.

    The other issue is that when selecting polygons/edges/points on the input object the cache doesn't get rebuilt ... so if the goal is to do something with selected and unselected polygons/edges/points the cache doesn't get updated with the change of selection of the input object.

  • Hi @C4DS,

    so we talked a bit about it and there is unfortunately no way to do this with built in functionalities of the SDK. I do not fully understand your solution in all detail, but you seem to have to come to the right conclusion that this will have its limits for array-like generators where the caches can get very complex. This ambiguity of what is then "original" is ultimately also why Cinema does/can not offer this functionality.

    I cannot tell you anymore than that and depending on what you are trying to do, a general shift of design might be necessary when you run into major problems.


  • @ferdinand said in SceneHook and object caches:

    so we talked a bit about it and there is unfortunately no way to do this ...

    Thanks for having taken the time to further investigate and discuss.

    ... depending on what you are trying to do, a general shift of design might be necessary when you run into major problems.

    I wouldn't mind a shift of design, unfortunately at this point in time I am out of ideas how to proceed.
    Maybe I am too focused on trying to reach my goal using the available cache. If there would be any other possibility to obtain the information I need to use ... I am all ears.

    The goal I am trying to reach might have been lost with all the technical details, so let me recap it with an example here:
    Let's say I have a regular polygon cube with 6 faces, 8 points.
    I put this cube into a subdivision-surface, resulting in the cube being smoothed, modifying the points' coordinates.
    What I would like to achieve is to know the modified coordinates of the 8 points.

    So, traversing the cache of the SDS and getting the particular cached object is one thing ...
    But since the SDS will subdivide the input object, the resulting cube (in the cache) will have more than 6 faces, 8 points.
    Second step is then to find out which original point index matches which point index in the modified object.

    Cinema4D does seem to have a way to know that relation.
    With the example of the cube under an SDS, enable islone editing, switch to point mode and select any of the 8 points of the cube.
    Cinema4D will display to original point as selected, but also the modified point.
    The one point has the original coordinates, the other has the modified coordinates.

    My goal is thus to find a similar way to obtain the modified coordinates of a point.

  • Hi @C4DS,

    sorry, I was probably a bit cryptic when I talked about a "shift in design". The reason why I was so cryptic is that such design decisions formally fall out of the scope of support (see Forum Guidelines: Scope of Support). It is not because we are lazy, but such decisions often come with a certain vagueness and uncertainty which makes is either very hard or impossible to give reliable answers. So please take my following ideas with a grain of salt.

    I would look at it from the standpoint of reducing complexity. The major problems seems to be the ambiguity of caches. One way to circumvent this could be moving from a SceneHook to a ObjectData plugin. The idea would be to write a thin wrapper around Cinema's SDS object. In GVO you could just collapse all the input objects into a single polygon object and then just return that collapsed object as a child of a SDS-object. In Draw you could then just evaluate this very simple cache of your object (the input would already be taken care of due to the collapsing), i.e. you could directly draw your vertex-to-vertex relations. This would of course come with a considerable amount of overhead due to the collapsing.

    Another approach could be to try to introduce tags as markers which will allow the user to guide your plugin what is considered to be the original. I cannot say you anymore than this without doing it myself (which falls out of the scope of support). The small insight would be here that for humans that decision what is the "original" might often be trivial.

    For the SDS/subdivision vertex-to-vertex relations: My first instinct was that Cinema's subdivisions do not change anything about the index order of the source vertices (i.e., the vertices object that is going to be subdivided) due to the unnecessary overhead that would be introduced by inserting the new topological connected points in the index neighborhood of an "old" vertex. A quick test seems to confirm that, both for the SDS object as well as the "subdivide" command. If you want more clarity on this subject, I would have to ask you to open a new topic on that, so that we can keep topics in a single subject order.

    The selected vertices are all of index 8, the object in the middle was the source for the objects on the left and right.

    Thank you for your understanding and cheers,

  • No worries, I didn't want to imply that support should help in design decisions.
    Not having a complete knowledge of every possibility with the current SDK, I was only wondering if other aspect of the SDK could lead to a solution.
    The only problem here is: if one doesn't know what is possible, it's difficult to know what questions to ask.

    Anyway, at this point in time I am giving up.
    The inbuilt isoline editing seemed to indicate a solution is possible, at least natively.
    From the detailed information provided so far, I can only understand there is no solution for 3rd party plugins.

    As for your remark regarding the vertex-to-vertex relation of objects and their subdivided counterpart, you are correct. But that is only the case for vertices. Edge and polygon indeces can obviously not be reused.
    But as you suggested, I will open a different topic to further discuss this - when needed.

    Now, I am not sure what to do with this topic.
    I could set it to "solved", but there actually is no solution.