TweetFollow Us on Twitter

3D World Plug-Ins

Volume Number: 14 (1998)
Issue Number: 10
Column Tag: Plugging-In

3D Made Easy Using 3D World Plug-Ins

by Robin Landsbert
Edited by Steve and Patricia Sheets

How to write QuickDraw 3D plug-ins for 3D World

Introduction

Creating an entire 3D application from scratch is a difficult task, even for an experienced Macintosh developer. Many of the concepts and user interface guidelines learned with 2D applications do not apply. To solve this problem, 3D World is available for developers to use as an 3D Graphic Shell program. 3D World provides an open and easy architecture for users to create and edit QuickDraw 3D data. For developers, 3D World is a method to quickly create QuickDraw 3D solutions. A developer can tailor 3D World for their specific market.

This article will explain how to develop 3D applications without starting from scratch. By using 3D World as an application shell you can easily add custom features for your specific market in the form of plug-ins. These could be import or export plug-ins for specific custom data sets or data formats, or whole vertical market applications like Interior Design. Thus, 3D World provide a standard way for the users to enter the data, while a third party developer can provide a plug-in which manipulates the data in a unique way.

3D World can be used by a developer to create custom applications such as: Modeling, Architecture, Interior Design, 3D Logos, 3D Charting, Contour mapping, Mathematics graphing, Educational Programming and many others. Developers can also export and import data in new and unique ways. Export examples include QuickTime tween movies, QuickTime VR panoramic and QuickTime VR object). On the input side, 3D World plug-ins can be created to communicate with 3D input devices, digitizers, 3D mice or data gloves. Although 3D World plug-ins do not need to use QuickDraw 3D, most do.

Additionally, the 3D World QuickDraw 3D Shared Library can be used by application developers to build their own QuickDraw 3D applications. This provides C++ wrappers to the QuickDraw 3D calls (with all C++'s advantages), and numerous extra utility routines to aid QuickDraw 3D development. The C++ headers are available on request from Microspot.

Figure 1. 3D World Application.

3D World

3D World was started at a QuickDraw 3D kitchen at Nice, France back in March 1995 when QuickDraw 3D 1.0 was in alpha. From the start, 3D World was designed to sit solely on top of QuickDraw 3D and was written entirely in object oriented C++ using Metrowerks PowerPlant as its application framework. All of the QuickDraw 3D calls where wrapped in C++ classes using the same object hierarchy as QuickDraw 3D uses.

Soon after that kitchen, it was decided a plug-in architecture was needed for 3D World. This was not only so we could extend 3D World's functionality and also allow third parties to write 3D World plug-ins, but also to allow us to tailor 3D World's functionality to the specific features needed. We did not want 3D World to succumb to bloatware as we wrote more and more features. Also junior programmers could work on plug-ins without affecting or needing to understand the main program. Plug-ins became a good training ground for QuickDraw 3D programming without needing to learn to write a whole 3D application.

Later it was decided to remove most of the functionality from 3D World and leave it as an application shell on which to build features. Plug-ins became hot-loadable by double clicking them. The plug-in architecture was also extended to allow more categories of plug-ins; No longer were you limited to just geometry entry tools and geometry modifier tools but now you could have plug-in commands, import and export plug-ins, and maybe the most powerful of all, plug-in palette windows. The application also became a shared library, exporting a number of routines for the plug-ins to use. Even the QuickDraw 3D C++ classes were shifted into a shared library so the application and all its plug-ins could share common code, thus reducing the memory overhead of having the same code in each of the plug-ins. This rearchitecting allows 3rd parties to use 3D World as a customizable vertical market 3D application shell. Developers can write the specific feature they need using plug-ins without having to write an entire 3D application.

3D World Plug-ins

There are currently 7 types of 3D World plug-ins - Entry, Modifier, Command, Import, Export, Palette and Idler. A plug-in may register itself as one or more of these types. The Entry and Modifier plug-ins are installed in the main Tools palette as a clickable icon. When the associated icon is selected and the user clicks in the main 3D window, the plug-in is invoked. At that time, entry tools create geometries and modifier tools change the attributes of the object clicked on. The Command plug-ins are similar to PhotoShop filters and are installed in the Plug-ins menu. When their menus are selected, the plug-in usually act on the selected objects. They can also set a mode. Import and Export plug-ins get installed as sub-menus in the File menu. Selecting their menu items import or export the entire document into or from the appropriate format. Palette plug-ins get installed in the Palettes menu and create their own palettes for the user to play with. Selecting the palette's menu shows the palette and clicking on any items within the palette executes its functionality. Idler plug-ins have no interface at all. They periodically get called by 3D World and just do background tasks like garbage collection, mode setting or animation. All plug-ins have full access to 3D World's retained mode QuickDraw 3D object hierarchy, so they may change that data at any time using QuickDraw 3D's routines. 3D World has no knowledge of what the plug-ins are doing and simply tells QuickDraw 3D to redraw the objects.

3D World plug-ins usually reside in the Plug-ins Folder which is located in the same folder as the 3D World application. Plug-ins can be put anywhere, but they will only be loaded automatically on launching 3D World if they are in the Plug-ins folder or any sub-folder within it. Other plug-ins can be "Hot-loaded" after launch just by double-clicking on them or dragging them into 3D World's main window (a bit like OpenDoc).

How to write a 3D World Plug-in

A 3D World plug-in is a standard Shared Library with a single exported entry point with the following prototype:

#pragma export on
extern "C" OSErr DoPlugInMessage(
   TPlugInMessage message,   // a series of messages from 3D World
   TIOParams* ioParams,      // info about the document, and callback routines
   long* refcon);            // a long for the plug-in to use to store its globals
#pragma export off

The main part of the plug-in is a big switch statement on the message parameter passed in by 3D World. There should always be a default case which should return notHandledErr so future plug-in messages are flagged as not implemented. The first message 3D World will pass in is the whatSortOfPlugInAreYou message and the routine is required to answer it to be loaded. This will occur as the application loads your plug-in, either at startup or when the user hot loads the plug-in after startup. The reply is passed back in ioParams->ioParam and can be one or multiple of the following enumerated types ORed together:

enum TPlugInType {
   geometryEntry      = 1,
   geometryModifier   = 1 << 1,
   palette            = 1 << 2,
   menuCommand        = 1 << 3,
   importPlugin       = 1 << 4,
   exportPlugin       = 1 << 5,
   idlerPlugin        = 1 << 6 };

The plug-in must also set the ioParams->paramRecVersion to the minimum version of 3D World it is willing to work with. In most cases this will be 0x00030000. All 3D World version information holds the major version in the top word, the minor version in the next byte and the bug-fix version in the last byte. So version 1.5.2 would be 0x00010502.

If the plug-in does not want to be loaded, it can return any non-zero error like notHandledErr and 3D World will not load the plug-in. For example, if 3D World was running in Demo mode, (ioParams->entryMask & eDemo) == eDemo) would be true. This is a good location to check for any libraries or environments that may be required. If they are not there, notHandledErr is returned. Otherwise return noErr, and 3D World will call the plug-in back with an initialize message. (Because 3D World is written in England, variable names will usually be "spelt the English way").

