diff --git a/include/data_loader.h b/include/data_loader.h new file mode 100644 index 00000000..b4295de5 --- /dev/null +++ b/include/data_loader.h @@ -0,0 +1,120 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DATA_LOADER_H +#define DATA_LOADER_H + +#include +#include +#include +#include + +database_t load_database(const std::filesystem::path& path); + +struct image_t; + +struct sampler_interface_t +{ + virtual void clear() + {} + + virtual std::optional get_sample() = 0; + + virtual ~sampler_interface_t() = default; +}; + +struct sampler_single_value_t final : sampler_interface_t +{ + explicit sampler_single_value_t(const blt::vec3 value): value{value} + {} + + std::optional get_sample() override + { + return value; + } + + blt::vec3 value; +}; + +struct sampler_one_point_t final : sampler_interface_t +{ + explicit sampler_one_point_t(const image_t& image); + + void clear() override + { + found = false; + } + + std::optional get_sample() override + { + if (found) + return {}; + found = true; + return average; + } + + bool found = false; + blt::vec3 average; +}; + +struct comparator_interface_t +{ + virtual ~comparator_interface_t() = default; + virtual blt::vec3 compare(sampler_interface_t& s1, sampler_interface_t& s2) = 0; + + blt::vec3 compare(sampler_interface_t& s1, const blt::vec3 point) + { + sampler_single_value_t value{point}; + return compare(s1, value); + } +}; + +struct comparator_euclidean_t final : comparator_interface_t +{ + blt::vec3 compare(sampler_interface_t& s1, sampler_interface_t& s2) override; +}; + +struct image_t +{ + blt::i32 width, height; + std::vector data; + + [[nodiscard]] auto get_default_sampler() const + { + return sampler_one_point_t{*this}; + } +}; + +struct assets_t +{ + blt::hashmap_t> images; +}; + +class data_loader_t +{ +public: + explicit data_loader_t(database_t& data): db{&data} + {} + + [[nodiscard]] assets_t load() const; + +private: + database_t* db; +}; + +#endif //DATA_LOADER_H diff --git a/include/sql.h b/include/sql.h index 9aa34afe..04baec16 100644 --- a/include/sql.h +++ b/include/sql.h @@ -82,10 +82,10 @@ public: return static_cast(sqlite3_column_double(statement, col)); } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - return T(sqlite3_column_text(statement, col)); + return T{reinterpret_cast(sqlite3_column_text(statement, col))}; } else { - return static_cast(sqlite3_column_blob(statement, col)); + return static_cast*>(sqlite3_column_blob(statement, col)); } } @@ -191,6 +191,35 @@ private: sqlite3_stmt* statement; }; +struct statement_result_t +{ + explicit statement_result_t(const int error_code): error_code{error_code} + {} + + [[nodiscard]] bool has_error() const + { + return !(error_code == SQLITE_ROW || error_code == SQLITE_DONE || error_code == SQLITE_OK); + } + + [[nodiscard]] bool has_row() const + { + return error_code == SQLITE_ROW; + } + + explicit operator bool() const + { + return !has_error(); + } + + [[nodiscard]] int get_error() const + { + return error_code; + } + +private: + int error_code = 0; +}; + class statement_t { public: @@ -217,7 +246,7 @@ public: } // returns true if the statement has a row. false otherwise. optional is empty if there is an error - [[jetbrains::has_side_effects]] std::optional execute() const; // NOLINT + [[jetbrains::has_side_effects]] statement_result_t execute() const; // NOLINT [[nodiscard]] column_t fetch() const { diff --git a/src/asset_loader.cpp b/src/asset_loader.cpp index 6c03f106..61aabe2c 100644 --- a/src/asset_loader.cpp +++ b/src/asset_loader.cpp @@ -359,12 +359,13 @@ std::optional asset_loader_t::load_assets(const std::string& ass database_t& asset_loader_t::load_textures() { + BLT_INFO("[Phase 2] Loading Textures"); auto texture_table = db.builder().create_table("solid_textures"); texture_table.with_column("namespace").primary_key(); texture_table.with_column("name").primary_key(); texture_table.with_column("width").not_null(); texture_table.with_column("height").not_null(); - texture_table.with_column("data").not_null(); + texture_table.with_column("data").not_null(); texture_table.build().execute(); auto non_texture_table = db.builder().create_table("non_solid_textures"); @@ -372,7 +373,7 @@ database_t& asset_loader_t::load_textures() non_texture_table.with_column("name").primary_key(); non_texture_table.with_column("width").not_null(); non_texture_table.with_column("height").not_null(); - non_texture_table.with_column("data").not_null(); + non_texture_table.with_column("data").not_null(); non_texture_table.build().execute(); const static auto insert_solid_sql = "INSERT INTO solid_textures VALUES (?, ?, ?, ?, ?)"; @@ -384,12 +385,14 @@ database_t& asset_loader_t::load_textures() { for (const auto& texture : textures) process_texture(insert_solid_stmt, namespace_str, texture); + BLT_INFO("[Phase 2] Loaded {} solid textures for namespace {}", textures.size(), namespace_str); } for (const auto& [namespace_str, textures] : data.non_solid_textures_to_load) { for (const auto& texture : textures) process_texture(insert_non_solid_stmt, namespace_str, texture); + BLT_INFO("[Phase 2] Loaded {} non-solid textures for namespace {}", textures.size(), namespace_str); } auto tag_table = db.builder().create_table("tags"); @@ -409,26 +412,38 @@ database_t& asset_loader_t::load_textures() const static auto insert_tag_model_sql = "INSERT INTO tag_models VALUES (?, ?, ?, ?)"; const auto insert_tag_stmt = db.prepare(insert_tag_sql); const auto insert_tag_model_stmt = db.prepare(insert_tag_model_sql); + + BLT_DEBUG("[Phase 2] Begin tag storage"); + size_t tag_list_count = 0; + size_t tag_model_count = 0; for (const auto& [namespace_str, jdata] : data.json_data) { for (const auto& [tag_name, tag_data] : jdata.tags) { + if (tag_data.list.empty() && tag_data.models.empty()) + continue; for (const auto& block_tag : tag_data.list) { + ++tag_list_count; insert_tag_stmt.bind().bind_all(namespace_str, tag_name, block_tag); if (!insert_tag_stmt.execute()) BLT_WARN("[Tag List] Unable to insert {} into {}:{} reason '{}'", block_tag, namespace_str, tag_name, db.get_error()); } + BLT_DEBUG("[Phase 2] Loaded {} blocks to tag {}:{}", tag_data.list.size(), namespace_str, tag_name); for (const auto& [model_namespace, model_list] : tag_data.models) { for (const auto& model : model_list) { + ++tag_model_count; insert_tag_model_stmt.bind().bind_all(namespace_str, tag_name, model_namespace, model); if (!insert_tag_model_stmt.execute()) - BLT_WARN("[Model List] Unable to insert {}:{} into {}:{} reason '{}'", namespace_str, tag_name, model_namespace, model, db.get_error()); + BLT_WARN("[Model List] Unable to insert {}:{} into {}:{} reason '{}'", namespace_str, tag_name, model_namespace, model, + db.get_error()); } } } + BLT_INFO("[Phase 2] Loaded {} blocks to tags.", tag_list_count); + BLT_INFO("[Phase 2] Loaded {} models to tags.", tag_model_count); } return db; @@ -440,10 +455,12 @@ void asset_loader_t::process_texture(const statement_t& stmt, const std::string& if (!std::filesystem::exists(texture_path)) return; int width, height, channels; - const auto data = stbi_load(texture_path.c_str(), &width, &height, &channels, 4); + const auto data = stbi_loadf(texture_path.c_str(), &width, &height, &channels, 4); if (data == nullptr) return; - stmt.bind().bind_all(namespace_str, texture, width, height, blt::span{data, static_cast(width * height * 4)}); + stmt.bind().bind_all(namespace_str, texture, width, height, blt::span{ + reinterpret_cast(data), static_cast(width * height * 4) * sizeof(float) + }); if (!stmt.execute()) { BLT_WARN("Failed to insert texture '{}:{}' into database. Error: '{}'", namespace_str, texture, db.get_error()); diff --git a/src/data_loader.cpp b/src/data_loader.cpp new file mode 100644 index 00000000..e931c472 --- /dev/null +++ b/src/data_loader.cpp @@ -0,0 +1,103 @@ +/* + * + * Copyright (C) 2025 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +database_t load_database(const std::filesystem::path& path) +{ + database_t db{path.string()}; + return db; +} + +assets_t data_loader_t::load() const +{ + constexpr auto SQL = "SELECT * FROM solid_textures"; + const auto stmt = db->prepare(SQL); + + assets_t assets; + + while (stmt.execute().has_row()) + { + auto column = stmt.fetch(); + + const auto [namespace_str, name, width, height, ptr] = column.get(); + + const auto size_floats = width * height * 4; + + auto& namespace_hash = assets.images[namespace_str]; + auto& image = namespace_hash[name]; + image.width = width; + image.height = height; + image.data.resize(size_floats); + std::memcpy(image.data.data(), ptr, size_floats * sizeof(float)); + } + + return assets; +} + +struct sample_pair +{ + sample_pair(sampler_interface_t& s1, sampler_interface_t& s2): sample1{s1.get_sample()}, sample2{s2.get_sample()} + {} + + [[nodiscard]] bool has_value() const + { + return sample1.has_value() && sample2.has_value(); + } + + explicit operator bool() const + { + return has_value(); + } + + std::optional sample1; + std::optional sample2; +}; + +sampler_one_point_t::sampler_one_point_t(const image_t& image) +{ + float alpha = 0; + for (blt::i32 y = 0; y < image.height; y++) + { + for (blt::i32 x = 0; x < image.width; x++) + { + const auto a = image.data[y * image.width + x + 3]; + average += blt::vec3{ + image.data[y * image.width + x + 0], image.data[y * image.width + x + 1], image.data[y * image.width + x + 2] + } * a; + alpha += a; + } + } + if (alpha != 0) + average = average / (alpha / 3); +} + +blt::vec3 comparator_euclidean_t::compare(sampler_interface_t& s1, sampler_interface_t& s2) +{ + s1.clear(); + s2.clear(); + blt::vec3 total{}; + while (auto sampler = sample_pair{s1, s2}) + { + auto [sample1, sample2] = sampler; + const auto dif = *sample1 - *sample2; + total += dif * dif; + } + return {std::sqrt(total[0]), std::sqrt(total[1]), std::sqrt(total[2])}; +} diff --git a/src/main.cpp b/src/main.cpp index 01d7c91a..7b42e61a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,8 @@ #include "blt/gfx/renderer/camera.h" #include #include +#include +#include blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -57,20 +59,47 @@ void destroy(const blt::gfx::window_data&) blt::gfx::cleanup(); } +void use_database(database_t& db) +{ + const data_loader_t data{db}; + auto assets = data.load(); + + const auto i1 = assets.images["minecraft"]["block/redstone_block"]; + const auto i2 = assets.images["minecraft"]["block/red_wool"]; + + auto s1 = i1.get_default_sampler(); + auto s2 = i2.get_default_sampler(); + + comparator_euclidean_t compare{}; + + const auto difference = compare.compare(s1, s2); + + BLT_TRACE("{}", difference); +} + int main() { - std::filesystem::remove("1.21.5.assets"); + // std::filesystem::remove("1.21.5.assets"); // blt::gfx::init(blt::gfx::window_data{"Minecraft Color Picker", init, update, destroy}.setSyncInterval(1)); - asset_loader_t loader{"1.21.5"}; - - if (const auto result = loader.load_assets("../res/assets", "../res/data")) + if (!std::filesystem::exists("1.21.5.assets")) { - BLT_ERROR("Failed to load assets. Reason: {}", result->to_string()); - return 1; + asset_loader_t loader{"1.21.5"}; + + if (const auto result = loader.load_assets("../res/assets", "../res/data")) + { + BLT_ERROR("Failed to load assets. Reason: {}", result->to_string()); + return 1; + } + + auto& db = loader.load_textures(); + use_database(db); + } else { + auto db = load_database("1.21.5.assets"); + use_database(db); } - auto& asset_data = loader.load_textures(); + return 0; } \ No newline at end of file diff --git a/src/sql.cpp b/src/sql.cpp index 48d3c19a..3a28399d 100644 --- a/src/sql.cpp +++ b/src/sql.cpp @@ -24,14 +24,10 @@ statement_t::statement_t(sqlite3* db, const std::string& stmt): db{db} BLT_ERROR("Failed to create statement object '{}' cause '{}'", stmt, sqlite3_errmsg(db)); } -std::optional statement_t::execute() const +statement_result_t statement_t::execute() const { const auto v = sqlite3_step(statement); - if (v == SQLITE_ROW) - return true; - if (v == SQLITE_OK || v == SQLITE_DONE) - return false; - return {}; + return statement_result_t{v}; } statement_t::~statement_t()