Instancing is a common use-case for third-parties. Normally you have an object generator in your plugin which generates Instance Objects. This renders fine in C4DtoA, however it's not optimal: generating the Instance Objects (building the object cache) in the C4D scene needs time and memory.

The API offers two easy methods to optimize the render.

Instancing Via Messages

The easiest method is to respond to custom messages in your object sent by C4DtoA. It works similar to the message API of the shader network. C4DtoA sends a MSG_BASECONTAINER message which contains the message type, parameters and you can set return values. See the example below.

Note that in this case, you don't have to link the C4DtoA API in your plugin.

 

The following table summarizes the sent messages, their parameters and return values:

Message id

C4DTOA_MSG_GET_NUM_INSTANCES = 1058

DescriptionSent during the IPR to detect if the number of instances has been changed.
ParametersC4DTOA_MSG_PARAM1 (Int32): the version number of the Instancer API.
Return values

C4DTOA_MSG_RESP1 (Int32): the number of actual instances.


Message id

C4DTOA_MSG_GET_INSTANCE_REFS = 1059

DescriptionSent during the export to create all translators of the instances.
ParametersC4DTOA_MSG_PARAM1 (Int32): the version number of the Instancer API.
Return values

C4DTOA_MSG_RESP1 (Int32): the number of instances.

C4DTOA_MSG_RESP2 (Void - BaseObject**): the references of all instances as an array of BaseObject pointers.


Message id

C4DTOA_MSG_GET_INSTANCE_PROPS = 1060

DescriptionSent during the export in each motion step to request the properties of all instances (e.g. transformation matrix, shape override flags).
Parameters

C4DTOA_MSG_PARAM1 (Int32): the version number of the Instancer API.

C4DTOA_MSG_PARAM2 (Int32): the current motion step index (starts from 0).

Return values

C4DTOA_MSG_RESP1 (Int32): the number of instances.

C4DTOA_MSG_RESP2 (BaseContainer): the properties of all instances in separate arrays. The following indexes are used in the BaseContainer:

  • C4DTOA_INSTANCE_DATA__XFORMS = 1 (Void - Matrix*): transformation matrix of all instances.
  • C4DTOA_INSTANCE_DATA__VISIBILITY = 2 (Void - unsigned char*): visibility override of all instances.
  • C4DTOA_INSTANCE_DATA__SELF_SHADOWS = 3 (Void - bool*): self shadow flag override of all instances.
  • C4DTOA_INSTANCE_DATA__OPAQUE = 4 (Void - bool*): opaque flag override of all instances.
  • C4DTOA_INSTANCE_DATA__MATTE = 5 (Void - bool*): matte flag override of all instances.
  • C4DTOA_INSTANCE_DATA__DISPLAY_COLOR = 6 (Void - Vector*): display color of all instances. Can be read via a user_data_rgb shader.

Note that only the transformation matrix has to be set in each motion step. The shape override flags (e.g. visibility, opaque, display color, etc.) are needed only in the first motion step.

 

This example shows how to respond to these messages:

// from util/Constants.h
#define C4DTOA_MSG_TYPE 1000
#define C4DTOA_MSG_PARAM1 2001
#define C4DTOA_MSG_PARAM2 2002
#define C4DTOA_MSG_PARAM3 2003
#define C4DTOA_MSG_PARAM4 2004
#define C4DTOA_MSG_RESP1 2011
#define C4DTOA_MSG_RESP2 2012
#define C4DTOA_MSG_RESP3 2013
#define C4DTOA_MSG_RESP4 2014

#define C4DTOA_MSG_GET_NUM_INSTANCES 1058
#define C4DTOA_MSG_GET_INSTANCE_REFS 1059
#define C4DTOA_MSG_GET_INSTANCE_PROPS 1060

#define C4DTOA_INSTANCE_DATA__XFORMS 1
#define C4DTOA_INSTANCE_DATA__VISIBILITY 2
#define C4DTOA_INSTANCE_DATA__SELF_SHADOWS 3
#define C4DTOA_INSTANCE_DATA__OPAQUE 4
#define C4DTOA_INSTANCE_DATA__MATTE 5
#define C4DTOA_INSTANCE_DATA__DISPLAY_COLOR 6
 
