2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 package com.jme.input;
35 import java.util.HashMap;
37 import com.jme.input.action.InputActionEvent;
38 import com.jme.input.action.KeyInputAction;
39 import com.jme.input.thirdperson.MovementPermitter;
40 import com.jme.input.thirdperson.ThirdPersonBackwardAction;
41 import com.jme.input.thirdperson.ThirdPersonForwardAction;
42 import com.jme.input.thirdperson.ThirdPersonJoystickPlugin;
43 import com.jme.input.thirdperson.ThirdPersonLeftAction;
44 import com.jme.input.thirdperson.ThirdPersonRightAction;
45 import com.jme.input.thirdperson.ThirdPersonStrafeLeftAction;
46 import com.jme.input.thirdperson.ThirdPersonStrafeRightAction;
47 import com.jme.math.FastMath;
48 import com.jme.math.Quaternion;
49 import com.jme.math.Vector3f;
50 import com.jme.renderer.Camera;
51 import com.jme.scene.Spatial;
54 * <code>ThirdPersonHandler</code> defines an InputHandler that sets input to
55 * be controlled similar to games such as Zelda Windwaker and Mario 64, etc.
57 * @author <a href="mailto:josh@renanse.com">Joshua Slack</a>
58 * @version $Revision: 1.30 $
61 public class ThirdPersonHandler extends InputHandler {
62 public static final String PROP_TURNSPEED = "turnSpeed";
63 public static final String PROP_DOGRADUAL = "doGradual";
64 public static final String PROP_ROTATEONLY = "rotateOnly";
65 public static final String PROP_PERMITTER = "permitter";
66 public static final String PROP_UPVECTOR = "upVector";
67 public static final String PROP_LOCKBACKWARDS = "lockBackwards";
68 public static final String PROP_CAMERAALIGNEDMOVE = "cameraAlignedMovement";
69 public static final String PROP_STRAFETARGETALIGN = "targetAlignStrafe";
71 public static final String PROP_KEY_FORWARD = "fwdKey";
72 public static final String PROP_KEY_BACKWARD = "backKey";
73 public static final String PROP_KEY_LEFT = "leftKey";
74 public static final String PROP_KEY_RIGHT = "rightKey";
75 public static final String PROP_KEY_STRAFELEFT = "strfLeftKey";
76 public static final String PROP_KEY_STRAFERIGHT = "strfRightKey";
79 /** Default character turn speed is 1.5pi per sec. */
80 public static final float DEFAULT_TURNSPEED = 1.5f * FastMath.PI;
82 public static float angleEpsilon = 0.001f;
84 protected float speed = 0;
86 /** The Spatial we are controlling with this handler. */
87 protected Spatial targetSpatial;
90 * The previous location of the target node... used to maintain where the
91 * node is before actions are run. This allows a comparison to see where the
92 * node wants to be taken.
94 protected Vector3f prevLoc = new Vector3f();
97 * The previous rotation of the target node... used to maintain the node's
98 * rotation before actions are run. This is used when target aligned
99 * movement is used and strafing is done.
101 protected Quaternion prevRot = new Quaternion();
104 * Stores the new location of the node after actions. used internally by
107 protected Vector3f loc = new Vector3f();
110 * The current facing direction of the controlled target in radians in terms
111 * of relationship to the world.
113 protected float faceAngle;
116 * How fast the character can turn per second. Used when doGradualRotation
119 protected float turnSpeed = DEFAULT_TURNSPEED;
122 * When true, the controlled target will do turns by moving forward and
123 * turning at the same time. When false, a turn will cause immediate
124 * rotation to the given angle.
126 protected boolean doGradualRotation = true;
129 * When not null, gives a means for denying movement to the controller. See
130 * MovementPermitter javadoc for more.
132 protected MovementPermitter permitter;
134 /** World up vector. Currently 0,1,0 is the only guarenteed value to work. */
135 protected Vector3f upVector = new Vector3f(0, 1, 0);
137 /** An internal vector used for calculations to prevent object creation. */
138 protected Vector3f calcVector = new Vector3f();
140 /** The camera this handler uses for determining action movement. */
141 protected Camera camera;
144 * if true, backwards movement will not cause the target to rotate around to
145 * point backwards. (useful for vehicle movement) Default is false.
147 protected boolean lockBackwards;
150 * if true, strafe movements will always be target aligned, even if other
151 * movement is camera aligned. Default is false.
153 protected boolean strafeAlignTarget;
156 * if true, left and right keys will rotate the target instead of moving them.
159 protected boolean rotateOnly;
162 * if true, movements of the character are in relation to the current camera
163 * view. If false, they are in relation to the current target's facing
164 * vector. Default is true.
166 protected boolean cameraAlignedMovement;
169 * internally used boolean for denoting that a backwards action is currently
172 protected boolean walkingBackwards;
175 * internally used boolean for denoting that a forward action is currently
178 protected boolean walkingForward;
181 * internally used boolean for denoting that a turning action is currently
184 protected boolean nowTurning;
187 * internally used boolean for denoting that a turning action is currently
190 protected boolean nowStrafing;
192 protected ThirdPersonJoystickPlugin plugin = null;
194 protected KeyInputAction actionForward;
195 protected KeyInputAction actionBack;
196 protected KeyInputAction actionRight;
197 protected KeyInputAction actionLeft;
198 protected KeyInputAction actionStrafeRight;
199 protected KeyInputAction actionStrafeLeft;
202 * Basic constructor for the ThirdPersonHandler. Sets all non specified args
208 * the camera for movements to be in relation to
210 public ThirdPersonHandler(Spatial target, Camera cam) {
211 this(target, cam, null);
215 * Full constructor for the ThirdPersonHandler. Properties in the props arg
216 * will be used to set handler fields if set, otherwise default values are
222 * the camera for movements to be in relation to
224 * a hashmap of properties used to set handler characteristics
225 * where the key is one of this class's static PROP_XXXX fields.
227 public ThirdPersonHandler(Spatial target, Camera cam, HashMap<String, Object> props) {
228 this.targetSpatial = target;
231 updateProperties(props);
237 * <code>setProperties</code> sets up class fields from the given hashmap.
238 * It also calls updateKeyBindings for you.
242 public void updateProperties(HashMap<String, Object> props) {
243 turnSpeed = getFloatProp(props, PROP_TURNSPEED, DEFAULT_TURNSPEED);
244 doGradualRotation = getBooleanProp(props, PROP_DOGRADUAL, true);
245 lockBackwards = getBooleanProp(props, PROP_LOCKBACKWARDS, false);
246 strafeAlignTarget = getBooleanProp(props, PROP_STRAFETARGETALIGN, false);
247 cameraAlignedMovement = getBooleanProp(props, PROP_CAMERAALIGNEDMOVE, true);
248 rotateOnly = getBooleanProp(props, PROP_ROTATEONLY, false);
249 permitter = (MovementPermitter)getObjectProp(props, PROP_PERMITTER, null);
250 upVector = (Vector3f)getObjectProp(props, PROP_UPVECTOR, Vector3f.UNIT_Y.clone());
251 updateKeyBindings(props);
256 * <code>updateKeyBindings</code> allows a user to update the keys mapped to the various actions.
260 public void updateKeyBindings(HashMap<String, Object> props) {
261 KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
262 keyboard.set(PROP_KEY_FORWARD, getIntProp(props, PROP_KEY_FORWARD, KeyInput.KEY_W));
263 keyboard.set(PROP_KEY_BACKWARD, getIntProp(props, PROP_KEY_BACKWARD, KeyInput.KEY_S));
264 keyboard.set(PROP_KEY_LEFT, getIntProp(props, PROP_KEY_LEFT, KeyInput.KEY_A));
265 keyboard.set(PROP_KEY_RIGHT, getIntProp(props, PROP_KEY_RIGHT, KeyInput.KEY_D));
266 keyboard.set(PROP_KEY_STRAFELEFT, getIntProp(props, PROP_KEY_STRAFELEFT, KeyInput.KEY_Q));
267 keyboard.set(PROP_KEY_STRAFERIGHT, getIntProp(props, PROP_KEY_STRAFERIGHT, KeyInput.KEY_E));
272 * <code>setActions</code> sets the keyboard actions with the
273 * corresponding key command.
276 protected void setActions() {
277 actionForward = new ThirdPersonForwardAction( this, 100f );
278 actionBack = new ThirdPersonBackwardAction( this, 100f );
279 actionRight = new ThirdPersonRightAction( this, 100f );
280 actionLeft = new ThirdPersonLeftAction( this, 100f );
281 actionStrafeRight = new ThirdPersonStrafeRightAction( this, 100f );
282 actionStrafeLeft = new ThirdPersonStrafeLeftAction( this, 100f );
283 addAction( actionForward, PROP_KEY_FORWARD, true );
284 addAction( actionBack, PROP_KEY_BACKWARD, true );
285 addAction( actionRight, PROP_KEY_RIGHT, true );
286 addAction( actionLeft, PROP_KEY_LEFT, true );
287 addAction( actionStrafeRight, PROP_KEY_STRAFERIGHT, true );
288 addAction( actionStrafeLeft, PROP_KEY_STRAFELEFT, true );
292 * <code>update</code> updates the position and rotation of the target
293 * based on the movement requested by the user.
296 * @see com.jme.input.InputHandler#update(float)
298 public void update(float time) {
299 if ( !isEnabled() ) return;
301 walkingForward = false;
302 walkingBackwards = false;
306 prevLoc.set(targetSpatial.getLocalTranslation());
310 if (walkingBackwards && walkingForward && !nowStrafing && !nowTurning) {
311 targetSpatial.getLocalTranslation().set(prevLoc);
314 targetSpatial.getLocalTranslation().subtract(loc, loc);
315 if (!loc.equals(Vector3f.ZERO)) {
316 float distance = loc.length();
317 if (distance != 0 && distance != 1.0f)
318 loc.divideLocal(distance); // this is same as normalizeLocal.
321 targetSpatial.getLocalRotation().getRotationColumn(2, calcVector);
322 if (upVector.y == 1) {
323 actAngle = FastMath.atan2(loc.z, loc.x);
324 if (!nowTurning && !nowStrafing) {
325 faceAngle = FastMath.atan2(calcVector.z, calcVector.x);
327 } else if (upVector.x == 1) {
328 actAngle = FastMath.atan2(loc.z, loc.y);
329 if (!nowTurning && !nowStrafing)
330 faceAngle = FastMath.atan2(calcVector.z, calcVector.y);
331 } else if (upVector.z == 1) {
332 actAngle = FastMath.atan2(loc.x, loc.y) - FastMath.HALF_PI;
333 if (!nowTurning && !nowStrafing)
334 faceAngle = FastMath.atan2(calcVector.x, calcVector.y) - FastMath.HALF_PI;
337 float oldFace = faceAngle;
338 calcFaceAngle(actAngle, time);
340 faceAngle = actAngle;
341 prevRot.set(targetSpatial.getLocalRotation());
343 targetSpatial.getLocalRotation().fromAngleNormalAxis(-(faceAngle - FastMath.HALF_PI), upVector);
344 targetSpatial.getLocalRotation().getRotationColumn(2, calcVector).multLocal(distance);
347 if (!strafeAlignTarget && cameraAlignedMovement) {
348 if (upVector.y == 1) {
349 faceAngle = FastMath.atan2(camera.getDirection().z, camera.getDirection().x);
350 } else if (upVector.x == 1) {
351 faceAngle = FastMath.atan2(camera.getDirection().z, camera.getDirection().y);
352 } else if (upVector.z == 1) {
353 faceAngle = FastMath.atan2(camera.getDirection().x, camera.getDirection().y) - FastMath.HALF_PI;
355 targetSpatial.getLocalRotation().fromAngleNormalAxis(-faceAngle, upVector);
357 targetSpatial.getLocalRotation().set(prevRot);
362 targetSpatial.getLocalTranslation().set(prevLoc);
363 if (lockBackwards && walkingBackwards && !nowStrafing)
364 targetSpatial.getLocalTranslation().subtractLocal(calcVector);
365 else if (rotateOnly && nowTurning && !walkingBackwards && !walkingForward)
368 targetSpatial.getLocalTranslation().addLocal(calcVector);
372 protected void doInputUpdate(float time) {
374 updateFromJoystick(time);
377 protected void updateFromJoystick(float time) {
378 if (plugin == null) return;
379 float xAmnt = plugin.getJoystick().getAxisValue(plugin.getXAxis());
380 float yAmnt = plugin.getJoystick().getAxisValue(plugin.getYAxis());
382 InputActionEvent evt = new InputActionEvent();
384 evt.setTime(time*xAmnt);
385 actionRight.performAction(evt);
386 } else if (xAmnt < 0) {
387 evt.setTime(time*-xAmnt);
388 actionLeft.performAction(evt);
392 evt.setTime(time*yAmnt);
393 actionBack.performAction(evt);
394 } else if (yAmnt < 0) {
395 evt.setTime(time*-yAmnt);
396 actionForward.performAction(evt);
401 * <code>calcFaceAngle</code>
405 protected void calcFaceAngle(float actAngle, float time) {
406 if (doGradualRotation) {
407 faceAngle = FastMath.normalize(faceAngle, -FastMath.PI, FastMath.PI);
408 float oldAct = actAngle;
410 // Check the difference between action angle and current facing angle.
411 actAngle -= faceAngle;
412 actAngle = FastMath.normalize(actAngle, -FastMath.PI, FastMath.PI);
413 if (FastMath.abs(actAngle) <= angleEpsilon) {
417 boolean above = faceAngle > oldAct;
418 if (lockBackwards && walkingBackwards) {
419 // update faceangle rotation towards action angle
420 if (actAngle > angleEpsilon && actAngle < FastMath.PI)
421 faceAngle -= time * turnSpeed;
422 else if (actAngle < -angleEpsilon || actAngle > FastMath.PI)
423 faceAngle += time * turnSpeed;
425 // update faceangle rotation towards action angle
426 if (actAngle > angleEpsilon && actAngle < FastMath.PI) {
427 faceAngle += time * turnSpeed;
428 if (!above && faceAngle > oldAct) faceAngle = oldAct;
429 } else if (actAngle < -angleEpsilon || actAngle > FastMath.PI) {
430 faceAngle -= time * turnSpeed;
431 if (above && faceAngle < oldAct) faceAngle = oldAct;
435 if (lockBackwards && walkingBackwards)
436 faceAngle = FastMath.PI + actAngle;
438 faceAngle = actAngle;
443 * @return Returns the turnSpeed.
445 public float getTurnSpeed() {
451 * The turnSpeed to set.
453 public void setTurnSpeed(float turnSpeed) {
454 this.turnSpeed = turnSpeed;
458 * @return Returns the upAngle.
460 public Vector3f getUpVector() {
466 * The upAngle to set (as copy)
468 public void setUpVector(Vector3f upAngle) {
469 this.upVector.set(upAngle);
473 * @return Returns the faceAngle (in radians)
475 public float getFaceAngle() {
480 * @return Returns the doGradualRotation.
482 public boolean isDoGradualRotation() {
483 return doGradualRotation;
487 * @param doGradualRotation
488 * The doGradualRotation to set.
490 public void setDoGradualRotation(boolean doGradualRotation) {
491 this.doGradualRotation = doGradualRotation;
494 public MovementPermitter getPermitter() {
498 public Spatial getTarget() {
499 return targetSpatial;
502 public void setTarget(Spatial target) {
503 this.targetSpatial = target;
506 public Camera getCamera() {
510 public void setStrafeAlignTarget(boolean b) {
511 strafeAlignTarget = b;
514 public boolean isStrafeAlignTarget() {
515 return strafeAlignTarget;
518 public void setLockBackwards(boolean b) {
522 public boolean isLockBackwards() {
523 return lockBackwards;
526 public void setRotateOnly(boolean b) {
530 public boolean isRotateOnly() {
534 public void setCameraAlignedMovement(boolean b) {
535 cameraAlignedMovement = b;
538 public boolean isCameraAlignedMovement() {
539 return cameraAlignedMovement;
543 * Internal method used to let the handler know that the target is currently
544 * moving forward (via use of the forward key.)
548 public void setGoingForward(boolean forward) {
549 walkingForward = forward;
553 * Internal method used to let the handler know that the target is currently
554 * moving backwards (via use of the back key.)
558 public void setGoingBackwards(boolean backwards) {
559 walkingBackwards = backwards;
563 * Internal method used to let the handler know that the target is currently
564 * turning/moving left/right (via use of the left/right keys.)
568 public void setTurning(boolean turning) {
569 nowTurning = turning;
573 * Internal method used to let the handler know that the target is currently
574 * strafing left/right (via use of the strafe left/right keys.)
578 public void setStrafing(boolean strafe) {
579 nowStrafing = strafe;
583 * @return true if last update of handler included a turn left/right action.
585 public boolean isNowTurning() {
590 * @return true if last update of handler included a walk backwards action.
592 public boolean isWalkingBackwards() {
593 return walkingBackwards;
597 * @return true if last update of handler included a walk forward action.
599 public boolean isWalkingForward() {
600 return walkingForward;
604 * @return true if last update of handler included a walk forward action.
606 public boolean isStrafing() {
610 public void setActionSpeed(float speed) {
611 super.setActionSpeed(speed);
615 public float getSpeed() {
620 * @return Returns the joystick plugin or null if not set.
622 public ThirdPersonJoystickPlugin getJoystickPlugin() {
627 * @param plugin The joystick plugin to set.
629 public void setJoystickPlugin(ThirdPersonJoystickPlugin plugin) {
630 this.plugin = plugin;