/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.utility.camera.flight; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.zip.InflaterInputStream; import org.openmali.vecmath2.Matrix3f; import org.openmali.vecmath2.Point3f; import org.xith3d.loop.UpdatingThread.TimingMode; import org.xith3d.scenegraph.Transformable; /** * This class can be used to replay a pre-recorded camera flight. * * @see CameraFlightRecorder * * @author Marvin Froehlich (aka Qudus) */ public class CameraFlight { public enum Format { UNCOMPRESSED( (byte)0 ), COMPRESSED( (byte)1 ); private byte b; public byte getByte() { return ( b ); } private Format( byte b ) { this.b = b; } public static Format get( byte b ) { switch ( b ) { case 0: return ( Format.UNCOMPRESSED ); case 1: return ( Format.COMPRESSED ); default: throw new IllegalArgumentException( "Unknown type " + b ); } } } public class InterpolationPoint { public Matrix3f rot; public Point3f pos; public float deltaTime; } private List< InterpolationPoint > interPoints; private long startTime; private Matrix3f camRot; private float t0i, t0t; private float d; private int i1; private int frames; private InterpolationPoint ip1, ip2; private List< CameraFlightListener > listeners = new ArrayList< CameraFlightListener >( 1 ); /** * Adds a new CameraFlightListener to the list * * @param l the new listener to be added */ public void addCameraFlightListener( CameraFlightListener l ) { listeners.add( l ); } /** * Remvoes a CameraFlightListener from the list * * @param l the listener to be removed */ public void removeCameraFlightListener( CameraFlightListener l ) { listeners.remove( l ); } /** * Interpolates View rotation and position. * * @param cam the View to be updated * @param gameTime the current game time * @param timingMode */ public void updateCamera( Transformable cam, long gameTime, TimingMode timingMode ) { t0t = (float)timingMode.getMicroSeconds( gameTime - startTime ); ip1 = interPoints.get( i1 ); while ( t0t > ( t0i + ip1.deltaTime ) ) { t0i += ip1.deltaTime; if ( i1 + 2 >= interPoints.size() ) { restart( gameTime, timingMode ); } ip1 = interPoints.get( ++i1 ); } ip2 = interPoints.get( i1 + 1 ); if ( ip1.deltaTime > 0.0f ) d = ( t0t - t0i ) / ip1.deltaTime; else d = 0.0f; camRot.interpolate( ip1.rot, ip2.rot, d ); cam.getTransform().set( camRot ); cam.getTransform().setTranslation( ip1.pos.getX() + ( ( ip2.pos.getX() - ip1.pos.getX() ) * d ), ip1.pos.getY() + ( ( ip2.pos.getY() - ip1.pos.getY() ) * d ), ip1.pos.getZ() + ( ( ip2.pos.getZ() - ip1.pos.getZ() ) * d ) ); frames++; } /** * * @param gameTime * @param timingMode */ public void restart( long gameTime, TimingMode timingMode ) { float averageFPS = ( (float)frames / (float)t0t * 1000000.0f ); for ( int i = 0; i < listeners.size(); i++ ) listeners.get( i ).onCameraFlightEnded( frames, (long)t0t, averageFPS ); // restart at the beginning startTime = gameTime; t0i = 0.0f; t0t = 0.0f; i1 = -1; frames = 0; } public void start( long startTime ) { this.camRot = new Matrix3f(); this.startTime = startTime; this.t0i = 0.0f; this.i1 = 0; this.frames = 0; } /** * Adds a camera-transformation-matrix to the list. * * @param rot * @param pos * @param deltaTime */ public void addRotPos( Matrix3f rot, Point3f pos, float deltaTime ) { InterpolationPoint interPoint = new InterpolationPoint(); interPoint.rot = new Matrix3f( rot ); interPoint.pos = new Point3f( pos ); interPoint.deltaTime = deltaTime * 1000f; // convert to micors! interPoints.add( interPoint ); } private String readLine( InputStream in ) throws IOException { StringBuffer str = new StringBuffer(); char c; final char EOL = '\n'; while ( ( in.available() > 0 ) && ( ( c = (char)in.read() ) != EOL ) ) str.append( c ); return ( str.toString() ); } /** * Loads the CameraFlight from the specified InputStream. * * @param in the InputStream to load from * * @throws IOException */ public void load( InputStream in ) throws IOException { interPoints = new ArrayList< InterpolationPoint >(); Matrix3f rot = new Matrix3f(); Point3f pos = new Point3f(); float t; Format format = Format.get( Byte.valueOf( readLine( in ) ) ); readLine( in ); // skip blank line if ( format == Format.COMPRESSED ) { if ( in instanceof BufferedInputStream ) throw new IllegalArgumentException( "The InputStream must not be a BufferedInputStream, if read from a COMPRESSED file." ); in = new InflaterInputStream( in ); } if ( !( in instanceof BufferedInputStream ) ) in = new BufferedInputStream( in ); String line; String[] comps; while ( true ) { line = readLine( in ); if ( ( line == null ) || ( line.length() == 0 ) || ( Character.getType( line.charAt( 0 ) ) == 0 ) ) break; comps = line.split( " " ); rot.m00( Float.parseFloat( comps[ 0 ] ) ); rot.m01( Float.parseFloat( comps[ 1 ] ) ); rot.m02( Float.parseFloat( comps[ 2 ] ) ); line = readLine( in ); comps = line.split( " " ); rot.m10( Float.parseFloat( comps[ 0 ] ) ); rot.m11( Float.parseFloat( comps[ 1 ] ) ); rot.m12( Float.parseFloat( comps[ 2 ] ) ); line = readLine( in ); comps = line.split( " " ); rot.m20( Float.parseFloat( comps[ 0 ] ) ); rot.m21( Float.parseFloat( comps[ 1 ] ) ); rot.m22( Float.parseFloat( comps[ 2 ] ) ); line = readLine( in ); comps = line.split( " " ); pos.setX( Float.parseFloat( comps[ 0 ] ) ); pos.setY( Float.parseFloat( comps[ 1 ] ) ); pos.setZ( Float.parseFloat( comps[ 2 ] ) ); line = readLine( in ); t = (float)( Long.parseLong( line ) ); line = readLine( in ); addRotPos( rot, pos, t ); } } /** * Loads the CameraFlight from the specified URL.<br> * If read from a URL, the resource must not be a compressed flight file. * * @param url the URL to load from * * @throws IOException */ public void load( URL url ) throws IOException { load( url.openStream() ); } /** * Loads the CameraFlight from the specified File. * * @param file the File to load from * * @throws IOException */ public void load( File file ) throws IOException { load( new FileInputStream( file ) ); } /** * Loads the CameraFlight from the specified file. * * @param filename the file to load from * * @throws IOException */ public void load( String filename ) throws IOException { load( new File( filename ) ); } /** * Creates a new CameraFlight */ public CameraFlight() { } public CameraFlight( List< InterpolationPoint > interPoints ) { this.interPoints = interPoints; } /** * Creates a new CameraFlight and loads data from the given InputStream. */ public CameraFlight( InputStream in ) throws IOException { this(); load( in ); } /** * Creates a new CameraFlight and loads data from the given URL. * If read from a URL, the resource must not be a compressed flight file. */ public CameraFlight( URL url ) throws IOException { this(); load( url ); } /** * Creates a new CameraFlight and loads data from the given file. */ public CameraFlight( File file ) throws IOException { this(); load( file ); } /** * Creates a new CameraFlight and loads data from the given file. */ public CameraFlight( String filename ) throws IOException { this(); load( filename ); } }