differences

dev
Brett 2025-07-14 22:06:10 -04:00
parent 4d5587565f
commit 120a9ba63f
6 changed files with 315 additions and 21 deletions

120
include/data_loader.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef DATA_LOADER_H
#define DATA_LOADER_H
#include <sql.h>
#include <filesystem>
#include <blt/math/vectors.h>
#include <blt/std/hashmap.h>
database_t load_database(const std::filesystem::path& path);
struct image_t;
struct sampler_interface_t
{
virtual void clear()
{}
virtual std::optional<blt::vec3> 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<blt::vec3> 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<blt::vec3> 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<float> data;
[[nodiscard]] auto get_default_sampler() const
{
return sampler_one_point_t{*this};
}
};
struct assets_t
{
blt::hashmap_t<std::string, blt::hashmap_t<std::string, image_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

View File

@ -82,10 +82,10 @@ public:
return static_cast<float>(sqlite3_column_double(statement, col));
} else if constexpr (std::is_same_v<Decay, std::string> || std::is_same_v<Decay, std::string_view> || std::is_same_v<Decay, char*>)
{
return T(sqlite3_column_text(statement, col));
return T{reinterpret_cast<const char*>(sqlite3_column_text(statement, col))};
} else
{
return static_cast<T>(sqlite3_column_blob(statement, col));
return static_cast<const std::remove_pointer_t<T>*>(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<bool> execute() const; // NOLINT
[[jetbrains::has_side_effects]] statement_result_t execute() const; // NOLINT
[[nodiscard]] column_t fetch() const
{

View File

@ -359,12 +359,13 @@ std::optional<load_failure_t> 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<std::string>("namespace").primary_key();
texture_table.with_column<std::string>("name").primary_key();
texture_table.with_column<blt::i32>("width").not_null();
texture_table.with_column<blt::i32>("height").not_null();
texture_table.with_column<std::byte*>("data").not_null();
texture_table.with_column<const std::byte*>("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<std::string>("name").primary_key();
non_texture_table.with_column<blt::i32>("width").not_null();
non_texture_table.with_column<blt::i32>("height").not_null();
non_texture_table.with_column<std::byte*>("data").not_null();
non_texture_table.with_column<const std::byte*>("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<blt::size_t>(width * height * 4)});
stmt.bind().bind_all(namespace_str, texture, width, height, blt::span{
reinterpret_cast<char*>(data), static_cast<blt::size_t>(width * height * 4) * sizeof(float)
});
if (!stmt.execute())
{
BLT_WARN("Failed to insert texture '{}:{}' into database. Error: '{}'", namespace_str, texture, db.get_error());

103
src/data_loader.cpp Normal file
View File

@ -0,0 +1,103 @@
/*
* <Short Description>
* 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 <https://www.gnu.org/licenses/>.
*/
#include <blt/math/log_util.h>
#include <data_loader.h>
#include <blt/logging/logging.h>
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<std::string, std::string, blt::i32, blt::i32, const std::byte*>();
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<blt::vec3> sample1;
std::optional<blt::vec3> 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])};
}

View File

@ -22,6 +22,8 @@
#include "blt/gfx/renderer/camera.h"
#include <imgui.h>
#include <sql.h>
#include <data_loader.h>
#include <blt/math/log_util.h>
blt::gfx::matrix_state_manager global_matrices;
blt::gfx::resource_manager resources;
@ -57,11 +59,31 @@ 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));
if (!std::filesystem::exists("1.21.5.assets"))
{
asset_loader_t loader{"1.21.5"};
if (const auto result = loader.load_assets("../res/assets", "../res/data"))
@ -70,7 +92,14 @@ int main()
return 1;
}
auto& asset_data = loader.load_textures();
auto& db = loader.load_textures();
use_database(db);
} else {
auto db = load_database("1.21.5.assets");
use_database(db);
}
return 0;
}

View File

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