This commit is contained in:
Xertis 2026-03-02 20:53:23 +03:00
commit 55f30caa35
26 changed files with 368 additions and 64 deletions

View file

@ -241,6 +241,15 @@ Number of block inventory slots. Default - 0 (no inventory).
Array of three integers. Default value is `[1, 1, 1]`. Array of three integers. Default value is `[1, 1, 1]`.
### *grounding-behaviour*
Defines the behavior of the extended grounded block. Available behavior options:
- "partial" - at least one segment must be grounded (default)
- "complete" - all segments must be grounded
- "origin" - origin segment must be grounded
## Block fields ## Block fields
Block fields allow you to write more data unique to a specified voxel than the user bits allow. Block fields allow you to write more data unique to a specified voxel than the user bits allow.

View file

@ -21,5 +21,6 @@ Documentation for 0.31.
- [Rigging](rigging.md) - [Rigging](rigging.md)
- [Scripting](scripting.md) - [Scripting](scripting.md)
- [Text styles](text-styles.md) - [Text styles](text-styles.md)
- [VCM models format](vcm.md)
- [World generator engine](world-generator.md) - [World generator engine](world-generator.md)
- [XML UI building](xml-ui-layouts.md) - [XML UI building](xml-ui-layouts.md)

View file

@ -67,7 +67,7 @@ block.get_picking_item(id: int) -> int
block.get_variant(x: int, y: int, z: int) -> int block.get_variant(x: int, y: int, z: int) -> int
-- Sets the block variant by index -- Sets the block variant by index
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int)
-- Checks if an block has specified tag -- Checks if an block has specified tag
block.has_tag(id: int, tag: str) -> bool block.has_tag(id: int, tag: str) -> bool

101
doc/en/vcm.md Normal file
View file

@ -0,0 +1,101 @@
# VCM
A text format for describing 3D models, replacing the deprecated `model-primitives` property.
Syntax:
```
@primitive attribute (value1, value2,...) attribute2 "value3" {
@inner_primitive ...
}
```
Same structure in XML:
```xml
<primitive attribute="value1, value2,..." attribute2="value3">
<inner_primitive .../>
</primitive>
```
Unlike XML, the root can contain multiple elements.
Currently, there are three types of primitives: `tri`, `box`, `rect` and `part` (describes a part of a primitive, such as the side of a cube).
> [!NOTE]
> By default, texture coordinates are determined by the dimensions of the primitives.
> For sizes greater than 1.0, the result is undefined and may change in future updates.
> Use `region` or `region-scale` for manual adjustments.
## Primitives
### `tri` Properties
This primitive describes a triangle.
- `a` - Point A. Example: `a (0,0,0)`
- `b` - Point B. Example: `b (1,0,0)`
- `c` - Point C. Example: `c (1,1,0)`
- `uv` - Normalized texture coordinates of the points (6 numbers, 2 per point). Example: `uv (0,0,1,0,1)`
- `texture` - The texture to display. Default: `$0`. Example: `texture "blocks:sand"`
- `region` - UV region within the selected texture, defined by the positions of opposite corners of a square of normalized texture coordinates. Example: `region (0,0,1,1)`
- `region-scale` - multiplier vector for the UV region. Example: `region-scale (0.5,1)`
- `shading` - determines whether shading is enabled on the primitive. Example: `shading off`
By default, texture coordinates are not affected by shape or size, unlike other primitives.
### `rect` Properties
This primitive describes a parallelogram using the right and up vectors.
- `from` - the origin of the primitive. Example: `from (0,0.5,0.125)`
- `right` - the X vector, which also determines the width of the primitive. Example: `right (1,0,0)`
- `up` - the Y vector, which also determines the primitive's height. Example: `up (0,1,0)`
- `texture` - the texture to display. Default: `$0`. Example: `texture "blocks:sand"`
- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)`
- `region-scale` - the multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)`
- `shading` - determines whether shading is enabled on the primitive. Example: `shading off`
- `flip` - flips the UV region horizontally or vertically. Example: `flip v`
### `box` Properties
This primitive describes a rectangular parallelepiped.
- `from` - the primitive's origin. Example: `from (0,0,0)`
- `to` - the point opposite the origin. Example: `to (1,1,1)`
- `origin` - the point relative to which the rotation will be applied. Default: the center of the primitive. Example: `origin (0.5,0.5,0.5)`
- `rotate` - rotation around the (x,y,z) axes in degrees, or a quaternion (x,y,z,w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)`
- `texture` - the default texture to display for all sides.
- `shading` determines whether shading is enabled on the primitive. Example: `shading off`
- `delete` deletes sides by name (top, bottom, east, west, north, south)
### `part` properties (nested in `box`)
This primitive describes the properties of the sides of a rectangular parallelepiped with the specified tags (top/bottom/north/south/east/west).
- `tags` - determines which side the properties will be applied to. Example: `tags (top,bottom)`
- `texture` - the texture to display. Default: `$side_index`.
- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)`
- `region-scale` is a multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)`
## Skeleton
VCM can describe the [skeleton](rigging.md) of a model. The `@bone` element is used for this.
There are two types of bones:
- unnamed - used only for transformations on inner primitives and not included in the final skeleton.
- named - will be included in the final skeleton. The name is specified via the `name` attribute.
The main bone (`root`) is created automatically for the entire model.
If bones are explicitly specified, the model will be divided into several parts named as `model_name.bone_name`.
Since the model is an asset and has no prefix, the loaded skeleton receives the model name, also without a prefix.
The loaded skeleton can be used in the [entity](entity-properties.md#skeleton-name) via the `skeleton-name` property.
In the `pack/preload.json` list, if `"squash": true` is specified, the vcm will be loaded as a single model, without a skeleton.
### `bone` Properties
- `name` - The bone name. If omitted, the bone will be "dissolved" and will not be included in the final skeleton, but transformations will be applied.
- `move` - Moves nested elements.
- `scale` - Scales nested elements. Applied before moving.
- `rotate` - Rotates nested elements around the (x, y, z) axes in degrees, or as a quaternion (x, y, z, w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)`

