Common Files/QTVRUtilities.c

//////////
//
//  File:       QTVRUtilities.c
//
//  Contains:   Some utilities for working with QuickTime VR movies.
//              All utilities start with the prefix "QTVRUtils_".
//
//  Written by: Tim Monroe
//
//  Copyright:  © 1996-1998 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <26>     09/27/00    rtm     modified QTVRUtils_AddStr255ToAtomContainer so it doesn't add a null byte to
//                                  the string in the atom container; also added some bullet-proofing
//     <25>     04/15/99    rtm     added QTVRUtils_PanAngleToPoint3D, from Bryce Wolfson
//     <24>     04/05/99    rtm     added QTVRUtils_IsBackBufferHorizontal
//     <23>     03/11/99    rtm     moved _GetControllerType and _SetControllerType to QTUtilities
//     <22>     02/03/99    rtm     moved non-QTVR-specific utilities to QTUtilities
//     <21>     02/12/98    rtm     added QTVRUtils_HideHotSpotNames and siblings, *Show* and *Toggle*
//     <20>     01/27/98    rtm     revised QTVRUtils_IsQTVRMgrInstalled and QTVRUtils_GetQTVRVersion
//     <19>     01/26/98    rtm     revised QTVRUtils_GetHotSpotName to look also in hot spot atom for name atom
//     <18>     01/14/98    rtm     added QTVRUtils_SetControllerType and QTVRUtils_AddStr255ToAtomContainer
//     <17>     10/20/97    rtm     added QTVRUtils_IsMultiNode; added Endian*_BtoN macros to file format routines
//     <16>     10/17/97    rtm     fixed QTVRUtils_IsControllerButtonVisible behavior for speaker button
//     <15>     10/07/97    rtm     added cannotFindAtomErr result code to QTVRUtils_Get*AtomData functions
//     <14>     09/15/97    rtm     added QTVRUtils_ToggleControllerBar
//     <13>     08/21/97    rtm     added QTVRUtils_IsControllerButtonVisible
//     <12>     08/19/97    rtm     added #ifdefs to support Windows compilation
//     <11>     08/05/97    rtm     added QTVRUtils_GetNodeComment; still needs testing
//     <10>     07/27/97    rtm     fixed QTVRUtils_GetHotSpotCount; added QTVRUtils_GetHotSpotIDByIndex
//     <9>      07/25/97    rtm     revised QTVRUtils_Get*AtomData functions to use QTCopyAtomDataToPtr;
//                                  rewrote QTVRUtils_GetStringFromAtom
//     <8>      07/24/97    rtm     removed sound volume utilities; added QTVRUtils_IsZoomAvailable;
//                                  revised QTVRUtils_IsQTVRMovie to use GetUserDataItem, not GetUserData
//     <7>      07/23/97    rtm     revised file format utilities; added QTVRUtils_Get*AtomData functions
//     <6>      07/22/97    rtm     fixed QTVRUtils_GetHotSpotCount to make sure handle is actually resized
//     <5>      07/21/97    rtm     added QTVRUtils_GetNodeCount
//     <4>      06/04/97    rtm     fixed QTVRUtils_ShowControllerButton and QTVRUtils_HideControllerButton,
//                                  and added some explanation of them; added QTVRUtils_ResetControllerButton
//     <3>      02/03/97    rtm     revised QTVRUtils_ShowControllerButton and QTVRUtils_HideControllerButton 
//                                  to use explicit flag
//     <2>      12/03/96    rtm     added controller bar utilities
//     <1>      11/27/96    rtm     first file
//     
//////////
 
//////////
//
// header files
//
//////////
 
#ifndef __QTVRUtilities__
#include "QTVRUtilities.h"
#endif
 
#ifndef __QTUtilities__
#include "QTUtilities.h"
#endif
 
#include <math.h>
#include <stdlib.h>
#include <string.h>
 
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// General utilities.
//
// Use these functions to get information about the availability/features of QuickTime VR or other services.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTVRUtils_IsQTVRMgrInstalled
// Is the QuickTime VR Manager installed?
//
//////////
 
Boolean QTVRUtils_IsQTVRMgrInstalled (void) 
{
    Boolean     myQTVRAvail = false;
    long        myAttrs;
    OSErr       myErr = noErr;
 
    myErr = Gestalt(gestaltQTVRMgrAttr, &myAttrs);
    if (myErr == noErr)
        if (myAttrs & (1L << gestaltQTVRMgrPresent))
            myQTVRAvail = true;
 
    return(myQTVRAvail);
}
 
 
//////////
//
// QTVRUtils_GetQTVRVersion
// Get the version of the QuickTime VR Manager installed.
//
// The low-order word of the returned long integer contains the version number,
// so you can test a version like this:
//
//      if (QTVRUtils_GetQTVRVersion() < 0x0210)        // we require QTVR 2.1 or greater
//          return;
//
//////////
 
