1081 lines
41 KiB
C++
1081 lines
41 KiB
C++
/*
|
|
* Copyright (C) 2005-2015 Erwin Coumans http://continuousphysics.com/Bullet/
|
|
*
|
|
* Permission to use, copy, modify, distribute and sell this software
|
|
* and its documentation for any purpose is hereby granted without fee,
|
|
* provided that the above copyright notice appear in all copies.
|
|
* Erwin Coumans makes no representations about the suitability
|
|
* of this software for any purpose.
|
|
* It is provided "as is" without express or implied warranty.
|
|
*/
|
|
|
|
#include "LinearMath/btVector3.h"
|
|
#include "btKart.hpp"
|
|
|
|
#include "BulletDynamics/ConstraintSolver/btSolve2LinearConstraint.h"
|
|
#include "BulletDynamics/ConstraintSolver/btJacobianEntry.h"
|
|
#include "LinearMath/btQuaternion.h"
|
|
#include "BulletDynamics/Dynamics/btDynamicsWorld.h"
|
|
#include "BulletDynamics/Vehicle/btVehicleRaycaster.h"
|
|
#include "BulletDynamics/Vehicle/btWheelInfo.h"
|
|
#include "LinearMath/btMinMax.h"
|
|
#include "LinearMath/btIDebugDraw.h"
|
|
#include "BulletDynamics/ConstraintSolver/btContactConstraint.h"
|
|
|
|
#include "graphics/material.hpp"
|
|
#include "karts/kart.hpp"
|
|
#include "karts/kart_model.hpp"
|
|
#include "karts/kart_properties.hpp"
|
|
#include "physics/triangle_mesh.hpp"
|
|
#include "tracks/terrain_info.hpp"
|
|
#include "tracks/track.hpp"
|
|
|
|
#define ROLLING_INFLUENCE_FIX
|
|
|
|
|
|
btRigidBody& btKart::getFixedBody()
|
|
{
|
|
static btRigidBody s_fixed(0, 0,0);
|
|
s_fixed.setMassProps(btScalar(0.),btVector3(btScalar(0.),
|
|
btScalar(0.),btScalar(0.)));
|
|
return s_fixed;
|
|
}
|
|
|
|
// ============================================================================
|
|
btKart::btKart(btRigidBody* chassis, btVehicleRaycaster* raycaster,
|
|
Kart *kart)
|
|
: m_vehicleRaycaster(raycaster)
|
|
{
|
|
m_chassisBody = chassis;
|
|
m_indexRightAxis = 0;
|
|
m_indexUpAxis = 1;
|
|
m_indexForwardAxis = 2;
|
|
m_kart = kart;
|
|
reset();
|
|
} // btKart
|
|
|
|
// ----------------------------------------------------------------------------
|
|
btKart::~btKart()
|
|
{
|
|
} // ~btKart
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
//
|
|
// basically most of the code is general for 2 or 4 wheel vehicles, but some
|
|
// of it needs to be reviewed
|
|
//
|
|
btWheelInfo& btKart::addWheel(const btVector3& connectionPointCS,
|
|
const btVector3& wheelDirectionCS0,
|
|
const btVector3& wheelAxleCS,
|
|
btScalar suspensionRestLength,
|
|
btScalar wheelRadius,
|
|
const btVehicleTuning& tuning,
|
|
bool isFrontWheel)
|
|
{
|
|
|
|
btWheelInfoConstructionInfo ci;
|
|
|
|
ci.m_chassisConnectionCS = connectionPointCS;
|
|
ci.m_wheelDirectionCS = wheelDirectionCS0;
|
|
ci.m_wheelAxleCS = wheelAxleCS;
|
|
ci.m_suspensionRestLength = suspensionRestLength;
|
|
ci.m_wheelRadius = wheelRadius;
|
|
ci.m_bIsFrontWheel = isFrontWheel;
|
|
ci.m_suspensionStiffness = tuning.m_suspensionStiffness;
|
|
ci.m_wheelsDampingCompression = tuning.m_suspensionCompression;
|
|
ci.m_wheelsDampingRelaxation = tuning.m_suspensionDamping;
|
|
ci.m_frictionSlip = tuning.m_frictionSlip;
|
|
ci.m_maxSuspensionTravel = tuning.m_maxSuspensionTravel;
|
|
ci.m_maxSuspensionForce = tuning.m_maxSuspensionForce;
|
|
|
|
m_wheelInfo.push_back( btWheelInfo(ci));
|
|
m_visual_contact_point.push_back(btVector3());
|
|
|
|
btWheelInfo& wheel = m_wheelInfo[getNumWheels()-1];
|
|
|
|
updateWheelTransformsWS( wheel , false );
|
|
updateWheelTransform(getNumWheels()-1,false);
|
|
|
|
m_forwardWS.resize(m_wheelInfo.size());
|
|
m_axle.resize(m_wheelInfo.size());
|
|
m_forwardImpulse.resize(m_wheelInfo.size());
|
|
m_sideImpulse.resize(m_wheelInfo.size());
|
|
|
|
return wheel;
|
|
} // addWheel
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Resets the kart before a (re)start, to make sure all physics variable
|
|
* are properly defined. This is especially important for physics replay.
|
|
*/
|
|
void btKart::reset()
|
|
{
|
|
for(int i=0; i<getNumWheels(); i++)
|
|
{
|
|
btWheelInfo &wheel = m_wheelInfo[i];
|
|
wheel.m_raycastInfo.m_suspensionLength = 0;
|
|
updateWheelTransform(i, true);
|
|
}
|
|
m_visual_wheels_touch_ground = false;
|
|
m_zipper_speed = btScalar(0);
|
|
m_skid_angular_velocity = 0;
|
|
m_is_skidding = false;
|
|
m_allow_sliding = false;
|
|
m_num_wheels_on_ground = 0;
|
|
m_additional_impulse = btVector3(0,0,0);
|
|
m_time_additional_impulse = 0;
|
|
m_additional_rotation = btVector3(0,0,0);
|
|
m_time_additional_rotation = 0;
|
|
m_visual_rotation = 0;
|
|
|
|
// Set the brakes so that karts don't slide downhill
|
|
setAllBrakes(5.0f);
|
|
|
|
} // reset
|
|
|
|
// ----------------------------------------------------------------------------
|
|
const btTransform& btKart::getWheelTransformWS( int wheelIndex ) const
|
|
{
|
|
btAssert(wheelIndex < getNumWheels());
|
|
const btWheelInfo& wheel = m_wheelInfo[wheelIndex];
|
|
return wheel.m_worldTransform;
|
|
|
|
} // getWheelTransformWS
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::updateWheelTransform(int wheelIndex, bool interpolatedTransform)
|
|
{
|
|
|
|
btWheelInfo& wheel = m_wheelInfo[ wheelIndex ];
|
|
updateWheelTransformsWS(wheel,interpolatedTransform);
|
|
btVector3 up = -wheel.m_raycastInfo.m_wheelDirectionWS;
|
|
const btVector3& right = wheel.m_raycastInfo.m_wheelAxleWS;
|
|
btVector3 fwd = up.cross(right);
|
|
fwd = fwd.normalize();
|
|
|
|
//rotate around steering over de wheelAxleWS
|
|
btScalar steering = wheel.m_steering;
|
|
|
|
btQuaternion steeringOrn(up,steering);//wheel.m_steering);
|
|
btMatrix3x3 steeringMat(steeringOrn);
|
|
|
|
btMatrix3x3 basis2(
|
|
right[0],fwd[0],up[0],
|
|
right[1],fwd[1],up[1],
|
|
right[2],fwd[2],up[2]
|
|
);
|
|
|
|
wheel.m_worldTransform.setBasis(steeringMat * basis2);
|
|
wheel.m_worldTransform.setOrigin(
|
|
wheel.m_raycastInfo.m_hardPointWS
|
|
+ wheel.m_raycastInfo.m_wheelDirectionWS
|
|
*wheel.m_raycastInfo.m_suspensionLength);
|
|
} // updateWheelTransform
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::resetSuspension()
|
|
{
|
|
|
|
int i;
|
|
for (i=0;i<m_wheelInfo.size(); i++)
|
|
{
|
|
btWheelInfo& wheel = m_wheelInfo[i];
|
|
wheel.m_raycastInfo.m_suspensionLength =
|
|
wheel.getSuspensionRestLength();
|
|
wheel.m_suspensionRelativeVelocity = btScalar(0.0);
|
|
|
|
wheel.m_raycastInfo.m_contactNormalWS =
|
|
- wheel.m_raycastInfo.m_wheelDirectionWS;
|
|
//wheel_info.setContactFriction(btScalar(0.0));
|
|
wheel.m_clippedInvContactDotSuspension = btScalar(1.0);
|
|
}
|
|
} // resetSuspension
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::updateWheelTransformsWS(btWheelInfo& wheel,
|
|
bool interpolatedTransform,
|
|
float fraction)
|
|
{
|
|
wheel.m_raycastInfo.m_isInContact = false;
|
|
|
|
btTransform chassisTrans = getChassisWorldTransform();
|
|
if (interpolatedTransform && (getRigidBody()->getMotionState()))
|
|
{
|
|
getRigidBody()->getMotionState()->getWorldTransform(chassisTrans);
|
|
}
|
|
|
|
wheel.m_raycastInfo.m_hardPointWS =
|
|
chassisTrans( wheel.m_chassisConnectionPointCS*fraction );
|
|
wheel.m_raycastInfo.m_wheelDirectionWS = chassisTrans.getBasis() *
|
|
wheel.m_wheelDirectionCS ;
|
|
wheel.m_raycastInfo.m_wheelAxleWS = chassisTrans.getBasis() *
|
|
wheel.m_wheelAxleCS;
|
|
} // updateWheelTransformsWS
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/**
|
|
*/
|
|
btScalar btKart::rayCast(unsigned int index, float fraction)
|
|
{
|
|
btWheelInfo &wheel = m_wheelInfo[index];
|
|
|
|
// Work around a bullet problem: when using a convex hull the raycast
|
|
// would sometimes hit the chassis (which does not happen when using a
|
|
// box shape). Therefore set the collision mask in the chassis body so
|
|
// that it is not hit anymore.
|
|
short int old_group=0;
|
|
if(m_chassisBody->getBroadphaseHandle())
|
|
{
|
|
old_group = m_chassisBody->getBroadphaseHandle()
|
|
->m_collisionFilterGroup;
|
|
m_chassisBody->getBroadphaseHandle()->m_collisionFilterGroup = 0;
|
|
}
|
|
|
|
updateWheelTransformsWS( wheel,false, fraction);
|
|
|
|
btScalar max_susp_len = wheel.getSuspensionRestLength()
|
|
+ wheel.m_maxSuspensionTravel;
|
|
|
|
// Do a slightly longer raycast to see if the kart might soon hit the
|
|
// ground and some 'cushioning' is needed to avoid that the chassis
|
|
// hits the ground.
|
|
btScalar raylen = max_susp_len + 0.5f;
|
|
|
|
btVector3 rayvector = wheel.m_raycastInfo.m_wheelDirectionWS * (raylen);
|
|
const btVector3& source = wheel.m_raycastInfo.m_hardPointWS;
|
|
wheel.m_raycastInfo.m_contactPointWS = source + rayvector;
|
|
const btVector3& target = wheel.m_raycastInfo.m_contactPointWS;
|
|
|
|
btVehicleRaycaster::btVehicleRaycasterResult rayResults;
|
|
|
|
btAssert(m_vehicleRaycaster);
|
|
|
|
void* object = m_vehicleRaycaster->castRay(source,target,rayResults);
|
|
|
|
wheel.m_raycastInfo.m_groundObject = 0;
|
|
|
|
btScalar depth = raylen * rayResults.m_distFraction;
|
|
if (object && depth < max_susp_len)
|
|
{
|
|
wheel.m_raycastInfo.m_contactNormalWS = rayResults.m_hitNormalInWorld;
|
|
wheel.m_raycastInfo.m_contactNormalWS.normalize();
|
|
wheel.m_raycastInfo.m_isInContact = true;
|
|
///@todo for driving on dynamic/movable objects!;
|
|
wheel.m_raycastInfo.m_triangle_index = rayResults.m_triangle_index;;
|
|
wheel.m_raycastInfo.m_groundObject = &getFixedBody();
|
|
|
|
wheel.m_raycastInfo.m_suspensionLength = depth;
|
|
|
|
//clamp on max suspension travel
|
|
btScalar minSuspensionLength = wheel.getSuspensionRestLength()
|
|
- wheel.m_maxSuspensionTravel;
|
|
btScalar maxSuspensionLength = wheel.getSuspensionRestLength()
|
|
+ wheel.m_maxSuspensionTravel;
|
|
if (wheel.m_raycastInfo.m_suspensionLength < minSuspensionLength)
|
|
{
|
|
wheel.m_raycastInfo.m_suspensionLength = minSuspensionLength;
|
|
}
|
|
if (wheel.m_raycastInfo.m_suspensionLength > maxSuspensionLength)
|
|
{
|
|
wheel.m_raycastInfo.m_suspensionLength = maxSuspensionLength;
|
|
}
|
|
|
|
wheel.m_raycastInfo.m_contactPointWS = rayResults.m_hitPointInWorld;
|
|
|
|
btScalar denominator = wheel.m_raycastInfo.m_contactNormalWS.dot(
|
|
wheel.m_raycastInfo.m_wheelDirectionWS );
|
|
|
|
btVector3 chassis_velocity_at_contactPoint;
|
|
btVector3 relpos = wheel.m_raycastInfo.m_contactPointWS
|
|
- getRigidBody()->getCenterOfMassPosition();
|
|
|
|
chassis_velocity_at_contactPoint =
|
|
getRigidBody()->getVelocityInLocalPoint(relpos);
|
|
|
|
btScalar projVel = wheel.m_raycastInfo.m_contactNormalWS.dot(
|
|
chassis_velocity_at_contactPoint );
|
|
|
|
if ( denominator >= btScalar(-0.1))
|
|
{
|
|
wheel.m_suspensionRelativeVelocity = btScalar(0.0);
|
|
wheel.m_clippedInvContactDotSuspension = btScalar(1.0) / btScalar(0.1);
|
|
}
|
|
else
|
|
{
|
|
btScalar inv = btScalar(-1.) / denominator;
|
|
wheel.m_suspensionRelativeVelocity = projVel * inv;
|
|
wheel.m_clippedInvContactDotSuspension = inv;
|
|
}
|
|
|
|
} else
|
|
{
|
|
depth = btScalar(-1.0);
|
|
//put wheel info as in rest position
|
|
wheel.m_raycastInfo.m_suspensionLength = wheel.getSuspensionRestLength();
|
|
wheel.m_suspensionRelativeVelocity = btScalar(0.0);
|
|
wheel.m_raycastInfo.m_contactNormalWS =
|
|
- wheel.m_raycastInfo.m_wheelDirectionWS;
|
|
wheel.m_clippedInvContactDotSuspension = btScalar(1.0);
|
|
}
|
|
|
|
#define USE_VISUAL
|
|
#ifndef USE_VISUAL
|
|
m_visual_contact_point[index] = rayResults.m_hitPointInWorld;
|
|
#else
|
|
if(index==2 || index==3)
|
|
{
|
|
btTransform chassisTrans = getChassisWorldTransform();
|
|
if (getRigidBody()->getMotionState())
|
|
{
|
|
getRigidBody()->getMotionState()->getWorldTransform(chassisTrans);
|
|
}
|
|
btQuaternion q(m_visual_rotation, 0, 0);
|
|
btQuaternion rot_new = chassisTrans.getRotation() * q;
|
|
chassisTrans.setRotation(rot_new);
|
|
btVector3 pos = m_kart->getKartModel()->getWheelGraphicsPosition(index);
|
|
pos.setZ(pos.getZ()*0.9f);
|
|
btVector3 source = chassisTrans( pos );
|
|
btVector3 target = source + rayvector;
|
|
btVehicleRaycaster::btVehicleRaycasterResult rayResults;
|
|
|
|
void* object = m_vehicleRaycaster->castRay(source,target,rayResults);
|
|
m_visual_contact_point[index] = rayResults.m_hitPointInWorld;
|
|
m_visual_contact_point[index-2] = source;
|
|
m_visual_wheels_touch_ground &= (object!=NULL);
|
|
}
|
|
#endif
|
|
|
|
if(m_chassisBody->getBroadphaseHandle())
|
|
{
|
|
m_chassisBody->getBroadphaseHandle()->m_collisionFilterGroup
|
|
= old_group;
|
|
}
|
|
|
|
|
|
return depth;
|
|
|
|
} // rayCast
|
|
|
|
// ----------------------------------------------------------------------------
|
|
const btTransform& btKart::getChassisWorldTransform() const
|
|
{
|
|
return getRigidBody()->getCenterOfMassTransform();
|
|
} // getChassisWorldTransform
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::updateAllWheelPositions()
|
|
{
|
|
for (int i=0;i<getNumWheels();i++)
|
|
{
|
|
updateWheelTransform(i,false);
|
|
}
|
|
|
|
} // updateAllWheelPositions
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::updateVehicle( btScalar step )
|
|
{
|
|
updateAllWheelPositions();
|
|
|
|
const btTransform& chassisTrans = getChassisWorldTransform();
|
|
|
|
btVector3 forwardW(chassisTrans.getBasis()[0][m_indexForwardAxis],
|
|
chassisTrans.getBasis()[1][m_indexForwardAxis],
|
|
chassisTrans.getBasis()[2][m_indexForwardAxis]);
|
|
|
|
// Simulate suspension
|
|
// -------------------
|
|
|
|
m_num_wheels_on_ground = 0;
|
|
m_visual_wheels_touch_ground = true;
|
|
for (int i=0;i<m_wheelInfo.size();i++)
|
|
{
|
|
rayCast( i);
|
|
if(m_wheelInfo[i].m_raycastInfo.m_isInContact)
|
|
m_num_wheels_on_ground++;
|
|
else
|
|
{
|
|
// If the original raycast did not hit the ground,
|
|
// try a little bit (5%) closer to the centre of the chassis.
|
|
// Some tracks have very minor gaps that would otherwise
|
|
// trigger odd physical behaviour.
|
|
rayCast(i, 0.95f);
|
|
if (m_wheelInfo[i].m_raycastInfo.m_isInContact)
|
|
m_num_wheels_on_ground++;
|
|
}
|
|
}
|
|
|
|
// Test if the kart is falling so fast
|
|
// that the chassis might hit the track
|
|
// ------------------------------------
|
|
bool needs_cushioning_test = false;
|
|
for(int i=0; i<m_wheelInfo.size(); i++)
|
|
{
|
|
btWheelInfo &wheel = m_wheelInfo[i];
|
|
if(!wheel.m_was_on_ground && wheel.m_raycastInfo.m_isInContact)
|
|
{
|
|
needs_cushioning_test = true;
|
|
break;
|
|
}
|
|
}
|
|
if(needs_cushioning_test)
|
|
{
|
|
const btVector3 &v = m_chassisBody->getLinearVelocity();
|
|
btVector3 down(0, 1, 0);
|
|
btVector3 v_down = (v * down) * down;
|
|
// Estimate what kind of downward speed can be compensated by the
|
|
// suspension. Atm the parameters are set that the suspension is
|
|
// actually capped at max suspension force, so the maximum
|
|
// speed that can be caught by the suspension without the chassis
|
|
// hitting the ground can be based on that. Note that there are
|
|
// 4 suspensions, all adding together.
|
|
btScalar max_compensate_speed = m_wheelInfo[0].m_maxSuspensionForce
|
|
* m_chassisBody->getInvMass()
|
|
* step * 4;
|
|
// If the downward speed is too fast to be caught by the suspension,
|
|
// slow down the falling speed by applying an appropriately impulse:
|
|
if(-v_down.getY() > max_compensate_speed)
|
|
{
|
|
btVector3 impulse = down * (-v_down.getY() - max_compensate_speed)
|
|
/ m_chassisBody->getInvMass()*0.5f;
|
|
//float v_old = m_chassisBody->getLinearVelocity().getY();
|
|
//float x = m_wheelInfo[0].m_raycastInfo.m_isInContact ? m_wheelInfo[0].m_raycastInfo.m_contactPointWS.getY() : -100;
|
|
m_chassisBody->applyCentralImpulse(impulse);
|
|
//Log::verbose("physics", "Cushioning %f from %f m/s to %f m/s wheel %f kart %f", impulse.getY(),
|
|
// v_old, m_chassisBody->getLinearVelocity().getY(), x,
|
|
// m_chassisBody->getWorldTransform().getOrigin().getY()
|
|
// );
|
|
}
|
|
}
|
|
for(int i=0; i<m_wheelInfo.size(); i++)
|
|
m_wheelInfo[i].m_was_on_ground = m_wheelInfo[i].m_raycastInfo.m_isInContact;
|
|
|
|
|
|
// If the kart is flying, try to keep it parallel to the ground.
|
|
// -------------------------------------------------------------
|
|
if(m_num_wheels_on_ground==0)
|
|
{
|
|
btVector3 kart_up = getChassisWorldTransform().getBasis().getColumn(1);
|
|
btVector3 terrain_up =
|
|
m_kart->getMaterial() && m_kart->getMaterial()->hasGravity() ?
|
|
m_kart->getNormal() : Vec3(0, 1, 0);
|
|
// Length of axis depends on the angle - i.e. the further awat
|
|
// the kart is from being upright, the larger the applied impulse
|
|
// will be, resulting in fast changes when the kart is on its
|
|
// side, but not overcompensating (and therefore shaking) when
|
|
// the kart is not much away from being upright.
|
|
btVector3 axis = kart_up.cross(terrain_up);
|
|
|
|
// To avoid the kart going backwards/forwards (or rolling sideways),
|
|
// set the pitch/roll to 0 before applying the 'straightening' impulse.
|
|
// TODO: make this works if gravity is changed.
|
|
btVector3 av = m_chassisBody->getAngularVelocity();
|
|
av.setX(0);
|
|
av.setZ(0);
|
|
m_chassisBody->setAngularVelocity(av);
|
|
// Give a nicely balanced feeling for rebalancing the kart
|
|
m_chassisBody->applyTorqueImpulse(axis * m_kart->getKartProperties()->getStabilitySmoothFlyingImpulse());
|
|
}
|
|
|
|
// Apply suspension forcen (i.e. upwards force)
|
|
// --------------------------------------------
|
|
updateSuspension(step);
|
|
|
|
for (int i=0;i<m_wheelInfo.size();i++)
|
|
{
|
|
//apply suspension force
|
|
btWheelInfo& wheel = m_wheelInfo[i];
|
|
|
|
btScalar suspensionForce = wheel.m_wheelsSuspensionForce;
|
|
|
|
if (suspensionForce > wheel.m_maxSuspensionForce)
|
|
{
|
|
suspensionForce = wheel.m_maxSuspensionForce;
|
|
}
|
|
btVector3 impulse = wheel.m_raycastInfo.m_contactNormalWS
|
|
* suspensionForce * step;
|
|
btVector3 relpos = wheel.m_raycastInfo.m_contactPointWS
|
|
- getRigidBody()->getCenterOfMassPosition();
|
|
|
|
getRigidBody()->applyImpulse(impulse, relpos);
|
|
|
|
}
|
|
|
|
// Update friction (i.e. forward force)
|
|
// ------------------------------------
|
|
updateFriction( step);
|
|
|
|
// If configured, add a force to keep karts on the track
|
|
// -----------------------------------------------------
|
|
float dif = m_kart->getKartProperties()->getStabilityDownwardImpulseFactor();
|
|
if(dif!=0 && m_num_wheels_on_ground==4)
|
|
{
|
|
float f = -fabsf(m_kart->getSpeed()) * dif;
|
|
btVector3 downwards_impulse = m_chassisBody->getWorldTransform().getBasis()
|
|
* btVector3(0, f, 0);
|
|
m_chassisBody->applyCentralImpulse(downwards_impulse);
|
|
}
|
|
|
|
// Apply additional impulse set by supertuxkart
|
|
// --------------------------------------------
|
|
if(m_time_additional_impulse>0)
|
|
{
|
|
float dt = step > m_time_additional_impulse
|
|
? m_time_additional_impulse
|
|
: step;
|
|
m_chassisBody->applyCentralImpulse(m_additional_impulse*dt);
|
|
m_time_additional_impulse -= dt;
|
|
}
|
|
|
|
// Apply additional rotation set by supertuxkart
|
|
// ---------------------------------------------
|
|
if(m_time_additional_rotation>0)
|
|
{
|
|
btTransform &t = m_chassisBody->getWorldTransform();
|
|
float dt = step > m_time_additional_rotation
|
|
? m_time_additional_rotation
|
|
: step;
|
|
btQuaternion add_rot(m_additional_rotation.getY()*dt,
|
|
m_additional_rotation.getX()*dt,
|
|
m_additional_rotation.getZ()*dt);
|
|
t.setRotation(t.getRotation()*add_rot);
|
|
m_chassisBody->setWorldTransform(t);
|
|
// Also apply the rotation to the interpolated world transform.
|
|
// This is important (at least if the rotation is only applied
|
|
// in one frame) since STK will actually use the interpolated
|
|
// transform, which would otherwise only be updated one frame
|
|
// later, resulting in a one-frame incorrect rotation of the
|
|
// kart, or a strongly 'visual jolt' of the kart
|
|
btTransform &iwt=m_chassisBody->getInterpolationWorldTransform();
|
|
iwt.setRotation(iwt.getRotation()*add_rot);
|
|
m_time_additional_rotation -= dt;
|
|
}
|
|
} // updateVehicle
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::setSteeringValue(btScalar steering, int wheel)
|
|
{
|
|
btAssert(wheel>=0 && wheel < getNumWheels());
|
|
|
|
btWheelInfo& wheelInfo = getWheelInfo(wheel);
|
|
wheelInfo.m_steering = steering;
|
|
} // setSteeringValue
|
|
|
|
// ----------------------------------------------------------------------------
|
|
btScalar btKart::getSteeringValue(int wheel) const
|
|
{
|
|
return getWheelInfo(wheel).m_steering;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::applyEngineForce(btScalar force, int wheel)
|
|
{
|
|
btAssert(wheel>=0 && wheel < getNumWheels());
|
|
btWheelInfo& wheelInfo = getWheelInfo(wheel);
|
|
wheelInfo.m_engineForce = force;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
const btWheelInfo& btKart::getWheelInfo(int index) const
|
|
{
|
|
btAssert((index >= 0) && (index < getNumWheels()));
|
|
|
|
return m_wheelInfo[index];
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
btWheelInfo& btKart::getWheelInfo(int index)
|
|
{
|
|
btAssert((index >= 0) && (index < getNumWheels()));
|
|
|
|
return m_wheelInfo[index];
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::setAllBrakes(btScalar brake)
|
|
{
|
|
for(int i=0; i<getNumWheels(); i++)
|
|
getWheelInfo(i).m_brake = brake;
|
|
} // setAllBrakes
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::updateSuspension(btScalar deltaTime)
|
|
{
|
|
(void)deltaTime;
|
|
|
|
btScalar chassisMass = btScalar(1.) / m_chassisBody->getInvMass();
|
|
|
|
for (int w_it=0; w_it<getNumWheels(); w_it++)
|
|
{
|
|
btWheelInfo &wheel_info = m_wheelInfo[w_it];
|
|
if ( !wheel_info.m_raycastInfo.m_isInContact )
|
|
{
|
|
// A very unphysical thing to handle slopes that are a bit too
|
|
// steep or uneven (resulting in only one wheel on the ground)
|
|
// If only the front or only the rear wheels are on the ground, add
|
|
// a force pulling the axis down (towards the ground). Note that it
|
|
// is already guaranteed that either both or no wheels on one axis
|
|
// are on the ground, so we have to test only one of the wheels
|
|
wheel_info.m_wheelsSuspensionForce =
|
|
-m_kart->getKartProperties()->getStabilityTrackConnectionAccel()
|
|
* chassisMass;
|
|
continue;
|
|
}
|
|
|
|
btScalar force;
|
|
|
|
// Spring
|
|
btScalar susp_length = wheel_info.getSuspensionRestLength();
|
|
btScalar current_length = wheel_info.m_raycastInfo.m_suspensionLength;
|
|
btScalar length_diff = (susp_length - current_length);
|
|
if(m_kart->getKartProperties()->getSuspensionExpSpringResponse())
|
|
length_diff *= fabsf(length_diff)/susp_length;
|
|
force = wheel_info.m_suspensionStiffness * length_diff
|
|
* wheel_info.m_clippedInvContactDotSuspension;
|
|
|
|
// Damper
|
|
btScalar projected_rel_vel = wheel_info.m_suspensionRelativeVelocity;
|
|
btScalar susp_damping = projected_rel_vel < btScalar(0.0)
|
|
? wheel_info.m_wheelsDampingCompression
|
|
: wheel_info.m_wheelsDampingRelaxation;
|
|
force -= susp_damping * projected_rel_vel;
|
|
|
|
// RESULT
|
|
wheel_info.m_wheelsSuspensionForce = force * chassisMass;
|
|
if (wheel_info.m_wheelsSuspensionForce < btScalar(0.))
|
|
{
|
|
wheel_info.m_wheelsSuspensionForce = btScalar(0.);
|
|
}
|
|
} // for (int w_it=0; w_it<getNumWheels(); w_it++)
|
|
|
|
} // updateSuspension
|
|
|
|
// ----------------------------------------------------------------------------
|
|
struct btWheelContactPoint
|
|
{
|
|
btRigidBody* m_body0;
|
|
btRigidBody* m_body1;
|
|
btVector3 m_frictionPositionWorld;
|
|
btVector3 m_frictionDirectionWorld;
|
|
btScalar m_jacDiagABInv;
|
|
btScalar m_maxImpulse;
|
|
|
|
|
|
btWheelContactPoint(btRigidBody* body0, btRigidBody* body1,
|
|
const btVector3& frictionPosWorld,
|
|
const btVector3& frictionDirectionWorld,
|
|
btScalar maxImpulse)
|
|
:m_body0(body0),
|
|
m_body1(body1),
|
|
m_frictionPositionWorld(frictionPosWorld),
|
|
m_frictionDirectionWorld(frictionDirectionWorld),
|
|
m_maxImpulse(maxImpulse)
|
|
{
|
|
btScalar denom0 = body0->computeImpulseDenominator(frictionPosWorld,
|
|
frictionDirectionWorld);
|
|
btScalar denom1 = body1->computeImpulseDenominator(frictionPosWorld,
|
|
frictionDirectionWorld);
|
|
btScalar relaxation = 1.f;
|
|
m_jacDiagABInv = relaxation/(denom0+denom1);
|
|
}
|
|
|
|
|
|
|
|
}; // struct btWheelContactPoint
|
|
|
|
// ----------------------------------------------------------------------------
|
|
btScalar btKart::calcRollingFriction(btWheelContactPoint& contactPoint)
|
|
{
|
|
|
|
const btVector3& contactPosWorld = contactPoint.m_frictionPositionWorld;
|
|
|
|
btVector3 rel_pos1 = contactPosWorld
|
|
- contactPoint.m_body0->getCenterOfMassPosition();
|
|
btVector3 rel_pos2 = contactPosWorld
|
|
- contactPoint.m_body1->getCenterOfMassPosition();
|
|
|
|
btScalar maxImpulse = contactPoint.m_maxImpulse;
|
|
|
|
btVector3 vel1 = contactPoint.m_body0->getVelocityInLocalPoint(rel_pos1);
|
|
btVector3 vel2 = contactPoint.m_body1->getVelocityInLocalPoint(rel_pos2);
|
|
btVector3 vel = vel1 - vel2;
|
|
|
|
btScalar vrel = contactPoint.m_frictionDirectionWorld.dot(vel);
|
|
|
|
// calculate j that moves us to zero relative velocity
|
|
// Note that num_wheels_on_ground > 0 since this function is called
|
|
// for wheels that touch the ground/
|
|
btScalar j1 = -vrel * contactPoint.m_jacDiagABInv / m_num_wheels_on_ground;
|
|
btSetMin(j1, maxImpulse);
|
|
btSetMax(j1, -maxImpulse);
|
|
|
|
return j1;
|
|
} // calcRollingFriction
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void btKart::updateFriction(btScalar timeStep)
|
|
{
|
|
//calculate the impulse, so that the wheels don't move sidewards
|
|
for (int i=0;i<getNumWheels();i++)
|
|
{
|
|
m_sideImpulse[i] = btScalar(0.);
|
|
btWheelInfo& wheelInfo = m_wheelInfo[i];
|
|
|
|
btRigidBody* groundObject =
|
|
(btRigidBody*) wheelInfo.m_raycastInfo.m_groundObject;
|
|
|
|
if(!groundObject) continue;
|
|
const btTransform& wheelTrans = getWheelTransformWS( i );
|
|
|
|
btMatrix3x3 wheelBasis0 = wheelTrans.getBasis();
|
|
m_axle[i] = btVector3(wheelBasis0[0][m_indexRightAxis],
|
|
wheelBasis0[1][m_indexRightAxis],
|
|
wheelBasis0[2][m_indexRightAxis] );
|
|
|
|
const btVector3& surfNormalWS =
|
|
wheelInfo.m_raycastInfo.m_contactNormalWS;
|
|
btScalar proj = m_axle[i].dot(surfNormalWS);
|
|
m_axle[i] -= surfNormalWS * proj;
|
|
m_axle[i] = m_axle[i].normalize();
|
|
|
|
m_forwardWS[i] = surfNormalWS.cross(m_axle[i]);
|
|
m_forwardWS[i].normalize();
|
|
|
|
resolveSingleBilateral(*m_chassisBody,
|
|
wheelInfo.m_raycastInfo.m_contactPointWS,
|
|
*groundObject,
|
|
wheelInfo.m_raycastInfo.m_contactPointWS,
|
|
btScalar(0.), m_axle[i],m_sideImpulse[i],
|
|
timeStep);
|
|
|
|
btScalar sideFrictionStiffness2 = btScalar(1.0);
|
|
m_sideImpulse[i] *= sideFrictionStiffness2;
|
|
}
|
|
|
|
btScalar sideFactor = btScalar(1.);
|
|
btScalar fwdFactor = 0.5;
|
|
|
|
bool sliding = false;
|
|
for (int wheel=0; wheel<getNumWheels(); wheel++)
|
|
{
|
|
btWheelInfo& wheelInfo = m_wheelInfo[wheel];
|
|
m_wheelInfo[wheel].m_skidInfo = btScalar(1.);
|
|
m_forwardImpulse[wheel] = btScalar(0.);
|
|
|
|
btRigidBody* groundObject =
|
|
(btRigidBody*) wheelInfo.m_raycastInfo.m_groundObject;
|
|
if(!groundObject) continue;
|
|
|
|
if(m_zipper_speed > 0)
|
|
{
|
|
if (wheel==2 || wheel==3)
|
|
{
|
|
// The zipper velocity is the speed that should be
|
|
// reached. So compute the impulse to accelerate the
|
|
// kart up to that speed:
|
|
m_forwardImpulse[wheel] =
|
|
0.5f*(m_zipper_speed -
|
|
getRigidBody()->getLinearVelocity().length())
|
|
/ m_chassisBody->getInvMass();
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
btScalar rollingFriction = 0.f;
|
|
|
|
if (wheelInfo.m_engineForce != 0.f)
|
|
{
|
|
rollingFriction = wheelInfo.m_engineForce* timeStep;
|
|
}
|
|
else
|
|
{
|
|
btScalar defaultRollingFrictionImpulse = 0.f;
|
|
btScalar maxImpulse = wheelInfo.m_brake
|
|
? wheelInfo.m_brake
|
|
: defaultRollingFrictionImpulse;
|
|
btWheelContactPoint contactPt(m_chassisBody, groundObject,
|
|
wheelInfo.m_raycastInfo.m_contactPointWS,
|
|
m_forwardWS[wheel],maxImpulse);
|
|
rollingFriction = calcRollingFriction(contactPt);
|
|
// This is a work around for the problem that a kart shakes
|
|
// if it is braking: we get a minor impulse forward, which
|
|
// bullet then tries to offset by applying a backward
|
|
// impulse - which is a bit too big, causing a impulse
|
|
// backwards, ... till the kart is shaking backwards and
|
|
// forwards. By only applying half of the impulse in case
|
|
// of low friction this goes away.
|
|
if(wheelInfo.m_brake && fabsf(rollingFriction)<10)
|
|
rollingFriction*=0.5f;
|
|
}
|
|
|
|
m_forwardImpulse[wheel] = rollingFriction;
|
|
}
|
|
if(m_time_additional_impulse>0)
|
|
{
|
|
sliding = true;
|
|
m_wheelInfo[wheel].m_skidInfo = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
btScalar maximp = wheelInfo.m_wheelsSuspensionForce
|
|
* timeStep * wheelInfo.m_frictionSlip;
|
|
btScalar maximpSide = maximp;
|
|
btScalar maximpSquared = maximp * maximpSide;
|
|
|
|
btScalar x = (m_forwardImpulse[wheel] ) * fwdFactor;
|
|
btScalar y = (m_sideImpulse[wheel] ) * sideFactor;
|
|
|
|
btScalar impulseSquared = (x*x + y*y);
|
|
|
|
if (impulseSquared > maximpSquared)
|
|
{
|
|
sliding = true;
|
|
btScalar factor = maximp / btSqrt(impulseSquared);
|
|
m_wheelInfo[wheel].m_skidInfo *= factor;
|
|
} // if impulseSquared > maximpSquared
|
|
} // else (!m_timed_impulse
|
|
} // for (int wheel=0; wheel<getNumWheels(); wheel++)
|
|
|
|
|
|
// Note: don't reset zipper speed, or the kart rewinder will
|
|
// get incorrect zipper information.
|
|
|
|
// The kart just stopped skidding. Adjust the velocity so that
|
|
// it points in the right direction.
|
|
// FIXME: this is not good enough, we need some smooth interpolation here.
|
|
if(m_is_skidding && m_skid_angular_velocity == 0)
|
|
{
|
|
btVector3 v = m_chassisBody->getLinearVelocity();
|
|
v.setZ(sqrt(v.getX()*v.getX()+v.getZ()*v.getZ()));
|
|
v.setX(0);
|
|
btVector3 v_new = m_chassisBody->getWorldTransform().getBasis()*v;
|
|
m_chassisBody->setLinearVelocity(v_new);
|
|
m_is_skidding = false;
|
|
}
|
|
|
|
if(m_skid_angular_velocity!=0)
|
|
{
|
|
m_is_skidding = true;
|
|
// Skidding is implemented by not having any forward impulse,
|
|
// but only add a side impulse
|
|
for(unsigned int i=0; i<4; i++)
|
|
{
|
|
m_forwardImpulse[i] = 0;
|
|
m_sideImpulse[i] = 0;
|
|
}
|
|
btVector3 av = m_chassisBody->getAngularVelocity();
|
|
av.setY(m_skid_angular_velocity);
|
|
m_chassisBody->setAngularVelocity(av);
|
|
}
|
|
else if (sliding && (m_allow_sliding || m_time_additional_impulse>0) )
|
|
{
|
|
for (int wheel = 0; wheel < getNumWheels(); wheel++)
|
|
{
|
|
if (m_sideImpulse[wheel] != btScalar(0.) &&
|
|
m_wheelInfo[wheel].m_skidInfo< btScalar(1.) )
|
|
{
|
|
m_forwardImpulse[wheel] *= m_wheelInfo[wheel].m_skidInfo;
|
|
m_sideImpulse[wheel] *= m_wheelInfo[wheel].m_skidInfo;
|
|
}
|
|
} // for wheel <getNumWheels
|
|
} // if sliding
|
|
|
|
// Apply the impulses
|
|
// ------------------
|
|
for (int wheel = 0;wheel<getNumWheels() ; wheel++)
|
|
{
|
|
btWheelInfo& wheelInfo = m_wheelInfo[wheel];
|
|
btVector3 rel_pos = wheelInfo.m_raycastInfo.m_contactPointWS
|
|
- m_chassisBody->getCenterOfMassPosition();
|
|
|
|
if (m_forwardImpulse[wheel] != btScalar(0.))
|
|
{
|
|
m_chassisBody->applyImpulse(
|
|
m_forwardWS[wheel]*(m_forwardImpulse[wheel]),
|
|
#define COMPATIBLE_0_7_3
|
|
#ifdef COMPATIBLE_0_7_3
|
|
// This was apparently done to help hexley
|
|
btVector3(0,0,0));
|
|
#else
|
|
rel_pos);
|
|
#endif
|
|
}
|
|
if (m_sideImpulse[wheel] != btScalar(0.))
|
|
{
|
|
btRigidBody* groundObject =
|
|
(btRigidBody*) m_wheelInfo[wheel].m_raycastInfo.m_groundObject;
|
|
btVector3 rel_pos2 = wheelInfo.m_raycastInfo.m_contactPointWS
|
|
- groundObject->getCenterOfMassPosition();
|
|
//adjust relative position above ground so that force only
|
|
// acts sideways
|
|
btVector3 delta_vec = (wheelInfo.m_raycastInfo.m_hardPointWS
|
|
- wheelInfo.m_raycastInfo.m_contactPointWS);
|
|
if (delta_vec.length() != btScalar (0))
|
|
{
|
|
delta_vec = delta_vec.normalize();
|
|
rel_pos -= delta_vec * rel_pos.dot(delta_vec);
|
|
}
|
|
|
|
btVector3 sideImp = m_axle[wheel] * m_sideImpulse[wheel];
|
|
|
|
#if defined ROLLING_INFLUENCE_FIX && !defined COMPATIBLE_0_7_3
|
|
// fix. It only worked if car's up was along Y - VT.
|
|
btVector3 vChassisWorldUp =
|
|
getRigidBody()->getCenterOfMassTransform()
|
|
.getBasis().getColumn(m_indexUpAxis);
|
|
rel_pos -= vChassisWorldUp * (vChassisWorldUp.dot(rel_pos) *
|
|
(1.f-wheelInfo.m_rollInfluence) );
|
|
#else
|
|
rel_pos[m_indexUpAxis] *= wheelInfo.m_rollInfluence;
|
|
#endif
|
|
m_chassisBody->applyImpulse(sideImp,rel_pos);
|
|
|
|
//apply friction impulse on the ground
|
|
groundObject->applyImpulse(-sideImp,rel_pos2);
|
|
} // if (m_sideImpulse[wheel] != btScalar(0.))
|
|
} // for wheel<getNumWheels()
|
|
|
|
|
|
|
|
} // updateFriction
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void btKart::debugDraw(btIDebugDraw* debugDrawer)
|
|
{
|
|
const btVector3 &from = m_kart->getTerrainInfo()->getOrigin();
|
|
const btVector3 &to = m_kart->getTerrainInfo()->getHitPoint();
|
|
debugDrawer->drawLine(from, to, btVector3(0.5, 0.5, 0));
|
|
|
|
for (int v=0;v<getNumWheels();v++)
|
|
{
|
|
btVector3 wheelColor(0,1,1);
|
|
const btWheelInfo &w = getWheelInfo(v);
|
|
if (w.m_raycastInfo.m_isInContact)
|
|
{
|
|
wheelColor.setValue(0,0,1);
|
|
} else
|
|
{
|
|
wheelColor.setValue(1,0,1);
|
|
}
|
|
|
|
btVector3 wheelPosWS = w.m_worldTransform.getOrigin();
|
|
|
|
btVector3 axle = btVector3(
|
|
w.m_worldTransform.getBasis()[0][getRightAxis()],
|
|
w.m_worldTransform.getBasis()[1][getRightAxis()],
|
|
w.m_worldTransform.getBasis()[2][getRightAxis()]);
|
|
|
|
//debug wheels (cylinders)
|
|
debugDrawer->drawLine(wheelPosWS,wheelPosWS+axle,wheelColor);
|
|
debugDrawer->drawLine(wheelPosWS,
|
|
w.m_raycastInfo.m_contactPointWS,
|
|
wheelColor);
|
|
// Draw the (interpolated) normal of the ground at the wheel position
|
|
btVector3 white(1.0f, 1.0f, 1.0f);
|
|
debugDrawer->drawLine(w.m_raycastInfo.m_contactPointWS,
|
|
w.m_raycastInfo.m_contactPointWS+
|
|
w.m_raycastInfo.m_contactNormalWS,
|
|
white);
|
|
int n = w.m_raycastInfo.m_triangle_index;
|
|
if (n > -1)
|
|
{
|
|
const TriangleMesh &tm = Track::getCurrentTrack()->getTriangleMesh();
|
|
btVector3 p1, p2, p3;
|
|
tm.getTriangle(n, &p1, &p2, &p3);
|
|
btVector3 n1, n2, n3;
|
|
tm.getNormals(n, &n1, &n2, &n3);
|
|
// Draw the normals at the vertices
|
|
debugDrawer->drawLine(p1, p1 + n1, white);
|
|
debugDrawer->drawLine(p2, p2 + n2, white);
|
|
debugDrawer->drawLine(p3, p3 + n3, white);
|
|
// Also draw the triangle in white, it can make it easier
|
|
// to identify which triangle a wheel is on
|
|
debugDrawer->drawTriangle(p1, p2, p3, white, 1.0f);
|
|
}
|
|
|
|
} // for i < getNumWheels
|
|
btVector3 yellow(1.0f, 1.0f, 0.0f);
|
|
debugDrawer->drawLine(m_visual_contact_point[0], m_visual_contact_point[2], yellow);
|
|
debugDrawer->drawLine(m_visual_contact_point[1], m_visual_contact_point[3], yellow);
|
|
} // debugDraw
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Enables or disables sliding.
|
|
* \param active Enable (true) or disable sliding.
|
|
*/
|
|
void btKart::setSliding(bool active)
|
|
{
|
|
m_allow_sliding = active;
|
|
} // setSliding
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Activates an additional speedup for the kart so that it reaches the
|
|
* specified speed.
|
|
* \param speed The speed to reach.
|
|
*/
|
|
void btKart::instantSpeedIncreaseTo(float speed)
|
|
{
|
|
// Avoid that a speed 'increase' might cause a slowdown
|
|
if (m_chassisBody->getLinearVelocity().length2() > speed*speed)
|
|
{
|
|
return;
|
|
}
|
|
m_zipper_speed = speed;
|
|
} // activateZipper
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Caps the speed at a given value. If necessary the kart will
|
|
* instantaneously change its speed. */
|
|
void btKart::capSpeed(float max_speed)
|
|
{
|
|
const btVector3 &velocity = m_chassisBody->getLinearVelocity();
|
|
float speed = velocity.length();
|
|
if(speed!=0)
|
|
{
|
|
const float velocity_ratio = max_speed / speed;
|
|
m_chassisBody->setLinearVelocity(velocity * velocity_ratio);
|
|
}
|
|
} // capSpeed
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//Shorter version of above raycast function. This is used when projecting
|
|
//vehicles towards the ground at the start of a race
|
|
btScalar btKart::rayCast(btWheelInfo& wheel, const btVector3& ray)
|
|
{
|
|
updateWheelTransformsWS( wheel,false);
|
|
|
|
btScalar depth = -1;
|
|
|
|
const btVector3& source = wheel.m_raycastInfo.m_hardPointWS;
|
|
wheel.m_raycastInfo.m_contactPointWS = source + ray;
|
|
const btVector3& target = source + ray;
|
|
|
|
btVehicleRaycaster::btVehicleRaycasterResult rayResults;
|
|
|
|
assert(m_vehicleRaycaster);
|
|
|
|
void* object = m_vehicleRaycaster->castRay(source,target,rayResults);
|
|
|
|
wheel.m_raycastInfo.m_groundObject = 0;
|
|
|
|
if (object)
|
|
{
|
|
depth = ray.length() * rayResults.m_distFraction;
|
|
|
|
wheel.m_raycastInfo.m_contactPointWS = rayResults.m_hitPointInWorld;
|
|
wheel.m_raycastInfo.m_contactNormalWS = rayResults.m_hitNormalInWorld;
|
|
wheel.m_raycastInfo.m_isInContact = true;
|
|
wheel.m_raycastInfo.m_triangle_index = rayResults.m_triangle_index;
|
|
}
|
|
|
|
return depth;
|
|
} // rayCast(btWheelInfo& wheel, const btVector3& ray
|
|
|
|
// ----------------------------------------------------------------------------
|