Merge branch 'main' into dev

This commit is contained in:
MihailRis 2025-11-04 16:02:46 +03:00
commit 4f88e3b964
12 changed files with 119 additions and 17 deletions

View File

@ -98,6 +98,32 @@ Properties available for variance:
Variants are managed via `block.set_variant(x, y, z, index)`. 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 ## Lighting
### *emission* ### *emission*

View File

@ -119,6 +119,18 @@ player.get_name(playerid: int) -> str
Player name setter and getter 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 ```lua
player.set_selected_slot(playerid: int, slotid: int) player.set_selected_slot(playerid: int, slotid: int)
``` ```

View File

@ -190,6 +190,12 @@ function on_hud_open(playerid: int)
Called after world open. Called after world open.
```lua
function on_hud_render()
```
Called every frame. Used for client-side tasks such as animation and camera control.
```lua ```lua
function on_hud_close(playerid: int) function on_hud_close(playerid: int)
``` ```

View File

@ -106,6 +106,32 @@
Управление состоянием производится через `block.set_variant(x, y, z, index)`. Управление состоянием производится через `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*: ### Излучение - *emission*:

View File

@ -119,6 +119,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 ```lua
player.set_selected_slot(playerid: int, slotid: int) player.set_selected_slot(playerid: int, slotid: int)
``` ```

View File

@ -190,6 +190,12 @@ function on_hud_open(playerid: int)
Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы.
```lua
function on_hud_render()
```
Вызывается каждый кадр. Используется для клиентских задач, таких как анимация, управление камерой.
```lua ```lua
function on_hud_close(playerid: int) function on_hud_close(playerid: int)
``` ```

View File

@ -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; ContentGfxCache::~ContentGfxCache() = default;
const model::Model& ContentGfxCache::getModel(blockid_t id) const { const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const {
const auto& found = models.find(id); const auto& found = models.find(modelKey(id, variant));
if (found == models.end()) { if (found == models.end()) {
throw std::runtime_error("model not found"); throw std::runtime_error("model not found");
} }

View File

@ -27,7 +27,11 @@ class ContentGfxCache {
// array of block sides uv regions (6 per block) // array of block sides uv regions (6 per block)
std::unique_ptr<UVRegion[]> sideregions; std::unique_ptr<UVRegion[]> sideregions;
std::unordered_map<blockid_t, model::Model> models; std::unordered_map<uint64_t, model::Model> models;
static inline uint64_t modelKey(blockid_t id, uint8_t variant) {
return (uint64_t(id) << 8) | uint64_t(variant & 0xFF);
}
void refreshVariant( void refreshVariant(
const Block& def, const Block& def,
@ -53,7 +57,7 @@ public:
return sideregions[getRegionIndex(id, variant, side, !dense)]; 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); void refresh(const Block& block, const Atlas& atlas);

View File

@ -67,7 +67,7 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f);
offset.y += (1.0f - hitbox).y * 0.5f; offset.y += (1.0f - hitbox).y * 0.5f;
shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); 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& mesh : model.meshes) {
for (const auto& vertex : mesh.vertices) { for (const auto& vertex : mesh.vertices) {

View File

@ -308,7 +308,7 @@ void BlocksRenderer::blockCustomModel(
Z = orient.axes[2]; 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) { for (const auto& mesh : model.meshes) {
if (vertexCount + mesh.vertices.size() >= capacity) { if (vertexCount + mesh.vertices.size() >= capacity) {
overflow = true; overflow = true;

View File

@ -118,6 +118,11 @@ namespace util {
return x * x + y * y + z * z; 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 /// @brief Find nearest point on segment to given
/// @param a segment point A /// @param a segment point A
/// @param b segment point B /// @param b segment point B
@ -144,12 +149,17 @@ namespace util {
inline glm::ivec3 closest_point_on_segment( inline glm::ivec3 closest_point_on_segment(
const glm::ivec3& a, const glm::ivec3& b, const glm::ivec3& point const glm::ivec3& a, const glm::ivec3& b, const glm::ivec3& point
) { ) {
auto vec = b - a; glm::ivec3 vec = b - a;
float da = distance2(point, a); int len2 = length2(vec);
float db = distance2(point, b);
float len = length2(vec); if (len2 == 0) return a;
float t = (((da - db) / len) * 0.5f + 0.5f);
t = std::min(1.0f, std::max(0.0f, t)); glm::ivec3 ap = point - a;
return a + glm::ivec3(glm::vec3(vec) * t); int dot_product = dot(ap, vec);
float t = static_cast<float>(dot_product) / static_cast<float>(len2);
t = glm::clamp(t, 0.0f, 1.0f);
return a + glm::ivec3(glm::round(glm::vec3(vec) * t));
} }
} }

View File

@ -547,13 +547,13 @@ void WorldGenerator::generateLine(
auto b = line.b; auto b = line.b;
int minX = std::max(0, std::min(a.x-radius-cgx, b.x-radius-cgx)); 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 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 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 y = minY; y < maxY; y++) {
for (int z = minZ; z < maxZ; z++) { for (int z = minZ; z < maxZ; z++) {