diff --git a/dev/tests/raycast.lua b/dev/tests/raycast.lua new file mode 100644 index 000000000..6016989d8 --- /dev/null +++ b/dev/tests/raycast.lua @@ -0,0 +1,47 @@ +local util = require "core:tests_util" + +-- Create world and prepare settings +util.create_demo_world("core:default") +app.set_setting("chunks.load-distance", 3) +app.set_setting("chunks.load-speed", 1) + +-- Create player +local pid = player.create("Xerxes") +player.set_spawnpoint(pid, 0, 100, 0) +player.set_pos(pid, 0, 100, 0) +app.sleep_until(function () return block.get(0, 0, 0) ~= -1 end) +app.sleep_until(function () return block.get(-5, 8, 2) ~= -1 end) + +local sand_idx = block.index("base:sand") +block.set(0, 1, 0, sand_idx) + +local start = {-5, 8, 2} +local target = {0.5, 1.5, 0.5} +local ray = world.raycast({ + start = start, + dir = vec3.normalize(vec3.sub(target, start)), + distance = 10, +}) +debug.print(ray) + +assert(ray, "ray didn't hit") +util.check_int(sand_idx, ray.block, "ray.block") +util.check_vec3({0, 1, 0}, ray.normal, "ray.normal") +util.check_vec3({0.0769, 2, 0.6153}, ray.endpoint, "ray.endpoint") +util.check_vec3({0, 1, 0}, ray.iendpoint, "ray.iendpoint") +util.check_float(7.9807, ray.length, "ray.length") + +ray = world.raycast({ + start = start, + dir = vec3.normalize(vec3.sub(target, start)), + distance = 10, + filter_blocks = {sand_idx}, +}) +debug.print(ray) + +assert(ray, "ray didn't hit") +util.check_int(block.index("core:obstacle"), ray.block, "ray.block") +util.check_vec3({0, 1, 0}, ray.normal, "ray.normal") +util.check_vec3({0.923, 1, 0.3846}, ray.endpoint, "ray.endpoint") +util.check_vec3({0, 0, 0}, ray.iendpoint, "ray.iendpoint") +util.check_float(9.3108, ray.length, "ray.length") diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index db407e92a..4513d57cf 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -159,6 +159,8 @@ The function returns a table with the results or nil if the ray does not hit any The result will use the destination table instead of creating a new one if the optional argument specified. +Use `world.raycast(...)` for more detailed configuration. + ## Model and physics ```lua diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md index d65d95f05..e34cd7a53 100644 --- a/doc/en/scripting/builtins/libentities.md +++ b/doc/en/scripting/builtins/libentities.md @@ -71,3 +71,5 @@ entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [opt The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity. Accordingly, this will affect the presence of the *entity* and *block* fields. + +Use `world.raycast(...)` for more detailed configuration. diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index b2453b34b..54b44fe15 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -70,4 +70,45 @@ world.save_chunk_data( -- compressed chunk data data: Bytearray ) + +-- Casts a ray from the start point in the dir direction. +world.raycast( + params: table { + [==[ required parameters ]==] + -- starting point + start: vec3, + -- ray direction vector + dir: vec3, + -- maximum distance + distance: number, + + [==[ additional parameters (entities) ]==] + -- consider entities + entities: bool = true, + -- uid of the ignored entity + ignore_uid: number = 0 (ENTITY_NONE), + -- id of the entity types used for filtering + filter_entities: table = nil, + -- exclusion filtering mode: + -- if true, filter_entities defines the list of ignored entity types + entities_exclusion: bool = false, + + [==[ additional parameters (blocks) ]==] + -- id of the block types used for filtering + filter_blocks: table = nil, + -- exclusion filtering mode: + -- if true, filter_blocks defines the list Ignored + blocks_exclusion: bool = true, + -- include blocks with selectable=false + nonselect_blocks: bool = false; + } +) -> { + block: int or nil, -- block id + entity: int or nil, -- entity uid + endpoint: vec3, -- ray touchpoint + iendpoint: vec3, -- position of the block touched by the ray + length: number, -- ray length + normal: vec3, -- normal vector of the surface touched by the ray +} or nil if the ray did not touch a block or entity +``` ``` diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md index b7af3e462..9725d2b3d 100644 --- a/doc/ru/scripting/builtins/libblock.md +++ b/doc/ru/scripting/builtins/libblock.md @@ -115,6 +115,8 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент. +Для более тонкой настройки используйте `world.raycast(...)`. + ## Вращение ```lua @@ -209,4 +211,4 @@ block.get_field( name: string, [опционально] index: int = 0 ) -> any | nil -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md index f37dc1b32..27d8d03b1 100644 --- a/doc/ru/scripting/builtins/libentities.md +++ b/doc/ru/scripting/builtins/libentities.md @@ -69,4 +69,6 @@ entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [opt Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности. -Соответственно это повлияет на наличие полей *entity* и *block*. \ No newline at end of file +Соответственно это повлияет на наличие полей *entity* и *block*. + +Для более тонкой настройки используйте `world.raycast(...)`. diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index 215329df3..6b945aa76 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -69,4 +69,44 @@ world.save_chunk_data( -- сжатые данные чанка data: Bytearray ) -``` \ No newline at end of file + +-- Бросает луч из точки start в направлении dir. +world.raycast( + params: table { + [==[ обязательные параметры ]==] + -- стартовая точка + start: vec3, + -- вектор направления луча + dir: vec3, + -- максимальное расстояние + distance: number, + + [==[ дополнительные параметры (сущности) ]==] + -- учитывать сущности + entities: bool = true, + -- uid игнорируемой сущности + ignore_uid: number = 0 (ENTITY_NONE), + -- id типов сущностей используемых для фильтрации + filter_entities: table = nil, + -- исключающий режим фильтрации: + -- при значении (true) filter_entities определяет список игнорируемых + entities_exclusion: bool = false, + + [==[ дополнительные параметры (блоки) ]==] + -- id типов блоков используемых для фильтрации + filter_blocks: table = nil, + -- исключающий режим фильтрации: + -- при значении (true) filter_blocks определяет список игнорируемых + blocks_exclusion: bool = true, + -- учитывать блоки с selectable=false + nonselect_blocks: bool = false; + } +) -> { + block: int или nil, -- id блока + entity: int или nil, -- uid сущности + endpoint: vec3, -- точка касания луча + iendpoint: vec3, -- позиция блока, которого касается луч + length: number, -- длина луча + normal: vec3, -- вектор нормали поверхности, которой касается луч +} или nil если луч не коснулся блока или сущности +``` diff --git a/res/modules/tests_util.lua b/res/modules/tests_util.lua index 1e2207c24..e4ab73bc3 100644 --- a/res/modules/tests_util.lua +++ b/res/modules/tests_util.lua @@ -5,4 +5,25 @@ function util.create_demo_world(generator) app.new_world("demo", "2019", generator or "core:default") end +function util.check_vec3(expected, fact, name, delta) + delta = delta or 1e-4 + for i=1,3 do + assert(math.abs(fact[i] - expected[i]) < delta, + name.."["..i.."] expected: "..expected[i]..", fact: "..fact[i]) + end +end + +function util.check_float(expected, fact, name, delta) + delta = delta or 1e-4 + local actual_delta = math.abs(fact - expected) + assert(actual_delta < delta, + name.." expected: "..expected..", fact: "..fact.. + " (delta: "..actual_delta.." > "..delta..")") +end + +function util.check_int(expected, fact, name) + assert(fact == expected, + name.." expected: "..expected..", fact: "..fact) +end + return util diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 093501d75..b658fe096 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -312,6 +312,8 @@ entities.get_all = function(uids) return stdcomp.get_all(uids) end end +world.raycast = entities.__world_raycast +entities.__world_raycast = nil __vc_scripts_registry = require "core:internal/scripts_registry" 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..64e8e126e 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,59 @@ 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, + bool entities +) { glm::vec3 end; glm::ivec3 normal; glm::ivec3 iend; @@ -187,61 +214,139 @@ 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); + auto ray = + entities + ? level->entities->rayCast(start, dir, maxDistance, entityRaycast) + : std::nullopt; + 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); + if (lua::isnumber(L, -1)) { + dst.insert(lua::touinteger(L, -1)); } else { - lua::createtable(L, 0, 6); + 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); + if (lua::isnumber(L, -1)) { + dst.insert(lua::touinteger(L, -1)); + } else { + 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_world_raycast(lua::State* L) { + blocks_agent::RaycastSettings blocksRaycast {}; + Entities::RaycastSettings entityRaycast {}; + + std::set filteredBlocks {}; + std::set filteredEntities {}; + + glm::vec3 start, dir; + float maxDistance; + bool entities = true; + + 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, "entities", 1)) { + entities = lua::toboolean(L, -1); + lua::pop(L); + } + + if (lua::getfield(L, "ignore_uid", 1)) { + entityRaycast.ignoredUid = lua::tointeger(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "filter_entities")) { + load_entities_filter(L, -1, filteredEntities); + blocksRaycast.filter = &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); + blocksRaycast.filter = &filteredBlocks; + lua::pop(L); + } + if (lua::getfield(L, "blocks_exclusion")) { + blocksRaycast.blocksFilterExcludeMode = lua::toboolean(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "nonselect_blocks", 1)) { + blocksRaycast.includeNonSelectable = lua::toboolean(L, -1); + lua::pop(L); + } + return l_raycast( + L, start, dir, maxDistance, blocksRaycast, entityRaycast, entities + ); +} + +static int l_raycast(lua::State* L) { + blocks_agent::RaycastSettings blocksRaycast {}; + Entities::RaycastSettings entityRaycast {}; + std::set filteredBlocks {}; + + const int luaStackSize = lua::gettop(L); + glm::vec3 start = lua::tovec<3>(L, 1); + glm::vec3 dir = lua::tovec<3>(L, 2); + float 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); + blocksRaycast.filter = &filteredBlocks; + } else { + throw std::runtime_error("table expected for filter"); + } + } + if (luaStackSize >= 7) { + blocksRaycast.includeNonSelectable = lua::toboolean(L, 6); + } + return l_raycast( + L, start, dir, maxDistance, blocksRaycast, entityRaycast, true + ); } static int l_reload_component(lua::State* L) { @@ -278,6 +383,7 @@ const luaL_Reg entitylib[] = { {"get_all_in_box", lua::wrap}, {"get_all_in_radius", lua::wrap}, {"raycast", lua::wrap}, + {"__world_raycast", lua::wrap}, {"reload_component", lua::wrap}, {nullptr, nullptr} }; 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..9d1f0cc17 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; @@ -240,9 +239,8 @@ static inline voxel* raycast_blocks( int iy = std::floor(py); int iz = std::floor(pz); - int stepx = (dx > 0.0f) ? 1 : -1; - int stepy = (dy > 0.0f) ? 1 : -1; - int stepz = (dz > 0.0f) ? 1 : -1; + glm::ivec3 step { + (dx > 0.0f) ? 1 : -1, (dy > 0.0f) ? 1 : -1, (dz > 0.0f) ? 1 : -1}; constexpr float infinity = std::numeric_limits::infinity(); constexpr float epsilon = 1e-6f; // 0.000001 @@ -250,31 +248,31 @@ static inline voxel* raycast_blocks( float tyDelta = (std::fabs(dy) < epsilon) ? infinity : std::fabs(1.0f / dy); float tzDelta = (std::fabs(dz) < epsilon) ? infinity : std::fabs(1.0f / dz); - float xdist = (stepx > 0) ? (ix + 1 - px) : (px - ix); - float ydist = (stepy > 0) ? (iy + 1 - py) : (py - iy); - float zdist = (stepz > 0) ? (iz + 1 - pz) : (pz - iz); + float xdist = (step.x > 0) ? (ix + 1 - px) : (px - ix); + float ydist = (step.y > 0) ? (iy + 1 - py) : (py - iy); + float zdist = (step.z > 0) ? (iz + 1 - pz) : (pz - iz); float txMax = (txDelta < infinity) ? txDelta * xdist : infinity; float tyMax = (tyDelta < infinity) ? tyDelta * ydist : infinity; 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())) { - end.x = px + t * dx; - end.y = py + t * dy; - end.z = pz + t * dz; - iend.x = ix; - iend.y = iy; - iend.z = iz; + if (voxel->id != BLOCK_AIR && + (def.selectable || includeNonSelectable) && + ((filter == nullptr || filter->find(def.rt.id) == filter->end()) == + settings.blocksFilterExcludeMode)) { + end = start + t * dir; + iend = {ix, iy, iz}; if (!def.rt.solid) { const std::vector& hitboxes = @@ -309,51 +307,41 @@ static inline voxel* raycast_blocks( if (hit) return voxel; } else { - iend.x = ix; - iend.y = iy; - iend.z = iz; - - norm.x = norm.y = norm.z = 0; - if (steppedIndex == 0) norm.x = -stepx; - if (steppedIndex == 1) norm.y = -stepy; - if (steppedIndex == 2) norm.z = -stepz; + iend = {ix, iy, iz}; + norm = {0, 0, 0}; + norm[steppedIndex] = -step[steppedIndex]; return voxel; } } if (txMax < tyMax) { if (txMax < tzMax) { - ix += stepx; + ix += step.x; t = txMax; txMax += txDelta; steppedIndex = 0; } else { - iz += stepz; + iz += step.z; t = tzMax; tzMax += tzDelta; steppedIndex = 2; } } else { if (tyMax < tzMax) { - iy += stepy; + iy += step.y; t = tyMax; tyMax += tyDelta; steppedIndex = 1; } else { - iz += stepz; + iz += step.z; t = tzMax; tzMax += tzDelta; steppedIndex = 2; } } } - iend.x = ix; - iend.y = iy; - iend.z = iz; - - end.x = px + t * dx; - end.y = py + t * dy; - end.z = pz + t * dz; - norm.x = norm.y = norm.z = 0; + iend = {ix, iy, iz}; + end = start + t * dir; + norm = {0, 0, 0}; return nullptr; } @@ -365,10 +353,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 +366,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..22144c618 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -412,6 +412,12 @@ inline void set_rotation( } } +struct RaycastSettings { + const std::set* filter = nullptr; + bool blocksFilterExcludeMode = true; + 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 +437,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 +459,7 @@ voxel* raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter, - bool includeNonSelectable + const RaycastSettings& settings ); void get_voxels(