From 5adafb5cd06888c1a6ecd8970afa559fb36e2ce2 Mon Sep 17 00:00:00 2001 From: Veyrdite Date: Mon, 5 Jul 2021 09:34:22 +1000 Subject: [PATCH] CBoat::ApplyWaterResistance: Incomplete work fixing slow boats at high FPS. Fixed minor mathematical bug with Pow() and Abs(). Added detailed explanation of physics. --- src/vehicles/Boat.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp index 586fb5356..fe998d72e 100644 --- a/src/vehicles/Boat.cpp +++ b/src/vehicles/Boat.cpp @@ -841,6 +841,79 @@ float fSeaPlaneWaterResistance = 30.0f; void CBoat::ApplyWaterResistance(void) { +#ifdef FIX_BUGS + // TODO: confirm if the explanation below makes sense + float depthResistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass; + if(GetModelIndex() == MI_SKIMMER) + depthResistance *= fSeaPlaneWaterResistance; + float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward()); + + // Water resistances goes up with the square of boat speed (in real life it goes up by the + // cube? close enough!). An extra 0.05f fudge factor was probably put in to make sure the + // boat still has resistance at low speeds (ie auto-brakes to standstill). + float waterResistance = (SQR(fwdSpeed) + 0.05f) * Abs(depthResistance); // Abs() used defensively, negative numbers stuff things up later + // waterResistance will now be a small number like 0.002 or 0.015 + + // An odd use of Abs() was in the original binary. It's possible that the developers did not + // put this in intentionally, instead the compiler may have silently added it to avoid + // Pow() having to deal with negative numbers to a fractional power (undefined) later. + // Regardless it was done badly, making assumptions like vecMoveRes never accidentally + // being negative, so the use of Abs() has changed a little bit in this FIX_BUGS. In + // real gameplay these corner cases should rarely (never?) be encountered anyway. + + // Our boat has different water resistances when travelling forwards (y axis) versus + // sideways (x axis). Boats tend to find it hard to move sideways. + float rx = Abs(pBoatHandling->vecMoveRes.x/(waterResistance + 1.0f)); // Abs() used defensively, negative numbers stuff things up later + float ry = Abs(pBoatHandling->vecMoveRes.y/(waterResistance + 1.0f)); + float rz = Abs(pBoatHandling->vecMoveRes.z/(waterResistance + 1.0f)); + // These rx, ry, rz resistance numbers will each be something like 0.8 or 0.9 or so + + // Fun fact: the above equations are _approximately_ the same as: + // + // pBoatHandling->vecMoveRes.x * (1.0f - waterResistance) + // + // If you change the equations to this then boating feels about the same in-game. + // Which version makes more sense compared to physics in real life? Probably neither :P + // This second version of the equation is a little more efficient however (no division). + + // Now how do we apply these rx ry rz resistance numbers to the boat speed? + // It's not simple: + // + // - We can't multiply them into the speed once per frame, because then players with + // higher framerates will get a lot more friction when boating (lower top speed). + // + // - We can't linearly modify each r number based off frametime, as higher FPS players + // will still end up with more friction. This is for the same reason why linearly + // reducing your bank account's yearly interest into monthly amounts but then + // compounding it monthly will yield you more money than just compounding it yearly. + // + // - We could try compounding each r number based off how many fixed units of time have + // passed (eg multiply itself by itself for every 1ms elapsed this frame). This will + // work fairly regardless of framerate. + // + // We don't actually have to limit ourselves to a fixed time unit (like 1ms chunks), + // instead we can raise the resistance to some power of time using Pow(). + float rrx = Pow(rx, 0.5f*CTimer::GetTimeStep()); // Why 0.5f? Taste? + float rry = Pow(ry, 0.5f*CTimer::GetTimeStep()); + float rrz = Pow(rz, 0.5f*CTimer::GetTimeStep()); + + m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // convert velocities to boat-local space (y = boat forwards, x = sideways, z = up/down) + m_vecMoveSpeed.x *= rrx; + m_vecMoveSpeed.y *= rry; + m_vecMoveSpeed.z *= rrz; + float force = (rry - 1.0f) * m_vecMoveSpeed.y * m_fMass; + m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world space + + // Is this for "flipping the boat over"? Seems to have almost zero effect normally? + ApplyTurnForce(force*GetForward(), -GetUp()); + + // What the hell? Why arbitrarily compound in one more factor of rrz? + // This is framerate dependent! Bah! + if(m_vecMoveSpeed.z > 0.0f) + m_vecMoveSpeed.z *= rrz; + else + m_vecMoveSpeed.z *= (1.0f - rrz)*0.5f + rrz; +#else // TODO: figure out how this works float resistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass; if(GetModelIndex() == MI_SKIMMER) @@ -865,6 +938,7 @@ CBoat::ApplyWaterResistance(void) m_vecMoveSpeed.z *= fz; else m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz; +#endif } RwObject*