The Ring Array's implementation of ClassDesc::BeginCreate() installs a command mode that manages the creation process. This command mode is an instance of class RingMasterCreateMode. See the section on CommandModes and Mouse Procs for more information about command modes. Below is the method of ClassDesc to begin the creation of the Ring Array. Note how the command mode is pushed on the command stack.
int RingMasterClassDesc::BeginCreate(Interface *i) { IObjCreate *iob = i->GetIObjCreate(); // This creates a new ring master object and starts the editing // of the objects parameters. theRingMasterCreateMode.Begin( iob, this ); // This sets the creation command mode as the active mode // at the top of the command stack. iob->PushCommandMode( &theRingMasterCreateMode ); return TRUE; }
A command mode has a mouse proc that handles the user / mouse interaction while the command mode is in effect. A method of the command mode, MouseProc(), is expected to return an instance of a class derived from MouseCallBack. This is the procedure that defines the user / mouse interaction during the system creation. The RingMasterCreateMode returns a pointer to an instance of RingMasterCreationManager. The RingMasterCreationManager is derived from both MouseCallback and ReferenceMaker. This allows it to handle the user / mouse interaction and to make references to the nodes it creates in the scene. Below is the code for the command mode:
// This is the command mode that manages the overall process when // the system is created. class RingMasterCreateMode : publicCommandMode { // This instance of RingMasterCreationMangaer handles the user/mouse // interaction as the Ring Array is created. RingMasterCreationManager proc; public: // These two methods just call the creation proc method of // the same name. // This creates a new ring master object and starts the editing // of the objects parameters. This is called just before the // command mode is pushed on the stack to begin the creation // process. void Begin( IObjCreate *ioc, ClassDesc *desc ) { proc.Begin( ioc, desc ); } // This terminates the editing of the ring masters parameters. // This is called just before the command mode is removed from // the command stack. void End() { proc.End(); } // This returns the type of command mode this is. See the online // help under this method for a list of the available choices. // In this case we are a creation command mode. int Class() { return CREATE_COMMAND; } // Returns the ID of the command mode. This value should be the // constant CID_USER plus some random value chosen // by the developer. int ID() { return CID_RINGCREATE; } // This method returns a pointer to the mouse proc that will // handle the user/mouse interaction. It also establishes // the number of points that may be accepted by the mouse proc. // In this case we set the number of points to 100000. The user // process will terminate prior to this many points being // entered when the mouse proc returns FALSE. // The mouse proc returned from this method is an instance of // RingMasterCreationManager. Note that that class is derived // from MouseCallBack. MouseCallBack *MouseProc(int *numPoints) { *numPoints = 100000; return &proc; } // This method is called to flag nodes in the foreground plane. // We just return the standard CHANGE_FG_SELECTED value // to indicate that selected nodes will go into the foreground. // This allows the system to speed up screen redraws. See the // Advanced Topics section on Foreground / Background planes // for more details. ChangeForegroundCallback *ChangeFGProc() { return CHANGE_FG_SELECTED; } // This method returns TRUE if the command mode needs to // change the foreground proc (using ChangeFGProc()) and FALSE // if it does not. BOOL ChangeFG( CommandMode *oldMode ) { return (oldMode->ChangeFGProc() != CHANGE_FG_SELECTED); } // This method is called when a command mode becomes active. We // don't need to do anything at this time so our implementation // is NULL void EnterMode() {} // This method is called when the command mode is replaced by // another mode. Again, we don't need to do anything special here. void ExitMode() {} };
Note that the MouseProc() method returns a number of points it uses in its creation process. This implementation returns 100000. The user will never actually enter 100000 points -- the process is aborted when the mouse proc returns FALSE. This happens after the user has released the mouse button and the process is finished, or if the user aborts the process.
The mouse proc handles the user / mouse interaction. This process works as follows: When the user first presses the mouse button the nodes are created in the scene -- a single central node (the parent) and the user-specified number of child nodes. The children are arranged in a circle around the parent. As the user holds the mouse button down and drags, the radius of the system is changed to reflect the mouse position. When the user releases the mouse button the creation process is finished and the nodes positions are set. If the user right clicks the mouse while the left mouse button is still down the process is aborted. What happens in detail during creation is described below:
When the user first presses the left mouse button in the viewport the system first creates the dummy object at the center of the system. It does this by calling Interface::CreateInstance(). This method creates a new instance of a plug-in class by specifying the Class_ID and SuperClassID of the plug-in. A node in the scene is then created from this instance using Interface::CreateObjectNode(). Next the method GenBoxObject::SetParams() is called to set the parameters used by the box objects. At this point, a single dummy object has been created in the scene. The code to do this is shown below:
// Snap the inital mouse point mat.IdentityMatrix(); center = vpx->SnapPoint(m,m,NULL,SNAP_IN_PLANE); mat.SetTrans(center); // This method creates an instance of a plug-in object // given its SuperClassID and ClassID. Here we create // a dummy object at the center of the Ring Array system. DummyObject *dumObj = (DummyObject *)createInterface-> CreateInstance(HELPER_CLASS_ID,Class_ID(DUMMY_CLASS_ID,0)); assert(dumObj); // This method creates a node given the instance. dummyNode = createInterface->CreateObjectNode(dumObj); // This sets the size of the dummy object // box representation dumObj->SetBox(Box3(Point3(-DUMSZ,-DUMSZ,-DUMSZ), Point3(DUMSZ,DUMSZ,DUMSZ)));
The plug-in then creates another dummy object for the slave nodes. The code to do this is similar:
// These are the instances that surround the center of // the Ring Array GenBoxObject *ob = ( GenBoxObject *)createInterface-> CreateInstance(GEOMOBJECT_CLASS_ID,Class_ID(BOXOBJ_CLASS_ID,0)); ob->SetParams(BOXSZ,BOXSZ,BOXSZ,1,1,1,FALSE);
The next step in the creation process is to create the user-specified number of ring nodes, those that surround the center. The loop below is where this is done.
// This is the loop that creates the nodes in the ring // array, sets their controller, and attaches them to // the parent node. for (int i=0; i<theMaster-> GetNum(createInterface->GetTime()); i++) { // Create a new node given the instance. newNode = createInterface->CreateObjectNode(ob); // Create a new slave controller of the master control. // This constructor creates a reference from the slave // controller to the master. This is done so the // system is aware of the dependency between the // slave controller and the RingMaster object. SlaveControl* slave = new SlaveControl(theMaster,i); // Set the transform controller used by the node. newNode->SetTMController(slave); // Attach the new node as a child of the central node. dummyNode->AttachChild(newNode); // Set the new node as the 'i-th' slave node // of the master object. theMaster->SetSlaveNode(i,newNode); }
This code fragment demonstrates several concepts.
The statement newNode = createInterface->CreateObjectNode(ob); calls a method of the interface class that creates a new node in the scene given an object pointer. The object pointer in this case is the dummy helper object.
The statement SlaveControl* slave = new SlaveControl(theMaster,i); creates a new slave controller object. It passes the RingMaster object and the index of this node to the constructor.
The statement newNode->SetTMController(slave); establishes the transform controller used by the node. We set it to our slave controller (and again, the slave controller just uses the master controller to get all of its values).
The statement dummyNode->AttachChild(newNode); sets the new node as a child of the dummy node at the center. In this way the child nodes will inherit the transformation of the parent node (for example, as it is moved or rotated in the scene). The attached nodes are still of course free to move on their own, independent of the parent (but driven by the master).
The statement theMaster->SetSlaveNode(i,newNode); is used to add the node to the list of nodes maintained by the master.
As the user drags the mouse while the left button is still down, the plug-in mouse proc receives MOUSE_MOVE messages. The plug-in responds by updating the radius value and redrawing the viewports.
case MOUSE_MOVE: if (node0) { r = (float)fabs(vpx->SnapLength(vpx->GetCPDisp(center,Point3(0,1,0),pt0,m))); theMaster->SetRad(0,r); theMaster->radSpin->SetValue(r, FALSE ); createInterface->RedrawViews(createInterface->GetTime(),REDRAW_NORMAL,theMaster); } res = TRUE; break;
When the user releases the mouse button to end the drag operation they have finalized the creation process.
else { // The mouse has been released - finish the creation // process. Select the first node so if we go into // the motion branch we'll see its parameters. // We set the flag to ignore the selection change // because the NotifyRefChanged() method receives // a message when the node is selected or deselected // and terminates the parameter editing process. This // flag causes it to ignore the selection change. ignoreSelectionChange = TRUE; createInterface->SelectNode( theMaster->GetSlaveNode(0) ); ignoreSelectionChange = FALSE; // This registers the undo object with the system so // the user may undo the creation. theHold.Accept(IDS_CREATE); // Set the return value to FALSE to indicate the // creation process is finished. This mouse proc will // no longer be called until the user creates another // Ring Array. res = FALSE; }
The code above shows how the Ring Array is created. When the mouse proc has finished the user is still in the Ring Array command mode and may create another Ring Array. The system calls ClassDesc::EndCreate() when the object creation process is finished. This may happen if the user changes to another branch of the command panel or selects a tool from the toolbar that replaces the creation command mode.