src/vrInteraction.java

/*
    File:       vrInteraction.java
    
    Description:    Shows the introspection of a Movie and the presentation of mixer
                        channels to edit the playback volumes and balance of audio media.
        
    Author:     Apple Computer, Inc.
 
    Copyright:  © Copyright 2002 Apple Computer, Inc. All rights reserved.
    
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Computer, Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                
    Change History (most recent first):
            11/22/2002  md  new SampleCode revisions
 
*/
 
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
 
import quicktime.*;
import quicktime.io.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.util.*;
import quicktime.vr.*;
 
import quicktime.app.display.QTCanvas;
import quicktime.app.players.QTPlayer;
 
public class vrInteraction extends Frame implements Errors {
 
    public static void main (String args[]) {
        try {
            System.out.println ("Open a VR Movie...");
            QTSession.open (QTSession.kInitVR);
            
            // QTVR interaction methods are new in QTJava 501
            vrInteraction.checkQTJavaVersion(5,0,1);
                
            vrInteraction pm = new vrInteraction("vrInteraction");
            pm.pack();
            pm.show();
            pm.toFront();
            
        } catch (QTException e) {
            if (e.errorCode() == userCanceledErr) {
                QTSession.close();
                System.exit(0);
            }
            e.printStackTrace();
            QTSession.close();
        }
    }
    
    static final double  kHotSpotZoomInFactor = 0.75; // factor to zoom in on hotspot click
    
