Question about BIT_CONTROLOBJECT



  • Hi all,
    The BIT_CONTROLOBJECT flag is set for objects that are used as input objects by a generator. But a generator object will also itself have this flag set.

    Is there any reliable way to distinguish between these two cases?

    Best regards
    /Filip



  • The docs say this:

    "BIT_CONTROLOBJECT: Input objects are marked by a generator with this bit using BaseObject::Touch(). This bit is also set for the generator object itself."

    I want to know if an object is a "proper" input object, i.e., Touch():ed by another generator, or if it is just itself a generator.

    /Filip



  • Hi,

    maybe I am misunderstanding your problem, but I would like to point out that there are actually only very few object types that are not generators. To test if something is a generator you could try to write to ID_BASEOBJECT_GENERATOR_FLAG. Only for generators this can be set to 0. For non-generators that flag is read-only and always 1. Below you will find a Python example, but the vast majority of objects will pass this test, so I am not sure if this is what you would consider a generator.

    Cheers,
    zipit

    import c4d
    
    def main():
        """
        """
        state = op[c4d.ID_BASEOBJECT_GENERATOR_FLAG]
        if not state:
            print op.GetName(), "is a generator."
            return
        op[c4d.ID_BASEOBJECT_GENERATOR_FLAG] = 0
        if not op[c4d.ID_BASEOBJECT_GENERATOR_FLAG]:
            print op.GetName(), "is a generator."
        else:
            print op.GetName(), "is not a generator."
        op[c4d.ID_BASEOBJECT_GENERATOR_FLAG] = state
        
    
    if __name__ == "__main__":
        main()
    


  • Maybe a stupid question, but why do you need such information, because in Cinema 4D almost everything (except cache and edited polygon Object/Spline) is a Generator?
    Giving us more context or whats your final goal could probably help us to guide you :)

    Cheers,
    Maxime



  • OK, here is some context: This is for a rendering plugin (https://gitlab.com/3Delight/3delight-for-cinema-4d). We have created a system where we can register custom "translators" that translate various objects in a scene to the appropriate rendering commands. I.e., there is a "meshtranslator" for translating polygon objects, various "light translators" for translating custom light types etc.

    During rendering, we traverse the scene hierarchy (including caches) and export objects to the renderer. Objects that have BIT_CONTROLOBJECT set are not exported, since we assume them to be input objects to generators.

    The problem comes when we want to add a custom translator for an object type that is a generator (say, hair objects). These will then not be exported, because they have the BIT_CONTROLOBJECT set due to being a generator. On the other hand, we can't skip checking for BIT_CONTROLOBJECT, since then the object would be (incorrectly) rendered in the case that it was indeed being used as an input object to another generator.

    So: We need a reliable mechanism to determine if a given object A is used as an input object to another object B, and thus should be excluded from rendering. This test needs to work regardless of wheter A is a generator or not.

    Does this make sense?

    /Filip



  • Here is a hypothesis: An object is a control object (i.e., used as input to another generator object) if and only if the object and all recursively generated objects in its cache have the BIT_CONTROL object set.

    I wrote this recursive function to check this:

    bool IsInputObject(BaseObject* obj, bool inCache = false) {
    	if (!obj) { return true; }
    
    	if (!obj->GetBit(BIT_CONTROLOBJECT)) {
    		return false;
    	}
    
    	if (!IsInputObject(obj->GetCache(), true)) {
    		return false;
    	}
    
    	if (inCache) {
    		if (!IsInputObject(obj->GetDown())) {
    			return false;
    		}
    
    		if (!IsInputObject(obj->GetNext())) {
    			return false;
    		}
    	}
    
    	return true;
    
    }
    

    Testing on a few simple scenes, this appears to work. Could you think of a corner case that would violate this hypothesis?

    /Filip



  • Sorry, that should be:

    bool IsInputObject(BaseObject* obj, bool inCache = false) {
    	if (!obj) { return true; }
    
    	if (!obj->GetBit(BIT_CONTROLOBJECT)) {
    		return false;
    	}
    
    	if (!IsInputObject(obj->GetCache(), true)) {
    		return false;
    	}
    
    	if (inCache) {
    		if (!IsInputObject(obj->GetDown(), true)) {
    			return false;
    		}
    
    		if (!IsInputObject(obj->GetNext(), true)) {
    			return false;
    		}
    	}
    
    	return true;
    
    }
    


  • After some more testing the approach outlined above seems to work as intended. I am marking this topic as solved for now.

    In case anyone else has a need to determine if a given object is an input object of another generator object, here is the current version of my code to check this:

    //Function to determine if an object is used as an input to another generator. 
    //Just checking if the BIT_CONTROLOBJECT bit is set for the object is not sufficient, 
    //as this bit is also set for generators. 
    //Instead, we check this bit for ALL recursively generated objects in the cache of "obj". 
    //If at least one object in the cache does not have this bit set, 
    //then "obj" is not an input object.
    //
    //Calling IsInputObject(obj) returns true if obj is an input object, and false otherwise
    
    bool IsInputObject(BaseObject* obj, bool inCache = false) {
    	while (obj) {
    
    		if (!obj->GetBit(BIT_CONTROLOBJECT)) {
    			return false;
    		}
    
    		if (!IsInputObject(obj->GetCache(), true)) {
    			return false;
    		}
    
    		if (inCache) {
    			if (!IsInputObject(obj->GetDown(), true)) {
    				return false;
    			}
    
    			obj = obj->GetNext();
    		}
    	        else {
    			obj = NULL;
    		}
    	}
    
    	return true;
    
    }
    


  • Hi Filip, thanks for sharing your code.

    I confirm that it looks correct and it should work in all the cases I can think of.
    Two notes:

    • never use NULL to check your pointers against to or to assign it to your pointers: rather prefer using nullptr cause while the first comes from a define the second is a built-in pointer type.
    • with regard to the last implementation, why not using:
    ...
    		if (inCache) {
    			if (!IsInputObject(obj->GetDown(), true)) {
    				return false;
    			}
    		}
    		obj = obj->GetNext();
    ...
    

    Cheers, R



  • Thanks for confirming that this approach looks correct!
    "never use NULL to check your pointers against to or to assign it to your pointers: rather prefer using nullptr cause while the first comes from a define the second is a built-in pointer type."
    -Thanks, noted!

    "with regard to the last implementation, why not using:"
    -Because we only want to check the next object (obj=obj->GetNext()) if "inCache" is true. Otherwise, the function would not only check the object passed to the function and its cache, but also all subsequent objects on the same level of the hierarchy.


Log in to reply