Softimage SDK Programmer's Guide > Customizing Softimage using the SDK > Custom Primitives >

# Creating a Custom Primitive

Before you can create a custom primitive object, you must register it in the XSILoadPlugin callback, which is called when Softimage loads a self-installing plug-in. You can use the PluginRegistrar.RegisterPrimitive method to register the custom primitive.

    SICALLBACK XSILoadPlugin(PluginRegistrar& in_reg)
    {

    	in_reg.PutAuthor(L"SoftimageUser");
    	in_reg.PutName(L"CustomPrimitives Plug-in");
    	in_reg.PutVersion(1,0);

    	// Register primitive types
    	in_reg.RegisterPrimitive("Box");

    	...

    }

After registering the custom primitive object, it can be created using the create primitive commands like GetPrim and X3DObject::AddPrimitive. You can pass in the registered custom primitive type as the preset argument to the commands.

    GetPrim "Box"

## Adding Parameters

You can define the parameters for a custom primitive object using the Define callback and tweak where they appear in the property page using the DefineLayout callback.

    SICALLBACK Box_Define(const CRef& in_ref)
    {
    	Context in_ctxt(in_ref);
    	CustomPrimitive in_prim(in_ctxt.GetSource());
    	if(in_prim.IsValid())
    	 {
    	  Application().LogMessage(CString("Box_Define: ") + in_prim.GetFullName());
    	  Factory l_fact = Application().GetFactory();
    	  double dMin = 0.01;
    	  double dMax = 10.0;
    	  CRef l_widthDef = l_fact.CreateParamDef(“Width”, CValue::siDouble, 1.0,   dMin, dMax);
    	  CRef l_lengthDef = l_fact.CreateParamDef(“Length”, CValue::siDouble, 1.0, dMin, dMax);
    	  CRef l_heightDef = l_fact.CreateParamDef(“Height”, CValue::siDouble, 1.0, dMin, dMax);
    	  Parameter l_width, l_length, l_height;
    	  in_prim.AddParameter(l_widthDef, l_width);
    	  in_prim.AddParameter(l_lengthDef, l_length);
    	  in_prim.AddParameter(l_heightDef, l_height);
    	}
    	return CStatus::OK;
    }

    SICALLBACK Box_DefineLayout(const CRef& in_ref)
    {
    	Context in_ctxt(in_ref);

    	PPGLayout layout = in_ctxt.GetSource();
    	layout.Clear();
    	layout.AddGroup("Box Group");
    	layout.AddItem(“Width”, "Half-Width");
    	layout.AddItem(“Length”, "Half-Length");
    	layout.AddItem(“Height”, "Half-Height");
    	layout.AddButton("ConvertGeom", "Convert to PolygonMesh");
    	layout.AddButton("CloseTheInspector", "Close the Inspector");
    	layout.EndGroup();
    	return CStatus::OK;
    }

The PPGEvent callback can be used for tracking the parameter changes using the siParameterChange event id.

    SICALLBACK Box_PPGEvent(const CRef& in_ref)
    {
    	PPGEventContext in_ctxt(in_ref);

    	switch (in_ctxt.GetEventID())
    	  {
    	    case siOnInit:
    	      {
    	        CustomPrimitive primitive = in_ctxt.GetSource();
    	        Application().LogMessage(CString("Box_PPGEvent: OnInit: " + primitive.GetName()));
    	        break;
    	      }
    	    case siOnClosed:
    	      {
    	        CustomPrimitive primitive = in_ctxt.GetSource();
    	        Application().LogMessage(CString("Box_PPGEvent: siOnClosed: " +  primitive.GetName()));
    	        break;
    	      }
    	    case siParameterChange:
    	      {
    	        Parameter changed = in_ctxt.GetSource();
    	        CustomPrimitive primitive = changed.GetParent();
    	        CString paramName = changed.GetScriptName();
    	        // Show that it is possible to change a value in the callback.
    	        // Note that the Draw() call might see the intermediary value before
    	        // it is changed this way.

    	        if (paramName == kWidthName)
    	        {
    	          primitive.PutParameterValue(kHeightName, changed.GetValue());
    	        }
    	          else if (paramName == kHeightName)
    	        {
    	          primitive.PutParameterValue(kWidthName, changed.GetValue());
    	        }
    	        Application().LogMessage(CString("Box_PPGEvent: siParameterChange: " + primitive.GetName() + CString("/") + paramName
    	        + CString(" = ") + changed.GetValue().GetAsText()));
    	        break;
    	      }
    	      ...
    	  }

    	return CStatus::OK;
    }

## Drawing the Custom Primitive Object

The Draw callback draws the custom primitive object in the scene. The custom primitive object is drawn in the scene directly using the OpenGL calls.

    SICALLBACK Box_Draw(const CRef& in_ref)
    {
    	Context in_ctxt(in_ref);
    	CustomPrimitive in_prim(in_ctxt.GetSource());
    	if(!in_prim.IsValid())
    	{
    	  return CStatus::Fail;
    	}

    	// Keep a cache of primitive data.

    	Box_CachedData& data = g_Cache.Get(in_prim);
    	double boxHalfWidth  = data.halfWidth;
    	double boxHalfLength = data.halfLength;
    	double boxHalfHeight = data.halfHeight;

    	// Draw Box
    	::glBegin();

    	...

    	::glEnd();

    	return CStatus::OK;

    }

