/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.input;
import java.util.ArrayList;
import org.jagatoo.input.InputSystem;
import org.jagatoo.input.InputSystemException;
import org.jagatoo.input.devices.components.InputState;
import org.jagatoo.input.events.MouseMovedEvent;
import org.jagatoo.input.handlers.InputHandler;
import org.jagatoo.input.listeners.MouseAdapter;
import org.jagatoo.input.managers.InputBindingsManager;
import org.openmali.FastMath;
import org.openmali.spatial.AxisIndicator;
import org.openmali.types.twodee.Sized2iRO;
import org.openmali.vecmath2.Matrix3f;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Tuple3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.input.modules.fpih.FPIHConfig;
import org.xith3d.input.modules.fpih.FPIHInputAction;
import org.xith3d.input.modules.fpih.FPIHInputBindingsManager;
import org.xith3d.input.modules.fpih.FPIHInputStatesManager;
import org.xith3d.input.modules.fpih.FPIHMovementConstraints;
import org.xith3d.input.modules.fpih.FPIHPhysics;
import org.xith3d.input.modules.fpih.MovementListener;
import org.xith3d.scenegraph.Transformable;
import org.xith3d.scenegraph.View;
import org.xith3d.scenegraph.avatar.AvatarTransform;
/**
* This class handles the Keyboard and Mouse input for first person shooter like Views (EGO-perspective).
*
* @author Marvin Froehlich (aka Qudus)
*/
public class FirstPersonInputHandler extends InputHandler< FPIHInputAction >
{
public static final float DEFAULT_MOUSE_X_SPEED = FPIHConfig.DEFAULT_MOUSE_X_SPEED;
public static final float DEFAULT_MOUSE_Y_SPEED = FPIHConfig.DEFAULT_MOUSE_Y_SPEED;
public static final boolean DEFAULT_MOUSE_Y_INVERTED = FPIHConfig.DEFAULT_MOUSE_Y_INVERTED;
public static final float DEFAULT_MOVEMENT_FORWARD_SPEED = FPIHConfig.DEFAULT_MOVEMENT_FORWARD_SPEED;
public static final float DEFAULT_MOVEMENT_BACKWARD_SPEED = FPIHConfig.DEFAULT_MOVEMENT_BACKWARD_SPEED;
public static final float DEFAULT_MOVEMENT_SIDEWARD_SPEED = FPIHConfig.DEFAULT_MOVEMENT_SIDEWARD_SPEED;
public static final float DEFAULT_MAX_ANGLE_UP_DOWN = FPIHConfig.DEFAULT_MAX_ANGLE_UP_DOWN;
private AxisIndicator upAxis = AxisIndicator.POSITIVE_Y_AXIS;
private Matrix3f rotMat = new Matrix3f();
private MouseLstnr mouseListener;
private int canvasWidth;
private int canvasHeight;
private float mouseXSpeed = DEFAULT_MOUSE_X_SPEED;
private float mouseYSpeed = DEFAULT_MOUSE_Y_SPEED;
private float movementSpeedForward = DEFAULT_MOVEMENT_FORWARD_SPEED;
private float movementSpeedBackward = DEFAULT_MOVEMENT_BACKWARD_SPEED;
private float movementSpeedSideward = DEFAULT_MOVEMENT_SIDEWARD_SPEED;
private float crouchSizeRegression = 2.0f;
private int discreteZoomDelta = 0;
private final Transformable view;
private final Vector3f DEFAULT_TP_OFFSET = new Vector3f( 0f, 0f, 1.0f );
private Vector3f org_tpOffset = null;
private Vector3f tpOffset = null;
private final Vector3f tpOffset_transformed = new Vector3f();
private float minTPDistance = 0.0f;
private float maxTPDistance = 7.0f;
private float discreteTPStepSize = 0.5f;
private final Point3f viewPosition = new Point3f();
private final Tuple3f viewEuler;
private int mouseDX = 0;
private int mouseDY = 0;
private long lastMouseQueryTime = -1L;
private FPIHPhysics physicsObject = null;
private final ArrayList<AvatarTransform> avatarTransforms = new ArrayList<AvatarTransform>();
private boolean mouseMoved = false;
private ArrayList<MovementListener> movementListeners;
private boolean isFirst = true;
private FPIHConfig config = null;
private FPIHMovementConstraints constraints = new FPIHMovementConstraints();
/**
* Sets the up-axis to use.<br>
* Default is {@link AxisIndicator#POSITIVE_Y_AXIS}.
*
* @param upAxis
*/
public void setUpAxis( AxisIndicator upAxis )
{
if ( upAxis == null )
throw new IllegalArgumentException( "upAxis must not be null!" );
this.upAxis = upAxis;
}
/**
* @return the up-axis to use.<br>
* Default is {@link AxisIndicator#POSITIVE_Y_AXIS}.
*/
public final AxisIndicator getUpAxis()
{
return ( upAxis );
}
/**
* @return the View used by this FirstPersonInputAdapter
*/
public final Transformable getTransformNode()
{
return ( view );
}
/**
* @return the View used by this FirstPersonInputAdapter
*/
public final View getView()
{
return ( (View)view );
}
/**
* Adds a MovementListener to the List.
*
* @param l the new MovmentListener to add
*/
public void addMovementListener( MovementListener l )
{
movementListeners.add( l );
}
/**
* Removes a MovementListener from the List.
*
* @param l the MovmentListener to be removed
*/
public void removeMovementListener( MovementListener l )
{
movementListeners.remove( l );
}
/**
* Called when the player starts to move into any direction.
* This method will never contain any code and can easily been
* overridden.
*
* @param command the KeyCommand, that invoked this event
*/
public void startMovement( FPIHInputAction command )
{
}
/**
* Called when the player stopps to move into any direction.
* This method will never contain any code and can easily been
* overridden.
*
* @param command the KeyCommand, that invoked this event
*/
public void stopMovement( FPIHInputAction command )
{
}
/**
* Makes the player crouch.
*/
public void startCrouch()
{
if ( crouchSizeRegression > 0.0f )
{
final Tuple3f currPos = view.getPosition();
view.setPosition( new Vector3f( currPos.getX(),
currPos.getY() - crouchSizeRegression,
currPos.getZ()
)
);
}
}
/**
* Makes the player stand up from crouch.
*/
public void stopCrouch()
{
if ( crouchSizeRegression > 0.0f )
{
final Tuple3f currPos = view.getPosition();
view.setPosition( new Vector3f( currPos.getX(),
currPos.getY() + crouchSizeRegression,
currPos.getZ()
)
);
}
}
/**
* Called, when the player jumped.
* This method will never contain any code and can easily been
* overridden.
*/
public void startJump()
{
if ( getPhysicsObject() != null )
{
getPhysicsObject().startJump( getUpAxis() );
}
}
/**
* Calculates internal angle-values from the current View-rotation.
* Invoke this method, when the view has been rotated from something else
* than this class instance.
*/
public void updateViewInverse()
{
view.getTransform().getEuler( viewEuler );
view.getTransform().getTranslation( viewPosition );
if ( getPhysicsObject() != null )
{
getPhysicsObject().updateFromView( viewPosition );
}
if ( tpOffset != null )
{
view.getTransform().getMatrix4f().transform( tpOffset, tpOffset_transformed );
viewPosition.add( tpOffset_transformed );
}
else
{
tpOffset_transformed.set( 0f, 0f, 0f );
}
//updateAvatars( view.getTransform(), viewEuler.x, viewEuler.y, tpOffset2 );
isFirst = true;
}
/**
* {@inheritDoc}
*/
@Override
public final FPIHInputBindingsManager getBindingsManager()
{
return ( (FPIHInputBindingsManager)super.getBindingsManager() );
}
/**
* {@inheritDoc}
*/
@Override
public final FPIHInputStatesManager getStatesManager()
{
return ( (FPIHInputStatesManager)super.getStatesManager() );
}
/**
* Sets the third-person-offset in view-local coordinates.
*
* @param tpDirectionX
* @param tpDirectionY
* @param tpDirectionZ
* @param distance
*/
public void setThirdPersonOffset( float tpDirectionX, float tpDirectionY, float tpDirectionZ, float distance )
{
if ( (tpDirectionX == 0f) && (tpDirectionY == 0f) && (tpDirectionZ == 0f) )
{
this.org_tpOffset = null;
this.tpOffset = null;
updateViewInverse();
return;
}
if ( this.tpOffset == null )
this.tpOffset = new Vector3f( tpDirectionX, tpDirectionY, tpDirectionZ );
else
this.tpOffset.set( tpDirectionX, tpDirectionY, tpDirectionZ );
if ( distance >= 0f )
{
this.tpOffset.normalize();
this.tpOffset.scale( distance );
}
if ( this.org_tpOffset == null )
this.org_tpOffset = new Vector3f( this.tpOffset );
else
this.org_tpOffset.set( this.tpOffset );
updateViewInverse();
}
public void setThirdPersonDistance( float dist )
{
discreteZoomDelta = (int)( dist / (float)discreteTPStepSize );
}
/**
* Sets the third-person-offset in view-local coordinates.
*
* @param tpOffsetX
* @param tpOffsetY
* @param tpOffsetZ
*/
public void setThirdPersonOffset( float tpOffsetX, float tpOffsetY, float tpOffsetZ )
{
setThirdPersonOffset( tpOffsetX, tpOffsetY, tpOffsetZ, -1f );
}
/**
* Sets the third-person-offset in view-local coordinates.
*
* @param tpDirection
*/
public void setThirdPersonOffset( Tuple3f tpDirection, float distance )
{
if ( tpDirection == null )
{
this.org_tpOffset = null;
this.tpOffset = null;
updateViewInverse();
return;
}
setThirdPersonOffset( tpDirection.getX(), tpDirection.getY(), tpDirection.getZ(), distance );
}
/**
* Sets the third-person-offset in view-local coordinates.
*
* @param tpOffset
*/
public void setThirdPersonOffset( Tuple3f tpOffset )
{
setThirdPersonOffset( tpOffset, -1f );
}
/**
* @return the third-person-offset in view-local coordinates.
*/
public final Vector3f getThirdPersonOffset()
{
return ( tpOffset );
}
/**
* Sets the minimum third-person distance.
*
* @param minDist
*/
public void setMinThirdPersonDistance( float minDist )
{
this.minTPDistance = minDist;
}
/**
* @return the minimum third-person distance.
*/
public final float getMinThirdPersonDistance()
{
return ( minTPDistance );
}
/**
* Sets the maximum third-person distance.
*
* @param maxDist
*/
public void setMaxThirdPersonDistance( float maxDist )
{
this.maxTPDistance = maxDist;
}
/**
* @return the maximum third-person distance.
*/
public final float getMaxThirdPersonDistance()
{
return ( maxTPDistance );
}
/**
* Sets the stepsize of discrete third-person offset manipulation.
*
* @param stepSize
*/
public void setDiscreteThirdPersonStepSize( float stepSize )
{
this.discreteTPStepSize = stepSize;
}
/**
* @return the stepsize of discrete third-person offset manipulation.
*/
public final float getDiscreteThirdPersonStepSize()
{
return ( discreteTPStepSize );
}
/**
* Sets the mouse movement speed for the x-axis.
*
* @param speedX the new speed for the x-axis
*/
public void setMouseXSpeed( float speedX )
{
this.mouseXSpeed = speedX;
if ( config != null )
{
config.setMouseXSpeed( speedX );
}
}
/**
* @return the mouse movement speed for the x-axis
*/
public final float getMouseXSpeed()
{
return ( mouseXSpeed );
}
/**
* Sets the mouse movement speed for the y-axis.
*
* @param speedY the new speed for the y-axis
*/
public void setMouseYSpeed( float speedY )
{
this.mouseYSpeed = speedY;
if ( config != null )
{
config.setMouseYSpeed( speedY );
}
}
/**
* @return the mouse movement speed for the y-axis
*/
public final float getMouseYSpeed()
{
return ( mouseYSpeed );
}
/**
* Flips the mouse-y-axis movement.<br>
* This is the same as<br>
* <code>
* setMouseSpeedY( -getMouseSpeedY() );
* </code>
*/
public void flipMouseYAxis()
{
setMouseYSpeed( -getMouseYSpeed() );
}
/**
* Sets the speed the player moves by forward and backward.
*
* @param speed the new moving speed
*/
public void setMovementSpeed( float speed )
{
this.movementSpeedForward = speed;
this.movementSpeedBackward = speed;
this.movementSpeedSideward = speed;
if ( config != null )
{
config.setMovementSpeed( speed );
}
}
/**
* @return the speed the player moves by forward and backward.
*/
public final float getMovementSpeed()
{
return ( (movementSpeedForward + movementSpeedBackward + movementSpeedSideward) / 3.0f );
}
/**
* Sets the speed the player moves by forward.
*
* @param speed the new (forward) moving speed
*/
public void setMovementSpeedForward( float speed )
{
this.movementSpeedForward = speed;
if ( config != null )
{
config.setMovementSpeedForward( speed );
}
}
/**
* @return the speed the player moves by forward.
*/
public final float getMovementSpeedForward()
{
return ( movementSpeedForward );
}
/**
* Sets the speed the player moves by backward.
*
* @param speed the new (backward) moving speed
*/
public void setMovementSpeedBackward( float speed )
{
this.movementSpeedBackward = speed;
if ( config != null )
{
config.setMovementSpeedBackward( speed );
}
}
/**
* @return the speed the player moves by backward.
*/
public final float getMovementSpeedBackward()
{
return ( movementSpeedBackward );
}
/**
* Sets the speed the player moves by sideward.
*
* @param speed the new (sideward) moving speed
*/
public void setMovementSpeedSideward( float speed )
{
this.movementSpeedSideward = speed;
if ( config != null )
{
config.setMovementSpeedSideward( speed );
}
}
/**
* @return the speed the player moves by sideward.
*/
public final float getMovementSpeedSideward()
{
return ( movementSpeedSideward );
}
/**
* Applies the given {@link FPIHConfig} to this {@link FirstPersonInputHandler}.
* The config instance is stored in this object and is notified of any change.
*
* @param config
*/
public void applyConfig( FPIHConfig config )
{
this.config = null;
this.setMouseXSpeed( config.getMouseXSpeed() );
this.setMouseYSpeed( config.getMouseYSpeed() );
this.setMovementSpeedForward( config.getMovementSpeedForward() );
this.setMovementSpeedBackward( config.getMovementSpeedBackward() );
this.setMovementSpeedSideward( config.getMovementSpeedSideward() );
this.config = config;
}
/**
* Extracts a {@link FPIHConfig} from this {@link FirstPersonInputHandler}.
*
* @param config
*/
public void extractConfig( FPIHConfig config )
{
this.config = null;
this.setMouseXSpeed( config.getMouseXSpeed() );
this.setMouseYSpeed( config.getMouseYSpeed() );
this.setMovementSpeedForward( config.getMovementSpeedForward() );
this.setMovementSpeedBackward( config.getMovementSpeedBackward() );
this.setMovementSpeedSideward( config.getMovementSpeedSideward() );
this.config = config;
}
/**
* Sets the value the view will we lowered with the player crouches.
*
* @param sizeDelta the value the view will be lowered on crouch
*/
public void setPlayerCrouchSizeRegression( float sizeDelta )
{
this.crouchSizeRegression = sizeDelta;
}
/**
* @return value the view will we lowered with the player crouches.
*/
public float getPlayerCrouchSizeRegression()
{
return ( crouchSizeRegression );
}
/**
* Sets the constraints to be used by this {@link FirstPersonInputHandler}.
*
* @param constraints
*/
public void setMovementConstraints( FPIHMovementConstraints constraints )
{
if ( constraints == null )
throw new IllegalArgumentException( "constraints must not be null" );
this.constraints = constraints;
}
/**
* @return the constraints to be used by this {@link FirstPersonInputHandler}.
*/
public final FPIHMovementConstraints getMovementConstraints()
{
return ( constraints );
}
/**
* {@inheritDoc}
*/
@Override
public boolean setSuspendMask( int suspendMask )
{
boolean imms = isMouseMovementSuspended();
if ( super.setSuspendMask( suspendMask ) )
{
if ( getInputSystem() != null )
{
try
{
if ( getInputSystem().hasMouse() )
getInputSystem().getMouse().setAbsolute( isMouseMovementSuspended() );
}
catch ( InputSystemException e )
{
throw new RuntimeException( e );
}
}
if ( imms && !isMouseMovementSuspended() )
{
updateViewInverse();
}
return ( true );
}
return ( false );
}
/**
* Adds an Avatar to the FPIH,<br>
* An Avatar always follows the main Transformable (View) possibly with an offset.
*
* @param at
*/
public void addAvatar( AvatarTransform at )
{
if ( at == null )
throw new NullPointerException( "at must not be null" );
this.avatarTransforms.add( at );
updateViewInverse();
}
/**
* Removes an Avatar from the FPIH,<br>
* An Avatar always follows the main Transformable (View) possibly with an offset.
*
* @param at the avatar Transformable
*/
public void removeAvatar( AvatarTransform at )
{
if ( at == null )
throw new NullPointerException( "at must not be null" );
avatarTransforms.remove( at );
}
public void setPhysicsObject( FPIHPhysics physicsObject )
{
this.physicsObject = physicsObject;
if ( physicsObject != null )
{
physicsObject.init( getTransformNode(), getThirdPersonOffset() );
if ( !isSuspended() )
{
updateViewInverse();
}
}
}
public final FPIHPhysics getPhysicsObject()
{
return ( physicsObject );
}
/**
* This transforms additional Transformables to follow the main Transformable (View).
*
* @param gameMicros
* @param view
* @param rotX
* @param rotY
* @param thirdPersonOffset
*/
protected void updateAvatars( long gameMicros, long frameMicros, Transformable view, Vector3f viewTranslation, float rotX, float rotY, Vector3f thirdPersonOffset )
{
if ( getPhysicsObject() != null )
{
getPhysicsObject().update( gameMicros, frameMicros, view, viewTranslation, rotX, rotY, thirdPersonOffset );
isFirst = true;
}
for ( int i = 0; i < avatarTransforms.size(); i++ )
{
avatarTransforms.get( i ).transform( view.getTransform(), rotX, rotY, thirdPersonOffset );
}
}
private class MouseLstnr extends MouseAdapter
{
@Override
public void onMouseMoved( MouseMovedEvent e, int x, int y, int dx, int dy )
{
if ( isMouseMovementSuspended() )
return;
mouseDX += dx;
mouseDY += dy;
mouseMoved = true;
}
}
/**
* {@inheritDoc}
*/
@Override
public void update( long nanoSeconds, float seconds, long nanoFrame, float frameSeconds ) throws InputSystemException
{
final long gameMicros = nanoSeconds / 1000L;
final long frameMicros = nanoFrame / 1000L;
if ( getPhysicsObject() != null )
{
getPhysicsObject().updateGameTime( gameMicros );
}
final FPIHInputStatesManager statesManager = getStatesManager();
if ( statesManager.getInputState( FPIHInputAction.DISCRETE_ZOOM_IN ) == InputState.MADE_POSITIVE )
discreteZoomDelta--;
if ( statesManager.getInputState( FPIHInputAction.DISCRETE_ZOOM_OUT ) == InputState.MADE_POSITIVE )
discreteZoomDelta++;
final boolean wasZooming = (discreteZoomDelta != 0) || statesManager.isZooming();
Vector3f viewTranslation = Vector3f.fromPool();
if ( statesManager.isMoving() || statesManager.isTurning() || mouseMoved || wasZooming )
{
view.getTransform().getTranslation( viewPosition );
viewTranslation.setZero();
}
if ( (tpOffset != null) && ( statesManager.isMoving() || statesManager.isTurning() || mouseMoved || wasZooming ) )
{
view.getTransform().getMatrix4f().transform( tpOffset, tpOffset_transformed );
viewPosition.sub( tpOffset_transformed );
}
/*
* If we're turning (not by mouse movevent), we need to apply
* a rotation to the view, which depends on the passed time.
*/
if ( statesManager.isTurning() )
{
final float turnH = mouseXSpeed * 5f * frameSeconds;
final float turnV = mouseYSpeed * 5f * frameSeconds;
if ( statesManager.isTurningLeft() )
{
viewEuler.addY( turnH );
}
if ( statesManager.isTurningRight() )
{
viewEuler.subY( turnH );
}
if ( statesManager.isAimingUp() )
{
viewEuler.addX( turnV );
}
if ( statesManager.isAimingDown() )
{
viewEuler.subX( turnV );
}
constraints.applyRotationalConstraints( viewEuler, getUpAxis() );
view.getTransform().setEuler( viewEuler );
}
final boolean mouseWasMoved;
if ( !isMouseMovementSuspended() )
{
if ( mouseMoved )
{
mouseWasMoved = true;
float dx = (float)mouseDX * 1f * FastMath.TWO_PI / ( mouseXSpeed * (float)canvasWidth );
float dy = (float)mouseDY * 1f * FastMath.TWO_PI / ( mouseYSpeed * (float)canvasHeight );
if ( isMouseSmoothingEnabled() )
{
if ( lastMouseQueryTime != -1L )
{
float dt = 100f * Math.min( 0.3f, ( nanoSeconds - lastMouseQueryTime ) / 1000000000f );
dx /= dt;
dy /= dt;
}
lastMouseQueryTime = nanoSeconds;
}
switch ( getUpAxis() )
{
case POSITIVE_X_AXIS:
viewEuler.sub( dx, dy, 0f );
break;
case NEGATIVE_X_AXIS:
viewEuler.add( dx, dy, 0f );
break;
case POSITIVE_Y_AXIS:
viewEuler.sub( dy, dx, 0f );
break;
case NEGATIVE_Y_AXIS:
viewEuler.add( dy, dx, 0f );
break;
case POSITIVE_Z_AXIS:
viewEuler.sub( dy, 0f, dx );
break;
case NEGATIVE_Z_AXIS:
viewEuler.add( dy, 0f, dx );
break;
}
constraints.applyRotationalConstraints( viewEuler, getUpAxis() );
view.getTransform().setEuler( viewEuler );
mouseDX = 0;
mouseDY = 0;
mouseMoved = false;
}
else
{
mouseWasMoved = false;
}
}
else
{
mouseWasMoved = false;
}
// if a movement key is pressed --> calculate the new translation
if ( statesManager.isMoving() && ( nanoFrame > 0L ) )
{
Vector3f viewFacingDirection = Vector3f.fromPool();
Vector3f viewRightDirection = Vector3f.fromPool();
// calculate facing-direction
//view.getFacingDirection( viewFacingDirection );
view.getTransform().getRotation( rotMat );
rotMat.mul( Vector3f.NEGATIVE_Z_AXIS, viewFacingDirection );
//calculate right-direction
//view.getRightDirection( viewRightDirection );
view.getTransform().getRotation( rotMat );
rotMat.mul( Vector3f.POSITIVE_X_AXIS, viewRightDirection );
final boolean isMovingF = statesManager.isMovingForward();
final boolean isMovingB = statesManager.isMovingBackward();
final boolean isMovingL = statesManager.isMovingLeft();
final boolean isMovingR = statesManager.isMovingRight();
/*
* This code will prevent the player from moving faster
* when moving forward/backward and sideward at the same time.
*/
final float distF;
if ( isMovingF && ( isMovingL || isMovingR ) )
distF = movementSpeedForward * 7.072135785007072135f * frameSeconds;
else
distF = movementSpeedForward * 10f * frameSeconds;
final float distB;
if ( isMovingB && ( isMovingL || isMovingR ) )
distB = movementSpeedBackward * 7.072135785007072135f * frameSeconds;
else
distB = movementSpeedBackward * 10f * frameSeconds;
final float distS ;
if ( ( isMovingL || isMovingR ) && ( isMovingF || isMovingB ) )
distS = movementSpeedSideward * 7.072135785007072135f * frameSeconds;
else
distS = movementSpeedSideward * 10f * frameSeconds;
Vector3f tmpVec = Vector3f.fromPool();
/*
* Apply the scaled forward-movement to the delta-translation.
* The scale is defined by the mouse-speed-settings.
*/
if ( isMovingF )
{
tmpVec.set( viewFacingDirection );
tmpVec.normalize();
tmpVec.mul( distF );
viewTranslation.add( tmpVec );
}
/*
* Apply the scaled backward-movement to the delta-translation.
* The scale is defined by the mouse-speed-settings.
*/
if ( isMovingB )
{
tmpVec.set( viewFacingDirection );
tmpVec.normalize();
tmpVec.mul( -distB );
viewTranslation.add( tmpVec );
}
/*
* Apply the scaled right-movement to the delta-translation.
* The scale is defined by the mouse-speed-settings.
*/
if ( isMovingR )
{
tmpVec.set( viewRightDirection );
tmpVec.normalize();
tmpVec.mul( distS );
viewTranslation.add( tmpVec );
}
/*
* Apply the scaled left-movement to the delta-translation.
* The scale is defined by the mouse-speed-settings.
*/
if ( isMovingL )
{
tmpVec.set( viewRightDirection );
tmpVec.normalize();
tmpVec.mul( -distS );
viewTranslation.add( tmpVec );
}
Vector3f.toPool( tmpVec );
Vector3f.toPool( viewRightDirection );
Vector3f.toPool( viewFacingDirection );
constraints.applyMovementDeltaConstraints( viewTranslation, getUpAxis() );
/*
* Add the delta-movement to the absolute one.
*/
viewPosition.add( viewTranslation );
constraints.applyMovementConstraints( viewPosition, getUpAxis() );
}
if ( wasZooming )
{
/*
* Calculate the third-person-offset delta
* from discrete steps and smooth zooming...
*/
float delta = (float)discreteZoomDelta * discreteTPStepSize;
discreteZoomDelta = 0;
if ( statesManager.isZoomingIn() )
delta -= discreteTPStepSize * 8f * frameSeconds;
if ( statesManager.isZoomingOut() )
delta += discreteTPStepSize * 8f * frameSeconds;
/*
* If the new delta is non-zero or the current TP-offset is used...
* (A null-tpOffset means, that it has a zero distance.)
*/
if ( (delta > 0) || (tpOffset != null) )
{
// Set the tpOffset to the initial value...
if ( tpOffset == null )
{
if ( org_tpOffset == null )
org_tpOffset = new Vector3f( DEFAULT_TP_OFFSET );
tpOffset = new Vector3f( 0f, 0f, 0f );
}
/*
* Constraint to minimum and maximum...
*/
float dist = tpOffset.length();
if ( Float.isNaN( dist ) )
dist = minTPDistance;
dist += delta;
if ( dist < minTPDistance )
dist = minTPDistance;
else if ( dist > maxTPDistance )
dist = maxTPDistance;
/*
* Apply the new distance to the tpOffset,
* if it is greater than zero or set it to null,
* if it has zero distance.
*/
if ( dist > 0f )
{
tpOffset.normalize( org_tpOffset );
tpOffset.scale( dist );
}
else
{
tpOffset = null;
}
}
}
/*
* If the tpOffset is currently used and we're moving or turning or zooming, etc.,
* we need to transform the tpOffset by the current view-matrix
* and apply it to the new view-position.
*/
if ( (tpOffset != null) && (statesManager.isMoving() || statesManager.isTurning() || mouseWasMoved || wasZooming) )
{
view.getTransform().getMatrix4f().transform( tpOffset, tpOffset_transformed );
viewPosition.add( tpOffset_transformed );
}
/*
* If the current tpOffset is zero (instance is null),
* we need to set the transformed value to zero, too,
* since it is used below.
*/
if ( tpOffset == null )
{
tpOffset_transformed.set( 0f, 0f, 0f );
}
/*
* If we're moving, zooming or turning, we need to apply the translation.
*/
if ( statesManager.isMoving() || statesManager.isTurning() || mouseWasMoved || wasZooming )
{
view.getTransform().setTranslation( viewPosition );
view.setTransform( view.getTransform() );
}
/*
* Update avatars, if necessary.
*/
if ( statesManager.isMoving() || statesManager.isTurning() || mouseWasMoved || isFirst || ( getPhysicsObject() != null ) )
{
updateAvatars( gameMicros, frameMicros, view, viewTranslation, viewEuler.getX(), viewEuler.getY(), tpOffset_transformed );
}
Vector3f.toPool( viewTranslation );
isFirst = false;
}
/**
* {@inheritDoc}
*/
@Override
public void setInputSystem( InputSystem inputSystem )
{
try
{
if ( inputSystem != this.getInputSystem() )
{
if ( ( this.getInputSystem() != null ) && ( mouseListener != null ) )
{
if ( !isMouseMovementSuspended() )
this.getInputSystem().getMouse().setAbsolute( true );
this.getInputSystem().getMouse().removeMouseListener( mouseListener );
}
if ( inputSystem != null )
{
super.setInputSystem( inputSystem );
if ( mouseListener == null )
mouseListener = new MouseLstnr();
inputSystem.getMouse().addMouseListener( mouseListener );
inputSystem.getMouse().setAbsolute( isMouseMovementSuspended() );
}
}
}
catch ( Throwable t )
{
Error e = new Error( "You must register a mouse before the FirstPersonInputHandler can be added to the InputHandler." );
e.initCause( t );
throw e;
}
}
@Override
protected FPIHInputStatesManager createInputStatesManager( InputBindingsManager< FPIHInputAction > bindingsManager )
{
this.movementListeners = new ArrayList< MovementListener >( 1 );
return ( new FPIHInputStatesManager( this, movementListeners ) );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeedForeward the new foreward movement speed
* @param movementSpeedBackward the new backward movement speed
* @param movementSpeedSideward the new sideward movement speed
*/
public FirstPersonInputHandler( Transformable view, int resolutionX, int resolutionY, float mouseXSpeed, float mouseYSpeed, float movementSpeedForeward, float movementSpeedBackward, float movementSpeedSideward )
{
super( new FPIHInputBindingsManager() );
this.view = view;
this.viewEuler = new Tuple3f( 0f, 0f, 0f );
updateViewInverse();
this.canvasWidth = resolutionX;
this.canvasHeight = resolutionY;
this.setMouseXSpeed( mouseXSpeed );
this.setMouseYSpeed( mouseYSpeed );
this.setMovementSpeedForward( movementSpeedForeward );
this.setMovementSpeedBackward( movementSpeedBackward );
this.setMovementSpeedSideward( movementSpeedSideward );
this.setMouseSmoothingEnabled( true );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeed the new movement speed
*/
public FirstPersonInputHandler( Transformable view, int resolutionX, int resolutionY, float mouseXSpeed, float mouseYSpeed, float movementSpeed )
{
this( view, resolutionX, resolutionY, mouseXSpeed, mouseYSpeed, movementSpeed, movementSpeed, movementSpeed );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param mouseYInverted
* @param movementSpeed the new movement speed
*/
public FirstPersonInputHandler( Transformable view, int resolutionX, int resolutionY, float mouseXSpeed, float mouseYSpeed, boolean mouseYInverted, float movementSpeed )
{
this( view, resolutionX, resolutionY, mouseXSpeed, mouseYInverted ? -mouseYSpeed : mouseYSpeed, movementSpeed, movementSpeed, movementSpeed );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeedForeward the new foreward movement speed
* @param movementSpeedBackward the new backward movement speed
* @param movementSpeedSideward the new sideward movement speed
*/
public FirstPersonInputHandler( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, float movementSpeedForeward, float movementSpeedBackward, float movementSpeedSideward )
{
this( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, movementSpeedForeward, movementSpeedBackward, movementSpeedSideward );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeed the new movement speed
*/
public FirstPersonInputHandler( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, float movementSpeed )
{
this( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, movementSpeed );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param yInverted
* @param movementSpeed the new movement speed
*/
public FirstPersonInputHandler( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, boolean yInverted, float movementSpeed )
{
this( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, yInverted, movementSpeed );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
*/
public FirstPersonInputHandler( Transformable view, int resolutionX, int resolutionY )
{
this( view, resolutionX, resolutionY, DEFAULT_MOUSE_X_SPEED, DEFAULT_MOUSE_Y_SPEED, DEFAULT_MOUSE_Y_INVERTED, DEFAULT_MOVEMENT_FORWARD_SPEED );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolution the Canvas to take as calculation basis for resolution-independent turn speed
*/
public FirstPersonInputHandler( Transformable view, Sized2iRO resolution )
{
this( view, resolution.getWidth(), resolution.getHeight() );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param config
*/
public FirstPersonInputHandler( Transformable view, int resolutionX, int resolutionY, FPIHConfig config )
{
this( view, resolutionX, resolutionY );
applyConfig( config );
}
/**
* Creates a new FirstPersonInputHandler.
*
* @param view the View to be used.
* @param resolution the Canvas to take as calculation basis for resolution-independent turn speed
* @param config
*/
public FirstPersonInputHandler( Transformable view, Sized2iRO resolution, FPIHConfig config )
{
this( view, resolution.getWidth(), resolution.getHeight(), config );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeedForeward the new foreward movement speed
* @param movementSpeedBackward the new backward movement speed
* @param movementSpeedSideward the new sideward movement speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, int resolutionX, int resolutionY, float mouseXSpeed, float mouseYSpeed, float movementSpeedForeward, float movementSpeedBackward, float movementSpeedSideward )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolutionX, resolutionY, mouseXSpeed, mouseYSpeed, movementSpeedForeward, movementSpeedBackward, movementSpeedSideward );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeed the new movement speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, int resolutionX, int resolutionY, float mouseXSpeed, float mouseYSpeed, float movementSpeed )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolutionX, resolutionY, mouseXSpeed, mouseYSpeed, movementSpeed, movementSpeed, movementSpeed );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeedForeward the new foreward movement speed
* @param movementSpeedBackward the new backward movement speed
* @param movementSpeedSideward the new sideward movement speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, float movementSpeedForeward, float movementSpeedBackward, float movementSpeedSideward )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, movementSpeedForeward, movementSpeedBackward, movementSpeedSideward );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param movementSpeed the new movement speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, float movementSpeed )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, movementSpeed );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas3D to take as calculation basis for resolution-independent turn speed
* @param mouseXSpeed the new x-axis mouse speed
* @param mouseYSpeed the new y-axis mouse speed
* @param mouseYInverted
* @param movementSpeed the new movement speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution, float mouseXSpeed, float mouseYSpeed, boolean mouseYInverted, float movementSpeed )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolution.getWidth(), resolution.getHeight(), mouseXSpeed, mouseYSpeed, mouseYInverted, movementSpeed );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param mouseYInverted
*/
public static final FirstPersonInputHandler createDefault( Transformable view, int resolutionX, int resolutionY, boolean mouseYInverted )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolutionX, resolutionY, DEFAULT_MOUSE_X_SPEED, DEFAULT_MOUSE_Y_SPEED, mouseYInverted, DEFAULT_MOVEMENT_FORWARD_SPEED );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, int resolutionX, int resolutionY )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolutionX, resolutionY, DEFAULT_MOUSE_X_SPEED, DEFAULT_MOUSE_Y_SPEED, DEFAULT_MOUSE_Y_INVERTED, DEFAULT_MOVEMENT_FORWARD_SPEED );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas to take as calculation basis for resolution-independent turn speed
* @param mouseYInverted
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution, boolean mouseYInverted )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolution.getWidth(), resolution.getHeight(), DEFAULT_MOUSE_X_SPEED, DEFAULT_MOUSE_Y_SPEED, mouseYInverted, DEFAULT_MOVEMENT_FORWARD_SPEED );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas to take as calculation basis for resolution-independent turn speed
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolution.getWidth(), resolution.getHeight() );
fpHandler.getBindingsManager().createDefaultBindings();
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolutionX the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param resolutionY the Canvas3D-width to take as calculation basis for resolution-independent turn speed
* @param config
*/
public static final FirstPersonInputHandler createDefault( Transformable view, int resolutionX, int resolutionY, FPIHConfig config )
{
final FirstPersonInputHandler fpHandler = new FirstPersonInputHandler( view, resolutionX, resolutionY, DEFAULT_MOUSE_X_SPEED, DEFAULT_MOUSE_Y_SPEED, DEFAULT_MOUSE_Y_INVERTED, DEFAULT_MOVEMENT_FORWARD_SPEED );
fpHandler.getBindingsManager().createDefaultBindings();
fpHandler.applyConfig( config );
return ( fpHandler );
}
/**
* Creates a new FirstPersonInputHandler and applies default key-bindings.
*
* @param view the View to be used.
* @param resolution the Canvas to take as calculation basis for resolution-independent turn speed
* @param config
*/
public static final FirstPersonInputHandler createDefault( Transformable view, Sized2iRO resolution, FPIHConfig config )
{
return ( createDefault( view, resolution.getWidth(), resolution.getHeight(), config) );
}
}