Plug-in Display Interface

Overview

The Plugin Display SDK (previously called the Nitrous SDK) can be used by object/modifier plugins and material plugins to customize the way they display in the Nitrous viewport. The following diagram is a high-level overview of the classes and interfaces in this SDK.

Legacy Object/Modifier Plug-in Display Interface

Note:

The IObjectDisplay interface is removed as of 3ds Max 2015, and IObjectDisplay2 must now be used.

Traditionally in 3ds Max, the object/modifier plug-in is displayed by implementing its Display() function. In this 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.

Implementing IObjectDisplay2

To make an object display correctly under Nitrous, the plug-in must implement the Nitrous display interface, IObjectDisplay2. specifically, the plug-in needs to:

Below is the declaration of IObjectDisplay2:

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

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
      }
    }
}

Making the IObjectDisplay2 Interface Accessible

The IObjectDisplay2 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_DISPLAY2_INTERFACE_ID)
    {
        // Redirect the IObjectDisplay2 interface ID to parent class
        return GeomObject::GetInterface(iid);
    }
    ...
}

Reusing Legacy Display Code

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 uses 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 GetObjectDisplayRequirement() to change this behavior and prevent Nitrous from calling the legacy display function. The following example shows how to handle the legacy display code:

Unsigned long MyObject:: GetObjectDisplayRequirement() const
{
    // Make sure legacy Display code be called under Nitrous
    // Because we still want to reuse some legacy display code
    return ObjectDisplayRequireLegacyDisplayMode;
}

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 IObjectDisplay2 methods to provide render items to Nitrous.

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 ObjectDisplayRequireLegacyDisplayMode, which means that the legacy display code is not used, and IObjectDisplay2::UpdatePerViewItems() is not called.

Providing Render Items for Mesh and MNMesh

To provide mesh render items under Nitrous, object plugins should override the methods in the IMeshDisplay2 interface to build the render items, and pass the built render items to the targetRenderItemContainer parameter in IObjectDisplay2::UpdatePerNodeItems() or IObjectDisplay2::UpdatePerViewItems().

For more details of render items, see the About RenderItem.

IObjectDisplay2::PrepareDisplay()

An object can be referenced by multiple nodes at the same time. Therefore, different nodes might display each object differently (for example, using backface cull or non-backface cull, vertex color shading or material shading, and so on). 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.

Note:

This 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 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 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(
            renderItemContext,
            nodeContext,
            renderItems);
    }
    targetRenderItemContainer.ClearAllRenderItems();
    targetRenderItemContainer.AddRenderItems(renderItems);
    return true;
}