QTVRLingoEngine

©2000/01 Joachim Baur, Medienwerkstatt Schorndorf

 

A short table of contents:

 

Update of April 30th, 2001

• improved lingo (faster rendering and, even more important, now absolutely correct dewarping of the panorama!).
The "pXcorrInit"- und "pYcorrInit"-properties set in the "init...room"-script are now obsolete, as all the neccessary calculations can be done (with the correct math ;-) using the panorama-dimensions only.

The update has of course been integrated into the main project, downloadable further below, but can also be applied separately for those of you who already dowloaded the 3+MBs of the prior (first) version. Click on these links to download just the updated "QTVRLingo"-castlib (just paste it over the existing one):

Macintosh Version (.sit-Archiv, 10 KB)
Windows Version (.zip-Archiv, 12 KB)

 

1. Introduction

The QTVRLingoEngine is a small collection of scripts for rendering interactive cylindrical QTVR-panoramas using Macromedia Director without the installation of QuickTime or any additional Xtras.

This is especially useful for web-based panos, other benefits are:

AND

The QTVRLingoEngine can NOT display cubicVRs, as integrated in QuickTime 5.
For this purpose, please take a look at the famous ShockTimeVR by Nonoche.

 

A CAVEAT FIRST:

This documentation is not in any way complete or self-explanatory ;-) It is meant to explain the basic principles and functions, but you should know your way around lingo if you want to really understand how it works or modify the engine for your own purposes! Sorry that I didn't comment on more of the code and the underlying math, but this is a non-commercial sparetime-project. Pay me and I'll answer anything ;-)

I had the urge to code the QTVRLingoEngine because as a graphic designer I was partly unsatisfied with the original QTVRs functionality, render methods and navigation methods.
So I wanted to have panos with a completely customized user-interface, with animations in them, sound etc, and all lingo-based for maximum flexibility...

If you want to see what it's all about and how it works, I have made a sample director movie with 2 panos in it (one 360° and one 180°) including hotspots and some animations to play with.

Click below to see the Shockwave 8 version in your browser:
QTVRLingoEngine_example.htm (300 KB)

You can download the unprotected source-movie of the .dcr here:
Macintosh version (.sit-archive, 1.3 MB)
Windows version (.zip-archive, 1.5 MB)

 

2. What is built into the example.dcr?

2.1 QTVR-controls

Klicking on the buttons

Pan Left Pan Right Tilt up Tilt Down Rotate Left/Right

causes the pano to move in that direction as long as the mouse is pressed.

You can also just click on the QTVR itself and drag in any direction to pan and tilt the pano.

A single click on this button

ShowHotSpots

shows the hotspot-areas (marked in blue) while the mouse is pressed.
If the ShowHotspots-button is doubleclicked, the hotspot-areas stay visible until the button is doubleclicked again.

SwingTo

The SwingTo-function calculates the shortest distance between the current and the target position of the panorama. The motion is not very smooth and can certainly be improved, if anybody wants to take a look at it...

 

2.2 Rendering Settings

Except for those QTVR-control buttons, there are three sets of radiobuttons that control general rendering properties:

the useFastQuads True/Flase

This sets the corresponding Director-property. The recalc-time should improve with this property set to true, more noticably so on slow machines ;-)

perspective correction None/Partial/Full

This controls the math that is applied when displaying the panorama:

correction = none

None

just draws the sourcepict-image with linear scaling - the edges within the panorama-image are bend, tilting just moves the pano up and down

Partial

corrects the edges in the pano, so the image looks right when panning to the left and right. The tilt buttons just move the pano up and down though

Full

corrects the the edges in the pano and applies perspective distortion when tilting. This is the default mode and most CPU-intense, of course

 

mask overlay on/off

The QTVRs edges are feathered - this is achieved by a greenish bitmap with a feathered hole (alpha channel) in the center. If this masking bitmap is hidden (Off), you can see the complete sprite-quads.

 

2.3 Built-in hotspot-actions to play with

The two lamps in the first (green) room can be switched on and off independently from each other by clicking on them. The phone is ringing when the pano is turned towards it (the volume depends on the current pan-position). If you click on the phone, it stops ringing. You can click on the door to go to another room (blue) with a transition. This second room is just a partial pano with no hotspot-functionality in it except for the door back to the green room.

 

3. How is the QTVR rendered?

