edits by admazzola
*/
public class CustomBetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener {
protected static final Logger logger = Logger.getLogger(CustomBetterCharacterControl.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 canWalkUpRamp = false;
protected boolean ducked = false;
protected boolean wantToUnDuck = false;
/**
- Only used for serialization, do not use this constructor.
*/
public CustomBetterCharacterControl() {
jumpForce = new Vector3f();
}
Vector3f DEFAULT_GRAVITY = new Vector3f(0,-9.81f,0);
/**
-
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 CustomBetterCharacterControl(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);
rigidBody.setRestitution(0);
rigidBody.setFriction(1);
setGravity(DEFAULT_GRAVITY);
localUp.set(DEFAULT_GRAVITY).normalizeLocal().negateLocal();
updateLocalCoordinateSystem();
// rigidBody.setGravity(Vector3f.ZERO.clone());
}
@Override
public void update(float tpf) {
super.update(tpf);
rigidBody.getPhysicsLocation(location);
//rotation has been set through viewDirection
applyPhysicsTransform(location, rotation);
if(debugTools != null){
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);
if(debugTools != null){
debugTools.show(rm, vp);
}
}
/**
-
Used internally, don’t call manually
-
-
@param space
-
@param tpf
*/
public void prePhysicsTick(PhysicsSpace space, float tpf) {
checkOnGround();
checkCanWalkUpRamp();
checkUnderwater();
updateFallingVelocity(tpf);
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);
if(debugTools != null){
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
if(debugTools != null){
debugTools.setYellowArrow(location, localWalkDirection);
}
velocity.addLocal(localWalkDirection);
vars.release();
} else {
if(debugTools != null){
debugTools.setYellowArrow(location, Vector3f.ZERO);
}
}*/
rigidBody.setFriction(0.1f);
if(canWalkUpRamp){
System.out.println(“walking up ramp”);
walkDirection.addLocal(0, 11, 0);
}
rigidBody.setLinearVelocity( walkDirection.add( fallingVelocity ) );
if (jump) {
//TODO: precalculate jump force
TempVars vars = TempVars.get();
Vector3f rotatedJumpForce = vars.vect1;
rotatedJumpForce.set(jumpForce).multLocal(5f);
// rigidBody.applyImpulse(localForwardRotation.multLocal(rotatedJumpForce), Vector3f.ZERO);
jump = false;
vars.release();
System.out.println("JUMPING");
fallingVelocity = getGravity().negate();
}
}
Vector3f fallingVelocity = new Vector3f();
private void updateFallingVelocity( float tpf) {
if(onGround || underWater){
if(fallingVelocity.y <= 0){
fallingVelocity = new Vector3f(0,0,0);
}
}else{
if(Math.abs(fallingVelocity.y) < Math.abs(getTerminalVelocity().y) ){
fallingVelocity.addLocal( getGravity().mult(tpf*2) );
}
}
}
private Vector3f getTerminalVelocity() {
// TODO Auto-generated method stub
return getGravity().mult(3);
}
/**
-
Used internally, don’t call manually
-
-
@param space
-
@param tpf
*/
public void physicsTick(PhysicsSpace space, float tpf) {
rigidBody.getLinearVelocity(velocity);
if(debugTools != null){
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() {
if (!onGround && (rigidBody.getPhysicsLocation().y > MainApp.getGameState().getGamedata().OCEAN_LEVEL - 2) ) {
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) {
if (vec == null) {
vec = Vector3f.UNIT_Z;
}
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
*/
Vector3f gravity = new Vector3f(0,9.81f,0);
public void setGravity(Vector3f gravity) {
//rigidBody.setGravity(gravity);
this.gravity.set(gravity);
}
public void setGravity(float magnitude) {
setGravity(new Vector3f(0,-magnitude,0));
}
/**
- Get the current gravity of the character.
-
-
@return
*/
public Vector3f getGravity() {
return gravity;
}
/**
- 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 rayLoc = vars.vect1;
Vector3f rayVector = vars.vect2;
float height = getFinalHeight();
rayLoc.set(localUp).multLocal(height).addLocal(this.location);
rayVector.set(localUp).multLocal(-height - 0.1f).addLocal(rayLoc);
if(debugTools != null){
debugTools.setMagentaArrow(rayLoc, rayVector.subtract(rayLoc));
}
List<PhysicsRayTestResult> results = space.rayTest(rayLoc, rayVector);
vars.release();
for (PhysicsRayTestResult physicsRayTestResult : results) {
if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) {
onGround = true;
return;
}
}
onGround = false;
}
protected void checkCanWalkUpRamp() {
TempVars vars = TempVars.get();
Vector3f rayLoc = vars.vect1;
Vector3f rayVector = vars.vect2;
//float height = getFinalHeight();
boolean blockAtTorso = false;
boolean blockAtFeet = false;
//if block is at feet but not at torso, automatically increase pos.y to walk up ramp
Vector3f horizWalkDirection = walkDirection.clone();
horizWalkDirection.y = 0;
rayLoc.set(localUp).multLocal(1).addLocal(this.location);
rayVector.set(horizWalkDirection).multLocal(0.2f).addLocal(rayLoc);
List<PhysicsRayTestResult> results = space.rayTest(rayLoc, rayVector);
//vars.release();
for (PhysicsRayTestResult physicsRayTestResult : results) {
if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) {
blockAtFeet = true;
}
}
rayLoc.set(localUp).multLocal(4).addLocal(this.location);
rayVector.set(horizWalkDirection).multLocal(0.2f).addLocal(rayLoc);
results = space.rayTest(rayLoc, rayVector);
vars.release();
for (PhysicsRayTestResult physicsRayTestResult : results) {
if (!physicsRayTestResult.getCollisionObject().equals(rigidBody)) {
blockAtTorso = true;
}
}
canWalkUpRamp = (blockAtFeet && !blockAtTorso);
}
boolean underWater;
protected void checkUnderwater() {
underWater = getPhysicsLocation().y < MainApp.getGameState().getGamedata().OCEAN_LEVEL - 8f;
}
/**
-
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);
if(debugTools != null){
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;
}
}
if(debugTools != null){
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);
}
protected Vector3f getPhysicsLocation(){
return location;
}
/**
- 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);
}
@Override
protected void createSpatialData(Spatial spat) {
rigidBody.setUserObject(spatial);
}
@Override
protected void removeSpatialData(Spatial spat) {
rigidBody.setUserObject(null);
}
public Control cloneForSpatial(Spatial spatial) {
CustomBetterCharacterControl control = new CustomBetterCharacterControl(radius, height, mass);
control.setJumpForce(jumpForce);
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);
}
public void addCollideWithGroup(int collisiongroup) {
//FIX ME
}
public void removeCollideWithGroup(int collisiongroup) {
//FIX ME
}