Relations: BaseObject,RayObject,OPolygon [SOLVED]

On 15/10/2014 at 13:38, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   15 
Platform:   Windows  ;   
Language(s) :     C++  ;

Hi PluginCafe,

here I will try to summarize the object-oriented relations between:
* BaseObject
* RayObject
* PolygonObject (type: OPolygon)
* BaseObject (type: OSphere)
because they keep confusing me.

The documentation lacks a clear overview of such relations and my plugin might heavily rely on assumptions that could be wrong in practice which could lead to undefined behavior and bugs that are hard to nail down.

Please correct me anywhere when I am making wrong assumptions.

Imagine the following scene of a Null, which is the parent of a Cone, which is the parent of a Cube:
_<_img src="" height="393" width="430" border="0" /_>_

Now I'm writing a VideoPostData plugin and in the Execute method I iterate over all RayObjects that are available in the VolumeData.

From my understanding each RayObject has a link, which seems to be NOT nullptr for Cube and Cone. The link is a BaseObject-pointer and is some sort of special. It has no parents and seems not to be in the "normal" hierarchy of objects. The GetUp() function, for example, returns a nullptr. When this is the case one can use GetCacheParent() which seems to return the "real" object in the hierarchy, visible in C4D. This BaseObject actually has parents etc. so that GetUp(), for example, returns a valid object. In our example it looks like this:
RayObject (Cube) has a link to a BaseObject (Cube) that is somewhat special which has a CacheParent (Cube) that is the object from the hierarchy which then has a "normal" parent Cone, which then has a parent Null.
I try to illustrate the relationship here:

Now I want to find a tag for a given RayObject. I'm interested in the "nearest" tag. So basically it should behave like the material system of C4D, that is: If an object has the tag of interest, get it and return it. If it does not have the specified tag, search upwards in the hierarchy until you find a tag of interest. When you find one, return it (in some sense, the object inherits the tag from one of its parents above). When we reach the top of the hierarchy without finding a tag, return nullptr.
Here is the code:

BaseTag* C4dHelper::findNearestTag(BaseObject* pBaseObject, Int32 tagId)
    BaseObject* pCurrent = pBaseObject;
    while (pCurrent != nullptr)
        BaseTag* pBaseTag = pCurrent->GetTag(tagId);
        if (pBaseTag != nullptr)
            return pBaseTag;
        if (pCurrent->GetUp() != nullptr)
            pCurrent = pCurrent->GetUp();
            pCurrent = pCurrent->GetCacheParent();
    return nullptr;

This code works great, at least I think so, because all my knowledge is gathered by try-and-error.

When I want to do the same with a hierarchy that looks like this:
<_<_img src="" height="304" width="321" border="0" /_>_" />

... everything seems to change because the sphere is of type O_SPHERE (in the RayObject) and of type Osphere (in the BaseObject). My guess is, that spheres can be handled extermely fast in a raytracer, so this might be an optimization for such objects. Therefore, no vertex, normal, uv buffers are created and the Object is not a PolygonObject, but some "other strange thing".
Again, here is an attempt to visualize the relationship:

So how would I retrieve the "nearest tag" now? Where can I find a connection between a RayObject (that is a sphere) and its "real" object in the hierarchy?

What I am additionally doing is accessing the polygon-buffers for vertices, normals and uv-coordinates from the "linked" BaseObjects. So I do not extract the buffers from the RayObject, because I had problems working with the RayPolygons. My code that extracts the vertex, index, normal and uv-buffers from a PolygonObject works great so far, so I just fed it the RayObject->link and it worked.
But how would I extract those buffers from a sphere?

My hacky attempt would be this:
1. handle all RayObjects of type O_POLYGON as I described above
2. don't handle RayObject of type O_SPHERE, O_SKY and O_FLOOR at all
3. go through the scene hierarchy and test all objects for the type OSphere
4. Create copies of the BaseObjects that have type OSphere and convert them to editable objects
5. The spheres are now PolygonObjects and can be processed as always

This approach just doesn't feel right. Has anyone a better solution?

I have the strong feeling, that I am doing something really wrong, or that I have misunderstood very important concepts in C4D.

And if you have read this far, you could even provide me with hints of extracting tangents as well ;-)

Thank you all for your time, this post might take some time to respond to^^

On 15/10/2014 at 15:06, xxxxxxxx wrote:

Have you tried using GetParent() on the sphere instead of GetCacheParent() ?

On 15/10/2014 at 23:28, xxxxxxxx wrote:

GetParent() is a method of GeListHead, which is another branch in the inheritance tree:

So BaseObject does not have GetParent()

On 16/10/2014 at 00:48, xxxxxxxx wrote:

Uups, I mean GetUp(). :)

On 16/10/2014 at 01:18, xxxxxxxx wrote:

Ah ok. Yes but it returns only a nullptr. See the second blue block in my diagram, where it says:
"This is not in the object hierarchy. It has no parents, so GetUp() returns nullptr"

On 16/10/2014 at 02:00, xxxxxxxx wrote:

Gosh, sorry for not reading it! :O Well then I don't really know.. :/

