/*
* 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;
if (suspensionForce > wheel.maxSuspensionForce) {
suspensionForce = wheel.maxSuspensionForce;
}
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 = 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);
}
}
}