From 86abf1675fb173994d62969d499ffd1e05355256 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 9 Nov 2005 20:01:28 +0000 Subject: [PATCH] A bunch of business related to moving the camera along paths. Also repackaged camera handling stuff into com.threerings.jme.camera. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3751 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- src/java/com/threerings/jme/JmeApp.java | 32 ++++- .../com/threerings/jme/camera/CameraPath.java | 54 ++++++++ .../jme/{input => camera}/GodViewHandler.java | 2 +- .../com/threerings/jme/camera/SwingPath.java | 127 ++++++++++++++++++ .../com/threerings/jme/camera/rotate_zoom.dia | Bin 0 -> 1672 bytes .../com/threerings/jme/camera/rotate_zoom.png | Bin 0 -> 16237 bytes 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/java/com/threerings/jme/camera/CameraPath.java rename src/java/com/threerings/jme/{input => camera}/GodViewHandler.java (99%) create mode 100644 src/java/com/threerings/jme/camera/SwingPath.java create mode 100644 src/java/com/threerings/jme/camera/rotate_zoom.dia create mode 100644 src/java/com/threerings/jme/camera/rotate_zoom.png diff --git a/src/java/com/threerings/jme/JmeApp.java b/src/java/com/threerings/jme/JmeApp.java index e64d04b09..c8b006a2b 100644 --- a/src/java/com/threerings/jme/JmeApp.java +++ b/src/java/com/threerings/jme/JmeApp.java @@ -51,7 +51,8 @@ import com.jme.light.PointLight; import com.jme.math.Vector3f; import com.jme.util.Timer; -import com.threerings.jme.input.GodViewHandler; +import com.threerings.jme.camera.CameraPath; +import com.threerings.jme.camera.GodViewHandler; /** * Defines a basic application framework providing integration with the @@ -180,6 +181,27 @@ public class JmeApp } } + /** + * Starts the camera moving along a path which will be updated every tick + * until it is complete. + */ + public void moveCamera (CameraPath path) + { + if (_campath != null) { + _campath.abort(); + } + _campath = path; + } + + /** + * Returns true if the camera is currently animating along a path, false if + * it is not. + */ + public boolean cameraIsMoving () + { + return (_campath != null); + } + /** * Instructs the application to stop the main loop, cleanup and exit. */ @@ -394,6 +416,13 @@ public class JmeApp float timePerFrame = _timer.getTimePerFrame(); _root.updateGeometricState(timePerFrame, true); + // if there's a camera path, update that as well + if (_campath != null) { + if (_campath.tick(timePerFrame)) { + _campath = null; + } + } + // update our stats display if we have one if (_stats != null) { _stats.update(_timer, _display.getRenderer()); @@ -509,6 +538,7 @@ public class JmeApp protected PropertiesIO _properties; protected DisplaySystem _display; protected Camera _camera; + protected CameraPath _campath; protected InputHandler _input; protected BRootNode _rnode; diff --git a/src/java/com/threerings/jme/camera/CameraPath.java b/src/java/com/threerings/jme/camera/CameraPath.java new file mode 100644 index 000000000..f573174d0 --- /dev/null +++ b/src/java/com/threerings/jme/camera/CameraPath.java @@ -0,0 +1,54 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.jme.camera; + +import com.jme.renderer.Camera; + +/** + * Used to move the camera along a particular path. + */ +public abstract class CameraPath +{ + /** + * This is called on every frame to allow the path to adjust the position + * of the camera. + * + * @return true if the path is completed and can be disposed, false if it + * is not yet completed. + */ + public abstract boolean tick (float secondsSince); + + /** + * Called if this path is aborted prior to completion due to being replaced + * by a new camera path. + */ + public void abort () + { + } + + protected CameraPath (Camera camera) + { + _camera = camera; + } + + protected Camera _camera; +} diff --git a/src/java/com/threerings/jme/input/GodViewHandler.java b/src/java/com/threerings/jme/camera/GodViewHandler.java similarity index 99% rename from src/java/com/threerings/jme/input/GodViewHandler.java rename to src/java/com/threerings/jme/camera/GodViewHandler.java index 744e0c6a1..4edc78342 100644 --- a/src/java/com/threerings/jme/input/GodViewHandler.java +++ b/src/java/com/threerings/jme/camera/GodViewHandler.java @@ -1,7 +1,7 @@ // // $Id$ -package com.threerings.jme.input; +package com.threerings.jme.camera; import com.jme.math.FastMath; import com.jme.math.Matrix3f; diff --git a/src/java/com/threerings/jme/camera/SwingPath.java b/src/java/com/threerings/jme/camera/SwingPath.java new file mode 100644 index 000000000..45a9799ce --- /dev/null +++ b/src/java/com/threerings/jme/camera/SwingPath.java @@ -0,0 +1,127 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.jme.camera; + +import com.jme.math.FastMath; +import com.jme.math.Matrix3f; +import com.jme.math.Vector3f; +import com.jme.renderer.Camera; + +import com.threerings.jme.Log; + +/** + * Swings the camera around a point of interest (which should be somewhere + * along the camera's view vector). Also optionally zooms the camera in or out + * (moves it along its view vector) in the process. + * + *

