Merge branch 'main' of https://github.com/MihailRis/VoxelEngine-Cpp
This commit is contained in:
@@ -5,11 +5,13 @@
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <typeindex>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "util/stringutil.hpp"
|
||||
#include "graphics/core/TextureAnimation.hpp"
|
||||
|
||||
class Assets;
|
||||
@@ -84,6 +86,15 @@ public:
|
||||
return static_cast<T*>(found->second.get());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T& require(const std::string& name) const {
|
||||
T* asset = get<T>(name);
|
||||
if (asset == nullptr) {
|
||||
throw std::runtime_error(util::quote(name) + " not found");
|
||||
}
|
||||
return *asset;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<const assets_map*> getMap() const {
|
||||
const auto& mapIter = assets.find(typeid(T));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "objects/rigging.hpp"
|
||||
#include "util/ThreadPool.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "Assets.hpp"
|
||||
#include "assetload_funcs.hpp"
|
||||
|
||||
@@ -212,12 +213,6 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
|
||||
loader.tryAddSound(material.breakSound);
|
||||
}
|
||||
|
||||
addLayouts(
|
||||
0,
|
||||
"core",
|
||||
loader.getPaths()->getMainRoot() / fs::path("layouts"),
|
||||
loader
|
||||
);
|
||||
for (auto& entry : content->getPacks()) {
|
||||
auto pack = entry.second.get();
|
||||
auto& info = pack->getInfo();
|
||||
@@ -228,7 +223,11 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
|
||||
for (auto& entry : content->getSkeletons()) {
|
||||
auto& skeleton = *entry.second;
|
||||
for (auto& bone : skeleton.getBones()) {
|
||||
auto& model = bone->model.name;
|
||||
std::string model = bone->model.name;
|
||||
size_t pos = model.rfind('.');
|
||||
if (pos != std::string::npos) {
|
||||
model = model.substr(0, pos);
|
||||
}
|
||||
if (!model.empty()) {
|
||||
loader.add(
|
||||
AssetType::MODEL, MODELS_FOLDER + "/" + model, model
|
||||
@@ -236,6 +235,15 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [_, def] : content->items.getDefs()) {
|
||||
if (def->modelName.find(':') == std::string::npos) {
|
||||
loader.add(
|
||||
AssetType::MODEL,
|
||||
MODELS_FOLDER + "/" + def->modelName,
|
||||
def->modelName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "coders/imageio.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/obj.hpp"
|
||||
#include "coders/vec3.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "files/engine_paths.hpp"
|
||||
@@ -23,6 +24,7 @@
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/TextureAnimation.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "Assets.hpp"
|
||||
#include "AssetsLoader.hpp"
|
||||
|
||||
@@ -39,18 +41,32 @@ static bool animation(
|
||||
Atlas* dstAtlas
|
||||
);
|
||||
|
||||
assetload::postfunc assetload::
|
||||
texture(AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr<AssetCfg>&) {
|
||||
std::shared_ptr<ImageData> image(
|
||||
imageio::read(paths->find(filename + ".png").u8string()).release()
|
||||
);
|
||||
return [name, image](auto assets) {
|
||||
assets->store(Texture::from(image.get()), name);
|
||||
};
|
||||
assetload::postfunc assetload::texture(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string& filename,
|
||||
const std::string& name,
|
||||
const std::shared_ptr<AssetCfg>&
|
||||
) {
|
||||
auto actualFile = paths->find(filename + ".png").u8string();
|
||||
try {
|
||||
std::shared_ptr<ImageData> image(imageio::read(actualFile).release());
|
||||
return [name, image, actualFile](auto assets) {
|
||||
assets->store(Texture::from(image.get()), name);
|
||||
};
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << actualFile << ": " << err.what();
|
||||
return [](auto) {};
|
||||
}
|
||||
}
|
||||
|
||||
assetload::postfunc assetload::
|
||||
shader(AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr<AssetCfg>&) {
|
||||
assetload::postfunc assetload::shader(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string& filename,
|
||||
const std::string& name,
|
||||
const std::shared_ptr<AssetCfg>&
|
||||
) {
|
||||
fs::path vertexFile = paths->find(filename + ".glslv");
|
||||
fs::path fragmentFile = paths->find(filename + ".glslf");
|
||||
|
||||
@@ -181,8 +197,7 @@ assetload::postfunc assetload::sound(
|
||||
if (!fs::exists(variantFile)) {
|
||||
break;
|
||||
}
|
||||
baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)
|
||||
);
|
||||
baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM));
|
||||
}
|
||||
|
||||
auto sound = baseSound.release();
|
||||
@@ -191,21 +206,50 @@ assetload::postfunc assetload::sound(
|
||||
};
|
||||
}
|
||||
|
||||
assetload::postfunc assetload::
|
||||
model(AssetsLoader* loader, const ResPaths* paths, const std::string& file, const std::string& name, const std::shared_ptr<AssetCfg>&) {
|
||||
auto path = paths->find(file + ".obj");
|
||||
static void request_textures(AssetsLoader* loader, const model::Model& model) {
|
||||
for (auto& mesh : model.meshes) {
|
||||
if (mesh.texture.find('$') == std::string::npos) {
|
||||
auto filename = TEXTURES_FOLDER + "/" + mesh.texture;
|
||||
loader->add(
|
||||
AssetType::TEXTURE, filename, mesh.texture, nullptr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assetload::postfunc assetload::model(
|
||||
AssetsLoader* loader,
|
||||
const ResPaths* paths,
|
||||
const std::string& file,
|
||||
const std::string& name,
|
||||
const std::shared_ptr<AssetCfg>&
|
||||
) {
|
||||
auto path = paths->find(file + ".vec3");
|
||||
if (fs::exists(path)) {
|
||||
auto bytes = files::read_bytes_buffer(path);
|
||||
auto modelVEC3 = std::make_shared<vec3::File>(vec3::load(path.u8string(), bytes));
|
||||
return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) {
|
||||
for (auto& [modelName, model] : modelVEC3->models) {
|
||||
request_textures(loader, model.model);
|
||||
std::string fullName = name;
|
||||
if (name != modelName) {
|
||||
fullName += "." + modelName;
|
||||
}
|
||||
assets->store(
|
||||
std::make_unique<model::Model>(model.model),
|
||||
fullName
|
||||
);
|
||||
logger.info() << "store model " << util::quote(modelName)
|
||||
<< " as " << util::quote(fullName);
|
||||
}
|
||||
};
|
||||
}
|
||||
path = paths->find(file + ".obj");
|
||||
auto text = files::read_string(path);
|
||||
try {
|
||||
auto model = obj::parse(path.u8string(), text).release();
|
||||
return [=](Assets* assets) {
|
||||
for (auto& mesh : model->meshes) {
|
||||
if (mesh.texture.find('$') == std::string::npos) {
|
||||
auto filename = TEXTURES_FOLDER + "/" + mesh.texture;
|
||||
loader->add(
|
||||
AssetType::TEXTURE, filename, mesh.texture, nullptr
|
||||
);
|
||||
}
|
||||
}
|
||||
request_textures(loader, *model);
|
||||
assets->store(std::unique_ptr<model::Model>(model), name);
|
||||
};
|
||||
} catch (const parsing_error& err) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "assets_util.hpp"
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
|
||||
util::TextureRegion util::get_texture_region(
|
||||
const Assets& assets, const std::string& name, const std::string& fallback
|
||||
) {
|
||||
size_t sep = name.find(':');
|
||||
if (sep == std::string::npos) {
|
||||
return {assets.get<Texture>(name), UVRegion(0,0,1,1)};
|
||||
} else {
|
||||
auto atlas = assets.get<Atlas>(name.substr(0, sep));
|
||||
if (atlas) {
|
||||
if (auto reg = atlas->getIf(name.substr(sep+1))) {
|
||||
return {atlas->getTexture(), *reg};
|
||||
} else if (!fallback.empty()){
|
||||
return util::get_texture_region(assets, fallback, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
return {nullptr, UVRegion()};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "maths/UVRegion.hpp"
|
||||
|
||||
class Assets;
|
||||
class Texture;
|
||||
|
||||
namespace util {
|
||||
struct TextureRegion {
|
||||
const Texture* texture;
|
||||
UVRegion region;
|
||||
};
|
||||
|
||||
TextureRegion get_texture_region(
|
||||
const Assets& assets,
|
||||
const std::string& name,
|
||||
const std::string& fallback
|
||||
);
|
||||
}
|
||||
@@ -107,6 +107,14 @@ void ByteReader::checkMagic(const char* data, size_t size) {
|
||||
pos += size;
|
||||
}
|
||||
|
||||
void ByteReader::get(char* dst, size_t size) {
|
||||
if (pos + size > this->size) {
|
||||
throw std::runtime_error("buffer underflow");
|
||||
}
|
||||
std::memcpy(dst, data+pos, size);
|
||||
pos += size;
|
||||
}
|
||||
|
||||
ubyte ByteReader::get() {
|
||||
if (pos == size) {
|
||||
throw std::runtime_error("buffer underflow");
|
||||
|
||||
@@ -52,6 +52,8 @@ public:
|
||||
ByteReader(const ubyte* data);
|
||||
|
||||
void checkMagic(const char* data, size_t size);
|
||||
/// @brief Get N bytes
|
||||
void get(char* dst, size_t size);
|
||||
/// @brief Read one byte (unsigned 8 bit integer)
|
||||
ubyte get();
|
||||
/// @brief Read one byte (unsigned 8 bit integer) without pointer move
|
||||
|
||||
@@ -360,6 +360,13 @@ std::string BasicParser::parseString(char quote, bool closeRequired) {
|
||||
ss << (char)parseSimpleInt(8);
|
||||
continue;
|
||||
}
|
||||
if (c == 'u') {
|
||||
int codepoint = parseSimpleInt(16);
|
||||
ubyte bytes[4];
|
||||
int size = util::encode_utf8(codepoint, bytes);
|
||||
ss.write(reinterpret_cast<char*>(bytes), size);
|
||||
continue;
|
||||
}
|
||||
switch (c) {
|
||||
case 'n': ss << '\n'; break;
|
||||
case 'r': ss << '\r'; break;
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "vec3.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "byte_utils.hpp"
|
||||
#include "util/data_io.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "graphics/core/Model.hpp"
|
||||
|
||||
inline constexpr int VERSION = 1;
|
||||
|
||||
inline constexpr int FLAG_ZLIB = 0x1;
|
||||
inline constexpr int FLAG_16BIT_INDICES = 0x2;
|
||||
|
||||
using namespace vec3;
|
||||
|
||||
vec3::Model::~Model() = default;
|
||||
|
||||
enum AttributeType {
|
||||
POSITION = 0,
|
||||
UV,
|
||||
NORMAL,
|
||||
COLOR
|
||||
};
|
||||
|
||||
struct VertexAttribute {
|
||||
AttributeType type;
|
||||
int flags;
|
||||
util::Buffer<float> data;
|
||||
|
||||
VertexAttribute() = default;
|
||||
|
||||
VertexAttribute(VertexAttribute&&) = default;
|
||||
|
||||
VertexAttribute& operator=(VertexAttribute&& o) {
|
||||
type = o.type;
|
||||
flags = o.flags;
|
||||
data = std::move(o.data);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static VertexAttribute load_attribute(ByteReader& reader) {
|
||||
auto type = static_cast<AttributeType>(reader.get());
|
||||
int flags = reader.get();
|
||||
assert(type >= POSITION && flags <= COLOR);
|
||||
if (flags != 0) {
|
||||
throw std::runtime_error("attribute compression is not supported yet");
|
||||
}
|
||||
int size = reader.getInt32();
|
||||
|
||||
util::Buffer<float> data(size / sizeof(float));
|
||||
reader.get(reinterpret_cast<char*>(data.data()), size);
|
||||
if (dataio::is_big_endian()) {
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i] = dataio::swap(data[i]);
|
||||
}
|
||||
}
|
||||
return VertexAttribute {type, flags, std::move(data)};
|
||||
}
|
||||
|
||||
static model::Mesh build_mesh(
|
||||
const std::vector<VertexAttribute>& attrs,
|
||||
const util::Buffer<uint16_t>& indices,
|
||||
const std::string& texture
|
||||
) {
|
||||
const glm::vec3* coords = nullptr;
|
||||
const glm::vec2* uvs = nullptr;
|
||||
const glm::vec3* normals = nullptr;
|
||||
|
||||
int coordsIndex, uvsIndex, normalsIndex;
|
||||
|
||||
for (int i = 0; i < attrs.size(); i++) {
|
||||
const auto& attr = attrs[i];
|
||||
switch (attr.type) {
|
||||
case POSITION:
|
||||
coords = reinterpret_cast<const glm::vec3*>(attr.data.data());
|
||||
coordsIndex = i;
|
||||
break;
|
||||
case UV:
|
||||
uvs = reinterpret_cast<const glm::vec2*>(attr.data.data());
|
||||
uvsIndex = i;
|
||||
break;
|
||||
case NORMAL:
|
||||
normals = reinterpret_cast<const glm::vec3*>(attr.data.data());
|
||||
normalsIndex = i;
|
||||
break;
|
||||
case COLOR: // unused
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::vector<model::Vertex> vertices;
|
||||
int attrsCount = attrs.size();
|
||||
int verticesCount = indices.size() / attrsCount;
|
||||
for (int i = 0; i < verticesCount; i++) {
|
||||
model::Vertex vertex {};
|
||||
if (coords) {
|
||||
vertex.coord = coords[indices[i * attrsCount + coordsIndex]];
|
||||
}
|
||||
if (uvs) {
|
||||
vertex.uv = uvs[indices[i * attrsCount + uvsIndex]];
|
||||
}
|
||||
if (normals) {
|
||||
vertex.normal = normals[indices[i * attrsCount + normalsIndex]];
|
||||
} else if (coords) {
|
||||
// Flat normal calculation
|
||||
int idx = (i / 3) * 3;
|
||||
auto a = coords[indices[idx * attrsCount + coordsIndex]];
|
||||
auto b = coords[indices[(idx + 1) * attrsCount + coordsIndex]];
|
||||
auto c = coords[indices[(idx + 2) * attrsCount + coordsIndex]];
|
||||
vertex.normal = glm::normalize(glm::cross(b - a, c - a));
|
||||
}
|
||||
vertices.push_back(std::move(vertex));
|
||||
}
|
||||
return model::Mesh {texture, std::move(vertices)};
|
||||
}
|
||||
|
||||
static model::Mesh load_mesh(
|
||||
ByteReader& reader, const std::vector<Material>& materials
|
||||
) {
|
||||
int triangleCount = reader.getInt32();
|
||||
int materialId = reader.getInt16();
|
||||
int flags = reader.getInt16();
|
||||
int attributeCount = reader.getInt16();
|
||||
if (flags == FLAG_ZLIB) {
|
||||
throw std::runtime_error("compression is not supported yet");
|
||||
}
|
||||
std::vector<VertexAttribute> attributes;
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
attributes.push_back(load_attribute(reader));
|
||||
}
|
||||
|
||||
util::Buffer<uint16_t> indices(triangleCount * 3 * attributeCount);
|
||||
if ((flags & FLAG_16BIT_INDICES) == 0){
|
||||
util::Buffer<uint8_t> smallIndices(indices.size());
|
||||
reader.get(
|
||||
reinterpret_cast<char*>(smallIndices.data()),
|
||||
indices.size() * sizeof(uint8_t)
|
||||
);
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
indices[i] = smallIndices[i];
|
||||
}
|
||||
} else {
|
||||
reader.get(
|
||||
reinterpret_cast<char*>(indices.data()),
|
||||
indices.size() * sizeof(uint16_t)
|
||||
);
|
||||
}
|
||||
if (dataio::is_big_endian()) {
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
indices[i] = dataio::swap(indices[i]);
|
||||
}
|
||||
}
|
||||
return build_mesh(
|
||||
attributes,
|
||||
indices,
|
||||
materials.at(materialId).name
|
||||
);
|
||||
}
|
||||
|
||||
static Model load_model(
|
||||
ByteReader& reader, const std::vector<Material>& materials
|
||||
) {
|
||||
int nameLength = reader.getInt16();
|
||||
assert(nameLength >= 0);
|
||||
float x = reader.getFloat32();
|
||||
float y = reader.getFloat32();
|
||||
float z = reader.getFloat32();
|
||||
int meshCount = reader.getInt32();
|
||||
assert(meshCount >= 0);
|
||||
|
||||
std::vector<model::Mesh> meshes;
|
||||
for (int i = 0; i < meshCount; i++) {
|
||||
meshes.push_back(load_mesh(reader, materials));
|
||||
}
|
||||
util::Buffer<char> chars(nameLength);
|
||||
reader.get(chars.data(), nameLength);
|
||||
std::string name(chars.data(), nameLength);
|
||||
|
||||
glm::vec3 offset {x, y, z};
|
||||
for (auto& mesh : meshes) {
|
||||
for (auto& vertex : mesh.vertices) {
|
||||
vertex.coord -= offset;
|
||||
}
|
||||
}
|
||||
return Model {std::move(name), model::Model {std::move(meshes)}, {x, y, z}};
|
||||
}
|
||||
|
||||
static Material load_material(ByteReader& reader) {
|
||||
int flags = reader.getInt16();
|
||||
int nameLength = reader.getInt16();
|
||||
assert(nameLength >= 0);
|
||||
util::Buffer<char> chars(nameLength);
|
||||
reader.get(chars.data(), nameLength);
|
||||
std::string name(chars.data(), nameLength);
|
||||
return Material {flags, std::move(name)};
|
||||
}
|
||||
|
||||
File vec3::load(
|
||||
const std::string_view file, const util::Buffer<ubyte>& src
|
||||
) {
|
||||
ByteReader reader(src.data(), src.size());
|
||||
|
||||
// Header
|
||||
reader.checkMagic("\0\0VEC3\0\0", 8);
|
||||
int version = reader.getInt16();
|
||||
int reserved = reader.getInt16();
|
||||
if (version > VERSION) {
|
||||
throw std::runtime_error("unsupported VEC3 version");
|
||||
}
|
||||
assert(reserved == 0);
|
||||
|
||||
// Body
|
||||
int materialCount = reader.getInt16();
|
||||
int modelCount = reader.getInt16();
|
||||
assert(materialCount >= 0);
|
||||
assert(modelCount >= 0);
|
||||
|
||||
std::vector<Material> materials;
|
||||
for (int i = 0; i < materialCount; i++) {
|
||||
materials.push_back(load_material(reader));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Model> models;
|
||||
for (int i = 0; i < modelCount; i++) {
|
||||
Model model = load_model(reader, materials);
|
||||
models[model.name] = std::move(model);
|
||||
}
|
||||
return File {std::move(models), std::move(materials)};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "util/Buffer.hpp"
|
||||
#include "graphics/core/Model.hpp"
|
||||
|
||||
/// See /doc/specs/vec3_model_spec.md
|
||||
namespace vec3 {
|
||||
struct Material {
|
||||
int flags;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct Model {
|
||||
std::string name;
|
||||
model::Model model;
|
||||
glm::vec3 origin;
|
||||
|
||||
Model& operator=(Model&&) = default;
|
||||
|
||||
~Model();
|
||||
};
|
||||
|
||||
struct File {
|
||||
std::unordered_map<std::string, Model> models;
|
||||
std::vector<Material> materials;
|
||||
|
||||
File(File&&) = default;
|
||||
|
||||
File& operator=(File&&) = default;
|
||||
};
|
||||
|
||||
File load(const std::string_view file, const util::Buffer<ubyte>& src);
|
||||
}
|
||||
@@ -44,6 +44,8 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
def.rt.hitboxes[i].push_back(aabb);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
def.rt.hitboxes->emplace_back(AABB(glm::vec3(1.0f)));
|
||||
}
|
||||
|
||||
blockDefsIndices.push_back(&def);
|
||||
|
||||
@@ -326,6 +326,7 @@ void ContentLoader::loadBlock(
|
||||
root.at("ui-layout").get(def.uiLayout);
|
||||
root.at("inventory-size").get(def.inventorySize);
|
||||
root.at("tick-interval").get(def.tickInterval);
|
||||
root.at("overlay-texture").get(def.overlayTexture);
|
||||
|
||||
if (root.has("fields")) {
|
||||
def.dataStruct = std::make_unique<StructLayout>();
|
||||
@@ -418,17 +419,18 @@ void ContentLoader::loadItem(
|
||||
std::string iconTypeStr = "";
|
||||
root.at("icon-type").get(iconTypeStr);
|
||||
if (iconTypeStr == "none") {
|
||||
def.iconType = item_icon_type::none;
|
||||
def.iconType = ItemIconType::NONE;
|
||||
} else if (iconTypeStr == "block") {
|
||||
def.iconType = item_icon_type::block;
|
||||
def.iconType = ItemIconType::BLOCK;
|
||||
} else if (iconTypeStr == "sprite") {
|
||||
def.iconType = item_icon_type::sprite;
|
||||
def.iconType = ItemIconType::SPRITE;
|
||||
} else if (iconTypeStr.length()) {
|
||||
logger.error() << name << ": unknown icon type" << iconTypeStr;
|
||||
}
|
||||
root.at("icon").get(def.icon);
|
||||
root.at("placing-block").get(def.placingBlock);
|
||||
root.at("script-name").get(def.scriptName);
|
||||
root.at("model-name").get(def.modelName);
|
||||
root.at("stack-size").get(def.stackSize);
|
||||
|
||||
// item light emission [r, g, b] where r,g,b in range [0..15]
|
||||
@@ -532,7 +534,7 @@ void ContentLoader::loadBlock(
|
||||
auto& item = builder.items.create(full + BLOCK_ITEM_SUFFIX);
|
||||
item.generated = true;
|
||||
item.caption = def.caption;
|
||||
item.iconType = item_icon_type::block;
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = full;
|
||||
item.placingBlock = full;
|
||||
|
||||
|
||||
@@ -202,6 +202,16 @@ void ContentLoader::loadGenerator(
|
||||
map.at("biome-parameters").get(def.biomeParameters);
|
||||
map.at("biome-bpd").get(def.biomesBPD);
|
||||
map.at("heights-bpd").get(def.heightsBPD);
|
||||
std::string interpName;
|
||||
map.at("heights-interpolation").get(interpName);
|
||||
if (auto interp = InterpolationType_from(interpName)) {
|
||||
def.heightsInterpolation = *interp;
|
||||
}
|
||||
map.at("biomes-interpolation").get(interpName);
|
||||
if (auto interp = InterpolationType_from(interpName)) {
|
||||
def.biomesInterpolation = *interp;
|
||||
}
|
||||
|
||||
map.at("sea-level").get(def.seaLevel);
|
||||
map.at("wide-structs-chunks-radius").get(def.wideStructsChunksRadius);
|
||||
if (map.has("heightmap-inputs")) {
|
||||
|
||||
+4
-4
@@ -25,13 +25,13 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
}
|
||||
{
|
||||
ItemDef& item = builder->items.create(CORE_EMPTY);
|
||||
item.iconType = item_icon_type::none;
|
||||
item.iconType = ItemIconType::NONE;
|
||||
}
|
||||
|
||||
auto bindsFile = paths->getResourcesFolder()/fs::path("bindings.toml");
|
||||
if (fs::is_regular_file(bindsFile)) {
|
||||
Events::loadBindings(
|
||||
bindsFile.u8string(), files::read_string(bindsFile)
|
||||
bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
block.hitboxes = {AABB()};
|
||||
block.breakable = false;
|
||||
ItemDef& item = builder->items.create(CORE_OBSTACLE+".item");
|
||||
item.iconType = item_icon_type::block;
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = CORE_OBSTACLE;
|
||||
item.placingBlock = CORE_OBSTACLE;
|
||||
item.caption = block.caption;
|
||||
@@ -59,7 +59,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
|
||||
block.hitboxes = {AABB()};
|
||||
block.obstacle = false;
|
||||
ItemDef& item = builder->items.create(CORE_STRUCT_AIR+".item");
|
||||
item.iconType = item_icon_type::block;
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = CORE_STRUCT_AIR;
|
||||
item.placingBlock = CORE_STRUCT_AIR;
|
||||
item.caption = block.caption;
|
||||
|
||||
@@ -27,6 +27,8 @@ inline const std::string BIND_PLAYER_FLIGHT = "player.flight";
|
||||
inline const std::string BIND_PLAYER_ATTACK = "player.attack";
|
||||
inline const std::string BIND_PLAYER_BUILD = "player.build";
|
||||
inline const std::string BIND_PLAYER_PICK = "player.pick";
|
||||
inline const std::string BIND_PLAYER_FAST_INTERACTOIN =
|
||||
"player.fast_interaction";
|
||||
inline const std::string BIND_HUD_INVENTORY = "hud.inventory";
|
||||
|
||||
class EnginePaths;
|
||||
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
return value;
|
||||
}
|
||||
|
||||
const T& getDefault() const {
|
||||
return initial;
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return value;
|
||||
}
|
||||
|
||||
+25
-4
@@ -20,6 +20,7 @@
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "graphics/render/ModelsGenerator.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
@@ -131,7 +132,7 @@ void Engine::loadControls() {
|
||||
if (fs::is_regular_file(controls_file)) {
|
||||
logger.info() << "loading controls";
|
||||
std::string text = files::read_string(controls_file);
|
||||
Events::loadBindings(controls_file.u8string(), text);
|
||||
Events::loadBindings(controls_file.u8string(), text, BindType::BIND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,8 +185,11 @@ void Engine::mainloop() {
|
||||
if (!Window::isIconified()) {
|
||||
renderFrame(batch);
|
||||
}
|
||||
Window::setFramerate(Window::isIconified() ? 20 :
|
||||
settings.display.framerate.get());
|
||||
Window::setFramerate(
|
||||
Window::isIconified() && settings.display.limitFpsIconified.get()
|
||||
? 20
|
||||
: settings.display.framerate.get()
|
||||
);
|
||||
|
||||
processPostRunnables();
|
||||
|
||||
@@ -280,6 +284,17 @@ void Engine::loadAssets() {
|
||||
}
|
||||
}
|
||||
assets = std::move(new_assets);
|
||||
|
||||
if (content) {
|
||||
for (auto& [name, def] : content->items.getDefs()) {
|
||||
assets->store(
|
||||
std::make_unique<model::Model>(
|
||||
ModelsGenerator::generate(*def, *content, *assets)
|
||||
),
|
||||
name + ".model"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void load_configs(const fs::path& root) {
|
||||
@@ -287,12 +302,14 @@ static void load_configs(const fs::path& root) {
|
||||
auto bindsFile = configFolder/fs::path("bindings.toml");
|
||||
if (fs::is_regular_file(bindsFile)) {
|
||||
Events::loadBindings(
|
||||
bindsFile.u8string(), files::read_string(bindsFile)
|
||||
bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::loadContent() {
|
||||
scripting::cleanup();
|
||||
|
||||
auto resdir = paths->getResourcesFolder();
|
||||
|
||||
std::vector<std::string> names;
|
||||
@@ -338,6 +355,7 @@ void Engine::loadContent() {
|
||||
}
|
||||
|
||||
void Engine::resetContent() {
|
||||
scripting::cleanup();
|
||||
auto resdir = paths->getResourcesFolder();
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
@@ -388,6 +406,9 @@ double Engine::getDelta() const {
|
||||
}
|
||||
|
||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
// unblock all bindings
|
||||
Events::enableBindings();
|
||||
// reset audio channels (stop all sources)
|
||||
audio::reset_channel(audio::get_channel_index("regular"));
|
||||
audio::reset_channel(audio::get_channel_index("ambient"));
|
||||
this->screen = std::move(screen);
|
||||
|
||||
+7
-8
@@ -75,7 +75,11 @@ std::unique_ptr<ubyte[]> files::read_bytes(
|
||||
const fs::path& filename, size_t& length
|
||||
) {
|
||||
std::ifstream input(filename, std::ios::binary);
|
||||
if (!input.is_open()) return nullptr;
|
||||
if (!input.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"could not to load file '" + filename.string() + "'"
|
||||
);
|
||||
}
|
||||
input.seekg(0, std::ios_base::end);
|
||||
length = input.tellg();
|
||||
input.seekg(0, std::ios_base::beg);
|
||||
@@ -102,16 +106,11 @@ std::vector<ubyte> files::read_bytes(const fs::path& filename) {
|
||||
|
||||
std::string files::read_string(const fs::path& filename) {
|
||||
size_t size;
|
||||
std::unique_ptr<ubyte[]> bytes(read_bytes(filename, size));
|
||||
if (bytes == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"could not to load file '" + filename.string() + "'"
|
||||
);
|
||||
}
|
||||
auto bytes = read_bytes(filename, size);
|
||||
return std::string((const char*)bytes.get(), size);
|
||||
}
|
||||
|
||||
bool files::write_string(const fs::path& filename, const std::string content) {
|
||||
bool files::write_string(const fs::path& filename, std::string_view content) {
|
||||
std::ofstream file(filename);
|
||||
if (!file) {
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ namespace files {
|
||||
uint append_bytes(const fs::path& file, const ubyte* data, size_t size);
|
||||
|
||||
/// @brief Write string to the file
|
||||
bool write_string(const fs::path& filename, const std::string content);
|
||||
bool write_string(const fs::path& filename, std::string_view content);
|
||||
|
||||
/// @brief Write dynamic data to the JSON file
|
||||
/// @param nice if true, human readable format will be used, otherwise
|
||||
|
||||
@@ -51,6 +51,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
||||
builder.add("samples", &settings.display.samples);
|
||||
builder.add("framerate", &settings.display.framerate);
|
||||
builder.add("fullscreen", &settings.display.fullscreen);
|
||||
builder.add("limit-fps-iconified", &settings.display.limitFpsIconified);
|
||||
|
||||
builder.section("camera");
|
||||
builder.add("sensitivity", &settings.camera.sensitivity);
|
||||
@@ -102,6 +103,26 @@ dv::value SettingsHandler::getValue(const std::string& name) const {
|
||||
}
|
||||
}
|
||||
|
||||
dv::value SettingsHandler::getDefault(const std::string& name) const {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
throw std::runtime_error("setting '" + name + "' does not exist");
|
||||
}
|
||||
auto setting = found->second;
|
||||
|
||||
if (auto number = dynamic_cast<NumberSetting*>(setting)) {
|
||||
return static_cast<number_t>(number->getDefault());
|
||||
} else if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
|
||||
return static_cast<integer_t>(integer->getDefault());
|
||||
} else if (auto flag = dynamic_cast<FlagSetting*>(setting)) {
|
||||
return flag->getDefault();
|
||||
} else if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
||||
return string->getDefault();
|
||||
} else {
|
||||
throw std::runtime_error("type is not implemented for '" + name + "'");
|
||||
}
|
||||
}
|
||||
|
||||
std::string SettingsHandler::toString(const std::string& name) const {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
SettingsHandler(EngineSettings& settings);
|
||||
|
||||
dv::value getValue(const std::string& name) const;
|
||||
dv::value getDefault(const std::string& name) const;
|
||||
void setValue(const std::string& name, const dv::value& value);
|
||||
std::string toString(const std::string& name) const;
|
||||
Setting* getSetting(const std::string& name) const;
|
||||
|
||||
@@ -37,10 +37,10 @@ LevelFrontend::LevelFrontend(
|
||||
auto soundsCamera = currentPlayer->currentCamera.get();
|
||||
if (soundsCamera == currentPlayer->spCamera.get() ||
|
||||
soundsCamera == currentPlayer->tpCamera.get()) {
|
||||
soundsCamera = currentPlayer->camera.get();
|
||||
soundsCamera = currentPlayer->fpCamera.get();
|
||||
}
|
||||
bool relative = player == currentPlayer &&
|
||||
soundsCamera == currentPlayer->camera.get();
|
||||
soundsCamera == currentPlayer->fpCamera.get();
|
||||
if (!relative) {
|
||||
pos = player->getPosition();
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ void Hud::processInput(bool visible) {
|
||||
setPause(true);
|
||||
}
|
||||
}
|
||||
if (!pause && Events::active(BIND_DEVTOOLS_CONSOLE)) {
|
||||
if (!pause && Events::jactive(BIND_DEVTOOLS_CONSOLE)) {
|
||||
showOverlay(assets->get<UiDocument>("core:console"), false);
|
||||
}
|
||||
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
|
||||
|
||||
@@ -37,7 +37,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr<Level> level)
|
||||
auto menu = engine->getGUI()->getMenu();
|
||||
menu->reset();
|
||||
|
||||
controller = std::make_unique<LevelController>(settings, std::move(level));
|
||||
controller = std::make_unique<LevelController>(engine, std::move(level));
|
||||
frontend = std::make_unique<LevelFrontend>(controller->getPlayer(), controller.get(), assets);
|
||||
|
||||
worldRenderer = std::make_unique<WorldRenderer>(engine, frontend.get(), controller->getPlayer());
|
||||
@@ -48,7 +48,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr<Level> level)
|
||||
worldRenderer->clear();
|
||||
}));
|
||||
keepAlive(settings.camera.fov.observe([=](double value) {
|
||||
controller->getPlayer()->camera->setFov(glm::radians(value));
|
||||
controller->getPlayer()->fpCamera->setFov(glm::radians(value));
|
||||
}));
|
||||
keepAlive(Events::getBinding(BIND_CHUNKS_RELOAD).onactived.add([=](){
|
||||
controller->getLevel()->chunks->saveAndClear();
|
||||
@@ -93,7 +93,7 @@ void LevelScreen::saveWorldPreview() {
|
||||
int previewSize = settings.ui.worldPreviewSize.get();
|
||||
|
||||
// camera special copy for world preview
|
||||
Camera camera = *player->camera;
|
||||
Camera camera = *player->fpCamera;
|
||||
camera.setFov(glm::radians(70.0f));
|
||||
|
||||
DrawContext pctx(nullptr, {Window::width, Window::height}, batch.get());
|
||||
@@ -101,7 +101,7 @@ void LevelScreen::saveWorldPreview() {
|
||||
Viewport viewport(previewSize * 1.5, previewSize);
|
||||
DrawContext ctx(&pctx, viewport, batch.get());
|
||||
|
||||
worldRenderer->draw(ctx, &camera, false, true, 0.0f, postProcessing.get());
|
||||
worldRenderer->draw(ctx, camera, false, true, 0.0f, postProcessing.get());
|
||||
auto image = postProcessing->toImage();
|
||||
image->flipY();
|
||||
imageio::write(paths->resolve("world:preview.png").u8string(), image.get());
|
||||
@@ -164,7 +164,9 @@ void LevelScreen::draw(float delta) {
|
||||
Viewport viewport(Window::width, Window::height);
|
||||
DrawContext ctx(nullptr, viewport, batch.get());
|
||||
|
||||
worldRenderer->draw(ctx, camera.get(), hudVisible, hud->isPause(), delta, postProcessing.get());
|
||||
worldRenderer->draw(
|
||||
ctx, *camera, hudVisible, hud->isPause(), delta, postProcessing.get()
|
||||
);
|
||||
|
||||
if (hudVisible) {
|
||||
hud->draw(ctx);
|
||||
|
||||
@@ -75,7 +75,7 @@ void Batch2D::vertex(
|
||||
buffer[index++] = a;
|
||||
}
|
||||
|
||||
void Batch2D::texture(Texture* new_texture){
|
||||
void Batch2D::texture(const Texture* new_texture){
|
||||
if (currentTexture == new_texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class Batch2D : public Flushable {
|
||||
std::unique_ptr<Texture> blank;
|
||||
size_t index;
|
||||
glm::vec4 color;
|
||||
Texture* currentTexture;
|
||||
const Texture* currentTexture;
|
||||
DrawPrimitive primitive = DrawPrimitive::triangle;
|
||||
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
~Batch2D();
|
||||
|
||||
void begin();
|
||||
void texture(Texture* texture);
|
||||
void texture(const Texture* texture);
|
||||
void untexture();
|
||||
void setRegion(UVRegion region);
|
||||
void sprite(float x, float y, float w, float h, const UVRegion& region, glm::vec4 tint);
|
||||
|
||||
@@ -106,7 +106,7 @@ void Batch3D::face(
|
||||
tint.r, tint.g, tint.b, tint.a);
|
||||
}
|
||||
|
||||
void Batch3D::texture(Texture* new_texture){
|
||||
void Batch3D::texture(const Texture* new_texture){
|
||||
if (currentTexture == new_texture)
|
||||
return;
|
||||
flush();
|
||||
|
||||
@@ -18,7 +18,7 @@ class Batch3D : public Flushable {
|
||||
std::unique_ptr<Texture> blank;
|
||||
size_t index;
|
||||
|
||||
Texture* currentTexture;
|
||||
const Texture* currentTexture;
|
||||
|
||||
void vertex(
|
||||
float x, float y, float z,
|
||||
@@ -47,11 +47,36 @@ public:
|
||||
~Batch3D();
|
||||
|
||||
void begin();
|
||||
void texture(Texture* texture);
|
||||
void sprite(glm::vec3 pos, glm::vec3 up, glm::vec3 right, float w, float h, const UVRegion& uv, glm::vec4 tint);
|
||||
void xSprite(float w, float h, const UVRegion& uv, const glm::vec4 tint, bool shading=true);
|
||||
void cube(const glm::vec3 coords, const glm::vec3 size, const UVRegion(&texfaces)[6], const glm::vec4 tint, bool shading=true);
|
||||
void blockCube(const glm::vec3 size, const UVRegion(&texfaces)[6], const glm::vec4 tint, bool shading=true);
|
||||
void texture(const Texture* texture);
|
||||
void sprite(
|
||||
glm::vec3 pos,
|
||||
glm::vec3 up,
|
||||
glm::vec3 right,
|
||||
float w,
|
||||
float h,
|
||||
const UVRegion& uv,
|
||||
glm::vec4 tint
|
||||
);
|
||||
void xSprite(
|
||||
float w,
|
||||
float h,
|
||||
const UVRegion& uv,
|
||||
const glm::vec4 tint,
|
||||
bool shading = true
|
||||
);
|
||||
void cube(
|
||||
const glm::vec3 coords,
|
||||
const glm::vec3 size,
|
||||
const UVRegion (&texfaces)[6],
|
||||
const glm::vec4 tint,
|
||||
bool shading = true
|
||||
);
|
||||
void blockCube(
|
||||
const glm::vec3 size,
|
||||
const UVRegion (&texfaces)[6],
|
||||
const glm::vec4 tint,
|
||||
bool shading = true
|
||||
);
|
||||
void vertex(glm::vec3 pos, glm::vec2 uv, glm::vec4 tint);
|
||||
void point(glm::vec3 pos, glm::vec4 tint);
|
||||
void flush() override;
|
||||
|
||||
@@ -30,10 +30,10 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
|
||||
}
|
||||
}
|
||||
|
||||
void Cubemap::bind(){
|
||||
void Cubemap::bind() const {
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
|
||||
}
|
||||
|
||||
void Cubemap::unbind() {
|
||||
void Cubemap::unbind() const {
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ class Cubemap : public GLTexture {
|
||||
public:
|
||||
Cubemap(uint width, uint height, ImageFormat format);
|
||||
|
||||
virtual void bind() override;
|
||||
virtual void unbind() override;
|
||||
virtual void bind() const override;
|
||||
virtual void unbind() const override;
|
||||
};
|
||||
|
||||
@@ -33,11 +33,11 @@ GLTexture::~GLTexture() {
|
||||
glDeleteTextures(1, &id);
|
||||
}
|
||||
|
||||
void GLTexture::bind(){
|
||||
void GLTexture::bind() const {
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
}
|
||||
|
||||
void GLTexture::unbind() {
|
||||
void GLTexture::unbind() const {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ public:
|
||||
GLTexture(const ubyte* data, uint width, uint height, ImageFormat format);
|
||||
virtual ~GLTexture();
|
||||
|
||||
virtual void bind() override;
|
||||
virtual void unbind() override;
|
||||
virtual void bind() const override;
|
||||
virtual void unbind() const override;
|
||||
virtual void reload(const ubyte* data);
|
||||
|
||||
void setNearestFilter();
|
||||
|
||||
@@ -29,6 +29,11 @@ void Mesh::addBox(glm::vec3 pos, glm::vec3 size) {
|
||||
addPlane(pos-X*size, Z*size, Y*size, -X);
|
||||
}
|
||||
|
||||
void Mesh::scale(const glm::vec3& size) {
|
||||
for (auto& vertex : vertices) {
|
||||
vertex.coord *= size;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::clean() {
|
||||
meshes.erase(
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace model {
|
||||
|
||||
void addPlane(glm::vec3 pos, glm::vec3 right, glm::vec3 up, glm::vec3 norm);
|
||||
void addBox(glm::vec3 pos, glm::vec3 size);
|
||||
void scale(const glm::vec3& size);
|
||||
};
|
||||
|
||||
struct Model {
|
||||
|
||||
@@ -17,8 +17,8 @@ public:
|
||||
|
||||
virtual ~Texture() {}
|
||||
|
||||
virtual void bind() = 0;
|
||||
virtual void unbind() = 0;
|
||||
virtual void bind() const = 0;
|
||||
virtual void unbind() const = 0;
|
||||
|
||||
virtual void reload(const ImageData& image) = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ModelBatch.hpp"
|
||||
|
||||
#include "assets/assets_util.hpp"
|
||||
#include "graphics/core/Mesh.hpp"
|
||||
#include "graphics/core/Model.hpp"
|
||||
#include "graphics/core/Atlas.hpp"
|
||||
@@ -77,6 +78,7 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix,
|
||||
const texture_names_map* varTextures,
|
||||
bool backlight) {
|
||||
glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
gpos += lightsOffset;
|
||||
light_t light = chunks->getLight(
|
||||
std::floor(gpos.x),
|
||||
std::floor(std::min(CHUNK_H-1.0f, gpos.y)),
|
||||
@@ -137,9 +139,13 @@ void ModelBatch::render() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
void ModelBatch::setLightsOffset(const glm::vec3& offset) {
|
||||
lightsOffset = offset;
|
||||
}
|
||||
|
||||
void ModelBatch::setTexture(const std::string& name,
|
||||
const texture_names_map* varTextures) {
|
||||
if (name.at(0) == '$') {
|
||||
if (varTextures && name.at(0) == '$') {
|
||||
const auto& found = varTextures->find(name);
|
||||
if (found == varTextures->end()) {
|
||||
return setTexture(nullptr);
|
||||
@@ -147,25 +153,13 @@ void ModelBatch::setTexture(const std::string& name,
|
||||
return setTexture(found->second, varTextures);
|
||||
}
|
||||
}
|
||||
size_t sep = name.find(':');
|
||||
if (sep == std::string::npos) {
|
||||
setTexture(assets->get<Texture>(name));
|
||||
} else {
|
||||
auto atlas = assets->get<Atlas>(name.substr(0, sep));
|
||||
if (atlas == nullptr) {
|
||||
setTexture(nullptr);
|
||||
} else {
|
||||
setTexture(atlas->getTexture());
|
||||
if (auto reg = atlas->getIf(name.substr(sep+1))) {
|
||||
region = *reg;
|
||||
} else {
|
||||
setTexture("blocks:notfound", varTextures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto textureRegion = util::get_texture_region(*assets, name, "blocks:notfound");
|
||||
setTexture(textureRegion.texture);
|
||||
region = textureRegion.region;
|
||||
}
|
||||
|
||||
void ModelBatch::setTexture(Texture* texture) {
|
||||
void ModelBatch::setTexture(const Texture* texture) {
|
||||
if (texture == nullptr) {
|
||||
texture = blank.get();
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ class ModelBatch {
|
||||
|
||||
Assets* assets;
|
||||
Chunks* chunks;
|
||||
Texture* texture = nullptr;
|
||||
const Texture* texture = nullptr;
|
||||
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
const EngineSettings* settings;
|
||||
glm::vec3 lightsOffset {};
|
||||
|
||||
static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f};
|
||||
|
||||
@@ -71,7 +72,7 @@ class ModelBatch {
|
||||
bool backlight);
|
||||
void setTexture(const std::string& name,
|
||||
const texture_names_map* varTextures);
|
||||
void setTexture(Texture* texture);
|
||||
void setTexture(const Texture* texture);
|
||||
void flush();
|
||||
|
||||
struct DrawEntry {
|
||||
@@ -96,4 +97,6 @@ public:
|
||||
const model::Model* model,
|
||||
const texture_names_map* varTextures);
|
||||
void render();
|
||||
|
||||
void setLightsOffset(const glm::vec3& offset);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "ModelsGenerator.hpp"
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("models-generator");
|
||||
|
||||
static void configure_textures(
|
||||
model::Model& model,
|
||||
const Block& blockDef,
|
||||
const Assets& assets
|
||||
) {
|
||||
for (auto& mesh : model.meshes) {
|
||||
auto& texture = mesh.texture;
|
||||
if (texture.empty() || texture.at(0) != '$') {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int index = std::stoi(texture.substr(1));
|
||||
texture = "blocks:"+blockDef.textureFaces.at(index);
|
||||
} catch (const std::invalid_argument& err) {
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << err.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static model::Model create_flat_model(
|
||||
const std::string& texture, const Assets& assets
|
||||
) {
|
||||
auto model = assets.require<model::Model>("drop-item");
|
||||
for (auto& mesh : model.meshes) {
|
||||
if (mesh.texture == "$0") {
|
||||
mesh.texture = texture;
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
model::Model ModelsGenerator::generate(
|
||||
const ItemDef& def, const Content& content, const Assets& assets
|
||||
) {
|
||||
if (def.iconType == ItemIconType::BLOCK) {
|
||||
auto model = assets.require<model::Model>("block");
|
||||
const auto& blockDef = content.blocks.require(def.icon);
|
||||
if (blockDef.model == BlockModel::xsprite) {
|
||||
return create_flat_model(
|
||||
"blocks:" + blockDef.textureFaces.at(0), assets
|
||||
);
|
||||
}
|
||||
for (auto& mesh : model.meshes) {
|
||||
switch (blockDef.model) {
|
||||
case BlockModel::aabb: {
|
||||
glm::vec3 size = blockDef.hitboxes.at(0).size();
|
||||
float m = glm::max(size.x, glm::max(size.y, size.z));
|
||||
m = glm::min(1.0f, m);
|
||||
mesh.scale(size / m);
|
||||
break;
|
||||
} default:
|
||||
break;
|
||||
}
|
||||
mesh.scale(glm::vec3(0.3f));
|
||||
}
|
||||
configure_textures(model, blockDef, assets);
|
||||
return model;
|
||||
} else if (def.iconType == ItemIconType::SPRITE) {
|
||||
return create_flat_model(def.icon, assets);
|
||||
} else {
|
||||
return model::Model();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics/core/Model.hpp"
|
||||
|
||||
struct ItemDef;
|
||||
class Assets;
|
||||
class Content;
|
||||
|
||||
class ModelsGenerator {
|
||||
public:
|
||||
static model::Model generate(
|
||||
const ItemDef& def, const Content& content, const Assets& assets
|
||||
);
|
||||
};
|
||||
@@ -58,11 +58,13 @@ Skybox::Skybox(uint size, Shader* shader)
|
||||
|
||||
Skybox::~Skybox() = default;
|
||||
|
||||
void Skybox::drawBackground(Camera* camera, Assets* assets, int width, int height) {
|
||||
auto backShader = assets->get<Shader>("background");
|
||||
void Skybox::drawBackground(
|
||||
const Camera& camera, const Assets& assets, 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()/(M_PI*0.5f));
|
||||
backShader->uniformMatrix("u_view", camera.getView(false));
|
||||
backShader->uniform1f("u_zoom", camera.zoom*camera.getFov()/(M_PI*0.5f));
|
||||
backShader->uniform1f("u_ar", float(width)/float(height));
|
||||
backShader->uniform1i("u_cubemap", 1);
|
||||
bind();
|
||||
@@ -93,8 +95,8 @@ void Skybox::drawStars(float angle, float opacity) {
|
||||
|
||||
void Skybox::draw(
|
||||
const DrawContext& pctx,
|
||||
Camera* camera,
|
||||
Assets* assets,
|
||||
const Camera& camera,
|
||||
const Assets& assets,
|
||||
float daytime,
|
||||
float fog)
|
||||
{
|
||||
@@ -107,9 +109,9 @@ void Skybox::draw(
|
||||
DrawContext ctx = pctx.sub();
|
||||
ctx.setBlendMode(BlendMode::addition);
|
||||
|
||||
auto p_shader = assets->get<Shader>("ui3d");
|
||||
auto p_shader = assets.get<Shader>("ui3d");
|
||||
p_shader->use();
|
||||
p_shader->uniformMatrix("u_projview", camera->getProjView(false));
|
||||
p_shader->uniformMatrix("u_projview", camera.getProjView(false));
|
||||
p_shader->uniformMatrix("u_apply", glm::mat4(1.0f));
|
||||
batch3d->begin();
|
||||
|
||||
@@ -117,7 +119,7 @@ void Skybox::draw(
|
||||
float opacity = glm::pow(1.0f-fog, 7.0f);
|
||||
|
||||
for (auto& sprite : sprites) {
|
||||
batch3d->texture(assets->get<Texture>(sprite.texture));
|
||||
batch3d->texture(assets.get<Texture>(sprite.texture));
|
||||
|
||||
float sangle = daytime * float(M_PI)*2.0 + sprite.phase;
|
||||
float distance = sprite.distance;
|
||||
@@ -136,6 +138,7 @@ void Skybox::draw(
|
||||
}
|
||||
|
||||
void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) {
|
||||
float dayTime = t;
|
||||
DrawContext ctx = pctx.sub();
|
||||
ctx.setDepthMask(false);
|
||||
ctx.setDepthTest(false);
|
||||
@@ -180,10 +183,12 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality)
|
||||
};
|
||||
t *= M_PI*2.0f;
|
||||
|
||||
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
|
||||
shader->uniform1i("u_quality", quality);
|
||||
shader->uniform1f("u_mie", mie);
|
||||
shader->uniform1f("u_fog", mie - 1.0f);
|
||||
shader->uniform3f("u_lightDir", glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)));
|
||||
shader->uniform3f("u_lightDir", lightDir);
|
||||
shader->uniform1f("u_dayTime", dayTime);
|
||||
for (uint face = 0; face < 6; face++) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap->getId(), 0);
|
||||
shader->uniform3f("u_xaxis", xaxs[face]);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "typedefs.hpp"
|
||||
#include "maths/fastmaths.hpp"
|
||||
|
||||
@@ -27,21 +28,24 @@ class Skybox {
|
||||
Shader* shader;
|
||||
bool ready = false;
|
||||
FastRandom random;
|
||||
glm::vec3 lightDir;
|
||||
|
||||
std::unique_ptr<Mesh> mesh;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
std::vector<skysprite> sprites;
|
||||
|
||||
void drawStars(float angle, float opacity);
|
||||
void drawBackground(Camera* camera, Assets* assets, int width, int height);
|
||||
void drawBackground(
|
||||
const Camera& camera, const Assets& assets, int width, int height
|
||||
);
|
||||
public:
|
||||
Skybox(uint size, Shader* shader);
|
||||
~Skybox();
|
||||
|
||||
void draw(
|
||||
const DrawContext& pctx,
|
||||
Camera* camera,
|
||||
Assets* assets,
|
||||
const Camera& camera,
|
||||
const Assets& assets,
|
||||
float daytime,
|
||||
float fog
|
||||
);
|
||||
@@ -52,4 +56,8 @@ public:
|
||||
bool isReady() const {
|
||||
return ready;
|
||||
}
|
||||
|
||||
const glm::vec3 getLightDir() const {
|
||||
return lightDir;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "assets/assets_util.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "engine.hpp"
|
||||
#include "frontend/LevelFrontend.hpp"
|
||||
@@ -78,17 +79,17 @@ WorldRenderer::WorldRenderer(
|
||||
WorldRenderer::~WorldRenderer() = default;
|
||||
|
||||
bool WorldRenderer::drawChunk(
|
||||
size_t index, Camera* camera, Shader* shader, bool culling
|
||||
size_t index, const Camera& camera, Shader* shader, bool culling
|
||||
) {
|
||||
auto chunk = level->chunks->getChunks()[index];
|
||||
if (!chunk->flags.lighted) {
|
||||
return false;
|
||||
}
|
||||
float distance = glm::distance(
|
||||
camera->position,
|
||||
camera.position,
|
||||
glm::vec3(
|
||||
(chunk->x + 0.5f) * CHUNK_W,
|
||||
camera->position.y,
|
||||
camera.position.y,
|
||||
(chunk->z + 0.5f) * CHUNK_D
|
||||
)
|
||||
);
|
||||
@@ -113,7 +114,9 @@ bool WorldRenderer::drawChunk(
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) {
|
||||
void WorldRenderer::drawChunks(
|
||||
Chunks* chunks, const Camera& camera, Shader* shader
|
||||
) {
|
||||
auto assets = engine->getAssets();
|
||||
auto atlas = assets->get<Atlas>("blocks");
|
||||
|
||||
@@ -127,8 +130,8 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) {
|
||||
if (chunks->getChunks()[i] == nullptr) continue;
|
||||
indices.emplace_back(i);
|
||||
}
|
||||
float px = camera->position.x / static_cast<float>(CHUNK_W) - 0.5f;
|
||||
float pz = camera->position.z / static_cast<float>(CHUNK_D) - 0.5f;
|
||||
float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f;
|
||||
float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f;
|
||||
std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) {
|
||||
const auto& chunksBuffer = chunks->getChunks();
|
||||
const auto a = chunksBuffer[i].get();
|
||||
@@ -141,7 +144,7 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) {
|
||||
});
|
||||
bool culling = engine->getSettings().graphics.frustumCulling.get();
|
||||
if (culling) {
|
||||
frustumCulling->update(camera->getProjView());
|
||||
frustumCulling->update(camera.getProjView());
|
||||
}
|
||||
chunks->visible = 0;
|
||||
for (size_t i = 0; i < indices.size(); i++) {
|
||||
@@ -151,20 +154,21 @@ void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) {
|
||||
|
||||
void WorldRenderer::setupWorldShader(
|
||||
Shader* shader,
|
||||
Camera* camera,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float fogFactor
|
||||
) {
|
||||
shader->use();
|
||||
shader->uniformMatrix("u_model", glm::mat4(1.0f));
|
||||
shader->uniformMatrix("u_proj", camera->getProjection());
|
||||
shader->uniformMatrix("u_view", camera->getView());
|
||||
shader->uniformMatrix("u_proj", camera.getProjection());
|
||||
shader->uniformMatrix("u_view", camera.getView());
|
||||
shader->uniform1f("u_timer", timer);
|
||||
shader->uniform1f("u_gamma", settings.graphics.gamma.get());
|
||||
shader->uniform1f("u_fogFactor", fogFactor);
|
||||
shader->uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
|
||||
shader->uniform1f("u_dayTime", level->getWorld()->getInfo().daytime);
|
||||
shader->uniform3f("u_cameraPos", camera->position);
|
||||
shader->uniform2f("u_lightDir", skybox->getLightDir());
|
||||
shader->uniform3f("u_cameraPos", camera.position);
|
||||
shader->uniform1i("u_cubemap", 1);
|
||||
|
||||
auto indices = level->content->getIndices();
|
||||
@@ -186,7 +190,7 @@ void WorldRenderer::setupWorldShader(
|
||||
|
||||
void WorldRenderer::renderLevel(
|
||||
const DrawContext&,
|
||||
Camera* camera,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause
|
||||
@@ -251,10 +255,10 @@ void WorldRenderer::renderBlockSelection() {
|
||||
}
|
||||
|
||||
void WorldRenderer::renderLines(
|
||||
Camera* camera, Shader* linesShader, const DrawContext& pctx
|
||||
const Camera& camera, Shader* linesShader, const DrawContext& pctx
|
||||
) {
|
||||
linesShader->use();
|
||||
linesShader->uniformMatrix("u_projview", camera->getProjView());
|
||||
linesShader->uniformMatrix("u_projview", camera.getProjView());
|
||||
if (player->selection.vox.id != BLOCK_VOID) {
|
||||
renderBlockSelection();
|
||||
}
|
||||
@@ -268,7 +272,7 @@ void WorldRenderer::renderLines(
|
||||
}
|
||||
|
||||
void WorldRenderer::renderDebugLines(
|
||||
const DrawContext& pctx, Camera* camera, Shader* linesShader
|
||||
const DrawContext& pctx, const Camera& camera, Shader* linesShader
|
||||
) {
|
||||
DrawContext ctx = pctx.sub(lineBatch.get());
|
||||
const auto& viewport = ctx.getViewport();
|
||||
@@ -280,8 +284,8 @@ void WorldRenderer::renderDebugLines(
|
||||
linesShader->use();
|
||||
|
||||
if (showChunkBorders) {
|
||||
linesShader->uniformMatrix("u_projview", camera->getProjView());
|
||||
glm::vec3 coord = player->camera->position;
|
||||
linesShader->uniformMatrix("u_projview", camera.getProjView());
|
||||
glm::vec3 coord = player->fpCamera->position;
|
||||
if (coord.x < 0) coord.x--;
|
||||
if (coord.z < 0) coord.z--;
|
||||
int cx = floordiv(static_cast<int>(coord.x), CHUNK_W);
|
||||
@@ -310,7 +314,7 @@ void WorldRenderer::renderDebugLines(
|
||||
-length,
|
||||
length
|
||||
) * model *
|
||||
glm::inverse(camera->rotation)
|
||||
glm::inverse(camera.rotation)
|
||||
);
|
||||
|
||||
ctx.setDepthTest(false);
|
||||
@@ -327,9 +331,70 @@ void WorldRenderer::renderDebugLines(
|
||||
lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
void WorldRenderer::renderHands(const Camera& camera, const Assets& assets) {
|
||||
auto entityShader = assets.get<Shader>("entity");
|
||||
auto indices = level->content->getIndices();
|
||||
|
||||
// get current chosen item
|
||||
const auto& inventory = player->getInventory();
|
||||
int slot = player->getChosenSlot();
|
||||
const ItemStack& stack = inventory->getSlot(slot);
|
||||
const auto& def = indices->items.require(stack.getItemId());
|
||||
|
||||
// prepare modified HUD camera
|
||||
Camera hudcam = camera;
|
||||
hudcam.far = 100.0f;
|
||||
hudcam.setFov(1.2f);
|
||||
hudcam.position = {};
|
||||
|
||||
// configure model matrix
|
||||
const glm::vec3 itemOffset(0.08f, 0.035f, -0.1);
|
||||
|
||||
static glm::mat4 prevRotation(1.0f);
|
||||
|
||||
const float speed = 24.0f;
|
||||
glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset);
|
||||
matrix = glm::scale(matrix, glm::vec3(0.1f));
|
||||
glm::mat4 rotation = camera.rotation;
|
||||
glm::quat rot0 = glm::quat_cast(prevRotation);
|
||||
glm::quat rot1 = glm::quat_cast(rotation);
|
||||
glm::quat finalRot =
|
||||
glm::slerp(rot0, rot1, static_cast<float>(engine->getDelta() * speed));
|
||||
rotation = glm::mat4_cast(finalRot);
|
||||
matrix = rotation * matrix *
|
||||
glm::rotate(
|
||||
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
|
||||
);
|
||||
prevRotation = rotation;
|
||||
auto offset = -(camera.position - player->getPosition());
|
||||
float angle = glm::radians(player->cam.x - 90);
|
||||
float cos = glm::cos(angle);
|
||||
float sin = glm::sin(angle);
|
||||
|
||||
float newX = offset.x * cos - offset.z * sin;
|
||||
float newZ = offset.x * sin + offset.z * cos;
|
||||
offset = glm::vec3(newX, offset.y, newZ);
|
||||
matrix = matrix * glm::translate(glm::mat4(1.0f), offset);
|
||||
|
||||
// render
|
||||
modelBatch->setLightsOffset(camera.position);
|
||||
modelBatch->draw(
|
||||
matrix,
|
||||
glm::vec3(1.0f),
|
||||
assets.get<model::Model>(def.modelName),
|
||||
nullptr
|
||||
);
|
||||
Window::clearDepth();
|
||||
setupWorldShader(entityShader, hudcam, engine->getSettings(), 0.0f);
|
||||
skybox->bind();
|
||||
modelBatch->render();
|
||||
modelBatch->setLightsOffset(glm::vec3());
|
||||
skybox->unbind();
|
||||
}
|
||||
|
||||
void WorldRenderer::draw(
|
||||
const DrawContext& pctx,
|
||||
Camera* camera,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
bool pause,
|
||||
float delta,
|
||||
@@ -338,15 +403,15 @@ void WorldRenderer::draw(
|
||||
timer += delta * !pause;
|
||||
auto world = level->getWorld();
|
||||
const Viewport& vp = pctx.getViewport();
|
||||
camera->aspect = vp.getWidth() / static_cast<float>(vp.getHeight());
|
||||
camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight());
|
||||
|
||||
const auto& settings = engine->getSettings();
|
||||
const auto& worldInfo = world->getInfo();
|
||||
|
||||
skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4);
|
||||
|
||||
auto assets = engine->getAssets();
|
||||
auto linesShader = assets->get<Shader>("lines");
|
||||
const auto& assets = *engine->getAssets();
|
||||
auto linesShader = assets.get<Shader>("lines");
|
||||
|
||||
// World render scope with diegetic HUD included
|
||||
{
|
||||
@@ -367,22 +432,70 @@ void WorldRenderer::draw(
|
||||
// Debug lines
|
||||
if (hudVisible) {
|
||||
renderLines(camera, linesShader, ctx);
|
||||
if (player->currentCamera == player->fpCamera) {
|
||||
renderHands(camera, assets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hudVisible && player->debug) {
|
||||
renderDebugLines(wctx, camera, linesShader);
|
||||
}
|
||||
renderBlockOverlay(wctx, assets);
|
||||
}
|
||||
|
||||
// Rendering fullscreen quad with
|
||||
auto screenShader = assets->get<Shader>("screen");
|
||||
auto screenShader = assets.get<Shader>("screen");
|
||||
screenShader->use();
|
||||
screenShader->uniform1f("u_timer", timer);
|
||||
screenShader->uniform1f("u_dayTime", worldInfo.daytime);
|
||||
postProcessing->render(pctx, screenShader);
|
||||
}
|
||||
|
||||
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& assets) {
|
||||
int x = std::floor(player->currentCamera->position.x);
|
||||
int y = std::floor(player->currentCamera->position.y);
|
||||
int z = std::floor(player->currentCamera->position.z);
|
||||
auto block = level->chunks->get(x, y, z);
|
||||
if (block && block->id) {
|
||||
const auto& def =
|
||||
level->content->getIndices()->blocks.require(block->id);
|
||||
if (def.overlayTexture.empty()) {
|
||||
return;
|
||||
}
|
||||
auto textureRegion = util::get_texture_region(
|
||||
assets, def.overlayTexture, "blocks:notfound"
|
||||
);
|
||||
DrawContext ctx = wctx.sub();
|
||||
ctx.setDepthTest(false);
|
||||
ctx.setCullFace(false);
|
||||
|
||||
auto& shader = assets.require<Shader>("ui3d");
|
||||
shader.use();
|
||||
batch3d->begin();
|
||||
shader.uniformMatrix("u_projview", glm::mat4(1.0f));
|
||||
shader.uniformMatrix("u_apply", glm::mat4(1.0f));
|
||||
auto light = level->chunks->getLight(x, y, z);
|
||||
float s = Lightmap::extract(light, 3) / 15.0f;
|
||||
glm::vec4 tint(
|
||||
glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s),
|
||||
glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s),
|
||||
glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s),
|
||||
1.0f
|
||||
);
|
||||
batch3d->texture(textureRegion.texture);
|
||||
batch3d->sprite(
|
||||
glm::vec3(),
|
||||
glm::vec3(0, 1, 0),
|
||||
glm::vec3(1, 0, 0),
|
||||
2,
|
||||
2,
|
||||
textureRegion.region,
|
||||
tint
|
||||
);
|
||||
batch3d->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::drawBorders(
|
||||
int sx, int sy, int sz, int ex, int ey, int ez
|
||||
) {
|
||||
|
||||
@@ -23,6 +23,7 @@ class Skybox;
|
||||
class PostProcessing;
|
||||
class DrawContext;
|
||||
class ModelBatch;
|
||||
class Assets;
|
||||
struct EngineSettings;
|
||||
|
||||
namespace model {
|
||||
@@ -41,16 +42,20 @@ class WorldRenderer {
|
||||
std::unique_ptr<ModelBatch> modelBatch;
|
||||
float timer = 0.0f;
|
||||
|
||||
bool drawChunk(size_t index, Camera* camera, Shader* shader, bool culling);
|
||||
void drawChunks(Chunks* chunks, Camera* camera, Shader* shader);
|
||||
bool drawChunk(size_t index, const Camera& camera, Shader* shader, bool culling);
|
||||
void drawChunks(Chunks* chunks, const Camera& camera, Shader* shader);
|
||||
|
||||
/// @brief Render block selection lines
|
||||
void renderBlockSelection();
|
||||
|
||||
void renderHands(const Camera& camera, const Assets& assets);
|
||||
|
||||
/// @brief Render lines (selection and debug)
|
||||
/// @param camera active camera
|
||||
/// @param linesShader shader used
|
||||
void renderLines(Camera* camera, Shader* linesShader, const DrawContext& pctx);
|
||||
void renderLines(
|
||||
const Camera& camera, Shader* linesShader, const DrawContext& pctx
|
||||
);
|
||||
|
||||
/// @brief Render all debug lines (chunks borders, coord system guides)
|
||||
/// @param context graphics context
|
||||
@@ -58,13 +63,15 @@ class WorldRenderer {
|
||||
/// @param linesShader shader used
|
||||
void renderDebugLines(
|
||||
const DrawContext& context,
|
||||
Camera* camera,
|
||||
const Camera& camera,
|
||||
Shader* linesShader
|
||||
);
|
||||
|
||||
void renderBlockOverlay(const DrawContext& context, const Assets& assets);
|
||||
|
||||
void setupWorldShader(
|
||||
Shader* shader,
|
||||
Camera* camera,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float fogFactor
|
||||
);
|
||||
@@ -77,7 +84,7 @@ public:
|
||||
|
||||
void draw(
|
||||
const DrawContext& context,
|
||||
Camera* camera,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
bool pause,
|
||||
float delta,
|
||||
@@ -91,7 +98,7 @@ public:
|
||||
/// @param settings engine settings
|
||||
void renderLevel(
|
||||
const DrawContext& context,
|
||||
Camera* camera,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "InventoryView.hpp"
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "assets/assets_util.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "frontend/LevelFrontend.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
@@ -161,9 +162,9 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
|
||||
|
||||
auto& item = indices->items.require(stack.getItemId());
|
||||
switch (item.iconType) {
|
||||
case item_icon_type::none:
|
||||
case ItemIconType::NONE:
|
||||
break;
|
||||
case item_icon_type::block: {
|
||||
case ItemIconType::BLOCK: {
|
||||
const Block& cblock = content->blocks.require(item.icon);
|
||||
batch->texture(previews->getTexture());
|
||||
|
||||
@@ -173,23 +174,14 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
|
||||
0, 0, 0, region, false, true, tint);
|
||||
break;
|
||||
}
|
||||
case item_icon_type::sprite: {
|
||||
size_t index = item.icon.find(':');
|
||||
std::string name = item.icon.substr(index+1);
|
||||
UVRegion region(0.0f, 0.0, 1.0f, 1.0f);
|
||||
if (index == std::string::npos) {
|
||||
batch->texture(assets->get<Texture>(name));
|
||||
} else {
|
||||
std::string atlasname = item.icon.substr(0, index);
|
||||
auto atlas = assets->get<Atlas>(atlasname);
|
||||
if (atlas && atlas->has(name)) {
|
||||
region = atlas->get(name);
|
||||
batch->texture(atlas->getTexture());
|
||||
}
|
||||
}
|
||||
case ItemIconType::SPRITE: {
|
||||
auto textureRegion =
|
||||
util::get_texture_region(*assets, item.icon, "blocks:notfound");
|
||||
|
||||
batch->texture(textureRegion.texture);
|
||||
batch->rect(
|
||||
pos.x, pos.y, slotSize, slotSize,
|
||||
0, 0, 0, region, false, true, tint);
|
||||
0, 0, 0, textureRegion.region, false, true, tint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,9 @@ void TextBox::paste(const std::wstring& text) {
|
||||
input.erase(std::remove(input.begin(), input.end(), '\r'), input.end());
|
||||
refreshLabel();
|
||||
setCaret(caret + text.length());
|
||||
validate();
|
||||
if (validate()) {
|
||||
onInput();
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Remove part of the text and move caret to start of the part
|
||||
@@ -470,6 +472,12 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::onInput() {
|
||||
if (subconsumer) {
|
||||
subconsumer(input);
|
||||
}
|
||||
}
|
||||
|
||||
void TextBox::performEditingKeyboardEvents(keycode key) {
|
||||
bool shiftPressed = Events::pressed(keycode::LEFT_SHIFT);
|
||||
bool breakSelection = getSelectionLength() != 0 && !shiftPressed;
|
||||
@@ -480,19 +488,23 @@ void TextBox::performEditingKeyboardEvents(keycode key) {
|
||||
}
|
||||
input = input.substr(0, caret-1) + input.substr(caret);
|
||||
setCaret(caret-1);
|
||||
validate();
|
||||
if (validate()) {
|
||||
onInput();
|
||||
}
|
||||
}
|
||||
} else if (key == keycode::DELETE) {
|
||||
if (!eraseSelected() && caret < input.length()) {
|
||||
input = input.substr(0, caret) + input.substr(caret + 1);
|
||||
validate();
|
||||
if (validate()) {
|
||||
onInput();
|
||||
}
|
||||
}
|
||||
} else if (key == keycode::ENTER) {
|
||||
if (multiline) {
|
||||
paste(L"\n");
|
||||
} else {
|
||||
defocus();
|
||||
if (validate() && consumer) {
|
||||
if (validate()) {
|
||||
consumer(label->getText());
|
||||
}
|
||||
}
|
||||
@@ -591,6 +603,10 @@ void TextBox::setTextConsumer(wstringconsumer consumer) {
|
||||
this->consumer = std::move(consumer);
|
||||
}
|
||||
|
||||
void TextBox::setTextSubConsumer(wstringconsumer consumer) {
|
||||
this->subconsumer = std::move(consumer);
|
||||
}
|
||||
|
||||
void TextBox::setTextValidator(wstringchecker validator) {
|
||||
this->validator = std::move(validator);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace gui {
|
||||
std::wstring placeholder;
|
||||
wstringsupplier supplier = nullptr;
|
||||
wstringconsumer consumer = nullptr;
|
||||
wstringconsumer subconsumer = nullptr;
|
||||
wstringchecker validator = nullptr;
|
||||
runnable onEditStart = nullptr;
|
||||
runnable onUpPressed;
|
||||
@@ -65,6 +66,8 @@ namespace gui {
|
||||
void performEditingKeyboardEvents(keycode key);
|
||||
|
||||
void refreshLabel();
|
||||
|
||||
void onInput();
|
||||
public:
|
||||
TextBox(
|
||||
std::wstring placeholder,
|
||||
@@ -79,6 +82,10 @@ namespace gui {
|
||||
/// @param consumer std::wstring consumer function
|
||||
virtual void setTextConsumer(wstringconsumer consumer);
|
||||
|
||||
/// @brief Sub-consumer called while editing text
|
||||
/// @param consumer std::wstring consumer function
|
||||
virtual 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
|
||||
|
||||
@@ -355,6 +355,13 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
|
||||
reader.getFilename()
|
||||
));
|
||||
}
|
||||
if (element->has("sub-consumer")) {
|
||||
textbox->setTextSubConsumer(scripting::create_wstring_consumer(
|
||||
reader.getEnvironment(),
|
||||
element->attr("sub-consumer").getText(),
|
||||
reader.getFilename()
|
||||
));
|
||||
}
|
||||
if (element->has("supplier")) {
|
||||
textbox->setTextSupplier(scripting::create_wstring_supplier(
|
||||
reader.getEnvironment(),
|
||||
|
||||
@@ -14,4 +14,5 @@ void ItemDef::cloneTo(ItemDef& dst) {
|
||||
dst.icon = icon;
|
||||
dst.placingBlock = placingBlock;
|
||||
dst.scriptName = scriptName;
|
||||
dst.modelName = modelName;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ struct item_funcs_set {
|
||||
bool on_block_break_by : 1;
|
||||
};
|
||||
|
||||
enum class item_icon_type {
|
||||
none, // invisible (core:empty) must not be rendered
|
||||
sprite, // textured quad: icon is `atlas_name:texture_name`
|
||||
block, // block preview: icon is string block id
|
||||
enum class ItemIconType {
|
||||
NONE, // invisible (core:empty) must not be rendered
|
||||
SPRITE, // textured quad: icon is `atlas_name:texture_name`
|
||||
BLOCK, // block preview: icon is string block id
|
||||
};
|
||||
|
||||
struct ItemDef {
|
||||
@@ -29,12 +29,14 @@ struct ItemDef {
|
||||
bool generated = false;
|
||||
uint8_t emission[4] {0, 0, 0, 0};
|
||||
|
||||
item_icon_type iconType = item_icon_type::sprite;
|
||||
ItemIconType iconType = ItemIconType::SPRITE;
|
||||
std::string icon = "blocks:notfound";
|
||||
|
||||
std::string placingBlock = "core:air";
|
||||
std::string scriptName = name.substr(name.find(':') + 1);
|
||||
|
||||
std::string modelName = name + ".model";
|
||||
|
||||
struct {
|
||||
itemid_t id;
|
||||
blockid_t placingBlock;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine.hpp"
|
||||
#include "files/WorldFiles.hpp"
|
||||
#include "interfaces/Object.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
@@ -15,10 +16,8 @@
|
||||
|
||||
static debug::Logger logger("level-control");
|
||||
|
||||
LevelController::LevelController(
|
||||
EngineSettings& settings, std::unique_ptr<Level> level
|
||||
)
|
||||
: settings(settings),
|
||||
LevelController::LevelController(Engine* engine, std::unique_ptr<Level> level)
|
||||
: settings(engine->getSettings()),
|
||||
level(std::move(level)),
|
||||
blocks(std::make_unique<BlocksController>(
|
||||
this->level.get(), settings.chunks.padding.get()
|
||||
@@ -27,7 +26,7 @@ LevelController::LevelController(
|
||||
this->level.get(), settings.chunks.padding.get()
|
||||
)),
|
||||
player(std::make_unique<PlayerController>(
|
||||
this->level.get(), settings, blocks.get()
|
||||
settings, this->level.get(), blocks.get()
|
||||
)) {
|
||||
scripting::on_world_load(this);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "ChunksController.hpp"
|
||||
#include "PlayerController.hpp"
|
||||
|
||||
class Engine;
|
||||
class Level;
|
||||
class Player;
|
||||
struct EngineSettings;
|
||||
@@ -19,7 +20,7 @@ class LevelController {
|
||||
std::unique_ptr<ChunksController> chunks;
|
||||
std::unique_ptr<PlayerController> player;
|
||||
public:
|
||||
LevelController(EngineSettings& settings, std::unique_ptr<Level> level);
|
||||
LevelController(Engine* engine, std::unique_ptr<Level> level);
|
||||
|
||||
/// @param delta time elapsed since the last update
|
||||
/// @param input is user input allowed to be handled
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "content/Content.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "items/Inventory.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "items/ItemStack.hpp"
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "BlocksController.hpp"
|
||||
#include "scripting/scripting.hpp"
|
||||
|
||||
const float INTERACTION_RELOAD = 0.160f;
|
||||
const float STEPS_SPEED = 2.2f;
|
||||
const float CAM_SHAKE_OFFSET = 0.0075f;
|
||||
const float CAM_SHAKE_OFFSET_Y = 0.031f;
|
||||
@@ -41,7 +43,7 @@ CameraControl::CameraControl(
|
||||
const std::shared_ptr<Player>& player, const CameraSettings& settings
|
||||
)
|
||||
: player(player),
|
||||
camera(player->camera),
|
||||
camera(player->fpCamera),
|
||||
settings(settings),
|
||||
offset(0.0f, 0.7f, 0.0f) {
|
||||
}
|
||||
@@ -187,11 +189,10 @@ void CameraControl::update(PlayerInput input, float delta, Chunks* chunks) {
|
||||
}
|
||||
|
||||
PlayerController::PlayerController(
|
||||
Level* level,
|
||||
const EngineSettings& settings,
|
||||
const EngineSettings& settings, Level* level,
|
||||
BlocksController* blocksController
|
||||
)
|
||||
: level(level),
|
||||
: settings(settings), level(level),
|
||||
player(level->getObject<Player>(0)),
|
||||
camControl(player, settings.camera),
|
||||
blocksController(blocksController) {
|
||||
@@ -262,7 +263,7 @@ void PlayerController::postUpdate(float delta, bool input, bool pause) {
|
||||
player->postUpdate();
|
||||
camControl.update(this->input, pause ? 0.0f : delta, level->chunks.get());
|
||||
if (input) {
|
||||
updateInteraction();
|
||||
updateInteraction(delta);
|
||||
} else {
|
||||
player->selection = {};
|
||||
}
|
||||
@@ -353,7 +354,7 @@ static void pick_block(
|
||||
voxel* PlayerController::updateSelection(float maxDistance) {
|
||||
auto indices = level->content->getIndices();
|
||||
auto chunks = level->chunks.get();
|
||||
auto camera = player->camera.get();
|
||||
auto camera = player->fpCamera.get();
|
||||
auto& selection = player->selection;
|
||||
|
||||
glm::vec3 end;
|
||||
@@ -416,7 +417,7 @@ voxel* PlayerController::updateSelection(float maxDistance) {
|
||||
void PlayerController::processRightClick(const Block& def, const Block& target) {
|
||||
const auto& selection = player->selection;
|
||||
auto chunks = level->chunks.get();
|
||||
auto camera = player->camera.get();
|
||||
auto camera = player->fpCamera.get();
|
||||
|
||||
blockstate state {};
|
||||
state.rotation = determine_rotation(&def, selection.normal, camera->dir);
|
||||
@@ -480,17 +481,24 @@ void PlayerController::updateEntityInteraction(
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerController::updateInteraction() {
|
||||
void PlayerController::updateInteraction(float delta) {
|
||||
auto indices = level->content->getIndices();
|
||||
auto chunks = level->chunks.get();
|
||||
const auto& selection = player->selection;
|
||||
|
||||
bool xkey = Events::pressed(keycode::X);
|
||||
bool lclick = Events::jactive(BIND_PLAYER_ATTACK) ||
|
||||
(xkey && Events::active(BIND_PLAYER_ATTACK));
|
||||
bool rclick = Events::jactive(BIND_PLAYER_BUILD) ||
|
||||
(xkey && Events::active(BIND_PLAYER_BUILD));
|
||||
|
||||
if (interactionTimer > 0.0f) {
|
||||
interactionTimer -= delta;
|
||||
}
|
||||
bool xkey = Events::active(BIND_PLAYER_FAST_INTERACTOIN);
|
||||
float maxDistance = xkey ? 200.0f : 10.0f;
|
||||
bool longInteraction = interactionTimer <= 0 || xkey;
|
||||
bool lclick = Events::jactive(BIND_PLAYER_ATTACK) ||
|
||||
(longInteraction && Events::active(BIND_PLAYER_ATTACK));
|
||||
bool rclick = Events::jactive(BIND_PLAYER_BUILD) ||
|
||||
(longInteraction && Events::active(BIND_PLAYER_BUILD));
|
||||
if (lclick || rclick) {
|
||||
interactionTimer = INTERACTION_RELOAD;
|
||||
}
|
||||
|
||||
auto inventory = player->getInventory();
|
||||
const ItemStack& stack = inventory->getSlot(player->getChosenSlot());
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "objects/Player.hpp"
|
||||
|
||||
class Engine;
|
||||
class Camera;
|
||||
class Level;
|
||||
class Block;
|
||||
@@ -13,6 +14,7 @@ class Chunks;
|
||||
class BlocksController;
|
||||
struct Hitbox;
|
||||
struct CameraSettings;
|
||||
struct EngineSettings;
|
||||
|
||||
class CameraControl {
|
||||
std::shared_ptr<Player> player;
|
||||
@@ -45,17 +47,19 @@ public:
|
||||
};
|
||||
|
||||
class PlayerController {
|
||||
const EngineSettings& settings;
|
||||
Level* level;
|
||||
std::shared_ptr<Player> player;
|
||||
PlayerInput input {};
|
||||
CameraControl camControl;
|
||||
BlocksController* blocksController;
|
||||
|
||||
float interactionTimer = 0.0f;
|
||||
void updateKeyboard();
|
||||
void resetKeyboard();
|
||||
void updatePlayer(float delta);
|
||||
void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick);
|
||||
void updateInteraction();
|
||||
void updateInteraction(float delta);
|
||||
|
||||
float stepsTimer = 0.0f;
|
||||
void onFootstep(const Hitbox& hitbox);
|
||||
@@ -65,9 +69,7 @@ class PlayerController {
|
||||
voxel* updateSelection(float maxDistance);
|
||||
public:
|
||||
PlayerController(
|
||||
Level* level,
|
||||
const EngineSettings& settings,
|
||||
BlocksController* blocksController
|
||||
const EngineSettings& settings, Level* level, BlocksController* blocksController
|
||||
);
|
||||
void update(float delta, bool input, bool pause);
|
||||
void postUpdate(float delta, bool input, bool pause);
|
||||
|
||||
@@ -35,6 +35,7 @@ extern const luaL_Reg playerlib[];
|
||||
extern const luaL_Reg quatlib[]; // quat.cpp
|
||||
extern const luaL_Reg timelib[];
|
||||
extern const luaL_Reg tomllib[];
|
||||
extern const luaL_Reg utf8lib[];
|
||||
extern const luaL_Reg vec2lib[]; // vecn.cpp
|
||||
extern const luaL_Reg vec3lib[]; // vecn.cpp
|
||||
extern const luaL_Reg vec4lib[]; // vecn.cpp
|
||||
|
||||
@@ -154,6 +154,8 @@ static int l_get_setting_info(lua::State* L) {
|
||||
lua::setfield(L, "min");
|
||||
lua::pushnumber(L, number->getMax());
|
||||
lua::setfield(L, "max");
|
||||
lua::pushnumber(L, number->getDefault());
|
||||
lua::setfield(L, "def");
|
||||
return 1;
|
||||
}
|
||||
if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
|
||||
@@ -161,12 +163,32 @@ static int l_get_setting_info(lua::State* L) {
|
||||
lua::setfield(L, "min");
|
||||
lua::pushinteger(L, integer->getMax());
|
||||
lua::setfield(L, "max");
|
||||
lua::pushinteger(L, integer->getDefault());
|
||||
lua::setfield(L, "def");
|
||||
return 1;
|
||||
}
|
||||
if (auto boolean = dynamic_cast<FlagSetting*>(setting)) {
|
||||
lua::pushboolean(L, boolean->getDefault());
|
||||
lua::setfield(L, "def");
|
||||
return 1;
|
||||
}
|
||||
if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
||||
lua::pushstring(L, string->getDefault());
|
||||
lua::setfield(L, "def");
|
||||
return 1;
|
||||
}
|
||||
lua::pop(L);
|
||||
throw std::runtime_error("unsupported setting type");
|
||||
}
|
||||
|
||||
#include "util/platform.hpp"
|
||||
|
||||
static int l_open_folder(lua::State* L) {
|
||||
auto path = engine->getPaths()->resolve(lua::require_string(L, 1));
|
||||
platform::open_folder(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// @brief Quit the game
|
||||
static int l_quit(lua::State*) {
|
||||
Window::setShouldClose(true);
|
||||
@@ -184,5 +206,6 @@ const luaL_Reg corelib[] = {
|
||||
{"set_setting", lua::wrap<l_set_setting>},
|
||||
{"str_setting", lua::wrap<l_str_setting>},
|
||||
{"get_setting_info", lua::wrap<l_get_setting_info>},
|
||||
{"open_folder", lua::wrap<l_open_folder>},
|
||||
{"quit", lua::wrap<l_quit>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@@ -34,6 +34,14 @@ static int l_def_name(lua::State* L) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_def_hitbox(lua::State* L) {
|
||||
if (auto def = require_entity_def(L)) {
|
||||
return lua::pushvec(L, def->hitbox);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_defs_count(lua::State* L) {
|
||||
return lua::pushinteger(L, indices->entities.count());
|
||||
}
|
||||
@@ -202,6 +210,7 @@ const luaL_Reg entitylib[] = {
|
||||
{"exists", lua::wrap<l_exists>},
|
||||
{"def_index", lua::wrap<l_def_index>},
|
||||
{"def_name", lua::wrap<l_def_name>},
|
||||
{"def_hitbox", lua::wrap<l_def_hitbox>},
|
||||
{"get_def", lua::wrap<l_get_def>},
|
||||
{"defs_count", lua::wrap<l_defs_count>},
|
||||
{"spawn", lua::wrap<l_spawn>},
|
||||
|
||||
@@ -45,6 +45,7 @@ static int l_load_fragment(lua::State* L) {
|
||||
|
||||
auto fragment = std::make_shared<VoxelFragment>();
|
||||
fragment->deserialize(map);
|
||||
fragment->prepare(*content);
|
||||
return lua::newuserdata<lua::LuaVoxelFragment>(L, std::move(fragment));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include "engine.hpp"
|
||||
#include "files/files.hpp"
|
||||
#include "frontend/hud.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
@@ -109,6 +112,38 @@ static int l_is_pressed(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
static void resetPackBindings(fs::path& packFolder) {
|
||||
auto configFolder = packFolder/fs::path("config");
|
||||
auto bindsFile = configFolder/fs::path("bindings.toml");
|
||||
if (fs::is_regular_file(bindsFile)) {
|
||||
Events::loadBindings(
|
||||
bindsFile.u8string(),
|
||||
files::read_string(bindsFile),
|
||||
BindType::REBIND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static int l_reset_bindings(lua::State*) {
|
||||
auto resFolder = engine->getPaths()->getResourcesFolder();
|
||||
resetPackBindings(resFolder);
|
||||
for (auto& pack : engine->getContentPacks()) {
|
||||
resetPackBindings(pack.folder);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_enabled(lua::State* L) {
|
||||
std::string bindname = lua::require_string(L, 1);
|
||||
bool enable = lua::toboolean(L, 2);
|
||||
const auto& bind = Events::bindings.find(bindname);
|
||||
if (bind == Events::bindings.end()) {
|
||||
throw std::runtime_error("unknown binding " + util::quote(bindname));
|
||||
}
|
||||
Events::bindings[bindname].enable = enable;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg inputlib[] = {
|
||||
{"keycode", lua::wrap<l_keycode>},
|
||||
{"mousecode", lua::wrap<l_mousecode>},
|
||||
@@ -118,4 +153,6 @@ const luaL_Reg inputlib[] = {
|
||||
{"get_binding_text", lua::wrap<l_get_binding_text>},
|
||||
{"is_active", lua::wrap<l_is_active>},
|
||||
{"is_pressed", lua::wrap<l_is_pressed>},
|
||||
{"reset_bindings", lua::wrap<l_reset_bindings>},
|
||||
{"set_enabled", lua::wrap<l_set_enabled>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@@ -10,55 +10,84 @@ static const ItemDef* get_item_def(lua::State* L, int idx) {
|
||||
return indices->items.get(id);
|
||||
}
|
||||
|
||||
static int l_item_name(lua::State* L) {
|
||||
static int l_name(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushstring(L, def->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_item_index(lua::State* L) {
|
||||
static int l_index(lua::State* L) {
|
||||
auto name = lua::require_string(L, 1);
|
||||
return lua::pushinteger(L, content->items.require(name).rt.id);
|
||||
}
|
||||
|
||||
static int l_item_stack_size(lua::State* L) {
|
||||
static int l_stack_size(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushinteger(L, def->stackSize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_item_defs_count(lua::State* L) {
|
||||
static int l_defs_count(lua::State* L) {
|
||||
return lua::pushinteger(L, indices->items.count());
|
||||
}
|
||||
|
||||
static int l_item_get_icon(lua::State* L) {
|
||||
static int l_get_icon(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
switch (def->iconType) {
|
||||
case item_icon_type::none:
|
||||
case ItemIconType::NONE:
|
||||
return 0;
|
||||
case item_icon_type::sprite:
|
||||
case ItemIconType::SPRITE:
|
||||
return lua::pushstring(L, def->icon);
|
||||
case item_icon_type::block:
|
||||
case ItemIconType::BLOCK:
|
||||
return lua::pushstring(L, "block-previews:" + def->icon);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_item_caption(lua::State* L) {
|
||||
static int l_caption(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushstring(L, def->caption);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_placing_block(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushinteger(L, def->rt.placingBlock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_model_name(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
return lua::pushstring(L, def->modelName);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_emission(lua::State* L) {
|
||||
if (auto def = get_item_def(L, 1)) {
|
||||
lua::createtable(L, 4, 0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
lua::pushinteger(L, def->emission[i]);
|
||||
lua::rawseti(L, i+1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg itemlib[] = {
|
||||
{"index", lua::wrap<l_item_index>},
|
||||
{"name", lua::wrap<l_item_name>},
|
||||
{"stack_size", lua::wrap<l_item_stack_size>},
|
||||
{"defs_count", lua::wrap<l_item_defs_count>},
|
||||
{"icon", lua::wrap<l_item_get_icon>},
|
||||
{"caption", lua::wrap<l_item_caption>},
|
||||
{"index", lua::wrap<l_index>},
|
||||
{"name", lua::wrap<l_name>},
|
||||
{"stack_size", lua::wrap<l_stack_size>},
|
||||
{"defs_count", lua::wrap<l_defs_count>},
|
||||
{"icon", lua::wrap<l_get_icon>},
|
||||
{"caption", lua::wrap<l_caption>},
|
||||
{"placing_block", lua::wrap<l_placing_block>},
|
||||
{"model_name", lua::wrap<l_model_name>},
|
||||
{"emission", lua::wrap<l_emission>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@@ -85,7 +85,7 @@ static int l_set_rot(lua::State* L) {
|
||||
|
||||
static int l_get_dir(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
return lua::pushvec3(L, player->camera->front);
|
||||
return lua::pushvec3(L, player->fpCamera->front);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <cwctype>
|
||||
|
||||
#include "../lua_custom_types.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
static int l_encode(lua::State* L) {
|
||||
std::string_view string = lua::require_string(L, 1);
|
||||
if (lua::toboolean(L, 2)) {
|
||||
lua::createtable(L, string.length(), 0);
|
||||
for (size_t i = 0; i < string.length(); i++) {
|
||||
lua::pushinteger(L, string[i] & 0xFF);
|
||||
lua::rawseti(L, i+1);
|
||||
}
|
||||
} else {
|
||||
lua::newuserdata<lua::LuaBytearray>(L, string.length());
|
||||
auto bytearray = lua::touserdata<lua::LuaBytearray>(L, -1);
|
||||
bytearray->data().reserve(string.length());
|
||||
std::memcpy(bytearray->data().data(), string.data(), string.length());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_decode(lua::State* L) {
|
||||
if (lua::istable(L, 1)) {
|
||||
size_t size = lua::objlen(L, 1);
|
||||
util::Buffer<char> buffer(size);
|
||||
return lua::pushstring(L, std::string(buffer.data(), size));
|
||||
} else if (auto bytes = lua::touserdata<lua::LuaBytearray>(L, 1)) {
|
||||
return lua::pushstring(
|
||||
L,
|
||||
std::string(
|
||||
reinterpret_cast<char*>(bytes->data().data()),
|
||||
bytes->data().size()
|
||||
)
|
||||
);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_length(lua::State* L) {
|
||||
auto string = lua::require_string(L, 1);
|
||||
return lua::pushinteger(L, util::length_utf8(string));
|
||||
}
|
||||
|
||||
static int l_codepoint(lua::State* L) {
|
||||
std::string_view string = lua::require_string(L, 1);
|
||||
if (string.empty()) {
|
||||
return lua::pushinteger(L, 0);
|
||||
}
|
||||
uint size;
|
||||
return lua::pushinteger(L, util::decode_utf8(size, string.data()));
|
||||
}
|
||||
|
||||
static int l_sub(lua::State* L) {
|
||||
auto string = util::str2u32str_utf8(lua::require_string(L, 1));
|
||||
int start = std::max(0, static_cast<int>(lua::tointeger(L, 2) - 1));
|
||||
int end = string.length();
|
||||
if (lua::gettop(L) >= 3) {
|
||||
end = std::max(0, static_cast<int>(lua::tointeger(L, 3) - 1));
|
||||
}
|
||||
return lua::pushstring(L, util::u32str2str_utf8(string.substr(start, end)));
|
||||
}
|
||||
|
||||
static int l_upper(lua::State* L) {
|
||||
auto string = util::str2u32str_utf8(lua::require_string(L, 1));
|
||||
for (auto& c : string) {
|
||||
c = std::towupper(c);
|
||||
}
|
||||
return lua::pushstring(L, util::u32str2str_utf8(string));
|
||||
}
|
||||
|
||||
static int l_lower(lua::State* L) {
|
||||
auto string = util::str2u32str_utf8(lua::require_string(L, 1));
|
||||
for (auto& c : string) {
|
||||
c = std::towlower(c);
|
||||
}
|
||||
return lua::pushstring(L, util::u32str2str_utf8(string));
|
||||
}
|
||||
|
||||
const luaL_Reg utf8lib[] = {
|
||||
{"tobytes", lua::wrap<l_encode>},
|
||||
{"tostring", lua::wrap<l_decode>},
|
||||
{"length", lua::wrap<l_length>},
|
||||
{"codepoint", lua::wrap<l_codepoint>},
|
||||
{"sub", lua::wrap<l_sub>},
|
||||
{"upper", lua::wrap<l_upper>},
|
||||
{"lower", lua::wrap<l_lower>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
@@ -12,7 +13,14 @@
|
||||
using namespace scripting;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static int l_world_get_list(lua::State* L) {
|
||||
static WorldInfo& require_world_info() {
|
||||
if (level == nullptr) {
|
||||
throw std::runtime_error("no world open");
|
||||
}
|
||||
return level->getWorld()->getInfo();
|
||||
}
|
||||
|
||||
static int l_get_list(lua::State* L) {
|
||||
auto paths = engine->getPaths();
|
||||
auto worlds = paths->scanForWorlds();
|
||||
|
||||
@@ -41,59 +49,64 @@ static int l_world_get_list(lua::State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_world_get_total_time(lua::State* L) {
|
||||
return lua::pushnumber(L, level->getWorld()->getInfo().totalTime);
|
||||
static int l_get_total_time(lua::State* L) {
|
||||
return lua::pushnumber(L, require_world_info().totalTime);
|
||||
}
|
||||
|
||||
static int l_world_get_day_time(lua::State* L) {
|
||||
return lua::pushnumber(L, level->getWorld()->getInfo().daytime);
|
||||
static int l_get_day_time(lua::State* L) {
|
||||
return lua::pushnumber(L, require_world_info().daytime);
|
||||
}
|
||||
|
||||
static int l_world_set_day_time(lua::State* L) {
|
||||
static int l_set_day_time(lua::State* L) {
|
||||
auto value = lua::tonumber(L, 1);
|
||||
level->getWorld()->getInfo().daytime = std::fmod(value, 1.0);
|
||||
require_world_info().daytime = std::fmod(value, 1.0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_world_set_day_time_speed(lua::State* L) {
|
||||
static int l_set_day_time_speed(lua::State* L) {
|
||||
auto value = lua::tonumber(L, 1);
|
||||
level->getWorld()->getInfo().daytimeSpeed = std::abs(value);
|
||||
require_world_info().daytimeSpeed = std::abs(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_world_get_day_time_speed(lua::State* L) {
|
||||
return lua::pushnumber(L, level->getWorld()->getInfo().daytimeSpeed);
|
||||
static int l_get_day_time_speed(lua::State* L) {
|
||||
return lua::pushnumber(L, require_world_info().daytimeSpeed);
|
||||
}
|
||||
|
||||
static int l_world_get_seed(lua::State* L) {
|
||||
return lua::pushinteger(L, level->getWorld()->getSeed());
|
||||
static int l_get_seed(lua::State* L) {
|
||||
return lua::pushinteger(L, require_world_info().seed);
|
||||
}
|
||||
|
||||
static int l_world_exists(lua::State* L) {
|
||||
static int l_exists(lua::State* L) {
|
||||
auto name = lua::require_string(L, 1);
|
||||
auto worldsDir = engine->getPaths()->getWorldFolderByName(name);
|
||||
return lua::pushboolean(L, fs::is_directory(worldsDir));
|
||||
}
|
||||
|
||||
static int l_world_is_day(lua::State* L) {
|
||||
auto daytime = level->getWorld()->getInfo().daytime;
|
||||
static int l_is_day(lua::State* L) {
|
||||
auto daytime = require_world_info().daytime;
|
||||
return lua::pushboolean(L, daytime >= 0.333 && daytime <= 0.833);
|
||||
}
|
||||
|
||||
static int l_world_is_night(lua::State* L) {
|
||||
auto daytime = level->getWorld()->getInfo().daytime;
|
||||
static int l_is_night(lua::State* L) {
|
||||
auto daytime = require_world_info().daytime;
|
||||
return lua::pushboolean(L, daytime < 0.333 || daytime > 0.833);
|
||||
}
|
||||
|
||||
static int l_get_generator(lua::State* L) {
|
||||
return lua::pushstring(L, require_world_info().generator);
|
||||
}
|
||||
|
||||
const luaL_Reg worldlib[] = {
|
||||
{"get_list", lua::wrap<l_world_get_list>},
|
||||
{"get_total_time", lua::wrap<l_world_get_total_time>},
|
||||
{"get_day_time", lua::wrap<l_world_get_day_time>},
|
||||
{"set_day_time", lua::wrap<l_world_set_day_time>},
|
||||
{"set_day_time_speed", lua::wrap<l_world_set_day_time_speed>},
|
||||
{"get_day_time_speed", lua::wrap<l_world_get_day_time_speed>},
|
||||
{"get_seed", lua::wrap<l_world_get_seed>},
|
||||
{"is_day", lua::wrap<l_world_is_day>},
|
||||
{"is_night", lua::wrap<l_world_is_night>},
|
||||
{"exists", lua::wrap<l_world_exists>},
|
||||
{"get_list", lua::wrap<l_get_list>},
|
||||
{"get_total_time", lua::wrap<l_get_total_time>},
|
||||
{"get_day_time", lua::wrap<l_get_day_time>},
|
||||
{"set_day_time", lua::wrap<l_set_day_time>},
|
||||
{"set_day_time_speed", lua::wrap<l_set_day_time_speed>},
|
||||
{"get_day_time_speed", lua::wrap<l_get_day_time_speed>},
|
||||
{"get_seed", lua::wrap<l_get_seed>},
|
||||
{"get_generator", lua::wrap<l_get_generator>},
|
||||
{"is_day", lua::wrap<l_is_day>},
|
||||
{"is_night", lua::wrap<l_is_night>},
|
||||
{"exists", lua::wrap<l_exists>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@@ -51,6 +51,7 @@ static void create_libs(State* L, StateType stateType) {
|
||||
openlib(L, "quat", quatlib);
|
||||
openlib(L, "time", timelib);
|
||||
openlib(L, "toml", tomllib);
|
||||
openlib(L, "utf8", utf8lib);
|
||||
openlib(L, "vec2", vec2lib);
|
||||
openlib(L, "vec3", vec3lib);
|
||||
openlib(L, "vec4", vec4lib);
|
||||
|
||||
@@ -229,8 +229,10 @@ static int l_resize(lua::State* L) {
|
||||
uint height = touinteger(L, 3);
|
||||
auto interpName = tostring(L, 4);
|
||||
auto interpolation = InterpolationType::NEAREST;
|
||||
if (!std::strcmp(interpName, "linear")) {
|
||||
if (std::strcmp(interpName, "linear") == 0) {
|
||||
interpolation = InterpolationType::LINEAR;
|
||||
} else if (std::strcmp(interpName, "cubic") == 0) {
|
||||
interpolation = InterpolationType::CUBIC;
|
||||
}
|
||||
heightmap->getHeightmap()->resize(width, height, interpolation);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "world/generator/VoxelFragment.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
using namespace lua;
|
||||
|
||||
@@ -20,8 +21,20 @@ static int l_crop(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_place(lua::State* L) {
|
||||
if (auto fragment = touserdata<LuaVoxelFragment>(L, 1)) {
|
||||
auto offset = tovec3(L, 2);
|
||||
int rotation = tointeger(L, 3) & 0b11;
|
||||
fragment->getFragment()->place(
|
||||
*scripting::level->chunks, offset, rotation
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, lua_CFunction> methods {
|
||||
{"crop", lua::wrap<l_crop>},
|
||||
{"place", lua::wrap<l_place>},
|
||||
};
|
||||
|
||||
static int l_meta_tostring(lua::State* L) {
|
||||
|
||||
@@ -164,32 +164,40 @@ void scripting::on_world_load(LevelController* controller) {
|
||||
|
||||
auto L = lua::get_main_state();
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ".worldopen");
|
||||
lua::emit_event(L, pack.id + ":.worldopen");
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_world_tick() {
|
||||
auto L = lua::get_main_state();
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ".worldtick");
|
||||
lua::emit_event(L, pack.id + ":.worldtick");
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_world_save() {
|
||||
auto L = lua::get_main_state();
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ".worldsave");
|
||||
lua::emit_event(L, pack.id + ":.worldsave");
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_world_quit() {
|
||||
auto L = lua::get_main_state();
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
lua::emit_event(L, pack.id + ".worldquit");
|
||||
lua::emit_event(L, pack.id + ":.worldquit");
|
||||
}
|
||||
scripting::level = nullptr;
|
||||
scripting::content = nullptr;
|
||||
scripting::indices = nullptr;
|
||||
scripting::blocks = nullptr;
|
||||
scripting::controller = nullptr;
|
||||
}
|
||||
|
||||
void scripting::cleanup() {
|
||||
auto L = lua::get_main_state();
|
||||
lua::getglobal(L, "pack");
|
||||
for (auto& pack : scripting::engine->getContentPacks()) {
|
||||
for (auto& pack : scripting::engine->getAllContentPacks()) {
|
||||
lua::getfield(L, "unload");
|
||||
lua::pushstring(L, pack.id);
|
||||
lua::call_nothrow(L, 1);
|
||||
@@ -199,11 +207,6 @@ void scripting::on_world_quit() {
|
||||
if (lua::getglobal(L, "__scripts_cleanup")) {
|
||||
lua::call_nothrow(L, 0);
|
||||
}
|
||||
scripting::level = nullptr;
|
||||
scripting::content = nullptr;
|
||||
scripting::indices = nullptr;
|
||||
scripting::blocks = nullptr;
|
||||
scripting::controller = nullptr;
|
||||
}
|
||||
|
||||
void scripting::on_blocks_tick(const Block& block, int tps) {
|
||||
@@ -248,7 +251,7 @@ void scripting::on_block_placed(
|
||||
if (pack->worldfuncsset.onblockplaced) {
|
||||
lua::emit_event(
|
||||
lua::get_main_state(),
|
||||
packid + ".blockplaced",
|
||||
packid + ":.blockplaced",
|
||||
world_event_args
|
||||
);
|
||||
}
|
||||
@@ -280,7 +283,7 @@ void scripting::on_block_broken(
|
||||
if (pack->worldfuncsset.onblockbroken) {
|
||||
lua::emit_event(
|
||||
lua::get_main_state(),
|
||||
packid + ".blockbroken",
|
||||
packid + ":.blockbroken",
|
||||
world_event_args
|
||||
);
|
||||
}
|
||||
@@ -613,7 +616,7 @@ bool scripting::register_event(
|
||||
if (lua::getfield(L, name)) {
|
||||
lua::pop(L);
|
||||
lua::getglobal(L, "events");
|
||||
lua::getfield(L, "on");
|
||||
lua::getfield(L, "reset");
|
||||
lua::pushstring(L, id);
|
||||
lua::getfield(L, name, -4);
|
||||
lua::call_nothrow(L, 2);
|
||||
@@ -686,14 +689,14 @@ void scripting::load_world_script(
|
||||
int env = *senv;
|
||||
lua::pop(lua::get_main_state(), load_script(env, "world", file));
|
||||
register_event(env, "init", prefix + ".init");
|
||||
register_event(env, "on_world_open", prefix + ".worldopen");
|
||||
register_event(env, "on_world_tick", prefix + ".worldtick");
|
||||
register_event(env, "on_world_save", prefix + ".worldsave");
|
||||
register_event(env, "on_world_quit", prefix + ".worldquit");
|
||||
register_event(env, "on_world_open", prefix + ":.worldopen");
|
||||
register_event(env, "on_world_tick", prefix + ":.worldtick");
|
||||
register_event(env, "on_world_save", prefix + ":.worldsave");
|
||||
register_event(env, "on_world_quit", prefix + ":.worldquit");
|
||||
funcsset.onblockplaced =
|
||||
register_event(env, "on_block_placed", prefix + ".blockplaced");
|
||||
register_event(env, "on_block_placed", prefix + ":.blockplaced");
|
||||
funcsset.onblockbroken =
|
||||
register_event(env, "on_block_broken", prefix + ".blockbroken");
|
||||
register_event(env, "on_block_broken", prefix + ":.blockbroken");
|
||||
}
|
||||
|
||||
void scripting::load_layout_script(
|
||||
@@ -703,6 +706,7 @@ void scripting::load_layout_script(
|
||||
uidocscript& script
|
||||
) {
|
||||
int env = *senv;
|
||||
|
||||
lua::pop(lua::get_main_state(), load_script(env, "layout", file));
|
||||
script.onopen = register_event(env, "on_open", prefix + ".open");
|
||||
script.onprogress =
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace scripting {
|
||||
void on_world_tick();
|
||||
void on_world_save();
|
||||
void on_world_quit();
|
||||
void cleanup();
|
||||
void on_blocks_tick(const Block& block, int tps);
|
||||
void update_block(const Block& block, int x, int y, int z);
|
||||
void random_update_block(const Block& block, int x, int y, int z);
|
||||
|
||||
@@ -22,7 +22,7 @@ void scripting::on_frontend_init(Hud* hud) {
|
||||
for (auto& pack : engine->getContentPacks()) {
|
||||
lua::emit_event(
|
||||
lua::get_main_state(),
|
||||
pack.id + ".hudopen",
|
||||
pack.id + ":.hudopen",
|
||||
[&](lua::State* L) {
|
||||
return lua::pushinteger(L, hud->getPlayer()->getId());
|
||||
}
|
||||
@@ -34,7 +34,7 @@ void scripting::on_frontend_render() {
|
||||
for (auto& pack : engine->getContentPacks()) {
|
||||
lua::emit_event(
|
||||
lua::get_main_state(),
|
||||
pack.id + ".hudrender",
|
||||
pack.id + ":.hudrender",
|
||||
[&](lua::State* L) { return 0; }
|
||||
);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ void scripting::on_frontend_close() {
|
||||
for (auto& pack : engine->getContentPacks()) {
|
||||
lua::emit_event(
|
||||
lua::get_main_state(),
|
||||
pack.id + ".hudclose",
|
||||
pack.id + ":.hudclose",
|
||||
[&](lua::State* L) {
|
||||
return lua::pushinteger(L, hud->getPlayer()->getId());
|
||||
}
|
||||
@@ -62,8 +62,8 @@ void scripting::load_hud_script(
|
||||
|
||||
lua::execute(lua::get_main_state(), env, src, file.u8string());
|
||||
|
||||
register_event(env, "init", packid + ".init");
|
||||
register_event(env, "on_hud_open", packid + ".hudopen");
|
||||
register_event(env, "on_hud_render", packid + ".hudrender");
|
||||
register_event(env, "on_hud_close", packid + ".hudclose");
|
||||
register_event(env, "init", packid + ":.init");
|
||||
register_event(env, "on_hud_open", packid + ":.hudopen");
|
||||
register_event(env, "on_hud_render", packid + ":.hudrender");
|
||||
register_event(env, "on_hud_close", packid + ":.hudclose");
|
||||
}
|
||||
|
||||
+41
-3
@@ -5,8 +5,15 @@
|
||||
#include <stdexcept>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
static inline float smootherstep(float x) {
|
||||
return glm::smoothstep(std::floor(x), std::floor(x)+1, x);
|
||||
std::optional<InterpolationType> InterpolationType_from(std::string_view str) {
|
||||
if (str == "nearest") {
|
||||
return InterpolationType::NEAREST;
|
||||
} else if (str == "linear") {
|
||||
return InterpolationType::LINEAR;
|
||||
} else if (str == "cubic") {
|
||||
return InterpolationType::CUBIC;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static inline float sample_at(
|
||||
@@ -17,6 +24,27 @@ static inline float sample_at(
|
||||
return buffer[y*width+x];
|
||||
}
|
||||
|
||||
static inline float sample_at(
|
||||
const float* buffer,
|
||||
uint width, uint height,
|
||||
uint x, uint y
|
||||
) {
|
||||
return buffer[(y >= height ? height-1 : y)*width+(x >= width ? width-1 : x)];
|
||||
}
|
||||
|
||||
static inline float interpolate_cubic(float p[4], float x) {
|
||||
return p[1] + 0.5 * x*(p[2] - p[0] + x*(2.0*p[0] - 5.0*p[1] + 4.0*p[2] -
|
||||
p[3] + x*(3.0*(p[1] - p[2]) + p[3] - p[0])));
|
||||
}
|
||||
|
||||
static inline float interpolate_bicubic(float p[4][4], float x, float y) {
|
||||
float q[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
q[i] = interpolate_cubic(p[i], y);
|
||||
}
|
||||
return interpolate_cubic(q, x);
|
||||
}
|
||||
|
||||
static inline float sample_at(
|
||||
const float* buffer,
|
||||
uint width, uint height,
|
||||
@@ -50,7 +78,17 @@ static inline float sample_at(
|
||||
|
||||
return a00 + a10*tx + a01*ty + a11*tx*ty;
|
||||
}
|
||||
// TODO: implement CUBIC (Bicubic) interpolation
|
||||
case InterpolationType::CUBIC: {
|
||||
float p[4][4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
p[i][j] = sample_at(
|
||||
buffer, width, height, ix + j - 1, iy + i - 1
|
||||
);
|
||||
}
|
||||
}
|
||||
return interpolate_bicubic(p, ty, tx);
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("interpolation type is not implemented");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "maths/Heightmap.hpp"
|
||||
@@ -12,6 +13,8 @@ enum class InterpolationType {
|
||||
CUBIC,
|
||||
};
|
||||
|
||||
std::optional<InterpolationType> InterpolationType_from(std::string_view str);
|
||||
|
||||
class Heightmap {
|
||||
std::vector<float> buffer;
|
||||
uint width, height;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "EntityDef.hpp"
|
||||
|
||||
|
||||
void EntityDef::cloneTo(EntityDef& dst) {
|
||||
dst.components = components;
|
||||
dst.bodyType = bodyType;
|
||||
@@ -8,5 +10,4 @@ void EntityDef::cloneTo(EntityDef& dst) {
|
||||
dst.skeletonName = skeletonName;
|
||||
dst.blocking = blocking;
|
||||
dst.save = save;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ struct EntityDef {
|
||||
} skeleton;
|
||||
struct {
|
||||
bool velocity = true;
|
||||
bool settings = false;
|
||||
bool settings = true;
|
||||
} body;
|
||||
} save {};
|
||||
|
||||
|
||||
+10
-10
@@ -40,11 +40,11 @@ Player::Player(
|
||||
position(position),
|
||||
inventory(std::move(inv)),
|
||||
eid(eid),
|
||||
camera(level->getCamera("core:first-person")),
|
||||
fpCamera(level->getCamera("core:first-person")),
|
||||
spCamera(level->getCamera("core:third-person-front")),
|
||||
tpCamera(level->getCamera("core:third-person-back")),
|
||||
currentCamera(camera) {
|
||||
camera->setFov(glm::radians(90.0f));
|
||||
currentCamera(fpCamera) {
|
||||
fpCamera->setFov(glm::radians(90.0f));
|
||||
spCamera->setFov(glm::radians(90.0f));
|
||||
tpCamera->setFov(glm::radians(90.0f));
|
||||
}
|
||||
@@ -93,16 +93,16 @@ void Player::updateInput(PlayerInput& input, float delta) {
|
||||
|
||||
glm::vec3 dir(0, 0, 0);
|
||||
if (input.moveForward) {
|
||||
dir += camera->dir;
|
||||
dir += fpCamera->dir;
|
||||
}
|
||||
if (input.moveBack) {
|
||||
dir -= camera->dir;
|
||||
dir -= fpCamera->dir;
|
||||
}
|
||||
if (input.moveRight) {
|
||||
dir += camera->right;
|
||||
dir += fpCamera->right;
|
||||
}
|
||||
if (input.moveLeft) {
|
||||
dir -= camera->right;
|
||||
dir -= fpCamera->right;
|
||||
}
|
||||
if (glm::length(dir) > 0.0f) {
|
||||
dir = glm::normalize(dir);
|
||||
@@ -166,7 +166,7 @@ void Player::postUpdate() {
|
||||
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
|
||||
skeleton.visible = currentCamera != camera;
|
||||
skeleton.visible = currentCamera != fpCamera;
|
||||
|
||||
auto body = skeleton.config->find("body");
|
||||
auto head = skeleton.config->find("head");
|
||||
@@ -252,7 +252,7 @@ entityid_t Player::getSelectedEntity() const {
|
||||
return selectedEid;
|
||||
}
|
||||
|
||||
std::shared_ptr<Inventory> Player::getInventory() const {
|
||||
const std::shared_ptr<Inventory>& Player::getInventory() const {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ void Player::deserialize(const dv::value& src) {
|
||||
const auto& posarr = src["position"];
|
||||
|
||||
dv::get_vec(posarr, position);
|
||||
camera->position = position;
|
||||
fpCamera->position = position;
|
||||
|
||||
const auto& rotarr = src["rotation"];
|
||||
dv::get_vec(rotarr, cam);
|
||||
|
||||
@@ -52,7 +52,7 @@ class Player : public Object, public Serializable {
|
||||
entityid_t eid;
|
||||
entityid_t selectedEid;
|
||||
public:
|
||||
std::shared_ptr<Camera> camera, spCamera, tpCamera;
|
||||
std::shared_ptr<Camera> fpCamera, spCamera, tpCamera;
|
||||
std::shared_ptr<Camera> currentCamera;
|
||||
bool debug = false;
|
||||
glm::vec3 cam {};
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
|
||||
entityid_t getSelectedEntity() const;
|
||||
|
||||
std::shared_ptr<Inventory> getInventory() const;
|
||||
const std::shared_ptr<Inventory>& getInventory() const;
|
||||
|
||||
glm::vec3 getPosition() const {
|
||||
return position;
|
||||
|
||||
@@ -29,6 +29,8 @@ struct DisplaySettings {
|
||||
IntegerSetting samples {0};
|
||||
/// @brief Framerate limit
|
||||
IntegerSetting framerate {-1, -1, 120};
|
||||
/// @brief Limit framerate when window is iconified
|
||||
FlagSetting limitFpsIconified {false};
|
||||
};
|
||||
|
||||
struct ChunksSettings {
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace util {
|
||||
|
||||
Buffer(std::nullptr_t) noexcept : ptr(nullptr), length(0) {}
|
||||
|
||||
Buffer& operator=(Buffer&&) = default;
|
||||
|
||||
inline bool operator==(std::nullptr_t) const noexcept {
|
||||
return ptr == nullptr;
|
||||
}
|
||||
|
||||
+39
-2
@@ -5,14 +5,14 @@
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "stringutil.hpp"
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
|
||||
#include "stringutil.hpp"
|
||||
|
||||
void platform::configure_encoding() {
|
||||
// set utf-8 encoding to console output
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
@@ -36,6 +36,26 @@ std::string platform::detect_locale() {
|
||||
.substr(0, 5);
|
||||
}
|
||||
|
||||
void platform::sleep(size_t millis) {
|
||||
// Uses implementation from the SFML library
|
||||
// https://github.com/SFML/SFML/blob/master/src/SFML/System/Win32/SleepImpl.cpp
|
||||
|
||||
// Get the minimum supported timer resolution on this system
|
||||
static const UINT periodMin = []{
|
||||
TIMECAPS tc;
|
||||
timeGetDevCaps(&tc, sizeof(TIMECAPS));
|
||||
return tc.wPeriodMin;
|
||||
}();
|
||||
|
||||
// Set the timer resolution to the minimum for the Sleep call
|
||||
timeBeginPeriod(periodMin);
|
||||
|
||||
// Wait...
|
||||
Sleep(static_cast<DWORD>(millis));
|
||||
|
||||
// Reset the timer resolution back to the system default
|
||||
timeEndPeriod(periodMin);
|
||||
}
|
||||
#else
|
||||
|
||||
void platform::configure_encoding() {
|
||||
@@ -50,4 +70,21 @@ std::string platform::detect_locale() {
|
||||
return preferredLocaleName.substr(0, 5);
|
||||
}
|
||||
|
||||
void platform::sleep(size_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||||
}
|
||||
#endif
|
||||
|
||||
void platform::open_folder(const std::filesystem::path& folder) {
|
||||
if (!std::filesystem::is_directory(folder)) {
|
||||
return;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
auto cmd = "open " + util::quote(folder.u8string());
|
||||
#elif defined(_WIN32)
|
||||
auto cmd = "start explorer " + util::quote(folder.u8string());
|
||||
#else
|
||||
auto cmd = "xdg-open " + util::quote(folder.u8string());
|
||||
#endif
|
||||
system(cmd.c_str());
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
namespace platform {
|
||||
void configure_encoding();
|
||||
// @return environment locale in ISO format ll_CC
|
||||
/// @return environment locale in ISO format ll_CC
|
||||
std::string detect_locale();
|
||||
/// @brief Open folder using system file manager asynchronously
|
||||
/// @param folder target folder
|
||||
void open_folder(const std::filesystem::path& folder);
|
||||
/// Makes the current thread sleep for the specified amount of milliseconds.
|
||||
void sleep(size_t millis);
|
||||
}
|
||||
|
||||
+22
-2
@@ -11,7 +11,9 @@
|
||||
std::string util::escape(const std::string& s) {
|
||||
std::stringstream ss;
|
||||
ss << '"';
|
||||
for (char c : s) {
|
||||
size_t pos = 0;
|
||||
while (pos < s.length()) {
|
||||
char c = s[pos];
|
||||
switch (c) {
|
||||
case '\n':
|
||||
ss << "\\n";
|
||||
@@ -35,6 +37,13 @@ std::string util::escape(const std::string& s) {
|
||||
ss << "\\\\";
|
||||
break;
|
||||
default:
|
||||
if (c & 0x80) {
|
||||
uint cpsize;
|
||||
int codepoint = decode_utf8(cpsize, s.data() + pos);
|
||||
pos += cpsize-1;
|
||||
ss << "\\u" << std::hex << codepoint;
|
||||
break;
|
||||
}
|
||||
if (c < ' ') {
|
||||
ss << "\\" << std::oct << uint(ubyte(c));
|
||||
break;
|
||||
@@ -42,6 +51,7 @@ std::string util::escape(const std::string& s) {
|
||||
ss << c;
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
ss << '"';
|
||||
return ss.str();
|
||||
@@ -128,7 +138,7 @@ inline uint utf8_len(ubyte cp) {
|
||||
if ((cp & 0xF8) == 0xF0) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
throw std::runtime_error("utf8 decode error");
|
||||
}
|
||||
|
||||
uint32_t util::decode_utf8(uint& size, const char* chr) {
|
||||
@@ -156,6 +166,16 @@ size_t util::crop_utf8(std::string_view s, size_t maxSize) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
size_t util::length_utf8(std::string_view s) {
|
||||
size_t length = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < s.length()) {
|
||||
pos += utf8_len(s[pos]);
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
template<class C>
|
||||
std::string xstr2str_utf8(const std::basic_string<C>& xs) {
|
||||
std::vector<char> chars;
|
||||
|
||||
@@ -44,6 +44,11 @@ namespace util {
|
||||
/// @param maxSize max encoded string length after crop
|
||||
/// @return cropped string size (less or equal to maxSize)
|
||||
size_t crop_utf8(std::string_view s, size_t maxSize);
|
||||
|
||||
/// @brief Measure utf8-encoded string length
|
||||
/// @param s source encoded string
|
||||
/// @return unicode string length (number of codepoints)
|
||||
size_t length_utf8(std::string_view s);
|
||||
|
||||
bool is_integer(const std::string& text);
|
||||
bool is_integer(const std::wstring& text);
|
||||
|
||||
@@ -141,6 +141,7 @@ void Block::cloneTo(Block& dst) {
|
||||
dst.uiLayout = uiLayout;
|
||||
dst.inventorySize = inventorySize;
|
||||
dst.tickInterval = tickInterval;
|
||||
dst.overlayTexture = overlayTexture;
|
||||
}
|
||||
|
||||
static std::set<std::string, std::less<>> RESERVED_BLOCK_FIELDS {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "maths/UVRegion.hpp"
|
||||
#include "maths/aabb.hpp"
|
||||
@@ -111,7 +112,7 @@ public:
|
||||
std::string caption;
|
||||
|
||||
/// @brief Textures set applied to block sides
|
||||
std::string textureFaces[6]; // -x,x, -y,y, -z,z
|
||||
std::array<std::string, 6> textureFaces; // -x,x, -y,y, -z,z
|
||||
|
||||
std::vector<std::string> modelTextures = {};
|
||||
std::vector<BoxModel> modelBoxes = {};
|
||||
@@ -184,6 +185,9 @@ public:
|
||||
/// @brief Block will be used instead of this if generated on surface
|
||||
std::string surfaceReplacement = name;
|
||||
|
||||
/// @brief Texture will be shown on screen if camera is inside of the block
|
||||
std::string overlayTexture;
|
||||
|
||||
/// @brief Default block layout will be used by hud.open_block(...)
|
||||
std::string uiLayout = name;
|
||||
|
||||
|
||||
@@ -29,21 +29,21 @@ void Camera::rotate(float x, float y, float z) {
|
||||
updateVectors();
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getProjection() {
|
||||
glm::mat4 Camera::getProjection() const {
|
||||
constexpr float epsilon = 1e-6f; // 0.000001
|
||||
float aspect_ratio = this->aspect;
|
||||
if (std::fabs(aspect_ratio) < epsilon) {
|
||||
aspect_ratio = (float)Window::width / (float)Window::height;
|
||||
}
|
||||
if (perspective)
|
||||
return glm::perspective(fov * zoom, aspect_ratio, 0.05f, 1500.0f);
|
||||
return glm::perspective(fov * zoom, aspect_ratio, near, far);
|
||||
else if (flipped)
|
||||
return glm::ortho(0.0f, fov * aspect_ratio, fov, 0.0f);
|
||||
else
|
||||
return glm::ortho(0.0f, fov * aspect_ratio, 0.0f, fov);
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getView(bool pos) {
|
||||
glm::mat4 Camera::getView(bool pos) const {
|
||||
glm::vec3 camera_pos = this->position;
|
||||
if (!pos) {
|
||||
camera_pos = glm::vec3(0.0f);
|
||||
@@ -55,7 +55,7 @@ glm::mat4 Camera::getView(bool pos) {
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getProjView(bool pos) {
|
||||
glm::mat4 Camera::getProjView(bool pos) const {
|
||||
return getProjection() * getView(pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ public:
|
||||
bool perspective = true;
|
||||
bool flipped = false;
|
||||
float aspect = 0.0f;
|
||||
float near = 0.05f;
|
||||
float far = 1500.0f;
|
||||
|
||||
Camera() {
|
||||
updateVectors();
|
||||
@@ -27,9 +29,9 @@ public:
|
||||
void updateVectors();
|
||||
void rotate(float x, float y, float z);
|
||||
|
||||
glm::mat4 getProjection();
|
||||
glm::mat4 getView(bool position = true);
|
||||
glm::mat4 getProjView(bool position = true);
|
||||
glm::mat4 getProjection() const;
|
||||
glm::mat4 getView(bool position = true) const;
|
||||
glm::mat4 getProjView(bool position = true) const;
|
||||
|
||||
void setFov(float fov);
|
||||
float getFov() const;
|
||||
|
||||
+25
-4
@@ -78,6 +78,10 @@ void Events::pollEvents() {
|
||||
|
||||
for (auto& entry : bindings) {
|
||||
auto& binding = entry.second;
|
||||
if (!binding.enable) {
|
||||
binding.state = false;
|
||||
continue;
|
||||
}
|
||||
binding.justChange = false;
|
||||
|
||||
bool newstate = false;
|
||||
@@ -106,9 +110,9 @@ void Events::pollEvents() {
|
||||
}
|
||||
|
||||
Binding& Events::getBinding(const std::string& name) {
|
||||
auto found = bindings.find(name);
|
||||
const auto found = bindings.find(name);
|
||||
if (found == bindings.end()) {
|
||||
throw std::runtime_error("binding '" + name + "' does not exists");
|
||||
throw std::runtime_error("binding '" + name + "' does not exist");
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
@@ -126,6 +130,10 @@ void Events::bind(const std::string& name, inputtype type, int code) {
|
||||
}
|
||||
|
||||
void Events::rebind(const std::string& name, inputtype type, int code) {
|
||||
const auto& found = bindings.find(name);
|
||||
if (found == bindings.end()) {
|
||||
throw std::runtime_error("binding '" + name + "' does not exist");
|
||||
}
|
||||
bindings[name] = Binding(type, code);
|
||||
}
|
||||
|
||||
@@ -193,7 +201,8 @@ std::string Events::writeBindings() {
|
||||
}
|
||||
|
||||
void Events::loadBindings(
|
||||
const std::string& filename, const std::string& source
|
||||
const std::string& filename, const std::string& source,
|
||||
BindType bindType
|
||||
) {
|
||||
auto map = toml::parse(filename, source);
|
||||
for (auto& [sectionName, section] : map.asObject()) {
|
||||
@@ -214,7 +223,19 @@ void Events::loadBindings(
|
||||
<< util::quote(key) << ")";
|
||||
continue;
|
||||
}
|
||||
Events::bind(key, type, code);
|
||||
if (bindType == BindType::BIND) {
|
||||
Events::bind(key, type, code);
|
||||
} else if (bindType == BindType::REBIND) {
|
||||
Events::rebind(key, type, code);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Events::enableBindings() {
|
||||
for (auto& entry : bindings) {
|
||||
auto& binding = entry.second;
|
||||
binding.enable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
inline constexpr short KEYS_BUFFER_SIZE = 1036;
|
||||
|
||||
enum class BindType {
|
||||
BIND = 0,
|
||||
REBIND = 1
|
||||
};
|
||||
|
||||
class Events {
|
||||
static bool keys[KEYS_BUFFER_SIZE];
|
||||
static uint frames[KEYS_BUFFER_SIZE];
|
||||
@@ -52,6 +57,8 @@ public:
|
||||
|
||||
static std::string writeBindings();
|
||||
static void loadBindings(
|
||||
const std::string& filename, const std::string& source
|
||||
const std::string& filename, const std::string& source,
|
||||
BindType bindType
|
||||
);
|
||||
static void enableBindings();
|
||||
};
|
||||
|
||||
+10
-5
@@ -14,6 +14,8 @@
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
#include "Events.hpp"
|
||||
|
||||
#include "util/platform.hpp"
|
||||
|
||||
static debug::Logger logger("window");
|
||||
|
||||
GLFWwindow* Window::window = nullptr;
|
||||
@@ -357,11 +359,14 @@ bool Window::isFullscreen() {
|
||||
void Window::swapBuffers() {
|
||||
glfwSwapBuffers(window);
|
||||
Window::resetScissor();
|
||||
double currentTime = time();
|
||||
if (framerate > 0 && currentTime - prevSwap < (1.0 / framerate)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(
|
||||
(1.0 / framerate - (currentTime - prevSwap)) * 1000
|
||||
)));
|
||||
if (framerate > 0) {
|
||||
auto elapsedTime = time() - prevSwap;
|
||||
auto frameTime = 1.0 / framerate;
|
||||
if (elapsedTime < frameTime) {
|
||||
platform::sleep(
|
||||
static_cast<size_t>((frameTime - elapsedTime) * 1000)
|
||||
);
|
||||
}
|
||||
}
|
||||
prevSwap = time();
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ struct Binding {
|
||||
int code;
|
||||
bool state = false;
|
||||
bool justChange = false;
|
||||
bool enable = true;
|
||||
|
||||
Binding() = default;
|
||||
Binding(inputtype type, int code) : type(type), code(code) {
|
||||
|
||||
@@ -209,6 +209,12 @@ struct GeneratorDef {
|
||||
/// @brief Heightmap blocks per dot
|
||||
uint heightsBPD = 4;
|
||||
|
||||
/// @brief Biome parameter maps interpolation method
|
||||
InterpolationType biomesInterpolation = InterpolationType::LINEAR;
|
||||
|
||||
/// @brief Height maps interpolation method
|
||||
InterpolationType heightsInterpolation = InterpolationType::LINEAR;
|
||||
|
||||
/// @brief Number of chunks must be generated before and after wide
|
||||
/// structures placement triggered
|
||||
uint wideStructsChunksRadius = 3;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "data/dv_util.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/ChunksStorage.hpp"
|
||||
#include "voxels/VoxelsVolume.hpp"
|
||||
@@ -20,7 +21,7 @@ std::unique_ptr<VoxelFragment> VoxelFragment::create(
|
||||
bool entities
|
||||
) {
|
||||
auto start = glm::min(a, b);
|
||||
auto size = glm::abs(a - b);
|
||||
auto size = glm::abs(a - b) + 1;
|
||||
|
||||
if (crop) {
|
||||
VoxelsVolume volume(size.x, size.y, size.z);
|
||||
@@ -168,6 +169,29 @@ void VoxelFragment::prepare(const Content& content) {
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelFragment::place(
|
||||
Chunks& chunks, const glm::ivec3& offset, ubyte rotation
|
||||
) {
|
||||
auto& structVoxels = getRuntimeVoxels();
|
||||
for (int y = 0; y < size.y; y++) {
|
||||
int sy = y + offset.y;
|
||||
if (sy < 0 || sy >= CHUNK_H) {
|
||||
continue;
|
||||
}
|
||||
for (int z = 0; z < size.z; z++) {
|
||||
int sz = z + offset.z;
|
||||
for (int x = 0; x < size.x; x++) {
|
||||
int sx = x + offset.x;
|
||||
const auto& structVoxel =
|
||||
structVoxels[vox_index(x, y, z, size.x, size.z)];
|
||||
if (structVoxel.id) {
|
||||
chunks.set(sx, sy, sz, structVoxel.id, structVoxel.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<VoxelFragment> VoxelFragment::rotated(const Content& content) const {
|
||||
std::vector<voxel> newVoxels(voxels.size());
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ inline constexpr int STRUCTURE_FORMAT_VERSION = 1;
|
||||
|
||||
class Level;
|
||||
class Content;
|
||||
class Chunks;
|
||||
|
||||
class VoxelFragment : public Serializable {
|
||||
glm::ivec3 size;
|
||||
@@ -41,6 +42,11 @@ public:
|
||||
/// @param content world content
|
||||
void prepare(const Content& content);
|
||||
|
||||
/// @brief Place fragment to the world
|
||||
/// @param offset target location
|
||||
/// @param rotation rotation index
|
||||
void place(Chunks& chunks, const glm::ivec3& offset, ubyte rotation);
|
||||
|
||||
/// @brief Create structure copy rotated 90 deg. clockwise
|
||||
std::unique_ptr<VoxelFragment> rotated(const Content& content) const;
|
||||
|
||||
|
||||
@@ -307,13 +307,13 @@ void WorldGenerator::generateBiomes(
|
||||
copy->resize(
|
||||
floordiv(CHUNK_W, def.heightsBPD) + 1,
|
||||
floordiv(CHUNK_D, def.heightsBPD) + 1,
|
||||
InterpolationType::LINEAR
|
||||
def.heightsInterpolation
|
||||
);
|
||||
prototype.heightmapInputs.push_back(std::move(copy));
|
||||
}
|
||||
for (const auto& map : biomeParams) {
|
||||
map->resize(
|
||||
CHUNK_W + bpd, CHUNK_D + bpd, InterpolationType::LINEAR
|
||||
CHUNK_W + bpd, CHUNK_D + bpd, def.biomesInterpolation
|
||||
);
|
||||
map->crop(0, 0, CHUNK_W, CHUNK_D);
|
||||
}
|
||||
@@ -345,7 +345,7 @@ void WorldGenerator::generateHeightmap(
|
||||
);
|
||||
prototype.heightmap->clamp();
|
||||
prototype.heightmap->resize(
|
||||
CHUNK_W + bpd, CHUNK_D + bpd, InterpolationType::LINEAR
|
||||
CHUNK_W + bpd, CHUNK_D + bpd, def.heightsInterpolation
|
||||
);
|
||||
prototype.heightmap->crop(0, 0, CHUNK_W, CHUNK_D);
|
||||
prototype.level = ChunkPrototypeLevel::HEIGHTMAP;
|
||||
|
||||
Reference in New Issue
Block a user