#include "vehicle_body.h" #define ROLLING_INFLUENCE_FIX class btVehicleJacobianEntry { public: Vector3 m_linearJointAxis; Vector3 m_aJ; Vector3 m_bJ; Vector3 m_0MinvJt; Vector3 m_1MinvJt; //Optimization: can be stored in the w/last component of one of the vectors real_t m_Adiag; real_t getDiagonal() const { return m_Adiag; } btVehicleJacobianEntry() {}; //constraint between two different rigidbodies btVehicleJacobianEntry( const Matrix3& world2A, const Matrix3& world2B, const Vector3& rel_pos1, const Vector3& rel_pos2, const Vector3& jointAxis, const Vector3& inertiaInvA, const real_t massInvA, const Vector3& inertiaInvB, const real_t massInvB) :m_linearJointAxis(jointAxis) { m_aJ = world2A.xform(rel_pos1.cross(m_linearJointAxis)); m_bJ = world2B.xform(rel_pos2.cross(-m_linearJointAxis)); m_0MinvJt = inertiaInvA * m_aJ; m_1MinvJt = inertiaInvB * m_bJ; m_Adiag = massInvA + m_0MinvJt.dot(m_aJ) + massInvB + m_1MinvJt.dot(m_bJ); //btAssert(m_Adiag > real_t(0.0)); } real_t getRelativeVelocity(const Vector3& linvelA,const Vector3& angvelA,const Vector3& linvelB,const Vector3& angvelB) { Vector3 linrel = linvelA - linvelB; Vector3 angvela = angvelA * m_aJ; Vector3 angvelb = angvelB * m_bJ; linrel *= m_linearJointAxis; angvela += angvelb; angvela += linrel; real_t rel_vel2 = angvela[0]+angvela[1]+angvela[2]; return rel_vel2 + CMP_EPSILON; } }; void VehicleWheel::_notification(int p_what) { if (p_what==NOTIFICATION_ENTER_SCENE) { if (!get_parent()) return; VehicleBody *cb = get_parent()->cast_to(); if (!cb) return; body=cb; local_xform=get_transform(); cb->wheels.push_back(this); m_chassisConnectionPointCS = get_transform().origin; m_wheelDirectionCS = -get_transform().basis.get_axis(Vector3::AXIS_Y).normalized(); m_wheelAxleCS = get_transform().basis.get_axis(Vector3::AXIS_X).normalized(); } if (p_what==NOTIFICATION_EXIT_SCENE) { if (!get_parent()) return; VehicleBody *cb = get_parent()->cast_to(); if (!cb) return; cb->wheels.erase(this); body=NULL; } } void VehicleWheel::_update(PhysicsDirectBodyState *s) { if (m_raycastInfo.m_isInContact) { real_t project= m_raycastInfo.m_contactNormalWS.dot( m_raycastInfo.m_wheelDirectionWS ); Vector3 chassis_velocity_at_contactPoint; Vector3 relpos = m_raycastInfo.m_contactPointWS - s->get_transform().origin; chassis_velocity_at_contactPoint = s->get_linear_velocity() + (s->get_angular_velocity()).cross(relpos);// * mPos); real_t projVel = m_raycastInfo.m_contactNormalWS.dot( chassis_velocity_at_contactPoint ); if ( project >= real_t(-0.1)) { m_suspensionRelativeVelocity = real_t(0.0); m_clippedInvContactDotSuspension = real_t(1.0) / real_t(0.1); } else { real_t inv = real_t(-1.) / project; m_suspensionRelativeVelocity = projVel * inv; m_clippedInvContactDotSuspension = inv; } } else // Not in contact : position wheel in a nice (rest length) position { m_raycastInfo.m_suspensionLength = m_suspensionRestLength; m_suspensionRelativeVelocity = real_t(0.0); m_raycastInfo.m_contactNormalWS = -m_raycastInfo.m_wheelDirectionWS; m_clippedInvContactDotSuspension = real_t(1.0); } } void VehicleWheel::_bind_methods() { } VehicleWheel::VehicleWheel() { m_steering = real_t(0.); m_engineForce = real_t(0.); m_rotation = real_t(0.); m_deltaRotation = real_t(0.); m_brake = real_t(0.); m_rollInfluence = real_t(0.1); m_suspensionRestLength = 0.15; m_wheelRadius = 0.5;//0.28; m_suspensionStiffness = 5.88; m_wheelsDampingCompression = 0.83; m_wheelsDampingRelaxation = 0.88; m_frictionSlip = 10.5; m_bIsFrontWheel = false; m_maxSuspensionTravelCm = 500; m_maxSuspensionForce = 6000; m_suspensionRelativeVelocity=0; m_clippedInvContactDotSuspension=1.0; m_raycastInfo.m_isInContact=false; body=NULL; } void VehicleBody::_update_wheel_transform(VehicleWheel& wheel ,PhysicsDirectBodyState *s) { wheel.m_raycastInfo.m_isInContact = false; Transform chassisTrans = s->get_transform(); //if (interpolatedTransform && (getRigidBody()->getMotionState())) //{ // getRigidBody()->getMotionState()->getWorldTransform(chassisTrans); //} wheel.m_raycastInfo.m_hardPointWS = chassisTrans.xform( wheel.m_chassisConnectionPointCS ); wheel.m_raycastInfo.m_wheelDirectionWS = chassisTrans.get_basis().xform( wheel.m_wheelDirectionCS).normalized(); wheel.m_raycastInfo.m_wheelAxleWS = chassisTrans.get_basis().xform( wheel.m_wheelAxleCS ).normalized(); } void VehicleBody::_update_wheel(int p_idx,PhysicsDirectBodyState *s) { VehicleWheel& wheel = *wheels[p_idx]; _update_wheel_transform(wheel,s); Vector3 up = -wheel.m_raycastInfo.m_wheelDirectionWS; const Vector3& right = wheel.m_raycastInfo.m_wheelAxleWS; Vector3 fwd = up.cross(right); fwd = fwd.normalized(); // up = right.cross(fwd); // up.normalize(); //rotate around steering over de wheelAxleWS real_t steering = wheel.m_steering; Matrix3 steeringMat(up,steering); Matrix3 rotatingMat(right,-wheel.m_rotation); Matrix3 basis2( right[0],up[0],fwd[0], right[1],up[1],fwd[1], right[2],up[2],fwd[2] ); wheel.m_worldTransform.set_basis(steeringMat * rotatingMat * basis2); wheel.m_worldTransform.set_origin( wheel.m_raycastInfo.m_hardPointWS + wheel.m_raycastInfo.m_wheelDirectionWS * wheel.m_raycastInfo.m_suspensionLength ); } real_t VehicleBody::_ray_cast(int p_idx,PhysicsDirectBodyState *s) { VehicleWheel& wheel = *wheels[p_idx]; _update_wheel_transform(wheel,s); real_t depth = -1; real_t raylen = wheel.m_suspensionRestLength+wheel.m_wheelRadius; Vector3 rayvector = wheel.m_raycastInfo.m_wheelDirectionWS * (raylen); const Vector3& source = wheel.m_raycastInfo.m_hardPointWS; wheel.m_raycastInfo.m_contactPointWS = source + rayvector; const Vector3& target = wheel.m_raycastInfo.m_contactPointWS; real_t param = real_t(0.); PhysicsDirectSpaceState::RayResult rr; PhysicsDirectSpaceState *ss=s->get_space_state(); bool col = ss->intersect_ray(source,target,rr,exclude); wheel.m_raycastInfo.m_groundObject = 0; if (col) { //print_line("WHEEL "+itos(p_idx)+" FROM "+source+" TO: "+target); //print_line("WHEEL "+itos(p_idx)+" COLLIDE? "+itos(col)); param = source.distance_to(rr.position)/source.distance_to(target); depth = raylen * param; wheel.m_raycastInfo.m_contactNormalWS = rr.normal; wheel.m_raycastInfo.m_isInContact = true; if (rr.collider) wheel.m_raycastInfo.m_groundObject=rr.collider->cast_to(); real_t hitDistance = param*raylen; wheel.m_raycastInfo.m_suspensionLength = hitDistance - wheel.m_wheelRadius; //clamp on max suspension travel real_t minSuspensionLength = wheel.m_suspensionRestLength - wheel.m_maxSuspensionTravelCm*real_t(0.01); real_t maxSuspensionLength = wheel.m_suspensionRestLength+ wheel.m_maxSuspensionTravelCm*real_t(0.01); 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 = rr.position; real_t denominator= wheel.m_raycastInfo.m_contactNormalWS.dot( wheel.m_raycastInfo.m_wheelDirectionWS ); Vector3 chassis_velocity_at_contactPoint; //Vector3 relpos = wheel.m_raycastInfo.m_contactPointWS-getRigidBody()->getCenterOfMassPosition(); //chassis_velocity_at_contactPoint = getRigidBody()->getVelocityInLocalPoint(relpos); chassis_velocity_at_contactPoint = s->get_linear_velocity() + (s->get_angular_velocity()).cross(wheel.m_raycastInfo.m_contactPointWS-s->get_transform().origin);// * mPos); real_t projVel = wheel.m_raycastInfo.m_contactNormalWS.dot( chassis_velocity_at_contactPoint ); if ( denominator >= real_t(-0.1)) { wheel.m_suspensionRelativeVelocity = real_t(0.0); wheel.m_clippedInvContactDotSuspension = real_t(1.0) / real_t(0.1); } else { real_t inv = real_t(-1.) / denominator; wheel.m_suspensionRelativeVelocity = projVel * inv; wheel.m_clippedInvContactDotSuspension = inv; } } else { wheel.m_raycastInfo.m_isInContact = false; //put wheel info as in rest position wheel.m_raycastInfo.m_suspensionLength = wheel.m_suspensionRestLength; wheel.m_suspensionRelativeVelocity = real_t(0.0); wheel.m_raycastInfo.m_contactNormalWS = - wheel.m_raycastInfo.m_wheelDirectionWS; wheel.m_clippedInvContactDotSuspension = real_t(1.0); } return depth; } void VehicleBody::_update_suspension(PhysicsDirectBodyState *s) { real_t deltaTime = s->get_step(); real_t chassisMass = mass; for (int w_it=0; w_it real_t(1.1)) { impulse = real_t(0.); return; } Vector3 rel_pos1 = pos1 - s->get_transform().origin; Vector3 rel_pos2; if (body2) rel_pos2 = pos2 - body2->get_global_transform().origin; //this jacobian entry could be re-used for all iterations Vector3 vel1 = s->get_linear_velocity() + (s->get_angular_velocity()).cross(rel_pos1);// * mPos); Vector3 vel2; if (body2) vel2=body2->get_linear_velocity() + body2->get_angular_velocity().cross(rel_pos2); Vector3 vel = vel1 - vel2; Matrix3 b2trans; float b2invmass=0; Vector3 b2lv; Vector3 b2av; Vector3 b2invinertia; //todo if (body2) { b2trans = body2->get_global_transform().basis.transposed(); b2invmass = body2->get_inverse_mass(); b2lv = body2->get_linear_velocity(); b2av = body2->get_angular_velocity(); } btVehicleJacobianEntry jac(s->get_transform().basis.transposed(), b2trans, rel_pos1, rel_pos2, normal, s->get_inverse_inertia(), 1.0/mass, b2invinertia, b2invmass); real_t jacDiagAB = jac.getDiagonal(); real_t jacDiagABInv = real_t(1.) / jacDiagAB; real_t rel_vel = jac.getRelativeVelocity( s->get_linear_velocity(), s->get_transform().basis.transposed().xform(s->get_angular_velocity()), b2lv, b2trans.xform(b2av)); real_t a; a=jacDiagABInv; rel_vel = normal.dot(vel); //todo: move this into proper structure real_t contactDamping = real_t(0.4); #define ONLY_USE_LINEAR_MASS #ifdef ONLY_USE_LINEAR_MASS real_t massTerm = real_t(1.) / ((1.0/mass) + b2invmass); impulse = - contactDamping * rel_vel * massTerm; #else real_t velocityImpulse = -contactDamping * rel_vel * jacDiagABInv; impulse = velocityImpulse; #endif } VehicleBody::btVehicleWheelContactPoint::btVehicleWheelContactPoint(PhysicsDirectBodyState *s,PhysicsBody* body1,const Vector3& frictionPosWorld,const Vector3& frictionDirectionWorld, real_t maxImpulse) :m_s(s), m_body1(body1), m_frictionPositionWorld(frictionPosWorld), m_frictionDirectionWorld(frictionDirectionWorld), m_maxImpulse(maxImpulse) { float denom0=0; float denom1=0; { Vector3 r0 = frictionPosWorld - s->get_transform().origin; Vector3 c0 = (r0).cross(frictionDirectionWorld); Vector3 vec = s->get_inverse_inertia_tensor().xform_inv(c0).cross(r0); denom0= s->get_inverse_mass() + frictionDirectionWorld.dot(vec); } if (body1) { Vector3 r0 = frictionPosWorld - body1->get_global_transform().origin; Vector3 c0 = (r0).cross(frictionDirectionWorld); Vector3 vec = s->get_inverse_inertia_tensor().xform_inv(c0).cross(r0); //denom1= body1->get_inverse_mass() + frictionDirectionWorld.dot(vec); denom1=0; } real_t relaxation = 1.f; m_jacDiagABInv = relaxation/(denom0+denom1); } real_t VehicleBody::_calc_rolling_friction(btVehicleWheelContactPoint& contactPoint) { real_t j1=0.f; const Vector3& contactPosWorld = contactPoint.m_frictionPositionWorld; Vector3 rel_pos1 = contactPosWorld - contactPoint.m_s->get_transform().origin; Vector3 rel_pos2; if (contactPoint.m_body1) rel_pos2 = contactPosWorld - contactPoint.m_body1->get_global_transform().origin; real_t maxImpulse = contactPoint.m_maxImpulse; Vector3 vel1 = contactPoint.m_s->get_linear_velocity() + (contactPoint.m_s->get_angular_velocity()).cross(rel_pos1);// * mPos); Vector3 vel2; if (contactPoint.m_body1) { vel2=contactPoint.m_body1->get_linear_velocity() + contactPoint.m_body1->get_angular_velocity().cross(rel_pos2); } Vector3 vel = vel1 - vel2; real_t vrel = contactPoint.m_frictionDirectionWorld.dot(vel); // calculate j that moves us to zero relative velocity j1 = -vrel * contactPoint.m_jacDiagABInv; return CLAMP(j1,-maxImpulse,maxImpulse); } static const real_t sideFrictionStiffness2 = real_t(1.0); void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { //calculate the impulse, so that the wheels don't move sidewards int numWheel = wheels.size(); if (!numWheel) return; m_forwardWS.resize(numWheel); m_axle.resize(numWheel); m_forwardImpulse.resize(numWheel); m_sideImpulse.resize(numWheel); int numWheelsOnGround = 0; //collapse all those loops into one! for (int i=0;iget_step(); } else { real_t defaultRollingFrictionImpulse = 0.f; real_t maxImpulse = wheelInfo.m_brake ? wheelInfo.m_brake : defaultRollingFrictionImpulse; btVehicleWheelContactPoint contactPt(s,wheelInfo.m_raycastInfo.m_groundObject,wheelInfo.m_raycastInfo.m_contactPointWS,m_forwardWS[wheel],maxImpulse); rollingFriction = _calc_rolling_friction(contactPt); } } //switch between active rolling (throttle), braking and non-active rolling friction (no throttle/break) m_forwardImpulse[wheel] = real_t(0.); wheelInfo.m_skidInfo= real_t(1.); if (wheelInfo.m_raycastInfo.m_isInContact) { wheelInfo.m_skidInfo= real_t(1.); real_t maximp = wheelInfo.m_wheelsSuspensionForce * s->get_step() * wheelInfo.m_frictionSlip; real_t maximpSide = maximp; real_t maximpSquared = maximp * maximpSide; m_forwardImpulse[wheel] = rollingFriction;//wheelInfo.m_engineForce* timeStep; real_t x = (m_forwardImpulse[wheel] ) * fwdFactor; real_t y = (m_sideImpulse[wheel] ) * sideFactor; real_t impulseSquared = (x*x + y*y); if (impulseSquared > maximpSquared) { sliding = true; real_t factor = maximp / Math::sqrt(impulseSquared); wheelInfo.m_skidInfo *= factor; } } } } if (sliding) { for (int wheel = 0;wheel < wheels.size(); wheel++) { if (m_sideImpulse[wheel] != real_t(0.)) { if (wheels[wheel]->m_skidInfo< real_t(1.)) { m_forwardImpulse[wheel] *= wheels[wheel]->m_skidInfo; m_sideImpulse[wheel] *= wheels[wheel]->m_skidInfo; } } } } // apply the impulses { for (int wheel = 0;wheelget_transform().origin; if (m_forwardImpulse[wheel] != real_t(0.)) { s->apply_impulse(rel_pos,m_forwardWS[wheel]*(m_forwardImpulse[wheel])); } if (m_sideImpulse[wheel] != real_t(0.)) { PhysicsBody* groundObject = wheelInfo.m_raycastInfo.m_groundObject; Vector3 rel_pos2; if (groundObject) { rel_pos2=wheelInfo.m_raycastInfo.m_contactPointWS - groundObject->get_global_transform().origin; } Vector3 sideImp = m_axle[wheel] * m_sideImpulse[wheel]; #if defined ROLLING_INFLUENCE_FIX // fix. It only worked if car's up was along Y - VT. Vector3 vChassisWorldUp = s->get_transform().basis.transposed()[1];//getRigidBody()->getCenterOfMassTransform().getBasis().getColumn(m_indexUpAxis); rel_pos -= vChassisWorldUp * (vChassisWorldUp.dot(rel_pos) * (1.f-wheelInfo.m_rollInfluence)); #else rel_pos[1] *= wheelInfo.m_rollInfluence; //? #endif s->apply_impulse(rel_pos,sideImp); //apply friction impulse on the ground //todo //groundObject->applyImpulse(-sideImp,rel_pos2); } } } } void VehicleBody::_direct_state_changed(Object *p_state) { PhysicsDirectBodyState *s = p_state->cast_to(); set_ignore_transform_notification(true); set_global_transform(s->get_transform()); set_ignore_transform_notification(false); float step = s->get_step(); for(int i=0;i wheel.m_maxSuspensionForce) { suspensionForce = wheel.m_maxSuspensionForce; } Vector3 impulse = wheel.m_raycastInfo.m_contactNormalWS * suspensionForce * step; Vector3 relpos = wheel.m_raycastInfo.m_contactPointWS - s->get_transform().origin; s->apply_impulse(relpos,impulse); //getRigidBody()->applyImpulse(impulse, relpos); } _update_friction(s); for (int i=0;iget_transform().origin; Vector3 vel = s->get_linear_velocity() + (s->get_angular_velocity()).cross(relpos);// * mPos); if (wheel.m_raycastInfo.m_isInContact) { const Transform& chassisWorldTransform = s->get_transform(); Vector3 fwd ( chassisWorldTransform.basis[0][Vector3::AXIS_Z], chassisWorldTransform.basis[1][Vector3::AXIS_Z], chassisWorldTransform.basis[2][Vector3::AXIS_Z]); real_t proj = fwd.dot(wheel.m_raycastInfo.m_contactNormalWS); fwd -= wheel.m_raycastInfo.m_contactNormalWS * proj; real_t proj2 = fwd.dot(vel); wheel.m_deltaRotation = (proj2 * step) / (wheel.m_wheelRadius); wheel.m_rotation += wheel.m_deltaRotation; } else { wheel.m_rotation += wheel.m_deltaRotation; } wheel.m_deltaRotation *= real_t(0.99);//damping of rotation when not in contact } } void VehicleBody::set_mass(real_t p_mass) { mass=p_mass; PhysicsServer::get_singleton()->body_set_param(get_rid(),PhysicsServer::BODY_PARAM_MASS,mass); } real_t VehicleBody::get_mass() const{ return mass; } void VehicleBody::set_friction(real_t p_friction) { friction=p_friction; PhysicsServer::get_singleton()->body_set_param(get_rid(),PhysicsServer::BODY_PARAM_FRICTION,friction); } real_t VehicleBody::get_friction() const{ return friction; } void VehicleBody::_bind_methods(){ ObjectTypeDB::bind_method(_MD("set_mass","mass"),&VehicleBody::set_mass); ObjectTypeDB::bind_method(_MD("get_mass"),&VehicleBody::get_mass); ObjectTypeDB::bind_method(_MD("set_friction","friction"),&VehicleBody::set_friction); ObjectTypeDB::bind_method(_MD("get_friction"),&VehicleBody::get_friction); ObjectTypeDB::bind_method(_MD("_direct_state_changed"),&VehicleBody::_direct_state_changed); ADD_PROPERTY( PropertyInfo(Variant::REAL,"body/mass",PROPERTY_HINT_RANGE,"0.01,65536,0.01"),_SCS("set_mass"),_SCS("get_mass")); ADD_PROPERTY( PropertyInfo(Variant::REAL,"body/friction",PROPERTY_HINT_RANGE,"0.01,1,0.01"),_SCS("set_friction"),_SCS("get_friction")); } VehicleBody::VehicleBody() : PhysicsBody(PhysicsServer::BODY_MODE_RIGID) { m_pitchControl=0; m_currentVehicleSpeedKmHour = real_t(0.); m_steeringValue = real_t(0.); mass=1; friction=1; ccd=false; exclude.insert(get_rid()); PhysicsServer::get_singleton()->body_set_force_integration_callback(get_rid(),this,"_direct_state_changed"); }