Bool MyObject::Message(GeListNode* node, Int32 type, void* d)
{
   switch (type)
   {
      case MSG_BASECONTAINER:
      {
         BaseObject* object = (BaseObject*)node;
         BaseContainer* data = object->GetDataInstance();
         
         BaseContainer* msg = (BaseContainer*)d;
         int msgType = msg->GetInt32(C4DTOA_MSG_TYPE);
         
         // Tell C4DtoA the number of instances.
         // Called during the IPR to check if the number of instances has been changed.
         if (msgType == C4DTOA_MSG_GET_NUM_INSTANCES)
         {
            Int32 numInstances = data->GetInt32(OMYINSTANCER2_NUM_INSTANCES);
            msg->SetInt32(C4DTOA_MSG_RESP1, numInstances);
         }
         
         // Tell C4DtoA the referenced objects of each instance.
         // Called during the export and IPR update to create the Arnold instances.
         else if (msgType == C4DTOA_MSG_GET_INSTANCE_REFS)
         {
            // In this example we use the child objects.
            std::vector<BaseObject*> children;
            for (BaseObject* child = object->GetDown(); child; child = child->GetNext())
               children.push_back(child);
            unsigned int numChildren = (unsigned int)children.size();
            if (!numChildren)
            {
               GePrint("Please add a child object!");
               return TRUE;
            }
            
            // define data of all instances
            Int32 numInstances = data->GetInt32(OMYINSTANCER2_NUM_INSTANCES);
            BaseObject** refs = new BaseObject*[numInstances];
            for (int i=0; i<numInstances; i++)
               refs[i] = children[i % numChildren];


            // send the data to C4DtoA
            msg->SetInt32(C4DTOA_MSG_RESP1, numInstances);
            msg->SetVoid(C4DTOA_MSG_RESP2, refs);
         }


         // Tell C4DtoA the properties of each instance.
         // Called during the export and IPR update to setup the Arnold instances.
         // Called in each motion step when motion blur is enabled.
         else if (msgType == C4DTOA_MSG_GET_INSTANCE_PROPS)
         {
            Int32 step = msg->GetInt32(C4DTOA_MSG_PARAM2);

            Random random;
            random.Init(42);
            // In this example we create the instances in a line started from the parent object.
            // We disable the 'visible in specular' flag of every second instance.
            // We disable casting shadows of every third instance.
            // We also set a random display color.
            Int32 numInstances = data->GetInt32(OMYINSTANCER2_NUM_INSTANCES);
            Matrix* xforms = new Matrix[numInstances];
            // NOTE shape overrides needed only in the first step
            unsigned char* visibilityFlags = step == 0 ? new unsigned char[numInstances] : nullptr;
            Vector* displayColors = step == 0 ? new Vector[numInstances] : nullptr;

            Matrix objectXform = object->GetMg();
            for (int i=0; i<numInstances; i++)
            {
               xforms[i] = objectXform;
               // NOTE we test motion blur by adding step*10.0
               xforms[i].off += Vector(i*100.0 + step*10.0, 0.0, 0.0);

               if (visibilityFlags)
               {
                  visibilityFlags[i] = AI_RAY_ALL;
                  // disable visible in specular of each second instance
                  if (i % 2 == 0)
                     visibilityFlags[i] &= ~AI_RAY_GLOSSY;
                  // disable shadow of each third instance
                  if (i % 3 == 0)
                     visibilityFlags[i] &= ~AI_RAY_SHADOW;
               }
 
               // display color
               if (displayColors)
                  displayColors[i] = Vector(random.Get01(), random.Get01(), random.Get01());
            }

            // send the data to C4DtoA
            BaseContainer instanceProps;
            instanceProps.SetVoid(C4DTOA_INSTANCE_DATA__XFORMS, xforms);
            if (visibilityFlags)
               instanceProps.SetVoid(C4DTOA_INSTANCE_DATA__VISIBILITY, visibilityFlags);
            if (displayColors)
               instanceProps.SetVoid(C4DTOA_INSTANCE_DATA__DISPLAY_COLOR, displayColors);
            msg->SetInt32(C4DTOA_MSG_RESP1, numInstances);
            msg->SetContainer(C4DTOA_MSG_RESP2, instanceProps);
         }
         break;
      }
   }

   return ObjectData::Message(node, type, d);
}

 

There's one more important thing. You have to tell C4DtoA that your object needs the default instancer translator. To achieve this you have to set an int flag in the object's BaseContainer:

