/* 
 | 
 * Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz> 
 | 
 * 
 | 
 * Bullet Continuous Collision Detection and Physics Library 
 | 
 * Copyright (c) 2003-2008 Erwin Coumans  http://www.bulletphysics.com/ 
 | 
 * 
 | 
 * This software is provided 'as-is', without any express or implied warranty. 
 | 
 * In no event will the authors be held liable for any damages arising from 
 | 
 * the use of this software. 
 | 
 * 
 | 
 * Permission is granted to anyone to use this software for any purpose, 
 | 
 * including commercial applications, and to alter it and redistribute it 
 | 
 * freely, subject to the following restrictions: 
 | 
 * 
 | 
 * 1. The origin of this software must not be misrepresented; you must not 
 | 
 *    claim that you wrote the original software. If you use this software 
 | 
 *    in a product, an acknowledgment in the product documentation would be 
 | 
 *    appreciated but is not required. 
 | 
 * 2. Altered source versions must be plainly marked as such, and must not be 
 | 
 *    misrepresented as being the original software. 
 | 
 * 3. This notice may not be removed or altered from any source distribution. 
 | 
 */ 
 | 
  
 | 
package com.bulletphysics.dynamics.character; 
 | 
  
 | 
import com.bulletphysics.BulletGlobals; 
 | 
import com.bulletphysics.collision.broadphase.BroadphasePair; 
 | 
import com.bulletphysics.collision.dispatch.CollisionObject; 
 | 
import com.bulletphysics.collision.dispatch.CollisionWorld; 
 | 
import com.bulletphysics.collision.dispatch.GhostObject; 
 | 
import com.bulletphysics.collision.dispatch.PairCachingGhostObject; 
 | 
import com.bulletphysics.collision.narrowphase.ManifoldPoint; 
 | 
import com.bulletphysics.collision.narrowphase.PersistentManifold; 
 | 
import com.bulletphysics.collision.shapes.ConvexShape; 
 | 
import com.bulletphysics.dynamics.ActionInterface; 
 | 
import com.bulletphysics.linearmath.IDebugDraw; 
 | 
import com.bulletphysics.linearmath.Transform; 
 | 
import com.bulletphysics.util.ObjectArrayList; 
 | 
import cz.advel.stack.Stack; 
 | 
import javax.vecmath.Vector3f; 
 | 
  
 | 
/** 
 | 
 * KinematicCharacterController is an object that supports a sliding motion in 
 | 
 * a world. It uses a {@link GhostObject} and convex sweep test to test for upcoming 
 | 
 * collisions. This is combined with discrete collision detection to recover 
 | 
 * from penetrations.<p> 
 | 
 * 
 | 
 * Interaction between KinematicCharacterController and dynamic rigid bodies 
 | 
 * needs to be explicity implemented by the user. 
 | 
 *  
 | 
 * @author tomrbryn 
 | 
 */ 
 | 
public class KinematicCharacterController extends ActionInterface { 
 | 
  
 | 
    private static Vector3f[] upAxisDirection = new Vector3f[] { 
 | 
        new Vector3f(1.0f, 0.0f, 0.0f), 
 | 
        new Vector3f(0.0f, 1.0f, 0.0f), 
 | 
        new Vector3f(0.0f, 0.0f, 1.0f), 
 | 
    }; 
 | 
  
 | 
    protected float halfHeight; 
 | 
     
 | 
    protected PairCachingGhostObject ghostObject; 
 | 
  
 | 
    // is also in ghostObject, but it needs to be convex, so we store it here 
 | 
    // to avoid upcast 
 | 
    protected ConvexShape convexShape; 
 | 
  
 | 
    protected float verticalVelocity; 
 | 
    protected float verticalOffset; 
 | 
     
 | 
    protected float fallSpeed; 
 | 
    protected float jumpSpeed; 
 | 
    protected float maxJumpHeight; 
 | 
     
