diff --git a/rwengine/include/engine/GameData.hpp b/rwengine/include/engine/GameData.hpp index 6afcc09b..73539635 100644 --- a/rwengine/include/engine/GameData.hpp +++ b/rwengine/include/engine/GameData.hpp @@ -260,6 +260,8 @@ public: * The "real" water heights */ uint8_t realWater[128*128]; + + int getWaterIndexAt(const glm::vec3& ws) const; }; #endif diff --git a/rwengine/include/engine/GameObject.hpp b/rwengine/include/engine/GameObject.hpp index a9082128..385c6001 100644 --- a/rwengine/include/engine/GameObject.hpp +++ b/rwengine/include/engine/GameObject.hpp @@ -27,14 +27,17 @@ struct GameObject GameWorld* engine; Animator* animator; /// Object's animator. - + /** * Health value */ float mHealth; + bool _inWater; + GameObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelHandle* model) - : position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), mHealth(0.f) + : position(pos), rotation(rot), model(model), engine(engine), animator(nullptr), mHealth(0.f), + _inWater(false) {} virtual ~GameObject() {} @@ -96,6 +99,8 @@ struct GameObject virtual bool isFrameVisible(ModelFrame* frame) const { return true; } virtual bool isAnimationFixed() const { return true; } + + virtual bool isInWater() const { return _inWater; } }; #endif // __GAMEOBJECTS_HPP__ diff --git a/rwengine/include/engine/RWTypes.hpp b/rwengine/include/engine/RWTypes.hpp index 8face189..bd56573b 100644 --- a/rwengine/include/engine/RWTypes.hpp +++ b/rwengine/include/engine/RWTypes.hpp @@ -7,6 +7,12 @@ #include #include +#define NO_WATER_INDEX 48 +#define WATER_LQ_DATA_SIZE 64 +#define WATER_HQ_DATA_SIZE 128 +#define WATER_WORLD_SIZE 4096.f +#define WATER_HQ_DISTANCE 128.f + class Animation; typedef std::map AnimationSet; diff --git a/rwengine/include/objects/VehicleObject.hpp b/rwengine/include/objects/VehicleObject.hpp index 49cf48b4..92b9f7ff 100644 --- a/rwengine/include/objects/VehicleObject.hpp +++ b/rwengine/include/objects/VehicleObject.hpp @@ -36,6 +36,9 @@ private: float brake; bool handbrake; unsigned int damageFlags; + + // Used to determine in water status + float _lastHeight; public: VehicleDataHandle vehicle; @@ -103,6 +106,8 @@ public: void setPartDamaged(unsigned int flag, bool damaged); virtual bool isFrameVisible(ModelFrame *frame) const; + + void applyWaterFloat(const glm::vec3& relPt, float force, float waterOffset); }; /** diff --git a/rwengine/src/ai/PlayerController.cpp b/rwengine/src/ai/PlayerController.cpp index bc1db6de..5ef85e0b 100644 --- a/rwengine/src/ai/PlayerController.cpp +++ b/rwengine/src/ai/PlayerController.cpp @@ -91,6 +91,8 @@ glm::vec3 PlayerController::getTargetPosition() void PlayerController::jump() { - character->jump(); + if(! character->isInWater() ) { + character->jump(); + } } diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 113a3860..702ac95a 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -581,3 +581,15 @@ TextureAtlas* GameData::getAtlas(size_t i) } return nullptr; } + +int GameData::getWaterIndexAt(const glm::vec3 &ws) const +{ + 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)); + + if( wX >= 0 && wX < WATER_HQ_DATA_SIZE && wY >= 0 && wY < WATER_HQ_DATA_SIZE ) { + int i = (wX*WATER_HQ_DATA_SIZE) + wY; + return engine->gameData.realWater[i]; + } + return 0; +} diff --git a/rwengine/src/objects/CharacterObject.cpp b/rwengine/src/objects/CharacterObject.cpp index 884018c5..0f3d5b89 100644 --- a/rwengine/src/objects/CharacterObject.cpp +++ b/rwengine/src/objects/CharacterObject.cpp @@ -224,10 +224,27 @@ void CharacterObject::updateCharacter(float dt) } } } + + // Handle above waist height water. + auto wi = engine->gameData.getWaterIndexAt(getPosition()); + if( wi != NO_WATER_INDEX ) { + float wh = engine->gameData.waterHeights[wi]; + auto ws = getPosition(); + if( ws.z < wh ) { + ws.z = wh; + setPosition(ws); + physCharacter->setGravity(0.f); + _inWater = true; + } + else { + physCharacter->setGravity(9.81f); + _inWater = false; + } + } if(currentActivity == CharacterObject::Jump) { - if(physCharacter->onGround()) + if(physCharacter->onGround() || isInWater()) { enterAction(CharacterObject::Idle); } diff --git a/rwengine/src/objects/VehicleObject.cpp b/rwengine/src/objects/VehicleObject.cpp index eac69de3..39b2e49b 100644 --- a/rwengine/src/objects/VehicleObject.cpp +++ b/rwengine/src/objects/VehicleObject.cpp @@ -9,7 +9,8 @@ VehicleObject::VehicleObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, ModelHandle* model, VehicleDataHandle data, VehicleInfoHandle info, const glm::vec3& prim, const glm::vec3& sec) : GameObject(engine, pos, rot, model), steerAngle(0.f), throttle(0.f), brake(0.f), handbrake(false), - damageFlags(0), vehicle(data), info(info), colourPrimary(prim), + damageFlags(0), _lastHeight(0.f), vehicle(data), + info(info), colourPrimary(prim), colourSecondary(sec), physBody(nullptr), physVehicle(nullptr) { mHealth = 100.f; @@ -181,6 +182,63 @@ void VehicleObject::tick(float dt) //physVehicle->setSteeringValue(std::min(3.141f/2.f, std::abs(steerAngle)) * sign, w); } } + + 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)); + float vH = ws.z - info->handling.dimensions.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->gameData.realWater[i]; + if( hI < NO_WATER_INDEX ) { + wH = engine->gameData.waterHeights[hI]; + // 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 ) { + float oZ = 0.f; + if( vehicle->type != VehicleData::BOAT ) { + oZ = -((-info->handling.dimensions.z/2.f) + info->handling.dimensions.z * (info->handling.percentSubmerged/100.f)); + } + auto vFwd = getRotation() * glm::vec3(info->handling.dimensions.y, 0.f, 0.f); + auto vRt = getRotation() * glm::vec3(0.f, info->handling.dimensions.x, 0.f); + + float buoyancyForce = info->handling.mass * 9.81f * 0.75f; + + // Damper motion + physBody->setDamping(0.9f, 0.9f); + + applyWaterFloat( vFwd, buoyancyForce, oZ); + applyWaterFloat(-vFwd, buoyancyForce, oZ); + applyWaterFloat( vRt, buoyancyForce, oZ); + applyWaterFloat(-vRt, buoyancyForce, oZ); + } + else { + physBody->setDamping(0.0f, 0.0f); + } + + _lastHeight = vH; } } @@ -318,6 +376,19 @@ bool VehicleObject::isFrameVisible(ModelFrame *frame) const return true; } +void VehicleObject::applyWaterFloat(const glm::vec3 &relPt, float force, float waterOffset) +{ + auto ws = getPosition() + relPt; + auto wi = engine->gameData.getWaterIndexAt(ws); + if(wi != NO_WATER_INDEX) { + float h = engine->gameData.waterHeights[wi] + waterOffset; + if ( ws.z <= h ) { + physBody->applyForce(btVector3(0.f, 0.f, (1.f + h - ws.z) * force), + btVector3(relPt.x, relPt.y, relPt.z)); + } + } +} + // Dammnit Bullet class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index a0eefd4e..57aa2770 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -436,11 +436,6 @@ void GameRenderer::renderWorld(float alpha) glUseProgram( waterProgram ); // TODO: Add some kind of draw distance -#define NO_WATER_INDEX 48 -#define WATER_LQ_DATA_SIZE 64 -#define WATER_HQ_DATA_SIZE 128 -#define WATER_WORLD_SIZE 4096.f -#define WATER_HQ_DISTANCE 128.f float blockLQSize = WATER_WORLD_SIZE/WATER_LQ_DATA_SIZE; float blockHQSize = WATER_WORLD_SIZE/WATER_HQ_DATA_SIZE; diff --git a/rwgame/ingamestate.cpp b/rwgame/ingamestate.cpp index 805a9b0b..a2488b11 100644 --- a/rwgame/ingamestate.cpp +++ b/rwgame/ingamestate.cpp @@ -9,15 +9,17 @@ IngameState::IngameState() : _player(nullptr), _playerCharacter(nullptr) { - _playerCharacter = getWorld()->createPedestrian(1, {-1240.f, -875.f, 13.f}); + _playerCharacter = getWorld()->createPedestrian(1, {-1000.f, -990.f, 13.f}); _player = new PlayerController(_playerCharacter); setPlayerCharacter( _playerCharacter ); float j = 0; - auto spawnPos = glm::vec3( -1200.f, -885.f, 14.f ); + auto spawnPos = glm::vec3( -1000.f, -1000.f, 14.f ); for( auto& vi : getWorld()->vehicleTypes ) { - auto v = getWorld()->createVehicle(vi.first, spawnPos, glm::quat()); + auto sp = spawnPos; + if(vi.first == 120) sp = { -1000.f, -1050.f, 5.f }; + auto v = getWorld()->createVehicle(vi.first, sp, glm::quat()); spawnPos -= glm::vec3( 2.f + v->info->handling.dimensions.x, 0.f, 0.f); if( ++j > 33 ) break; } diff --git a/tests/test_buoyancy.cpp b/tests/test_buoyancy.cpp new file mode 100644 index 00000000..1a57256d --- /dev/null +++ b/tests/test_buoyancy.cpp @@ -0,0 +1,61 @@ +#include +#include "test_globals.hpp" +#include + +BOOST_AUTO_TEST_SUITE(BuoyancyTests) + +BOOST_AUTO_TEST_CASE(test_vehicle_buoyancy) +{ + glm::vec2 tpos(-WATER_WORLD_SIZE/2.f + 10.f); + { + VehicleObject* vehicle = Global::get().e->createVehicle(90u, glm::vec3(tpos, 100.f), glm::quat()); + + BOOST_REQUIRE(vehicle != nullptr); + + BOOST_REQUIRE(vehicle->info != nullptr); + BOOST_REQUIRE(vehicle->vehicle != nullptr); + + // Relies on tile 0,0 being watered... + + BOOST_CHECK( ! vehicle->isInWater() ); + + // Move it under the water + vehicle->setPosition(glm::vec3(tpos, -5.f)); + + // Allow the object to update + vehicle->tick(0.0016f); + + BOOST_CHECK( vehicle->isInWater() ); + + // Ensure that the in water state sticks + vehicle->tick(0.0016f); + + BOOST_CHECK( vehicle->isInWater() ); + + vehicle->setPosition(glm::vec3(tpos, 5.f)); + vehicle->tick(0.0016f); + BOOST_CHECK( ! vehicle->isInWater() ); + + // TODO: fix magic numbers + auto orgval = Global::get().e->gameData.realWater[0]; + Global::get().e->gameData.realWater[0] = NO_WATER_INDEX; + + vehicle->tick(0.0016f); + BOOST_CHECK( ! vehicle->isInWater() ); + + vehicle->setPosition(glm::vec3(tpos, -5.f)); + + vehicle->tick(0.0016f); + BOOST_CHECK( ! vehicle->isInWater() ); + + Global::get().e->gameData.realWater[0] = orgval; + + vehicle->tick(0.0016f); + BOOST_CHECK( ! vehicle->isInWater() ); + + Global::get().e->destroyObject(vehicle); + } +} + +BOOST_AUTO_TEST_SUITE_END() +