Java tutorial
/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.input.control; import com.ardor3d.framework.Canvas; import com.ardor3d.input.MouseState; import com.ardor3d.input.logical.InputTrigger; import com.ardor3d.input.logical.LogicalLayer; import com.ardor3d.input.logical.MouseWheelMovedCondition; import com.ardor3d.input.logical.TriggerAction; import com.ardor3d.input.logical.TriggerConditions; import com.ardor3d.input.logical.TwoInputStates; import com.ardor3d.math.MathUtils; import com.ardor3d.math.Vector3; import com.ardor3d.math.type.ReadOnlyVector3; import com.ardor3d.renderer.Camera; import com.ardor3d.scenegraph.Spatial; import com.google.common.base.Predicate; import com.google.common.base.Predicates; /** * <p> * Orbital type Camera controller. Basically, this class references a camera and provides methods for moving that camera * around a target position or spatial using spherical polar coordinates. * </p> * <p> * To use, create a new instance of OrbitCamController. Update the controller in your update loop. * </p> * <p> * Example: Creates a new control, adds mouse triggers to a Logical Layer, and sets the default location (15 units away, * 0 degrees ascent, 0 degrees azimuth). * </p> * * <pre> * // ... in init * control = new OrbitCamControl(myCamera, targetLocation); * control.setupMouseTriggers(myLogicalLayer, true); * control.setSphereCoords(15, 0, 0); * * // ...in update loop * control.update(timer.getTimePerFrame()); * </pre> */ public class OrbitCamControl { /** * Our absolute min/max ascent (pitch) angle, in radians. This is set at 89.95 degrees to prevent the camera's * direction from becoming parallel to the world up vector. */ public static final double ABSOLUTE_MAXASCENT = 89.95 * MathUtils.DEG_TO_RAD; /** * The camera we are modifying. */ protected Camera _camera; protected Vector3 _worldUpVec = new Vector3(Vector3.UNIT_Y); protected Vector3 _sphereCoords = new Vector3(); protected Vector3 _camPosition = new Vector3(); protected Vector3 _lookAtPoint = new Vector3(); protected Spatial _lookAtSpatial = null; protected TargetType _targetType; protected boolean _invertedX = false; protected boolean _invertedY = false; protected boolean _invertedWheel = true; protected double _zoomSpeed = 0.01; protected double _baseDistance = 15; protected double _minZoomDistance = 1; protected double _maxZoomDistance = 100; protected double _minAscent = -ABSOLUTE_MAXASCENT; protected double _maxAscent = ABSOLUTE_MAXASCENT; protected double _xSpeed = 0.01; protected double _ySpeed = 0.01; protected boolean _dirty = true; protected InputTrigger _mouseTrigger; public enum TargetType { Point, Spatial } /** * Construct a new orbit controller * * @param cam * the camera to control * @param target * a world location to lock our sights on. */ public OrbitCamControl(final Camera cam, final ReadOnlyVector3 target) { _camera = cam; _targetType = TargetType.Point; _lookAtPoint.set(target); } /** * Construct a new orbit controller * * @param cam * the camera to control * @param target * a spatial whose world location we'll lock our sights on. */ public OrbitCamControl(final Camera cam, final Spatial target) { _camera = cam; _targetType = TargetType.Spatial; _lookAtSpatial = target; } public Camera getCamera() { return _camera; } public void setCamera(final Camera camera) { _camera = camera; } public ReadOnlyVector3 getWorldUpVec() { return _worldUpVec; } public void setWorldUpVec(final ReadOnlyVector3 worldUpVec) { _worldUpVec.set(worldUpVec); _dirty = true; } public void setInvertedWheel(final boolean invertedWheel) { _invertedWheel = invertedWheel; } public boolean isInvertedWheel() { return _invertedWheel; } public void setInvertedX(final boolean invertedX) { _invertedX = invertedX; } public boolean isInvertedX() { return _invertedX; } public void setInvertedY(final boolean invertedY) { _invertedY = invertedY; } public boolean isInvertedY() { return _invertedY; } public Vector3 getLookAtPoint() { return _lookAtPoint; } /** * Sets a specific world location for the camera to point at and circle around. * * @param point */ public void setLookAtPoint(final Vector3 point) { _dirty = !point.equals(_lookAtPoint); _lookAtPoint = point; _targetType = TargetType.Point; } public Spatial getLookAtSpatial() { return _lookAtSpatial; } /** * Sets a spatial to look at. We'll use the world transform of the spatial, so its transform needs to be up to date. * * @param spatial */ public void setLookAtSpatial(final Spatial spatial) { _dirty = spatial != _lookAtSpatial; // identity equality _lookAtSpatial = spatial; _targetType = TargetType.Spatial; } public TargetType getTargetType() { return _targetType; } public double getZoomSpeed() { return _zoomSpeed; } public void setZoomSpeed(final double zoomSpeed) { _zoomSpeed = zoomSpeed; } public double getBaseDistance() { return _baseDistance; } public void setBaseDistance(final double baseDistance) { _baseDistance = baseDistance; zoom(0); } public double getMaxAscent() { return _maxAscent; } public void setMaxAscent(final double maxAscent) { _maxAscent = Math.min(maxAscent, ABSOLUTE_MAXASCENT); move(0, 0); } public double getMinAscent() { return _minAscent; } public void setMinAscent(final double minAscent) { _minAscent = Math.max(minAscent, -ABSOLUTE_MAXASCENT); move(0, 0); } public double getMaxZoomDistance() { return _maxZoomDistance; } public void setMaxZoomDistance(final double maxZoomDistance) { _maxZoomDistance = maxZoomDistance; zoom(0); } public double getMinZoomDistance() { return _minZoomDistance; } public void setMinZoomDistance(final double minZoomDistance) { _minZoomDistance = minZoomDistance; zoom(0); } public double getXSpeed() { return _xSpeed; } public void setXSpeed(final double speed) { _xSpeed = speed; } public double getYSpeed() { return _ySpeed; } public void setYSpeed(final double speed) { _ySpeed = speed; } public void setSphereCoords(final ReadOnlyVector3 sphereCoords) { _sphereCoords.set(sphereCoords); makeDirty(); } public void setSphereCoords(final double x, final double y, final double z) { _sphereCoords.set(x, y, z); makeDirty(); } protected void updateTargetPos() { if (_targetType == TargetType.Spatial) { final double x = _lookAtPoint.getX(); final double y = _lookAtPoint.getY(); final double z = _lookAtPoint.getZ(); _lookAtSpatial.getWorldTransform().applyForward(Vector3.ZERO, _lookAtPoint); if (x != _lookAtPoint.getX() || y != _lookAtPoint.getY() || z != _lookAtPoint.getZ()) { makeDirty(); } } } public void makeDirty() { _dirty = true; } /** * Zoom camera in/out from the target point. * * @param percent * a value applied to the baseDistance to determine how far in/out to zoom. Inverted if * {@link #isInvertedWheel()} is true. */ public void zoom(final double percent) { final double amount = (_invertedWheel ? -1 : 1) * percent * _baseDistance; _sphereCoords.setX(MathUtils.clamp(_sphereCoords.getX() + amount, _minZoomDistance, _maxZoomDistance)); makeDirty(); } /** * * @param xDif * a value applied to the azimuth value of our spherical coordinates. Inverted if {@link #isInvertedX()} * is true. * @param yDif * a value applied to the theta value of our spherical coordinates. Inverted if {@link #isInvertedY()} is * true. */ public void move(final double xDif, final double yDif) { final double azimuthAccel = _invertedX ? -xDif : xDif; final double thetaAccel = _invertedY ? -yDif : yDif; // update our master spherical coords, using x and y movement _sphereCoords.setY(MathUtils.moduloPositive(_sphereCoords.getY() - azimuthAccel, MathUtils.TWO_PI)); _sphereCoords.setZ(MathUtils.clamp(_sphereCoords.getZ() + thetaAccel, _minAscent, _maxAscent)); makeDirty(); } /** * Update the position of the Camera controlled by this object. * * @param time * a delta time, in seconds. Not used currently, but might be useful for doing "ease-in" of camera * movements. */ public void update(final double time) { updateTargetPos(); if (!_dirty) { return; } if (_worldUpVec.getY() == 1) { MathUtils.sphericalToCartesian(_sphereCoords, _camPosition); } else if (_worldUpVec.getZ() == 1) { MathUtils.sphericalToCartesianZ(_sphereCoords, _camPosition); } _camera.setLocation(_camPosition.addLocal(_lookAtPoint)); _camera.lookAt(_lookAtPoint, _worldUpVec); _dirty = false; } public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) { // Mouse look final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(), Predicates.or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown())); final Predicate<TwoInputStates> scrollWheelMoved = new MouseWheelMovedCondition(); final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown); final TriggerAction mouseAction = new TriggerAction() { // Test boolean to allow us to ignore first mouse event. First event can wildly vary based on platform. private boolean firstPing = true; public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { final MouseState mouse = inputStates.getCurrent().getMouseState(); if (mouse.getDx() != 0 || mouse.getDy() != 0) { if (!firstPing) { move(_xSpeed * mouse.getDx(), _ySpeed * mouse.getDy()); } else { firstPing = false; } } if (mouse.getDwheel() != 0) { zoom(_zoomSpeed * mouse.getDwheel()); } } }; final Predicate<TwoInputStates> predicate = Predicates.or(scrollWheelMoved, dragOnly ? dragged : TriggerConditions.mouseMoved()); _mouseTrigger = new InputTrigger(predicate, mouseAction); layer.registerTrigger(_mouseTrigger); } }