 | 
    protected float maxSlopeRadians; // Slope angle that is set (used for returning the exact value)  
 | 
    protected float maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) 
 | 
  
 | 
    protected float gravity; 
 | 
     
 | 
    protected float turnAngle; 
 | 
  
 | 
    protected float stepHeight; 
 | 
  
 | 
    protected float addedMargin; // @todo: remove this and fix the code 
 | 
  
 | 
    // this is the desired walk direction, set by the user 
 | 
    protected Vector3f walkDirection = new Vector3f(); 
 | 
    protected Vector3f normalizedDirection = new Vector3f(); 
 | 
  
 | 
    // some internal variables 
 | 
    protected Vector3f currentPosition = new Vector3f(); 
 | 
    protected float currentStepOffset; 
 | 
    protected Vector3f targetPosition = new Vector3f(); 
 | 
  
 | 
    // keep track of the contact manifolds 
 | 
    ObjectArrayList<PersistentManifold> manifoldArray = new ObjectArrayList<PersistentManifold>(); 
 | 
  
 | 
    protected boolean touchingContact; 
 | 
    protected Vector3f touchingNormal = new Vector3f(); 
 | 
  
 | 
    protected boolean wasOnGround; 
 | 
     
 | 
    protected boolean useGhostObjectSweepTest; 
 | 
    protected boolean useWalkDirection; 
 | 
    protected float velocityTimeInterval; 
 | 
    protected int upAxis; 
 | 
  
 | 
    protected CollisionObject me; 
 | 
  
 | 
    public KinematicCharacterController(PairCachingGhostObject ghostObject, ConvexShape convexShape, float stepHeight) { 
 | 
        this(ghostObject, convexShape, stepHeight, 1); 
 | 
    } 
 | 
  
 | 
    public KinematicCharacterController(PairCachingGhostObject ghostObject, ConvexShape convexShape, float stepHeight, int upAxis) { 
 | 
        this.upAxis = upAxis; 
 | 
        this.addedMargin = 0.02f; 
 | 
        this.walkDirection.set(0, 0, 0); 
 | 
        this.useGhostObjectSweepTest = true; 
 | 
        this.ghostObject = ghostObject; 
 | 
        this.stepHeight = stepHeight; 
 | 
        this.turnAngle = 0.0f; 
 | 
        this.convexShape = convexShape; 
 | 
        this.useWalkDirection = true; 
 | 
        this.velocityTimeInterval = 0.0f; 
 | 
        this.verticalVelocity = 0.0f; 
 | 
        this.verticalOffset = 0.0f; 
 | 
        this.gravity = 9.8f; // 1G acceleration 
 | 
        this.fallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. 
 | 
        this.jumpSpeed = 10.0f; // ? 
 | 
        this.wasOnGround = false; 
 | 
        setMaxSlope((float)((50.0f/180.0f) * Math.PI)); 
 | 
    } 
 | 
  
 | 
    private PairCachingGhostObject getGhostObject() { 
 | 
        return ghostObject; 
 | 
    } 
 | 
  
 | 
    // ActionInterface interface 
 | 
    public void updateAction(CollisionWorld collisionWorld, float deltaTime) { 
 | 
        preStep(collisionWorld); 
 | 
        playerStep(collisionWorld, deltaTime); 
 | 
    } 
 | 
  
 | 
    // ActionInterface interface 
 | 
    public void debugDraw(IDebugDraw debugDrawer) { 
 | 
    } 
 | 
  
 | 
    public void setUpAxis(int axis) { 
 | 
        if (axis < 0) { 
 | 
            axis = 0; 
 | 
        } 
 | 
        if (axis > 2) { 
 | 
            axis = 2; 
 | 
        } 
 | 
        upAxis = axis; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * This should probably be called setPositionIncrementPerSimulatorStep. This 
 | 
     * is neither a direction nor a velocity, but the amount to increment the 
 | 
     * position each simulation iteration, regardless of dt.<p> 
 | 
     * 
 | 
     * This call will reset any velocity set by {@link #setVelocityForTimeInterval}. 
 | 
     */ 
 | 
    public void    setWalkDirection(Vector3f walkDirection) { 
 | 
        useWalkDirection = true; 
 | 
        this.walkDirection.set(walkDirection); 
 | 
        normalizedDirection.set(getNormalizedVector(walkDirection, Stack.alloc(Vector3f.class))); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Caller provides a velocity with which the character should move for the 
 | 
     * given time period. After the time period, velocity is reset to zero. 
 | 
     * This call will reset any walk direction set by {@link #setWalkDirection}. 
 | 
     * Negative time intervals will result in no motion. 
 | 
     */ 
 | 
    public void setVelocityForTimeInterval(Vector3f velocity, float timeInterval) { 
 | 
        useWalkDirection = false; 
 | 
        walkDirection.set(velocity); 
 | 
        normalizedDirection.set(getNormalizedVector(walkDirection, Stack.alloc(Vector3f.class))); 
 | 
        velocityTimeInterval = timeInterval; 
 | 
    } 
 | 
  
 | 
    public void reset() { 
 | 
    } 
 | 
  
 | 
    public void warp(Vector3f origin) { 
 | 
        Transform xform = Stack.alloc(Transform.class); 
 | 
        xform.setIdentity(); 
 | 
        xform.origin.set(origin); 
 | 
        ghostObject.setWorldTransform(xform); 
 | 
    } 
 | 
  
 | 
    public void preStep(CollisionWorld collisionWorld) { 
 | 
        int numPenetrationLoops = 0; 
 | 
        touchingContact = false; 
 | 
        while (recoverFromPenetration(collisionWorld)) { 
 | 
            numPenetrationLoops++; 
 | 
            touchingContact = true; 
 | 
            if (numPenetrationLoops > 4) { 
 | 
                //printf("character could not recover from penetration = %d\n", numPenetrationLoops); 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        currentPosition.set(ghostObject.getWorldTransform(Stack.alloc(Transform.class)).origin); 
 | 
        targetPosition.set(currentPosition); 
 | 
        //printf("m_targetPosition=%f,%f,%f\n",m_targetPosition[0],m_targetPosition[1],m_targetPosition[2]); 
 | 
    } 
 | 
     
 | 
    public void playerStep(CollisionWorld collisionWorld, float dt) { 
 | 
        //printf("playerStep(): "); 
 | 
        //printf("  dt = %f", dt); 
 | 
  
 | 
        // quick check... 
 | 
        if (!useWalkDirection && velocityTimeInterval <= 0.0f) { 
 | 
            //printf("\n"); 
 | 
            return; // no motion 
 | 
        } 
 | 
         
 | 
        wasOnGround = onGround(); 
 | 
         
 | 
        // Update fall velocity. 
 | 
        verticalVelocity -= gravity * dt; 
 | 
        if(verticalVelocity > 0.0 && verticalVelocity > jumpSpeed) 
 | 
        { 
 | 
            verticalVelocity = jumpSpeed; 
 | 
        } 
 | 
        if(verticalVelocity < 0.0 && Math.abs(verticalVelocity) > Math.abs(fallSpeed)) 
 | 
        { 
 | 
            verticalVelocity = -Math.abs(fallSpeed); 
 | 
        } 
 | 
        verticalOffset = verticalVelocity * dt; 
 | 
  
 | 
        Transform xform = ghostObject.getWorldTransform(Stack.alloc(Transform.class)); 
 | 
  
 | 
        //printf("walkDirection(%f,%f,%f)\n",walkDirection[0],walkDirection[1],walkDirection[2]); 
 | 
        //printf("walkSpeed=%f\n",walkSpeed); 
 | 
  
 | 
        stepUp(collisionWorld); 
 | 
        if (useWalkDirection) { 
 | 
            //System.out.println("playerStep 3"); 
 | 
            stepForwardAndStrafe(collisionWorld, walkDirection); 
 | 
        } 
 | 
        else { 
 | 
            System.out.println("playerStep 4"); 
 | 
            //printf("  time: %f", m_velocityTimeInterval); 
 | 
  
 | 
            // still have some time left for moving! 
 | 
            float dtMoving = (dt < velocityTimeInterval) ? dt : velocityTimeInterval; 
 | 
            velocityTimeInterval -= dt; 
 | 
  
 | 
            // how far will we move while we are moving? 
 | 
            Vector3f move = Stack.alloc(Vector3f.class); 
 | 
            move.scale(dtMoving, walkDirection); 
 | 
  
 | 
            //printf("  dtMoving: %f", dtMoving); 
 | 
  
 | 
            // okay, step 
 | 
            stepForwardAndStrafe(collisionWorld, move); 
 | 
        } 
 | 
        stepDown(collisionWorld, dt); 
 | 
  
 | 
        //printf("\n"); 
 | 
  
 | 
        xform.origin.set(currentPosition); 
 | 
        ghostObject.setWorldTransform(xform); 
 | 
    } 
 | 
  
 | 
    public void setFallSpeed(float fallSpeed) { 
 | 
        this.fallSpeed = fallSpeed; 
 | 
    } 
 | 
     
 | 
    public void setJumpSpeed(float jumpSpeed) { 
 | 
        this.jumpSpeed = jumpSpeed; 
 | 
    } 
 | 
  
 | 
    public void setMaxJumpHeight(float maxJumpHeight) { 
 | 
        this.maxJumpHeight = maxJumpHeight; 
 | 
    } 
 | 
     
 | 
    public boolean canJump() { 
 | 
        return onGround(); 
 | 
    } 
 | 
     
 | 
    public void jump() { 
 | 
        if (!canJump()) return; 
 | 
         
 | 
        verticalVelocity = jumpSpeed; 
 | 
  
 | 
        //#if 0 
 | 
        //currently no jumping. 
 | 
        //btTransform xform; 
 | 
        //m_rigidBody->getMotionState()->getWorldTransform (xform); 
 | 
        //btVector3 up = xform.getBasis()[1]; 
 | 
        //up.normalize (); 
 | 
        //btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0); 
 | 
        //m_rigidBody->applyCentralImpulse (up * magnitude); 
 | 
        //#endif 
 | 
    } 
 | 
     
 | 
    public void setGravity(float gravity) { 
 | 
        this.gravity = gravity; 
 | 
    } 
 | 
     
 | 
    public float getGravity() { 
 | 
        return gravity; 
 | 
    } 
 | 
     
 | 
    public void setMaxSlope(float slopeRadians) { 
 | 
        maxSlopeRadians = slopeRadians; 
 | 
        maxSlopeCosine = (float)Math.cos((float)slopeRadians); 
 | 
    } 
 | 
     
 | 
    public float getMaxSlope() { 
 | 
        return maxSlopeRadians; 
 | 
    } 
 | 
     
 | 
    public boolean onGround() { 
 | 
        return verticalVelocity == 0.0f && verticalOffset == 0.0f; 
 | 
    } 
 | 
  
 | 
    // static helper method 
 | 
    private static Vector3f getNormalizedVector(Vector3f v, Vector3f out) { 
 | 
        out.set(v); 
 | 
        out.normalize(); 
 | 
        if (out.length() < BulletGlobals.SIMD_EPSILON) { 
 | 
            out.set(0, 0, 0); 
 | 
        } 
 | 
        return out; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the reflection direction of a ray going 'direction' hitting a surface 
 | 
     * with normal 'normal'.<p> 
 | 
     * 
 | 
     * From: http://www-cs-students.stanford.edu/~adityagp/final/node3.html 
 | 
     */ 
 | 
    protected Vector3f computeReflectionDirection(Vector3f direction, Vector3f normal, Vector3f out) { 
 | 
        // return direction - (btScalar(2.0) * direction.dot(normal)) * normal; 
 | 
        out.set(normal); 
 | 
        out.scale(-2.0f * direction.dot(normal)); 
 | 
        out.add(direction); 
 | 
        return out; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the portion of 'direction' that is parallel to 'normal' 
 | 
     */ 
 | 
    protected Vector3f parallelComponent(Vector3f direction, Vector3f normal, Vector3f out) { 
 | 
        //btScalar magnitude = direction.dot(normal); 
 | 
        //return normal * magnitude; 
 | 
        out.set(normal); 
 | 
        out.scale(direction.dot(normal)); 
 | 
        return out; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the portion of 'direction' that is perpindicular to 'normal' 
 | 
     */ 
 | 
    protected Vector3f perpindicularComponent(Vector3f direction, Vector3f normal, Vector3f out) { 
 | 
        //return direction - parallelComponent(direction, normal); 
 | 
        Vector3f perpendicular = parallelComponent(direction, normal, out); 
 | 
        perpendicular.scale(-1); 
 | 
        perpendicular.add(direction); 
 | 
        return perpendicular; 
 | 
    } 
 | 
  
 | 
    protected boolean recoverFromPenetration(CollisionWorld collisionWorld) { 
 | 
        boolean penetration = false; 
 | 
  
 | 
        collisionWorld.getDispatcher().dispatchAllCollisionPairs( 
 | 
                ghostObject.getOverlappingPairCache(), collisionWorld.getDispatchInfo(), collisionWorld.getDispatcher()); 
 | 
  
 | 
        currentPosition.set(ghostObject.getWorldTransform(Stack.alloc(Transform.class)).origin); 
 | 
  
 | 
        float maxPen = 0.0f; 
 | 
        for (int i=0; i<ghostObject.getOverlappingPairCache().getNumOverlappingPairs(); i++) { 
 | 
            manifoldArray.clear(); 
 | 
  
 | 
            BroadphasePair collisionPair = ghostObject.getOverlappingPairCache().getOverlappingPairArray().getQuick(i); 
 | 
  
 | 
            if (collisionPair.algorithm != null) { 
 | 
                collisionPair.algorithm.getAllContactManifolds(manifoldArray); 
 | 
            } 
 | 
  
 | 
            for (int j=0; j<manifoldArray.size(); j++) { 
 | 
                PersistentManifold manifold = manifoldArray.getQuick(j); 
 | 
                float directionSign = manifold.getBody0() == ghostObject? -1.0f : 1.0f; 
 | 
                for (int p=0; p<manifold.getNumContacts(); p++) { 
 | 
                    ManifoldPoint pt = manifold.getContactPoint(p); 
 | 
  
 | 
                    float dist = pt.getDistance(); 
 | 
                    if (dist < 0.0f) { 
 | 
                        if (dist < maxPen) { 
 | 
                            maxPen = dist; 
 | 
                            touchingNormal.set(pt.normalWorldOnB);//?? 
 | 
                            touchingNormal.scale(directionSign); 
 | 
                        } 
 | 
  
 | 
                        currentPosition.scaleAdd(directionSign * dist * 0.2f, pt.normalWorldOnB, currentPosition); 
 | 
  
 | 
                        penetration = true; 
 | 
                    } 
 | 
                    else { 
 | 
                        //printf("touching %f\n", dist); 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                //manifold->clearManifold(); 
 | 
            } 
 | 
        } 
 | 
         
 | 
        Transform newTrans = ghostObject.getWorldTransform(Stack.alloc(Transform.class)); 
 | 
        newTrans.origin.set(currentPosition); 
 | 
        ghostObject.setWorldTransform(newTrans); 
 | 
        //printf("m_touchingNormal = %f,%f,%f\n",m_touchingNormal[0],m_touchingNormal[1],m_touchingNormal[2]); 
 | 
  
 | 
        //System.out.println("recoverFromPenetration "+penetration+" "+touchingNormal); 
 | 
  
 | 
        return penetration; 
 | 
    } 
 | 
     
 | 
    protected void stepUp(CollisionWorld world) { 
 | 
        // phase 1: up 
 | 
        Transform start = Stack.alloc(Transform.class); 
 | 
        Transform end = Stack.alloc(Transform.class); 
 | 
        targetPosition.scaleAdd(stepHeight + (verticalOffset > 0.0?verticalOffset:0.0f), upAxisDirection[upAxis], currentPosition); 
 | 
  
 | 
        start.setIdentity (); 
 | 
        end.setIdentity (); 
 | 
  
 | 
        /* FIXME: Handle penetration properly */ 
 | 
        start.origin.scaleAdd(convexShape.getMargin() + addedMargin, upAxisDirection[upAxis], currentPosition); 
 | 
        end.origin.set(targetPosition); 
 | 
         
 | 
        // Find only sloped/flat surface hits, avoid wall and ceiling hits... 
 | 
        Vector3f up = Stack.alloc(Vector3f.class); 
 | 
        up.scale(-1f, upAxisDirection[upAxis]); 
 | 
        KinematicClosestNotMeConvexResultCallback callback = new KinematicClosestNotMeConvexResultCallback(ghostObject, up, 0.0f); 
 | 
        callback.collisionFilterGroup = getGhostObject().getBroadphaseHandle().collisionFilterGroup; 
 | 
        callback.collisionFilterMask = getGhostObject().getBroadphaseHandle().collisionFilterMask; 
 | 
  
 | 
        if (useGhostObjectSweepTest) { 
 | 
            ghostObject.convexSweepTest(convexShape, start, end, callback, world.getDispatchInfo().allowedCcdPenetration); 
 | 
        } 
 | 
        else { 
 | 
            world.convexSweepTest(convexShape, start, end, callback); 
 | 
        } 
 | 
  
 | 
        if (callback.hasHit()) { 
 | 
            // we moved up only a fraction of the step height 
 | 
            currentStepOffset = stepHeight * callback.closestHitFraction; 
 | 
            currentPosition.interpolate(currentPosition, targetPosition, callback.closestHitFraction); 
 | 
            verticalVelocity = 0.0f; 
 | 
            verticalOffset = 0.0f; 
 | 
        } 
 | 
        else { 
 | 
            currentStepOffset = stepHeight; 
 | 
            currentPosition.set(targetPosition); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected void updateTargetPositionBasedOnCollision (Vector3f hitNormal) { 
 | 
        updateTargetPositionBasedOnCollision(hitNormal, 0f, 1f); 
 | 
    } 
 | 
  
 | 
    protected void updateTargetPositionBasedOnCollision(Vector3f hitNormal, float tangentMag, float normalMag) { 
 | 
        Vector3f movementDirection = Stack.alloc(Vector3f.class); 
 | 
        movementDirection.sub(targetPosition, currentPosition); 
 | 
        float movementLength = movementDirection.length(); 
 | 
        if (movementLength>BulletGlobals.SIMD_EPSILON) { 
 | 
            movementDirection.normalize(); 
 | 
  
 | 
            Vector3f reflectDir = computeReflectionDirection(movementDirection, hitNormal, Stack.alloc(Vector3f.class)); 
 | 
            reflectDir.normalize(); 
 | 
  
 | 
            Vector3f parallelDir = parallelComponent(reflectDir, hitNormal, Stack.alloc(Vector3f.class)); 
 | 
            Vector3f perpindicularDir = perpindicularComponent(reflectDir, hitNormal, Stack.alloc(Vector3f.class)); 
 | 
  
 | 
            targetPosition.set(currentPosition); 
 | 
            if (false) //tangentMag != 0.0) 
 | 
            { 
 | 
                Vector3f parComponent = Stack.alloc(Vector3f.class); 
 | 
                parComponent.scale(tangentMag * movementLength, parallelDir); 
 | 
                //printf("parComponent=%f,%f,%f\n",parComponent[0],parComponent[1],parComponent[2]); 
 | 
                targetPosition.add(parComponent); 
 | 
            } 
 | 
  
 | 
            if (normalMag != 0.0f) { 
 | 
                Vector3f perpComponent = Stack.alloc(Vector3f.class); 
 | 
                perpComponent.scale(normalMag * movementLength, perpindicularDir); 
 | 
                //printf("perpComponent=%f,%f,%f\n",perpComponent[0],perpComponent[1],perpComponent[2]); 
 | 
                targetPosition.add(perpComponent); 
 | 
            } 
 | 
        } 
 | 
        else { 
 | 
            //printf("movementLength don't normalize a zero vector\n"); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected void stepForwardAndStrafe(CollisionWorld collisionWorld, Vector3f walkMove) { 
 | 
        // printf("m_normalizedDirection=%f,%f,%f\n", 
 | 
        //     m_normalizedDirection[0],m_normalizedDirection[1],m_normalizedDirection[2]); 
 | 
        // phase 2: forward and strafe 
 | 
        Transform start = Stack.alloc(Transform.class); 
 | 
        Transform end = Stack.alloc(Transform.class); 
 | 
        targetPosition.add(currentPosition, walkMove); 
 | 
        start.setIdentity (); 
 | 
        end.setIdentity (); 
 | 
  
 | 
        float fraction = 1.0f; 
 | 
        Vector3f distance2Vec = Stack.alloc(Vector3f.class); 
 | 
        distance2Vec.sub(currentPosition, targetPosition); 
 | 
        float distance2 = distance2Vec.lengthSquared(); 
 | 
        //printf("distance2=%f\n",distance2); 
 | 
  
 | 
        /*if (touchingContact) { 
 | 
            if (normalizedDirection.dot(touchingNormal) > 0.0f) { 
 | 
                updateTargetPositionBasedOnCollision(touchingNormal); 
 | 
            } 
 | 
        }*/ 
 | 
  
 | 
        int maxIter = 10; 
 | 
  
 | 
        while (fraction > 0.01f && maxIter-- > 0) { 
 | 
            start.origin.set(currentPosition); 
 | 
            end.origin.set(targetPosition); 
 | 
  
 | 
            KinematicClosestNotMeConvexResultCallback callback = new KinematicClosestNotMeConvexResultCallback(ghostObject, upAxisDirection[upAxis], -1.0f); 
 | 
            callback.collisionFilterGroup = getGhostObject().getBroadphaseHandle().collisionFilterGroup; 
 | 
            callback.collisionFilterMask = getGhostObject().getBroadphaseHandle().collisionFilterMask; 
 | 
  
 | 
            float margin = convexShape.getMargin(); 
 | 
            convexShape.setMargin(margin + addedMargin); 
 | 
  
 | 
            if (useGhostObjectSweepTest) { 
 | 
                ghostObject.convexSweepTest(convexShape, start, end, callback, collisionWorld.getDispatchInfo().allowedCcdPenetration); 
 | 
            } 
 | 
            else { 
 | 
                collisionWorld.convexSweepTest(convexShape, start, end, callback); 
 | 
            } 
 | 
  
 | 
            convexShape.setMargin(margin); 
 | 
  
 | 
            fraction -= callback.closestHitFraction; 
 | 
  
 | 
            if (callback.hasHit()) { 
 | 
                // we moved only a fraction 
 | 
                Vector3f hitDistanceVec = Stack.alloc(Vector3f.class); 
 | 
                hitDistanceVec.sub(callback.hitPointWorld, currentPosition); 
 | 
                //float hitDistance = hitDistanceVec.length(); 
 | 
  
 | 
                // if the distance is farther than the collision margin, move 
 | 
                //if (hitDistance > addedMargin) { 
 | 
                //    //printf("callback.m_closestHitFraction=%f\n",callback.m_closestHitFraction); 
 | 
                //    currentPosition.interpolate(currentPosition, targetPosition, callback.closestHitFraction); 
 | 
                //} 
 | 
  
 | 
                updateTargetPositionBasedOnCollision(callback.hitNormalWorld); 
 | 
  
 | 
                Vector3f currentDir = Stack.alloc(Vector3f.class); 
 | 
                currentDir.sub(targetPosition, currentPosition); 
 | 
                distance2 = currentDir.lengthSquared(); 
 | 
                if (distance2 > BulletGlobals.SIMD_EPSILON) { 
 | 
                    currentDir.normalize(); 
 | 
                    // see Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." 
 | 
                    if (currentDir.dot(normalizedDirection) <= 0.0f) { 
 | 
                        break; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    //printf("currentDir: don't normalize a zero vector\n"); 
 | 
                    break; 
 | 
                } 
 | 
            } 
 | 
            else { 
 | 
                // we moved whole way 
 | 
                currentPosition.set(targetPosition); 
 | 
            } 
 | 
  
 | 
            //if (callback.m_closestHitFraction == 0.f) 
 | 
            //    break; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    protected void stepDown(CollisionWorld collisionWorld, float dt) { 
 | 
        Transform start = Stack.alloc(Transform.class); 
 | 
        Transform end = Stack.alloc(Transform.class); 
 | 
  
 | 
        // phase 3: down 
 | 
        float additionalDownStep = (wasOnGround /*&& !onGround()*/) ? stepHeight : 0.0f; 
 | 
        Vector3f step_drop = Stack.alloc(Vector3f.class); 
 | 
        step_drop.scale(currentStepOffset + additionalDownStep, upAxisDirection[upAxis]); 
 | 
        float downVelocity = (additionalDownStep == 0.0f && verticalVelocity<0.0f?-verticalVelocity:0.0f) * dt; 
 | 
        Vector3f gravity_drop = Stack.alloc(Vector3f.class); 
 | 
        gravity_drop.scale(downVelocity, upAxisDirection[upAxis]); 
 | 
        targetPosition.sub(step_drop); 
 | 
        targetPosition.sub(gravity_drop); 
 | 
  
 | 
        start.setIdentity (); 
 | 
        end.setIdentity (); 
 | 
  
 | 
        start.origin.set(currentPosition); 
 | 
        end.origin.set(targetPosition); 
 | 
  
 | 
        KinematicClosestNotMeConvexResultCallback callback = new KinematicClosestNotMeConvexResultCallback(ghostObject, upAxisDirection[upAxis], maxSlopeCosine); 
 | 
        callback.collisionFilterGroup = getGhostObject().getBroadphaseHandle().collisionFilterGroup; 
 | 
        callback.collisionFilterMask = getGhostObject().getBroadphaseHandle().collisionFilterMask; 
 | 
  
 | 
        if (useGhostObjectSweepTest) { 
 | 
            ghostObject.convexSweepTest(convexShape, start, end, callback, collisionWorld.getDispatchInfo().allowedCcdPenetration); 
 | 
        } 
 | 
        else { 
 | 
            collisionWorld.convexSweepTest(convexShape, start, end, callback); 
 | 
        } 
 | 
  
 | 
        if (callback.hasHit()) { 
 | 
            // we dropped a fraction of the height -> hit floor 
 | 
            currentPosition.interpolate(currentPosition, targetPosition, callback.closestHitFraction); 
 | 
            verticalVelocity = 0.0f; 
 | 
            verticalOffset = 0.0f; 
 | 
        } 
 | 
        else { 
 | 
            // we dropped the full height 
 | 
            currentPosition.set(targetPosition); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    //////////////////////////////////////////////////////////////////////////// 
 | 
  
 | 
    private static class KinematicClosestNotMeRayResultCallback extends CollisionWorld.ClosestRayResultCallback { 
 | 
        protected CollisionObject me; 
 | 
  
 | 
        public KinematicClosestNotMeRayResultCallback(CollisionObject me) { 
 | 
            super(new Vector3f(), new Vector3f()); 
 | 
            this.me = me; 
 | 
        } 
 | 
  
 | 
        @Override 
 | 
        public float addSingleResult(CollisionWorld.LocalRayResult rayResult, boolean normalInWorldSpace) { 
 | 
            if (rayResult.collisionObject == me) { 
 | 
                return 1.0f; 
 | 
            } 
 | 
  
 | 
            return super.addSingleResult(rayResult, normalInWorldSpace); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    //////////////////////////////////////////////////////////////////////////// 
 | 
  
 | 
    private static class KinematicClosestNotMeConvexResultCallback extends CollisionWorld.ClosestConvexResultCallback { 
 | 
        protected CollisionObject me; 
 | 
        protected final Vector3f up; 
 | 
        protected float minSlopeDot; 
 | 
  
 | 
        public KinematicClosestNotMeConvexResultCallback(CollisionObject me, final Vector3f up, float minSlopeDot) { 
 | 
            super(new Vector3f(), new Vector3f()); 
 | 
            this.me = me; 
 | 
            this.up = up; 
 | 
            this.minSlopeDot = minSlopeDot; 
 | 
        } 
 | 
  
 | 
        @Override 
 | 
        public float addSingleResult(CollisionWorld.LocalConvexResult convexResult, boolean normalInWorldSpace) { 
 | 
            if (convexResult.hitCollisionObject == me) { 
 | 
                return 1.0f; 
 | 
            } 
 | 
             
 | 
            Vector3f hitNormalWorld; 
 | 
            if (normalInWorldSpace) { 
 | 
                hitNormalWorld = convexResult.hitNormalLocal; 
 | 
            } else { 
 | 
                //need to transform normal into worldspace 
 | 
                hitNormalWorld = Stack.alloc(Vector3f.class); 
 | 
                hitCollisionObject.getWorldTransform(Stack.alloc(Transform.class)).basis.transform(convexResult.hitNormalLocal, hitNormalWorld); 
 | 
            } 
 | 
             
 | 
            float dotUp = up.dot(hitNormalWorld); 
 | 
            if (dotUp < minSlopeDot) { 
 | 
                return 1.0f; 
 | 
            } 
 | 
  
 | 
            return super.addSingleResult(convexResult, normalInWorldSpace); 
 | 
        } 
 | 
    } 
 | 
     
 | 
} 
 |