long QTVRUtils_GetQTVRVersion (void)
{
    long        myVersion = 0L;
    OSErr       myErr = noErr;
 
    myErr = Gestalt(gestaltQTVRMgrVers, &myVersion);
    if (myErr == noErr)
        return(myVersion);
    else
        return(0L);
}
 
 
//////////
//
// QTVRUtils_IsQTVRMovie
// Is the specified movie a QTVR movie?
//
// WARNING: This function is intended for use ONLY when you want to determine if you've got a QTVR movie
// but you don't want to use the QuickTime VR API (perhaps QTVR isn't installed...). The preferred way to
// determine if a movie is a QTVR movie is to call QTVRGetQTVRTrack and then QTVRGetQTVRInstance; if you
// get back a non-NULL instance, you've got a QTVR movie.
//
//////////
 
Boolean QTVRUtils_IsQTVRMovie (Movie theMovie) 
{
    Boolean         myIsQTVRMovie = false;
    OSType          myType;
    
    // QTVR movies have a special piece of user data identifying the movie controller type
    myType = QTUtils_GetControllerType(theMovie);
    
    if ((myType == kQTVRQTVRType) || (myType == kQTVROldPanoType) || (myType == kQTVROldObjectType))
        myIsQTVRMovie = true; 
        
    return(myIsQTVRMovie);
}
 
 
//////////
//
// QTVRUtils_Is20QTVRMovie
// Is the specified QTVR movie version 2.0 or greater?
//
//////////
 
Boolean QTVRUtils_Is20QTVRMovie (Movie theMovie) 
{
    Boolean         myIs20QTVRMovie = false;
    OSType          myType;
    
    // QTVR movies have a special piece of user data identifying the movie controller type
    myType = QTUtils_GetControllerType(theMovie);
    
    if (myType == kQTVRQTVRType)
        myIs20QTVRMovie = true; 
        
    return(myIs20QTVRMovie);
}
 
 
//////////
//
// QTVRUtils_IsTranslateAvailable
// Is translation currently enabled for the specified object node?
//
//////////
 
Boolean QTVRUtils_IsTranslateAvailable (QTVRInstance theInstance) 
{
    Boolean     myState;
    
    QTVRGetControlSetting(theInstance, kQTVRTranslation, &myState);
    return(myState);
}
 
 
//////////
//
// QTVRUtils_IsZoomAvailable
// Is zooming currently enabled for the specified object node?
//
//////////
 
Boolean QTVRUtils_IsZoomAvailable (QTVRInstance theInstance) 
{
    Boolean     myState;
    
    QTVRGetControlSetting(theInstance, kQTVRCanZoom, &myState);
    return(myState);
}
 
 
//////////
//
// QTVRUtils_IsPanoNode
// Is the specified node a panoramic node?
//
//////////
 
Boolean QTVRUtils_IsPanoNode (QTVRInstance theInstance) 
{
    return(QTVRGetNodeType(theInstance, kQTVRCurrentNode) == kQTVRPanoramaType);
}
 
 
//////////
//
// QTVRUtils_IsObjectNode
// Is the specified node an object node?
//
//////////
 
Boolean QTVRUtils_IsObjectNode (QTVRInstance theInstance) 
{
    return(QTVRGetNodeType(theInstance, kQTVRCurrentNode) == kQTVRObjectType);
}
 
 
//////////
//
// QTVRUtils_IsHotSpotInNode
// Does the specified node contain at least one hot spot (whether visible, enabled, or whatever)?
//
// NOTE: This is not an easy function to implement using just the QTVR 2.1 API. We do have our own
// utility QTVRUtils_GetHotSpotCount, but that function returns the number of hot spot information atoms
// in the node, which is not (necessarily) the number of hot spot regions in the hot spot image track.
// For panoramas, we could check to see if the panorama sample atom structure contains a reference
// to a hot spot image track; if it does, we'd blindly assume that that track isn't empty. For objects,
// we'll have to rely on QTVRUtils_GetHotSpotCount. So it goes....
//
// In an ideal world, there would be a hot spot information atom for each and every hot spot region in
// the hot spot image track, in which case we could be happier using QTVRUtils_GetHotSpotCount.
//
//////////
 
Boolean QTVRUtils_IsHotSpotInNode (QTVRInstance theInstance) 
{
    return(QTVRUtils_GetHotSpotCount(theInstance, QTVRGetCurrentNodeID(theInstance), NULL) > 0);
}
 
 
//////////
//
// QTVRUtils_IsMultiNode
// Does the specified QuickTime VR instance contain more than one node?
//
//////////
 
