// // $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.Plane; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.samskivert.util.ObserverList; /** * Provides various useful mechanisms for manipulating the camera. */ public class CameraHandler { /** * Creates a new camera handler. The camera begins life at the origin, * facing in the negative z direction (pointing at the ground). */ public CameraHandler (Camera camera) { _camera = camera; resetAxes(); } /** * Resets the camera orientation to its initial state. */ public void resetAxes () { _camera.getDirection().set(0, 0, -1); _camera.getLeft().set(-1, 0, 0); _camera.getUp().set(0, 1, 0); _camera.update(); _rxdir.set(1, 0, 0); _rydir.set(0, 1, 0); } /** * Configures limits on the camera tilt. */ public void setTiltLimits (float minAngle, float maxAngle) { _minTilt = minAngle; _maxTilt = maxAngle; } /** * Configures limits on the distance the camera can be panned. * * @param boundViaFrustum if true instead of bounding the camera's * position, we will compute the intersections of the view frustum with the * ground plan and bound that rectangle into the specified bounds. * Note: the camera must generally be pointing down at the ground * (up to perhaps 45 degrees or so) for this to work. At higher angles the * back of the view frustum will intersect the ground plane at or near * infinity. */ public void setPanLimits (float minX, float minY, float maxX, float maxY, boolean boundViaFrustum) { _minX = minX; _minY = minY; _maxX = maxX; _maxY = maxY; _boundViaFrustum = boundViaFrustum; } /** * Configures the minimum and maximum z-axis elevation allowed for the * camera. */ public void setZoomLimits (float minZ, float maxZ) { _minZ = minZ; _maxZ = maxZ; } /** * Sets the camera zoom level to a value between zero (zoomed in maximally) * and 1 (zoomed out maximally). Zoom limits must have already been set up * via a call to {@link #setZoomLimits}. */ public void setZoomLevel (float level) { // Log.info("Zoom " + level + " " + _camera.getLocation()); level = Math.max(0f, Math.min(level, 1f)); _camera.getLocation().z = _minZ + (_maxZ - _minZ) * level; _camera.update(); } /** * Returns the current camera zoom level. */ public float getZoomLevel () { return (_camera.getLocation().z - _minZ) / (_maxZ - _minZ); } /** * Adds a camera path observer. */ public void addCameraObserver (CameraPath.Observer camobs) { _campathobs.add(camobs); } /** * Removes a camera path observer. */ public void removeCameraObserver (CameraPath.Observer camobs) { _campathobs.remove(camobs); } /** * 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(); _campathobs.apply(new CompletedOp(_campath)); } _campath = path; } /** * Returns true if the camera is currently animating along a path, false if * it is not. */ public boolean cameraIsMoving () { return (_campath != null); } /** * This is called by the {@link JmeApp} on every frame to allow the handler * to update the camera as necessary. */ public void update (float frameTime) { if (_campath != null) { if (_campath.tick(frameTime)) { _campathobs.apply(new CompletedOp(_campath)); _campath = null; } } } /** * Returns the camera being manipulated by this handler. */ public Camera getCamera () { return _camera; } /** * Adjusts the camera's location. The specified location will be bounded * within the current pan and zoom limits. */ public void setLocation (Vector3f location) { _camera.setLocation(bound(location)); _camera.update(); } /** * Pans the camera the specified distance in the x and y directions. These * distances will be multiplied by the current "view" x and y axes and * added to the camera's position. */ public void panCamera (float x, float y) { Vector3f loc = _camera.getLocation(); loc.addLocal(_rxdir.mult(x, _temp)); loc.addLocal(_rydir.mult(y, _temp)); setLocation(loc); } /** * Zooms the camera in (distance < 0) and out (distance > 0) by the * specified amount. The distance is multiplied by the camera's current * direction and added to its current position, which is then bounded into * the pan and zoom volume. */ public void zoomCamera (float distance) { Vector3f loc = _camera.getLocation(); loc.subtractLocal(_camera.getDirection().mult(distance, _temp)); setLocation(loc); } /** * Locates the point on the ground at which the camera is "looking" and * rotates the camera from that point around the specified vector by the * specified angle. Additionally zooms the camera in (deltaZoom < 0) or out * (deltaZoom > 0) along its direction of view by the specified amount. */ public void rotateCamera ( Vector3f spot, Vector3f axis, float deltaAngle, float deltaZoom) { // get a vector from the camera's current position to the point around // which we're going to orbit Vector3f direction = _camera.getLocation().subtract(spot); // create a rotation matrix _rotm.fromAxisAngle(axis, deltaAngle); // rotate the direction 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()); // if we're rotating around the ground normal, we need to update our // notion of side-to-side and forward for panning if (axis == _ground.normal) { _rotm.mult(_rxdir, _rxdir); _rotm.mult(_rydir, _rydir); } // finally move the camera to its new location, zooming in or out in // the process float scale = 1 + (deltaZoom / direction.length()); direction.scaleAdd(scale, spot); setLocation(direction); } /** * Swings the camera perpendicular to the ground normal, around the point * on the ground at which it is looking. */ public void orbitCamera (float deltaAngle) { rotateCamera(getGroundPoint(), _ground.normal, deltaAngle, 0); } /** * Swings the camera perpendicular to its left vector around the point on * the ground at which it is looking. */ public void tiltCamera (float deltaAngle) { rotateCamera(getGroundPoint(), _camera.getLeft(), deltaAngle, 0); } /** * Returns the point on the ground (z = 0) at which the camera is looking. */ public Vector3f getGroundPoint () { float dist = -1f * _ground.normal.dot(_camera.getLocation()) / _ground.normal.dot(_camera.getDirection()); return _camera.getLocation().add(_camera.getDirection().mult(dist)); } /** * Returns the ground normal. Z is the default ground plane and the normal * points in the positive z direction. Do not modify this value. */ public Vector3f getGroundNormal () { return _ground.normal; } protected Vector3f bound (Vector3f loc) { if (_boundViaFrustum) { bound(_camera.getFrustumLeft(), _camera.getFrustumTop(), loc); bound(_camera.getFrustumLeft(), _camera.getFrustumBottom(), loc); bound(_camera.getFrustumRight(), _camera.getFrustumTop(), loc); bound(_camera.getFrustumRight(), _camera.getFrustumBottom(), loc); } else { loc.x = Math.max(Math.min(loc.x, _maxX), _minX); loc.y = Math.max(Math.min(loc.y, _maxY), _minY); } loc.z = Math.max(Math.min(loc.z, _maxZ), _minZ); return loc; } protected void bound (float left, float up, Vector3f loc) { // start with the location of the camera moved out into the near // frustum plane _temp.set(loc); _temp.scaleAdd(_camera.getFrustumNear(), _camera.getDirection(), loc); // then slide it over to a corner of the near frustum rectangle _temp.scaleAdd(left, _camera.getLeft(), _temp); _temp.scaleAdd(up, _camera.getUp(), _temp); // turn this into a vector with origin at the camera's location _temp.subtractLocal(loc); _temp.normalizeLocal(); // determine the intersection of said vector with the ground plane float dist = -1f * _ground.normal.dot(loc) / _ground.normal.dot(_temp); _temp.scaleAdd(dist, _temp, loc); // we then assume that if the corner of the "viewable ground rectangle" // is outside our bounds that we can simply adjust the camera location // by the amount it is out of bounds if (_temp.x > _maxX) { loc.x -= (_temp.x - _maxX); } else if (_temp.x < _minX) { loc.x += (_minX - _temp.x); } if (_temp.y > _maxY) { loc.y -= (_temp.y - _maxY); } else if (_temp.y < _minY) { loc.y += (_minY - _temp.y); } } /** Used to dispatch {@link CameraPath.Observer#pathCompleted}. */ protected static class CompletedOp implements ObserverList.ObserverOp { public CompletedOp (CameraPath path) { _path = path; } public boolean apply (Object observer) { return ((CameraPath.Observer)observer).pathCompleted(_path); } protected CameraPath _path; } protected Camera _camera; protected CameraPath _campath; protected ObserverList _campathobs = new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY); protected Matrix3f _rotm = new Matrix3f(); protected Vector3f _temp = new Vector3f(); protected boolean _boundViaFrustum; protected float _minX = Float.MIN_VALUE, _maxX = Float.MAX_VALUE; protected float _minY = Float.MIN_VALUE, _maxY = Float.MAX_VALUE; protected float _minZ = Float.MIN_VALUE, _maxZ = Float.MAX_VALUE; protected float _minTilt = Float.MIN_VALUE, _maxTilt = Float.MAX_VALUE; protected Vector3f _rxdir = new Vector3f(1, 0, 0); protected Vector3f _rydir = new Vector3f(0, 1, 0); protected static final Vector3f _xdir = new Vector3f(1, 0, 0); protected static final Vector3f _ydir = new Vector3f(0, 1, 0); protected static final Plane _ground = new Plane(new Vector3f(0, 0, 1), 0); }