In the Draw callback, you can call the OpenGL API to draw the custom primitive. When the Draw callback is called, the OpenGL state is set to the local coordinates of the custom primitive object. You must not make changes to the OpenGL states, otherwise rendering of the whole scene is affected. It is best to keep this method optimized because it is called in each draw cycle.

## Bounding Box

A bounding box represents a simplified version of a custom primitive object and it is used for improving performance in certain operations. The BoundingBox callback returns the bounding box for the custom primitive.

    SICALLBACK Box_BoundingBox(const CRef& in_ref)
    {
    	Context in_ctxt(in_ref);
    	CustomPrimitive in_prim(in_ctxt.GetSource());
    	if(!in_prim.IsValid())
    	{
    	  return CStatus::Fail;
    	}

    	// Keep a cache of primitive data.

    	Box_CachedData& data = g_Cache.Get(in_prim);
    	double boxHalfWidth  = data.halfWidth;
    	double boxHalfHeight = data.halfHeight;
    	double boxHalfLength = data.halfLength;

    	in_ctxt.PutAttribute("LowerBoundX", -boxHalfWidth);
    	in_ctxt.PutAttribute("LowerBoundY", -boxHalfHeight);
    	in_ctxt.PutAttribute("LowerBoundZ", -boxHalfLength);
    	in_ctxt.PutAttribute("UpperBoundX", boxHalfWidth);
    	in_ctxt.PutAttribute("UpperBoundY", boxHalfHeight);
    	in_ctxt.PutAttribute("UpperBoundZ", boxHalfLength);

    	return CStatus::OK;

    }

Unlike the Draw callback, the bounding box values are cached internally and only called when there are updates such as changes to parameter values. You must ensure that the BoundingBox callback always returns the correct dimensions, or a box that contains the whole custom primitive object.

## Converting to a Geometry

Like other implicit primitives, a custom primitive object can be converted to a geometry using either the SIConvert command or CreatePrim command. In Softimage 2014, a custom primitive object can be converted to PolygonMesh only.

    SICALLBACK Box_ConvertToGeom(const CRef& in_ref)
    //
    // This callback is invoked when the SIConvert or CreatePrim commands are run
    //	for a custom primitive object.
    //

    {
    	CustomPrimitiveContext in_ctxt(in_ref);
    	CustomPrimitive in_prim(in_ctxt.GetSource());
    	if(!in_prim.IsValid())
    	{
    	  return CStatus::Fail;
    	}

    	Box_CachedData& data = g_Cache.Get(in_prim);
    	double boxHalfWidth  = data.halfWidth;
    	double boxHalfHeight = data.halfHeight;
    	double boxHalfLength = data.halfLength;

    	double boxMinPt[3];
    	double boxMaxPt[3];

    	boxMinPt[0] = -boxHalfWidth;
    	boxMinPt[1] = -boxHalfHeight;
    	boxMinPt[2] = 0;

    	boxMaxPt[0] = boxHalfWidth;
    	boxMaxPt[1] = boxHalfHeight;
    	boxMaxPt[2] = 0;

    	Geometry out_geo = in_ctxt.GetGeometry();

    	// PolygonMesh support

    	PolygonMesh out_mesh(out_geo);
    	if(out_mesh.IsValid())
    	{
    	  // Convert to polygon
    	  MATH::CVector3Array out_verts;
    	  CLongArray out_faces;

    	  int index = 0;
    	  for (int i = -boxHalfLength; i <= boxHalfLength; ++i)
    	  {
    	    MATH::CVector3 vMinXMinY(-boxHalfWidth, -boxHalfHeight, 1.0 * i);
    	    MATH::CVector3 vMinXMaxY(-boxHalfWidth, boxHalfHeight, 1.0 * i);
    	    MATH::CVector3 vMaxXMinY(boxHalfWidth, -boxHalfHeight, 1.0 * i);
    	    MATH::CVector3 vMaxXMaxY(boxHalfWidth, boxHalfHeight, 1.0 * i);

    	    out_verts.Add(vMinXMinY);
    	    out_verts.Add(vMinXMaxY);
    	    out_verts.Add(vMaxXMaxY);
    	    out_verts.Add(vMaxXMinY);

    	    out_faces.Add(4); // Vertex count for tri
    	    out_faces.Add(index + 0);
    	    out_faces.Add(index + 1);
    	    out_faces.Add(index + 2);
    	    out_faces.Add(index + 3);
    	    index += 4;
    	  }

    	  return out_mesh.Set(out_verts, out_faces);
    	}

    }

In the ConvertToGeom callback, a geometry is attached to the context in the callback. You can update the geometry with the result of the conversion.

For more information, see the custom primitive examples in the SDK workgroup (<your_installation_folder>\Softimage2014\XSISDK\examples\workgroup\Addons\CustomPrimitive).

You can use the Custom Primitive Wizard to generate the code for a self-installing plug-in that contains a custom primitive object. To access the Custom Primitive Wizard, select Plug-in Manager File New Primitive.