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.
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.
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:
GetInterface()
method returns the IObjectDisplay2
interface.GetObjectDisplayRequirement()
method to reuse or skip the whole legacy Display()
function. If the legacy Display()
function is still needed in Nitrous, the plug-in needs to skip the unsupported legacy display code in its Display()
method.PrepareDisplay()
, UpdatePerNodeItems()
, UpdatePerViewItems()
, and provide RenderItems
to Nitrous.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);
};
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
Interface AccessibleThe 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);
}
...
}
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.
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.
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.
IObjectDisplay2
InterfaceThe 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;
}