1/*
2 * MovieQTVRUtils.c --
3 *
4 *		Utilities for QTVR movies, part of QuickTimeTcl.
5 *      Several code snippets from Apples AddVRActions example.
6 *
7 * $Id: MovieQTVRUtils.c,v 1.1.1.1 2003/04/04 16:24:24 matben Exp $
8 */
9
10#ifdef _WIN32
11#   include "QuickTimeTclWin.h"
12#endif
13
14#include "QuickTimeTcl.h"
15
16#define kFloat 1
17#define kBoolean 2
18
19/*
20 * For dispatching timecode commands.
21 */
22
23static char *allHotSpotCmds[] = {
24	"configure", "setid", "setwiredactions",
25    (char *) NULL
26};
27
28enum {
29	kHotSpotCmdConfigure					= 0L,
30    kHotSpotCmdSetID,
31    kHotSpotCmdSetWiredActions
32};
33
34/* -enabled treated separately. */
35static char *allHotSpotSetWiredOptions[] = {
36	"-fov", "-pan", "-tilt",
37    (char *) NULL
38};
39
40enum {
41    kHotSpotSetWiredFov						= 0L,
42    kHotSpotSetWiredPan,
43    kHotSpotSetWiredTilt
44};
45
46static OSErr        ReplaceQTVRMedia( Movie movie, QTVRInstance qtvrInst,
47                            QTAtomContainer nodeInfoContainer );
48static int          AddWiredActionsToHotSpot( Tcl_Interp *interp, Movie movie,
49	                        QTVRInstance qtvrInst, int hotspotID, int objc, Tcl_Obj *CONST objv[] );
50static int          CreateHotSpotActionContainer( Tcl_Interp *interp,
51                            QTAtomContainer *actionContainer, int objc, Tcl_Obj *CONST objv[] );
52static OSErr        SetWiredActionsToHotSpot ( Handle theSample, long theHotSpotID,
53                            QTAtomContainer actionContainer );
54static OSErr        WriteTheMediaPropertyAtom ( Media theMedia, long propertyID,
55                            long thePropertySize, void *theProperty );
56static OSErr        CreateFrameLoadedHotSpotEnabledActionContainer(
57                            QTAtomContainer *actionContainer,
58                            long hotSpotID, Boolean enabled );
59static OSErr        SetWiredActionsToNode ( Handle theSample,
60                            QTAtomContainer actionContainer, UInt32 theActionType );
61
62
63
64/*
65 *----------------------------------------------------------------------
66 *
67 * ProcessHotspotSubCmd --
68 *
69 *		Process the "hotspot" subcommand.
70 *
71 * Results:
72 *  	Normal TCL results
73 *
74 * Side effects:
75 *		Depends on the command.
76 *
77 *----------------------------------------------------------------------
78 */
79
80int
81ProcessHotspotSubCmd( Tcl_Interp *interp,
82		Movie movie,
83	    QTVRInstance qtvrInst,
84	    int objc,
85	    Tcl_Obj *CONST objv[] )
86{
87	int 			    result = TCL_OK;
88	int                 nodeID;
89	int                 hotspotID;
90	int                 newHotspotID;
91	int                 iarg;
92	int                 len;
93	int                 rebuild = false;
94	int					cmdIndex;
95	int					intValue;
96	short               index;
97    OSErr               err = noErr;
98    QTAtomContainer     nodeInfoContainer = NULL;
99    QTAtomContainer     vrWorld = NULL;
100    QTVRHotSpotInfoAtomPtr  hotspotInfoAtomPtr;
101    QTAtom              hotspotParentAtom = 0;
102    QTAtom              hotspotAtom = 0;
103    QTAtom              hotspotInfoAtom = 0;
104    QTAtom              nameAtom = 0;
105    QTAtomID            atomID;
106    Boolean             enabled;
107	Tcl_DString         ds;
108
109    if (objc < 3) {
110		Tcl_WrongNumArgs( interp, 0, objv,
111				"pathName hotspot command nodeId hotspotId ?args?" );
112        return TCL_ERROR;
113    }
114    if (GetNodeCount( qtvrInst ) != 1) {
115		Tcl_SetObjResult( interp, Tcl_NewStringObj(
116				"Didn't recognize this as a single node QTVR movie", -1 ) );
117        return TCL_ERROR;
118    }
119	if (Tcl_GetIntFromObj( interp, objv[1], &nodeID ) != TCL_OK) {
120		Tcl_AddErrorInfo( interp, "\n	(processing nodeID value)" );
121        return TCL_ERROR;
122	}
123    nodeID = kQTVRCurrentNode;
124    nodeID = QTVRGetCurrentNodeID( qtvrInst );
125	if (Tcl_GetIntFromObj( interp, objv[2], &hotspotID ) != TCL_OK) {
126		Tcl_AddErrorInfo( interp, "\n	(processing hotspotID value)" );
127        return TCL_ERROR;
128	}
129
130    /*
131     * This gets only a *copy* of the atom container maintained internally.
132     * If we make any changes, we need to add it back, see below.
133     */
134
135    err = QTVRGetNodeInfo( qtvrInst, nodeID, &nodeInfoContainer );
136    if (err != noErr) {
137        CheckAndSetErrorResult( interp, err );
138        return TCL_ERROR;
139    }
140    QTLockContainer( nodeInfoContainer );
141
142    /*
143     * Get hotspot parent atom which is the parent of all hot spot atoms of the node.
144     */
145
146    hotspotParentAtom = QTFindChildByID( nodeInfoContainer, kParentAtomIsContainer,
147            kQTVRHotSpotParentAtomType, 1, NULL );
148    if (hotspotParentAtom != 0) {
149        hotspotAtom = QTFindChildByID( nodeInfoContainer, hotspotParentAtom,
150               kQTVRHotSpotAtomType, hotspotID, &index );
151        if (hotspotAtom != 0) {
152
153            /*
154             * Get the hotspot info atom here.
155             */
156
157            hotspotInfoAtom = QTFindChildByIndex( nodeInfoContainer, hotspotAtom,
158                    kQTVRHotSpotInfoAtomType, 1, &atomID );
159            if (hotspotInfoAtom != 0) {
160                err = QTGetAtomDataPtr( nodeInfoContainer, hotspotInfoAtom, NULL,
161                        (Ptr *) &hotspotInfoAtomPtr );
162                if (err == noErr) {
163                    if (EndianS32_BtoN(hotspotInfoAtomPtr->nameAtomID) != 0) {
164                        nameAtom = QTFindChildByID( nodeInfoContainer, hotspotAtom,
165                                kQTVRStringAtomType,
166                			    EndianS32_BtoN(hotspotInfoAtomPtr->nameAtomID), NULL );
167                    }
168                }
169            }
170        }
171    }
172
173    /*
174     * Must be unlocked if we want to change something.
175     */
176
177    QTUnlockContainer( nodeInfoContainer );
178
179	if (Tcl_GetIndexFromObj( interp, objv[0], allHotSpotCmds, "hotspot command",
180			TCL_EXACT, &cmdIndex ) != TCL_OK ) {
181	    return TCL_ERROR;
182	}
183
184    switch (cmdIndex) {
185
186        case kHotSpotCmdConfigure: {
187
188	        /*
189	         * Parse configure command options. Starting with iarg 3.
190	         */
191
192	        if (objc % 2 == 0) {
193				Tcl_WrongNumArgs( interp, 0, objv,
194						"pathName hotspot configure nodeId hotspotId -key value ?-key value?" );
195	      	    result = TCL_ERROR;
196	      	    goto error;
197	        }
198	        for (iarg = 3; iarg < objc; iarg = iarg + 2) {
199
200	            if (strcmp(Tcl_GetString( objv[iarg] ), "-enabled") == 0) {
201					if (Tcl_GetBooleanFromObj( interp, objv[iarg+1], &intValue )
202							!= TCL_OK) {
203						Tcl_AddErrorInfo( interp,
204								"\n	(processing -enabled option)" );
205						return TCL_ERROR;
206					}
207	                enabled = (Boolean) intValue;
208	                err = QTVREnableHotSpot( qtvrInst, kQTVRHotSpotID, hotspotID, enabled );
209	                if (err != noErr) {
210	                    CheckAndSetErrorResult( interp, err );
211	                    result = TCL_ERROR;
212	               	    goto error;
213	                }
214	            } else if (strcmp(Tcl_GetString( objv[iarg] ), "-name") == 0) {
215	                if (nameAtom != 0) {
216	                    QTVRStringAtomPtr   strAtomPtr = NULL;
217	                    UInt16              size;
218
219	                    Tcl_UtfToExternalDString( gQTTclTranslationEncoding,
220	                    		Tcl_GetString( objv[iarg+1] ), -1, &ds );
221	                    len = Tcl_DStringLength(&ds);
222					    len = (len > 250) ? 250 : len;
223
224	                    size = sizeof(QTVRStringAtom) - 4 + len;
225	                    strAtomPtr = (QTVRStringAtomPtr) NewPtrClear(size);
226
227	                    if (strAtomPtr != NULL) {
228	                        strAtomPtr->stringUsage = EndianU16_NtoB(1);
229	                        strAtomPtr->stringLength = EndianU16_NtoB(len);
230	                        BlockMove( Tcl_DStringValue(&ds), strAtomPtr->theString, len );
231	                        err = QTSetAtomData( nodeInfoContainer, nameAtom, size,
232	                                (Ptr) strAtomPtr );
233	                        DisposePtr( (Ptr) strAtomPtr );
234	                        Tcl_DStringFree( &ds );
235	                        if (err != noErr) {
236	                            CheckAndSetErrorResult( interp, err );
237	                            result = TCL_ERROR;
238	                       	    goto error;
239	                        }
240	                        rebuild = true;
241	                    }
242	                }
243	        	} else {
244					Tcl_SetObjResult( interp, Tcl_NewStringObj(
245							"Unrecognized option for hotspot configure", -1 ) );
246	           	    result = TCL_ERROR;
247	           	    goto error;
248	            }
249	        }
250        	break;
251        }
252
253        case kHotSpotCmdSetID: {
254
255	        /*
256	         * The hotspot id should be changed to a new id.
257	         */
258
259			if (Tcl_GetIntFromObj( interp, objv[3], &newHotspotID )
260					!= TCL_OK) {
261				Tcl_AddErrorInfo( interp,
262						"\n		(processing newHotspotID option)" );
263				return TCL_ERROR;
264			}
265	        err = QTSetAtomID( nodeInfoContainer, hotspotAtom, newHotspotID );
266	        if (err != noErr) {
267	            CheckAndSetErrorResult( interp, err );
268	       	    result = TCL_ERROR;
269	       	    goto error;
270	        }
271	        rebuild = true;
272        	break;
273        }
274
275        case kHotSpotCmdSetWiredActions: {
276
277	        /*
278	         * Set wired actions for the specified hotspot. We rebuild inside.
279	         */
280
281	        result = AddWiredActionsToHotSpot( interp, movie, qtvrInst, hotspotID,
282	                objc - 3, objv + 3 );
283	        if (result != TCL_OK) {
284	            goto error;
285	        }
286        	break;
287        }
288	}
289
290	if (rebuild) {
291        err = ReplaceQTVRMedia( movie, qtvrInst, nodeInfoContainer );
292        if (err != noErr) {
293            CheckAndSetErrorResult( interp, err );
294       	    result = TCL_ERROR;
295            goto error;
296        }
297	}
298    QTVRUpdate( qtvrInst, kQTVRStatic );
299
300error:
301    if (nodeInfoContainer != NULL) {
302        QTDisposeAtomContainer( nodeInfoContainer );
303    }
304    if (vrWorld != NULL) {
305        QTDisposeAtomContainer( vrWorld );
306    }
307    return result;
308}
309
310/*
311 *----------------------------------------------------------------------
312 *
313 * AddWiredActionsToHotSpot --
314 *
315 *
316 *
317 * Results:
318 *  	Tcl result
319 *
320 * Side effects:
321 *		None.
322 *
323 *----------------------------------------------------------------------
324 */
325
326static int
327AddWiredActionsToHotSpot( Tcl_Interp *interp,
328		Movie movie,
329	    QTVRInstance qtvrInst,
330	    int hotspotID,
331	    int objc,
332	    Tcl_Obj *CONST objv[] )
333{
334	Track							track = NULL;
335	Media							media = NULL;
336	TimeValue						trackOffset;
337	TimeValue						mediaTime;
338	TimeValue						sampleDuration;
339	QTVRSampleDescriptionHandle		theQTVRDesc = NULL;
340	Handle							sample = NULL;
341	short							sampleFlags;
342	QTAtomContainer					actionContainer = NULL;
343	Boolean							hasActions;
344	Boolean                         enabled;
345	OSErr							err = noErr;
346	int                             result = TCL_OK;
347	int                             iarg;
348	int                             hsObjc = 0;
349	int                             frameObjc = 0;
350	int								booleanInt;
351	Tcl_Obj                         *hsObjv[10];
352	Tcl_Obj                         *frameObjv[10];
353
354    track = QTVRGetQTVRTrack( movie, 1 );
355    if (track == NULL) {
356        CheckAndSetErrorResult( interp, noErr );
357        goto bail;
358    }
359
360	/* Get the first media sample in the QTVR track.
361	 *
362	 * The QTVR track contains one media sample for each node in the movie;
363	 * that sample contains a node information atom container, which contains
364	 * general information about the node (such as its type, its ID, its name,
365	 * and a list of its hot spots)
366	 */
367
368	media = GetTrackMedia(track);
369	if (media == NULL) {
370        CheckAndSetErrorResult( interp, noErr );
371		goto bail;
372	}
373	trackOffset = GetTrackOffset( track );
374	mediaTime = TrackTimeToMediaTime( trackOffset, track );
375
376	theQTVRDesc = (QTVRSampleDescriptionHandle) NewHandle(4);
377	if (theQTVRDesc == NULL) {
378        CheckAndSetErrorResult( interp, noErr );
379		goto bail;
380    }
381	sample = NewHandle(0);
382	if (sample == NULL) {
383        CheckAndSetErrorResult( interp, noErr );
384		goto bail;
385    }
386	err = GetMediaSample( media, sample, 0, NULL, mediaTime, NULL,
387	        &sampleDuration, (SampleDescriptionHandle) theQTVRDesc, NULL, 1,
388	        NULL, &sampleFlags );
389	if (err != noErr) {
390        CheckAndSetErrorResult( interp, noErr );
391		goto bail;
392	}
393
394	/*
395	 * Some of the arguments must be stored in a 'frame loaded container',
396	 * while the other shall be put in the 'hotspot action container'.
397	 */
398
399    for (iarg = 0; iarg < objc; iarg = iarg + 2) {
400        if ((strcmp(Tcl_GetString( objv[iarg] ), "-enabled") == 0)) {
401            frameObjv[frameObjc] = objv[iarg];
402            frameObjv[frameObjc+1] = objv[iarg+1];
403            frameObjc += 2;
404			if (Tcl_GetBooleanFromObj( interp, frameObjv[iarg+1],
405					&booleanInt ) != TCL_OK) {
406				Tcl_AddErrorInfo( interp,
407						"\n	(processing -enabled option)" );
408				result = TCL_ERROR;
409				goto bail;
410			}
411    	    enabled = (Boolean) booleanInt;
412        } else {
413            hsObjv[hsObjc] = objv[iarg];
414            hsObjv[hsObjc+1] = objv[iarg+1];
415            hsObjc += 2;
416		}
417	}
418
419	/*
420	 * The enabled action is put in a frame loaded container.
421	 */
422
423	if (frameObjc > 0) {
424        err = CreateFrameLoadedHotSpotEnabledActionContainer( &actionContainer,
425                hotspotID, enabled );
426    	if (err != noErr) {
427	        CheckAndSetErrorResult( interp, err );
428            goto bail;
429    	}
430        err = SetWiredActionsToNode ( sample, actionContainer, kQTEventFrameLoaded );
431    	if (err != noErr) {
432	        CheckAndSetErrorResult( interp, err );
433    		goto bail;
434    	}
435    	if (actionContainer != NULL) {
436    		QTDisposeAtomContainer(actionContainer);
437    		actionContainer = NULL;
438    	}
439	}
440
441	/*
442	 * Add hot-spot actions.
443	 */
444
445	if (hsObjc > 0) {
446        err = CreateHotSpotActionContainer( interp, &actionContainer, hsObjc, hsObjv );
447    	if (err != noErr) {
448	        CheckAndSetErrorResult( interp, err );
449            goto bail;
450    	}
451    	err = SetWiredActionsToHotSpot( sample, hotspotID, actionContainer );
452    	if (err != noErr) {
453	        CheckAndSetErrorResult( interp, err );
454    		goto bail;
455    	}
456    	if (actionContainer != NULL) {
457    		QTDisposeAtomContainer(actionContainer);
458    		actionContainer = NULL;
459    	}
460    }
461
462	/*
463	 * Replace sample in media.
464	 */
465
466    err = ReplaceQTVRMedia( movie, qtvrInst, (QTAtomContainer) sample );
467	if (err != noErr) {
468	    CheckAndSetErrorResult( interp, err );
469		goto bail;
470	}
471
472	/*
473	 * Set the actions property atom, to enable wired action processing.
474	 */
475
476	hasActions = true;
477	// since sizeof(Boolean) == 1, there is no need to swap bytes here
478	err = WriteTheMediaPropertyAtom( media, kSpriteTrackPropertyHasActions,
479	        sizeof(Boolean), &hasActions );
480	if (err != noErr) {
481	    CheckAndSetErrorResult( interp, err );
482		goto bail;
483	}
484
485bail:
486	if (actionContainer != NULL) {
487		QTDisposeAtomContainer(actionContainer);
488	}
489	if (sample != NULL) {
490		DisposeHandle(sample);
491	}
492	if (theQTVRDesc != NULL) {
493		DisposeHandle((Handle)theQTVRDesc);
494	}
495	if (result != TCL_OK) {
496        CheckAndSetErrorResult( interp, err );
497	}
498	return result;
499}
500
501/*
502 *----------------------------------------------------------------------
503 *
504 * ReplaceQTVRMedia --
505 *
506 *
507 *
508 * Results:
509 *  	OSErr result
510 *
511 * Side effects:
512 *		None.
513 *
514 *----------------------------------------------------------------------
515 */
516
517static OSErr
518ReplaceQTVRMedia( Movie movie,
519		QTVRInstance qtvrInst,
520        QTAtomContainer nodeInfoContainer )     // the actual sample (Handle)
521{
522    OSErr               err = noErr;
523    Track               track = NULL;
524    Media               media = NULL;
525	Handle				sample = NULL;
526	short				sampleFlags;
527	TimeValue			sampleDuration;
528    TimeValue           trackOffset;
529    TimeValue           mediaTime;
530    TimeValue           newMediaTime;
531	TimeValue			selectionDuration;
532	Fixed 				trackEditRate;
533    QTVRSampleDescriptionHandle theQTVRDesc = NULL;
534
535    /*
536     * We need to replace the QTVR track with the newly edited information.
537     * A single node QTVR movie contains only a single media sample, with:
538     *  1) The VR World Atom Container as sample description, and
539     *  2) The Node Info Atom Container as the media sample.
540     * So replace the old with the new. Fails for multi node movies!
541     */
542
543    track = QTVRGetQTVRTrack( movie, 1 );
544    if (track == NULL) {
545        goto error;
546    }
547    media = GetTrackMedia( track );
548    if (media == NULL) {
549        goto error;
550    }
551	trackOffset = GetTrackOffset( track );
552	mediaTime = TrackTimeToMediaTime( trackOffset, track );
553	theQTVRDesc = (QTVRSampleDescriptionHandle) NewHandle(4);
554	if (theQTVRDesc == NULL) {
555		goto error;
556    }
557	err = GetMediaSample( media, sample, 0, NULL, mediaTime, NULL,
558	        &sampleDuration, (SampleDescriptionHandle) theQTVRDesc, NULL, 1,
559	        NULL, &sampleFlags );
560	if (err != noErr) {
561		goto error;
562	}
563	trackEditRate = GetTrackEditRate( track, trackOffset );
564	if (GetMoviesError() != noErr)
565		goto error;
566
567	GetTrackNextInterestingTime( track, nextTimeMediaSample | nextTimeEdgeOK,
568	        trackOffset, fixed1, NULL, &selectionDuration );
569	if (GetMoviesError() != noErr)
570		goto error;
571
572    /* Get rid of the old QTVR sample first. */
573
574	err = DeleteTrackSegment( track, trackOffset, selectionDuration );
575	if (err != noErr) {
576		goto error;
577    }
578    err = BeginMediaEdits( media );
579    if (err != noErr) {
580        goto error;
581    }
582    err = AddMediaSample( media,
583          (Handle) nodeInfoContainer,             // the actual data
584          0,                                      // no offset in data
585          GetHandleSize((Handle) nodeInfoContainer),  // the data size
586    	  sampleDuration,                          // duration
587          (SampleDescriptionHandle) theQTVRDesc,
588          1,                                      // one sample
589          sampleFlags,                            // key frame or not
590          &newMediaTime );
591    if (err != noErr) {
592        goto error;
593    }
594    err = EndMediaEdits( media );
595    if (err != noErr) {
596        goto error;
597    }
598    err = InsertMediaIntoTrack( track,          // the track
599         trackOffset,                           // track time, 0 for single node
600         newMediaTime,                          // media start time in media's time
601         selectionDuration,                     // media's duration in media's time
602         trackEditRate );                       // the media's rate
603    if (err != noErr) {
604        goto error;
605    }
606
607error:
608    if (sample != NULL) {
609        DisposeHandle( sample );
610    }
611    return(err);
612}
613
614/*
615 *----------------------------------------------------------------------
616 *
617 * CreateFrameLoadedHotSpotEnabledActionContainer --
618 *
619 *
620 *
621 * Results:
622 *		Standard OSErr result
623 *
624 * Side effects:
625 *
626 *----------------------------------------------------------------------
627 */
628
629static OSErr
630CreateFrameLoadedHotSpotEnabledActionContainer( QTAtomContainer *actionContainer,
631        long hotSpotID, Boolean enabled )
632{
633	QTAtom			actionAtom = 0;
634	QTAtom          eventAtom = 0;
635	long			action;
636	OSErr			err = noErr;
637
638	err = QTNewAtomContainer( actionContainer );
639	if (err != noErr) {
640		goto bail;
641	}
642	err = QTInsertChild( *actionContainer, kParentAtomIsContainer, kQTEventFrameLoaded,
643	        1, 1, 0, NULL, &eventAtom );
644	if (err != noErr) {
645		goto bail;
646    }
647	err = QTInsertChild( *actionContainer, eventAtom, kAction, 1, 1, 0, NULL,
648	        &actionAtom );
649	if (err != noErr) {
650		goto bail;
651    }
652	action = EndianS32_NtoB( kActionQTVREnableHotSpot );
653	hotSpotID = EndianS32_NtoB( hotSpotID );
654	err = QTInsertChild( *actionContainer, actionAtom, kWhichAction, 1, 1,
655	        sizeof(long), &action, NULL);
656	if (err != noErr) {
657		goto bail;
658    }
659	err = QTInsertChild( *actionContainer, actionAtom, kActionParameter, 0, 0,
660	        sizeof(long), &hotSpotID, NULL );
661	if (err != noErr) {
662		goto bail;
663	}
664	err = QTInsertChild( *actionContainer, actionAtom, kActionParameter, 0, 0,
665	        sizeof(Boolean), &enabled, NULL );
666	if (err != noErr) {
667		goto bail;
668    }
669
670bail:
671	return(err);
672}
673
674/*
675 *----------------------------------------------------------------------
676 *
677 * CreateHotSpotActionContainer --
678 *
679 *		Return, through the actionContainer parameter, an atom container
680 *		that contains one or many hot spot actions.
681 *
682 * Results:
683 *		Standard TCL result
684 *
685 * Side effects:
686 *
687 *----------------------------------------------------------------------
688 */
689
690static int
691CreateHotSpotActionContainer( Tcl_Interp *interp,
692		QTAtomContainer *actionContainer,
693        int objc,
694        Tcl_Obj *CONST objv[] )
695{
696	QTAtom			eventAtom = 0;
697	QTAtom			actionAtom = 0;
698	long			action;
699	float			aFloat;
700	double			aDouble;
701	OSErr			err = noErr;
702	Tcl_Obj			*resultObjPtr;
703	int             iarg;
704	int				optIndex;
705
706	err = QTNewAtomContainer( actionContainer );
707	if (err != noErr) {
708		goto error;
709	}
710	err = QTInsertChild( *actionContainer, kParentAtomIsContainer, kQTEventType,
711	        kQTEventMouseClick, 1, 0, NULL, &eventAtom );
712	if (err != noErr) {
713		goto error;
714	}
715
716    /*
717     * Parse configure command options. Starting with iarg 0.
718     */
719
720    for (iarg = 0; iarg < objc; iarg = iarg + 2) {
721
722    	if (Tcl_GetIndexFromObj( interp, objv[iarg], allHotSpotSetWiredOptions,
723    	        "hotspot setwiredactions option", TCL_EXACT, &optIndex ) != TCL_OK ) {
724    	    goto error;
725    	}
726    	if (iarg + 1 == objc) {
727    		resultObjPtr = Tcl_GetObjResult( interp );
728    		Tcl_AppendStringsToObj( resultObjPtr, "value for \"",
729    				Tcl_GetString(objv[iarg]), "\"missing", (char *) NULL );
730    	    goto error;
731    	}
732
733        switch (optIndex) {
734
735            case kHotSpotSetWiredFov: {
736	    	    action = EndianS32_NtoB( kActionQTVRSetFieldOfView );
737		    	if (Tcl_GetDoubleFromObj( interp, objv[iarg+1], &aDouble )
738		    				!= TCL_OK) {
739					Tcl_AddErrorInfo( interp,
740							"\n		(processing -fov option)" );
741		    	    goto error;
742				}
743	    	    aFloat = (float) aDouble;
744		        ConvertFloatToBigEndian( &aFloat );
745				break;
746			}
747
748            case kHotSpotSetWiredPan: {
749	    	    action = EndianS32_NtoB( kActionQTVRSetPanAngle );
750		    	if (Tcl_GetDoubleFromObj( interp, objv[iarg+1], &aDouble )
751		    				!= TCL_OK) {
752					Tcl_AddErrorInfo( interp,
753							"\n		(processing -pan option)" );
754		    	    goto error;
755				}
756	    	    aFloat = (float) aDouble;
757		        ConvertFloatToBigEndian( &aFloat );
758				break;
759			}
760
761            case kHotSpotSetWiredTilt: {
762	    	    action = EndianS32_NtoB( kActionQTVRSetTiltAngle );
763		    	if (Tcl_GetDoubleFromObj( interp, objv[iarg+1], &aDouble )
764		    				!= TCL_OK) {
765					Tcl_AddErrorInfo( interp,
766							"\n		(processing -tilt option)" );
767		    	    goto error;
768				}
769	    	    aFloat = (float) aDouble;
770		        ConvertFloatToBigEndian( &aFloat );
771				break;
772			}
773    	}
774
775    	/*
776    	 * For each action we need an Action atom to hold the WhichAction,
777    	 * and ActionParameter atoms.
778    	 */
779
780    	err = QTInsertChild( *actionContainer, eventAtom, kAction, 0,
781    	        0, 0, NULL, &actionAtom );
782    	if (err != noErr) {
783    		goto error;
784        }
785        err = QTInsertChild( *actionContainer, actionAtom, kWhichAction, 1,
786                1, sizeof(long), &action, NULL );
787        if (err != noErr) {
788            goto error;
789        }
790        err = QTInsertChild( *actionContainer, actionAtom, kActionParameter, 1,
791                1, sizeof(float), &aFloat, NULL );
792        if (err != noErr) {
793            goto error;
794        }
795    }
796    return TCL_OK;
797
798error:
799    CheckAndSetErrorResult( interp, err );
800    return TCL_ERROR;
801}
802
803/*
804 *----------------------------------------------------------------------
805 *
806 * CreateFrameLoadedActionContainer --
807 *
808 *		Return, through the actionContainer parameter, an atom container
809 *		that contains a frame-loaded event action.
810 *
811 * Results:
812 *		Standard OSErr result
813 *
814 * Side effects:
815 *
816 *----------------------------------------------------------------------
817 */
818
819static OSErr
820CreateFrameLoadedActionContainer( QTAtomContainer *actionContainer )
821{
822	QTAtom			eventAtom = 0;
823	QTAtom			actionAtom = 0;
824	long			action;
825	float			panAngle;
826	OSErr			err = noErr;
827
828	err = QTNewAtomContainer(actionContainer);
829	if (err != noErr) {
830		goto bail;
831	}
832	err = QTInsertChild(*actionContainer, kParentAtomIsContainer, kQTEventFrameLoaded,
833	        1, 1, 0, NULL, &eventAtom);
834	if (err != noErr) {
835		goto bail;
836	}
837	err = QTInsertChild(*actionContainer, eventAtom, kAction, 1, 1,
838	        0, NULL, &actionAtom);
839	if (err != noErr) {
840		goto bail;
841	}
842	action = EndianS32_NtoB(kActionQTVRSetPanAngle);
843	err = QTInsertChild(*actionContainer, actionAtom, kWhichAction, 1,
844	        1, sizeof(long), &action, NULL);
845	if (err != noErr) {
846		goto bail;
847	}
848	panAngle = 180.0;
849	ConvertFloatToBigEndian(&panAngle);
850	err = QTInsertChild(*actionContainer, actionAtom, kActionParameter, 1,
851	        1, sizeof(float), &panAngle, NULL);
852	if (err != noErr) {
853		goto bail;
854    }
855
856bail:
857	return(err);
858}
859
860/*
861 *----------------------------------------------------------------------
862 *
863 * CreateIdleActionContainer --
864 *
865 *		Return, through the actionContainer parameter, an atom container
866 *		that contains an idle event action.
867 *
868 * Results:
869 *		Standard OSErr result
870 *
871 * Side effects:
872 *
873 *----------------------------------------------------------------------
874 */
875
876static OSErr
877CreateIdleActionContainer (QTAtomContainer *actionContainer)
878{
879	QTAtom			eventAtom = 0;
880	QTAtom			actionAtom = 0;
881	long			action;
882	float			panAngle;
883	UInt32			flags;
884	OSErr			err = noErr;
885
886	err = QTNewAtomContainer(actionContainer);
887	if (err != noErr)
888		goto bail;
889
890	err = QTInsertChild(*actionContainer, kParentAtomIsContainer, kQTEventIdle,
891	        1, 1, 0, NULL, &eventAtom);
892	if (err != noErr)
893		goto bail;
894
895	err = QTInsertChild(*actionContainer, eventAtom, kAction, 1,
896	        1, 0, NULL, &actionAtom);
897	if (err != noErr)
898		goto bail;
899
900	action = EndianS32_NtoB(kActionQTVRSetPanAngle);
901	err = QTInsertChild(*actionContainer, actionAtom, kWhichAction, 1,
902	        1, sizeof(long), &action, NULL);
903	if (err != noErr)
904		goto bail;
905
906	panAngle = 10.0;
907	ConvertFloatToBigEndian(&panAngle);
908	err = QTInsertChild(*actionContainer, actionAtom, kActionParameter,
909	        1, 1, sizeof(float), &panAngle, NULL);
910	if (err != noErr)
911		goto bail;
912
913	flags = EndianU32_NtoB(kActionFlagActionIsDelta | kActionFlagParameterWrapsAround);
914	err = QTInsertChild(*actionContainer, actionAtom, kActionFlags, 1,
915	        1, sizeof(UInt32), &flags, NULL);
916	if (err != noErr)
917		goto bail;
918
919bail:
920	return(err);
921}
922
923/*
924 *----------------------------------------------------------------------
925 *
926 * SetWiredActionsToNode --
927 *
928 *		Set the specified actions to be a node action of the specified
929 *		type. If actionContainer is NULL, remove any existing action of
930 *		that type from theSample. The theSample parameter is assumed to
931 *		be a node information atom container; any actions that are
932 *		global to the node should be inserted at the root level of this
933 *		atom container; in addition, the container type should be the
934 *		same as the event type and should have an atom ID of 1.
935 *
936 * Results:
937 *		Standard OSErr result
938 *
939 * Side effects:
940 *
941 *----------------------------------------------------------------------
942 */
943
944static OSErr
945SetWiredActionsToNode ( Handle theSample, QTAtomContainer actionContainer,
946        UInt32 theActionType )
947{
948	QTAtom			eventAtom = 0;
949	QTAtom			targetAtom = 0;
950	OSErr			err = noErr;
951
952	/* Look for a frame-loaded action atom in the specified actions atom container. */
953	if (actionContainer != NULL) {
954		eventAtom = QTFindChildByID(actionContainer, kParentAtomIsContainer,
955		        theActionType, 1, NULL);
956    }
957
958	/* Look for a frame-loaded action atom in the node information atom container. */
959	targetAtom = QTFindChildByID(theSample, kParentAtomIsContainer,
960	        theActionType, 1, NULL);
961	if (targetAtom != 0) {
962
963		/*
964		 * If there is already a frame-loaded event atom in the node information
965		 * atom container, then either replace it with the one we were passed or
966		 * remove it.
967		 */
968
969		if (actionContainer != NULL) {
970			err = QTReplaceAtom(theSample, targetAtom, actionContainer, eventAtom);
971		} else {
972			err = QTRemoveAtom(theSample, targetAtom);
973		}
974	} else {
975
976		/*
977		 * There is no frame-loaded event atom in the node information atom container,
978		 * so add in the one we were passed.
979		 */
980
981		if (actionContainer != NULL) {
982			err = QTInsertChildren(theSample, kParentAtomIsContainer, actionContainer);
983		}
984	}
985
986	return(err);
987}
988
989/*
990 *----------------------------------------------------------------------
991 *
992 * SetWiredActionsToHotSpot --
993 *
994 *		Set the specified actions to be a hot-spot action. If
995 *		actionContainer is NULL, remove any existing hot-spot actions
996 *		for the specified hot spot from theSample.
997 *
998 * Results:
999 *		Standard OSErr result
1000 *
1001 * Side effects:
1002 *
1003 *----------------------------------------------------------------------
1004 */
1005
1006static OSErr
1007SetWiredActionsToHotSpot ( Handle theSample, long theHotSpotID,
1008        QTAtomContainer actionContainer )
1009{
1010	QTAtom			hotSpotParentAtom = 0;
1011	QTAtom			hotSpotAtom = 0;
1012	short			count,
1013					index;
1014	OSErr			err = noErr;
1015
1016	hotSpotParentAtom = QTFindChildByIndex(theSample, kParentAtomIsContainer,
1017	        kQTVRHotSpotParentAtomType, 1, NULL);
1018	if (hotSpotParentAtom == 0) {
1019		goto bail;
1020	}
1021	hotSpotAtom = QTFindChildByID(theSample, hotSpotParentAtom,
1022	        kQTVRHotSpotAtomType, theHotSpotID, NULL);
1023	if (hotSpotAtom == 0) {
1024		goto bail;
1025	}
1026
1027	/* See how many events are already associated with the specified hot spot. */
1028	count = QTCountChildrenOfType(theSample, hotSpotAtom, kQTEventType);
1029
1030	for (index = count; index > 0; index--) {
1031		QTAtom		targetAtom = 0;
1032
1033		/* Remove all the existing events. */
1034		targetAtom = QTFindChildByIndex(theSample, hotSpotAtom, kQTEventType,
1035		        index, NULL);
1036		if (targetAtom != 0) {
1037			err = QTRemoveAtom(theSample, targetAtom);
1038			if (err != noErr) {
1039				goto bail;
1040			}
1041		}
1042	}
1043
1044	if (actionContainer) {
1045		err = QTInsertChildren(theSample, hotSpotAtom, actionContainer);
1046		if (err != noErr) {
1047			goto bail;
1048		}
1049	}
1050
1051bail:
1052	return(err);
1053}
1054
1055/*
1056 *----------------------------------------------------------------------
1057 *
1058 * WriteTheMediaPropertyAtom --
1059 *
1060 *		Add a media property action to the specified media.
1061 *		We assume that the data passed to us through the theProperty
1062 *		parameter is big-endian.
1063 *
1064 * Results:
1065 *		Standard OSErr result
1066 *
1067 * Side effects:
1068 *
1069 *----------------------------------------------------------------------
1070 */
1071
1072static OSErr
1073WriteTheMediaPropertyAtom ( Media theMedia, long propertyID,
1074        long thePropertySize, void *theProperty )
1075{
1076	QTAtomContainer		propertyAtom = NULL;
1077	QTAtom				atom = 0;
1078	OSErr				err = noErr;
1079
1080	/* Get the current media property atom. */
1081	err = GetMediaPropertyAtom(theMedia, &propertyAtom);
1082	if (err != noErr)
1083		goto bail;
1084
1085	/* If there isn't one yet, then create one. */
1086	if (propertyAtom == NULL) {
1087		err = QTNewAtomContainer(&propertyAtom);
1088		if (err != noErr)
1089			goto bail;
1090	}
1091
1092	/*
1093	 * See if there is an existing atom of the specified type; if not,
1094	 * then create one.
1095	 */
1096
1097	atom = QTFindChildByID(propertyAtom, kParentAtomIsContainer,
1098	        propertyID, 1, NULL);
1099	if (atom == 0) {
1100		err = QTInsertChild(propertyAtom, kParentAtomIsContainer,
1101		        propertyID, 1, 0, 0, NULL, &atom);
1102		if ((err != noErr) || (atom == 0))
1103			goto bail;
1104	}
1105
1106	/* Set the data of the specified atom to the data passed in. */
1107	err = QTSetAtomData(propertyAtom, atom, thePropertySize, (Ptr)theProperty);
1108	if (err != noErr)
1109		goto bail;
1110
1111	/* Write the new atom data out to the media property atom. */
1112	err = SetMediaPropertyAtom(theMedia, propertyAtom);
1113
1114bail:
1115	if (propertyAtom != NULL) {
1116		err = QTDisposeAtomContainer(propertyAtom);
1117	}
1118	return(err);
1119}
1120
1121/*
1122 *----------------------------------------------------------------------
1123 *
1124 * PanoramaGetInfoNode --
1125 *
1126 *  	Gets all info from this panorama node, and fills the result
1127 *		object.
1128 *
1129 * Results:
1130 *  	Tcl result.
1131 *
1132 * Side effects:
1133 *		Fills the result object with data.
1134 *
1135 *----------------------------------------------------------------------
1136 */
1137
1138int
1139PanoramaGetInfoNode( Tcl_Interp *interp,
1140		Movie movie,
1141		QTVRInstance qtvrInst,
1142        UInt32 nodeID,
1143        Tcl_Obj **resObj )
1144{
1145    OSErr               err = noErr;
1146    QTAtomContainer     nodeInfoContainer = NULL;
1147    QTVRNodeHeaderAtomPtr nodeHeaderPtr;
1148    QTVRStringAtomPtr   stringAtomPtr;
1149    QTAtom              headerAtom = 0;
1150    QTAtom              hotspotParentAtom = 0;
1151    QTAtom              hotspotAtom = 0;
1152    QTAtom              hotspotInfoAtom = 0;
1153    QTAtom              nameAtom = 0;
1154    QTAtomID            atomHotSpotID;
1155    QTAtomID            atomID;
1156    OSType              nodeType;
1157    Track               panoTrack = NULL;
1158	Tcl_Obj             *listObjPtr;
1159    Tcl_Obj             *hotspotObjPtr;
1160    Tcl_Obj             *rectListObjPtr;
1161	Tcl_DString         ds;
1162    char                tmpstr[255];
1163    short               index;
1164    int                 len;
1165    UInt32              currentNodeID;
1166
1167    *resObj = Tcl_NewListObj( 0, (Tcl_Obj **) NULL );
1168    listObjPtr = Tcl_NewListObj( 0, (Tcl_Obj **) NULL );
1169
1170    currentNodeID = QTVRGetCurrentNodeID( qtvrInst );
1171    Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewStringObj("-nodeid", -1) );
1172    Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewIntObj(currentNodeID) );
1173    nodeType = QTVRGetNodeType( qtvrInst, nodeID );
1174    Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewStringObj("-nodetype", -1) );
1175    if (nodeType == kQTVRPanoramaType) {
1176        Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewStringObj("panorama", -1) );
1177    } else if (nodeType == kQTVRObjectType) {
1178        Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewStringObj("object", -1) );
1179    } else {
1180        Tcl_ListObjAppendElement( interp, *resObj, Tcl_NewStringObj("unknown", -1) );
1181    }
1182
1183    /*
1184     * Start by getting the panorama sample structure.
1185     */
1186
1187    panoTrack = GetMovieIndTrackType( movie, 1, FOUR_CHAR_CODE('pano'),
1188    	    movieTrackMediaType );
1189    if (panoTrack != NULL) {
1190        Media                   media = NULL;
1191        Handle                  dataOut = NULL;
1192        Ptr                     dataPtr = NULL;
1193        long                    size;
1194        TimeValue               sampleTime;
1195        TimeValue               durationPerSample;
1196        SampleDescriptionHandle descHandle = NULL;
1197        long                    descIndex;
1198        long                    numOfSamples;
1199        QTAtomContainer         containerHandle = NULL;
1200        QTAtom                  sampleAtom = 0;
1201        QTVRPanoSampleAtom      *samplePtr;
1202
1203        media = GetTrackMedia( panoTrack );
1204        if (media != NULL) {
1205            dataOut = NewHandle(4);
1206            err = GetMediaSample( media,
1207                    dataOut,
1208                    0,
1209                    &size,
1210                    0,                  // media time
1211                    &sampleTime,
1212                    &durationPerSample,
1213                    descHandle,
1214                    &descIndex,
1215                    0,
1216                    &numOfSamples,
1217                    NULL );
1218    		if (err != noErr) {
1219                CheckAndSetErrorResult( interp, err );
1220    			return TCL_ERROR;
1221    		}
1222            HLock( dataOut );
1223            containerHandle = (QTAtomContainer) dataOut;
1224            QTLockContainer( containerHandle );
1225
1226            sampleAtom = QTFindChildByID( containerHandle, kParentAtomIsContainer,
1227                    kQTVRPanoSampleDataAtomType, 1, NULL );
1228            if (sampleAtom != 0) {
1229                err = QTGetAtomDataPtr( containerHandle, sampleAtom, NULL, (Ptr *) &samplePtr );
1230                if (err == noErr) {
1231					ConvertBigEndianFloatToNative( &(samplePtr->minPan) );
1232					ConvertBigEndianFloatToNative( &(samplePtr->maxPan) );
1233					ConvertBigEndianFloatToNative( &(samplePtr->minTilt) );
1234					ConvertBigEndianFloatToNative( &(samplePtr->maxTilt) );
1235					ConvertBigEndianFloatToNative( &(samplePtr->minFieldOfView) );
1236					ConvertBigEndianFloatToNative( &(samplePtr->maxFieldOfView) );
1237					ConvertBigEndianFloatToNative( &(samplePtr->defaultPan) );
1238					ConvertBigEndianFloatToNative( &(samplePtr->defaultTilt) );
1239					ConvertBigEndianFloatToNative( &(samplePtr->defaultFieldOfView) );
1240
1241       		    	Tcl_ListObjAppendElement( interp, *resObj,
1242                               Tcl_NewStringObj("-majorversion", -1) );
1243       		    	Tcl_ListObjAppendElement( interp, *resObj,
1244                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->majorVersion)) );
1245       		    	Tcl_ListObjAppendElement( interp, *resObj,
1246                               Tcl_NewStringObj("-minorversion", -1) );
1247       		    	Tcl_ListObjAppendElement( interp, *resObj,
1248                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->minorVersion)) );
1249       		    	Tcl_ListObjAppendElement( interp, *resObj,
1250                               Tcl_NewStringObj("-imagereftrackindex", -1) );
1251       		    	Tcl_ListObjAppendElement( interp, *resObj,
1252                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->imageRefTrackIndex)) );
1253       		    	Tcl_ListObjAppendElement( interp, *resObj,
1254                               Tcl_NewStringObj("-hotspotreftrackindex", -1) );
1255       		    	Tcl_ListObjAppendElement( interp, *resObj,
1256                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->hotSpotRefTrackIndex)) );
1257       		    	Tcl_ListObjAppendElement( interp, *resObj,
1258                               Tcl_NewStringObj("-minpan", -1) );
1259       		    	Tcl_ListObjAppendElement( interp, *resObj,
1260                               Tcl_NewDoubleObj(samplePtr->minPan) );
1261       		    	Tcl_ListObjAppendElement( interp, *resObj,
1262                               Tcl_NewStringObj("-maxpan", -1) );
1263       		    	Tcl_ListObjAppendElement( interp, *resObj,
1264                               Tcl_NewDoubleObj(samplePtr->maxPan) );
1265       		    	Tcl_ListObjAppendElement( interp, *resObj,
1266                               Tcl_NewStringObj("-mintilt", -1) );
1267       		    	Tcl_ListObjAppendElement( interp, *resObj,
1268                               Tcl_NewDoubleObj(samplePtr->minTilt) );
1269       		    	Tcl_ListObjAppendElement( interp, *resObj,
1270                               Tcl_NewStringObj("-maxtilt", -1) );
1271       		    	Tcl_ListObjAppendElement( interp, *resObj,
1272                               Tcl_NewDoubleObj(samplePtr->maxTilt) );
1273       		    	Tcl_ListObjAppendElement( interp, *resObj,
1274                               Tcl_NewStringObj("-minfov", -1) );
1275       		    	Tcl_ListObjAppendElement( interp, *resObj,
1276                               Tcl_NewDoubleObj(samplePtr->minFieldOfView) );
1277       		    	Tcl_ListObjAppendElement( interp, *resObj,
1278                               Tcl_NewStringObj("-maxfov", -1) );
1279       		    	Tcl_ListObjAppendElement( interp, *resObj,
1280                               Tcl_NewDoubleObj(samplePtr->maxFieldOfView) );
1281       		    	Tcl_ListObjAppendElement( interp, *resObj,
1282                               Tcl_NewStringObj("-defaultpan", -1) );
1283       		    	Tcl_ListObjAppendElement( interp, *resObj,
1284                               Tcl_NewDoubleObj(samplePtr->defaultPan) );
1285       		    	Tcl_ListObjAppendElement( interp, *resObj,
1286                               Tcl_NewStringObj("-defaulttilt", -1) );
1287       		    	Tcl_ListObjAppendElement( interp, *resObj,
1288                               Tcl_NewDoubleObj(samplePtr->defaultTilt) );
1289       		    	Tcl_ListObjAppendElement( interp, *resObj,
1290                               Tcl_NewStringObj("-defaultfov", -1) );
1291       		    	Tcl_ListObjAppendElement( interp, *resObj,
1292                               Tcl_NewDoubleObj(samplePtr->defaultFieldOfView) );
1293       		    	Tcl_ListObjAppendElement( interp, *resObj,
1294                               Tcl_NewStringObj("-imagesizex", -1) );
1295       		    	Tcl_ListObjAppendElement( interp, *resObj,
1296                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->imageSizeX)) );
1297       		    	Tcl_ListObjAppendElement( interp, *resObj,
1298                               Tcl_NewStringObj("-imagesizey", -1) );
1299       		    	Tcl_ListObjAppendElement( interp, *resObj,
1300                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->imageSizeY)) );
1301       		    	Tcl_ListObjAppendElement( interp, *resObj,
1302                               Tcl_NewStringObj("-imagenumframesx", -1) );
1303       		    	Tcl_ListObjAppendElement( interp, *resObj,
1304                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->imageNumFramesX)) );
1305       		    	Tcl_ListObjAppendElement( interp, *resObj,
1306                               Tcl_NewStringObj("-imagenumframesy", -1) );
1307       		    	Tcl_ListObjAppendElement( interp, *resObj,
1308                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->imageNumFramesY)) );
1309       		    	Tcl_ListObjAppendElement( interp, *resObj,
1310                               Tcl_NewStringObj("-hotspotsizex", -1) );
1311       		    	Tcl_ListObjAppendElement( interp, *resObj,
1312                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->hotSpotSizeX)) );
1313       		    	Tcl_ListObjAppendElement( interp, *resObj,
1314                               Tcl_NewStringObj("-hotspotsizey", -1) );
1315       		    	Tcl_ListObjAppendElement( interp, *resObj,
1316                               Tcl_NewIntObj(EndianU32_BtoN(samplePtr->hotSpotSizeY)) );
1317       		    	Tcl_ListObjAppendElement( interp, *resObj,
1318                               Tcl_NewStringObj("-hotspotnumframesx", -1) );
1319       		    	Tcl_ListObjAppendElement( interp, *resObj,
1320                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->hotSpotNumFramesX)) );
1321       		    	Tcl_ListObjAppendElement( interp, *resObj,
1322                               Tcl_NewStringObj("-hotspotnumframesy", -1) );
1323       		    	Tcl_ListObjAppendElement( interp, *resObj,
1324                               Tcl_NewIntObj(EndianU16_BtoN(samplePtr->hotSpotNumFramesY)) );
1325                }
1326            }
1327            DisposeHandle( dataOut );
1328            DisposeHandle( (Handle) descHandle );
1329        }
1330    }
1331
1332    /*
1333     * Investigate what's in the node information atom container.
1334     */
1335
1336    err = QTVRGetNodeInfo( qtvrInst, nodeID, &nodeInfoContainer );
1337    if (err != noErr) {
1338        CheckAndSetErrorResult( interp, err );
1339        return TCL_ERROR;
1340    }
1341    headerAtom = QTFindChildByID( nodeInfoContainer, kParentAtomIsContainer,
1342            kQTVRNodeHeaderAtomType, 1, NULL );
1343    if (headerAtom == 0) {
1344        CheckAndSetErrorResult( interp, err );
1345        return TCL_ERROR;
1346    }
1347    QTLockContainer( nodeInfoContainer );
1348    err = QTGetAtomDataPtr( nodeInfoContainer, headerAtom, NULL, (Ptr *) &nodeHeaderPtr );
1349
1350    /*
1351     * Find any name atom.
1352     */
1353
1354    if ((err == noErr) && (EndianS32_BtoN(nodeHeaderPtr->nameAtomID) != 0)) {
1355        nameAtom = QTFindChildByID( nodeInfoContainer, kParentAtomIsContainer,
1356                kQTVRStringAtomType, EndianS32_BtoN(nodeHeaderPtr->nameAtomID), NULL );
1357        if (nameAtom != 0) {
1358            err = QTGetAtomDataPtr( nodeInfoContainer, nameAtom, NULL,
1359                    (Ptr *) &stringAtomPtr );
1360            if (err == noErr) {
1361                len = EndianU16_BtoN(stringAtomPtr->stringLength);
1362				len = (len > 250) ? 250 : len;
1363				memset( tmpstr, 0, 255 );
1364				memcpy( tmpstr, &stringAtomPtr->theString, len );
1365    		    Tcl_ListObjAppendElement( interp, listObjPtr,
1366                        Tcl_NewStringObj("-name", -1) );
1367                Tcl_ExternalToUtfDString( gQTTclTranslationEncoding, tmpstr, -1, &ds );
1368		    	Tcl_ListObjAppendElement( interp, listObjPtr,
1369                        Tcl_NewStringObj(Tcl_DStringValue(&ds), -1) );
1370                Tcl_DStringFree(&ds);
1371            }
1372        }
1373    }
1374
1375    /*
1376     * Get hotspot parent atom which is the parent of all hot spot atoms of the node.
1377     */
1378
1379    hotspotParentAtom = QTFindChildByID( nodeInfoContainer, kParentAtomIsContainer,
1380            kQTVRHotSpotParentAtomType, 1, NULL );
1381    if (hotspotParentAtom != 0) {
1382        short                   numHotspots = 0;
1383        QTVRHotSpotInfoAtomPtr  hotspotAtomPtr;
1384
1385        numHotspots = QTCountChildrenOfType( nodeInfoContainer, hotspotParentAtom,
1386                kQTVRHotSpotAtomType );
1387    	Tcl_ListObjAppendElement( interp, *resObj,
1388                Tcl_NewStringObj("-hotspots", -1) );
1389
1390        /*
1391         * Loop over all hotspots.
1392         */
1393
1394        for (index = 1; index <= numHotspots; index++) {
1395            hotspotAtom = 0;
1396            hotspotAtom = QTFindChildByIndex( nodeInfoContainer, hotspotParentAtom,
1397                    kQTVRHotSpotAtomType, index, &atomHotSpotID );
1398            if (hotspotAtom != 0) {
1399    		    hotspotObjPtr = Tcl_NewListObj( 0, (Tcl_Obj **) NULL );
1400   		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1401                           Tcl_NewStringObj("-hotspotid", -1) );
1402   		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1403                           Tcl_NewIntObj(atomHotSpotID) );
1404
1405                /*
1406                 * Get the hotspot info atom here.
1407                 */
1408
1409                hotspotInfoAtom = 0;
1410                hotspotInfoAtom = QTFindChildByIndex( nodeInfoContainer, hotspotAtom,
1411                        kQTVRHotSpotInfoAtomType, 1, &atomID );
1412                if (hotspotInfoAtom != 0) {
1413                    err = QTGetAtomDataPtr( nodeInfoContainer, hotspotInfoAtom, NULL,
1414                            (Ptr *) &hotspotAtomPtr );
1415                    if (err == noErr) {
1416                        if (EndianS32_BtoN(hotspotAtomPtr->nameAtomID) != 0) {
1417
1418                            nameAtom = QTFindChildByID( nodeInfoContainer, hotspotAtom,
1419                                    kQTVRStringAtomType,
1420									EndianS32_BtoN(hotspotAtomPtr->nameAtomID), NULL );
1421                            if (nameAtom != 0) {
1422                                err = QTGetAtomDataPtr( nodeInfoContainer, nameAtom, NULL,
1423                                        (Ptr *) &stringAtomPtr );
1424                                if (err == noErr) {
1425					                len = EndianU16_BtoN(stringAtomPtr->stringLength);
1426									len = (len > 250) ? 250 : len;
1427                    				memset( tmpstr, 0, 255 );
1428                    				memcpy( tmpstr, &stringAtomPtr->theString, len );
1429                        		    Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1430                                            Tcl_NewStringObj("-name", -1) );
1431            	                    Tcl_ExternalToUtfDString( gQTTclTranslationEncoding, tmpstr, -1, &ds );
1432                    		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1433                                            Tcl_NewStringObj(Tcl_DStringValue(&ds), -1) );
1434                            	    Tcl_DStringFree(&ds);
1435                                }
1436                            }
1437                        }
1438                        if (EndianS32_BtoN(hotspotAtomPtr->commentAtomID) != 0) {
1439
1440                            nameAtom = QTFindChildByID( nodeInfoContainer, hotspotAtom,
1441                                    kQTVRStringAtomType,
1442									EndianS32_BtoN(hotspotAtomPtr->commentAtomID), NULL );
1443                            if (nameAtom != 0) {
1444                                err = QTGetAtomDataPtr( nodeInfoContainer, nameAtom, NULL,
1445                                        (Ptr *) &stringAtomPtr );
1446                                if (err == noErr) {
1447					                len = EndianU16_BtoN(stringAtomPtr->stringLength);
1448									len = (len > 250) ? 250 : len;
1449                    				memset( tmpstr, 0, 255 );
1450                    				memcpy( tmpstr, &stringAtomPtr->theString, len );
1451                        		    Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1452                                            Tcl_NewStringObj("-comment", -1) );
1453            	                    Tcl_ExternalToUtfDString( gQTTclTranslationEncoding, tmpstr, -1, &ds );
1454                    		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1455                                            Tcl_NewStringObj(Tcl_DStringValue(&ds), -1) );
1456                            	    Tcl_DStringFree(&ds);
1457                                }
1458                            }
1459                        }
1460						ConvertBigEndianFloatToNative( &(hotspotAtomPtr->bestPan) );
1461						ConvertBigEndianFloatToNative( &(hotspotAtomPtr->bestTilt) );
1462						ConvertBigEndianFloatToNative( &(hotspotAtomPtr->bestFOV) );
1463
1464        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1465                                Tcl_NewStringObj("-hotspottype", -1) );
1466                        if (EndianU32_BtoN(hotspotAtomPtr->hotSpotType) == kQTVRHotSpotLinkType) {
1467            		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1468                                    Tcl_NewStringObj("link", -1) );
1469                        } else if (EndianU32_BtoN(hotspotAtomPtr->hotSpotType) == kQTVRHotSpotURLType) {
1470            		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1471                                    Tcl_NewStringObj("url", -1) );
1472                        } else if (EndianU32_BtoN(hotspotAtomPtr->hotSpotType) == kQTVRHotSpotUndefinedType) {
1473            		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1474                                    Tcl_NewStringObj("undefined", -1) );
1475                        } else {
1476            		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1477                                    Tcl_NewStringObj("undefined", -1) );
1478                        }
1479        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1480                                Tcl_NewStringObj("-bestpan", -1) );
1481        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1482                                Tcl_NewDoubleObj(hotspotAtomPtr->bestPan) );
1483        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1484                                Tcl_NewStringObj("-besttilt", -1) );
1485        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1486                                Tcl_NewDoubleObj(hotspotAtomPtr->bestTilt) );
1487        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1488                                Tcl_NewStringObj("-bestfov", -1) );
1489        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1490                                Tcl_NewDoubleObj(hotspotAtomPtr->bestFOV) );
1491        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr,
1492                                Tcl_NewStringObj("-bounds", -1) );
1493            		    rectListObjPtr = Tcl_NewListObj( 0, (Tcl_Obj **) NULL );
1494        		    	Tcl_ListObjAppendElement( interp, rectListObjPtr,
1495                                Tcl_NewIntObj(EndianS16_BtoN(hotspotAtomPtr->hotSpotRect.left)) );
1496        		    	Tcl_ListObjAppendElement( interp, rectListObjPtr,
1497                                Tcl_NewIntObj(EndianS16_BtoN(hotspotAtomPtr->hotSpotRect.top )) );
1498        		    	Tcl_ListObjAppendElement( interp, rectListObjPtr,
1499                                Tcl_NewIntObj(EndianS16_BtoN(hotspotAtomPtr->hotSpotRect.right)) );
1500        		    	Tcl_ListObjAppendElement( interp, rectListObjPtr,
1501                                Tcl_NewIntObj(EndianS16_BtoN(hotspotAtomPtr->hotSpotRect.bottom)) );
1502
1503        		    	Tcl_ListObjAppendElement( interp, hotspotObjPtr, rectListObjPtr );
1504                    }
1505    		    	Tcl_ListObjAppendElement( interp, listObjPtr, hotspotObjPtr );
1506                }
1507            }
1508        }
1509    }
1510    if (nodeInfoContainer) {
1511        QTUnlockContainer( nodeInfoContainer );
1512        QTDisposeAtomContainer( nodeInfoContainer );
1513    }
1514	Tcl_ListObjAppendElement( interp, *resObj, listObjPtr );
1515
1516	return TCL_OK;
1517}
1518
1519/*
1520 *----------------------------------------------------------------------
1521 *
1522 * GetNodeCount, SetPanoramaByDegrees, ZoomInOrOutPanorama --
1523 *
1524 *  	A collection of utilities for pano movies.
1525 *
1526 * Results:
1527 *  	Tcl result, Boolean, None.
1528 *
1529 * Side effects:
1530 *		None.
1531 *
1532 *----------------------------------------------------------------------
1533 */
1534
1535int
1536GetNodeCount( QTVRInstance qtvrInst )
1537{
1538    QTAtomContainer     vrWorld = NULL;
1539    QTAtom              nodeParentAtom;
1540    int                 numNodes = 0;
1541    OSErr               err = noErr;
1542
1543    err = QTVRGetVRWorld( qtvrInst, &vrWorld );
1544    if (err != noErr) {
1545        return numNodes;
1546    }
1547    nodeParentAtom = QTFindChildByIndex( vrWorld, kParentAtomIsContainer,
1548            kQTVRNodeParentAtomType, 1, NULL );
1549    if (nodeParentAtom != 0) {
1550        numNodes = QTCountChildrenOfType( vrWorld, nodeParentAtom, kQTVRNodeIDAtomType );
1551    }
1552    QTDisposeAtomContainer( vrWorld );
1553    return numNodes;
1554}
1555
1556Boolean
1557SetPanoramaByDegrees( QTVRInstance qtvrInst, long direction, float angle )
1558{
1559    Boolean     moved;
1560
1561    switch (direction) {
1562        case kDirUp:
1563            QTVRSetTiltAngle( qtvrInst, angle );
1564            break;
1565        case kDirDown:
1566            QTVRSetTiltAngle( qtvrInst, -angle );
1567            break;
1568        case kDirLeft:
1569            QTVRSetPanAngle( qtvrInst, angle );
1570            break;
1571        case kDirRight:
1572            QTVRSetPanAngle( qtvrInst, -angle );
1573            break;
1574        default:
1575            break;
1576    }
1577
1578    /* Update the image on the screen. */
1579    QTVRUpdate( qtvrInst, kQTVRStatic );
1580
1581    switch (direction) {
1582        case kDirUp:
1583        case kDirDown:
1584            moved = (angle != QTVRGetTiltAngle( qtvrInst ));
1585            break;
1586        case kDirLeft:
1587        case kDirRight:
1588            moved = (angle != QTVRGetPanAngle( qtvrInst ));
1589            break;
1590        default:
1591            break;
1592    }
1593    return moved;
1594}
1595
1596void
1597ZoomInOrOutPanorama( QTVRInstance qtvrInst, long direction, float fov )
1598{
1599    float   oldFov;
1600
1601    // we just set it directly, later could use a factor instead.
1602    oldFov = QTVRGetFieldOfView( qtvrInst );
1603    switch (direction) {
1604        case kDirIn:
1605
1606            break;
1607        case kDirOut:
1608
1609            break;
1610        default:
1611            break;
1612    }
1613    QTVRSetFieldOfView( qtvrInst, fov );
1614    QTVRUpdate( qtvrInst, kQTVRStatic );
1615}
1616
1617/*---------------------------------------------------------------------------*/