When the initialize message is received, the plug-in must initialize its global variables, open its resource fork (if needed) using ioParams->yourFileSpec, set up any palettes (if necessary), and return noErr. Again, if the plug-in can not, or is not to initialize its environment, return a non-zero error and the plug-in will not be loaded. From now on the shared library will remain loaded until it receives a dispose message, when it should dispose of any memory allocated in the initialize message.

After the initialize message, the plug-in will receive a doYouIdle message. This message is the application's way of asking the plug-in which idle times it requires. The reply should be in ioParams->ioParam and is a combination of TIdleFlags listed below:

enum TIdleFlags {
   kBeforeEvent            = 1L,
   kAfterEvent             = 1L <<  1,
   kBeforeDraw             = 1L <<  2,
   kAfterDraw              = 1L <<  3,
   kDuringDraw             = 1L <<  4,
   kAfterStartRender       = 1L <<  5,
   kBeforeEndRender        = 1L <<  6,
   kAfterStartWrite        = 1L <<  7,
   kBeforeEndWrite         = 1L <<  8,
   kBeforeAddObject        = 1L <<  9,
   kAfterAddObject         = 1L << 10,
   kBeforeDeleteObject     = 1L << 11,
   kAfterDeleteObject      = 1L << 12,
   kAfterSetUpWidgets      = 1L << 13,
   kPreAnimate             = 1L << 14,
   kAnimate                = 1L << 15,
   kPostAnimate            = 1L << 16,
   kSelectionChanged       = 1L << 17,
   kEndOfRenderLoop        = 1L << 18,
   kBeforeRevert           = 1L << 19,
   kAfterRevert            = 1L << 20 };

If idles are also requested before or after standard Macintosh events, the plug-in needs to return the event types they need in ioParams->theEvent.what. This is in the form of a standard Macintosh event mask. The plug-in will always get nullEvents. However the doYouIdle message is optional and the plug-in can just return a notHandledErr. More on idle messages later in this article.

This is the end of the sequence of messages a plug-in will receive when it is loaded during the launch of 3D World. Other messages will arrive when certain things happen in 3D World like user actions or updates or idles.