+ */ +public class SwingPath extends CameraPath +{ + /** + * Creates a rotating, zooming path for the specified camera. + * + * @param spot the point of interest around which to swing the camera. + * @param axis the axis around which to rotate the camera. + * @param angle the angle through which to rotate the camera. + * @param angvel the (absolute value of the) velocity at which to rotate + * the camera (in radians per second). + * @param zoom the distance to zoom along the camera's view axis (negative + * = in, positive = out). + */ + public SwingPath (Camera camera, Vector3f spot, Vector3f axis, + float angle, float angvel, float zoom) + { + super(camera); + + if (angle == 0) { + Log.warning("Requested to swing camera through zero degrees " + + "[spot=" + spot + ", axis=" + axis + + ", angvel=" + angvel + ", zoom=" + zoom + "]."); + angle = 0.0001f; + } + if (angvel <= 0) { + Log.warning("Requested to swing camera with invalid velocity " + + "[spot=" + spot + ", axis=" + axis + ", angle=" + angle + + ", angvel=" + angvel + ", zoom=" + zoom + "]."); + angvel = FastMath.PI; + } + + _spot = spot; + _axis = axis; + _angle = angle; + _angvel = (angle > 0) ? angvel : -1 * angvel; + _zoom = zoom; + _zoomvel = _zoom * _angvel / _angle; + +// Log.info("Swinging camera [angle=" + _angle + ", angvel=" + _angvel + +// ", zoom=" + _zoom + ", zoomvel=" + _zoomvel + "]."); + } + + // documentation inherited + public boolean tick (float secondsSince) + { + float deltaAngle = (secondsSince * _angvel); + float deltaZoom = (secondsSince * _zoomvel); + _rotated += deltaAngle; + _zoomed += deltaZoom; + + // clamp our rotation at the target angle and determine whether or not + // we're done + boolean done = false; + if (_angle > 0 && _rotated > _angle || + _angle < 0 && _rotated < _angle) { + deltaAngle -= (_rotated - _angle); + deltaZoom -= (_zoomed - _zoom); + _rotated = _angle; + _zoomed = _zoom; + done = true; + } + + // get a vector from the camera's position to the point around which + // we're going to orbit + Vector3f camloc = _camera.getLocation(); + Vector3f direction = camloc.subtract(_spot); + + // create a rotation matrix + _rotm.fromAxisAngle(_axis, deltaAngle); + + // rotate the center to camera vector and the camera itself + _rotm.mult(direction, direction); + _rotm.mult(_camera.getUp(), _camera.getUp()); + _rotm.mult(_camera.getLeft(), _camera.getLeft()); + _rotm.mult(_camera.getDirection(), _camera.getDirection()); + + // and move the camera to its new location (accounting for the zoom) + float scale = 1 + (deltaZoom / direction.length()); + direction.scaleAdd(scale, _spot); + _camera.setLocation(direction); + _camera.update(); + + return done; + } + + protected Vector3f _spot, _axis; + protected float _angle, _angvel, _rotated; + protected float _zoom, _zoomvel, _zoomed; + protected Matrix3f _rotm = new Matrix3f(); +} diff --git a/src/java/com/threerings/jme/camera/rotate_zoom.dia b/src/java/com/threerings/jme/camera/rotate_zoom.dia new file mode 100644 index 0000000000000000000000000000000000000000..603bd5cc9de1b4a0407623bb8a8ad1d68226cfae GIT binary patch literal 1672 zcmV;326y=%iwFP!000001MORFkJ>m8{=UB=qF+ak5Cq;9@VS)j^AUo9F3)`QPg}J4S)cFk?fqdBGk7XHpy;sE+rgiB*FIH^KpIfro`II>H!os7 z5mklv>7q&YWKtFFVD>th#pWxGE8!Ck&m)QvhQ9?ow*!pQBpI7zTF(A>o%a7vaciZM z)goF}q3c4j-p_|mOWd^PznXMm;8PJS`d=&Ua`2y@t&8Hi!N-s9dFFJQ)T!DYwxXK#n2DX9@AWi)sjwb|yp=C=v*tp@0Qk^+=9@ zLT~5-OCVqL@*H_yi-SDC;?7o7AisN&U8^M7IL>^05en;_|+t z@tN@c;_*31=ZL&A)>#?rqxqcIFc$C3SOh{@QpmniO2zu6tSu6yfK*bJfcLN^Ymb~4 zW;xJfs*GuLepl_7R{Hlp!#Jlsn{9@;G}pPAHuUsgXoNM@uuvh_!57!Pd%dvn8fK}J zY~YmyRyx7vqraK>!4$Rcb1FW4278;rTE`A6n>XGKbk{vka?v($yDh>acZ_X-bZlqI z!ql&6!=CG9^iN2|2gH(9uhFatnS&AQlT3{`_FKN2C(#b{W zq~0yBbaI|v@$Xq0nb>6BM`z0*ts$47E4c(n_!KEBFz>Z(%)v(jK$0X3!;wTH;Fy;+ zOKargddHm=JgSG>V+)yHJIL1#^0k9}Oa=BKjn}g@so_c4xd>xOFdFz01)*ZS7GVr= zfs5uWlZ1+*2ooXqLljUVTQ2?*3^P95j%w1eW~hKks$!A?2P8PqZ03d35QMk3-1ifj z{8BC(c6M2ycMhHd1scVq&-4yRIDnm{LW)TV`v!`z2$T=6gay62Y?F@l^(v>zFDDy@ z2ZrKOYN1D4%bnR}&vKZix*w)VhDCYEBuTG61tb(OP6{N3 S+4+{#-Oc}ihc=u^JpceP8b`YT literal 0 HcmV?d00001 diff --git a/src/java/com/threerings/jme/camera/rotate_zoom.png b/src/java/com/threerings/jme/camera/rotate_zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..82ce4f88a5f69c41646182fe46f308efe2d50958 GIT binary patch literal 16237 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