/* 
 | 
 * 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.vehicle; 
 | 
  
 | 
import com.bulletphysics.dynamics.RigidBody; 
 | 
import com.bulletphysics.dynamics.constraintsolver.ContactConstraint; 
 | 
import com.bulletphysics.dynamics.constraintsolver.TypedConstraint; 
 | 
import com.bulletphysics.dynamics.constraintsolver.TypedConstraintType; 
 | 
import com.bulletphysics.linearmath.MatrixUtil; 
 | 
import com.bulletphysics.linearmath.MiscUtil; 
 | 
import com.bulletphysics.linearmath.QuaternionUtil; 
 | 
import com.bulletphysics.linearmath.Transform; 
 | 
import com.bulletphysics.util.ArrayPool; 
 | 
import com.bulletphysics.util.FloatArrayList; 
 | 
import com.bulletphysics.util.ObjectArrayList; 
 | 
import cz.advel.stack.Stack; 
 | 
import javax.vecmath.Matrix3f; 
 | 
import javax.vecmath.Quat4f; 
 | 
import javax.vecmath.Vector3f; 
 | 
  
 | 
/** 
 | 
 * Raycast vehicle, very special constraint that turn a rigidbody into a vehicle. 
 | 
 *  
 | 
 * @author jezek2 
 | 
 */ 
 | 
public class RaycastVehicle extends TypedConstraint { 
 | 
     
 | 
    private final ArrayPool<float[]> floatArrays = ArrayPool.get(float.class); 
 | 
  
 | 
    private static RigidBody s_fixedObject = new RigidBody(0, null, null); 
 | 
    private static final float sideFrictionStiffness2 = 1.0f; 
 | 
     
 | 
    protected ObjectArrayList<Vector3f> forwardWS = new ObjectArrayList<Vector3f>(); 
 | 
    protected ObjectArrayList<Vector3f> axle = new ObjectArrayList<Vector3f>(); 
 | 
    protected FloatArrayList forwardImpulse = new FloatArrayList(); 
 | 
    protected FloatArrayList sideImpulse = new FloatArrayList(); 
 | 
  
 | 
    private float tau; 
 | 
    private float damping; 
 | 
    private VehicleRaycaster vehicleRaycaster; 
 | 
    private float pitchControl = 0f; 
 | 
    private float steeringValue;  
 | 
    private float currentVehicleSpeedKmHour; 
 | 
  
 | 
    private RigidBody chassisBody; 
 | 
  
 | 
    private int indexRightAxis = 0; 
 | 
    private int indexUpAxis = 2; 
 | 
    private int indexForwardAxis = 1; 
 | 
     
 | 
    public ObjectArrayList<WheelInfo> wheelInfo = new ObjectArrayList<WheelInfo>(); 
 | 
  
 | 
    // constructor to create a car from an existing rigidbody 
 | 
    public RaycastVehicle(VehicleTuning tuning, RigidBody chassis, VehicleRaycaster raycaster) { 
 | 
        super(TypedConstraintType.VEHICLE_CONSTRAINT_TYPE); 
 | 
        this.vehicleRaycaster = raycaster; 
 | 
        this.chassisBody = chassis; 
 | 
        defaultInit(tuning); 
 | 
    } 
 | 
     