View file

@ -247,6 +247,14 @@
Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`. Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`.
### Поведение приземленности - *grounding-behaviour*
Определяет поведение расширенного приземленного блока. Доступные варианты поведения:
- "partial" - как минимум один сегмент блока должен быть приземленным (по-умолчанию)
- "complete" - все сегменты блока должны быть приземленными
- "origin" - главный сегмент должен быть приземленным
## Поля блока ## Поля блока
Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты. Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты.

View file

@ -22,5 +22,5 @@
- [Свойства сущностей](entity-properties.md) - [Свойства сущностей](entity-properties.md)
- [Скриптинг](scripting.md) - [Скриптинг](scripting.md)
- [Стили текста](text-styles.md) - [Стили текста](text-styles.md)
- [Частицы](particles.md)
- [Формат моделей VCM](vcm.md) - [Формат моделей VCM](vcm.md)
- [Частицы](particles.md)

View file

@ -46,7 +46,7 @@ block.destruct(x: int, y: int, z: int, playerid: int)
block.get_variant(x: int, y: int, z: int) -> int block.get_variant(x: int, y: int, z: int) -> int
-- Устанавливает вариант блока по индексу -- Устанавливает вариант блока по индексу
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int)
``` ```
> [!WARNING] > [!WARNING]

View file

@ -1,6 +1,19 @@
#include "Assets.hpp" #include "Assets.hpp"
Assets::~Assets() = default; Assets::Assets(util::ObjectsKeeper* vault) : vault(vault) {
}
Assets::~Assets() {
if (vault == nullptr) {
return;
}
for (auto& [_, map] : assets) {
for (auto& [__, asset] : map) {
vault->keepAlive(std::move(asset));
}
map.clear();
}
}
const std::vector<TextureAnimation>& Assets::getAnimations() { const std::vector<TextureAnimation>& Assets::getAnimations() {
return animations; return animations;

View file

@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp" #include "graphics/core/TextureAnimation.hpp"
class Assets; class Assets;
@ -64,13 +65,14 @@ namespace assetload {
} }
class Assets { class Assets {
util::ObjectsKeeper* vault;
std::vector<TextureAnimation> animations; std::vector<TextureAnimation> animations;
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>; using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
std::unordered_map<std::type_index, assets_map> assets; std::unordered_map<std::type_index, assets_map> assets;
std::vector<assetload::setupfunc> setupFuncs; std::vector<assetload::setupfunc> setupFuncs;
public: public:
Assets() = default; Assets(util::ObjectsKeeper* vault);
Assets(const Assets&) = delete; Assets(const Assets&) = delete;
~Assets(); ~Assets();
@ -79,12 +81,20 @@ public:
template <class T> template <class T>
void store(std::unique_ptr<T> asset, const std::string& name) { void store(std::unique_ptr<T> asset, const std::string& name) {
assets[typeid(T)][name].reset(asset.release()); auto& dst = assets[typeid(T)][name];
if (vault != nullptr && dst != nullptr) {
vault->keepAlive(std::move(dst));
}
dst.reset(asset.release());
} }
template <class T> template <class T>
void store(std::shared_ptr<T> asset, const std::string& name) { void store(std::shared_ptr<T> asset, const std::string& name) {
assets[typeid(T)][name] = std::move(asset); auto& dst = assets[typeid(T)][name];
if (vault != nullptr && dst != nullptr) {
vault->keepAlive(std::move(dst));
}
dst = std::move(asset);
} }
template <class T> template <class T>

View file

@ -33,6 +33,8 @@ inline constexpr int CHUNK_W = 16;
inline constexpr int CHUNK_H = 256; inline constexpr int CHUNK_H = 256;
inline constexpr int CHUNK_D = 16; inline constexpr int CHUNK_D = 16;
inline constexpr int EXTENDED_BLOCK_LIMIT = CHUNK_W; // must not be greater than chunk width and depth
inline constexpr uint VOXEL_USER_BITS = 8; inline constexpr uint VOXEL_USER_BITS = 8;
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;

View file

@ -12,6 +12,7 @@
#include "presets/ParticlesPreset.hpp" #include "presets/ParticlesPreset.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "constants.hpp"
using namespace data; using namespace data;
@ -120,8 +121,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
} }
def.variants = std::make_unique<Variants>(); def.variants = std::make_unique<Variants>();
def.variants->offset = 0; def.variants->offset = offset;
def.variants->mask = 0xF; def.variants->mask = (1ULL << bitsCount) - 1;
def.variants->variants.push_back(def.defaults); def.variants->variants.push_back(def.defaults);
for (int i = 0; i < variants.size(); i++) { for (int i = 0; i < variants.size(); i++) {
Variant variant = def.defaults; Variant variant = def.defaults;
@ -184,7 +185,11 @@ template<> void ContentUnitLoader<Block>::loadUnit(
if (auto found = root.at("emission")) { if (auto found = root.at("emission")) {
const auto& emissionarr = *found; const auto& emissionarr = *found;
for (size_t i = 0; i < 3; i++) { for (size_t i = 0; i < 3; i++) {
def.emission[i] = glm::clamp(emissionarr[i].asInteger(), static_cast<integer_t>(0), static_cast<integer_t>(15)); def.emission[i] = glm::clamp(
emissionarr[i].asInteger(),
static_cast<integer_t>(0),
static_cast<integer_t>(15)
);
} }
} }
@ -199,13 +204,31 @@ template<> void ContentUnitLoader<Block>::loadUnit(
"block " + util::quote(def.name) + ": invalid block size" "block " + util::quote(def.name) + ": invalid block size"
); );
} }
if (def.size.x > EXTENDED_BLOCK_LIMIT ||
def.size.y > EXTENDED_BLOCK_LIMIT ||
def.size.z > EXTENDED_BLOCK_LIMIT) {
throw std::runtime_error(
"extended block " + util::quote(def.name) + " size limit " +
std::to_string(EXTENDED_BLOCK_LIMIT) + " exceeded"
);
}
// should variant modify hitbox? // should variant modify hitbox?
if (def.defaults.model.type == BlockModelType::BLOCK && if (def.defaults.model.type == BlockModelType::BLOCK &&
(def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { (def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) {
def.defaults.model.type = BlockModelType::AABB; def.defaults.model.type = BlockModelType::AABB;
def.hitboxes = {AABB(def.size)}; def.hitboxes = {AABB(def.size)};
} }
// grounding behaviour
if (root.has("grounding-behaviour")) {
std::string groundingBehaviourName = GroundingBehaviourMeta.getNameString(def.groundingBehaviour);
root.at("grounding-behaviour").get(groundingBehaviourName);
if (!GroundingBehaviourMeta.getItem(groundingBehaviourName, def.groundingBehaviour)) {
logger.error() << "unknown grounding behaviour: " << groundingBehaviourName;
}
}
} }
// primitive properties // primitive properties

View file

@ -51,7 +51,9 @@ void AssetsManagement::loadAssets(Content* content) {
logger.info() << "loading assets"; logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths); Shader::preprocessor->setPaths(&paths.resPaths);
auto new_assets = std::make_unique<Assets>(); auto new_assets = std::make_unique<Assets>(
settings.system.preserveAssetsDuringFrame.get() ? &assetsVault : nullptr
);
AssetsLoader loader(engine, *new_assets, paths.resPaths); AssetsLoader loader(engine, *new_assets, paths.resPaths);
AssetsLoader::addDefaults(loader, content); AssetsLoader::addDefaults(loader, content);
@ -78,6 +80,7 @@ void AssetsManagement::loadAssets(Content* content) {
} }
void AssetsManagement::update() { void AssetsManagement::update() {
assetsVault.clearKeepedObjects();
if (backgroundLoaderTask) { if (backgroundLoaderTask) {
backgroundLoaderTask->update(); backgroundLoaderTask->update();
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "util/ObjectsKeeper.hpp"
#include <memory> #include <memory>
@ -30,4 +31,5 @@ private:
std::unique_ptr<Assets> assets; std::unique_ptr<Assets> assets;
std::unique_ptr<AssetsLoader> backgroundLoader; std::unique_ptr<AssetsLoader> backgroundLoader;
std::shared_ptr<Task> backgroundLoaderTask; std::shared_ptr<Task> backgroundLoaderTask;
util::ObjectsKeeper assetsVault;
}; };

View file

@ -70,7 +70,7 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
refreshVariant(def, def.defaults, 0, atlas); refreshVariant(def, def.defaults, 0, atlas);
if (def.variants) { if (def.variants) {
const auto& variants = def.variants->variants; const auto& variants = def.variants->variants;
for (int i = 1; i < variants.size() - 1; i++) { for (int i = 1; i < variants.size(); i++) {
refreshVariant(def, variants[i], i, atlas); refreshVariant(def, variants[i], i, atlas);
} }
def.variants->variants.at(0) = def.defaults; def.variants->variants.at(0) = def.defaults;

View file

@ -114,7 +114,7 @@ LevelScreen::~LevelScreen() {
} }
scripting::on_frontend_close(); scripting::on_frontend_close();
input.getBindings().enableAll(); input.getBindings().enableAll();
playerController->getPlayer()->chunks->saveAndClear(); playerController->getPlayer().chunks->saveAndClear();
controller->onWorldQuit(); controller->onWorldQuit();
} }
@ -168,12 +168,12 @@ void LevelScreen::saveDecorations() {
void LevelScreen::saveWorldPreview() { void LevelScreen::saveWorldPreview() {
try { try {
logger.info() << "saving world preview"; logger.info() << "saving world preview";
auto player = playerController->getPlayer(); const Player& player = playerController->getPlayer();
auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
int previewSize = settings.ui.worldPreviewSize.get(); int previewSize = settings.ui.worldPreviewSize.get();
// camera special copy for world preview // camera special copy for world preview
Camera camera = *player->fpCamera; Camera& camera = *player.fpCamera;
camera.setFov(glm::radians(70.0f)); camera.setFov(glm::radians(70.0f));
DrawContext pctx(nullptr, engine.getWindow(), batch.get()); DrawContext pctx(nullptr, engine.getWindow(), batch.get());
@ -215,18 +215,18 @@ void LevelScreen::updateHotkeys() {
} }
void LevelScreen::updateAudio() { void LevelScreen::updateAudio() {
auto player = playerController->getPlayer(); Player& player = playerController->getPlayer();
auto camera = player->currentCamera; Camera& camera = *player.currentCamera;
bool paused = hud->isPause(); bool paused = hud->isPause();
audio::get_channel("regular")->setPaused(paused); audio::get_channel("regular")->setPaused(paused);
audio::get_channel("ambient")->setPaused(paused); audio::get_channel("ambient")->setPaused(paused);
glm::vec3 velocity {}; glm::vec3 velocity {};
if (auto hitbox = player->getHitbox()) { if (auto hitbox = player.getHitbox()) {
velocity = hitbox->velocity; velocity = hitbox->velocity;
} }
audio::set_listener( audio::set_listener(
camera->position, velocity, camera->dir, glm::vec3(0, 1, 0) camera.position, velocity, camera.dir, glm::vec3(0, 1, 0)
); );
} }
@ -257,22 +257,22 @@ void LevelScreen::update(float delta) {
hud->update(hudVisible); hud->update(hudVisible);
const auto& weather = renderer->getWeather(); const Weather& weather = renderer->getWeather();
const auto& player = *playerController->getPlayer(); const Player& player = playerController->getPlayer();
const auto& camera = *player.currentCamera; const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather); decorator->update(paused ? 0.0f : delta, camera, weather);
} }
void LevelScreen::draw(float delta) { void LevelScreen::draw(float delta) {
auto camera = playerController->getPlayer()->currentCamera; Camera& camera = *playerController->getPlayer().currentCamera;
DrawContext ctx(nullptr, engine.getWindow(), batch.get()); const DrawContext ctx(nullptr, engine.getWindow(), batch.get());
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
renderer->update(*camera, delta * !hud->isPause()); renderer->update(camera, delta * !hud->isPause());
renderer->renderFrame(ctx, *camera, hudVisible, *postProcessing); renderer->renderFrame(ctx, camera, hudVisible, *postProcessing);
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_frontend_render(); scripting::on_frontend_render();
} }

View file

@ -203,7 +203,7 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
); );
auto mesh = getOrRender( auto mesh = getOrRender(
chunk, chunk,
distance < CHUNK_W * 1.5f * 10.0f, distance < CHUNK_W * 1.5f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5 distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
); );
if (mesh == nullptr) { if (mesh == nullptr) {

View file

@ -92,6 +92,12 @@ void ModelsGenerator::prepareModel(
void ModelsGenerator::prepare(Content& content, Assets& assets) { void ModelsGenerator::prepare(Content& content, Assets& assets) {
for (auto& [name, def] : content.blocks.getDefs()) { for (auto& [name, def] : content.blocks.getDefs()) {
prepareModel(assets, *def, def->defaults, 0); prepareModel(assets, *def, def->defaults, 0);
if (def->variants) {
auto& variants = def->variants->variants;
for (int i = 1; i < variants.size(); i++) {
prepareModel(assets, *def, variants[i], i);
}
}
} }
for (auto& [name, def] : content.items.getDefs()) { for (auto& [name, def] : content.items.getDefs()) {
assets.store( assets.store(

View file

@ -100,6 +100,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("system"); builder.addSection("system");
builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders); builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders);
builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame);
} }
dv::value SettingsHandler::getValue(const std::string& name) const { dv::value SettingsHandler::getValue(const std::string& name) const {

View file

@ -111,10 +111,17 @@ void BlocksController::updateBlock(int x, int y, int z) {
if (vox == nullptr) return; if (vox == nullptr) return;
const auto& def = level.content.getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.grounded) { if (def.grounded) {
const auto& vec = get_ground_direction(def, vox->state.rotation); if (def.rt.extended) {
if (!blocks_agent::is_solid_at(chunks, x + vec.x, y + vec.y, z + vec.z)) { auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
breakBlock(nullptr, def, x, y, z); if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, origin)) {
return; breakBlock(nullptr, def, origin.x, origin.y, origin.z);
return;
}
} else {
if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, {x, y, z})) {
breakBlock(nullptr, def, x, y, z);
return;
}
} }
} }
if (def.rt.funcsset.update) { if (def.rt.funcsset.update) {

View file

@ -22,6 +22,7 @@
#include "scripting/scripting.hpp" #include "scripting/scripting.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/blocks_agent.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/voxel.hpp" #include "voxels/voxel.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
@ -252,18 +253,18 @@ void PlayerController::onFootstep(const Hitbox& hitbox) {
} }
void PlayerController::updateFootsteps(float delta) { void PlayerController::updateFootsteps(float delta) {
const float pi = glm::pi<float>(); constexpr float GLM_PI = glm::pi<float>();
auto hitbox = player.getHitbox(); auto hitbox = player.getHitbox();
if (hitbox && hitbox->grounded) { if (hitbox && hitbox->grounded) {
const glm::vec3& vel = hitbox->velocity; const glm::vec3& vel = hitbox->velocity;
float f = glm::length(glm::vec2(vel.x, vel.z)); float f = glm::length(glm::vec2(vel.x, vel.z));
stepsTimer += delta * f * STEPS_SPEED; stepsTimer += delta * f * STEPS_SPEED;
if (stepsTimer >= pi) { if (stepsTimer >= GLM_PI) {
stepsTimer = fmod(stepsTimer, pi); stepsTimer = fmod(stepsTimer, GLM_PI);
onFootstep(*hitbox); onFootstep(*hitbox);
} }
} else { } else {
stepsTimer = pi; stepsTimer = GLM_PI;
} }
} }
@ -448,10 +449,7 @@ void PlayerController::processRightClick(
return; return;
} }
if (def.grounded) { if (def.grounded) {
const auto& vec = get_ground_direction(def, state.rotation); if (!blocks_agent::check_grounding(chunks, def, state.rotation, coord)) {
if (!chunks.isSolidBlock(
coord.x + vec.x, coord.y + vec.y, coord.z + vec.z
)) {
return; return;
} }
} }
@ -556,6 +554,6 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta)
} }
} }
Player* PlayerController::getPlayer() { Player& PlayerController::getPlayer() {
return &player; return player;
} }

