BetterCharacterControl acts like trampoline?

@normen said: @mifth Well, the test also uses a big box mesh with triangles larger than the character. Try walking on the sphere.

@normen , hi again. I tested the capsule model on a big plane. And it does not flicker. I think the issue is that the BetterCharacterControl applies LinearVelosity when character stands. Also, the issue can be in a small damping…

I’m working on my custom implementation of CharacterContol. Which does not flicker and it can be used on BIG quads. Is there interest in a new Control?

First test:
[video]https://www.youtube.com/watch?v=jHs1dDHsDEQ[/video]

1 Like

No, why make yet another control? If you have code improvements for the existing ones, post them. I will not tear apart some other code to integrate it.

Also, if you work on a custom version of the charactercontrol, then it will never replace the bettercharactercontrol. This because it’s just 2 different approach, not a good one and a bad one. The charactercontrol is kinematic, i.e. it doesn’t react to forces (you even can’t apply forces to it). Even if the bettercharactercontrol doesn’t react to force when you walk, the idea of the better* is to be more “physic”.

@normen (i don’t know if it worth to create a new topic for that, so i just take a small place here) : you must modify your bettercharactercontrol cause as it is now you can jump on ghostcontrols. This is because of the way ou check if the character is on the ground : you do a ray test. But, if the ray test collide with a ghost control, you still consider that it’s a valid collision. This is also true for physics object which doens’t share any collide group with the bettercharactercontrol, you can jump on them. I can give a code to fix it (but you can imagine it as well, it’s pretty a plain check “is this physic control a ghost ? Do i share any collision group with it” ?).

And i use a very larg area and i don’t have problems with the bettercharactercontrol. Maybe it has something to do with the normals or anything else used by the physics engine (for example i have sometime a bug about “islands” on some map and i think it’s because of the particular shape of the mesh).

@bubuche: I never got any actual test case for the shaking, it could well be wrong usage or corner cases, yes.
About the issue with jumping: you mean that you can jump at all when your feet are close to a ghost control, right? The way you write it it sounds like your BetterCharacter is standing on top of a ghost control, which should not happen (and doesn’t for me). If its the former then yes, the check for “onGround” and “canJump” should generally still be improved.

@normen said: No, why make yet another control? If you have code improvements for the existing ones, post them. I will not tear apart some other code to integrate it.

i’ll try to check what i can do for the BetterCharacterControl. My control has a different approach.

@bubuche I dunno want to replace THe BetterCharacterControl. I try to make the control i need for my games. As the BetterCharacterControl has some issues for me. :frowning:
My control is not kinematic.

You get the problem when you jump in a ghostcontrol and keep the key pressed. So, you “jump” (or try to do it) every frame, and when you reach the border (or actually everyframe, not sure why) of the ghostcontrol you “jump” again, resulting in a very very high jump.
The fun fact is that i discovered that when i was trying to create a “push back” element, which was supposed to apply a push back force to any physics in the ghostcontrol area. I was happy in the first place when i jumped very high as i took it for the right comportement, then i realized that even without the pushback effect this element allowed me to do super-jump. Yes, there was some magic there, i thought, the element was doing what it was supposed to do, even without the code.

then, i ended with the results i mentioned : when you jump in a ghostcontrol, you jump very high.

And i have an other problem with ghostcontrols : when they collide with complex shape, the whole app slow down a LOT. I think i can provide test case for this. If you replace it with a rigidbodycontrol (with the correct group) the bug vanish. But i think i’ll create a topic for things like that.

@normen said: No, why make yet another control? If you have code improvements for the existing ones, post them. I will not tear apart some other code to integrate it.

Ok, I did some changes. The character now can sleep. As damping should be applied only when the character stop moving.
My patch: # This patch file was generated by NetBeans IDE# This patch can be applied usi - Pastebin.com

VideoTest (Capsule now can become BLUE):
[video]http://www.youtube.com/watch?v=LmeGvZl2GCo&feature=youtu.be[/video]

Flickering still happens when the character moves. You have a mistake in the math somewhere in the PreTickListaener. But I cannot find it as i’m not so skilled in math.
But when the character stands - NO FLICKERING HAPPENS! :slight_smile:

@normen , can you review my patch?

