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()