Merge branch 'MihailRis:main' into main

This commit is contained in:
Astazium 2026-07-17 12:23:48 +03:00 • committed by GitHub
commit 73229e34fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 456 additions and 169 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

@ -9,14 +9,14 @@ Particles are a table, all fields of which are optional.
| lighting | Lighting. | true | | lighting | Lighting. | true |
| collision | Collision detection. | true | | collision | Collision detection. | true |
| max_distance | Maximum distance from the camera at which particles may spawn. | 16.0 | | max_distance | Maximum distance from the camera at which particles may spawn. | 16.0 |
| spawn_interval | Particle spawn interval in seconds. | 1.0 | | spawn_interval | Particle spawn interval in seconds. Also initial spawn delay | 1.0 |
| lifetime | Average lifetime of particles in seconds. | 5.0 | | lifetime | Average lifetime of particles in seconds. | 5.0 |
| lifetime_spread | Maximum deviation of particle lifetime (from 0.0 to 1.0). | 0.2 | | lifetime_spread | Maximum deviation of particle lifetime (from 0.0 to 1.0). | 0.2 |
| velocity | Initial linear velocity of particles. | {0, 0, 0} | | velocity | Initial linear velocity of particles. | {0, 0, 0} |
| acceleration | Particles acceleration. | {0, -16, 0} | | acceleration | Particles acceleration. | {0, -16, 0} |
| explosion | Force of particles explosion on spawn. | {2, 2, 2} | | explosion | Force of particles explosion on spawn. | {2, 2, 2} |
| size | Size of particles. | {0.1, 0.1, 0.1} | | size | Size of particles. | {0.1, 0.1, 0.1} |
| size_spread | Maximum particle size spread over time. | 0.2 | | size_spread | Maximum particle size spread. | 0.2 |
| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 | | angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 |
| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 | | min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 |
| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 | | max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 |
@ -35,7 +35,8 @@ gfx.particles.emit(
count: int, count: int,
-- particle settings table -- particle settings table
preset: table, preset: table,
-- additional particle settings table -- particle settings table overriding 'preset' parameters
-- use it instead of 'presets' table copying for modification
[optional] extension: table [optional] extension: table
) -> int ) -> int
``` ```
@ -63,7 +64,7 @@ Returns the static position or uid of the entity the emitter is bound to.
Returns nil if the emitter does not exist. Returns nil if the emitter does not exist.
``lua ```lua
gfx.particles.set_origin(id: int, origin: vec3 | int) gfx.particles.set_origin(id: int, origin: vec3 | int)
``` ```

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. 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 ## Model and physics
```lua ```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. 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. 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 -- compressed chunk data
data: Bytearray 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

@ -10,14 +10,14 @@
| lighting | Освещение. | true | | lighting | Освещение. | true |
| collision | Обнаружение столкновений. | true | | collision | Обнаружение столкновений. | true |
| max_distance | Максимальная дистанция от камеры, при которой происходит спавн частиц. | 16.0 | | max_distance | Максимальная дистанция от камеры, при которой происходит спавн частиц. | 16.0 |
| spawn_interval | Интервал спавна частиц в секундах. | 1.0 | | spawn_interval | Интервал спавна частиц в секундах, также задержка перед спавном первой. | 1.0 |
| lifetime | Среднее время жизни частиц в секундах. | 5.0 | | lifetime | Среднее время жизни частиц в секундах. | 5.0 |
| lifetime_spread | Максимальное отклонение времени жизни частицы (от 0.0 до 1.0). | 0.2 | | lifetime_spread | Максимальное отклонение времени жизни частицы (от 0.0 до 1.0). | 0.2 |
| velocity | Начальная линейная скорость частиц. | {0, 0, 0} | | velocity | Начальная линейная скорость частиц. | {0, 0, 0} |
| acceleration | Ускорение частиц. | {0, -16, 0} | | acceleration | Ускорение частиц. | {0, -16, 0} |
| explosion | Сила разлёта частиц при спавне. | {2, 2, 2} | | explosion | Сила разлёта частиц при спавне. | {2, 2, 2} |
| size | Размер частиц. | {0.1, 0.1, 0.1} | | size | Размер частиц. | {0.1, 0.1, 0.1} |
| size_spread | Максимальное отклонение времени размера частиц. | 0.2 | | size_spread | Максимальное отклонение размера частиц. | 0.2 |
| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 | | angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 |
| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | | min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | | max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
@ -36,7 +36,8 @@ gfx.particles.emit(
count: int, count: int,
-- таблица настроек частиц -- таблица настроек частиц
preset: table, preset: table,
-- дополнительная таблица настроек частиц -- таблица настроек частиц перезаписывающая значения параметров
-- используется вместо копирования и модификации 'preset'
[опционально] extension: table [опционально] extension: table
) -> int ) -> int
``` ```

View file

@ -115,6 +115,8 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ
Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент. Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент.
Для более тонкой настройки используйте `world.raycast(...)`.
## Вращение ## Вращение
```lua ```lua
@ -209,4 +211,4 @@ block.get_field(
name: string, name: string,
[опционально] index: int = 0 [опционально] index: int = 0
) -> any | nil ) -> 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). Возвращает таблицу с результатами если луч касается блока, либо сущности. Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.
Соответственно это повлияет на наличие полей *entity* и *block*. Соответственно это повлияет на наличие полей *entity* и *block*.
Для более тонкой настройки используйте `world.raycast(...)`.

View file

@ -69,4 +69,44 @@ world.save_chunk_data(
-- сжатые данные чанка -- сжатые данные чанка
data: Bytearray 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") app.new_world("demo", "2019", generator or "core:default")
end 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 return util

View file

@ -312,6 +312,8 @@ entities.get_all = function(uids)
return stdcomp.get_all(uids) return stdcomp.get_all(uids)
end end
end end
world.raycast = entities.__world_raycast
entities.__world_raycast = nil
__vc_scripts_registry = require "core:internal/scripts_registry" __vc_scripts_registry = require "core:internal/scripts_registry"

View file

@ -22,6 +22,7 @@
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "WorldRenderer.hpp" #include "WorldRenderer.hpp"
@ -98,7 +99,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
auto& chunks = *player.chunks; auto& chunks = *player.chunks;
const auto& start = camera.position; const auto& start = camera.position;
float rayLength = 40.0f; float length = 40.0f;
auto& blocks = level.content.getIndices()->blocks; auto& blocks = level.content.getIndices()->blocks;
@ -107,7 +108,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
float averageDistance = 0.0f; float averageDistance = 0.0f;
float averageAbsorption = 0.0f; float averageAbsorption = 0.0f;
float speedOfSound = 300.0f; float speedOfSound = 300.0f;
float minDistance = rayLength; float minDistance = length;
for (int i = 0; i < rays; i++) { for (int i = 0; i < rays; i++) {
float u1 = random.randFloat(); float u1 = random.randFloat();
float u2 = random.randFloat(); float u2 = random.randFloat();
@ -121,12 +122,13 @@ void Decorator::updateAcoustics(const Camera& camera) {
glm::vec3 end; glm::vec3 end;
glm::ivec3 norm; glm::ivec3 norm;
glm::ivec3 iend; 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) { if (vox == nullptr) {
continue; continue;
} }
auto distance = glm::distance(start, end); auto distance = glm::distance(start, end);
if (distance >= rayLength * 0.98f) { if (distance >= length * 0.98f) {
continue; continue;
} }
if (distance < minDistance) { if (distance < minDistance) {
@ -149,7 +151,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
averageAbsorption /= hit; 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); float escapeRatio = static_cast<float>(rays - hit) / static_cast<float>(rays);
acoustics.reverbGain = glm::pow(hit / static_cast<float>(rays), 3.0f); 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) auto castRay = [](Player& player, Camera& camera, const glm::vec3& front)
-> glm::vec3 { -> glm::vec3 {
auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f); 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( auto entityRay = player.getLevel().entities->rayCast(
camera.position, front, 3.0f, player.getEntity(), true camera.position, front, 3.0f, raycast
); );
if (entityRay.has_value() && if (entityRay.has_value() &&
entityRay->distance < glm::distance(camera.position, blockEnd)) { entityRay->distance < glm::distance(camera.position, blockEnd)) {
@ -350,7 +355,13 @@ voxel* PlayerController::updateSelection(float maxDistance) {
glm::ivec3 iend; glm::ivec3 iend;
glm::ivec3 norm; glm::ivec3 norm;
voxel* vox = chunks.rayCast( 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) { if (vox) {
maxDistance = glm::distance(camera->position, end); maxDistance = glm::distance(camera->position, end);
@ -358,8 +369,12 @@ voxel* PlayerController::updateSelection(float maxDistance) {
auto prevEntity = selection.entity; auto prevEntity = selection.entity;
selection.entity = ENTITY_NONE; selection.entity = ENTITY_NONE;
selection.actualPosition = iend; selection.actualPosition = iend;
Entities::RaycastSettings raycast {};
raycast.ignoredUid = player.getEntity();
if (auto result = level.entities->rayCast( if (auto result = level.entities->rayCast(
camera->position, camera->front, maxDistance, player.getEntity() camera->position, camera->front, maxDistance, raycast
)) { )) {
selection.entity = result->entity; selection.entity = result->entity;
selection.hitPosition = selection.hitPosition =

View file

@ -543,13 +543,16 @@ static int l_destruct(lua::State* L) {
} }
static int l_raycast(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 start = lua::tovec<3>(L, 1);
auto dir = lua::tovec<3>(L, 2); auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3); auto maxDistance = lua::tonumber(L, 3);
bool includeNonSelectable = false;
blocks_agent::RaycastSettings raycast {};
std::set<blockid_t> filteredBlocks {}; std::set<blockid_t> filteredBlocks {};
raycast.filter = &filteredBlocks;
const int luaStackSize = lua::gettop(L); const int luaStackSize = lua::gettop(L);
if (luaStackSize >= 5) { if (luaStackSize >= 5) {
if (lua::istable(L, 5)) { if (lua::istable(L, 5)) {
@ -568,21 +571,14 @@ static int l_raycast(lua::State* L) {
} }
} }
if (luaStackSize >= 6) { if (luaStackSize >= 6) {
includeNonSelectable = lua::toboolean(L, 6); raycast.includeNonSelectable = lua::toboolean(L, 6);
} }
glm::vec3 end; glm::vec3 end;
glm::ivec3 normal; glm::ivec3 normal;
glm::ivec3 iend; glm::ivec3 iend;
if (auto voxel = blocks_agent::raycast( if (auto voxel = blocks_agent::raycast(
*level.chunks, *level.chunks, start, dir, maxDistance, end, normal, iend, raycast
start,
dir,
maxDistance,
end,
normal,
iend,
filteredBlocks,
includeNonSelectable
)) { )) {
if (luaStackSize >= 4 && !lua::isnil(L, 4)) { if (luaStackSize >= 4 && !lua::isnil(L, 4)) {
lua::pushvalue(L, 4); lua::pushvalue(L, 4);

View file

@ -17,6 +17,8 @@
#include "voxels/blocks_agent.hpp" #include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include <set>
using namespace scripting; using namespace scripting;
static const EntityDef* require_entity_def(lua::State* L) { static const EntityDef* require_entity_def(lua::State* L) {
@ -148,34 +150,59 @@ static int l_get_all_in_radius(lua::State* L) {
return 1; return 1;
} }
static int l_raycast(lua::State* L) { static int l_push_ray(
auto start = lua::tovec<3>(L, 1); lua::State* L,
auto dir = lua::tovec<3>(L, 2); const std::optional<Entities::RaycastResult>& ray,
auto maxDistance = lua::tonumber(L, 3); blockid_t block,
auto ignoreEntityId = lua::tointeger(L, 4); const glm::vec3& start,
bool includeNonSelectable = false; const glm::vec3& dir,
std::set<blockid_t> filteredBlocks {}; const glm::vec3& end,
const int luaStackSize = lua::gettop(L); const glm::ivec3& iend,
if (luaStackSize >= 6) { const glm::ivec3& normal
if (lua::istable(L, 6)) { ) {
int addLen = lua::objlen(L, 6); if (!ray.has_value() && block == BLOCK_VOID) {
for (int i = 0; i < addLen; i++) { return 0;
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");
}
} }
if (luaStackSize >= 7) { if (lua::gettop(L) >= 5 && lua::istable(L, 5)) {
includeNonSelectable = lua::toboolean(L, 6); 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::vec3 end;
glm::ivec3 normal; glm::ivec3 normal;
glm::ivec3 iend; glm::ivec3 iend;
@ -190,61 +217,139 @@ static int l_raycast(lua::State* L) {
end, end,
normal, normal,
iend, iend,
filteredBlocks, voxelRaycast
includeNonSelectable
)) { )) {
maxDistance = glm::distance(start, end); maxDistance = glm::distance(start, end);
block = voxel->id; block = voxel->id;
} }
if (auto ray = auto ray =
level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) { entities
if (luaStackSize >= 5 && !lua::isnil(L, 5)) { ? level->entities->rayCast(start, dir, maxDistance, entityRaycast)
lua::pushvalue(L, 5); : 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 { } 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::pop(L);
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;
} }
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) { static int l_reload_component(lua::State* L) {
@ -281,6 +386,7 @@ const luaL_Reg entitylib[] = {
{"get_all_in_box", lua::wrap<l_get_all_in_box>}, {"get_all_in_box", lua::wrap<l_get_all_in_box>},
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>}, {"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"raycast", lua::wrap<l_raycast>}, {"raycast", lua::wrap<l_raycast>},
{"__world_raycast", lua::wrap<l_world_raycast>},
{"reload_component", lua::wrap<l_reload_component>}, {"reload_component", lua::wrap<l_reload_component>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -166,8 +166,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
glm::vec3 start, glm::vec3 start,
glm::vec3 dir, glm::vec3 dir,
float maxDistance, float maxDistance,
entityid_t ignore, const RaycastSettings& settings
bool solidOnly
) { ) {
Ray ray(start, dir); Ray ray(start, dir);
auto view = registry->view<EntityId, Transform, Rigidbody>(); auto view = registry->view<EntityId, Transform, Rigidbody>();
@ -176,10 +175,18 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
glm::ivec3 foundNormal; glm::ivec3 foundNormal;
for (auto [entity, eid, transform, body] : view.each()) { 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; 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; glm::ivec3 normal;
double distance; double distance;
if (ray.intersectAABB( if (ray.intersectAABB(

View file

@ -5,6 +5,7 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "constants.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "Transform.hpp" #include "Transform.hpp"
#include "Rigidbody.hpp" #include "Rigidbody.hpp"
@ -46,12 +47,6 @@ class Entities final {
); );
void preparePhysics(float delta); void preparePhysics(float delta);
public: public:
struct RaycastResult {
entityid_t entity;
glm::ivec3 normal;
float distance;
};
Entities(Level& level); Entities(Level& level);
~Entities(); ~Entities();
@ -82,20 +77,33 @@ public:
std::optional<Entity> get(entityid_t id); 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 /// @brief Entities raycast. No blocks check included, use combined with
/// Chunks.rayCast /// Chunks.rayCast
/// @param start Ray start /// @param start Ray start
/// @param dir Ray direction normalized vector /// @param dir Ray direction normalized vector
/// @param maxDistance Max ray length /// @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 /// @return An optional structure containing entity, normal and distance
std::optional<RaycastResult> rayCast( std::optional<RaycastResult> rayCast(
glm::vec3 start, glm::vec3 start,
glm::vec3 dir, glm::vec3 dir,
float maxDistance, float maxDistance,
entityid_t ignore = -1, const RaycastSettings& settings
bool solidOnly = false
); );
void loadEntities(dv::value map); void loadEntities(dv::value map);

View file

@ -215,11 +215,10 @@ voxel* Chunks::rayCast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const blocks_agent::RaycastSettings& settings
bool includeNonSelectable
) const { ) const {
return blocks_agent::raycast( 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; template <int w, int h, int d> class StaticVoxelsVolume;
namespace blocks_agent {
struct RaycastSettings;
}
/// Player-centred chunks matrix /// Player-centred chunks matrix
class Chunks { class Chunks {
LevelEvents* events; LevelEvents* events;
@ -112,8 +116,7 @@ public:
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter = {}, const blocks_agent::RaycastSettings& settings
bool includeNonSelectable = false
) const; ) const;
glm::vec3 rayCastToObstacle( glm::vec3 rayCastToObstacle(

View file

@ -223,8 +223,7 @@ static inline voxel* raycast_blocks(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const RaycastSettings& settings
bool includeNonSelectable
) { ) {
const auto& blocks = chunks.getContentIndices().blocks; const auto& blocks = chunks.getContentIndices().blocks;
float px = start.x; float px = start.x;
@ -240,9 +239,8 @@ static inline voxel* raycast_blocks(
int iy = std::floor(py); int iy = std::floor(py);
int iz = std::floor(pz); int iz = std::floor(pz);
int stepx = (dx > 0.0f) ? 1 : -1; glm::ivec3 step {
int stepy = (dy > 0.0f) ? 1 : -1; (dx > 0.0f) ? 1 : -1, (dy > 0.0f) ? 1 : -1, (dz > 0.0f) ? 1 : -1};
int stepz = (dz > 0.0f) ? 1 : -1;
constexpr float infinity = std::numeric_limits<float>::infinity(); constexpr float infinity = std::numeric_limits<float>::infinity();
constexpr float epsilon = 1e-6f; // 0.000001 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 tyDelta = (std::fabs(dy) < epsilon) ? infinity : std::fabs(1.0f / dy);
float tzDelta = (std::fabs(dz) < epsilon) ? infinity : std::fabs(1.0f / dz); float tzDelta = (std::fabs(dz) < epsilon) ? infinity : std::fabs(1.0f / dz);
float xdist = (stepx > 0) ? (ix + 1 - px) : (px - ix); float xdist = (step.x > 0) ? (ix + 1 - px) : (px - ix);
float ydist = (stepy > 0) ? (iy + 1 - py) : (py - iy); float ydist = (step.y > 0) ? (iy + 1 - py) : (py - iy);
float zdist = (stepz > 0) ? (iz + 1 - pz) : (pz - iz); float zdist = (step.z > 0) ? (iz + 1 - pz) : (pz - iz);
float txMax = (txDelta < infinity) ? txDelta * xdist : infinity; float txMax = (txDelta < infinity) ? txDelta * xdist : infinity;
float tyMax = (tyDelta < infinity) ? tyDelta * ydist : infinity; float tyMax = (tyDelta < infinity) ? tyDelta * ydist : infinity;
float tzMax = (tzDelta < infinity) ? tzDelta * zdist : infinity; float tzMax = (tzDelta < infinity) ? tzDelta * zdist : infinity;
int steppedIndex = -1; int steppedIndex = -1;
bool includeNonSelectable = settings.includeNonSelectable;
while (t <= maxDist) { while (t <= maxDist) {
voxel* voxel = get(chunks, ix, iy, iz); voxel* voxel = get(chunks, ix, iy, iz);
if (voxel == nullptr) { if (voxel == nullptr) {
return nullptr; return nullptr;
} }
auto filter = settings.filter;
const auto& def = blocks.require(voxel->id); const auto& def = blocks.require(voxel->id);
if (voxel->id != BLOCK_AIR && (def.selectable || includeNonSelectable) && if (voxel->id != BLOCK_AIR &&
(filter.empty() || filter.find(def.rt.id) == filter.end())) { (def.selectable || includeNonSelectable) &&
end.x = px + t * dx; ((filter == nullptr || filter->find(def.rt.id) == filter->end()) ==
end.y = py + t * dy; settings.blocksFilterExcludeMode)) {
end.z = pz + t * dz; end = start + t * dir;
iend.x = ix; iend = {ix, iy, iz};
iend.y = iy;
iend.z = iz;
if (!def.rt.solid) { if (!def.rt.solid) {
const std::vector<AABB>& hitboxes = const std::vector<AABB>& hitboxes =
@ -309,51 +307,41 @@ static inline voxel* raycast_blocks(
if (hit) return voxel; if (hit) return voxel;
} else { } else {
iend.x = ix; iend = {ix, iy, iz};
iend.y = iy; norm = {0, 0, 0};
iend.z = iz; norm[steppedIndex] = -step[steppedIndex];
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;
return voxel; return voxel;
} }
} }
if (txMax < tyMax) { if (txMax < tyMax) {
if (txMax < tzMax) { if (txMax < tzMax) {
ix += stepx; ix += step.x;
t = txMax; t = txMax;
txMax += txDelta; txMax += txDelta;
steppedIndex = 0; steppedIndex = 0;
} else { } else {
iz += stepz; iz += step.z;
t = tzMax; t = tzMax;
tzMax += tzDelta; tzMax += tzDelta;
steppedIndex = 2; steppedIndex = 2;
} }
} else { } else {
if (tyMax < tzMax) { if (tyMax < tzMax) {
iy += stepy; iy += step.y;
t = tyMax; t = tyMax;
tyMax += tyDelta; tyMax += tyDelta;
steppedIndex = 1; steppedIndex = 1;
} else { } else {
iz += stepz; iz += step.z;
t = tzMax; t = tzMax;
tzMax += tzDelta; tzMax += tzDelta;
steppedIndex = 2; steppedIndex = 2;
} }
} }
} }
iend.x = ix; iend = {ix, iy, iz};
iend.y = iy; end = start + t * dir;
iend.z = iz; norm = {0, 0, 0};
end.x = px + t * dx;
end.y = py + t * dy;
end.z = pz + t * dz;
norm.x = norm.y = norm.z = 0;
return nullptr; return nullptr;
} }
@ -365,10 +353,9 @@ voxel* blocks_agent::raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const RaycastSettings& settings
bool includeNonSelectable
) { ) {
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( voxel* blocks_agent::raycast(
@ -379,10 +366,9 @@ voxel* blocks_agent::raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const RaycastSettings& settings
bool includeNonSelectable
) { ) {
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 // 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. /// @brief Cast ray to a selectable block with filter based on id.
/// @param chunks chunks matrix /// @param chunks chunks matrix
/// @param start ray start position /// @param start ray start position
@ -431,8 +437,7 @@ voxel* raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const RaycastSettings& settings
bool includeNonSelectable
); );
/// @brief Cast ray to a selectable block with filter based on id. /// @brief Cast ray to a selectable block with filter based on id.
@ -454,8 +459,7 @@ voxel* raycast(
glm::vec3& end, glm::vec3& end,
glm::ivec3& norm, glm::ivec3& norm,
glm::ivec3& iend, glm::ivec3& iend,
std::set<blockid_t> filter, const RaycastSettings& settings
bool includeNonSelectable
); );
void get_voxels( void get_voxels(