View file

@ -89,5 +89,5 @@ public:
void postUpdate( void postUpdate(
float delta, int windowHeight, const Input* inputEvents, bool pause float delta, int windowHeight, const Input* inputEvents, bool pause
); );
Player* getPlayer(); Player& getPlayer();
}; };

View file

@ -1,16 +1,17 @@
#include "api_lua.hpp" #include "api_lua.hpp"
#include "../usertypes/lua_type_canvas.hpp"
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "assets/AssetsLoader.hpp" #include "assets/AssetsLoader.hpp"
#include "coders/obj.hpp"
#include "coders/png.hpp" #include "coders/png.hpp"
#include "coders/vcm.hpp" #include "coders/vcm.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "graphics/commons/Model.hpp" #include "graphics/commons/Model.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp" #include "graphics/core/Atlas.hpp"
#include "graphics/core/Texture.hpp"
#include "util/Buffer.hpp" #include "util/Buffer.hpp"
#include "../usertypes/lua_type_canvas.hpp"
using namespace scripting; using namespace scripting;
@ -76,6 +77,11 @@ static int l_parse_model(lua::State* L) {
skeletonName = lua::require_string(L, 4); skeletonName = lua::require_string(L, 4);
} }
if (format == "obj") {
assets.store(obj::parse(name, string), name);
return 0;
}
if (format != "xml" && format != "vcm") { if (format != "xml" && format != "vcm") {
throw std::runtime_error( throw std::runtime_error(
"unknown format " + util::quote(std::string(format)) "unknown format " + util::quote(std::string(format))

View file

@ -246,11 +246,12 @@ static int l_get_user_bits(lua::State* L) {
} }
static int l_get_variant(lua::State* L) { static int l_get_variant(lua::State* L) {
auto& chunks = *level->chunks;
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(chunks, x, y, z);
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
@ -258,6 +259,13 @@ static int l_get_variant(lua::State* L) {
if (def.variants == nullptr) { if (def.variants == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z);
if (vox == nullptr) {
return lua::pushinteger(L, 0);
}
}
return lua::pushinteger( return lua::pushinteger(
L, (vox->state.userbits >> def.variants->offset) & def.variants->mask L, (vox->state.userbits >> def.variants->offset) & def.variants->mask
); );
@ -290,6 +298,14 @@ static int l_set_user_bits(lua::State* L) {
if (vox == nullptr) { if (vox == nullptr) {
return 0; return 0;
} }
int ocx = floordiv<CHUNK_W>(origin.x);
int ocz = floordiv<CHUNK_D>(origin.z);
if (cx != ocx || cz != ocz) {
chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
if (chunk == nullptr) {
return 0;
}
}
} }
vox->state.userbits = (vox->state.userbits & (~mask)) | value; vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved(); chunk->setModifiedAndUnsaved();
@ -318,8 +334,8 @@ static int l_set_variant(lua::State* L) {
} }
auto offset = def.variants->offset; auto offset = def.variants->offset;
auto mask = def.variants->mask; auto mask = def.variants->mask << offset;
auto value = (lua::tointeger(L, 4) << offset) & mask; auto value = (lua::tointeger(L, 4) << offset);
if (def.rt.extended) { if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
@ -327,6 +343,14 @@ static int l_set_variant(lua::State* L) {
if (vox == nullptr) { if (vox == nullptr) {
return 0; return 0;
} }
int ocx = floordiv<CHUNK_W>(origin.x);
int ocz = floordiv<CHUNK_D>(origin.z);
if (cx != ocx || cz != ocz) {
chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
if (chunk == nullptr) {
return 0;
}
}
} }
vox->state.userbits = (vox->state.userbits & (~mask)) | value; vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved(); chunk->setModifiedAndUnsaved();

View file

@ -122,7 +122,10 @@ struct NetworkSettings {
}; };
struct SystemSettings { struct SystemSettings {
/// @brief Max background assets loaders
IntegerSetting maxBgAssetLoaders {3, -4, 16}; IntegerSetting maxBgAssetLoaders {3, -4, 16};
/// @brief Preserve assets during current frame from being destroyed
FlagSetting preserveAssetsDuringFrame {true};
}; };
struct EngineSettings { struct EngineSettings {

View file

@ -140,6 +140,22 @@ VC_ENUM_METADATA(CullingMode)
{"disabled", CullingMode::DISABLED}, {"disabled", CullingMode::DISABLED},
VC_ENUM_END VC_ENUM_END
/// @brief Grounding behaviour for extended blocks
enum class GroundingBehaviour : uint8_t {
/// @brief at least one segment must be grounded
PARTIAL,
/// @brief all segments must be grounded
COMPLETE,
/// @brief origin segment must be grounded
ORIGIN
};
VC_ENUM_METADATA(GroundingBehaviour)
{"partial", GroundingBehaviour::PARTIAL},
{"complete", GroundingBehaviour::COMPLETE},
{"origin", GroundingBehaviour::ORIGIN},
VC_ENUM_END
/// @brief Common kit of block properties applied to groups of blocks /// @brief Common kit of block properties applied to groups of blocks
struct BlockMaterial : Serializable { struct BlockMaterial : Serializable {
std::string name; std::string name;
@ -239,6 +255,9 @@ public:
/// @brief Explicitly overriding 'solid' property if true assigned /// @brief Explicitly overriding 'solid' property if true assigned
bool explictlySolid = false; bool explictlySolid = false;
/// @brief Grounding behaviour
GroundingBehaviour groundingBehaviour = GroundingBehaviour::PARTIAL;
/// @brief Set of block physical hitboxes /// @brief Set of block physical hitboxes
std::vector<AABB> hitboxes {AABB()}; std::vector<AABB> hitboxes {AABB()};

View file

@ -2,23 +2,23 @@
/// blocks_agent is set of templates but not a class to minimize OOP overhead. /// blocks_agent is set of templates but not a class to minimize OOP overhead.
#include "voxel.hpp"
#include "Block.hpp" #include "Block.hpp"
#include "Chunk.hpp" #include "Chunk.hpp"
#include "Chunks.hpp" #include "Chunks.hpp"
#include "VoxelsVolume.hpp"
#include "GlobalChunks.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "typedefs.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "GlobalChunks.hpp"
#include "maths/voxmaths.hpp" #include "maths/voxmaths.hpp"
#include "typedefs.hpp"
#include "voxel.hpp"
#include "VoxelsVolume.hpp"
#include <algorithm> #include <algorithm>
#include <set>
#include <algorithm> #include <algorithm>
#include <stdint.h>
#include <stdexcept>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <set>
#include <stdexcept>
#include <stdint.h>
struct AABB; struct AABB;
@ -96,7 +96,6 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
return chunks.getContentIndices().blocks.require(id); return chunks.getContentIndices().blocks.require(id);
} }
/// @brief Check if block at specified position is solid. /// @brief Check if block at specified position is solid.
/// @tparam Storage chunks storage class /// @tparam Storage chunks storage class
/// @param chunks chunks storage /// @param chunks chunks storage
@ -104,8 +103,10 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
/// @param y position Y /// @param y position Y
/// @param z position Z /// @param z position Z
/// @return true if block exists and solid /// @return true if block exists and solid
template<class Storage> template <class Storage>
inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { inline bool is_solid_at(
const Storage& chunks, int32_t x, int32_t y, int32_t z
) {
if (auto vox = get(chunks, x, y, z)) { if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).rt.solid; return get_block_def(chunks, vox->id).rt.solid;
} }
@ -119,8 +120,10 @@ inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z)
/// @param y position Y /// @param y position Y
/// @param z position Z /// @param z position Z
/// @return true if block exists and replaceable /// @return true if block exists and replaceable
template<class Storage> template <class Storage>
inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) { inline bool is_replaceable_at(
const Storage& chunks, int32_t x, int32_t y, int32_t z
) {
if (auto vox = get(chunks, x, y, z)) { if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).replaceable; return get_block_def(chunks, vox->id).replaceable;
} }
@ -456,12 +459,18 @@ voxel* raycast(
bool includeNonSelectable bool includeNonSelectable
); );
void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(
const Chunks& chunks, VoxelsVolume* volume, bool backlight = false
);
void get_voxels(const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight=false); void get_voxels(
const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight = false
);
template <class Storage> template <class Storage>
inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float z) { inline const AABB* is_obstacle_at(
const Storage& chunks, float x, float y, float z
) {
int ix = std::floor(x); int ix = std::floor(x);
int iy = std::floor(y); int iy = std::floor(y);
int iz = std::floor(z); int iz = std::floor(z);
@ -494,4 +503,63 @@ inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float
return nullptr; return nullptr;
} }
/// @brief Check block grounding
/// @tparam Storage chunks storage class
/// @param chunks chunks storage
/// @param def block definition
/// @param rotationIndex target block rotation index
/// @param origin target block origin
/// @return true if grounded
template <class Storage>
inline bool check_grounding(
const Storage& chunks,
const Block& def,
uint8_t rotationIndex,
const glm::ivec3& origin
) {
const auto& vec = get_ground_direction(def, rotationIndex);
if (!def.rt.extended) {
return blocks_agent::is_solid_at(
chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
);
}
const auto& rotation = def.rotations.variants[rotationIndex];
if (def.groundingBehaviour == GroundingBehaviour::PARTIAL) {
for (int sz = 0; sz < def.size.z; sz++) {
for (int sx = 0; sx < def.size.x; sx++) {
auto pos = origin;
pos += rotation.axes[0] * sx;
pos += rotation.axes[2] * sz;
if (blocks_agent::is_solid_at(
chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
)) {
return true;
}
}
}
return false;
} else if (def.groundingBehaviour == GroundingBehaviour::COMPLETE) {
for (int sz = 0; sz < def.size.z; sz++) {
for (int sx = 0; sx < def.size.x; sx++) {
auto pos = origin;
pos += rotation.axes[0] * sx;
pos += rotation.axes[2] * sz;
if (!blocks_agent::is_solid_at(
chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
)) {
return false;
}
}
}
return true;
} else {
return blocks_agent::is_solid_at(
chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
);
}
}
} // blocks_agent } // blocks_agent