From ed9cf8800aea07c169719a3efddae7020e6be7e9 Mon Sep 17 00:00:00 2001 From: Eliot Byte Date: Thu, 23 Oct 2025 12:29:04 +0300 Subject: [PATCH 1/7] fix correct line generation algorithm and bounds calculation (#657) * fix correct line generation algorithm and bounds calculation - Fix closest_point_on_segment algorithm using proper dot product projection - Correct loop bounds calculation for radius=0 lines - Use precise integer arithmetic to avoid rounding errors - Resolves missing blocks in vertical line structures * fix ai hallucinations * polishing closest_point_on_segment * refactor: extract dot product calculation --- src/maths/util.hpp | 24 +++++++++++++++++------- src/world/generator/WorldGenerator.cpp | 6 +++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/maths/util.hpp b/src/maths/util.hpp index 00345942..2aa3605c 100644 --- a/src/maths/util.hpp +++ b/src/maths/util.hpp @@ -118,6 +118,11 @@ namespace util { return x * x + y * y + z * z; } + /// @return integer dot product of two vectors + inline int dot(const glm::ivec3& a, const glm::ivec3& b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + /// @brief Find nearest point on segment to given /// @param a segment point A /// @param b segment point B @@ -144,12 +149,17 @@ namespace util { inline glm::ivec3 closest_point_on_segment( const glm::ivec3& a, const glm::ivec3& b, const glm::ivec3& point ) { - auto vec = b - a; - float da = distance2(point, a); - float db = distance2(point, b); - float len = length2(vec); - float t = (((da - db) / len) * 0.5f + 0.5f); - t = std::min(1.0f, std::max(0.0f, t)); - return a + glm::ivec3(glm::vec3(vec) * t); + glm::ivec3 vec = b - a; + int len2 = length2(vec); + + if (len2 == 0) return a; + + glm::ivec3 ap = point - a; + int dot_product = dot(ap, vec); + + float t = static_cast(dot_product) / static_cast(len2); + t = glm::clamp(t, 0.0f, 1.0f); + + return a + glm::ivec3(glm::round(glm::vec3(vec) * t)); } } diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index e2bf4a86..ccd52db1 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -547,13 +547,13 @@ void WorldGenerator::generateLine( auto b = line.b; int minX = std::max(0, std::min(a.x-radius-cgx, b.x-radius-cgx)); - int maxX = std::min(CHUNK_W, std::max(a.x+radius-cgx, b.x+radius-cgx)); + int maxX = std::min(CHUNK_W, std::max(a.x+radius-cgx, b.x+radius-cgx) + 1); int minZ = std::max(0, std::min(a.z-radius-cgz, b.z-radius-cgz)); - int maxZ = std::min(CHUNK_D, std::max(a.z+radius-cgz, b.z+radius-cgz)); + int maxZ = std::min(CHUNK_D, std::max(a.z+radius-cgz, b.z+radius-cgz) + 1); int minY = std::max(0, std::min(a.y-radius, b.y-radius)); - int maxY = std::min(CHUNK_H, std::max(a.y+radius, b.y+radius)); + int maxY = std::min(CHUNK_H, std::max(a.y+radius, b.y+radius) + 1); for (int y = minY; y < maxY; y++) { for (int z = minZ; z < maxZ; z++) { From 2a1d2f9354ee2ab623aca9d0059c0a676395d6d0 Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Tue, 28 Oct 2025 23:09:28 +0300 Subject: [PATCH 2/7] fix enable per-variant custom model caching --- src/frontend/ContentGfxCache.cpp | 6 +++--- src/frontend/ContentGfxCache.hpp | 8 ++++++-- src/graphics/render/BlocksPreview.cpp | 2 +- src/graphics/render/BlocksRenderer.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 47443aff..20b7cdaa 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -62,7 +62,7 @@ void ContentGfxCache::refreshVariant( } } } - models[def.rt.id] = std::move(model); + models[modelKey(def.rt.id, variantIndex)] = std::move(model); } } @@ -94,8 +94,8 @@ void ContentGfxCache::refresh() { ContentGfxCache::~ContentGfxCache() = default; -const model::Model& ContentGfxCache::getModel(blockid_t id) const { - const auto& found = models.find(id); +const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const { + const auto& found = models.find(modelKey(id, variant)); if (found == models.end()) { throw std::runtime_error("model not found"); } diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index 8eda04e5..2d2bacaa 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -27,7 +27,11 @@ class ContentGfxCache { // array of block sides uv regions (6 per block) std::unique_ptr sideregions; - std::unordered_map models; + std::unordered_map models; + + static inline uint64_t modelKey(blockid_t id, uint8_t variant) { + return (uint64_t(id) << 8) | uint64_t(variant & 0xFF); + } void refreshVariant( const Block& def, @@ -53,7 +57,7 @@ public: return sideregions[getRegionIndex(id, variant, side, !dense)]; } - const model::Model& getModel(blockid_t id) const; + const model::Model& getModel(blockid_t id, uint8_t variant) const; void refresh(const Block& block, const Atlas& atlas); diff --git a/src/graphics/render/BlocksPreview.cpp b/src/graphics/render/BlocksPreview.cpp index 9a1bf97d..4b8bfee9 100644 --- a/src/graphics/render/BlocksPreview.cpp +++ b/src/graphics/render/BlocksPreview.cpp @@ -67,7 +67,7 @@ std::unique_ptr BlocksPreview::draw( glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f); offset.y += (1.0f - hitbox).y * 0.5f; shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); - const auto& model = cache.getModel(def.rt.id); + const auto& model = cache.getModel(def.rt.id, 0); for (const auto& mesh : model.meshes) { for (const auto& vertex : mesh.vertices) { diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 1f14d2c7..6df7967e 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -308,7 +308,7 @@ void BlocksRenderer::blockCustomModel( Z = orient.axes[2]; } - const auto& model = cache.getModel(block.rt.id); + const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits)); for (const auto& mesh : model.meshes) { if (vertexCount + mesh.vertices.size() >= capacity) { overflow = true; From cd6b73f1eb5268043d4b6abfa9e11d8bc1403116 Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Tue, 28 Oct 2025 23:09:48 +0300 Subject: [PATCH 3/7] docs: add custom model variant examples to block-propertie --- doc/en/block-properties.md | 26 ++++++++++++++++++++++++++ doc/ru/block-properties.md | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 1244da99..82811b63 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -98,6 +98,32 @@ Properties available for variance: Variants are managed via `block.set_variant(x, y, z, index)`. +### Custom model variants (geometry switching) + +You can use different custom models for different variants. Provide a separate `model-name` for each variant that needs different geometry. The renderer caches geometry per (block id, variant). + +The base model (specified in root) becomes variant 0. The variants array maps to indices 1+. + +Example (default + two custom variants): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +In this example: +- Variant 0 = `stairs_middle` (from root) +- Variant 1 = `stairs_left` (from variants[0]) +- Variant 2 = `stairs_right` (from variants[1]) + ## Lighting ### *emission* diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 0003cb92..4569cc49 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -106,6 +106,32 @@ Управление состоянием производится через `block.set_variant(x, y, z, index)`. +### Кастомные модели по вариантам (переключение геометрии) + +Для custom-моделей можно переключать геометрию по варианту. Укажите отдельный `model-name` в нужных вариантах — рендерер кэширует геометрию по паре (id блока, вариант). + +Базовая модель (из корня) становится вариантом 0. Массив variants соответствует индексам 1+. + +Пример (база + два кастомных варианта): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +В этом примере: +- Вариант 0 = `stairs_middle` (из корня) +- Вариант 1 = `stairs_left` (из variants[0]) +- Вариант 2 = `stairs_right` (из variants[1]) + ## Освещение ### Излучение - *emission*: From 934c5f194e89b8cca738929e67d0c2640ac2610f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 15:50:52 +0300 Subject: [PATCH 4/7] fix: on_hud_render is not documented --- doc/en/scripting/events.md | 6 ++++++ doc/ru/scripting/events.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 712dcb8c..fce3abfb 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -190,6 +190,12 @@ function on_hud_open(playerid: int) Called after world open. +```lua +function on_hud_render() +``` + +Called every frame. Used for client-side tasks such as animation and camera control. + ```lua function on_hud_close(playerid: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 4ca3fda5..73a6bc0c 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -190,6 +190,12 @@ function on_hud_open(playerid: int) Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. +```lua +function on_hud_render() +``` + +Вызывается каждый кадр. Используется для клиентских задач, таких как анимация, управление камерой. + ```lua function on_hud_close(playerid: int) ``` From 652f652e556b64401e17b10c749c9952ce90182c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 16:02:11 +0300 Subject: [PATCH 5/7] fix: player.get_camera, set_camera are not documented --- doc/en/scripting/builtins/libplayer.md | 12 ++++++++++++ doc/ru/scripting/builtins/libplayer.md | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index fa799198..48004674 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -112,6 +112,18 @@ player.get_name(playerid: int) -> str Player name setter and getter +```lua +player.get_camera(playerid: int) -> int +``` + +Returns the index of the player's current camera. + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Switches the player's camera. See [cameras](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index 2228f42c..87381193 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -112,6 +112,18 @@ player.get_name(playerid: int) -> str Сеттер и геттер имени игрока +```lua +player.get_camera(playerid: int) -> int +``` + +Возвращает индекс текущей камеры игрока + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Переключает камеру игрока. См. [камеры](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` From 751489b00363d354b64695acb5a90486e6bf9674 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 16:17:57 +0300 Subject: [PATCH 6/7] add input.get_mouse_delta() --- doc/en/scripting/builtins/libinput.md | 6 ++++++ doc/ru/scripting/builtins/libinput.md | 6 ++++++ src/logic/scripting/lua/libs/libinput.cpp | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/doc/en/scripting/builtins/libinput.md b/doc/en/scripting/builtins/libinput.md index 02f2a8d0..2c577389 100644 --- a/doc/en/scripting/builtins/libinput.md +++ b/doc/en/scripting/builtins/libinput.md @@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int} Returns cursor screen position. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Returns cursor movement delta. + ```lua input.get_bindings() --> strings array ``` diff --git a/doc/ru/scripting/builtins/libinput.md b/doc/ru/scripting/builtins/libinput.md index afe22ee6..96bc3105 100644 --- a/doc/ru/scripting/builtins/libinput.md +++ b/doc/ru/scripting/builtins/libinput.md @@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int} Возвращает позицию курсора на экране. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Возращает дельту позиции курсора. + ```lua input.get_bindings() --> массив строк ``` diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index 6d75fd4b..eb605671 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -82,6 +82,12 @@ static int l_get_mouse_pos(lua::State* L) { return lua::pushvec2(L, engine->getInput().getCursor().pos); } +static int l_get_mouse_delta(lua::State* L) { + if (engine->isHeadless()) + return 0; + return lua::pushvec2(L, engine->getInput().getCursor().delta); +} + static int l_get_bindings(lua::State* L) { if (engine->isHeadless()) return 0; @@ -171,6 +177,7 @@ const luaL_Reg inputlib[] = { {"mousecode", lua::wrap}, {"add_callback", lua::wrap}, {"get_mouse_pos", lua::wrap}, + {"get_mouse_delta", lua::wrap}, {"get_bindings", lua::wrap}, {"get_binding_text", lua::wrap}, {"is_active", lua::wrap}, From 9e817c39ce6fc5ee11f2005dabfa0dab1fb952fe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 17:55:01 +0300 Subject: [PATCH 7/7] disable mouse camera control if non-standard camera used --- src/frontend/LevelFrontend.cpp | 3 +-- src/logic/PlayerController.cpp | 5 ++--- src/objects/Player.cpp | 6 ++++++ src/objects/Player.hpp | 6 ++++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 5dfdf822..9a8553da 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend( auto sound = rassets.get(material->stepsSound); glm::vec3 pos {}; auto soundsCamera = currentPlayer->currentCamera.get(); - if (soundsCamera == currentPlayer->spCamera.get() || - soundsCamera == currentPlayer->tpCamera.get()) { + if (currentPlayer->isCurrentCameraBuiltin()) { soundsCamera = currentPlayer->fpCamera.get(); } bool relative = player == currentPlayer && diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 0ced9425..9de01404 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -207,8 +207,7 @@ void CameraControl::update( tpCamera->front = camera->front; tpCamera->right = camera->right; } - if (player.currentCamera == spCamera || player.currentCamera == tpCamera || - player.currentCamera == camera) { + if (player.isCurrentCameraBuiltin()) { player.currentCamera->setFov(glm::radians(settings.fov.get())); } } @@ -280,7 +279,7 @@ void PlayerController::postUpdate( updateFootsteps(delta); } - if (!pause && input) { + if (!pause && input && player.isCurrentCameraBuiltin()) { camControl.updateMouse(this->input, windowHeight); } camControl.refreshRotation(); diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index f14f9d2d..f27dc743 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -84,6 +84,12 @@ Hitbox* Player::getHitbox() { return nullptr; } +bool Player::isCurrentCameraBuiltin() const { + return currentCamera.get() == fpCamera.get() || + currentCamera.get() == spCamera.get() || + currentCamera.get() == tpCamera.get(); +} + void Player::updateSelectedEntity() { selectedEid = selection.entity; } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 7d6523f2..18c5a599 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -131,6 +131,8 @@ public: return position; } + bool isCurrentCameraBuiltin() const; + Hitbox* getHitbox(); void setSpawnPoint(glm::vec3 point); @@ -147,4 +149,8 @@ public: inline u64id_t getId() const { return id; } + + Level& getLevel() const { + return level; + } };