diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index b988898ce..1fa2abd0b 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -22,6 +22,7 @@ #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" +#include "voxels/blocks_agent.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" #include "WorldRenderer.hpp" @@ -98,7 +99,7 @@ void Decorator::updateAcoustics(const Camera& camera) { auto& chunks = *player.chunks; const auto& start = camera.position; - float rayLength = 40.0f; + float length = 40.0f; auto& blocks = level.content.getIndices()->blocks; @@ -107,7 +108,7 @@ void Decorator::updateAcoustics(const Camera& camera) { float averageDistance = 0.0f; float averageAbsorption = 0.0f; float speedOfSound = 300.0f; - float minDistance = rayLength; + float minDistance = length; for (int i = 0; i < rays; i++) { float u1 = random.randFloat(); float u2 = random.randFloat(); @@ -121,12 +122,13 @@ void Decorator::updateAcoustics(const Camera& camera) { glm::vec3 end; glm::ivec3 norm; glm::ivec3 iend; - auto vox = chunks.rayCast(start, dir, rayLength, end, norm, iend); + blocks_agent::RaycastSettings raycast {}; + auto vox = chunks.rayCast(start, dir, length, end, norm, iend, raycast); if (vox == nullptr) { continue; } auto distance = glm::distance(start, end); - if (distance >= rayLength * 0.98f) { + if (distance >= length * 0.98f) { continue; } if (distance < minDistance) { @@ -149,7 +151,7 @@ void Decorator::updateAcoustics(const Camera& camera) { averageAbsorption /= hit; } - float decayTime = 20.0f * (averageDistance / rayLength); + float decayTime = 20.0f * (averageDistance / length); float escapeRatio = static_cast(rays - hit) / static_cast(rays); acoustics.reverbGain = glm::pow(hit / static_cast(rays), 3.0f); diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 909ca3778..03abc07b2 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -200,8 +200,13 @@ void CameraControl::update( auto castRay = [](Player& player, Camera& camera, const glm::vec3& front) -> glm::vec3 { auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f); + + Entities::RaycastSettings raycast {}; + raycast.ignoredUid = player.getEntity(); + raycast.solidEntitiesOnly = true; + auto entityRay = player.getLevel().entities->rayCast( - camera.position, front, 3.0f, player.getEntity(), true + camera.position, front, 3.0f, raycast ); if (entityRay.has_value() && entityRay->distance < glm::distance(camera.position, blockEnd)) { @@ -350,7 +355,13 @@ voxel* PlayerController::updateSelection(float maxDistance) { glm::ivec3 iend; glm::ivec3 norm; voxel* vox = chunks.rayCast( - camera->position, camera->front, maxDistance, end, norm, iend + camera->position, + camera->front, + maxDistance, + end, + norm, + iend, + blocks_agent::RaycastSettings {} ); if (vox) { maxDistance = glm::distance(camera->position, end); @@ -358,8 +369,12 @@ voxel* PlayerController::updateSelection(float maxDistance) { auto prevEntity = selection.entity; selection.entity = ENTITY_NONE; selection.actualPosition = iend; + + Entities::RaycastSettings raycast {}; + raycast.ignoredUid = player.getEntity(); + if (auto result = level.entities->rayCast( - camera->position, camera->front, maxDistance, player.getEntity() + camera->position, camera->front, maxDistance, raycast )) { selection.entity = result->entity; selection.hitPosition = diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 6c9550dde..fc26ced38 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -543,13 +543,16 @@ static int l_destruct(lua::State* L) { } static int l_raycast(lua::State* L) { - auto& level = require_level(); + const auto& level = require_level(); auto start = lua::tovec<3>(L, 1); auto dir = lua::tovec<3>(L, 2); auto maxDistance = lua::tonumber(L, 3); - bool includeNonSelectable = false; + + blocks_agent::RaycastSettings raycast {}; std::set filteredBlocks {}; + raycast.filter = &filteredBlocks; + const int luaStackSize = lua::gettop(L); if (luaStackSize >= 5) { if (lua::istable(L, 5)) { @@ -568,21 +571,14 @@ static int l_raycast(lua::State* L) { } } if (luaStackSize >= 6) { - includeNonSelectable = lua::toboolean(L, 6); + raycast.includeNonSelectable = lua::toboolean(L, 6); } glm::vec3 end; glm::ivec3 normal; glm::ivec3 iend; + if (auto voxel = blocks_agent::raycast( - *level.chunks, - start, - dir, - maxDistance, - end, - normal, - iend, - filteredBlocks, - includeNonSelectable + *level.chunks, start, dir, maxDistance, end, normal, iend, raycast )) { if (luaStackSize >= 4 && !lua::isnil(L, 4)) { lua::pushvalue(L, 4); diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index d2c0c4bc5..ebf70d485 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -17,6 +17,8 @@ #include "voxels/blocks_agent.hpp" #include "window/Camera.hpp" +#include + using namespace scripting; static const EntityDef* require_entity_def(lua::State* L) { @@ -145,34 +147,58 @@ static int l_get_all_in_radius(lua::State* L) { return 1; } -static int l_raycast(lua::State* L) { - auto start = lua::tovec<3>(L, 1); - auto dir = lua::tovec<3>(L, 2); - auto maxDistance = lua::tonumber(L, 3); - auto ignoreEntityId = lua::tointeger(L, 4); - bool includeNonSelectable = false; - std::set filteredBlocks {}; - const int luaStackSize = lua::gettop(L); - if (luaStackSize >= 6) { - if (lua::istable(L, 6)) { - int addLen = lua::objlen(L, 6); - for (int i = 0; i < addLen; i++) { - lua::rawgeti(L, i + 1, 6); - auto blockName = std::string(lua::tostring(L, -1)); - const Block* block = content->blocks.find(blockName); - if (block != nullptr) { - filteredBlocks.insert(block->rt.id); - } - lua::pop(L); - } - } else { - throw std::runtime_error("table expected for filter"); - } +static int l_push_ray( + lua::State* L, + const std::optional& ray, + blockid_t block, + const glm::vec3& start, + const glm::vec3& dir, + const glm::vec3& end, + const glm::ivec3& iend, + const glm::ivec3& normal +) { + if (!ray.has_value() && block == BLOCK_VOID) { + return 0; } - if (luaStackSize >= 7) { - includeNonSelectable = lua::toboolean(L, 6); + if (lua::gettop(L) >= 5 && lua::istable(L, 5)) { + lua::pushvalue(L, 5); + } else { + lua::createtable(L, 0, 6); } + lua::pushvec3(L, ray ? (start + dir * ray->distance) : end); + lua::setfield(L, "endpoint"); + + lua::pushvec3(L, ray ? ray->normal : normal); + lua::setfield(L, "normal"); + + lua::pushnumber(L, glm::distance(start, end)); + lua::setfield(L, "length"); + + lua::pushvec3(L, iend); + lua::setfield(L, "iendpoint"); + + lua::pushinteger(L, block); + lua::setfield(L, "block"); + + if (ray.has_value()) { + lua::pushinteger(L, ray->entity); + lua::setfield(L, "entity"); + } else if (block != BLOCK_VOID) { + lua::pushinteger(L, block); + lua::setfield(L, "block"); + } + return 1; +} + +static int l_raycast( + lua::State* L, + const glm::vec3& start, + const glm::vec3& dir, + float maxDistance, + const blocks_agent::RaycastSettings& voxelRaycast, + const Entities::RaycastSettings& entityRaycast +) { glm::vec3 end; glm::ivec3 normal; glm::ivec3 iend; @@ -187,61 +213,108 @@ static int l_raycast(lua::State* L) { end, normal, iend, - filteredBlocks, - includeNonSelectable + voxelRaycast )) { maxDistance = glm::distance(start, end); block = voxel->id; } - if (auto ray = - level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) { - if (luaStackSize >= 5 && !lua::isnil(L, 5)) { - lua::pushvalue(L, 5); - } else { - lua::createtable(L, 0, 6); + auto ray = level->entities->rayCast(start, dir, maxDistance, entityRaycast); + return l_push_ray(L, ray, block, start, dir, end, iend, normal); +} + +static void load_blocks_filter(lua::State* L, int idx, std::set& dst) { + int count = lua::objlen(L, idx); + for (int i = 0; i < count; i++) { + lua::rawgeti(L, i + 1, idx); + auto blockName = std::string(lua::tostring(L, -1)); + const Block* block = content->blocks.find(blockName); + if (block != nullptr) { + dst.insert(block->rt.id); } - - lua::pushvec3(L, start + dir * ray->distance); - lua::setfield(L, "endpoint"); - - lua::pushvec3(L, ray->normal); - lua::setfield(L, "normal"); - - lua::pushnumber(L, glm::distance(start, end)); - lua::setfield(L, "length"); - - lua::pushvec3(L, iend); - lua::setfield(L, "iendpoint"); - - lua::pushinteger(L, block); - lua::setfield(L, "block"); - - lua::pushinteger(L, ray->entity); - lua::setfield(L, "entity"); - return 1; - } else if (block != BLOCK_VOID) { - if (lua::gettop(L) >= 5 && !lua::isnil(L, 5)) { - lua::pushvalue(L, 5); - } else { - lua::createtable(L, 0, 5); - } - lua::pushvec3(L, end); - lua::setfield(L, "endpoint"); - - lua::pushvec3(L, normal); - lua::setfield(L, "normal"); - - lua::pushnumber(L, glm::distance(start, end)); - lua::setfield(L, "length"); - - lua::pushvec3(L, iend); - lua::setfield(L, "iendpoint"); - - lua::pushinteger(L, block); - lua::setfield(L, "block"); - return 1; + lua::pop(L); } - return 0; +} + +static void load_entities_filter(lua::State* L, int idx, std::set& dst) { + int count = lua::objlen(L, idx); + for (int i = 0; i < count; i++) { + lua::rawgeti(L, i + 1, idx); + auto entityName = std::string(lua::tostring(L, -1)); + const EntityDef* entity = content->entities.find(entityName); + if (entity != nullptr) { + dst.insert(entity->rt.id); + } + lua::pop(L); + } +} + +static int l_raycast(lua::State* L) { + blocks_agent::RaycastSettings blocksRaycast {}; + Entities::RaycastSettings entityRaycast {}; + + std::set filteredBlocks {}; + std::set filteredEntities {}; + + glm::vec3 start, dir; + float maxDistance; + + const int luaStackSize = lua::gettop(L); + if (luaStackSize == 1) { + if (!lua::istable(L, 1)) { + throw std::runtime_error("invalid function overload"); + } + + lua::requirefield(L, "start", 1); + start = lua::tovec3(L, -1); + + lua::requirefield(L, "dir", 1); + dir = lua::tovec3(L, -1); + + lua::requirefield(L, "distance", 1); + maxDistance = lua::tonumber(L, -1); + + lua::pop(L, 3); + + if (lua::getfield(L, "ignore_uid", 1)) { + entityRaycast.ignoredUid = lua::tointeger(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "ignore_entities")) { + load_entities_filter(L, -1, filteredEntities); + lua::pop(L); + } + if (lua::getfield(L, "entities_exclusion")) { + entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "filter_blocks", 1)) { + load_blocks_filter(L, -1, filteredBlocks); + lua::pop(L); + } + } else { + start = lua::tovec<3>(L, 1); + dir = lua::tovec<3>(L, 2); + maxDistance = lua::tonumber(L, 3); + + entityRaycast.ignoredUid = lua::tointeger(L, 4); + if (luaStackSize >= 6) { + if (lua::istable(L, 6)) { + load_blocks_filter(L, 6, filteredBlocks); + } else { + throw std::runtime_error("table expected for filter"); + } + } + if (luaStackSize >= 7) { + blocksRaycast.includeNonSelectable = lua::toboolean(L, 6); + } + } + if (!filteredBlocks.empty()) { + blocksRaycast.filter = &filteredBlocks; + } + if (!filteredEntities.empty()) { + blocksRaycast.filter = &filteredEntities; + } + return l_raycast(L, start, dir, maxDistance, blocksRaycast, entityRaycast); } static int l_reload_component(lua::State* L) { diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 61b312a24..fafd99fe5 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -166,8 +166,7 @@ std::optional Entities::rayCast( glm::vec3 start, glm::vec3 dir, float maxDistance, - entityid_t ignore, - bool solidOnly + const RaycastSettings& settings ) { Ray ray(start, dir); auto view = registry->view(); @@ -176,10 +175,18 @@ std::optional Entities::rayCast( glm::ivec3 foundNormal; for (auto [entity, eid, transform, body] : view.each()) { - if (eid.uid == ignore || !body.enabled || (solidOnly && !eid.def.solid)) { + if (eid.uid == settings.ignoredUid || !body.enabled || + (settings.solidEntitiesOnly && !eid.def.solid)) { continue; } - auto& hitbox = body.hitbox; + if (settings.entitiesFilter) { + bool matches = settings.entitiesFilter->find(eid.def.rt.id) != + settings.entitiesFilter->end(); + if (matches == settings.entityFilterExcludeMode) { + continue; + } + } + const auto& hitbox = body.hitbox; glm::ivec3 normal; double distance; if (ray.intersectAABB( diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 33786118a..0d003ffb1 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -5,6 +5,7 @@ #include #include +#include "constants.hpp" #include "physics/Hitbox.hpp" #include "Transform.hpp" #include "Rigidbody.hpp" @@ -46,12 +47,6 @@ class Entities final { ); void preparePhysics(float delta); public: - struct RaycastResult { - entityid_t entity; - glm::ivec3 normal; - float distance; - }; - Entities(Level& level); ~Entities(); @@ -82,20 +77,33 @@ public: std::optional get(entityid_t id); + struct RaycastSettings { + /// @param ignore Ignored entity uid + entityid_t ignoredUid; + /// @param solidEntitiesOnly If true, only entities with solid hitboxes + /// will be checked + bool solidEntitiesOnly = false; + bool entityFilterExcludeMode = false; + const std::set* entitiesFilter = nullptr; + }; + + struct RaycastResult { + entityid_t entity; + glm::ivec3 normal; + float distance; + }; + /// @brief Entities raycast. No blocks check included, use combined with /// Chunks.rayCast /// @param start Ray start /// @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, - bool solidOnly = false + const RaycastSettings& settings ); void loadEntities(dv::value map); diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index 2a85b38bd..615ed2fbb 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -215,11 +215,10 @@ voxel* Chunks::rayCast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const blocks_agent::RaycastSettings& settings ) const { return blocks_agent::raycast( - *this, start, dir, maxDist, end, norm, iend, std::move(filter), includeNonSelectable + *this, start, dir, maxDist, end, norm, iend, settings ); } diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index 9fdebff1c..8b94026cb 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -24,6 +24,10 @@ class VoxelsVolume; template class StaticVoxelsVolume; +namespace blocks_agent { + struct RaycastSettings; +} + /// Player-centred chunks matrix class Chunks { LevelEvents* events; @@ -112,8 +116,7 @@ public: glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter = {}, - bool includeNonSelectable = false + const blocks_agent::RaycastSettings& settings ) const; glm::vec3 rayCastToObstacle( diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index 436372457..eb96c5e2a 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -223,8 +223,7 @@ static inline voxel* raycast_blocks( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ) { const auto& blocks = chunks.getContentIndices().blocks; float px = start.x; @@ -259,16 +258,20 @@ static inline voxel* raycast_blocks( float tzMax = (tzDelta < infinity) ? tzDelta * zdist : infinity; int steppedIndex = -1; - + bool includeNonSelectable = settings.includeNonSelectable; + while (t <= maxDist) { voxel* voxel = get(chunks, ix, iy, iz); if (voxel == nullptr) { return nullptr; } + auto filter = settings.filter; const auto& def = blocks.require(voxel->id); - if (voxel->id != BLOCK_AIR && (def.selectable || includeNonSelectable) && - (filter.empty() || filter.find(def.rt.id) == filter.end())) { + if (voxel->id != BLOCK_AIR + && (def.selectable || includeNonSelectable) + && (filter == nullptr || filter->empty() || + filter->find(def.rt.id) == filter->end())) { end.x = px + t * dx; end.y = py + t * dy; end.z = pz + t * dz; @@ -365,10 +368,9 @@ voxel* blocks_agent::raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ) { - return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable); + return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, settings); } voxel* blocks_agent::raycast( @@ -379,10 +381,9 @@ voxel* blocks_agent::raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ) { - return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable); + return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, settings); } // reduce nesting on next modification diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index a86b2e25b..3ac4c74d5 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -412,6 +412,11 @@ inline void set_rotation( } } +struct RaycastSettings { + const std::set* filter = nullptr; + bool includeNonSelectable = false; +}; + /// @brief Cast ray to a selectable block with filter based on id. /// @param chunks chunks matrix /// @param start ray start position @@ -431,8 +436,7 @@ voxel* raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ); /// @brief Cast ray to a selectable block with filter based on id. @@ -454,8 +458,7 @@ voxel* raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ); void get_voxels(