Merge branch 'main' into fix-assets-pipeline

This commit is contained in:
MihailRis 2026-08-19 23:54:50 +03:00
commit b9b8c6b7da
259 changed files with 5368 additions and 2080 deletions

View file

@ -173,6 +173,12 @@ void audio::initialize(
logger.info() << "initializing NoAudio backend";
backend = NoAudio::create().release();
}
if (settings.inputDevice.get() == "auto") {
auto inputDevices = audio::get_input_devices_names();
if (!inputDevices.empty()) {
settings.inputDevice.set(inputDevices.at(0));
}
}
struct {
std::string name;
NumberSetting* setting;

View file

@ -57,3 +57,7 @@ std::string parsing_error::errorLog() const {
ss << "^";
return ss.str();
}
std::runtime_error parsing_error::toRuntimeError() const {
return std::runtime_error("parsing error: " + errorLog());
}

View file

@ -73,4 +73,6 @@ public:
uint linestart
);
std::string errorLog() const;
std::runtime_error toRuntimeError() const;
};

View file

@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const {
}
static const std::unordered_map<std::string, int> side_indices {
{"east", 0},
{"west", 1},
{"bottom", 2},
{"top", 3},
{"south", 4},
{"north", 5},
{"west", 0}, // -X
{"east", 1}, // +X
{"bottom", 2}, // -Y
{"top", 3}, // +Y
{"north", 4}, // -Z
{"south", 5}, // +Z
};
static bool to_boolean(const xml::Attribute& attr) {
@ -267,12 +267,12 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
}
UVRegion regions[6] {};
regions[0].scale(to.x - from.x, to.y - from.y);
regions[1].scale(from.x - to.x, to.y - from.y);
regions[0].scale(to.z - from.z, to.y - from.y);
regions[1].scale(from.z - to.z, to.y - from.y);
regions[2].scale(to.x - from.x, to.z - from.z);
regions[3].scale(from.x - to.x, to.z - from.z);
regions[4].scale(to.z - from.z, to.y - from.y);
regions[5].scale(from.z - to.z, to.y - from.y);
regions[4].scale(to.x - from.x, to.y - from.y);
regions[5].scale(from.x - to.x, to.y - from.y);
auto center = (from + to) * 0.5f;
auto halfsize = (to - from) * 0.5f;
@ -306,7 +306,11 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
texfaces[idx] = elem->attr("texture").getText();
}
if (elem->has("region")) {
regions[idx].set(elem->attr("region").asVec4());
auto region = elem->attr("region").asVec4();
if (idx % 2 == 1) {
std::swap(region[0], region[2]);
}
regions[idx].set(region);
}
if (elem->has("region-scale")) {
regions[idx].scale(elem->attr("region-scale").asVec2());

View file

@ -30,6 +30,7 @@ namespace {
public:
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
: face(std::move(face)), buffer(std::move(buffer)) {
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
}
~FTFontFile() {

View file

@ -19,6 +19,12 @@ namespace vector_fonts {
virtual std::unique_ptr<Texture> renderPage(
int pageid, std::vector<Glyph>& glyphs, int size
) = 0;
bool isMonospace() const {
return monospace;
}
protected:
bool monospace = true;
};
void initialize();

View file

@ -385,10 +385,22 @@ public:
}
if (c == '(') {
nextChar();
// TODO: replace with array parsing after moving to dv::value's
std::string value = std::string(readUntil(')'));
int depth = 1;
size_t start = pos;
while (hasNext()) {
char c = nextChar();
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
if (depth == 0) {
goBack(1);
break;
}
}
}
expect(')');
return value;
return std::string(source.substr(start, pos - start - 1));
}
return std::string(readUntilWhitespace());
}
@ -528,3 +540,11 @@ std::string xml::stringify(
return ss.str();
}
std::string xml::stringify(
const Node& element, bool nice, const std::string& indentStr
) {
std::stringstream ss;
stringifyElement(ss, element, nice, indentStr, 0);
return ss.str();
}

View file

@ -119,6 +119,12 @@ namespace xml {
const std::string& indentStr = " "
);
std::string stringify(
const Node& element,
bool nice = true,
const std::string& indentStr = " "
);
/// @brief Read XML Document from string
/// @param filename file name will be shown in error messages
/// @param source xml source code string

View file

@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
inline const std::string ENGINE_VERSION_STRING = "0.32";
inline constexpr uint MAX_SUBPROCESSES = 1;
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
/// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3;

View file