Boolean QTVRUtils_IsMultiNode (QTVRInstance theInstance) 
{
    return(QTVRUtils_GetNodeCount(theInstance) > (UInt32)1);
}
 
 
//////////
//
// QTVRUtils_IsBackBufferHorizontal
// Is the back buffer oriented horizontally?
//
//////////
 
Boolean QTVRUtils_IsBackBufferHorizontal (QTVRInstance theInstance)
{
    UInt32      myGeometry;
    UInt16      myResolution;
    UInt32      myCachePixelFormat;
    SInt16      myCacheSize;
    OSErr       myErr = noErr;
 
    if (theInstance == NULL)
        return(false);
 
    myErr = QTVRGetBackBufferSettings(theInstance, &myGeometry, &myResolution, &myCachePixelFormat, &myCacheSize);
    if (myErr != noErr)
        return(false);
    else
        return(myGeometry == kQTVRHorizontalCylinder);
}
 
 
//////////
//
// QTVRUtils_HideHotSpotNames
// Disable the displaying of hot spot names in the controller bar.
//
//////////
 
void QTVRUtils_HideHotSpotNames (MovieController theMC) 
{
    QTUtils_HideControllerButton(theMC, kQTUtilsHotSpotNames);
}
 
 
//////////
//
// QTVRUtils_ShowHotSpotNames
// Enable the displaying of hot spot names in the controller bar.
//
//////////
 
void QTVRUtils_ShowHotSpotNames (MovieController theMC) 
{
    QTUtils_ShowControllerButton(theMC, kQTUtilsHotSpotNames);
}
 
 
//////////
//
// QTVRUtils_ToggleHotSpotNames
// Toggle the displaying of hot spot names in the controller bar.
//
//////////
 