On 16/10/2014 at 19:33, xxxxxxxx wrote:

Hi FrozenTarzan,

I can tell you almost have the full picture, but are fuzzy with the various relationships between the class hierarchies and the type values across the various objects.  I agree it's a bit confusing, it took me some time to sort it out.  Here's what I found:

- PolygonObject is a child class of PointObject, which is a child class of BaseObject.  Spheres are instantiated as BaseObjects, and so do not contain any PolygonObject data.  This affects the RayObjects in the same way, that is, in terms of not having PolygonObject data when they are of sphere type.

- RayObject is a struct that can be instantiated and does not inherit from anything.  Its data members are used or ignored depending on on how its data member called 'type' is set.  If it's of type polygon, several of its pointer data members are used to access data needed to represent it.  If it's of type sphere, the pointer data members only required for polygons are set to null because they are not needed.  That is, for some reason, there are no 'RayBaseObject' / 'RayPolygonObject' classes to mirror the BaseObject / PolygonObject relationship.

In your example:

Cone and Cube are instantiated PolygonObject objects, and Null is an instantiated BaseObject.  Therefore, the RayObjects for Cone and Cube are set to the polygon type, and the RayObjects point to PolygonObjects that contain the required data to represent them (polymorphism is used through BaseObject pointers).  You happen to be able to use GetUp() and GetCacheParent(), simply because the PolygonObjects exist at all.

As for the Sphere, it is represented as a BaseObject and its RayObject equivalent is set to type sphere, which does not point to any PolygonObjects because it does not need any polygon data, only the RayObject::mp to represent the center of the sphere, and its radius, represented by RayObject::rad.x and RayObject::rad.y.  It's not really an "other strange thing", as what is really the 'oddball' RayObject is the polygon type, because it's the only type that needs to point to PolygonObject data, unlike sphere, sky, and floor.  It's probably not an optimization, but simply a consequence of polygons requiring more data than the other types.

Therefore, if you absolutely need to retrieve tags, or do anything else that requires your sphere to have PolygonObject data representing it to get the connection, then you most likely have to convert it to a polygon, as you state.  Otherwise, there's nothing to make the connection you get with polygon type objects, because the data that creates the connection simply doesn't exist for spheres.  Perhaps this clarifies how you have to conceptualize these various objects, and you'll find a different solution that is less 'hacky', as you state. :)

I hope that helps!


On 17/10/2014 at 04:07, xxxxxxxx wrote:

Hey Joey,

thanks for the clarifications! I will try to implement my "hacky" approach.

My own raytracer also handled spheres in another fashion, because the math to compute a ray/sphere intersection is almost as complicated as to compute a ray/triangle intersection. So to get a smooth sphere one would require a huge amount of polygons/triangles, which is of course a great impact for performance.

Regarding the tangents of polygon objects I found this thread:

So I will compute them by my own, using this source:

If I have problems with cloning the Spheres (BaseObjects) and making them editable, I will report back to you ;-)

On 17/10/2014 at 04:35, xxxxxxxx wrote:

Hi FrozenTarzan,

I'm doing my own raytracer too , if you need any help in raytracing don't hesitate to ask

On 17/10/2014 at 06:14, xxxxxxxx wrote:

Hi FrozenTarzan,

I'm glad that cleared things up for you.  I know it means there isn't an easy way out, but you were already on the right track.  Sure, let us know if you have any issues, I know turning a sphere into a polygon probably means a lot more data is created than otherwise would have been necessary.

Thanks for replying back,

Joey Gaspe
SDK Support

On 17/10/2014 at 11:55, xxxxxxxx wrote:

It took me only a few minutes to run into a problem^^

When a user places a sphere into the scene, then this sphere is a parametric one (not editable). This sphere is treated specially, as we discussed earlier and is of type O_SPHERE in the raytracer. So my plan was to ignore it and handle spheres on my own by cloning them and making them editable, so in some sense bake everything so that I can access tags, vertices and so on. This would work, but when the user adds deformers to the spheres, the raytracer (of course) has to use polygons again, and so they disappear as RayObjects of type O_SPHERE and are RayObjects of type O_POLYGON.

Another fantastic image to make it clear ;-)

So it's simple: When I disable the deformers, the spheres are O_SPHERE and when I enable them they are O_POLYGON.

This behavior is really understandable, but I must find a way to work around it. Everything would be solved if I could get a link to the original object in the hierarchy. Then I would know that a O_POLYGON was originally a sphere, so I could tell my own algorithm that it should not export the sphere (by cloning, baking,...) because it already was transformed by C4D for me.

Does anyone have an idea to get such link? The link is, as discussed, always nullptr for O_SPHERE RayObjects. I really don't know why. It would be great if a developer could name a reason for this.

On 17/10/2014 at 12:59, xxxxxxxx wrote:

I'm not sure where is the problem, but from what you say:
if it has active deformers, then the type will be O_POLYGON, and if it hasn't then the type is O_SPHERE

what about dynamic casting? if it is O_POLYGON then pass it, if it is O_SPHERE then process it with your own bake methods

On 17/10/2014 at 21:17, xxxxxxxx wrote:

Hmm... I don't want to be mean, but from what I wrote in the whole thread, it should be clear, that there is no way to dynamic cast a RayObject to anything else, because there is no inheritance hierarchy.

And as we discussed earlier, it is not possible to get to the polygons of a O_SPHERE, because it has none, so my plan was to export a polygonized version of the Osphere (!) which is a BaseObject, not a RayObject. The problem is that I don't have the link to the original object, so I cannot tell if a O_POLYGON RayObject was a sphere originally, in which case I would not have to do the cloning thing.

On 17/10/2014 at 21:19, xxxxxxxx wrote:

Oh, and yes I could check the sphere in the object hierarchy for deformers, but I would have to be sure that this is the one and only thing where C4D transforms a parametric sphere into a polyogonized version automatically. There might be tags or other mechanisms that do the same. So if I cannot guarantee that only deformers do that, my code will not be stable in any circumstances.

On 18/10/2014 at 02:57, xxxxxxxx wrote:

well sorry for this :slightly_smiling_face:, wasn't reading the whole thread, now I understand a little more about how they inherit

"So it's simple: When I disable the deformers, the spheres are O_SPHERE and when I enable them they are O_POLYGON.

This behavior is really understandable, but I must find a way to work around it. Everything would be solved if I could get a link to the original object in the hierarchy. Then I would know that a O_POLYGON was originally a sphere, so I could tell my own algorithm that it should not export the sphere (by cloning, baking,...) because it already was transformed by C4D for me."

I sense I don't understand this part, if there is no deformers, then it will be O_SPHERE , where you will handle it by your own methods, now if there is deformers, then it will be O_POLYGON with all of its data ready for you, I can't figure out where is the problem here "may be I'm missing something"

On 18/10/2014 at 03:08, xxxxxxxx wrote:

unless you want to process the O_SPHERE as triangles, and from this thread I can see that Cinema 4D won't give you this data

a possible solution, is to compare all O_SPHERE objects with the object hierarchy "spheres" transformation matrix 4x4 + radius, so you can guarantee that they are exactly the same object (but this may fail if you have multiple spheres with the same radius at the same location, where they have different types "e.g. Tetrahedron, ..." but this seems to be a near impossible case)

another solution (which would be much better!!) , is:loop over all spheres on object hierarchy, and turn off "render perfect" , I think this should force them to be O_POLYGON

On 19/10/2014 at 12:43, xxxxxxxx wrote:

Regarding the deformers, I posted my thoughts above:
"Oh, and yes I could check the sphere in the object hierarchy for deformers, but I would have to be sure that this is the one and only thing where C4D transforms a parametric sphere into a polyogonized version automatically." ;-)

The hint about "render perfect" sounds promising. I will try that tomorrow! :-)

On 20/10/2014 at 05:38, xxxxxxxx wrote:

Hey Mohamed, thanks a lot, it works! :-)

Here's the code I use now to treat spheres as PolygonObjects. When "render perfect" is turned off, they are all converted to O_POLYGON RayObjects, so the rest of my code works as is :-)

// for each BaseObject in hierarchy:
if (pObject->GetType() == Osphere)
    BaseContainer* baseContainer = pObject->GetDataInstance();
    if (baseContainer != nullptr)
        bool renderPerfect = baseContainer->GetBool(PRIM_SPHERE_PERFECT);
        if (renderPerfect)
            GePrint("Parametric sphere found! Turing off \"render perfect\"!");
            baseContainer->SetBool(PRIM_SPHERE_PERFECT, false);

Thanks again, this made my day!

On 20/10/2014 at 06:02, xxxxxxxx wrote:

glad that it worked :wink: , but when do you call this function? "I think I will face the same problem at some point in my render engine, and I need to call the function before Cinema 4D data filling"

On 21/10/2014 at 03:14, xxxxxxxx wrote:

I call it in the Execute function of my VideoPostData plugin:

RENDERRESULT MyVideoPostDataPlugin::Execute(BaseVideoPost* node, VideoPostStruct* vps)
{ if (vps->error == nullptr || *vps->error != RENDERRESULT_OK || vps->thread->TestBreak()) { return RENDERRESULT_OK; } if (vps->vp == VIDEOPOSTCALL_FRAMESEQUENCE && vps->open) { C4dHelper::foreachObjectInHierarchy(vps->doc, vps->doc->GetFirstObject(), [&](BaseObject* pObject) -> bool { if (pObject->GetType() != Osphere) return true; BaseContainer* baseContainer = pObject->GetDataInstance(); if (baseContainer == nullptr) return true; if (!baseContainer->GetBool(PRIM_SPHERE_PERFECT)) return true; GePrint("Parametric sphere found! Turing off \"render perfect\"!"); baseContainer->SetBool(PRIM_SPHERE_PERFECT, false); return true; }); return RENDERRESULT_OK; } if (vps->vp == VIDEOPOSTCALL_INNER && vps->open) { // RayObjects of type O_SPHERE should not have been created now, // instead they are of type O_POLYGON return RENDERRESULT::RENDERRESULT_OK; } return RENDERRESULT::RENDERRESULT_OK; }