I sloppily edited BetterCharacterControl to forget it’s velocity, not bounce, use custom gravity (not from the phys engine), and walk up slopes. It is a dirty hack, but its the only way I could get my game working exactly how I wanted it to. Without hacking the gravity, I couldn’t have different fall speeds for in air/ in water. Now it all works flawlessly! Hopefully somebody can put my code to good use, but please remember it is very sloppy and uses dirty tricks.

[java]
/*

  • Copyright © 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 mygame.gameobjects;

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.control.AbstractPhysicsControl;
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;

import mygame.MainApp;

/**

  • 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

  • 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&lt;PhysicsRayTestResult&gt; 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 &amp;&amp; !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
    }

}[/java]

Also, my contribution of another character control here too http://hub.jmonkeyengine.org/forum/topic/simplecharactercontrol-contribution/
This is not BetterCharacter replacement. This is just another approach.

@admazzola
For the love of all things geeky, why paste the whole class like that!? Just use a diff. Nobody will go through line-by-line to check what has changed from the original one. A diff would tell us right away the changes you made. :confused:

@mifth
Why should I consider your control over the default one? It’s fine that you’ve put it public and offering it to whoever wants it, but you should explain what it does differently than the default one.

@madjack said: Why should I consider your control over the default one? It's fine that you've put it public and offering it to whoever wants it, but you should explain what it does differently than the default one.

My control is not default. It’s just another approach which works differently. Issues of BetterCharacterControl:

  • Flickering on big polygons.
  • CharacterControl does not sleep.

That’s why i did my version. I would use BetterCharacterControl if it worked ok for my purposes.
And this is not BetterCharacterControl replacement. If the BetterCharacterControl works ok for you - so use it. :slight_smile:
I use my control for my project.

@mifth
You’re not selling yourself (your code) very well. :stuck_out_tongue:

All I’m saying is, if you’re offering your code, at least inform other why they could use your code, what the difference it has between than what’s already offered. That’s all.

@madjack said: @mifth You're not selling yourself (your code) very well. :P

All I’m saying is, if you’re offering your code, at least inform other why they could use your code, what the difference it has between than what’s already offered. That’s all.

I’m not selling here anything. I just mention that BetterCharacterControl has some issues. For example, if you take NativeBullet lib and run the BetterCharacterControl test you will see flickering (up and down).

[video]http://www.youtube.com/watch?v=rMWOQw6-e_8&feature=youtu.be[/video]

So my Control solves such issues etc…
As i said before, If the betterCharacterControl works for you ok, so you can use it. :slight_smile:
But that’s not my case.

@mifth said: I'm not selling here anything. I just mention that BetterCharacterControl has some issues. For example, if you take NativeBullet lib and run the BetterCharacterControl test you will see flickering (up and down).

[video]http://www.youtube.com/watch?v=rMWOQw6-e_8&feature=youtu.be[/video]

So my Control solves such issues etc…
As i said before, If the betterCharacterControl works for you ok, so you can use it. :slight_smile:
But that’s not my case.

I think there is a language gap here.

Madjack was just suggesting that if you post a “new widget” that it’s helpful if you describe what your “new widget” does.

You seem to be interpreting it as him suggesting that you needed to defend your actions… but that’s not the case here. That’s what he means by “selling”. In other words, if you are interested in other people using a thing then it’s a good idea to describe why they might want to use such a thing.

1 Like
@pspeed said: I think there is a language gap here.

Madjack was just suggesting that if you post a “new widget” that it’s helpful if you describe what your “new widget” does.

You seem to be interpreting it as him suggesting that you needed to defend your actions… but that’s not the case here. That’s what he means by “selling”. In other words, if you are interested in other people using a thing then it’s a good idea to describe why they might want to use such a thing.

Sorry, english is not my mother language. Sometimes i can misunderstood something.
I think i already described my control here http://hub.jmonkeyengine.org/forum/topic/simplecharactercontrol-contribution/

Features:

  • Used RigidBody
  • Physical object sleeps
  • Simple Code to modify
  • Slope Angle limitation.
  • Control can be used on big polygons. No any flickering.

And i also did the video with explanation there.
The control and example test can be found on the first post.
Basically, I use RigidBody with linearVelosity applied.

Guys, i respect you all! :slight_smile:

2 Likes