void QTVRUtils_ToggleHotSpotNames (MovieController theMC) 
{
    QTUtils_ToggleControllerButton(theMC, kQTUtilsHotSpotNames);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// File format utilities.
//
// Use these functions to read information from QuickTime VR files that's not accessible using the API.
// Throughout, we assume that we're dealing with format 2.0 files. We begin with a series of functions that
// return a pointer to the data in an atom (QTVRUtils_Get*AtomData); you probably won't use these functions
// directly.
//
// Keep in mind that data stored in QuickTime atoms is big-endian. We'll need to convert any multi-byte data
// that we read from an atom to native format before we use it.
//
// Note that these file format utilities are all Getters. As yet, no Setters. Perhaps later?
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTVRUtils_GetVRWorldHeaderAtomData
// Get a pointer to the VR world header atom data in a QTVR movie.
//
//////////
 
OSErr QTVRUtils_GetVRWorldHeaderAtomData (QTVRInstance theInstance, QTVRWorldHeaderAtomPtr theVRWorldHdrAtomPtr)
{
    QTAtomContainer         myVRWorld;
    QTAtom                  myAtom;
    OSErr                   myErr = noErr;
        
    // get the VR world
    myErr = QTVRGetVRWorld(theInstance, &myVRWorld);
    if (myErr != noErr)
        return(myErr);
    
    // get the single VR world header atom in the VR world
    myAtom = QTFindChildByIndex(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, NULL);
    if (myAtom != 0)
        myErr = QTCopyAtomDataToPtr(myVRWorld, myAtom, false, sizeof(QTVRWorldHeaderAtom), theVRWorldHdrAtomPtr, NULL);
    else 
        myErr = cannotFindAtomErr;
    
    QTDisposeAtomContainer(myVRWorld);
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetNodeHeaderAtomData
// Get a pointer to the node header atom data for the node having the specified node ID.
//
//////////
 
OSErr QTVRUtils_GetNodeHeaderAtomData (QTVRInstance theInstance, UInt32 theNodeID, QTVRNodeHeaderAtomPtr theNodeHdrPtr)
{
    QTAtomContainer         myNodeInfo;
    QTAtom                  myAtom;
    OSErr                   myErr = noErr;
        
    // get the node information atom container for the specified node
    myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
    if (myErr != noErr)
        return(myErr);
    
    // get the single node header atom in the node information atom container
    myAtom = QTFindChildByID(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, NULL);
    if (myAtom != 0)
        myErr = QTCopyAtomDataToPtr(myNodeInfo, myAtom, false, sizeof(QTVRNodeHeaderAtom), theNodeHdrPtr, NULL);
    else 
        myErr = cannotFindAtomErr;
 
    QTDisposeAtomContainer(myNodeInfo);
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetHotSpotAtomData
// Get a pointer to the hot spot atom data for hot spot having the specified hot spot ID in the specified node.
//
//////////
 
OSErr QTVRUtils_GetHotSpotAtomData (QTVRInstance theInstance, UInt32 theNodeID, UInt32 theHotSpotID, QTVRHotSpotInfoAtomPtr theHotSpotInfoPtr)
{
    QTAtomContainer         myNodeInfo;
    QTAtom                  myHSParentAtom;
    OSErr                   myErr = noErr;
        
    // (1) the node information atom container contains a *hot spot parent atom*;
    // (2) the hot spot parent atom contains one or more *hot spot atoms*;
    // (3) the hot spot atom contains two children, a *general hot spot information atom*
    //     and a *specific hot spot information atom*.
    // We want to return a pointer to the general hot spot information atom data.
 
    // get the node information atom container for the specified node
    myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
    if (myErr != noErr)
        return(myErr);
    
    // get the single hot spot parent atom in the node information atom container
    myHSParentAtom = QTFindChildByID(myNodeInfo, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, NULL);
    if (myHSParentAtom != 0) {
        QTAtom              myHSAtom;
        
        // get the hot spot atom whose atom ID is the specified hot spot ID
        myHSAtom = QTFindChildByID(myNodeInfo, myHSParentAtom, kQTVRHotSpotAtomType, theHotSpotID, NULL);
        if (myHSAtom != 0) {
            QTAtom          myAtom;
            
            // get the single hot spot information atom in the hot spot atom
            myAtom = QTFindChildByIndex(myNodeInfo, myHSAtom, kQTVRHotSpotInfoAtomType, 1, NULL);
            if (myAtom != 0) {
                myErr = QTCopyAtomDataToPtr(myNodeInfo, myAtom, false, sizeof(QTVRHotSpotInfoAtom), theHotSpotInfoPtr, NULL);
            }
        } else {
            myErr = cannotFindAtomErr;
        }
    } else {
        myErr = cannotFindAtomErr;
    }
 
    QTDisposeAtomContainer(myNodeInfo);
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetStringFromAtom
// Get the string data from the string atom having the specified ID in the specified atom container.
//
// We use a different strategy here, since we don't know the size of the string data in advance.
//
//////////
 
char *QTVRUtils_GetStringFromAtom (QTAtomContainer theContainer, QTAtom theParent, QTAtomID theID)
{
    QTVRStringAtomPtr   myStringAtomPtr = NULL;
    QTAtom              myNameAtom;
    char                *myString = NULL;
    OSErr               myErr = noErr;
 
    if (theContainer == NULL)
        return(myString);
        
    QTLockContainer(theContainer);
    
    myNameAtom = QTFindChildByID(theContainer, theParent, kQTVRStringAtomType, theID, NULL);
    if (myNameAtom != 0) {
        myErr = QTGetAtomDataPtr(theContainer, myNameAtom, NULL, (Ptr *)&myStringAtomPtr);
        if ((myErr == noErr) && (myStringAtomPtr != NULL)) {
            UInt16      myLength;
                
            myLength = EndianU16_BtoN(myStringAtomPtr->stringLength);
            if (myLength > 0) {
                myString = malloc(myLength + 1);
                if (myString != NULL) {
                    memcpy(myString, myStringAtomPtr->theString, myLength);
                    myString[myLength] = '\0';
                }
            }           
        }
    }
    
    QTUnlockContainer(theContainer);
    return(myString);
}
 
 
//////////
//
// QTVRUtils_AddStr255ToAtomContainer
// Add a Pascal string to the specified atom container; return (through theID) the ID of the new string atom.
//
//////////
 
OSErr QTVRUtils_AddStr255ToAtomContainer (QTAtomContainer theContainer, QTAtom theParent, Str255 theString, QTAtomID *theID)
{
    OSErr                   myErr = noErr;
 
    if ((theContainer == NULL) || (theParent == 0) || (theID == NULL))
        return(paramErr);
        
    *theID = 0;             // initialize the returned atom ID
    
    if (theString[0] != 0) {
        QTAtom              myStringAtom;
        UInt16              mySize;
        QTVRStringAtomPtr   myStringAtomPtr = NULL;
        
        mySize = sizeof(QTVRStringAtom) - 4 + theString[0];
        myStringAtomPtr = (QTVRStringAtomPtr)NewPtrClear(mySize);
        
        if (myStringAtomPtr != NULL) {
            myStringAtomPtr->stringUsage = EndianU16_NtoB(1);
            myStringAtomPtr->stringLength = EndianU16_NtoB(theString[0]);
            BlockMove(theString + 1, myStringAtomPtr->theString, theString[0]);
            myErr = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
            DisposePtr((Ptr)myStringAtomPtr);
            
            if (myErr == noErr)
                QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
        }
    }
    
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetDefaultNodeID
// Get the ID of the default node in a QTVR movie.
//
//////////
 
UInt32 QTVRUtils_GetDefaultNodeID (QTVRInstance theInstance)
{
    QTVRWorldHeaderAtom     myVRWorldHeader;
    UInt32                  myNodeID = kQTVRCurrentNode;
    OSErr                   myErr = noErr;
        
    myErr = QTVRUtils_GetVRWorldHeaderAtomData(theInstance, &myVRWorldHeader);
    if (myErr == noErr)
        myNodeID = EndianU32_BtoN(myVRWorldHeader.defaultNodeID);
 
    return(myNodeID);
}
 
 
//////////
//
// QTVRUtils_GetSceneFlags
// Get the set of flags associated with the VR scene.
// (Currently these flags are undefined, however.)
//
//////////
 
UInt32 QTVRUtils_GetSceneFlags (QTVRInstance theInstance)
{
    QTVRWorldHeaderAtom     myVRWorldHeader;
    UInt32                  myFlags = 0L;
    OSErr                   myErr;
        
    myErr = QTVRUtils_GetVRWorldHeaderAtomData(theInstance, &myVRWorldHeader);
    if (myErr == noErr)
        myFlags = EndianU32_BtoN(myVRWorldHeader.vrWorldFlags);
 
    return(myFlags);
}
 
 
//////////
//
// QTVRUtils_GetSceneName
// Get the name of the VR scene.
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTVRUtils_GetSceneName (QTVRInstance theInstance)
{
    QTVRWorldHeaderAtom         myVRWorldHeader;
    char                        *mySceneName = NULL;
    OSErr                       myErr = noErr;
        
    myErr = QTVRUtils_GetVRWorldHeaderAtomData(theInstance, &myVRWorldHeader);
    if (myErr == noErr) {
        QTAtomID                myNameAtomID;
        
        // get the atom ID of the name string atom
        myNameAtomID = EndianU32_BtoN(myVRWorldHeader.nameAtomID);
        
        if (myNameAtomID != 0) {
            QTAtomContainer     myVRWorld;
            
            // the string atom containing the name of the scene is a *sibling* of the VR world header atom
            myErr = QTVRGetVRWorld(theInstance, &myVRWorld);
            if (myErr == noErr)
                mySceneName = QTVRUtils_GetStringFromAtom(myVRWorld, kParentAtomIsContainer, myNameAtomID);
            
            QTDisposeAtomContainer(myVRWorld);
        }
    }
 
    return(mySceneName);
}
 
 
//////////
//
// QTVRUtils_GetNodeCount
// Get the number of nodes in a QTVR movie.
//
//////////
 
UInt32 QTVRUtils_GetNodeCount (QTVRInstance theInstance)
{
    QTAtomContainer         myVRWorld;
    QTAtom                  myNodeParentAtom;
    UInt32                  myNumNodes = 0;
    OSErr                   myErr = noErr;
 
    // get the VR world
    myErr = QTVRGetVRWorld(theInstance, &myVRWorld);
    if (myErr != noErr)
        return(myNumNodes);
 
    // get the node parent atom, whose children contain info about all nodes in the scene
    myNodeParentAtom = QTFindChildByIndex(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, NULL);
    if (myNodeParentAtom != 0) {
        // now count the node ID children of the node parent atom, which is the number of nodes in the scene
        myNumNodes = QTCountChildrenOfType(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType);
    }   
 
    QTDisposeAtomContainer(myVRWorld);
    
    return(myNumNodes);
}
 
 
//////////
//
// QTVRUtils_GetNodeType
// Get the type of the node with the specified ID.
//
// NOTE: This function is redundant, given QTVRGetNodeType; it's included here for illustrative purposes only.
//
//////////
 
OSErr QTVRUtils_GetNodeType (QTVRInstance theInstance, UInt32 theNodeID, OSType *theNodeType)
{
    QTVRNodeHeaderAtom      myNodeHeader;
    OSErr                   myErr = noErr;
 
    // make sure we always return some meaningful value
    *theNodeType = kQTVRUnknownType;
    
    // get the node header atom data
    myErr = QTVRUtils_GetNodeHeaderAtomData(theInstance, theNodeID, &myNodeHeader);
    if (myErr == noErr)
        *theNodeType = EndianU32_BtoN(myNodeHeader.nodeType);
        
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetNodeName
// Get the name of the node with the specified ID.
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTVRUtils_GetNodeName (QTVRInstance theInstance, UInt32 theNodeID)
{
    QTVRNodeHeaderAtom      myNodeHeader;
    char                    *myNodeName = NULL;
    OSErr                   myErr = noErr;
    
    myErr = QTVRUtils_GetNodeHeaderAtomData(theInstance, theNodeID, &myNodeHeader);
    if (myErr == noErr) {
        QTAtomID                myNameAtomID;
        
        // get the atom ID of the name string atom
        myNameAtomID = EndianU32_BtoN(myNodeHeader.nameAtomID);
        
        if (myNameAtomID != 0) {
            QTAtomContainer     myNodeInfo;
            
            // the string atom containing the name of the node is a *sibling* of the node information atom
            myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
            if (myErr == noErr)
                myNodeName = QTVRUtils_GetStringFromAtom(myNodeInfo, kParentAtomIsContainer, myNameAtomID);
 
            QTDisposeAtomContainer(myNodeInfo);
        }
    }
    
    return(myNodeName);
}
 
 
//////////
//
// QTVRUtils_GetNodeComment
// Get the comment for the node with the specified ID.
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTVRUtils_GetNodeComment (QTVRInstance theInstance, UInt32 theNodeID)
{
    QTVRNodeHeaderAtom      myNodeHeader;
    char                    *myNodeCmt = NULL;
    OSErr                   myErr = noErr;
    
    myErr = QTVRUtils_GetNodeHeaderAtomData(theInstance, theNodeID, &myNodeHeader);
    if (myErr == noErr) {
        QTAtomID                myCmtAtomID;
        
        // get the atom ID of the comment string atom
        myCmtAtomID = EndianU32_BtoN(myNodeHeader.commentAtomID);
        
        if (myCmtAtomID != 0) {
            QTAtomContainer     myNodeInfo;
            
            // the string atom containing the comment for the node is a *sibling* of the node information atom
            myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
            if (myErr == noErr)
                myNodeCmt = QTVRUtils_GetStringFromAtom(myNodeInfo, kParentAtomIsContainer, myCmtAtomID);
 
            QTDisposeAtomContainer(myNodeInfo);
        }
    }
    
    return(myNodeCmt);
}
 
 
//////////
//
// QTVRUtils_GetHotSpotCount
// Return the number of hot spots in the node with specified ID,
// and fill the specified handle with a list of the hot spot IDs.
//
// If theHotSpotIDs == NULL on entry, do not pass back the list of IDs.
//
// WARNING: This routine determines the number of hot spots by counting
// the hot spot atoms in a hot spot parent atom; this might not be
// the same as counting the number of regions in the hot spot image track.
// Sigh.
//
//////////
 
UInt32 QTVRUtils_GetHotSpotCount (QTVRInstance theInstance, UInt32 theNodeID, Handle theHotSpotIDs)
{
    QTAtomContainer         myNodeInfo;
    QTAtom                  myHSParentAtom = 0;
    UInt32                  myNumHotSpots = 0;
    OSErr                   myErr = noErr;
    
    // get the node information atom container for the current node
    myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
    
    // get the hot spot parent atom
    if (myErr == noErr)
        myHSParentAtom = QTFindChildByID(myNodeInfo, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, NULL);
        
    if (myHSParentAtom != 0) {
        SignedByte          myHState;
        Size                mySize;
 
        // get the number of hot spots in the current node
        myNumHotSpots = QTCountChildrenOfType(myNodeInfo, myHSParentAtom, kQTVRHotSpotAtomType);
        
        // now pass back a list of the hot spot IDs;
        // if theHotSpotIDs is NULL on entry, we assume the caller doesn't want this information
        if (theHotSpotIDs != NULL) {
        
            // unlock the handle, if it's locked (so that we can resize it)
            myHState = HGetState(theHotSpotIDs);
            if (myHState & 0x80)            // 0x80 == the block-is-locked bit in the SignedByte returned by HGetState
                HUnlock(theHotSpotIDs);
 
            // resize the handle to the appropriate size
            mySize = sizeof(UInt32) * myNumHotSpots;
            SetHandleSize(theHotSpotIDs, mySize);
            
            // restore the original handle state
            HSetState(theHotSpotIDs, myHState);
            
            // make sure we actually did resize the handle
            if (GetHandleSize(theHotSpotIDs) == mySize) {
                short           myIndex;
                QTAtom          myAtom;
                QTAtomID        myID;
                UInt32          *myIDPtr;
                
                myIDPtr = (UInt32 *)*theHotSpotIDs;
 
                // loop thru all the hot spots to get their IDs
                for (myIndex = 1; myIndex <= (short)myNumHotSpots; myIndex++) {
                    myAtom = QTFindChildByIndex(myNodeInfo, myHSParentAtom, kQTVRHotSpotAtomType, myIndex, &myID);
                    myIDPtr[myIndex - 1] = (UInt32)myID;
                }
            }
        }
    }
    
    QTDisposeAtomContainer(myNodeInfo);
    return(myNumHotSpots);
}
 
 
//////////
//
// QTVRUtils_GetHotSpotIDByIndex
// Return the hot spot ID having the specified index in the list of hot spot IDs returned by QTVRUtils_GetHotSpotCount,
// or kQTVRUtils_InvalidHotSpotID if no such hot spot exists.
//
//////////
 
UInt32 QTVRUtils_GetHotSpotIDByIndex (QTVRInstance theInstance, Handle theHotSpotIDs, UInt32 theIndex)
{
    Size            mySize;
    UInt32          myID = kQTVRUtils_InvalidHotSpotID;
    UInt32          *myIDPtr;
                
    // make sure the instance and hot spot list are non-NULL
    if ((theInstance == NULL) || (theHotSpotIDs == NULL))
        return(myID);
    
    // make sure that the index is valid    
    mySize = GetHandleSize(theHotSpotIDs);
    if (theIndex >= (mySize / sizeof(UInt32)))
        return(myID);
 
    myIDPtr = (UInt32 *)*theHotSpotIDs;
    myID = myIDPtr[theIndex];
    return(myID);
}
 
 
//////////
//
// QTVRUtils_GetHotSpotType
// Return the type of the hot spot having the specified hot spot ID in the specified node.
//
// NOTE: This function is semi-redundant, given QTVRGetHotSpotType; it's included here for illustrative purposes only.
// (Note, however, that QTVRGetHotSpotType returns types only for hot spots in the current node; here we do any node!)
//
//////////
 
OSErr QTVRUtils_GetHotSpotType (QTVRInstance theInstance, UInt32 theNodeID, UInt32 theHotSpotID, OSType *theHotSpotType)
{
    QTVRHotSpotInfoAtom     myHotSpotAtomData;
    OSErr                   myErr = noErr;
    
    // make sure we always return some meaningful value
    *theHotSpotType = kQTVRHotSpotUndefinedType;
    
    // get the hot spot information atom data
    myErr = QTVRUtils_GetHotSpotAtomData(theInstance, theNodeID, theHotSpotID, &myHotSpotAtomData);
    if (myErr == noErr)
        *theHotSpotType = EndianU32_BtoN(myHotSpotAtomData.hotSpotType);        // return the hot spot type
    
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_GetHotSpotName
// Return the name of the hot spot having the specified hot spot ID in the specified node.
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTVRUtils_GetHotSpotName (QTVRInstance theInstance, UInt32 theNodeID, UInt32 theHotSpotID)
{
    QTVRHotSpotInfoAtom     myHotSpotAtomData;
    char                    *myHotSpotName = NULL;
    OSErr                   myErr = noErr;
    
    // get the hot spot information atom data
    myErr = QTVRUtils_GetHotSpotAtomData(theInstance, theNodeID, theHotSpotID, &myHotSpotAtomData);
    if (myErr == noErr) {
        QTAtomID                myNameAtomID;
        
        // get the atom ID of the name string atom
        myNameAtomID = EndianU32_BtoN(myHotSpotAtomData.nameAtomID);
        
        if (myNameAtomID != 0) {
            QTAtomContainer     myNodeInfo;
            QTAtom              myHSParentAtom;
            QTAtom              myHSAtom;
            QTAtom              myNameAtom = 0;
            
            // version 2.0 documentation says that the hot spot name is contained in a string atom
            // that is a sibling of the hot spot atom (that is, a child of the hot spot parent atom);
            // some other documents indicate that a string atom is always a sibling of the atom that
            // contains the reference (in this case, a sibling of the hot spot information atom, and
            // hence a child of the hot spot atom); we will look first in the hot spot atom and then
            // in the hot spot parent atom. The version 2.1 documentation corrects the earlier error.
            // Mea culpa!
 
            // get the hot spot parent atom and the hot spot atom
            myErr = QTVRGetNodeInfo(theInstance, theNodeID, &myNodeInfo);
            if (myErr == noErr) {
                myHSParentAtom = QTFindChildByID(myNodeInfo, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, NULL);
                if (myHSParentAtom != 0) {
                    myHSAtom = QTFindChildByID(myNodeInfo, myHSParentAtom, kQTVRHotSpotAtomType, theHotSpotID, NULL);
                    if (myHSAtom != 0) {
                        QTAtom  myParentAtom;
                        
                        // look for a string atom that is a child of the hot spot atom
                        myParentAtom = myHSAtom;
                        myNameAtom = QTFindChildByID(myNodeInfo, myParentAtom, kQTVRStringAtomType, theHotSpotID, NULL);
                        if (myNameAtom == 0) {
                            // no such atom in the hot spot atom; look in the hot spot parent atom
                            myParentAtom = myHSParentAtom;
                            myNameAtom = QTFindChildByID(myNodeInfo, myParentAtom, kQTVRStringAtomType, theHotSpotID, NULL);
                        }
                        
                        if (myNameAtom != 0)
                            myHotSpotName = QTVRUtils_GetStringFromAtom(myNodeInfo, myParentAtom, myNameAtomID);
                    }
                }
            }
            
            QTDisposeAtomContainer(myNodeInfo);
        }
    }
 
    return(myHotSpotName);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous utilities.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
//////////
//
// QTVRUtils_Point3DToPanAngle
// Return the QTVR pan angle for a given QD3D point.
// 
//////////
 
float QTVRUtils_Point3DToPanAngle (float theX, float theY, float theZ)
{
#pragma unused(theY)
 
    float   myPan;
    
    if (theZ != 0.0) {
        // note that atan always returns angles in the range -¹/2 to ¹/2
        myPan = atan(theX / theZ);
        myPan = (theZ > 0) ? myPan + kVRPi : myPan;
    } else {
        myPan = (theX > 0) ? kVR3PiOver2 : kVRPiOver2;
    }
    
    // make sure myPan is positive
    while (myPan < 0.0)
        myPan += kVR2Pi;
 
    return(myPan);
}
 
 
//////////
//
// QTVRUtils_Point3DToTiltAngle
// Return the QTVR tilt angle for a given QD3D point.
// 
//////////
 
float QTVRUtils_Point3DToTiltAngle (float theX, float theY, float theZ)
{
    float           myTilt;
    float           myDistance;
    TQ3Point3D      myPoint;
    
    myPoint.x = theX;
    myPoint.y = theY;
    myPoint.z = theZ;
    
    myDistance = QTVRUtils_GetDistance(myPoint);
    if (myDistance != 0.0)
        myTilt = asin(theY / myDistance); 
    else
        myTilt = 0.0;
    
    return(myTilt);
}
 
 
//////////
//
// QTVRUtils_PanAngleToPoint3D
// Return a unit vector for a given QTVR pan angle.
//
// The pan angle is expected to be in radians.
// 
//////////
 
void QTVRUtils_PanAngleToPoint3D (float thePanAngle, float *theX, float *theZ)
{
    // sin and cos are continuous functions and have no limits on input range;
    // output range is always -1..1
    *theX = sin(thePanAngle);
    *theZ = -cos(thePanAngle);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Node callback utilities.
//
// Use these to obtain standard behaviors when entering or exiting nodes.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTVRUtils_StandardEnteringNodeProc
// A standard procedure for entering a new node.
//
// This function performs actions that many applications will want done when entering a new node:
//  * display back button only if multinode movie
//  * display show-hot-spot button only if there are hotspots
//  * display the translate button only for object nodes that can translate
//  * (this space for rent)
//
//////////
 
PASCAL_RTN OSErr QTVRUtils_StandardEnteringNodeProc (QTVRInstance theInstance, long theNodeID, MovieController theMC)
{
#pragma unused(theNodeID)
 
    OSErr       myErr = noErr;
 
    if ((theInstance == NULL) || (theMC == NULL))
        return(paramErr);
        
    /////// 
    // all nodes
    /////// 
        
    // display the back button only if it's a multinode movie
    if (QTVRUtils_IsMultiNode(theInstance))
        QTUtils_ShowControllerButton(theMC, kQTUtilsBackButton);
    else
        QTUtils_HideControllerButton(theMC, kQTUtilsBackButton);
 
    // display the show-hot-spot button only if there are hotspots in the node
    if (QTVRUtils_IsHotSpotInNode(theInstance))
        QTUtils_ShowControllerButton(theMC, kQTUtilsHotSpotButton);
    else
        QTUtils_HideControllerButton(theMC, kQTUtilsHotSpotButton);
 
    /////// 
    // panoramic nodes
    /////// 
    
    if (QTVRUtils_IsPanoNode(theInstance)) {
    
        // hide the translate button
        QTUtils_HideControllerButton(theMC, kQTUtilsTranslateButton);
        
    } else {
    
    /////// 
    // object nodes
    /////// 
        
        // show the translate button, but only if translation is available
        if (QTVRUtils_IsTranslateAvailable(theInstance))
            QTUtils_ShowControllerButton(theMC, kQTUtilsTranslateButton);
        else
            QTUtils_HideControllerButton(theMC, kQTUtilsTranslateButton);
    }
    
    return(myErr);
}
 
 
//////////
//
// QTVRUtils_StandardLeavingNodeProc
// A standard procedure for leaving a node.
// This function performs actions that many applications will want done when leaving a node:
//  * (this space for rent)
//
// We assume that when this procedure is called, the application has decided NOT to cancel the move;
// accordingly, we always return false in theCancel.
//
//////////
 
PASCAL_RTN OSErr QTVRUtils_StandardLeavingNodeProc (QTVRInstance theInstance, long fromNodeID, long toNodeID, Boolean *theCancel, MovieController theMC)
{
#pragma unused(fromNodeID, toNodeID)
 
    OSErr       myErr = noErr;
 
    if ((theInstance == NULL) || (theMC == NULL))
        return(paramErr);
    
    // nothing yet....
 
    *theCancel = false;
    return(myErr);
}