@ -188,6 +188,7 @@ public:
ContentUnitDefs<GeneratorDef> generators;
std::unique_ptr<DrawGroups> const drawGroups;
ResourceIndicesSet resourceIndices {};
std::set<std::string> components {};
Content(
std::unique_ptr<ContentIndices> indices,

View file

@ -26,25 +26,31 @@ std::unique_ptr<Content> ContentBuilder::build() {
def.rt.tags.insert(tags.add(tag));
}
auto isSolid = [](const Variant& variant) {
return variant.model.type == BlockModelType::BLOCK ||
variant.culling == CullingMode::OPTIONAL;
};
if (def.variants) {
for (auto& variant : def.variants->variants) {
variant.rt.solid =
variant.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
variant.rt.solid = isSolid(variant) || def.explictlySolid;
}
def.defaults = def.variants->variants.at(0);
} else {
def.defaults.rt.solid =
def.defaults.model.type == BlockModelType::BLOCK ||
def.explictlySolid;
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
}
def.rt.solid = def.defaults.rt.solid;
const float EPSILON = 0.01f;
def.rt.solid =
def.rt.solid = def.rt.solid &&
def.obstacle &&
(glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size);
def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1;
if (def.material.empty()) {
defaults.at("block-material").get(def.material);
}
if (def.rotatable) {
for (uint i = 0; i < BlockRotProfile::MAX_COUNT; i++) {
def.rt.hitboxes[i].reserve(def.hitboxes.size());

View file

@ -178,7 +178,16 @@ void ContentUnitLoader<DefT>::loadUnit(
) {
auto folder = pack.folder;
auto configFile = folder / (defsDir + "/" + name + ".json");
if (io::exists(configFile)) loadUnit(def, full, configFile);
if (io::exists(configFile)) {
try {
loadUnit(def, full, configFile);
} catch (const std::runtime_error& err) {
throw std::runtime_error(
"file " + util::quote(configFile.string()) + ": " +
std::string(err.what())
);
}
}
}
void ContentLoader::loadBlockMaterial(
@ -497,6 +506,7 @@ void ContentLoader::loadScripts(Content& content) {
scripting::on_scripts_loading();
load_scripts(content, content.blocks);
load_scripts(content, content.items);
auto& tmpContent = content;
for (const auto& [packid, runtime] : content.getPacks()) {
auto env = runtime->getEnvironment();
@ -507,7 +517,7 @@ void ContentLoader::loadScripts(Content& content) {
// Load entity components
io::path componentsDir = folder / "scripts/components";
foreach_file(componentsDir, [&pack, env](const io::path& file) {
foreach_file(componentsDir, [&pack, env, &tmpContent](const io::path& file) {
auto name = pack.id + ":" + file.stem();
scripting::load_entity_component(
env,
@ -515,9 +525,21 @@ void ContentLoader::loadScripts(Content& content) {
file,
pack.id + ":scripts/components/" + file.name()
);
tmpContent.components.insert(name);
});
}
for (const auto& [eid, def] : content.entities.getDefs()) {
for (const auto& instance : def->components) {
if (content.components.find(instance.component) == content.components.end()) {
throw std::runtime_error(
"component " + instance.component +
" is not available (required by entity " + eid + ")"
);
}
}
}
scripting::on_content_loaded();
}

View file

@ -53,6 +53,9 @@ static void load_variant(
}
} else if (root.has("texture-faces")) {
const auto& texarr = root["texture-faces"];
if (texarr.length() < 6) {
throw std::runtime_error("texture-faces requires 6 elements");
}
for (uint i = 0; i < 6; i++) {
variant.textureFaces[i] = texarr[i].asString();
}
@ -148,6 +151,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
def.rotations = BlockRotProfile::PANE;
} else if (profile == BlockRotProfile::STAIRS_NAME) {
def.rotations = BlockRotProfile::STAIRS;
} else if (profile == BlockRotProfile::LADDER_NAME) {
def.rotations = BlockRotProfile::LADDER;
} else if (profile != "none") {
logger.error() << "unknown rotation profile " << profile;
def.rotatable = false;

View file

@ -89,6 +89,7 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
root.at("skeleton-name").get(def.skeletonName);
root.at("material").get(def.material);
root.at("blocking").get(def.blocking);
root.at("selectable").get(def.selectable);
root.at("solid").get(def.solid);
root.at("mass").get(def.mass);
root.at("elasticity").get(def.elasticity);

View file

@ -9,6 +9,10 @@
#include <stdexcept>
#include <unordered_map>
#ifdef VC_ENABLE_REFLECTION
#include "util/EnumMetadata.hpp"
#endif
namespace util {
template<class T> class Buffer;
}
@ -70,6 +74,14 @@ namespace dv {
return ptr != nullptr;
}
inline value* operator->() noexcept {
return ptr;
}
inline const value* operator->() const noexcept {
return ptr;
}
inline value& operator*() noexcept {
return *ptr;
}
@ -78,6 +90,11 @@ namespace dv {
return *ptr;
}
#ifdef VC_ENABLE_REFLECTION
template <typename T>
bool get(T& dst, const util::EnumMetadata<T>& mt) const;
#endif
bool get(std::string& dst) const;
bool get(bool& dst) const;
bool get(char& dst) const;
@ -552,6 +569,17 @@ namespace dv {
}
return false;
}
#ifdef VC_ENABLE_REFLECTION
template <typename T>
inline bool optionalvalue::get(T& dst, const util::EnumMetadata<T>& mt) const {
if (ptr) {
return mt.getItem(ptr->asString(), dst);
}
return false;
}
#endif
inline bool optionalvalue::get(std::string& dst) const {
if (ptr) {
dst = ptr->asString();

View file

@ -0,0 +1,59 @@
#include "AppScriptsControl.hpp"
#include "debug/Logger.hpp"
#include "engine/CoreParameters.hpp"
#include "io/io.hpp"
#include "Project.hpp"
#include "logic/scripting/scripting.hpp"
static debug::Logger logger("app-scripts");
AppScriptsControl::AppScriptsControl(const CoreParameters& params) {
io::path scriptFile =
params.scriptFile.empty()
? "project:start.lua"
: std::string("script:") + params.scriptFile.filename().u8string();
if (io::exists(scriptFile)) {
logger.info() << "starting script: " << params.scriptFile.u8string();
scriptCoroutine = scripting::start_app_script(scriptFile);
} else {
logger.warning() << "script does not exists: " << params.scriptFile.u8string();
}
if (!params.headless) {
loadProjectClientScript();
}
}
void AppScriptsControl::loadProjectClientScript() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project client script: " << scriptFile.string();
clientScript = scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project client script does not exists: " << scriptFile.string();
}
}
void AppScriptsControl::onScreenChange(const std::string& name, bool show) {
if (clientScript) {
clientScript->onScreenChange(name, show);
}
}
void AppScriptsControl::tick() {
if (scriptCoroutine && scriptCoroutine->isActive()) {
scriptCoroutine->update();
}
}
void AppScriptsControl::terminate(std::string_view reason) {
if (scriptCoroutine->isActive()) {
scriptCoroutine->terminate();
logger.info() << "script has been terminated due to " << reason;
}
}
bool AppScriptsControl::isFinished() const {
return scriptCoroutine == nullptr || !scriptCoroutine->isActive();
}

View file

@ -0,0 +1,29 @@
#pragma once
#include "interfaces/Process.hpp"
#include <memory>
#include <string>
namespace scripting {
class IClientProjectScript;
}
struct CoreParameters;
struct Project;
class AppScriptsControl {
public:
AppScriptsControl(const CoreParameters& params);
void tick();
void loadProjectClientScript();
void terminate(std::string_view reason);
void onScreenChange(const std::string& name, bool show);
bool isFinished() const;
private:
std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> scriptCoroutine;
};

View file

@ -2,6 +2,7 @@
#include "data/dv_util.hpp"
#include "debug/Logger.hpp"
#include "engine/CoreParameters.hpp"
#include "io/io.hpp"
#include "io/path.hpp"
#include "logic/scripting/scripting.hpp"
@ -40,26 +41,6 @@ void Project::deserialize(const dv::value& src) {
}
}
void Project::loadProjectClientScript() {
io::path scriptFile = "project:project_client.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project client script";
clientScript = scripting::load_client_project_script(scriptFile);
} else {
logger.warning() << "project client script does not exists";
}
}
void Project::loadProjectStartScript() {
io::path scriptFile = "project:start.lua";
if (io::exists(scriptFile)) {
logger.info() << "starting project start script";
setupCoroutine = scripting::start_app_script(scriptFile);
} else {
logger.warning() << "project start script does not exists";
}
}
bool Permissions::has(const std::string& name) const {
return permissions.find(name) != permissions.end();
}

View file

@ -1,22 +1,19 @@
#pragma once
#include "interfaces/Serializable.hpp"
#include "io/path.hpp"
#include <set>
#include <string>
#include <vector>
#include <memory>
#include "interfaces/Process.hpp"
#include "interfaces/Serializable.hpp"
namespace scripting {
class IClientProjectScript;
}
struct Permissions {
static inline std::string DEBUGGING = "debugging";
static inline std::string NETWORK = "network";
static inline std::string RECORD_AUDIO = "record-audio";
static inline std::string WRITE_TO_USER = "write-to-user";
static inline std::string SUB_INSTANCES = "sub-instances";
std::set<std::string> permissions;
@ -26,16 +23,12 @@ struct Permissions {
struct Project : Serializable {
std::string name;
std::string title;
io::path path;
std::vector<std::string> basePacks;
std::unique_ptr<scripting::IClientProjectScript> clientScript;
std::unique_ptr<Process> setupCoroutine;
Permissions permissions;
~Project();
dv::value serialize() const override;
void deserialize(const dv::value& src) override;
void loadProjectClientScript();
void loadProjectStartScript();
};

View file

@ -12,7 +12,9 @@ struct CoreParameters {
std::filesystem::path userFolder = ".";
std::filesystem::path scriptFile;
std::filesystem::path projectFolder;
std::filesystem::path logFile;
std::string debugServerString;
int tps = 20;
int subProcessDepth = 0;
std::unordered_map<std::string, std::string> projectArgs;
};

View file

@ -13,6 +13,7 @@
#include "content/ContentControl.hpp"
#include "core_defs.hpp"
#include "debug/Logger.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "devtools/DebuggingServer.hpp"
#include "devtools/Editor.hpp"
#include "devtools/Project.hpp"
@ -191,10 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) {
audio::set_input_device(name == "auto" ? "" : name);
}));
project->loadProjectStartScript();
if (!params.headless) {
project->loadProjectClientScript();
}
appScripts = std::make_unique<AppScriptsControl>(params);
if (params.stdinCommands) {
cmd::start_stdin_cmd_reader(*this);
}
@ -263,9 +262,7 @@ void Engine::detachDebugger() {
}
void Engine::applicationTick() {
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
project->setupCoroutine->update();
}
appScripts->tick();
}
void Engine::updateFrontend() {
@ -353,6 +350,7 @@ void Engine::close() {
network.reset();
clearKeepedObjects();
project.reset();
appScripts.reset();
scripting::close();
logger.info() << "scripting finished";
if (!params.headless) {
@ -382,13 +380,14 @@ void Engine::loadAssets() {
void Engine::loadProject() {
io::path projectFile = "project:project.toml";
project = std::make_unique<Project>();
project->path = "project:";
project->deserialize(io::read_object(projectFile));
logger.info() << "loaded project " << util::quote(project->name);
}
void Engine::setScreen(std::shared_ptr<Screen> screen) {
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), false);
if (this->screen) {
appScripts->onScreenChange(this->screen->getName(), false);
}
// reset audio channels (stop all sources)
audio::reset_channel(audio::get_channel_index("regular"));
@ -397,8 +396,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
if (this->screen) {
this->screen->onOpen();
}
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), true);
if (this->screen) {
appScripts->onScreenChange(this->screen->getName(), true);
window->setShouldRefresh();
}
}
@ -414,6 +413,7 @@ void Engine::onWorldClosed() {
}
void Engine::quit() {
logger.info() << "quitSignal set to true";
quitSignal = true;
if (!isHeadless()) {
window->setShouldClose(true);

View file

@ -12,6 +12,7 @@
class Assets;
class AssetsLoader;
class AssetsManagement;
class AppScriptsControl;
class ContentControl;
class EngineController;
class EnginePaths;
@ -55,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
std::unique_ptr<Project> project;
std::unique_ptr<SettingsHandler> settingsHandler;
std::unique_ptr<AssetsManagement> assets;
std::unique_ptr<AppScriptsControl> appScripts;
std::shared_ptr<Screen> screen;
std::unique_ptr<ContentControl> content;
std::unique_ptr<EngineController> controller;
@ -184,5 +186,9 @@ public:
return debuggingServer.get();
}
AppScriptsControl& getAppScripts() {
return *appScripts;
}
void detachDebugger();
};

View file

@ -1,14 +1,17 @@
#include "Mainloop.hpp"
#include "Engine.hpp"
#include "debug/Logger.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "devtools/Project.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "Engine.hpp"
#include "frontend/screens/LevelScreen.hpp"
#include "frontend/screens/MenuScreen.hpp"
#include "graphics/ui/elements/Container.hpp"
#include "graphics/ui/GUI.hpp"
#include "io/path.hpp"
#include "logic/scripting/scripting.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Container.hpp"
static debug::Logger logger("mainloop");
@ -19,6 +22,7 @@ void Mainloop::run() {
auto& time = engine.getTime();
auto& window = engine.getWindow();
auto& settings = engine.getSettings();
const auto& coreParams = engine.getCoreParameters();
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
if (level == nullptr) {
@ -35,9 +39,11 @@ void Mainloop::run() {
logger.info() << "starting menu screen";
engine.setScreen(std::make_shared<MenuScreen>(engine));
auto& appScripts = engine.getAppScripts();
logger.info() << "main loop started";
while (!window.isShouldClose()){
while (!window.isShouldClose() && !engine.isQuitSignal()) {
time.update(window.time());
engine.applicationTick();
engine.updateFrontend();
@ -50,6 +56,10 @@ void Mainloop::run() {
settings.display.adaptiveFpsInMenu.get() &&
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
);
if (coreParams.testMode && appScripts.isFinished()) {
logger.info() << "test finished";
engine.quit();
}
}
logger.info() << "main loop stopped";
}

View file

@ -1,13 +1,14 @@
#include "ServerMainloop.hpp"
#include "Engine.hpp"
#include "logic/scripting/scripting.hpp"
#include "devtools/AppScriptsControl.hpp"
#include "logic/LevelController.hpp"
#include "interfaces/Process.hpp"
#include "debug/Logger.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include "util/platform.hpp"
#include "devtools/Project.hpp"
#include <chrono>
@ -32,21 +33,12 @@ void ServerMainloop::run() {
setLevel(std::move(level));
});
auto process = scripting::start_app_script(
"script:" + coreParams.scriptFile.filename().u8string()
);
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
double delta = targetDelta;
auto begin = system_clock::now();
auto startupTime = begin;
while (process->isActive()) {
if (engine.isQuitSignal()) {
process->terminate();
logger.info() << "script has been terminated due to quit signal";
break;
}
while (!engine.isQuitSignal() && !engine.getAppScripts().isFinished()) {
if (coreParams.testMode) {
time.step(delta);
} else {
@ -55,9 +47,8 @@ void ServerMainloop::run() {
duration_cast<microseconds>(now - startupTime).count() / 1e6);
delta = time.getDelta();
}
process->update();
if (controller) {
controller->getLevel()->getWorld()->updateTimers(delta);
controller->getLevel()->getWorld().updateTimers(delta);
controller->update(glm::min(delta, 0.2), false);
}
engine.applicationTick();

View file

@ -30,6 +30,7 @@ void ContentGfxCache::refreshVariant(
uint8_t variantIndex,
const Atlas& atlas
) {
std::unordered_map<std::string, std::string> optionalTextures;
bool denseRender = settings.denseRender.get();
for (uint side = 0; side < 6; side++) {
std::string tex = variant.textureFaces[side];
@ -43,6 +44,7 @@ void ContentGfxCache::refreshVariant(
texOpaque = tex;
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
tex = texOpaque;
optionalTextures[variant.textureFaces[side]] = texOpaque;
}
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
sideregions[index] = atlas.get(tex);
@ -56,7 +58,12 @@ void ContentGfxCache::refreshVariant(
if (pos == std::string::npos) {
continue;
}
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
std::string texture = mesh.texture.substr(pos+1);
const auto& found = optionalTextures.find(texture);
if (found != optionalTextures.end()) {
texture = found->second;
}
if (auto region = atlas.getIf(texture)) {
for (auto& vertex : mesh.vertices) {
vertex.uv = region->apply(vertex.uv);
}

View file

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

View file

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

View file

@ -7,6 +7,9 @@
#include "graphics/ui/elements/InventoryView.hpp"
#include "graphics/ui/gui_xml.hpp"
#include "logic/scripting/scripting.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("ui-document");
UiDocument::UiDocument(
std::string id,
@ -17,6 +20,19 @@ UiDocument::UiDocument(
rebuildIndices();
}
UiDocument::~UiDocument() {
try {
scripting::on_ui_destroy(*this);
} catch (const std::exception& err) {
logger.error() << "an error occurred on calling on_destroy event for document '"
<< id << "': " << err.what();
} catch (...) {
logger.error() << "unknown exception caught on calling on_destroy "
"event for document '"
<< id << "'";
}
}
void UiDocument::rebuildIndices() {
map.clear();
gui::UINode::getIndices(root, map);

View file

@ -17,6 +17,7 @@ struct UiDocScript {
bool onopen : 1;
bool onprogress : 1;
bool onclose : 1;
bool ondestroy : 1;
};
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
@ -35,6 +36,8 @@ public:
scriptenv env
);
~UiDocument();
void rebuildIndices();
void pushIndices(const std::shared_ptr<gui::UINode>& node);

View file

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

View file

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

View file

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

View file

@ -86,7 +86,7 @@ UiDocument* menus::show(
);
auto document = documentPtr.get();
engine.getAssets()->store(std::move(documentPtr), fullname);
scripting::on_ui_open(document, std::move(args));
scripting::on_ui_open(*document, std::move(args));
menu->addPage(name, document->getRoot());
menu->setPage(name);
return document;
@ -99,13 +99,13 @@ void menus::show_process_panel(
auto menu = engine.getGUI().getMenu();
menu->reset();
auto doc =
auto document =
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
std::dynamic_pointer_cast<Container>(doc->getRoot())
std::dynamic_pointer_cast<Container>(document->getRoot())
->listenInterval(0.01f, [=]() {
task->update();
uint tasksDone = task->getWorkDone();
scripting::on_ui_progress(doc, tasksDone, initialWork);
scripting::on_ui_progress(*document, tasksDone, initialWork);
});
}

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
#include <GL/glew.h>
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
: Texture(0, width, height)
: Texture(0U, width, height, imageFormat)
{
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
@ -14,7 +14,6 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
uint format = gl::to_glenum(imageFormat);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
@ -37,3 +36,23 @@ void Cubemap::bind() const {
void Cubemap::unbind() const {
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}
void Cubemap::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
for (uint face = 0; face < 6; face++) {
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
0,
format,
width,
height,
0,
format,
GL_UNSIGNED_BYTE,
data
);
}
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}

View file

@ -9,4 +9,5 @@ public:
virtual void bind() const override;
virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h) override;
};

View file

@ -74,6 +74,18 @@ DrawContext::~DrawContext() {
if (lineWidth != parent->lineWidth) {
glLineWidth(parent->lineWidth);
}
for (int i = 0; i < textures.size(); i++) {
auto texture = parent->textures[i];
if (textures[i] != texture) {
glActiveTexture(GL_TEXTURE0 + i);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
}
}
}
const glm::uvec2& DrawContext::getViewport() const {
@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) {
lineWidth = width;
glLineWidth(width);
}
void DrawContext::useTexture(int target, const Bindable* texture) {
glActiveTexture(GL_TEXTURE0 + target);
if (texture == nullptr) {
glBindTexture(GL_TEXTURE_2D, 0);
} else {
texture->bind();
}
textures.at(target) = texture;
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
@ -23,6 +24,7 @@ class DrawContext {
BlendMode blendMode = BlendMode::normal;
int scissorsCount = 0;
float lineWidth = 1.0f;
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
public:
DrawContext(
const DrawContext* parent,
@ -44,4 +46,5 @@ public:
void setBlendMode(BlendMode mode);
void setScissors(const glm::vec4& area);
void setLineWidth(float width);
void useTexture(int target, const Bindable* texture);
};

View file

@ -26,6 +26,11 @@ Font::Font(
pages(std::move(pages)),
glyphs(std::move(glyphs)),
fontFile(std::move(fontFile)) {
if (this->fontFile.has_value()) {
if (auto fontPtr = this->fontFile->lock()) {
monospace = fontPtr->isMonospace();
}
}
}
Font::~Font() = default;
@ -34,6 +39,10 @@ int Font::getYOffset() const {
return yoffset;
}
bool Font::isMonospace() const {
return monospace;
}
int Font::getLineHeight() const {
return lineHeight;
}
@ -53,7 +62,7 @@ bool Font::isPrintableChar(uint codepoint) const {
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
auto font = this->font.has_value() ? this->font->lock() : nullptr;
if (font == nullptr) {
if (font == nullptr || font->isMonospace()) {
return std::min(text.length() - offset, length) * _glyphInterval;
}
int totalWidth = 0;
@ -153,6 +162,8 @@ static inline void draw_text(
const FontStylesScheme* styles,
size_t styleMapOffset
) {
bool is3d = std::is_same<Batch, Batch3D>();
static FontStylesScheme defStyles {{{}}, {0}};
if (styles == nullptr) {
@ -185,13 +196,26 @@ static inline void draw_text(
float advance = baseAdvance;
if (auto glyph = font.getGlyph(c)) {
yOffset = glyph->yOffset;
advance = glyph->xAdvance;
advance = glyph->xAdvance /
static_cast<float>(font.getLineHeight()) * 2.0f *
baseAdvance;
}
uint charpage = c >> 8;
if (charpage == page){
batch.texture(font.getPage(charpage));
draw_glyph(
batch, pos, glm::vec2(x, y - yOffset / static_cast<float>(font.getLineHeight())), c, right, up, interval, style
batch,
pos,
glm::vec2(
x,
y - yOffset * (is3d ? -1 : 1) /
static_cast<float>(font.getLineHeight())
),
c,
right,
up,
interval,
style
);
}
else if (charpage > page && charpage < next){

View file

@ -39,6 +39,7 @@ public:
);
~Font();
bool isMonospace() const;
int getLineHeight() const;
int getYOffset() const;
@ -94,6 +95,7 @@ private:
int lineHeight;
int yoffset;
int glyphInterval;
bool monospace = true;
std::vector<std::unique_ptr<Texture>> pages;
std::vector<Glyph> glyphs;
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;

View file

@ -9,8 +9,8 @@ class Font;
struct FontMetrics {
std::optional<std::weak_ptr<Font>> font;
int lineHeight;
int yoffset;
int lineHeight = 0;
int yoffset = 0;
int _glyphInterval = 8;
~FontMetrics();

View file

@ -1,14 +1,14 @@
#include "Framebuffer.hpp"
#include <GL/glew.h>
#include "Texture.hpp"
#include "debug/Logger.hpp"
static debug::Logger logger("gl-framebuffer");
Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
: fbo(fbo), depth(depth), texture(std::move(texture))
{
: fbo(fbo), depth(depth), texture(std::move(texture)) {
if (this->texture) {
width = this->texture->getWidth();
height = this->texture->getHeight();
@ -18,35 +18,55 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
}
}
static std::unique_ptr<Texture> create_texture(int width, int height, int format) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
static std::unique_ptr<Texture> create_texture(
int width, int height, bool alpha
) {
GLenum glformat = alpha ? GL_RGBA : GL_RGB;
GLuint textureid;
glGenTextures(1, &textureid);
glBindTexture(GL_TEXTURE_2D, textureid);
glTexImage2D(
GL_TEXTURE_2D,
0,
glformat,
width,
height,
0,
glformat,
GL_UNSIGNED_BYTE,
nullptr
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
return std::make_unique<Texture>(tex, width, height);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid, 0
);
return std::make_unique<Texture>(
textureid,
width,
height,
alpha ? ImageFormat::RGBA8888 : ImageFormat::RGB888
);
}
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height)
{
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
: width(width), height(height) {
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
format = alpha ? GL_RGBA : GL_RGB;
// Setup color attachment (texture)
texture = create_texture(width, height, format);
texture = create_texture(width, height, alpha);
// Setup depth attachment
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth
);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
logger.error() << "framebuffer is not complete!";
@ -60,11 +80,15 @@ Framebuffer::~Framebuffer() {
glDeleteRenderbuffers(1, &depth);
}
void Framebuffer::bind() {
void Framebuffer::setTexture(std::unique_ptr<Texture> texture) {
this->texture = std::move(texture);
}
void Framebuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
void Framebuffer::unbind() {
void Framebuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
texture = create_texture(width, height, format);
texture->resize(width, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const {
}
uint Framebuffer::getFBO() const {
return fbo;
return fbo;
}

View file

@ -12,18 +12,19 @@ class Framebuffer : public Bindable {
uint depth;
uint width;
uint height;
uint format;
std::shared_ptr<Texture> texture;
public:
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
Framebuffer(uint width, uint height, bool alpha=false);
~Framebuffer();
void setTexture(std::unique_ptr<Texture> texture);
/// @brief Use framebuffer
void bind() override;
void bind() const override;
/// @brief Stop using framebuffer
void unbind() override;
void unbind() const override;
/// @brief Update framebuffer texture size
/// @param width new width

View file

@ -189,7 +189,7 @@ GBuffer::~GBuffer() {
glDeleteFramebuffers(1, &ssaoFbo);
}
void GBuffer::bind() {
void GBuffer::bind() const {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClear(GL_COLOR_BUFFER_BIT);
}
@ -198,7 +198,7 @@ void GBuffer::bindSSAO() const {
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo);
}
void GBuffer::unbind() {
void GBuffer::unbind() const {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

View file

@ -9,9 +9,9 @@ public:
GBuffer(uint width, uint height);
~GBuffer() override;
void bind() override;
void bind() const override;
void bindSSAO() const;
void unbind() override;
void unbind() const override;
void bindBuffers() const;
void bindSSAOBuffer() const;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@
uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it
Texture::Texture(uint id, uint width, uint height)
: id(id), width(width), height(height) {
Texture::Texture(uint id, uint width, uint height, ImageFormat imageFormat)
: id(id), width(width), height(height), format(gl::to_glenum(imageFormat)) {
}
Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat)
@ -17,7 +17,7 @@ Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFo
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = gl::to_glenum(imageFormat);
format = gl::to_glenum(imageFormat);
glTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
@ -41,23 +41,27 @@ void Texture::unbind() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::resize(uint width, uint height) {
reload(nullptr, width, height);
}
void Texture::reload(const ImageData& image) {
width = image.getWidth();
height = image.getHeight();
reload(image.getData(), width, height);
}
void Texture::reload(const ubyte* data, uint width, uint height) {
this->width = width;
this->height = height;
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
glBindTexture(GL_TEXTURE_2D, id);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData());
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, GL_UNSIGNED_BYTE, image.getData());
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}

View file

@ -1,29 +1,32 @@
#pragma once
#include "typedefs.hpp"
#include "maths/UVRegion.hpp"
#include "ImageData.hpp"
#include <memory>
class Texture {
#include "ImageData.hpp"
#include "commons.hpp"
#include "maths/UVRegion.hpp"
#include "typedefs.hpp"
class Texture : public Bindable {
protected:
uint id;
uint width;
uint height;
uint format;
public:
Texture(uint id, uint width, uint height);
Texture(uint id, uint width, uint height, ImageFormat imageFormat);
Texture(const ubyte* data, uint width, uint height, ImageFormat format);
virtual ~Texture();
virtual void bind() const;
virtual void unbind() const;
void reload(const ubyte* data, uint w, uint h);
virtual void bind() const override;
virtual void unbind() const override;
virtual void reload(const ubyte* data, uint w, uint h);
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
void setNearestFilter();
void reload(const ImageData& image);
virtual void resize(uint w, uint h);
void setMipMapping(bool flag, bool pixelated);

View file

@ -59,6 +59,8 @@ namespace advanced_pipeline {
inline constexpr int TARGET_SSAO = 5;
inline constexpr int TARGET_SHADOWS0 = 6;
inline constexpr int TARGET_SHADOWS1 = 7;
inline constexpr int TARGETS_COUNT = 8;
}
VC_ENUM_METADATA(CursorShape)
@ -85,6 +87,6 @@ class Bindable {
public:
virtual ~Bindable() = default;
virtual void bind() = 0;
virtual void unbind() = 0;
virtual void bind() const = 0;
virtual void unbind() const = 0;
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,4 +34,7 @@ void HandsRenderer::render(const Camera& camera) {
glm::vec3(),
glm::vec3(1.0f)
);
modelBatch.render();
modelBatch.setLightsOffset(glm::vec3());
}

View file

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

View file

@ -1,91 +1,64 @@
#include "Skybox.hpp"
#include "assets/Assets.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/DrawContext.hpp"
#include "window/Window.hpp"
#include "window/Camera.hpp"
#include "maths/UVRegion.hpp"
#include <GL/glew.h>
#include <cmath>
#include <iostream>
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <limits>
#include "assets/Assets.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "maths/UVRegion.hpp"
#include "window/Camera.hpp"
#include "window/Window.hpp"
using namespace advanced_pipeline;
const int STARS_COUNT = 3000;
const int STARS_SEED = 632;
Skybox::Skybox(uint size, Shader& shader)
: size(size),
shader(shader),
batch3d(std::make_unique<Batch3D>(4096))
{
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
Skybox::Skybox(uint size, const Assets& assets)
: size(size),
assets(assets),
shader(assets.require<Shader>("skybox_gen")),
batch3d(std::make_unique<Batch3D>(4096)) {
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(fboid, 0, std::move(cubemap));
SkyboxVertex vertices[]{
SkyboxVertex vertices[] {
{{-1.0f, -1.0f}},
{{-1.0f, 1.0f}},
{{1.0f, 1.0f}},
{{-1.0f, -1.0f}},
{{1.0f, 1.0f}},
{{1.0f, -1.0f}}
};
{{1.0f, -1.0f}}};
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
sprites.push_back(SkySprite {
"misc/moon",
glm::pi<float>() * 0.5f,
4.0f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/moon_flare",
glm::pi<float>() * 0.5f,
0.5f,
false,
glm::pi<float>() * 0.25f,
});
sprites.push_back(SkySprite {
"misc/sun",
glm::pi<float>() * 1.5f,
4.0f,
true,
glm::pi<float>() * 0.25f,
});
setMode(mode);
}
Skybox::~Skybox() = default;
void Skybox::drawBackground(
const Camera& camera, const Assets& assets, int width, int height
) {
void Skybox::drawBackground(const Camera& camera, int width, int height) {
auto backShader = assets.get<Shader>("background");
backShader->use();
backShader->uniformMatrix("u_view", camera.getView(false));
backShader->uniform1f(
"u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>()
);
backShader->uniform1f("u_ar", float(width)/float(height));
backShader->uniform1f("u_ar", float(width) / float(height));
backShader->uniform1i("u_skybox", 1);
bind();
auto texture = fbo->getTexture();
texture->bind();
mesh->draw();
unbind();
texture->unbind();
}
void Skybox::drawStars(float angle, float opacity) {
@ -93,9 +66,7 @@ void Skybox::drawStars(float angle, float opacity) {
random.setSeed(STARS_SEED);
glm::mat4 rotation = glm::rotate(
glm::mat4(1.0f),
-angle + glm::pi<float>() * 0.5f,
glm::vec3(0, 0, -1)
glm::mat4(1.0f), -angle + glm::pi<float>() * 0.5f, glm::vec3(0, 0, -1)
);
rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0));
@ -108,41 +79,21 @@ void Skybox::drawStars(float angle, float opacity) {
glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation;
float sopacity = random.randFloat();
if (pos.y < 0.0f)
continue;
if (pos.y < 0.0f) continue;
sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f;
glm::vec4 tint (1,1,1, sopacity * opacity);
glm::vec4 tint(1, 1, 1, sopacity * opacity);
batch3d->point(pos * depth, tint);
}
batch3d->flushPoints();
}
void Skybox::draw(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
void Skybox::drawSkySprites(
float daytime,
float fog)
{
const glm::uvec2& viewport = pctx.getViewport();
glActiveTexture(GL_TEXTURE0);
drawBackground(camera, assets, viewport.x, viewport.y);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
float angle,
float opacity,
const std::vector<SkySprite>& sprites
) {
float depthScale = 2e3;
for (auto& sprite : sprites) {
batch3d->texture(assets.get<Texture>(sprite.texture));
@ -159,39 +110,124 @@ void Skybox::draw(
glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation;
glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation;
glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation;
glm::vec4 tint (1,1,1, opacity);
glm::vec4 tint(1, 1, 1, opacity);
if (!sprite.emissive) {
tint *= 0.6f + std::cos(angle)*0.4;
tint *= 0.6f + std::cos(angle) * 0.4;
}
batch3d->sprite(pos, right,
up, 1, 1, UVRegion(), tint);
batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint);
}
batch3d->flush();
drawStars(angle, opacity);
}
void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) {
void Skybox::draw(
const Environment& environment,
const DrawContext& pctx,
const Camera& camera,
float daytime,
float fog
) {
const glm::uvec2& viewport = pctx.getViewport();
drawBackground(camera, viewport.x, viewport.y);
float angle = daytime * glm::pi<float>() * 2.0f;
float opacity = glm::pow(1.0f - fog, 7.0f);
DrawContext ctx = pctx.sub();
ctx.setBlendMode(BlendMode::addition);
if (environment.sky.sprites.empty() && !environment.sky.stars) {
return;
}
auto shader = assets.get<Shader>("ui3d");
shader->use();
shader->uniformMatrix("u_projview", camera.getProjView(false));
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
batch3d->begin();
if (!environment.sky.sprites.empty()) {
drawSkySprites(daytime, angle, opacity, environment.sky.sprites);
}
if (environment.sky.stars) {
drawStars(angle, opacity);
}
}
void Skybox::setMode(SkyMode mode) {
if (this->mode == mode && fbo) {
return;
}
this->mode = mode;
uint size = mode == SkyMode::BOX ? this->size : 1U;
prevT = std::numeric_limits<float>().min();
uint fboid;
glGenFramebuffers(1, &fboid);
fbo = std::make_unique<Framebuffer>(
fboid,
false,
std::make_unique<Cubemap>(size, size, ImageFormat::RGB888)
);
}
void Skybox::refresh(
const Environment& environment,
const DrawContext& pctx,
float dayTime,
float mie,
const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
) {
setMode(environment.sky.mode);
if (mode == SkyMode::NONE) {
return;
} else if (mode == SkyMode::SOLID) {
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get());
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr);
for (int face = 0; face < 6; face++) {
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
0
);
glClearColor(tint.r, tint.g, tint.b, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
return;
}
frameid++;
float dayTime = t;
DrawContext ctx = pctx.sub();
ctx.setDepthMask(false);
ctx.setDepthTest(false);
ctx.setFramebuffer(fbo.get());
ctx.setViewport({size, size});
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
assert(cubemap != nullptr);
ready = true;
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
cubemap->bind();
shader.use();
t *= glm::two_pi<float>();
float t = dayTime * glm::two_pi<float>();
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
float sunAngle = glm::radians((t / glm::two_pi<float>() - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) * glm::radians(sunAltitude);
float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f);
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) *
glm::radians(sunAltitude);
float y = sunAngle - glm::pi<float>() * 0.5f;
float z = glm::radians(0.0f);
rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0));
@ -199,6 +235,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1));
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
shader.use();
shader.uniform1i("u_quality", quality);
shader.uniform1f("u_mie", mie);
shader.uniform3f("u_tint", tint);
@ -207,23 +244,22 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
shader.uniform3f("u_lightDir", lightDir);
shader.uniform1f("u_dayTime", dayTime);
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 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);
refreshFace(face, *cubemap);
}
} else {
uint face = frameid % 6;
refreshFace(face, cubemap);
refreshFace(face, *cubemap);
}
prevMie = mie;
prevT = t;
prevHighlight = hightlight;
cubemap->unbind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::refreshFace(uint face, Cubemap* cubemap) {
void Skybox::refreshFace(uint face, Cubemap& cubemap) {
const glm::vec3 xaxs[] = {
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, 1.0f},
@ -256,7 +292,7 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
cubemap->getId(),
cubemap.getId(),
0
);
shader.uniform3f("u_xaxis", xaxs[face]);
@ -265,14 +301,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
mesh->draw();
}
void Skybox::bind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->bind();
glActiveTexture(GL_TEXTURE0);
}
void Skybox::unbind() const {
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
fbo->getTexture()->unbind();
glActiveTexture(GL_TEXTURE0);
const Cubemap* Skybox::getCubemap() const {
return dynamic_cast<const Cubemap*>(fbo->getTexture());
}

View file

@ -1,14 +1,17 @@
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <vector>
#include <glm/glm.hpp>
#include "typedefs.hpp"
#include "maths/fastmaths.hpp"
#include "graphics/core/MeshData.hpp"
template<typename VertexStructure> class Mesh;
#include "graphics/core/MeshData.hpp"
#include "maths/fastmaths.hpp"
#include "typedefs.hpp"
#include "world/Environment.hpp"
template <typename VertexStructure>
class Mesh;
class Shader;
class Assets;
class Camera;
@ -21,29 +24,20 @@ struct SkyboxVertex {
glm::vec2 position;
static constexpr VertexAttribute ATTRIBUTES[] {
{VertexAttribute::Type::FLOAT, false, 2},
{{}, 0}};
};
struct SkySprite {
std::string texture;
float phase;
float distance;
bool emissive;
float altitude;
{VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}};
};
class Skybox {
std::unique_ptr<Framebuffer> fbo;
SkyMode mode = SkyMode::SOLID;
uint size;
std::unique_ptr<Framebuffer> fbo;
const Assets& assets;
Shader& shader;
bool ready = false;
FastRandom random;
glm::vec3 lightDir;
std::unique_ptr<Mesh<SkyboxVertex>> mesh;
std::unique_ptr<Batch3D> batch3d;
std::vector<SkySprite> sprites;
int frameid = 0;
float prevMie = -1.0f;
@ -53,30 +47,41 @@ class Skybox {
glm::mat4 rotation;
void drawStars(float angle, float opacity);
void drawBackground(
const Camera& camera, const Assets& assets, int width, int height
void drawBackground(const Camera& camera, int width, int height);
void drawSkySprites(
float daytime,
float angle,
float opacity,
const std::vector<SkySprite>& sprites
);
void refreshFace(uint face, Cubemap* cubemap);
void refreshFace(uint face, Cubemap& cubemap);
public:
Skybox(uint size, Shader& shader);
Skybox(uint size, const Assets& assets);
~Skybox();
void setMode(SkyMode mode);
void draw(
const DrawContext& pctx,
const Camera& camera,
const Assets& assets,
const Environment& environment,
const DrawContext& pctx,
const Camera& camera,
float daytime,
float fog
);
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 {
return ready;
}
void refresh(
const Environment& environment,
const DrawContext& pctx,
float t,
float mie,
const glm::vec3& tint,
const glm::vec3& hightlight,
uint quality
);
const glm::vec3 getLightDir() const {
const Cubemap* getCubemap() const;
const glm::vec3& getLightDir() const {
return lightDir;
}
};

View file

@ -58,7 +58,10 @@ void TextsRenderer::renderNote(
}
opacity = preset.xrayOpacity;
}
auto& font = assets.require<Font>(FONT_DEFAULT);
auto specifiedFont = assets.get<Font>(preset.font);
auto& font =
specifiedFont ? *specifiedFont : assets.require<Font>(FONT_DEFAULT);
glm::vec3 xvec = note.getAxisX();
glm::vec3 yvec = note.getAxisY();
@ -76,7 +79,10 @@ void TextsRenderer::renderNote(
yvec = camera.up;
}
float scale =
(1.0f - preset.perspective) * glm::pow(glm::distance(camera.position, pos), 1.0f-preset.perspective);
(1.0f - preset.perspective) *
glm::pow(
glm::distance(camera.position, pos), 1.0f - preset.perspective
);
xvec *= 1.0f + scale;
yvec *= 1.0f + scale;
}
@ -118,6 +124,7 @@ void TextsRenderer::renderNote(
}
auto color = preset.color;
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
font.draw(
batch,
text,

View file

@ -1,12 +1,39 @@
#include "WorldRenderer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "CloudsRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "Emitter.hpp"
#include "HandsRenderer.hpp"
#include "ModelBatch.hpp"
#include "NamedSkeletons.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "Skybox.hpp"
#include "TextNote.hpp"
#include "TextsRenderer.hpp"
#include "assets/Assets.hpp"
#include "assets/assets_util.hpp"
#include "coders/GLSLExtension.hpp"
#include "content/Content.hpp"
#include "engine/Engine.hpp"
#include "coders/GLSLExtension.hpp"
#include "frontend/LevelFrontend.hpp"
#include "frontend/ContentGfxCache.hpp"
#include "frontend/LevelFrontend.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/GBuffer.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/Cubemap.hpp"
#include "graphics/core/Texture.hpp"
#include "items/Inventory.hpp"
#include "items/ItemDef.hpp"
#include "items/ItemStack.hpp"
@ -24,33 +51,9 @@
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "world/LevelEvents.hpp"
#include "world/Weather.hpp"
#include "world/World.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Batch3D.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/Mesh.hpp"
#include "graphics/core/PostProcessing.hpp"
#include "graphics/core/Framebuffer.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/GBuffer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp"
#include "PrecipitationRenderer.hpp"
#include "HandsRenderer.hpp"
#include "NamedSkeletons.hpp"
#include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "ModelBatch.hpp"
#include "Skybox.hpp"
#include "Emitter.hpp"
#include "TextNote.hpp"
#include "CloudsRenderer.hpp"
#include "debug/Logger.hpp"
#include <assert.h>
#include <algorithm>
@ -58,14 +61,23 @@
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
static debug::Logger logger("world-renderer");
bool WorldRenderer::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false;
template <typename T>
static ObserverHandler observe_setting(
ObservableSetting<T>& setting, bool& dirtySettings
) {
return setting.observe([&dirtySettings](auto const&) {
dirtySettings = true;
});
}
WorldRenderer::WorldRenderer(
Engine& engine, LevelFrontend& frontend, Player& player
)
@ -73,6 +85,7 @@ WorldRenderer::WorldRenderer(
level(frontend.getLevel()),
player(player),
assets(*engine.getAssets()),
weather(frontend.getWeather()),
frustumCulling(std::make_unique<Frustum>()),
lineBatch(std::make_unique<LineBatch>()),
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
@ -105,8 +118,7 @@ WorldRenderer::WorldRenderer(
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
);
skybox = std::make_unique<Skybox>(
settings.graphics.skyboxResolution.get(),
assets.require<Shader>("skybox_gen")
settings.graphics.skyboxResolution.get(), assets
);
const auto& content = level.content;
@ -122,10 +134,41 @@ WorldRenderer::WorldRenderer(
shadowMapping = std::make_unique<Shadows>(level);
debugLines = std::make_unique<DebugLinesRenderer>(level);
cloudsRenderer = std::make_unique<CloudsRenderer>();
keepAlive(observe_setting(settings.graphics.advancedRender, dirtySettings));
keepAlive(observe_setting(settings.graphics.shadowsQuality, dirtySettings));
keepAlive(observe_setting(settings.graphics.ssao, dirtySettings));
}
WorldRenderer::~WorldRenderer() = default;
void WorldRenderer::refreshSettings() {
Shader* affectedShaders[] {
&assets.require<Shader>("main"),
&assets.require<Shader>("entity"),
&assets.require<Shader>("clouds"),
&assets.require<Shader>("translucent"),
&assets.require<PostEffect>("deferred_lighting").getShader(),
nullptr
};
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
std::vector<std::string> defines;
if (shadowsQuality != 0) defines.emplace_back("ENABLE_SHADOWS");
if (graphics.ssao.get()) defines.emplace_back("ENABLE_SSAO");
if (gbufferPipeline) defines.emplace_back("ADVANCED_RENDER");
logger.info() << "recompiling shaders due to settings change";
for (size_t i = 0; affectedShaders[i]; i++) {
affectedShaders[i]->recompile(defines);
}
}
static void setup_weather(Shader& shader, const Weather& weather) {
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
@ -158,9 +201,9 @@ void WorldRenderer::setupWorldShader(
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false);
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform1f("u_dayTime", level.getWorld().getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform1i("u_skybox", TARGET_SKYBOX);
shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
auto indices = level.content.getIndices();
// Light emission when an emissive item is chosen
@ -179,22 +222,23 @@ void WorldRenderer::setupWorldShader(
}
}
float WorldRenderer::calcFogFactor() const {
const auto& settings = engine.getSettings();
return 15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
}
void WorldRenderer::renderOpaque(
const DrawContext& ctx,
const Camera& camera,
const EngineSettings& settings,
bool hudVisible
) {
texts->render(ctx, camera, settings, hudVisible, false);
bool culling = engine.getSettings().graphics.frustumCulling.get();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
float fogFactor = calcFogFactor();
auto& entityShader = assets.require<Shader>("entity");
setupWorldShader(entityShader, camera, settings, fogFactor);
skybox->bind();
bool culling = engine.getSettings().graphics.frustumCulling.get();
if (culling) {
frustumCulling->update(camera.getProjView());
}
@ -211,27 +255,28 @@ void WorldRenderer::renderOpaque(
modelBatch->render();
particles->render(camera);
auto& shader = assets.require<Shader>("main");
auto& mainShader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& linesShader = assets.require<Shader>("lines");
setupWorldShader(shader, camera, settings, fogFactor);
setupWorldShader(mainShader, camera, settings, fogFactor);
chunksRenderer->drawChunks(camera, shader);
chunksRenderer->drawChunks(camera, mainShader);
blockWraps->draw(ctx);
int cloudsQuality = settings.graphics.cloudsQuality.get();
if (cloudsQuality > 0) {
setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsRenderer->draw(
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
if (level.environment.sky.clouds) {
int cloudsQuality = settings.graphics.cloudsQuality.get();
if (cloudsQuality > 0) {
setupWorldShader(cloudsShader, camera, settings, fogFactor);
cloudsRenderer->draw(
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
);
}
}
if (hudVisible) {
renderLines(camera, linesShader, ctx);
auto& linesShader = assets.require<Shader>("lines");
renderInWorldLines(camera, linesShader, ctx);
}
skybox->unbind();
}
void WorldRenderer::renderBlockSelection() {
@ -263,7 +308,7 @@ void WorldRenderer::renderBlockSelection() {
lineBatch->flush();
}
void WorldRenderer::renderLines(
void WorldRenderer::renderInWorldLines(
const Camera& camera, Shader& linesShader, const DrawContext& pctx
) {
linesShader.use();
@ -280,197 +325,230 @@ void WorldRenderer::renderLines(
}
}
void WorldRenderer::refreshSettings(Shader** shaders) {
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadowsQuality != 0,
graphics.ssao.get() && gbufferPipeline
};
if (
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao
) {
std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines);
}
prevCTShaderSettings = currentSettings;
}
}
void WorldRenderer::update(const Camera& camera, float delta) {
timer += delta;
chunksRenderer->update();
weather.update(delta);
precipitation->update(delta);
particles->update(camera, delta);
}
void WorldRenderer::renderFrameClassic(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
ctx.setFramebuffer(postProcessing.getFramebuffer());
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, calcFogFactor());
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrameAdvanced(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
float fogFactor = calcFogFactor();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
postProcessing.bindDepthBuffer();
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrame(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
auto projView = camera.getProjView();
auto world = level.getWorld();
const auto& vp = pctx.getViewport();
camera.setAspectRatio(vp.x / static_cast<float>(vp.y));
if (dirtySettings) {
refreshSettings();
dirtySettings = false;
}
auto& mainShader = assets.require<Shader>("main");
auto& entityShader = assets.require<Shader>("entity");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader,
&entityShader,
&cloudsShader,
&translucentShader,
&deferredShader,
nullptr
};
refreshSettings(affectedShaders);
const auto& worldInfo = world->getInfo();
const auto& worldInfo = level.getWorld().getInfo();
float clouds = weather.clouds();
clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh(
level.environment,
pctx,
worldInfo.daytime,
mie,
1.0f + clouds,
weather.skyTint(),
weather.highlight * random,
4
);
chunksRenderer->update();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
{
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
skybox->bind();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
renderOpaquePass(pctx, camera, hudVisible, postProcessing);
if (gbufferPipeline) {
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
}
{
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
} else {
ctx.setFramebuffer(postProcessing.getFramebuffer());
}
// Background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
if (debug && hudVisible) {
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush();
skybox->bind();
// Translucent blocks
{
auto sctx = ctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
// Weather effects
entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> weatherInstances {&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
skybox->unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
renderFrameAdvanced(pctx, camera, hudVisible, postProcessing);
} else {
renderFrameClassic(pctx, camera, hudVisible, postProcessing);
}
postProcessing.render(pctx, assets, timer, camera);
if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub();
renderHandsPass(pctx, camera);
}
{
auto bctx = pctx.sub();
bctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderBlockOverlay(pctx);
}
glActiveTexture(GL_TEXTURE0);
}
void WorldRenderer::renderOpaquePass(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
hands->render(camera);
display::clearDepth();
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
skybox->bind();
modelBatch->render();
modelBatch->setLightsOffset(glm::vec3());
skybox->unbind();
ctx.setDepthMask(false);
texts->render(wctx, camera, settings, hudVisible, true);
}
renderBlockOverlay(pctx);
}
glActiveTexture(GL_TEXTURE0);
void WorldRenderer::renderWeatherEffects(Camera& camera) {
const auto& settings = engine.getSettings();
auto& entityShader = assets.require<Shader>("entity");
entityShader.use();
setupWorldShader(entityShader, camera, settings, calcFogFactor());
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
}
void WorldRenderer::renderDebugLines(const DrawContext& ctx, Camera& camera) {
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
linesShader.uniformMatrix("u_projview", camera.getProjView());
lineBatch->flush();
}
void WorldRenderer::renderHandsPass(const DrawContext& pctx, Camera& camera) {
auto& entityShader = assets.require<Shader>("entity");
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
}
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
@ -486,13 +564,12 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
if (def.overlayTexture.empty()) {
return;
}
auto textureRegion = util::get_texture_region(
assets, def.overlayTexture, "blocks:notfound"
);
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();
@ -519,7 +596,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
batch3d->flush();
}
void WorldRenderer::clear() {
void WorldRenderer::resetCache() {
chunksRenderer->clear();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -48,10 +48,14 @@ void gui::Frame::draw(const DrawContext& pctx, const Assets& assets) {
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);
if (!outputTexture.empty()) {
assets.store(fbo->getSharedTexture(), outputTexture);
}
} else if (fbo == nullptr) {
fbo = std::make_unique<Framebuffer>(size.x, size.y, true);
assets.store(fbo->getSharedTexture(), outputTexture);
if (!outputTexture.empty()) {
assets.store(fbo->getSharedTexture(), outputTexture);
}
}
}

View file

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

View file

@ -13,7 +13,8 @@ InlineFrame::~InlineFrame() = default;
void InlineFrame::setSrc(const std::string& src) {
this->src = src;
if (document) {
scripting::on_ui_close(document.get(), nullptr);
scripting::on_ui_close(*document, nullptr);
clear();
document = nullptr;
root = nullptr;
}
@ -31,7 +32,7 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
root->setSize(size);
gui.postRunnable([this]() {
scripting::on_ui_open(this->document.get(), {});
scripting::on_ui_open(*this->document, {});
});
}

View file

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

View file

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

View file

@ -207,6 +207,9 @@ uint Label::getLinesNumber() const {
void Label::draw(const DrawContext& pctx, const Assets& assets) {
auto batch = pctx.getBatch2D();
auto font = assets.getShared<Font>(fontName);
if (font == nullptr) {
return;
}
cache.prepare(
font,
font->getMetrics(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -353,7 +353,9 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
auto font = assets.getShared<Font>(label->getFontName());
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
if (font != nullptr) {
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
}
glm::vec2 pos = calcPos();
@ -450,7 +452,7 @@ void TextBox::refreshLabel() {
uint height = label->getLinesNumber() * rawTextCache.metrics.lineHeight *
label->getLineInterval();
label->setSize(glm::vec2(label->getSize().x, height));
actualLength = height;
actualLengthY = height;
} else {
setScrollable(false);
}
@ -1131,10 +1133,10 @@ void TextBox::setOnControlCombination(key_handler handler) {
}
void TextBox::setFocusedColor(glm::vec4 color) {
this->focusedColor = color;
this->focusedColor = std::move(color);
}
glm::vec4 TextBox::getFocusedColor() const {
const glm::vec4& TextBox::getFocusedColor() const {
return focusedColor;
}
@ -1142,7 +1144,7 @@ void TextBox::setTextColor(glm::vec4 color) {
this->textColor = color;
}
glm::vec4 TextBox::getTextColor() const {
const glm::vec4& TextBox::getTextColor() const {
return textColor;
}
@ -1246,7 +1248,7 @@ void TextBox::setPadding(glm::vec4 padding) {
refresh();
}
glm::vec4 TextBox::getPadding() const {
const glm::vec4& TextBox::getPadding() const {
return padding;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -173,8 +173,12 @@ bool io::write_binary_json(
}
dv::value io::read_json(const path& filename) {
std::string text = io::read_string(filename);
return json::parse(filename.string(), text);
auto text = io::read_string(filename);
try {
return json::parse(filename.string(), text);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
dv::value io::read_binary_json(const path& file) {
@ -184,7 +188,12 @@ dv::value io::read_binary_json(const path& file) {
}
dv::value io::read_toml(const path& file) {
return toml::parse(file.string(), io::read_string(file));
auto text = io::read_string(file);
try {
return toml::parse(file.string(), text);
} catch (const parsing_error& err) {
throw err.toRuntimeError();
}
}
std::vector<std::string> io::read_list(const io::path& filename) {

View file

@ -15,8 +15,33 @@ public:
memory_streambuf(const memory_streambuf&) = delete;
memory_streambuf& operator=(const memory_streambuf&) = delete;
pos_type seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode mode = std::ios_base::in) override {
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
char* base = eback();
char* end = egptr();
if (!base || !end) return pos_type(off_type(-1));
std::ptrdiff_t current = gptr() - base;
std::ptrdiff_t newpos;
if (way == std::ios_base::beg) newpos = off;
else if (way == std::ios_base::cur) newpos = current + off;
else if (way == std::ios_base::end) newpos = (end - base) + off;
else return pos_type(off_type(-1));
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
setg(base, base + newpos, end);
return pos_type(off_type(newpos));
}
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
return seekoff(off_type(sp), std::ios_base::beg, mode);
}
protected:
int_type underflow() override {
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
return traits_type::eof();
}
@ -36,7 +61,7 @@ private:
class memory_view_streambuf : public std::streambuf {
public:
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
: buffer(std::move(buffer)) {
: buffer(buffer) {
char* base = const_cast<char*>(this->buffer.data());
char* end = base + this->buffer.size();
setg(base, base, end);
@ -45,8 +70,33 @@ public:
memory_view_streambuf(const memory_view_streambuf&) = delete;
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
pos_type seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode mode = std::ios_base::in) override {
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
char* base = eback();
char* end = egptr();
if (!base || !end) return pos_type(off_type(-1));
std::ptrdiff_t current = gptr() - base;
std::ptrdiff_t newpos;
if (way == std::ios_base::beg) newpos = off;
else if (way == std::ios_base::cur) newpos = current + off;
else if (way == std::ios_base::end) newpos = (end - base) + off;
else return pos_type(off_type(-1));
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
setg(base, base + newpos, end);
return pos_type(off_type(newpos));
}
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
return seekoff(off_type(sp), std::ios_base::beg, mode);
}
protected:
int_type underflow() override {
if (gptr() < egptr()) {
return traits_type::to_int_type(*gptr());
}
return traits_type::eof();
}

View file

@ -33,6 +33,8 @@ namespace io {
}
}
path(std::string_view str) : path(std::string(str)) {}
path(const char* str) : path(std::string(str)) {}
bool operator==(const std::string& other) const {

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