Skip to content

Third Person Control

Renanse edited this page Oct 24, 2012 · 2 revisions
    /**
     * Copyright (c) 2008-2010 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.Key;
    import com.ardor3d.input.KeyboardState;
    import com.ardor3d.input.MouseState;
    import com.ardor3d.input.logical.InputTrigger;
    import com.ardor3d.input.logical.LogicalLayer;
    import com.ardor3d.input.logical.TriggerAction;
    import com.ardor3d.input.logical.TriggerConditions;
    import com.ardor3d.input.logical.TwoInputStates;
    import com.ardor3d.math.Matrix3;
    import com.ardor3d.math.Vector3;
    import com.ardor3d.math.type.ReadOnlyVector3;
    import com.ardor3d.renderer.Camera;
    import com.ardor3d.scenegraph.Node;
    import com.ardor3d.scenegraph.extension.CameraNode;
    import com.google.common.base.Predicate;
    import com.google.common.base.Predicates;
     
    // This class extends FirstPersonControl only to make it easier to use
    // with ExampleBase.java. Remove this in your own projects!
    public class ThirdPersonControl extends FirstPersonControl {
     
        private final Vector3 _upAxis = new Vector3();
        private double _mouseRotateSpeed = .005;
     
        private double _moveSpeed = 50;
        private double _keyRotateSpeed = 2.25;
        private final Matrix3 _workerMatrix = new Matrix3();
        private final Vector3 _workerStoreA = new Vector3();
        private InputTrigger _mouseTrigger;
        private InputTrigger _keyTrigger;
     
        public Node _cameraRotationNode;
        public CameraNode _cameraTranslationNode = new CameraNode();
     
        private ThirdPersonControl(final ReadOnlyVector3 upAxis, final Node cameraRotationNode, final Camera cam,
                final boolean updateFromCamera) {
            super(upAxis);
            _upAxis.set(upAxis);
            _cameraRotationNode = cameraRotationNode;
            // cam.set(new Camera()); // reset all previous settings
            _cameraTranslationNode.setCamera(cam);
            if (updateFromCamera) {
                _cameraTranslationNode.updateFromCamera();
            } else {
                _cameraTranslationNode.setTranslation(new Vector3(0, 0, 20));
                final Matrix3 rotation = new Matrix3(_cameraTranslationNode.getRotation());
                rotation.setValue(0, 0, -1);
                rotation.setValue(1, 1, 1);
                rotation.setValue(2, 2, -1);
                // rotation.lookAt(new Vector3(0, 0, 21), Vector3.NEG_UNIT_Y);
                _cameraTranslationNode.setRotation(rotation);
            }
     
            // cam.
            // _cameraTranslationNode.updateFromCamera();
            // cameraTranslationNode.setTranslation(new Vector3(0, 40, 20));
            cameraRotationNode.attachChild(_cameraTranslationNode);
            cameraRotationNode.updateWorldTransform(true);
        }
     
        @Override
        public ReadOnlyVector3 getUpAxis() {
            return _upAxis;
        }
     
        @Override
        public void setUpAxis(final ReadOnlyVector3 upAxis) {
            _upAxis.set(upAxis);
        }
     
        @Override
        public double getMouseRotateSpeed() {
            return _mouseRotateSpeed;
        }
     
        @Override
        public void setMouseRotateSpeed(final double speed) {
            _mouseRotateSpeed = speed;
        }
     
        @Override
        public double getMoveSpeed() {
            return _moveSpeed;
        }
     
        @Override
        public void setMoveSpeed(final double speed) {
            _moveSpeed = speed;
        }
     
        @Override
        public double getKeyRotateSpeed() {
            return _keyRotateSpeed;
        }
     
        @Override
        public void setKeyRotateSpeed(final double speed) {
            _keyRotateSpeed = speed;
        }
     
        Vector3 temp = new Vector3();
     
        Vector3 getLeft(final Node n) {
            return n.getRotation().getColumn(0, temp);
        }
     
        Vector3 getUp(final Node n) {
            return n.getRotation().getColumn(1, temp);
        }
     
        Vector3 getDir(final Node n) {
            return n.getRotation().getColumn(2, temp);
        }
     
        Matrix3 tempM = new Matrix3();
     
        void setLeft(final Node n, final ReadOnlyVector3 v) {
            tempM.setColumn(0, v);
            n.setRotation(tempM);
        }
     
        void setUp(final Node n, final ReadOnlyVector3 v) {
            tempM.setColumn(1, v);
            n.setRotation(tempM);
        }
     
        void setDir(final Node n, final ReadOnlyVector3 v) {
            tempM.setColumn(2, v);
            n.setRotation(tempM);
        }
     
        @Override
        protected void move(final Camera camera, final KeyboardState kb, final double tpf) {
            // MOVEMENT
            int moveFB = 0, strafeLR = 0;
            if (kb.isDown(Key.W)) {
                moveFB -= 1;
            }
            if (kb.isDown(Key.S)) {
                moveFB += 1;
            }
            if (kb.isDown(Key.A)) {
                strafeLR -= 1;
            }
            if (kb.isDown(Key.D)) {
                strafeLR += 1;
            }
     
            if (moveFB != 0 || strafeLR != 0) {
                final Vector3 loc = _workerStoreA.zero();
                if (moveFB == 1) {
                    loc.addLocal(getDir(_cameraRotationNode));
                } else if (moveFB == -1) {
                    loc.subtractLocal(getDir(_cameraRotationNode));
                }
                if (strafeLR == 1) {
                    loc.addLocal(getLeft(_cameraRotationNode));
                } else if (strafeLR == -1) {
                    loc.subtractLocal(getLeft(_cameraRotationNode));
                }
                loc.normalizeLocal().multiplyLocal(_moveSpeed * tpf).addLocal(_cameraRotationNode.getTranslation());
                _cameraRotationNode.setTranslation(loc);
                _cameraRotationNode.updateWorldTransform(true);
            }
     
            // ROTATION
            int rotX = 0, rotY = 0;
            if (kb.isDown(Key.UP)) {
                rotY -= 1;
            }
            if (kb.isDown(Key.DOWN)) {
                rotY += 1;
            }
            if (kb.isDown(Key.LEFT)) {
                rotX += 1;
            }
            if (kb.isDown(Key.RIGHT)) {
                rotX -= 1;
            }
            if (rotX != 0 || rotY != 0) {
                rotate(camera, rotX * (_keyRotateSpeed / _mouseRotateSpeed) * tpf, rotY
                        * (_keyRotateSpeed / _mouseRotateSpeed) * tpf);
            }
        }
     
        @Override
        protected void rotate(final Camera camera, final double dx, final double dy) {
     
            if (dx != 0) {
                _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx, _upAxis != null ? _upAxis
                        : getUp(_cameraRotationNode));
                _workerMatrix.applyPost(getLeft(_cameraRotationNode), _workerStoreA);
                setLeft(_cameraRotationNode, _workerStoreA);
                _workerMatrix.applyPost(getDir(_cameraRotationNode), _workerStoreA);
                setDir(_cameraRotationNode, _workerStoreA);
                _workerMatrix.applyPost(getUp(_cameraRotationNode), _workerStoreA);
                setUp(_cameraRotationNode, _workerStoreA);
            }
     
            if (dy != 0) {
                _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy, getLeft(_cameraRotationNode));
                _workerMatrix.applyPost(getLeft(_cameraRotationNode), _workerStoreA);
                setLeft(_cameraRotationNode, _workerStoreA);
                _workerMatrix.applyPost(getDir(_cameraRotationNode), _workerStoreA);
                setDir(_cameraRotationNode, _workerStoreA);
                _workerMatrix.applyPost(getUp(_cameraRotationNode), _workerStoreA);
                setUp(_cameraRotationNode, _workerStoreA);
            }
     
            /*
             * final Quaternion transformAngle = Quaternion.fetchTempInstance();
             * 
             * transformAngle .fromEulerAngles((xAngle -= x) * MathUtils.DEG_TO_RAD, 0.0D, (yAngle += y) *
             * MathUtils.DEG_TO_RAD); _cameraRotationNode.setRotation(transformAngle); // transformAngle.apply(translation,
             * translation); // main.cameraNode.setTranslation(translation); Quaternion.releaseTempInstance(transformAngle);
             */
            _cameraRotationNode.updateWorldTransform(true);
        }
     
        /**
         * @param layer
         *            the logical layer to register with
         * @param upAxis
         *            the up axis of the camera
         * @param dragOnly
         *            if true, mouse input will only rotate the camera if one of the mouse buttons (left, center or right)
         *            is down.
         * @return a new ThirdPersonControl object
         */
        public static ThirdPersonControl setupTriggers(final LogicalLayer layer, final ReadOnlyVector3 upAxis,
                final boolean dragOnly, final Node controlNode, final Camera cam, final boolean updateFromCamera) {
     
            final ThirdPersonControl control = new ThirdPersonControl(upAxis, controlNode, cam, updateFromCamera);
            control.setupKeyboardTriggers(layer);
            control.setupMouseTriggers(layer, dragOnly);
            return control;
        }
     
        /**
         * Deregister the triggers of the given ThirdPersonControl from the given LogicalLayer.
         * 
         * @param layer
         * @param control
         */
        public static void removeTriggers(final LogicalLayer layer, final ThirdPersonControl control) {
            if (control._mouseTrigger != null) {
                layer.deregisterTrigger(control._mouseTrigger);
            }
            if (control._keyTrigger != null) {
                layer.deregisterTrigger(control._keyTrigger);
            }
        }
     
        @Override
        public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) {
            final ThirdPersonControl control = this;
            // Mouse look
            final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(), Predicates
                    .or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown()));
            final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown);
            final TriggerAction dragAction = 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) {
                            control.rotate(source.getCanvasRenderer().getCamera(), -mouse.getDx(), -mouse.getDy());
                        } else {
                            firstPing = false;
                        }
                    }
                }
            };
     
            _mouseTrigger = new InputTrigger(dragOnly ? dragged : TriggerConditions.mouseMoved(), dragAction);
            layer.registerTrigger(_mouseTrigger);
        }
     
        @Override
        public Predicate<TwoInputStates> setupKeyboardTriggers(final LogicalLayer layer) {
     
            final ThirdPersonControl control = this;
     
            // WASD control
            final Predicate<TwoInputStates> keysHeld = new Predicate<TwoInputStates>() {
                Key[] keys = new Key[] { Key.W, Key.A, Key.S, Key.D, Key.LEFT, Key.RIGHT, Key.UP, Key.DOWN };
     
                public boolean apply(final TwoInputStates states) {
                    for (final Key k : keys) {
                        if (states.getCurrent() != null && states.getCurrent().getKeyboardState().isDown(k)) {
                            return true;
                        }
                    }
                    return false;
                }
            };
     
            final TriggerAction moveAction = new TriggerAction() {
                public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
                    control.move(source.getCanvasRenderer().getCamera(), inputStates.getCurrent().getKeyboardState(), tpf);
                }
            };
            _keyTrigger = new InputTrigger(keysHeld, moveAction);
            layer.registerTrigger(_keyTrigger);
            return keysHeld;
        }
     
        @Override
        public InputTrigger getKeyTrigger() {
            return _keyTrigger;
        }
     
        @Override
        public InputTrigger getMouseTrigger() {
            return _mouseTrigger;
        }
    }