General Plug-in Messages

The plug-in may receive a giveAboutInfo message at any time when the user selects that plug-in from the About Plug-ins menu. The ioParams->ioParam field is used as a pointer to a TAboutInfo record which should be filled in so that it may be displayed to the user. This structure contains information associated with the plug-in needs to be displayed. If the plug-in provides a picture in AboutPtr->returnPict, the picture should contain all the information, as the other strings will not be used. The picture will be displayed in a dialog which will be dismissed when the user clicks on it. Otherwise a standard dialog will be displayed with the information strings displayed in it. It is suggested that these strings be stored in a STR# resource ID 32000. In fact all plug-in resources should be ID 32000 or over.

// ioParams->ioParam will be a pointer to a clear TAboutInfo record.
// Fill in as necessary although you may decide not to do anything.
case giveAboutInfo: {
   TAboutInfo* AboutPtr = (TAboutInfo*)(ioParams->ioParam);
   AboutPtr->version = GetVersion ();
   AboutPtr->returnPict =
                        PicHandle(Get1Resource('PICT',kBaseID));
   GetIndString (AboutPtr->author, kBaseID, 1);
   GetIndString (AboutPtr->company, kBaseID, 2);
   GetIndString (AboutPtr->address, kBaseID, 3);
   GetIndString (AboutPtr->phone, kBaseID, 4);
   GetIndString (AboutPtr->fax, kBaseID, 5);
   GetIndString (AboutPtr->eMail, kBaseID, 6);
   GetIndString (AboutPtr->aboutMessage, kBaseID, 7);
   return noErr ; }

A plug-in may also receive a userWantsToChangeOptions message. If the plug-in has any global options, this is the time to put up any modal dialog for the user to manipulate. Finally, just before the dispose message the plug-in will receive a savePrefs message. Save the preferences in a named resource of a particular type so that the plug-in can retrieve its globals again during the initialize message. When the savePrefs message and the initialize message are received, the current resource file is the 3D World Preferences file.

Geometry Entry and Modifier Plug-ins

If the plug-in sets the geometryEntry or geometryModifier bits in ioParams->ioParam in the whatSortOfPlugInAreYou message, it will receive a drawIcon message whenever the tool needs drawing in the Tools palette. It should draw the icon in a 24x24 icon at ioParams->theRect. The plug-in can also draw any help messages about the tool in the Help palette by calling ioParams->messageProc(message).

The clicked, doubleClicked and unclicked messages are very similar to the drawIcon message. When receiving these messages, the plug-in should draw its icon as in the drawIcon message, but it may also invoke some other functionality like show or hide a palette. For example, when the clicked message is received, the user has just clicked on the icon. When the user double clicks on an icon, the doubleClicked message will be sent immediately.

The plug-in receives an activate and deactivate message from 3D World when the tool gets revealed or hidden in a stack of plug-in tools in the Tool palette. At this time, the plug-in can open or close its resource file. Even if the plug-in has been deactivated, it will still receive idle messages if they are requested.

If the plug-in tool is selected, while the user moves the cursor over the main 3D window, the plug-in will constantly get an adjustCursor message. It can just return a notHandledErr in order to show the default cursor, or the plug-in can return noErr after having set its own cursor. This can depend on what object the cursor is over.

If the user presses a key while the plug-in tool is selected, it will get a keyPressed message. The event will be in ioParams->theEvent and the plug-in may either deal with the event and return noErr, or return notHandledErr and 3D World will deal with the key stroke itself. This message is useful if the plug-in handles arrow keys.

When the user clicks in the main 3D window with a plug-in selected, it will get different messages depending on whether it is an entry or a modifier tool.

If you are programming an entry tool, the first message the tool will get as the user clicks is a doCompleteEntry message. Most geometry entry plug-ins will return notHandledErr. If noErr is returned, the plug-in must handle the entire entry at this time. Otherwise the next message the plug-in will get is a multipleClickEntry message. To enter geometries using only a click and drag method, return notHandledErr and WaitMouseMoved will be called. Otherwise return noErr and the tool will immediately get the next message.

The next message the plug-in will get is a doYouNeedUndo message. To handle the undoing, return noErr, otherwise return notHandledErr and 3D World will immediately execute the default undo method for the tool type. Next the plug-in will get an addGeometryToGroup message in which the plug-in adds a complete QuickDraw 3D geometry object into ioParams->theGroup. The geometry must be scaled to fit within 0,0,0 and 1,1,1. However, if (ioParams->entryMask & eRadiusMode) == eRadiusMode, the geometry must be within a 2x2x2 cube centered around the origin. The geometry should also be colored using ioParams->curColor. A call to Q3Group_AddObject (ioParams->theGroup, myNewGeometryObject) adds the plug-in's geometry into 3D World's group. On returning noErr, 3D World will handle all the dragging out, resizing and shift key constrainer code until the user lets go of the mouse button. Otherwise if the plug-in set ioParams->ioParam != 0, the geometry should be placed at ioParams->mousePoint3D and sized according to ioParams->unitsPerMeter, and the user will not stretch the object during entry.

