Lesson 3: The Reference System

Plug-ins can use the 3ds Max reference system to be notified when another entity changes in the scene. A plug-in creates a reference to a second plug-in in the reference system schema, so if the second plug-in (the reference target) is changed, deleted or updated, the first plug-in (the reference maker) is automatically notified. A plug-in can be both a reference target and a reference maker at the same time. An example of references is a scene object following a second scene object. If the first object has a reference to the second one, it is notified whenever the coordinates of the second object changes, so it can update its own coordinates. Another example is a camera that is looking at a geometric object and its perspective needs to be updated whenever the parameters of the geometric object, such as position, orientation, size, material, etc. changes or when the object is deleted. We will also see in Lesson 6 that the plug-ins have a reference to their parameters (stored in a separate objects of the class iParamBlock2) and update themselves whenever one of their parameters change. You can refer to the topic reference system in the Programmers Guide to read detailed information about this.

In a 3ds Max plug-in, any pointer to an object that is set, accessed, modified or deleted using the specific guidelines of the reference system is called a reference. It is always recommended to maintain a 3ds Max reference rather than a general pointer when keeping track of an object. A general pointer only refers to a memory address and is not capable of determining what 3ds Max or other plug-ins do to the object in that address. The target object can even be deleted, leaving your plug-in with a hanging pointer. When you are using a reference, your plug-in is informed of any changes to its target object. In order to use the references system and register the pointers as references, your plug-in should extend from the class ReferenceMaker. Most of the plug-in types are already extended directly or indirectly from ReferenceMaker (although there are exceptions such as Utility plug-ins).

Being a ReferenceMaker, your plug-in will need to implement four virtual functions from that class. These functions are:

Please note that you should never directly assign the address of an object to your reference. Neither should you call ReferenceMaker::SetReference() for this purpose. You will need to call reference handling methods such as ReferenceMaker::DeleteReference(), or ReferenceMaker::ReplaceReference() to modify or update your references.

Adding some functionality to your utility plug-in

The plug-in project Lesson1 is a valid plug-in but does not perform any specific task other than prompting a string. To demonstrate how you can have your plug-in interact with 3ds Max, lets modify that utility plug-in to show the name of the selected scene object in the UI panel. We will need to add some new functions and edit some existing ones for this. We also add the data member INode* mpMyNode to make a reference to the node in the scene graph that represents the currently selected scene object.

Creating a UI

Before starting to edit the source code, we need to make sure that we have a placeholder for the name of the selected node in the plug-in UI panel. If you are using the plug-in wizard, add a static text resource to your dialog within the .rc file to show the name of the selected scene object. To do this, open the Resource View in Visual Studio to see the resource file in the resource view panel. Expand the .rc file and then expand the Dialog item to see your plug-in dialog, by default named "IDD_PANEL". Now open the toolbox, from which you can drag a Static Text control and drop it in your UI panel. Set the caption to "Uninitialized" and edit the ID to "IDC_NODENAME". If you are not using the plug-in wizard and are following the steps in the description of <yourprojectname>.rc to manually create your dialog, you already have this static text control. The project Lesson3 implements a utility plug-in class named SampleRef that is also a reference maker, and displays the name of the selected node in the viewport. The following code snippets are copied from that project.

Using the reference system in your utility plug-in

After creating the UI panel we need to know when a new scene object is selected by the user. Our data member INode* mpMyNode is a reference to a node in the scene graph, and not to a scene object. Therefore it will be our responsibility to update this reference when the selected object in the viewport changes. The function UtilityObj::SelectionSetChanged() will help us here. 3ds Max calls this function whenever the user selects a new object or deselects the old one. We will write a function to update the caption of the static text we added to the dialog. We name this function SetText() here with the following implementation:

void SampleRef::SetText(const MSTR& s)
{
    // MSTR is the main string class in 3ds Max SDK.
    // hPanel is a data member pointing to the rollout page and
    // IDC_NODENAME is the name of the static text field in that rollout page.
    SetDlgItemText(hPanel, IDC_NODENAME, s.data());	
} 

We will need to call this function to update the text in the rollout page when the plug-in starts, a new node is selected, a selected node is changed or the node is de-selected. 3ds Max calls different functions in our plug-in when each of these situations happen. These function are:

  • UtilityObj::BeginEditParams() is called when the utility plug-in starts and the rollout page appears. We introduced this function in lesson 1. Note that we only update our reference (and do not directly update the text) by calling the ReplaceReference() here. This will consequently cause 3ds Max to call our SetReference(), where we update the text.
  • UtilityObj::SelectionSetChanged() is called when the user selects another object in the viewport or when he deselects a previously selected object. The data member INode* mpMyNode in our plug-in is a reference to a node in the scene graph, and not directly to a scene object. When a new scene object is selected the currently referenced node stays unchanged and our reference will not be notified. We will use this function to update our reference manually in this case. Again, we only update our reference and the text is updated as a result, similar to BeginEditParams() above.
  • ReferenceMaker::NotifyRefChanged() is called when there is a change in one of the reference targets of our plug-in. In this example, when the scene object that our reference node is representing changes its position or its name or gets deleted, the corresponding node (our reference) will be changed or deleted automatically. 3ds Max calls NotifyRefChanged() to notify us of such an event. We change the text in the rollout page in that function to reflect the change.

The following code snippets show the implementation of these functions. The Interface::GetSelNode() function used in the implementation of SampleRef::SelectionSetChanged() returns a pointer to the INode object corresponding to the currently selected object in the viewport.

void SampleRef::SelectionSetChanged(Interface *ip,IUtil *iu)
{
    if (ip->GetSelNodeCount() > 0) 
        ReplaceReference(0, ip->GetSelNode(0));
    else
        ReplaceReference(0, NULL);
}
void SampleRef::SetReference(int i, ReferenceTarget* pTarget)
{
    switch (i)
    {
    case 0:				//we have only one reference in this plug-in
    {
        INode* tmp = dynamic_cast<INode*>(pTarget);
        if (tmp != NULL) 
        {
            mpMyNode = tmp;
            MSTR s(tmp->GetName());
            s += _T(" is being observed now.");
            SetText(s);
        }
        else 
        {
            SetText(_T("No node is currently being observed."));
        }
    }
    break;
}
}
and:
RefResult SampleRef::NotifyRefChanged(const Interval& changeInt,	RefTargetHandle hTarget, 
    PartID& partID,	RefMessage message, BOOL propagate)
{
    // Should be true, but just in case
    if (mpMyNode) 
    {
        MSTR s(mpMyNode->GetName());
        s += _T(" is changed.");
        SetText(s);
    }
    return REF_SUCCEED;
}

You can compile the Lesson3 project and then start 3ds Max to see your plug-in (named SampleRef) come to life. You can click on Utilities > More to find it in 3ds Max.

References and the Track View

The animatable references of a 3ds Max plug-in (i.e. references extended from the class Animatable) are displayed in the Track View. We will see in Lesson 4 that such references can also be viewed and possibly edited in the Curve Editor if they are controllers (i.e. extended from the class Control).