// from util/Constants.h
#define C4DTOA_CUSTOM_TRANSLATOR_ID 900000000
#define DEFAULT_INSTANCER_TRANSLATOR_ID 128974629
 
Bool MyObject::Init(GeListNode *node)
{
   BaseObject* object = (BaseObject*)node;
   BaseContainer* data = object->GetDataInstance();
   
   // Tell C4DtoA that this object requires a default instancer translator.
   data->SetInt32(C4DTOA_CUSTOM_TRANSLATOR_ID, DEFAULT_INSTANCER_TRANSLATOR_ID);

   return TRUE;
}

Download the complete example which contains the source code and a Visual Studio 2012 and Xcode project. Binaries are also included, built for C4D R17 and C4DtoA 1.6.0. Just copy the content to the plugins folder of your C4D install.

Instancing Via a Custom Translator

Another method is to implement a custom translator and use the ArnoldInstancer helper class in it. This is useful when you need any custom export logic above what the default translator offers. Obviousl, it requires you to link the C4DtoA API in your plugin (or C4DtoA module). See this page about how to define a custom translator.

Your translator must be inherited from the AbstractGeometryTranslator class and the following functions have to be overridden:

  • CreateRelatedTranslators(): Get the number of instances and the referenced geometry of each instance and call ArnoldInstancer::CreateInstances(). This will create local instance translators. The instancer should be called only if needed (e.g. the number of instances or the instanced geometry changes).
  • ClearRelatedTranslators(): Call ArnoldInstancer::Clear() when needed (e.g. the number of instances or the instanced geometry changes).
  • Export(): Get the properties of the instances (e.g. transformation matrix, visibility flag, etc.) and call ArnoldInstancer::Export(). Note that only the transformation matrix has to be set in each motion step. The shape override flags (e.g. visibility, opaque, dispaly color, etc.) are needed only in the first motion step.
  • CheckModified(): Get the number of actual instances and call ArnoldInstancer::CheckModified() which will check for changes and set the modification flag.
  • CollectObjectIds(): Call ArnoldInstancer::CollectObjectIds(). This is needed for the Pick Object and Render Only the Selected Object features in the IPR window. 

 

The above example implemented as a translator:

// Translator id.
#define INSTANCER_OBJECT_TRANSLATOR "MyInstancerObjectTranslator"

///
/// Custom translator to export instances.
/// Must be inherited from AbstractGeometryTranslator.
///
class MyInstancerObjectTranslator : public AbstractGeometryTranslator
{
public:
   ArnoldInstancer* instancer;
   
public:
   ///
   /// Constructor.
   ///
   MyInstancerObjectTranslator(BaseList2D* node, RenderContext* context) 
      : AbstractGeometryTranslator(INSTANCER_OBJECT_TRANSLATOR, (BaseObject*)node, context)
   {
      instancer = new ArnoldInstancer(this);
   }

   ///
   /// Destructor.
   ///
   ~MyInstancerObjectTranslator()
   {
      delete instancer;
   }

   ///
   /// @override
   /// Returns the name of the Arnold node entry this translator
   /// is responsible to create.
   ///
   /// We don't want to create any Arnold node for this parent
   /// object so leave it empty.
   ///
   virtual char* GetAiNodeEntryName()
   {
      return "";
   }

   ///
   /// @override
   /// Returns false if a child object does not have to be exported.
   ///
   /// We do not export the children when the generator flag is on.
   ///
   virtual bool IsDescendantEnabled(BaseObject* descendant)
   {
      BaseObject* object = (BaseObject*)GetC4DNode();
      if (!object) return true;

      // disable hierarchy when this generator is on
      GeData enabled; object->GetParameter(ID_BASEOBJECT_GENERATOR_FLAG, enabled, DESCFLAGS_GET_0);
      if (enabled.GetBool() == TRUE) return false;

      return true;
   }

   ///
   /// @override
   /// Returns the object ids of the exported Arnold nodes. 
   /// This is used for example in the IPR when only the selected
   /// object is rendered.
   ///
   virtual void CollectObjectIds(std::set<int>& objectIds)
   {
      instancer->CollectObjectIds(objectIds);
   }

   ///
   /// Returns true if the instances have to be recreated.
   ///
   bool NeedInstanceCreate()
   {
      return m_exportFlag & MODIFIED_INSTANCES;
   }

