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.
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.
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); };
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 } } }
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); } ... }
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.
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.
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.
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.
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.
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.
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; }