/*=========================================================================== physicslocomotion.cpp created April 24, 2002 by Greg Mayer Copyright (c) 2002 Radical Entertainment, Inc. All rights reserved. ===========================================================================*/ #include #include #include #include #include #include #include #include #include #include #include using namespace sim; /* data that should move from Vehicle to here... struct TerrainIntersectCache { rmt::Vector closestTriPosn; rmt::Vector closestTriNormal; rmt::Vector planePosn; rmt::Vector planeNorm; }; TerrainIntersectCache mTerrainIntersectCache[4]; // is this wasting too much memory? ??? float mRollingFrictionForce; float mTireLateralResistance; float mSlipGasModifier; ??? */ //------------------------------------------------------------------------ PhysicsLocomotion::PhysicsLocomotion(Vehicle* vehicle) : VehicleLocomotion(vehicle) { mVehicle = vehicle; // int i; for(i = 0; i < 4; i++) { mForceApplicationPoints[i].Clear(); mSuspensionPointVelocities[i].Clear(); mCachedSuspensionForceResults[i] = 0.0f; // TODO - initialize better? // everytime control is switched to VL_PHYSICS //mWheelTerrainCollisionFixDepth[i] = 0.0f; //mWheelTerrainCollisionNormals[i].x = 0.0f; //mWheelTerrainCollisionNormals[i].y = 1.0f; //mWheelTerrainCollisionNormals[i].z = 0.0f; //mWheelTerrainCollisionPoints[i].Clear(); //mGoodWheelTerrainCollisionValue[i] = false; } mCurrentSteeringForce = mVehicle->mDesignerParams.mDpTireLateralResistanceNormal; } //------------------------------------------------------------------------ PhysicsLocomotion::~PhysicsLocomotion() { // } //============================================================================= // PhysicsLocomotion::SetTerrainIntersectCachePointsForNewTransform //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::SetTerrainIntersectCachePointsForNewTransform() { rmt::Vector tempVec; int i; for(i = 0; i < 4; i++) { tempVec = mVehicle->mSuspensionWorldSpacePoints[i]; tempVec.y -= 2.0f * mVehicle->mWheels[i]->mRadius; mTerrainIntersectCache[i].closestTriPosn = tempVec; mTerrainIntersectCache[i].closestTriNormal.Set(0.0f, 1.0f, 0.0f); mTerrainIntersectCache[i].planePosn = tempVec; mTerrainIntersectCache[i].planeNorm.Set(0.0f, 1.0f, 0.0f); mTerrainIntersectCache[i].mTerrainType = TT_Road; mTerrainIntersectCache[i].mInteriorTerrain = false; mIntersectNormalUsed[i].Set(0.0f, 1.0f, 0.0f); } } //============================================================================= // PhysicsLocomotion::MoveWheelsToBottomOfSuspension //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::MoveWheelsToBottomOfSuspension() { poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); // want to call false here because we want the movement of the hierarchy based on animation to also be available for collision // TODO - make sure this is done in the traffic locomotion case as well. mVehicle->mPoseEngine->Begin(false); //Cary Here - I've moved it. //mVehicle->mPoseEngine->Begin(true); //Cary Here - I've moved it. // TODO - even need to do this? // TODO // note: // are we making the assumption that the animation doesn't move the suspension points around? // for now, yes. // TODO - only do this if wheelInCollision? int i; for(i = 0; i < 4; i++) { Wheel* wheel = mVehicle->mWheels[i]; poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); rmt::Vector trans = joint->GetObjectTranslation(); //trans.y -= mVehicle->mWheels[i]->mLimit; // TODO - verify that the -= is the thing to do here trans.y -= wheel->mLimit; trans.y += mVehicle->mDesignerParams.mDpSuspensionYOffset; joint->SetObjectTranslation(trans); // TODO - if this method actually works, make nicer interface with wheel to do this // // remember, mYOffset is the offset (upwards) from the bottom of the suspension limit, to fix wheel out of collision (or just barely in collision) wheel->mYOffset = -1.0f * wheel->mLimit; wheel->mWheelInCollision = false; wheel->mWheelBottomedOutThisFrame = false; } // just in case // // TODO - review why the hell you're doing this? - this one's pretty important I think CollisionObject* collObj = mVehicle->mSimStateArticulated->GetCollisionObject(); collObj->SetHasMoved(true); } //============================================================================= // PhysicsLocomotion::UpdateVehicleGroundPlane //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::UpdateVehicleGroundPlane() { // create a new transform for the ground plane sim state and set it. rmt::Vector p, n; p.Set(0.0f, 0.0f, 0.0f); n.Set(0.0f, 0.0f, 0.0f); /* int i; int count = 0; for(i = 0; i < 4; i++) { //if(mFoundPlane[i]) // try doing this with whatever's in there // old or new // // TODO - is this safe? { p.Add(mVehicle->mTerrainIntersectCache[i].planePosn); n.Add(mVehicle->mTerrainIntersectCache[i].planeNorm); } } p.Scale(0.25f); n.Scale(0.25f); */ // new idea for ground plane // pick the most upright normal, and the lowest height point // ?? p = mTerrainIntersectCache[0].planePosn; n = mTerrainIntersectCache[0].planeNorm; int i; for(i = 1; i < 4; i++) { if(mTerrainIntersectCache[i].planePosn.y < p.y) { p = mTerrainIntersectCache[i].planePosn; } if( GetWorldPhysicsManager()->mWorldUp.DotProduct(mTerrainIntersectCache[i].planeNorm) > GetWorldPhysicsManager()->mWorldUp.DotProduct(n) ) { n = mTerrainIntersectCache[i].planeNorm; } } // new approach with manual sim state mVehicle->mGroundPlaneWallVolume->mPosition = p; mVehicle->mGroundPlaneWallVolume->mNormal = n; sim::CollisionObject* co = mVehicle->mGroundPlaneSimState->GetCollisionObject(); co->PostManualUpdate(); /* rmt::Matrix groundPlaneTransform; groundPlaneTransform.Identity(); // TODO - optimize this call since we know what the heading is all the time //rmt::Vector heading(0.0f, 0.0f, 1.0f); rmt::Vector up(0.0f, 0.0f, 1.0f); // note: //!!! // // new approach - use our n as heading //groundPlaneTransform.FillHeading(heading, n); groundPlaneTransform.FillHeading(n, up); groundPlaneTransform.FillTranslate(p); //PushSimState(bool inReset=true) = 0; // better name for this would be Sync // ? mSimStateArticulated->SetControl(simAICtrl); mVehicle->mGroundPlaneSimState->SetTransform(groundPlaneTransform); // ? mSimStateArticulated->SetControl(simSimulationCtrl); // TODO - need to do this? // double check with martin!! //mVehicle->mGroundPlaneSimState->GetCollisionObject()->Update(); - don't seem to need this? */ } //------------------------------------------------------------------------ void PhysicsLocomotion::PreCollisionPrep(bool firstSubstep) { BEGIN_PROFILE("MoveWheelsToBottomOfSuspension") MoveWheelsToBottomOfSuspension(); END_PROFILE("MoveWheelsToBottomOfSuspension") // this is in substep, so comment out if we don't want it to happen if(firstSubstep) // only do this once per update, but it must be inside the loop the first time { if(mVehicle->mSimStateArticulated->GetControl() != sim::simAICtrl) { BEGIN_PROFILE("FetchWheelTerrainCollisionInfo") FetchWheelTerrainCollisionInfo(); END_PROFILE("FetchWheelTerrainCollisionInfo") } } else { int stophere = 1; } BEGIN_PROFILE("UpdateVehicleGroundPlane") UpdateVehicleGroundPlane(); END_PROFILE("UpdateVehicleGroundPlane") } //============================================================================= // PhysicsLocomotion::CompareNormalAndHeight //============================================================================= // Description: Comment // // Parameters: (rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) // // Return: void // //============================================================================= void PhysicsLocomotion::CompareNormalAndHeight(int index, rmt::Vector& normalPointingAtCar, rmt::Vector& groundContactPoint) { // important! // // assume this is being called from the collision solver agent _after_ we've already fetched new wheel terrain collision info // so.... // how to decide? // first try? // y value of contact points? rAssert(index >= 0 && index < 4); if(mFoundPlane[index]) { if(groundContactPoint.y > mTerrainIntersectCache[index].planePosn.y) { //mTerrainIntersectCache[index].planePosn = groundContactPoint; //mTerrainIntersectCache[index].planeNorm = normalPointingAtCar; // note this used to be in here?? mIntersectNormalUsed[index] = normalPointingAtCar; } } else { // take the more uprigth normal if(GetWorldPhysicsManager()->mWorldUp.DotProduct(normalPointingAtCar) > GetWorldPhysicsManager()->mWorldUp.DotProduct(mIntersectNormalUsed[index]) ) { mIntersectNormalUsed[index] = normalPointingAtCar; } } /* struct TerrainIntersectCache { rmt::Vector closestTriPosn; rmt::Vector closestTriNormal; rmt::Vector planePosn; rmt::Vector planeNorm; }; TerrainIntersectCache mTerrainIntersectCache[4]; // is this wasting too much memory? // need this because if we're driving on a static collision volume the terrain normal could be way off! rmt::Vector mIntersectNormalUsed[4]; */ } //============================================================================= // PhysicsLocomotion::PreSubstepUpdate //============================================================================= // Description: Comment // // Parameters: (Vehicle* vehicle) // // Return: void // //============================================================================= void PhysicsLocomotion::PreSubstepUpdate() { //MoveWheelsToBottomOfSuspension(); //FetchWheelTerrainCollisionInfo(); //UpdateVehicleGroundPlane(); } //------------------------------------------------------------------------ void PhysicsLocomotion::SetForceApplicationPoints() { int i; for(i = 0; i < 4; i++) { // TODO - clean up // for force application points should also add in wheel - nope, tried that, not so good // TODO // why do I have copies of thsi stuff here mForceApplicationPoints[i] = mVehicle->mSuspensionWorldSpacePoints[i]; // try something new: if(mVehicle->mVehicleType == VT_USER && (mVehicle->mVehicleState == VS_SLIP || mVehicle->mVehicleState == VS_EBRAKE_SLIP)) { rmt::Vector tireFix = mVehicle->mVehicleUp; // new test - decrease depending on % of top speed //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * 0.5f))); //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * 0.75f))); //tireFix.Scale(mVehicle->mWheels[i]->mRadius * -1.0f * (1.0f - (mVehicle->mPercentOfTopSpeed * mVehicle->mPercentOfTopSpeed))); tireFix.Scale(mVehicle->mWheels[i]->mRadius * mVehicle->mPercentOfTopSpeed);// * 0.5f); //mForceApplicationPoints[i].y -= mVehicle->mWheels[i]->mRadius; mForceApplicationPoints[i].Add(tireFix); } mSuspensionPointVelocities[i] = mVehicle->mSuspensionPointVelocities[i]; } } //------------------------------------------------------------------------ void PhysicsLocomotion::ApplySuspensionForces(float dt, bool atrest) { int i; for(i = 0; i < 4; i++) { //rmt::Vector force = mVehicle->mVehicleUp; //rmt::Vector force = mTerrainIntersectCache[i].planeNorm; rmt::Vector force; if(atrest) { force = mVehicle->mVehicleUp; } else { force = mIntersectNormalUsed[i]; } //?? /* // not helping if( GetWorldPhysicsManager()->mWorldUp.DotProduct(mVehicle->mVehicleUp) > GetWorldPhysicsManager()->mWorldUp.DotProduct(force) && mVehicle->mDoingJumpBoost) { force = mVehicle->mVehicleUp; } */ // debug //float cos30 = 0.866f; //float cos40 = 0.766f; //if(GetWorldPhysicsManager()->mWorldUp.DotProduct(force) < cos30) float yVelocityAlongSuspensionAxis = force.DotProduct(mSuspensionPointVelocities[i]); //mCachedSuspensionForceResults[i] = mWheels[i]->CalculateSuspensionForce(mSuspensionPointVelocities[i].y, dt); Wheel* wheel = mVehicle->mWheels[i]; float forceToUse = 0.0f; mCachedSuspensionForceResults[i] = CalculateSuspensionForce(wheel, yVelocityAlongSuspensionAxis, dt, forceToUse); force.Scale(forceToUse); ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force, &(mForceApplicationPoints[i])); } } //============================================================================= // PhysicsLocomotion::CalculateSuspensionForce //============================================================================= // Description: Comment // // note - moving thsi functionality from Wheel class to here because wheel // shouldn't do physics calculations, and this class shouldn't hold the // info about wheels - like k and c // // // Parameters: (Wheel* wheel, float suspensionPointYVelocity, float dt) // // Return: float // //============================================================================= float PhysicsLocomotion::CalculateSuspensionForce(Wheel* wheel, float suspensionPointYVelocity, float dt, float& forceToUse) { // crazy shit goin' on here! // the value we return we'll cachce for slip calculations and stuff // // the value we stuff in forceToUse is the one to make the car bank. // // the one we cache should not include the quadSpringForce float force = 0.0f; rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; float tip = mVehicle->mVehicleUp.DotProduct(up); float cos85 = 0.087f; float cos80 = 0.17365f; float cos65 = 0.4226f; if(wheel->mWheelInCollision && tip > cos65) // minor TODO - should this number match any others? { // test // // make the spring also only push up //float springForce = wheel->mk * wheel->mYOffset; float springForce = 0.0f; //if(wheel->mYOffset > 0.0f) if(!(mVehicle->mDoingJumpBoost && wheel->mYOffset < 0.0f)) { springForce = wheel->mk * wheel->mYOffset; } float quadSpringForce = 0.0f; if(wheel->mYOffset > 0.0f) { quadSpringForce = wheel->mqk * wheel->mYOffset * wheel->mYOffset; //springForce += quadSpringForce; } // // or... topping out! // if((wheel->mLimit - rmt::Fabs(wheel->mYOffset)) < wheel->mLimit * 0.25f) { //springForce *= 3.0f; } // for now, only add damping if chassis is trying to compress suspension - ie. y velocity is down, -ve float damperForce = 0.0f; if(suspensionPointYVelocity > 0.0f)// this would make the dampe pull down { if((mVehicle->mDamperShouldNotPullDown[wheel->mWheelNum]) || mVehicle->mDoingJumpBoost) { // no suspension force down } else { // regular damperForce = wheel->mc * -1.0f * suspensionPointYVelocity; } } else { // no worries damperForce = wheel->mc * -1.0f * suspensionPointYVelocity; } force = springForce + damperForce; forceToUse = springForce + damperForce + quadSpringForce; } else { // need to relax spring somehow // this is essentially useless.... float relaxDistance = wheel->mLimit / wheel->mSpringRelaxRate * dt; if(wheel->mYOffset >= relaxDistance) { wheel->mYOffset -= relaxDistance; } else if(wheel->mYOffset <= -relaxDistance) { wheel->mYOffset += relaxDistance; } } return force; } //============================================================================= // PhysicsLocomotion::ApplyDragForce //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::ApplyDragForce() { if(mVehicle->mSpeed > 1.0f) { rmt::Vector force = mVehicle->mVelocityCM; // new test - //force.y = 0.0f; force.NormalizeSafe(); float mag = mVehicle->mSpeed * mVehicle->mSpeed * 0.5f * mVehicle->mDragCoeff; // new // car slows down too much when you let off gas if(mVehicle->mGas == 0.0f && mVehicle->mSpeedKmh > 50.0f && mVehicle->mBrake == 0.0f) { mag *= 0.25f; } force.Scale(-1.0f * mag); ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force); } } //============================================================================= // PhysicsLocomotion::LowSpeedTest //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::LowSpeedTest() { //static float test = 0.8f; static float test = 0.8f; static float linearWhittle = 0.6f; static float angularWhittle = 0.1f; //const float lowspeed = 1.0f; //static float lowspeed = 2.0f; float lowspeed = 2.0f; float reallylowspeed = 0.05f; // only apply if at least 2 wheels in contact with the ground: int i; int count = 0; for(i = 0; i < 4; i++) { if(mVehicle->mWheels[i]->mWheelInCollision) { count++; } } if(count > 1) { if(mVehicle->mSpeed < lowspeed && mVehicle->mGas < 0.1f /*&& mVehicle->mReverse < 0.1f*/ && mVehicle->mBrake < 0.1f) // new... brake { // whittle away speed, or just reset? //mVehicle->mSimStateArticulated->ResetVelocities(); // I knew this was a bad idea if(mVehicle->mSpeed < reallylowspeed) { mVehicle->mSimStateArticulated->ResetVelocities(); } else { rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); linearVel.Scale(linearWhittle); angularVel.Scale(angularWhittle); } } } } //============================================================================= // PhysicsLocomotion::ApplyAngularDrag //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::ApplyAngularDrag() { if(mVehicle->mPercentOfTopSpeed > 0.01f) { const float magicshit = 3.0f; rmt::Vector angularDragForce = mVehicle->mSimStateArticulated->GetAngularVelocity(); angularDragForce.Scale(-1.0f * magicshit * mVehicle->mDesignerParams.mDpMass); sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); rAssert(phobj); phobj->AddTorque(angularDragForce); } } //============================================================================= // PhysicsLocomotion::ApplyRollingFriction //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::ApplyRollingFriction() { // to start with simplest possible model - constant? //static float test = 1.0f; //static float test2 = 2000.0f; //static float test = 0.9f; //static float test = 0.8f; static float test = 0.8f; //const float lowspeed = 1.0f; //static float lowspeed = 2.0f; static float lowspeed = 2.0f; static float reallylowspeed = 0.05f; static float torquemod = 0.001f; // only apply if at least 2 wheels in contact with the ground: int i; int count = 0; for(i = 0; i < 4; i++) { if(mVehicle->mWheels[i]->mWheelInCollision) { count++; } } if(count > 1) { if(mVehicle->mSpeed > reallylowspeed && mVehicle->mGas == 0.0f) { rmt::Vector force = mVehicle->mVelocityCM; force.Normalize(); force.Scale(-1.0f * mVehicle->mRollingFrictionForce * mVehicle->mDesignerParams.mDpMass); ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->AddForce(force); } else if(0)//mVehicle->mGas < 0.1f && mVehicle->mReverse < 0.1f && mVehicle->mBrake < 0.1f) // new... brake { // whittle away speed, or just reset? //mVehicle->mSimStateArticulated->ResetVelocities(); // I knew this was a bad idea //if(1)//mVehicle->mSpeed < reallylowspeed) /* if(mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->IsAtRest()) { mVehicle->mSimStateArticulated->ResetVelocities(); mVehicle->mSimStateArticulated->SetControl(sim::simAICtrl); //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->ResetAppliedForces(); } else { //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->ResetAppliedForces(); //rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); //rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); //linearVel.Scale(test); //angularVel.Scale(test); } */ } } } //============================================================================= // PhysicsLocomotion::HighSpeedInstabilityTest //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::HighSpeedInstabilityTest() { const float threshold = 0.5f; // from threshold to 100%, increase y cm offset if(mVehicle->mPercentOfTopSpeed > threshold) { // should do ... if(mVehicle->mInstabilityOffsetOn) { // already set // // nothing to do here } else { // set it rmt::Vector unstableAdjust = mVehicle->mCMOffset; //static float weeblemagic = 1.0f; /*static*/ float magic = 1.0f; // scale by point in range // set range of 0 to 1 for start to limit /* float range = 1.0f - ((tip - end) / (start - end)); if(range < 0.0f) { range = 0.0f; //rAssert(0); } if(range > 1.0f) { range = 1.0f; } float modifier = weeblemagic * range; */ //weeble.y -= modifier; unstableAdjust.y += magic; //weeble.y -= 3.0f; ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(unstableAdjust); mVehicle->mInstabilityOffsetOn = true; } } else { // if it's on, shut it off if(mVehicle->mInstabilityOffsetOn) { ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); mVehicle->mInstabilityOffsetOn = false; } } } //============================================================================= // PhysicsLocomotion::Weeble //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::Weeble() { bool doit = false; // only do this for sideways tipping rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; float tip = mVehicle->mVehicleUp.DotProduct(up); float cos35 = 0.8192f; float cos45 = 0.7071f; float cos55 = 0.5736f; float cos40 = 0.766f; float start = cos40; float end = cos55; // new test - // if we are completely airborn - set cmoffset to 0,0,0 //if(mVehicle->mAirBorn) //{ // if(!(mVehicle->mCMOffsetSetToOriginal)) // { // //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); // mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mOriginalCMOffset); // mVehicle->mCMOffsetSetToOriginal = true; // } //} //else { /* if(mVehicle->mCMOffsetSetToOriginal) { //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mOriginalCMOffset); mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); mVehicle->mCMOffsetSetToOriginal = false; } */ //if(!mVehicle->mAirBorn && tip < start) if(1)//tip < start) { bool slowdowntipping = false; if( !(mVehicle->mWheels[0]->mWheelInCollision) && !(mVehicle->mWheels[3]->mWheelInCollision) ) { doit = true; if(mVehicle->mWheels[1]->mWheelInCollision && mVehicle->mWheels[2]->mWheelInCollision) { slowdowntipping = true; } } if( !(mVehicle->mWheels[1]->mWheelInCollision) && !(mVehicle->mWheels[2]->mWheelInCollision) ) { doit = true; if(mVehicle->mWheels[0]->mWheelInCollision && mVehicle->mWheels[3]->mWheelInCollision) { slowdowntipping = true; } } if(0)//(slowdowntipping) { // only resist tipping UP! //static float magicshit = 3.0f; const float magicshit = 2.5f; float facingAngVel = (mVehicle->mSimStateArticulated->GetAngularVelocity()).DotProduct(mVehicle->mVehicleFacing); // a positive value means tipping left. // // so....... // we want to apply the torque if the transverse.dot.worldup is positive // or... if tipping right and transverse.dot.worldup is negative if( (facingAngVel > 0.0f && mVehicle->mVehicleTransverse.DotProduct(up) > 0.0f) || (facingAngVel < 0.0f && mVehicle->mVehicleTransverse.DotProduct(up) < 0.0f) ) { rmt::Vector torque = mVehicle->mVehicleFacing; torque.Scale(-1.0f * magicshit * mVehicle->mDesignerParams.mDpMass * facingAngVel); sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); rAssert(phobj); phobj->AddTorque(torque); } } } if(mVehicle->mAirBorn) { doit = true; } if(doit) { if(!(mVehicle->mWeebleOn)) { // weeble rmt::Vector weeble = mVehicle->mCMOffset; //static float weeblemagic = 1.0f; float weeblemagic = 1.0f; // scale by point in range // set range of 0 to 1 for start to limit /* float range = 1.0f - ((tip - end) / (start - end)); if(range < 0.0f) { range = 0.0f; //rAssert(0); } if(range > 1.0f) { range = 1.0f; } float modifier = weeblemagic * range; */ //weeble.y -= modifier; //weeble.y -= weeblemagic; weeble.y += mVehicle->mDesignerParams.mDpWeebleOffset; //weeble.y -= 3.0f; rmt::Vector current = ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->GetExternalCMOffset(); if(current.Equals(weeble)) { int stophere = 1; } else { ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(weeble); } mVehicle->mWeebleOn = true; } } else { if(mVehicle->mWeebleOn) { ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->SetExternalCMOffset(mVehicle->mCMOffset); } mVehicle->mWeebleOn = false; } } } //============================================================================= // PhysicsLocomotion::PreUpdate //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::PreUpdate() { //mVehicle->CalculateSuspensionLocationAndVelocity(); } //============================================================================= // PhysicsLocomotion::Update //============================================================================= // Description: Comment // // Parameters: (float dt) // // Return: void // //============================================================================= void PhysicsLocomotion::Update(float dt) { // recall // this is the set of function calls to physicsvehicle from vehicle in the old // system UseWheelTerrainCollisionInfo(dt); CorrectWheelYPositions(); // old name: PreUpdate(dt); mVehicle->CalculateSuspensionLocationAndVelocity(); //mVehicle->mSimStateArticulated->SetControl(sim::simSimulationCtrl); -- move this into SelfRestTest //bool rest = mVehicle->SelfRestTest(); bool rest = false; rest = mVehicle->SelfRestTest(); if(mVehicle->mSimStateArticulated->GetControl() != sim::simAICtrl) { // TODO - this call, and UpdateJointState, should be in the same place // mVehicle->mSimStateArticulated->StoreJointState(dt); // the update guts from physicsvehicle // // kind of a fucking mess //mVehicle->CalculateSuspensionLocationAndVelocity(); SetForceApplicationPoints(); ApplySuspensionForces(dt, rest); if(!(mVehicle->mAirBorn) && !rest) { ApplyControllerForces2(dt); } else { // at least do this mVehicle->mBurnoutLevel = 0.0f; } if(GetCheatInputSystem()->IsCheatEnabled(CHEAT_ID_NO_TOP_SPEED) && mVehicle->mVehicleType == VT_USER) { // no drag for you! } else { if(!(mVehicle->mSteeringWheelsOutOfContact) && !(mVehicle->mDoSpeedBurst) && !(mVehicle->mDoingJumpBoost)) { ApplyDragForce(); } } if(!(mVehicle->mDoingJumpBoost)) { ApplyRollingFriction(); // move low speed portion of this into a separate function to follow update... } //TipTest(); if(mVehicle->mAirBorn && mVehicle->mVehicleType == VT_USER ) { ApplyAngularDrag(); } Weeble(); if(mVehicle->mVehicleType == VT_USER) { //HighSpeedInstabilityTest(); } if(mVehicle->mVehicleType == VT_USER) { StuckOnSideTest(dt); } if(mVehicle->mAirBorn)// && mVehicle->mVehicleType == VT_USER) { DurangoStyleStabilizer(dt); } // // **** // // THE Update // // **** ((ArticulatedPhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject(-1)))->Update(dt); //LowSpeedTest(); //rest = mVehicle->SelfRestTest(); } //Update(dt); //PostUpdate(dt); // maybe for first pass implementation, implement methods with the same name? // ??????? // // do the pose driver updates here!!!! or back in vehicle <- for some reason // the second way seems much nicer to me. // the net effect on Vehicle result when this function returns is to have // modified the joint-space y values in the wheel joints to correct // the wheel positions, and to have applied forces and torques to the // simstate and had it update itself so there is a new world space transform // AND make sure the world space transform of joint 0 is in sync with this // also - make sure wheels have enough info for suspensionjointdriver to // do it's thing // // ie - cumulative rot // wheel turn angle // that's about it? // are we gonna have to make physicslocomotion a friend of the Vehicle? // is that the correct thing to do - make all the locomotions friends?? } //============================================================================= // PhysicsLocomotion::DurangoStyleStabilizer //============================================================================= // Description: Comment // // Parameters: (float dt) // // Return: void // //============================================================================= void PhysicsLocomotion::DurangoStyleStabilizer(float dt) { // assume airborn rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; float tip = mVehicle->mVehicleUp.DotProduct(up); // we want a fix torque proportional to 1.0 - tip. // 'direction' of the toruqe is vehicleup crossed into world up float cos10 = 0.9848f; if(tip < cos10) { rmt::Vector fixTorque; fixTorque.CrossProduct(mVehicle->mVehicleUp, up); // need this? fixTorque.NormalizeSafe(); const float hackmagicshit = 200.0f; float fixscale = 1.0f - tip; fixTorque.Scale(hackmagicshit * mVehicle->mDesignerParams.mDpMass * fixscale); sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); rAssert(phobj); phobj->AddTorque(fixTorque); // try some damping too const float dampingShit = 7.0f; rmt::Vector angularDragForce = mVehicle->mSimStateArticulated->GetAngularVelocity(); angularDragForce.Scale(-1.0f * dampingShit * mVehicle->mDesignerParams.mDpMass); phobj->AddTorque(angularDragForce); } } //============================================================================= // PhysicsLocomotion::StuckOnSideTest //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::StuckOnSideTest(float dt) { // only for user vehicle in physics locomotion const float lowspeed = 1.0f; if(mVehicle->mSpeed < lowspeed) { rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; float tip = mVehicle->mVehicleUp.DotProduct(up); //float cos85 = 0.0872f; float test = 0.2f; if(tip < test) { //static float magicshit = 1.0f; const float magicshit = 130.0f; float side = up.DotProduct(mVehicle->mVehicleTransverse); if(side > 0.0f) { // lying on it's left side - right side pointint to sky //if(mVehicle->mUnmodifiedInputWheelTurnAngle > 0.0f) if(mVehicle->mUnmodifiedInputWheelTurnAngle < 0.0f) { // apply some torque rmt::Vector torque = mVehicle->mVehicleFacing; //torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * -1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * 1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); rAssert(phobj); phobj->AddTorque(torque); } } else { // lying on it's right side - left side pointint to sky //if(mVehicle->mUnmodifiedInputWheelTurnAngle < 0.0f) if(mVehicle->mUnmodifiedInputWheelTurnAngle > 0.0f) { // apply some torque rmt::Vector torque = mVehicle->mVehicleFacing; //torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * -1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); torque.Scale(mVehicle->mUnmodifiedInputWheelTurnAngle * 1.0f * mVehicle->mDesignerParams.mDpMass * magicshit); sim::PhysicsObject* phobj = (sim::PhysicsObject*)(mVehicle->mSimStateArticulated->GetSimulatedObject()); rAssert(phobj); phobj->AddTorque(torque); } } } } } //============================================================================= // PhysicsLocomotion::TipTest //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::TipTest() { rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; float tip = mVehicle->mVehicleUp.DotProduct(up); float cos45 = 0.7071f; float cos80 = 0.17365f; float cos65 = 0.4226f; // need to figure out if the angular velocity is trying to tip us over more, or right us float start = cos45; float end = cos65; //static float tipRecoverPercentage = 75.0f; float tipRecoverPercentage = 75.0f; if(tip < start && tip > end) { // set range of 0 to 1 for start to limit float range = 1.0f - ((tip - end) / (start - end)); float lean = mVehicle->mVehicleTransverse.DotProduct(up); if(lean > cos45) { // tipping left rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); float proj = angularVel.DotProduct(mVehicle->mVehicleFacing); //if(proj > 0.0f) if(proj > 1.0f) { // the velocity is trying to tip us more //rmt::Vector recover = mVehicle->mVehicleFacing; //recover.Scale(-1.0f * proj * tipRecoverPercentage); //angularVel.Add(recover); //angularVel.Scale(tipRecoverPercentage); //angularVel.Scale(1.0f - range); angularVel.Scale(0.0f); //rmt::Vector recover = mVehicle->mVehicleFacing; //recover.Scale(-1.0f * proj * tipRecoverPercentage * mVehicle->mDesignerParams.mDpMass * range); //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(recover); } } else if(lean < -cos45) { //rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); rmt::Vector& angularVel = mVehicle->mSimStateArticulated->GetAngularVelocity(); float proj = angularVel.DotProduct(mVehicle->mVehicleFacing); // tipping over right //if(proj < 0.0f) if(proj < -1.0f) { //rmt::Vector recover = mVehicle->mVehicleFacing; //recover.Scale(-1.0f * proj * tipRecoverPercentage); //angularVel.Add(recover); //angularVel.Scale(tipRecoverPercentage); //angularVel.Scale(1.0f - range); angularVel.Scale(0.0f); //rmt::Vector recover = mVehicle->mVehicleFacing; //recover.Scale(-1.0f * proj * tipRecoverPercentage * mVehicle->mDesignerParams.mDpMass * range); //mVehicle->((ArticulatedPhysicsObject*)(mSimStateArticulated->GetSimulatedObject(-1)))->AddTorque(recover); } } // else don't do shit // what is our tendency to hold and recover from tipping? } } //============================================================================= // PhysicsLocomotion::PostUpdate //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::PostUpdate() { // this whole method is kind of annyoing // // TODO //??? shoudl be in Vehicle mVehicle->mTransform = mVehicle->mSimStateArticulated->GetTransform(-1); mVehicle->mVelocityCM = mVehicle->mSimStateArticulated->GetLinearVelocity(); mVehicle->mSpeed = mVehicle->mVelocityCM.Magnitude(); mVehicle->mPercentOfTopSpeed = mVehicle->mSpeed / (mVehicle->mDesignerParams.mDpTopSpeedKmh / 3.6f); if(mVehicle->mPercentOfTopSpeed > 1.0f) { mVehicle->mPercentOfTopSpeed = 1.0f; } // Roll up the terrain type and interior flag; // As soon as we get two tires or more of a terrain type, that's the type we'll consider the car on. // This isn't very exact since the rear two tires will have precidence over the other tires. int terrainCounts = 0; int interiorCount = 0; eTerrainType newType = TT_Road; for( int i = 0; i < 4; i++ ) { int index = i ^ 2; // We do this to change i from 0, 1, 2, 3 into 2, 3, 0, 1 so that the rear wheels are processed last. interiorCount += mTerrainIntersectCache[ index ].mInteriorTerrain ? 1 : 0; //? Do we want to mask out roads? int mask = ( 1 << (int)mTerrainIntersectCache[ index ].mTerrainType ) & ~(int)TT_Road; // Mask out roads so everything else takes precidence over it. int mask = 1 << (int)mTerrainIntersectCache[ index ].mTerrainType; if( ( terrainCounts & mask ) ) { // We've already got one, so this is number two. newType = mTerrainIntersectCache[ index ].mTerrainType; } else { terrainCounts |= mask; } } mVehicle->mTerrainType = newType; mVehicle->mInterior = ( interiorCount >= 2 ); } //------------------------------------------------------------------------ void PhysicsLocomotion::CorrectWheelYPositions() { poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); int i; for(i = 0; i < 4; i++) { // we don't want yOffset, we want the correction value!! // now just use mYOffset //float yOffset = mWheels[i]->mYOffset; fuck no!!! Wheel* wheel = mVehicle->mWheels[i]; float yCorrectionValue = wheel->GetYCorrectionValue(); poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); rmt::Vector trans = joint->GetObjectTranslation(); // these trans.y should still be at the bottom of their suspension range // new test so wheels don't hang so low if(wheel->mWheelInCollision) { trans.y += yCorrectionValue; } else if(mVehicle->mAirBorn) // add this here to remove the snap/bounce when turning hard and two wheels come out of contact { trans.y += wheel->mLimit; } // test //trans.y += mVehicle->mDesignerParams.mDpSuspensionYOffset; joint->SetObjectTranslation(trans); wheel->ResolveOffset(); } } //------------------------------------------------------------------------ // temp... //------------------------------------------------------------------------ inline void TempGetTerrainIntersects( rmt::Vector& trans, float radius, bool& foundtri, rmt::Vector& closestTriNormal, rmt::Vector& closestTriPosn, bool& foundplane, rmt::Vector& planeNormal, rmt::Vector& planePosn) { const float y = 3.0f; foundtri = false; foundplane = true; planeNormal.Set(0.0f, 1.0f, 0.0f); planePosn.x = trans.x; planePosn.y = y; planePosn.z = trans.z; } //============================================================================= // PhysicsLocomotion::FetchWheelTerrainCollisionInfo //============================================================================= // Description: Comment // // Parameters: () // // Return: void // //============================================================================= void PhysicsLocomotion::FetchWheelTerrainCollisionInfo() { poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); int i; for(i = 0; i < 4; i++) { BEGIN_PROFILE("PreInter") Wheel* wheel = mVehicle->mWheels[i]; // for each wheel, based on the world xz, get a y and a normal, ..... and a ground type //? could also do based on suspension points, no? // I guess this is more accurate poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); rmt::Vector trans = joint->GetWorldTranslation(); rmt::Vector closestTriNormal, closestTriPosn; rmt::Vector planeNormal, planePosn; int terrainType; //bool bFoundTri, bFoundPlane; mFoundTri[i] = false; mFoundPlane[i] = false; END_PROFILE("PreInter") BEGIN_PROFILE("GetIntersectManager()->FindIntersection") terrainType = GetIntersectManager()->FindIntersection( trans, mFoundPlane[i], planeNormal, planePosn ); /* TempGetTerrainIntersects( trans, wheel->mRadius, mFoundTri[i], closestTriNormal, closestTriPosn, mFoundPlane[i], planeNormal, planePosn); */ END_PROFILE("GetIntersectManager()->FindIntersection") BEGIN_PROFILE("PostInter") // fill in pieces of cache if(mFoundPlane[i]) { rmt::Vector up = GetWorldPhysicsManager()->mWorldUp; if(up.DotProduct(planeNormal) < 0.0f) { //rAssert(0); } mTerrainIntersectCache[i].planePosn = planePosn; mTerrainIntersectCache[i].planeNorm = planeNormal; mIntersectNormalUsed[i] = mTerrainIntersectCache[i].planeNorm; } else { // no plane //rAssert(0); } //Devin says this is not used. Cleaning up the warnings. // if(mFoundTri[i]) // { // mTerrainIntersectCache[i].closestTriPosn = closestTriPosn; // mTerrainIntersectCache[i].closestTriNormal = closestTriNormal; // } mTerrainIntersectCache[ i ].mTerrainType = (eTerrainType)( terrainType & ~0x80 ); mTerrainIntersectCache[ i ].mInteriorTerrain = ( terrainType & 0x80 ) == 0x80; // stuff in value for later comparison //mIntersectNormalUsed[i] = mTerrainIntersectCache[i].planeNorm; END_PROFILE("PostInter") } } //============================================================================= // PhysicsLocomotion::UseWheelTerrainCollisionInfo //============================================================================= // Description: Comment // // per-wheel, calculate and call Wheel::SetYOffsetFromCurrentPosition // // Parameters: (float dt) // // Return: void // //============================================================================= void PhysicsLocomotion::UseWheelTerrainCollisionInfo(float dt) { poser::Pose* pose = mVehicle->mPoseEngine->GetPose(); // take the highest value for this from all 4 wheels float bottomOutFix = 0.0f; int i; for(i = 0; i < 4; i++) { // make copies 'cause we may want to modify local result, but not cached value bool foundPlane = mFoundPlane[i]; bool foundTri = mFoundTri[i]; float fixHeight = 0.0f; bool thisWheelInCollision = false; rmt::Vector fixHeightNormal(0.0f, 1.0f, 0.0f); float fixAlongSuspensionAxis = 0.0f; Wheel* wheel = mVehicle->mWheels[i]; poser::Joint* joint = pose->GetJoint(mVehicle->mWheelToJointIndexMapping[i]); rmt::Vector trans = joint->GetWorldTranslation(); if(!mFoundPlane[i] && !mFoundTri[i]) { // we didn't get any good data back, so we need // to use last cached value //rAssert(0); // curious to see if we hit this - yes, a lot // just use plane y foundPlane = true; } if(foundPlane) { float y = mTerrainIntersectCache[i].planePosn.y; // first check // the pathologically bad case: if(mVehicle->mSuspensionWorldSpacePoints[i].y < y) { // this means the suspension rest point on the car is below the terrain // this is fucking bad. // TODO: take this out? //rAssert(0); // ?? actually need to do anything here? // TODO - bump car up? } //else { // hopefully sane case // in planePosn, we only really care about the y float penetratingDepth = y - (trans.y - wheel->mRadius); if(penetratingDepth > 0.0f) { thisWheelInCollision = true; fixHeight = penetratingDepth; // fixHeight is going pure up fixHeightNormal.Set(0.0f, 1.0f, 0.0f); fixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); } } } // TODO! revisit this! if(0)//foundTri) { // calculate fixAlongSuspensionAxis based on this also, and if it's greater than above, use it instead rmt::Vector penetrationVector; //penetrationVector.Sub(trans, mWheelTerrainCollisionPoints[i]); penetrationVector = trans; penetrationVector.Sub(mTerrainIntersectCache[i].closestTriPosn); // TODO - is this safe? float penMag = penetrationVector.Magnitude(); bool checkBump = false; if(trans.y < mTerrainIntersectCache[i].closestTriPosn.y) { // definately need to fix! fixHeight = wheel->mRadius + penMag; thisWheelInCollision = true; fixHeightNormal = mTerrainIntersectCache[i].closestTriNormal; float tempFixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); if(tempFixAlongSuspensionAxis > fixAlongSuspensionAxis) { fixAlongSuspensionAxis = tempFixAlongSuspensionAxis; } checkBump = true; } else if(penMag < wheel->mRadius) // normal case { fixHeight = wheel->mRadius - penMag; thisWheelInCollision = true; fixHeightNormal = mTerrainIntersectCache[i].closestTriNormal; float tempFixAlongSuspensionAxis = fixHeight / rmt::Fabs( (mVehicle->mVehicleUp).DotProduct(fixHeightNormal)); if(tempFixAlongSuspensionAxis > fixAlongSuspensionAxis) { fixAlongSuspensionAxis = tempFixAlongSuspensionAxis; } checkBump = true; } if(checkBump) { // if this is too horizontal, we need to deal with it specially float sin10 = 0.1736f; float sin20 = 0.342f; float sin30 = 0.5f; // TODO - what angle? if(rmt::Fabs(fixHeightNormal.DotProduct(mVehicle->mVehicleUp)) < sin20)// && fixHeight > wheel->mRadius) { // just bump the whole car back by penMag? rmt::Vector bump = fixHeightNormal; bump.NormalizeSafe(); bump.Scale(fixHeight); // TODO - true or false or tapered down or what? //mVehicle->mSimStateArticulated->DynamicPositionAdjustment(bump, dt, true); //mVehicle->mSimStateArticulated->DynamicPositionAdjustment(bump, dt, false); mVehicle->mBottomedOutThisFrame = true; continue; } } } if(thisWheelInCollision) { // perhaps this method should be renamed // it sets the wheelInCollision flag to true! float bottomedOut = wheel->SetYOffsetFromCurrentPosition(fixAlongSuspensionAxis); if(bottomedOut > bottomOutFix) { bottomOutFix = bottomedOut; wheel->mWheelBottomedOutThisFrame = true; // not sure if we'll use yet. //char buffy[128]; //sprintf(buffy, "wheel %d bottomed out this frame\n", i); //rDebugPrintf(buffy); } } } // end for(i if(bottomOutFix > 0.0f) { // this is the amount the suspension couldn't deal with. // // what to do? // in srr1 we just bumped the whole chassis up by this amount - seemed to work fairly well actually. //rmt::Vector inDeltaPos = mVehicle->mVehicleUp; // Note! // we hav to be careful how to deal with this 'cause we can get some weird ass results that make // the car go flying after seemingly minor crashes // hack, but might work well - only correct the vehicle straight up! //rmt::Vector inDeltaPos = fixHeightNormal; rmt::Vector inDeltaPos; inDeltaPos = GetWorldPhysicsManager()->mWorldUp; // sanity test? if(bottomOutFix > (mVehicle->mWheels[0]->mRadius * 0.5f)) { bottomOutFix = (mVehicle->mWheels[0]->mRadius * 0.5f); } if(inDeltaPos.y > 0.0f) // this test is pointless right now { inDeltaPos.Scale(bottomOutFix); // TODO - revisit this ///you are here //take this out - see what f1 car does // no this was not the problem. // get kevin to raise the bv mVehicle->mSimStateArticulated->DynamicPositionAdjustment(inDeltaPos, dt, false); // ? // how 'bout just applying some impulse to the sucker? /* if(0)//mVehicle->mOkToCrashLand) { rmt::Matrix groundPlaneMatrix = mVehicle->mGroundPlaneSimState->GetTransform(-1); rmt::Vector groundPlaneUp = groundPlaneMatrix.Row(2); // recall - ground plane is oriented weird rmt::Vector& linearVel = mVehicle->mSimStateArticulated->GetLinearVelocity(); static float test = 10.0f; groundPlaneUp.Scale(test); linearVel.Add(groundPlaneUp); mVehicle->mOkToCrashLand = false; } */ } mVehicle->mBottomedOutThisFrame = true; } }