case addGeometryToGroup: {
   if (ioParams->theGroup)   {
      TQ3BoxData boxData;
      boxData.orientation.x = boxData.orientation.z = 0;
      boxData.minorAxis.y = boxData.minorAxis.z = 0;
      boxData.majorAxis.x = boxData.majorAxis.y = 0;
      if ((ioParams->entryMask & eRadiusMode) == eRadiusMode)
         {
// create box size 2, centered about the origin
         boxData.origin.x = boxData.origin.y =
         boxData.origin.z = -1;
         boxData.orientation.y = boxData.minorAxis.x =
         boxData.majorAxis.z = 2;
         }
      else { 
// create unit box at origin
         boxData.origin.x = boxData.origin.y =
         boxData.origin.z = 0;
         boxData.orientation.y = boxData.minorAxis.x =
         boxData.majorAxis.z = 1;
         }
      boxData.faceAttributeSet = nil;
      boxData.boxAttributeSet = Q3AttributeSet_New ();
// add current color
      if (boxData.boxAttributeSet) 
         Q3AttributeSet_Add(boxData.boxAttributeSet,
          kQ3AttributeTypeDiffuseColor, &ioParams->curColor);
// create box object
      TQ3BoxObject boxObject = Q3Box_New (&boxData);
      Q3Object_Dispose (boxData.boxAttributeSet);
      if (boxObject) {
// add the box to the group
         Q3Group_AddObject (ioParams->theGroup, boxObject);
         Q3Object_Dispose (boxObject);
         return noErr;
         }
      }
   return notHandledErr;
   }

A geometry modifier tool will first get a giveSelectedGroup message. If noErr is returned, the tool will get the selected group in ioParams->hitObject in the following canYouDoAnythingToThisGeometry message. Otherwise during the canYouDoAnythingToThisGeometry message, the plug-in will get the geometry or group that the user clicked on in ioParams->hitObject. During the canYouDoAnythingToThisGeometry message, the plug-in should return noErr if it can modify the geometry in some way. If the plug-in returns an error, the user action will be aborted. The next message the plug-in receives is the doYouNeedUndo message. If the plug-in handles the undoing, return noErr; otherwise return notHandledErr and 3D World will immediately execute the default undo method for the tool type. When the plug-in receives the final modifyGeometry message, it should act on the ioParams->hitObject. The hit object usually is a group object, so if plug-in needs to modify the data of all the geometries in the group, it will have to iterate through the group. This can be done using the something like the following routine:

void IterateGroup (TQ3GroupObject theGroup)
{
TQ3GroupPosition pos;
if (Q3Group_GetFirstPosition(theGroup, &pos) == kQ3Success)
   {
   do {
      TQ3Object theObject;
      if (Q3Group_GetPositionObject(theGroup,pos,&theObject)
         == kQ3Success) {
         if (Q3Shared_GetType(theObject)==kQ3SharedTypeShape)
            {
            TQ3ObjectType theType=Q3Shape_GetType(theObject);
            if (theType == kQ3ShapeTypeGroup)
               IterateGroup(TQ3GroupObject(theObject));//recurse
            else if (theType == kQ3ShapeTypeGeometry)
               {
               theType = Q3Shape_GetLeafType (theObject);
               switch (theType) {
// modify the geometry in some way
                  }
               }
            }
         Q3Object_Dispose (theObject);
         }
      }
   while ((Q3Group_GetNextPosition (theGroup, &pos) ==
                              kQ3Success) && pos);
   }
}

A geometry modifier plug-in may actually want to modify the camera object and not a geometry, much as the Orbit, Walkthrough, VR and Teleport plug-ins do. In this case the modifyGeometry message might look like this:-

case modifyGeometry: {
   TQ3CameraObject theCamera;
   if (Q3View_GetCamera (ioParam->theView, &theCamera)
               == kQ3Success) && theCamera) {
      TQ3CameraData theData;
      if (Q3Camera_GetData(theCamera, &theData)==kQ3Success){
         while (StillDown ()) {
            Point theMouse;
            GetMouse (&theMouse);
// modify the camera data in some way based on the mouse position
            Q3Camera_SetData(theCamera, &theData);
//update the camera
            ioParams->drawProc (); // draws the main 3D view
            }
         }
      Q3Object_Dispose (theCamera);
      }
   }

