Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    Touch(), cache handling and dirty counts

    SDK Help
    0
    6
    1.6k
    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.
    • H
      Helper
      last edited by

      On 05/05/2018 at 06:46, xxxxxxxx wrote:

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

      ---------
      I am having trouble to determine when an input object of my generator changed compared to the
      last time that I handled and generated new geometry for it.

      When the generator is first invoked, there's no cached state and thus it generates new geometry
      for every input object. It saves the dirty count of the input object (DIRTYFLAGS_CACHE | DIRTYFLAGS_DATA)
      in a hashmap. After the geometry was generated from the PolygonObject's found in the input object's
      cache, it calls Touch() on the object.

      However, Touch() increases the DIRTYFLAGS_CACHE dirty count (eg. for Array and Cloner objects,
      not for Cube primitives for some reason).

      Now the dirty count that I stored for the object is incorrect as it has been modified by Touch().
      I need to call Touch() after I computed the geometry however because Touch() will free the cache
      of the input object, making it inaccessible for computing the new geometry.
      **
      Do I need to compute the dirty count a second time after calling Touch() and store that dirty count
      instead?**

      Now assume the above works properly and I determine that I don't need to recompute geometry
      from the input object. I simply call Touch() again in order to hide the input object in the viewport
      and reuse the previously generated cache.

      Does that mean that the input object's GetVirtualObjects() was called but the result is completely
      ignored and deleted again by my Touch() call? Wouldn't that be super unnecessary?

      Also, that Touch() call would again increase the dirty count.

      Are there some resources that explain how to manage this properly?

      On a side-note, I can't use the BaseObject DependenceList stuff because I want to treat all of the
      inputs separately. So if one input object changed, I may still re-use the previously generated cache
      of another input object.

      For the sake of completeness, in case it is of any relevance, this is how I retrieve all the input
      objects to my generator:

        
        inline void CollectGenerators(BaseObject* obj, maxon::BaseArray<BaseObject*>& objects) {  
        if (obj->GetDeformCache() || obj->GetCache() || obj->GetType() == Opolygon) {  
          objects.Append(obj);  
        }  
        else {  
          for (auto&& child : Utils::IterChildren(obj)) {  
            if (!child->GetBit(BIT_CONTROLOBJECT)) {  
              CollectGenerators(child, objects);  
            }  
          }  
        }  
        }  
        
          // Then in GetVirtualObjects() :  
        maxon::BaseArray<BaseObject*> generators;  
        for (auto&& child : Utils::IterChildren(op)) {  
          Utils::CollectGenerators(child, generators);  
        }  
        
         for (auto&& generator : generators) {  
             // TODO: Determine if the cache of the generator has changed -> Recompute the geometry only in that case  
        
               maxon::BaseArray<PolygonObject*> geos;  
               // Here I collect all PolygonObjects using the method described in the  
               // BaseObject::GetCache() documentation (checking for BIT_CONTROLOBJECT).    
        
             // Build new geometry from PolygonObjects  
        
             generator->Touch();  
         }
      

      Thanks in advance,

      Niklas


      1 Reply Last reply Reply Quote 0
      • H
        Helper
        last edited by

        On 07/05/2018 at 01:58, xxxxxxxx wrote:

        Small update: I noticed that calling Touch() on the input object will cause it to NOT generate a new
        cache the next time GVO is called. This can be tested easily with a Python Generator:

        import c4d  
        def main() :  
          array = op.GetDown()  
          print(array, array.GetCache())  
          array.Touch()  
          return c4d.BaseObject(c4d.Ocube)
        

        (<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71C50>, <c4d.BaseObject object called 'Array/Null' with ID 5140 at 0x000001F73CB71AD0>)  
        (<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71F50>, None)  
        (<c4d.BaseObject object called 'Array/Array' with ID 5150 at 0x000001F73CB71AD0>, None)
        

        This resolves one of my previous questions:

        Originally posted by xxxxxxxx

        Does that mean that the input object's GetVirtualObjects() was called but the result is completely
        ignored and deleted again by my Touch() call? Wouldn't that be super unnecessary?

        However, what if I actually need the cache but the input object has none because I called Touch()
        on it in the previous GVO() pass?

        1 Reply Last reply Reply Quote 0
        • H
          Helper
          last edited by

          On 07/05/2018 at 02:07, xxxxxxxx wrote:

          Another test from the C++ plugin

                auto c = op->GetDown();  
              if (c) {  
                GePrint(c->GetName() + ": Has Cache? " + String(c->GetCache() ? "yes" : "no"));  
                auto cc = c->GetDown();  
                if (cc) GePrint("    " + cc->GetName() + ", BIT_CONTROLOBJECT? " + String::IntToString(cc->GetBit(BIT_CONTROLOBJECT)));  
                c->Touch();  
              }  
              return BaseObject::Alloc(Onull);
          

          After I move the Array object under the ObjectData plugin, I get

          Array: Has Cache? yes  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0  
          Array: Has Cache? no  
            Car, BIT_CONTROLOBJECT? 0
          

          Which is the same behaviour as in the Pyhon Generator. I just wanted to make sure that it's not
          some kind of issue with the Python Generator, so I tested it in the C++ plugin, too.

          Shouldn't the BIT_CONTROLOBJECT be set on the "Car" polygon object? How else would I know
          that the object is already being used by the Array object?

          Thanks,

          Niklas

          1 Reply Last reply Reply Quote 0
          • H
            Helper
            last edited by

            On 07/05/2018 at 02:35, xxxxxxxx wrote:

            Hi Niklas, thanks for writing us.

            There's a in-depth description on the topic in the BaseObject manual at this link which I think it could be worth reading with regard on how/when to use BaseObject::Touch() .

            Let me know if any further help is needed.

            Best, Riccardo

            1 Reply Last reply Reply Quote 0
            • H
              Helper
              last edited by

              On 07/05/2018 at 03:36, xxxxxxxx wrote:

              Thanks Riccardo, that looks like what I was searching for. My bad for not finding it. 🙂
              I'll give all the info in that page a go and come back with any unanswered questions.

              Cheers,
              Niklas

              1 Reply Last reply Reply Quote 0
              • H
                Helper
                last edited by

                On 07/05/2018 at 09:14, xxxxxxxx wrote:

                Hello Riccardo,

                Unfortunately I am not getting very far. I fear my case is a little more complex than what is usually
                implemented for generator plugins.

                • GetAndCheckHierarchyClone() seems to always return a clone and tell me that the object is
                  dirty. Not very helpful unfortunately. Also, I would like to avoid the "cloning" part and read from
                  the cache directly as it will be much more efficient.

                • Using IsDirty() seems to be very useful and does not require me to store dirty counts, great!
                  But can I use it for any objects that are not direct children of my generator?

                For example, the Array object only takes its first child as its input. Any other of its child
                objects are ignored and left visible in the Viewport. I want to have my generator take this
                object into account instead (similar to how the Subdivision Surface can handle this case, see
                the image below)

                Also, there appear to be cases where IsDirty() returns true but the object has no cache
                that I can read from. I described this situation before when I wasn't using IsDirty(). How
                would I be able to access the cache when the generator is dirty but it doesn't have a cache?

                I noticed that the Subdivision Surface seems to be capable of handling the input objects similar to
                the way that I would like to handle them. It sees the objects generated by the array, but also the
                second child of the array that is not taken into account by the Array object itself.

                Any chance to get some insight on how the Subdivision Surface does all of this? Specifically

                • How does it find all the PolygonObjects to take into account from its child objects?
                • How and when does it call Touch() on these input objects?
                • How does it check if an input object changed?

                Thanks in advance,
                Niklas

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