void PhysicsVehicle::update(float elapsedTime, float steering, float braking, float driving) { float v = getSpeedKph(); //MathUtil::smooth(&_speedSmoothed, v, elapsedTime, 0, 1200); kmSmooth(&_speedSmoothed, v, elapsedTime, 0, 1200); if (elapsedTime > 0) { // Avoid accumulation of downforce while paused (zero elapsedTime) applyDownforce(); } // Adjust control inputs based on vehicle speed. steering = getSteering(v, steering); driving = getDriving(v, driving, braking); braking = getBraking(v, braking); // Allow braking to take precedence over driving. if (driving > 0 && braking > 0) { driving = 0; } PhysicsVehicleWheel* wheel; for (int i = 0; i < _vehicle->getNumWheels(); i++) { wheel = getWheel(i); if (wheel->isSteerable()) { _vehicle->setSteeringValue(steering * _steeringGain, i); } else { _vehicle->applyEngineForce(driving * _drivingForce, i); _vehicle->setBrake(braking * _brakingForce, i); } wheel->update(elapsedTime); wheel->transform(wheel->getNode()); } }
void VehicleObject::tickPhysics(float dt) { RW_UNUSED(dt); if( physVehicle ) { // todo: a real engine function float velFac = info->handling.maxVelocity; float engineForce = info->handling.acceleration * throttle * velFac; if (fabs(engineForce) >= 0.001f) { collision->getBulletBody()->activate(true); } float brakeF = getBraking(); if( handbrake ) { brakeF = 5.f; } for(int w = 0; w < physVehicle->getNumWheels(); ++w) { btWheelInfo& wi = physVehicle->getWheelInfo(w); if( info->handling.driveType == VehicleHandlingInfo::All || (info->handling.driveType == VehicleHandlingInfo::Forward && wi.m_bIsFrontWheel) || (info->handling.driveType == VehicleHandlingInfo::Rear && !wi.m_bIsFrontWheel)) { physVehicle->applyEngineForce(engineForce, w); } float brakeReal = 5.f * info->handling.brakeDeceleration * (wi.m_bIsFrontWheel? info->handling.brakeBias : 1.f - info->handling.brakeBias); physVehicle->setBrake(brakeReal * brakeF, w); if(wi.m_bIsFrontWheel) { float sign = std::signbit(steerAngle) ? -1.f : 1.f; physVehicle->setSteeringValue(std::min(info->handling.steeringLock*(3.141f/180.f), std::abs(steerAngle)) * sign, w); //physVehicle->setSteeringValue(std::min(3.141f/2.f, std::abs(steerAngle)) * sign, w); } } // Update passenger positions for (auto& seat : seatOccupants) { auto character = static_cast<CharacterObject*>(seat.second); glm::vec3 passPosition; if (character->isEnteringOrExitingVehicle()) { passPosition = getSeatEntryPositionWorld(seat.first); } else { passPosition = getPosition(); if (seat.first < info->seats.size()) { passPosition += getRotation() * (info->seats[seat.first].offset); } } seat.second->updateTransform(passPosition, getRotation()); } if( vehicle->type == VehicleData::BOAT ) { if( isInWater() ) { float sign = std::signbit(steerAngle) ? -1.f : 1.f; float steer = std::min(info->handling.steeringLock*(3.141f/180.f), std::abs(steerAngle)) * sign; auto orient = collision->getBulletBody()->getOrientation(); // Find the local-space velocity auto velocity = collision->getBulletBody()->getLinearVelocity(); velocity = velocity.rotate(-orient.getAxis(), orient.getAngle()); // Rudder force is proportional to velocity. float rAngle = steer * (velFac * 0.5f + 0.5f); btVector3 rForce = btVector3(1000.f * velocity.y() * rAngle, 0.f, 0.f) .rotate(orient.getAxis(), orient.getAngle()); btVector3 rudderPoint = btVector3(0.f, -info->handling.dimensions.y/2.f, 0.f) .rotate(orient.getAxis(), orient.getAngle()); collision->getBulletBody()->applyForce( rForce, rudderPoint); btVector3 rudderVector = btVector3(0.f, 1.f, 0.f) .rotate(orient.getAxis(), orient.getAngle()); collision->getBulletBody()->applyForce( rudderVector * engineForce * 100.f, rudderPoint); btVector3 dampforce( 10000.f * velocity.x(), velocity.y() * 100.f, 0.f ); collision->getBulletBody()->applyCentralForce(-dampforce.rotate(orient.getAxis(), orient.getAngle())); } } const auto& ws = getPosition(); auto wX = (int) ((ws.x + WATER_WORLD_SIZE/2.f) / (WATER_WORLD_SIZE/WATER_HQ_DATA_SIZE)); auto wY = (int) ((ws.y + WATER_WORLD_SIZE/2.f) / (WATER_WORLD_SIZE/WATER_HQ_DATA_SIZE)); btVector3 bbmin, bbmax; // This is in world space. collision->getBulletBody()->getAabb(bbmin, bbmax); float vH = bbmin.z(); float wH = 0.f; if( wX >= 0 && wX < WATER_HQ_DATA_SIZE && wY >= 0 && wY < WATER_HQ_DATA_SIZE ) { int i = (wX*WATER_HQ_DATA_SIZE) + wY; int hI = engine->data->realWater[i]; if( hI < NO_WATER_INDEX ) { wH = engine->data->waterHeights[hI]; wH += engine->data->getWaveHeightAt(ws); // If the vehicle is currently underwater if( vH <= wH ) { // and was not underwater here in the last tick if( _lastHeight >= wH ) { // we are for real, underwater inWater = true; } else if( inWater == false ) { // It's just a tunnel or something, we good. inWater = false; } } else { // The water is beneath us inWater = false; } } else { inWater = false; } } if( inWater ) { // Ensure that vehicles don't fall asleep at the top of a wave. if(! collision->getBulletBody()->isActive() ) { collision->getBulletBody()->activate(true); } float bbZ = info->handling.dimensions.z/2.f; float oZ = 0.f; oZ = -bbZ/2.f + (bbZ * (info->handling.percentSubmerged/120.f)); if( vehicle->type != VehicleData::BOAT ) { // Damper motion collision->getBulletBody()->setDamping(0.95f, 0.9f); } if( vehicle->type == VehicleData::BOAT ) { oZ = 0.f; } // Boats, Buoyancy offset is affected by the orientation of the chassis. // Vehicles, it isn't. glm::vec3 vFwd = glm::vec3(0.f, info->handling.dimensions.y/2.f, oZ), vBack = glm::vec3(0.f, -info->handling.dimensions.y/2.f, oZ); glm::vec3 vRt = glm::vec3( info->handling.dimensions.x/2.f, 0.f, oZ), vLeft = glm::vec3(-info->handling.dimensions.x/2.f, 0.f, oZ); vFwd = getRotation() * vFwd; vBack = getRotation() * vBack; vRt = getRotation() * vRt; vLeft = getRotation() * vLeft; // This function will try to keep v* at the water level. applyWaterFloat( vFwd); applyWaterFloat( vBack); applyWaterFloat( vRt); applyWaterFloat( vLeft); } else { if( vehicle->type == VehicleData::BOAT ) { collision->getBulletBody()->setDamping(0.1f, 0.8f); } else { collision->getBulletBody()->setDamping(0.05f, 0.0f); } } _lastHeight = vH; // Update hinge object rotations for(auto& it : dynamicParts) { if(it.second.body == nullptr) continue; if( it.second.moveToAngle ) { auto angledelta = it.second.targetAngle - it.second.constraint->getHingeAngle(); if( glm::abs(angledelta) <= 0.01f ) { it.second.constraint->enableAngularMotor(false, 1.f, 1.f); dynamicParts[it.first].moveToAngle = false; } else { it.second.constraint->enableAngularMotor(true, glm::sign(angledelta) * 5.f, 1.f); } } // If the part is moving quite fast and near the limit, lock it. /// @TODO not all parts rotate in the z axis. float zspeed = it.second.body->getAngularVelocity().getZ(); if(it.second.openAngle < 0.f) zspeed = -zspeed; if(zspeed >= PART_CLOSE_VELOCITY) { auto d = it.second.constraint->getHingeAngle() - it.second.closedAngle; if( glm::abs(d) < 0.05f ) { dynamicParts[it.first].moveToAngle = false; setPartLocked(&(it.second), true); } } } } }