Import And Export Plug-ins

Import plug-ins are also easy to program. The first message the plug-in will get on selecting the command is doYouNeedUndo. Most import plug-ins will not implement this message and will let 3D World implement the default import undo method. The only other message the plug-in will get is an addGeometryToGroup message. When this occurs, the plug-in should put its own StandardGetFile to select the file to import. If a user selects a file, then the data is added as QuickDraw 3D objects into ioParams->theGroup using Q3Group_AddObject(ioParam->theGroup, myImportedDataObject). Any error returned will cause 3D World to cancel adding the group to the document.

Export Plug-ins are even easier. They will get a modifyGeometry message and should call StandardPutFile. If the user selects a file, the contents of ioParams->theGroup are saved into this file in the appropriate format using a routine similar to IterateGroup.

Menu Command Plug-ins

Menu Command plug-ins are similar to geometry modifier plug-ins except they always apply to the entire selected group, and therefore their actions can be applied to more than one item at a time. Often plug-ins register themselves as both (geometryModifier + menuCommand), and show up in the tool palette and the plug-in menu. When a menu command plug-in is selected by the user, it will only get a doYouNeedUndo message followed by a modifyGeometry message where ioParams->theGroup is the selected group. However the plug-in will get a canYouDoAnythingToThisGeometry message whenever the user clicks on the menu bar. After examining ioParams->theGroup, if the plug-in returns noErr, its menu item will be enabled. If it returns notHandledErr, the menu item will be greyed out. If ioParams->ioParam is set to 1, the menu item will get a checkmark next to it. Otherwise if ioParams->ioParam is not 1 or 0, then ioParams->ioParam is assumed to be the character to be used as a checkmark for the plug-in's menu item.

Idler Plug-ins

If the plug-in returned noErr from the doYouIdle message, it will be called periodically when certain things happen in 3D World depending on the flags it passed back in ioParams->ioParam. An idle message handler should look something like this:

case idle: {
   if (ioParams->ioParam & kSelectionChanged) {
      TQ3GroupObject selectedGroup =
                        ioParams->getGroupObject(eSelectedGroup);
// display info on new selection
      }
   else if (ioParams->ioParam & kBeforeEvent) {
// look at ioParams->theEvent.what and act on it
// You can modify the event record and 3D World will deal with the new values
      }
   ioParams->ioParam = 6;
// return number of tick before next nullEvent callback
   return noErr;
   }

For any given call of the idle message, only one bit of ioParams->ioParam will be set. Each bit will signify a different time the idle message is being called. The plug-in can act appropriately depending on what the idle message is. At the end of each idle message, set the ioParams->ioParam to the number of ticks the plug-in is willing to wait until it receives another nullEvent event. The plug-in can also return an error of kDirtyViewErr and this will cause 3D World to refresh the window. However, returning kDirtyViewErr after a kBeforeDraw idle will cause 3D World to draw using GWorlds. This is useful to save a picture or record to a QuickTime movie as ioParams->thePort will actually be the rendered GWorld in the kAfterDraw idle.

case idle: {
   if (ioParams->ioParam & kBeforeDraw)
      return kDirtyViewErr; 
// causes 3D World to use GWorlds
   if (ioParams->ioParam & kAfterDraw) {
// save contents of ioParam->thePort which is a GWorld
      }
   ioParams->ioParam = 6;
// return number of tick before next nullEvent callback
   return noErr;
   }

The idle messages can be used to monitor things like changes in the camera position, and then update the camera palette to reflect those changes. NaviCam uses this type of code:

case idle: {
   if (ioParams->ioParam & kAfterDraw) {
      TQ3CameraObject theCamera;
      if (Q3View_GetCamera (ioParam->theView,
                 &theCamera) == kQ3Success) && theCamera)
         {
         TQ3CameraData theData;
         if (Q3Camera_GetData (theCamera,
                 &theData) == kQ3Success)
            {
            static TQ3CameraData sOldCameraData;
            if (theData != sOldCameraData) {
// update the palette based on the new camera data
               UpdatePalette (&theData);
               sOldCameraData = theData; 
// save the current data
               }
            }
         Q3Object_Dispose (theCamera);
         }      
      }
   ioParams->ioParam = 6;
// return number of tick before next nullEvent callback
   return noErr;
   }

Palette Plug-ins

The most powerful of 3D World's plug-in types are the palette plug-ins; They are also the most complicated plug-ins to write. They do not rely on the standard message handling entry routine. Instead they install callback routines using the Plug-in API that 3D World exports from the application (just as if it were itself a shared library). All the plug-in API routines that 3D World exports start with PL_. A complete listing of the current routines available are in PluginAPI.h.