    /**
     * vrInteraction
     */
    vrInteraction (String title) throws QTException {
        super (title);
 
        QTFile qtf = QTFile.standardGetFilePreview(QTFile.kStandardQTFileTypes);
        
        OpenMovieFile movieFile = OpenMovieFile.asRead(qtf);
        Movie m = Movie.fromFile (movieFile);
        MovieController mc = new MovieController (m);
 
        // Note it is good practice to explicitly setKeysEnabled and not rely
        // on the default keysEnabled value of the movie controller
        mc.setKeysEnabled (true); // set arrow keys / shift etc to control VR
        
        Track t = m.getQTVRTrack (1);
        if (t == null) {
            System.out.println(qtf.getName() + " is not a QTVR movie!");
        } else { //setup VR callbacks
            try {
                theVR = new QTVRInstance (t, mc);
                System.out.println(
                    "========== Install QTVR Callbacks ============");
                new TriggerHotSpotInterceptor(theVR);
                new MouseOverHotSpot(theVR);
                System.out.println(
                    "========= Initial Settings =======");
                System.out.println(
                    "** PanTiltSpeed= " 
                    + theVR.getInteractionPanTiltSpeed());
                System.out.println(
                    "** FieldOfView= "
                    + theVR.getFieldOfView());
                System.out.println(
                    "========= Hotspot Dynamic Behaviors =====");
                System.out.println(
                    "** On hotspot enter: pan/tilt speed => [2,5,8]");
                System.out.println(
                    "   speed = ((hotspotID %3) *3) +2 = [slow,normal,fast]");
                System.out.println(
                    "** On hotspot leave: reset pan/tilt speed and FOV");
                System.out.println(
                    "** When zoom-in on link hotspot: trigger node change");
                System.out.println(
                    "** On click in non 'link': zoom-in by "
                    + kHotSpotZoomInFactor);
                System.out.println(
                    "======================================================");
 
            } catch (QTVRException e) {
                e.printStackTrace();
            }
        }
        
        QTPlayer myQTPlayer = new QTPlayer (mc);
        QTCanvas myQTCanvas = new QTCanvas();
        add(myQTCanvas);
        myQTCanvas.setClient (myQTPlayer, true);        
 
        addWindowListener(
            new WindowAdapter () {
                public void windowClosing (WindowEvent we) {
                    if (theVR != null) {
                        try {
                            theVR.removeInterceptProc(QTVRConstants.kQTVRTriggerHotSpotSelector);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    QTSession.close();
                    dispose();
                }
                
                public void windowClosed (WindowEvent we) {
                    System.exit(0);
                }
            }
        );
    }
 
    // work around for Java 1.3 IllegalStateException : cannot dispose input context
    public void dispose() {
        if (QTSession.getJavaVersion() != 0x10001) {
            try {
                super.dispose();
            } catch (java.lang.IllegalStateException e) {
                System.exit(0);
            }
        } else {
            super.dispose();
        }
    }
 
    QTVRInstance theVR; // package scope for windowClosing()
    float hsEnterFOV;   // save the FOV when entering a hotspot, for later restore
    
    class TriggerHotSpotInterceptor implements QTVRInterceptor {
        TriggerHotSpotInterceptor(QTVRInstance vr) {
            try {
                System.out.println(
                    "*** installInterceptProc(TriggerHotSpotInterceptor) ***");
                vr.installInterceptProc(QTVRConstants.kQTVRTriggerHotSpotSelector, this, 0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        public boolean execute (QTVRInstance vr, QTVRInterceptRecord qtvrMsg) {
            int selector = qtvrMsg.getSelector();
            switch (selector) {
                case QTVRConstants.kQTVRTriggerHotSpotSelector:
                    triggerHotSpotIntercept(vr, qtvrMsg);
                    break;
                default:
                    System.out.println(
                        "InterceptTriggerHotSpot : " + Integer.toHexString(selector));
                    break;
            }
            return false;   // allow normal QTVR processing
        }
        
        void triggerHotSpotIntercept(QTVRInstance vr, QTVRInterceptRecord qtvrMsg) {
            float fov = hsEnterFOV;
            boolean setFOV = false;
            try {
                int hsID = qtvrMsg.getHotSpotID();
                if (vr.getHotSpotType(hsID) != QTVRConstants.kQTVRHotSpotLinkType) {
                    setFOV = true;  // set this flag if we fail to set the FOV (constraint reached)
                    fov = (float)(vr.getFieldOfView() * kHotSpotZoomInFactor) ;
                    System.out.println(
                        "InterceptTriggerHotSpot : trigger hotspot selector"
                        + " hs #" + hsID 
                        + " FOV=> " + fov);
                    vr.setFieldOfView(fov);
                }
            } catch (QTException qte) {
                qte.printStackTrace(); 
                try {
                    if (setFOV) {// failed to set the FOV (constraint reached)
                        vr.setFieldOfView(hsEnterFOV);
                        System.out.println(
                            "InterceptTriggerHotSpot : reset FOV");
                    }
                } catch (QTException qte2) {
                    qte2.printStackTrace(); 
                }
            }
        }
    }
    
    // QTVR default values
    static final int    kDefaultPanTiltSpeed = 5;
    static final int    kDefaultClickHysteresis = 2;
    static final int    kDefaultClickTimeOut = 30;
 
    class MouseOverHotSpot implements QTVRMouseOverHotSpot {
        MouseOverHotSpot(QTVRInstance vr) {
            try {
                System.out.println(
                    "*** setMouseOverHotSpotProc(MouseOverHotSpot) *** ");
                vr.setMouseOverHotSpotProc(this, 0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        public int execute (QTVRInstance vr, int hotSpotID, int flags) {
            int err = 0;
            try {
                switch (flags) {
                    case QTVRConstants.kQTVRHotSpotEnter:
                        err = hotSpotEnter(vr, hotSpotID);
                        break;
                    case QTVRConstants.kQTVRHotSpotWithin:
                        err = hotSpotWithin(vr, hotSpotID);
                        break;
                    case QTVRConstants.kQTVRHotSpotLeave:
                        err = hotSpotLeave(vr, hotSpotID);
                        break;
                }
            } catch (QTVRException e) {
                e.printStackTrace();
            }
            return err; // continue
        }
        
        // hotSpotEnter
        int hotSpotEnter(QTVRInstance vr, int hotSpotID) throws QTVRException {
            int err = 0;
            hsEnterFOV = vr.getFieldOfView();  // save the entering FOV
            int speed = 5;
            switch (hotSpotID % 3) {
                case 0:     speed = 2;  break;
                case 1:     speed = 5;  break;
                case 2:     speed = 8;  break;
            }
            
            System.out.println("Enter hotSpot #" + hotSpotID
                + ":" 
                + " speed=> " + speed
                + " FOV= " + hsEnterFOV
            );
            vr.setInteractionPanTiltSpeed(speed);
            return err;
        }
 
        // hotSpotWithin : 'link' type auto trigger hotspot if FOV -> < enterFOV / 2
        // hotSpotWithin : else if FOV -> < enterFOV / 2, FOV -> hsEnterFOV
        int hotSpotWithin(QTVRInstance vr, int hotSpotID) throws QTVRException {
            int err = noErr;
            float FOV = vr.getFieldOfView();
            if (FOV < (hsEnterFOV*0.75)) {
                switch (vr.getHotSpotType(hotSpotID)) {
                    case QTVRConstants.kQTVRHotSpotLinkType:
                        System.out.println(
                            " auto trigger hotspot on zoom in");
                        vr.setFieldOfView(hsEnterFOV); // better user experience
                        vr.triggerHotSpot (hotSpotID, null, null);
                        err = userCanceledErr;
                        break;
                    case QTVRConstants.kQTVRHotSpotURLType:
                        break;
                    default:
                        break;
                }
            }
            return err;
        }
 
        // hotSpotLeave : reset the FOV, speed
        int hotSpotLeave(QTVRInstance vr, int hotSpotID) throws QTVRException {
            int err = 0;
            int panTiltSpeed = kDefaultPanTiltSpeed;
            System.out.println(
                " Leave hotSpot #" + hotSpotID
                + ": Reset... "
                + " speed=> " + panTiltSpeed
                + " FOV=> " + hsEnterFOV
            );
            vr.setFieldOfView(hsEnterFOV); // reset the FOV to saved value when hotspot entered
            vr.setInteractionPanTiltSpeed(panTiltSpeed);
            return err;
        }
    }           
    
    /**
     * Utility to check QTJava version.
     */
    static void checkQTJavaVersion(int version, int subVersion, int qualifyingSubVersion) throws QTException {
        // express current build and the required versions of QTJava as comparable values
        int build = (QTBuild.getVersion() <<16) + (QTBuild.getSubVersion() <<8) + QTBuild.getQualifyingSubVersion();
        int require = (version <<16) + (subVersion <<8) + qualifyingSubVersion;
        if (build < require) {
            throw new QTException(
                "You need QTJava " 
                + version + "." +
                + subVersion + "."
                +  qualifyingSubVersion + " or better");
        }
    }
}