diff --git a/audio/MouthFX_VehicleShifting.ogg b/audio/MouthFX_VehicleShifting.ogg index 01c6022..7a34696 100644 Binary files a/audio/MouthFX_VehicleShifting.ogg and b/audio/MouthFX_VehicleShifting.ogg differ diff --git a/src/Audio.cpp b/src/Audio.cpp index 3aaa226..991debb 100644 --- a/src/Audio.cpp +++ b/src/Audio.cpp @@ -18,6 +18,10 @@ #include #include +#include +#include // rand +#include // sin + sound sfxVehicleStartup; sound sfxVehicleIdle; sound sfxVehicleAccelerate; @@ -115,8 +119,87 @@ void debugPrintAudio() } } +typedef short SoundSample; +class BrownianNoiseAudioStream : public sf::SoundStream +{ +public: + void initializeNoiseStream() + { + // TODO: Properly zero + for (unsigned int i = 0; i < ArraySize(sampleBuffer); ++i) + sampleBuffer[i] = 0; + unsigned int channelCount = 1; + unsigned int sampleRate = 44100; + initialize(channelCount, sampleRate); + } + +private: + SoundSample sampleBuffer[100]; + virtual bool onGetData(sf::SoundStream::Chunk& data) + { + const SoundSample maxMotion = 10; + const float pi = std::acos(-1); + SoundSample lastSample = sampleBuffer[ArraySize(sampleBuffer) - 1]; + int triangleWaveDirection = 1; + int triangleWaveSpeed = 1000; + for (unsigned int i = 0; i < ArraySize(sampleBuffer); ++i) + { + // TODO Overflow protection + // sampleBuffer[i] = lastSample + ((rand() % maxMotion) - maxMotion / 2); + + // Sawtooth wave + // sampleBuffer[i] = ((i * 7) / (float)ArraySize(sampleBuffer)) * std::numeric_limits::max(); + + // Square wave + // if (i < ArraySize(sampleBuffer) / 2) + // sampleBuffer[i] = std::numeric_limits::max(); + // else + // sampleBuffer[i] = std::numeric_limits::min(); + + // Triangle wave + // if (i == 0) + // sampleBuffer[i] = std::numeric_limits::min(); + // else if (sampleBuffer[i - 1] <= std::numeric_limits::min() + triangleWaveSpeed) + // triangleWaveDirection = 1; + // else if (sampleBuffer[i - 1] >= std::numeric_limits::max() - triangleWaveSpeed) + // triangleWaveDirection = -1; + // if (i > 0) + // sampleBuffer[i] = sampleBuffer[i - 1] + (triangleWaveDirection * triangleWaveSpeed); + + // Sine wave - Not working + sampleBuffer[i] = std::sin((i / (float)ArraySize(sampleBuffer)) * pi * 2) * (std::numeric_limits::max() / 2); + // if (i < 10) + // std::cout << sampleBuffer[i] << "\n"; + + lastSample = sampleBuffer[i]; + } + + data.sampleCount = ArraySize(sampleBuffer); + data.samples = sampleBuffer; + + // Keep playing forever + return true; + } + + virtual void onSeek(sf::Time timeOffset) + { + } +}; + void updateAudio(PhysicsVehicle& vehicle, float frameTime) { + // if (false) + { + static BrownianNoiseAudioStream noiseStream; + static bool initialized = false; + if (!initialized) + { + noiseStream.initializeNoiseStream(); + initialized = true; + } + noiseStream.play(); + } + const glm::vec3 vehiclePosition = vehicle.GetPosition(); audioListener.setPosition(vehiclePosition[0], vehiclePosition[1], vehiclePosition[2]); @@ -209,3 +292,8 @@ void playObjectiveGet() { sfxObjectiveGet.play(); } + +void playVehicleShifting() +{ + sfxVehicleShifting.play(); +} diff --git a/src/Audio.hpp b/src/Audio.hpp index dbb84a7..10ec3de 100644 --- a/src/Audio.hpp +++ b/src/Audio.hpp @@ -7,4 +7,6 @@ void debugPrintAudio(); class PhysicsVehicle; void updateAudio(PhysicsVehicle& vehicle, float frameTime); +// TODO Kill these void playObjectiveGet(); +void playVehicleShifting(); diff --git a/src/PhysicsVehicle.cpp b/src/PhysicsVehicle.cpp index 621b822..aa51cb6 100644 --- a/src/PhysicsVehicle.cpp +++ b/src/PhysicsVehicle.cpp @@ -2,6 +2,7 @@ #include "PhysicsWorld.hpp" +#include "Audio.hpp" #include "Logging.hpp" #include "Math.hpp" #include "Utilities.hpp" @@ -121,11 +122,11 @@ PhysicsVehicle::PhysicsVehicle(PhysicsWorld& physicsWorld) : ownerWorld(physicsW { // Gear 0 = neutral gearboxRatios.push_back(0.f); - gearboxRatios.push_back(1.f); - gearboxRatios.push_back(2.f); - gearboxRatios.push_back(3.f); - gearboxRatios.push_back(4.f); - + gearboxRatios.push_back(15.f); + gearboxRatios.push_back(10.f); + gearboxRatios.push_back(7.f); + gearboxRatios.push_back(5.f); + numGears = gearboxRatios.size(); } @@ -200,15 +201,54 @@ void PhysicsVehicle::Reset() // Assume a transmission efficiency of 100% and an ideal differential (both wheels get the same // amount of force regardless of conditions) // See "references/A Vehicle Dynamics Model for Driving Simulators.pdf" -float PhysicsVehicle::EngineForceFromThrottle(float throttlePercent, int selectedGear) const +float PhysicsVehicle::EngineForceFromThrottle(float deltaTime, float throttlePercent, + int selectedGear, float& engineRpmOut) const { // Directly control output force if (simpleDrivetrain) return maxEngineForce * throttlePercent; - float engineRadiansPerSecond = 0; + float gearboxRatio = 1.f; + if (selectedGear >= gearboxRatios.size()) + LOGE << "Gear " << selectedGear << " is not in range of gearbox (" << gearboxRatios.size() + << "gears)"; + else + gearboxRatio = gearboxRatios[selectedGear]; + + float engineRadiansPerStep = 0; // While clutch is engaged, engine speed comes from the speed of the wheels + if (ClutchPercent < 0.3f) + { + float maxDeltaRotation = -1000000.f; + // Only do rear wheels, because this is a rear-wheel drive + for (int i = 2; i < vehicle->getNumWheels(); i++) + { + const btWheelInfo& wheelInfo = vehicle->getWheelInfo(i); + maxDeltaRotation = glm::max(wheelInfo.m_deltaRotation, maxDeltaRotation); + } + + LOGD << "Rear wheel max Delta Rotation = " << maxDeltaRotation; + + // TODO This isn't actually per second yet + engineRadiansPerStep = maxDeltaRotation * gearboxRatio; + } + + // TODO Need to take the same step from the wheel update calculations to get the same result + engineRpmOut = + (engineRadiansPerStep * (1 / deltaTime)) * (1.f / (2.f * glm::pi())) * (60.f / 1.f); + LOGD << "Engine RPM = " << engineRpmOut; + + // Handle idle (manuals should stall I think? need to do some research on this) + if (engineRpmOut < idleEngineRpm) + engineRpmOut = idleEngineRpm; + + // Blown engine + if (engineRpmOut > maxEngineRpm) + engineRpmOut = maxEngineRpm; + + // TODO: Fix reverse always using 1st gear (limit reverse speed) + engineRpmOut = glm::abs(engineRpmOut); // TODO: Consider engine speed float engineInput = throttlePercent; @@ -220,20 +260,27 @@ float PhysicsVehicle::EngineForceFromThrottle(float throttlePercent, int selecte // From engine speed and throttle position, determine torque via interpolation float engineTorque = glm::mix(minEngineTorque, maxEngineTorque, engineInput); - float gearboxRatio = 1.f; - if (selectedGear > gearboxRatios.size()) - LOGE << "Gear " << selectedGear << " is not in range of gearbox (" << gearboxRatios.size() - << "gears)"; - else - gearboxRatio = gearboxRatios[selectedGear]; - float gearboxOutputForce = engineTorque * gearboxRatio; return gearboxOutputForce; } void PhysicsVehicle::Update(float deltaTime) { - float engineForce = EngineForceFromThrottle(ThrottlePercent, SelectedGear); + float engineForce = + EngineForceFromThrottle(deltaTime, ThrottlePercent, SelectedGear, engineRpm); + + // Automatic transmission + int gearBeforeAutoShift = SelectedGear; + if (engineRpm < 800.f && SelectedGear > 1) + SelectedGear--; + else if (engineRpm > 1400.f) + SelectedGear++; + SelectedGear = glm::clamp(SelectedGear, 0, gearboxRatios.size() - 1); + if (SelectedGear != gearBeforeAutoShift) + { + LOGV << "Shifted from " << gearBeforeAutoShift << " to " << SelectedGear; + playVehicleShifting(); + } LOGV << "Input throttle: " << ThrottlePercent << " Gear: " << SelectedGear << " output force: " << engineForce; @@ -323,7 +370,7 @@ bool PhysicsVehicle::WheelsContactingSurface() for (int i = 0; i < vehicle->getNumWheels(); i++) { const btWheelInfo& wheelInfo = vehicle->getWheelInfo(i); - if (wheelInfo.m_wheelsSuspensionForce > 0.1f) + if (wheelInfo.m_raycastInfo.m_isInContact > 0.1f) return true; } diff --git a/src/PhysicsVehicle.hpp b/src/PhysicsVehicle.hpp index 82c7bf0..d387bf8 100644 --- a/src/PhysicsVehicle.hpp +++ b/src/PhysicsVehicle.hpp @@ -43,7 +43,8 @@ public: void ApplyTorque(const glm::vec3& torque); // Drivetrain - float EngineForceFromThrottle(float throttlePercent, int selectedGear) const; + float EngineForceFromThrottle(float deltaTime, float throttlePercent, int selectedGear, + float& engineRpmOut) const; // Set in constructor. Don't change int numGears; @@ -82,9 +83,18 @@ public: // I'm not sure why this needs to be so big...need to learn more about physics // Only multiply by sixty to compensate for adding framerate independence on numbers that I // liked before making it rate independent - float airControlMaxPitchTorquePerSecond = 600.f * 60; - float airControlMaxRollTorquePerSecond = 600.f * 60; - + float airControlMaxPitchTorquePerSecond = 600.f * 60.f; + float airControlMaxRollTorquePerSecond = 600.f * 60.f; + + // TODO Make getter? + float engineRpm; + + float idleEngineRpm = 100.f; + float maxEngineRpm = 2500.f; + + // "Realistic" + // float idleEngineRpm = 600.f; + // float maxEngineRpm = 7500.f; private: // I couldn't find any hard numbers, so let's guess around 1000lbs. // 1000lbs = ~453kg.