The following code shows how to program a palette plug-in. During the initialize message, or at any appropriate time, SetUpPaletteWindow should be executed:

Figure 2. Plug-in Palette Window.

TPLWindow gPalette = nil;

OSErr SetUpPaletteWindow (TIOParams* ioParams) {
   OSErr err = noErr;
   GrafPtr oldPort;
   GetPort (&oldPort);
   Rect globalBounds = {34, 4, 270, 180};
// set up some default rect
   Handle h = Get1Resource ('RECT', kBaseID);
   if (h) {
// read rect from resource
      BlockMoveData (*h, &globalBounds, sizeof (Rect));
      ReleaseResource (h);
      }
   TPLWindowExtras extras = TPLWindowExtras (ePL_CloseBox |
                                            ePL_AddToMenu);
   gPalette = PL_NewWindow (ePL_Palette, extras,
                  ioParams->yourFileSpec.name,
                  &globalBounds, &globalBounds,
                  Get1Resource ('DITL', kBaseID), 0);
   if (gPalette) {
      TPLWindowItem item = PL_GetIndexWindowItem (gPalette, kApplyButton);
      PL_InstallWindowItemCallBack (item, ePL_Clicked, ClickButton); 
// call this function when this item is clicked
      item = PL_GetIndexWindowItem (gPalette, kSliderItem);
      PL_InstallWindowItemCallBack (item, ePL_ValueChanged, ValueChanged); 
// call this function when this item's value changes
      PL_SetWindowItemMin (item, 0);
      PL_SetWindowItemMax (item, 100);
      PL_SetWindowItemValue (item, 0);
      item = PL_GetIndexWindowItem(gPalette,
kSliderValueItem);
      PL_SetWindowItemValue (item, 0) 
// displays as text the slider's value
      item = PL_GetIndexWindowItem (gPalette, kUserItem);
      PL_InstallWindowItemCallBack (item, ePL_Draw, DrawUserItem); 
// install this function to draw this item
      item = PL_GetIndexWindowItem (gPalette, kOnItem);
      PL_InstallWindowItemCallBack (item, ePL_Clicked, ClickRadio); 
// install this function when this item is clicked
      PL_SetWindowItemValue (item, 1);
      item = PL_GetIndexWindowItem (gPalette, kOffItem);
      PL_InstallWindowItemCallBack (item, ePL_Clicked, ClickRadio);
      PL_SetWindowItemValue (item, 0);
      }
   else
      err = notHandledErr;
   SetPort (oldPort);
   return err;
   }

All the callbacks have the same prototype. They are passed two parameters (TPLWindowItem and TPLInheritedProcPtr) and return a long. TPLWindowItem is the item that the callback was installed in, while TPLInheritredProcPtr is the routine that provides the default behavior of 3D World.

long ClickButton (TPLWindowItem windowItem,
                        TPLInheritedProcPtr inheritedProc)
{
SysBeep (30);
return inheritedProc (windowItem);
// calls the default Click handler within 3D World
}

long ValueChanged (TPLWindowItem windowItem,
                        TPLInheritedProcPtr inheritedProc)
{
if (inheritedProc (windowItem)) {
// this handles the changing of the items value and returns true if 
// the value actually changed
   long val = PL_GetWindowItemValue (windowItem);
   TPLWindowItem item = PL_GetIndexWindowItem (gPalette, kSliderValueItem);
   PL_SetWindowItemValue (item, val);
// because this is a text item, 3D World converts the long to a string for you
   return true;
   }
return false;
}

long DrawUserItem (TPLWindowItem windowItem,
                        TPLInheritedProcPtr)
{
// no need to call the InheritedProc as for user items as it does nothing
Rect r;
PL_GetWindowItemRect (windowItem, &r);
FrameRoundRect (&r, 16, 16);
return true;
}

long ClickRadio (TPLWindowItem windowItemHit,
                        TPLInheritedProcPtr inheritedProc)
{
if (inheritedProc (windowItemHit)) {
// this handles the tracking of the mouse while it is down 
// and returns true if the mouse was released over the radio
   long val = PL_GetWindowItemValue (windowItemHit);
// check to see if it was actually turned on
   if (val) {
      TPLWindowItem item;
      short itemHit = PL_GetWindowItemNumber (windowItemHit);
      if (itemHit == kOnItem)
         item = PL_GetIndexWindowItem (gPalette, kOffItem);
      else
         item = PL_GetIndexWindowItem (gPalette, kOnItem);
      PL_SetWindowItemValue (item, 0); 
// turn the other radio button off
      }
   return true;
   }
return false;
}

