thothbot.parallax.core.client.controls.FirstPersonControls.java Source code

Java tutorial

Introduction

Here is the source code for thothbot.parallax.core.client.controls.FirstPersonControls.java

Source

/*
 * Copyright 2012 Alex Usachev, thothbot@gmail.com
 * 
 * This file is part of Parallax project.
 * 
 * Parallax is free software: you can redistribute it and/or modify it 
 * under the terms of the Creative Commons Attribution 3.0 Unported License.
 * 
 * Parallax 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 Creative Commons Attribution 
 * 3.0 Unported License. for more details.
 * 
 * You should have received a copy of the the Creative Commons Attribution 
 * 3.0 Unported License along with Parallax. 
 * If not, see http://creativecommons.org/licenses/by/3.0/.
 */

package thothbot.parallax.core.client.controls;

import thothbot.parallax.core.client.AnimatedScene;
import thothbot.parallax.core.shared.core.Object3D;
import thothbot.parallax.core.shared.math.Mathematics;
import thothbot.parallax.core.shared.math.Vector3;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * The control implements control from a first person.
 * There are disabled the context menu for the {@link #getWidget()} 
 * and implemented the following controls:
 * <p>
 * Mouse move - rotate the {@link #getObject()}.
 * <p>
 * Mouse down - translate the {@link #getObject()} forward.
 * <p>
 * Mouse up - translate the  {@link #getObject()} backward.
 * <p>
 * Keyboard: <br>
 * [W or *up*] - translate the  {@link #getObject()} forward.<br>
 * [S or *down*] - translate the  {@link #getObject()} backward.<br>
 * [A or *left*] - translate the {@link #getObject()} to the left.<br>
 * [D or *right*] - translate the {@link #getObject()} to the right.<br>
 * [R] - translate the {@link #getObject()} up.<br>
 * [F] - translate the {@link #getObject()} down.<br>
 * [Q] - freez the {@link #getObject()}.
 * <p>
 * Based on the three.js code.
 * 
 * @author thothbot
 *
 */
public class FirstPersonControls extends Controls implements MouseMoveHandler, MouseDownHandler, MouseUpHandler,
        KeyDownHandler, KeyUpHandler, ContextMenuHandler {
    private Vector3 target;
    private double movementSpeed = 1.0;
    private double lookSpeed = 0.005;

    private boolean lookVertical = true;
    private boolean autoForward = false;

    private boolean activeLook = true;

    private boolean heightSpeed = false;
    private double heightCoef = 1.0;
    private double heightMin = 0.0;
    // TODO: Check
    private double heightMax = 0.0;

    private boolean constrainVertical = false;
    private double verticalMin = 0.0;
    private double verticalMax = Math.PI;

    private double autoSpeedFactor = 0.0;

    private int mouseX = 0;
    private int mouseY = 0;

    private double lat = 0.0;
    private double lon = 0.0;
    private double phi = 0.0;
    private double theta = 0.0;

    private boolean moveForward = false;
    private boolean moveBackward = false;
    private boolean moveLeft = false;
    private boolean moveRight = false;
    private boolean moveUp = false;
    private boolean moveDown = false;
    private boolean freeze = false;

    private int viewHalfX;
    private int viewHalfY;

    public FirstPersonControls(Object3D object, Widget widget) {
        super(object, widget);

        this.viewHalfX = widget.getOffsetWidth() / 2;
        this.viewHalfY = widget.getOffsetHeight() / 2;

        if (getWidget().getClass() != RootPanel.class)
            getWidget().getElement().setAttribute("tabindex", "-1");

        this.target = new Vector3();

        getWidget().addDomHandler(this, ContextMenuEvent.getType());

        getWidget().addDomHandler(this, MouseMoveEvent.getType());
        getWidget().addDomHandler(this, MouseDownEvent.getType());
        getWidget().addDomHandler(this, MouseUpEvent.getType());
        RootPanel.get().addDomHandler(this, KeyDownEvent.getType());
        RootPanel.get().addDomHandler(this, KeyUpEvent.getType());
    }

    /**
     * Sets the movement speed. Default 1.0
     * 
     * @param movementSpeed the movement speed.
     */
    public void setMovementSpeed(double movementSpeed) {
        this.movementSpeed = movementSpeed;
    }

    /**
     * Sets look speed. Default 0.005.
     * 
     * @param lookSpeed the look speed.
     */
    public void setLookSpeed(double lookSpeed) {
        this.lookSpeed = lookSpeed;
    }

    /**
     * The method must be called in the {@link AnimatedScene}} onUpdate method.
     * 
     * @param delta the time in milliseconds needed to render one frame. 
     */
    public void update(double delta) {
        double actualMoveSpeed = 0;
        double actualLookSpeed;

        if (this.freeze) {
            return;
        } else {
            if (this.heightSpeed) {
                double y = Mathematics.clamp(getObject().getPosition().getY(), this.heightMin, this.heightMax);
                double heightDelta = y - this.heightMin;

                this.autoSpeedFactor = delta * (heightDelta * this.heightCoef);
            } else {
                this.autoSpeedFactor = 0.0;
            }

            actualMoveSpeed = delta * this.movementSpeed;

            if (this.moveForward || (this.autoForward && !this.moveBackward))
                getObject().translateZ(-(actualMoveSpeed + this.autoSpeedFactor));

            if (this.moveBackward)
                getObject().translateZ(actualMoveSpeed);

            if (this.moveLeft)
                getObject().translateX(-actualMoveSpeed);

            if (this.moveRight)
                getObject().translateX(actualMoveSpeed);

            if (this.moveUp)
                getObject().translateY(actualMoveSpeed);

            if (this.moveDown)
                getObject().translateY(-actualMoveSpeed);

            actualLookSpeed = delta * this.lookSpeed;

            if (!this.activeLook)
                actualLookSpeed = 0.0;

            this.lon += this.mouseX * actualLookSpeed;
            if (this.lookVertical)
                this.lat -= this.mouseY * actualLookSpeed; // * this.invertVertical?-1:1;

            this.lat = Math.max(-85.0, Math.min(85.0, this.lat));
            this.phi = (90.0 - this.lat) * Math.PI / 180.0;
            this.theta = this.lon * Math.PI / 180.0;

            Vector3 targetPosition = this.target;
            Vector3 position = getObject().getPosition();

            targetPosition.setX(position.getX() + 100.0 * Math.sin(this.phi) * Math.cos(this.theta));
            targetPosition.setY(position.getY() + 100.0 * Math.cos(this.phi));
            targetPosition.setZ(position.getZ() + 100.0 * Math.sin(this.phi) * Math.sin(this.theta));

        }

        double verticalLookRatio = 1.0;

        if (this.constrainVertical)
            verticalLookRatio = Math.PI / (this.verticalMax - this.verticalMin);

        this.lon += this.mouseX * actualLookSpeed;
        if (this.lookVertical)
            this.lat -= this.mouseY * actualLookSpeed * verticalLookRatio;

        this.lat = Math.max(-85.0, Math.min(85.0, this.lat));
        this.phi = (90.0 - this.lat) * Math.PI / 180.0;

        this.theta = this.lon * Math.PI / 180.0;

        if (this.constrainVertical)
            this.phi = Mathematics.mapLinear(this.phi, 0.0, Math.PI, this.verticalMin, this.verticalMax);

        Vector3 targetPosition = this.target;
        Vector3 position = getObject().getPosition();

        targetPosition.setX(position.getX() + 100.0 * Math.sin(this.phi) * Math.cos(this.theta));
        targetPosition.setY(position.getY() + 100.0 * Math.cos(this.phi));
        targetPosition.setZ(position.getZ() + 100.0 * Math.sin(this.phi) * Math.sin(this.theta));

        this.getObject().lookAt(targetPosition);
    }

    @Override
    public void onMouseMove(MouseMoveEvent event) {
        if (getWidget().getClass() == RootPanel.class) {
            this.mouseX = event.getX() - this.viewHalfX;
            this.mouseY = event.getY() - this.viewHalfY;
        } else {
            this.mouseX = event.getX() - getWidget().getAbsoluteLeft() - this.viewHalfX;
            this.mouseY = event.getY() - getWidget().getAbsoluteTop() - this.viewHalfY;
        }
    }

    @Override
    public void onMouseDown(MouseDownEvent event) {
        //      if (getWidget().getClass() != RootPanel.class)
        //         getWidget().setFocus(true);

        event.preventDefault();
        event.stopPropagation();

        if (this.activeLook) {
            switch (event.getNativeButton()) {
            case NativeEvent.BUTTON_LEFT:
                this.moveForward = true;
                break;
            case NativeEvent.BUTTON_RIGHT:
                this.moveBackward = true;
                break;
            }
        }
    }

    @Override
    public void onMouseUp(MouseUpEvent event) {
        event.preventDefault();
        event.stopPropagation();

        if (this.activeLook) {
            switch (event.getNativeButton()) {
            case NativeEvent.BUTTON_LEFT:
                this.moveForward = false;
                break;
            case NativeEvent.BUTTON_RIGHT:
                this.moveBackward = false;
                break;
            }
        }
    }

    @Override
    public void onKeyDown(KeyDownEvent event) {
        switch (event.getNativeEvent().getKeyCode()) {
        case 38: /*up*/
        case 87:
            /*W*/ this.moveForward = true;
            break;

        case 37: /*left*/
        case 65:
            /*A*/ this.moveLeft = true;
            break;

        case 40: /*down*/
        case 83:
            /*S*/ this.moveBackward = true;
            break;

        case 39: /*right*/
        case 68:
            /*D*/ this.moveRight = true;
            break;

        case 82:
            /*R*/ this.moveUp = true;
            break;
        case 70:
            /*F*/ this.moveDown = true;
            break;

        case 81:
            /*Q*/ this.freeze = !this.freeze;
            break;
        }
    }

    @Override
    public void onKeyUp(KeyUpEvent event) {
        switch (event.getNativeEvent().getKeyCode()) {
        case 38: /*up*/
        case 87:
            /*W*/ this.moveForward = false;
            break;

        case 37: /*left*/
        case 65:
            /*A*/ this.moveLeft = false;
            break;

        case 40: /*down*/
        case 83:
            /*S*/ this.moveBackward = false;
            break;

        case 39: /*right*/
        case 68:
            /*D*/ this.moveRight = false;
            break;

        case 82:
            /*R*/ this.moveUp = false;
            break;
        case 70:
            /*F*/ this.moveDown = false;
            break;
        }
    }

    @Override
    public void onContextMenu(ContextMenuEvent event) {
        event.preventDefault();
    }
}