   ///
   /// @override
   /// Compares the current and previous state to detect any changes
   /// which should trigger an IPR update.
   ///
   virtual void CheckModified()
   {
      // super
      AbstractGeometryTranslator::CheckModified();
      BaseObject* object = (BaseObject*)GetC4DNode();
      if (!object) return;

      // check instances
      unsigned int numInstances = (unsigned int)object->GetDataInstance()->GetInt32(OMYINSTANCER_NUM_INSTANCES);
      instancer->CheckModified(m_modificationFlag, numInstances);
   }

   ///
   /// @override
   /// Called in IPR to remove the related translators of the previous frame.
   ///
   /// We have to clear the instances only when they have to be recreated
   /// otherwise we keep the previously created translators (by calling KeepInstances()).
   ///
   virtual void ClearRelatedTranslators()
   {
      // clear instances
      if (NeedInstanceCreate())
         instancer->Clear(true);

      // super
      // NOTE this must be called after clearing the instances
      AbstractGeometryTranslator::ClearRelatedTranslators();
   }

   ///
   /// @override
   /// Called in IPR to add all related translators in the current frame.
   ///
   /// We have to create the instances here only when they have to be recreated.
   ///
   virtual void CreateRelatedTranslators()
   {
      // super
      AbstractGeometryTranslator::CreateRelatedTranslators();
      BaseObject* object = (BaseObject*)GetC4DNode();

      // create instances
      if (NeedInstanceCreate())
      {
         // In this example we use the child objects.
         std::vector<BaseObject*> children;
         for (BaseObject* child = object->GetDown(); child; child = child->GetNext())
            children.push_back(child);
         unsigned int numChildren = (unsigned int)children.size();
         if (!numChildren)
         {
            GePrint("Please add a child object!");
            return;
         }

         // generate instances
         Int32 numInstances = object->GetDataInstance()->GetInt32(OMYINSTANCER_NUM_INSTANCES);
         BaseObject** refs = new BaseObject*[numInstances];
         for (int i=0; i<numInstances; i++)
            refs[i] = children[i % numChildren];

         instancer->CreateInstances(numInstances, refs);

         delete [] refs;
      }
      else
         instancer->KeepInstances();
   }

   ///
   /// @override
   /// Translation logic.
   ///
   virtual void Export(int step)
   {
      BaseObject* object = (BaseObject*)GetC4DNode();
      if (!object) return;

      Random random;
      random.Init(42);
      // In this example we create the instances in a line started from the parent object.
      // We disable the 'visible in specular' flag of every second instance.
      // We disable casting shadows of every third instance.
      // We also set a random display color.
      Int32 numInstances = object->GetDataInstance()->GetInt32(OMYINSTANCER_NUM_INSTANCES);
      Matrix objectXform = object->GetMg();

      ArnoldInstanceProperties* props = new ArnoldInstanceProperties[numInstances];
      for (int i=0; i<numInstances; i++)
      {
         ArnoldInstanceProperties& prop = props[i];

         // transformation matrix
         prop.xform = objectXform;
         // NOTE we test motion blur by adding step*10.0
         prop.xform.off += Vector(i*100.0 + step*10.0, 0.0, 0.0);

         // NOTE shape overrides needed only in the first step
         if (step == 0)
         {
            // shape override flags
            // visibility
            AtByte visibility = AI_RAY_ALL;
            // disable visible in specular of each second instance
            if (i % 2 == 0)
               visibility &= ~AI_RAY_GLOSSY;
            // disable shadow of each third instance
            if (i % 3 == 0)
               visibility &= ~AI_RAY_SHADOW;
            prop.overrides.SetVisibility(visibility);

            // display color
            Vector displayColor(random.Get01(), random.Get01(), random.Get01());
            prop.overrides.SetDisplayColor(displayColor);
            // you can set other shape flags as well (e.g. opaque, self shadows, matte)
         }
      }

      // export the settings
      instancer->Export(step, props);
      delete [] props;
   }
};

Download the complete example which contains the source code and a Visual Studio 2012 and Xcode project. Binaries are also included, built for C4D R17 and C4DtoA 1.6.0. Just copy the content to the plugins folder of your C4D install.

  • No labels
Privacy settings / Do not sell my personal information / Privacy/Cookies