Once the SetUpPaletteWindow routine has been executed, nothing more really needs to be done in the DoPlugInMessage routine except handling the standard messages that all plug-ins must handle (like dispose, giveAboutInfo, etc.). 3D World now controls the palette window depending on user actions. For example, whenever the user clicks the Apply button in the palette, the ClickButton function gets called. Whenever the scrollbar's value changes because the user has clicked it, the ValueChanged function gets called, so the corresponding text item value is set to correspond to the scrollbar's value. Whenever the user item needs to be drawn, the DrawUserItem function gets called and a rounded rectangle gets drawn around the bounds of the user item. Finally, whenever the user clicks in either the on or off radio buttons, the ClickRadio function gets called and it sets the other radio button.

A wealth of PL_Routines are available in 3D World; The most commonly used ones are used in the above sample code. Most are very self explanatory. Obviously this sample code implements very simplistic functions, and the user interface is terrible, but you could apply some quite complex routines to alter the nature of the QuickDraw 3D groups within 3D World in exactly the same way. Just run the demo version of 3D World included on the CD and see what can be done using palette plug-ins.

Object Approach to QuickDraw 3D

QuickDraw 3D is not an object oriented API. Mcrospot has wrapped a majority of the QuickDraw 3D calls into C++ objects. Most of the 3D World plug-ins have been written using these QuickDraw 3D C++ classes, which is much simpler than the sample code given. Although some of the more obscure functionality of the plug-in API has not been described here, all of the common and basic ideas needed to write a 3D World plug-in have been discussed. Example plug-in code and the two relevant header files (3DWorldPlugIn.h and PluginAPI.h) are provided on the CD along with a demo version of 3D World 3.0.

Conclusion

Microspot has been able to factor out most of the functionality of 3D World by using a plug-in architecture. Over 100 different plug-ins have all be written using this messaging system. If you want to know more about the 3D World plug-in API you can write to Robin Landsbert direct at robin.landsbert@microspot.co.uk. You can also visit the 3D World API web page at http://www.microspot.com/dev/. For great example images of what can be done with 3D World 3.0 and the LightWorks renderers visit http://www.diamondschmitt.com/beta4.html.

Microspot USA Inc.
12380 Saratoga-Sunnyvale Road,
Saratoga, CA 95070
USA
Tel: 408 253 2000
Fax: 408 253 2055
http://www.microspot.com

Microspot UK Ltd.
London House,
Suite 6, 5-11, London Road,
Maidstone, Kent. ME16 8HR
England
Tel: +44 (0)1622-687771 (drop zero outside UK)
Fax: +44 (0)1622-690801
http://www.microspot.co.uk


Robin Landsbert has been programming Macintosh computers since 1985 and works for Microspot Ltd. He is the Project Manager of 3D World and can be reached at robin.landsbert@microspot.co.uk

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

OmniGraffle Pro 7.2.2 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
OmniGraffle 7.2.2 - Create diagrams, flo...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
Spotify 1.0.44.100. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
Microsoft OneNote 15.29 - Free digital n...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
WALTR 2 2.0.8 - $39.95
WALTR 2 helps you wirelessly drag-and-drop any music, ringtones, videos, PDF, and ePub files onto your iPhone, iPad, or iPod without iTunes. It is the second major version of Softorino's critically-... Read more
Dropbox 16.3.27 - Cloud backup and synch...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
EtreCheck 3.1.5 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
Carbon Copy Cloner 4.1.12 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
VueScan 9.5.62 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
SpamSieve 2.9.27 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more

Latest Forum Discussions

See All

