Merge branch 'main' into more-about-ui

This commit is contained in:
MihailRis 2026-08-11 19:34:33 +03:00
commit 63f2751910
61 changed files with 841 additions and 632 deletions

View file

@ -69,6 +69,7 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
- "pipe" - wood logs, pipes, pillars
- "pane" - panels, doors, signs
- "stairs" - "pane" + flipped variants
- "ladder" - like a "pipe", but on horizontal surfaces it is installed like "pane"
### *particles*

View file

@ -24,7 +24,7 @@ Called on block broken by player
function on_replaced(x, y, z, playerid)
```
Called on block replaced with other by player
Called before a player replaces a block
```lua
function on_interact(x, y, z, playerid) -> bool

View file

@ -212,10 +212,27 @@ local map = Heightmap(width, height)
Operations apply to all height values.
```lua
-- Convert to absolute values
map:abs()
```
Casts height values ​​to absolute.
-- Round down
map:floor()
-- Round up
map:ceil()
-- Round to the nearest integer
map:round()
-- Sine of height values ​​(in radians)
map:sin()
-- Cosine of height values ​​(in radians)
map:cos()
-- Tangent of height values ​​(in radians)
map:tan()
```
### Binary Operations

View file

@ -70,6 +70,7 @@
- "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка
- "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка
- "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты)
- "ladder" - профиль "лестница" (как "pipe", но на горизонтальных поверхностях как "pane")
### Испускаемые частицы - *particles*

View file

@ -34,7 +34,7 @@ function on_broken(x, y, z, playerid)
function on_replaced(x, y, z, playerid)
```
Вызывается после замены блока игроком
Вызывается перед заменой блока игроком
```lua
function on_interact(x, y, z, playerid) -> bool

View file

@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота)
Операции применяются ко всем значениям высоты.
```lua
-- Приведение значений высот к абсолютным
map:abs()
-- Округление к меньшему целому
map:floor()
-- Округление к большему целому
map:ceil()
-- Округление к ближайшему целому
map:round()
-- Синус от значений высот (в радианах)
map:sin()
-- Косинус от значений высот (в радианах)
map:cos()
-- Тангенс от значений высот (в радианах)
map:tan()
```
Приводит значения высот к абсолютным.
### Бинарные операции
Операции с применением второй карты или скаляра.

View file

@ -1,6 +1,6 @@
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}">
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'>
[%{id_verbose}]
<label color='#FFFFFF80' size='340,25' align='right' gravity='top-right'>
[%{id_verbose} %{version}]
</label>
<label pos='78,6'>%{title}</label>
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'

View file

@ -1,6 +1,9 @@
#include <shadows>
#include <fog>
#define CLOUDS_FOG_FACTOR_MUL 0.3f
#define CLOUDS_FOG_CURVE_MUL 0.4f
vec4 effect() {
vec4 pos = texture(u_position, v_uv);
float light = 1.0;
@ -32,6 +35,10 @@ vec4 effect() {
light = pow(light, u_gamma);
vec3 fogColor = texture(u_skybox, dir).rgb;
float fog = calc_fog(length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0);
float fog = calc_fog(
length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0,
mix(1.0, CLOUDS_FOG_FACTOR_MUL, emission),
mix(1.0, CLOUDS_FOG_CURVE_MUL, emission)
);
return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0);
}

View file

@ -15,11 +15,33 @@ uniform float u_fogCurve;
uniform bool u_alphaClip;
uniform vec3 u_sunDir;
const float BAYER_MATRIX[64] = float[](
0.0, 32.0, 8.0, 40.0, 2.0, 34.0, 10.0, 42.0,
48.0, 16.0, 56.0, 24.0, 50.0, 18.0, 58.0, 26.0,
12.0, 44.0, 4.0, 36.0, 14.0, 46.0, 6.0, 38.0,
60.0, 28.0, 52.0, 20.0, 62.0, 30.0, 54.0, 22.0,
3.0, 35.0, 11.0, 43.0, 1.0, 33.0, 9.0, 41.0,
51.0, 19.0, 59.0, 27.0, 49.0, 17.0, 57.0, 25.0,
15.0, 47.0, 7.0, 39.0, 13.0, 45.0, 5.0, 37.0,
63.0, 31.0, 55.0, 23.0, 61.0, 29.0, 53.0, 21.0
);
float calc_bayer(vec2 pos){
ivec2 p = ivec2(mod(pos, 8.0));
return BAYER_MATRIX[p.y * 8 + p.x] / 64.0;
}
void main() {
vec4 texColor = texture(u_texture0, a_texCoord);
float alpha = a_color.a * texColor.a;
// anyway it's any alpha-test alternative required
if (alpha < (u_alphaClip ? 0.5f : 0.15f)) {
if (u_alphaClip) {
float bayer = calc_bayer(gl_FragCoord.xy);
if (alpha - bayer < 0.01f) {
discard;
}
alpha = 1.0;
} else if (alpha < 0.05f) {
discard;
}
f_color = a_color * texColor;

View file

@ -7,13 +7,17 @@ uniform float u_weatherFogOpacity;
uniform float u_weatherFogDencity;
uniform float u_weatherFogCurve;
float calc_fog(float depth) {
float calc_fog(float depth, float fogFactor, float fogCurve) {
return min(
1.0,
max(pow(depth * u_fogFactor, u_fogCurve),
max(pow(depth * u_fogFactor * fogFactor, u_fogCurve * fogCurve),
min(pow(depth * u_weatherFogDencity, u_weatherFogCurve),
u_weatherFogOpacity))
);
}
float calc_fog(float depth) {
return calc_fog(depth, 1.0, 1.0);
}
#endif // FOG_GLSL_

View file

@ -151,6 +151,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
def.rotations = BlockRotProfile::PANE;
} else if (profile == BlockRotProfile::STAIRS_NAME) {
def.rotations = BlockRotProfile::STAIRS;
} else if (profile == BlockRotProfile::LADDER_NAME) {
def.rotations = BlockRotProfile::LADDER;
} else if (profile != "none") {
logger.error() << "unknown rotation profile " << profile;
def.rotatable = false;

View file

@ -48,7 +48,7 @@ void ServerMainloop::run() {
delta = time.getDelta();
}
if (controller) {
controller->getLevel()->getWorld()->updateTimers(delta);
controller->getLevel()->getWorld().updateTimers(delta);
controller->update(glm::min(delta, 0.2), false);
}
engine.applicationTick();

View file

@ -147,3 +147,7 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
LevelController& LevelFrontend::getController() const {
return controller;
}
Weather& LevelFrontend::getWeather() {
return weather;
}

View file

@ -1,5 +1,7 @@
#pragma once
#include "world/Weather.hpp"
#include <memory>
class Assets;
@ -15,6 +17,7 @@ class LevelFrontend {
LevelController& controller;
Assets& assets;
std::unique_ptr<ContentGfxCache> contentCache;
Weather weather {};
public:
LevelFrontend(
Engine& engine,
@ -28,4 +31,5 @@ public:
const ContentGfxCache& getContentGfxCache() const;
ContentGfxCache& getContentGfxCache();
LevelController& getController() const;
Weather& getWeather();
};

View file

@ -254,7 +254,7 @@ std::shared_ptr<UINode> create_debug_panel(
}
}));
panel->add(create_label(gui, [&]() {
return L"seed: " + std::to_wstring(level.getWorld()->getSeed());
return L"seed: " + std::to_wstring(level.getWorld().getSeed());
}));
for (int ax = 0; ax < 3; ax++) {
@ -294,7 +294,7 @@ std::shared_ptr<UINode> create_debug_panel(
sub->add(box, glm::vec2(20, 0));
panel->add(sub);
}
auto& worldInfo = level.getWorld()->getInfo();
auto& worldInfo = level.getWorld().getInfo();
panel->add(create_label(gui, [&]() {
int hour, minute, second;
timeutil::from_value(worldInfo.daytime, hour, minute, second);
@ -310,12 +310,6 @@ std::shared_ptr<UINode> create_debug_panel(
bar->setConsumer([&](double val) { worldInfo.daytime = val; });
panel->add(bar);
}
if (allowDebugCheats) {
auto bar = std::make_shared<TrackBar>(gui, 0.0f, 1.0f, 0.0f, 0.005f, 8);
bar->setSupplier([&]() { return worldInfo.fog; });
bar->setConsumer([&](double val) { worldInfo.fog = val; });
panel->add(bar);
}
{
auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Chunk Borders", glm::vec2(400, 24)

View file

@ -43,7 +43,7 @@ LevelScreen::LevelScreen(
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
)
: Screen(engine),
world(*levelPtr->getWorld()),
world(levelPtr->getWorld()),
postProcessing(std::make_unique<PostProcessing>(
levelPtr->content.getIndices(ResourceType::POST_EFFECT_SLOT).size()
)),
@ -81,7 +81,7 @@ LevelScreen::LevelScreen(
auto resetChunks = [=](bool) {
player->chunks->saveAndClear();
renderer->clear();
renderer->resetCache();
};
keepAlive(settings.graphics.backlight.observe(resetChunks));
keepAlive(settings.graphics.softLighting.observe(resetChunks));
@ -94,11 +94,11 @@ LevelScreen::LevelScreen(
}));
keepAlive(input.addCallback(BIND_CHUNKS_RELOAD, [=]() {
player->chunks->saveAndClear();
renderer->clear();
renderer->resetCache();
return false;
}));
controller->preQuitCallbacks.listen([this]() {
if (!controller->getLevel()->getWorld()->isNameless()) {
if (!controller->getLevel()->getWorld().isNameless()) {
saveWorldPreview();
}
});
@ -109,7 +109,7 @@ LevelScreen::LevelScreen(
}
LevelScreen::~LevelScreen() {
if (!controller->getLevel()->getWorld()->isNameless()) {
if (!controller->getLevel()->getWorld().isNameless()) {
saveDecorations();
}
scripting::on_frontend_close();
@ -153,7 +153,7 @@ void LevelScreen::loadDecorations() {
}
auto data = io::read_object(CLIENT_FILE);
if (data.has("weather")) {
renderer->getWeather().deserialize(data["weather"]);
frontend->getWeather().deserialize(data["weather"]);
}
}
@ -161,7 +161,7 @@ void LevelScreen::saveDecorations() {
io::create_directory("world:client");
auto data = dv::object();
data["weather"] = renderer->getWeather().serialize();
data["weather"] = frontend->getWeather().serialize();
io::write_json(CLIENT_FILE, data, true);
}
@ -257,7 +257,7 @@ void LevelScreen::update(float delta) {
hud->update(hudVisible);
const Weather& weather = renderer->getWeather();
const Weather& weather = frontend->getWeather();
const Player& player = playerController->getPlayer();
const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather);

View file

@ -372,7 +372,7 @@ void Batch2D::sprite(
void Batch2D::flush() {
if (index == 0)
return;
mesh->reload(buffer.get(), index);
mesh->reload(buffer.get(), index, true);
mesh->draw(gl::to_glenum(primitive));
index = 0;
}

View file

@ -292,13 +292,13 @@ void Batch3D::point(const glm::vec3& coord, const glm::vec4& tint) {
}
void Batch3D::flush() {
mesh->reload(buffer.get(), index);
mesh->reload(buffer.get(), index, true);
mesh->draw();
index = 0;
}
void Batch3D::flushPoints() {
mesh->reload(buffer.get(), index);
mesh->reload(buffer.get(), index, true);
mesh->draw(GL_POINTS);
index = 0;
}

View file

@ -57,7 +57,7 @@ void LineBatch::box(float x, float y, float z, float w, float h, float d,
void LineBatch::flush(){
if (index == 0)
return;
mesh->reload(buffer.get(), index);
mesh->reload(buffer.get(), index, true);
mesh->draw(GL_LINES);
index = 0;
}

View file

@ -47,12 +47,15 @@ public:
void reload(
const VertexStructure* vertexBuffer,
size_t vertexCount,
const std::vector<IndexBufferData>& indices
const std::vector<IndexBufferData>& indices,
bool streaming
);
void reload(const VertexStructure* vertexBuffer, size_t vertexCount) {
void reload(
const VertexStructure* vertexBuffer, size_t vertexCount, bool streaming
) {
static const std::vector<IndexBufferData> indices {};
reload(vertexBuffer, vertexCount, indices);
reload(vertexBuffer, vertexCount, indices, streaming);
}
/// @brief Draw mesh with specified primitives type
@ -61,6 +64,8 @@ public:
/// @brief Draw mesh as triangles
void draw() const;
private:
};
#include "graphics/core/Mesh.inl"

View file

@ -46,9 +46,9 @@ Mesh<VertexStructure>::Mesh(
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
reload(vertexBuffer, vertices, std::move(indices));
glBindVertexArray(vao);
reload(vertexBuffer, vertices, std::move(indices), false);
// attributes
int offset = 0;
for (int i = 0; attrs[i].count; i++) {
@ -82,20 +82,22 @@ template <typename VertexStructure>
void Mesh<VertexStructure>::reload(
const VertexStructure* vertexBuffer,
size_t vertexCount,
const std::vector<IndexBufferData>& indices
const std::vector<IndexBufferData>& indices,
bool streaming
) {
this->vertexCount = vertexCount;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (vertexBuffer != nullptr && vertexCount != 0) {
glBufferData(
GL_ARRAY_BUFFER,
vertexCount * sizeof(VertexStructure),
vertexBuffer,
GL_STREAM_DRAW
streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW
);
} else {
glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW);
glBufferData(
GL_ARRAY_BUFFER, 0, {}, streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW
);
}
for (int i = 0; i < ibos.size(); i++) {
@ -116,7 +118,6 @@ void Mesh<VertexStructure>::reload(
GL_STATIC_DRAW
);
}
glBindVertexArray(0);
}
template <typename VertexStructure>

View file

@ -186,7 +186,8 @@ void Shader::recompile(const std::vector<std::string>& defines) {
glDeleteProgram(id);
id = newProgram;
uniformLocations.clear();
logger.info() << "shader " << id << " has been recompiled";
logger.debug() << "shader program (" << vertexSource.file << ", "
<< fragmentSource.file << ") has been recompiled";
}
std::unique_ptr<Shader> Shader::create(

View file

@ -99,8 +99,8 @@ void Shadows::setup(Shader& shader, const Weather& weather) {
if (!shadows) {
return;
}
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
const auto& worldInfo = level.getWorld().getInfo();
float cloudsIntensity = weather.clouds();
float shadowsOpacity = 1.0f - cloudsIntensity;
shadowsOpacity *= glm::sqrt(glm::abs(
glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f
@ -158,8 +158,8 @@ void Shadows::generateShadowsMap(
float scale,
const std::function<void(Camera&)>& renderShadowPass
) {
auto world = level.getWorld();
const auto& worldInfo = world->getInfo();
const auto& world = level.getWorld();
const auto& worldInfo = world.getInfo();
int resolution = shadowMap.getResolution();
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;

View file

@ -10,15 +10,14 @@
#include "frontend/ContentGfxCache.hpp"
const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704);
const float DIRECTIONAL_LIGHT_FACTOR = 0.3f;
constexpr float DIRECTIONAL_LIGHT_FACTOR = 0.3f;
BlocksRenderer::BlocksRenderer(
size_t capacity,
const Content& content,
const Block* const* blockDefs,
const ContentGfxCache& cache,
const EngineSettings& settings
) : content(content),
vertexBuffer(std::make_unique<ChunkVertex[]>(capacity)),
) : vertexBuffer(std::make_unique<ChunkVertex[]>(capacity)),
indexBuffer(std::make_unique<uint32_t[]>(capacity)),
denseIndexBuffer(std::make_unique<uint32_t[]>(capacity)),
vertexCount(0),
@ -28,7 +27,7 @@ BlocksRenderer::BlocksRenderer(
cache(cache),
settings(settings)
{
blockDefsCache = content.getIndices()->blocks.getDefs();
blockDefsCache = blockDefs;
}
BlocksRenderer::~BlocksRenderer() = default;
@ -482,83 +481,78 @@ glm::vec4 BlocksRenderer::pickSoftLight(
}
void BlocksRenderer::render(
const voxel* voxels, const int beginEnds[256][2]
const voxel* voxels, int totalBegin, int totalEnd
) {
bool denseRender = this->denseRender;
bool densePass = this->densePass;
bool enableAO = settings.graphics.softLighting.get();
for (const auto drawGroup : *content.drawGroups) {
int begin = beginEnds[drawGroup][0];
if (begin == 0) {
for (int i = totalBegin; i < totalEnd; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
const auto& variant = def.getVariant(variantId);
if (id == 0 || state.segment) {
continue;
}
int end = beginEnds[drawGroup][1];
for (int i = begin-1; i <= end; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
const auto& variant = def.getVariant(variantId);
if (id == 0 || variant.drawGroup != drawGroup || state.segment) {
continue;
if (denseRender != (variant.culling == CullingMode::OPTIONAL)) {
continue;
}
if (def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
if (!denseRender)
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
if (denseRender != (variant.culling == CullingMode::OPTIONAL)) {
continue;
case BlockModelType::AABB: {
if (!denseRender)
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion && enableAO);
break;
}
if (def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
if (!denseRender)
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
if (!denseRender)
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion && enableAO);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (overflow) {
return;
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (overflow) {
return;
}
}
}
SortingMeshData BlocksRenderer::renderTranslucent(
const voxel* voxels, int beginEnds[256][2]
const voxel* voxels, int totalBegin, int totalEnd
) {
SortingMeshData sortingMesh {{}};
@ -568,106 +562,98 @@ SortingMeshData BlocksRenderer::renderTranslucent(
bool densePass = this->densePass;
bool enableAO = settings.graphics.softLighting.get();
for (const auto drawGroup : *content.drawGroups) {
int begin = beginEnds[drawGroup][0];
if (begin == 0) {
for (int i = totalBegin; i < totalEnd; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
if (id == 0 || state.segment) {
continue;
}
int end = beginEnds[drawGroup][1];
for (int i = begin-1; i <= end; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
const auto& variant = def.getVariant(variantId);
if (id == 0 || variant.drawGroup != drawGroup || state.segment) {
continue;
}
if (!def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
blockAABB(
{x, y, z},
texfaces,
&def,
vox.state.rotation,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (vertexCount == 0) {
continue;
}
SortingMeshEntry entry {
glm::vec3(
x + chunk->x * CHUNK_W + 0.5f,
y + 0.5f,
z + chunk->z * CHUNK_D + 0.5f
),
util::Buffer<ChunkVertex>(indexCount), 0};
totalSize += entry.vertexData.size();
for (int j = 0; j < indexCount; j++) {
std::memcpy(
entry.vertexData.data() + j,
vertexBuffer.get() + indexBuffer[j],
sizeof(ChunkVertex)
);
ChunkVertex& vertex = entry.vertexData[j];
if (!aabbInit) {
aabbInit = true;
aabb.a = aabb.b = vertex.position;
} else {
aabb.addPoint(vertex.position);
}
vertex.position.x += chunk->x * CHUNK_W + 0.5f;
vertex.position.y += 0.5f;
vertex.position.z += chunk->z * CHUNK_D + 0.5f;
}
sortingMesh.entries.push_back(std::move(entry));
vertexCount = 0;
vertexOffset = indexCount = 0;
if (!def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
blockAABB(
{x, y, z},
texfaces,
&def,
vox.state.rotation,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (vertexCount == 0) {
continue;
}
SortingMeshEntry entry {
glm::vec3(
x + chunk->x * CHUNK_W + 0.5f,
y + 0.5f,
z + chunk->z * CHUNK_D + 0.5f
),
util::Buffer<ChunkVertex>(indexCount), 0};
totalSize += entry.vertexData.size();
for (int j = 0; j < indexCount; j++) {
std::memcpy(
entry.vertexData.data() + j,
vertexBuffer.get() + indexBuffer[j],
sizeof(ChunkVertex)
);
ChunkVertex& vertex = entry.vertexData[j];
if (!aabbInit) {
aabbInit = true;
aabb.a = aabb.b = vertex.position;
} else {
aabb.addPoint(vertex.position);
}
vertex.position.x += chunk->x * CHUNK_W + 0.5f;
vertex.position.y += 0.5f;
vertex.position.z += chunk->z * CHUNK_D + 0.5f;
}
sortingMesh.entries.push_back(std::move(entry));
vertexCount = 0;
vertexOffset = indexCount = 0;
}
// additional powerful optimization
@ -709,19 +695,16 @@ void BlocksRenderer::build(
int totalBegin = chunk->bottom * (CHUNK_W * CHUNK_D);
int totalEnd = chunk->top * (CHUNK_W * CHUNK_D);
bool hasTranslucent = false;
int beginEnds[256][2] {};
for (int i = totalBegin; i < totalEnd; i++) {
const voxel& vox = voxels[i];
blockid_t id = vox.id;
const auto& def = *blockDefsCache[id];
const auto& variant = def.getVariantByBits(vox.state.userbits);
hasTranslucent = def.translucent || hasTranslucent;
if (beginEnds[variant.drawGroup][0] == 0) {
beginEnds[variant.drawGroup][0] = i + 1;
if (def.translucent) {
hasTranslucent = true;
break;
}
beginEnds[variant.drawGroup][1] = i;
}
cancelled = false;
@ -733,7 +716,7 @@ void BlocksRenderer::build(
densePass = false;
if (hasTranslucent) {
sortingMesh = renderTranslucent(voxels, beginEnds);
sortingMesh = renderTranslucent(voxels, totalBegin, totalEnd);
} else {
sortingMesh = {};
}
@ -746,13 +729,13 @@ void BlocksRenderer::build(
denseRender = false;
densePass = false;
render(voxels, beginEnds);
render(voxels, totalBegin, totalEnd);
size_t endIndex = indexCount;
denseRender = true;
densePass = settings.graphics.denseRender.get();
render(voxels, beginEnds);
render(voxels, totalBegin, totalEnd);
denseIndexCount = indexCount;
for (size_t i = 0; i < denseIndexCount; i++) {
@ -761,7 +744,7 @@ void BlocksRenderer::build(
indexCount = endIndex;
densePass = false;
render(voxels, beginEnds);
render(voxels, totalBegin, totalEnd);
}
ChunkMeshData BlocksRenderer::createMesh() {

View file

@ -25,7 +25,7 @@ class BlocksRenderer final {
public:
BlocksRenderer(
size_t capacity,
const Content& content,
const Block* const* blockDefs,
const ContentGfxCache& cache,
const EngineSettings& settings
);
@ -44,7 +44,6 @@ public:
}
private:
static const glm::vec3 SUN_VECTOR;
const Content& content;
std::unique_ptr<ChunkVertex[]> vertexBuffer;
std::unique_ptr<uint32_t[]> indexBuffer;
std::unique_ptr<uint32_t[]> denseIndexBuffer;
@ -145,13 +144,16 @@ private:
);
// Does block allow to see other blocks sides (is it transparent)
inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const {
bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const {
const auto& vox = voxelsBuffer->pickBlock(
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z
);
if (vox.id == BLOCK_VOID) {
return false;
}
if (def.rt.id == vox.id && def.rt.solid && variant.culling == CullingMode::DEFAULT) {
return false;
}
const auto& block = *blockDefsCache[vox.id];
const auto& blockVariant = block.getVariantByBits(vox.state.userbits);
uint8_t otherDrawGroup = blockVariant.drawGroup;
@ -178,6 +180,6 @@ private:
float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up
) const;
void render(const voxel* voxels, const int beginEnds[256][2]);
SortingMeshData renderTranslucent(const voxel* voxels, int beginEnds[256][2]);
void render(const voxel* voxels, int totalBegin, int totalEnd);
SortingMeshData renderTranslucent(const voxel* voxels, int totalBegin, int totalEnd);
};

View file

@ -11,9 +11,12 @@
#include "world/Level.hpp"
#include "window/Camera.hpp"
#include "maths/FrustumCulling.hpp"
#include "maths/util.hpp"
#include "util/listutil.hpp"
#include "util/ObjectsPool.hpp"
#include "util/timeutil.hpp"
#include "settings.hpp"
#include "content/Content.hpp"
static debug::Logger logger("chunks-render");
@ -33,7 +36,7 @@ public:
settings.graphics.denseRender.get()
? settings.graphics.chunkMaxVerticesDense.get()
: settings.graphics.chunkMaxVertices.get(),
level.content,
level.content.getIndices()->blocks.getDefs(),
cache,
settings
) {
@ -92,7 +95,7 @@ ChunksRenderer::ChunksRenderer(
threadPool.setStopOnFail(false);
renderer = std::make_unique<BlocksRenderer>(
settings.graphics.chunkMaxVertices.get(),
level.content, cache, settings
level.content.getIndices()->blocks.getDefs(), cache, settings
);
logger.info() << "created " << threadPool.getWorkersCount() << " workers";
logger.info() << "memory consumption is "
@ -122,30 +125,30 @@ std::shared_ptr<VoxelsRenderVolume> ChunksRenderer::prepareVoxelsVolume(
return voxelsBuffer;
}
const ChunkMesh* ChunksRenderer::render(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
void ChunksRenderer::renderBlocking(const std::shared_ptr<Chunk>& chunk) {
glm::ivec2 key(chunk->x, chunk->z);
ChunkMesh mesh {};
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] = std::move(mesh);
chunk->flags.modified = false;
}
void ChunksRenderer::render(
const std::shared_ptr<Chunk>& chunk, bool lowPriority
) {
glm::ivec2 key(chunk->x, chunk->z);
if (important) {
ChunkMesh mesh {};
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] = std::move(mesh);
chunk->flags.modified = false;
return &meshes[key];
}
if (inwork.find(key) != inwork.end() ||
((inwork.size() >= threadPool.getWorkersCount() ||
enqueuedInFrame >= MAX_CHUNKS_ENQUEUED_IN_FRAME) &&
lowPriority)) {
return nullptr;
return;
}
chunk->flags.modified = false;
enqueuedInFrame++;
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
threadPool.enqueueJob({chunk, std::move(voxelsBuffer)});
inwork[key] = true;
return nullptr;
}
void ChunksRenderer::unload(const Chunk* chunk) {
@ -161,76 +164,69 @@ void ChunksRenderer::clear() {
threadPool.clearQueue();
}
const ChunkMesh* ChunksRenderer::getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
) {
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end()) {
return render(chunk, important, lowPriority);
}
if (chunk->flags.modified && chunk->flags.lighted) {
render(chunk, important, lowPriority);
}
return &found->second;
}
void ChunksRenderer::update() {
threadPool.pullResults();
enqueuedInFrame = 0;
}
const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
size_t index, const Camera& camera, bool culling
) {
auto chunk = chunks.getChunks()[index];
if (chunk == nullptr) {
return nullptr;
}
if (!chunk->flags.lighted) {
const auto& found = meshes.find({chunk->x, chunk->z});
if (found == meshes.end()) {
return nullptr;
} else {
return found->second.mesh.get();
int width = chunks.getWidth();
int halfWidth = width / 2;
int halfHeight = chunks.getHeight() / 2;
int centerX = chunks.getOffsetX() + halfWidth;
int centerY = chunks.getOffsetY() + halfHeight;
meshBuildQueue.clear();
auto& chunksArray = chunks.getChunks();
for (int index = 0; index < chunks.getVolume(); index++) {
const auto& chunk = chunks.getChunks()[index];
if (chunk == nullptr || !chunk->flags.lighted) {
continue;
}
int x = chunk->x;
int z = chunk->z;
if (chunk->flags.modified || meshes.find({x, z}) == meshes.end()) {
meshBuildQueue.emplace_back(x - centerX, z - centerY);
}
}
float distance = glm::distance(
camera.position,
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
camera.position.y,
(chunk->z + 0.5f) * CHUNK_D
)
std::sort(
meshBuildQueue.begin(),
meshBuildQueue.end(),
[](const glm::ivec2& a, const glm::ivec2& b) {
return util::length2(a) < util::length2(b);
}
);
auto mesh = getOrRender(
chunk,
distance < CHUNK_W * 1.5f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
);
if (mesh == nullptr) {
return nullptr;
}
if (chunk->flags.dirtyHeights) {
chunk->updateHeights();
}
if (culling) {
const auto& meshAABB = mesh->meshAABB;
auto aabbMin = meshAABB.min();
auto aabbMax = meshAABB.max();
glm::vec3 min(
chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x),
chunk->bottom,
chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z)
);
glm::vec3 max(
chunk->x * CHUNK_W + aabbMax.x,
chunk->top,
chunk->z * CHUNK_D + aabbMax.z
);
if (!frustum.isBoxVisible(min, max)) return nullptr;
int loadDistance = settings.chunks.loadDistance.get();
const int topN = 10;
int top = std::min<int>(meshBuildQueue.size(), topN);
for (int i = 0; i < top; i++) {
glm::ivec2 offset = meshBuildQueue[i];
size_t index =
(offset.y + halfHeight) * width + offset.x + halfWidth;
auto& chunk = chunksArray[index];
assert(chunk != nullptr);
float distance = glm::distance(
glm::vec3 (centerX, 0.0f, centerY),
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
0,
(chunk->z + 0.5f) * CHUNK_D
)
);
bool important = distance < CHUNK_W * 1.5f;
bool lowPriority = distance > CHUNK_W * loadDistance * 0.5;
if (chunk->flags.dirtyHeights) {
chunk->updateHeights();
}
if (important) {
renderBlocking(chunk);
} else {
render(chunk, lowPriority);
}
}
return mesh->mesh.get();
}
void ChunksRenderer::drawShadowsPass(
@ -317,16 +313,43 @@ void ChunksRenderer::drawChunks(
// TODO: minimize draw calls number
for (int i = indices.size()-1; i >= 0; i--) {
auto& chunk = chunks.getChunks()[indices[i].index];
auto mesh = retrieveChunk(indices[i].index, camera, culling);
if (mesh == nullptr) {
if (chunk == nullptr) {
continue;
}
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end()) {
continue;
}
auto& mesh = found->second;
if (mesh.mesh == nullptr) {
continue;
}
if (culling) {
const auto& meshAABB = mesh.meshAABB;
auto aabbMin = meshAABB.min();
auto aabbMax = meshAABB.max();
glm::vec3 min(
chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x),
chunk->bottom,
chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z)
);
glm::vec3 max(
chunk->x * CHUNK_W + aabbMax.x,
chunk->top,
chunk->z * CHUNK_D + aabbMax.z
);
if (!frustum.isBoxVisible(min, max)) {
continue;
}
}
glm::vec3 coord(
chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f
);
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
shader.uniformMatrix("u_model", model);
mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1),
mesh.mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1),
(coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2);
visibleChunks++;
}

View file

@ -1,16 +1,17 @@
#pragma once
#define GLM_ENABLE_EXPERIMENTAL
#include "util/ThreadPool.hpp"
#include "commons.hpp"
#include <memory>
#include <vector>
#include <unordered_map>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>
#include "util/ThreadPool.hpp"
#include "commons.hpp"
template<typename VertexStructure> class Mesh;
class Chunk;
class Level;
@ -44,22 +45,6 @@ struct RendererJob {
};
class ChunksRenderer {
const Chunks& chunks;
const Assets& assets;
const Frustum& frustum;
const EngineSettings& settings;
std::unique_ptr<BlocksRenderer> renderer;
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
std::unordered_map<glm::ivec2, bool> inwork;
std::vector<ChunksSortEntry> indices;
util::ThreadPool<RendererJob, RendererResult> threadPool;
const Mesh<ChunkVertex>* retrieveChunk(
size_t index, const Camera& camera, bool culling
);
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
size_t enqueuedInFrame = 0;
public:
ChunksRenderer(
const Level& level,
@ -69,18 +54,11 @@ public:
const ContentGfxCache& cache,
const EngineSettings& settings
);
virtual ~ChunksRenderer();
~ChunksRenderer();
const ChunkMesh* render(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void unload(const Chunk* chunk);
void clear();
const ChunkMesh* getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void drawShadowsPass(
const Camera& camera, Shader& shader, const Camera& playerCamera
);
@ -92,4 +70,25 @@ public:
void update();
static size_t visibleChunks;
private:
const Chunks& chunks;
const Assets& assets;
const Frustum& frustum;
const EngineSettings& settings;
std::unique_ptr<BlocksRenderer> renderer;
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
std::unordered_map<glm::ivec2, bool> inwork;
std::vector<ChunksSortEntry> indices;
util::ThreadPool<RendererJob, RendererResult> threadPool;
std::vector<glm::ivec2> meshBuildQueue;
size_t enqueuedInFrame = 0;
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
void render(const std::shared_ptr<Chunk>& chunk, bool lowPriority);
void renderBlocking(const std::shared_ptr<Chunk>& chunk);
};

View file

@ -329,7 +329,7 @@ void CloudsRenderer::draw(
shader.uniform4f("u_tint", glm::vec4(weather.cloudsTint(), 1.0f));
shader.uniform1f("u_fogFactor", fogFactor * 0.03f);
shader.uniform1f("u_fogCurve", 0.7f - 0.3f);
shader.uniform1f("u_fogCurve", 0.4f);
for (int i = 0; i < std::min<int>(quality, layers.size()); i++) {
draw(layers[i], frustum, shader, camera, timer, i);

View file

@ -93,7 +93,7 @@ void DebugLinesRenderer::drawCoordSystem(
}
void DebugLinesRenderer::render(
DrawContext& pctx,
const DrawContext& pctx,
const Camera& camera,
LineBatch& linesBatch,
Shader& linesShader,

View file

@ -20,7 +20,7 @@ public:
/// @param linesShader Shader used for rendering lines
/// @param showChunkBorders Whether to show chunk borders
void render(
DrawContext& ctx,
const DrawContext& ctx,
const Camera& camera,
LineBatch& linesBatch,
Shader& linesShader,

View file

@ -25,6 +25,7 @@
#include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"
#include "world/Weather.hpp"
#include "WorldRenderer.hpp"
namespace fs = std::filesystem;

View file

@ -46,7 +46,7 @@ void MainBatch::flush() {
texture = blank.get();
}
texture->bind();
mesh->reload(buffer.get(), index);
mesh->reload(buffer.get(), index, true);
mesh->draw();
index = 0;
}

View file

@ -51,7 +51,9 @@
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "world/LevelEvents.hpp"
#include "world/Weather.hpp"
#include "world/World.hpp"
#include "debug/Logger.hpp"
#include <assert.h>
#include <algorithm>
@ -62,9 +64,20 @@
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
static debug::Logger logger("world-renderer");
bool WorldRenderer::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false;
template <typename T>
static ObserverHandler observe_setting(
ObservableSetting<T>& setting, bool& dirtySettings
) {
return setting.observe([&dirtySettings](auto const&) {
dirtySettings = true;
});
}
WorldRenderer::WorldRenderer(
Engine& engine, LevelFrontend& frontend, Player& player
)
@ -72,6 +85,7 @@ WorldRenderer::WorldRenderer(
level(frontend.getLevel()),
player(player),
assets(*engine.getAssets()),
weather(frontend.getWeather()),
frustumCulling(std::make_unique<Frustum>()),
lineBatch(std::make_unique<LineBatch>()),
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
@ -120,10 +134,41 @@ WorldRenderer::WorldRenderer(
shadowMapping = std::make_unique<Shadows>(level);
debugLines = std::make_unique<DebugLinesRenderer>(level);
cloudsRenderer = std::make_unique<CloudsRenderer>();
keepAlive(observe_setting(settings.graphics.advancedRender, dirtySettings));
keepAlive(observe_setting(settings.graphics.shadowsQuality, dirtySettings));
keepAlive(observe_setting(settings.graphics.ssao, dirtySettings));
}
WorldRenderer::~WorldRenderer() = default;
void WorldRenderer::refreshSettings() {
Shader* affectedShaders[] {
&assets.require<Shader>("main"),
&assets.require<Shader>("entity"),
&assets.require<Shader>("clouds"),
&assets.require<Shader>("translucent"),
&assets.require<PostEffect>("deferred_lighting").getShader(),
nullptr
};
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
std::vector<std::string> defines;
if (shadowsQuality != 0) defines.emplace_back("ENABLE_SHADOWS");
if (graphics.ssao.get()) defines.emplace_back("ENABLE_SSAO");
if (gbufferPipeline) defines.emplace_back("ADVANCED_RENDER");
logger.info() << "recompiling shaders due to settings change";
for (size_t i = 0; affectedShaders[i]; i++) {
affectedShaders[i]->recompile(defines);
}
}
static void setup_weather(Shader& shader, const Weather& weather) {
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
@ -156,7 +201,7 @@ void WorldRenderer::setupWorldShader(
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false);
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform1f("u_dayTime", level.getWorld().getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
@ -188,8 +233,6 @@ void WorldRenderer::renderOpaque(
const EngineSettings& settings,
bool hudVisible
) {
texts->render(ctx, camera, settings, hudVisible, false);
float fogFactor = calcFogFactor();
auto& entityShader = assets.require<Shader>("entity");
@ -212,12 +255,12 @@ void WorldRenderer::renderOpaque(
modelBatch->render();
particles->render(camera);
auto& shader = assets.require<Shader>("main");
auto& mainShader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds");
setupWorldShader(shader, camera, settings, fogFactor);
setupWorldShader(mainShader, camera, settings, fogFactor);
chunksRenderer->drawChunks(camera, shader);
chunksRenderer->drawChunks(camera, mainShader);
blockWraps->draw(ctx);
if (level.environment.sky.clouds) {
@ -232,7 +275,7 @@ void WorldRenderer::renderOpaque(
if (hudVisible) {
auto& linesShader = assets.require<Shader>("lines");
renderLines(camera, linesShader, ctx);
renderInWorldLines(camera, linesShader, ctx);
}
}
@ -265,7 +308,7 @@ void WorldRenderer::renderBlockSelection() {
lineBatch->flush();
}
void WorldRenderer::renderLines(
void WorldRenderer::renderInWorldLines(
const Camera& camera, Shader& linesShader, const DrawContext& pctx
) {
linesShader.use();
@ -282,193 +325,144 @@ void WorldRenderer::renderLines(
}
}
void WorldRenderer::refreshSettings(Shader** shaders) {
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadowsQuality != 0,
graphics.ssao.get() && gbufferPipeline};
if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao) {
std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender)
defines.emplace_back("ADVANCED_RENDER");
for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines);
}
prevCTShaderSettings = currentSettings;
}
}
void WorldRenderer::update(const Camera& camera, float delta) {
timer += delta;
chunksRenderer->update();
weather.update(delta);
precipitation->update(delta);
particles->update(camera, delta);
}
void WorldRenderer::renderFrameClassic(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
ctx.setFramebuffer(postProcessing.getFramebuffer());
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, calcFogFactor());
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrameAdvanced(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
float fogFactor = calcFogFactor();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
postProcessing.bindDepthBuffer();
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrame(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
auto projView = camera.getProjView();
auto world = level.getWorld();
const auto& vp = pctx.getViewport();
camera.setAspectRatio(vp.x / static_cast<float>(vp.y));
if (dirtySettings) {
refreshSettings();
dirtySettings = false;
}
auto& mainShader = assets.require<Shader>("main");
auto& entityShader = assets.require<Shader>("entity");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader,
&entityShader,
&cloudsShader,
&translucentShader,
&deferredShader,
nullptr};
refreshSettings(affectedShaders);
const auto& worldInfo = world->getInfo();
const auto& worldInfo = level.getWorld().getInfo();
float clouds = weather.clouds();
clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh(
level.environment,
pctx,
worldInfo.daytime,
mie,
1.0f + clouds,
weather.skyTint(),
weather.highlight * random,
4
);
chunksRenderer->update();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
{
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
float fogFactor = calcFogFactor();
renderOpaquePass(pctx, camera, hudVisible, postProcessing);
if (gbufferPipeline) {
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
}
{
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
} else {
ctx.setFramebuffer(postProcessing.getFramebuffer());
}
// Background sky plane
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, clouds
);
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
if (debug && hudVisible) {
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush();
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
// Weather effects
entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
renderFrameAdvanced(pctx, camera, hudVisible, postProcessing);
} else {
renderFrameClassic(pctx, camera, hudVisible, postProcessing);
}
postProcessing.render(pctx, assets, timer, camera);
if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
renderHandsPass(pctx, camera);
}
{
auto bctx = pctx.sub();
@ -478,6 +472,83 @@ void WorldRenderer::renderFrame(
glActiveTexture(GL_TEXTURE0);
}
void WorldRenderer::renderOpaquePass(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(wctx, camera, settings, hudVisible, true);
}
void WorldRenderer::renderWeatherEffects(Camera& camera) {
const auto& settings = engine.getSettings();
auto& entityShader = assets.require<Shader>("entity");
entityShader.use();
setupWorldShader(entityShader, camera, settings, calcFogFactor());
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
}
void WorldRenderer::renderDebugLines(const DrawContext& ctx, Camera& camera) {
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
linesShader.uniformMatrix("u_projview", camera.getProjView());
lineBatch->flush();
}
void WorldRenderer::renderHandsPass(const DrawContext& pctx, Camera& camera) {
auto& entityShader = assets.require<Shader>("entity");
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
}
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
int x = std::floor(player.currentCamera->position.x);
int y = std::floor(player.currentCamera->position.y);
@ -523,7 +594,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
batch3d->flush();
}
void WorldRenderer::clear() {
void WorldRenderer::resetCache() {
chunksRenderer->clear();
}

View file

@ -4,12 +4,11 @@
#include "typedefs.hpp"
#include "presets/WeatherPreset.hpp"
#include "world/Weather.hpp"
#include "window/Camera.hpp"
#include "util/ObjectsKeeper.hpp"
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
class Assets;
@ -36,18 +35,38 @@ class Skybox;
class TextsRenderer;
class CloudsRenderer;
struct EngineSettings;
struct Weather;
struct CompileTimeShaderSettings {
bool advancedRender = false;
bool shadows = false;
bool ssao = false;
};
class WorldRenderer final : public util::ObjectsKeeper {
public:
static bool showChunkBorders;
static bool showEntitiesDebug;
class WorldRenderer {
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer();
void update(const Camera& camera, float delta);
void renderFrame(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void resetCache();
void setDebug(bool flag);
void toggleLightsDebug();
Weather& getWeather();
private:
Engine& engine;
const Level& level;
Player& player;
const Assets& assets;
Weather& weather;
std::unique_ptr<Frustum> frustumCulling;
std::unique_ptr<LineBatch> lineBatch;
std::unique_ptr<Batch3D> batch3d;
@ -59,14 +78,12 @@ class WorldRenderer {
std::unique_ptr<DebugLinesRenderer> debugLines;
std::unique_ptr<PrecipitationRenderer> precipitation;
std::unique_ptr<CloudsRenderer> cloudsRenderer;
Weather weather {};
float timer = 0.0f;
bool debug = false;
bool lightsDebug = false;
bool gbufferPipeline = false;
CompileTimeShaderSettings prevCTShaderSettings {};
bool dirtySettings = true;
/// @brief Render block selection lines
void renderBlockSelection();
@ -74,7 +91,7 @@ class WorldRenderer {
/// @brief Render lines (selection and debug)
/// @param camera active camera
/// @param linesShader shader used
void renderLines(
void renderInWorldLines(
const Camera& camera, Shader& linesShader, const DrawContext& pctx
);
@ -98,7 +115,34 @@ class WorldRenderer {
bool hudVisible
);
void refreshSettings(Shader** shaders);
void renderOpaquePass(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void renderWeatherEffects(Camera& camera);
void renderHandsPass(const DrawContext& pctx, Camera& camera);
void renderDebugLines(const DrawContext& context, Camera& camera);
void renderFrameClassic(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void renderFrameAdvanced(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void refreshSettings();
float calcFogFactor() const;
public:
@ -106,27 +150,4 @@ public:
std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<NamedSkeletons> skeletons;
static bool showChunkBorders;
static bool showEntitiesDebug;
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer();
void update(const Camera& camera, float delta);
void renderFrame(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void clear();
void setDebug(bool flag);
void toggleLightsDebug();
Weather& getWeather();
};

View file

@ -2,7 +2,7 @@
#include <string>
#include <memory>
#include <iostream>
#include <iosfwd>
#include <filesystem>
#include "../path.hpp"

View file

@ -12,7 +12,7 @@ Inventories::Inventories(Level& level) : level(level) {
Inventories::~Inventories() = default;
std::shared_ptr<Inventory> Inventories::create(size_t size) {
int64_t id = level.getWorld()->getNextInventoryId();
int64_t id = level.getWorld().getNextInventoryId();
auto inv = std::make_shared<Inventory>(id, size);
store(inv);
return inv;
@ -52,7 +52,7 @@ std::shared_ptr<Inventory> Inventories::clone(int64_t id) {
auto original = get(id);
if (original == nullptr) return nullptr;
auto clone = std::make_shared<Inventory>(*original);
clone->setId(level.getWorld()->getNextInventoryId());
clone->setId(level.getWorld().getNextInventoryId());
store(clone);
return clone;
}

View file

@ -27,7 +27,7 @@ ChunksController::ChunksController(Level& level)
generator(std::make_unique<WorldGenerator>(
level.content.generators.require(level.environment.generator),
level.content,
level.getWorld()->getSeed()
level.getWorld().getSeed()
)) {}
ChunksController::~ChunksController() = default;

View file

@ -314,8 +314,8 @@ void EngineController::setLocalPlayer(int64_t player) {
localPlayer = player;
}
void EngineController::reopenWorld(World* world) {
std::string name = world->wfile->getFolder().name();
void EngineController::reopenWorld(World& world) {
std::string name = world.wfile->getFolder().name();
engine.onWorldClosed();
openWorld(name, true);
}
@ -384,16 +384,16 @@ void EngineController::reconfigPacks(
}
} else {
auto level = controller->getLevel();
auto world = level->getWorld();
auto& world = level->getWorld();
controller->processBeforeQuit();
controller->saveWorld();
auto names = PacksManager::getNames(world->getPacks());
auto names = PacksManager::getNames(world.getPacks());
auto& manager = contentControl.scan();
reconfig_packs_inside(manager, names, packsToAdd, packsToRemove);
const auto& settings = engine.getSettings();
auto& wfile = *world->wfile;
auto& wfile = *world.wfile;
if (!settings.debug.generatorTestMode.get()) {
wfile.removeIndices(packsToRemove);
}

View file

@ -41,5 +41,5 @@ public:
void setLocalPlayer(int64_t player);
void reopenWorld(World* world);
void reopenWorld(World& world);
};

View file

@ -140,16 +140,16 @@ void LevelController::processBeforeQuit() {
}
void LevelController::saveWorld() {
auto world = level->getWorld();
if (world->isNameless()) {
auto& world = level->getWorld();
if (world.isNameless()) {
logger.info() << "nameless world will not be saved";
return;
}
logger.info() << "writing world '" << world->getName() << "'";
world->wfile->createDirectories();
logger.info() << "writing world '" << world.getName() << "'";
world.wfile->createDirectories();
scripting::on_world_save();
level->onSave();
level->getWorld()->write(*level);
level->getWorld().write(*level);
}
void LevelController::onWorldQuit() {

View file

@ -340,6 +340,19 @@ static int determine_rotation(
if (camDir.z > 0.0f) return BLOCK_DIR_SOUTH | verticalBit;
if (camDir.z < 0.0f) return BLOCK_DIR_NORTH | verticalBit;
}
} else if (name == "ladder") {
if (norm.x < 0.0f) return BLOCK_DIR_EAST;
if (norm.x > 0.0f) return BLOCK_DIR_WEST;
if (norm.z > 0.0f) return BLOCK_DIR_NORTH;
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
if (abs(camDir.x) > abs(camDir.z)) {
if (camDir.x > 0.0f) return BLOCK_DIR_EAST;
if (camDir.x < 0.0f) return BLOCK_DIR_WEST;
}
if (abs(camDir.x) < abs(camDir.z)) {
if (camDir.z > 0.0f) return BLOCK_DIR_SOUTH;
if (camDir.z < 0.0f) return BLOCK_DIR_NORTH;
}
}
}
return 0;

View file

@ -2,6 +2,7 @@
#include "world/Level.hpp"
#include "world/World.hpp"
#include "world/Weather.hpp"
using namespace scripting;
@ -9,6 +10,11 @@ static Weather& require_weather() {
if (level == nullptr) {
throw std::runtime_error("world is not open");
}
if (renderer == nullptr) {
throw std::runtime_error(
"world frontend is not initialized yet (wait for on_hud_open event)"
);
}
return renderer->getWeather();
}

View file

@ -33,7 +33,7 @@ static Level& require_level() {
}
static WorldInfo& require_world_info() {
return require_level().getWorld()->getInfo();
return require_level().getWorld().getInfo();
}
static int l_is_open(lua::State* L) {
@ -139,7 +139,7 @@ static int l_get_chunk_data(lua::State* L) {
auto voxelData = std::make_unique<ubyte[]>(CHUNK_DATA_LEN);
std::vector<ubyte> chunkData;
if (chunk == nullptr) {
auto& regions = level->getWorld()->wfile->getRegions();
auto& regions = level->getWorld().wfile->getRegions();
if (!regions.getVoxels(x, z, voxelData.get())) {
return 0;
}
@ -218,7 +218,7 @@ static int l_save_chunk_data(lua::State* L) {
reinterpret_cast<const ubyte*>(buffer.data()),
reinterpret_cast<const ubyte*>(buffer.data()) + buffer.size()
),
level->getWorld()->wfile->getRegions()
level->getWorld().wfile->getRegions()
);
return 0;
}

View file

@ -264,6 +264,12 @@ static std::unordered_map<std::string, lua_CFunction> methods {
{"min", lua::wrap<l_binop_func<util::min>>},
{"max", lua::wrap<l_binop_func<util::max>>},
{"abs", lua::wrap<l_unaryop_func<util::abs>>},
{"floor", lua::wrap<l_unaryop_func<util::floor>>},
{"round", lua::wrap<l_unaryop_func<util::round>>},
{"ceil", lua::wrap<l_unaryop_func<util::ceil>>},
{"sin", lua::wrap<l_unaryop_func<util::sin>>},
{"cos", lua::wrap<l_unaryop_func<util::cos>>},
{"tan", lua::wrap<l_unaryop_func<util::tan>>},
{"resize", lua::wrap<l_resize>},
{"crop", lua::wrap<l_crop>},
{"at", lua::wrap<l_at>},

View file

@ -310,7 +310,7 @@ void scripting::on_world_load(LevelController* controller) {
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
return lua::pushboolean(
L, !scripting::level->getWorld()->getInfo().isLoaded
L, !scripting::level->getWorld().getInfo().isLoaded
);
});
}

View file

@ -109,12 +109,16 @@ namespace util {
/// @return integer square of vector length
/// @note glm::length2 does not support integer vectors
inline int length2(const glm::ivec3& a) {
inline long long int length2(const glm::ivec2& a) {
return a.x * a.x + a.y * a.y;
}
inline long long int length2(const glm::ivec3& a) {
return a.x * a.x + a.y * a.y + a.z * a.z;
}
/// @return integer square of vector length
inline int length2(int x, int y, int z) {
inline long long int length2(int x, int y, int z) {
return x * x + y * y + z * z;
}

View file

@ -67,7 +67,7 @@ Player* Players::getNearest(const glm::vec3& position) const {
}
Player* Players::create(int64_t id) {
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId;
int64_t& nextPlayerID = level.getWorld().getInfo().nextPlayerId;
if (id == NONE) {
id = nextPlayerID++;
} else {
@ -146,7 +146,7 @@ void Players::deserialize(const dv::value& src) {
auto& inventory = player->getInventory();
// invalid inventory id pre 0.25
if (inventory->getId() == 0) {
inventory->setId(level.getWorld()->getNextInventoryId());
inventory->setId(level.getWorld().getNextInventoryId());
}
level.inventories->store(inventory);
}

View file

@ -1,6 +1,5 @@
#include "Clock.hpp"
#include <cmath>
#include <algorithm>
using namespace util;
@ -20,10 +19,8 @@ int Clock::update(float delta) {
tickTimer -= parts * delay / tickParts;
tickTimer = std::min<float>(tickTimer, delay);
}
currentTickPart += parts;
if (currentTickPart >= tickParts) {
currentTickPart %= tickParts;
}
lastPartsStart = currentTickPart;
currentTickPart = (currentTickPart + parts) % tickParts;
return parts;
}
@ -40,5 +37,5 @@ int Clock::getTickId() const {
}
int Clock::convertPart(int index) const {
return (tickParts - currentTickPart) % tickParts + index;
return (lastPartsStart + index) % tickParts;
}

View file

@ -8,6 +8,7 @@ namespace util {
float tickTimer = 0.0f;
int tickId = 0;
int currentTickPart = 0;
int lastPartsStart = 0;
public:
Clock(int tickRate, int tickParts);

View file

@ -5,7 +5,6 @@
#include <optional>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <queue>
#include <thread>
#include <utility>

View file

@ -3,31 +3,26 @@
#include <glm/glm.hpp>
namespace util {
template<class T>
struct pow {
constexpr T operator()(T a, T b) const {
return glm::pow(a, b);
}
template<class T, T (*F)(T, T)>
struct binary_op {
constexpr T operator()(T a, T b) const { return F(a, b); }
};
template<class T>
struct min {
constexpr T operator()(T a, T b) const {
return glm::min(a, b);
}
template<class T> using pow = binary_op<T, glm::pow>;
template<class T> using min = binary_op<T, glm::min>;
template<class T> using max = binary_op<T, glm::max>;
template<class T, T (*F)(T)>
struct unary_op {
constexpr T operator()(T a) const { return F(a); }
};
template<class T>
struct max {
constexpr T operator()(T a, T b) const {
return glm::max(a, b);
}
};
template<class T>
struct abs {
constexpr T operator()(T a) const {
return glm::abs(a);
}
};
template<typename T> using abs = unary_op<T, glm::abs>;
template<typename T> using floor = unary_op<T, glm::floor>;
template<typename T> using round = unary_op<T, glm::round>;
template<typename T> using ceil = unary_op<T, glm::ceil>;
template<typename T> using sin = unary_op<T, glm::sin>;
template<typename T> using cos = unary_op<T, glm::cos>;
template<typename T> using tan = unary_op<T, glm::tan>;
}

View file

@ -109,6 +109,17 @@ const BlockRotProfile BlockRotProfile::STAIRS {
8
};
const BlockRotProfile BlockRotProfile::LADDER {
"ladder",
{
{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // North
{{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}, // East
{{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}}, // South
{{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}, // West
},
4
};
Block::Block(const std::string& name)
: name(name), caption(util::id_to_caption(name)) {
for (int i = 0; i < defaults.textureFaces.size(); i++) {

View file

@ -90,9 +90,13 @@ struct BlockRotProfile {
/// @brief Stairs, stairs and stairs
static const BlockRotProfile STAIRS;
/// @brief Ladders, wall signs, paintings
static const BlockRotProfile LADDER;
static inline std::string PIPE_NAME = "pipe";
static inline std::string PANE_NAME = "pane";
static inline std::string STAIRS_NAME = "stairs";
static inline std::string LADDER_NAME = "ladder";
};
enum class BlockModelType {

View file

@ -29,16 +29,16 @@ public:
voxel voxels[CHUNK_VOL] {};
std::shared_ptr<Lightmap> lightmap;
struct {
bool modified : 1;
bool ready : 1;
bool loaded : 1;
bool lighted : 1;
bool unsaved : 1;
bool loadedLights : 1;
bool entities : 1;
bool blocksData : 1;
bool dirtyHeights : 1;
bool inventoriesRemoved : 1;
bool modified : 1; // is chunk mesh should be updated
bool ready : 1; // is chunk ready for modifications (loaded / generated)
bool loaded : 1; // was chunk loaded from region
bool lighted : 1; // is lights built (chunk ready to be visualized)
bool unsaved : 1; // does chunk contain unsaved changes
bool loadedLights : 1; // was lights loaded from cache
bool entities : 1; // does chunk contain entities list changes
bool blocksData : 1; // does chunk contain block fields changes
bool dirtyHeights : 1; // is chunk bottom, top should be recalculated
bool inventoriesRemoved : 1; // was block inventories removed since the last save
} flags {};
uint64_t lastRandomTickId = -1;

View file

@ -107,7 +107,7 @@ std::shared_ptr<Chunk> GlobalChunks::create(int x, int z, bool lighting) {
chunks_pool.create(x, z, lighting ? lightmaps_pool.create() : nullptr);
chunksMap[keyfrom(x, z)] = chunk;
World& world = *level.getWorld();
World& world = level.getWorld();
auto& regions = world.wfile.get()->getRegions();
if (regions.getVoxels(chunk->x, chunk->z, voxelDataBuffer.get())) {
@ -197,7 +197,7 @@ void GlobalChunks::save(Chunk* chunk) {
if (!entities.empty()) {
chunk->flags.entities = true;
}
level.getWorld()->wfile->getRegions().put(
level.getWorld().wfile->getRegions().put(
chunk,
chunk->flags.entities ? json::to_binary(root, true)
: std::vector<ubyte>()

View file

@ -66,7 +66,7 @@ public:
return lights.get();
}
inline blockid_t pickBlockId(int bx, int by, int bz) const {
blockid_t pickBlockId(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) {
return BLOCK_VOID;
@ -75,7 +75,7 @@ public:
}
inline voxel pickBlock(int bx, int by, int bz) const {
voxel pickBlock(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) {
return {BLOCK_VOID, {}};
@ -83,7 +83,7 @@ public:
return voxels[vox_index(bx - x, by - y, bz - z, w, d)];
}
inline light_t pickLight(int bx, int by, int bz) const {
light_t pickLight(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) {
return 0;
@ -103,10 +103,10 @@ private:
template <int w, int h, int d>
class StaticVoxelsVolume {
public:
static inline constexpr size_t size = w * h * d;
static inline constexpr int width = w;
static inline constexpr int height = h;
static inline constexpr int depth = d;
static constexpr size_t size = w * h * d;
static constexpr int width = w;
static constexpr int height = h;
static constexpr int depth = d;
StaticVoxelsVolume()
: StaticVoxelsVolume(0, 0, 0) {}
@ -142,7 +142,7 @@ public:
return lights;
}
inline blockid_t pickBlockId(uint bx, uint by, uint bz) const {
blockid_t pickBlockId(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;
@ -153,7 +153,7 @@ public:
}
inline const voxel& pickBlock(uint bx, uint by, uint bz) const {
const voxel& pickBlock(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;
@ -164,7 +164,7 @@ public:
return voxels[vox_index(bx, by, bz, w, d)];
}
inline light_t pickLight(uint bx, uint by, uint bz) const {
light_t pickLight(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;

View file

@ -71,12 +71,12 @@ Level::Level(
Level::~Level() = default;
World* Level::getWorld() {
return world.get();
World& Level::getWorld() {
return *world;
}
const World* Level::getWorld() const {
return world.get();
const World& Level::getWorld() const {
return *world;
}
void Level::onSave() {

View file

@ -46,9 +46,9 @@ public:
);
~Level();
World* getWorld();
World& getWorld();
const World* getWorld() const;
const World& getWorld() const;
void onSave();

View file

@ -147,7 +147,7 @@ std::unique_ptr<Level> World::load(
if (!playerRoot["players"].empty()) {
if (!playerRoot["players"][0].has("id")) {
level->getWorld()->getInfo().nextPlayerId++;
level->getWorld().getInfo().nextPlayerId++;
}
}
}
@ -216,9 +216,6 @@ void WorldInfo::deserialize(const dv::value& root) {
daytimeSpeed = timeobj["day-time-speed"].asNumber();
totalTime = timeobj["total-time"].asNumber();
}
if (root.has("weather")) {
fog = root["weather"]["fog"].asNumber();
}
nextInventoryId = root["next-inventory-id"].asInteger(2);
nextEntityId = root["next-entity-id"].asInteger(1);
root.at("next-player-id").get(nextPlayerId);
@ -244,9 +241,6 @@ dv::value WorldInfo::serialize() const {
timeobj["day-time-speed"] = daytimeSpeed;
timeobj["total-time"] = totalTime;
root["weather"] = dv::object();
root["weather"]["fog"] = fog;
root["next-inventory-id"] = nextInventoryId;
root["next-entity-id"] = nextEntityId;
root["next-player-id"] = nextPlayerId;

View file

@ -39,9 +39,6 @@ struct WorldInfo : public Serializable {
/// @brief total time passed in the world (not depending on daytimeSpeed)
double totalTime = 0.0;
/// @brief will be replaced with weather in future
float fog = 0.0f;
entityid_t nextEntityId = 0;
int major = 0, minor = -1;
@ -70,6 +67,8 @@ public:
const std::vector<ContentPack>& packs
);
World(const World&) = delete;
~World();
/// @brief Update world day-time and total time

View file

@ -219,7 +219,8 @@ std::unique_ptr<VoxelFragment> VoxelFragment::rotated(const Content& content) co
| ((voxel.state.segment & 0b100) >> 2);
auto& def = content.blocks.require(blockNames[voxel.id]);
if (def.rotations.name == BlockRotProfile::PANE_NAME ||
def.rotations.name == BlockRotProfile::PIPE_NAME) {
def.rotations.name == BlockRotProfile::PIPE_NAME ||
def.rotations.name == BlockRotProfile::LADDER_NAME) {
if (voxel.state.rotation < 4) {
voxel.state.rotation = (voxel.state.rotation + 3) & 0b11;
}