The QTVR-sourcePict is cut into vertical slices using imaging lingo. In the example.dcr, the sourcePict was 440 pixels high and 1440 pixels wide for the complete 360° pano (green room). This sourcepict was then cut up into 72 slices, each one 20 pixels wide. 20 sprite-channels with a slice-member attached to each one of them at runtime are used to render the pano on stage.

For each panorama used in the director-movie a lingo-object is created that has its own properties, so it is also possible to display more than one panorama at the same time.

So let's take a tour of the example.dir...

 

3.1 First a short description of the castlibs:

Cast Internal Contains the interface elements that are used in the example.dir - flash-buttons (with built-in rollover- and active-states), text-fieldmembers and so on.

Cast Slices Contains the sliced up sourcePicts of the green room and the blue room.
The first 72 members (named greenRoom 1..72) were cut from the 360°-panorama of the green room, followed by 36 members (named blueRoom 1..36) that were cut from the blueRoom-sourcePict. The blue room is a partial panorma, covering only 180°.
After those you find the bitmaps that are used for the animations in the green room: the members named "greenRoomDark" consist of the slices with the two lamps turned off, the number after "greenRoomDark" shows their position within the original panorama (the floorlamp ranges from slice number 25 to slice 40 and the desklamp-light is visible in slices number 56 to 62. The other slices in the original sourcePict don't change when the lamps are switched off).
The bitmaps are each 20 pixels wide by 440 pixels high and have16bit colordepth. For the 300K-shockwave-version they were JPEG-compressed with a quality setting of 40.

Cast Hotspots Contains the sliced up hotspot-sourcepicts of the green room and the blue room. They have the same dimensions as the sourcePict-slices in the other cast (20 by 440 pixels) but their colordepth is 8bit. The palette of the bitmaps does not matter, but it is vital that "Trim White Space" is DISABLED when importing the hotspot-sourcePict!
First in the cast there are the 72 "greenRoomHotspots", followed by 72 "greenRoomHotspots ... showHS". Those are copies of the first 72 members, reduced to 1bit colordepth with all hotspotareas filled black. When the "ShowHotSpots"-button is clicked during runtime, these "...showHS"-members are brought onto the stage, and their sprites are assigned a blend value of 50 and a blue spritecolor - you can of course use any color you like to mark the hotspot-areas.

Cast QTVRLingo All scripts and behaviors that are used in the movie for rendering and animating the Lingo-QTVR.

 

3.2 The QTVRLingo-Cast

Script MovieScripts For each pano that is displayed on stage, on "startmovie" a different lingo-object (instance of the parentscript "QTVRLingoEngine") is created in memory. The references to these objects must be stored in global variables. To create such a QTVR-object, the following lines are necessary:

gBlueRoom = new(script "QTVRLingoEngine")
gCurrQTVR = gBlueRoom
initTheBlueRoom
gCurrQTVR.initNode()

The handler "initTheBlueRoom" sets all the "privately owned" properties of the blue room panorama. After these initial settings are complete, the "initNode()"-handler of the QTVR-object is called, which takes care of some other settings and calculations to prepare the display of this QTVR. The QTVR itself is not yet drawn on stage!

Script initBlueRoomScript initGreenRoom These scripts set properties of the QTVR-object stored in the global variable "gCurrQTVR". I know that this is not the proper way of coding objects (externally changing the internal properties of an object), but for convenience sake and because I'm a graphic designer and don't have to know about these things I decided that it is easiest for me to do it this way.

Script QTVRLingoEngine The "heart" of this whole thingie - here the actual math takes place that renders the QTVR on stage...

 

3.3 How the QTVR-controls work

3.3.1 Some button is pressed (e.g. "Pan Left")

The button-behaviors themselves do not initiate a recalculation/redrawing of the pano directly. They just set flags to indicate user-interaction. So the "Pan left"-behavior just sets the pPanFlag of the gCurrQTVR-object to 1 when the mouse is down (you can also set the flag on mouseEnter of course, to create buttons that respond to rollover rather than to clicks)

3.3.2 The "exitFrame"-handler of the "frameLoop"-script is called

All the different types of movement are checked one after the other to see if the corresponding flags are set and to recalculate the QTVR accordingly:

checkPanning()
checkTilting()
checkZooming()
checkRotation()

The QTVR on the stage is not yet updated!

If the pSwingFlag is set to 1 (there is an automatic "SwingTo"-motion happening) all the other button flags are ignored until the swing is complete.

3.3.3 The "QTVRredraw()"-handler of the gCurrQTVR-object is called if there were any changes/movements

The QTVR-display on stage is refreshed (similar to the "updateStage"-command)
If one of the check...()-handlers recalculates any values, it changes the pRedrawFlag of the gCurrQTVR-object to indicate that an "updateStage" of the QTVR is necessary. Depending on the type of movement, the pRedrawFlag is changed from #none to either #spritesOnly or #quadsAndSprites.
#spritesOnly means that only a "Pan"-action has happened, in this case it is not necessary to calculate any new sprite quads, just the members of the sprites are exchanged so the user gets the impression of the pano spinning to the left or right. All the other motions require a (more CPU-intense) recalculation of the sprite-quads, which is indicated by the #quadsAndSprites-state of the pRedrawFlag.

3.3.4 Checking for Hotspots

When "CheckHSRollOver" is called, the current mouseposition within the distorted spritequads has to be checked against the corresponding hotspot-slice to see if the mouse is over a hotspotarea. So after finding the corresponding hotspot-member (the 8bit-hotspot bitmap), "mapStagetoMember" is used to determine which pixel (x,y) the mouse is currently over. If the color of this pixel in the hotspot-bitmap is any other than white, we have hit a hotspot (the hotspot-number is determined by the palette index of the hotspot's color, therefore it doesn't matter WHICH palette the hotspot-bitmap uses)

3.3.5 Click-and-drag-functionality

In order for the familiar click-and-drag-spinning of a pano to work, the "SliceScript"-behavior must be attached to all the slice sprites. This behavior calls the "Trigger"-handler of the QTVR-object when the mouse is down on any of the slices. The handler first checks to see if the mouse was clicked on a hotspot and otherwise pans/tilts the pano in a repeat-loop towards the relative position of the mouse to the clickloc while the mouse is down.

 

4. Animations and hotspot-actions

What I missed most of all when working with Standard-QTVR-panoramas was an easy method of putting animations into the panorama-image - not just superimposed QuickTime-sprites or QTVR-objects, but background-animations that are rendered with the correct perspective/distortion when the user is panning, tilting and zooming the QTVR.

To make such animations possible, the QTVRLingoEngine uses an internal list (pSliceMemList) for reference to the single slice-sprite.members, so that any of the slices can be dynamically exchanged at any time.

4.1 Timed background animations

In order to create looping animations (e.g. the telephone ringing), it is easiest to create a timeout()-object that exchanges some members in the pSliceMemList each time it is called. The timeout-object for the phone is created in the "DisplayNode"-FrameScript:

timeout("telephone").new(500, #telephoneAnim)

so that it calls the "telephoneAnim"-handler (located in the "HotSpot Handlers"-MovieScript) every 500 milliSeconds. It plays the ringing sound once with each animation loop and changes the members for slices number 63, 64 and 65 with each pass.
As soon as the telephone is clicked (or the blue room is drawn on stage), the timeout-object is cleared and the animation stops.

4.2 Triggered animations / hotspot-actions

When the user clicks on a hotspot, the "hotSpotTriggerHandler" located in the "HotSpot Handlers"-MovieScript is called by the QTVR-object. The hotspot-properties are passed so the hotSpotTriggerHandler can decide what to do for each hotspot individually.

Hotspots can also be enabled/disabled at runtime by calling the "enableHS"-handler of the current QTVR-Object:

gCurrQTVR.enableHS(85,1) -- enables Hotspot number 85

gCurrQTVR.enableHS(85,0) -- disables Hotspot number 85

4.3 Transitions to other nodes

To prepare for a smooth node-switch, backbuffer-sprites are utilized. They are located in sprite-channels underneath the slice-sprites, so they are not visible as long as the slice-sprites are present on stage. The "switchNodes"-handler of the active QTVR-object fills the backbuffer with the slices of the new QTVR while copying the quads of the current slice-sprites and then jumps to the "goAnotherNode"-marker where the current QTVR slice-sprites are not present any more. This way, the new room can be entered using a transition and the exact same pan, tilt and fov-values of the current room.

 

5. How to use the QTVRLingoEngine for your own panoramas

Copy the "QTVRLingoEngine"-cast to your director-movie to start with

5.1 Setting up the Casts

Import your sourcePicts for the panorama and the hotspots (if there are any). I used separate castlibs for both sources, accordingly named "Slices" and "Hotspots". Please note when importing:

Once the sourcePicts are imported, use the "sliceSourcePict"-handler (located in the "Tools"-script of the "QTVRLingoEngine"-castlib) to let director automatically create the single slices using imaging lingo. Just type the following into the message window (substitute your own parameters for SliceCount, SrcPictMember and SrcPictCLib!):

sliceSourcePict SliceCount, SrcPictMember, SrcPictCLib

for example:
sliceSourcePict 72, 1, "Slices"
creates 72 new bitmap-slice-members out of member 1 of castlib "Slices"

The hotspot-slices are created using the same method.

After creating the Hotspot-Slices, it is necessary to create copies of those slices which are just black-and-white if you're planning to use the "ShowHotSpots"-function in your project (if not, don't bother) Type into the message window:

createShowHSMembers FirstHSmember, HotSpotCLib, SliceCount

for example:
createShowHSMembers 147, "Hotspots", 36
creates 36 new showHotspot-slice-member by duplicating the 36 hotspot-slice-members starting at member 147 of castlib "Hotspots"

There is another handler available that builds a list off all the hotspot-numbers present in your hotspot-sourcepict - useful, if you don't have a list of these numbers handy.
Type into the message window:

buildHSlist

And to complete the hotspot-section, it is of course possible to modify (or even create) the hotspot-sourcepict using director's own paint-window. Turn onion-skinning on to see the panorama-sourcepict dimmed beneath the hotspot-bitmap. Now erase or draw anything you like...
If there is no hotspot-sourcepict present at all and you have to create it from scratch, duplicate the panorama-sourcepict, change its colordepth to 8bit and turn the "Trim White Space"-option off. Select All in the paint window and fill with white. Now again use onion-skinning to paint hotspots over the right areas.

5.2 Setting up the score

There are three groups of sprite-dummies the QTVRLingoEngine works with (they must be arranged in this order in the score, group 1 underneath group 2 underneath group 3). Dummy means that you can put any odd bitmap in the sprite-channels and position it out of view (off the stage-area)
Group 1: sprites for the backbuffer-slices (only needed for switching from one node to another)
Group 2: sprites for the panorama-slices (always needed)
Group 3: sprites for the showhotspot-slices (only needed for the "showHotspots"-feature)

All groups must consist of the same number of sprite-channels, you can omit groups 1 and 3 if you don't use these features in your project.

THAT'S IT. Almost ready to go - just a few lines of lingo to adjust:

Open the "initBlueRoom"- or the "initGreenRoom"-script. Change all the values to correspond with your own QTVR (pFirstSprite, pSpriteCount, etc). Then duplicate this member for each further panorama you want to display, give the handler a unique name and adjust the few params that differ from QTVR to QTVR (pFirstSliceMem, etc). In the "startMovie"-handler be sure to create an instance of the QTVRLingoEngine for each QTVRpano and call the corresponding init-handlers you just created.

 

6. Known Issues...

6.1 Distorted (bend) edges within the panorama-image
Compared to the 100%-straight edges that Apple's MoviePlayer renders, the edges in QTVRLingo-panos are slightly crooked. I am not sure if this is due to the math that I'm using to un-distort the sourcePict-slices being wrong or due to the fact that the slices themselves have a width of 20 pixels - 1 pixel wide slices would be ideal of course, but would also need 400 sprite-channels instead of the 20 sprite-channels now used to render the QTVR on stage.

The April 30th-Update fixes this rendering bug!

 

6.2 QTVRLingo-sprites must be placed underneath all other sprites in the score
The slice-sprites, the backbuffer-sprites and the showhotspot-sprites all have to be in sprite-channels underneath all other interface-elements (or their z-index has to be manipulated by lingo) plus the border outside the desired QTVR viewrect has to be covered in some way to hide the off-viewrect parts of the sprite-quads...
Alternatively, a MIAW with only a pano in it can be used.

6.3 Quad-calculation problems
When changing the FoV (zooming in and out) without any tilting, 1 pixel wide vertical gaps are visible at certain zoomlevels, always at the same locations within the viewrect. The math shouldn't be the cause of the problem, because then the gaps would show up in the right half of the pano simultaneously, as the caculations are the same for both halves (thanks to symmetry). Also, when I do a "put" of the sprite-quads in the message-window, the adjacent edges of the sprite with the gap inbetween have the correct quad-coordinates... So I think the gaps must be due to internal rounding differences when director draws the quads onto stage. Changing the floatprecision did not make any difference, either.
My solution is to not allow a tilt of 1.0 (=no tilt), but always have a little tilt (0.95/1.05) to prevent the gaps from showing up.