Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
VRMakePano.c
/* |
File: VRMakePano.c |
Contains: Code for creating a QuickTime VR panoramic movie from a panoramic image. |
Written by: Ken Turkowski |
Major overhaul in the API by Ken Turkowski. |
Directly derived from VRMakePano.c by Tim Monroe. |
Based largely on MakeQTVRPanorama code by Ed Harp (and others?). |
Cubic projection code based on SampleMakeCubic.cp code by Ken Doyle. |
Warranty Information: |
Even though Apple has reviewed this software, Apple makes no warranty |
or representation, either express or implied, with respect to this |
software, its quality, accuracy, merchantability, or fitness for a |
particular purpose. As a result, this software is provided "as is," |
and you, its user, are assuming the entire risk as to its quality |
and accuracy. |
Copyright: © 1996-2001 by Apple Computer, Inc., all rights reserved. |
This code may be used and freely distributed as long as it includes |
this copyright notice and the above warranty information. |
Change History (most recent first): |
$Log: VRMakePano.c,v $ |
Revision 1.15 2001/10/18 18:21:47 turk |
We needed to do endian flipping in the fast start reference atom. |
Revision 1.14 2001/06/25 20:52:45 turk |
roundtol() for non-mac |
Revision 1.13 2001/05/15 02:41:25 turk |
update tile windows |
Revision 1.12 2001/05/10 09:12:40 turk |
fix tile display for Carbon |
Revision 1.11 2001/04/23 19:00:11 turk |
Enable setting of preview compression parameters. |
Revision 1.10 2001/04/02 22:55:14 turk |
Added warranty and copyright information. |
Revision 1.9 2001/03/08 00:54:06 turk |
Close the QTVR Flattener component after using it. |
Revision 1.8 2001/03/05 01:57:58 turk |
Make sure the time interval of all tracks is identical. |
Revision 1.7 2001/02/08 03:32:14 turk |
Make it more robust in looking FSSpecHandles; this fixes the case where the handle is not |
NULL, but has zero length. |
Revision 1.6 2001/02/03 01:39:58 turk |
Write the hot spot atoms; feed extra information to the flattener. |
Revision 1.5 2000/12/22 00:23:22 turk |
Call an FSSpecHandle an FSSpecHandle, not a FSSpec**, for they differ in their access: |
sh[0][n] vs. sh[n][0]. |
Revision 1.4 2000/11/23 02:46:10 turk |
Reverse coordinate system for tile Y coordinate. Merge Tim Monroe's changes for Windows |
and Carbon. |
Revision 1.3 2000/11/20 21:35:08 turk |
document changes in 1.2 |
Revision 1.2 2000/11/20 21:33:35 turk |
Get QTVR flattener working by setting flags. |
Revision 1.1 2000/11/15 23:11:39 turk |
Major rework of the API. Brought all parameters directly up to the API, rather than being |
assigned default values. Factorized procedures to avoid duplication of code. Provide |
parameter checking, and defaults are assigned in many cases if the parameter is zero. |
Added API's for creating both cylindrical and cubic panoramas from GWorlds. Tile picts |
into a single pre-flattened movie, rather than first into tile movies. Use QTVR flattener |
if it exists (although I haven't been able to access it yet). |
<15> 06/14/00 rtm modified ImportVideoTrack as per Ken Doyle's changes in MakeCubicPPC |
<14> 03/23/00 rtm added code to handle new cubic file format |
<13> 03/21/00 rtm made changes to get things running under CarbonLib |
<12> 11/24/99 rtm reworked file opening and saving to avoid using Standard File Package |
on Macintosh (for Carbonization effort) |
<11> 02/01/99 rtm reworked prompt and filename handling to remove "\p" sequences |
<10> 10/19/98 rtm added check in CreatePanoTrack to make sure we were passed a |
valid hot spot file spec (otherwise no panorama is created!); added |
the default movie progress procedure |
<9> 09/30/98 rtm tweaked call to AddMovieResource to create single-fork movies; |
tweaked call to FlattenMovieData to enable FastStart option |
<8> 08/06/98 rtm added VRPano_MakeHotSpotVers2x0 (based loosely on code from John Mott) |
to support version 2.0 hot spot atoms; sketched out preliminary |
support for 1.0 hot spots, but that's left as an exercise for the reader |
<7> 08/05/98 rtm added support for hot spots image tracks (both version 1.0 and 2.0) |
<6> 08/26/98 rtm added support for pan/tilt/field-of-view constraint atoms |
<5> 08/18/98 rtm added support for version 1.0 panoramic movies |
<4> 08/16/98 rtm final clean-up before initial release |
<3> 08/15/98 rtm reworked the image file handling using QuickTime graphics importers; |
added tile-display window for debugging purposes; |
now we run fine under both MacOS and Windows. Ta da! |
<2> 01/14/98 rtm further work; added Endian* macros; got working on MacOS |
<1> 01/12/98 rtm first file from CMovieMaker.cp in MakeQTVRPanorama 2.0b |
*/ |
/******************************************************************************** |
* This file contains functions that convert a panoramic image into a QuickTime VR movie. The image |
* can be a picture (of type 'PICT') or any other kind of image for which QuickTime has a graphics |
* importer component. Here, we can create both version 1.0 and version 2.0 QTVR panoramic movies. |
* We can also create hot spot image tracks and (for version 2.0 only) assemble the various hot spot |
* atoms. |
* |
* This file also contains a function that constructs a QuickTime VR movie that uses the new cubic |
* projection. In the following notes, we'll call such movies "cubic movies" or "cubic panoramas". |
* |
******************************************************************************** |
* |
* VERSION 2.0 FILE FORMAT |
* |
* The definitive source of information about creating QTVR 2.0 panoramic movies is Chapter 3 of the |
* book "Virtual Reality Programming With QuickTime VR 2.0". (This information is also available |
* online, at <http://dev.info.apple.com/dev/techsupport/insidemac/qtvr/qtvrapi-2.html>.) Here is |
* a condensed version of the info in that chapter, as pertains to panoramas: |
* |
* A panoramic movie is a QuickTime movie that contains at least three tracks: a QTVR track, a panorama |
* track, and a panorama image track. In addition, a QuickTime VR movie must contain some special user data |
* that specifies the QuickTime VR movie controller. A QuickTime VR movie can also contain other kinds of |
* tracks, such as hot spot image tracks and even sound tracks. |
* |
* A QuickTime VR movie contains a single "QTVR track", which maintains a list of the nodes in the movie. |
* Each individual sample in the QTVR track's media contains information about a single node, such as the |
* node's type, ID, and name. Since we are creating a single-node movie here, our QTVR track will contain |
* a single media sample. |
* |
* Every media sample in a QTVR track has the same sample description, whose type is QTVRSampleDescription. |
* The data field of that sample description is a "VR world", an atom container whose child atoms specify |
* information about the nodes in the movie, such as the default node ID and the default imaging properties. |
* We'll spend a good bit of time putting things into the VR world. |
* |
* A panoramic movie also contains a single "panorama track", which contains information specific to the |
* panorama. A panorama track has a media sample for each media sample in the QTVR track. As a result, |
* our panorama track will have one sample. The QTVRPanoSampleAtom structure defines the media sample data. |
* |
* The actual image data for a panoramic node is contained in a "panorama image track". The individual |
* frames in that track are the diced (and also perhaps compressed) tiles of the original panoramic image. |
* There may also be a "hot spot image track" that contains the diced (and also perhaps compressed) tiles |
* of the hot spot panoramic image. |
* |
* So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed): |
* |
* (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic |
* image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call |
* this movie the "hot spot tile movie". |
* (2) Create a new, empty movie. Call this movie the "QTVR movie". |
* (3) Create a QTVR track and its associated media. |
* (4) Create a VR world atom container; this is stored in the sample description for the QTVR track. |
* (5) Create a node information atom container for each node; this is stored as a media sample |
* in the QTVR track. |
* (6) Create a panorama track and add it to the movie. |
* (7) Create a panorama image track by copying the video track from the tile movie to the QTVR movie. |
* (8) Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie. |
* (9) Set up track references from the QTVR track to the panorama track, and from the panorama track |
* to the panorama image track and the hot spot image track. |
* (A) Add a user data item that identifies the QTVR movie controller. |
* (B) Flatten the QTVR movie into the final panoramic movie. |
* |
******************************************************************************** |
* |
* VERSION 1.0 FILE FORMAT |
* |
* The definitive source of information about creating QTVR 1.0 panoramic movies is Technote 1035, |
* "QuickTime VR 1.0 Panorama Movie File Format" released in March 1996, available online at the address |
* <http://devworld.apple.com/dev/technotes/tn/tn1035.html>. Here is a condensed version of the info |
* in that technote: |
* |
* For version 1.0 panoramic movies, the file format is somewhat simpler. A single-node panoramic movie |
* contains a "scene track" and a "panorama track". The scene track is an inactive video track that contains |
* the diced (and also perhaps compressed) tiles of the original panoramic image. A version 1.0 scene track |
* is essentially a version 2.0 panorama image track. |
* |
* A panoramic movie also contains a single "panorama track", which contains information specific to the |
* panorama. The panorama track in version 1.0 differs significantly from the panorama track in version 2.0, |
* so don't let the names confuse you. |
* |
* A "panorama track" in version 1.0 contains one media sample for each node in the movie. Every media sample |
* in a panorama track has the same sample description, whose type is PanoramaDescription. This structure |
* contains information about how the panoramic image was diced, along with information about any hot spot |
* and low-resolution scene tracks that might be contained in the movie file. |
* |
* Each individual sample in the panorama track's media contains information about a single node, |
* such as the node's default view angles, pan and zoom constraints, and hot spot information. |
* Since we are creating a single-node movie here, our panorama track will contain a single media sample. |
* The data in a panorama track sample is organized as a sequence of "atom data structures"; each such |
* structure begins with size and type fields, so you can easily read thru the atom data structures by |
* starting at the beginning of the sample data and hopping over each structure. The atoms can appear in |
* any order in the panorama track sample data. |
* |
* -> IMPORTANT: The "atom data structures" that comprise a panorama track sample are *not* QTAtoms, and the |
* -> panorama track sample itself is *not* a QTAtomContainer. The version 1.0 panorama file format predates |
* -> QuickTime 2.1, which introduced the atom data routines. As a result, you cannot use the atom data routines |
* -> to read or write version 1.0 panorama track samples. |
* |
* A panorama track sample must contain a "panorama header atom", whose structure is PanoSampleHeaderAtom. |
* The sample may contain other atoms as well, such as a string table atom (which contains strings, such as |
* node names) and a hot spot table atom (which lists all of the hot spots in a node). In this sample code, |
* we add a hot spot image track to the VR movie, but we do not (yet) build a hot spot atom table; as a result, |
* our panorama track sample will need to contain only a panorama header atom. |
* |
* So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed): |
* |
* (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic |
* image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call |
* this movie the "hot spot tile movie". |
* (2) Create a new, empty movie. Call this movie the "QTVR movie". |
* (3) Create a scene track by copying the video track from the tile movie to the QTVR movie. |
* (4) Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie. |
* (5) Create a panorama track and add it to the movie. |
* (6) Add a user data item that identifies the QTVR movie controller. |
* (7) Flatten the QTVR movie into the final panoramic movie. |
* |
******************************************************************************** |
* |
* CUBIC PANORAMA FILE FORMAT |
* |
* QuickTime VR version 5.0 introduces "cubic panoramas", which store the panoramic image as 6 (or more) separate images that |
* are (during playback) projected onto the sides of a cube (polyhedron). This allows the user to look straight up and straight |
* down. The file format for cubic panoramas is identical with the Version 2.0 cylindrical file format, with these |
* exceptions: |
* |
* The image track (and any associated hot spot and preview tracks) must contain 6 samples/frames/faces |
* (these can be subtiled, though tile based memory management may not be available initially). The first |
* sample is the front cube face, the second is the right-hand cube face, and so on; sample 5 is the top of the cube |
* and sample 6 is the bottom of the cube. You can change this default orientation with the 'cufa' descriptor. |
* The frames are oriented horizontally (except for the top and bottom), not rotated as with cylindrical panoramas. |
* |
* For cubic panoramas, some of the fields in the panorama sample atom should be assigned special values that allow |
* the file to be displayed with the cylindrical engine if the cubic engine is not available. The cubic engine ignores |
* those values, instead using values stored in the new "cubic view atom". See the function CreatePanoTrack for |
* the special values you should use. |
* |
* |
* NOTES: |
* |
* *** (1) *** |
* This code is based largely on the existing MakeQTVRPanorama sample code written by Ed Harp (and others?). |
* MakeQTVRPanorama is a full-featured application written in C++ using Metrowerks' PowerPlant, a Mac-only |
* application framework. Here I've uncoupled the central portion of that code, contained in the file CMovieMaker.cp, |
* and converted it into straight C. I have taken the liberty of reworking that code as necessary to make it run |
* also on Windows platforms and to bring it into line with the other QuickTime code samples. So far the biggest |
* changes involve using graphics importers instead of the original PICT-reading code and inserting all those |
* Endian macros. |
* |
* *** (2) *** |
* All data in QTAtom structures must be in big-endian format. We use macros like EndianU32_NtoB to convert |
* values into the proper format before inserting them into atoms. See CreateVRWorld for some examples. |
* |
********************************************************************************/ |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** compiler flags |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
/* The following flag determines whether we show the uncompressed tiles in a window as they get processed; |
* I did this mainly for debugging purposes, but it's also a useful progress indicator.... |
*/ |
#define USE_TILE_DISPLAY_WINDOW 1 |
#define USE_TEMP_MEM_FIRST |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** header files |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
#include <Endian.h> |
#include <QuickTimeVR.h> |
#include <QuickTimeVRFormat.h> |
#include <QuickTimeComponents.h> |
#include <FixMath.h> |
#include <Script.h> |
#include <Sound.h> |
#include <stdio.h> |
#include <string.h> |
#if TARGET_OS_MAC |
# include <fp.h> |
#else /* !TARGET_OS_MAC */ |
# include <math.h> |
#endif /* !TARGET_OS_MAC */ |
#include "VRMakePano.h" |
#pragma mark #Constants |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** Constants |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
enum { |
QTVRFlattenerType = FOUR_CHAR_CODE('vrwe') /* An oversight prevented this from going into QuickTimeVRFormat.h */ |
}; |
#define D_PI 3.1415926535898 |
#define D_2PI 6.2831853071796 |
#define D_PI_2 1.5707963267949 |
#define D_DEGREES_PER_RADIAN 57.295779513082 |
#define D_RADIANS_PER_DEGREE 0.017453292519943 |
#define kQTVRStandardTimeScale 3600 /* time scale for QTVR panoramas */ |
#define kDefaultNodeID 1 /* default node ID */ |
#define kQTVRCylindricalVersion1 (long)1 |
#define kQTVRCylindricalVersion2v (long)2 |
#define kQTVRCylindricalVersion2h (long)3 |
#define kQTVRCubicVersion1 (long)4 |
#define kPanDescType FOUR_CHAR_CODE('pano') |
#define kPanHeaderType FOUR_CHAR_CODE('pHdr') |
#define kStringTableType FOUR_CHAR_CODE('strT') |
#define kHotSpotTableType FOUR_CHAR_CODE('pHot') |
#define kQTVRPanoTypeCube FOUR_CHAR_CODE('cube') |
#define kQTVRCubicViewAtomType FOUR_CHAR_CODE('cuvw') |
#define kQTVRCubicFaceDataAtomType FOUR_CHAR_CODE('cufa') |
#define kCreateMovieFlags (createMovieFileDeleteCurFile | createMovieFileDontCreateResFile) |
#pragma mark #Data Structures |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** Data Structures |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
/* version 1.0 data types */ |
#pragma options align=mac68k |
/* panorama description: |
* this defines the structure of a sample description for a media sample in a panorama track |
*/ |
typedef struct PanoramaDescription { |
long size; /* total size of PanoramaDescription */ |
long type; /* must be 'pano' */ |
long reserved1; /* must be zero */ |
long reserved2; /* must be zero */ |
short majorVersion; /* must be zero */ |
short minorVersion; /* must be zero */ |
long sceneTrackID; /* ID of video track that contains panoramic scene */ |
long loResSceneTrackID; /* ID of video track that contains lo res panoramic scene */ |
long reserved3[6]; /* must be zero */ |
long hotSpotTrackID; /* ID of video track that contains hotspot image */ |
long loResHotSpotTrackID; /* ID of video track that contains lo res hotspot image */ |
long reserved4[8]; /* must be zero */ |
Fixed hPanStart; /* horizontal pan range start angle (eg. 0) */ |
Fixed hPanEnd; /* horizontal pan range end angle (eg. 360) */ |
Fixed vPanTop; /* vertical pan range top angle(eg. 42.5) */ |
Fixed vPanBottom; /* vertical pan range bottom angle (eg. -42.5) */ |
Fixed minimumZoom; /* minimum zoom angle (eg. 5; use 0 for default)) */ |
Fixed maximumZoom; /* maximum zoom angle (eg. 65; use 0 for default) */ |
/* info for highest res version of scene track */ |
long sceneSizeX; /* pixel width of the panorama (eg. 768) */ |
long sceneSizeY; /* pixel height of the panorama (eg. 2496) */ |
long numFrames; /* number of diced frames (eg. 24) */ |
short reserved5; /* must be zero */ |
short sceneNumFramesX; /* diced frames wide (eg. 1) */ |
short sceneNumFramesY; /* diced frames high (eg. 24) */ |
short sceneColorDepth; /* bit depth of the scene track (eg. 32) */ |
/* info for highest res version of hotSpot track */ |
long hotSpotSizeX; /* pixel width of the hot spot panorama (eg. 768) */ |
long hotSpotSizeY; /* pixel height of the hot spot panorama (eg. 2496) */ |
short reserved6; /* must be zero */ |
short hotSpotNumFramesX; /* diced frames wide (eg. 1) */ |
short hotSpotNumFramesY; /* diced frames high (eg. 24) */ |
short hotSpotColorDepth; /* must be 8 */ |
} PanoramaDescription, *PanoramaDescriptionPtr, **PanoramaDescriptionHandle; |
/* panorama sample header atom: |
* this defines the structure of (part of) a media sample in a panorama track |
*/ |
typedef struct PanoSampleHeaderAtom { |
long size; |
OSType type; /* must be 'pHdr' */ |
unsigned long nodeID; /* corresponds to a node ID in the idToTime table */ |
Fixed defHPan; /* default horizontal pan angle when displaying this node */ |
Fixed defVPan; /* default vertical pan angle when displaying this node */ |
Fixed defZoom; /* default zoom angle when displaying this node */ |
Fixed minHPan; /* constraints for this node; use zero for default */ |
Fixed minVPan; |
Fixed minZoom; |
Fixed maxHPan; |
Fixed maxVPan; |
Fixed maxZoom; |
long reserved1; /* must be zero */ |
long reserved2; /* must be zero */ |
long nameStrOffset; /* offset into string table atom */ |
long commentStrOffset; /* offset into string table atom */ |
} PanoSampleHeaderAtom, *PanoSampleHeaderAtomPtr, **PanoSampleHeaderAtomHandle; |
/* hot spot atom */ |
typedef struct HotSpot { |
unsigned short hotSpotID; /* the ID of this hot spot */ |
short reserved1; /* must be zero */ |
OSType type; /* the hot spot type (e.g. 'link', 'navg', etc) */ |
unsigned long typeData; /* for link and navg, the ID in the link and navg table */ |
Fixed viewHPan; /* canonical view for this hot spot */ |
Fixed viewVPan; |
Fixed viewZoom; |
Rect hotSpotRect; /* bounding rectangle of the hot spot in the panorama */ |
long mouseOverCursorID; |
long mouseDownCursorID; |
long mouseUpCursorID; |
long reserved2; /* must be 0 */ |
long nameStrOffset; /* offset into string table atom */ |
long commentStrOffset; /* offset into string table atom */ |
} HotSpot, *HotSpotPtr, **HotSpotHandle; |
/* hot spot table atom */ |
typedef struct HotSpotTableAtom { |
long size; |
OSType type; /* must be 'pHot' */ |
short pad; /* must be 0 */ |
short numHotSpots; |
HotSpot hotSpots[1]; /* list of hot spots */ |
} HotSpotTableAtom, *HotSpotTableAtomPtr, **HotSpotTableAtomHandle; |
typedef struct StringTableAtom { |
long size; |
OSType type; /* must be 'strT' */ |
char bunchOstrings[1]; /* concatenated Pascal strings */ |
} StringTableAtom, *StringTableAtomPtr, **StringTableAtomHandle; |
#pragma options align=reset |
typedef struct MyPanoTrackDescriptor { |
long versionToCreate; |
long imageTileWidth; |
long imageTileHeight; |
long imageRefTrackIndex; |
long hotSpotTileWidth; |
long hotSpotTileHeight; |
long hotSpotRefTrackIndex; |
long fastStartTileWidth; |
long fastStartTileHeight; |
long fastStartRefTrackIndex; |
} MyPanoTrackDescriptor; |
typedef struct FaceOrientation { float x, y, z, theta; } FaceOrientation; |
#pragma mark #Global Variables |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** Global Variables |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
static FaceOrientation faceOrientation[6] = { |
/* X Y Z theta */ |
0.0f, 1.0f, 0.0f, 0.0f, /* Front (no rotation) */ |
0.0f, 1.0f, 0.0f, -90.0f, /* Right (rotate 90 degrees rightward about positive Y axis) */ |
0.0f, 1.0f, 0.0f, -180.0f, /* Back (rotate 180 degrees rightward about positive Y axis) */ |
0.0f, 1.0f, 0.0f, -270.0f, /* Left (rotate 90 degrees rightward about negative Y axis) */ |
1.0f, 0.0f, 0.0f, 90.0f, /* Top (rotate 90 degrees upward about positive X axis) */ |
1.0f, 0.0f, 0.0f, -90.0f /* Bottom (rotate 90 degrees downward about positive X axis) */ |
}; |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** Support Functions |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
/******************************************************************************** |
* MyNewHandle |
********************************************************************************/ |
static Handle |
MyNewHandle(Size size) |
{ |
OSErr err; |
Handle h; |
#ifdef USE_TEMP_MEM_FIRST |
h = TempNewHandle(size, &err); |
if (err != noErr) |
h = NewHandle(size); |
#else /* USE_HEAP_MEM_FIRST */ |
h = NewHandle(size); |
if (h == NULL) |
h = TempNewHandle(size, &err); |
#endif /* USE_HEAP_MEM_FIRST */ |
return(h); |
} |
/******************************************************************************** |
* MyNewHandleClear |
********************************************************************************/ |
#if !TARGET_OS_MAC |
#include <string.h> |
#define BlockZero(p, z) memset(p, 0, z) |
#endif /* !TARGET_OS_MAC */ |
static Handle |
MyNewHandleClear(Size size) |
{ |
OSErr err; |
Handle h; |
#ifdef USE_TEMP_MEM_FIRST |
h = TempNewHandle(size, &err); |
if (err == noErr) |
BlockZero(*h, size); |
else |
h = NewHandleClear(size); |
#else /* USE_HEAP_MEM_FIRST */ |
h = NewHandleClear(size); |
if (h == NULL) { |
h = TempNewHandle(size, &err); |
if (err == noErr) |
BlockZero(*h, size); |
} |
#endif /* USE_HEAP_MEM_FIRST */ |
return(h); |
} |
/******************************************************************************* |
* MakeTempFSSpec |
* |
* Tack the given tmpSuffix onto the origSpec, resulting in the tmpSpec. |
*******************************************************************************/ |
static void |
MakeTempFSSpec(const FSSpec *origSpec, const char *tmpSuffix, FSSpec *tmpSpec) |
{ |
long sufSize, namSize; |
char *t; |
*tmpSpec = *origSpec; |
namSize = tmpSpec->name[0]; |
sufSize = strlen(tmpSuffix); |
#if TARGET_OS_MAC |
if (namSize > (32 - 1 - sufSize)) |
namSize = (32 - 1 - sufSize); |
#else /* !TARGET_OS_MAC */ |
if (namSize > (sizeof(StrFileName) - 1 - sufSize)) |
namSize = (sizeof(StrFileName) - 1 - sufSize); |
#endif /* !TARGET_OS_MAC */ |
tmpSpec->name[0] = namSize + sufSize; |
t = (char*)(&tmpSpec->name[namSize+1]); |
while (sufSize--) |
*t++ = *tmpSuffix++; |
} |
/******************************************************************************* |
* ClearFSSpec |
*******************************************************************************/ |
static void |
ClearFSSpec(FSSpec *fsSpec) |
{ |
fsSpec->vRefNum = 0; |
fsSpec->parID = 0; |
fsSpec->name[0] = 0; |
} |
/******************************************************************************** |
* HasOneFSSpec |
********************************************************************************/ |
static Boolean |
HasOneFSSpec(FSSpecHandle sh) |
{ |
return(( |
(sh != NULL) && |
(GetHandleSize((Handle)sh) >= sizeof(FSSpec)) && |
(MemError() == noErr) && |
(sh[0][0].name[0] != 0) |
) ? |
true : false |
); |
} |
/******************************************************************************** |
* ConvertFloatToBigEndian |
* EndianF32_NtoB |
* |
* Convert the specified floating-point number to big-endian format. |
********************************************************************************/ |
#if TARGET_RT_BIG_ENDIAN |
# define EndianF32_NtoB(f) ((float)(f)) |
#else /* TARGET_RT_LITTLE_ENDIAN */ |
static float |
EndianF32_NtoB(float f) |
{ |
unsigned long fl; |
*((float*)(&fl)) = f; |
fl = EndianU32_NtoB(fl); |
f = *((float*)(&fl)); |
return(f); |
} |
#endif /* TARGET_RT_LITTLE_ENDIAN */ |
#ifndef macintosh |
/******************************************************************************* |
* roundtol |
*******************************************************************************/ |
static long |
roundtol(double_t f) |
{ |
f += (f < 0) ? -0.5 : 0.5; |
return((long)f); |
} |
#endif /* macintosh */ |
/******************************************************************************* |
* EndianF32NtoFixedB |
*******************************************************************************/ |
static Fixed |
EndianF32NtoFixedB(float f) |
{ |
Fixed x; |
x = roundtol(f * 65536.0); |
x = EndianU32_NtoB(x); |
return(x); |
} |
/******************************************************************************** |
* FocalLengthToFOV |
* t = 2 atan((h-1)/(2f)) |
********************************************************************************/ |
static float |
FocalLengthToFOV(float focalLength, long height) |
{ |
return(2.0F * atan((height - 1) * 0.5F / focalLength)); |
} |
/******************************************************************************** |
* PixelZoomToFOV |
********************************************************************************/ |
static float |
PixelZoomToFOV(float srcFocalLength, long windowHeight, float pixelZoom) |
{ |
return(FocalLengthToFOV(srcFocalLength * pixelZoom, windowHeight)); |
} |
#pragma mark - |
/******************************************************************************* |
* AddCStringToAtomContainer |
* Add a C string to the specified atom container; |
* return (through theID) the ID of the new string atom. |
*******************************************************************************/ |
static OSErr |
AddCStringToAtomContainer(QTAtomContainer theContainer, QTAtom theParent, const char *theString, QTAtomID *theID) |
{ |
OSErr err = noErr; |
QTAtom myStringAtom; |
UInt16 mySize; |
QTVRStringAtomPtr myStringAtomPtr; |
unsigned short stringLength; |
*theID = 0; /* initialize the returned atom ID */ |
if ((theContainer == NULL) || (theParent == 0)) |
return(paramErr); |
stringLength = strlen(theString); |
mySize = sizeof(QTVRStringAtom) - 4 + stringLength; |
myStringAtomPtr = (QTVRStringAtomPtr)NewPtr(mySize); |
if (myStringAtomPtr != NULL) { |
myStringAtomPtr->stringUsage = EndianU16_NtoB(1); |
myStringAtomPtr->stringLength = EndianU16_NtoB(stringLength); |
BlockMoveData(theString, myStringAtomPtr->theString, stringLength); |
err = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom); |
DisposePtr((Ptr)myStringAtomPtr); |
if (err == noErr) |
QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID); |
} |
return(err); |
} |
/******************************************************************************* |
* AddStringHandleToAtomContainer |
* Add a string, contained in a Handle, to the specified atom container; |
* return (through theID) the ID of the new string atom. |
*******************************************************************************/ |
static OSErr |
AddStringHandleToAtomContainer(QTAtomContainer theContainer, QTAtom theParent, Handle theString, QTAtomID *theID) |
{ |
OSErr err = noErr; |
QTAtom myStringAtom; |
UInt16 mySize; |
QTVRStringAtomPtr myStringAtomPtr; |
unsigned short stringLength; |
*theID = 0; /* initialize the returned atom ID */ |
if ((theContainer == NULL) || (theParent == 0)) |
return(paramErr); |
stringLength = GetHandleSize(theString); |
mySize = sizeof(QTVRStringAtom) - 4 + stringLength; |
myStringAtomPtr = (QTVRStringAtomPtr)NewPtr(mySize); |
if (myStringAtomPtr != NULL) { |
myStringAtomPtr->stringUsage = EndianU16_NtoB(1); |
myStringAtomPtr->stringLength = EndianU16_NtoB(stringLength); |
BlockMoveData(*theString, myStringAtomPtr->theString, stringLength); |
err = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom); |
DisposePtr((Ptr)myStringAtomPtr); |
if (err == noErr) |
QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID); |
} |
return(err); |
} |
/******************************************************************************* |
* SetQTControllerType |
* |
* Set the controller type of the specified movie. |
* |
* This function adds an item to the movie's user data; |
* the updated user data is written to the movie file when the movie is next updated |
* (by calling AddMovieResource or UpdateMovieResource). |
* |
*******************************************************************************/ |
static OSErr |
SetQTControllerType(Movie theMovie, OSType theType) |
{ |
UserData myUserData; |
OSErr err = noErr; |
/* make sure we've got a movie */ |
if (theMovie == NULL) |
return(paramErr); |
/* get the movie's user data list */ |
myUserData = GetMovieUserData(theMovie); |
if (myUserData == NULL) |
return(paramErr); |
theType = EndianU32_NtoB(theType); |
err = SetUserDataItem(myUserData, &theType, sizeof(theType), kQTControllerType, 0); |
return(err); |
} |
/******************************************************************************* |
* MakeQTVRHotSpot1x0 |
* |
* Create a hot spot table atom with atoms for a single hot spot; also, if necessary, |
* resize the string table handle theStringTable to contain a name and comment for the |
* hot spot. |
* |
* NOTE: This function builds hot spots that conform to version 1.0 of the QuickTime VR file format. |
*******************************************************************************/ |
static HotSpotTableAtomHandle |
MakeQTVRHotSpot1x0(StringTableAtomHandle theStringTable) |
{ |
#pragma unused(theStringTable) |
HotSpotTableAtomHandle myHandle = NULL; |
/* [left as an exercise for the reader] */ |
return(myHandle); |
} |
/******************************************************************************* |
* AddQTVRHotSpot2x0 |
* |
* Create and add a version 2.0 hot spot atom to the specified node info atom. |
* |
* This atom in turn, has at least one child: a hot spot information atom, |
* which contains general info about the hot spot. |
* |
* A second child is contained when the hot spot is a URL link. |
* |
* Only two types of hot spots are accommodated by this API: |
* kQTVRHotSpotURLType |
* or |
* kQTVRHotSpotUndefinedType. |
* They are distionguished by whether the URL Handle is NULL or not. |
* |
* Note that this API does not accommodate kQTVRHotSpotLinkType hot spots. |
*******************************************************************************/ |
static OSErr |
AddQTVRHotSpot2x0( |
QTAtomContainer theNodeInfo, |
QTAtom theHSParent, |
UInt32 theIndex, |
Handle theHSName, |
Handle theURL |
) |
{ |
QTAtom myHSAtom; |
QTVRHotSpotInfoAtom myHSInfoAtom; |
QTAtomID nameID; |
OSErr err = noErr; |
/* the atom ID should be the same as the hot spot ID, which is an index in a 8-bit color table */ |
err = QTInsertChild(theNodeInfo, theHSParent, kQTVRHotSpotAtomType, theIndex, 0, 0, NULL, &myHSAtom); |
if (err != noErr) |
goto bail; |
/* |
* Set the value of the hot spot information atom |
*/ |
myHSInfoAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
myHSInfoAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
myHSInfoAtom.hotSpotType = theURL ? EndianU32_NtoB(kQTVRHotSpotURLType) |
: EndianU32_NtoB(kQTVRHotSpotUndefinedType); |
if ((err = AddStringHandleToAtomContainer(theNodeInfo, myHSAtom, theHSName, &nameID)) != noErr) |
goto bail; |
myHSInfoAtom.nameAtomID = EndianU32_NtoB(nameID); /* Add the hot spot name */ |
myHSInfoAtom.commentAtomID = EndianU32_NtoB(0L); /* Add the hot spot comment (none) */ |
myHSInfoAtom.cursorID[0] = EndianU32_NtoB(0L); /* Set the custom cursor IDs; 0 means that no custom cursors exist */ |
myHSInfoAtom.cursorID[1] = EndianU32_NtoB(0L); |
myHSInfoAtom.cursorID[2] = EndianU32_NtoB(0L); |
myHSInfoAtom.bestPan = EndianF32_NtoB(0.0); /* Set the viewing parameters to best see this hot spot */ |
myHSInfoAtom.bestTilt = EndianF32_NtoB(0.0); |
myHSInfoAtom.bestFOV = EndianF32_NtoB(0.0); |
myHSInfoAtom.bestViewCenter.x = EndianF32_NtoB(0.0); |
myHSInfoAtom.bestViewCenter.y = EndianF32_NtoB(0.0); |
myHSInfoAtom.hotSpotRect.top = EndianU16_NtoB(0); /* Set hot spot bounding rectangle; apparently unused */ |
myHSInfoAtom.hotSpotRect.left = EndianU16_NtoB(0); |
myHSInfoAtom.hotSpotRect.bottom = EndianU16_NtoB(0); |
myHSInfoAtom.hotSpotRect.right = EndianU16_NtoB(0); |
myHSInfoAtom.flags = EndianU32_NtoB(0L); |
myHSInfoAtom.reserved1 = EndianU32_NtoB(0L); |
myHSInfoAtom.reserved2 = EndianU32_NtoB(0L); |
if ((err = QTInsertChild(theNodeInfo, myHSAtom, /* Insert the hot spot information atom into the hot spot atom */ |
kQTVRHotSpotInfoAtomType, 1, 0, sizeof(myHSInfoAtom), &myHSInfoAtom, NULL)) != noErr |
) |
goto bail; |
if (theURL) { /* Add a URL hot spot atom as a child of the hot spot atom */ |
HLock(theURL); |
err = QTInsertChild(theNodeInfo, myHSAtom, kQTVRHotSpotURLType, 1, 0, GetHandleSize(theURL), *theURL, NULL); |
HUnlock(theURL); |
} |
bail: |
return(err); |
} |
/******************************************************************************** |
* AddAllHotSpots2x0 |
********************************************************************************/ |
static OSErr |
AddAllHotSpots2x0( |
QTAtomContainer nodeInfoAtom, |
VRHotSpotDefinitionHandle hotSpots |
) |
{ |
QTAtom hsParentAtom; |
long i, n; |
OSErr err = noErr; |
if (hotSpots != NULL) { |
/* Insert the hot spot parent atom into the node info atom container */ |
err = QTInsertChild(nodeInfoAtom, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, 1, 0, NULL, &hsParentAtom); |
if (err != noErr) |
goto bail; |
n = GetHandleSize((Handle)(hotSpots)) / sizeof(VRHotSpotDefinition); |
for (i = 0; i < n; i++) { |
VRHotSpotDefinition *hs = *hotSpots + i; |
if ((err = AddQTVRHotSpot2x0(nodeInfoAtom, hsParentAtom, hs->index, hs->name, hs->url)) != noErr) |
goto bail; |
} |
} |
bail: |
return(err); |
} |
/******************************************************************************* |
* CountTrackSamples |
*******************************************************************************/ |
static long |
CountTrackSamples(Track track) |
{ |
long numSamples; |
TimeValue currentTime, interestingTime; |
OSErr err; |
err = GetMoviesStickyError(); |
interestingTime = 1; |
currentTime = 0; |
numSamples = -1; /* will iterate one more time than there is slices */ |
while (interestingTime != currentTime) { |
currentTime = interestingTime; |
++numSamples; |
GetTrackNextInterestingTime(track, nextTimeMediaSample, currentTime, 1, &interestingTime, NULL); |
} |
if (err == noErr) |
ClearMoviesStickyError(); /* Because the last GetTrackNextInterestingTime() goes beyond the end */ |
return(numSamples); |
} |
/******************************************************************************* |
* ImportFirstVideoTrackFromMovie |
* |
* Copy a video track from one movie (the source) to another (the destination). |
* |
* We assume that we want the first video track in theSrcMovie. |
*******************************************************************************/ |
static OSErr |
ImportFirstVideoTrackFromMovie( |
Movie srcMovie, /* Transfer the first video track of this movie... */ |
Movie dstMovie, /* to this movie. */ |
TimeValue dstSequenceDuration, /* duration of the whole sequence */ |
long numSamples, /* the number of samples, or zero if the whole track is desired */ |
long *pDstTrackWidth, /* returned track width, in integral units */ |
long *pDstTrackHeight, /* returned track height, in integral units */ |
Track *pDstTrack /* the track added to the dstMovie */ |
) |
{ |
Track srcTrack = NULL; |
Media srcMedia = NULL; |
Track dstTrack = NULL; |
Media dstMedia = NULL; |
Fixed trackWidth, trackHeight; |
OSType mediaType; |
SampleDescriptionHandle sampleDesc = (SampleDescriptionHandle)MyNewHandle(0); |
Handle sampleData = MyNewHandle(0); |
long sampleDataSize; |
TimeValue srcMediaTime = 0; |
TimeValue srcSampleDuration, dstSampleDuration; |
ClearMoviesStickyError(); |
/* get the first video track in the source movie */ |
srcTrack = GetMovieIndTrackType(srcMovie, 1, VideoMediaType, movieTrackMediaType); |
if (srcTrack == NULL) |
return(paramErr); |
/* get the track's media and dimensions */ |
srcMedia = GetTrackMedia(srcTrack); |
GetTrackDimensions(srcTrack, &trackWidth, &trackHeight); |
/* create a destination track */ |
dstTrack = NewMovieTrack(dstMovie, trackWidth, trackHeight, GetTrackVolume(srcTrack)); |
if (pDstTrackWidth != NULL) *pDstTrackWidth = trackWidth >> 16; |
if (pDstTrackHeight != NULL) *pDstTrackHeight = trackHeight >> 16; |
if (pDstTrack != NULL) *pDstTrack = dstTrack; |
/* create a destination media */ |
GetMediaHandlerDescription(srcMedia, &mediaType, NULL, NULL); |
dstMedia = NewTrackMedia(dstTrack, mediaType, kQTVRStandardTimeScale, 0, 0); |
/* determine the number of samples */ |
if (numSamples == 0) |
numSamples = CountTrackSamples(srcTrack); |
dstSampleDuration = dstSequenceDuration / numSamples; |
/* extract samples one at a time */ |
BeginMediaEdits(dstMedia); |
while (numSamples--) { |
GetMediaSample(srcMedia, sampleData, 0, &sampleDataSize, srcMediaTime, NULL, &srcSampleDuration, sampleDesc, NULL, 1, NULL, NULL); |
srcMediaTime += srcSampleDuration; |
AddMediaSample(dstMedia, sampleData, 0, sampleDataSize, dstSampleDuration, sampleDesc, 1, 0, NULL); |
} |
EndMediaEdits(dstMedia); |
InsertMediaIntoTrack(dstTrack, 0, 0, GetMediaDuration(dstMedia), 1L << 16); |
DisposeHandle((Handle)sampleDesc); |
DisposeHandle(sampleData); |
/* a panorama image track should always be disabled */ |
SetTrackEnabled(dstTrack, false); |
return(GetMoviesStickyError()); |
} |
/******************************************************************************* |
* ImportFirstVideoTrackFromFile |
*******************************************************************************/ |
static OSErr |
ImportFirstVideoTrackFromFile( |
const FSSpec *srcMovieSpec, /* Transfer the first video track from this movie... */ |
Movie dstMovie, /* ... to this movie */ |
TimeValue dstSequenceDuration, /* Desired time duration of the sequence */ |
long numSamples, /* The number of samples, or zero if the whole track is desired */ |
long *pDstFrameWidth, /* Return the width of the track frame */ |
long *pDstFrameHeight, /* Return the height of the track frame */ |
Track *pDstTrack /* Return the new track */ |
) |
{ |
Movie srcMovie = NULL; |
short srcRefNum = 0; |
short srcResID = 0; |
OSErr err; |
err = OpenMovieFile(srcMovieSpec, &srcRefNum, fsRdPerm); |
if (err != noErr) |
goto bail; |
err = NewMovieFromFile(&srcMovie, srcRefNum, &srcResID, 0, 0, 0); |
if (err != noErr) |
goto bail; |
SetMoviePlayHints(srcMovie, hintsHighQuality, hintsHighQuality); |
err = ImportFirstVideoTrackFromMovie(srcMovie, dstMovie, dstSequenceDuration, numSamples, |
pDstFrameWidth, pDstFrameHeight, pDstTrack |
); |
bail: |
if (srcMovie != NULL) DisposeMovie(srcMovie); |
if (srcRefNum != 0) CloseMovieFile(srcRefNum); |
return(err); |
} |
/******************************************************************************* |
* VerifyPanoParameters |
*******************************************************************************/ |
static void |
VerifyPanoParameters(register VRMakeQTVRParams *qtvrParams, const MyPanoTrackDescriptor *panoDesc) |
{ |
float circumference, axialHeight, focalLengthCircumferential, focalLengthAxial, maxFOV; |
if (qtvrParams->trackDuration <= 0) |
qtvrParams->trackDuration = 7200; |
/* Set default pan range, if not set appropriately */ |
if (qtvrParams->maxPan == qtvrParams->minPan) { |
qtvrParams->minPan = 0.0f; /* Default: 360 degree wrapping pano */ |
qtvrParams->maxPan = 360.0f; |
qtvrParams->wraps = 1; |
} |
if ((panoDesc->versionToCreate == kQTVRCylindricalVersion2v) || (panoDesc->versionToCreate == kQTVRCylindricalVersion2h)) { /* we're building a version 2.0 cylindrical panorama */ |
/* Check for valid parameters, and assign defaults. */ |
if (panoDesc->versionToCreate == kQTVRCylindricalVersion2h) { /* non-rotated pano */ |
circumference = panoDesc->imageTileWidth * qtvrParams->tilesH; if (!qtvrParams->wraps) circumference--; |
axialHeight = panoDesc->imageTileHeight * qtvrParams->tilesV; |
} |
else { /* rotated pano */ |
circumference = panoDesc->imageTileHeight * qtvrParams->tilesV; if (!qtvrParams->wraps) circumference--; |
axialHeight = panoDesc->imageTileWidth * qtvrParams->tilesH; |
} |
focalLengthCircumferential = circumference / (qtvrParams->maxPan - qtvrParams->minPan) * D_DEGREES_PER_RADIAN; |
if (qtvrParams->maxTilt == qtvrParams->minTilt) { |
focalLengthAxial = focalLengthCircumferential; /* Default: square pixels */ |
qtvrParams->maxTilt = atan((axialHeight - 1) / (2.0f * focalLengthAxial)) * D_DEGREES_PER_RADIAN; |
qtvrParams->minTilt = -qtvrParams->maxTilt; |
} |
else { |
focalLengthAxial = (axialHeight - 1) / (tan(qtvrParams->maxTilt * D_RADIANS_PER_DEGREE) - tan(qtvrParams->minTilt * D_RADIANS_PER_DEGREE)); |
} |
maxFOV = qtvrParams->maxTilt - qtvrParams->minTilt; |
if ((qtvrParams->maxFieldOfView <= 0.0f) || (qtvrParams->maxFieldOfView > maxFOV)) |
qtvrParams->maxFieldOfView = maxFOV; |
if (qtvrParams->minFieldOfView <= 0.0f) /* Set minimum to 2:1 pixel zoom */ |
qtvrParams->minFieldOfView = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 2.0f) * D_DEGREES_PER_RADIAN; |
if (qtvrParams->minFieldOfView > qtvrParams->maxFieldOfView) |
qtvrParams->minFieldOfView = qtvrParams->maxFieldOfView; |
if (qtvrParams->defaultFieldOfView == 0.0f) /* Set default to 1:1 pixel zoom */ |
qtvrParams->defaultFieldOfView = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 1.0f) * D_DEGREES_PER_RADIAN; |
if (qtvrParams->defaultFieldOfView > qtvrParams->maxFieldOfView) |
qtvrParams->defaultFieldOfView = qtvrParams->maxFieldOfView; |
if (qtvrParams->defaultFieldOfView < qtvrParams->minFieldOfView) |
qtvrParams->defaultFieldOfView = qtvrParams->minFieldOfView; |
} |
else { /* We're building a cubic panorama */ |
/* Check for valid parameters, and set to default if not */ |
if (qtvrParams->maxTilt == qtvrParams->minTilt) { /* Default: peg at the poles */ |
qtvrParams->minTilt = -90.0f; |
qtvrParams->maxTilt = 90.0f; |
} |
focalLengthAxial = (panoDesc->imageTileHeight * qtvrParams->tilesV - 1) * 0.5f; /* These are questionable for partial cubic panos */ |
maxFOV = qtvrParams->maxTilt - qtvrParams->minTilt; |
if (maxFOV > 170.1f) /* 170.1 degrees (= 3 radians) limited by numerical precision in the computations */ |
maxFOV = 170.1f; |
if (qtvrParams->maxFieldOfView <= 0.0f) { |
qtvrParams->maxFieldOfView = maxFOV; |
if (qtvrParams->maxFieldOfView > 120.0) |
qtvrParams->maxFieldOfView = 120.0f; /* 120 degrees is the default maximum FOV */ |
} |
else if (qtvrParams->maxFieldOfView > maxFOV) |
qtvrParams->maxFieldOfView = maxFOV; |
if (qtvrParams->minFieldOfView <= 0.0f) /* Set minimum to 2:1 pixel zoom */ |
qtvrParams->minFieldOfView = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 2.0f) * D_DEGREES_PER_RADIAN; |
if (qtvrParams->minFieldOfView > qtvrParams->maxFieldOfView) |
qtvrParams->minFieldOfView = qtvrParams->maxFieldOfView; |
if (qtvrParams->defaultFieldOfView <= 0.0f) /* Set default to 1:1 pixel zoom */ |
qtvrParams->defaultFieldOfView = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 1.0f) * D_DEGREES_PER_RADIAN; |
if (qtvrParams->defaultFieldOfView > qtvrParams->maxFieldOfView) |
qtvrParams->defaultFieldOfView = qtvrParams->maxFieldOfView; |
if (qtvrParams->defaultFieldOfView < qtvrParams->minFieldOfView) |
qtvrParams->defaultFieldOfView = qtvrParams->minFieldOfView; |
} |
} |
/******************************************************************************* |
* SetCubicViewData |
*******************************************************************************/ |
static void |
SetCubicViewData(const VRMakeQTVRParams *qtvrParams, QTVRCubicViewAtom *cubicViewData) |
{ |
cubicViewData->minPan = EndianF32_NtoB(qtvrParams->minPan); |
cubicViewData->maxPan = EndianF32_NtoB(qtvrParams->maxPan); |
cubicViewData->minTilt = EndianF32_NtoB(qtvrParams->minTilt); |
cubicViewData->maxTilt = EndianF32_NtoB(qtvrParams->maxTilt); |
cubicViewData->minFieldOfView = EndianF32_NtoB(qtvrParams->minFieldOfView); |
cubicViewData->maxFieldOfView = EndianF32_NtoB(qtvrParams->maxFieldOfView); |
cubicViewData->defaultPan = EndianF32_NtoB(qtvrParams->defaultPan); |
cubicViewData->defaultTilt = EndianF32_NtoB(qtvrParams->defaultTilt); |
cubicViewData->defaultFieldOfView = EndianF32_NtoB(qtvrParams->defaultFieldOfView); |
} |
/******************************************************************************** |
* SetOneCubicFaceData |
* |
* This assumes that the faces are equal subtiles of 6 cube faces. |
********************************************************************************/ |
static void |
SetOneCubicFaceData( |
QTVRCubicFaceData *face, |
float x, /* axis of rotation */ |
float y, |
float z, |
float degrees, /* rotation about axis */ |
long tilesH, /* the number of subdivisions to each face */ |
long tilesV, |
long h, /* The horizontal index of this sub-face */ |
long v /* The vertical index of this sub-face */ |
) |
{ |
double halfAngle, norm, sqrtCotVFOV, s, c; |
sqrtCotVFOV = sqrt((double)tilesV); |
halfAngle = degrees * D_PI / 360.0; |
norm = x*x + y*y + z*z; |
if (norm != 0.0) |
norm = 1.0 / sqrt(norm); |
if (fabs(s = sin(halfAngle)) < 1.0e-8) s = 0.0; /* Toss out numerically insignificant values ... */ |
if (fabs(c = cos(halfAngle)) < 1.0e-8) c = 0.0; /* ... so the file format looks nice */ |
norm *= s * sqrtCotVFOV; |
face->orientation[0] = EndianF32_NtoB(c * sqrtCotVFOV); |
face->orientation[1] = EndianF32_NtoB(x * norm); |
face->orientation[2] = EndianF32_NtoB(y * norm); |
face->orientation[3] = EndianF32_NtoB(z * norm); |
/* Center, normalized by the vertical dimension */ |
face->center[0] = EndianF32_NtoB( (2 * h - tilesH + 1) * (float)tilesV / (float)tilesH); |
face->center[1] = EndianF32_NtoB(-(2 * v - tilesV + 1)); /* reverse from scan direction to world up direction */ |
face->aspect = EndianF32_NtoB(1.0f); |
face->skew = EndianF32_NtoB(0.0f); |
} |
/******************************************************************************* |
* SetCubicFaceData |
*******************************************************************************/ |
static void |
SetCubicFaceData(const VRMakeQTVRParams *qtvrParams, QTVRCubicFaceData* faceData) |
{ |
long i, j, k; |
FaceOrientation *fo; |
for (k = 0, fo = faceOrientation; k < 6; k++, fo++) |
for (j = 0; j < qtvrParams->tilesV; j++) |
for (i = 0; i < qtvrParams->tilesH; i++, faceData++) |
SetOneCubicFaceData(faceData, fo->x, fo->y, fo->z, fo->theta, qtvrParams->tilesH, qtvrParams->tilesV, i, j); |
} |
/******************************************************************************* |
* SetPanoSampleData |
*******************************************************************************/ |
static void |
SetPanoSampleData( |
const VRMakeQTVRParams *qtvrParams, |
const MyPanoTrackDescriptor *panoDescriptor, |
QTVRPanoSampleAtom *panoSampleData |
) |
{ |
panoSampleData->majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
panoSampleData->minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
panoSampleData->imageRefTrackIndex = EndianU32_NtoB(panoDescriptor->imageRefTrackIndex); |
panoSampleData->hotSpotRefTrackIndex = EndianU32_NtoB(panoDescriptor->hotSpotRefTrackIndex); |
if ((panoDescriptor->versionToCreate == kQTVRCylindricalVersion2v) || (panoDescriptor->versionToCreate == kQTVRCylindricalVersion2h)) { /* we're building a version 2.0 cylindrical panorama */ |
/* Set cylindrical parameters in Pano Sample Atom */ |
panoSampleData->minPan = EndianF32_NtoB(qtvrParams->minPan); |
panoSampleData->maxPan = EndianF32_NtoB(qtvrParams->maxPan); |
panoSampleData->minTilt = EndianF32_NtoB(qtvrParams->minTilt); |
panoSampleData->maxTilt = EndianF32_NtoB(qtvrParams->maxTilt); |
panoSampleData->minFieldOfView = EndianF32_NtoB(qtvrParams->minFieldOfView); |
panoSampleData->maxFieldOfView = EndianF32_NtoB(qtvrParams->maxFieldOfView); |
panoSampleData->defaultPan = EndianF32_NtoB(qtvrParams->defaultPan); |
panoSampleData->defaultTilt = EndianF32_NtoB(qtvrParams->defaultTilt); |
panoSampleData->defaultFieldOfView = EndianF32_NtoB(qtvrParams->defaultFieldOfView); |
panoSampleData->imageSizeX = EndianU32_NtoB(panoDescriptor->imageTileWidth * qtvrParams->tilesH); |
panoSampleData->imageSizeY = EndianU32_NtoB(panoDescriptor->imageTileHeight * qtvrParams->tilesV); |
panoSampleData->imageNumFramesX = EndianU16_NtoB(qtvrParams->tilesH); |
panoSampleData->imageNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
panoSampleData->hotSpotSizeX = EndianU32_NtoB(panoDescriptor->hotSpotTileWidth * qtvrParams->tilesH); |
panoSampleData->hotSpotSizeY = EndianU32_NtoB(panoDescriptor->hotSpotTileHeight * qtvrParams->tilesV); |
panoSampleData->hotSpotNumFramesX = EndianU16_NtoB(qtvrParams->tilesH); |
panoSampleData->hotSpotNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
if (panoDescriptor->versionToCreate == kQTVRCylindricalVersion2h) { |
panoSampleData->panoType = EndianU32_NtoB(kQTVRHorizontalCylinder); /* non-rotated pano */ |
//panoSampleData->flags = EndianU32_NtoB((qtvrParams->wraps ? kQTVRPanoFlagAlwaysWrap : 0L) | kQTVRPanoFlagHorizontal); // include for compatibility with QT 4.0 |
panoSampleData->flags = EndianU32_NtoB(kQTVRPanoFlagHorizontal); // include for compatibility with QT 4.0 |
} |
else { |
panoSampleData->panoType = EndianU32_NtoB(kQTVRVerticalCylinder); /* rotated pano */ |
//panoSampleData->flags = EndianU32_NtoB(qtvrParams->wraps ? kQTVRPanoFlagAlwaysWrap : 0L); |
panoSampleData->flags = EndianU32_NtoB(0L); |
} |
panoSampleData->reserved2 = EndianU32_NtoB(0L); |
} |
else { /* We're building a cubic panorama: build backward-compatible pano sample data */ |
panoSampleData->minPan = EndianF32_NtoB(qtvrParams->minPan); |
panoSampleData->maxPan = EndianF32_NtoB(qtvrParams->maxPan); |
panoSampleData->minTilt = EndianF32_NtoB(qtvrParams->minTilt < -45.0f ? -45.0f : qtvrParams->minTilt); |
panoSampleData->maxTilt = EndianF32_NtoB(qtvrParams->maxTilt > 45.0f ? 45.0f : qtvrParams->maxTilt); |
panoSampleData->minFieldOfView = EndianF32_NtoB(qtvrParams->minFieldOfView); |
panoSampleData->maxFieldOfView = EndianF32_NtoB(qtvrParams->maxFieldOfView > 90.0f ? 90.0f : qtvrParams->maxFieldOfView); |
panoSampleData->defaultPan = EndianF32_NtoB(qtvrParams->defaultPan); |
panoSampleData->defaultTilt = EndianF32_NtoB(qtvrParams->defaultTilt); |
panoSampleData->defaultFieldOfView = EndianF32_NtoB(qtvrParams->defaultFieldOfView); |
panoSampleData->imageSizeX = EndianU32_NtoB(panoDescriptor->imageTileWidth * qtvrParams->tilesH * 4); |
panoSampleData->imageSizeY = EndianU32_NtoB(panoDescriptor->imageTileHeight * qtvrParams->tilesV); |
panoSampleData->imageNumFramesX = EndianU16_NtoB(qtvrParams->tilesH * 4); |
panoSampleData->imageNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
panoSampleData->hotSpotSizeX = EndianU32_NtoB(panoDescriptor->hotSpotTileWidth * qtvrParams->tilesH * 4); |
panoSampleData->hotSpotSizeY = EndianU32_NtoB(panoDescriptor->hotSpotTileHeight * qtvrParams->tilesV); |
panoSampleData->hotSpotNumFramesX = EndianU16_NtoB(qtvrParams->tilesH * 4); |
panoSampleData->hotSpotNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
/* Set the horizontal flag so that pre-cubic QTVR engine can at least display the four sides of the cube */ |
panoSampleData->flags = EndianU32_NtoB(kQTVRPanoFlagHorizontal); |
panoSampleData->panoType = EndianU32_NtoB(kQTVRCube); |
panoSampleData->reserved2 = EndianU32_NtoB(0L); |
} |
} |
/******************************************************************************** |
* TileGWorldsToNewTrack |
********************************************************************************/ |
static OSErr |
TileGWorldsToNewTrack( |
long numGWs, /* the number of GWorlds */ |
GWorldPtr *srcGWs, /* Source GWorlds - in standard order */ |
long overlapTiles, /* Whether the tiles should overlap or not */ |
CodecType tileCodec, |
CodecQ tileQuality, |
long tilesH, |
long tilesV, |
long tileDepth, |
Movie dstMovie, /* to be tiled and added to this movie. */ |
TimeValue dstSequenceDuration, /* duration of the whole sequence */ |
long *pDstTrackWidth, /* returned track width, in integral units */ |
long *pDstTrackHeight, /* returned track height, in integral units */ |
Track *pDstTrack /* the track added to the dstMovie */ |
) |
{ |
PixMapHandle pm; |
Rect tileRect; |
Track tileTrack = NULL; |
Media tileMedia = NULL; |
ImageDescriptionHandle tileImageDesc = NULL; |
Handle tileData = NULL; |
Ptr tileDataPtr = NULL; |
long tileFrameSize; |
TimeValue dstTileDuration = dstSequenceDuration / (numGWs * tilesH * tilesV); |
OSErr err = noErr; |
long i, j, k; |
#if USE_TILE_DISPLAY_WINDOW |
WindowRef tileWindow; |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
/* Determine tile size */ |
#if ACCESSOR_CALLS_ARE_FUNCTIONS |
GetPortBounds(srcGWs[0], &tileRect); |
#else /* ACCESSOR_CALLS_ARE_FUNCTIONS */ |
tileRect = srcGWs[0]->portRect; |
#endif /* ACCESSOR_CALLS_ARE_FUNCTIONS */ |
MacOffsetRect(&tileRect, -tileRect.left, -tileRect.top); |
if (overlapTiles) { |
tileRect.right += tilesH - 1; /* Adjust for overlap at seams */ |
tileRect.bottom += tilesV - 1; /* Adjust for overlap at seams */ |
} |
if ((tileRect.right % tilesH != 0) || (tileRect.bottom % tilesV != 0)) { /* Check for evenly divisible tiles */ |
err = paramErr; /* Oops: tiles are not evenly divisible */ |
goto bail; |
} |
tileRect.right /= tilesH; |
tileRect.bottom /= tilesV; |
if (pDstTrackWidth != NULL) *pDstTrackWidth = tileRect.right; |
if (pDstTrackHeight != NULL) *pDstTrackHeight = tileRect.bottom; |
/* Create tile view, if desired */ |
#if USE_TILE_DISPLAY_WINDOW |
MacOffsetRect(&tileRect, 20, 100); |
tileWindow = NewCWindow(NULL, &tileRect, "\pUncompressed tiles", true, noGrowDocProc, (WindowPtr)-1L, true, 0); |
MacOffsetRect(&tileRect, -20, -100); |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
/* Create the tile movie track and media */ |
tileTrack = NewMovieTrack(dstMovie, (long)tileRect.right << 16, (long)tileRect.bottom << 16, kNoVolume); |
tileMedia = NewTrackMedia(tileTrack, VideoMediaType, kQTVRStandardTimeScale, NULL, 0); |
if ((tileTrack == NULL) || (tileMedia == NULL)) |
goto bail; |
if (pDstTrack != NULL) *pDstTrack = tileTrack; |
/* |
* dice the picture into pieces, compress them, and add the compressed tiles as video samples to the movie |
*/ |
/* create an image description handle; this is resized and filled in below by FCompressImage */ |
tileImageDesc = (ImageDescriptionHandle)MyNewHandleClear(4); |
if (tileImageDesc == NULL) |
goto bail; |
pm = GetGWorldPixMap(srcGWs[0]); |
LockPixels(pm); |
err = GetMaxCompressionSize(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, &tileFrameSize); |
UnlockPixels(pm); |
if (err != noErr) |
goto bail; |
tileData = MyNewHandle(tileFrameSize); |
if (tileData == NULL) |
goto bail; |
HLock(tileData); |
tileDataPtr = *tileData; |
BeginMediaEdits(tileMedia); |
/* tile, compress, and write tiles */ |
for (k = 0; k < numGWs; k++) { |
pm = GetGWorldPixMap(srcGWs[k]); |
for (j = 0; j < tilesV; j++) { |
for (i = 0; i < tilesH; i++) { |
Rect r; |
if (overlapTiles) { |
r.left = i * (tileRect.right - 1); |
r.top = j * (tileRect.bottom - 1); |
} |
else { |
r.left = i * tileRect.right; |
r.top = j * tileRect.bottom; |
} |
r.right = r.left + tileRect.right; |
r.bottom = r.top + tileRect.bottom; |
LockPixels(pm); |
/* Draw the picture into the window we've created */ |
#if USE_TILE_DISPLAY_WINDOW |
if (tileWindow != NULL) { |
GrafPtr savePort; |
GetPort(&savePort); |
#if TARGET_API_MAC_CARBON |
{ Rect pr; |
CGrafPtr tilePort = GetWindowPort(tileWindow); |
MacSetPort(tilePort); |
CopyBits((BitMap*)(*(pm)), (BitMap*)*GetPortPixMap(tilePort), |
&r, GetWindowPortBounds(tileWindow, &pr), ditherCopy, NULL |
); |
if (QDIsPortBuffered(tilePort)) |
QDFlushPortBuffer(tilePort, NULL); |
} |
#else /* !TARGET_API_MAC_CARBON */ |
MacSetPort(tileWindow); |
CopyBits((BitMap*)(*(pm)), &(tileWindow->portBits), &r, &tileWindow->portRect, ditherCopy, NULL); |
#endif /* !TARGET_API_MAC_CARBON */ |
MacSetPort(savePort); |
} |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
err = FCompressImage(pm, &r, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, NULL, 0, 0, NULL, NULL, tileImageDesc, tileDataPtr); |
UnlockPixels(pm); |
if (err != noErr) |
goto bail; |
err = AddMediaSample(tileMedia, tileData, 0, (**tileImageDesc).dataSize, dstTileDuration, (SampleDescriptionHandle)tileImageDesc, 1L, 0, NULL); |
if (err != noErr) |
goto bail; |
} |
} |
} |
HUnlock(tileData); |
EndMediaEdits(tileMedia); |
/* add the media to the track, at time 0 */ |
err = InsertMediaIntoTrack(tileTrack, 0, 0, GetMediaDuration(tileMedia), fixed1); |
if (err != noErr) |
goto bail; |
/* a panorama image track should always be disabled */ |
SetTrackEnabled(tileTrack, false); |
bail: |
if (tileData != NULL) DisposeHandle(tileData); |
if (tileImageDesc != NULL) DisposeHandle((Handle)tileImageDesc); |
#if USE_TILE_DISPLAY_WINDOW |
if (tileWindow!= NULL) DisposeWindow(tileWindow); |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
return(err); |
} |
/******************************************************************************* |
* TilePictsToNewTrack |
*******************************************************************************/ |
static OSErr |
TilePictsToNewTrack( |
long numPicts, |
FSSpecHandle srcPictSpecs, |
long overlapTiles, /* Whether the tiles should overlap or not */ |
CodecType tileCodec, |
CodecQ tileQuality, |
long tilesH, |
long tilesV, |
short tileDepth, |
Movie dstMovie, /* to be tiled and added to this movie. */ |
TimeValue dstSequenceDuration, /* duration of the whole sequence */ |
long *pDstTrackWidth, /* returned track width, in integral units */ |
long *pDstTrackHeight, /* returned track height, in integral units */ |
Track *pDstTrack /* the track added to the dstMovie */ |
) |
{ |
GraphicsImportComponent theImporter = NULL; |
Rect pictRect, tileRect, drawRect; |
long tileWidth, tileHeight; |
GWorldPtr gw = NULL; |
PixMapHandle pm = NULL; |
Track tileTrack = NULL; |
Media tileMedia = NULL; |
ImageDescriptionHandle tileImageDesc = NULL; |
Handle tileData = NULL; |
Ptr tileDataPtr = NULL; |
long tileFrameSize; |
TimeValue dstTileDuration = dstSequenceDuration / (numPicts * tilesH * tilesV); |
OSErr err = noErr; |
long i, j, k; |
FSSpec fsSpec; |
#if USE_TILE_DISPLAY_WINDOW |
WindowRef tileWindow; |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
if ((srcPictSpecs == NULL) || (srcPictSpecs[0] == NULL) || (srcPictSpecs[0][0].name[0] == 0)) { |
err = paramErr; |
goto bail; |
} |
/* Create a GWorld to draw the uncompressed tiles into */ |
fsSpec = srcPictSpecs[0][0]; |
err = GetGraphicsImporterForFile(&fsSpec, &theImporter); |
if (err != noErr) |
goto bail; |
err = GraphicsImportGetBoundsRect(theImporter, &pictRect); |
if (err != noErr) |
goto bail; |
CloseComponent(theImporter); |
theImporter = NULL; |
tileWidth = pictRect.right - pictRect.left; |
tileHeight = pictRect.bottom - pictRect.top; |
if (overlapTiles) { |
tileWidth += tilesH - 1; /* Adjust for overlap at seams */ |
tileHeight += tilesV - 1; /* Adjust for overlap at seams */ |
} |
if ((tileWidth % tilesH != 0) || (tileHeight % tilesV != 0)) { /* Check for evenly divisible tiles */ |
err = paramErr; /* Oops: tiles are not evenly divisible */ |
goto bail; |
} |
tileWidth /= tilesH; |
tileHeight /= tilesV; |
if (pDstTrackWidth != NULL) *pDstTrackWidth = tileWidth; |
if (pDstTrackHeight != NULL) *pDstTrackHeight = tileHeight; |
MacSetRect(&tileRect, 0, 0, tileWidth, tileHeight); |
err = NewGWorld(&gw, tileDepth, &tileRect, NULL, NULL, 0L); |
if (err != noErr) |
goto bail; |
/* Debugging or progress window */ |
#if USE_TILE_DISPLAY_WINDOW |
MacSetRect(&tileRect, 0, 0, tileWidth, tileHeight); |
MacOffsetRect(&tileRect, 20, 100); |
tileWindow = NewCWindow(NULL, &tileRect, "\pUncompressed tiles", true, noGrowDocProc, (WindowPtr)-1L, true, 0); |
MacOffsetRect(&tileRect, -20, -100); |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
tileTrack = NewMovieTrack(dstMovie, tileWidth << 16, tileHeight << 16, kNoVolume); |
tileMedia = NewTrackMedia(tileTrack, VideoMediaType, kQTVRStandardTimeScale, NULL, 0); |
if ((tileTrack == NULL) || (tileMedia == NULL)) |
goto bail; |
if (pDstTrack != NULL) *pDstTrack = tileTrack; |
/* Dice the picture into pieces, compress them, and add the compressed tiles as video samples to the movie */ |
tileImageDesc = (ImageDescriptionHandle)MyNewHandleClear(4); /* this is resized and filled in below by FCompressImage */ |
if (tileImageDesc == NULL) |
goto bail; |
pm = GetGWorldPixMap(gw); |
LockPixels(pm); |
err = GetMaxCompressionSize(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, &tileFrameSize); |
UnlockPixels(pm); |
if (err != noErr) |
goto bail; |
tileData = MyNewHandle(tileFrameSize); |
if (tileData == NULL) |
goto bail; |
BeginMediaEdits(tileMedia); |
for (k = 0; k < numPicts; k++) { |
fsSpec = srcPictSpecs[0][k]; |
err = GetGraphicsImporterForFile(&fsSpec, &theImporter); |
if (err != noErr) |
goto bail; |
for (j = 0; j < tilesV; j++) { |
for (i = 0; i < tilesH; i++) { |
/* Offset to the proper subrect for this tile */ |
drawRect = pictRect; |
if (overlapTiles) MacOffsetRect(&drawRect, -i * (tileWidth-1), -j * (tileHeight-1)); |
else MacOffsetRect(&drawRect, -i * tileWidth, -j * tileHeight); |
/* Draw the picture into the window we've created */ |
#if USE_TILE_DISPLAY_WINDOW |
if (tileWindow != NULL) { |
GraphicsImportSetGWorld(theImporter, (CGrafPtr)tileWindow, NULL); |
GraphicsImportSetBoundsRect(theImporter, &drawRect); |
GraphicsImportDraw(theImporter); |
} |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
/* Draw the picture into the uncompressed tile GWorld */ |
GraphicsImportSetGWorld(theImporter, gw, NULL); |
GraphicsImportSetBoundsRect(theImporter, &drawRect); |
GraphicsImportDraw(theImporter); |
/* Compress the tile and add it tile to the movie */ |
HLock(tileData); |
tileDataPtr = *tileData; |
LockPixels(pm); |
err = FCompressImage(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, |
NULL, 0, 0, NULL, NULL, tileImageDesc, tileDataPtr |
); |
UnlockPixels(pm); |
if (err != noErr) |
goto bail; |
err = AddMediaSample(tileMedia, tileData, 0, (**tileImageDesc).dataSize, dstTileDuration, |
(SampleDescriptionHandle)tileImageDesc, 1L, 0, NULL |
); |
if (err != noErr) |
goto bail; |
} |
} |
CloseComponent(theImporter); |
theImporter = NULL; |
} |
EndMediaEdits(tileMedia); |
DisposeHandle(tileData); |
tileData = NULL; |
/* add the media to the track, at time 0 */ |
err = InsertMediaIntoTrack(tileTrack, 0, 0, GetMediaDuration(tileMedia), fixed1); |
if (err != noErr) |
goto bail; |
/* a panorama image track should always be disabled */ |
SetTrackEnabled(tileTrack, false); |
bail: |
if (theImporter != NULL) CloseComponent(theImporter); |
if (gw != NULL) DisposeGWorld(gw); |
if (tileData != NULL) DisposeHandle(tileData); |
if (tileImageDesc != NULL) DisposeHandle((Handle)tileImageDesc); |
#if USE_TILE_DISPLAY_WINDOW |
if (tileWindow!= NULL) DisposeWindow(tileWindow); |
#endif /* USE_TILE_DISPLAY_WINDOW */ |
return(err); |
} |
/******************************************************************************** |
* FlattenQTVRMovie |
********************************************************************************/ |
static OSErr |
FlattenQTVRMovie(Movie srcMovie, unsigned short flattenFlags, unsigned short previewResolution, FSSpec *dstMovieSpec) |
{ |
ComponentDescription desc; |
Component flattener; |
ComponentInstance qtvrExport = nil; |
OSErr err; |
desc.componentType = MovieExportType; |
desc.componentSubType = MovieFileType; |
desc.componentManufacturer = QTVRFlattenerType; |
desc.componentFlags = 0; |
desc.componentFlagsMask = 0; |
flattener = FindNextComponent(NULL, &desc); |
/* Use the specialized QuickTimeVR flattener if it is available */ |
if (flattener && ((qtvrExport = OpenComponent(flattener)) != NULL)) { |
QTAtomContainer exportData; |
QTAtom parent; |
Boolean makePreview = (flattenFlags & kVRMakePano_GeneratePreview) ? true : false; |
Boolean blur = (flattenFlags & kVRMakePano_BlurGeneratedPreview) ? true : false; |
err = QTNewAtomContainer(&exportData); |
err = QTInsertChild(exportData, kParentAtomIsContainer, kQTVRFlattenerSettingsParentAtomType, 1, 0, 0, nil, &parent); |
err = QTInsertChild(exportData, parent, kQTVRFlattenerCreatePreviewAtomType, 1, 0, sizeof(Boolean), &makePreview, nil); |
if (makePreview) { |
err = QTInsertChild(exportData, parent, kQTVRFlattenerPreviewResAtomType, 1, 0, sizeof(short), &previewResolution, nil); |
err = QTInsertChild(exportData, parent, kQTVRFlattenerBlurPreviewAtomType, 1, 0, sizeof(Boolean), &blur, nil); |
} |
MovieExportSetSettingsFromAtomContainer(qtvrExport, exportData); |
QTDisposeAtomContainer(exportData); |
DeleteMovieFile(dstMovieSpec); /* I don't know why, but sometimes this is necessary */ |
err = MovieExportToFile(qtvrExport, dstMovieSpec, srcMovie, NULL, 0, 0); |
CloseComponent(qtvrExport); |
} |
else { /* Use the generic movie flattener */ |
Movie dstMovie; |
SetMovieProgressProc(srcMovie, (MovieProgressUPP)-1, 0L); |
dstMovie = FlattenMovieData(srcMovie, |
flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData, |
dstMovieSpec, FOUR_CHAR_CODE('TVOD'), |
smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile |
); |
if ((dstMovie == NULL) || ((err = GetMoviesError()) == fnOpnErr)) { |
DeleteMovieFile(dstMovieSpec); /* I don't know why, but sometimes this is necessary */ |
dstMovie = FlattenMovieData(srcMovie, |
flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData, |
dstMovieSpec, FOUR_CHAR_CODE('TVOD'), |
smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile |
); |
if ((dstMovie == NULL) && ((err = GetMoviesError()) == noErr)) |
err = writErr; /* Something is wrong */ |
} |
if (dstMovie != NULL) |
DisposeMovie(dstMovie); |
} |
return(err); |
} |
#pragma mark - |
/******************************************************************************** |
* CreateVRWorld |
* |
* Create a VR world atom container and add the basic required atoms to it |
* Also, create a node information atom container and add a node header atom to it. |
* Return both atom containers. |
* |
* The caller is responsible for disposing of the VR world and the node information atom |
* (by calling QTDisposeAtomContainer). |
* |
* This function assumes that the scene described by the VR world contains |
* a single node whose type is specified by the theNodeType parameter. |
********************************************************************************/ |
static OSErr |
CreateVRWorld( |
OSType theNodeType, |
const VRMakeQTVRParams *qtvrParams, |
QTAtomContainer *theVRWorld, |
QTAtomContainer *theNodeInfo |
) |
{ |
QTAtomContainer myVRWorld = NULL; |
QTAtomContainer myNodeInfo = NULL; |
QTVRWorldHeaderAtom myVRWorldHeaderAtom; |
QTAtom myImagingParentAtom; |
QTAtom myNodeParentAtom; |
QTAtom myNodeAtom; |
QTVRPanoImagingAtom myPanoImagingAtom; |
QTVRNodeLocationAtom myNodeLocationAtom; |
QTVRNodeHeaderAtom myNodeHeaderAtom; |
UInt32 myIndex; |
OSErr err = noErr; |
/* Create a VR world atom container */ |
err = QTNewAtomContainer(&myVRWorld); |
if (err != noErr) |
goto bail; |
/* Add a VR world header atom to the VR world */ |
myVRWorldHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
myVRWorldHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
/* Insert the scene name string, if we have one; if not, set nameAtomID to 0 */ |
if (qtvrParams->sceneName) { |
QTAtomID myID; |
err = AddCStringToAtomContainer(myVRWorld, kParentAtomIsContainer, qtvrParams->sceneName, &myID); |
myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(myID); |
} else { |
myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(0L); |
} |
myVRWorldHeaderAtom.defaultNodeID = EndianU32_NtoB(kDefaultNodeID); |
myVRWorldHeaderAtom.vrWorldFlags = EndianU32_NtoB(0L); |
myVRWorldHeaderAtom.reserved1 = EndianU32_NtoB(0L); |
myVRWorldHeaderAtom.reserved2 = EndianU32_NtoB(0L); |
/* Add the atom to the atom container (the VR world) */ |
err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, 1, sizeof(QTVRWorldHeaderAtom), &myVRWorldHeaderAtom, NULL); |
if (err != noErr) |
goto bail; |
/* Add an imaging parent atom to the VR world and insert imaging atoms into it |
* |
* Imaging atoms describe the default imaging characteristics for the different types of nodes in the scene; |
* currently, only the panorama imaging atoms are defined, so we'll include those (even in object movies) |
*/ |
err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRImagingParentAtomType, 1, 1, 0, NULL, &myImagingParentAtom); |
if (err != noErr) |
goto bail; |
/* Fill in the fields of the panorama imaging atom structure */ |
myPanoImagingAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
myPanoImagingAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
myPanoImagingAtom.correction = EndianU32_NtoB(kQTVRFullCorrection); |
myPanoImagingAtom.imagingValidFlags = EndianU32_NtoB(kQTVRValidCorrection | kQTVRValidQuality | kQTVRValidDirectDraw); |
myPanoImagingAtom.reserved1 = EndianU32_NtoB(0L); |
myPanoImagingAtom.reserved2 = EndianU32_NtoB(0L); |
for (myIndex = 0; myIndex < 6; myIndex++) |
myPanoImagingAtom.imagingProperties[myIndex] = EndianU32_NtoB(0L); |
/* Add a panorama imaging atom for kQTVRMotion state */ |
myPanoImagingAtom.quality = EndianU32_NtoB(qtvrParams->dynamicQuality); |
myPanoImagingAtom.directDraw = EndianU32_NtoB(true); |
myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRMotion); |
err = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL); |
if (err != noErr) |
goto bail; |
/* Add a panorama imaging atom for kQTVRStatic state */ |
myPanoImagingAtom.quality = EndianU32_NtoB(qtvrParams->staticQuality); |
myPanoImagingAtom.directDraw = EndianU32_NtoB(false); |
myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRStatic); |
err = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL); |
if (err != noErr) |
goto bail; |
/* Add a node parent atom to the VR world and insert node ID atoms into it */ |
err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, 1, 0, NULL, &myNodeParentAtom); |
if (err != noErr) |
goto bail; |
/* Add a node ID atom */ |
err = QTInsertChild(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType, kDefaultNodeID, 0, 0, NULL, &myNodeAtom); |
if (err != noErr) |
goto bail; |
/* Add a single node location atom to the node ID atom */ |
myNodeLocationAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
myNodeLocationAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
myNodeLocationAtom.nodeType = EndianU32_NtoB(theNodeType); |
myNodeLocationAtom.locationFlags = EndianU32_NtoB(kQTVRSameFile); |
myNodeLocationAtom.locationData = EndianU32_NtoB(0); |
myNodeLocationAtom.reserved1 = EndianU32_NtoB(0); |
myNodeLocationAtom.reserved2 = EndianU32_NtoB(0); |
/* Insert the node location atom into the node ID atom */ |
err = QTInsertChild(myVRWorld, myNodeAtom, kQTVRNodeLocationAtomType, 1, 1, sizeof(QTVRNodeLocationAtom), &myNodeLocationAtom, NULL); |
if (err != noErr) |
goto bail; |
/* Create a node information atom container and add a node header atom to it */ |
err = QTNewAtomContainer(&myNodeInfo); |
if (err != noErr) |
goto bail; |
myNodeHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion); |
myNodeHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion); |
myNodeHeaderAtom.nodeType = EndianU32_NtoB(theNodeType); |
myNodeHeaderAtom.nodeID = EndianU32_NtoB(kDefaultNodeID); |
myNodeHeaderAtom.commentAtomID = EndianU32_NtoB(0L); |
myNodeHeaderAtom.reserved1 = EndianU32_NtoB(0L); |
myNodeHeaderAtom.reserved2 = EndianU32_NtoB(0L); |
/* Insert the node name string into the node info atom container */ |
if (qtvrParams->nodeName) { |
QTAtomID myID; |
err = AddCStringToAtomContainer(myNodeInfo, kParentAtomIsContainer, qtvrParams->nodeName, &myID); |
myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(myID); |
} else { |
myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(0L); |
} |
/* Insert the node header atom into the node info atom container */ |
err = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, 1, sizeof(QTVRNodeHeaderAtom), &myNodeHeaderAtom, NULL); |
if (err != noErr) |
goto bail; |
bail: |
/* return the atom containers that we've created and configured here */ |
*theVRWorld = myVRWorld; |
*theNodeInfo = myNodeInfo; |
return(err); |
} |
/******************************************************************************* |
* CreateQTVRTrack |
*******************************************************************************/ |
static OSErr |
CreateQTVRTrack(const VRMakeQTVRParams *qtvrParams, TimeValue dstTrackDuration, Movie movie, Track *qtvrTrack) |
{ |
Track myQTVRTrack; |
Media myQTVRMedia; |
QTAtomContainer myVRWorld = NULL; |
QTAtomContainer myNodeInfo = NULL; |
QTVRSampleDescriptionHandle myQTVRDesc = NULL; |
long mySize; |
OSErr err; |
myQTVRTrack = NewMovieTrack(movie, qtvrParams->windowWidth << 16, qtvrParams->windowHeight << 16, kFullVolume); |
myQTVRMedia = NewTrackMedia(myQTVRTrack, kQTVRQTVRType, kQTVRStandardTimeScale, NULL, 0); |
if ((myQTVRTrack == NULL) || (myQTVRMedia == NULL)) { |
err = GetMoviesError(); |
goto bail; |
} |
SetMovieTimeScale(movie, kQTVRStandardTimeScale); |
/* create a VR world atom container and a node information atom container; |
* remember that the VR world becomes part of the QTVR sample description, |
* and the node information atom container becomes the media sample data |
*/ |
err = CreateVRWorld(kQTVRPanoramaType, qtvrParams, &myVRWorld, &myNodeInfo); |
if ((err != noErr) || (myVRWorld == NULL) || (myNodeInfo == NULL)) |
goto bail; |
/* Add hot spot information if given */ |
AddAllHotSpots2x0(myNodeInfo, qtvrParams->hotSpots); |
/* create a QTVR sample description */ |
mySize = sizeof(QTVRSampleDescription) + GetHandleSize((Handle)myVRWorld) - sizeof(long); |
myQTVRDesc = (QTVRSampleDescriptionHandle)MyNewHandleClear(mySize); |
if (myQTVRDesc == NULL) |
goto bail; |
(**myQTVRDesc).descSize = mySize; |
(**myQTVRDesc).descType = kQTVRQTVRType; |
(**myQTVRDesc).reserved1 = 0; |
(**myQTVRDesc).reserved2 = 0; |
(**myQTVRDesc).dataRefIndex = 0; |
/* copy the VR world atom container into the data field of the QTVR sample description */ |
BlockMoveData(*((Handle)myVRWorld), &((**myQTVRDesc).data), GetHandleSize((Handle)myVRWorld)); |
/* Create the media sample and add it to the QTVR track */ |
BeginMediaEdits(myQTVRMedia); |
err = AddMediaSample(myQTVRMedia, (Handle)myNodeInfo, 0, GetHandleSize((Handle)myNodeInfo), dstTrackDuration, (SampleDescriptionHandle)myQTVRDesc, 1, 0, NULL); |
if (err != noErr) |
goto bail; |
EndMediaEdits(myQTVRMedia); |
err = InsertMediaIntoTrack(myQTVRTrack, 0, 0, dstTrackDuration, fixed1); |
bail: |
if (myQTVRDesc != NULL) DisposeHandle((Handle)myQTVRDesc); |
if (myVRWorld != NULL) QTDisposeAtomContainer(myVRWorld); |
if (myNodeInfo != NULL) QTDisposeAtomContainer(myNodeInfo); |
*qtvrTrack = myQTVRTrack; |
return(err); |
} |
/******************************************************************************* |
* CreatePanoMediaSample |
*******************************************************************************/ |
static OSErr |
CreatePanoMediaSample( |
const VRMakeQTVRParams *qtvrParams, |
const MyPanoTrackDescriptor *panoDescriptor, |
QTAtomContainer *thePanoSample |
) |
{ |
OSErr err; |
QTAtomContainer panoSample; |
QTVRPanoSampleAtom myPanoSampleData; |
/* Create a pano media sample container */ |
err = QTNewAtomContainer(&panoSample); |
if (err != noErr) |
goto bail; |
/* Insert the pano sample atom into the pano sample atom container */ |
SetPanoSampleData(qtvrParams, panoDescriptor, &myPanoSampleData); |
err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRPanoSampleDataAtomType, 1, 1, sizeof(QTVRPanoSampleAtom), &myPanoSampleData, NULL); |
if (err != noErr) |
goto bail; |
/* Attach a fast start preview track, if applicable */ |
if (panoDescriptor->fastStartRefTrackIndex) { |
QTVRTrackRefEntry trackRefEntry; |
trackRefEntry.trackRefType = EndianU32_NtoB(kQTVRImageTrackRefType); |
trackRefEntry.trackResolution = EndianU16_NtoB(kQTVRPreviewTrackRes); |
trackRefEntry.trackRefIndex = EndianU32_NtoB(panoDescriptor->fastStartRefTrackIndex); |
err = QTInsertChild(panoSample, kParentAtomIsContainer, |
kQTVRTrackRefArrayAtomType, 1, 1, sizeof(QTVRTrackRefEntry), &trackRefEntry, nil); |
} |
/* Set the view and face orientation for cubic panoramas; |
* this atom will be ignored in pre-cubic versions of QTVR |
*/ |
if (panoDescriptor->versionToCreate == kQTVRCubicVersion1) { |
/* Specify the default and media-constrained view parameters */ |
QTVRCubicViewAtom myCubicViewAtom; |
SetCubicViewData(qtvrParams, &myCubicViewAtom); |
err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRCubicViewAtomType, 1, 1, sizeof(QTVRCubicViewAtom), &myCubicViewAtom, NULL); |
if (err != noErr) |
goto bail; |
/* Specify the orientation of each face */ |
#define FORCE_CUBIC_FACE_DATA |
#ifndef FORCE_CUBIC_FACE_DATA |
if (qtvrParams->tilesH * qtvrParams->tilesV != 1) /* We use the default if the faces are not subtiled */ |
#endif /* FORCE_CUBIC_FACE_DATA */ |
{ |
QTVRCubicFaceData *cubicData; |
cubicData = (QTVRCubicFaceData*)NewPtr(sizeof(QTVRCubicFaceData) * 6 * qtvrParams->tilesH * qtvrParams->tilesV); |
SetCubicFaceData(qtvrParams, cubicData); |
err = QTInsertChild(panoSample, kParentAtomIsContainer, |
kQTVRCubicFaceDataAtomType, 1, 1, sizeof(QTVRCubicFaceData) * 6 * qtvrParams->tilesH * qtvrParams->tilesV, cubicData, nil |
); |
DisposePtr((Ptr)cubicData); |
if (err != noErr) |
goto bail; |
} |
} |
/* add pan constraint, tilt constraint, and field-of-view constraint atoms here |
* [left as an exercise for the reader; here's the basic idea:] |
*/ |
if (false) { |
QTVRAngleRangeAtom myPanRangeAtom; |
myPanRangeAtom.minimumAngle = EndianF32_NtoB( 0.0); |
myPanRangeAtom.maximumAngle = EndianF32_NtoB(270.0); |
err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRPanConstraintAtomType, 1, 1, sizeof(QTVRAngleRangeAtom), &myPanRangeAtom, NULL); |
} |
bail: |
*thePanoSample = (err == noErr) ? panoSample : NULL; |
return(err); |
} |
/******************************************************************************* |
* InitMyPanoDesc |
* |
* Create a (version 2.0) panorama track, appending the given video tracks. |
*******************************************************************************/ |
static void |
InitMyPanoDesc(MyPanoTrackDescriptor *myPanoDesc, long versionToCreate) |
{ |
myPanoDesc->versionToCreate = versionToCreate; |
myPanoDesc->imageTileWidth = 0; |
myPanoDesc->imageTileHeight = 0; |
myPanoDesc->imageRefTrackIndex = 0; |
myPanoDesc->hotSpotTileWidth = 0; |
myPanoDesc->hotSpotTileHeight = 0; |
myPanoDesc->hotSpotRefTrackIndex = 0; |
myPanoDesc->fastStartTileWidth = 0; |
myPanoDesc->fastStartTileHeight = 0; |
myPanoDesc->fastStartRefTrackIndex = 0; |
} |
/******************************************************************************* |
* CreatePanoTrackFromMovies |
* |
* Create a (version 2.0) panorama track, appending the given video tracks. |
*******************************************************************************/ |
static OSErr |
CreatePanoTrackFromMovies( |
const FSSpec *srcMovieSpec, |
const FSSpec *srcHSMovieSpec, |
const FSSpec *srcFSMovieSpec, |
const VRMakeQTVRParams *qtvrParams, |
long versionToCreate, |
Movie dstMovie, |
Track qtvrTrack, |
Track *thePanoTrack |
) |
{ |
Track panoTrack = NULL; |
Track imageTrack = NULL; |
Track hsImageTrack = NULL; |
Track fsImageTrack = NULL; |
Media panoMedia = NULL; |
SampleDescriptionHandle sampleDesc = NULL; |
QTAtomContainer myPanoSample = NULL; |
OSErr err = noErr; |
VRMakeQTVRParams myParams = *qtvrParams; |
MyPanoTrackDescriptor myPanoDesc; |
/* Initialize myPanoDesc */ |
InitMyPanoDesc(&myPanoDesc, versionToCreate); |
/* Create a new pano track and media */ |
panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0); |
if (panoTrack == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0); |
if (panoMedia == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
/* Create a fast start image track, if given; a reference to it is contained in the pano track */ |
if ((srcFSMovieSpec != NULL) && (srcFSMovieSpec->name[0] != 0)) { |
err = ImportFirstVideoTrackFromFile(srcFSMovieSpec, dstMovie, myParams.trackDuration, 0, |
&myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create a panorama image track; a reference to it is contained in the pano track */ |
err = ImportFirstVideoTrackFromFile(srcMovieSpec, dstMovie, myParams.trackDuration, 0, |
&myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack |
); |
if (err != noErr) |
goto bail; |
/* Create a hot spot image track, if given; a reference to it is contained in the pano track */ |
if ((srcHSMovieSpec != NULL) && (srcHSMovieSpec->name[0] != 0)) { |
err = ImportFirstVideoTrackFromFile(srcHSMovieSpec, dstMovie, myParams.trackDuration, 0, |
&myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create track references from QTVR track to panorama track, |
* and from the panorama track to the panorama image track and the hot spot image track |
*/ |
if (panoTrack != NULL) AddTrackReference(qtvrTrack, panoTrack, kQTVRPanoramaType, NULL); |
if (imageTrack != NULL) AddTrackReference(panoTrack, imageTrack, kQTVRImageTrackRefType, &myPanoDesc.imageRefTrackIndex); |
if (hsImageTrack != NULL) AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex); |
if (fsImageTrack != NULL) AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType, &myPanoDesc.fastStartRefTrackIndex); |
/* Create a sample description; this contains no real info, but AddMediaSample requires it. */ |
sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription)); |
/* Check pano parameters */ |
VerifyPanoParameters(&myParams, &myPanoDesc); |
/* Create panorama media sample and add it to the panorama track */ |
err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample); |
if (err != noErr) |
goto bail; |
BeginMediaEdits(panoMedia); |
err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL); |
if (err != noErr) |
goto bail; |
EndMediaEdits(panoMedia); |
err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1); |
QTDisposeAtomContainer(myPanoSample); |
DisposeHandle((Handle)sampleDesc); |
bail: |
*thePanoTrack = panoTrack; |
return(err); |
} |
/******************************************************************************* |
* CreatePanoTrackFromPicts |
* |
* Create a (version 2.0) panorama track, appending the given Picts. |
*******************************************************************************/ |
static OSErr |
CreatePanoTrackFromPicts( |
long numFaces, |
FSSpecHandle srcPictSpecs, /* Source images */ |
FSSpecHandle srcHSPictSpecs, /* Hot spot images */ |
FSSpecHandle srcFSPictSpecs, /* Fast start images */ |
long versionToCreate, |
const VRMakeQTVRParams *qtvrParams, |
Movie dstMovie, |
Track qtvrTrack, |
Track *thePanoTrack |
) |
{ |
Track imageTrack = NULL; |
Track hsImageTrack = NULL; |
Track fsImageTrack = NULL; |
Track panoTrack = NULL; |
Media panoMedia = NULL; |
long imageRefIndex = 0; |
long hsRefIndex = 0; |
long fsRefIndex = 0; |
SampleDescriptionHandle sampleDesc = NULL; |
QTAtomContainer myPanoSample = NULL; |
OSErr err = noErr; |
long overlapTiles = (long)(versionToCreate == kQTVRCubicVersion1); |
VRMakeQTVRParams myParams = *qtvrParams; |
MyPanoTrackDescriptor myPanoDesc; |
/* Initialize myPanoDesc */ |
InitMyPanoDesc(&myPanoDesc, versionToCreate); |
/* Create a new pano track and media */ |
panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0); |
if (panoTrack == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0); |
if (panoTrack == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
/* Create a fast start image track, if given; a reference to it is contained in the pano track */ |
if (HasOneFSSpec(srcFSPictSpecs)) { |
err = TilePictsToNewTrack(numFaces, srcFSPictSpecs, overlapTiles, myParams.previewCodec, myParams.previewQuality, |
myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration, |
&myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create a panorama image track; a reference to it is contained in the pano track */ |
err = TilePictsToNewTrack(numFaces, srcPictSpecs, overlapTiles, myParams.tileCodec, myParams.tileQuality, |
myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration, |
&myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack |
); |
if (err != noErr) |
goto bail; |
/* Create a hot spot image track, if given; a reference to it is contained in the pano track */ |
if (HasOneFSSpec(srcHSPictSpecs)) { |
err = TilePictsToNewTrack(numFaces, srcHSPictSpecs, overlapTiles, kGraphicsCodecType, codecLosslessQuality, |
myParams.tilesH, myParams.tilesV, 8, dstMovie, myParams.trackDuration, |
&myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create track references from QTVR track to panorama track, |
* and from the panorama track to the panorama image track and the hot spot image track |
*/ |
if (panoTrack != NULL) AddTrackReference(qtvrTrack, panoTrack, kQTVRPanoramaType, NULL); |
if (imageTrack != NULL) AddTrackReference(panoTrack, imageTrack, kQTVRImageTrackRefType, &myPanoDesc.imageRefTrackIndex); |
if (hsImageTrack != NULL) AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex); |
if (fsImageTrack != NULL) AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType, &myPanoDesc.fastStartRefTrackIndex); |
/* Create a sample description; this contains no real info, but AddMediaSample requires it. */ |
sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription)); |
/* Check pano parameters */ |
VerifyPanoParameters(&myParams, &myPanoDesc); |
/* Create panorama media sample and add it to the panorama track */ |
err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample); |
if (err != noErr) |
goto bail; |
BeginMediaEdits(panoMedia); |
err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL); |
if (err != noErr) |
goto bail; |
EndMediaEdits(panoMedia); |
err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1); |
bail: |
if (myPanoSample != NULL) QTDisposeAtomContainer(myPanoSample); |
if (sampleDesc != NULL) DisposeHandle((Handle)sampleDesc); |
*thePanoTrack = panoTrack; |
return(err); |
} |
/******************************************************************************* |
* CreatePanoTrackFromGWorlds |
* |
* Create a (version 2.0) panorama track, appending the given GWorlds. |
*******************************************************************************/ |
static OSErr |
CreatePanoTrackFromGWorlds( |
long numFaces, |
GWorldPtr *panoGWs, |
GWorldPtr *hsGWs, |
GWorldPtr *fsGWs, |
long versionToCreate, |
const VRMakeQTVRParams *qtvrParams, |
Movie dstMovie, |
Track qtvrTrack, |
Track *thePanoTrack |
) |
{ |
Track imageTrack = NULL; |
Track hsImageTrack = NULL; |
Track fsImageTrack = NULL; |
Track panoTrack = NULL; |
Media panoMedia = NULL; |
long imageRefIndex = 0; |
long hsRefIndex = 0; |
long fsRefIndex = 0; |
SampleDescriptionHandle sampleDesc = NULL; |
QTAtomContainer myPanoSample = NULL; |
OSErr err = noErr; |
long overlapTiles = (long)(versionToCreate == kQTVRCubicVersion1); |
VRMakeQTVRParams myParams = *qtvrParams; |
MyPanoTrackDescriptor myPanoDesc; |
/* Initialize myPanoDesc */ |
InitMyPanoDesc(&myPanoDesc, versionToCreate); |
/* Create a new pano track and media */ |
panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0); |
if (panoTrack == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0); |
if (panoTrack == NULL) { |
err = GetMoviesError(); |
goto bail; |
} |
/* Create a fast start image track, if given; a reference to it is contained in the pano track */ |
if ((fsGWs != NULL) && (fsGWs[0] != NULL)) { |
err = TileGWorldsToNewTrack(numFaces, fsGWs, overlapTiles, myParams.previewCodec, myParams.previewQuality, |
myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration, |
&myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create a panorama image track; a reference to it is contained in the pano track */ |
err = TileGWorldsToNewTrack(numFaces, panoGWs, overlapTiles, myParams.tileCodec, myParams.tileQuality, |
myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration, |
&myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack |
); |
if (err != noErr) |
goto bail; |
/* Create a hot spot image track, if given; a reference to it is contained in the pano track */ |
if ((hsGWs != NULL) && (hsGWs[0] != NULL)) { |
err = TileGWorldsToNewTrack(numFaces, hsGWs, overlapTiles, kGraphicsCodecType, codecLosslessQuality, |
myParams.tilesH, myParams.tilesV, 8, dstMovie, myParams.trackDuration, |
&myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack |
); |
if (err != noErr) |
goto bail; |
} |
/* Create track references from QTVR track to panorama track, |
* and from the panorama track to the panorama image track and the hot spot image track |
*/ |
if (panoTrack != NULL) AddTrackReference(qtvrTrack, panoTrack, kQTVRPanoramaType, NULL); |
if (imageTrack != NULL) AddTrackReference(panoTrack, imageTrack, kQTVRImageTrackRefType, &myPanoDesc.imageRefTrackIndex); |
if (hsImageTrack != NULL) AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex); |
if (fsImageTrack != NULL) AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType, &myPanoDesc.fastStartRefTrackIndex); |
/* Create a sample description; this contains no real info, but AddMediaSample requires it. */ |
sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription)); |
/* Check pano parameters */ |
VerifyPanoParameters(&myParams, &myPanoDesc); |
/* Create panorama media sample and add it to the panorama track */ |
err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample); |
if (err != noErr) |
goto bail; |
BeginMediaEdits(panoMedia); |
err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL); |
if (err != noErr) |
goto bail; |
EndMediaEdits(panoMedia); |
err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1); |
bail: |
if (myPanoSample != NULL) QTDisposeAtomContainer(myPanoSample); |
if (sampleDesc != NULL) DisposeHandle((Handle)sampleDesc); |
*thePanoTrack = panoTrack; |
return(err); |
} |
#pragma mark - |
#pragma mark ### Cylindrical API ### |
#pragma mark - |
/******************************************************************************** |
******************************************************************************** |
******************************************************************************** |
*** API |
******************************************************************************** |
******************************************************************************** |
********************************************************************************/ |
/******************************************************************************* |
* VRMovieToQTVRCylPano1x0 |
* Create a single-node panoramic QTVR movie from the specified tile movie(s). |
* |
* NOTE: This function builds a movie that conforms to version 1.0 of the QuickTime VR file format. |
* |
* The newly-created movie contains references to the original tile movie, not the actual movie data. |
* We do this because we assume that the caller will flatten the movie into a third movie, which will |
* contain the movie data. Also, the interim file is much smaller than it would be if we copied the data, |
* thus saving time and disk space. |
*******************************************************************************/ |
OSErr |
VRMovieToQTVRCylPano1x0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcTileSpec, /* Cylindrical tile movie for the panoramic image */ |
FSSpec *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
PanoramaDescriptionHandle myPanoDesc = NULL; |
PanoSampleHeaderAtomHandle myPanoHeader = NULL; |
HotSpotTableAtomHandle myHotSpotTable = NULL; |
StringTableAtomHandle myStringTable = NULL; |
short myResRefNum = -1; |
short myResID = movieInDataForkResID; |
short myHSResRefNum = -1; |
short myTilResRefNum = -1; |
short myHSTilResRefNum = -1; |
Movie myMovie = NULL; |
Movie myTileMovie = NULL; |
Movie myHSTileMovie = NULL; |
Track myPanoTrack; |
Media myPanoMedia; |
Track myImageTrack; |
Track myHSImageTrack; |
Fixed myTheta; |
UInt16 myIndex; |
long myTrackWidth, myTrackHeight; |
ComponentResult err = noErr; |
/* |
* create a new movie |
*/ |
/* create a movie file for the destination movie */ |
err = CreateMovieFile(dstMovieSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &myMovie); |
if (err != noErr) |
goto bail; |
/* |
* copy the video track from the tile movie to the new movie; this is the "scene track" |
*/ |
/* open the tile movie file */ |
err = ImportFirstVideoTrackFromFile(srcTileSpec, myMovie, qtvrParams->trackDuration, 0, &myTrackWidth, &myTrackHeight, &myImageTrack); |
if (err != noErr) |
goto bail; |
/* |
* copy the hot spot image track from the tile movie to the new movie |
*/ |
if ((srcHSTileSpec != NULL) && (srcHSTileSpec->name[0] != 0)) { |
err = ImportFirstVideoTrackFromFile(srcHSTileSpec, myMovie, qtvrParams->trackDuration, 0, &myTrackWidth, &myTrackHeight, &myHSImageTrack); |
if (err != noErr) |
goto bail; |
} |
/* |
* create a panorama track and add it to the movie |
*/ |
/* create a panorama track and media */ |
myPanoTrack = NewMovieTrack(myMovie, qtvrParams->windowWidth << 16, qtvrParams->windowHeight << 16, 0); |
myPanoMedia = NewTrackMedia(myPanoTrack, kQTVROldPanoType, kQTVRStandardTimeScale, NULL, 0); |
if (((err = GetMoviesError()) != noErr) || (myPanoTrack == NULL) || (myPanoMedia == NULL)) |
goto bail; |
/* create a panorama sample description */ |
myPanoDesc = (PanoramaDescriptionHandle)MyNewHandleClear(sizeof(PanoramaDescription)); |
if (myPanoDesc == NULL) |
goto bail; |
/* fill in the panorama sample description; |
* all the data in this sample description that follows the first 4 long words must be in big-endian format; |
* the first four long words are used by AddMediaSample and must therefore be in native-endian format |
*/ |
(**myPanoDesc).size = sizeof(PanoramaDescription); |
(**myPanoDesc).type = kPanDescType; |
(**myPanoDesc).reserved1 = 0L; |
(**myPanoDesc).reserved2 = 0L; |
(**myPanoDesc).majorVersion = EndianU16_NtoB(0); |
(**myPanoDesc).minorVersion = EndianU16_NtoB(0); |
(**myPanoDesc).sceneTrackID = EndianU32_NtoB(GetTrackID(myImageTrack)); |
(**myPanoDesc).loResSceneTrackID = EndianU32_NtoB(0L); /* no lo-res video track */ |
for (myIndex = 1; myIndex < 6; myIndex++) { |
(**myPanoDesc).reserved3[myIndex] = EndianU32_NtoB(0L); |
(**myPanoDesc).reserved4[myIndex] = EndianU32_NtoB(0L); |
} |
(**myPanoDesc).hotSpotTrackID = EndianU32_NtoB(GetTrackID(myHSImageTrack)); |
(**myPanoDesc).loResHotSpotTrackID = EndianU32_NtoB(0L); /* no lo-res hot spot track */ |
myTheta = 180.0 * (atan((myTrackWidth * qtvrParams->tilesH) * D_PI / (myTrackHeight * qtvrParams->tilesV))) / D_PI * 65536.0; |
(**myPanoDesc).hPanStart = EndianF32NtoFixedB(qtvrParams->minPan); |
(**myPanoDesc).hPanEnd = EndianF32NtoFixedB(qtvrParams->maxPan); |
(**myPanoDesc).vPanTop = EndianF32NtoFixedB(qtvrParams->maxTilt); |
(**myPanoDesc).vPanBottom = EndianF32NtoFixedB(qtvrParams->minTilt); |
(**myPanoDesc).minimumZoom = EndianF32NtoFixedB(qtvrParams->minFieldOfView); |
(**myPanoDesc).maximumZoom = EndianF32NtoFixedB(qtvrParams->maxFieldOfView); |
(**myPanoDesc).sceneSizeX = EndianU32_NtoB((long)(myTrackWidth * qtvrParams->tilesH)); |
(**myPanoDesc).sceneSizeY = EndianU32_NtoB((long)(myTrackHeight * qtvrParams->tilesV)); |
(**myPanoDesc).numFrames = EndianU32_NtoB((long)qtvrParams->tilesV); |
(**myPanoDesc).sceneNumFramesX = EndianU16_NtoB(qtvrParams->tilesH); |
(**myPanoDesc).sceneNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
(**myPanoDesc).sceneColorDepth = EndianU16_NtoB(32); |
(**myPanoDesc).hotSpotSizeX = EndianU32_NtoB((long)(myTrackWidth * qtvrParams->tilesH)); |
(**myPanoDesc).hotSpotSizeY = EndianU32_NtoB((long)(myTrackHeight * qtvrParams->tilesV)); |
(**myPanoDesc).hotSpotNumFramesX = EndianU16_NtoB(qtvrParams->tilesH); |
(**myPanoDesc).hotSpotNumFramesY = EndianU16_NtoB(qtvrParams->tilesV); |
(**myPanoDesc).hotSpotColorDepth = EndianU16_NtoB(8); |
/* create a panorama header atom; |
* the data in this atom (which is *not* a QTAtom) must be in big-endian format |
*/ |
myPanoHeader = (PanoSampleHeaderAtomHandle)MyNewHandleClear(sizeof(PanoSampleHeaderAtom)); |
if (myPanoHeader == NULL) |
goto bail; |
(**myPanoHeader).size = EndianU32_NtoB(sizeof(PanoSampleHeaderAtom)); |
(**myPanoHeader).type = EndianU32_NtoB(kPanHeaderType); |
(**myPanoHeader).nodeID = EndianU32_NtoB(0L); |
/* set the default pan, tilt, and zoom */ |
(**myPanoHeader).defHPan = EndianF32NtoFixedB(qtvrParams->defaultPan); |
(**myPanoHeader).defVPan = EndianF32NtoFixedB(qtvrParams->defaultTilt); |
(**myPanoHeader).defZoom = EndianF32NtoFixedB(qtvrParams->defaultFieldOfView); |
/* set constraints for this node; use 0 for the default constraints */ |
(**myPanoHeader).minHPan = EndianS32_NtoB(0L); |
(**myPanoHeader).minVPan = EndianS32_NtoB(0L); |
(**myPanoHeader).maxHPan = EndianS32_NtoB(0L); |
(**myPanoHeader).maxVPan = EndianS32_NtoB(0L); |
(**myPanoHeader).maxZoom = EndianS32_NtoB(0L); |
(**myPanoHeader).nameStrOffset = EndianU32_NtoB(0L); /* no name or comment */ |
(**myPanoHeader).commentStrOffset = EndianU32_NtoB(0L); |
/* create a string table atom; |
* the data in this atom (which is *not* a QTAtom) must be in big-endian format |
*/ |
myStringTable = (StringTableAtomHandle)MyNewHandleClear(sizeof(StringTableAtom)); |
if (myStringTable == NULL) |
goto bail; |
(**myStringTable).size = EndianU32_NtoB(sizeof(StringTableAtom)); |
(**myStringTable).type = EndianU32_NtoB(kStringTableType); |
(**myStringTable).bunchOstrings[0] = EndianU32_NtoB(0); |
/* create a hot spot table atom and expand the string table atom to include the |
* hot spot names and comments |
*/ |
myHotSpotTable = MakeQTVRHotSpot1x0(myStringTable); |
/* append the hot spot table atom and the string table atom to the panorama header atom |
* [left as an exercise for the reader] |
*/ |
/* Add the media sample to the panorama track */ |
BeginMediaEdits(myPanoMedia); |
err = AddMediaSample(myPanoMedia, (Handle)myPanoHeader, 0, GetHandleSize((Handle)myPanoHeader), qtvrParams->trackDuration, (SampleDescriptionHandle)myPanoDesc, 1, 0, NULL); |
if (err != noErr) |
goto bail; |
EndMediaEdits(myPanoMedia); |
err = InsertMediaIntoTrack(myPanoTrack, 0, 0, qtvrParams->trackDuration, fixed1); |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(myMovie, kQTVROldPanoType); |
if (err != noErr) |
goto bail; |
/* Add the movie resource to the panorama movie */ |
err = AddMovieResource(myMovie, myResRefNum, &myResID, NULL); |
bail: |
if (myPanoDesc != NULL) DisposeHandle((Handle)myPanoDesc); |
if (myPanoHeader != NULL) DisposeHandle((Handle)myPanoHeader); |
if (myResRefNum != -1) CloseMovieFile(myResRefNum); |
if (myHSResRefNum != -1) CloseMovieFile(myHSResRefNum); |
if (myMovie != NULL) DisposeMovie(myMovie); |
if (myTilResRefNum != -1) CloseMovieFile(myTilResRefNum); |
if (myHSTilResRefNum != -1) CloseMovieFile(myHSTilResRefNum); |
if (myTileMovie != NULL) DisposeMovie(myTileMovie); |
if (myHSTileMovie != NULL) DisposeMovie(myHSTileMovie); |
return(err); |
} |
/******************************************************************************* |
* VRMovieToQTVRCylPano2 |
* Create a single-node panoramic QTVR movie from the specified tile movie(s). |
* |
* NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format. |
* |
* The newly-created movie contains references to the original tile movie, not the actual movie data. |
* We do this because we assume that the caller will flatten the movie into a third movie, which will |
* contain the movie data. Also, the interim file is much smaller than it would be if we copied the data, |
* thus saving time and disk space. |
*******************************************************************************/ |
static OSErr |
VRMovieToQTVRCylPano2( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
long panoType, /* Parameters used to create the movie */ |
FSSpec *srcTileSpec, /* Cylindrical tile movie for the panoramic image */ |
FSSpec *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */ |
FSSpec *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
short myResRefNum = -1; |
Movie tmpMovie = NULL; |
Track myQTVRTrack; |
Track myPanoTrack; |
ComponentResult err = noErr; |
FSSpec tmpSpec; |
/* Create a movie file for the destination movie */ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
err = CreatePanoTrackFromMovies(srcTileSpec, srcHSTileSpec, srcFSTileSpec, |
qtvrParams, panoType, tmpMovie, myQTVRTrack, |
&myPanoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (myResRefNum != -1) CloseMovieFile(myResRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
/******************************************************************************* |
* VRMovieToQTVRCylPano2v0 |
* Create a single-node panoramic QTVR movie from the specified tile movie(s). |
* |
* NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format. |
* |
* The newly-created movie contains references to the original tile movie, not the actual movie data. |
* We do this because we assume that the caller will flatten the movie into a third movie, which will |
* contain the movie data. Also, the interim file is much smaller than it would be if we copied the data, |
* thus saving time and disk space. |
*******************************************************************************/ |
OSErr |
VRMovieToQTVRCylPano2v0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcTileSpec, /* Cylindrical tile movie for the panoramic image */ |
FSSpec *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */ |
FSSpec *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRMovieToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcTileSpec, srcHSTileSpec, srcFSTileSpec, dstMovieSpec)); |
} |
/******************************************************************************* |
* VRMovieToQTVRCylPano2h0 |
* |
* Create a single-node QuickTime VR panoramic movie from the specified image file. |
*******************************************************************************/ |
OSErr |
VRMovieToQTVRCylPano2h0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcTileSpec, /* Cylindrical tile movie for the panoramic image */ |
FSSpec *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */ |
FSSpec *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRMovieToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcTileSpec, srcHSTileSpec, srcFSTileSpec, dstMovieSpec)); |
} |
/******************************************************************************* |
* VRPictToQTVRCylPano2 |
* |
* Create a single-node QuickTime VR panoramic movie from the specified image file. |
*******************************************************************************/ |
static OSErr |
VRPictToQTVRCylPano2( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
long panoType, /* Version 1, or version 2 (recommended) */ |
FSSpec *srcPictSpec, /* Source image */ |
FSSpec *srcHSPictSpec, /* Hot spot image */ |
FSSpec *srcFSPictSpec, /* Fast start image */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
short myResRefNum = -1; |
Movie tmpMovie = NULL; |
Track myQTVRTrack; |
Track myPanoTrack; |
ComponentResult err = noErr; |
FSSpec *srcH, *hsH, *fsH; |
FSSpec tmpSpec; |
/* Create a movie file for the destination movie */ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
srcH = srcPictSpec; hsH = srcHSPictSpec; fsH = srcFSPictSpec; |
err = CreatePanoTrackFromPicts(1, &srcH, (hsH ? &hsH : NULL), (fsH ? &fsH : NULL), |
panoType, qtvrParams, tmpMovie, myQTVRTrack, |
&myPanoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (myResRefNum != -1) CloseMovieFile(myResRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
/******************************************************************************* |
* VRPictToQTVRCylPano2v0 |
*******************************************************************************/ |
OSErr |
VRPictToQTVRCylPano2v0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcPictSpec, /* Source image, rotated 90 degrees counter-clockwise (classic orientation) */ |
FSSpec *srcHSPictSpec, /* Hot spot image, rotated 90 degrees counter-clockwise (classic orientation) */ |
FSSpec *srcFSPictSpec, /* Fast start image, rotated 90 degrees counter-clockwise (classic orientation) */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRPictToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcPictSpec, srcHSPictSpec, srcFSPictSpec, dstMovieSpec)); |
} |
/******************************************************************************* |
* VRPictToQTVRCylPano2h0 |
*******************************************************************************/ |
OSErr |
VRPictToQTVRCylPano2h0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcPictSpec, /* Source image, non-rotated (modern orientation) */ |
FSSpec *srcHSPictSpec, /* Hot spot image, non-rotated (modern orientation)) */ |
FSSpec *srcFSPictSpec, /* Fast start image, non-rotated (modern orientation)) */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRPictToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcPictSpec, srcHSPictSpec, srcFSPictSpec, dstMovieSpec)); |
} |
/******************************************************************************* |
* VRGWorldToQTVRCylPano2 |
* |
* Create a single-node QuickTime VR panoramic movie from the specified image file. |
*******************************************************************************/ |
static OSErr |
VRGWorldToQTVRCylPano2( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
long panoType, /* Version 1, or version 2 (recommended) */ |
GWorldPtr srcGW, /* Source GWorld */ |
GWorldPtr srcHSGW, /* Source hot spot GWorld */ |
GWorldPtr srcFSGW, /* Source fast start GWorld */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
short myResRefNum = -1; |
Movie tmpMovie = NULL; |
Track myQTVRTrack; |
Track myPanoTrack; |
ComponentResult err; |
GWorldPtr srGW, hsGW, fsGW; |
FSSpec tmpSpec; |
/* Create a movie file for the destination movie */ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
srGW = srcGW; hsGW = srcHSGW; fsGW = srcFSGW; |
err = CreatePanoTrackFromGWorlds(1, &srcGW, (hsGW ? &hsGW : NULL), (fsGW ? &fsGW : NULL), |
panoType, qtvrParams, tmpMovie, myQTVRTrack, |
&myPanoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (myResRefNum != -1) CloseMovieFile(myResRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
/******************************************************************************* |
* VRGWorldToQTVRCylPano2v0 |
*******************************************************************************/ |
OSErr |
VRGWorldToQTVRCylPano2v0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
GWorldPtr srcGW, /* Source GWorld, rotated 90 degrees counter-clockwise (classic orientation) */ |
GWorldPtr srcHSGW, /* Source hot spot GWorld, rotated 90 degrees counter-clockwise (classic orientation) */ |
GWorldPtr srcFSGW, /* Source fast start GWorld, rotated 90 degrees counter-clockwise (classic orientation) */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRGWorldToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcGW, srcHSGW, srcFSGW, dstMovieSpec)); |
} |
/******************************************************************************* |
* VRGWorldToQTVRCylPano2h0 |
*******************************************************************************/ |
OSErr |
VRGWorldToQTVRCylPano2h0( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
GWorldPtr srcGW, /* Source GWorld, non-rotated (modern orientation) */ |
GWorldPtr srcHSGW, /* Source hot spot GWorld, non-rotated (modern orientation) */ |
GWorldPtr srcFSGW, /* Source fast start GWorld, non-rotated (modern orientation) */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
return(VRGWorldToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcGW, srcHSGW, srcFSGW, dstMovieSpec)); |
} |
#pragma mark - |
#pragma mark ### Cubic API ### |
#pragma mark - |
/******************************************************************************* |
* VRMovieToQTVRCubicPano |
* |
* Create a single-node cubic panoramic QTVR movie from the specified 6-frame movie. |
*******************************************************************************/ |
OSErr |
VRMovieToQTVRCubicPano( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpec *srcFramesSpec, /* Source movie with the panorama faces */ |
FSSpec *srcHSFramesSpec, /* Source movie with the hot spot faces */ |
FSSpec *srcFSFramesSpec, /* Source movie with the fast start faces */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
FSSpec tmpSpec; |
short myResRefNum = -1; |
Movie tmpMovie = NULL; |
Track tmpQTVRTrack; |
Track tmpPanoTrack; |
ComponentResult err; |
/* Create a temporary version of the panorama movie file, |
* located in the same directory as the destination panorama movie file |
*/ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &tmpQTVRTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
err = CreatePanoTrackFromMovies(srcFramesSpec, srcHSFramesSpec, srcFSFramesSpec, |
qtvrParams, kQTVRCubicVersion1, tmpMovie, tmpQTVRTrack, |
&tmpPanoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (myResRefNum != -1) CloseMovieFile(myResRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
/******************************************************************************** |
* VRPictsToQTVRCubicPano |
* |
* This converts a set of 6 picture files to a cubic QuickTime VR panorama movie. |
* |
* This implements tiling of the faces, but the face dimensions must be appropriately divisible: |
* (width - tilesH + 1) / tilesH = integer |
* (height - tilesV + 1) / tilesV = integer |
* e.g. {dim=512,tiles=1}, {dim=511,tiles=2}, {dim=510,tiles=3} |
********************************************************************************/ |
OSErr |
VRPictsToQTVRCubicPano( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
FSSpecHandle srcPictSpecs, /* Source images */ |
FSSpecHandle srcHSPictSpecs, /* Hot spot images */ |
FSSpecHandle srcFSPictSpecs, /* Fast start images */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
FSSpec tmpSpec; |
short tmpRefNum = -1; |
Movie tmpMovie = NULL; |
Track qtvrTrack = NULL; |
Track panoTrack = NULL; |
ComponentResult err; |
/* Create a temporary version of the panorama movie file, |
* located in the same directory as the destination panorama movie file |
*/ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &tmpRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &qtvrTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
err = CreatePanoTrackFromPicts(6, srcPictSpecs, srcHSPictSpecs, srcFSPictSpecs, kQTVRCubicVersion1, |
qtvrParams, tmpMovie, qtvrTrack, &panoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (tmpRefNum != -1) CloseMovieFile(tmpRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
/******************************************************************************** |
* VRGWorldsToQTVRCubicPano |
* |
* Given 6 GWorlds (and possible 6 hotspot GWorlds), create a cubic panorama movie. |
* |
* This implements tiling of the faces, but the face dimensions must be appropriately divisible: |
* (width - tilesH + 1) / tilesH = integer |
* (height - tilesV + 1) / tilesV = integer |
* e.g. {dim=512,tiles=1}, {dim=511,tiles=2}, {dim=510,tiles=3} |
********************************************************************************/ |
OSErr |
VRGWorldsToQTVRCubicPano( |
VRMakeQTVRParams *qtvrParams, /* Parameters used to create the movie */ |
GWorldPtr *srcGWs, /* 6 Source GWorlds - in standard order */ |
GWorldPtr *srcHSGWs, /* 6 Source hot spot GWorlds - in standard order */ |
GWorldPtr *srcFSGWs, /* 6 Source fast start GWorlds - in standard order */ |
FSSpec *dstMovieSpec /* Destination movie */ |
) |
{ |
FSSpec tmpSpec; |
short tmpRefNum = -1; |
Movie tmpMovie = NULL; |
Track qtvrTrack = NULL; |
Track panoTrack = NULL; |
ComponentResult err; |
/* Create a temporary version of the panorama movie file, |
* located in the same directory as the destination panorama movie file |
*/ |
MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec); |
err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &tmpRefNum, &tmpMovie); |
if (err != noErr) |
goto bail; |
/* Create the QTVR movie track and media */ |
err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &qtvrTrack); |
if (err != noErr) |
goto bail; |
/* Create panorama track and media, and add them to the movie */ |
err = CreatePanoTrackFromGWorlds(6, srcGWs, srcHSGWs, srcFSGWs, kQTVRCubicVersion1, |
qtvrParams, tmpMovie, qtvrTrack, &panoTrack |
); |
if (err != noErr) |
goto bail; |
/* Add a user data item that identifies the QTVR movie controller */ |
err = SetQTControllerType(tmpMovie, kQTVRQTVRType); |
if (err != noErr) |
goto bail; |
/* Create the final, flattened movie, from the temporary file into a new movie file; |
* put the movie resource first so that FastStart is possible |
*/ |
err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec); |
bail: |
if (tmpRefNum != -1) CloseMovieFile(tmpRefNum); |
if (tmpMovie != NULL) DisposeMovie(tmpMovie); |
DeleteMovieFile(&tmpSpec); |
return(err); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14