diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index 7da294c22..79289cbe5 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -114,6 +114,11 @@ body:set_crouching(enabled: bool) body:get_body_type() -> str -- Sets the physical body type body:set_body_type(type: str) + +-- Returns the body material (same as for blocks) +body:get_material() -> str +-- Sets the body material +body:set_material(material: str) ``` ### Skeleton diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index 6c1b041d7..4bc13bc31 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -114,6 +114,11 @@ body:set_crouching(enabled: bool) body:get_body_type() -> str -- Устанавливает тип физического тела body:set_body_type(type: str) + +-- Возвращает материал тела (то же, что и у блоков) +body:get_material() -> str +-- Устанавливает материал тела +body:set_material(material: str) ``` ### Skeleton diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index 7cedd1887..d85c11963 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -32,6 +32,8 @@ local Rigidbody = {__index={ set_crouching=function(self, b) return __rigidbody.set_crouching(self.eid, b) end, get_body_type=function(self) return __rigidbody.get_body_type(self.eid) end, set_body_type=function(self, s) return __rigidbody.set_body_type(self.eid, s) end, + get_material=function(self) return __rigidbody.get_material(self.eid) end, + set_material=function(self, s) return __rigidbody.set_material(self.eid, s) end, }} local function new_Rigidbody(eid) diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index 66fdda825..840bce743 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -60,6 +60,7 @@ end local prepared = false body:set_enabled(false) +mob:set_flight(true) function on_physics_update(delta) if not prepared then diff --git a/src/content/loading/EntityLoader.cpp b/src/content/loading/EntityLoader.cpp index 12e5313a7..91967ea89 100644 --- a/src/content/loading/EntityLoader.cpp +++ b/src/content/loading/EntityLoader.cpp @@ -88,5 +88,8 @@ template<> void ContentUnitLoader::loadUnit( root.at("skeleton-name").get(def.skeletonName); root.at("blocking").get(def.blocking); + root.at("solid").get(def.solid); + root.at("mass").get(def.mass); + root.at("elasticity").get(def.elasticity); root.at("step-height").get(def.stepHeight); } diff --git a/src/engine/AssetsManagement.cpp b/src/engine/AssetsManagement.cpp index 1ca1935c9..13df11220 100644 --- a/src/engine/AssetsManagement.cpp +++ b/src/engine/AssetsManagement.cpp @@ -18,6 +18,8 @@ AssetsManagement::AssetsManagement(Engine& engine) AssetsManagement::~AssetsManagement() { finishBackgroundLoader(); + assets.reset(); + assetsVault.clearKeepedObjects(); } const Assets* AssetsManagement::getStorage() const { diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 9a8553da6..d70f09ba2 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -10,17 +10,19 @@ #include "logic/LevelController.hpp" #include "logic/PlayerController.hpp" #include "objects/Player.hpp" +#include "physics/Hitbox.hpp" #include "voxels/Block.hpp" +#include "voxels/Chunks.hpp" #include "world/Level.hpp" #include "engine/Engine.hpp" LevelFrontend::LevelFrontend( Engine& engine, - Player* currentPlayer, - LevelController* controller, + PlayerController& playerController, + LevelController& controller, const EngineSettings& settings ) - : level(*controller->getLevel()), + : level(*controller.getLevel()), controller(controller), assets(*engine.getAssets()), contentCache(std::make_unique( @@ -36,45 +38,79 @@ LevelFrontend::LevelFrontend( "block-previews" ); - auto& rassets = assets; - controller->getBlocksController()->listenBlockInteraction( - [currentPlayer, controller, &rassets](auto player, const auto& pos, const auto& def, BlockInteraction type) { - const auto& level = *controller->getLevel(); + auto& currentPlayer = playerController.getPlayer(); + auto& assets = this->assets; + auto& level = this->level; + + playerController.setFootstepCallback( + [&level, ¤tPlayer, &assets](const Hitbox& hitbox) { + const BlockMaterial* material = nullptr; + if (hitbox.groundMaterial.empty()) { + const auto& pos = hitbox.position; + const auto& half = hitbox.getHalfSize(); + + auto& blockIndices = level.content.getIndices()->blocks; + for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { + for (int offsetX = -1; offsetX <= 1; offsetX++) { + int x = std::floor(pos.x + half.x * offsetX); + int y = std::floor(pos.y - half.y * 1.1f); + int z = std::floor(pos.z + half.z * offsetZ); + auto vox = currentPlayer.chunks->get(x, y, z); + if (vox) { + auto& def = blockIndices.require(vox->id); + if (!def.obstacle) { + continue; + } + material = level.content.findBlockMaterial(def.material); + break; + } + } + } + } else { + material = level.content.findBlockMaterial(hitbox.groundMaterial); + } + if (material == nullptr) { + return; + } + + auto sound = assets.get(material->stepsSound); + glm::vec3 pos {}; + auto soundsCamera = currentPlayer.currentCamera.get(); + if (currentPlayer.isCurrentCameraBuiltin()) { + soundsCamera = currentPlayer.fpCamera.get(); + } + bool relative = soundsCamera == currentPlayer.fpCamera.get(); + if (!relative) { + pos = currentPlayer.getPosition(); + } + audio::play( + sound, + pos, + relative, + 0.333f, + 1.0f + (rand() % 6 - 3) * 0.05f, + false, + audio::PRIORITY_LOW, + audio::get_channel_index("regular") + ); + }); + + controller.getBlocksController()->listenBlockInteraction( + [&level, &assets] + (auto player, const auto& pos, const auto& def, BlockInteraction type) { auto material = level.content.findBlockMaterial(def.material); if (material == nullptr) { return; } - if (type == BlockInteraction::step) { - auto sound = rassets.get(material->stepsSound); - glm::vec3 pos {}; - auto soundsCamera = currentPlayer->currentCamera.get(); - if (currentPlayer->isCurrentCameraBuiltin()) { - soundsCamera = currentPlayer->fpCamera.get(); - } - bool relative = player == currentPlayer && - soundsCamera == currentPlayer->fpCamera.get(); - if (!relative) { - pos = player->getPosition(); - } - audio::play( - sound, - pos, - relative, - 0.333f, - 1.0f + (rand() % 6 - 3) * 0.05f, - false, - audio::PRIORITY_LOW, - audio::get_channel_index("regular") - ); - } else { + if (type != BlockInteraction::step) { audio::Sound* sound = nullptr; switch (type) { case BlockInteraction::placing: - sound = rassets.get(material->placeSound); + sound = assets.get(material->placeSound); break; case BlockInteraction::destruction: - sound = rassets.get(material->breakSound); + sound = assets.get(material->breakSound); break; default: break; @@ -108,6 +144,6 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const { return *contentCache; } -LevelController* LevelFrontend::getController() const { +LevelController& LevelFrontend::getController() const { return controller; } diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index cc4f996b2..451f88675 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -2,24 +2,24 @@ #include -class Level; class Assets; -class Player; -class Engine; class ContentGfxCache; +class Engine; +class Level; class LevelController; +class PlayerController; struct EngineSettings; class LevelFrontend { Level& level; - LevelController* controller; + LevelController& controller; Assets& assets; std::unique_ptr contentCache; public: LevelFrontend( Engine& engine, - Player* currentPlayer, - LevelController* controller, + PlayerController& currentPlayer, + LevelController& controller, const EngineSettings& settings ); ~LevelFrontend(); @@ -27,5 +27,5 @@ public: Level& getLevel(); const ContentGfxCache& getContentGfxCache() const; ContentGfxCache& getContentGfxCache(); - LevelController* getController() const; + LevelController& getController() const; }; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 6f3593610..6a4afbd89 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -272,7 +272,7 @@ void Hud::updateWorldGenDebug() { const auto& chunks = *player.chunks; uint padding = engine.getSettings().chunks.padding.get(); auto generator = - frontend.getController()->getChunksController()->getGenerator(); + frontend.getController().getChunksController()->getGenerator(); auto debugInfo = generator->createDebugInfo(); int width = debugImgWorldGen->getWidth(); @@ -294,7 +294,7 @@ void Hud::updateWorldGenDebug() { bool isInLoadingZone = frontend.getController() - ->getChunksController() + .getChunksController() ->isInLoadingZone(player, padding, ax + ox, az + oz); data[(flippedZ * width + x) * 4 + 1] = diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 13618d33a..737742afd 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -68,7 +68,7 @@ LevelScreen::LevelScreen( ); frontend = std::make_unique( - engine, player, controller.get(), settings + engine, *playerController, *controller, settings ); renderer = std::make_unique( engine, *frontend, *player diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index e707d1637..663ff1fa1 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -44,7 +44,9 @@ namespace { const float CROUCH_SHIFT_Y = -0.2f; } -CameraControl::CameraControl(Player& player, const CameraSettings& settings) +CameraControl::CameraControl( + Player& player, const CameraSettings& settings +) : player(player), camera(player.fpCamera), settings(settings), @@ -195,18 +197,30 @@ void CameraControl::update( refreshPosition(); + auto castRay = [](Player& player, Camera& camera, const glm::vec3& front) + -> glm::vec3 { + auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f); + auto entityRay = player.getLevel().entities->rayCast( + camera.position, front, 3.0f, player.getEntity(), true + ); + if (entityRay.has_value() && + entityRay->distance < glm::distance(camera.position, blockEnd)) { + return (camera.position + front * entityRay->distance); + } else { + return blockEnd; + } + }; + camera->updateVectors(); if (player.currentCamera == spCamera) { spCamera->position = - chunks.rayCastToObstacle(camera->position, camera->front, 3.0f) - - 0.4f * camera->front; + castRay(player, *camera, camera->front) - 0.4f * camera->front; spCamera->dir = -camera->dir; spCamera->front = -camera->front; spCamera->right = -camera->right; } else if (player.currentCamera == tpCamera) { - tpCamera->position = - chunks.rayCastToObstacle(camera->position, -camera->front, 3.0f) + - 0.4f * camera->front; + tpCamera->position = castRay(player, *camera, camera->front * -1.0f) + + 0.4f * camera->front; tpCamera->dir = camera->dir; tpCamera->front = camera->front; tpCamera->right = camera->right; @@ -229,26 +243,8 @@ PlayerController::PlayerController( } void PlayerController::onFootstep(const Hitbox& hitbox) { - auto pos = hitbox.position; - auto half = hitbox.halfsize; - - for (int offsetZ = -1; offsetZ <= 1; offsetZ++) { - for (int offsetX = -1; offsetX <= 1; offsetX++) { - int x = std::floor(pos.x + half.x * offsetX); - int y = std::floor(pos.y - half.y * 1.1f); - int z = std::floor(pos.z + half.z * offsetZ); - auto vox = player.chunks->get(x, y, z); - if (vox) { - auto& def = level.content.getIndices()->blocks.require(vox->id); - if (!def.obstacle) { - continue; - } - blocksController.onBlockInteraction( - &player, glm::ivec3(x, y, z), def, BlockInteraction::step - ); - return; - } - } + if (footstepCallback) { + footstepCallback(hitbox); } } @@ -557,3 +553,7 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta) Player& PlayerController::getPlayer() { return player; } + +void PlayerController::setFootstepCallback(FootstepCallback&& callback) { + footstepCallback = std::move(callback); +} diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index e730ff37c..5ec8e4b50 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -18,6 +18,8 @@ struct Hitbox; struct CameraSettings; struct EngineSettings; +using FootstepCallback = std::function; + class CameraControl { Player& player; std::shared_ptr camera; @@ -57,6 +59,7 @@ class PlayerController { CameraControl camControl; BlocksController& blocksController; float interactionTimer = 0.0f; + FootstepCallback footstepCallback; void updateKeyboard(const Input& inputEvents); void resetKeyboard(); @@ -89,5 +92,8 @@ public: void postUpdate( float delta, int windowHeight, const Input* inputEvents, bool pause ); + Player& getPlayer(); + + void setFootstepCallback(FootstepCallback&& callback); }; diff --git a/src/logic/scripting/lua/libs/lib__rigidbody.cpp b/src/logic/scripting/lua/libs/lib__rigidbody.cpp index f32e9d986..52234f91e 100644 --- a/src/logic/scripting/lua/libs/lib__rigidbody.cpp +++ b/src/logic/scripting/lua/libs/lib__rigidbody.cpp @@ -150,6 +150,48 @@ static int l_set_linear_damping(lua::State* L) { return 0; } +static int l_set_material(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + entity->getRigidbody().hitbox.material = lua::require_string(L, 2); + } + return 0; +} + +static int l_get_material(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + return lua::pushstring(L, entity->getRigidbody().hitbox.material); + } + return 0; +} + +static int l_get_mass(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + return lua::pushnumber(L, entity->getRigidbody().mass); + } + return 0; +} + +static int l_set_mass(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + entity->getRigidbody().mass = lua::tonumber(L, 2); + } + return 0; +} + +static int l_get_elasticity(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + return lua::pushnumber(L, entity->getRigidbody().elasticity); + } + return 0; +} + +static int l_set_elasticity(lua::State* L) { + if (auto entity = get_entity(L, 1)) { + entity->getRigidbody().elasticity = lua::tonumber(L, 2); + } + return 0; +} + const luaL_Reg rigidbodylib[] = { {"is_enabled", lua::wrap}, {"set_enabled", lua::wrap}, @@ -169,5 +211,11 @@ const luaL_Reg rigidbodylib[] = { {"set_crouching", lua::wrap}, {"get_body_type", lua::wrap}, {"set_body_type", lua::wrap}, + {"set_material", lua::wrap}, + {"get_material", lua::wrap}, + {"get_mass", lua::wrap}, + {"set_mass", lua::wrap}, + {"get_elasticity", lua::wrap}, + {"set_elasticity", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/lib__transform.cpp b/src/logic/scripting/lua/libs/lib__transform.cpp index 7cf7bed62..16662daaa 100644 --- a/src/logic/scripting/lua/libs/lib__transform.cpp +++ b/src/logic/scripting/lua/libs/lib__transform.cpp @@ -31,7 +31,7 @@ static int l_set_pos(lua::State* L) { auto vec = lua::tovec3(L, 2); check_valid(vec); entity->getTransform().setPos(vec); - entity->getRigidbody().hitbox.position = vec; + entity->getRigidbody().hitbox.setPos(vec); } return 0; } diff --git a/src/maths/aabb.hpp b/src/maths/aabb.hpp index 471441951..ca19a81fe 100644 --- a/src/maths/aabb.hpp +++ b/src/maths/aabb.hpp @@ -38,15 +38,23 @@ struct AABB { return AABB(a + pos, b + pos); } + AABB operator+(const glm::vec3& offset) const { + return translated(offset); + } + + AABB operator-(const glm::vec3& offset) const { + return translated(-offset); + } + /// @brief Multiply AABB size from center - inline void scale(const glm::vec3 mul) { + inline void scale(const glm::vec3& mul) { glm::vec3 center = (a + b) * 0.5f; a = (a - center) * mul + center; b = (b - center) * mul + center; } /// @brief Multiply AABB size from given origin - inline void scale(const glm::vec3 mul, const glm::vec3 orig) { + inline void scale(const glm::vec3& mul, const glm::vec3& orig) { glm::vec3 beg = min(); glm::vec3 end = max(); glm::vec3 center = glm::mix(beg, end, orig); @@ -91,14 +99,15 @@ struct AABB { addPoint(matrix * glm::vec4(pb.x, pa.y, pb.z, 1.0f)); } - inline bool intersect(const AABB& aabb) { + inline bool intersects(const AABB& aabb) const { return ( - a.x <= aabb.b.x && b.x >= aabb.a.x && a.y <= aabb.b.y && - b.y >= aabb.a.y && a.z <= aabb.b.z && b.z >= aabb.a.z + a.x <= aabb.b.x && b.x >= aabb.a.x && + a.y <= aabb.b.y && b.y >= aabb.a.y && + a.z <= aabb.b.z && b.z >= aabb.a.z ); } - inline bool intersect(const AABB& aabb, float margin) { + inline bool intersects(const AABB& aabb, float margin) const { return ( a.x <= aabb.b.x + margin && b.x >= aabb.a.x - margin && a.y <= aabb.b.y + margin && b.y >= aabb.a.y - margin && diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index cbb507c89..82e293522 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -1,28 +1,30 @@ #define VC_ENABLE_REFLECTION #include "Entities.hpp" -#include -#include -#include - #include "assets/Assets.hpp" #include "content/Content.hpp" #include "data/dv_util.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" +#include "Entity.hpp" +#include "EntityDef.hpp" +#include "graphics/commons/Model.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/LineBatch.hpp" -#include "graphics/commons/Model.hpp" #include "graphics/render/ModelBatch.hpp" #include "logic/scripting/scripting.hpp" #include "maths/FrustumCulling.hpp" #include "maths/rays.hpp" -#include "EntityDef.hpp" -#include "Entity.hpp" -#include "rigging.hpp" +#include "maths/util.hpp" #include "physics/PhysicsSolver.hpp" +#include "rigging.hpp" #include "world/Level.hpp" +#include +#include +#include +#include + static debug::Logger logger("entities"); Entities::Entities(Level& level) @@ -79,7 +81,7 @@ entityid_t Entities::spawn( entities[id] = entity; uids[entity] = id; - registry->emplace(entity, static_cast(id), def); + registry->emplace(entity, id, def); const auto& tsf = registry->emplace( entity, position, @@ -91,7 +93,7 @@ entityid_t Entities::spawn( auto& body = registry->emplace( entity, true, - Hitbox {def.bodyType, position, def.hitbox * 0.5f}, + Hitbox {id, def.bodyType, position, def.hitbox * 0.5f}, std::vector {} ); body.initialize(def, id, *this); @@ -161,7 +163,11 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { } std::optional Entities::rayCast( - glm::vec3 start, glm::vec3 dir, float maxDistance, entityid_t ignore + glm::vec3 start, + glm::vec3 dir, + float maxDistance, + entityid_t ignore, + bool solidOnly ) { Ray ray(start, dir); auto view = registry->view(); @@ -170,7 +176,7 @@ std::optional Entities::rayCast( glm::ivec3 foundNormal; for (auto [entity, eid, transform, body] : view.each()) { - if (eid.uid == ignore || !body.enabled) { + if (eid.uid == ignore || !body.enabled || (solidOnly && !eid.def.solid)) { continue; } auto& hitbox = body.hitbox; @@ -278,13 +284,18 @@ void Entities::updateSensors( } void Entities::preparePhysics(float delta) { + auto& physics = *level.physics; + auto& hitboxes = physics.getHitboxesWriteable(); + auto& solidHitboxes = physics.getSolidHitboxesWriteable(); + if (sensorsTickClock.update(delta)) { auto part = sensorsTickClock.getPart(); auto parts = sensorsTickClock.getParts(); + auto& sensors = physics.getSensorsWriteable(); + sensors.clear(); + auto view = registry->view(); - auto physics = level.physics.get(); - std::vector sensors; for (auto [entity, eid, transform, rigidbody] : view.each()) { if (!rigidbody.enabled) { continue; @@ -294,7 +305,25 @@ void Entities::preparePhysics(float delta) { } updateSensors(rigidbody, transform, sensors); } - physics->setSensors(std::move(sensors)); + } + + hitboxes.clear(); + solidHitboxes.clear(); + + auto view = registry->view(); + for (auto [entity, eid, rigidbody] : view.each()) { + if (eid.destroyFlag || !rigidbody.enabled) { + continue; + } + rigidbody.hitbox.mass = eid.def.bodyType == BodyType::DYNAMIC + ? rigidbody.mass + : std::numeric_limits::infinity(); + rigidbody.hitbox.elasticity = rigidbody.elasticity; + hitboxes.emplace_back(&rigidbody.hitbox); + if (!eid.def.solid) { + continue; + } + solidHitboxes.emplace_back(&rigidbody.hitbox); } } @@ -303,29 +332,31 @@ void Entities::updatePhysics(float delta) { auto view = registry->view(); auto physics = level.physics.get(); + + int substeps = std::max(std::min(delta * 1000, 200), 8); + physics->step(*level.chunks, delta, substeps); + for (auto [entity, eid, transform, rigidbody] : view.each()) { - if (!rigidbody.enabled || rigidbody.hitbox.type == BodyType::STATIC) { + if (!rigidbody.enabled || + rigidbody.hitbox.type == BodyType::STATIC) { continue; } auto& hitbox = rigidbody.hitbox; - auto prevVel = hitbox.velocity; - bool grounded = hitbox.grounded; - - float vel = glm::length(prevVel); - int substeps = static_cast(delta * vel * 20); - substeps = std::min(100, std::max(2, substeps)); - physics->step(*level.chunks, hitbox, delta, substeps, eid.uid); - hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f) - ? 8.0f - : (!grounded ? 2.0f : 10.0f); hitbox.scale = transform.size; - transform.setPos(hitbox.position); - if (hitbox.grounded && !grounded) { + if (util::is_nan_or_inf(hitbox.position)) { + logger.error() + << "physics simulation produced nan or inf (entity " + << eid.def.name << "#" << eid.uid << ")"; + hitbox.position = transform.pos; + } else { + transform.setPos(hitbox.position); + } + if (hitbox.grounded && !hitbox.prevGrounded) { scripting::on_entity_grounded( - *get(eid.uid), glm::length(prevVel - hitbox.velocity) + *get(eid.uid), glm::length(hitbox.prevVelocity - hitbox.velocity) ); } - if (!hitbox.grounded && grounded) { + if (!hitbox.grounded && hitbox.prevGrounded) { scripting::on_entity_fall(*get(eid.uid)); } } @@ -450,7 +481,7 @@ void Entities::render( bool Entities::hasBlockingInside(AABB aabb) { auto view = registry->view(); for (auto [entity, eid, body] : view.each()) { - if (eid.def.blocking && aabb.intersect(body.hitbox.getAABB(), -0.05f)) { + if (eid.def.blocking && aabb.intersects(body.hitbox.getAABB(), -0.05f)) { return true; } } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 804ce17f4..33786118a 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -88,12 +88,14 @@ public: /// @param dir Ray direction normalized vector /// @param maxDistance Max ray length /// @param ignore Ignored entity ID + /// @param solidOnly If true, only entities with solid hitboxes will be checked /// @return An optional structure containing entity, normal and distance std::optional rayCast( glm::vec3 start, glm::vec3 dir, float maxDistance, - entityid_t ignore = -1 + entityid_t ignore = -1, + bool solidOnly = false ); void loadEntities(dv::value map); diff --git a/src/objects/EntityDef.cpp b/src/objects/EntityDef.cpp index 4cc858710..ae1f87c68 100644 --- a/src/objects/EntityDef.cpp +++ b/src/objects/EntityDef.cpp @@ -9,5 +9,9 @@ void EntityDef::cloneTo(EntityDef& dst) { dst.radialSensors = radialSensors; dst.skeletonName = skeletonName; dst.blocking = blocking; + dst.solid = solid; + dst.mass = mass; + dst.elasticity = elasticity; + dst.stepHeight = stepHeight; dst.save = save; } diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index 3752f3063..195d07a17 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -42,6 +42,15 @@ struct EntityDef { /// @brief Does entity prevent blocks setup bool blocking = true; + /// @brief Is the entity solid (blocks other entities) + bool solid = false; + + /// @brief Mass for physics calculations + float mass = 1.0f; + + /// @brief Elasticity for physics calculations + float elasticity = 0.0f; + /// @brief Max obstacle height that does not require jumping float stepHeight = 0.5f; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index a63341d49..b77a07fdd 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -114,7 +114,7 @@ void Player::teleport(glm::vec3 position) { this->position = position; if (auto entity = level.entities->get(eid)) { - entity->getRigidbody().hitbox.position = position; + entity->getRigidbody().hitbox.setPos(position); entity->getTransform().setPos(position); entity->setInterpolatedPosition(position); } diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp index 9e2edba4a..2c885f8e0 100644 --- a/src/objects/Rigidbody.cpp +++ b/src/objects/Rigidbody.cpp @@ -22,6 +22,8 @@ dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { if (hitbox.crouching) { bodymap["crouch"] = hitbox.crouching; } + bodymap["mass"] = mass; + bodymap["elasticity"] = elasticity; } return bodymap; } @@ -33,6 +35,8 @@ void Rigidbody::deserialize(const dv::value& root) { BodyTypeMeta.getItem(bodyTypeName, hitbox.type); root["crouch"].asBoolean(hitbox.crouching); root["damping"].asNumber(hitbox.linearDamping); + root["mass"].asNumber(mass); + root["elasticity"].asNumber(elasticity); } template @@ -49,6 +53,8 @@ static sensorcallback create_sensor_callback(Entities& entities) { void Rigidbody::initialize( const EntityDef& def, entityid_t id, Entities& entities ) { + mass = def.mass; + elasticity = def.elasticity; sensors.resize(def.radialSensors.size() + def.boxSensors.size()); for (auto& [i, box] : def.boxSensors) { SensorParams params {}; diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp index 91790d8a2..bc2a85f90 100644 --- a/src/objects/Rigidbody.hpp +++ b/src/objects/Rigidbody.hpp @@ -13,6 +13,8 @@ struct Rigidbody { bool enabled = true; Hitbox hitbox; std::vector sensors; + float mass; + float elasticity; dv::value serialize(bool saveVelocity, bool saveBodySettings) const; void deserialize(const dv::value& root); diff --git a/src/physics/Hitbox.cpp b/src/physics/Hitbox.cpp index adf078418..f49e0c2b8 100644 --- a/src/physics/Hitbox.cpp +++ b/src/physics/Hitbox.cpp @@ -1,10 +1,12 @@ #include "Hitbox.hpp" -#include - -Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize) - : type(type), - position(position), - halfsize(halfsize), - velocity(0.0f,0.0f,0.0f) -{} +Hitbox::Hitbox( + entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize +) + : entity(entity), + type(type), + position(position), + halfsize(halfsize), + velocity(0.0f, 0.0f, 0.0f), + prevPosition(position) { +} diff --git a/src/physics/Hitbox.hpp b/src/physics/Hitbox.hpp index 909a48451..ecf41faa3 100644 --- a/src/physics/Hitbox.hpp +++ b/src/physics/Hitbox.hpp @@ -1,5 +1,7 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL + #include "maths/aabb.hpp" #include "typedefs.hpp" #include "util/EnumMetadata.hpp" @@ -8,6 +10,7 @@ #include #include #include +#include enum class SensorType { AABB, @@ -48,6 +51,7 @@ VC_ENUM_METADATA(BodyType) VC_ENUM_END struct Hitbox { + entityid_t entity; BodyType type; glm::vec3 position; glm::vec3 halfsize; @@ -60,14 +64,34 @@ struct Hitbox { float gravityScale = 1.0f; bool crouching = false; float stepHeight = 0.5f; + float mass = 1.0f; + float elasticity = 0.0f; + std::string material; + std::string groundMaterial; + glm::vec3 groundVelocity {}; + + glm::vec3 prevPosition {}; + glm::vec3 prevVelocity {}; + bool prevGrounded = false; - Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize); + static inline constexpr float TELEPORT_THRESOLD_SQR = 0.5f; + + Hitbox( + entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize + ); AABB getAABB() const { - return AABB(position-halfsize, position+halfsize); + return AABB(position - halfsize, position + halfsize); } glm::vec3 getHalfSize() const { return halfsize * scale; } + + void setPos(const glm::vec3& vec) { + position = vec; + if (glm::distance2(position, prevPosition) >= TELEPORT_THRESOLD_SQR) { + prevPosition = vec; + } + } }; diff --git a/src/physics/PhysicsSolver.cpp b/src/physics/PhysicsSolver.cpp index e20bafacd..92780fe5e 100644 --- a/src/physics/PhysicsSolver.cpp +++ b/src/physics/PhysicsSolver.cpp @@ -5,197 +5,133 @@ #include "voxels/Block.hpp" #include "voxels/GlobalChunks.hpp" #include "voxels/voxel.hpp" +#include "objects/Entities.hpp" +#include "debug/Logger.hpp" -#include #include #define GLM_ENABLE_EXPERIMENTAL #include -inline const float E = 0.03f; -inline const float MAX_FIX = 0.1f; +inline constexpr float E = 0.03f; +inline constexpr float MAX_FIX = 0.1f; -PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) {} +static debug::Logger logger("physics-solver"); -void PhysicsSolver::step( - const GlobalChunks& chunks, - Hitbox& hitbox, - float delta, - uint substeps, - entityid_t entity +PhysicsSolver::PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity) + : chunks(chunks), gravity(std::move(gravity)) { +} + +static glm::vec3 calc_collsion_velocity_result( + const Hitbox& a, const Hitbox& b ) { - float dt = delta / static_cast(substeps); - float linearDamping = hitbox.linearDamping * hitbox.friction; - float s = 2.0f/BLOCK_AABB_GRID; - - auto half = hitbox.getHalfSize(); - glm::vec3& pos = hitbox.position; - glm::vec3& vel = hitbox.velocity; - float gravityScale = hitbox.gravityScale; - - bool prevGrounded = hitbox.grounded; - hitbox.grounded = false; - for (uint i = 0; i < substeps; i++) { - float px = pos.x; - float py = pos.y; - float pz = pos.z; - - vel += gravity * dt * gravityScale; - if (hitbox.type == BodyType::DYNAMIC) { - colisionCalc(chunks, hitbox, vel, pos, half, - (prevGrounded && gravityScale > 0.0f) ? hitbox.stepHeight : 0.0f); - } - - pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; - if (hitbox.grounded && pos.y < py) { - pos.y = py; - } - - if (hitbox.crouching && hitbox.grounded){ - float y = (pos.y-half.y-E); - hitbox.grounded = false; - for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { - float x = (px-half.x+E) + ix * s; - for (int iz = 0; iz <= (half.z-E)*2/s; iz++){ - float z = (pos.z-half.z+E) + iz * s; - if (chunks.isObstacleAt(x,y,z)){ - hitbox.grounded = true; - break; - } - } - } - if (!hitbox.grounded) { - pos.z = pz; - vel.z = 0.0f; - } - hitbox.grounded = false; - for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { - float x = (pos.x-half.x+E) + ix * s; - for (int iz = 0; iz <= (half.z-E)*2/s; iz++){ - float z = (pz-half.z+E) + iz * s; - if (chunks.isObstacleAt(x,y,z)){ - hitbox.grounded = true; - break; - } - } - } - if (!hitbox.grounded) { - pos.x = px; - vel.x = 0.0f; - } - hitbox.grounded = true; - } + const auto& vA = a.velocity; + const auto& vB = b.velocity; + if (glm::isinf(a.mass)) { + return vA; } - vel.x /= 1.0f + delta * linearDamping; - vel.z /= 1.0f + delta * linearDamping; - if (hitbox.verticalDamping > 0.0f) { - vel.y /= 1.0f + delta * linearDamping * hitbox.verticalDamping; - } - - AABB aabb; - aabb.a = hitbox.position - hitbox.getHalfSize(); - aabb.b = hitbox.position + hitbox.getHalfSize(); - for (size_t i = 0; i < sensors.size(); i++) { - auto& sensor = *sensors[i]; - if (sensor.entity == entity) { - continue; - } - - bool triggered = false; - switch (sensor.type) { - case SensorType::AABB: - triggered = aabb.intersect(sensor.calculated.aabb); - break; - case SensorType::RADIUS: - triggered = glm::distance2( - hitbox.position, glm::vec3(sensor.calculated.radial)) - < sensor.calculated.radial.w; - break; - } - if (triggered) { - if (sensor.prevEntered.find(entity) == sensor.prevEntered.end()) { - sensor.enterCallback(sensor.entity, sensor.index, entity); - } - sensor.nextEntered.insert(entity); - } + if (glm::isinf(b.mass)) { + return vB - a.elasticity * (vA - vB); } + const auto& mA = a.mass; + const auto& mB = b.mass; + return (mA * vA + mB * vB + a.elasticity * mB * (vB - vA)) / (mA + mB); } static float calc_step_height( const GlobalChunks& chunks, const glm::vec3& pos, const glm::vec3& half, - float stepHeight, - float s + float stepHeight ) { - if (stepHeight > 0.0f) { - for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { - float x = (pos.x-half.x+E) + ix * s; - for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { - float z = (pos.z-half.z+E) + iz * s; - if (chunks.isObstacleAt(x, pos.y+half.y+stepHeight, z)) { - return 0.0f; - } + AABB aabb(-half, +half); + aabb.scale(glm::vec3(1.0f - E * 2, 1.0f, 1.0f - E * 2)); + aabb = aabb + pos + glm::vec3(0.0f, stepHeight, 0.0f); + if (stepHeight <= 0.0f) { + return stepHeight; + } + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + float x = (pos.x - half.x) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) { + float z = (pos.z - half.z) + iz; + if (chunks.isObstacleAt(x, pos.y + half.y + stepHeight, z, aabb)) { + return 0.0f; } } } return stepHeight; } -template -static bool calc_collision_neg( +// todo: reduce code duplication + +template +static void calc_collision( + Hitbox& hitbox, const GlobalChunks& chunks, - glm::vec3& pos, - glm::vec3& vel, + const std::vector& solidHitboxes, const glm::vec3& half, - float stepHeight, - float s + float stepHeight ) { - if (vel[nx] >= 0.0f) { - return false; - } - glm::vec3 offset(0.0f, stepHeight, 0.0f); - for (int iy = 0; iy <= std::max(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) { - glm::vec3 coord; - coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; - for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++){ - coord[nz] = (pos[nz]-half[nz]+E) + iz * s; - coord[nx] = (pos[nx]-half[nx]-E); - if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) { - vel[nx] = 0.0f; - float newx = std::floor(coord[nx]) + half[nx] + aabb->max()[nx] + E; - if (newx - pos[nx] <= E) { - pos[nx] = newx; - } - return true; + auto& pos = hitbox.position; + auto& vel = hitbox.velocity; + + glm::vec3 offset(0.0f, stepHeight + E, 0.0f); + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + auto boxhalf = box->getHalfSize(); + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + scale[nz] = 1.0f - E * 8.0f; + scale[ny] = 1.0f - E * 2.0f; + aabb.scale(scale); + aabb = aabb + offset; + aabb.b.y -= stepHeight + E * 2; + + if ((box->position[nx] - pos[nx]) * sign <= 0.0f || !box->getAABB().intersects(aabb)) { + continue; + } + + float newnegx = box->position[nx] - boxhalf[nx] - half[nx]; + float newposx = box->position[nx] + boxhalf[nx] + half[nx]; + float newx; + if ((glm::abs(newnegx - pos[nx]) - glm::abs(newposx - pos[nx]))*sign < 0.0f) { + newx = sign > 0 ? newnegx : newposx; + } else { + continue; + } + if ((pos[nx] - newx) * sign > 0.0f && glm::abs(pos[nx] - newx) < MAX_FIX) { + auto velA = calc_collsion_velocity_result(hitbox, *box); + auto velB = calc_collsion_velocity_result(*box, hitbox); + if ((vel[nx] - box->velocity[nx]) * sign > 0.0f) { + vel[nx] = velA[nx]; + box->velocity[nx] = velB[nx]; } + pos[nx] = newx; } } - return false; -} - -template -static void calc_collision_pos( - const GlobalChunks& chunks, - glm::vec3& pos, - glm::vec3& vel, - const glm::vec3& half, - float stepHeight, - float s -) { - if (vel[nx] <= 0.0f) { + if (vel[nx] * sign <= 0.0f && hitbox.groundVelocity[nx] * sign <= 0.0f) { return; } - glm::vec3 offset(0.0f, stepHeight, 0.0f); - for (int iy = 0; iy <= std::max(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) { + for (int iy = 0; iy <= glm::ceil(((half - offset * 0.5f)[ny] - E) * 2); iy++) { glm::vec3 coord; - coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; - for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++) { - coord[nz] = (pos[nz]-half[nz]+E) + iz * s; - coord[nx] = (pos[nx]+half[nx]+E); - if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) { - vel[nx] = 0.0f; - float newx = std::floor(coord[nx]) - half[nx] + aabb->min()[nx] - E; - if (newx - pos[nx] <= E) { + coord[ny] = ((pos + offset)[ny] - half[ny] + E) + iy; + for (int iz = 0; iz <= glm::ceil((half[nz] - E) * 2); iz++) { + coord[nz] = (pos[nz] - half[nz] + E) + iz; + coord[nx] = pos[nx] + (half[nx] + E * 2) * sign; + + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + scale[nz] = 1.0f - E * 8.0f; + aabb.scale(scale); + aabb = aabb + offset; + aabb.b.y -= stepHeight + E * 2; + + if (const auto obstacle = chunks.isObstacleAt(coord.x, coord.y, coord.z, aabb)) { + float newx = std::floor(coord[nx]) - half[nx] * sign + + (sign > 0 ? obstacle->min() : obstacle->max())[nx]; + if ((pos[nx] - newx) * sign > 0.0f && glm::abs(pos[nx] - newx) < MAX_FIX) { + vel[nx] = -hitbox.elasticity * vel[nx]; pos[nx] = newx; } return; @@ -204,93 +140,314 @@ static void calc_collision_pos( } } -void PhysicsSolver::colisionCalc( - const GlobalChunks& chunks, - Hitbox& hitbox, - glm::vec3& vel, - glm::vec3& pos, - const glm::vec3 half, - float stepHeight +bool PhysicsSolver::calcCollisionNegY( + Hitbox& hitbox, const glm::vec3& half, float dt ) { - // step size (smaller - more accurate, but slower) // TODO: GET RID OF THIS - float s = 2.0f/BLOCK_AABB_GRID; + auto& pos = hitbox.position; + auto& vel = hitbox.velocity; - stepHeight = calc_step_height(chunks, pos, half, stepHeight, s); + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + scale.x = 1.0f - E * 4.0f; + scale.z = 1.0f - E * 4.0f; + aabb.scale(scale); - const AABB* aabb; - - calc_collision_neg<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); - calc_collision_pos<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); + auto boxhalf = box->getHalfSize(); + if (box->position.y < pos.y && box->getAABB().intersects(aabb)) { + float newy = box->position.y + boxhalf.y + half.y; + if (pos.y < newy && glm::abs(pos.y - newy) < boxhalf.y) { + pos.y = newy; + } - calc_collision_neg<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); - calc_collision_pos<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); + auto velA = calc_collsion_velocity_result(hitbox, *box); + auto velB = calc_collsion_velocity_result(*box, hitbox); - if (calc_collision_neg<1, 0, 2>(chunks, pos, vel, half, 0.0f, s)) { - hitbox.grounded = true; - } - - if (stepHeight > 0.0 && vel.y <= 0.0f){ - for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { - float x = (pos.x-half.x+E) + ix * s; - for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { - float z = (pos.z-half.z+E) + iz * s; - float y = (pos.y-half.y+E); - if ((aabb = chunks.isObstacleAt(x,y,z))){ - vel.y = 0.0f; - float newy = std::floor(y) + aabb->max().y + half.y; - if (std::abs(newy-pos.y) <= MAX_FIX+stepHeight) { - pos.y = newy; - } - break; + hitbox.groundVelocity = box->position - box->prevPosition; + if (vel.y < hitbox.groundVelocity.y / dt) { + vel.y = velA.y; + box->velocity.y = velB.y; + if (hitbox.groundMaterial.empty() && !box->material.empty()) { + hitbox.groundMaterial = box->material; } + return true; } } } + if (vel.y >= 0.0f) { + return false; + } + hitbox.groundVelocity = {}; + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + glm::vec3 coord; + coord.x = (pos.x - half.x + E) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) { + coord.z = (pos.z - half.z + E) + iz; + coord.y = pos.y - half.y - E * 2; + + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + scale.x = 1.0f - E * 4.0f; + scale.z = 1.0f - E * 4.0f; + aabb.scale(scale); + + if (const auto obstacle = + chunks.isObstacleAt(coord.x, coord.y, coord.z, aabb)) { + float newy = std::floor(coord.y) + half.y + obstacle->max().y; + + if (newy >= pos.y) { + vel.y = -hitbox.elasticity * vel.y; + pos.y = newy; + } + return true; + } + } + } + return false; +} + +void PhysicsSolver::calcCollisions( + Hitbox& hitbox, + glm::vec3& vel, + glm::vec3& pos, + const glm::vec3& half, + float stepHeight, + float dt +) { + stepHeight = calc_step_height(chunks, pos, half, stepHeight); + + auto prevPos = pos; + + calc_collision<0, 1, 2, -1>(hitbox, chunks, solidHitboxes, half, stepHeight); + calc_collision<0, 1, 2, 1>(hitbox, chunks, solidHitboxes, half, stepHeight); + + float xpos = pos.x; + pos.x = prevPos.x; + + calc_collision<2, 1, 0, -1>(hitbox, chunks, solidHitboxes, half, stepHeight); + calc_collision<2, 1, 0, 1>(hitbox, chunks, solidHitboxes, half, stepHeight); + pos.x = xpos; + + if (calcCollisionNegY(hitbox, half, dt)) { + hitbox.grounded = true; + } + if (vel.y > 0.0f){ - for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { - float x = (pos.x-half.x+E) + ix * s; - for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { - float z = (pos.z-half.z+E) + iz * s; - float y = (pos.y+half.y+E); - if ((aabb = chunks.isObstacleAt(x,y,z))){ - vel.y = 0.0f; + AABB boxAABB = AABB(-half, +half); + boxAABB.scale(glm::vec3(1.0f - E * 4, 1.0f + E * 2, 1.0f - E * 4)); + boxAABB = boxAABB.translated(pos); + + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + float x = (pos.x - half.x + E) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) { + float z = (pos.z - half.z + E) + iz; + float y = (pos.y + half.y + E) + 0.5f; + if (auto aabb = chunks.isObstacleAt(x, y, z, boxAABB)) { float newy = std::floor(y) - half.y + aabb->min().y - E; - if (std::abs(newy-pos.y) <= MAX_FIX) { + if (pos.y >= newy) { + vel.y = -hitbox.elasticity * vel.y; pos.y = newy; } break; } } } + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + auto boxhalf = box->getHalfSize(); + if (box->position.y > pos.y && box->getAABB().intersects(boxAABB)) { + float newy = box->position.y - boxhalf.y - half.y; + if (pos.y > newy && glm::abs(pos.y - newy) < 0.5f) { + pos.y = newy; + } + auto velA = calc_collsion_velocity_result(hitbox, *box); + auto velB = calc_collsion_velocity_result(*box, hitbox); + if (vel.y > hitbox.groundVelocity.y / dt) { + vel.y = velA.y; + box->velocity.y = velB.y; + break; + } + } + } + } + + // step on + if (stepHeight > 0.0 && vel.y <= 0.0f) { + AABB boxAABB = AABB(pos - half, pos + half); + boxAABB.scale(glm::vec3(1.0f - E * 2, 1.0f - E * 2, 1.0f - E * 2)); + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + float x = (pos.x - half.x) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) { + float z = (pos.z - half.z) + iz; + float y = (pos.y - half.y + E) + E; + if (auto aabb = chunks.isObstacleAt(x, y, z, boxAABB)) { + if (vel.y < 0.0f) { + vel.y = 0.0f; + } + float newy = std::floor(y) + aabb->max().y + half.y; + if (std::abs(newy - pos.y) <= stepHeight) { + pos.y = newy; + } + break; + } + } + } + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + auto boxhalf = box->getHalfSize(); + if (box->getAABB().intersects(boxAABB)) { + vel.y = 0.0f; + float newy = box->position.y + boxhalf.y + half.y; + if (std::abs(newy - pos.y) <= stepHeight + E * 4) { + pos.y = newy; + } + } + } } } -bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) { - const glm::vec3& pos = hitbox->position; - auto half = hitbox->getHalfSize(); - return x >= floor(pos.x-half.x) && x <= floor(pos.x+half.x) && - z >= floor(pos.z-half.z) && z <= floor(pos.z+half.z) && - y >= floor(pos.y-half.y) && y <= floor(pos.y+half.y); +void PhysicsSolver::calcSubstep( + Hitbox& hitbox, glm::vec3& vel, glm::vec3& pos, float dt +) { + auto initpos = pos; + auto half = hitbox.getHalfSize(); + float gravityScale = hitbox.gravityScale; + + if (hitbox.type == BodyType::DYNAMIC) { + calcCollisions( + hitbox, + vel, + pos, + half, + (hitbox.prevGrounded && gravityScale > 0.0f) ? hitbox.stepHeight + : 0.0f, + dt + ); + } + + vel += gravity * dt * gravityScale; + pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; + + // crouching + if (!hitbox.crouching || !hitbox.grounded) { + return; + } + float y = (pos.y - half.y - E); + for (int axis = 0; axis <= 2; axis += 2) { + hitbox.grounded = false; + + auto checkPos = pos; + checkPos.x = axis != 0 ? initpos.x : pos.x; + checkPos.z = axis == 0 ? initpos.z : pos.z; + + AABB boxAABB(checkPos - half, checkPos + half); + boxAABB.scale(glm::vec3(1.0f - E * 4, 1.5f, 1.0f - E * 4)); + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + float x = (pos.x - half.x + E) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++){ + float z = (pos.z - half.z + E) + iz; + if (chunks.isObstacleAt(x, y, z, boxAABB)){ + hitbox.grounded = true; + break; + } + } + } + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + if (box->position.y < pos.y && box->getAABB().intersects(boxAABB)) { + hitbox.grounded = true; + break; + } + } + if (!hitbox.grounded) { + pos[axis] = initpos[axis]; + vel[axis] = 0.0f; + } + } + hitbox.grounded = true; } -bool PhysicsSolver::isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox) { - const float e = 0.001f; // inaccuracy - const glm::vec3& pos = hitbox->position; - auto half = hitbox->getHalfSize(); - const auto& boxes = def->rotatable - ? def->rt.hitboxes[state.rotation] - : def->hitboxes; - for (const auto& block_hitbox : boxes) { - glm::vec3 min = block_hitbox.min(); - glm::vec3 max = block_hitbox.max(); - if (min.x < pos.x+half.x-x-e && max.x > pos.x-half.x-x+e && - min.z < pos.z+half.z-z-e && max.z > pos.z-half.z-z+e && - min.y < pos.y+half.y-y-e && max.y > pos.y-half.y-y+e) - return true; +void PhysicsSolver::step( + const GlobalChunks& chunks, float delta, uint substeps +) { + for (auto hitbox : hitboxes) { + hitbox->groundMaterial.clear(); + hitbox->prevGrounded = hitbox->grounded; + hitbox->grounded = false; + hitbox->prevVelocity = hitbox->velocity; + } + + float dt = delta / static_cast(substeps); + for (uint i = 0; i < substeps; i++) { + for (auto hitbox : hitboxes) { + glm::vec3& pos = hitbox->position; + hitbox->prevPosition = hitbox->position; + calcSubstep(*hitbox, hitbox->velocity, pos, dt); + } + } + + for (auto hitbox : hitboxes) { + float linearDamping = hitbox->linearDamping * hitbox->friction; + + glm::vec3& vel = hitbox->velocity; + auto diff = hitbox->groundVelocity / dt - vel; + vel.x += diff.x * delta * linearDamping; + vel.z += diff.z * delta * linearDamping; + + if (hitbox->verticalDamping > 0.0f) { + vel.y /= 1.0f + delta * linearDamping * hitbox->verticalDamping; + } + if (!hitbox->grounded) { + hitbox->groundVelocity = {}; + } + + updateSensors(*hitbox); + hitbox->friction = glm::abs(hitbox->gravityScale <= 1e-7f) + ? 8.0f + : (!hitbox->prevGrounded ? 2.0f : 10.0f); + } +} + +void PhysicsSolver::updateSensors(Hitbox& hitbox) { + auto aabb = hitbox.getAABB(); + + for (size_t i = 0; i < sensors.size(); i++) { + auto& sensor = *sensors[i]; + if (sensor.entity == hitbox.entity) { + continue; + } + bool triggered = false; + switch (sensor.type) { + case SensorType::AABB: + triggered = aabb.intersects(sensor.calculated.aabb); + break; + case SensorType::RADIUS: + triggered = glm::distance2( + hitbox.position, glm::vec3(sensor.calculated.radial)) + < sensor.calculated.radial.w; + break; + } + if (!triggered) { + continue; + } + if (sensor.prevEntered.find(hitbox.entity) == sensor.prevEntered.end()) { + sensor.enterCallback(sensor.entity, sensor.index, hitbox.entity); + } + sensor.nextEntered.insert(hitbox.entity); } - return false; } void PhysicsSolver::removeSensor(Sensor* sensor) { - sensors.erase(std::remove(sensors.begin(), sensors.end(), sensor), sensors.end()); + sensors.erase( + std::remove(sensors.begin(), sensors.end(), sensor), sensors.end() + ); } diff --git a/src/physics/PhysicsSolver.hpp b/src/physics/PhysicsSolver.hpp index b7810cefd..e616696ca 100644 --- a/src/physics/PhysicsSolver.hpp +++ b/src/physics/PhysicsSolver.hpp @@ -9,35 +9,48 @@ #include class Block; +class Entities; class GlobalChunks; struct Sensor; class PhysicsSolver { - glm::vec3 gravity; - std::vector sensors; public: - PhysicsSolver(glm::vec3 gravity); - void step( - const GlobalChunks& chunks, - Hitbox& hitbox, - float delta, - uint substeps, - entityid_t entity - ); - void colisionCalc( - const GlobalChunks& chunks, - Hitbox& hitbox, - glm::vec3& vel, - glm::vec3& pos, - const glm::vec3 half, - float stepHeight - ); - bool isBlockInside(int x, int y, int z, Hitbox* hitbox); - bool isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox); + PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity); - void setSensors(std::vector sensors) { - this->sensors = std::move(sensors); + void step(const GlobalChunks& chunks, float delta, uint substeps); + + auto& getSensorsWriteable() { + return sensors; + } + + auto& getSolidHitboxesWriteable() { + return solidHitboxes; + } + + auto& getHitboxesWriteable() { + return hitboxes; } void removeSensor(Sensor* sensor); +private: + const GlobalChunks& chunks; + glm::vec3 gravity; + std::vector sensors; + std::vector solidHitboxes; + std::vector hitboxes; + + void calcCollisions( + Hitbox& hitbox, + glm::vec3& vel, + glm::vec3& pos, + const glm::vec3& half, + float stepHeight, + float dt + ); + + void calcSubstep(Hitbox& hitbox, glm::vec3& vel, glm::vec3& pos, float dt); + + bool calcCollisionNegY(Hitbox& hitbox, const glm::vec3& half, float dt); + + void updateSensors(Hitbox& hitbox); }; diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index dc3f6153f..2a85b38bd 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -1,11 +1,5 @@ #include "Chunks.hpp" -#include - -#include -#include -#include - #include "data/StructLayout.hpp" #include "coders/byte_utils.hpp" #include "content/Content.hpp" @@ -20,6 +14,11 @@ #include "VoxelsVolume.hpp" #include "blocks_agent.hpp" +#include +#include +#include +#include + Chunks::Chunks( int32_t w, int32_t d, @@ -67,20 +66,21 @@ const AABB* Chunks::isObstacleAt(float x, float y, float z) const { } } const auto& def = indices.blocks.require(v->id); - if (def.obstacle) { - glm::ivec3 offset {}; - if (v->state.segment) { - glm::ivec3 point(ix, iy, iz); - offset = seekOrigin(point, def, v->state) - point; - } - const auto& boxes = - def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; - for (const auto& hitbox : boxes) { - if (hitbox.contains( - {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} - )) { - return &hitbox; - } + if (!def.obstacle) { + return nullptr; + } + glm::ivec3 offset {}; + if (v->state.segment) { + glm::ivec3 point(ix, iy, iz); + offset = seekOrigin(point, def, v->state) - point; + } + const auto& boxes = + def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; + for (const auto& hitbox : boxes) { + if (hitbox.contains( + {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} + )) { + return &hitbox; } } return nullptr; diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index 4fe24f289..9fdebff1c 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -1,17 +1,16 @@ #pragma once -#include - -#include -#include -#include -#include - #include "typedefs.hpp" #include "voxel.hpp" #include "constants.hpp" #include "util/AreaMap2D.hpp" +#include +#include +#include +#include +#include + class VoxelRenderer; struct AABB; diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 032ae17e2..d24d8b26d 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -214,6 +214,6 @@ void GlobalChunks::putChunk(std::shared_ptr chunk) { chunksMap[keyfrom(chunk->x, chunk->z)] = std::move(chunk); } -const AABB* GlobalChunks::isObstacleAt(float x, float y, float z) const { - return blocks_agent::is_obstacle_at(*this, x, y, z); +std::optional GlobalChunks::isObstacleAt(float x, float y, float z, const AABB& aabb) const { + return blocks_agent::is_obstacle_at(*this, x, y, z, aabb); } diff --git a/src/voxels/GlobalChunks.hpp b/src/voxels/GlobalChunks.hpp index 7120658c2..de62cbc58 100644 --- a/src/voxels/GlobalChunks.hpp +++ b/src/voxels/GlobalChunks.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #define GLM_ENABLE_EXPERIMENTAL @@ -57,7 +58,7 @@ public: void putChunk(std::shared_ptr chunk); - const AABB* isObstacleAt(float x, float y, float z) const; + std::optional isObstacleAt(float x, float y, float z, const AABB& aabb) const; inline Chunk* getChunk(int cx, int cz) const { const auto& found = chunksMap.find(keyfrom(cx, cz)); diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 0742a7293..a86b2e25b 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -13,7 +13,6 @@ #include "voxel.hpp" #include "VoxelsVolume.hpp" -#include #include #include #include @@ -468,8 +467,8 @@ void get_voxels( ); template -inline const AABB* is_obstacle_at( - const Storage& chunks, float x, float y, float z +inline std::optional is_obstacle_at( + const Storage& chunks, float x, float y, float z, const AABB& aabb ) { int ix = std::floor(x); int iy = std::floor(y); @@ -477,30 +476,34 @@ inline const AABB* is_obstacle_at( voxel* v = get(chunks, ix, iy, iz); if (v == nullptr) { if (iy >= CHUNK_H) { - return nullptr; + return std::nullopt; } else { - static const AABB empty; - return ∅ + return AABB(); } } const auto& def = chunks.getContentIndices().blocks.require(v->id); - if (def.obstacle) { - glm::ivec3 offset {}; - if (v->state.segment) { - glm::ivec3 point(ix, iy, iz); - offset = seek_origin(chunks, point, def, v->state) - point; - } - const auto& boxes = - def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; - for (const auto& hitbox : boxes) { - if (hitbox.contains( - {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} - )) { - return &hitbox; - } + if (!def.obstacle) { + return std::nullopt; + } + glm::ivec3 offset {}; + if (v->state.segment) { + glm::ivec3 point(ix, iy, iz); + offset = seek_origin(chunks, point, def, v->state) - point; + } + const auto& boxes = + def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; + + for (const auto& hitbox : boxes) { + if (hitbox.intersects(aabb - glm::ivec3(ix, iy, iz))) { + return hitbox + offset; } } - return nullptr; + return std::nullopt; +} + +template +inline std::optional is_obstacle_at(const Storage& chunks, float x, float y, float z) { + return is_obstacle_at(chunks, x, y, z, AABB({x, y, z}, {x + 1, y + 1, z + 1})); } /// @brief Check block grounding diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 2a1583bd7..e6d9000e1 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -26,7 +26,7 @@ Level::Level( : world(std::move(worldPtr)), content(content), chunks(std::make_unique(*this)), - physics(std::make_unique(glm::vec3(0, -22.6f, 0))), + physics(std::make_unique(*chunks, glm::vec3(0, -22.6f, 0))), events(std::make_unique()), entities(std::make_unique(*this)), players(std::make_unique(*this)), diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index 1f9a2241d..260a28e3c 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -173,7 +173,7 @@ void WorldGenerator::placeStructure( } auto& otherPrototype = *found->second; auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); - if (chunkAABB.intersect(aabb)) { + if (chunkAABB.intersects(aabb)) { otherPrototype.placements.emplace_back( priority, StructurePlacement {