Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

Adding Custom Data to QuickDraw 3D Objects: Difference between revisions

From Higher Intellect Vintage Wiki
(Created page with "by PABLO FERNICOLA, NICK THOMPSON and KENT DAVIDSON<br> <i>Custom attributes and elements provide a way to attach data such as scaling information, sound, and strings to Qui...")
 
No edit summary
 
Line 415: Line 415:
because it doesn't need to be inheritable. When the object we attach the custom
because it doesn't need to be inheritable. When the object we attach the custom
element to is read into one of the many viewers that support custom elements,
element to is read into one of the many viewers that support custom elements,
the viewer can communicate through Apple events with applications like Netscape
the viewer can communicate through Apple events with applications like [[Netscape Navigator]]<sup>(TM)</sup> (or your favorite Web browser) to produce 3D
Navigator<sup>(TM)</sup> (or your favorite Web browser) to produce 3D
navigation. A sample application that illustrates the idea is included on this
navigation. A sample application that illustrates the idea is included on this
issue's CD. See "3D Web Content Using 3DMF and Netscape Navigator" for more
issue's CD. See "3D Web Content Using 3DMF and Netscape Navigator" for more
Line 428: Line 427:
</h3>
</h3>
BY JOHN LOUCH<p>
BY JOHN LOUCH<p>
With the advent of Netscape Navigator 2.0 and its plug-in architecture, you can
With the advent of [[Netscape Navigator]] 2.0 and its plug-in architecture, you can
extend content on the Web to handle multimedia or 3D media. The ease of
extend content on the Web to handle multimedia or 3D media. The ease of
publishing 3D content will make Apple's 3DMF data format ubiquitous on the Web.
publishing 3D content will make Apple's 3DMF data format ubiquitous on the Web.
Line 478: Line 477:
and the URL is displayed in Netscape's toolbar. Clicking on that object causes
and the URL is displayed in Netscape's toolbar. Clicking on that object causes
Netscape to go to that URL, which could be anything from another QuickDraw 3D
Netscape to go to that URL, which could be anything from another QuickDraw 3D
object to any type of page that [[Netscape]] understands. Currently, the only way
object to any type of page that Netscape understands. Currently, the only way
to add anchors to a [[QuickDraw 3D]] object is through the applications BeWhurled
to add anchors to a [[QuickDraw 3D]] object is through the applications BeWhurled
(on this issue's CD), 3D World, and Studio Pro Blitz, but the URL example in
(on this issue's CD), 3D World, and Studio Pro Blitz, but the URL example in
Line 974: Line 973:
</pre>
</pre>
<h4>
<h4>
<a name="RTFToC27">W3INLINE</a></h4>
W3INLINE</h4>
This element contains a URL in the form of an C string (ASCII only). The group
This element contains a URL in the form of an C string (ASCII only). The group
or geometry that this element is attached to acts as a proxy for the data
or geometry that this element is attached to acts as a proxy for the data

Latest revision as of 21:21, 2 September 2020

by PABLO FERNICOLA, NICK THOMPSON and KENT DAVIDSON
Custom attributes and elements provide a way to attach data such as scaling information, sound, and strings to QuickDraw 3D objects. In this article we explain how to create and attach custom attributes and elements. We illustrate the process by showing you how to attach a string containing a World Wide Web URL to a QuickDraw 3D object to enable 3D navigation through the Web. We also describe six new custom elements with implementations included on this issue's

CD.

In QuickDraw 3D, attribute objects (known more simply as attributes) generally store information about the surface properties of objects in a model, such as color and transparency. QuickDraw 3D defines 12 basic attribute types, and it also allows you to define custom attribute and element types so that you can attach data different from the predefined types to QuickDraw 3D objects. Your custom data need not apply to the appearance of objects or to how objects are drawn, although it can.

For example, with custom attributes and elements you can add scaling information, directional information, or sound to objects in your 3D scene. You can add a string containing a name for a QuickDraw 3D object, so that you can refer to that object by name and control it from a scripting language in your application. Your application can enable users to navigate through the World Wide Web in 3D, by attaching a URL to a QuickDraw 3D object, as illustrated by the code discussed in this article and included on this issue's CD. These are just a few of the ways you can extend the functionality of QuickDraw 3D and add value to your 3D application by adding custom data to objects.

Before we explain and illustrate how to create custom attributes and elements, and how to attach them to QuickDraw 3D objects, we'll look at how attributes and elements relate to each other and to other QuickDraw 3D objects. To get the most from this article, you should already be familiar with the basics of QuickDraw 3D, as presented in the previous develop articles "QuickDraw 3D: A New Dimension for Macintosh Graphics" (Issue 22) and "The Basics of QuickDraw 3D Geometries" (Issue 23). The book 3D Graphics Programming With QuickDraw 3D, included on this issue's CD, provides complete documentation for the QuickDraw 3D programming interfaces.

ABOUT ATTRIBUTES AND ELEMENTS

Attributes and elements are types of QuickDraw 3D objects used to store information about objects they're attached to. Each consists of a type and some associated data. You apply attributes and elements to objects by creating an instance of a specific type of attribute or element, defining its data, adding it to a set, and then attaching the set to an object (if the set isn't already

attached).

Note that attributes and elements are attached to objects, as opposed to simply being added to a group. The reason for binding data to objects is that both QuickDraw 3D and the 3DMF format maintain a strong data encapsulation model. For example, this allows QuickDraw 3D objects to be moved from file to file without losing data.

ATTRIBUTES AND ELEMENTS IN THE QUICKDRAW 3D HIERARCHY

To better understand how attributes and elements relate to each other and to other QuickDraw 3D objects, take a look at the partial class hierarchy shown in Figure 1. As you can see, an attribute is actually a type of element (that is, it's a subclass of the Element class, TQ3ElementObject). An element is any QuickDraw 3D object that can be part of a set. In contrast with shared objects (objects of the class TQ3SharedObject), elements aren't shared (that is, they can't be referenced by multiple objects or the application at the same time) and are always removed from memory whenever they're disposed of. An attribute has all of these properties but also can be inherited by subclasses of the object it's attached to.

(image missing)

Figure 1. Partial QuickDraw 3D class hierarchy, showing set and attribute set attachment

Custom data to be attached to an object can be stored in an element or an attribute. So how do you decide which to use? Use an attribute when you want your custom data to be inherited. For example, suppose we create a custom attribute named Temperature and we want to be able to assign a different temperature to an entire geometry, a face, or a vertex. During a view traversal loop, our attribute will be inherited along with the other attributes. This becomes extremely important with the introduction of plug-in renderers, which will be available in a future QuickDraw 3D release. A particular renderer might take advantage of this inherited attribute by coloring each vertex according to the temperature inherited.

SETS AND ATTRIBUTE SETS

We mentioned earlier that attributes and elements are usually collected in sets. A set is an instance of the Set class (TQ3SetObject), which in turn is a subclass of the Shared class (TQ3SharedObject), as shown in Figure 1. A set collects zero or more different elements or attributes and their associated data; it can contain only one element or attribute of a given type. An attribute set is a type of set; in fact, TQ3AttributeSet is the only subclass of the class TQ3SetObject. An attribute set has all the properties of a set but

also allows inheritance.

Both elements and attributes can be collected in sets and attribute sets. Since the AttributeSet class is derived from the Set class, you can call Q3Set_XX on an attribute set, but you can't call Q3AttributeSet_XX on a set. In the text that follows, be sure to pay attention to whether we're talking about sets or attribute sets; we don't use the terms interchangeably.

Sets and attribute sets can't be attached to just any QuickDraw 3D object, but only to those objects for which it makes sense to store additional data in this way. Attribute sets can be attached to view objects, group objects, and geometric objects, plus most of the parts of a geometric object: faces, vertexes, mesh edges, and mesh corners. (See "How to Attach Attribute Sets" for details.) In contrast, sets can be attached only to objects in the Shape class or subclasses of the Shape class. (Attaching a set to a shape is fairly straightforward; we give an example of how to do this later, in Listing 3.) The Shape class actually has a class field of type set, meaning that any class derived from Shape has a set object. The Geometry class has a class field of type set (inherited from the Shape class) plus a class field of type attributeSet, meaning that any class derived from Geometry has both a set object and an attribute set object.


    HOW TO ATTACH ATTRIBUTE SETS

    It may be news to you that attribute sets can be attached to views or groups,

    because how to do this is less than obvious. We'll tell you how.

    To attach an attribute set to a view object, use the Q3View_GetDefaultAttributeSet routine to get the default attribute set (all view objects have one), and then use Q3AttributeSet_Add to add attributes to that set. For example, the following code shows how to apply a default specular color to all objects submitted to a view.

Q3View_GetDefaultAttributeSet(theDocument
   ->theView, &viewSet);
Q3AttributeSet_Add(viewSet, 
   kQ3AttributeTypeSpecularColor, &clearColor);
Q3Object_Dispose(viewSet);

You can still override the default behavior of the view by attaching attributes to objects before submitting them. If you write the view hints out in 3DMF format using the QuickDraw 3D API, the attribute set for the view will also be written out. You can preserve these settings by looking in and using the view hints

when you read the 3DMF data back in.

To attach an attribute set to a group object, just add the attribute set to the group before you add the object you want it to be applied to.

Attaching an attribute set to a geometric object or a part of a geometric object is much more obvious, so we won't go into details here. Later in this article, Listing 2 gives an example of how to do it.



Currently, the renderers shipped with QuickDraw 3D ignore custom data attached to shape objects, but when plug-in renderers become available, they may pay attention to such data and use it to control certain rendering features. For example, a ray tracer renderer may need custom data about surfaces to render them with bump mapping.

ATTRIBUTES AND INHERITANCE

When the objects in a view are rendered, attributes attached to the objects are applied according to a strict hierarchy. The attribute sets of objects higher in the view hierarchy are inherited by objects below them, unless some other attribute set overrides them. Inheritance proceeds from view to group to geometric object to face to mesh edge to vertex to mesh corner. In other words, in the hierarchy, view attributes are always inherited unless a group contains overriding attributes; group attributes can be overridden by geometric object

attributes, which can be overridden by face attributes, and so on.

When you define a custom attribute, you can specify that you want it to be inherited by including an attribute inheritance method in your metahandler. (More on metahandlers later.) Inheritance happens when you call Q3AttributeSet_Inherit:

TQ3Status Q3AttributeSet_Inherit(TQ3AttributeSet parent, 
                              TQ3AttributeSet child, TQ3AttributeSet result);

This call takes three attribute sets: the parent, the child, and a result attribute set to store results in, which becomes the effective attribute set after inheritance. During inheritance, any attribute in the parent that's not in the child is copied into the result, and all child attributes are copied into the result, as illustrated by the example in Figure 2. As mentioned earlier, only attributes can be inherited; elements, such as the name element "Jane" in this example, can exist in an attribute set but aren't inherited.

(image missing)

Figure 2.Attribute inheritance

WORKING WITH CUSTOM ATTRIBUTES AND ELEMENTS

Now that you have a sense of how custom attributes and elements relate to each other and to other QuickDraw 3D objects, we'll outline how you define, register, and attach your custom data to the QuickDraw 3D objects of your choice. We'll further illustrate the process later in our example of attaching a URL to a QuickDraw 3D object.

DEFINING AND REGISTERING YOUR CUSTOM DATA

To define a custom attribute or element type, you need to provide a definition of the data associated with that type and write a metahandler to define a set of attribute- or element-handling methods. Once you've defined and registered your custom attribute or element type, you manipulate objects of that type exactly as you manipulate the standard QuickDraw 3D attributes. For example, you create a new attribute set by calling Q3AttributeSet_New, and you add custom attributes to the attribute set by calling Q3AttributeSet_Add. Finally, you attach the attribute set to an object by calling an appropriate QuickDraw

3D routine.

Before you can use your custom element or attribute, you must register it with QuickDraw 3D by calling Q3ElementClass_Register or Q3AttributeClass_Register:

TQ3ObjectClass Q3ElementClass_Register(TQ3ElementType elementType,
   const char *name, unsigned long sizeOfElement, TQ3MetaHandler metaHandler);
TQ3ObjectClass Q3AttributeClass_Register(TQ3AttributeType   attributeType,
   const char *name, unsigned long sizeOfElement, TQ3MetaHandler metaHandler);

The functions take these parameters:

  • elementType (or attributeType) -- The type constant used in the binary metafile and in accessing your element (or attribute) from a set.

  • name -- The string constant used to write your custom element or attribute in a text metafile. You should register your attribute or element types and names with Apple's Developer Support Center to prevent name space collisions. In general, you should name your custom elements and attributes in the form "Company:DataType"; for instance, if you work at Sun, you might name an attribute "Sun:JavaCode."

  • sizeOfElement -- The memory size that your element or attribute uses internally. QuickDraw 3D needs to know this when copying your element or attribute, because the data describing the element or attribute is copied from the public side of the API to internal storage.

  • metaHandler -- A pointer to the metahandler for your element or attribute.

A metahandler is an application-defined function that returns

the addresses of the methods associated with the custom attribute or element type. QuickDraw 3D calls these methods at certain times to handle operations on sets and attribute sets that contain your custom data. Particular methods are required for each QuickDraw 3D object type, and QuickDraw 3D asks the metahandler repeatedly for these required methods. Your metahandler should, by default, return NULL for unrecognized methods; this allows Apple to add methods in the future without breaking the implementation of old versions of elements

and attributes.

A metahandler can define some or all of the methods indicated by the constants listed below. Custom elements or attributes that are to be read from and written to files should support the I/O methods associated with objects (those methods beginning with "kQ3MethodTypeObject" in the following list). The metahandler can also support all the methods associated with elements (those methods beginning with "kQ3MethodTypeElement" in the list) and attributes (those methods beginning with "kQ3MethodTypeAttribute"). Note that the copy methods always take the source as the first parameter (from) and the destination as the second parameter (to), although what these point to differs for each copy method. All of the following method types are optional. If you supply no method for a particular attribute or element type, your attribute or element will inherit the default behavior of the parent class.

  • kQ3MethodTypeObjectReadData -- Reads the data from a file object, gathers any subobjects, and adds the element to a set.

  • kQ3MethodTypeObjectTraverse -- Calculates the size of the data to be written out, submits any subobjects, and gathers any state needed from the view object.

  • kQ3MethodTypeObjectWrite -- Actually writes the data to the file. Data is written through one of the low-level calls provided by QuickDraw 3D for basic data types. If your data size is always 0, no ObjectWrite method is required.

  • kQ3MethodTypeElementCopyAdd -- Called when an application calls Q3Set_Add or Q3AttributeSet_Add on your element and the element wasn't in the set. The from parameter is whatever the user passes in as the data pointer in Q3Set_Add. The to parameter is a pointer to an uninitialized block of sizeOfElement (from the Register call) bytes. If this method isn't supplied, the default is to copy sizeOfElement bytes from the source to the destination.

  • kQ3MethodTypeElementCopyReplace -- Called when an application calls Q3Set_Add or Q3AttributeSet_Add on your element and the element already exists in the set. The from parameter is whatever the user passes in as the data pointer in Q3Set_Add. The to parameter is a pointer to a block of sizeOfElement bytes that contains the element data to be replaced. You must reuse or delete any data in the destination before copying over it. If this method isn't supplied, the default is to call ElementDelete on the to parameter, then CopyAdd(from, to).

  • kQ3MethodTypeElementCopyGet -- Called when an application calls Q3Set_Get or Q3AttributeSet_Get on your element. The from parameter is a pointer to the block of element data to get. The to parameter is a pointer to whatever the user passes in as the data pointer in Q3Set_Get. If this method isn't supplied, the default is to copy sizeOfElement bytes from the source to the destination.

  • kQ3MethodTypeElementCopyDuplicate -- Called when an application calls Q3Object_Duplicate on a set or attribute set, or duplicates an object containing a set. The from parameter is a pointer to the block of element data to duplicate. The to parameter is a pointer to an uninitialized block of sizeOfElement bytes. If your element contains objects, call Q3Object_Duplicate to create an identical copy. If this method isn't supplied, the default is to copy sizeOfElement bytes from the source to the destination.

  • kQ3MethodTypeElementDelete -- Called when an application deletes a set containing your element, or clears your element with Q3Set_Clear or Q3AttributeSet_Clear. It takes a pointer to the block of element data. It should deallocate any data in your custom element. If this method isn't supplied, the default is a no-op.

  • kQ3MethodTypeAttributeInherit -- Your metahandler should return a TQ3Boolean value for this method. Returning kQ3True indicates that this attribute should be inherited in the hierarchy, kQ3False that it should not. The default is kQ3False.

  • kQ3MethodTypeAttributeCopyInherit -- Called when your attribute is inherited in the view stack (during rendering) or when the user calls Q3AttributeSet_Inherit with an attribute set containing your attribute. The from parameter is a pointer to the block of attribute data to inherit. The to parameter is a pointer to an uninitialized block of sizeOfElement bytes. The semantics of this call are similar to kQ3MethodTypeElementCopyDuplicate, although you should avoid duplicating data unless required. For example, if your attribute contains pointers to shared objects, you should copy them by calling Q3Shared_GetReference instead of Q3Object_Duplicate. If this method isn't supplied, the default is to copy sizeOfElement bytes from the source to the destination. This method should be implemented to be as fast as possible, as it occurs during rendering.

Listing 1 shows a typical metahandler for a

custom element in QuickDraw 3D. Take a look at the QuickDraw 3D header file QD3DIO.h to see the object methods and at QD3DSet.h to see the element and

attribute methods.

Listing 1. A typical metahandler for a custom element

TQ3FunctionPointer MyMetaHandler(TQ3MethodType methodType) 
{
   switch (methodType) {
      case kQ3MethodTypeObjectTraverse:
         return (TQ3FunctionPointer) MyElementTraverse;
      case kQ3MethodTypeObjectWrite:   
         return (TQ3FunctionPointer) MyElementWrite;
      case kQ3MethodTypeObjectReadData:   
         return (TQ3FunctionPointer) MyElementReadData;
      case kQ3MethodTypeElementCopyAdd:   
         return (TQ3FunctionPointer) MyElementCopyAdd;
      case kQ3MethodTypeElementCopyReplace:
         return (TQ3FunctionPointer) MyElementCopyReplace;
      case kQ3MethodTypeElementCopyGet:   
         return (TQ3FunctionPointer) MyElementCopyGet;
      case kQ3MethodTypeElementCopyDuplicate:
         return (TQ3FunctionPointer) MyElementCopyDuplicate;
      case kQ3MethodTypeElementDelete:   
         return (TQ3FunctionPointer) MyElementDelete;
      default:
         return (TQ3FunctionPointer) NULL;
   }
}


ATTACHING YOUR CUSTOM DATA

Now we'll show you how to add the custom data you've defined to a set or an attribute set and then attach that set or attribute set to an object. Note that when you want to attach custom data to a geometric object or some part of a geometric object, you actually have a choice of where to attach the data. You can add the data to an attribute set and attach it to the geometry or some part of the geometry, or you can add the same data to a set and attach that set to a shape, since the geometry inherits from the shape. Where and how data is attached to an object is really up to the semantics of your application. Just be sure to consistently attach data in the same place on all objects, and document what you've done, especially if you want your custom element or

attribute to be used by other developers.

To illustrate this concept, Listing 2 creates a new attribute set, adds our custom data to the attribute set, and attaches the attribute set to a mesh vertex. This is a fine way to customize a geometric object or some part of a geometric object. But if you want to add your custom data to some other subclass of the Shape class, you'll want to add the data to a set and attach that set to the shape. Listing 3 does just that.

Listing 2. Attaching an attribute set to a vertex

/* Get the existing attribute set (if any). */
Q3Mesh_GetVertexAttributeSet(mesh, someVertex, &theAttrSet);

/* If there's no attribute set we get back NULL and create one. */
if (theAttrSet == NULL) {
   /* Create a new empty attribute set. */
   theAttrSet = Q3AttributeSet_New();
   if (theAttrSet == NULL)
      return kQ3Failure;
   Q3Mesh_SetVertexAttributeSet(mesh, someVertex, theAttrSet);
}

/* Add the custom data to the attribute set. */
if (Q3AttributeSet_Add(theAttrSet, kMyCustomDataType, &myCustomData) 
      == kQ3Failure) {
   Q3Object_Dispose(theAttrSet);
   return kQ3Failure;
}
Q3Object_Dispose(theAttrSet);
return kQ3Success;


Listing 3. Attaching a set to a shape

/* Get the existing set (if any). */
Q3Shape_GetSet(shape, &theSet);

/* If there's no set, add one. */
if (theSet == NULL) {
   theSet = Q3Set_New();
   if (theSet == NULL)
      return kQ3Failure;
   Q3Shape_SetSet(shape, theSet);
}

/* Add the custom data to the set. */
if (Q3Set_Add(theSet, kMyCustomDataType, &myCustomData) 
      == kQ3Failure) {
   Q3Object_Dispose(theSet);
   return kQ3Failure;
}
Q3Object_Dispose(theSet);
return kQ3Success;

A CASE IN POINT: ATTACHING A URL TO AN OBJECT

Now we're going to illustrate how to define, register, and attach a custom element to a QuickDraw 3D object, and how to extract and use that custom data. Our custom element is a string containing a URL (uniform resource locator, a popular way of specifying the location of an online resource on the Web); we'll attach it to a geometry object. We make it an element rather than an attribute because it doesn't need to be inheritable. When the object we attach the custom element to is read into one of the many viewers that support custom elements, the viewer can communicate through Apple events with applications like Netscape Navigator(TM) (or your favorite Web browser) to produce 3D navigation. A sample application that illustrates the idea is included on this issue's CD. See "3D Web Content Using 3DMF and Netscape Navigator" for more

details.

The custom attribute we define and use here, W3Anchor, is one of the six custom elements described later in this article.


    3D WEB CONTENT USING 3DMF AND NETSCAPE NAVIGATOR

    BY JOHN LOUCH

    With the advent of Netscape Navigator 2.0 and its plug-in architecture, you can extend content on the Web to handle multimedia or 3D media. The ease of publishing 3D content will make Apple's 3DMF data format ubiquitous on the Web. A sample Netscape plug-in, Whurlplug, on this issue's CD shows what a 3D plug-in based on QuickDraw 3D might look like.

    Whurlplug uses the QuickDraw 3D Viewer shared library as its interface for displaying 3D Web content. The Viewer gives users of Whurlplug a seamless integration with the current metaphors for handling 3D content on the Mac OS. Whurlplug also tries to use the same human interface metaphors and behaviors as Netscape.

    Whurlplug can be embedded in a Hypertext Markup Language (HTML) page or take over the whole window (as in the URL example in this article). If the plug-in is embedded, it will assume the same background color as the HTML page it's embedded in. Holding down the mouse button on the Viewer toolbar will pop up a menu allowing you to set the Viewer options and save the Web-based 3DMF object to disk, so it's consistent with other elements of the Netscape browser's user interface.

    There are a number of ways to present a 3D scene to a Web user. You can enable the user to fly through a 3D world, or simply to view an HTML page with 3D content. To handle the different ways that Whurlplug might be used, we extended the HTML syntax that the plug-in understands if it's embedded in a page. Here's the Embed command syntax:

    <EMBED SRC="3DObject.3dmf" WIDTH=100 HEIGHT=200>

    Six more arguments for this extension to HTML can be used in a description of a 3DMF object:

    • ACTIVE -- If this is set to true, the user can examine the 3D object through the controls provided by the QuickDraw 3D Viewer and keyboard navigation. If it's false, the user can interact with the 3D object only if it has URL links to other pages inside it.

    • BGCOLOR -- Allows the page author to set the background color of the plug-in or model to the color supplied. BGCOLOR="#ffffff" would set the background color to white. The string is defined as a number consisting of six hexadecimal digits, each pair of which describes the red, blue, and green components (in that order).

    • SPIN -- If this is set to true, the 3D object will spin about a moving axis defined by Whurlpug; otherwise, the object won't spin.

    • ROTATE -- This also allows the 3D object to spin when viewed, but the page author defines the axis of rotation. The syntax is ROTATE="x y z" where x, y, and z are floating-point values from -180.0 to 180.0 defining the axis of rotation.

    • TOOLBAR -- If this is set to false, the toolbar at the bottom of the viewer isn't shown. The default is true.

    • RENDER -- Tells the plug-in which renderer to use. RENDER=interactive (the default) indicates the interactive software renderer; RENDER=wireframe indicates the wireframe renderer that ships with QuickDraw 3D.

    Whurlplug

    understands 3D models that have URL or anchor links in them. If the cursor moves over a 3D object that has an anchor link in it, the object flashes red and the URL is displayed in Netscape's toolbar. Clicking on that object causes Netscape to go to that URL, which could be anything from another QuickDraw 3D object to any type of page that Netscape understands. Currently, the only way to add anchors to a QuickDraw 3D object is through the applications BeWhurled (on this issue's CD), 3D World, and Studio Pro Blitz, but the URL example in this article shows how you can add the anchor custom attribute to data in your

    own 3D application.

    The mime type and subtype for 3DMF are x-world/x-3dmf. The extensions that Whurplug understands are .3dmf, .3dm, .qd3d, and .qd3. Your Web server has to either set the mime type and subtype of 3D files to x-world/x-3dmf or name the files so that the extension is one of those Whurlplug understands.

    Following is a trivially simple HTML description of a Web page that uses this viewer. By the time you read this, there will be (we hope) a number of sites with 3DMF data on their Web pages that can be viewed in Netscape. Check out the QuickDraw 3D Web page for more details.

<TITLE> A 3D Web page <\TITLE>
<EMBED SRC="3DObject.3dmf" WIDTH=200 HEIGHT=200
   SPIN=true ACTIVE=false>
<P>
<A HREF="3DObject.3dmf">Click here for a full
   view</A>

DEFINING OUR DATA STRUCTURE

We first need to define the internal structure of the data associated with our custom element type. We'll use the W3AnchorData structure, defined like this:

typedef enum W3AnchorOptions {
   kW3AnchorOptionNone      = 0,
   kW3AnchorOptionUseMap   = 1
} W3AnchorOptions;

typedef struct W3AnchorData {
   char               *url;
   TQ3StringObject   description;
   W3AnchorOptions   options;
} W3AnchorData;

The url field is a C string consisting of the URL data. The description object is information that the application must present to users to enable them to decide whether the site or data pointed to by the URL is worth examining (since the process could take some time). Note that since the description is a string object, it can be specified in a script other than Roman. The options field specifies whether the position (x,y) that was clicked should be passed back to the Web viewer.

REGISTERING OUR CUSTOM ELEMENT

Before we can use our custom element, we need to tell QuickDraw 3D that we've defined it, by implementing a registration routine. There may be occasions when we want to recognize a custom element for only a limited period of time, so an

unregister routine can also be implemented.

We need to define a couple of parameters before we can register our custom element: an object type, which is a four-character identifier packed into a long word, and a string, which is used to help uniquely identify the element. As mentioned earlier, both of these need to be registered with the Developer Support Center to avoid name space collisions, and each must be unique within their respective name spaces.

#define kElementTypeW3Anchor \ 
   ((TQ3ElementType) Q3_OBJECT_TYPE('w','w','w','a'))
#define kElementNameW3Anchor   "W3Anchor"

Now we register the custom element:

TQ3Status W3Anchor_Register(void)
{
   gW3AnchorClass = Q3ElementClass_Register(kElementTypeW3Anchor,
      kElementNameW3Anchor, sizeof(W3AnchorData), W3Anchor_MetaHandler);
   return (gW3AnchorClass == NULL ? kQ3Failure : kQ3Success);
}

When you register custom attributes or elements with Q3ElementClass_Register, the name you use doesn't have to be the exact same name used by other developers for that type. As an example, the W3Anchor type is defined as 'wwwa' and its name is "W3Anchor." An Apple implementation of this attribute might be registered as Q3ElementClass_Register('wwwa', "Apple:W3Anchor"), and a third party's implementation might be registered as Q3ElementClass_Register('wwwa', "Microspot:W3Anchor"). The name is unimportant; because both of the implementations have the same type, data written by one will, if the implementation of both is the same, be read by the other.

DEFINING OUR METAHANDLER

Whenever QuickDraw 3D needs to operate on the data encapsulated by our custom element, it will call our metahandler, which we supplied a pointer to in the registration routine. Our metahandler (Listing 4) returns the addresses of the methods associated with our element type. We supply object I/O methods to preserve our element during I/O, and copy methods to allocate and manage the string memory. We return NULL by default, to indicate that unknown methods aren't supported and that a default method should be used. The definition for

each of the routines is on this issue's CD.

Listing 4. The metahandler for our custom element

static TQ3FunctionPointer W3Anchor_MetaHandler(TQ3MethodType methodType)
{
   switch (methodType) {
      case kQ3MethodTypeObjectTraverse:
         return (TQ3FunctionPointer) W3Anchor_Traverse;
      case kQ3MethodTypeObjectWrite:
         return (TQ3FunctionPointer) W3Anchor_Write;
      case kQ3MethodTypeObjectReadData:
         return (TQ3FunctionPointer) W3Anchor_ReadData;
      case kQ3MethodTypeElementCopyAdd:
      case kQ3MethodTypeElementCopyGet:
      case kQ3MethodTypeElementCopyDuplicate:
         return (TQ3FunctionPointer) W3Anchor_CopyAdd;
      case kQ3MethodTypeElementCopyReplace:
         return (TQ3FunctionPointer) W3Anchor_CopyReplace;
      case kQ3MethodTypeElementDelete:
         return (TQ3FunctionPointer) W3Anchor_Delete;
      default:
         return (TQ3FunctionPointer) NULL;
   }
}

IMPLEMENTING THE METHODS

Listing 5 shows how the three element methods -- W3Anchor_CopyAdd, W3Anchor_CopyReplace, and W3Anchor_Delete -- are implemented. Note in Listing 4 that the same function, W3Anchor_CopyAdd, is used for the CopyAdd, CopyGet, and CopyDuplicate methods. This means that the data pointer passed into Q3Set_Add and Q3Set_Get is a pointer to the same structure as the internal structure. If you want to see how the I/O methods are implemented, look at the source code

for our custom element on the CD.

Listing 5. Implementing the element methods

/* W3Anchor_CopyAdd adds the WWW data from src to dst. */
static TQ3Status W3Anchor_CopyAdd(W3AnchorData *src, W3AnchorData *dst)
{
   long   i;
   
   /* Check to see if src is a valid W3Anchor. */
   if (src->url == NULL)
      return kQ3Failure;

   /* We need to allocate memory for the string that belongs to dst. */
   i = strlen(src->url);
   if (i == 0)
      return kQ3Failure;
   dst->url = (char *) malloc(i + 1);
   if (dst->url == NULL)
      return kQ3Failure;

   /* Copy the string from src to dst. */
   strcpy(dst->url, src->url);

   /* Check to see if src had a description. */
   if (src->description) {
      TQ3StringObject   stringReference;
      /* Get a reference to src's description object. */
      stringReference = Q3Shared_GetReference(src->description);
      if (stringReference == NULL)
         return kQ3Failure;
      dst->description = stringReference;
   } else
      dst->description = NULL;

   /* Just copy the options, since they're just values. */
   dst->options = src->options;
   return kQ3Success;
}
/* W3Anchor_CopyReplace substitutes the WWW data in src for the data 
   in dst. */
static TQ3Status W3Anchor_CopyReplace(W3AnchorData *src, 
                                       W3AnchorData *dst)
{
   long   i;
   char   *c;
   
   /* Check to see if src is a valid W3Anchor. */
   if (src->url == NULL)
      return kQ3Failure;

   /* We need to have enough memory for the string from src. */
   i = strlen(src->url);
   if (i == 0)
      return kQ3Failure;
   c = (char *) realloc(dst->url, i + 1);
   if (c == NULL)
      return kQ3Failure;

   dst->url = c;
   strcpy(dst->url, src->url);
   if (src->description) {
      TQ3StringObject   stringReference;

      /* Get a reference to src's description object. */
      stringReference = Q3Shared_GetReference(src->description);

      if (stringReference == NULL)
         return kQ3Failure;
      if (dst->description)
         Q3Object_Dispose(dst->description);
      dst->description = stringReference;
   } else
      dst->description = NULL;
   dst->options = src->options;
   return kQ3Success;
}

/* W3Anchor_Delete cleans up the references and memory allocations. */
static TQ3Status W3Anchor_Delete(W3AnchorData *myURLData)
{
   if (myURLData->url != NULL) {
      free(myURLData->url);
      myURLData->url = NULL;
   }
   if (myURLData->description != NULL) {
      Q3Object_Dispose(myURLData->description);
      myURLData->description = NULL;
   }
   return kQ3Success;
}

ATTACHING THE CUSTOM ELEMENT

Once we've set up our metahandler and associated routines, we can use the normal set and attribute set routines to add elements and attributes of our custom type. Listing 6 shows how we add our custom element to a set and attach the set to a shape object. Our geometric object will then inherit the set from

the shape.

Listing 6. Adding our custom element to a set and attaching the set to an object

TQ3AttributeSet   theSet;
W3AnchorData      QD3DHomePage;

theSet = Q3Set_New();
if (theSet) {
   char   *description = "Apple QuickDraw 3D Home Page",
         *url = "http://www.info.apple.com/qd3d";
   QD3DHomePage.url = malloc(strlen(url) + 1);
   if (QD3DHomePage.url) {
      strcpy(url, QD3DHomePage.url);
      QD3DHomePage.description = Q3CString_New(description);
      QD3DHomePage.options = 0;

      /* Add the anchor data to the set. */
      Q3Set_Add(theSet, kElementTypeW3Anchor, &QD3DHomePage);

      /* The data has been copied and objects referenced, so we need to
         clean up after ourselves. */
      free(QD3DHomePage.url);
      Q3Object_Dispose(QD3DHomePage.description);
   }

   /* Attach the set to a shape. */
   Q3Shape_SetSet(aShape, theSet);
   Q3Object_Dispose(theSet);
} 

GETTING THE CUSTOM DATA FROM THE OBJECT

At some point your application will want to extract the custom data you've attached to an object. In our sample application, that point is reached when the user clicks on an object or the cursor passes over an object. The W3Anchor_GetFromObject routine (Listing 7) gets custom data from an object passed into the routine, using the QuickDraw 3D routines Q3Set_Contains and

Q3Set_Get.

Listing 7. Getting our custom data from the object

TQ3Boolean W3Anchor_GetFromObject(TQ3Object object, W3AnchorData *data)
{
   TQ3SetObject   set;
   TQ3Boolean      result;
   
   W3Anchor_Empty(data);
   data->url = NULL;
   data->description = NULL;
   set = NULL;

   /* The object passed in must be a shape or a geometry. */
   if (Q3Object_IsType(object, kQ3ShapeTypeGeometry) == kQ3True) {
      Q3Geometry_GetAttributeSet(object, &set);
      if (set != NULL) {
         result = W3Anchor_GetFromSet(set, data);
         Q3Object_Dispose(set);
         if (result == kQ3True)
            return result;
         set = NULL;
      }
   }
   if (Q3Object_IsType(object, kQ3SharedTypeShape) == kQ3True) {
      Q3Shape_GetSet(object, &set);
      if (set != NULL) {
         result = W3Anchor_GetFromSet(set, data);
         Q3Object_Dispose(set);
         return result;
      }
   }
   return kQ3False;
}

TQ3Boolean W3Anchor_GetFromSet(TQ3SetObject set, W3AnchorData *data)
{
   TQ3Object         unkObj;
   TQ3Boolean         result;
   TQ3GroupPosition   position;

   result = kQ3False;

   /* Ideally, you'll find one of these. */
   if (Q3Set_Contains(set, kElementTypeW3Anchor) == kQ3True) {
      if (Q3Set_Get(set, kElementTypeW3Anchor, data) == kQ3Failure)
         return kQ3False;   /* Error: Contains, but can't get! */
      return kQ3True;
   }

   /* But due to a bug in QuickDraw 3D versions prior to 1.0.4, the
      element may be contained within another set in the unknown 
      element. */
   if (Q3Set_Contains(set, kQ3ElementTypeUnknown) == kQ3True) {
      if (Q3Set_Get(set, kQ3ElementTypeUnknown, &unkObj) == kQ3Failure)
         return kQ3False;   /* Error: Contains, but can't get! */
      if (unkObj == NULL)
         return kQ3False;
      /* Unknown objects may contain one object or a group. */
      if (Q3Object_IsType(unkObj, kQ3SharedTypeSet) == kQ3True)
         result = W3Anchor_GetFromSet(unkObj, data);
      else if (Q3Object_IsType(unkObj, kQ3ShapeTypeGroup) == kQ3True) {
         Q3Group_GetFirstPositionOfType(unkObj, kQ3SharedTypeSet,
            &position);
         if (position != NULL) {
            Q3Group_GetPositionObject(unkObj, position, &set);
            result = W3Anchor_GetFromSet(set, data);
         }
      }
      Q3Object_Dispose(unkObj);
   }
   return result;
}

W3Anchor_GetFromObject includes a workaround for an interesting problem. In QuickDraw 3D before version 1.0.4, if an element or attribute type was unknown (in other words, if a metahandler wasn't installed for the element or attribute), the element or attribute would be read as an unknown object. When a set was defined as part of an object derived from the Shape class, the set was written out to the metafile just fine; but when the set was read from the metafile into the shape's set, it was read as an unknown object, resulting in an additional, unnecessary level of containment, as illustrated in Figure 3. If you're reading custom elements or attributes from 3DMF files, you need to ensure that your users have version 1.0.4 or later, or you'll need to work around this issue.

(image missing)

Figure 3. The problem with reading a set from a metafile

Similarly, if a metafile containing an object with a custom element attached to it is read by an application that doesn't know about that custom element, when the object is written out its associated custom element will be written out as an unknown object. The moral of this story is that you should check inside an unknown object to see if the type of attribute it contains is the one you're looking for.

SENDING THE URL TO A BROWSER

Once we've extracted the URL from our custom element, we want to send it to Netscape Navigator or a similar browser. Listing 8 shows the basics of how to do this (we've left out the proper error handling in the interest of saving space).

    A more complete example on this issue's CD shows how to detect whether Netscape Navigator is running and, if not, to launch the application. It shows other cool uses for custom elements and attributes as well.

Listing 8. Sending the URL to a browser

Boolean OpenURL(char *name)
{
   AppleEvent   theAppleEvent, theReply;
   OSErr         err;

   /* If Netscape isn't around, get out. */
   if (Find_Netscape() == false)
      return false;

   /* Netscape is here; let's send them an Apple event. */
   err = AECreateAppleEvent('WWW!', 'OURL', &theAddressDesc,
            kAutoGenerateReturnID, kAnyTransactionID, &theAppleEvent);
   err = AEPutParamPtr(&theAppleEvent, keyDirectObject, typeChar, name, 
            strlen(name));
   err = AESend(&theAppleEvent, &theReply, kAEWaitReply, 
            kAENormalPriority, kNoTimeOut, NewAEIdleProc(MyIdle),
            NewAEFilterProc(MyFilter));
   if (err == noErr)
      return true;
   else
      return false;   
}

NEW CUSTOM ELEMENTS AND ATTRIBUTES

In future releases of QuickDraw 3D, you'll be able to ship your custom elements and attributes as a shared library that plugs into QuickDraw 3D, as opposed to having to compile the code within your application. This will allow for the custom elements and attributes to be valid for all QuickDraw 3D applications

running on the machine.

In the meantime, for your custom element or attribute to be shared and understood by other applications, you can propose it to the Developer Support Center, and they'll pass the information on to the QuickDraw 3D team. Be sure to specify the data format, describe how the object is to be used, and include a C-based implementation. We want to avoid the problems experienced with QuickDraw's picture comments, where the behavior or meaning of the data was often not clear. If enough developers request similar attributes or elements, we'll add them to the next release of QuickDraw 3D, so that they get registered at startup time. In any case, we'll make the specifications for custom attributes and elements available on the Developer CD Series, the develop Bookmark CD, and the Web.

Following are descriptions of six new custom elements that address needs expressed by several of our developers. Implementations for these are provided on the CD, in the file CustomAttribute_Lib.c. (Yes, these are elements, even though we on the QuickDraw 3D team have been in the sloppy habit of referring to them as attributes, and both the filename and the names of some of the elements reflect that habit. Just make sure that you're more precise in your use of the terms element and attribute, now that you know what the difference is from reading this article.) In addition, the CD contains a Technote describing some custom elements and attributes defined by our developer community.

NAMEATTRIBUTE

This element contains a string object. It can be attached to any object in the Shape class or any subclass of the Shape class. It can also be added to an attribute set and assigned to a geometry or faces. (In future releases we'll have other subclasses to the String class, allowing you to use non-ASCII

characters.)

Written out in a 3DMF text metafile, this element appears as follows:

Container (
   NameAttribute ( )
   CString ( "1 meter box" )
)

SCALEATTRIBUTE

This element, of type double, determines the relation between one unit in the model and one meter. For example, if one unit in your model is equivalent to 10

meters, the scale should be set to 10.

This element, and all the elements whose descriptions follow, should be attached only to groups or geometry objects. Also, for each of these elements, traversal to find the element should be top down; this means that if it's attached to a group, there's no need to traverse the objects within the group.

If you add objects that have a scale element to a group, make sure that the objects are transformed (placed in a group with a transform and then added to the main group) so that the scale for the group is uniform.

ScaleAttribute ( 1.0 )

UPVECTOR AND FORWARDDIRECTION

These elements, of type TQ3Vector3D, specify the up vector and forward direction for a model. They're used to ensure that the orientation of an object read from a metafile is correct (that is, that it has the right side up and faces the right way). As for ScaleAttribute, if you add objects that have either of these elements to a group, make sure that the objects are transformed.

UpVector ( 0.0 1.0 0.0 )
ForwardDirection ( 1.0 0.0 0.5 )

W3ANCHOR

This element contains a URL in the form of a C string (ASCII only), an option field, which can be set to kURLReferenceOptionUseMap (meaning that the application should attach a pointer to the URL before sending the URL to the server), and a string object to encapsulate the description of the site pointed to by the URL (note that this allows for non-ASCII descriptions in the future). In the following 3DMF text, the "0" signifies that there's no map. "Apple's home page" is shown as a CString when it's actually a TQ3StringObject, because that's how we decided to represent strings in the metafile.

Container (
   W3Anchor ( "http://www.info.apple.com" 0 )
   CString ( "Apple's home page" )
)

W3INLINE

This element contains a URL in the form of an C string (ASCII only). The group or geometry that this element is attached to acts as a proxy for the data pointed to by the URL. This allows the application to perform the URL data retrieval on a separate thread or in the background, or delay the operation until the user expresses interest in the proxy. Once the URL data is retrieved, the data should replace the object that holds the element.

W3Inline ( "http:www.info.apple.com" )

CUSTOMIZING YOUR WORLD

This article has given an overview of what you can do with custom elements and attributes. With your imagination and a few simple routines, you can extend the rich capabilities of QuickDraw 3D. Have fun with custom elements and attributes, and don't forget to tell us about them if you want other developers to be able to use yours!