Merge branch 'main' into animation

This commit is contained in:
MihailRis 2026-07-22 23:56:17 +03:00
commit 7c06a74244
67 changed files with 1231 additions and 260 deletions

View file

@ -22,9 +22,26 @@ namespace {
}
inline double power(double base, int64_t power) {
if (power == 0) {
return 1.0;
}
uint64_t exp;
if (power < 0) {
base = 1.0 / base;
exp = static_cast<uint64_t>(-(power + 1)) + 1;
} else {
exp = static_cast<uint64_t>(power);
}
double result = 1.0;
for (int64_t i = 0; i < power; i++) {
result *= base;
while (exp > 0) {
if (exp & 1)
result *= base;
base *= base;
exp >>= 1;
}
return result;
}

View file

@ -49,9 +49,9 @@ static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte*
png_write_info(png_ptr, info_ptr);
auto row = std::make_unique<png_byte[]>(pixsize * width);
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width; x++) {
for (uint i = 0; i < pixsize; i++) {
for (int y = height-1; y >= 0; y--) {
for (int x = 0; x < width; x++) {
for (int i = 0; i < pixsize; i++) {
row[x * pixsize + i] =
(png_byte)data[(y * width + x) * pixsize + i];
}

View file

@ -528,3 +528,11 @@ std::string xml::stringify(
return ss.str();
}
std::string xml::stringify(
const Node& element, bool nice, const std::string& indentStr
) {
std::stringstream ss;
stringifyElement(ss, element, nice, indentStr, 0);
return ss.str();
}

View file

@ -119,6 +119,12 @@ namespace xml {
const std::string& indentStr = " "
);
std::string stringify(
const Node& element,
bool nice = true,
const std::string& indentStr = " "
);
/// @brief Read XML Document from string
/// @param filename file name will be shown in error messages
/// @param source xml source code string

View file

@ -9,8 +9,8 @@ class Font;
struct FontMetrics {
std::optional<std::weak_ptr<Font>> font;
int lineHeight;
int yoffset;
int lineHeight = 0;
int yoffset = 0;
int _glyphInterval = 8;
~FontMetrics();

View file

@ -1,5 +1,7 @@
#include "ImageData.hpp"
#include "debug/Logger.hpp"
#include <glm/glm.hpp>
#include <assert.h>
#include <stdexcept>
@ -7,6 +9,8 @@
#include <cmath>
#include <algorithm>
static debug::Logger logger("image-data");
ImageData::ImageData(ImageFormat format, uint width, uint height)
: format(format), width(width), height(height) {
size_t pixsize;
@ -106,47 +110,83 @@ std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int heigh
return subImage;
}
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
const int left = 0;
const int right = width;
const int bottom = 0;
const int top = height;
static bool clip_line(
int& x0, int& y0, int& x1, int& y1, int width, int height
) {
if (width <= 0 || height <= 0) {
return false;
}
constexpr int INSIDE = 0;
constexpr int LEFT = 1;
constexpr int RIGHT = 2;
constexpr int BOTTOM = 4;
constexpr int TOP = 8;
int dx = x2 - x1;
int dy = y2 - y1;
const int xmin = 0;
const int ymin = 0;
const int xmax = width - 1;
const int ymax = height - 1;
float t0 = 0.0f;
float t1 = 1.0f;
auto outcode = [&](int x, int y) {
int code = INSIDE;
auto clip = [](int p, int q, float& t0, float& t1) {
if (p == 0) {
return q >= 0;
}
float t = static_cast<float>(q) / p;
if (p < 0) {
if (t > t1) return false;
if (t > t0) t0 = t;
} else {
if (t < t0) return false;
if (t < t1) t1 = t;
}
return true;
if (x < xmin)
code |= LEFT;
else if (x > xmax)
code |= RIGHT;
if (y < ymin)
code |= BOTTOM;
else if (y > ymax)
code |= TOP;
return code;
};
if (!clip(-dx, x1 - left, t0, t1)) return false;
if (!clip( dx, right - x1, t0, t1)) return false;
if (!clip(-dy, y1 - bottom, t0, t1)) return false;
if (!clip( dy, top - y1, t0, t1)) return false;
int c0 = outcode(x0, y0);
int c1 = outcode(x1, y1);
if (t1 < 1.0f) {
x2 = x1 + static_cast<int>(std::round(t1 * dx));
y2 = y1 + static_cast<int>(std::round(t1 * dy));
while (true) {
if (!(c0 | c1)) {
return true;
}
if (c0 & c1) {
return false;
}
const int out = c0 ? c0 : c1;
double x = 0.0;
double y = 0.0;
if (out & TOP) {
y = ymax;
x = x0 + (x1 - x0) * static_cast<double>(ymax - y0) / (y1 - y0);
} else if (out & BOTTOM) {
y = ymin;
x = x0 + (x1 - x0) * static_cast<double>(ymin - y0) / (y1 - y0);
} else if (out & RIGHT) {
x = xmax;
y = y0 + (y1 - y0) * static_cast<double>(xmax - x0) / (x1 - x0);
} else { // LEFT
x = xmin;
y = y0 + (y1 - y0) * static_cast<double>(xmin - x0) / (x1 - x0);
}
if (out == c0) {
x0 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y0 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c0 = outcode(x0, y0);
} else {
x1 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
y1 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
c1 = outcode(x1, y1);
}
}
if (t0 > 0.0f) {
x1 = x1 + static_cast<int>(std::round(t0 * dx));
y1 = y1 + static_cast<int>(std::round(t0 * dy));
}
return true;
}
static bool is_point_outside(int x, int y, int width, int height) {
return x < 0 || y < 0 || x >= width || y >= height;
}
template<uint channels>
@ -155,11 +195,24 @@ static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const gl
uint width = image.getWidth();
uint height = image.getHeight();
glm::ivec4 init {x1, y1, x2, y2};
if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width ||
y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) &&
!clip_line(x1, y1, x2, y2, width, height)) {
return;
}
// spam info for bug report
if (is_point_outside(x1, y1, width, height) || is_point_outside(x2, y2, width, height)) {
logger.warning() << "clip_line fault: [" << init.x << ", " << init.y
<< "] - [" << init.z << ", " << init.w << "] --> ["
<< x1 << ", " << y1 << "] - [" << x2 << ", " << y2
<< "]";
x1 = glm::clamp(x1, 0, static_cast<int>(width) - 1);
y1 = glm::clamp(y1, 0, static_cast<int>(height) - 1);
x2 = glm::clamp(x2, 0, static_cast<int>(width) - 1);
y2 = glm::clamp(y2, 0, static_cast<int>(height) - 1);
}
int dx = std::abs(x2 - x1);
int dy = -std::abs(y2 - y1);

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

@ -207,6 +207,9 @@ uint Label::getLinesNumber() const {
void Label::draw(const DrawContext& pctx, const Assets& assets) {
auto batch = pctx.getBatch2D();
auto font = assets.getShared<Font>(fontName);
if (font == nullptr) {
return;
}
cache.prepare(
font,
font->getMetrics(),

View file

@ -353,7 +353,9 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
auto font = assets.getShared<Font>(label->getFontName());
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
if (font != nullptr) {
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
}
glm::vec2 pos = calcPos();

View file

@ -55,6 +55,16 @@ public:
return fields;
}
void setFields(dv::value&& table, bool clear) {
if (clear) {
fields = std::move(table);
return;
}
for (auto&& [key, value] : table.asObject()) {
fields[std::move(key)] = std::move(value);
}
}
bool hasFields() const {
return fields != nullptr;
}

View file

@ -82,7 +82,6 @@ void LevelController::update(float delta, bool pause) {
continue;
}
player->rotationInterpolation.updateTimer(delta);
player->updateEntity();
glm::vec3 position = player->getPosition();
player->chunks->configure(
glm::floor(position.x),
@ -96,9 +95,9 @@ void LevelController::update(float delta, bool pause) {
*player,
player.get() == clientPlayer
);
player->updateEntity();
}
if (!pause) {
// update all objects that needed
blocks->update(delta, settings.chunks.padding.get());
level->entities->update(delta);
for (const auto& [_, player] : *level->players) {

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

@ -53,6 +53,7 @@ extern const luaL_Reg vec3lib[]; // vecn.cpp
extern const luaL_Reg vec4lib[]; // vecn.cpp
extern const luaL_Reg weatherlib[]; // gfx.weather
extern const luaL_Reg worldlib[];
extern const luaL_Reg xmllib[];
extern const luaL_Reg yamllib[];
// Components

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) {
@ -48,6 +50,13 @@ static int l_def_hitbox(lua::State* L) {
return 0;
}
static int l_def_solid(lua::State* L) {
if (auto def = require_entity_def(L)) {
return lua::pushboolean(L, def->solid);
}
return 0;
}
static int l_defs_count(lua::State* L) {
return lua::pushinteger(L, indices->entities.count());
}
@ -145,34 +154,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 +221,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) {
@ -268,6 +380,7 @@ const luaL_Reg entitylib[] = {
{"def_index", lua::wrap<l_def_index>},
{"def_name", lua::wrap<l_def_name>},
{"def_hitbox", lua::wrap<l_def_hitbox>},
{"def_solid", lua::wrap<l_def_solid>},
{"get_def", lua::wrap<l_get_def>},
{"defs_count", lua::wrap<l_defs_count>},
{"spawn", lua::wrap<l_spawn>},
@ -278,6 +391,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

@ -237,6 +237,14 @@ static int l_get_all_data(lua::State* L, ItemStack& stack) {
return lua::pushvalue(L, stack.getFields());
}
static int l_set_all_data(lua::State* L, ItemStack& stack) {
if (!lua::istable(L, 3)) {
throw std::runtime_error("table expected as argument 1");
}
stack.setFields(lua::tovalue(L, 3), lua::toboolean(L, 4));
return 0;
}
static int l_has_data(lua::State* L, ItemStack& stack) {
auto key = lua::tostring(L, 3);
if (key == nullptr) {
@ -267,6 +275,7 @@ const luaL_Reg inventorylib[] = {
{"get_data", lua::wrap<wrap_slot<l_get_data>>},
{"set_data", lua::wrap<wrap_slot<l_set_data>>},
{"get_all_data", lua::wrap<wrap_slot<l_get_all_data>>},
{"set_all_data", lua::wrap<wrap_slot<l_set_all_data>>},
{"has_data", lua::wrap<wrap_slot<l_has_data>>},
{"create", lua::wrap<l_create>},
{"remove", lua::wrap<l_remove>},

View file

@ -0,0 +1,105 @@
#include "coders/xml.hpp"
#include "api_lua.hpp"
static const char* TAG_ATTR = "#";
static const char* DEFAULT_ROOT_TAG = "root";
static int push_xml(lua::State* L, const xml::xmlelement& elem) {
if (elem.isText()) {
return lua::pushlstring(L, elem.getInnerText());
}
const auto& attrs = elem.getAttributes();
const auto& elems = elem.getElements();
const auto& tag = elem.getTag();
lua::createtable(L, elem.size(), attrs.size() + 1);
lua::pushlstring(L, tag);
lua::setfield(L, TAG_ATTR);
for (int i = 0; i < elems.size(); i++) {
push_xml(L, *elems[i]);
lua::rawseti(L, i + 1);
}
for (const auto& [key, value] : attrs) {
lua::pushlstring(L, value.getText());
lua::setfield(L, key);
}
return 1;
}
static std::unique_ptr<xml::xmlelement> toxml(lua::State* L) {
lua::getfield(L, TAG_ATTR);
auto tag = lua::require_lstring(L, -1);
lua::pop(L);
auto elem = std::make_unique<xml::xmlelement>(std::string(tag));
int length = lua::objlen(L, -1);
for (int i = 0; i < length; i++) {
lua::rawgeti(L, i + 1);
elem->add(toxml(L));
lua::pop(L);
}
lua::pushnil(L);
while (lua::next(L, -2)) {
lua::pushvalue(L, -2);
do {
if (!lua::isstring(L, -1)) {
break;
}
auto key = lua::require_lstring(L, -1);
if (key == "@") {
break;
}
auto value = lua::require_lstring(L, -2);
elem->set(std::string(key), std::string(value));
} while (false);
lua::pop(L, 2);
}
return elem;
}
static int l_tostring(lua::State* L) {
bool nice = lua::isboolean(L, 2) ? lua::toboolean(L, 2) : true;
lua::pushvalue(L, 1);
auto xml = toxml(L);
lua::pop(L);
return lua::pushstring(L, xml::stringify(*xml, nice));
}
static int l_parse(lua::State* L) {
auto string = lua::require_string(L, 1);
auto document = xml::parse("[string]", string);
return push_xml(L, *document->getRoot());
}
static int l_parse_vcd(lua::State* L) {
auto string = lua::require_string(L, 1);
auto rootTag = lua::tostring(L, 2);
auto document = xml::parse_vcm(
"[string]", string, rootTag ? rootTag : ""
);
const auto& root = *document->getRoot();
if (rootTag != nullptr) {
return push_xml(L, root);
}
int rootElements = root.size();
if (rootElements != 1) {
throw std::runtime_error(
"one root element expected, got " + std::to_string(rootElements)
);
}
return push_xml(L, *root.getElements()[0]);
}
const luaL_Reg xmllib[] = {
{"tostring", lua::wrap<l_tostring>},
{"parse", lua::wrap<l_parse>},
{"parse_vcd", lua::wrap<l_parse_vcd>},
{nullptr, nullptr}
};

View file

@ -66,6 +66,7 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "vec2", vec2lib);
openlib(L, "vec3", vec3lib);
openlib(L, "vec4", vec4lib);
openlib(L, "xml", xmllib);
openlib(L, "yaml", yamllib);
openlib(L, "__vc_app", applib);

View file

@ -81,16 +81,17 @@ union RGBA {
uint8_t arr[4];
uint32_t rgba;
};
static_assert(sizeof(RGBA) == 4);
static RGBA* get_at(const ImageData& data, uint index) {
if (index >= data.getWidth() * data.getHeight()) {
return nullptr;
}
static RGBA* get_at(const ImageData& data, size_t index) {
return reinterpret_cast<RGBA*>(data.getData() + index * sizeof(RGBA));
}
static RGBA* get_at(const ImageData& data, uint x, uint y) {
return get_at(data, y * data.getWidth() + x);
if (x >= data.getWidth() || y >= data.getHeight()) {
return nullptr;
}
return get_at(data, static_cast<size_t>(y) * data.getWidth() + x);
}
static RGBA* get_at(State* L, uint x, uint y) {

View file

@ -142,6 +142,7 @@ namespace scripting {
void on_entity_grounded(const Entity& entity, float force);
void on_entity_fall(const Entity& entity);
void on_entity_save(const Entity& entity);
void on_entity_player_set(const Entity& entity, int64_t pid);
void on_entities_update(int tps, int parts, int part);
void on_entities_physics_update(float delta);
void on_entities_render(float delta);

View file

@ -71,6 +71,7 @@ static void init_component_events(
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
funcsset.on_used = lua::hasfield(L, "on_used");
funcsset.on_player_set = lua::hasfield(L, "on_player_set");
lua::pop(L);
}
@ -238,6 +239,15 @@ void scripting::on_entity_save(const Entity& entity) {
);
}
void scripting::on_entity_player_set(const Entity& entity, int64_t pid) {
process_entity_callback(
entity,
"on_player_set",
&EntityFuncsSet::on_player_set,
[pid](auto L) { return lua::pushinteger(L, pid); }
);
}
void scripting::on_sensor_enter(
const Entity& entity, size_t index, entityid_t oid
) {

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

@ -113,6 +113,11 @@ int64_t Entity::getPlayer() const {
}
void Entity::setPlayer(int64_t id) {
registry.get<EntityId>(entity).player = id;
auto& eid = registry.get<EntityId>(entity);
if (eid.player == id) {
return;
}
eid.player = id;
scripting::on_entity_player_set(*this, id);
}

View file

@ -81,7 +81,7 @@ void Player::updateEntity() {
}
}
Hitbox* Player::getHitbox() {
Hitbox* Player::getHitbox() const {
if (auto entity = level.entities->get(eid)) {
return &entity->getRigidbody().hitbox;
}

View file

@ -135,7 +135,7 @@ public:
bool isCurrentCameraBuiltin() const;
Hitbox* getHitbox();
Hitbox* getHitbox() const;
void setSpawnPoint(glm::vec3 point);
glm::vec3 getSpawnPoint() const;

View file

@ -17,6 +17,7 @@ struct EntityFuncsSet {
bool on_aim_off;
bool on_attacked;
bool on_used;
bool on_player_set;
};
struct UserComponent {

View file

@ -11,8 +11,10 @@
namespace util {
template<typename T>
inline T read_int_le(const uint8_t* src, size_t offset=0) {
return dataio::le2h(*(reinterpret_cast<const T*>(src) + offset));
inline T read_int_le(const uint8_t* src) {
T tmp;
std::memcpy(&tmp, src, sizeof(T));
return dataio::le2h(tmp);
}
/// @brief Simple heap implementation for memory-optimal sparse array of
@ -87,8 +89,7 @@ namespace util {
std::memset(found, 0, entrySize);
return found;
}
this->free(found);
return allocate(index, size);
free(found);
}
for (size_t i = 0; i < entriesCount; i++) {
auto data = buffer.data() + offset;
@ -110,9 +111,12 @@ namespace util {
entriesCount++;
auto data = buffer.data() + offset;
*reinterpret_cast<Tindex*>(data) = dataio::h2le(index);
Tindex indexTmp = dataio::h2le(index);
Tsize sizeTmp = dataio::h2le(size);
std::memcpy(data, &indexTmp, sizeof(Tindex));
data += sizeof(Tindex);
*reinterpret_cast<Tsize*>(data) = dataio::h2le(size);
std::memcpy(data, &sizeTmp, sizeof(Tsize));
return data + sizeof(Tsize);
}
@ -122,7 +126,7 @@ namespace util {
if (ptr == nullptr) {
return 0;
}
return read_int_le<Tsize>(ptr, -1);
return read_int_le<Tsize>(ptr - sizeof(Tsize));
}
/// @return number of entries
@ -147,7 +151,8 @@ namespace util {
ubyte* dst = out.data();
const ubyte* src = buffer.data();
*reinterpret_cast<Tindex*>(dst) = dataio::h2le(entriesCount);
Tindex countTmp = dataio::h2le(entriesCount);
std::memcpy(dst, &countTmp, sizeof(Tindex));
dst += sizeof(Tindex);
std::memcpy(dst, src, buffer.size());
@ -172,7 +177,7 @@ namespace util {
) : buffer(buffer), index(index), offset(offset) {}
Tsize size() const {
return read_int_le<Tsize>(buffer.data() + offset, -1);
return read_int_le<Tsize>(buffer.data() + offset - sizeof(Tsize));
}
bool operator!=(const const_iterator& o) const {

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,43 @@ 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};
if (steppedIndex != -1) {
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 +355,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 +368,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(

View file

@ -33,6 +33,7 @@ static void init_gl_extensions_list() {
}
}
[[maybe_unused]]
static bool is_gl_extension_supported(const char *extension) {
if (!extension || !*extension) {
return false;
@ -40,6 +41,7 @@ static bool is_gl_extension_supported(const char *extension) {
return supported_gl_extensions.find(extension) != supported_gl_extensions.end();
}
#ifndef __APPLE__
static const char* gl_error_name(int error) {
switch (error) {
case GL_DEBUG_TYPE_ERROR: return "ERROR";
@ -80,6 +82,7 @@ static void GLAPIENTRY gl_message_callback(
logger.warning() << "GL:" << gl_error_name(type) << ":"
<< gl_severity_name(severity) << ": " << message;
}
#endif
static bool initialize_gl(int width, int height) {
glewExperimental = GL_TRUE;