Merge branch 'MihailRis:main' into main

This commit is contained in:
Astazium 2026-08-16 16:35:30 +03:00 • committed by GitHub
commit 3e20166401
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
119 changed files with 1501 additions and 957 deletions

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

@ -106,6 +106,7 @@ std::shared_ptr<UINode> HudElement::getNode() const {
std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto& content = frontend.getLevel().content;
auto& indices = *content.getIndices();
auto& input = this->input;
auto inventory = player.getInventory();
size_t itemsCount = indices.items.count();
@ -115,12 +116,19 @@ std::shared_ptr<InventoryView> Hud::createContentAccess() {
}
SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr,
[inventory, &indices](uint, ItemStack& item) {
[inventory, &indices, &input](uint, ItemStack& item) {
auto copy = ItemStack(item);
if (input.pressed(Keycode::LEFT_CONTROL)) {
copy.maximizeCount(indices);
}
inventory->move(copy, indices);
},
[this, inventory](uint, ItemStack& item) {
inventory->getSlot(player.getChosenSlot()).set(item);
[this, inventory, &indices, &input](uint, ItemStack& item) {
auto copy = ItemStack(item);
if (input.pressed(Keycode::LEFT_CONTROL)) {
copy.maximizeCount(indices);
}
inventory->getSlot(player.getChosenSlot()).set(copy);
});
InventoryBuilder builder(gui);
@ -174,6 +182,15 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
gui,
"<container size='4000' color='#00000080' z-index='-1' visible='false'/>"
);
inventoryDropArea = guiutil::create(
gui,
R"(<container size='4000' color='#00000000' z-index='-1' visible='false'
onclick='events.emit(\"core:drop_outside_inventory\", 0)'
onrightclick='events.emit(\"core:drop_outside_inventory\", 1)'
onmiddleclick='events.emit(\"core:drop_outside_inventory\", 2)'
/>)"
);
uicamera = std::make_unique<Camera>(glm::vec3(), 1);
uicamera->perspective = false;
@ -188,6 +205,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
gui.add(debugPanel);
gui.add(darkOverlay);
gui.add(inventoryDropArea);
gui.add(hotbarView);
gui.add(contentAccessPanel);
@ -213,6 +231,7 @@ Hud::~Hud() {
}
gui.remove(hotbarView);
gui.remove(darkOverlay);
gui.remove(inventoryDropArea);
gui.remove(contentAccessPanel);
gui.remove(debugPanel);
}
@ -358,6 +377,7 @@ void Hud::update(bool visible) {
contentAccess->setMinSize(glm::vec2(1, windowSize.y));
hotbarView->setVisible(visible && !(secondUI && !inventoryView));
darkOverlay->setVisible(isMenuOpen);
inventoryDropArea->setVisible(inventoryOpen);
menu.setVisible(isMenuOpen);
if (visible) {
@ -739,6 +759,10 @@ std::shared_ptr<Inventory> Hud::getSecondInventory() {
return nullptr;
}
std::shared_ptr<Inventory> Hud::getExchangeInventory() {
return exchangeSlotInv;
}
bool Hud::isContentAccess() const {
return showContentPanel;
}

View file

@ -93,6 +93,8 @@ class Hud : public util::ObjectsKeeper {
std::shared_ptr<gui::UINode> debugPanel;
/// @brief Overlay used in pause mode
std::shared_ptr<gui::UINode> darkOverlay;
/// @brief Overlay used in inventory
std::shared_ptr<gui::UINode> inventoryDropArea;
/// @brief Inventories interaction agent (grabbed item)
std::shared_ptr<gui::SlotView> exchangeSlot;
/// @brief Exchange slot inventory (1 slot only)
@ -207,6 +209,8 @@ public:
std::shared_ptr<Inventory> getSecondInventory();
std::shared_ptr<Inventory> getExchangeInventory();
bool isContentAccess() const;
void setContentAccess(bool flag);

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();
@ -283,193 +326,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();
@ -479,6 +473,85 @@ 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);
ctx.setDepthMask(false);
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);
@ -524,7 +597,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

@ -14,7 +14,6 @@
#include "frontend/UiDocument.hpp"
#include "frontend/locale.hpp"
#include "graphics/core/Batch2D.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/DrawContext.hpp"
@ -188,8 +187,8 @@ void GUI::actMouse(Frame& frame, float delta, const CursorState& cursor) {
doubleClicked = true;
} else {
pressed->click(cursorPos.x, cursorPos.y);
doubleClickTimer = 0.0f;
}
doubleClickTimer = 0.0f;
if (focus && focus != pressed) {
focus->defocus();
}
@ -260,7 +259,7 @@ void GUI::act(float delta, const glm::uvec2& vp) {
hover = nullptr;
}
}
if (focus) {
if (focus && focusedOnStart == focus.get()) {
actFocused();
}
if (focus && !focus->isFocused()) {
@ -274,6 +273,7 @@ void GUI::postAct() {
postRunnables.pop();
callback();
}
focusedOnStart = focus.get();
}
void GUI::draw(const DrawContext& pctx, Assets& assets) {

View file

@ -72,6 +72,7 @@ namespace gui {
std::shared_ptr<UINode> hover;
std::shared_ptr<UINode> pressed;
std::shared_ptr<UINode> focus;
const UINode* focusedOnStart = nullptr;
std::shared_ptr<UINode> tooltip;
std::shared_ptr<UiDocument> rootDocument;
std::unique_ptr<FontStylesScheme> syntaxColorScheme;

View file

@ -6,7 +6,7 @@
#include "Panel.hpp"
namespace gui {
class CheckBox : public UINode {
class CheckBox final : public UINode {
protected:
glm::vec4 checkColor {1.0f, 1.0f, 1.0f, 0.4f};
boolsupplier supplier = nullptr;
@ -15,23 +15,22 @@ namespace gui {
public:
explicit CheckBox(GUI& gui, bool checked = false);
virtual void draw(const DrawContext& pctx, const Assets& assets)
override;
void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void mouseRelease(int x, int y) override;
void mouseRelease(int x, int y) override;
virtual void setSupplier(boolsupplier supplier);
virtual void setConsumer(boolconsumer consumer);
void setSupplier(boolsupplier supplier);
void setConsumer(boolconsumer consumer);
virtual CheckBox* setChecked(bool flag);
CheckBox* setChecked(bool flag);
virtual bool isChecked() const {
bool isChecked() const {
if (supplier) return supplier();
return checked;
}
};
class FullCheckBox : public Panel {
class FullCheckBox final : public Panel {
protected:
std::shared_ptr<CheckBox> checkbox;
std::shared_ptr<Label> label;
@ -43,28 +42,28 @@ namespace gui {
bool checked = false
);
virtual void setSupplier(boolsupplier supplier) {
void setSupplier(boolsupplier supplier) {
checkbox->setSupplier(std::move(supplier));
}
virtual void setConsumer(boolconsumer consumer) {
void setConsumer(boolconsumer consumer) {
checkbox->setConsumer(std::move(consumer));
}
virtual void setChecked(bool flag) {
void setChecked(bool flag) {
checkbox->setChecked(flag);
}
virtual bool isChecked() const {
bool isChecked() const {
return checkbox->isChecked();
}
virtual void setTooltip(const std::wstring& text) override {
void setTooltip(const std::wstring& text) override {
Panel::setTooltip(text);
checkbox->setTooltip(text);
}
virtual void setSize(const glm::vec2& new_size) override {
void setSize(const glm::vec2& new_size) override {
Panel::setSize(new_size);
checkbox->setSize(glm::vec2(size.y, size.y));
}

View file

@ -1,9 +1,9 @@
#include "Container.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Batch2D.hpp"
#include "window/Window.hpp"
#include "../GUI.hpp"
#include "graphics/core/Batch2D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "window/Window.hpp"
#include <algorithm>
#include <utility>
@ -11,7 +11,7 @@
using namespace gui;
Container::Container(GUI& gui, glm::vec2 size) : UINode(gui, size) {
actualLength = size.y;
actualLengthY = size.y;
setColor(glm::vec4());
}
@ -26,15 +26,15 @@ std::shared_ptr<UINode> Container::getAt(const glm::vec2& pos) {
if (!isInside(pos)) {
return nullptr;
}
int diff = (actualLength-size.y);
if (scrollable && diff > 0 && pos.x > calcPos().x + getSize().x - scrollBarWidth) {
int diff = (actualLengthY - size.y);
if (scrollable && diff > 0 &&
pos.x > calcPos().x + getSize().x - scrollBarWidth) {
return UINode::getAt(pos);
}
for (int i = nodes.size()-1; i >= 0; i--) {
for (int i = nodes.size() - 1; i >= 0; i--) {
auto& node = nodes[i];
if (!node->isVisible())
continue;
if (!node->isVisible()) continue;
auto hover = node->getAt(pos);
if (hover != nullptr) {
return hover;
@ -45,7 +45,7 @@ std::shared_ptr<UINode> Container::getAt(const glm::vec2& pos) {
void Container::click(int x, int y) {
UINode::click(x, y);
if (scrollable) {
auto pos = calcPos();
x -= pos.x;
@ -65,11 +65,12 @@ void Container::mouseMove(int x, int y) {
auto pos = calcPos();
x -= pos.x;
y -= pos.y;
int diff = (actualLength-size.y);
int diff = (actualLengthY - size.y);
if (diff > 0) {
scroll -= (y - prevScrollY) / static_cast<float>(size.y) * actualLength;
scroll = -glm::min(
glm::max(static_cast<float>(-scroll), 0.0f), actualLength - size.y
scrollY -=
(y - prevScrollY) / static_cast<float>(size.y) * actualLengthY;
scrollY = -glm::min(
glm::max(static_cast<float>(-scrollY), 0.0f), actualLengthY - size.y
);
}
prevScrollY = y;
@ -101,23 +102,28 @@ void Container::act(float delta) {
}
}
}
intervalEvents.erase(std::remove_if(
intervalEvents.begin(), intervalEvents.end(),
[](const IntervalEvent& event) {
return event.repeat == 0;
}
), intervalEvents.end());
intervalEvents.erase(
std::remove_if(
intervalEvents.begin(),
intervalEvents.end(),
[](const IntervalEvent& event) { return event.repeat == 0; }
),
intervalEvents.end()
);
}
void Container::scrolled(int value) {
int diff = (actualLength-getSize().y);
auto size = getSize();
bool isVertical = actualLengthY > size.y;
int diff = (isVertical ? actualLengthY - size.y : actualLengthX - size.x);
int& scroll = (isVertical ? scrollY : scrollX);
if (scroll < 0 && diff <= 0) {
scroll = 0;
}
if (diff > 0 && scrollable) {
scroll += value * scrollStep;
if (scroll > 0)
scroll = 0;
if (scroll > 0) scroll = 0;
if (-scroll > diff) {
scroll = -diff;
}
@ -140,21 +146,25 @@ void Container::draw(const DrawContext& pctx, const Assets& assets) {
if (!nodes.empty()) {
batch->flush();
DrawContext ctx = pctx.sub();
ctx.setScissors(glm::vec4(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y)));
ctx.setScissors(
glm::vec4(pos.x, pos.y, glm::ceil(size.x), glm::ceil(size.y))
);
for (const auto& node : nodes) {
if (node->isVisible())
node->draw(pctx, assets);
if (node->isVisible()) node->draw(pctx, assets);
}
int diff = (actualLength-size.y);
int diff = (actualLengthY - size.y);
if (scrollable && diff > 0) {
int h = glm::max(size.y / actualLength * size.y, scrollBarWidth / 2.0f);
int h = glm::max(
size.y / actualLengthY * size.y, scrollBarWidth / 2.0f
);
batch->untexture();
batch->setColor(glm::vec4(1, 1, 1, 0.3f));
batch->rect(
pos.x + size.x - scrollBarWidth,
pos.y - scroll / static_cast<float>(diff) * (size.y - h),
scrollBarWidth, h
pos.y - scrollY / static_cast<float>(diff) * (size.y - h),
scrollBarWidth,
h
);
}
batch->flush();
@ -163,8 +173,7 @@ void Container::draw(const DrawContext& pctx, const Assets& assets) {
void Container::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec4 color = calcColor();
if (color.a <= 0.001f)
return;
if (color.a <= 0.001f) return;
glm::vec2 pos = calcPos();
auto batch = pctx.getBatch2D();
@ -194,11 +203,16 @@ void Container::add(const std::shared_ptr<UINode>& node, glm::vec2 pos) {
void Container::remove(UINode* selected) {
selected->setParent(nullptr);
nodes.erase(std::remove_if(nodes.begin(), nodes.end(),
[selected](const std::shared_ptr<UINode>& node) {
return node.get() == selected;
}
), nodes.end());
nodes.erase(
std::remove_if(
nodes.begin(),
nodes.end(),
[selected](const std::shared_ptr<UINode>& node) {
return node.get() == selected;
}
),
nodes.end()
);
refresh();
}
@ -242,13 +256,17 @@ void Container::setScrollStep(int step) {
}
void Container::refresh() {
std::stable_sort(nodes.begin(), nodes.end(), [](const auto& a, const auto& b) {
return a->getZIndex() < b->getZIndex();
});
std::stable_sort(
nodes.begin(),
nodes.end(),
[](const auto& a, const auto& b) {
return a->getZIndex() < b->getZIndex();
}
);
}
void Container::setScroll(int scroll) {
this->scroll = scroll;
this->scrollY = scroll;
}
const std::vector<std::shared_ptr<UINode>>& Container::getNodes() const {

View file

@ -13,10 +13,12 @@ namespace gui {
protected:
std::vector<std::shared_ptr<UINode>> nodes;
std::vector<IntervalEvent> intervalEvents;
int scroll = 0;
int scrollY = 0;
int scrollX = 0;
int scrollStep = 40;
int scrollBarWidth = 10;
int actualLength = 0;
int actualLengthY = 0;
int actualLengthX = 0;
bool scrollable = true;
bool isScrolling() const {
@ -40,7 +42,7 @@ namespace gui {
virtual void setScrollable(bool flag);
void listenInterval(float interval, OnTimeOut callback, int repeat=-1);
virtual glm::vec2 getContentOffset() const override {
return glm::vec2(0.0f, scroll);
return glm::vec2(scrollX, scrollY);
};
virtual void setSize(const glm::vec2& size) override;
virtual int getScrollStep() const;

View file

@ -9,7 +9,7 @@ namespace gui {
class Frame final : public Container {
public:
Frame(GUI& gui, std::string id, std::string outputTexture);
virtual ~Frame();
~Frame();
void draw(const DrawContext& pctx, const Assets& assets) override;

View file

@ -14,6 +14,7 @@ void InlineFrame::setSrc(const std::string& src) {
this->src = src;
if (document) {
scripting::on_ui_close(*document, nullptr);
clear();
document = nullptr;
root = nullptr;
}

View file

@ -12,11 +12,8 @@
#include "graphics/core/Batch2D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/render/BlocksPreview.hpp"
#include "graphics/ui/GUI.hpp"
#include "items/Inventories.hpp"
#include "items/Inventory.hpp"
#include "items/ItemDef.hpp"
#include "logic/scripting/scripting.hpp"
@ -369,28 +366,12 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
);
return;
}
auto indices = *content->getIndices();
if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) {
action = InteractionAction::PUT;
stack.move(grabbed, *content->getIndices());
stack.move(grabbed, indices);
} else {
if (layout.itemSource) {
if (grabbed.isEmpty()) {
action = InteractionAction::TAKE;
grabbed.set(stack);
} else {
action = InteractionAction::PUT;
grabbed.clear();
}
} else if (grabbed.isEmpty()) {
if (layout.taking) {
action = InteractionAction::TAKE;
std::swap(grabbed, stack);
}
} else if (layout.taking && layout.placing) {
action = InteractionAction::PUT;
std::swap(grabbed, stack);
}
actIfCannotPut(stack, grabbed, action);
}
if (action != InteractionAction::UNDEFINED) {
@ -403,6 +384,39 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
}
}
void SlotView::actIfCannotPut(ItemStack& stack, ItemStack& grabbed, InteractionAction& action) {
const auto& input = gui.getInput();
auto indices = *content->getIndices();
if (layout.itemSource) {
if (grabbed.isEmpty()) {
action = InteractionAction::TAKE;
grabbed.set(stack);
if (input.pressed(Keycode::LEFT_CONTROL)) {
grabbed.maximizeCount(*content->getIndices());
}
} else {
if (grabbed.accepts(stack)) {
auto& def = indices.items.require(stack.getItemId());
if (grabbed.getCount() < def.stackSize) {
action = InteractionAction::TAKE;
grabbed.setCount(grabbed.getCount() + 1);
}
} else {
action = InteractionAction::PUT;
grabbed.clear();
}
}
} else if (grabbed.isEmpty()) {
if (layout.taking) {
action = InteractionAction::TAKE;
std::swap(grabbed, stack);
}
} else if (layout.taking && layout.placing) {
action = InteractionAction::PUT;
std::swap(grabbed, stack);
}
}
void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
if (layout.rightClick) {
layout.rightClick(inventoryId, stack);

View file

@ -51,7 +51,7 @@ namespace gui {
);
};
class SlotView : public gui::UINode {
class SlotView final : public gui::UINode {
struct {
ItemStack stack {};
std::wstring countStr;
@ -89,14 +89,14 @@ namespace gui {
public:
SlotView(GUI& gui, SlotLayout layout);
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
void draw(const DrawContext& pctx, const Assets& assets) override;
void setHighlighted(bool flag);
bool isHighlighted() const;
virtual void clicked(Mousecode) override;
virtual void onFocus() override;
virtual const std::wstring& getTooltip() const override;
void clicked(Mousecode) override;
void onFocus() override;
const std::wstring& getTooltip() const override;
void bind(
int64_t inventoryid,
@ -123,6 +123,8 @@ namespace gui {
SHARE = 2,
UNDEFINED,
};
private:
void actIfCannotPut(ItemStack& stack, ItemStack& grabbed, InteractionAction& action);
};
class InventoryView final : public gui::Container {

View file

@ -30,7 +30,7 @@ namespace gui {
uint getLineByTextIndex(size_t index) const;
};
class Label : public UINode {
class Label final : public UINode {
LabelCache cache;
glm::vec2 calcSize();
@ -69,64 +69,64 @@ namespace gui {
Label(GUI& gui, const std::string& text, std::string fontName="normal");
Label(GUI& gui, const std::wstring& text, std::string fontName="normal");
virtual ~Label();
~Label();
virtual void setText(std::wstring text);
void setText(std::wstring text);
const std::wstring& getText() const;
virtual void setFontName(std::string name);
virtual const std::string& getFontName() const;
void setFontName(std::string name);
const std::string& getFontName() const;
/// @brief Set text vertical alignment (default value: center)
/// @param align Align::top / Align::center / Align::bottom
virtual void setVerticalAlign(Align align);
virtual Align getVerticalAlign() const;
void setVerticalAlign(Align align);
Align getVerticalAlign() const;
/// @brief Get line height multiplier used for multiline labels
/// (default value: 1.5)
virtual float getLineInterval() const;
float getLineInterval() const;
/// @brief Set line height multiplier used for multiline labels
virtual void setLineInterval(float interval);
void setLineInterval(float interval);
/// @brief Get Y position of the text relative to label position
/// @return Y offset
virtual int getTextYOffset() const;
int getTextYOffset() const;
/// @brief Get Y position of the line relative to label position
/// @param line target line index
/// @return Y offset
virtual int getLineYOffset(uint line) const;
int getLineYOffset(uint line) const;
/// @brief Get position of line start in the text
/// @param line target line index
/// @return position in the text [0..length]
virtual size_t getTextLineOffset(size_t line) const;
size_t getTextLineOffset(size_t line) const;
/// @brief Get line index by its Y offset relative to label position
/// @param offset target Y offset
/// @return line index [0..+]
virtual uint getLineByYOffset(int offset) const;
virtual uint getLineByTextIndex(size_t index) const;
virtual uint getLinesNumber() const;
virtual bool isFakeLine(size_t line) const;
uint getLineByYOffset(int offset) const;
uint getLineByTextIndex(size_t index) const;
uint getLinesNumber() const;
bool isFakeLine(size_t line) const;
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void textSupplier(wstringsupplier supplier);
void textSupplier(wstringsupplier supplier);
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;
void setAutoResize(bool flag);
bool isAutoResize() const;
virtual void setMultiline(bool multiline);
virtual bool isMultiline() const;
void setMultiline(bool multiline);
bool isMultiline() const;
virtual void setTextWrapping(bool flag);
virtual bool isTextWrapping() const;
void setTextWrapping(bool flag);
bool isTextWrapping() const;
virtual void setMarkup(std::string_view lang);
virtual const std::string& getMarkup() const;
void setMarkup(std::string_view lang);
const std::string& getMarkup() const;
virtual void setStyles(std::unique_ptr<FontStylesScheme> styles);
void setStyles(std::unique_ptr<FontStylesScheme> styles);
};
}

View file

@ -13,7 +13,7 @@ namespace gui {
using PageLoaderFunc = std::function<std::shared_ptr<UINode>(const std::string&)>;
class Menu : public Container {
class Menu final : public Container {
protected:
std::unordered_map<std::string, Page> pages;
std::stack<Page> pageStack;

View file

@ -13,6 +13,7 @@
#include "window/Window.hpp"
#include "../GUI.hpp"
// TODO: remove
// TODO: remove
#include <GL/glew.h>
@ -24,8 +25,7 @@ ModelViewer::ModelViewer(
: Container(gui, size),
modelName(modelName),
camera(),
batch(std::make_unique<Batch3D>(1024)),
fbo(std::make_unique<Framebuffer>(size.x, size.y)) {
batch(std::make_unique<Batch3D>(1024)) {
camera.perspective = true;
camera.position = glm::vec3(2, 2, 2);
}
@ -115,7 +115,13 @@ void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
return;
}
auto& prevShader = Shader::getUsed();
if (!fbo) {
if (size.x <= 0 || size.y <= 0) {
return;
}
fbo = std::make_unique<Framebuffer>(size.x, size.y);
}
fbo->resize(size.x, size.y);
{
glDisable(GL_SCISSOR_TEST);
@ -154,6 +160,13 @@ void ModelViewer::draw(const DrawContext& pctx, const Assets& assets) {
Container::draw(pctx, assets);
}
void ModelViewer::setVisible(bool flag) {
if (!flag && fbo) {
fbo.reset();
}
Container::setVisible(flag);
}
void ModelViewer::setCenter(const glm::vec3& center) {
this->center = center;
}

View file

@ -1,15 +1,16 @@
#pragma once
#include "Container.hpp"
#include "window/Camera.hpp"
#include <memory>
#include <string>
#include "Container.hpp"
#include "window/Camera.hpp"
class Batch3D;
class Framebuffer;
namespace gui {
class ModelViewer : public Container {
class ModelViewer final : public Container {
private:
std::string modelName;
Camera camera;
@ -22,7 +23,6 @@ namespace gui {
bool grabbing = false;
public:
ModelViewer(GUI& gui, const glm::vec2& size, const std::string& modelName);
~ModelViewer();
void setModel(const std::string& modelName);
@ -33,6 +33,7 @@ namespace gui {
void act(float delta) override;
void draw(const DrawContext& pctx, const Assets& assets) override;
void setVisible(bool flag) override;
void setRotation(const glm::vec3& euler);
void setCenter(const glm::vec3& center);

View file

@ -41,10 +41,14 @@ glm::vec2 Panel::getContentSize() const {
void Panel::cropToContent() {
if (maxLength > 0.0f) {
setSize(glm::vec2(
getSize().x, glm::max(minLength, glm::min(maxLength, actualLength))
glm::max(minLength, glm::min(maxLength, actualLengthX)),
glm::max(minLength, glm::min(maxLength, actualLengthY))
));
} else {
setSize(glm::vec2(getSize().x, glm::max(minLength, actualLength)));
setSize(glm::vec2(
glm::max(minLength, actualLengthX),
glm::max(minLength, actualLengthY)
));
}
}
@ -93,21 +97,30 @@ void Panel::refresh() {
y += nodeSize.y + margin.w + interval;
maxw = fmax(maxw, ex + nodeSize.x + margin.z + padding.z);
}
actualLength = y + padding.w;
actualLengthX = size.x;
actualLengthY = y + padding.w;
} else {
float maxh = size.y;
for (auto& node : nodes) {
glm::vec2 nodesize = node->getSize();
const glm::vec4 margin = node->getMargin();
x += margin.x;
node->setPos(glm::vec2(x, y + margin.y));
x += nodesize.x + margin.z + interval;
node->refresh();
maxh = fmax(
maxh, y + margin.y + node->getSize().y + margin.w + padding.w
float ey = y + margin.y;
node->setPos(glm::vec2(x, ey));
int height = glm::floor(
size.y - padding.y - padding.w - margin.y - margin.w
);
if (node->isResizing()) {
node->setMaxSize({node->getMaxSize().x, height});
node->setSize(glm::vec2(node->getSize().x, height));
}
node->refresh();
glm::vec2 nodesize = node->getSize();
x += nodesize.x + margin.z + interval;
maxh = fmax(maxh, ey + nodesize.y + margin.w + padding.w);
}
actualLength = size.y;
actualLengthY = size.y;
actualLengthX = x + padding.z;
}
}

View file

@ -10,7 +10,7 @@ class Assets;
class DrawContext;
namespace gui {
class Plotter : public UINode {
class Plotter final : public UINode {
std::unique_ptr<int[]> points;
float multiplier;
int index = 0;

View file

@ -13,14 +13,21 @@ SelectBox::SelectBox(
GUI& gui,
std::vector<Option>&& options,
Option selected,
Mode mode,
int contentWidth,
const glm::vec4& padding
)
: Button(gui, selected.text, padding, nullptr, glm::vec2(contentWidth, -1)),
options(std::move(options)) {
listenAction(UIAction::CLICK, [this](GUI& gui) {
auto panel = std::make_shared<Panel>(gui, getSize());
: Button(gui, selected.text, padding, nullptr, glm::vec2(-1, -1)),
options(std::move(options)), mode(mode) {
listenAction(UIAction::CLICK, [this, contentWidth](GUI& gui) {
auto panel = std::make_shared<Panel>(
gui,
getMode() == Mode::BUTTON ? glm::vec2 {contentWidth, contentWidth}
: getSize(),
glm::vec4 {2.0f},
0.0f
);
panel->setColor({});
panel->setPadding(glm::vec4(0, size.y, 0, 0));
panel->setPos(calcPos() + glm::vec2(0, 0));
for (const auto& option : this->options) {
@ -51,7 +58,9 @@ void SelectBox::listenChange(OnStringChange&& callback) {
void SelectBox::setSelected(const Option& selected) {
this->selected = selected;
this->label->setText(selected.text);
if (mode == Mode::SELECT) {
this->label->setText(selected.text);
}
}
const SelectBox::Option& SelectBox::getSelected() const {
@ -66,6 +75,14 @@ void SelectBox::setOptions(std::vector<Option>&& options) {
this->options = std::move(options);
}
SelectBox::Mode SelectBox::getMode() const {
return mode;
}
void SelectBox::setMode(SelectBox::Mode mode) {
this->mode = mode;
}
void SelectBox::drawBackground(const DrawContext& pctx, const Assets&) {
glm::vec2 pos = calcPos();
auto batch = pctx.getBatch2D();
@ -73,6 +90,10 @@ void SelectBox::drawBackground(const DrawContext& pctx, const Assets&) {
batch->setColor(calcColor());
batch->rect(pos.x, pos.y, size.x, size.y);
batch->setColor({1.0f, 1.0f, 1.0f, 0.333f});
if (mode != Mode::SELECT) {
return;
}
int paddingRight = padding.w;
int widthHalf = 8;

View file

@ -11,15 +11,21 @@ namespace gui {
std::string value;
std::wstring text;
};
enum class Mode {
SELECT,
BUTTON,
};
private:
std::vector<Option> options;
Option selected {};
StringCallbacksSet changeCallbacks;
Mode mode;
public:
SelectBox(
GUI& gui,
std::vector<Option>&& elements,
Option selected,
Mode mode,
int contentWidth,
const glm::vec4& padding
);
@ -34,8 +40,13 @@ namespace gui {
void setOptions(std::vector<Option>&& options);
Mode getMode() const;
void setMode(Mode mode);
void drawBackground(const DrawContext& pctx, const Assets&) override;
std::shared_ptr<Label> getLabel() const;
};
}

View file

@ -452,7 +452,7 @@ void TextBox::refreshLabel() {
uint height = label->getLinesNumber() * rawTextCache.metrics.lineHeight *
label->getLineInterval();
label->setSize(glm::vec2(label->getSize().x, height));
actualLength = height;
actualLengthY = height;
} else {
setScrollable(false);
}
@ -1133,10 +1133,10 @@ void TextBox::setOnControlCombination(key_handler handler) {
}
void TextBox::setFocusedColor(glm::vec4 color) {
this->focusedColor = color;
this->focusedColor = std::move(color);
}
glm::vec4 TextBox::getFocusedColor() const {
const glm::vec4& TextBox::getFocusedColor() const {
return focusedColor;
}
@ -1144,7 +1144,7 @@ void TextBox::setTextColor(glm::vec4 color) {
this->textColor = color;
}
glm::vec4 TextBox::getTextColor() const {
const glm::vec4& TextBox::getTextColor() const {
return textColor;
}
@ -1248,7 +1248,7 @@ void TextBox::setPadding(glm::vec4 padding) {
refresh();
}
glm::vec4 TextBox::getPadding() const {
const glm::vec4& TextBox::getPadding() const {
return padding;
}

View file

@ -8,7 +8,7 @@ class ActionsHistory;
namespace gui {
class TextBoxHistorian;
class TextBox : public Container {
class TextBox final : public Container {
const Input& inputEvents;
LabelCache rawTextCache;
std::shared_ptr<ActionsHistory> history;
@ -100,124 +100,124 @@ namespace gui {
glm::vec4 padding=glm::vec4(4.0f)
);
virtual ~TextBox();
~TextBox();
void paste(const std::wstring& text, bool history=true);
void erase(size_t start, size_t length);
void resetSelection();
virtual void setTextSupplier(wstringsupplier supplier);
void setTextSupplier(wstringsupplier supplier);
/// @brief Consumer called on stop editing text (textbox defocus)
/// @param consumer std::wstring consumer function
virtual void setTextConsumer(wstringconsumer consumer);
void setTextConsumer(wstringconsumer consumer);
/// @brief Sub-consumer called while editing text
/// @param consumer std::wstring consumer function
virtual void setTextSubConsumer(wstringconsumer consumer);
void setTextSubConsumer(wstringconsumer consumer);
/// @brief Text validator called while text editing and returns true if
/// text is valid
/// @param validator std::wstring consumer returning boolean
virtual void setTextValidator(wstringchecker validator);
void setTextValidator(wstringchecker validator);
virtual void setOnControlCombination(key_handler handler);
void setOnControlCombination(key_handler handler);
virtual void setFocusedColor(glm::vec4 color);
virtual glm::vec4 getFocusedColor() const;
void setFocusedColor(glm::vec4 color);
const glm::vec4& getFocusedColor() const;
virtual void setTextColor(glm::vec4 color);
virtual glm::vec4 getTextColor() const;
void setTextColor(glm::vec4 color);
const glm::vec4& getTextColor() const;
/// @brief Set color of textbox marked by validator as invalid
virtual void setErrorColor(glm::vec4 color);
void setErrorColor(glm::vec4 color);
/// @brief Get color of textbox marked by validator as invalid
virtual glm::vec4 getErrorColor() const;
glm::vec4 getErrorColor() const;
/// @brief Get TextBox content text or placeholder if empty
virtual const std::wstring& getText() const;
const std::wstring& getText() const;
/// @brief Set TextBox content text
virtual void setText(const std::wstring &value);
void setText(const std::wstring &value);
/// @brief Get text placeholder
virtual const std::wstring& getPlaceholder() const;
const std::wstring& getPlaceholder() const;
/// @brief Set text placeholder
/// @param text will be used instead of empty
virtual void setPlaceholder(const std::wstring& text);
void setPlaceholder(const std::wstring& text);
/// @brief Get textbox hint
virtual const std::wstring& getHint() const;
const std::wstring& getHint() const;
/// @brief Set textbox hint
/// @param text will be shown instead of empty
virtual void setHint(const std::wstring& text);
void setHint(const std::wstring& text);
/// @brief Get selected text
virtual std::wstring getSelection() const;
std::wstring getSelection() const;
/// @brief Get current caret position in text
/// @return integer in range [0, text.length()]
virtual size_t getCaret() const;
size_t getCaret() const;
/// @brief Set caret position in the text
/// @param position integer in range [0, text.length()]
virtual void setCaret(size_t position);
void setCaret(size_t position);
/// @brief Set caret position in the text
/// @param position integer in range [-text.length(), text.length()]
virtual void setCaret(ptrdiff_t position);
void setCaret(ptrdiff_t position);
/// @brief Select part of the text
/// @param start index of the first selected character
/// @param end index of the last selected character + 1
virtual void select(int start, int end);
void select(int start, int end);
/// @brief Get number of line at specific position in text
/// @param position target position
/// @return line number
virtual uint getLineAt(size_t position) const;
uint getLineAt(size_t position) const;
/// @brief Get specific line text position
/// @param line target line
/// @return line position in text
virtual size_t getLinePos(uint line) const;
size_t getLinePos(uint line) const;
int calcIndexAt(int x, int y) const;
int getLineYOffset(int line) const;
/// @brief Check text with validator set with setTextValidator
/// @return true if text is valid
virtual bool validate();
bool validate();
virtual void setValid(bool valid);
virtual bool isValid() const;
void setValid(bool valid);
bool isValid() const;
/// @brief Enable/disable multiline mode
virtual void setMultiline(bool multiline);
void setMultiline(bool multiline);
/// @brief Check if multiline mode is enabled
virtual bool isMultiline() const;
bool isMultiline() const;
/// @brief Enable/disable text wrapping
virtual void setTextWrapping(bool flag);
void setTextWrapping(bool flag);
/// @brief Check if text wrapping is enabled
virtual bool isTextWrapping() const;
bool isTextWrapping() const;
/// @brief Enable/disable text editing feature
virtual void setEditable(bool editable);
void setEditable(bool editable);
/// @brief Check if text editing feature is enabled
virtual bool isEditable() const;
bool isEditable() const;
virtual bool isEdited() const;
virtual void setUnedited();
bool isEdited() const;
void setUnedited();
virtual void setPadding(glm::vec4 padding);
glm::vec4 getPadding() const;
void setPadding(glm::vec4 padding);
const glm::vec4& getPadding() const;
size_t getSelectionStart() const;
size_t getSelectionEnd() const;
@ -226,34 +226,34 @@ namespace gui {
bool isKeepLineSelection() const;
/// @brief Set runnable called on textbox focus
virtual void setOnEditStart(runnable oneditstart);
void setOnEditStart(runnable oneditstart);
virtual void setAutoResize(bool flag);
virtual bool isAutoResize() const;
void setAutoResize(bool flag);
bool isAutoResize() const;
virtual void setShowLineNumbers(bool flag);
virtual bool isShowLineNumbers() const;
void setShowLineNumbers(bool flag);
bool isShowLineNumbers() const;
virtual void reposition() override;
virtual void onFocus() override;
virtual void refresh() override;
virtual void doubleClick(int x, int y) override;
virtual void click(int, int) override;
virtual void mouseMove(int x, int y) override;
virtual bool isFocuskeeper() const override {return true;}
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void drawBackground(const DrawContext& pctx, const Assets& assets) override;
virtual void typed(unsigned int codepoint) override;
virtual void keyPressed(Keycode key) override;
virtual std::shared_ptr<UINode> getAt(const glm::vec2& pos) override;
virtual void setOnUpPressed(const runnable &callback);
virtual void setOnDownPressed(const runnable &callback);
void reposition() override;
void onFocus() override;
void refresh() override;
void doubleClick(int x, int y) override;
void click(int, int) override;
void mouseMove(int x, int y) override;
bool isFocuskeeper() const override {return true;}
void draw(const DrawContext& pctx, const Assets& assets) override;
void drawBackground(const DrawContext& pctx, const Assets& assets) override;
void typed(unsigned int codepoint) override;
void keyPressed(Keycode key) override;
std::shared_ptr<UINode> getAt(const glm::vec2& pos) override;
void setOnUpPressed(const runnable& callback);
void setOnDownPressed(const runnable& callback);
virtual void setSyntax(std::string_view lang);
virtual const std::string& getSyntax() const;
void setSyntax(std::string_view lang);
const std::string& getSyntax() const;
virtual void setMarkup(std::string_view lang);
virtual const std::string& getMarkup() const;
void setMarkup(std::string_view lang);
const std::string& getMarkup() const;
std::shared_ptr<Label> getLabel() const;
};

View file

@ -99,7 +99,7 @@ int TrackBar::getTrackWidth() const {
return trackWidth;
}
glm::vec4 TrackBar::getTrackColor() const {
const glm::vec4& TrackBar::getTrackColor() const {
return trackColor;
}

View file

@ -3,7 +3,7 @@
#include "UINode.hpp"
namespace gui {
class TrackBar : public UINode {
class TrackBar final : public UINode {
protected:
glm::vec4 trackColor {1.0f, 1.0f, 1.0f, 0.4f};
doublesupplier supplier = nullptr;
@ -24,29 +24,29 @@ namespace gui {
double step = 1.0,
int trackWidth = 12
);
virtual void draw(const DrawContext& pctx, const Assets& assets) override;
void draw(const DrawContext& pctx, const Assets& assets) override;
virtual void setSupplier(doublesupplier);
virtual void setConsumer(doubleconsumer);
virtual void setSubConsumer(doubleconsumer);
void setSupplier(doublesupplier);
void setConsumer(doubleconsumer);
void setSubConsumer(doubleconsumer);
virtual void mouseMove(int x, int y) override;
virtual void mouseRelease(int x, int y) override;
void mouseMove(int x, int y) override;
void mouseRelease(int x, int y) override;
virtual double getValue() const;
virtual double getMin() const;
virtual double getMax() const;
virtual double getStep() const;
virtual int getTrackWidth() const;
virtual glm::vec4 getTrackColor() const;
virtual bool isChangeOnRelease() const;
double getValue() const;
double getMin() const;
double getMax() const;
double getStep() const;
int getTrackWidth() const;
const glm::vec4& getTrackColor() const;
bool isChangeOnRelease() const;
virtual void setValue(double);
virtual void setMin(double);
virtual void setMax(double);
virtual void setStep(double);
virtual void setTrackWidth(int);
virtual void setTrackColor(glm::vec4);
virtual void setChangeOnRelease(bool);
void setValue(double);
void setMin(double);
void setMax(double);
void setStep(double);
void setTrackWidth(int);
void setTrackColor(glm::vec4);
void setChangeOnRelease(bool);
};
}

View file

@ -79,6 +79,8 @@ void UINode::click(int, int) {
void UINode::clicked(Mousecode button) {
if (button == Mousecode::BUTTON_2) {
actions.notify(UIAction::RIGHT_CLICK, gui);
} else if (button == Mousecode::BUTTON_3) {
actions.notify(UIAction::MIDDLE_CLICK, gui);
}
}

View file

@ -58,6 +58,7 @@ namespace gui {
FOCUS,
DEFOCUS,
RIGHT_CLICK,
MIDDLE_CLICK,
MOUSE_OVER,
MOUSE_OUT,
MOUSE_ENTER,

View file

@ -181,6 +181,7 @@ static void read_uinode(
register_action(node, reader, element, "onclick", UIAction::CLICK);
register_action(node, reader, element, "onrightclick", UIAction::RIGHT_CLICK);
register_action(node, reader, element, "onmiddleclick", UIAction::MIDDLE_CLICK);
register_action(node, reader, element, "onfocus", UIAction::FOCUS);
register_action(node, reader, element, "ondefocus", UIAction::DEFOCUS);
register_action(node, reader, element, "ondoubleclick", UIAction::DOUBLE_CLICK);
@ -458,8 +459,7 @@ static std::shared_ptr<UINode> read_select(
}
auto value = elem->attr("value").getText();
auto text = parse_inner_text(*elem, reader.getContext());
options.push_back(SelectBox::Option {std::move(value), std::move(text)}
);
options.push_back(SelectBox::Option {std::move(value), std::move(text)});
}
if (element.has("selected")) {
@ -481,10 +481,19 @@ static std::shared_ptr<UINode> read_select(
selected.text = innerText;
}
SelectBox::Mode mode = SelectBox::Mode::SELECT;
if (element.has("mode")) {
auto modeName = element.attr("mode").getText();
if (modeName == "button") {
mode = SelectBox::Mode::BUTTON;
}
}
auto selectBox = std::make_shared<SelectBox>(
gui,
std::move(options),
std::move(selected),
mode,
contentWidth,
std::move(padding)
);

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

@ -48,6 +48,14 @@ void ItemStack::setCount(itemcount_t count) {
}
}
void ItemStack::maximizeCount(const ContentIndices& indices) {
auto& def = indices.items.require(getItemId());
this->count = def.stackSize;
if (count == 0) {
clear();
}
}
void ItemStack::setField(std::string_view name, dv::value value) {
if (fields == nullptr) {
if (value == nullptr) {

View file

@ -18,6 +18,7 @@ public:
void set(const ItemStack& item);
void set(ItemStack&& item);
void setCount(itemcount_t count);
void maximizeCount(const ContentIndices& indices);
/// @brief Set a field in the item stack data.
void setField(std::string_view name, dv::value value);

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

@ -283,7 +283,7 @@ void EngineController::createWorld(
// generator-based environments must die in 1.0
bool genBasedEnv = false;
if (auto genDef = generators.find(environment)) {
if (generators.find(environment)) {
genBasedEnv = true;
}
@ -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

@ -42,7 +42,7 @@ static int l_get_model(lua::State* L) {
auto& rigConfig = *skeleton->config;
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
const auto& modelOverride = skeleton->modelOverrides[index];
if (!modelOverride.model) {
if (modelOverride.model) {
return lua::pushstring(L, modelOverride.name);
}
return lua::pushstring(L, rigConfig.getBones()[index]->model.name);

View file

@ -1127,6 +1127,7 @@ static int l_gui_load_document(lua::State* L) {
}
auto env = scripting::create_doc_environment(parentEnv, alias);
auto envId = *env;
// namespace extension
if (lua::istable(L, 4)) {
if (lua::get_from(L, "table", "extend")) {
@ -1148,7 +1149,7 @@ static int l_gui_load_document(lua::State* L) {
engine->requireAssets().store(std::move(documentPtr), alias);
scripting::on_ui_open(*document, {args});
return 0;
return lua::pushenv(L, envId);
}
static int l_set_syntax_styles(lua::State* L) {

View file

@ -149,6 +149,15 @@ static int l_get_second_inventory(lua::State* L) {
}
}
static int l_get_exchange_inventory(lua::State* L) {
auto inventory = hud->getExchangeInventory();
if (inventory == nullptr) {
return lua::pushinteger(L, 0);
} else {
return lua::pushinteger(L, inventory->getId());
}
}
static int l_get_player(lua::State* L) {
auto player = hud->getPlayer();
return lua::pushinteger(L, player->getId());
@ -216,6 +225,7 @@ const luaL_Reg hudlib[] = {
{"show_overlay", wrap_hud<l_show_overlay>},
{"get_block_inventory", wrap_hud<l_get_block_inventory>},
{"get_second_inventory", wrap_hud<l_get_second_inventory>},
{"get_exchange_inventory", wrap_hud<l_get_exchange_inventory>},
{"close", wrap_hud<l_close>},
{"pause", wrap_hud<l_pause>},
{"resume", wrap_hud<l_resume>},

View file

@ -2,6 +2,7 @@
#include "items/Inventories.hpp"
#include "items/ItemStack.hpp"
#include "logic/BlocksController.hpp"
#include "logic/scripting/lua/lua_util.hpp"
#include "world/Level.hpp"
#include "api_lua.hpp"
@ -180,7 +181,7 @@ static int l_move(lua::State* L) {
validate_slotid(slotAid, invA);
auto invBid = lua::tointeger(L, 3);
auto slotBid = lua::isnil(L, 4) ? -1 : lua::tointeger(L, 4);
auto slotBid = lua::isnoneornil(L, 4) ? -1 : lua::tointeger(L, 4);
auto& invB = get_inventory(invBid, 3);
auto& slot = invA.getSlot(slotAid);
if (slotBid == -1) {

View file

@ -1,6 +1,8 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <iostream>
#include <glm/gtx/string_cast.hpp>
#include "api_lua.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/quaternion.hpp>
#include <sstream>
@ -31,14 +33,14 @@ static int l_tostring(lua::State* L) {
lua::check_argc(L, 1);
auto quat = lua::toquat(L, 1);
float components[4] = {quat.w, quat.x, quat.y, quat.z};
std::stringstream ss;
ss << "quat"
<< "{";
ss << "quat {";
for (int i = 0; i < 4; i++) {
if (i > 0) {
ss << ", ";
}
ss << quat[i];
ss << components[i];
}
ss << "}";
return lua::pushstring(L, ss.str());
@ -52,10 +54,42 @@ static int l_from_euler(lua::State *L) {
return lua::pushquat(L, glm::quat(euler));
}
static int l_mul(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3);
glm::quat a = lua::toquat(L, 1);
glm::quat b = lua::toquat(L, 2);
glm::quat result = a * b;
if (argc == 3) {
lua::setquat(L, 3, result);
return 0;
}
return lua::pushquat(L, result);
}
static int l_mul_vec3(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3);
glm::quat q = lua::toquat(L, 1);
glm::vec3 v = lua::tovec3(L, 2);
glm::vec3 result = q * v;
if (argc == 3) {
lua::setvec(L, 3, result);
return 0;
}
return lua::pushvec3(L, result);
}
const luaL_Reg quatlib[] = {
{"from_mat4", lua::wrap<l_from_mat4>},
{"from_euler", lua::wrap<l_from_euler>},
{"slerp", lua::wrap<l_slerp>},
{"tostring", lua::wrap<l_tostring>},
{"mul", lua::wrap<l_mul>},
{"mul_vec3", lua::wrap<l_mul_vec3>},
{nullptr, nullptr}
};

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

@ -495,10 +495,11 @@ void Entities::render(
}
bool Entities::hasBlockingInside(AABB aabb) {
constexpr float eps = 0.05f;
auto view = registry->view<EntityId, Rigidbody>();
for (auto [entity, eid, body] : view.each()) {
AABB bodyAABB(body.hitbox.getAABB());
bodyAABB.scale({1, 0.95f, 1});
bodyAABB.scale(glm::vec3(1.0f - eps));
if (eid.def.blocking && aabb.intersects(bodyAABB)) {
return true;
}

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

@ -40,6 +40,15 @@ static std::unordered_map<std::string, int> keycodes {
{"right", GLFW_KEY_RIGHT},
{"down", GLFW_KEY_DOWN},
{"up", GLFW_KEY_UP},
{"num-lock", GLFW_KEY_NUM_LOCK},
{"print-screen", GLFW_KEY_PRINT_SCREEN},
{"num-add", GLFW_KEY_KP_ADD},
{"num-multiply", GLFW_KEY_KP_MULTIPLY},
{"num-divide", GLFW_KEY_KP_DIVIDE},
{"num-subtract", GLFW_KEY_KP_SUBTRACT},
{"num-enter", GLFW_KEY_KP_ENTER},
{"num-equal", GLFW_KEY_KP_EQUAL},
{"num-decimal", GLFW_KEY_KP_DECIMAL},
};
static std::unordered_map<std::string, int> mousecodes {
@ -95,6 +104,9 @@ void input_util::initialize() {
for (char i = 'a'; i <= 'z'; i++) {
keycodes[std::string({i})] = GLFW_KEY_A - 'a' + i;
}
for (int i = 0; i <= 9; i++) {
keycodes["num-" + std::to_string(i)] = GLFW_KEY_KP_0 + i;
}
for (const auto& entry : keycodes) {
keynames[entry.second] = entry.first;
}
@ -119,8 +131,34 @@ Mousecode input_util::mousecode_from(const std::string& name) {
return static_cast<Mousecode>(found->second);
}
static const char* numpad_key_names[] {
"Num 0",
"Num 1",
"Num 2",
"Num 3",
"Num 4",
"Num 5",
"Num 6",
"Num 7",
"Num 8",
"Num 9",
"Num .",
"Num /",
"Num *",
"Num -",
"Num +",
"Num Enter",
"Num =",
};
std::string input_util::to_string(Keycode code) {
int icode_repr = static_cast<int>(code);
if (icode_repr == -1) {
return "Unknown";
}
if (icode_repr >= GLFW_KEY_KP_0 && icode_repr <= GLFW_KEY_KP_EQUAL) {
return numpad_key_names[icode_repr - GLFW_KEY_KP_0];
}
const char* name =
glfwGetKeyName(icode_repr, glfwGetKeyScancode(icode_repr));
if (name == nullptr) {

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;
}