- add first version of BetterCharacterControl, WIP
TODO: damping of local x/z plane physics forces git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10363 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
5e0fa6459c
commit
191c8290f6
@ -0,0 +1,685 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 'jMonkeyEngine' 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) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.bullet.control;
|
||||
|
||||
import com.jme3.bullet.PhysicsSpace;
|
||||
import com.jme3.bullet.PhysicsTickListener;
|
||||
import com.jme3.bullet.collision.PhysicsRayTestResult;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
|
||||
import com.jme3.bullet.debug.DebugTools;
|
||||
import com.jme3.bullet.objects.PhysicsRigidBody;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.util.TempVars;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This is intended to be a replacement for the internal bullet character class.
|
||||
* A RigidBody with cylinder collision shape is used and its velocity is set
|
||||
* continuously, a ray test is used to check if the character is on the ground.
|
||||
*
|
||||
* The character keeps his own local coordinate system which adapts based on the
|
||||
* gravity working on the character so the character will always stand upright.
|
||||
*
|
||||
* Forces in the local x/z plane are dampened while those in the local y
|
||||
* direction are applied fully (e.g. jumping, falling).
|
||||
*
|
||||
* @author normenhansen
|
||||
*/
|
||||
public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName());
|
||||
protected PhysicsRigidBody rigidBody;
|
||||
protected float radius;
|
||||
protected float height;
|
||||
protected float mass;
|
||||
protected float duckedFactor = 0.6f;
|
||||
/**
|
||||
* Local up direction, derived from gravity.
|
||||
*/
|
||||
protected final Vector3f localUp = new Vector3f(0, 1, 0);
|
||||
/**
|
||||
* Local absolute z-forward direction, derived from gravity and UNIT_Z,
|
||||
* updated continuously when gravity changes.
|
||||
*/
|
||||
protected final Vector3f localForward = new Vector3f(0, 0, 1);
|
||||
/**
|
||||
* Local z-forward quaternion for the "local absolute" z-forward direction.
|
||||
*/
|
||||
protected final Quaternion localForwardRotation = new Quaternion(Quaternion.DIRECTION_Z);
|
||||
/**
|
||||
* Is a z-forward vector based on the view direction and the current local
|
||||
* x/z plane.
|
||||
*/
|
||||
protected final Vector3f viewDirection = new Vector3f(0, 0, 1);
|
||||
/**
|
||||
* Stores final spatial location, corresponds to RigidBody location.
|
||||
*/
|
||||
protected final Vector3f location = new Vector3f();
|
||||
/**
|
||||
* Stores final spatial rotation, is a z-forward rotation based on the view
|
||||
* direction and the current local x/z plane. See also rotatedViewDirection.
|
||||
*/
|
||||
protected final Quaternion rotation = new Quaternion(Quaternion.DIRECTION_Z);
|
||||
protected final Vector3f rotatedViewDirection = new Vector3f(0, 0, 1);
|
||||
protected final Vector3f walkDirection = new Vector3f();
|
||||
protected final Vector3f jumpForce;
|
||||
protected final Vector3f physicsDampening = new Vector3f(0.3f, 0, 0.3f);
|
||||
protected final Vector3f scale = new Vector3f(1, 1, 1);
|
||||
protected final Vector3f velocity = new Vector3f();
|
||||
protected boolean jump = false;
|
||||
protected boolean onGround = false;
|
||||
protected boolean ducked = false;
|
||||
protected boolean wantToUnDuck = false;
|
||||
|
||||
/**
|
||||
* Only used for serialization, do not use this constructor.
|
||||
*/
|
||||
public BetterCharacterControl() {
|
||||
jumpForce = new Vector3f();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new character with the given properties. Note that to avoid
|
||||
* issues the final height when ducking should be larger than 2x radius. The
|
||||
* jumpForce will be set to an upwards force of 5x mass.
|
||||
*
|
||||
* @param radius
|
||||
* @param height
|
||||
* @param mass
|
||||
*/
|
||||
public BetterCharacterControl(float radius, float height, float mass) {
|
||||
this.radius = radius;
|
||||
this.height = height;
|
||||
this.mass = mass;
|
||||
rigidBody = new PhysicsRigidBody(getShape(), mass);
|
||||
jumpForce = new Vector3f(0, mass * 5, 0);
|
||||
rigidBody.setAngularFactor(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
super.update(tpf);
|
||||
rigidBody.getPhysicsLocation(location);
|
||||
//rotation has been set through viewDirection
|
||||
applyPhysicsTransform(location, rotation);
|
||||
debugTools.setPinkArrow(location, localForward);
|
||||
}
|
||||
private DebugTools debugTools = null;
|
||||
|
||||
public void setDebugTools(DebugTools debugTools) {
|
||||
this.debugTools = debugTools;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderManager rm, ViewPort vp) {
|
||||
super.render(rm, vp);
|
||||
debugTools.show(rm, vp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally, don't call manually
|
||||
*
|
||||
* @param space
|
||||
* @param tpf
|
||||
*/
|
||||
public void prePhysicsTick(PhysicsSpace space, float tpf) {
|
||||
checkOnGround();
|
||||
if (wantToUnDuck && checkCanUnDuck()) {
|
||||
setHeightPercent(1);
|
||||
wantToUnDuck = false;
|
||||
ducked = false;
|
||||
}
|
||||
|
||||
//TODO: this damping (physicsInfluence) is not framerate decoupled
|
||||
// Vector3f physicsPlane = localForwardRotation.mult(physicsDampening);
|
||||
// Vector3f counter = velocity.mult(physicsPlane).negateLocal().multLocal(tpf * 100.0f);
|
||||
// velocity.addLocal(counter);
|
||||
// debugTools.setGreenArrow(location, counter);
|
||||
|
||||
debugTools.setBlueArrow(location, walkDirection);
|
||||
|
||||
float designatedVelocity = walkDirection.length();
|
||||
if (designatedVelocity > 0) {
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f localWalkDirection = vars.vect1;
|
||||
//normalize walkdirection
|
||||
localWalkDirection.set(walkDirection).normalizeLocal();
|
||||
//check for the existing velocity in the desired direction
|
||||
float existingVelocity = velocity.dot(localWalkDirection);
|
||||
//calculate the final velocity in the desired direction
|
||||
float finalVelocity = designatedVelocity - existingVelocity;
|
||||
localWalkDirection.multLocal(finalVelocity);
|
||||
//add resulting vector to existing velocity
|
||||
debugTools.setYellowArrow(location, localWalkDirection);
|
||||
velocity.addLocal(localWalkDirection);
|
||||
vars.release();
|
||||
} else {
|
||||
debugTools.setYellowArrow(location, Vector3f.ZERO);
|
||||
}
|
||||
rigidBody.setLinearVelocity(velocity);
|
||||
if (jump) {
|
||||
//TODO: precalculate jump force
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f rotatedJumpForce = vars.vect1;
|
||||
rotatedJumpForce.set(jumpForce);
|
||||
rigidBody.applyImpulse(localForwardRotation.multLocal(rotatedJumpForce), Vector3f.ZERO);
|
||||
jump = false;
|
||||
vars.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used internally, don't call manually
|
||||
*
|
||||
* @param space
|
||||
* @param tpf
|
||||
*/
|
||||
public void physicsTick(PhysicsSpace space, float tpf) {
|
||||
rigidBody.getLinearVelocity(velocity);
|
||||
debugTools.setRedArrow(location, velocity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the character somewhere. Note the character also takes the location
|
||||
* of any spatial its being attached to in the moment it is attached.
|
||||
*
|
||||
* @param vec The new character location.
|
||||
*/
|
||||
public void warp(Vector3f vec) {
|
||||
setPhysicsLocation(vec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the character jump with the set jump force.
|
||||
*/
|
||||
public void jump() {
|
||||
//TODO: debounce over some frames
|
||||
if (!onGround) {
|
||||
return;
|
||||
}
|
||||
jump = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the jump force as a Vector3f. The jump force is local to the
|
||||
* characters coordinate system, which normally is always z-forward (in
|
||||
* world coordinates, parent coordinates when set to applyLocalPhysics)
|
||||
*
|
||||
* @param jumpForce The new jump force
|
||||
*/
|
||||
public void setJumpForce(Vector3f jumpForce) {
|
||||
this.jumpForce.set(jumpForce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current jump force. The default is 5 * character mass in y
|
||||
* direction.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f getJumpForce() {
|
||||
return jumpForce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the character is on the ground. This is determined by a ray test
|
||||
* in the center of the character and might return false even if the
|
||||
* character is not falling yet.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isOnGround() {
|
||||
return onGround;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle character ducking. When ducked the characters capsule collision
|
||||
* shape height will be multiplied by duckedFactor to make the capsule
|
||||
* smaller. When unducking, the character will check with a ray test if it
|
||||
* can in fact unduck and only do so when its possible. You can check the
|
||||
* state of the unducking by checking isDucked().
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setDucked(boolean enabled) {
|
||||
if (enabled) {
|
||||
setHeightPercent(duckedFactor);
|
||||
ducked = true;
|
||||
wantToUnDuck = false;
|
||||
} else {
|
||||
if (checkCanUnDuck()) {
|
||||
setHeightPercent(1);
|
||||
ducked = false;
|
||||
} else {
|
||||
wantToUnDuck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the character is ducking, either due to user input or due to
|
||||
* unducking being impossible at the moment (obstacle above).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isDucked() {
|
||||
return ducked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height multiplication factor for ducking.
|
||||
*
|
||||
* @param factor The factor by which the height should be multiplied when
|
||||
* ducking
|
||||
*/
|
||||
public void setDuckedFactor(float factor) {
|
||||
duckedFactor = factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height multiplication factor for ducking.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public float getDuckedFactor() {
|
||||
return duckedFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the walk direction of the character. This parameter is framerate
|
||||
* independent and the character will move continuously in the direction
|
||||
* given by the vector with the speed given by the vector length in m/s.
|
||||
*
|
||||
* @param vec The movement direction and speed in m/s
|
||||
*/
|
||||
public void setWalkDirection(Vector3f vec) {
|
||||
walkDirection.set(vec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current walk direction and speed of the character. The length of
|
||||
* the vector defines the speed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f getWalkDirection() {
|
||||
return walkDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the view direction for the character. Note this only defines the
|
||||
* rotation of the spatial in the local x/z plane of the character.
|
||||
*
|
||||
* @param vec
|
||||
*/
|
||||
public void setViewDirection(Vector3f vec) {
|
||||
viewDirection.set(vec);
|
||||
updateLocalViewDirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current view direction, note this doesn't need to correspond
|
||||
* with the spatials forward direction.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f getViewDirection() {
|
||||
return viewDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realign the local forward vector to given direction vector, if null is
|
||||
* supplied Vector3f.UNIT_Z is used. Input vector has to be perpendicular to
|
||||
* current gravity vector. This normally only needs to be called when the
|
||||
* gravity direction changed continuously and the local forward vector is
|
||||
* off due to drift. E.g. after walking around on a sphere "planet" for a
|
||||
* while and then going back to a y-up coordinate system the local z-forward
|
||||
* might not be 100% alinged with Z axis.
|
||||
*
|
||||
* @param vec The new forward vector, has to be perpendicular to the current
|
||||
* gravity vector!
|
||||
*/
|
||||
public void resetForward(Vector3f vec) {
|
||||
localForward.set(vec);
|
||||
updateLocalCoordinateSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current linear velocity along the three axes of the character.
|
||||
* This is prepresented in world coordinates, parent coordinates when the
|
||||
* control is set to applyLocalPhysics.
|
||||
*
|
||||
* @return The current linear velocity of the character
|
||||
*/
|
||||
public Vector3f getVelocity() {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gravity for this character. Note that this also realigns the
|
||||
* local coordinate system of the character so that continuous changes in
|
||||
* gravity direction are possible while maintaining a sensible control over
|
||||
* the character.
|
||||
*
|
||||
* @param gravity
|
||||
*/
|
||||
public void setGravity(Vector3f gravity) {
|
||||
rigidBody.setGravity(gravity);
|
||||
localUp.set(gravity).normalizeLocal().negateLocal();
|
||||
updateLocalCoordinateSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current gravity of the character.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f getGravity() {
|
||||
return rigidBody.getGravity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current gravity of the character.
|
||||
*
|
||||
* @param store The vector to store the result in
|
||||
* @return
|
||||
*/
|
||||
public Vector3f getGravity(Vector3f store) {
|
||||
return rigidBody.getGravity(store);
|
||||
}
|
||||
|
||||
/**
|
||||
* This actually sets a new collision shape to the character to change the
|
||||
* height of the capsule.
|
||||
*
|
||||
* @param percent
|
||||
*/
|
||||
protected void setHeightPercent(float percent) {
|
||||
scale.setY(percent);
|
||||
rigidBody.setCollisionShape(getShape());
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the character is on the ground by doing a ray test.
|
||||
*/
|
||||
protected void checkOnGround() {
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f location = vars.vect1;
|
||||
Vector3f rayVector = vars.vect2;
|
||||
float height = getFinalHeight();
|
||||
location.set(localUp).multLocal(height).addLocal(this.location);
|
||||
rayVector.set(localUp).multLocal(-height - FastMath.ZERO_TOLERANCE).addLocal(location);
|
||||
debugTools.setMagentaArrow(location, rayVector.subtract(location));
|
||||
List<PhysicsRayTestResult> results = space.rayTest(location, rayVector);
|
||||
vars.release();
|
||||
for (PhysicsRayTestResult physicsRayTestResult : results) {
|
||||
if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) {
|
||||
onGround = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
onGround = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the character can go from ducked to unducked state by
|
||||
* doing a ray test.
|
||||
*/
|
||||
protected boolean checkCanUnDuck() {
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f location = vars.vect1;
|
||||
Vector3f rayVector = vars.vect2;
|
||||
location.set(localUp).multLocal(FastMath.ZERO_TOLERANCE).addLocal(this.location);
|
||||
rayVector.set(localUp).multLocal(height + FastMath.ZERO_TOLERANCE).addLocal(location);
|
||||
debugTools.setMagentaArrow(location, rayVector.subtract(location));
|
||||
List<PhysicsRayTestResult> results = space.rayTest(location, rayVector);
|
||||
vars.release();
|
||||
for (PhysicsRayTestResult physicsRayTestResult : results) {
|
||||
if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
debugTools.setMagentaArrow(location, Vector3f.ZERO);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new collision shape based on the current scale parameter. The
|
||||
* created collisionshape is a capsule collision shape that is attached to a
|
||||
* compound collision shape with an offset to set the object center at the
|
||||
* bottom of the capsule.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected CollisionShape getShape() {
|
||||
//TODO: cleanup size mess..
|
||||
CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(getFinalRadius(), (getFinalHeight() - (2 * getFinalRadius())));
|
||||
CompoundCollisionShape compoundCollisionShape = new CompoundCollisionShape();
|
||||
Vector3f addLocation = new Vector3f(0, (getFinalHeight() / 2.0f), 0);
|
||||
compoundCollisionShape.addChildShape(capsuleCollisionShape, addLocation);
|
||||
return compoundCollisionShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scaled height.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected float getFinalHeight() {
|
||||
return height * scale.getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scaled radius.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected float getFinalRadius() {
|
||||
return radius * scale.getZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local coordinate system from the localForward and localUp
|
||||
* vectors, adapts localForward, sets localForwardRotation quaternion to
|
||||
* local z-forward rotation.
|
||||
*/
|
||||
protected void updateLocalCoordinateSystem() {
|
||||
//gravity vector has possibly changed, calculate new world forward (UNIT_Z)
|
||||
calculateNewForward(localForwardRotation, localForward, localUp);
|
||||
rigidBody.setPhysicsRotation(localForwardRotation);
|
||||
updateLocalViewDirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local x/z-flattened view direction and the corresponding
|
||||
* rotation quaternion for the spatial.
|
||||
*/
|
||||
protected void updateLocalViewDirection() {
|
||||
//update local rotation quaternion to use for view rotation
|
||||
localForwardRotation.multLocal(rotatedViewDirection.set(viewDirection));
|
||||
calculateNewForward(rotation, rotatedViewDirection, localUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method works similar to Camera.lookAt but where lookAt sets the
|
||||
* priority on the direction, this method sets the priority on the up vector
|
||||
* so that the result direction vector and rotation is guaranteed to be
|
||||
* perpendicular to the up vector.
|
||||
*
|
||||
* @param rotation The rotation to set the result on or null to create a new
|
||||
* Quaternion, this will be set to the new "z-forward" rotation if not null
|
||||
* @param direction The direction to base the new look direction on, will be
|
||||
* set to the new direction
|
||||
* @param worldUpVector The up vector to use, the result direction will be
|
||||
* perpendicular to this
|
||||
* @return
|
||||
*/
|
||||
protected final void calculateNewForward(Quaternion rotation, Vector3f direction, Vector3f worldUpVector) {
|
||||
if (direction == null) {
|
||||
return;
|
||||
}
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f newLeft = vars.vect1;
|
||||
Vector3f newLeftNegate = vars.vect2;
|
||||
|
||||
newLeft.set(worldUpVector).crossLocal(direction).normalizeLocal();
|
||||
if (newLeft.equals(Vector3f.ZERO)) {
|
||||
if (direction.x != 0) {
|
||||
newLeft.set(direction.y, -direction.x, 0f).normalizeLocal();
|
||||
} else {
|
||||
newLeft.set(0f, direction.z, -direction.y).normalizeLocal();
|
||||
}
|
||||
logger.log(Level.INFO, "Zero left for direction {0}, up {1}", new Object[]{direction, worldUpVector});
|
||||
}
|
||||
newLeftNegate.set(newLeft).negateLocal();
|
||||
direction.set(worldUpVector).crossLocal(newLeftNegate).normalizeLocal();
|
||||
if (direction.equals(Vector3f.ZERO)) {
|
||||
direction.set(Vector3f.UNIT_Z);
|
||||
logger.log(Level.INFO, "Zero left for left {0}, up {1}", new Object[]{newLeft, worldUpVector});
|
||||
}
|
||||
if (rotation != null) {
|
||||
rotation.fromAxes(newLeft, worldUpVector, direction);
|
||||
}
|
||||
vars.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is implemented from AbstractPhysicsControl and called when the
|
||||
* spatial is attached for example.
|
||||
*
|
||||
* @param vec
|
||||
*/
|
||||
@Override
|
||||
protected void setPhysicsLocation(Vector3f vec) {
|
||||
rigidBody.setPhysicsLocation(vec);
|
||||
location.set(vec);
|
||||
}
|
||||
|
||||
/**
|
||||
* We set the current spatial as UserObject so the user can find his
|
||||
* spatial.
|
||||
*
|
||||
* @param spatial
|
||||
*/
|
||||
@Override
|
||||
public void setSpatial(Spatial spatial) {
|
||||
super.setSpatial(spatial);
|
||||
rigidBody.setUserObject(spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is implemented from AbstractPhysicsControl and called when the
|
||||
* spatial is attached for example. We don't set the actual physics rotation
|
||||
* but the view rotation here. It might actually be altered by the
|
||||
* calculateNewForward method.
|
||||
*
|
||||
* @param quat
|
||||
*/
|
||||
@Override
|
||||
protected void setPhysicsRotation(Quaternion quat) {
|
||||
rotation.set(quat);
|
||||
rotation.multLocal(rotatedViewDirection.set(viewDirection));
|
||||
updateLocalViewDirection();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is implemented from AbstractPhysicsControl and called when the
|
||||
* control is supposed to add all objects to the physics space.
|
||||
*
|
||||
* @param space
|
||||
*/
|
||||
@Override
|
||||
protected void addPhysics(PhysicsSpace space) {
|
||||
space.getGravity(localUp).normalizeLocal().negateLocal();
|
||||
updateLocalCoordinateSystem();
|
||||
|
||||
space.addCollisionObject(rigidBody);
|
||||
space.addTickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is implemented from AbstractPhysicsControl and called when the
|
||||
* control is supposed to remove all objects from the physics space.
|
||||
*
|
||||
* @param space
|
||||
*/
|
||||
@Override
|
||||
protected void removePhysics(PhysicsSpace space) {
|
||||
space.removeCollisionObject(rigidBody);
|
||||
space.removeTickListener(this);
|
||||
}
|
||||
|
||||
public Control cloneForSpatial(Spatial spatial) {
|
||||
BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
|
||||
control.setJumpForce(jumpForce);
|
||||
control.setSpatial(spatial);
|
||||
return control;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
super.write(ex);
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.write(radius, "radius", 1);
|
||||
oc.write(height, "height", 1);
|
||||
oc.write(mass, "mass", 1);
|
||||
oc.write(jumpForce, "jumpForce", new Vector3f(0, mass * 5, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
super.read(im);
|
||||
InputCapsule in = im.getCapsule(this);
|
||||
this.radius = in.readFloat("radius", 1);
|
||||
this.height = in.readFloat("height", 2);
|
||||
this.mass = in.readFloat("mass", 80);
|
||||
this.jumpForce.set((Vector3f) in.readSavable("jumpForce", new Vector3f(0, mass * 5, 0)));
|
||||
rigidBody = new PhysicsRigidBody(getShape(), mass);
|
||||
jumpForce.set(new Vector3f(0, mass * 5, 0));
|
||||
rigidBody.setAngularFactor(0);
|
||||
}
|
||||
}
|
287
engine/src/test/jme3test/bullet/TestBetterCharacter.java
Normal file
287
engine/src/test/jme3test/bullet/TestBetterCharacter.java
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. <p/>
|
||||
* 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. <p/> * 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. <p/> * Neither the name of
|
||||
* 'jMonkeyEngine' nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written
|
||||
* permission. <p/> 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) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package jme3test.bullet;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.PhysicsSpace;
|
||||
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
|
||||
import com.jme3.bullet.control.BetterCharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.debug.DebugTools;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.CameraNode;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.control.CameraControl.ControlDirection;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.system.AppSettings;
|
||||
|
||||
/**
|
||||
* A walking physical character followed by a 3rd person camera. (No animation.)
|
||||
*
|
||||
* @author normenhansen, zathras
|
||||
*/
|
||||
public class TestBetterCharacter extends SimpleApplication implements ActionListener {
|
||||
|
||||
private BulletAppState bulletAppState;
|
||||
private BetterCharacterControl physicsCharacter;
|
||||
private Node characterNode;
|
||||
private CameraNode camNode;
|
||||
boolean rotate = false;
|
||||
private Vector3f walkDirection = new Vector3f(0, 0, 0);
|
||||
private Vector3f viewDirection = new Vector3f(0, 0, 1);
|
||||
boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
|
||||
leftRotate = false, rightRotate = false;
|
||||
private Vector3f normalGravity = new Vector3f(0, -9.81f, 0);
|
||||
private Geometry planet;
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestBetterCharacter app = new TestBetterCharacter();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setRenderer(AppSettings.LWJGL_OPENGL2);
|
||||
settings.setAudioRenderer(AppSettings.LWJGL_OPENAL);
|
||||
app.setSettings(settings);
|
||||
app.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
//setup keyboard mapping
|
||||
setupKeys();
|
||||
|
||||
// activate physics
|
||||
bulletAppState = new BulletAppState();
|
||||
stateManager.attach(bulletAppState);
|
||||
bulletAppState.setDebugEnabled(true);
|
||||
|
||||
// init a physics test scene
|
||||
PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());
|
||||
PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());
|
||||
setupPlanet();
|
||||
|
||||
// Create a node for the character model
|
||||
characterNode = new Node("character node");
|
||||
characterNode.setLocalTranslation(new Vector3f(4, 5, 2));
|
||||
|
||||
// Add a character control to the node so we can add other things and
|
||||
// control the model rotation
|
||||
physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f);
|
||||
physicsCharacter.setDebugTools(new DebugTools(assetManager));
|
||||
characterNode.addControl(physicsCharacter);
|
||||
getPhysicsSpace().add(physicsCharacter);
|
||||
|
||||
// Load model, attach to character node
|
||||
Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o");
|
||||
model.setLocalScale(1.50f);
|
||||
characterNode.attachChild(model);
|
||||
|
||||
// Add character node to the rootNode
|
||||
rootNode.attachChild(characterNode);
|
||||
|
||||
// Set forward camera node that follows the character, only used when
|
||||
// view is "locked"
|
||||
camNode = new CameraNode("CamNode", cam);
|
||||
camNode.setControlDir(ControlDirection.SpatialToCamera);
|
||||
camNode.setLocalTranslation(new Vector3f(0, 2, -6));
|
||||
Quaternion quat = new Quaternion();
|
||||
// These coordinates are local, the camNode is attached to the character node!
|
||||
quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
|
||||
camNode.setLocalRotation(quat);
|
||||
characterNode.attachChild(camNode);
|
||||
// Disable by default, can be enabled via keyboard shortcut
|
||||
camNode.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {
|
||||
// Apply planet gravity to character if close enough (see below)
|
||||
checkPlanetGravity();
|
||||
|
||||
// Get current forward and left vectors of model by using its rotation
|
||||
// to rotate the unit vectors
|
||||
Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z);
|
||||
Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X);
|
||||
|
||||
// WalkDirection is global!
|
||||
// You *can* make your character fly with this.
|
||||
walkDirection.set(0, 0, 0);
|
||||
if (leftStrafe) {
|
||||
walkDirection.addLocal(modelLeftDir.mult(3));
|
||||
} else if (rightStrafe) {
|
||||
walkDirection.addLocal(modelLeftDir.negate().multLocal(3));
|
||||
}
|
||||
if (forward) {
|
||||
walkDirection.addLocal(modelForwardDir.mult(3));
|
||||
} else if (backward) {
|
||||
walkDirection.addLocal(modelForwardDir.negate().multLocal(3));
|
||||
}
|
||||
physicsCharacter.setWalkDirection(walkDirection);
|
||||
|
||||
// ViewDirection is local to characters physics system!
|
||||
// The final world rotation depends on the gravity and on the state of
|
||||
// setApplyPhysicsLocal()
|
||||
if (leftRotate) {
|
||||
Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y);
|
||||
rotateL.multLocal(viewDirection);
|
||||
} else if (rightRotate) {
|
||||
Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y);
|
||||
rotateR.multLocal(viewDirection);
|
||||
}
|
||||
physicsCharacter.setViewDirection(viewDirection);
|
||||
fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround());
|
||||
if (!lockView) {
|
||||
cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPlanet() {
|
||||
Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
|
||||
//immovable sphere with mesh collision shape
|
||||
Sphere sphere = new Sphere(64, 64, 20);
|
||||
planet = new Geometry("Sphere", sphere);
|
||||
planet.setMaterial(material);
|
||||
planet.setLocalTranslation(30, -15, 30);
|
||||
planet.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
|
||||
rootNode.attachChild(planet);
|
||||
getPhysicsSpace().add(planet);
|
||||
}
|
||||
|
||||
private void checkPlanetGravity() {
|
||||
Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation());
|
||||
if (planetDist.length() < 24) {
|
||||
physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f));
|
||||
} else {
|
||||
physicsCharacter.setGravity(normalGravity);
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsSpace getPhysicsSpace() {
|
||||
return bulletAppState.getPhysicsSpace();
|
||||
}
|
||||
|
||||
public void onAction(String binding, boolean value, float tpf) {
|
||||
if (binding.equals("Strafe Left")) {
|
||||
if (value) {
|
||||
leftStrafe = true;
|
||||
} else {
|
||||
leftStrafe = false;
|
||||
}
|
||||
} else if (binding.equals("Strafe Right")) {
|
||||
if (value) {
|
||||
rightStrafe = true;
|
||||
} else {
|
||||
rightStrafe = false;
|
||||
}
|
||||
} else if (binding.equals("Rotate Left")) {
|
||||
if (value) {
|
||||
leftRotate = true;
|
||||
} else {
|
||||
leftRotate = false;
|
||||
}
|
||||
} else if (binding.equals("Rotate Right")) {
|
||||
if (value) {
|
||||
rightRotate = true;
|
||||
} else {
|
||||
rightRotate = false;
|
||||
}
|
||||
} else if (binding.equals("Walk Forward")) {
|
||||
if (value) {
|
||||
forward = true;
|
||||
} else {
|
||||
forward = false;
|
||||
}
|
||||
} else if (binding.equals("Walk Backward")) {
|
||||
if (value) {
|
||||
backward = true;
|
||||
} else {
|
||||
backward = false;
|
||||
}
|
||||
} else if (binding.equals("Jump")) {
|
||||
physicsCharacter.jump();
|
||||
} else if (binding.equals("Duck")) {
|
||||
if (value) {
|
||||
physicsCharacter.setDucked(true);
|
||||
} else {
|
||||
physicsCharacter.setDucked(false);
|
||||
}
|
||||
} else if (binding.equals("Lock View")) {
|
||||
if (value && lockView) {
|
||||
lockView = false;
|
||||
} else if (value && !lockView) {
|
||||
lockView = true;
|
||||
}
|
||||
flyCam.setEnabled(!lockView);
|
||||
camNode.setEnabled(lockView);
|
||||
}
|
||||
}
|
||||
private boolean lockView = false;
|
||||
|
||||
private void setupKeys() {
|
||||
inputManager.addMapping("Strafe Left",
|
||||
new KeyTrigger(KeyInput.KEY_U),
|
||||
new KeyTrigger(KeyInput.KEY_Z));
|
||||
inputManager.addMapping("Strafe Right",
|
||||
new KeyTrigger(KeyInput.KEY_O),
|
||||
new KeyTrigger(KeyInput.KEY_X));
|
||||
inputManager.addMapping("Rotate Left",
|
||||
new KeyTrigger(KeyInput.KEY_J),
|
||||
new KeyTrigger(KeyInput.KEY_LEFT));
|
||||
inputManager.addMapping("Rotate Right",
|
||||
new KeyTrigger(KeyInput.KEY_L),
|
||||
new KeyTrigger(KeyInput.KEY_RIGHT));
|
||||
inputManager.addMapping("Walk Forward",
|
||||
new KeyTrigger(KeyInput.KEY_I),
|
||||
new KeyTrigger(KeyInput.KEY_UP));
|
||||
inputManager.addMapping("Walk Backward",
|
||||
new KeyTrigger(KeyInput.KEY_K),
|
||||
new KeyTrigger(KeyInput.KEY_DOWN));
|
||||
inputManager.addMapping("Jump",
|
||||
new KeyTrigger(KeyInput.KEY_F),
|
||||
new KeyTrigger(KeyInput.KEY_SPACE));
|
||||
inputManager.addMapping("Duck",
|
||||
new KeyTrigger(KeyInput.KEY_G),
|
||||
new KeyTrigger(KeyInput.KEY_LSHIFT),
|
||||
new KeyTrigger(KeyInput.KEY_RSHIFT));
|
||||
inputManager.addMapping("Lock View",
|
||||
new KeyTrigger(KeyInput.KEY_RETURN));
|
||||
inputManager.addListener(this, "Strafe Left", "Strafe Right");
|
||||
inputManager.addListener(this, "Rotate Left", "Rotate Right");
|
||||
inputManager.addListener(this, "Walk Forward", "Walk Backward");
|
||||
inputManager.addListener(this, "Jump", "Duck", "Lock View");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleRender(RenderManager rm) {
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user