diff --git a/doc/en/main-page.md b/doc/en/main-page.md
index 57a5b25d8..5bb2d8f2d 100644
--- a/doc/en/main-page.md
+++ b/doc/en/main-page.md
@@ -21,5 +21,6 @@ Documentation for 0.31.
- [Rigging](rigging.md)
- [Scripting](scripting.md)
- [Text styles](text-styles.md)
+- [VCM models format](vcm.md)
- [World generator engine](world-generator.md)
- [XML UI building](xml-ui-layouts.md)
diff --git a/doc/en/vcm.md b/doc/en/vcm.md
new file mode 100644
index 000000000..d384bb7d4
--- /dev/null
+++ b/doc/en/vcm.md
@@ -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
+
+
+
+```
+
+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)`
diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md
index c1773d5d1..517dcdfad 100644
--- a/doc/ru/main-page.md
+++ b/doc/ru/main-page.md
@@ -22,5 +22,5 @@
- [Свойства сущностей](entity-properties.md)
- [Скриптинг](scripting.md)
- [Стили текста](text-styles.md)
-- [Частицы](particles.md)
- [Формат моделей VCM](vcm.md)
+- [Частицы](particles.md)
diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua
index f64bfddb6..c7c756f1c 100644
--- a/res/modules/internal/bytearray.lua
+++ b/res/modules/internal/bytearray.lua
@@ -261,7 +261,8 @@ local function create_FFIview_class(name, typename, typesize)
return function()
i = i + 1
if i <= self.bytes.size / typesize then
- return i, self.ptr[i - 1]
+ local ptr = FFI.cast(ptrtype, self.bytes.bytes)
+ return i, ptr[i - 1]
end
end
end
diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp
index d05ccce58..527fc7c71 100644
--- a/src/audio/AL/ALAudio.cpp
+++ b/src/audio/AL/ALAudio.cpp
@@ -47,6 +47,7 @@ static LPALDELETEEFFECTS alDeleteEffects = nullptr;
static LPALISEFFECT alIsEffect = nullptr;
static LPALEFFECTI alEffecti = nullptr;
static LPALEFFECTF alEffectf = nullptr;
+static LPALGETEFFECTF alGetEffectf = nullptr;
static LPALGENFILTERS alGenFilters = nullptr;
static LPALDELETEFILTERS alDeleteFilters = nullptr;
static LPALISFILTER alIsFilter = nullptr;
@@ -562,6 +563,7 @@ static bool request_efx_ext_functions() {
&& get_proc_address("alIsEffect", alIsEffect)
&& get_proc_address("alEffecti", alEffecti)
&& get_proc_address("alEffectf", alEffectf)
+ && get_proc_address("alGetEffectf", alGetEffectf)
&& get_proc_address("alGenFilters", alGenFilters)
&& get_proc_address("alDeleteFilters", alDeleteFilters)
&& get_proc_address("alIsFilter", alIsFilter)
@@ -799,77 +801,73 @@ void ALAudio::setAcoustics(Acoustics acoustics) {
alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, 0);
return;
}
+
+ auto setEffectValue = [](int effect, ALenum param, float value, float min, float max) {
+ const float smoothFactor = 0.5f;
+ value = glm::clamp(value, min, max);
+ float prevValue = 0.0f;
+ alGetEffectf(effect, param, &prevValue);
+ AL_CHECK(alEffectf(
+ effect,
+ param,
+ glm::clamp(
+ prevValue * (1.0f - smoothFactor) + value * smoothFactor,
+ min,
+ max
+ )
+ ));
+ };
+
int reverbEffect = effects[REVERB_EFFECT];
- AL_CHECK(alEffectf(
+ setEffectValue(
reverbEffect,
AL_REVERB_GAIN,
- glm::clamp(glm::min(acoustics.reverbGain, 0.8f) *
+ acoustics.reverbGain *
(1.0f - glm::min(1.0f, acoustics.reverbAbsorption)),
- AL_REVERB_MIN_GAIN,
- AL_REVERB_MAX_GAIN)
- ));
- AL_CHECK(alEffectf(
+ AL_REVERB_MIN_GAIN,
+ AL_REVERB_MAX_GAIN
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_REFLECTIONS_DELAY,
- glm::clamp(
- acoustics.reverbReflectionsDelay,
- AL_REVERB_MIN_REFLECTIONS_DELAY,
- AL_REVERB_MAX_REFLECTIONS_DELAY
- )
- ));
- AL_CHECK(alEffectf(
+ acoustics.reverbReflectionsDelay,
+ AL_REVERB_MIN_REFLECTIONS_DELAY,
+ AL_REVERB_MAX_REFLECTIONS_DELAY
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_LATE_REVERB_DELAY,
- glm::clamp(
- acoustics.reverbLateReflectionsDelay,
- AL_REVERB_MIN_LATE_REVERB_DELAY,
- AL_REVERB_MAX_LATE_REVERB_DELAY
- )
- ));
- AL_CHECK(alEffectf(
+ acoustics.reverbLateReflectionsDelay,
+ AL_REVERB_MIN_LATE_REVERB_DELAY,
+ AL_REVERB_MAX_LATE_REVERB_DELAY
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_DECAY_TIME,
- glm::clamp(
- acoustics.reverbDecayTime,
- AL_REVERB_MIN_DECAY_TIME,
- AL_REVERB_MAX_DECAY_TIME
- )
- ));
- AL_CHECK(alEffectf(
+ acoustics.reverbDecayTime,
+ AL_REVERB_MIN_DECAY_TIME,
+ AL_REVERB_MAX_DECAY_TIME
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_ROOM_ROLLOFF_FACTOR,
- glm::clamp(
- acoustics.reverbRoomRolloff,
- AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR,
- AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR
- )
- ));
- AL_CHECK(alEffectf(
+ acoustics.reverbRoomRolloff,
+ AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR,
+ AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_REFLECTIONS_GAIN,
- glm::clamp(
- glm::pow(acoustics.reverbGain, 2.0f),
- AL_REVERB_MIN_REFLECTIONS_GAIN,
- AL_REVERB_MAX_REFLECTIONS_GAIN
- )
- ));
- AL_CHECK(alEffectf(
+ glm::pow(acoustics.reverbGain, 2.0f),
+ AL_REVERB_MIN_REFLECTIONS_GAIN,
+ AL_REVERB_MAX_REFLECTIONS_GAIN
+ );
+ setEffectValue(
reverbEffect,
AL_REVERB_LATE_REVERB_GAIN,
- glm::clamp(
- glm::pow(acoustics.reverbGain, 2.0f),
- AL_REVERB_MIN_LATE_REVERB_GAIN,
- AL_REVERB_MAX_LATE_REVERB_GAIN
- )
- ));
- AL_CHECK(alEffectf(
- reverbEffect,
- AL_REVERB_DECAY_HFRATIO,
- glm::clamp(
- 1.0f - acoustics.reverbAbsorption,
- AL_REVERB_MIN_DECAY_HFRATIO,
- AL_REVERB_MAX_DECAY_HFRATIO
- )
- ));
+ glm::pow(acoustics.reverbGain, 2.0f),
+ AL_REVERB_MIN_LATE_REVERB_GAIN,
+ AL_REVERB_MAX_LATE_REVERB_GAIN
+ );
alAuxiliaryEffectSloti(effectSlots[0], AL_EFFECTSLOT_EFFECT, reverbEffect);
}
diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp
index 8498091c9..57603d63e 100644
--- a/src/graphics/render/ChunksRenderer.cpp
+++ b/src/graphics/render/ChunksRenderer.cpp
@@ -203,7 +203,7 @@ const Mesh* ChunksRenderer::retrieveChunk(
);
auto mesh = getOrRender(
chunk,
- distance < CHUNK_W * 1.5f * 10.0f,
+ distance < CHUNK_W * 1.5f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
);
if (mesh == nullptr) {
diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp
index 0b1bcaddb..80291194e 100644
--- a/src/graphics/render/Decorator.cpp
+++ b/src/graphics/render/Decorator.cpp
@@ -1,29 +1,30 @@
#include "Decorator.hpp"
-#include "ParticlesRenderer.hpp"
-#include "WorldRenderer.hpp"
-#include "TextsRenderer.hpp"
-#include "TextNote.hpp"
-#include "assets/Assets.hpp"
#include "assets/assets_util.hpp"
+#include "assets/Assets.hpp"
+#include "audio/audio.hpp"
#include "content/Content.hpp"
-#include "voxels/Chunks.hpp"
-#include "voxels/Chunk.hpp"
-#include "voxels/Block.hpp"
-#include "world/Level.hpp"
-#include "window/Camera.hpp"
-#include "objects/Player.hpp"
-#include "objects/Players.hpp"
-#include "objects/Entities.hpp"
-#include "objects/Entity.hpp"
-#include "logic/LevelController.hpp"
-#include "util/stringutil.hpp"
+#include "debug/Logger.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "io/io.hpp"
-#include "audio/audio.hpp"
+#include "logic/LevelController.hpp"
#include "maths/util.hpp"
-#include "debug/Logger.hpp"
+#include "objects/Entities.hpp"
+#include "objects/Entity.hpp"
+#include "objects/Player.hpp"
+#include "objects/Players.hpp"
+#include "ParticlesRenderer.hpp"
+#include "settings.hpp"
+#include "TextNote.hpp"
+#include "TextsRenderer.hpp"
+#include "util/stringutil.hpp"
+#include "voxels/Block.hpp"
+#include "voxels/Chunk.hpp"
+#include "voxels/Chunks.hpp"
+#include "window/Camera.hpp"
+#include "world/Level.hpp"
+#include "WorldRenderer.hpp"
namespace fs = std::filesystem;
@@ -48,6 +49,7 @@ Decorator::Decorator(
)
: level(*controller.getLevel()),
assets(assets),
+ settings(engine.getSettings()),
player(player),
renderer(renderer) {
controller.getBlocksController()->listenBlockInteraction(
@@ -90,6 +92,9 @@ void Decorator::addParticles(const Block& def, const glm::ivec3& pos) {
}
void Decorator::updateAcoustics(const Camera& camera) {
+ if (!settings.audio.acousticEffects.get()) {
+ return;
+ }
audio::Acoustics acoustics {};
util::PseudoRandom random(34621U);
diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp
index 4fe52c50a..cb89dfc37 100644
--- a/src/graphics/render/Decorator.hpp
+++ b/src/graphics/render/Decorator.hpp
@@ -20,10 +20,12 @@ class LevelController;
class WorldRenderer;
struct Weather;
struct WeatherPreset;
+struct EngineSettings;
class Decorator {
const Level& level;
const Assets& assets;
+ const EngineSettings& settings;
Player& player;
WorldRenderer& renderer;
std::unordered_map blockEmitters;
diff --git a/src/graphics/render/ModelBatch.cpp b/src/graphics/render/ModelBatch.cpp
index cefe94ac1..35e891aff 100644
--- a/src/graphics/render/ModelBatch.cpp
+++ b/src/graphics/render/ModelBatch.cpp
@@ -135,7 +135,7 @@ void ModelBatch::setLightsOffset(const glm::vec3& offset) {
void ModelBatch::setTexture(const std::string& name,
const texture_names_map* varTextures) {
- if (varTextures && name.at(0) == '$') {
+ if (varTextures && !name.empty() && name.at(0) == '$') {
const auto& found = varTextures->find(name);
if (found == varTextures->end()) {
return batch->setTexture(nullptr);
diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp
index 1e3ad2441..2de34442f 100644
--- a/src/logic/BlocksController.cpp
+++ b/src/logic/BlocksController.cpp
@@ -16,6 +16,11 @@
#include "world/World.hpp"
#include "objects/Player.hpp"
#include "objects/Players.hpp"
+#include "util/random.hpp"
+
+#include
+
+static inline constexpr int CHUNK_RANDOM_TICK_SEGMENTS = 4;
BlocksController::BlocksController(const Level& level, Lighting* lighting)
: level(level),
@@ -143,20 +148,36 @@ void BlocksController::onBlocksTick(int tickid, int parts) {
}
void BlocksController::randomTick(
- const Chunk& chunk, int segments, const ContentIndices* indices
+ const Chunk& chunk, const ContentIndices* indices
) {
+ const int segments = CHUNK_RANDOM_TICK_SEGMENTS;
const int segheight = CHUNK_H / segments;
+ static std::array randomPattern;
+ static bool randomPatternInitialized = false;
+ if (!randomPatternInitialized) {
+ randomPatternInitialized = true;
+ for (int i = 0; i < randomPattern.size(); i++) {
+ randomPattern[i] = i;
+ }
+ auto randomDevice = std::random_device{};
+ auto randomEngine =
+ util::seeded_random_engine(randomDevice);
+ std::shuffle(randomPattern.begin(), randomPattern.end(), randomEngine);
+ }
+
for (int s = 0; s < segments; s++) {
- for (int i = 0; i < 4; i++) {
+ int repetions = 4;
+ for (int i = 0; i < repetions; i++) {
int segmentY = s * segheight;
- if (segmentY > chunk.top) {
+ if (segmentY > chunk.top) {
break;
}
- int bx = random.rand() % CHUNK_W;
- int by = random.rand() % segheight + segmentY;
- int bz = random.rand() % CHUNK_D;
- const voxel& vox = chunk.voxels[vox_index(bx, by, bz)];
+ size_t index = randomPattern[(s * repetions + i + randomTickId) % randomPattern.size()];
+ int bx = index % CHUNK_W;
+ int bz = (index / CHUNK_W) % CHUNK_D;
+ int by = (index / (CHUNK_W * CHUNK_D)) + segmentY;
+ const voxel& vox = chunk.voxels[index + segmentY * CHUNK_W * CHUNK_D];
auto& block = indices->blocks.require(vox.id);
if (block.rt.funcsset.randupdate) {
scripting::random_update_block(
@@ -177,7 +198,6 @@ void BlocksController::randomTick(int tickid, int parts, uint padding) {
const auto& chunks = *player->chunks;
int width = chunks.getWidth();
int height = chunks.getHeight();
- int segments = 4;
for (uint z = padding; z < height - padding; z++) {
for (uint x = padding; x < width - padding; x++) {
@@ -193,7 +213,7 @@ void BlocksController::randomTick(int tickid, int parts, uint padding) {
continue;
}
chunk->lastRandomTickId = randomTickId;
- randomTick(*chunk, segments, indices);
+ randomTick(*chunk, indices);
}
}
}
diff --git a/src/logic/BlocksController.hpp b/src/logic/BlocksController.hpp
index ed6ebca5b..afd3f59f4 100644
--- a/src/logic/BlocksController.hpp
+++ b/src/logic/BlocksController.hpp
@@ -3,7 +3,6 @@
#include
#include
-#include "maths/fastmaths.hpp"
#include "typedefs.hpp"
#include "util/Clock.hpp"
#include "voxels/voxel.hpp"
@@ -31,7 +30,6 @@ class BlocksController {
util::Clock randTickClock;
util::Clock blocksTickClock;
util::Clock worldTickClock;
- FastRandom random {};
std::vector blockInteractionCallbacks;
uint64_t randomTickId = 0;
public:
@@ -47,9 +45,7 @@ public:
);
void update(float delta, uint padding);
- void randomTick(
- const Chunk& chunk, int segments, const ContentIndices* indices
- );
+ void randomTick(const Chunk& chunk, const ContentIndices* indices);
void randomTick(int tickid, int parts, uint padding);
void onBlocksTick(int tickid, int parts);
int64_t createBlockInventory(int x, int y, int z);
diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp
index fa0b51cdb..a9691038e 100644
--- a/src/logic/scripting/lua/libs/libassets.cpp
+++ b/src/logic/scripting/lua/libs/libassets.cpp
@@ -1,16 +1,17 @@
#include "api_lua.hpp"
+#include "../usertypes/lua_type_canvas.hpp"
#include "assets/Assets.hpp"
#include "assets/AssetsLoader.hpp"
+#include "coders/obj.hpp"
#include "coders/png.hpp"
#include "coders/vcm.hpp"
#include "debug/Logger.hpp"
#include "engine/Engine.hpp"
#include "graphics/commons/Model.hpp"
-#include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp"
+#include "graphics/core/Texture.hpp"
#include "util/Buffer.hpp"
-#include "../usertypes/lua_type_canvas.hpp"
using namespace scripting;
@@ -76,6 +77,11 @@ static int l_parse_model(lua::State* L) {
skeletonName = lua::require_string(L, 4);
}
+ if (format == "obj") {
+ assets.store(obj::parse(name, string), name);
+ return 0;
+ }
+
if (format != "xml" && format != "vcm") {
throw std::runtime_error(
"unknown format " + util::quote(std::string(format))