Track Santa with these three festive app...
Christmas is fast approaching and that means it's time to prepare for Santa's yearly pilgrimage around the globe. Christmas Eve is an exciting time as parents help their kids get ready to welcome Santa. You've got the cookies and milk all planned... | Read more »
Galaxy on Fire 3 and four other fantasti...
Galaxy on Fire 3 - Manticore brings the series back for another round of daring space battles. It's familiar territory for folks who are familiar with the franchise. If you've beaten the game and are looking to broaden your horizons, might we... | Read more »
The best apps for your holiday gift exch...
What's that, you say? You still haven't started your holiday shopping? Don't beat yourself up over it -- a lot of people have been putting it off, too. It's become easier and easier to procrastinate gift shopping thanks to a number of apps that... | Read more »
Toca Hair Salon 3 (Education)
Toca Hair Salon 3 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Winter comes to Darkwood as Seekers Note...
MyTona, based in the chilly Siberian city of Yakutsk, has brought a little festive fun to its hidden object game Seekers Notes: Hidden Mystery. The Christmas update introduces some new inhabitants to players, and with them a chance to win plenty of... | Read more »
Bully: Anniversary Edition (Games)
Bully: Anniversary Edition 1.03.1 Device: iOS Universal Category: Games Price: $6.99, Version: 1.03.1 (iTunes) Description: *** PLEASE NOTE: This game is officially supported on the following devices: iPhone 5 and newer, iPod Touch... | Read more »
PINE GROVE (Games)
PINE GROVE 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: A pine grove where there are no footsteps of people due to continuous missing cases. The case is still unsolved and nothing has... | Read more »
Niantic teases new Pokémon announcement...
After rumors started swirling yesterday, it turns out there is an official Pokémon GO update on its way. We’ll find out what’s in store for us and our growing Pokémon collections tomorrow during the Starbucks event, but Niantic will be revealing... | Read more »
3 reasons why Nicki Minaj: The Empire is...
Nicki Minaj is as business-savvy as she is musically talented and she’s proved that by launching her own game. Designed by Glu, purveyors of other fine celebrity games like cult favorite Kim Kardashian: Hollywood, Nicki Minaj: The Empire launched... | Read more »
Clash of Clans is getting its own animat...
Riding on its unending wave of fame and success, Clash of Clans is getting an animated web series based on its Clash-A-Rama animated shorts.As opposed to the current shorts' 60 second run time, the new and improved Clash-A-Rama will be comprised of... | Read more »

Price Scanner via MacPrices.net

New 2016 13-inch Touch Bar MacBook Pros on sa...
B&H Photo the new 2016 Apple 13″ 2.9GHz/256GB Touch Bar MacBook Pros on sale for $50 off MSRP, each including free shipping plus NY sales tax only: - 13″ 2.9GHz/256GB Touch Bar MacBook Pro Space... Read more
12-inch 1.2GHz Space Gray Retina MacBook on s...
B&H Photo has dropped their price on the 2016 Apple 12″ 1.2GHz Space Gray Retina MacBook (MLH82LL/A) to $1399 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’... Read more
Never Settle for Low Performing Wifi With iOS...
AppYogi Software has announced the release of WiFi Signal Strength Status App 1.0, the company’s new utility developed exclusively for macOS. WiFi Signal Strength Status App features a unique, single... Read more
New 2016 13-inch Touch Bar MacBook Pros in st...
B&H Photo has stock of new 2016 Apple 13″ Touch Bar MacBook Pro models, each including free shipping plus NY sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray: $1999 - 13″ 2.... Read more
New 2016 15″ Touch Bar MacBook Pros in stock...
B&H Photo has new 2016 Apple 15″ Touch Bar MacBook Pro models in stock today including free shipping plus NY sales tax only: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray: $2799 - 15″ 2.7GHz... Read more
DietSensor App Targeting Diabetes and Obesity...
DietSensor, Inc., a developer of smart food and nutrition applications designed to fight diabetes and obesity and help improve overall fitness, has announced the launch of its DietSensor app for... Read more
Holiday 2016 13-inch 2.0GHz MacBook Pro sales...
B&H has the non-Touch Bar 13″ MacBook Pros in stock today for $50-$100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz MacBook Pro Space Gray (MLL42LL/A): $1449 $... Read more
Holiday sale: Apple TVs for $51-$40 off MSRP,...
Best Buy has dropped their price on the 64GB Apple TV to $159.99 including free shipping. That’s $40 off MSRP. 32GB Apple TVs are on sale right now for $98 on Sams Club’s online store. That’s $51 off... Read more
12-inch Retina MacBooks, Apple refurbished, n...
Apple has restocked a full line of Certified Refurbished 2016 12″ Retina MacBooks, now available for $200-$260 off MSRP. Refurbished 2015 models are available starting at $929. Apple will include a... Read more
Holiday sale: 12-inch Retina MacBook for $100...
B&H has 12″ Retina MacBooks on sale for $100 off MSRP as part of their Holiday sale. Shipping is free, and B&H charges NY sales tax only: - 12″ 1.1GHz Space Gray Retina MacBook: $1199 $100... Read more

Jobs Board

Integration Technician, *Apple* - Zones, In...
…at Zones and for our customers each day. Position Overview The Apple Integration Technician will be responsible for performing customer specific configuration Read more
*Apple* Brand Ambassador (Macy's) - The...
…(T-ROC), is proud of its unprecedented relationship with our partner and client, APPLE ,in bringing amazing" APPLE ADVOCATES"to "non" Apple store locations. Read more
*Apple* Retail - Multiple Positions- Trumbul...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
US- *Apple* Store Leader Program - Apple (Un...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.