Plug-in Display Interface

 
 
 

Legacy Object/Modifier Plug-in Display Interface

Traditionally in 3ds Max, the object/modifier plug-in is displayed by implementing its Display() function. In the Display() function, the plug-in uses the GraphicsWindow interface to draw lines, points, markers, meshes, and text as shown in the following example:

int MyObject::Display(
    TimeValue t, 
    INode* inode, 
    ViewExp* vpt, 
    int flags)
{
    GraphicsWindow* gw = vpt->getGW();
    gw->setTransform(inode->GetObjectTM(t));
    mMesh.render(gw, inode->Mtls(), NULL, COMP_ALL, inode->NumMtls());
    // draw some lines
    gw->polyline(...);
}

This example code works only for legacy display drivers, such as legacy software driver, legacy d3d driver, and legacy OpenGL driver.

IObjectDisplay (Obsolete)

In 3ds Max 2012 and 3ds Max 2013, the Nitrous display interface must be implemented to make an object/modifier plug-in display correctly. The plug-in needs to do the following:

Making IObjectDisplay Interface Accessible

The IObjectDisplay interface is exposed by the BaseObject class, which is the root class for all objects and modifiers. The plug-in needs to redirect the interface ID to BaseObject::GetInterface(), when it meets an IOBJECT_DISPLAY_INTERFACE_ID in its GetInterface() method as shown in the following example:

BaseInterface* TriObject::GetInterface(Interface_ID iid)
{
    if (iid == IOBJECT_DISPLAY_INTERFACE_ID)
    {
        // Redirect the IObjectDisplay interface ID to parent class
        return GeomObject::GetInterface(iid);
    }
    ...
}

Reusing the Legacy Display Code

The legacy display code (XXXObject::Display() that uses GraphicsWindow interface) for drawing lines, points, markers, and text is supported by Nitrous. However, the performance is not good.

All legacy display code that use Mesh::render(), MNMesh::render(), or any other mesh display functions that use GraphicsWindow is not supported by Nitrous. There might be some display, but these functions must no longer be used. These function calls must be skipped in XXXObject::Display.

By default, the legacy display function of the plug-in is called. Plug-ins can override IObjectDisplay::RequiresSupportForLegacyDisplayMode() to change this behavior and prevent Nitrous from calling the legacy display function. The following example shows how to handle the legacy display code:

bool MyObject::RequiresSupportForLegacyDisplayMode() const
{
    // Make sure the legacy Display code is called under Nitrous
    // because we still want to reuse some legacy display code
    return true;
}

int MyObject::Display(...)
{
    gw->setRndLimits(...);
    if (!MaxSDK::Graphics::IsRetainedModeEnabled())
    {
        // Only render mesh if Nitrous is not enabled.
        mMesh.render(...);
    }
    gw->polyline(...);
}

As a replacement for Mesh::render(), the plug-ins must override the UpdateDisplay() method to provide render items to Nitrous.

Providing Render Items for Mesh and MNMesh

To provide mesh render items to Nitrous, the object plug-ins override the UpdateDisplay() function. Following is an example that shows the UpdateDisplay() implementation:

bool MyObject::UpdateDisplay(UpdateDisplayContext& context)
{
    // Simple, but difficult to customize the display
    mRenderItemHandles = mMesh.GenerateRenderItems(context);
    return true;
}

Current Implementation (IObjectDisplay2)

The IObjectDisplay interface is simple to implement, but it is difficult for the object plug-ins to customize their display according to the node properties (for example, node matrix, material, and others) and viewport properties (for example, view matrix, visual style, and others).

Now, the object plug-ins can control the display better using the IObjectDisplay2 interface. This enables the object plug-ins to provide different render items for different nodes and different views. The following figure illustrates the changes:

If an object plug-in provides both IObjectDisplay2 and IObjectDisplay interfaces, only IObjectDisplay2 is used. Nitrous still supports IObjectDisplay interface, but it is deprecated.

The following example shows the declaration of IObjectDisplay2:

class IObjectDisplay2:public BaseInterface
{
public:
    virtual unsigned long GetObjectDisplayRequirement() const;
    virtual bool PrepareDisplay(
        const MaxContext& maxContext, 
        const UpdateDisplayContext& updateDisplayContext) = 0;
    virtual bool UpdatePerNodeItems(
        const MaxContext& maxContext, 
        const UpdateDisplayContext& updateDisplayContext,
        UpdateNodeContext& nodeContext,
        IRenderItemContainer& targetRenderItemContainer) = 0;
    virtual bool UpdatePerViewItems(
        const MaxContext& maxContext, 
        const UpdateDisplayContext& updateDisplayContext,
        UpdateNodeContext& nodeContext, 
        UpdateViewContext& viewContext,
        IRenderItemContainer& targetRenderItemContainer);
};

The IObjectDisplay2 interface is not provided by default. The object/modifier plug-in must explicitly expose this interface using its GetInterface() function.

Code Flow for Object Plug-in Display

The following pseudo-example shows the steps in displaying an object plug-in in the Nitrous viewport:

void Nitrous::DrawAllViewports()
{
    // Preparation of each object. Irrespective of the object being referenced 
    // by many nodes, each object prepares the display data 
    // only once.
    foreach object in scene
    {
      object.PrepareDisplay
    }
    // Add per-node display items
    foreach node in scene
    {
      node.object.UpdatePerNodeItems
    }
    // display pass for all views
    foreach view 
    {
      foreach node in scene
      {
        node.object.UpdatePerViewItems
      }
      foreach RenderItem
      {
        RenderItem.Draw
      }
    }
}

IObjectDisplay2::GetObjectDisplayRequirement()

This function is an extension of the IObjectDisplay::RequiresSupportForLegacyDisplayMode() method. It returns a flag that can be a combination of the following values:

Flag Description
ObjectDisplayRequireLegacyDisplayMode If this flag is set, the plug-in’s legacy Display() function is called in Nitrous.
ObjectDisplayRequireUpdatePerViewItems If this flag is set, the IObjectDisplay2::UpdatePerViewItems() function is called.

By default, the IObjectDisplay2::GetObjectDisplayRequirement() function returns zero, which means that the legacy display code is not used, and IObjectDisplay2::UpdatePerViewItems() is not called.

IObjectDisplay2::PrepareDisplay()

An object can be referenced by multiple nodes at the same time. Therefore, different nodes might display each object differently (using backface cull or non-backface cull, vertex color shading or material shading, and others). The IObjectDisplay2::PrepareDisplay() function provides the object plug-ins a chance to prepare display data for all nodes, thus avoiding duplicate display data generation for each node or each view.

If an object contains a Mesh or MNMesh to display, the IMeshDisplay2::PrepareDisplay() is usually called in this function.

IObjectDisplay2::UpdatePerNodeItems()

This function is called for each owner node of an object plug-in. Plug-ins can create different render items depending on the input node properties. For example, if an object wants to display a high-resolution mesh when the node is selected, and display a low-resolution mesh when node is not selected, the plug-in can use this function to create different render items.

IObjectDisplay2::UpdatePerViewItems()

This function is called for each viewport and each owner node of an object plug-in. If the plug-in needs to display or hide certain render items for a view property, the plug-in can override this function and update the render item based on the input view properties.

NoteThis function is optional. If the plug-in does not have a view-dependent render item, it does not need to override this function, and therefore the performance is not affected.

Example Implementation of IObjectDisplay2 Interface

The following code shows an example of object display. In this example, a higher resolution mesh is shown when the object is selected.

class MyObject:public Object, public IObjectDisplay2
{
public:
    // Object declaration code.
private:
    // High resolution mesh is displayed when node is selected
    Mesh mHighResMesh;
    // Low resolution mesh is displayed when node is unselected
    Mesh mLowResMesh;
};

BaseInterface* MyObject::GetInterface(Interface_ID iid)
{
    if (iid == IOBJECT_DISPLAY2_INTERFACE_ID)
    {
        return (IObjectDisplay2*)this;
    }

    return __super::GetInterface(iid);
}

unsigned long MyObject::GetObjectDisplayRequirement() const
{
    // we do not want legacy display code called
    // we also do not have any per-view items
    return 0;
}

bool MyObject::PrepareDisplay(
    const MaxContext& maxContext,
    const UpdateDisplayContext& displayContext)
{
    GenerateMeshRenderItemsContext renderItemContext;
    renderItemContext.GenerateDefaultContext(displayContext);
	
    IMeshDisplay2* pMeshDisplay = NULL;

    pMeshDisplay = static_cast<IMeshDisplay2*>(
        mHighResMesh.GetInterface(IMesh_DISPLAY2_INTERFACE_ID));
    if (pMeshDisplay != NULL)
        pMeshDisplay->PrepareDisplay(renderItemContext);
    pMeshDisplay = static_cast<IMeshDisplay2*>(
        mLowResMesh.GetInterface(IMesh_DISPLAY2_INTERFACE_ID));
    if (pMeshDisplay != NULL)
        pMeshDisplay->PrepareDisplay(renderItemContext);
    return true;
}

bool MyObject::UpdatePerNodeItems(
    const MaxContext& maxContext,
    const UpdateDisplayContext& displayContext,
    UpdateNodeContext& nodeContext,
    IRenderItemContainer& targetRenderItemContainer)
{
    GenerateMeshRenderItemsContext renderItemContext;
    renderItemContext.GenerateDefaultContext(displayContext);
    renderItemContext.RemoveInvisibleMeshElementDescriptions(
        nodeContext.GetRenderNode());

    IMeshDisplay2* pMeshDisplay = NULL;
    RenderItemHandleArray renderItems;
    if (nodeContext.GetRenderNode().GetSelected())
    {
        // If node is selected, use high resolution mesh
        pMeshDisplay = static_cast<IMeshDisplay2*>(
            mHighResMesh.GetInterface(IMesh_DISPLAY2_INTERFACE_ID));
    }
    else
    {
        // Otherwise use low resolution mesh
        pMeshDisplay = static_cast<IMeshDisplay2*>(
            mLowResMesh.GetInterface(IMesh_DISPLAY2_INTERFACE_ID));
    }
    if (pMeshDisplay != NULL)
    {
        pMeshDisplay->GetRenderItems(
            maxContext,
            renderItemContext,
            nodeContext,
            renderItems);
    }
    targetRenderItemContainer.ClearAllRenderItems();
    targetRenderItemContainer.AddRenderItems(renderItems);
    return true;
}