Merge pull request #862 from MihailRis/update-raycast

add advanced raycast function
This commit is contained in:
MihailRis 2026-07-17 02:32:56 +03:00 • committed by GitHub
commit 8d7fab1f2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 447 additions and 162 deletions

47
dev/tests/raycast.lua Normal file
View file

@ -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")

View file

@ -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

View file

@ -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.

View file

@ -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<string|number> = 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<string|number> = 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
```
```

View file

@ -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
```
```

View file

@ -69,4 +69,6 @@ entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [opt
Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.
Соответственно это повлияет на наличие полей *entity* и *block*.
Соответственно это повлияет на наличие полей *entity* и *block*.
Для более тонкой настройки используйте `world.raycast(...)`.

View file

@ -69,4 +69,44 @@ world.save_chunk_data(
-- сжатые данные чанка
data: Bytearray
)
```
-- Бросает луч из точки 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<string|number> = nil,
-- исключающий режим фильтрации:
-- при значении (true) filter_entities определяет список игнорируемых
entities_exclusion: bool = false,
[==[ дополнительные параметры (блоки) ]==]
-- id типов блоков используемых для фильтрации
filter_blocks: table<string|number> = 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 если луч не коснулся блока или сущности
```

View file

@ -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

View file

@ -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"

View file

@ -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<float>(rays - hit) / static_cast<float>(rays);
acoustics.reverbGain = glm::pow(hit / static_cast<float>(rays), 3.0f);

View file

@ -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 =

View file

@ -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<blockid_t> 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);

View file

@ -17,6 +17,8 @@
#include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp"
#include <set>
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<blockid_t> 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<Entities::RaycastResult>& 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<blockid_t>& 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<entitydefid_t>& 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<blockid_t> filteredBlocks {};
std::set<entitydefid_t> 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<blockid_t> 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<l_get_all_in_box>},
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"raycast", lua::wrap<l_raycast>},
{"__world_raycast", lua::wrap<l_world_raycast>},
{"reload_component", lua::wrap<l_reload_component>},
{nullptr, nullptr}
};

View file

@ -166,8 +166,7 @@ std::optional<Entities::RaycastResult> 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<EntityId, Transform, Rigidbody>();
@ -176,10 +175,18 @@ std::optional<Entities::RaycastResult> 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(

View file

@ -5,6 +5,7 @@
#include <optional>
#include <vector>
#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<Entity> 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<entitydefid_t>* 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<RaycastResult> rayCast(
glm::vec3 start,
glm::vec3 dir,
float maxDistance,
entityid_t ignore = -1,
bool solidOnly = false
const RaycastSettings& settings
);
void loadEntities(dv::value map);

View file

@ -215,11 +215,10 @@ voxel* Chunks::rayCast(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> 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
);
}

View file

@ -24,6 +24,10 @@ class VoxelsVolume;
template <int w, int h, int d> 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<blockid_t> filter = {},
bool includeNonSelectable = false
const blocks_agent::RaycastSettings& settings
) const;
glm::vec3 rayCastToObstacle(

View file

@ -223,8 +223,7 @@ static inline voxel* raycast_blocks(
glm::vec3& end,
glm::ivec3& norm,
glm::ivec3& iend,
std::set<blockid_t> 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<float>::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<AABB>& 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<blockid_t> 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<blockid_t> 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

View file

@ -412,6 +412,12 @@ inline void set_rotation(
}
}
struct RaycastSettings {
const std::set<blockid_t>* 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<blockid_t> 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<blockid_t> filter,
bool includeNonSelectable
const RaycastSettings& settings
);
void get_voxels(