Plug-ins can use the 3ds Max reference system to be notified when another entity changes in the schene. In the reference system schema a plug-in creates a reference to a second plug-in so if the second plug-in (reference target) is changed, deleted or updated, the first plug-in (reference maker) will be automatically notified. A plug-in can be both a reference target and a reference maker at the same time. An example of using references is when a scene object is following a second scene object. If the first object has a reference to the second one, it will be 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 have detailed information on 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. Contrary to this, your plug-in will be informed of the changes on its target object when we are using a reference. 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 listed below:
3ds Max calls this function to know how many references we are holding in our plug-in class.
3ds Max calls this function to give us a reference. We should store the given pointer in the reference whose index is given, and 3ds Max will notify us of any changes to the target node. Note: Only 3ds Max calls this function. Never call it to set or update one of your references. Use ReferenceMaker::ReplaceReference() instead.
3ds Max calls this function to get the ith reference managed by our plug-in instance.
3ds Max calls this function to let us know that one of our references has been changed. The complementary information is provided in the list of arguments:
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.
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. For this, select resource view from the view menu (or hit ctrl+shift+E) 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 select Toolbox from the view menu (ctrl+Alt+X) to 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 RefSample 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 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:
After creating the UI panel we need a function to update the caption of the static text in the dialog. The function SetText() below will do this:
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 of our plug-in when each of these situations happen. These function are:
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 += " is being observed now."; SetText(s); } else { SetText("No node is currently being observed."); } } break; } }
RefResult SampleRef::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message) { // Should be true, but just in case if (mpMyNode) { MSTR s(mpMyNode->GetName()); s += " 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.
The animatable references of a 3ds Max plug-in (i.e. references extended from the class Animatable) will be displayed in the Track View. We will see later 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).