Merge pull request #748 from MihailRis/volume-renderer

yet another something renderer
This commit is contained in:
MihailRis 2026-02-13 02:31:58 +03:00 • committed by GitHub
commit 99106df230
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 1889 additions and 704 deletions

View file

@ -40,6 +40,37 @@ gui.get_locales_info() -> table of tables {
Returns information about all loaded locales (res/texts/\*).
## Frames
```lua
-- A replacement for menu:reset() to close the pause menu, deactivating the main UI frame.
gui.close_menu()
-- Creates a frame.
gui.create_frame(
-- Global frame id (not related to the UI 'id' property).
id: str,
-- The texture to render the frame to.
-- If the string is empty, the frame is rendered to the screen.
output_texture: str,
-- Frame size. For example: {640, 480}
size: vec2
) -> Element, Document
-- Returns the id of the active frame (not the element id).
gui.get_active_frame() -> str
-- Sets the active frame, receiving user input.
-- An empty string specifies the null frame capturing the cursor.
gui.set_active_frame(
-- ID of the frame created via gui.create_frame
id: str,
-- Function providing the cursor position in the frame.
-- Used for custom projection (e.g., in 3D)
[optional] cursorLocator
)
```
```lua
gui.clear_markup(
-- markup language ("md" - Markdown)

View file

@ -40,6 +40,37 @@ gui.get_env(document: string) -> table
gui.get_locales_info() -> table
```
## Фреймы
```lua
-- Замена для menu:reset() для закрытия меню паузы, деактивирующая основной фрейм UI.
gui.close_menu()
-- Создаёт фрейм.
gui.create_frame(
-- Глобальный id фрейма (не связан с UI свойством 'id').
id: str,
-- Текстура, в которую будет производиться рендер фрейма.
-- В случае пустой строки рендер производится на экран.
output_texture: str,
-- Размер фрейма. Например: {640, 480}
size: vec2
) -> Element, Document
-- Возвращает id активного фрейма (не id элемента).
gui.get_active_frame() -> str
-- Устанавливает активный фрейм, получающий пользовательский ввод.
-- Пустая строка указывает null-фрейм, при котором захватывается курсор.
gui.set_active_frame(
-- id фрейма, созданного через gui.create_frame
id: str,
-- Функция-поставщик позиции курсора во фрейме.
-- Используется для пользовательской проекции (например в 3D)
[опционально] cursorLocator
)
```
## Разметка
```lua
@ -109,4 +140,4 @@ gui.template(
```lua
-- Корневой UI документ.
gui.root: Document
```
```

View file

@ -1,5 +1,5 @@
<panel size='400' color='0' interval='1' context='menu'>
<button onclick='menu:reset()'>@Continue</button>
<button onclick='gui.close_menu()'>@Continue</button>
<button onclick='menu.page="content?mode=world"'>@Content</button>
<button onclick='menu.page="settings"'>@Settings</button>
<button onclick='app.close_world(true)'>

View file

@ -1,4 +1,4 @@
<container size='668,418' color='#0F1E2DB2' context='menu'>
<container size='668,444' color='#0F1E2DB2' context='menu'>
<panel pos='6' size='250' color='0' interval='1'>
<button id='s_aud' onclick='sections:set("audio")'>@Audio</button>
<button id='s_dsp' onclick='sections:set("display")'>@Display</button>

View file

@ -46,4 +46,5 @@ function on_open()
create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip")
create_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip")
create_setting("graphics.shadows-quality", "Shadows quality", 1)
create_setting("graphics.clouds-quality", "Clouds quality", 1)
end

View file

@ -8,7 +8,8 @@
"translucent",
"background",
"skybox_gen",
"shadows"
"shadows",
"clouds"
],
"post-effects": [
"default",
@ -48,7 +49,8 @@
"gui/world",
"gui/hud",
"gui/entity",
"gui/half_block"
"gui/half_block",
"misc/blank"
],
"fonts": [
{

View file

@ -1,4 +1,7 @@
{
"clouds": 0.8
"clouds": 1.0,
"clouds_tint": [0.6, 0.7, 0.8],
"sky_tint": [1.5, 1.5, 1.5],
"min_sky_light": [0.05, 0.07, 0.1]
}

View file

@ -2,5 +2,6 @@
"fog_opacity": 0.98,
"fog_dencity": 3.6,
"fog_curve": 0.25,
"clouds": 0.8
"clouds": 0.9,
"min_sky_light": [0.0, 0.0, 0.0]
}

View file

@ -19,5 +19,8 @@
"fog_opacity": 0.5,
"fog_dencity": 1.7,
"fog_curve": 0.5,
"clouds": 0.9
"clouds": 0.96,
"sky_tint": [2.0, 2.0, 2.0],
"clouds_tint": [0.6, 0.7, 0.8],
"min_sky_light": [0.05, 0.07, 0.1]
}

View file

@ -11,5 +11,8 @@
"fog_opacity": 0.8,
"fog_dencity": 2.5,
"fog_curve": 0.5,
"clouds": 0.5
"clouds": 1.0,
"sky_tint": [1.9, 1.9, 1.9],
"clouds_tint": [0.7, 0.8, 0.9],
"min_sky_light": [0.05, 0.07, 0.1]
}

View file

@ -19,5 +19,7 @@
"fog_dencity": 2.0,
"fog_curve": 0.5,
"clouds": 0.7,
"thunder_rate": 0.1
"thunder_rate": 0.1,
"clouds_tint": [0.3, 0.4, 0.5],
"min_sky_light": [0.0, 0.0, 0.0]
}

View file