 | 
    private void defaultInit(VehicleTuning tuning) { 
 | 
        currentVehicleSpeedKmHour = 0f; 
 | 
        steeringValue = 0f; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Basically most of the code is general for 2 or 4 wheel vehicles, but some of it needs to be reviewed. 
 | 
     */ 
 | 
    public WheelInfo addWheel(Vector3f connectionPointCS, Vector3f wheelDirectionCS0, Vector3f wheelAxleCS, float suspensionRestLength, float wheelRadius, VehicleTuning tuning, boolean isFrontWheel) { 
 | 
        WheelInfoConstructionInfo ci = new WheelInfoConstructionInfo(); 
 | 
  
 | 
        ci.chassisConnectionCS.set(connectionPointCS); 
 | 
        ci.wheelDirectionCS.set(wheelDirectionCS0); 
 | 
        ci.wheelAxleCS.set(wheelAxleCS); 
 | 
        ci.suspensionRestLength = suspensionRestLength; 
 | 
        ci.wheelRadius = wheelRadius; 
 | 
        ci.suspensionStiffness = tuning.suspensionStiffness; 
 | 
        ci.wheelsDampingCompression = tuning.suspensionCompression; 
 | 
        ci.wheelsDampingRelaxation = tuning.suspensionDamping; 
 | 
        ci.frictionSlip = tuning.frictionSlip; 
 | 
        ci.bIsFrontWheel = isFrontWheel; 
 | 
        ci.maxSuspensionTravelCm = tuning.maxSuspensionTravelCm; 
 | 
  
 | 
        wheelInfo.add(new WheelInfo(ci)); 
 | 
  
 | 
        WheelInfo wheel = wheelInfo.getQuick(getNumWheels() - 1); 
 | 
  
 | 
        updateWheelTransformsWS(wheel, false); 
 | 
        updateWheelTransform(getNumWheels() - 1, false); 
 | 
        return wheel; 
 | 
    } 
 | 
  
 | 
    public Transform getWheelTransformWS(int wheelIndex, Transform out) { 
 | 
        assert (wheelIndex < getNumWheels()); 
 | 
        WheelInfo wheel = wheelInfo.getQuick(wheelIndex); 
 | 
        out.set(wheel.worldTransform); 
 | 
        return out; 
 | 
    } 
 | 
  
 | 
    public void updateWheelTransform(int wheelIndex) { 
 | 
        updateWheelTransform(wheelIndex, true); 
 | 
    } 
 | 
     
 | 
    public void updateWheelTransform(int wheelIndex, boolean interpolatedTransform) { 
 | 
        WheelInfo wheel = wheelInfo.getQuick(wheelIndex); 
 | 
        updateWheelTransformsWS(wheel, interpolatedTransform); 
 | 
        Vector3f up = Stack.alloc(Vector3f.class); 
 | 
        up.negate(wheel.raycastInfo.wheelDirectionWS); 
 | 
        Vector3f right = wheel.raycastInfo.wheelAxleWS; 
 | 
        Vector3f fwd = Stack.alloc(Vector3f.class); 
 | 
        fwd.cross(up, right); 
 | 
        fwd.normalize(); 
 | 
        // up = right.cross(fwd); 
 | 
        // up.normalize(); 
 | 
  
 | 
        // rotate around steering over de wheelAxleWS 
 | 
        float steering = wheel.steering; 
 | 
  
 | 
        Quat4f steeringOrn = Stack.alloc(Quat4f.class); 
 | 
        QuaternionUtil.setRotation(steeringOrn, up, steering); //wheel.m_steering); 
 | 
        Matrix3f steeringMat = Stack.alloc(Matrix3f.class); 
 | 
        MatrixUtil.setRotation(steeringMat, steeringOrn); 
 | 
  
 | 
        Quat4f rotatingOrn = Stack.alloc(Quat4f.class); 
 | 
        QuaternionUtil.setRotation(rotatingOrn, right, -wheel.rotation); 
 | 
        Matrix3f rotatingMat = Stack.alloc(Matrix3f.class); 
 | 
        MatrixUtil.setRotation(rotatingMat, rotatingOrn); 
 | 
  
 | 
        Matrix3f basis2 = Stack.alloc(Matrix3f.class); 
 | 
        basis2.setRow(0, right.x, fwd.x, up.x); 
 | 
        basis2.setRow(1, right.y, fwd.y, up.y); 
 | 
        basis2.setRow(2, right.z, fwd.z, up.z); 
 | 
  
 | 
        Matrix3f wheelBasis = wheel.worldTransform.basis; 
 | 
        wheelBasis.mul(steeringMat, rotatingMat); 
 | 
        wheelBasis.mul(basis2); 
 | 
  
 | 
        wheel.worldTransform.origin.scaleAdd(wheel.raycastInfo.suspensionLength, wheel.raycastInfo.wheelDirectionWS, wheel.raycastInfo.hardPointWS); 
 | 
    } 
 | 
     
 | 
    public void resetSuspension() { 
 | 
        int i; 
 | 
        for (i = 0; i < wheelInfo.size(); i++) { 
 | 
            WheelInfo wheel = wheelInfo.getQuick(i); 
 | 
            wheel.raycastInfo.suspensionLength = wheel.getSuspensionRestLength(); 
 | 
            wheel.suspensionRelativeVelocity = 0f; 
 | 
  
 | 
            wheel.raycastInfo.contactNormalWS.negate(wheel.raycastInfo.wheelDirectionWS); 
 | 
            //wheel_info.setContactFriction(btScalar(0.0)); 
 | 
            wheel.clippedInvContactDotSuspension = 1f; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public void updateWheelTransformsWS(WheelInfo wheel) { 
 | 
        updateWheelTransformsWS(wheel, true); 
 | 
    } 
 | 
     
 | 
    public void updateWheelTransformsWS(WheelInfo wheel, boolean interpolatedTransform) { 
 | 
        wheel.raycastInfo.isInContact = false; 
 | 
  
 | 
        Transform chassisTrans = getChassisWorldTransform(Stack.alloc(Transform.class)); 
 | 
        if (interpolatedTransform && (getRigidBody().getMotionState() != null)) { 
 | 
            getRigidBody().getMotionState().getWorldTransform(chassisTrans); 
 | 
        } 
 | 
  
 | 
        wheel.raycastInfo.hardPointWS.set(wheel.chassisConnectionPointCS); 
 | 
        chassisTrans.transform(wheel.raycastInfo.hardPointWS); 
 | 
  
 | 
        wheel.raycastInfo.wheelDirectionWS.set(wheel.wheelDirectionCS); 
 | 
        chassisTrans.basis.transform(wheel.raycastInfo.wheelDirectionWS); 
 | 
  
 | 
        wheel.raycastInfo.wheelAxleWS.set(wheel.wheelAxleCS); 
 | 
        chassisTrans.basis.transform(wheel.raycastInfo.wheelAxleWS); 
 | 
    } 
 | 
  
 | 
    public float rayCast(WheelInfo wheel) { 
 | 
        updateWheelTransformsWS(wheel, false); 
 | 
  
 | 
        float depth = -1f; 
 | 
  
 | 
        float raylen = wheel.getSuspensionRestLength() + wheel.wheelsRadius; 
 | 
  
 | 
        Vector3f rayvector = Stack.alloc(Vector3f.class); 
 | 
        rayvector.scale(raylen, wheel.raycastInfo.wheelDirectionWS); 
 | 
        Vector3f source = wheel.raycastInfo.hardPointWS; 
 | 
        wheel.raycastInfo.contactPointWS.add(source, rayvector); 
 | 
        Vector3f target = wheel.raycastInfo.contactPointWS; 
 | 
  
 | 
        float param = 0f; 
 | 
  
 | 
        VehicleRaycasterResult rayResults = new VehicleRaycasterResult(); 
 | 
  
 | 
        assert (vehicleRaycaster != null); 
 | 
  
 | 
        Object object = vehicleRaycaster.castRay(source, target, rayResults); 
 | 
  
 | 
        wheel.raycastInfo.groundObject = null; 
 | 
  
 | 
        if (object != null) { 
 | 
            param = rayResults.distFraction; 
 | 
            depth = raylen * rayResults.distFraction; 
 | 
            wheel.raycastInfo.contactNormalWS.set(rayResults.hitNormalInWorld); 
 | 
            wheel.raycastInfo.isInContact = true; 
 | 
  
 | 
            wheel.raycastInfo.groundObject = s_fixedObject; // todo for driving on dynamic/movable objects!; 
 | 
            //wheel.m_raycastInfo.m_groundObject = object; 
 | 
  
 | 
            float hitDistance = param * raylen; 
 | 
            wheel.raycastInfo.suspensionLength = hitDistance - wheel.wheelsRadius; 
 | 
            // clamp on max suspension travel 
 | 
  
 | 
            float minSuspensionLength = wheel.getSuspensionRestLength() - wheel.maxSuspensionTravelCm * 0.01f; 
 | 
            float maxSuspensionLength = wheel.getSuspensionRestLength() + wheel.maxSuspensionTravelCm * 0.01f; 
 | 
            if (wheel.raycastInfo.suspensionLength < minSuspensionLength) { 
 | 
                wheel.raycastInfo.suspensionLength = minSuspensionLength; 
 | 
            } 
 | 
            if (wheel.raycastInfo.suspensionLength > maxSuspensionLength) { 
 | 
                wheel.raycastInfo.suspensionLength = maxSuspensionLength; 
 | 
            } 
 | 
  
 | 
            wheel.raycastInfo.contactPointWS.set(rayResults.hitPointInWorld); 
 | 
  
 | 
            float denominator = wheel.raycastInfo.contactNormalWS.dot(wheel.raycastInfo.wheelDirectionWS); 
 | 
  
 | 
            Vector3f chassis_velocity_at_contactPoint = Stack.alloc(Vector3f.class); 
 | 
            Vector3f relpos = Stack.alloc(Vector3f.class); 
 | 
            relpos.sub(wheel.raycastInfo.contactPointWS, getRigidBody().getCenterOfMassPosition(Stack.alloc(Vector3f.class))); 
 | 
  
 | 
            getRigidBody().getVelocityInLocalPoint(relpos, chassis_velocity_at_contactPoint); 
 | 
  
 | 
            float projVel = wheel.raycastInfo.contactNormalWS.dot(chassis_velocity_at_contactPoint); 
 | 
  
 | 
            if (denominator >= -0.1f) { 
 | 
                wheel.suspensionRelativeVelocity = 0f; 
 | 
                wheel.clippedInvContactDotSuspension = 1f / 0.1f; 
 | 
            } 
 | 
            else { 
 | 
                float inv = -1f / denominator; 
 | 
                wheel.suspensionRelativeVelocity = projVel * inv; 
 | 
                wheel.clippedInvContactDotSuspension = inv; 
 | 
            } 
 | 
  
 | 
        } 
 | 
        else { 
 | 
            // put wheel info as in rest position 
 | 
            wheel.raycastInfo.suspensionLength = wheel.getSuspensionRestLength(); 
 | 
            wheel.suspensionRelativeVelocity = 0f; 
 | 
            wheel.raycastInfo.contactNormalWS.negate(wheel.raycastInfo.wheelDirectionWS); 
 | 
            wheel.clippedInvContactDotSuspension = 1f; 
 | 
        } 
 | 
  
 | 
        return depth; 
 | 
    } 
 | 
     
 | 
    public Transform getChassisWorldTransform(Transform out) { 
 | 
        /* 
 | 
        if (getRigidBody()->getMotionState()) 
 | 
        { 
 | 
            btTransform chassisWorldTrans; 
 | 
            getRigidBody()->getMotionState()->getWorldTransform(chassisWorldTrans); 
 | 
            return chassisWorldTrans; 
 | 
        } 
 | 
        */ 
 | 
  
 | 
        return getRigidBody().getCenterOfMassTransform(out); 
 | 
    } 
 | 
     
 | 
    public void updateVehicle(float step) { 
 | 
        for (int i = 0; i < getNumWheels(); i++) { 
 | 
            updateWheelTransform(i, false); 
 | 
        } 
 | 
         
 | 
        Vector3f tmp = Stack.alloc(Vector3f.class); 
 | 
  
 | 
        currentVehicleSpeedKmHour = 3.6f * getRigidBody().getLinearVelocity(tmp).length(); 
 | 
  
 | 
        Transform chassisTrans = getChassisWorldTransform(Stack.alloc(Transform.class)); 
 | 
  
 | 
        Vector3f forwardW = Stack.alloc(Vector3f.class); 
 | 
        forwardW.set( 
 | 
                chassisTrans.basis.getElement(0, indexForwardAxis), 
 | 
                chassisTrans.basis.getElement(1, indexForwardAxis), 
 | 
                chassisTrans.basis.getElement(2, indexForwardAxis)); 
 | 
  
 | 
        if (forwardW.dot(getRigidBody().getLinearVelocity(tmp)) < 0f) { 
 | 
            currentVehicleSpeedKmHour *= -1f; 
 | 
        } 
 | 
  
 | 
        // 
 | 
        // simulate suspension 
 | 
        // 
 | 
  
 | 
        int i = 0; 
 | 
        for (i = 0; i < wheelInfo.size(); i++) { 
 | 
            float depth; 
 | 
            depth = rayCast(wheelInfo.getQuick(i)); 
 | 
        } 
 | 
  
 | 
        updateSuspension(step); 
 | 
  
 | 
        for (i = 0; i < wheelInfo.size(); i++) { 
 | 
            // apply suspension force 
 | 
            WheelInfo wheel = wheelInfo.getQuick(i); 
 | 
  
 | 
            float suspensionForce = wheel.wheelsSuspensionForce; 
 | 
  
 | 
            float gMaxSuspensionForce = 6000f; 
 | 
            if (suspensionForce > gMaxSuspensionForce) { 
 | 
                suspensionForce = gMaxSuspensionForce; 
 | 
            } 
 | 
            Vector3f impulse = Stack.alloc(Vector3f.class); 
 | 
            impulse.scale(suspensionForce * step, wheel.raycastInfo.contactNormalWS); 
 | 
            Vector3f relpos = Stack.alloc(Vector3f.class); 
 | 
            relpos.sub(wheel.raycastInfo.contactPointWS, getRigidBody().getCenterOfMassPosition(tmp)); 
 | 
  
 | 
            getRigidBody().applyImpulse(impulse, relpos); 
 | 
        } 
 | 
  
 | 
        updateFriction(step); 
 | 
  
 | 
        for (i = 0; i < wheelInfo.size(); i++) { 
 | 
            WheelInfo wheel = wheelInfo.getQuick(i); 
 | 
            Vector3f relpos = Stack.alloc(Vector3f.class); 
 | 
            relpos.sub(wheel.raycastInfo.hardPointWS, getRigidBody().getCenterOfMassPosition(tmp)); 
 | 
            Vector3f vel = getRigidBody().getVelocityInLocalPoint(relpos, Stack.alloc(Vector3f.class)); 
 | 
  
 | 
            if (wheel.raycastInfo.isInContact) { 
 | 
                Transform chassisWorldTransform = getChassisWorldTransform(Stack.alloc(Transform.class)); 
 | 
  
 | 
                Vector3f fwd = Stack.alloc(Vector3f.class); 
 | 
                fwd.set( 
 | 
                        chassisWorldTransform.basis.getElement(0, indexForwardAxis), 
 | 
                        chassisWorldTransform.basis.getElement(1, indexForwardAxis), 
 | 
                        chassisWorldTransform.basis.getElement(2, indexForwardAxis)); 
 | 
  
 | 
                float proj = fwd.dot(wheel.raycastInfo.contactNormalWS); 
 | 
                tmp.scale(proj, wheel.raycastInfo.contactNormalWS); 
 | 
                fwd.sub(tmp); 
 | 
  
 | 
                float proj2 = fwd.dot(vel); 
 | 
  
 | 
                wheel.deltaRotation = (proj2 * step) / (wheel.wheelsRadius); 
 | 
                wheel.rotation += wheel.deltaRotation; 
 | 
  
 | 
            } 
 | 
            else { 
 | 
                wheel.rotation += wheel.deltaRotation; 
 | 
            } 
 | 
  
 | 
            wheel.deltaRotation *= 0.99f; // damping of rotation when not in contact 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public void setSteeringValue(float steering, int wheel) { 
 | 
        assert (wheel >= 0 && wheel < getNumWheels()); 
 | 
  
 | 
        WheelInfo wheel_info = getWheelInfo(wheel); 
 | 
        wheel_info.steering = steering; 
 | 
    } 
 | 
     
 | 
    public float getSteeringValue(int wheel) { 
 | 
        return getWheelInfo(wheel).steering; 
 | 
    } 
 | 
  
 | 
    public void applyEngineForce(float force, int wheel) { 
 | 
        assert (wheel >= 0 && wheel < getNumWheels()); 
 | 
        WheelInfo wheel_info = getWheelInfo(wheel); 
 | 
        wheel_info.engineForce = force; 
 | 
    } 
 | 
  
 | 
    public WheelInfo getWheelInfo(int index) { 
 | 
        assert ((index >= 0) && (index < getNumWheels())); 
 | 
  
 | 
        return wheelInfo.getQuick(index); 
 | 
    } 
 | 
  
 | 
    public void setBrake(float brake, int wheelIndex) { 
 | 
        assert ((wheelIndex >= 0) && (wheelIndex < getNumWheels())); 
 | 
        getWheelInfo(wheelIndex).brake = brake; 
 | 
    } 
 | 
  
 | 
    public void updateSuspension(float deltaTime) { 
 | 
        float chassisMass = 1f / chassisBody.getInvMass(); 
 | 
  
 | 
        for (int w_it = 0; w_it < getNumWheels(); w_it++) { 
 | 
            WheelInfo wheel_info = wheelInfo.getQuick(w_it); 
 | 
  
 | 
            if (wheel_info.raycastInfo.isInContact) { 
 | 
                float force; 
 | 
                //    Spring 
 | 
                { 
 | 
                    float susp_length = wheel_info.getSuspensionRestLength(); 
 | 
                    float current_length = wheel_info.raycastInfo.suspensionLength; 
 | 
  
 | 
                    float length_diff = (susp_length - current_length); 
 | 
  
 | 
                    force = wheel_info.suspensionStiffness * length_diff * wheel_info.clippedInvContactDotSuspension; 
 | 
                } 
 | 
  
 | 
                // Damper 
 | 
                { 
 | 
                    float projected_rel_vel = wheel_info.suspensionRelativeVelocity; 
 | 
                    { 
 | 
                        float susp_damping; 
 | 
                        if (projected_rel_vel < 0f) { 
 | 
                            susp_damping = wheel_info.wheelsDampingCompression; 
 | 
                        } 
 | 
                        else { 
 | 
                            susp_damping = wheel_info.wheelsDampingRelaxation; 
 | 
                        } 
 | 
                        force -= susp_damping * projected_rel_vel; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                // RESULT 
 | 
                wheel_info.wheelsSuspensionForce = force * chassisMass; 
 | 
                if (wheel_info.wheelsSuspensionForce < 0f) { 
 | 
                    wheel_info.wheelsSuspensionForce = 0f; 
 | 
                } 
 | 
            } 
 | 
            else { 
 | 
                wheel_info.wheelsSuspensionForce = 0f; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
     
 | 
    private float calcRollingFriction(WheelContactPoint contactPoint) { 
 | 
        Vector3f tmp = Stack.alloc(Vector3f.class); 
 | 
         
 | 
        float j1 = 0f; 
 | 
         
 | 
        Vector3f contactPosWorld = contactPoint.frictionPositionWorld; 
 | 
  
 | 
        Vector3f rel_pos1 = Stack.alloc(Vector3f.class); 
 | 
        rel_pos1.sub(contactPosWorld, contactPoint.body0.getCenterOfMassPosition(tmp)); 
 | 
        Vector3f rel_pos2 = Stack.alloc(Vector3f.class); 
 | 
        rel_pos2.sub(contactPosWorld, contactPoint.body1.getCenterOfMassPosition(tmp)); 
 | 
  
 | 
        float maxImpulse = contactPoint.maxImpulse; 
 | 
  
 | 
        Vector3f vel1 = contactPoint.body0.getVelocityInLocalPoint(rel_pos1, Stack.alloc(Vector3f.class)); 
 | 
        Vector3f vel2 = contactPoint.body1.getVelocityInLocalPoint(rel_pos2, Stack.alloc(Vector3f.class)); 
 | 
        Vector3f vel = Stack.alloc(Vector3f.class); 
 | 
        vel.sub(vel1, vel2); 
 | 
  
 | 
        float vrel = contactPoint.frictionDirectionWorld.dot(vel); 
 | 
  
 | 
        // calculate j that moves us to zero relative velocity 
 | 
        j1 = -vrel * contactPoint.jacDiagABInv; 
 | 
        j1 = Math.min(j1, maxImpulse); 
 | 
        j1 = Math.max(j1, -maxImpulse); 
 | 
  
 | 
        return j1; 
 | 
    } 
 | 
     
 | 
    public void updateFriction(float timeStep) { 
 | 
        // calculate the impulse, so that the wheels don't move sidewards 
 | 
        int numWheel = getNumWheels(); 
 | 
        if (numWheel == 0) { 
 | 
            return; 
 | 
        } 
 | 
  
 | 
        MiscUtil.resize(forwardWS, numWheel, Vector3f.class); 
 | 
        MiscUtil.resize(axle, numWheel, Vector3f.class); 
 | 
        MiscUtil.resize(forwardImpulse, numWheel, 0f); 
 | 
        MiscUtil.resize(sideImpulse, numWheel, 0f); 
 | 
  
 | 
        Vector3f tmp = Stack.alloc(Vector3f.class); 
 | 
  
 | 
        int numWheelsOnGround = 0; 
 | 
  
 | 
        // collapse all those loops into one! 
 | 
        for (int i = 0; i < getNumWheels(); i++) { 
 | 
            WheelInfo wheel_info = wheelInfo.getQuick(i); 
 | 
            RigidBody groundObject = (RigidBody) wheel_info.raycastInfo.groundObject; 
 | 
            if (groundObject != null) { 
 | 
                numWheelsOnGround++; 
 | 
            } 
 | 
            sideImpulse.set(i, 0f); 
 | 
            forwardImpulse.set(i, 0f); 
 | 
        } 
 | 
  
 | 
        { 
 | 
            Transform wheelTrans = Stack.alloc(Transform.class); 
 | 
            for (int i = 0; i < getNumWheels(); i++) { 
 | 
  
 | 
                WheelInfo wheel_info = wheelInfo.getQuick(i); 
 | 
  
 | 
                RigidBody groundObject = (RigidBody) wheel_info.raycastInfo.groundObject; 
 | 
  
 | 
                if (groundObject != null) { 
 | 
                    getWheelTransformWS(i, wheelTrans); 
 | 
  
 | 
                    Matrix3f wheelBasis0 = (Matrix3f) Stack.alloc(wheelTrans.basis); 
 | 
                    axle.getQuick(i).set( 
 | 
                            wheelBasis0.getElement(0, indexRightAxis), 
 | 
                            wheelBasis0.getElement(1, indexRightAxis), 
 | 
                            wheelBasis0.getElement(2, indexRightAxis)); 
 | 
  
 | 
                    Vector3f surfNormalWS = wheel_info.raycastInfo.contactNormalWS; 
 | 
                    float proj = axle.getQuick(i).dot(surfNormalWS); 
 | 
                    tmp.scale(proj, surfNormalWS); 
 | 
                    axle.getQuick(i).sub(tmp); 
 | 
                    axle.getQuick(i).normalize(); 
 | 
  
 | 
                    forwardWS.getQuick(i).cross(surfNormalWS, axle.getQuick(i)); 
 | 
                    forwardWS.getQuick(i).normalize(); 
 | 
  
 | 
                    float[] floatPtr = floatArrays.getFixed(1); 
 | 
                    ContactConstraint.resolveSingleBilateral(chassisBody, wheel_info.raycastInfo.contactPointWS, 
 | 
                            groundObject, wheel_info.raycastInfo.contactPointWS, 
 | 
                            0f, axle.getQuick(i), floatPtr, timeStep); 
 | 
                    sideImpulse.set(i, floatPtr[0]); 
 | 
                    floatArrays.release(floatPtr); 
 | 
  
 | 
                    sideImpulse.set(i, sideImpulse.get(i) * sideFrictionStiffness2); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        float sideFactor = 1f; 
 | 
        float fwdFactor = 0.5f; 
 | 
  
 | 
        boolean sliding = false; 
 | 
        { 
 | 
            for (int wheel = 0; wheel < getNumWheels(); wheel++) { 
 | 
                WheelInfo wheel_info = wheelInfo.getQuick(wheel); 
 | 
                RigidBody groundObject = (RigidBody) wheel_info.raycastInfo.groundObject; 
 | 
  
 | 
                float rollingFriction = 0f; 
 | 
  
 | 
                if (groundObject != null) { 
 | 
                    if (wheel_info.engineForce != 0f) { 
 | 
                        rollingFriction = wheel_info.engineForce * timeStep; 
 | 
                    } 
 | 
                    else { 
 | 
                        float defaultRollingFrictionImpulse = 0f; 
 | 
                        float maxImpulse = wheel_info.brake != 0f ? wheel_info.brake : defaultRollingFrictionImpulse; 
 | 
                        WheelContactPoint contactPt = new WheelContactPoint(chassisBody, groundObject, wheel_info.raycastInfo.contactPointWS, forwardWS.getQuick(wheel), maxImpulse); 
 | 
                        rollingFriction = calcRollingFriction(contactPt); 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                // switch between active rolling (throttle), braking and non-active rolling friction (no throttle/break) 
 | 
  
 | 
                forwardImpulse.set(wheel, 0f); 
 | 
                wheelInfo.getQuick(wheel).skidInfo = 1f; 
 | 
  
 | 
                if (groundObject != null) { 
 | 
                    wheelInfo.getQuick(wheel).skidInfo = 1f; 
 | 
  
 | 
                    float maximp = wheel_info.wheelsSuspensionForce * timeStep * wheel_info.frictionSlip; 
 | 
                    float maximpSide = maximp; 
 | 
  
 | 
                    float maximpSquared = maximp * maximpSide; 
 | 
  
 | 
                    forwardImpulse.set(wheel, rollingFriction); //wheelInfo.m_engineForce* timeStep; 
 | 
  
 | 
                    float x = (forwardImpulse.get(wheel)) * fwdFactor; 
 | 
                    float y = (sideImpulse.get(wheel)) * sideFactor; 
 | 
  
 | 
                    float impulseSquared = (x * x + y * y); 
 | 
  
 | 
                    if (impulseSquared > maximpSquared) { 
 | 
                        sliding = true; 
 | 
  
 | 
                        float factor = maximp / (float) Math.sqrt(impulseSquared); 
 | 
  
 | 
                        wheelInfo.getQuick(wheel).skidInfo *= factor; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
            } 
 | 
        } 
 | 
  
 | 
        if (sliding) { 
 | 
            for (int wheel = 0; wheel < getNumWheels(); wheel++) { 
 | 
                if (sideImpulse.get(wheel) != 0f) { 
 | 
                    if (wheelInfo.getQuick(wheel).skidInfo < 1f) { 
 | 
                        forwardImpulse.set(wheel, forwardImpulse.get(wheel) * wheelInfo.getQuick(wheel).skidInfo); 
 | 
                        sideImpulse.set(wheel, sideImpulse.get(wheel) * wheelInfo.getQuick(wheel).skidInfo); 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // apply the impulses 
 | 
        { 
 | 
            for (int wheel = 0; wheel < getNumWheels(); wheel++) { 
 | 
                WheelInfo wheel_info = wheelInfo.getQuick(wheel); 
 | 
  
 | 
                Vector3f rel_pos = Stack.alloc(Vector3f.class); 
 | 
                rel_pos.sub(wheel_info.raycastInfo.contactPointWS, chassisBody.getCenterOfMassPosition(tmp)); 
 | 
  
 | 
                if (forwardImpulse.get(wheel) != 0f) { 
 | 
                    tmp.scale(forwardImpulse.get(wheel), forwardWS.getQuick(wheel)); 
 | 
                    chassisBody.applyImpulse(tmp, rel_pos); 
 | 
                } 
 | 
                if (sideImpulse.get(wheel) != 0f) { 
 | 
                    RigidBody groundObject = (RigidBody) wheelInfo.getQuick(wheel).raycastInfo.groundObject; 
 | 
  
 | 
                    Vector3f rel_pos2 = Stack.alloc(Vector3f.class); 
 | 
                    rel_pos2.sub(wheel_info.raycastInfo.contactPointWS, groundObject.getCenterOfMassPosition(tmp)); 
 | 
  
 | 
                    Vector3f sideImp = Stack.alloc(Vector3f.class); 
 | 
                    sideImp.scale(sideImpulse.get(wheel), axle.getQuick(wheel)); 
 | 
  
 | 
                    rel_pos.z *= wheel_info.rollInfluence; 
 | 
                    chassisBody.applyImpulse(sideImp, rel_pos); 
 | 
  
 | 
                    // apply friction impulse on the ground 
 | 
                    tmp.negate(sideImp); 
 | 
                    groundObject.applyImpulse(tmp, rel_pos2); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
     
 | 
    @Override 
 | 
    public void buildJacobian() { 
 | 
        // not yet 
 | 
    } 
 | 
  
 | 
    @Override 
 | 
    public void solveConstraint(float timeStep) { 
 | 
        // not yet 
 | 
    } 
 | 
     
 | 
    public int getNumWheels() { 
 | 
        return wheelInfo.size(); 
 | 
    } 
 | 
  
 | 
    public void setPitchControl(float pitch) { 
 | 
        this.pitchControl = pitch; 
 | 
    } 
 | 
  
 | 
    public RigidBody getRigidBody() { 
 | 
        return chassisBody; 
 | 
    } 
 | 
  
 | 
    public int getRightAxis() { 
 | 
        return indexRightAxis; 
 | 
    } 
 | 
  
 | 
    public int getUpAxis() { 
 | 
        return indexUpAxis; 
 | 
    } 
 | 
  
 | 
    public int getForwardAxis() { 
 | 
        return indexForwardAxis; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Worldspace forward vector. 
 | 
     */ 
 | 
    public Vector3f getForwardVector(Vector3f out) { 
 | 
        Transform chassisTrans = getChassisWorldTransform(Stack.alloc(Transform.class)); 
 | 
  
 | 
        out.set( 
 | 
                chassisTrans.basis.getElement(0, indexForwardAxis), 
 | 
                chassisTrans.basis.getElement(1, indexForwardAxis), 
 | 
                chassisTrans.basis.getElement(2, indexForwardAxis)); 
 | 
  
 | 
        return out; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Velocity of vehicle (positive if velocity vector has same direction as foward vector). 
 | 
     */ 
 | 
    public float getCurrentSpeedKmHour() { 
 | 
        return currentVehicleSpeedKmHour; 
 | 
    } 
 | 
  
 | 
    public void setCoordinateSystem(int rightIndex, int upIndex, int forwardIndex) { 
 | 
        this.indexRightAxis = rightIndex; 
 | 
        this.indexUpAxis = upIndex; 
 | 
        this.indexForwardAxis = forwardIndex; 
 | 
    } 
 | 
     
 | 
    //////////////////////////////////////////////////////////////////////////// 
 | 
     
 | 
    private static class WheelContactPoint { 
 | 
        public RigidBody body0; 
 | 
        public RigidBody body1; 
 | 
        public final Vector3f frictionPositionWorld = new Vector3f(); 
 | 
        public final Vector3f frictionDirectionWorld = new Vector3f(); 
 | 
        public float jacDiagABInv; 
 | 
        public float maxImpulse; 
 | 
  
 | 
        public WheelContactPoint(RigidBody body0, RigidBody body1, Vector3f frictionPosWorld, Vector3f frictionDirectionWorld, float maxImpulse) { 
 | 
            this.body0 = body0; 
 | 
            this.body1 = body1; 
 | 
            this.frictionPositionWorld.set(frictionPosWorld); 
 | 
            this.frictionDirectionWorld.set(frictionDirectionWorld); 
 | 
            this.maxImpulse = maxImpulse; 
 | 
  
 | 
            float denom0 = body0.computeImpulseDenominator(frictionPosWorld, frictionDirectionWorld); 
 | 
            float denom1 = body1.computeImpulseDenominator(frictionPosWorld, frictionDirectionWorld); 
 | 
            float relaxation = 1f; 
 | 
            jacDiagABInv = relaxation / (denom0 + denom1); 
 | 
        } 
 | 
    } 
 | 
  
 | 
} 
 |