@ -232,6 +232,22 @@ _GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu
menu = _MENU
gui.root = _GUI_ROOT
gui.main_frame_id = "core:main"
function gui.close_menu()
if menu then
menu:reset()
end
gui.set_active_frame("")
end
local __gui_create_frame = gui.create_frame
function gui.create_frame(id, output, size)
__gui_create_frame(id, output, size)
local document = Document.new(id)
return document.root, document
end
do
local status, err = pcall(function()
@ -435,9 +451,12 @@ function __vc_on_hud_open()
if menu.page ~= "" then
if not menu:back() then
menu:reset()
gui.set_active_frame("")
end
elseif hud.is_inventory_open() then
hud.close_inventory()
elseif gui.get_active_frame() then
gui.set_active_frame("")
else
hud.pause()
end

31
res/shaders/clouds.glslf Normal file
View file

@ -0,0 +1,31 @@
layout (location = 0) out vec4 f_color;
layout (location = 1) out vec4 f_position;
layout (location = 2) out vec4 f_normal;
layout (location = 3) out vec4 f_emission;
#include <world_fragment_header>
in vec4 a_torchLight;
uniform sampler2D u_texture0;
uniform vec3 u_sunDir;
// flags
uniform bool u_alphaClip;
uniform bool u_debugLights;
uniform bool u_debugNormals;
uniform vec4 u_tint;
void main() {
f_color.rgb = max(a_torchLight.rgb, a_skyLight);
f_color.a = 1.0;
f_color *= u_tint;
#ifndef ADVANCED_RENDER
vec3 fogColor = texture(u_skybox, a_dir).rgb;
f_color = mix(f_color, vec4(fogColor, 1.0), a_fog);
#endif
f_position = vec4(a_position, 1.0);
f_normal = vec4(a_normal, 1.0);
f_emission = vec4(vec3(a_emission), 1.0);
}

49
res/shaders/clouds.glslv Normal file
View file

@ -0,0 +1,49 @@
#include <commons>
layout (location = 0) in vec3 v_position;
layout (location = 1) in vec2 v_texCoord;
layout (location = 2) in vec4 v_light;
layout (location = 3) in vec4 v_normal;
#include <world_vertex_header>
#include <lighting>
#include <fog>
#include <sky>
uniform float u_dayTime;
out vec4 a_torchLight;
void main() {
a_modelpos = u_model * vec4(v_position, 1.0f);
a_modelpos.x += sin(u_timer * 0.05 + a_modelpos.z * 0.002) * 1.5;
a_modelpos.z += sin(u_timer * 0.2 + a_modelpos.x * 0.002) * 1.5;
a_modelpos.y += sin(u_timer * 0.1 + (a_modelpos.x + a_modelpos.z) * 0.001) * 2.0;
vec3 pos3d = a_modelpos.xyz - u_cameraPos;
a_realnormal = v_normal.xyz * 2.0 - 1.0;
a_normal = calc_screen_normal(a_realnormal);
a_torchLight = vec4(calc_torch_light(
v_light.rgb, a_realnormal, a_modelpos.xyz, u_torchlightColor, u_gamma
), 1.0);
a_texCoord = v_texCoord;
a_dir = a_modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
a_skyLight = skyLightColor.rgb*v_light.a;
mat4 viewmodel = u_view * u_model;
a_distance = length(viewmodel * vec4(pos3d, 0.0));
#ifndef ADVANCED_RENDER
a_fog = calc_fog(length(viewmodel * vec4(pos3d * FOG_POS_SCALE, 0.0)) / 256.0);
#endif
a_emission = v_normal.w;
vec4 viewmodelpos = u_view * a_modelpos;
a_position = viewmodelpos.xyz;
gl_Position = u_proj * viewmodelpos;
}

View file

@ -13,6 +13,8 @@ layout (location = 4) in vec4 v_normal;
out vec4 a_color;
uniform float u_dayTime;
void main() {
a_modelpos = u_model * vec4(v_position, 1.0);
vec3 pos3d = a_modelpos.xyz - u_cameraPos;
@ -26,7 +28,7 @@ void main() {
a_texCoord = v_texCoord;
a_dir = a_modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_skybox);
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
a_color.rgb = max(a_color.rgb, skyLightColor.rgb * v_light.a) * v_color;
a_color.a = u_opacity;

View file

@ -8,9 +8,8 @@
#define CURVATURE_FACTOR 0.002
// lighting
#define SKY_LIGHT_MUL 2.9
#define SKY_LIGHT_TINT (vec3(1.0, 0.95, 0.9) * 2.0)
#define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33)
#define SKY_LIGHT_MUL 2.5
#define SKY_LIGHT_TINT (vec3(1.0, 0.75, 0.6) * 2.0)
// fog
#define FOG_POS_SCALE vec3(1.0, 0.2, 1.0)

View file

@ -3,11 +3,12 @@
#include <constants>
vec3 pick_sky_color(samplerCube cubemap) {
vec3 skyLightColor = texture(cubemap, vec3(0.4f, 0.05f, 0.4f)).rgb;
vec3 pick_sky_color(samplerCube cubemap, float dayTime, vec3 minSkyLight) {
float elevation = sin(dayTime * PI2) * 0.2;
vec3 skyLightColor = texture(cubemap, vec3(0.0f, elevation, -0.4f)).rgb;
skyLightColor *= SKY_LIGHT_TINT;
skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL);
skyLightColor = max(MIN_SKY_LIGHT, skyLightColor);
skyLightColor = max(minSkyLight, skyLightColor);
return skyLightColor;
}

View file

@ -11,5 +11,6 @@ uniform float u_timer;
uniform samplerCube u_skybox;
uniform vec3 u_torchlightColor;
uniform float u_torchlightDistance;
uniform vec3 u_minSkyLight;
#endif // GLSL_WORLD_UNIFORMS_

View file

@ -10,6 +10,8 @@ layout (location = 3) in vec4 v_normal;
#include <fog>
#include <sky>
uniform float u_dayTime;
out vec4 a_torchLight;
void main() {
@ -25,7 +27,7 @@ void main() {
a_texCoord = v_texCoord;
a_dir = a_modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_skybox);
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
a_skyLight = skyLightColor.rgb*v_light.a;
mat4 viewmodel = u_view * u_model;

View file

@ -241,6 +241,8 @@ uniform vec3 u_xaxis;
uniform vec3 u_yaxis;
uniform vec3 u_zaxis;
uniform vec3 u_lightDir;
uniform vec3 u_tint;
uniform vec3 u_hightlight;
uniform int u_quality;
uniform float u_mie;
uniform float u_fog;
@ -295,5 +297,5 @@ void main() {
col = 1.0 - exp(-col);
col = min(col, vec3(1.0));
// Output to screen
f_color = vec4(col, 1.0);
f_color = vec4(col * u_tint + u_hightlight, 1.0);
}

View file

@ -12,6 +12,8 @@ layout (location = 3) in vec4 v_normal;
out vec4 a_torchLight;
uniform float u_dayTime;
void main() {
a_modelpos = u_model * vec4(v_position, 1.0f);
vec3 pos3d = a_modelpos.xyz - u_cameraPos;
@ -25,7 +27,7 @@ void main() {
a_texCoord = v_texCoord;
a_dir = a_modelpos.xyz - u_cameraPos;
vec3 skyLightColor = pick_sky_color(u_skybox);
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
a_skyLight = skyLightColor.rgb*v_light.a;
mat4 viewmodel = u_view * u_model;

View file

@ -112,6 +112,7 @@ settings.Windowed=Оконный
settings.Borderless=Безрамочный
settings.Microphone=Микрофон
settings.microphone.None=Нет
settings.Clouds quality=Качество облаков
settings.Acoustic effects=Акустические эффекты
# Управление

BIN
res/textures/misc/blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

View file

@ -375,7 +375,7 @@ std::shared_ptr<Task> AssetsLoader::startTask(runnable onDone, int maxWorkers) {
auto pool =
std::make_shared<util::ThreadPool<aloader_entry, assetload::postfunc>>(
"assets-loader-pool",
[=]() { return std::make_shared<LoaderWorker>(this); },
[=]() { return std::make_unique<LoaderWorker>(this); },
[this](const assetload::postfunc& func) { func(&assets); },
maxWorkers
);

View file

@ -251,13 +251,13 @@ std::unique_ptr<ImageData> png::load_image(const ubyte* bytes, size_t size) {
}
png_read_image(pngPtr, rowPointers.get());
ImageFormat format = ImageFormat::rgba8888;
ImageFormat format = ImageFormat::RGBA8888;
switch (colorType) {
case PNG_COLOR_TYPE_RGBA:
format = ImageFormat::rgba8888;
format = ImageFormat::RGBA8888;
break;
case PNG_COLOR_TYPE_RGB:
format = ImageFormat::rgb888;
format = ImageFormat::RGB888;
break;
default:
png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
@ -296,7 +296,7 @@ void png::write_image(const std::string& filename, const ImageData* image) {
image->getWidth(),
image->getHeight(),
(const ubyte*)image->getData(),
image->getFormat() == ImageFormat::rgba8888
image->getFormat() == ImageFormat::RGBA8888
);
}
@ -306,6 +306,6 @@ util::Buffer<ubyte> png::encode_image(const ImageData& image) {
image.getWidth(),
image.getHeight(),
image.getData(),
format == ImageFormat::rgba8888
format == ImageFormat::RGBA8888
);
}

View file

@ -52,8 +52,6 @@ std::vector<std::string>& ContentControl::getBasePacks() {
}
void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
paths.setCurrentWorldFolder("");
scripting::cleanup(nonReset);
std::vector<PathsRoot> resRoots;
{

View file

@ -306,6 +306,9 @@ void Engine::startPauseLoop() {
}
void Engine::renderFrame() {
if (input->isCursorLocked() != (gui->getActiveFrame() == nullptr)) {
input->toggleCursor();
}
screen->draw(time.getDelta());
DrawContext ctx(nullptr, *window, nullptr);

View file

@ -1,7 +1,6 @@
#include "ServerMainloop.hpp"
#include "Engine.hpp"
#include "EnginePaths.hpp"
#include "logic/scripting/scripting.hpp"
#include "logic/LevelController.hpp"
#include "interfaces/Process.hpp"
@ -80,11 +79,10 @@ void ServerMainloop::run() {
void ServerMainloop::setLevel(std::unique_ptr<Level> level) {
if (level == nullptr) {
controller->onWorldQuit();
engine.getPaths().setCurrentWorldFolder("");
controller = nullptr;
} else {
controller = std::make_unique<LevelController>(
&engine, std::move(level), nullptr
engine, std::move(level), nullptr
);
}
}

View file

@ -10,7 +10,7 @@
UiDocument::UiDocument(
std::string id,
uidocscript script,
UiDocScript script,
const std::shared_ptr<gui::UINode>& root,
scriptenv env
) : id(std::move(id)), script(script), root(root), env(std::move(env)) {
@ -48,7 +48,7 @@ std::shared_ptr<gui::UINode> UiDocument::get(const std::string& id) const {
return found->second.lock();
}
const uidocscript& UiDocument::getScript() const {
const UiDocScript& UiDocument::getScript() const {
return script;
}
@ -73,7 +73,7 @@ std::unique_ptr<UiDocument> UiDocument::read(
gui::UiXmlReader reader(gui, scriptenv(env));
auto view = reader.readXML(file.string(), *xmldoc->getRoot());
view->setId("root");
uidocscript script {};
UiDocScript script {};
auto scriptFile = io::path(file.string()+".lua");
if (io::is_regular_file(scriptFile)) {
scripting::load_layout_script(

View file

@ -13,7 +13,7 @@ namespace gui {
class UINode;
}
struct uidocscript {
struct UiDocScript {
bool onopen : 1;
bool onprogress : 1;
bool onclose : 1;
@ -23,14 +23,14 @@ using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
class UiDocument {
std::string id;
uidocscript script;
UiDocScript script;
UINodesMap map;
std::shared_ptr<gui::UINode> root;
scriptenv env;
public:
UiDocument(
std::string id,
uidocscript script,
UiDocScript script,
const std::shared_ptr<gui::UINode> &root,
scriptenv env
);
@ -42,7 +42,7 @@ public:
const UINodesMap& getMap() const;
std::shared_ptr<gui::UINode> getRoot() const;
std::shared_ptr<gui::UINode> get(const std::string& id) const;
const uidocscript& getScript() const;
const UiDocScript& getScript() const;
scriptenv getEnvironment() const;
static std::unique_ptr<UiDocument> read(

View file

@ -1,9 +1,3 @@
#include <bitset>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "audio/audio.hpp"
#include "constants.hpp"
#include "content/Content.hpp"
@ -37,6 +31,12 @@
#include "world/Level.hpp"
#include "world/World.hpp"
#include <bitset>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
using namespace gui;
static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {

View file

@ -157,7 +157,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
frontend(frontend),
player(player),
debugImgWorldGen(std::make_unique<ImageData>(
ImageFormat::rgba8888, WORLDGEN_IMG_SIZE, WORLDGEN_IMG_SIZE
ImageFormat::RGBA8888, WORLDGEN_IMG_SIZE, WORLDGEN_IMG_SIZE
)) {
contentAccess = createContentAccess();
contentAccess->setId("hud.content-access");
@ -207,9 +207,6 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
}
Hud::~Hud() {
if (input.isCursorLocked()) {
input.toggleCursor();
}
// removing all controlled ui
for (auto& element : elements) {
onRemove(element);
@ -342,9 +339,6 @@ void Hud::update(bool visible) {
if (!gui.isFocusCaught()) {
processInput(visible);
}
if ((isMenuOpen || inventoryOpen) == input.isCursorLocked()) {
input.toggleCursor();
}
if (blockUI) {
voxel* vox = chunks.get(blockPos.x, blockPos.y, blockPos.z);
@ -395,6 +389,7 @@ void Hud::openInventory(bool playerInventory) {
inventoryView->bind(inventory, &content);
add(HudElement(HudElementMode::INVENTORY, inventoryDocument, inventoryView, false));
}
gui.setActiveFrame(GUI::CORE_MAIN);
}
std::shared_ptr<Inventory> Hud::openInventory(
@ -548,6 +543,7 @@ void Hud::closeInventory() {
}
}
cleanup();
gui.setActiveFrame("");
}
void Hud::add(const HudElement& element, const dv::value& argsArray) {
@ -710,9 +706,11 @@ void Hud::setPause(bool pause) {
if (!pause && menu.hasOpenPage()) {
menu.reset();
gui.setActiveFrame("");
}
if (pause && !menu.hasOpenPage()) {
menu.setPage("pause");
gui.setActiveFrame(GUI::CORE_MAIN);
}
}

View file

@ -10,7 +10,6 @@
#include <glm/glm.hpp>
class Camera;
class Block;
class Assets;
class Player;
class Engine;
@ -25,7 +24,6 @@ namespace gui {
class GUI;
class Menu;
class UINode;
class Panel;
class Container;
class InventoryView;
class SlotView;

View file

@ -56,12 +56,13 @@ LevelScreen::LevelScreen(
auto& assets = *engine.getAssets();
auto menu = engine.getGUI().getMenu();
menu->reset();
gui.setActiveFrame("");
auto player = level->players->get(localPlayer);
assert(player != nullptr);
controller =
std::make_unique<LevelController>(&engine, std::move(levelPtr), player);
std::make_unique<LevelController>(engine, std::move(levelPtr), player);
playerController = std::make_unique<PlayerController>(
settings, *level, *player, *controller->getBlocksController()
);
@ -112,11 +113,10 @@ LevelScreen::~LevelScreen() {
saveDecorations();
}
scripting::on_frontend_close();
// unblock all bindings
input.getBindings().enableAll();
playerController->getPlayer()->chunks->saveAndClear();
controller->onWorldQuit();
engine.getPaths().setCurrentWorldFolder("");
}
void LevelScreen::onOpen() {
@ -184,7 +184,7 @@ void LevelScreen::saveWorldPreview() {
static_cast<uint>(previewSize)}
);
renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing);
renderer->renderFrame(ctx, camera, false, *postProcessing);
auto image = postProcessing->toImage();
image->flipY();
imageio::write("world:preview.png", image.get());
@ -240,7 +240,7 @@ void LevelScreen::update(float delta) {
auto menu = gui.getMenu();
bool inputLocked =
menu->hasOpenPage() || hud->isInventoryOpen() || gui.isFocusCaught();
gui.getActiveFrame() || hud->isInventoryOpen() || gui.isFocusCaught();
bool paused = hud->isPause();
if (!paused) {
world.updateTimers(delta);
@ -271,9 +271,11 @@ void LevelScreen::draw(float delta) {
if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta());
}
renderer->renderFrame(
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
);
renderer->update(*camera, delta * !hud->isPause());
renderer->renderFrame(ctx, *camera, hudVisible, *postProcessing);
if (!hud->isPause()) {
scripting::on_frontend_render();
}
if (hudVisible) {
hud->draw(ctx);

View file

@ -95,7 +95,7 @@ std::unique_ptr<Atlas> AtlasBuilder::build(uint extrusion, bool prepare, uint ma
}
}
auto canvas = std::make_unique<ImageData>(ImageFormat::rgba8888, width, height);
auto canvas = std::make_unique<ImageData>(ImageFormat::RGBA8888, width, height);
std::unordered_map<std::string, UVRegion> regions;
std::vector<rectangle> rects = packer.getResult();
for (uint i = 0; i < entries.size(); i++) {

View file

@ -15,7 +15,7 @@ Batch2D::Batch2D(size_t capacity) : capacity(capacity), color(1.0f){
const ubyte pixels[] = {
0xFF, 0xFF, 0xFF, 0xFF
};
ImageData image(ImageFormat::rgba8888, 1, 1, pixels);
ImageData image(ImageFormat::RGBA8888, 1, 1, pixels);
blank = Texture::from(&image);
currentTexture = nullptr;
}

View file

@ -20,7 +20,7 @@ Batch3D::Batch3D(size_t capacity)
const ubyte pixels[] = {
255, 255, 255, 255,
};
ImageData image(ImageFormat::rgba8888, 1, 1, pixels);
ImageData image(ImageFormat::RGBA8888, 1, 1, pixels);
blank = Texture::from(&image);
currentTexture = nullptr;
}

View file

@ -89,6 +89,10 @@ Texture* Framebuffer::getTexture() const {
return texture.get();
}
std::shared_ptr<Texture> Framebuffer::getSharedTexture() const {
return texture;
}
uint Framebuffer::getWidth() const {
return width;
}

View file

@ -13,7 +13,7 @@ class Framebuffer : public Bindable {
uint width;
uint height;
uint format;
std::unique_ptr<Texture> texture;
std::shared_ptr<Texture> texture;
public:
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
Framebuffer(uint width, uint height, bool alpha=false);
@ -33,6 +33,8 @@ public:
/// @brief Get framebuffer color attachment
Texture* getTexture() const;
std::shared_ptr<Texture> getSharedTexture() const;
/// @brief Get framebuffer width
uint getWidth() const;
/// @brief Get framebuffer height

View file

@ -260,7 +260,7 @@ std::unique_ptr<ImageData> GBuffer::toImage() const {
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data.get());
glBindTexture(GL_TEXTURE_2D, 0);
return std::make_unique<ImageData>(
ImageFormat::rgb888, width, height, std::move(data)
ImageFormat::RGB888, width, height, std::move(data)
);
}

View file

@ -11,8 +11,8 @@ ImageData::ImageData(ImageFormat format, uint width, uint height)
: format(format), width(width), height(height) {
size_t pixsize;
switch (format) {
case ImageFormat::rgb888: pixsize = 3; break;
case ImageFormat::rgba8888: pixsize = 4; break;
case ImageFormat::RGB888: pixsize = 3; break;
case ImageFormat::RGBA8888: pixsize = 4; break;
default:
throw std::runtime_error("format is not supported");
}
@ -27,8 +27,8 @@ ImageData::ImageData(ImageFormat format, uint width, uint height, const ubyte* d
: format(format), width(width), height(height) {
size_t pixsize;
switch (format) {
case ImageFormat::rgb888: pixsize = 3; break;
case ImageFormat::rgba8888: pixsize = 4; break;
case ImageFormat::RGB888: pixsize = 3; break;
case ImageFormat::RGBA8888: pixsize = 4; break;
default:
throw std::runtime_error("format is not supported");
}
@ -40,9 +40,9 @@ ImageData::~ImageData() = default;
void ImageData::flipX() {
switch (format) {
case ImageFormat::rgb888:
case ImageFormat::rgba8888: {
uint size = (format == ImageFormat::rgba8888) ? 4 : 3;
case ImageFormat::RGB888:
case ImageFormat::RGBA8888: {
uint size = (format == ImageFormat::RGBA8888) ? 4 : 3;
for (uint y = 0; y < height; y++) {
for (uint x = 0; x < width / 2; x++) {
for (uint c = 0; c < size; c++) {
@ -62,9 +62,9 @@ void ImageData::flipX() {
void ImageData::flipY() {
switch (format) {
case ImageFormat::rgb888:
case ImageFormat::rgba8888: {
uint size = (format == ImageFormat::rgba8888) ? 4 : 3;
case ImageFormat::RGB888:
case ImageFormat::RGBA8888: {
uint size = (format == ImageFormat::RGBA8888) ? 4 : 3;
for (uint y = 0; y < height/2; y++) {
for (uint x = 0; x < width; x++) {
for (uint c = 0; c < size; c++) {
@ -87,8 +87,8 @@ void ImageData::blit(const ImageData& image, int x, int y) {
blitMatchingFormat(image, x, y);
return;
}
if (format == ImageFormat::rgba8888 &&
image.format == ImageFormat::rgb888) {
if (format == ImageFormat::RGBA8888 &&
image.format == ImageFormat::RGB888) {
blitRGB_on_RGBA(image, x, y);
return;
}
@ -188,10 +188,10 @@ static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const gl
void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color) {
switch (format) {
case ImageFormat::rgb888:
case ImageFormat::RGB888:
draw_line<3>(*this, x1, y1, x2, y2, color);
break;
case ImageFormat::rgba8888:
case ImageFormat::RGBA8888:
draw_line<4>(*this, x1, y1, x2, y2, color);
break;
default:
@ -223,10 +223,10 @@ static void draw_rect(ImageData& image, int dstX, int dstY, int width, int heigh
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
switch (format) {
case ImageFormat::rgb888:
case ImageFormat::RGB888:
draw_rect<3>(*this, x, y, width, height, color);
break;
case ImageFormat::rgba8888:
case ImageFormat::RGBA8888:
draw_rect<4>(*this, x, y, width, height, color);
break;
default:
@ -260,8 +260,8 @@ void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
void ImageData::blitMatchingFormat(const ImageData& image, int x, int y) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -295,8 +295,8 @@ void ImageData::blitMatchingFormat(const ImageData& image, int x, int y) {
void ImageData::extrude(int x, int y, int w, int h) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -431,8 +431,8 @@ static void check_matching(const ImageData& a, const ImageData& b) {
void ImageData::mulColor(const glm::ivec4& color) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -453,8 +453,8 @@ void ImageData::addColor(const ImageData& other, int multiplier) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -473,8 +473,8 @@ void ImageData::addColor(const ImageData& other, int multiplier) {
void ImageData::extend(int newWidth, int newHeight) {
size_t comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -499,8 +499,8 @@ void ImageData::extend(int newWidth, int newHeight) {
void ImageData::addColor(const glm::ivec4& color, int multiplier) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -521,8 +521,8 @@ void ImageData::mulColor(const ImageData& other) {
uint comps;
switch (format) {
case ImageFormat::rgb888: comps = 3; break;
case ImageFormat::rgba8888: comps = 4; break;
case ImageFormat::RGB888: comps = 3; break;
case ImageFormat::RGBA8888: comps = 4; break;
default:
throw std::runtime_error("only unsigned byte formats supported");
}
@ -541,7 +541,7 @@ void ImageData::mulColor(const ImageData& other) {
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) {
// RGBA is only supported
assert(image->getFormat() == ImageFormat::rgba8888);
assert(image->getFormat() == ImageFormat::RGBA8888);
assert(image->getWidth() == image->getHeight());
int srcwidth = image->getWidth();

View file

@ -6,8 +6,8 @@
#include <memory>
enum class ImageFormat {
rgb888,
rgba8888
RGB888,
RGBA8888
};
class ImageData {
@ -57,7 +57,7 @@ public:
}
size_t getDataSize() const {
size_t channels = 3 + (format == ImageFormat::rgba8888);
size_t channels = 3 + (format == ImageFormat::RGBA8888);
return width * height * channels;
}
};

View file

@ -0,0 +1,26 @@
#include "PaletteAtlasBuilder.hpp"
#include "../core/Atlas.hpp"
#include "../core/ImageData.hpp"
PaletteAtlasBuilder::PaletteAtlasBuilder() = default;
void PaletteAtlasBuilder::put(const std::string& name, const glm::vec4& color) {
entries[name] = { glm::clamp(color, glm::vec4(0.0f), glm::vec4(1.0f)) };
}
std::unique_ptr<Atlas> PaletteAtlasBuilder::build() const {
AtlasBuilder builder;
for (const auto& [name, entry] : entries) {
ubyte data[4] {
static_cast<ubyte>(entry.color.r * 255),
static_cast<ubyte>(entry.color.g * 255),
static_cast<ubyte>(entry.color.b * 255),
static_cast<ubyte>(entry.color.a * 255),
};
builder.add(name, std::make_unique<ImageData>(
ImageFormat::RGBA8888, 1, 1, data
));
}
return builder.build(1, true);
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <memory>
#include <map>
#include <string>
#include <glm/vec4.hpp>
class Atlas;
class PaletteAtlasBuilder {
public:
struct Entry {
glm::vec4 color;
};
PaletteAtlasBuilder();
void put(const std::string& name, const glm::vec4& color);
std::unique_ptr<Atlas> build() const;
private:
std::map<std::string, Entry> entries;
};

View file

@ -96,41 +96,56 @@ void Shadows::setQuality(int quality) {
}
void Shadows::setup(Shader& shader, const Weather& weather) {
if (shadows) {
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, 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
));
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", shadowsOpacity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
glActiveTexture(TEXTURE_MAIN);
if (!shadows) {
return;
}
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, 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
));
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", shadowsOpacity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
glActiveTexture(TEXTURE_MAIN);
}
void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function<void(Camera&)> renderShadowPass) {
void Shadows::refresh(
const Camera& camera,
const DrawContext& pctx,
const std::function<void(Camera&)>& renderShadowPass
) {
static int frameid = 0;
if (shadows) {
if (frameid % 2 == 0) {
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass);
} else {
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass);
}
if (!shadows) {
return;
}
if (frameid % 2 == 0) {
generateShadowsMap(
camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass
);
} else {
generateShadowsMap(
camera,
pctx,
*wideShadowMap,
wideShadowCamera,
3.0f,
renderShadowPass
);
}
frameid++;
}
@ -141,7 +156,7 @@ void Shadows::generateShadowsMap(
ShadowMap& shadowMap,
Camera& shadowCamera,
float scale,
std::function<void(Camera&)> renderShadowPass
const std::function<void(Camera&)>& renderShadowPass
) {
auto world = level.getWorld();
const auto& worldInfo = world->getInfo();
@ -162,10 +177,7 @@ void Shadows::generateShadowsMap(
shadowCamera.setAspectRatio(1.0f);
float t = worldInfo.daytime - 0.25f;
if (t < 0.0f) {
t += 1.0f;
}
t = fmod(t, 0.5f);
t = glm::mod(t < 0.0f ? t + 1.0f : t, 0.5f);
float sunCycleStep = 1.0f / 500.0f;
float sunAngle = glm::radians(
@ -192,15 +204,13 @@ void Shadows::generateShadowsMap(
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
{
auto sctx = pctx.sub();
sctx.setDepthTest(true);
sctx.setCullFace(true);
sctx.setViewport({resolution, resolution});
shadowMap.bind();
if (renderShadowPass) {
renderShadowPass(shadowCamera);
}
shadowMap.unbind();
auto sctx = pctx.sub();
sctx.setDepthTest(true);
sctx.setCullFace(true);
sctx.setViewport({resolution, resolution});
shadowMap.bind();
if (renderShadowPass) {
renderShadowPass(shadowCamera);
}
shadowMap.unbind();
}

View file

@ -14,7 +14,7 @@ class DrawContext;
struct EngineSettings;
class ShadowMap;
class Shadows {
class Shadows final {
public:
Shadows(const Level& level);
~Shadows();
@ -24,7 +24,7 @@ public:
void refresh(
const Camera& camera,
const DrawContext& pctx,
std::function<void(Camera&)> renderShadowPass
const std::function<void(Camera&)>& renderShadowPass
);
private:
const Level& level;
@ -41,6 +41,6 @@ private:
ShadowMap& shadowMap,
Camera& shadowCamera,
float scale,
std::function<void(Camera&)> renderShadowPass
const std::function<void(Camera&)>& renderShadowPass
);
};

View file

@ -68,7 +68,7 @@ std::unique_ptr<ImageData> Texture::readData() {
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.get());
glBindTexture(GL_TEXTURE_2D, 0);
return std::make_unique<ImageData>(
ImageFormat::rgba8888, width, height, std::move(data)
ImageFormat::RGBA8888, width, height, std::move(data)
);
}

View file

@ -9,8 +9,8 @@
namespace gl {
inline GLenum to_glenum(ImageFormat imageFormat) {
switch (imageFormat) {
case ImageFormat::rgb888: return GL_RGB;
case ImageFormat::rgba8888: return GL_RGBA;
case ImageFormat::RGB888: return GL_RGB;
case ImageFormat::RGBA8888: return GL_RGBA;
default:
return 0;
}

View file

@ -42,21 +42,22 @@ void BlocksRenderer::vertex(
const glm::vec3& normal,
float emission
) {
vertexBuffer[vertexCount].position = coord;
vertexBuffer[vertexCount].uv = {u,v};
vertexBuffer[vertexCount].normal[0] = static_cast<uint8_t>(normal.r * 127 + 128);
vertexBuffer[vertexCount].normal[1] = static_cast<uint8_t>(normal.g * 127 + 128);
vertexBuffer[vertexCount].normal[2] = static_cast<uint8_t>(normal.b * 127 + 128);
vertexBuffer[vertexCount].normal[3] = static_cast<uint8_t>(emission * 255);
vertexBuffer[vertexCount].color[0] = static_cast<uint8_t>(light.r * 255);
vertexBuffer[vertexCount].color[1] = static_cast<uint8_t>(light.g * 255);
vertexBuffer[vertexCount].color[2] = static_cast<uint8_t>(light.b * 255);
vertexBuffer[vertexCount].color[3] = static_cast<uint8_t>(light.a * 255);
vertexCount++;
vertexBuffer[vertexCount++] = {
coord,
{u, v},
{
static_cast<uint8_t>(light.r * 255),
static_cast<uint8_t>(light.g * 255),
static_cast<uint8_t>(light.b * 255),
static_cast<uint8_t>(light.a * 255),
}, {
static_cast<uint8_t>(normal.x * 127 + 128),
static_cast<uint8_t>(normal.y * 127 + 128),
static_cast<uint8_t>(normal.z * 127 + 128),
static_cast<uint8_t>(emission * 255)
}
};
}
void BlocksRenderer::index(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f) {
@ -440,28 +441,11 @@ void BlocksRenderer::blockCube(
}
}
bool BlocksRenderer::isOpenForLight(int x, int y, int z) const {
blockid_t id = voxelsBuffer->pickBlockId(chunk->x * CHUNK_W + x,
y,
chunk->z * CHUNK_D + z);
if (id == BLOCK_VOID) {
return false;
}
const Block& block = *blockDefsCache[id];
if (block.lightPassing) {
return true;
}
return !id;
}
glm::vec4 BlocksRenderer::pickLight(int x, int y, int z) const {
if (isOpenForLight(x, y, z)) {
light_t light = voxelsBuffer->pickLight(chunk->x * CHUNK_W + x, y,
chunk->z * CHUNK_D + z);
return Lightmap::extractNormalized(light);
} else {
return glm::vec4(0.0f);
}
light_t light = voxelsBuffer->pickLight(
chunk->x * CHUNK_W + x, y, chunk->z * CHUNK_D + z
);
return light ? Lightmap::extractNormalized(light) : glm::vec4(0.0f);
}
glm::vec4 BlocksRenderer::pickLight(const glm::ivec3& coord) const {
@ -700,7 +684,9 @@ SortingMeshData BlocksRenderer::renderTranslucent(
return sortingMesh;
}
void BlocksRenderer::build(const Chunk* chunk, const VoxelsVolume& volume) {
void BlocksRenderer::build(
const Chunk* chunk, const VoxelsRenderVolume& volume
) {
this->chunk = chunk;
this->voxelsBuffer = &volume;
if (voxelsBuffer->pickBlockId(
@ -713,16 +699,17 @@ void BlocksRenderer::build(const Chunk* chunk, const VoxelsVolume& volume) {
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;
beginEnds[variant.drawGroup][0] = i + 1;
}
beginEnds[variant.drawGroup][1] = i;
}
@ -735,7 +722,11 @@ void BlocksRenderer::build(const Chunk* chunk, const VoxelsVolume& volume) {
denseRender = false;
densePass = false;
sortingMesh = renderTranslucent(voxels, beginEnds);
if (hasTranslucent) {
sortingMesh = renderTranslucent(voxels, beginEnds);
} else {
sortingMesh = {};
}
overflow = false;
vertexCount = 0;
@ -781,7 +772,7 @@ ChunkMeshData BlocksRenderer::createMesh() {
}
ChunkMesh BlocksRenderer::render(
const Chunk* chunk, const VoxelsVolume& volume
const Chunk* chunk, const VoxelsRenderVolume& volume
) {
build(chunk, volume);
@ -795,7 +786,7 @@ ChunkMesh BlocksRenderer::render(
IndexBufferData {indexBuffer.get(), indexCount},
IndexBufferData {denseIndexBuffer.get(), denseIndexCount},
}
), std::move(sortingMesh)};
), std::move(sortingMesh), nullptr};
}
size_t BlocksRenderer::getMemoryConsumption() const {

View file

@ -1,10 +1,7 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
#include "voxels/voxel.hpp"
#include "typedefs.hpp"
#include "voxels/voxel.hpp"
#include "voxels/Block.hpp"
#include "voxels/Chunk.hpp"
#include "voxels/VoxelsVolume.hpp"
@ -12,16 +9,39 @@
#include "commons.hpp"
#include "settings.hpp"
#include <memory>
#include <glm/glm.hpp>
template<typename VertexStructure> class Mesh;
class Content;
class Block;
class Chunk;
class Chunks;
class VoxelsVolume;
class ContentGfxCache;
struct UVRegion;
class BlocksRenderer {
class BlocksRenderer final {
public:
BlocksRenderer(
size_t capacity,
const Content& content,
const ContentGfxCache& cache,
const EngineSettings& settings
);
~BlocksRenderer();
void build(const Chunk* chunk, const VoxelsRenderVolume& volume);
ChunkMesh render(
const Chunk* chunk, const VoxelsRenderVolume& volume
);
ChunkMeshData createMesh();
size_t getMemoryConsumption() const;
bool isCancelled() const {
return cancelled;
}
private:
static const glm::vec3 SUN_VECTOR;
const Content& content;
std::unique_ptr<ChunkVertex[]> vertexBuffer;
@ -37,7 +57,7 @@ class BlocksRenderer {
bool densePass = false;
bool denseRender = false;
const Chunk* chunk = nullptr;
const VoxelsVolume* voxelsBuffer = nullptr;
const VoxelsRenderVolume* voxelsBuffer = nullptr;
const Block* const* blockDefsCache;
const ContentGfxCache& cache;
@ -122,11 +142,9 @@ class BlocksRenderer {
bool ao
);
bool isOpenForLight(int x, int y, int z) const;
// 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 {
auto vox = voxelsBuffer->pickBlock(
const auto& vox = voxelsBuffer->pickBlock(
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z
);
if (vox.id == BLOCK_VOID) {
@ -160,24 +178,4 @@ class BlocksRenderer {
void render(const voxel* voxels, const int beginEnds[256][2]);
SortingMeshData renderTranslucent(const voxel* voxels, int beginEnds[256][2]);
public:
BlocksRenderer(
size_t capacity,
const Content& content,
const ContentGfxCache& cache,
const EngineSettings& settings
);
virtual ~BlocksRenderer();
void build(const Chunk* chunk, const VoxelsVolume& volume);
ChunkMesh render(
const Chunk* chunk, const VoxelsVolume& volume
);
ChunkMeshData createMesh();
size_t getMemoryConsumption() const;
bool isCancelled() const {
return cancelled;
}
};

View file

@ -19,6 +19,8 @@ static debug::Logger logger("chunks-render");
size_t ChunksRenderer::visibleChunks = 0;
static constexpr inline size_t MAX_CHUNKS_ENQUEUED_IN_FRAME = 4;
class RendererWorker : public util::Worker<RendererJob, RendererResult> {
BlocksRenderer renderer;
public:
@ -51,11 +53,10 @@ public:
}
};
static util::ObjectsPool<VoxelsVolume> voxelsVolumesPool {};
static inline const int VOXELS_BUFFER_PADDING = 2;
static util::ObjectsPool<VoxelsRenderVolume> voxelsVolumesPool {};
ChunksRenderer::ChunksRenderer(
const Level* level,
const Level& level,
const Chunks& chunks,
const Assets& assets,
const Frustum& frustum,
@ -69,25 +70,27 @@ ChunksRenderer::ChunksRenderer(
threadPool(
"chunks-render-pool",
[&]() {
return std::make_shared<RendererWorker>(
*level, cache, settings
return std::make_unique<RendererWorker>(
level, cache, settings
);
},
[&](RendererResult& result) {
if (!result.cancelled) {
auto meshData = std::move(result.meshData);
meshes[result.key] = ChunkMesh {
std::make_unique<Mesh<ChunkVertex>>(meshData.mesh),
std::move(meshData.sortingMesh)};
}
inwork.erase(result.key);
[&](RendererResult&& result) {
if (!result.cancelled) {
auto meshData = std::move(result.meshData);
auto chunk = std::make_unique<Mesh<ChunkVertex>>(meshData.mesh);
meshes[result.key] = ChunkMesh {
std::move(chunk),
std::move(meshData.sortingMesh),
nullptr};
}
inwork.erase(result.key);
},
settings.graphics.chunkMaxRenderers.get()
) {
threadPool.setStopOnFail(false);
renderer = std::make_unique<BlocksRenderer>(
settings.graphics.chunkMaxVertices.get(),
level->content, cache, settings
level.content, cache, settings
);
logger.info() << "created " << threadPool.getWorkersCount() << " workers";
logger.info() << "memory consumption is "
@ -103,14 +106,10 @@ ChunksRenderer::ChunksRenderer(
ChunksRenderer::~ChunksRenderer() = default;
std::shared_ptr<VoxelsVolume> ChunksRenderer::prepareVoxelsVolume(
std::shared_ptr<VoxelsRenderVolume> ChunksRenderer::prepareVoxelsVolume(
const Chunk& chunk
) {
auto voxelsBuffer = voxelsVolumesPool.create(
CHUNK_W + VOXELS_BUFFER_PADDING * 2,
CHUNK_H,
CHUNK_D + VOXELS_BUFFER_PADDING * 2
);
auto voxelsBuffer = voxelsVolumesPool.create();
voxelsBuffer->setPosition(
chunk.x * CHUNK_W - VOXELS_BUFFER_PADDING, 0,
chunk.z * CHUNK_D - VOXELS_BUFFER_PADDING
@ -122,28 +121,27 @@ std::shared_ptr<VoxelsVolume> ChunksRenderer::prepareVoxelsVolume(
}
const Mesh<ChunkVertex>* ChunksRenderer::render(
const std::shared_ptr<Chunk>& chunk, bool important
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
) {
glm::ivec2 key(chunk->x, chunk->z);
chunk->flags.modified = false;
if (important) {
ChunkMesh mesh {};
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
auto mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] =
ChunkMesh {std::move(mesh.mesh), std::move(mesh.sortingMeshData)};
mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] = std::move(mesh);
return meshes[key].mesh.get();
}
if (inwork.find(key) != inwork.end()) {
if (inwork.find(key) != inwork.end() ||
((inwork.size() >= threadPool.getWorkersCount() ||
enqueuedInFrame >= MAX_CHUNKS_ENQUEUED_IN_FRAME) &&
lowPriority)) {
return nullptr;
}
enqueuedInFrame++;
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
inwork[key] = true;
chunks.getVoxels(
*voxelsBuffer, settings.graphics.backlight.get(), chunk->top + 1
);
threadPool.enqueueJob({chunk, std::move(voxelsBuffer)});
inwork[key] = true;
return nullptr;
}
@ -161,20 +159,21 @@ void ChunksRenderer::clear() {
}
const Mesh<ChunkVertex>* ChunksRenderer::getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important
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);
return render(chunk, important, lowPriority);
}
if (chunk->flags.modified && chunk->flags.lighted) {
render(chunk, important);
render(chunk, important, lowPriority);
}
return found->second.mesh.get();
}
void ChunksRenderer::update() {
threadPool.update();
threadPool.pullResults();
enqueuedInFrame = 0;
}
const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
@ -200,7 +199,11 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
(chunk->z + 0.5f) * CHUNK_D
)
);
auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f);
auto mesh = getOrRender(
chunk,
distance < CHUNK_W * 1.5f * 10.0f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
);
if (mesh == nullptr) {
return nullptr;
}
@ -238,7 +241,7 @@ void ChunksRenderer::drawShadowsPass(
continue;
}
glm::ivec2 pos {chunk->x, chunk->z};
const auto& found = meshes.find({chunk->x, chunk->z});
const auto& found = meshes.find(pos);
if (found == meshes.end()) {
continue;
}
@ -247,11 +250,11 @@ void ChunksRenderer::drawShadowsPass(
pos.x * CHUNK_W + 0.5f, 0.5f, pos.y * CHUNK_D + 0.5f
);
glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D);
glm::vec3 min(pos.x * CHUNK_W, chunk->bottom, pos.y * CHUNK_D);
glm::vec3 max(
chunk->x * CHUNK_W + CHUNK_W,
pos.x * CHUNK_W + CHUNK_W,
chunk->top,
chunk->z * CHUNK_D + CHUNK_D
pos.y * CHUNK_D + CHUNK_D
);
if (!frustum.isBoxVisible(min, max)) {
@ -305,17 +308,17 @@ void ChunksRenderer::drawChunks(
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) {
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),
(coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2);
visibleChunks++;
if (mesh == nullptr) {
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),
(coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2);
visibleChunks++;
}
}

View file

@ -21,7 +21,6 @@ class Chunks;
class Frustum;
class BlocksRenderer;
class ContentGfxCache;
class VoxelsVolume;
struct EngineSettings;
struct ChunksSortEntry {
@ -41,7 +40,7 @@ struct RendererResult {
struct RendererJob {
std::shared_ptr<Chunk> chunk;
std::shared_ptr<VoxelsVolume> volume;
std::shared_ptr<VoxelsRenderVolume> volume;
};
class ChunksRenderer {
@ -58,10 +57,12 @@ class ChunksRenderer {
const Mesh<ChunkVertex>* retrieveChunk(
size_t index, const Camera& camera, bool culling
);
std::shared_ptr<VoxelsVolume> prepareVoxelsVolume(const Chunk& chunk);
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
size_t enqueuedInFrame = 0;
public:
ChunksRenderer(
const Level* level,
const Level& level,
const Chunks& chunks,
const Assets& assets,
const Frustum& frustum,
@ -71,13 +72,13 @@ public:
virtual ~ChunksRenderer();
const Mesh<ChunkVertex>* render(
const std::shared_ptr<Chunk>& chunk, bool important
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void unload(const Chunk* chunk);
void clear();
const Mesh<ChunkVertex>* getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void drawShadowsPass(

View file

@ -0,0 +1,337 @@
#define GLM_ENABLE_EXPERIMENTAL
#include "CloudsRenderer.hpp"
#include "assets/Assets.hpp"
#include "coders/imageio.hpp"
#include "debug/Logger.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "io/io.hpp"
#include "lighting/Lightmap.hpp"
#include "maths/FastNoiseLite.h"
#include "maths/FrustumCulling.hpp"
#include "maths/voxmaths.hpp"
#include "window/Camera.hpp"
#include "world/Weather.hpp"
#include <glm/ext.hpp>
#include <glm/gtx/norm.hpp>
#include <glm/gtc/matrix_transform.hpp>
static debug::Logger logger("clouds-render");
static inline constexpr int MAP_SIZE = 512;
static inline constexpr float CLOUD_VOXEL_SCALE = 8.0f;
static inline constexpr float CLOUDS_SPEED = 4.0f;
class CloudsMap {
public:
glm::ivec3 size;
const bool* voxels;
CloudsMap() = default;
CloudsMap(const glm::ivec3& size, const bool* voxels)
: size(size), voxels(voxels) {
}
bool isOpen(int x, int y, int z) const {
if (x >= 0 && x < size.x && y >= 0 && y < size.y && z >= 0 && z < size.z) {
return !voxels[vox_index(x, y, z, size.x, size.z)];
}
return true;
}
};
class VolumeRenderer final {
public:
VolumeRenderer(size_t capacity)
: vertices(std::make_unique<ChunkVertex[]>(capacity)),
indexBuffer(std::make_unique<uint32_t[]>(capacity * 6)),
capacity(capacity) {
}
~VolumeRenderer() = default;
void build(CloudsMap volumeMap) {
this->map = std::move(volumeMap);
overflow = false;
offset = 0;
indexOffset = 0;
indexCount = 0;
int end = map.size.x * map.size.y * map.size.z;
for (int i = 0; i < end; i++) {
int x = i % map.size.x;
int y = i / (map.size.z * map.size.x);
int z = (i / map.size.x) % map.size.z;
const auto& vox = map.voxels[i];
if (!vox) {
continue;
}
cube({x, y, z});
if (overflow) {
return;
}
}
}
MeshData<ChunkVertex> createMesh() const {
return MeshData(
util::Buffer(vertices.get(), offset),
std::vector<util::Buffer<uint32_t>> {
util::Buffer(indexBuffer.get(), indexCount),
},
util::Buffer(
ChunkVertex::ATTRIBUTES,
sizeof(ChunkVertex::ATTRIBUTES) / sizeof(VertexAttribute)
)
);
}
private:
std::unique_ptr<ChunkVertex[]> vertices;
std::unique_ptr<uint32_t[]> indexBuffer;
size_t capacity;
uint32_t indexOffset = 0;
uint32_t indexCount = 0;
uint32_t offset = 0;
bool overflow = false;
CloudsMap map {};
void vertex(
const glm::vec3& coord,
const glm::vec3& normal
) {
auto& vert = vertices[offset++];
vert.position = coord;
vert.uv = {};
vert.normal = {
static_cast<uint8_t>(normal.r * 127 + 128),
static_cast<uint8_t>(normal.g * 127 + 128),
static_cast<uint8_t>(normal.b * 127 + 128),
255
};
vert.color = {
0, 0, 0, static_cast<uint8_t>((coord.y / 8.0f * 0.25f + 0.75f) * 255)
};
}
void face(
const glm::vec3& coord,
const glm::vec3& X,
const glm::vec3& Y,
const glm::vec3& Z
) {
if (!isOpen(coord + Z)) {
return;
}
if (offset + 4 >= capacity) {
overflow = true;
return;
}
float s = 0.5f;
vertex(coord + (-X - Y + Z) * s, Z);
vertex(coord + ( X - Y + Z) * s, Z);
vertex(coord + ( X + Y + Z) * s, Z);
vertex(coord + (-X + Y + Z) * s, Z);
const uint32_t indices[] {0, 1, 2, 0, 2, 3};
for (size_t i = 0; i < 6; i++) {
indexBuffer[indexCount++] = indexOffset + indices[i];
}
indexOffset += 4;
}
bool isOpen(const glm::ivec3& pos) const {
return pos.y < 0 || pos.y >= map.size.y ||
map.isOpen(pos.x, pos.y, pos.z);
}
void cube(const glm::ivec3& coord) {
const glm::ivec3 X(1, 0, 0);
const glm::ivec3 Y(0, 1, 0);
const glm::ivec3 Z(0, 0, 1);
face(coord, X, Y, Z);
face(coord, -X, Y, -Z);
face(coord, X, -Z, Y);
face(coord, X, Z, -Y);
face(coord, -Z, Y, X);
face(coord, Z, Y, -X);
}
};
static void generate_heightmap(
float* heightmap, fnl_state& state, int w, int dd, int layerid
) {
float pi2 = glm::two_pi<float>();
for (int lz = 0; lz < dd; lz++) {
for (int lx = 0; lx < w; lx++) {
float x = glm::sin(lx / static_cast<float>(w) * pi2) * w / pi2;
float y = -glm::cos(lx / static_cast<float>(w) * pi2) * w / pi2;
float z = lz;
float s = 1.5f;
auto n = fnlGetNoise3D(&state, x * s * 0.7, y * s, z * s * 0.7);
n += fnlGetNoise3D(&state, x * s + fnlGetNoise2D(&state, x * s * 4 + 2, z * s * 4) * 2.0f, y * s, z * 3.0f) * 0.5f;
n += fnlGetNoise3D(&state, x * s * 2, y * s * 2, z * s * 2) * 0.25f;
n += fnlGetNoise3D(&state, x * s * 4, y * s * 4, z * s * 4) * 0.125f * 2;
n += fnlGetNoise3D(&state, x * s * 8, y * s * 8, z * s * 8) * 0.125f * 0.5f * 2;
n += fnlGetNoise3D(&state, x * s * 16, y * s * 16, z * s * 16) * 0.125f * 0.25f * 3;
n = glm::max(0.0f, n);
n += -0.1f - layerid * 0.3f;
heightmap[lz * w + lx] = n;
}
}
}
static void sample_voxels(
bool* voxels,
const float* heightmap,
int height,
int segmentSize,
int segmentX,
int segmentZ
) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < segmentSize; z++) {
for (int x = 0; x < segmentSize; x++) {
int gx = (segmentX * segmentSize) + x;
int gz = (segmentZ * segmentSize) + z;
float n = heightmap[gz * MAP_SIZE + gx];
if (gz < MAP_SIZE / 2) {
float t = gz / static_cast<float>(MAP_SIZE / 2);
n = n * t +
heightmap[(MAP_SIZE + gz) * MAP_SIZE + gx] * (1.0f - t);
}
bool solid = y <= n * height && y >= (0.5f - n * 0.5f) * height;
voxels[vox_index(x, y, z, segmentSize, segmentSize)] = solid;
}
}
}
}
CloudsRenderer::CloudsRenderer() {
VolumeRenderer volumeRenderer(1024 * 512);
const int diameter = 4;
const int segmentSize = MAP_SIZE / diameter;
const int w = MAP_SIZE;
const int h = 8;
const int d = MAP_SIZE;
const int dd = d * 1.5;
auto heightmap = std::make_unique<float[]>(w * dd);
for (int layerid = 0; layerid < 2; layerid++) {
auto& layer = layers[layerid];
layer.diameter = diameter;
layer.segmentSize = segmentSize;
fnl_state state = fnlCreateState();
state.seed = 5265 + layerid * 3521;
generate_heightmap(heightmap.get(), state, w, dd, layerid);
bool voxels[segmentSize * h * segmentSize];
for (int sz = 0; sz < diameter; sz++) {
for (int sx = 0; sx < diameter; sx++) {
sample_voxels(voxels, heightmap.get(), h, segmentSize, sx, sz);
CloudsMap map({segmentSize, h, segmentSize}, voxels);
volumeRenderer.build(map);
layer.meshes.push_back(std::make_unique<Mesh<ChunkVertex>>(
volumeRenderer.createMesh()
));
}
}
}
}
CloudsRenderer::~CloudsRenderer() = default;
void CloudsRenderer::draw(
Layer& layer,
Frustum& frustum,
Shader& shader,
const Camera& camera,
float timer,
int layerId
) {
float scale = CLOUD_VOXEL_SCALE;
int totalDiameter = layer.segmentSize * scale;
int gcellX = floordiv(camera.position.x, totalDiameter);
int gcellZ = floordiv(camera.position.z, totalDiameter);
float speed = CLOUDS_SPEED;
float speedX = glm::sin(layerId * 0.3f + 0.4f) * speed / (layerId + 1);
float speedZ = -glm::cos(layerId * 0.3f + 0.4f) * speed / (layerId + 1);
int radius = 4;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
int lcellX = gcellX - floordiv(glm::floor(timer * speedX), totalDiameter);
int lcellZ = gcellZ - floordiv(glm::floor(timer * speedZ), totalDiameter);
glm::vec3 position(
-128 * scale + (x + gcellX) * layer.segmentSize * scale +
glm::mod(timer * speedX, static_cast<float>(totalDiameter)),
250 + layerId * 200,
-128 * scale + (z + gcellZ) * layer.segmentSize * scale +
glm::mod(timer * speedZ, static_cast<float>(totalDiameter))
);
if (glm::distance2(
glm::vec2(
position.x + totalDiameter * 0.5f,
position.z + totalDiameter * 0.5f
),
glm::vec2(camera.position.x, camera.position.z)
) > 4e6) {
continue;
}
if (!frustum.isBoxVisible(position, position + glm::vec3(layer.segmentSize * scale))) {
continue;
}
auto matrix = glm::mat4(1.0f);
matrix = glm::translate(matrix, position);
matrix = glm::scale(matrix, glm::vec3(scale, scale, scale));
shader.uniformMatrix("u_model", matrix);
int lx = (x + radius + lcellX) % layer.diameter;
int lz = (z + radius + lcellZ) % layer.diameter;
if (lx < 0) lx += layer.diameter;
if (lz < 0) lz += layer.diameter;
layer.meshes[lz * layer.diameter + lx]->draw();
}
}
}
void CloudsRenderer::draw(
Shader& shader,
const Weather& weather,
float timer,
float fogFactor,
const Camera& camera,
int quality
) {
Frustum frustum;
frustum.update(camera.getProjView());
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);
for (int i = 0; i < std::min<int>(quality, layers.size()); i++) {
draw(layers[i], frustum, shader, camera, timer, i);
}
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "commons.hpp"
#include <array>
#include <memory>
#include <vector>
class Camera;
class Shader;
struct Weather;
class Frustum;
class CloudsRenderer final {
public:
CloudsRenderer();
~CloudsRenderer();
void draw(
Shader& shader,
const Weather& weather,
float timer,
float fogFactor,
const Camera& camera,
int quality
);
private:
struct Layer {
int diameter;
int segmentSize;
std::vector<std::unique_ptr<Mesh<ChunkVertex>>> meshes;
};
std::array<Layer, 2> layers;
void draw(
Layer& layer,
Frustum& frustum,
Shader& shader,
const Camera& camera,
float timer,
int layerId
);
};

View file

@ -21,9 +21,7 @@ HandsRenderer::HandsRenderer(
skeleton(std::move(skeleton)) {
}
void HandsRenderer::renderHands(
const Camera& camera, float delta
) {
void HandsRenderer::render(const Camera& camera) {
auto& skeleton = *this->skeleton;
const auto& config = *skeleton.config;

View file

@ -18,7 +18,7 @@ public:
std::shared_ptr<rigging::Skeleton> skeleton
);
void renderHands(const Camera& camera, float delta);
void render(const Camera& camera);
private:
const Assets& assets;
ModelBatch& modelBatch;

View file

@ -16,7 +16,7 @@ MainBatch::MainBatch(size_t capacity)
const ubyte pixels[] = {
255, 255, 255, 255,
};
ImageData image(ImageFormat::rgba8888, 1, 1, pixels);
ImageData image(ImageFormat::RGBA8888, 1, 1, pixels);
blank = Texture::from(&image);
}

View file

@ -19,12 +19,12 @@ ParticlesRenderer::ParticlesRenderer(
const Assets& assets,
const Level& level,
const Chunks& chunks,
const GraphicsSettings* settings
const GraphicsSettings& settings
)
: chunks(chunks),
assets(assets),
settings(settings),
batch(std::make_unique<MainBatch>(4096)) {
batch(std::make_unique<MainBatch>(settings.particlesBatchVertices.get())) {
}
ParticlesRenderer::~ParticlesRenderer() = default;
@ -46,12 +46,7 @@ static inline void update_particle(
particle.lifetime -= delta;
}
void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
const auto& right = camera.right;
const auto& up = camera.up;
bool backlight = settings->backlight.get();
void ParticlesRenderer::updateParticles(float delta) {
std::vector<const Texture*> unusedTextures;
for (auto& [texture, vec] : particles) {
@ -59,8 +54,6 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
unusedTextures.push_back(texture);
continue;
}
batch->setTexture(texture);
visibleParticles += vec.size();
auto iter = vec.begin();
@ -87,59 +80,6 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
}
}
update_particle(particle, delta, chunks);
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;
glm::vec4 light(1, 1, 1, 0);
if (preset.lighting) {
light = MainBatch::sampleLight(
particle.position,
chunks,
backlight
);
auto size = glm::max(glm::vec3(0.5f), preset.size * scale);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
light = glm::max(
light,
MainBatch::sampleLight(
particle.position -
size * glm::vec3(x, y, z),
chunks,
backlight
)
);
}
}
}
light *= 0.9f + (particle.random % 100) * 0.001f;
}
glm::vec3 localRight = right;
glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up;
float angle = particle.angle;
if (glm::abs(angle) >= 0.005f) {
glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f);
glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f);
localRight = right * rotatedRight.x + localUp * rotatedRight.y +
camera.front * rotatedRight.z;
localUp = right * rotatedUp.x + localUp * rotatedUp.y +
camera.front * rotatedUp.z;
}
batch->quad(
particle.position,
localRight,
localUp,
-camera.front,
preset.size * scale,
light,
glm::vec3(1.0f),
particle.region,
preset.lighting ? 0.0f : 1.0f
);
if (particle.lifetime <= 0.0f) {
iter = vec.erase(iter);
emitter.refCount--;
@ -148,19 +88,85 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
}
}
}
batch->flush();
for (const auto& texture : unusedTextures) {
particles.erase(texture);
}
}
void ParticlesRenderer::render(const Camera& camera, float delta) {
batch->begin();
aliveEmitters = emitters.size();
visibleParticles = 0;
static inline glm::vec4 calc_lights(
Particle& particle,
ParticlesPreset& preset,
bool backlight,
float scale,
const Chunks& chunks
) {
auto light = MainBatch::sampleLight(
particle.position,
chunks,
backlight
);
auto size = glm::max(glm::vec3(0.5f), preset.size * scale);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
light = glm::max(
light,
MainBatch::sampleLight(
particle.position - size * glm::vec3(x, y, z),
chunks,
backlight
)
);
}
}
}
light *= 0.9f + (particle.random % 100) * 0.001f;
return light;
}
renderParticles(camera, delta);
void ParticlesRenderer::renderParticle(
Particle& particle, const Camera& camera, bool backlight
) {
const auto& right = camera.right;
const auto& up = camera.up;
auto& emitter = *particle.emitter;
auto& preset = emitter.preset;
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;
glm::vec4 light(1, 1, 1, 0);
if (preset.lighting) {
light = calc_lights(particle, preset, backlight, scale, chunks);
}
glm::vec3 localRight = right;
glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up;
float angle = particle.angle;
if (glm::abs(angle) >= 0.005f) {
glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f);
glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f);
localRight = right * rotatedRight.x + localUp * rotatedRight.y +
camera.front * rotatedRight.z;
localUp = right * rotatedUp.x + localUp * rotatedUp.y +
camera.front * rotatedUp.z;
}
batch->quad(
particle.position,
localRight,
localUp,
-camera.front,
preset.size * scale,
light,
glm::vec3(1.0f),
particle.region,
preset.lighting ? 0.0f : 1.0f
);
}
void ParticlesRenderer::update(const Camera& camera, float delta) {
updateParticles(delta);
auto iter = emitters.begin();
while (iter != emitters.end()) {
@ -178,6 +184,33 @@ void ParticlesRenderer::render(const Camera& camera, float delta) {
}
}
void ParticlesRenderer::render(const Camera& camera) {
aliveEmitters = emitters.size();
visibleParticles = 0;
bool backlight = settings.backlight.get();
batch->begin();
for (auto& [texture, vec] : particles) {
batch->setTexture(texture);
auto iter = vec.begin();
while (iter != vec.end()) {
auto& particle = *iter;
renderParticle(particle, camera, backlight);
if (particle.lifetime <= 0.0f) {
iter = vec.erase(iter);
particle.emitter->refCount--;
} else {
iter++;
}
}
}
batch->flush();
}
Emitter* ParticlesRenderer::getEmitter(u64id_t id) const {
const auto& found = emitters.find(id);
if (found == emitters.end()) {

View file

@ -18,24 +18,28 @@ struct GraphicsSettings;
class ParticlesRenderer {
const Chunks& chunks;
const Assets& assets;
const GraphicsSettings* settings;
const GraphicsSettings& settings;
std::unordered_map<const Texture*, std::vector<Particle>> particles;
std::unique_ptr<MainBatch> batch;
std::unordered_map<u64id_t, std::unique_ptr<Emitter>> emitters;
u64id_t nextEmitter = 1;
void renderParticles(const Camera& camera, float delta);
void renderParticle(
Particle& particle, const Camera& camera, bool backlight
);
void updateParticles(float delta);
public:
ParticlesRenderer(
const Assets& assets,
const Level& level,
const Chunks& chunks,
const GraphicsSettings* settings
const GraphicsSettings& settings
);
~ParticlesRenderer();
void render(const Camera& camera, float delta);
void update(const Camera& camera, float delta);
void render(const Camera& camera);
u64id_t add(std::unique_ptr<Emitter> emitter);

View file

@ -90,11 +90,13 @@ static UVRegion calc_uv(
return {u1, v1, u1 + m * scale, v1 + FACE_SIZE.y * scale};
}
void PrecipitationRenderer::render(
const Camera& camera, float delta, const WeatherPreset& weather
) {
void PrecipitationRenderer::update(float delta) {
timer += delta;
}
void PrecipitationRenderer::render(
const Camera& camera, const WeatherPreset& weather
) {
const int radius = 6;
const int depth = 12;
@ -149,7 +151,7 @@ void PrecipitationRenderer::render(
glm::cross(glm::vec3(0, 1, 0), face.right),
FACE_SIZE,
light_at(chunks, pos.x, y, pos.z),
glm::vec3(3.0f),
glm::vec3(1.0f),
calc_uv(pos, face.right, timer, weather)
);
}

View file

@ -27,5 +27,7 @@ public:
~PrecipitationRenderer();
void render(const Camera& camera, float delta, const WeatherPreset& weather);
void update(float delta);
void render(const Camera& camera, const WeatherPreset& weather);
};

View file

@ -28,7 +28,7 @@ Skybox::Skybox(uint size, Shader& shader)
shader(shader),
batch3d(std::make_unique<Batch3D>(4096))
{
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::rgb888);
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
uint fboid;
glGenFramebuffers(1, &fboid);
@ -170,7 +170,7 @@ void Skybox::draw(
drawStars(angle, opacity);
}
void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) {
void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) {
frameid++;
float dayTime = t;
DrawContext ctx = pctx.sub();
@ -201,11 +201,13 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality)
shader.uniform1i("u_quality", quality);
shader.uniform1f("u_mie", mie);
shader.uniform3f("u_tint", tint);
shader.uniform3f("u_hightlight", hightlight);
shader.uniform1f("u_fog", mie - 1.0f);
shader.uniform3f("u_lightDir", lightDir);
shader.uniform1f("u_dayTime", dayTime);
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) >= 0.01) {
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) {
for (uint face = 0; face < 6; face++) {
refreshFace(face, cubemap);
}
@ -215,6 +217,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality)
}
prevMie = mie;
prevT = t;
prevHighlight = hightlight;
cubemap->unbind();
glActiveTexture(GL_TEXTURE0);

View file

@ -49,6 +49,7 @@ class Skybox {
float prevMie = -1.0f;
float prevT = -1.0f;
float sunAltitude = 45.0f;
glm::vec3 prevHighlight {1.0f};
glm::mat4 rotation;
void drawStars(float angle, float opacity);
@ -68,7 +69,7 @@ public:
float fog
);
void refresh(const DrawContext& pctx, float t, float mie, uint quality);
void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality);
void bind() const;
void unbind() const;
bool isReady() const {

View file

@ -1,12 +1,5 @@
#include "WorldRenderer.hpp"
#include <assert.h>
#include <algorithm>
#include <glm/ext.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
#include "assets/Assets.hpp"
#include "assets/assets_util.hpp"
#include "content/Content.hpp"
@ -18,7 +11,6 @@
#include "items/ItemDef.hpp"
#include "items/ItemStack.hpp"
#include "logic/PlayerController.hpp"
#include "logic/scripting/scripting_hud.hpp"
#include "maths/FrustumCulling.hpp"
#include "maths/voxmaths.hpp"
#include "objects/Entities.hpp"
@ -58,6 +50,13 @@
#include "Skybox.hpp"
#include "Emitter.hpp"
#include "TextNote.hpp"
#include "CloudsRenderer.hpp"
#include <assert.h>
#include <algorithm>
#include <glm/ext.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
using namespace advanced_pipeline;
@ -81,44 +80,44 @@ WorldRenderer::WorldRenderer(
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
)),
chunksRenderer(std::make_unique<ChunksRenderer>(
&level,
level,
*player.chunks,
assets,
*frustumCulling,
frontend.getContentGfxCache(),
engine.getSettings()
)),
precipitation(std::make_unique<PrecipitationRenderer>(
assets, level, *player.chunks, &engine.getSettings().graphics
)),
particles(std::make_unique<ParticlesRenderer>(
assets, level, *player.chunks, &engine.getSettings().graphics
assets, level, *player.chunks, engine.getSettings().graphics
)),
texts(std::make_unique<TextsRenderer>(*batch3d, assets, *frustumCulling)),
blockWraps(
std::make_unique<BlockWrapsRenderer>(assets, level, *player.chunks)
),
precipitation(std::make_unique<PrecipitationRenderer>(
assets, level, *player.chunks, &engine.getSettings().graphics
)) {
) {
auto& settings = engine.getSettings();
level.events->listen(
LevelEventType::CHUNK_HIDDEN,
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
);
auto assets = engine.getAssets();
skybox = std::make_unique<Skybox>(
settings.graphics.skyboxResolution.get(),
assets->require<Shader>("skybox_gen")
assets.require<Shader>("skybox_gen")
);
const auto& content = level.content;
skeletons = std::make_unique<NamedSkeletons>();
const auto& skeletonConfig = assets->require<rigging::SkeletonConfig>(
const auto& skeletonConfig = assets.require<rigging::SkeletonConfig>(
content.getDefaults()["hand-skeleton"].asString()
);
hands = std::make_unique<HandsRenderer>(
*assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig)
assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig)
);
shadowMapping = std::make_unique<Shadows>(level);
debugLines = std::make_unique<DebugLinesRenderer>(level);
cloudsRenderer = std::make_unique<CloudsRenderer>();
}
WorldRenderer::~WorldRenderer() = default;
@ -127,6 +126,7 @@ static void setup_weather(Shader& shader, const Weather& weather) {
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
shader.uniform3f("u_minSkyLight", weather.minSkyLight());
}
static void setup_camera(Shader& shader, const Camera& camera) {
@ -179,8 +179,6 @@ void WorldRenderer::renderOpaque(
const DrawContext& ctx,
const Camera& camera,
const EngineSettings& settings,
float delta,
bool pause,
bool hudVisible
) {
texts->render(ctx, camera, settings, hudVisible, false);
@ -203,15 +201,14 @@ void WorldRenderer::renderOpaque(
assets,
*modelBatch,
culling ? frustumCulling.get() : nullptr,
delta,
pause,
player.currentCamera.get() == player.fpCamera.get() ? player.getEntity()
: 0
);
modelBatch->render();
particles->render(camera, delta * !pause);
particles->render(camera);
auto& shader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& linesShader = assets.require<Shader>("lines");
setupWorldShader(shader, camera, settings, fogFactor);
@ -219,12 +216,16 @@ void WorldRenderer::renderOpaque(
chunksRenderer->drawChunks(camera, shader);
blockWraps->draw(ctx, player);
if (hudVisible) {
renderLines(camera, linesShader, ctx);
int cloudsQuality = settings.graphics.cloudsQuality.get();
if (cloudsQuality > 0) {
setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsRenderer->draw(
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
}
if (!pause) {
scripting::on_frontend_render();
if (hudVisible) {
renderLines(camera, linesShader, ctx);
}
skybox->unbind();
}
@ -275,45 +276,17 @@ void WorldRenderer::renderLines(
}
}
void WorldRenderer::renderFrame(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
bool pause,
float uiDelta,
PostProcessing& postProcessing
) {
// TODO: REFACTOR WHOLE RENDER ENGINE
void WorldRenderer::refreshSettings(Shader** shaders) {
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
auto projView = camera.getProjView();
float delta = uiDelta * !pause;
timer += delta;
weather.update(delta);
auto world = level.getWorld();
const auto& vp = pctx.getViewport();
camera.setAspectRatio(vp.x / static_cast<float>(vp.y));
auto& mainShader = assets.require<Shader>("main");
auto& entityShader = assets.require<Shader>("entity");
auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader, &entityShader, &translucentShader, &deferredShader
};
gbufferPipeline = settings.graphics.advancedRender.get();
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadowsQuality != 0,
settings.graphics.ssao.get() && gbufferPipeline
graphics.ssao.get() && gbufferPipeline
};
if (
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
@ -325,11 +298,50 @@ void WorldRenderer::renderFrame(
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
for (auto shader : affectedShaders) {
shader->recompile(defines);
for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines);
}
prevCTShaderSettings = currentSettings;
}
}
void WorldRenderer::update(const Camera& camera, float delta) {
timer += delta;
weather.update(delta);
precipitation->update(delta);
particles->update(camera, delta);
}
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));
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();
@ -337,7 +349,15 @@ void WorldRenderer::renderFrame(
clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
skybox->refresh(pctx, worldInfo.daytime, mie, 4);
float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh(
pctx,
worldInfo.daytime,
mie,
weather.skyTint(),
weather.highlight * random,
4
);
chunksRenderer->update();
@ -356,7 +376,7 @@ void WorldRenderer::renderFrame(
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
@ -375,7 +395,7 @@ void WorldRenderer::renderFrame(
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
} else {
postProcessing.getFramebuffer()->bind();
ctx.setFramebuffer(postProcessing.getFramebuffer());
}
// Background sky plane
@ -391,15 +411,14 @@ void WorldRenderer::renderFrame(
linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush();
skybox->bind();
// Translucent blocks
{
auto sctx = ctx.sub();
sctx.setCullFace(true);
skybox->bind();
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
skybox->unbind();
}
// Weather effects
@ -415,9 +434,10 @@ void WorldRenderer::renderFrame(
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, pause ? 0.0f : delta, *weather);
precipitation->render(camera, *weather);
}
}
skybox->unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -434,7 +454,7 @@ void WorldRenderer::renderFrame(
hudcam.setFov(0.9f);
hudcam.position = {};
hands->renderHands(camera, delta);
hands->render(camera);
display::clearDepth();
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
@ -454,43 +474,45 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
int y = std::floor(player.currentCamera->position.y);
int z = std::floor(player.currentCamera->position.z);
auto block = player.chunks->get(x, y, z);
if (block && block->id) {
const auto& def = level.content.getIndices()->blocks.require(block->id);
if (def.overlayTexture.empty()) {
return;
}
auto textureRegion = util::get_texture_region(
assets, def.overlayTexture, "blocks:notfound"
);
DrawContext ctx = wctx.sub();
ctx.setDepthTest(false);
ctx.setCullFace(false);
auto& shader = assets.require<Shader>("ui3d");
shader.use();
batch3d->begin();
shader.uniformMatrix("u_projview", glm::mat4(1.0f));
shader.uniformMatrix("u_apply", glm::mat4(1.0f));
auto light = player.chunks->getLight(x, y, z);
float s = Lightmap::extract(light, 3) / 15.0f;
glm::vec4 tint(
glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s),
glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s),
glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s),
1.0f
);
batch3d->texture(textureRegion.texture);
batch3d->sprite(
glm::vec3(),
glm::vec3(0, 1, 0),
glm::vec3(1, 0, 0),
2,
2,
textureRegion.region,
tint
);
batch3d->flush();
if (block == nullptr || block->id == BLOCK_AIR || block->id == BLOCK_VOID) {
return;
}
const auto& def = level.content.getIndices()->blocks.require(block->id);
if (def.overlayTexture.empty()) {
return;
}
auto textureRegion = util::get_texture_region(
assets, def.overlayTexture, "blocks:notfound"
);
DrawContext ctx = wctx.sub();
ctx.setDepthTest(false);
ctx.setCullFace(false);
auto& shader = assets.require<Shader>("ui3d");
shader.use();
batch3d->begin();
shader.uniformMatrix("u_projview", glm::mat4(1.0f));
shader.uniformMatrix("u_apply", glm::mat4(1.0f));
auto light = player.chunks->getLight(x, y, z);
float s = Lightmap::extract(light, 3) / 15.0f;
glm::vec4 tint(
glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s),
glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s),
glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s),
1.0f
);
batch3d->texture(textureRegion.texture);
batch3d->sprite(
glm::vec3(),
glm::vec3(0, 1, 0),
glm::vec3(1, 0, 0),
2,
2,
textureRegion.region,
tint
);
batch3d->flush();
}
void WorldRenderer::clear() {

View file

@ -1,40 +1,40 @@
#pragma once
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include "commons.hpp"
#include "typedefs.hpp"
#include "presets/WeatherPreset.hpp"
#include "world/Weather.hpp"
#include "window/Camera.hpp"
class Level;
class Player;
class Camera;
class Batch3D;
class LineBatch;
class ChunksRenderer;
class ParticlesRenderer;
class BlockWrapsRenderer;
class PrecipitationRenderer;
class HandsRenderer;
class NamedSkeletons;
class TextsRenderer;
class Shader;
class Frustum;
class Engine;
class LevelFrontend;
class Skybox;
class PostProcessing;
class DrawContext;
class ModelBatch;
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
class Assets;
class Shadows;
class GBuffer;
class Batch3D;
class BlockWrapsRenderer;
class ChunksRenderer;
class DebugLinesRenderer;
class DrawContext;
class Engine;
class Frustum;
class HandsRenderer;
class Level;
class LevelFrontend;
class LineBatch;
class ModelBatch;
class NamedSkeletons;
class ParticlesRenderer;
class Player;
class PostProcessing;
class PrecipitationRenderer;
class Shader;
class Shadows;
class Skybox;
class TextsRenderer;
class CloudsRenderer;
struct EngineSettings;
struct CompileTimeShaderSettings {
@ -57,6 +57,8 @@ class WorldRenderer {
std::unique_ptr<Skybox> skybox;
std::unique_ptr<Shadows> shadowMapping;
std::unique_ptr<DebugLinesRenderer> debugLines;
std::unique_ptr<PrecipitationRenderer> precipitation;
std::unique_ptr<CloudsRenderer> cloudsRenderer;
Weather weather {};
float timer = 0.0f;
@ -93,15 +95,14 @@ class WorldRenderer {
const DrawContext& context,
const Camera& camera,
const EngineSettings& settings,
float delta,
bool pause,
bool hudVisible
);
void refreshSettings(Shader** shaders);
public:
std::unique_ptr<ParticlesRenderer> particles;
std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<PrecipitationRenderer> precipitation;
std::unique_ptr<NamedSkeletons> skeletons;
static bool showChunkBorders;
@ -110,12 +111,12 @@ public:
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer();
void update(const Camera& camera, float delta);
void renderFrame(
const DrawContext& context,
Camera& camera,
bool hudVisible,
bool pause,
float delta,
PostProcessing& postProcessing
);

View file

@ -1,14 +1,15 @@
#pragma once
#include "constants.hpp"
#include "graphics/core/MeshData.hpp"
#include "util/Buffer.hpp"
#include <vector>
#include <array>
#include <memory>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include "graphics/core/MeshData.hpp"
#include "util/Buffer.hpp"
/// @brief Chunk mesh vertex format
struct ChunkVertex {
glm::vec3 position;
@ -49,5 +50,14 @@ struct ChunkMeshData {
struct ChunkMesh {
std::unique_ptr<Mesh<ChunkVertex>> mesh;
SortingMeshData sortingMeshData;
std::unique_ptr<Mesh<ChunkVertex> > sortedMesh = nullptr;
std::unique_ptr<Mesh<ChunkVertex> > sortedMesh;
};
inline constexpr int VOXELS_BUFFER_PADDING = 2;
template<int, int, int> class StaticVoxelsVolume;
using VoxelsRenderVolume = StaticVoxelsVolume<
CHUNK_W + VOXELS_BUFFER_PADDING * 2,
CHUNK_H,
CHUNK_D + VOXELS_BUFFER_PADDING * 2>;

View file

@ -1,13 +1,14 @@
#include "GUI.hpp"
#include <algorithm>
#include <iostream>
#include <utility>
#include "assets/Assets.hpp"
#include "debug/Logger.hpp"
#include "elements/Label.hpp"
#include "elements/Menu.hpp"
#include "elements/Panel.hpp"
#include "elements/Frame.hpp"
#include "elements/UINode.hpp"
#include "engine/Engine.hpp"
#include "frontend/UiDocument.hpp"
@ -26,13 +27,15 @@
#include <algorithm>
#include <utility>
static debug::Logger logger("gui");
using namespace gui;
GUI::GUI(Engine& engine)
: engine(engine),
input(engine.getInput()),
batch2D(std::make_unique<Batch2D>(1024)),
container(std::make_shared<Container>(*this, glm::vec2(1000))) {
container(std::make_shared<Frame>(*this, CORE_MAIN, "")) {
container->setId("root");
uicamera =
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y);
@ -59,10 +62,12 @@ GUI::GUI(Engine& engine)
rootDocument = std::make_unique<UiDocument>(
"core:root",
uidocscript {},
UiDocScript {},
std::dynamic_pointer_cast<gui::UINode>(container),
nullptr
);
addFrame(container);
activeFrame = container;
}
GUI::~GUI() = default;
@ -117,12 +122,19 @@ void GUI::updateTooltip(float delta) {
}
/// @brief Mouse related input and logic handling
void GUI::actMouse(float delta, const CursorState& cursor) {
void GUI::actMouse(Frame& frame, float delta, const CursorState& cursor) {
float mouseDelta = glm::length(cursor.delta);
doubleClicked = false;
doubleClickTimer += delta + mouseDelta * 0.1f;
auto hover = container->getAt(cursor.pos);
auto cursorPos = cursor.pos;
if (cursorLocator) {
cursorPos = cursorLocator();
if (activeFrame) {
cursorPos.y += activeFrame->getPos().y;
}
}
auto hover = frame.getAt(cursorPos);
if (this->hover && this->hover != hover) {
this->hover->setMouseEnter(false);
}
@ -157,13 +169,13 @@ void GUI::actMouse(float delta, const CursorState& cursor) {
}
for (auto it = mouseOver.begin(); it != mouseOver.end(); ) {
auto node = it->lock();
if (node) {
if (node->isInside(cursor.pos)) {
auto mouseOverNode = it->lock();
if (mouseOverNode) {
if (mouseOverNode->isInside(cursorPos)) {
++it;
continue;
}
node->setMouseOver(false);
mouseOverNode->setMouseOver(false);
}
it = mouseOver.erase(it);
}
@ -172,10 +184,10 @@ void GUI::actMouse(float delta, const CursorState& cursor) {
if (pressed == nullptr && this->hover) {
pressed = hover;
if (doubleClickTimer < doubleClickDelay) {
pressed->doubleClick(cursor.pos.x, cursor.pos.y);
pressed->doubleClick(cursorPos.x, cursorPos.y);
doubleClicked = true;
} else {
pressed->click(cursor.pos.x, cursor.pos.y);
pressed->click(cursorPos.x, cursorPos.y);
}
doubleClickTimer = 0.0f;
if (focus && focus != pressed) {
@ -192,7 +204,7 @@ void GUI::actMouse(float delta, const CursorState& cursor) {
focus = nullptr;
}
} else if (!input.clicked(Mousecode::BUTTON_1) && pressed) {
pressed->mouseRelease(cursor.pos.x, cursor.pos.y);
pressed->mouseRelease(cursorPos.x, cursorPos.y);
pressed = nullptr;
}
@ -232,14 +244,16 @@ void GUI::actFocused() {
void GUI::act(float delta, const glm::uvec2& vp) {
container->setSize(vp);
container->act(delta);
for (auto& pair : frames) {
pair.second->act(delta);
}
auto prevfocus = focus;
updateTooltip(delta);
const auto& cursor = input.getCursor();
if (!cursor.locked) {
actMouse(delta, cursor);
if (!cursor.locked && activeFrame) {
actMouse(*activeFrame, delta, cursor);
} else {
if (hover) {
hover->setMouseEnter(false);
@ -262,7 +276,7 @@ void GUI::postAct() {
}
}
void GUI::draw(const DrawContext& pctx, const Assets& assets) {
void GUI::draw(const DrawContext& pctx, Assets& assets) {
auto ctx = pctx.sub(batch2D.get());
auto& viewport = ctx.getViewport();
@ -284,7 +298,10 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) {
uishader->uniformMatrix("u_projview", uicamera->getProjView());
batch2D->begin();
container->draw(ctx, assets);
for (auto& [outputTexture, frame] : frames) {
frame->updateOutput(assets);
frame->draw(ctx, assets);
}
if (hover) {
engine.getWindow().setCursor(hover->getCursor());
@ -337,6 +354,30 @@ void GUI::add(std::shared_ptr<UINode> node) {
container->add(std::move(node));
}
void GUI::addFrame(std::shared_ptr<Frame> frame) {
const auto& id = frame->getFrameId();
frames[id] = std::move(frame);
}
void GUI::setActiveFrame(const std::string& id, vec2supplier cursorLocator) {
this->cursorLocator = std::move(cursorLocator);
if (id.empty()) {
activeFrame = nullptr;
return;
}
const auto& found = frames.find(id);
if (found == frames.end()) {
logger.error() << "attempted to make non-existing frame '" << id
<< "' as active";
return;
}
activeFrame = found->second;
}
std::shared_ptr<gui::Frame> GUI::getActiveFrame() const {
return activeFrame;
}
void GUI::remove(UINode* node) noexcept {
container->remove(node);
}

View file

@ -61,13 +61,14 @@ namespace gui {
class UINode;
class Container;
class Menu;
class Frame;
/// @brief The main UI controller
class GUI {
Engine& engine;
Input& input;
std::unique_ptr<Batch2D> batch2D;
std::shared_ptr<Container> container;
std::shared_ptr<Frame> container;
std::shared_ptr<UINode> hover;
std::shared_ptr<UINode> pressed;
std::shared_ptr<UINode> focus;
@ -75,11 +76,14 @@ namespace gui {
std::shared_ptr<UiDocument> rootDocument;
std::unique_ptr<FontStylesScheme> syntaxColorScheme;
std::unordered_map<std::string, std::shared_ptr<UINode>> storage;
std::shared_ptr<Frame> activeFrame;
std::unique_ptr<Camera> uicamera;
std::shared_ptr<Menu> menu;
std::queue<runnable> postRunnables;
std::vector<std::weak_ptr<UINode>> mouseOver;
std::unordered_map<std::string, std::shared_ptr<Frame>> frames;
vec2supplier cursorLocator;
float tooltipTimer = 0.0f;
float doubleClickTimer = 0.0f;
@ -87,11 +91,12 @@ namespace gui {
bool doubleClicked = false;
bool debug = false;
void actMouse(float delta, const CursorState& cursor);
void actMouse(Frame& frame, float delta, const CursorState& cursor);
void actFocused();
void updateTooltip(float delta);
void resetTooltip();
public:
static inline std::string CORE_MAIN = "core:main";
static constexpr int CONTEXT_MENU_ZINDEX = 999;
GUI(Engine& engine);
@ -115,7 +120,7 @@ namespace gui {
/// @brief Draw all visible elements on main container
/// @param pctx parent graphics context
/// @param assets active assets storage
void draw(const DrawContext& pctx, const Assets& assets);
void draw(const DrawContext& pctx, Assets& assets);
void postAct();
@ -123,6 +128,14 @@ namespace gui {
/// @param node UI element
void add(std::shared_ptr<UINode> node);
void addFrame(std::shared_ptr<Frame> frame);
void setActiveFrame(
const std::string& id, vec2supplier cursorLocator = nullptr
);
std::shared_ptr<Frame> getActiveFrame() const;
/// @brief Remove node from the main container
void remove(UINode* node) noexcept;

View file

@ -0,0 +1,64 @@
#include "Frame.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Batch2D.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Texture.hpp"
#include "frontend/UiDocument.hpp"
#include "window/Window.hpp"
#include "../GUI.hpp"
using namespace gui;
static inline constexpr int MAX_TEXTURE_SIZE = 2048;
gui::Frame::Frame(GUI& gui, std::string id, std::string outputTexture)
: Container(gui, {}),
fbo(nullptr),
outputTexture(std::move(outputTexture)) {
frameId = std::move(id);
}
gui::Frame::~Frame() = default;
void gui::Frame::draw(const DrawContext& pctx, const Assets& assets) {
if (outputTexture.empty()) {
Container::draw(pctx, assets);
return;
}
if (fbo == nullptr) {
return;
}
glm::ivec2 size = getSize();
if (size.x <= 0 || size.y <= 0 ||
size.x > MAX_TEXTURE_SIZE || size.y > MAX_TEXTURE_SIZE) {
return;
}
// ui uses flipped camera with matrix based on main viewport
setPos({0, pctx.getViewport().y - size.y});
auto ctx = pctx.sub();
ctx.setFramebuffer(fbo.get());
display::clear();
Container::draw(ctx, assets);
ctx.getBatch2D()->flush();
}
void gui::Frame::updateOutput(Assets& assets) {
if (fbo && (fbo->getWidth() != size.x || fbo->getHeight() != size.y)) {
fbo->resize(size.x, size.y);
assets.store(fbo->getSharedTexture(), outputTexture);
} else if (fbo == nullptr) {
fbo = std::make_unique<Framebuffer>(size.x, size.y, true);
assets.store(fbo->getSharedTexture(), outputTexture);
}
}
const std::string& gui::Frame::getOutputTexture() const {
return outputTexture;
}
const std::string& gui::Frame::getFrameId() const {
return frameId;
}

View file

@ -0,0 +1,26 @@
#pragma once
#include "Container.hpp"
class Framebuffer;
class UiDocument;
namespace gui {
class Frame final : public Container {
public:
Frame(GUI& gui, std::string id, std::string outputTexture);
virtual ~Frame();
void draw(const DrawContext& pctx, const Assets& assets) override;
void updateOutput(Assets& assets);
const std::string& getOutputTexture() const;
const std::string& getFrameId() const;
private:
std::string frameId;
std::unique_ptr<Framebuffer> fbo;
std::string outputTexture;
};
}

View file

@ -78,6 +78,7 @@ void guiutil::alert(
}));
menu.addPage("<alert>", panel, true);
menu.setPage("<alert>");
gui.setActiveFrame(GUI::CORE_MAIN);
}
void guiutil::confirm(
@ -155,6 +156,7 @@ void guiutil::confirm(
panel->refresh();
menu->addPage("<confirm>", container, true);
menu->setPage("<confirm>");
gui.setActiveFrame(GUI::CORE_MAIN);
}
void guiutil::confirm_with_memo(
@ -201,4 +203,5 @@ void guiutil::confirm_with_memo(
panel->refresh();
menu->addPage("<confirm>", panel, true);
menu->setPage("<confirm>");
gui.setActiveFrame(GUI::CORE_MAIN);
}

View file

@ -652,7 +652,7 @@ static std::shared_ptr<UINode> read_canvas(
size = element.attr("size").asVec2();
}
auto image =
std::make_shared<Canvas>(reader.getGUI(), ImageFormat::rgba8888, size);
std::make_shared<Canvas>(reader.getGUI(), ImageFormat::RGBA8888, size);
read_uinode(reader, element, *image);
return image;
}

View file

@ -77,11 +77,13 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.add("chunk-max-vertices", &settings.graphics.chunkMaxVertices);
builder.add("chunk-max-vertices-dense", &settings.graphics.chunkMaxVerticesDense);
builder.add("chunk-max-renderers", &settings.graphics.chunkMaxRenderers);
builder.add("particles-batch-vertices", &settings.graphics.particlesBatchVertices);
builder.add("advanced-render", &settings.graphics.advancedRender);
builder.add("ssao", &settings.graphics.ssao);
builder.add("shadows-quality", &settings.graphics.shadowsQuality);
builder.add("dense-render-distance", &settings.graphics.denseRenderDistance);
builder.add("soft-lighting", &settings.graphics.softLighting);
builder.add("clouds-quality", &settings.graphics.cloudsQuality);
builder.addSection("ui");
builder.add("language", &settings.ui.language);

View file

@ -259,7 +259,7 @@ void BlocksController::onBlockInteraction(
}
void BlocksController::listenBlockInteraction(
const on_block_interaction& callback
const OnBlockInteraction& callback
) {
blockInteractionCallbacks.push_back(callback);
}

View file

@ -20,7 +20,7 @@ class ContentIndices;
enum class BlockInteraction { step, destruction, placing };
/// @brief Player argument is nullable
using on_block_interaction = std::function<
using OnBlockInteraction = std::function<
void(Player*, const glm::ivec3&, const Block&, BlockInteraction)>;
/// BlocksController manages block updates and data (inventories, metadata)
@ -32,7 +32,7 @@ class BlocksController {
util::Clock blocksTickClock;
util::Clock worldTickClock;
FastRandom random {};
std::vector<on_block_interaction> blockInteractionCallbacks;
std::vector<OnBlockInteraction> blockInteractionCallbacks;
uint64_t randomTickId = 0;
public:
BlocksController(const Level& level, Lighting* lighting);
@ -61,5 +61,5 @@ public:
);
/// @brief Add block interaction callback
void listenBlockInteraction(const on_block_interaction& callback);
void listenBlockInteraction(const OnBlockInteraction& callback);
};

View file

@ -133,9 +133,8 @@ static void check_world(const EnginePaths& paths, const io::path& folder) {
}
static const Content* load_world_content(Engine& engine, const io::path& folder) {
auto& paths = engine.getPaths();
const auto& paths = engine.getPaths();
auto& contentControl = engine.getContentControl();
paths.setCurrentWorldFolder(folder);
check_world(paths, folder);
call(engine, [&contentControl]() {
@ -221,9 +220,10 @@ static void confirm(
}
void EngineController::openWorld(const std::string& name, bool confirmConvert) {
const auto& paths = engine.getPaths();
auto& paths = engine.getPaths();
auto& debugSettings = engine.getSettings().debug;
auto folder = paths.getWorldsFolder() / name;
paths.setCurrentWorldFolder(folder);
auto content = load_world_content(engine, folder);
auto worldFiles = std::make_shared<WorldFiles>(folder, debugSettings);

View file

@ -4,6 +4,7 @@
#include "debug/Logger.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "world/files/WorldFiles.hpp"
#include "maths/voxmaths.hpp"
#include "objects/Entities.hpp"
@ -22,9 +23,10 @@
static debug::Logger logger("level-control");
LevelController::LevelController(
Engine* engine, std::unique_ptr<Level> levelPtr, Player* clientPlayer
Engine& engine, std::unique_ptr<Level> levelPtr, Player* clientPlayer
)
: settings(engine->getSettings()),
: engine(engine),
settings(engine.getSettings()),
level(std::move(levelPtr)),
chunks(std::make_unique<ChunksController>(*level)),
playerTickClock(20, 3) {
@ -147,6 +149,7 @@ void LevelController::saveWorld() {
void LevelController::onWorldQuit() {
scripting::on_world_quit();
engine.getPaths().setCurrentWorldFolder("");
}
Level* LevelController::getLevel() {

View file

@ -14,6 +14,7 @@ struct EngineSettings;
/// @brief LevelController manages other controllers
class LevelController {
Engine& engine;
EngineSettings& settings;
std::unique_ptr<Level> level;
// Sub-controllers
@ -24,7 +25,7 @@ class LevelController {
public:
CallbacksSet<> preQuitCallbacks;
LevelController(Engine* engine, std::unique_ptr<Level> level, Player* clientPlayer);
LevelController(Engine& engine, std::unique_ptr<Level> level, Player* clientPlayer);
/// @param delta time elapsed since the last update
/// @param pause is world and player simulation paused

View file

@ -3,6 +3,7 @@
#include <algorithm>
#include <cmath>
#include <glm/gtc/constants.hpp>
#include "BlocksController.hpp"
#include "content/Content.hpp"
@ -28,17 +29,19 @@
#include "window/input.hpp"
#include "world/Level.hpp"
const float INTERACTION_RELOAD = 0.160f;
const float STEPS_SPEED = 2.2f;
const float CAM_SHAKE_OFFSET = 0.0075f;
const float CAM_SHAKE_OFFSET_Y = 0.031f;
const float CAM_SHAKE_SPEED = STEPS_SPEED;
const float CAM_SHAKE_DELTA_K = 10.0f;
const float ZOOM_SPEED = 16.0f;
const float CROUCH_ZOOM = 0.9f;
const float RUN_ZOOM = 1.1f;
const float C_ZOOM = 0.1f;
const float CROUCH_SHIFT_Y = -0.2f;
namespace {
const float INTERACTION_RELOAD = 0.160f;
const float STEPS_SPEED = 2.2f;
const float CAM_SHAKE_OFFSET = 0.0075f;
const float CAM_SHAKE_OFFSET_Y = 0.031f;
const float CAM_SHAKE_SPEED = STEPS_SPEED;
const float CAM_SHAKE_DELTA_K = 10.0f;
const float ZOOM_SPEED = 16.0f;
const float CROUCH_ZOOM = 0.9f;
const float RUN_ZOOM = 1.1f;
const float C_ZOOM = 0.1f;
const float CROUCH_SHIFT_Y = -0.2f;
}
CameraControl::CameraControl(Player& player, const CameraSettings& settings)
: player(player),
@ -249,17 +252,18 @@ void PlayerController::onFootstep(const Hitbox& hitbox) {
}
void PlayerController::updateFootsteps(float delta) {
const float pi = glm::pi<float>();
auto hitbox = player.getHitbox();
if (hitbox && hitbox->grounded) {
const glm::vec3& vel = hitbox->velocity;
float f = glm::length(glm::vec2(vel.x, vel.z));
stepsTimer += delta * f * STEPS_SPEED;
if (stepsTimer >= M_PI) {
stepsTimer = fmod(stepsTimer, M_PI);
if (stepsTimer >= pi) {
stepsTimer = fmod(stepsTimer, pi);
onFootstep(*hitbox);
}
} else {
stepsTimer = M_PI;
stepsTimer = pi;
}
}

View file

@ -36,6 +36,9 @@ static rigging::Skeleton* get_skeleton(lua::State* L) {
static int l_get_model(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
if (skeleton->config == nullptr) {
return 0;
}
auto& rigConfig = *skeleton->config;
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
const auto& modelOverride = skeleton->modelOverrides[index];
@ -96,6 +99,9 @@ static int l_set_texture(lua::State* L) {
static int l_index(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
if (skeleton->config == nullptr) {
return 0;
}
if (auto bone = skeleton->config->find(lua::require_string(L, 2))) {
return lua::pushinteger(L, bone->getIndex());
}

View file

@ -16,6 +16,7 @@
#include "graphics/ui/elements/TrackBar.hpp"
#include "graphics/ui/elements/InlineFrame.hpp"
#include "graphics/ui/elements/ModelViewer.hpp"
#include "graphics/ui/elements/Frame.hpp"
#include "graphics/ui/gui_util.hpp"
#include "graphics/ui/markdown.hpp"
#include "graphics/core/Font.hpp"
@ -1149,6 +1150,62 @@ static int l_set_syntax_styles(lua::State* L) {
return 0;
}
static int l_create_frame(lua::State* L) {
if (engine->isHeadless()) {
return 0;
}
std::string id = lua::require_string(L, 1);
std::string outputTexture = lua::require_string(L, 2);
auto size = lua::tovec2(L, 3);
auto& gui = engine->getGUI();
auto frame = std::make_shared<gui::Frame>(gui, id, outputTexture);
frame->setSize(std::move(size));
auto& assets = *engine->getAssets();
auto document = std::make_shared<UiDocument>(
id, UiDocScript {}, frame, nullptr
);
assets.store(document, id);
gui.addFrame(std::move(frame));
return 0;
}
static int l_set_active_frame(lua::State* L) {
if (engine->isHeadless()) {
return 0;
}
std::string id = lua::require_string(L, 1);
vec2supplier cursorLocator = nullptr;
if (lua::isfunction(L, 2)) {
auto lambda = lua::create_lambda(L);
cursorLocator = [lambda]() -> glm::vec2 {
auto table = lambda({});
if (!table.isList()) {
throw std::runtime_error("invalid value returned from locator");
}
glm::vec2 pos {};
table.at(0).get(pos.x);
table.at(1).get(pos.y);
return pos;
};
}
auto& gui = engine->getGUI();
gui.setActiveFrame(id, std::move(cursorLocator));
return 0;
}
static int l_get_active_frame(lua::State* L) {
if (engine->isHeadless()) {
return 0;
}
auto& gui = engine->getGUI();
auto frame = gui.getActiveFrame();
if (frame == nullptr) {
return 0;
}
return lua::pushstring(L, frame->getId());
}
const luaL_Reg guilib[] = {
{"get_viewport", lua::wrap<l_gui_getviewport>},
{"getattr", lua::wrap<l_gui_getattr>},
@ -1162,6 +1219,9 @@ const luaL_Reg guilib[] = {
{"alert", lua::wrap<l_gui_alert>},
{"load_document", lua::wrap<l_gui_load_document>},
{"set_syntax_styles", lua::wrap<l_set_syntax_styles>},
{"create_frame", lua::wrap<l_create_frame>},
{"set_active_frame", lua::wrap<l_set_active_frame>},
{"get_active_frame", lua::wrap<l_get_active_frame>},
{"__reindex", lua::wrap<l_gui_reindex>},
{nullptr, nullptr}
};

View file

@ -401,7 +401,7 @@ static int l_meta_meta_call(lua::State* L) {
return newuserdata<LuaCanvas>(
L,
nullptr,
std::make_shared<ImageData>(ImageFormat::rgba8888, size.x, size.y)
std::make_shared<ImageData>(ImageFormat::RGBA8888, size.x, size.y)
);
}

View file

@ -56,7 +56,7 @@ static int l_dump(lua::State* L) {
io::path file = require_string(L, 2);
uint w = heightmap->getWidth();
uint h = heightmap->getHeight();
ImageData image(ImageFormat::rgb888, w, h);
ImageData image(ImageFormat::RGB888, w, h);
auto heights = heightmap->getValues();
auto raster = image.getData();
for (uint y = 0; y < h; y++) {

View file

@ -768,7 +768,7 @@ void scripting::load_layout_script(
const std::string& prefix,
const io::path& file,
const std::string& fileName,
uidocscript& script
UiDocScript& script
) {
int env = *senv;

View file

@ -28,7 +28,7 @@ struct ItemFuncsSet;
struct ItemFuncNamesCache;
struct WorldFuncsSet;
struct UserComponent;
struct uidocscript;
struct UiDocScript;
class BlocksController;
class LevelController;
class Entity;
@ -231,7 +231,7 @@ namespace scripting {
const std::string& prefix,
const io::path& file,
const std::string& fileName,
uidocscript& script
UiDocScript& script
);
/// @brief Finalize lua state. Using scripting after will lead to Lua panic

View file

@ -147,7 +147,7 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
if (map.has(COMP_TRANSFORM)) {
transform.deserialize(map[COMP_TRANSFORM]);
}
if (skeleton == nullptr) {
if (skeleton == nullptr || skeleton->config == nullptr) {
return;
}
std::string skeletonName = skeleton->config->getName();
@ -341,6 +341,16 @@ void Entities::update(float delta) {
}
updatePhysics(delta);
scripting::on_entities_physics_update(delta);
auto view = registry->view<Transform, rigging::Skeleton>();
for (auto [entity, transform, skeleton] : view.each()) {
if (transform.dirty) {
transform.refresh();
}
if (skeleton.interpolation.isEnabled()) {
skeleton.interpolation.updateTimer(delta);
}
}
}
static void debug_render_skeleton(
@ -397,6 +407,9 @@ void Entities::renderDebug(
ctx.setLineWidth(2);
for (auto [entity, transform, skeleton] : view.each()) {
auto config = skeleton.config;
if (config == nullptr) {
continue;
}
const auto& pos = transform.pos;
const auto& size = transform.size;
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
@ -412,8 +425,6 @@ void Entities::render(
const Assets& assets,
ModelBatch& batch,
const Frustum* frustum,
float delta,
bool pause,
entityid_t fpsEntity
) {
auto view = registry->view<EntityId, Transform, rigging::Skeleton>();
@ -421,12 +432,6 @@ void Entities::render(
if (eid.uid == fpsEntity) {
continue;
}
if (transform.dirty) {
transform.refresh();
}
if (skeleton.interpolation.isEnabled()) {
skeleton.interpolation.updateTimer(delta);
}
const auto& pos = transform.pos;
const auto& size = transform.size;
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
@ -434,9 +439,11 @@ void Entities::render(
}
const auto* rigConfig = skeleton.config;
rigConfig->render(
assets, batch, skeleton, transform.rot, pos, size
);
if (rigConfig) {
rigConfig->render(
assets, batch, skeleton, transform.rot, pos, size
);
}
}
}

View file

@ -69,8 +69,6 @@ public:
const Assets& assets,
ModelBatch& batch,
const Frustum* frustum,
float delta,
bool pause,
entityid_t fpsEntity
);

View file

@ -64,7 +64,7 @@ dv::value Entity::serialize() const {
root[COMP_RIGIDBODY] =
rigidbody.serialize(def.save.body.velocity, def.save.body.settings);
if (skeleton != nullptr) {
if (skeleton != nullptr && skeleton->config != nullptr) {
if (skeleton->config->getName() != def.skeletonName) {
root["skeleton-name"] = skeleton->config->getName();
}

View file

@ -23,6 +23,9 @@ dv::value WeatherPreset::serialize() const {
root["fog_opacity"] = fogOpacity;
root["fog_dencity"] = fogDencity;
root["fog_curve"] = fogCurve;
root["sky_tint"] = dv::to_value(skyTint);
root["clouds_tint"] = dv::to_value(cloudsTint);
root["min_sky_light"] = dv::to_value(minSkyLight);
root["clouds"] = clouds;
root["thunder_rate"] = thunderRate;
@ -51,6 +54,9 @@ void WeatherPreset::deserialize(const dv::value& src) {
src.at("fog_opacity").get(fogOpacity);
src.at("fog_dencity").get(fogDencity);
src.at("fog_curve").get(fogCurve);
dv::get_vec(src, "sky_tint", skyTint);
dv::get_vec(src, "clouds_tint", cloudsTint);
dv::get_vec(src, "min_sky_light", minSkyLight);
src.at("clouds").get(clouds);
src.at("thunder_rate").get(thunderRate);
}

View file

@ -60,6 +60,15 @@ struct WeatherPreset : Serializable {
/// @brief Weather effects intensity
float intensity = 0.0f;
/// @brief Skybox color multiplier
glm::vec3 skyTint {1.0f, 1.0f, 1.0f};
/// @brief Clouds color multiplier
glm::vec3 cloudsTint {1.0f, 1.0f, 1.0f};
/// @brief Minimal sky-emitted lights (moon light)
glm::vec3 minSkyLight {0.2f, 0.25f, 0.33f};
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
};

View file

@ -81,6 +81,8 @@ struct GraphicsSettings {
IntegerSetting chunkMaxVerticesDense {800'000, 0, 8'000'000};
/// @brief Limit of chunk renderers count
IntegerSetting chunkMaxRenderers {6, -4, 32};
/// @brief Particles renderer vertices buffer capacity
IntegerSetting particlesBatchVertices {4'096, 0, 1'000'000};
/// @brief Advanced render pipeline
FlagSetting advancedRender {true};
/// @brief Screen space ambient occlusion quality
@ -91,6 +93,8 @@ struct GraphicsSettings {
IntegerSetting denseRenderDistance {56, 0, 10'000};
/// @brief Soft lighting for blocks
FlagSetting softLighting {true};
/// @brief Clouds quality level
IntegerSetting cloudsQuality {2, 0, 2};
};
struct PathfindingSettings {

View file

@ -50,6 +50,10 @@ namespace util {
size_t countTotal() const {
return objects.size();
}
size_t countFree() const {
return freeObjects.size();
}
private:
std::vector<std::unique_ptr<void, AlignedDeleter>> objects;
std::queue<void*> freeObjects;

View file

@ -43,7 +43,7 @@ namespace util {
std::condition_variable jobsMutexCondition;
std::mutex jobsMutex;
std::vector<std::unique_lock<std::mutex>> workersBlocked;
consumer<R&> resultConsumer;
consumer<R&&> resultConsumer;
consumer<T&> onJobFailed = nullptr;
runnable onComplete = nullptr;
std::atomic<int> busyWorkers = 0;
@ -54,7 +54,7 @@ namespace util {
bool standaloneResults = true;
bool stopOnFail = true;
void threadLoop(int index, std::shared_ptr<Worker<T, R>> worker) {
void threadLoop(int index, std::unique_ptr<Worker<T, R>> worker) {
std::condition_variable variable;
std::mutex mutex;
bool locked = false;
@ -78,7 +78,7 @@ namespace util {
{
std::lock_guard<std::mutex> lock(resultsMutex);
results.push(ThreadPoolResult<T, R> {
job, variable, index, locked, result});
job, variable, index, locked, std::move(result)});
if (!standaloneResults) {
locked = true;
}
@ -117,8 +117,8 @@ namespace util {
/// unlimited, -2 is half of auto count, -4 is quarter.
ThreadPool(
std::string name,
supplier<std::shared_ptr<Worker<T, R>>> workersSupplier,
consumer<R&> resultConsumer,
supplier<std::unique_ptr<Worker<T, R>>> workersSupplier,
consumer<R&&> resultConsumer,
int maxWorkers=UNLIMITED
)
: logger(std::move(name)), resultConsumer(resultConsumer) {
@ -184,22 +184,28 @@ namespace util {
}
void update() override {
pullResults();
}
size_t pullResults(size_t maxResults = -1) {
if (!working) {
return;
return 0;
}
if (failed) {
throw std::runtime_error("some job failed");
}
bool complete = false;
size_t resultsProcessed = 0;
{
std::lock_guard<std::mutex> lock(resultsMutex);
while (!results.empty()) {
ThreadPoolResult<T, R> entry = results.front();
while (!results.empty() && resultsProcessed < maxResults) {
ThreadPoolResult<T, R> entry = std::move(results.front());
results.pop();
++resultsProcessed;
try {
resultConsumer(entry.entry);
resultConsumer(std::move(entry.entry));
} catch (std::exception& err) {
logger.error() << err.what();
if (onJobFailed) {
@ -219,7 +225,7 @@ namespace util {
}
}
if (onComplete && busyWorkers == 0) {
if (onComplete && busyWorkers == 0 && results.empty()) {
std::lock_guard<std::mutex> jobsLock(jobsMutex);
if (jobs.empty()) {
onComplete();
@ -249,9 +255,10 @@ namespace util {
if (complete) {
terminate();
}
return resultsProcessed;
}
void enqueueJob(T job) {
void enqueueJob(T&& job) {
{
std::lock_guard<std::mutex> lock(jobsMutex);
jobs.push(std::move(job));

View file

@ -5,6 +5,7 @@
using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
using std::chrono::microseconds;
using std::chrono::nanoseconds;
timeutil::Timer::Timer() {
start = high_resolution_clock::now();
@ -14,6 +15,11 @@ int64_t timeutil::Timer::stop() {
.count();
}
int64_t timeutil::Timer::stopNs() {
return duration_cast<nanoseconds>(high_resolution_clock::now() - start)
.count();
}
timeutil::ScopeLogTimer::ScopeLogTimer(long long id) : scopeid_(id) {
}

View file

@ -10,6 +10,7 @@ namespace timeutil {
public:
Timer();
int64_t stop();
int64_t stopNs();
};
/**

View file

@ -128,7 +128,7 @@ VC_ENUM_METADATA(BlockModelType)
{"custom", BlockModelType::CUSTOM},
VC_ENUM_END
enum class CullingMode {
enum class CullingMode : uint8_t {
DEFAULT,
OPTIONAL,
DISABLED,

View file

@ -336,26 +336,103 @@ bool Chunks::putChunk(const std::shared_ptr<Chunk>& chunk) {
return false;
}
// reduce nesting on next modification
// 25.06.2024: not now
// 11.11.2024: not now
// 12.12.2025: not now
void Chunks::getVoxels(VoxelsVolume& volume, bool backlight, int top) const {
voxel* voxels = volume.getVoxels();
light_t* lights = volume.getLights();
int x = volume.getX();
int y = volume.getY();
int z = volume.getZ();
static void fill_with_void(
voxel* voxels,
light_t* lights,
const glm::ivec3& pos,
const glm::ivec3& size,
int cx,
int cz
) {
for (int ly = pos.y; ly < pos.y + size.y; ly++) {
for (int lz = std::max(pos.z, cz * CHUNK_D);
lz < std::min(pos.z + size.z, (cz + 1) * CHUNK_D);
lz++) {
for (int lx = std::max(pos.x, cx * CHUNK_W);
lx < std::min(pos.x + size.x, (cx + 1) * CHUNK_W);
lx++) {
uint idx = vox_index(
lx - pos.x, ly - pos.y, lz - pos.z, size.x, size.z
);
voxels[idx].id = BLOCK_VOID;
lights[idx] = 0;
}
}
}
}
int w = volume.getW();
int h = std::min<int>(volume.getH(), top);
int d = volume.getD();
static inline light_t apply_backlight(light_t light) {
return Lightmap::combine(
std::min(15, Lightmap::extract(light, 0) + 1),
std::min(15, Lightmap::extract(light, 1) + 1),
std::min(15, Lightmap::extract(light, 2) + 1),
std::min(15, static_cast<int>(Lightmap::extract(light, 3)))
);
}
int scx = floordiv<CHUNK_W>(x);
int scz = floordiv<CHUNK_D>(z);
// ugly
static inline void sample_chunk(
const decltype(ContentIndices::blocks)& defs,
const Chunk& chunk,
voxel* voxels,
light_t* lights,
const glm::ivec3& pos,
const glm::ivec3& size,
int cx,
int cz,
bool backlight
) {
const auto cvoxels = chunk.voxels;
const auto clights = chunk.lightmap ? chunk.lightmap->getLights() : nullptr;
for (int ly = pos.y; ly < pos.y + size.y; ly++) {
for (int lz = std::max(pos.z, cz * CHUNK_D);
lz < std::min(pos.z + size.z, (cz + 1) * CHUNK_D);
lz++) {
for (int lx = std::max(pos.x, cx * CHUNK_W);
lx < std::min(pos.x + size.x, (cx + 1) * CHUNK_W);
lx++) {
uint vidx = vox_index(
lx - pos.x, ly - pos.y, lz - pos.z, size.x, size.z
);
uint cidx = vox_index(
lx - cx * CHUNK_W,
ly,
lz - cz * CHUNK_D,
CHUNK_W,
CHUNK_D
);
auto& vox = voxels[vidx];
vox = cvoxels[cidx];
light_t light = clights ? clights[cidx]
: Lightmap::SUN_LIGHT_ONLY;
// todo: move to the BlocksRenderer
if (backlight) {
const auto block = defs.get(vox.id);
if (block && block->lightPassing) {
light = apply_backlight(light);
}
}
lights[vidx] = light;
}
}
}
}
int ecx = floordiv<CHUNK_W>(x + w);
int ecz = floordiv<CHUNK_D>(z + d);
void Chunks::getVoxels(
voxel* voxels,
light_t* lights,
const glm::ivec3& pos,
const glm::ivec3& size,
bool backlight,
int top
) const {
int h = std::min<int>(size.y, top);
int scx = floordiv<CHUNK_W>(pos.x);
int scz = floordiv<CHUNK_D>(pos.z);
int ecx = floordiv<CHUNK_W>(pos.x + size.x);
int ecz = floordiv<CHUNK_D>(pos.z + size.z);
int cw = ecx - scx + 1;
int cd = ecz - scz + 1;
@ -365,67 +442,37 @@ void Chunks::getVoxels(VoxelsVolume& volume, bool backlight, int top) const {
for (int cx = scx; cx < scx + cw; cx++) {
const auto chunk = getChunk(cx, cz);
if (chunk == nullptr) {
// no chunk loaded -> filling with BLOCK_VOID
for (int ly = y; ly < y + h; ly++) {
for (int lz = std::max(z, cz * CHUNK_D);
lz < std::min(z + d, (cz + 1) * CHUNK_D);
lz++) {
for (int lx = std::max(x, cx * CHUNK_W);
lx < std::min(x + w, (cx + 1) * CHUNK_W);
lx++) {
uint idx = vox_index(lx - x, ly - y, lz - z, w, d);
voxels[idx].id = BLOCK_VOID;
lights[idx] = 0;
}
}
}
} else {
const voxel* cvoxels = chunk->voxels;
const light_t* clights =
chunk->lightmap ? chunk->lightmap->getLights() : nullptr;
for (int ly = y; ly < y + h; ly++) {
for (int lz = std::max(z, cz * CHUNK_D);
lz < std::min(z + d, (cz + 1) * CHUNK_D);
lz++) {
for (int lx = std::max(x, cx * CHUNK_W);
lx < std::min(x + w, (cx + 1) * CHUNK_W);
lx++) {
uint vidx = vox_index(lx - x, ly - y, lz - z, w, d);
uint cidx = vox_index(
lx - cx * CHUNK_W,
ly,
lz - cz * CHUNK_D,
CHUNK_W,
CHUNK_D
);
voxels[vidx] = cvoxels[cidx];
light_t light = clights ? clights[cidx]
: Lightmap::SUN_LIGHT_ONLY;
if (backlight) {
const auto block =
indices.blocks.get(voxels[vidx].id);
if (block && block->lightPassing) {
light = Lightmap::combine(
std::min(15,
Lightmap::extract(light, 0) + 1),
std::min(15,
Lightmap::extract(light, 1) + 1),
std::min(15,
Lightmap::extract(light, 2) + 1),
std::min(15,
static_cast<int>(Lightmap::extract(light, 3)))
);
}
}
lights[vidx] = light;
}
}
}
fill_with_void(
voxels, lights, pos, {size.x, h, size.z}, cx, cz
);
continue;
}
sample_chunk(
indices.blocks,
*chunk,
voxels,
lights,
pos,
{size.x, h, size.z},
cx,
cz,
backlight
);
}
}
}
void Chunks::getVoxels(VoxelsVolume& volume, bool backlight, int top) const {
getVoxels(
volume.getVoxels(),
volume.getLights(),
{volume.getX(), volume.getY(), volume.getZ()},
{volume.getW(), volume.getH(), volume.getD()},
backlight,
top
);
}
void Chunks::saveAndClear() {
areaMap.clear();
}

View file

@ -23,6 +23,8 @@ class LevelEvents;
class Block;
class VoxelsVolume;
template <int w, int h, int d> class StaticVoxelsVolume;
/// Player-centred chunks matrix
class Chunks {
LevelEvents* events;
@ -129,7 +131,34 @@ public:
bool isReplaceableBlock(int32_t x, int32_t y, int32_t z);
bool isObstacleBlock(int32_t x, int32_t y, int32_t z);
void getVoxels(VoxelsVolume& volume, bool backlight = false, int top = CHUNK_H) const;
void getVoxels(
VoxelsVolume& volume, bool backlight = false, int top = CHUNK_H
) const;
template <int w, int h, int d>
void getVoxels(
StaticVoxelsVolume<w, h, d>& volume,
bool backlight = false,
int top = CHUNK_H
) const {
getVoxels(
volume.getVoxels(),
volume.getLights(),
{volume.getX(), volume.getY(), volume.getZ()},
{w, h, d},
backlight,
top
);
}
void getVoxels(
voxel* voxels,
light_t* lights,
const glm::ivec3& pos,
const glm::ivec3& size,
bool backlight,
int top
) const;
void setCenter(int32_t x, int32_t z);
void resize(uint32_t newW, uint32_t newD);

View file

@ -1,28 +1,55 @@
#include "VoxelsVolume.hpp"
VoxelsVolume::VoxelsVolume(int x, int y, int z, int w, int h, int d)
: x(x),
y(y),
z(z),
w(w),
h(h),
d(d),
voxels(std::make_unique<voxel[]>(w * h * d)),
lights(std::make_unique<light_t[]>(w * h * d)) {
for (int i = 0; i < w * h * d; i++) {
voxels[i].id = BLOCK_VOID;
}
}
VoxelsVolume::VoxelsVolume(int w, int h, int d)
: VoxelsVolume(0, 0, 0, w, h, d) {
}
VoxelsVolume::~VoxelsVolume() {
}
void VoxelsVolume::setPosition(int x, int y, int z) {
this->x = x;
this->y = y;
this->z = z;
}
#include "VoxelsVolume.hpp"
#include "constants.hpp"
#include "content/Content.hpp"
#include "voxels/Block.hpp"
#include <assert.h>
void VoxelsVolume::compressInto(VoxelsVolume& dst, const Content& content) const {
assert(dst.w < w && dst.h < h && dst.d < d);
assert(w % dst.w == 0 && h % dst.h == 0 && d % dst.d == 0);
int stepW = w / dst.w;
int stepH = h / dst.h;
int stepD = d / dst.d;
int height = dst.h;
int depth = dst.d;
int width = dst.w;
auto dstVoxels = dst.getVoxels();
auto dstLights = dst.getLights();
const auto& blockDefs = content.getIndices()->blocks;
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
voxel selectedVoxel {BLOCK_AIR, {}};
light_t light = 0;
for (int ly = 0; ly < stepH; ly++) {
for (int lz = 0; lz < stepD; lz++) {
for (int lx = 0; lx < stepW; lx++) {
size_t srcIndex = vox_index(
x * stepW + lx, y * stepH + ly, z * stepD, w, d
);
auto vox = voxels[srcIndex];
if (vox.id == BLOCK_VOID) {
continue;
}
const auto& def = blockDefs.require(vox.id);
if (def.rt.solid) {
selectedVoxel = std::move(vox);
} else if (light == 0) {
light = lights[srcIndex];
}
}
}
}
size_t dstIndex = vox_index(x, y, z, dst.w, dst.d);
dstLights[dstIndex] = light;
dstVoxels[dstIndex] = std::move(selectedVoxel);
}
}
}
}

View file

@ -4,17 +4,27 @@
#include "typedefs.hpp"
#include "voxel.hpp"
class VoxelsVolume {
int x, y, z;
int w, h, d;
std::unique_ptr<voxel[]> voxels;
std::unique_ptr<light_t[]> lights;
public:
VoxelsVolume(int w, int h, int d);
VoxelsVolume(int x, int y, int z, int w, int h, int d);
virtual ~VoxelsVolume();
#include <cstring>
void setPosition(int x, int y, int z);
class Content;
class VoxelsVolume {
public:
VoxelsVolume(int w, int h, int d)
: VoxelsVolume(0, 0, 0, w, h, d) {}
VoxelsVolume(int x, int y, int z, int w, int h, int d)
: x(x), y(y), z(z), w(w), h(h), d(d),
voxels(std::make_unique<voxel[]>(w * h * d)),
lights(std::make_unique<light_t[]>(w * h * d)) {
std::memset(voxels.get(), 0xFF, sizeof(voxel) * w * h * d);
}
void setPosition(int x, int y, int z) {
this->x = x;
this->y = y;
this->z = z;
}
int getX() const {
return x;
@ -40,11 +50,19 @@ public:
return d;
}
voxel* getVoxels() const {
voxel* getVoxels() {
return voxels.get();
}
light_t* getLights() const {
const voxel* getVoxels() const {
return voxels.get();
}
light_t* getLights() {
return lights.get();
}
const light_t* getLights() const {
return lights.get();
}
@ -72,4 +90,92 @@ public:
}
return lights[vox_index(bx - x, by - y, bz - z, w, d)];
}
void compressInto(VoxelsVolume& dst, const Content& content) const;
private:
int x, y, z;
int w, h, d;
std::unique_ptr<voxel[]> voxels;
std::unique_ptr<light_t[]> lights;
};
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;
StaticVoxelsVolume()
: StaticVoxelsVolume(0, 0, 0) {}
StaticVoxelsVolume(int x, int y, int z)
: x(x), y(y), z(z) {
std::memset(voxels, 0xFF, sizeof(voxel) * size);
}
void setPosition(int x, int y, int z) {
this->x = x;
this->y = y;
this->z = z;
}
int getX() const {
return x;
}
int getY() const {
return y;
}
int getZ() const {
return z;
}
voxel* getVoxels() {
return voxels;
}
light_t* getLights() {
return lights;
}
inline blockid_t pickBlockId(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;
if (bx >= w || by >= h || bz >= d) {
return BLOCK_VOID;
}
return voxels[vox_index(bx, by, bz, w, d)].id;
}
inline const voxel& pickBlock(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;
if (bx >= w || by >= h || bz >= d) {
static voxel voidVoxel {BLOCK_VOID, {}};
return voidVoxel;
}
return voxels[vox_index(bx, by, bz, w, d)];
}
inline light_t pickLight(uint bx, uint by, uint bz) const {
bx -= x;
by -= y;
bz -= z;
if (bx >= w || by >= h || bz >= d) {
return 0;
}
return lights[vox_index(bx, by, bz, w, d)];
}
private:
int x, y, z;
voxel voxels[size];
light_t lights[size];
};

Some files were not shown because too many files have changed in this diff Show more