This commit is contained in:
Jorijn van der Graaf 2026-02-19 03:00:13 +01:00
commit 057e7b94d7
10 changed files with 425 additions and 2323 deletions

View file

@ -1,163 +0,0 @@
/*
Crafter®.Asset
Copyright (C) 2025 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <cstdint>
#include <string>
#include <vector>
#include <fstream>
#include <exception>
#include <filesystem>
#include <iostream>
#include <fstream>
module Crafter.Asset;
using namespace Crafter;
namespace fs = std::filesystem;
struct HeaderData {
std::uint32_t nameLenght;
std::uint32_t typeLenght;
std::uint32_t offset;
std::uint32_t lenght;
};
AssetLoad::AssetLoad(fs::path path) : f(path, std::ios::binary) {
if(!f) {
throw std::runtime_error(std::format("Error opening file {} not found", path.generic_string()));
}
std::uint32_t version;
f.read(reinterpret_cast<char*>(&version), sizeof(std::uint32_t));
std::uint32_t entryCount;
f.read(reinterpret_cast<char*>(&entryCount), sizeof(std::uint32_t));
entries.resize(entryCount);
std::vector<std::uint32_t> lenghts(entryCount);
for(std::uint32_t i = 0; i < entryCount; i++) {
HeaderData header;
f.read(reinterpret_cast<char*>(&header), sizeof(std::uint32_t)*4);
entries[i].name.resize(header.nameLenght);
f.read(reinterpret_cast<char*>(&entries[i].name[0]), header.nameLenght);
entries[i].type.resize(header.typeLenght);
f.read(reinterpret_cast<char*>(&entries[i].type[0]), header.typeLenght);
lenghts[i] = header.lenght;
entries[i].lenght = header.lenght;
entries[i].offset = header.offset;
headerLength+=header.nameLenght + header.typeLenght;
}
headerLength += sizeof(std::uint32_t)*2 + (sizeof(std::uint32_t)*4*entryCount);
}
std::vector<char> AssetLoad::Load(std::string_view name) {
auto it = std::find_if(entries.begin(), entries.end(), [&](const auto& entry) {
return entry.name == name;
});
return LoadOffset(it->offset, it->lenght);
}
std::vector<char> AssetLoad::Load(std::string_view name, uint32_t lenght) {
auto it = std::find_if(entries.begin(), entries.end(), [&](const auto& entry) {
return entry.name == name;
});
return LoadOffset(it->offset, lenght);
}
void AssetLoad::Load(std::string_view name, void* data) {
auto it = std::find_if(entries.begin(), entries.end(), [&](const auto& entry) {
return entry.name == name;
});
return LoadOffset(it->offset, it->lenght, data);
}
void AssetLoad::Load(std::string_view name, void* data, uint32_t lenght) {
auto it = std::find_if(entries.begin(), entries.end(), [&](const auto& entry) {
return entry.name == name;
});
return LoadOffset(it->offset, lenght, data);
}
std::vector<char> AssetLoad::Load(uint32_t index) {
return LoadOffset(entries[index].offset, entries[index].lenght);
}
std::vector<char> AssetLoad::Load(uint32_t index, uint32_t lenght) {
return LoadOffset(entries[index].offset, lenght);
}
void AssetLoad::Load(uint32_t index, void* data) {
return LoadOffset(entries[index].offset, entries[index].lenght, data);
}
void AssetLoad::Load(uint32_t index, void* data, uint32_t lenght) {
return LoadOffset(entries[index].offset, lenght, data);
}
std::vector<char> AssetLoad::Load(const AssetEntry& entry) {
return LoadOffset(entry.offset, entry.lenght);
}
std::vector<char> AssetLoad::Load(const AssetEntry& entry, uint32_t lenght) {
return LoadOffset(entry.offset, lenght);
}
void AssetLoad::Load(const AssetEntry& entry, void* data) {
return LoadOffset(entry.offset, entry.lenght, data);
}
void AssetLoad::Load(const AssetEntry& entry, void* data, uint32_t lenght) {
return LoadOffset(entry.offset, lenght, data);
}
std::vector<char> AssetLoad::LoadOffset(uint32_t offset, uint32_t lenght) {
f.seekg(offset+headerLength, std::ios_base::beg);
std::vector<char> vector(lenght);
f.read(vector.data(), lenght);
return vector;
}
void AssetLoad::LoadOffset(uint32_t offset, uint32_t lenght, void* data) {
f.seekg(offset+headerLength, std::ios_base::beg);
f.read(reinterpret_cast<char*>(data), lenght);
}
std::vector<char> AssetLoad::LoadAll() {
f.seekg(headerLength, std::ios_base::beg);
return std::vector<char>((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
AssetSave::AssetSave(std::span<AssetEntry const> entries, std::span<char const> buffer) : entries(entries.begin(), entries.end()), buffer(buffer.begin(), buffer.end()) {
}
void AssetSave::AddEntry(std::string_view name, std::string_view type, std::span<char const> data) {
entries.emplace_back(name, type, data.size());
buffer.insert(buffer.end(), data.begin(), data.end());
}
void AssetSave::Save(fs::path path) {
std::ofstream f(path, std::ios::binary);
std::uint32_t version = 0;
f.write(reinterpret_cast<const char*>(&version), sizeof(std::uint32_t));
std::uint32_t entriesCount = entries.size();
f.write(reinterpret_cast<const char*>(&entriesCount), sizeof(std::uint32_t));
std::uint32_t offset = 0;
for(const AssetEntry& entry : entries) {
std::uint32_t nameSize = entry.name.size();
f.write(reinterpret_cast<const char*>(&nameSize), sizeof(std::uint32_t));
std::uint32_t typeSize = entry.type.size();
f.write(reinterpret_cast<const char*>(&typeSize), sizeof(std::uint32_t));
f.write(reinterpret_cast<const char*>(&offset), sizeof(std::uint32_t));
f.write(reinterpret_cast<const char*>(&entry.lenght), sizeof(std::uint32_t));
offset+=entry.lenght;
f.write(reinterpret_cast<const char*>(&entry.name[0]), entry.name.size());
f.write(reinterpret_cast<const char*>(&entry.type[0]), entry.type.size());
}
f.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
}

View file

@ -1,244 +0,0 @@
/*
Crafter®.Asset
Copyright (C) 2025 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <cstdint>
#include <string>
#include <vector>
#include <filesystem>
#include <unordered_map>
#include <iostream>
#include <functional>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
export module Crafter.Asset:Asset;
namespace fs = std::filesystem;
struct __attribute__((packed)) Float3 {
float x;
float y;
float z;
};
struct __attribute__((packed)) Float2 {
float x;
float y;
};
struct __attribute__((packed)) VertexUV {
Float3 position;
Float2 uv;
};
void SplitString(const std::string& s, const char delimiter, std::vector<std::string>& tokens) {
std::istringstream iss(s);
std::string token;
while (std::getline(iss, token, delimiter)) {
tokens.push_back(token);
}
}
uint32_t ProcessVertex(std::vector<VertexUV>& verticies, std::vector<Float3> positions, std::vector<Float2> uvs, uint32_t posIndex, uint32_t uvIndex) {
Float3 pos = positions[posIndex - 1];
Float2 uv = uvs[uvIndex - 1];
for (uint32_t i = 0; i < verticies.size(); i++) {
VertexUV vert = verticies[i];
if (vert.position.x == pos.x && vert.position.y == pos.y && vert.position.z == pos.z && vert.uv.x == uv.x && vert.uv.y == uv.y) {
return i;
}
}
verticies.push_back({ pos ,uv });
return verticies.size() - 1;
}
export namespace Crafter {
std::unordered_map<std::string, std::function<std::vector<char>(fs::path)>> serializers = {
{
"PNG:Crafter.Graphics.TextureR8B8G8A8",
[](fs::path file) {
int x,y,n;
unsigned char *imageData = stbi_load(file.generic_string().c_str(),&x, &y, &n, 4);
std::uint32_t x2 = x;
std::uint32_t y2 = y;
std::vector<char> data(sizeof(std::uint32_t)+sizeof(std::uint32_t)+(x*y*4));
memcpy(&data[0], &x2, sizeof(std::uint32_t));
memcpy(&data[sizeof(std::uint32_t)], &y2, sizeof(std::uint32_t));
memcpy(&data[sizeof(std::uint32_t)*2], imageData, x*y*4);
return data;
}
},
{
"OBJ:Crafter.Graphics.Mesh",
[](fs::path path) {
std::ifstream file(path);
std::string line;
std::vector<Float3> positions;
std::vector<std::uint32_t> indices;
while (std::getline(file, line)) {
if (line.substr(0, 1) == "v") {
std::istringstream iss(line.substr(2));
Float3 pos{0,0,0};
iss >> pos.x >> pos.y >> pos.z;
positions.push_back(pos);
}
else if (line.substr(0, 1) == "f") {
std::vector<std::string> tokens;
SplitString(line.substr(2), ' ', tokens);
for (size_t i = 0; i < 3; ++i) {
indices.push_back(std::stoul(tokens[i])-1);
}
}
}
file.close();
std::vector<char> buffer((positions.size()*sizeof(Float3))+(indices.size()*sizeof(std::uint32_t))+sizeof(std::uint32_t)+sizeof(std::uint32_t));
std::uint32_t vertSize = positions.size();
std::uint32_t indexSize = indices.size();
memcpy(&buffer[0], &vertSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)], &indexSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)*2], positions.data(), positions.size()*sizeof(Float3));
memcpy(&buffer[positions.size()*sizeof(Float3)+sizeof(std::uint32_t)+sizeof(std::uint32_t)], indices.data(), indices.size()*sizeof(std::uint32_t));
return buffer;
}
},
{
"OBJ:Crafter.Graphics.MeshUV",
[](fs::path path) {
std::ifstream file(path);
std::string line;
std::vector<Float3> positions;
std::vector<Float2> uvs;
std::vector<VertexUV> verticies;
std::vector<uint32_t> indicies;
while (std::getline(file, line)) {
if (line.substr(0, 2) == "vt") {
std::istringstream iss(line.substr(3));
Float2 uv{0,0};
iss >> uv.x >> uv.y;
uvs.push_back(uv);
}
else if (line.substr(0, 1) == "v") {
std::istringstream iss(line.substr(2));
Float3 pos{0,0,0};
iss >> pos.x >> pos.y >> pos.z;
positions.push_back(pos);
}
else if (line.substr(0, 1) == "f") {
std::vector<std::string> tokens;
SplitString(line.substr(2), ' ', tokens);
for (size_t i = 0; i < 3; ++i) {
size_t slashPos = tokens[i].find('/');
indicies.push_back(ProcessVertex(verticies, positions, uvs, std::stoul(tokens[i].substr(0, slashPos)), std::stoul(tokens[i].substr(slashPos + 1))));
}
}
}
file.close();
std::vector<char> buffer((verticies.size()*sizeof(VertexUV))+(indicies.size()*sizeof(std::uint32_t))+sizeof(std::uint32_t)+sizeof(std::uint32_t));
std::uint32_t vertSize = verticies.size();
std::uint32_t indexSize = indicies.size();
memcpy(&buffer[0], &vertSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)], &indexSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)*2], verticies.data(), verticies.size()*sizeof(VertexUV));
memcpy(&buffer[verticies.size()*sizeof(VertexUV)+sizeof(std::uint32_t)+sizeof(std::uint32_t)], indicies.data(), indicies.size()*sizeof(std::uint32_t));
return buffer;
}
}
};
std::unordered_map<std::string, std::function<void(fs::path, const void*)>> extractors = {
{
"Crafter.Graphics.TextureR8B8G8A8:PNG",
[](fs::path dst, const void* data) {
stbi_write_png(dst.generic_string().c_str(), reinterpret_cast<const std::uint32_t*>(data)[0], reinterpret_cast<const std::uint32_t*>(data)[1], 4, reinterpret_cast<const char*>(data)+(sizeof(std::uint32_t)*2), reinterpret_cast<const std::uint32_t*>(data)[0]*4);
}
}
};
class AssetEntry {
public:
std::string name;
std::string type;
uint32_t offset;
uint32_t lenght;
AssetEntry() = default;
AssetEntry(std::string_view name, std::string_view type, uint32_t lenght) : name(name), type(type), lenght(lenght) {
}
};
class AssetLoad {
public:
std::vector<AssetEntry> entries;
AssetLoad(fs::path path);
AssetLoad() = default;
std::vector<char> Load(std::string_view name);
std::vector<char> Load(std::string_view name, uint32_t lenght);
void Load(std::string_view name, void* data);
void Load(std::string_view name, void* data, uint32_t lenght);
std::vector<char> Load(uint32_t index);
std::vector<char> Load(uint32_t index, uint32_t lenght);
void Load(uint32_t index, void* data);
void Load(uint32_t index, void* data, uint32_t lenght);
std::vector<char> Load(const AssetEntry& entry);
std::vector<char> Load(const AssetEntry& entry, uint32_t lenght);
void Load(const AssetEntry& entry, void* data);
void Load(const AssetEntry& entry, void* data, uint32_t lenght);
std::vector<char> LoadOffset(uint32_t offset, uint32_t lenght);
void LoadOffset(uint32_t offset, uint32_t lenght, void* data);
std::vector<char> LoadAll();
private:
uint32_t headerLength;
std::ifstream f;
};
class AssetSave {
public:
std::vector<AssetEntry> entries;
std::vector<char> buffer;
AssetSave() = default;
AssetSave(std::span<AssetEntry const> entries, std::span<char const> buffer);
void AddEntry(std::string_view name, std::string_view type, std::span<char const> data);
void Save(fs::path path);
};
}

63
implementations/main.cpp Normal file
View file

@ -0,0 +1,63 @@
/*
Crafter®.Asset
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import Crafter.Asset;
import Crafter.Math;
import std;
using namespace Crafter;
namespace fs = std::filesystem;
int main(int argc, char** argv) {
if (argc != 3) {
std::cout << "Usage: crafter-asset <input_file> <output_file>\n";
return 1;
}
fs::path inputPath = argv[1];
fs::path outputPath = argv[2];
if (!fs::exists(inputPath)) {
std::cerr << "Error: Input file does not exist.\n";
return 1;
}
std::string extension = inputPath.extension().string();
try {
if (extension == ".obj") {
// Load OBJ as MeshAsset with normal/tangent/uv data
auto mesh = MeshAsset<VertexNormalTangentUVPacked>::LoadOBJ(inputPath);
mesh.Save(outputPath);
}
else if (extension == ".png") {
// Load PNG as TextureAsset
auto texture = TextureAsset<Vector<std::uint8_t, 4, 0>>::LoadPNG(inputPath);
texture.Save(outputPath);
}
else {
std::cerr << "Unsupported file type: " << extension << "\n";
return 1;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
return 0;
}

View file

@ -0,0 +1,244 @@
/*
Crafter®.Asset
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
export module Crafter.Asset:Mesh;
import Crafter.Math;
import std;
namespace fs = std::filesystem;
export namespace Crafter {
struct __attribute__((packed)) VertexNormalTangentUVPacked {
Vector<float, 3, 4> normal;
Vector<float, 3, 4> tangent;
Vector<float, 2, 4> uv;
};
struct VertexNormalTangentUV {
Vector<float, 3, 0> normal;
Vector<float, 3, 0> tangent;
Vector<float, 2, 0> uv;
};
template <typename T>
struct MeshAsset {
std::vector<Vector<float, 3, 3>> vertexes;
std::vector<std::uint32_t> indexes;
std::vector<T> datas;
void Save(fs::path path) {
std::ofstream file(path, std::ios::binary);
std::uint32_t vertexCount = vertexes.size();
std::uint32_t indexCount = indexes.size();
std::uint32_t dataCount = datas.size();
file.write(reinterpret_cast<char*>(&vertexCount), sizeof(vertexCount));
file.write(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));
file.write(reinterpret_cast<char*>(&dataCount), sizeof(dataCount));
file.write(reinterpret_cast<char*>(vertexes.data()), vertexCount * sizeof(Vector<float, 3, 3>));
file.write(reinterpret_cast<char*>(indexes.data()), indexCount * sizeof(std::uint32_t));
file.write(reinterpret_cast<char*>(datas.data()), dataCount * sizeof(T));
}
static MeshAsset<T> Load(fs::path path) {
MeshAsset<T> mesh;
std::ifstream file(path, std::ios::binary);
std::uint32_t vertexCount = 0;
std::uint32_t indexCount = 0;
std::uint32_t dataCount = 0;
file.read(reinterpret_cast<char*>(&vertexCount), sizeof(vertexCount));
file.read(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));
file.read(reinterpret_cast<char*>(&dataCount), sizeof(dataCount));
mesh.vertexes.resize(vertexCount);
mesh.indexes.resize(indexCount);
mesh.datas.resize(dataCount);
file.read(reinterpret_cast<char*>(mesh.vertexes.data()), vertexCount * sizeof(Vector<float, 3, 3>));
file.read(reinterpret_cast<char*>(mesh.indexes.data()), indexCount * sizeof(std::uint32_t));
file.read(reinterpret_cast<char*>(mesh.datas.data()), dataCount * sizeof(T));
return mesh;
}
static MeshAsset<T> LoadOBJ(fs::path path) requires (std::same_as<T, VertexNormalTangentUVPacked> || std::same_as<T, VertexNormalTangentUV>) {
std::ifstream file(path);
MeshAsset<T> mesh;
std::string line;
std::vector<Vector<float, 3, 0>> positions;
std::vector<Vector<float, 3, 0>> normals;
std::vector<Vector<float, 2, 0>> uvs;
while (std::getline(file, line)) {
if (line.substr(0, 2) == "vt") {
std::istringstream iss(line.substr(3));
Vector<float,2, 0> uv;
iss >> uv.x >> uv.y;
uvs.push_back(uv);
} else if (line.substr(0, 2) == "vn") {
std::istringstream iss(line.substr(3));
Vector<float, 3, 0> normal;
iss >> normal.x >> normal.y >> normal.z;
normals.push_back(normal);
} else if (line.substr(0, 1) == "v") {
std::istringstream iss(line.substr(2));
Vector<float,3, 3> position;
iss >> position.x >> position.y >> position.z;
positions.push_back(position);
} else if (line.substr(0, 1) == "f") {
std::istringstream iss(line.substr(2));
std::string vertexGroup;
while (iss >> vertexGroup) {
std::istringstream vss(vertexGroup);
std::string vIndex, vtIndex, vnIndex;
std::getline(vss, vIndex, '/');
std::getline(vss, vtIndex, '/');
std::getline(vss, vnIndex, '/');
Vector<float, 3, 0> v = positions[std::stoi(vIndex) - 1];
Vector<float, 2, 0> vt = uvs[std::stoi(vtIndex) - 1];
Vector<float, 3, 0> vn = normals[std::stoi(vnIndex) - 1];
for (std::uint32_t i = 0; i < mesh.datas.size(); i++) {
if (mesh.datas[i].normal == vn && mesh.datas[i].uv == vt && mesh.vertexes[i] == v) {
mesh.indexes.push_back(i);
goto skip;
}
}
mesh.indexes.push_back(mesh.datas.size());
mesh.datas.push_back({vn, {0,0,0}, vt});
mesh.vertexes.push_back(v);
skip:;
}
}
}
// Accumulate face normals and tangents
for (std::uint32_t i = 0; i < mesh.indexes.size(); i += 3)
{
std::uint32_t i0 = mesh.indexes[i];
std::uint32_t i1 = mesh.indexes[i + 1];
std::uint32_t i2 = mesh.indexes[i + 2];
// Edges of the triangle
Vector<float, 3, 0> edge1 = mesh.vertexes[i1] - mesh.vertexes[i0];
Vector<float, 3, 0> edge2 = mesh.vertexes[i2] - mesh.vertexes[i0];
// Texture coordinate deltas
Vector<float, 2, 0> deltaUV1 = mesh.datas[i1].uv - mesh.datas[i0].uv;
Vector<float, 2, 0> deltaUV2 = mesh.datas[i2].uv - mesh.datas[i0].uv;
// Tangent calculation
float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
Vector<float, 3, 0> tangent;
tangent.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);
// Normalize tangent
tangent.Normalize();
// Accumulate normals and tangents for each vertex
mesh.datas[i0].tangent += tangent;
mesh.datas[i1].tangent += tangent;
mesh.datas[i2].tangent += tangent;
}
// Normalize vertex normals and tangents
for (T& v : mesh.datas) {
v.tangent.Normalize();
}
return mesh;
}
};
template <>
struct MeshAsset<void> {
std::vector<Vector<float, 3, 3>> vertexes;
std::vector<std::uint32_t> indexes;
void Save(fs::path path) {
std::ofstream file(path, std::ios::binary);
std::uint32_t vertexCount = vertexes.size();
std::uint32_t indexCount = indexes.size();
file.write(reinterpret_cast<char*>(&vertexCount), sizeof(vertexCount));
file.write(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));
file.write(reinterpret_cast<char*>(vertexes.data()), vertexCount * sizeof(Vector<float, 3, 3>));
file.write(reinterpret_cast<char*>(indexes.data()), indexCount * sizeof(std::uint32_t));
}
static MeshAsset<void> Load(fs::path path) {
MeshAsset<void> mesh;
std::ifstream file(path, std::ios::binary);
std::uint32_t vertexCount = 0;
std::uint32_t indexCount = 0;
file.read(reinterpret_cast<char*>(&vertexCount), sizeof(vertexCount));
file.read(reinterpret_cast<char*>(&indexCount), sizeof(indexCount));
mesh.vertexes.resize(vertexCount);
mesh.indexes.resize(indexCount);
file.read(reinterpret_cast<char*>(mesh.vertexes.data()), vertexCount * sizeof(Vector<float, 3, 3>));
file.read(reinterpret_cast<char*>(mesh.indexes.data()), indexCount * sizeof(std::uint32_t));
return mesh;
}
static MeshAsset<void> LoadOBJ(fs::path path) {
std::ifstream file(path);
MeshAsset<void> mesh;
std::string line;
while (std::getline(file, line)) {
if (line.rfind("v ", 0) == 0) {
std::istringstream iss(line.substr(2));
Vector<float,3, 3> position;
iss >> position.x >> position.y >> position.z;
mesh.vertexes.push_back(position);
} else if (line.rfind("f ", 0) == 0) {
std::istringstream iss(line.substr(2));
std::string vertexGroup;
while (iss >> vertexGroup) {
mesh.indexes.push_back(std::stoi(vertexGroup) - 1);
}
}
}
return mesh;
}
};
}

View file

@ -0,0 +1,81 @@
/*
Crafter®.Asset
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#define STB_IMAGE_IMPLEMENTATION
#include "../lib/stb_image.h"
export module Crafter.Asset:Texture;
import std;
import Crafter.Math;
namespace fs = std::filesystem;
export namespace Crafter {
template <typename T>
struct TextureAsset {
std::uint32_t sizeX;
std::uint32_t sizeY;
std::uint32_t sizeZ;
std::vector<T> pixels;
void Save(fs::path path) {
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<char*>(&sizeX), sizeof(sizeX));
file.write(reinterpret_cast<char*>(&sizeY), sizeof(sizeY));
file.write(reinterpret_cast<char*>(&sizeZ), sizeof(sizeZ));
file.write(reinterpret_cast<char*>(pixels.data()), pixels.size() * sizeof(T));
}
static TextureAsset<T> Load(fs::path path) {
TextureAsset<T> tex;
std::ifstream file(path, std::ios::binary);
file.read(reinterpret_cast<char*>(&tex.sizeX), sizeof(tex.sizeX));
file.read(reinterpret_cast<char*>(&tex.sizeY), sizeof(tex.sizeY));
file.read(reinterpret_cast<char*>(&tex.sizeZ), sizeof(tex.sizeZ));
tex.pixels.resize(tex.sizeX * tex.sizeY * tex.sizeZ);
file.read(reinterpret_cast<char*>(tex.pixels.data()), tex.sizeX * tex.sizeY * tex.sizeZ * sizeof(T));
}
static TextureAsset<T> LoadPNG(fs::path path) requires (std::same_as<T, Vector<std::uint8_t, 4, 0>>) {
TextureAsset<T> tex;
std::filesystem::path abs = std::filesystem::absolute(path);
int sizeX;
int sizeY;
unsigned char* data = stbi_load(abs.string().c_str(), &sizeX, &sizeY, nullptr, 4);
tex.sizeX = sizeX;
tex.sizeY = sizeY;
tex.sizeZ = 1;
tex.pixels.resize(tex.sizeX*tex.sizeY);
std::memcpy(tex.pixels.data(), data, tex.sizeX * tex.sizeY * 4);
return tex;
}
};
}

View file

@ -1,12 +1,11 @@
/*
Crafter®.Asset
Copyright (C) 2025 Catcrafts®
Catcrafts.net
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -18,5 +17,7 @@ License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
export module Crafter.Asset;
export import :Asset;
export import :Mesh;
export import :Texture;

166
main.cpp
View file

@ -1,166 +0,0 @@
/*
Crafter®.Asset
Copyright (C) 2025 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <iostream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <vector>
#include <functional>
import Crafter.Asset;
using namespace Crafter;
namespace fs = std::filesystem;
int main(int argc, char* argv[]) {
if(argc == 1) {
std::cout << "No arguments provided, use help for help" << std::endl;
return -1;
}
std::string command = std::string(argv[1]);
if(command == "help") {
std::cout << "help: Displays this help message.\nadd {file} {entry file} {entry type} {serializer} {entry name}: Adds an entry to an existing assset file or creates a new file if the file in {file} does not exist, {entry file} is the path to the file you want to add to the asset, {entry type} is the type of the resulting entry, {serializer} is the serializer to use to add the entry file to the asset, {entry name} is the name of the resulting entry\nremove {file} {entry}\nextract {file} {entry name} {extractor} {extracted path}: Extracts a entry with the name {entry name} from the asset file in {file}, using the extractor in {extractor} writes the result to the file {extracted path}\nlist serialize: Shows a list of the support serializers\nlist {file}: Lists all entries in the asset file\nlist extract: Shows a list of supported extractors" << std::endl;
} else if(command == "list") {
if(argc == 2) {
std::cout << "Unkown command, use help for help" << std::endl;
return -1;
}
std::string command2 = std::string(argv[2]);
if(command2 == "serialize") {
for (const auto& pair : serializers) {
std::cout << pair.first << std::endl;
}
} else if(command2 == "extract") {
for (const auto& pair : extractors) {
std::cout << pair.first << std::endl;
}
} else {
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
AssetLoad asset(argv[2]);
std::cout << "<name>\t<type>\t<size>" << std::endl;
for(const AssetEntry& entry : asset.entries) {
std::cout << std::format("{}\t{}\t{}", entry.name, entry.type, entry.lenght) << std::endl;
}
}
} else if(command == "remove") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {enty name}. Run help for help" << std::endl;
return -1;
}
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
AssetLoad asset(argv[2]);
if(asset.entries.size() == 0) {
std::cout << "Nothing to remove, asset file empty" << std::endl;
}
for (auto it = asset.entries.begin(); it != asset.entries.end(); std::advance(it, 1)) {
if (it->name == argv[3]) {
asset.entries.erase(it);
fs::remove(argv[2]);
AssetSave ass2(asset.entries, std::move(asset.LoadAll()));
ass2.Save(argv[2]);
return 0;
}
std::cout << std::format("Entry {} not found", argv[3]) << std::endl;
return -1;
}
} else if(command == "add") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {entry file}. Run help for help" << std::endl;
return -1;
} else if(argc == 4) {
std::cout << "Missing argument {entry type}. Run help for help" << std::endl;
return -1;
} else if(argc == 5) {
std::cout << "Missing argument {serializer}. Run help for help" << std::endl;
return -1;
} else if(argc == 6) {
std::cout << "Missing argument {entry name}. Run help for help" << std::endl;
return -1;
}
AssetEntry entry;
entry.name = std::string(argv[6]);
entry.type = std::string(argv[4]);
std::unordered_map<std::string, std::function<std::vector<char>(fs::path)>>::const_iterator pos = serializers.find(argv[5]);
if (pos == serializers.end()) {
std::cout << std::format("Unkown serializer {}", argv[5]) << std::endl;
return -1;
} else {
AssetSave asset;
if(fs::exists(argv[2])) {
AssetLoad assload(argv[2]);
asset = AssetSave(assload.entries, std::move(assload.LoadAll()));
fs::remove(argv[2]);
}
asset.AddEntry(entry.name, entry.type, pos->second(argv[3]));
asset.Save(argv[2]);
}
} else if(command == "extract") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {entry name}. Run help for help" << std::endl;
return -1;
} else if(argc == 4) {
std::cout << "Missing argument {extractor}. Run help for help" << std::endl;
return -1;
} else if(argc == 5) {
std::cout << "Missing argument {extracted path}. Run help for help" << std::endl;
return -1;
}
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
AssetLoad asset(argv[2]);
auto pos = extractors.find(argv[4]);
if (pos == extractors.end()) {
std::cout << std::format("Unkown extractor {}", argv[4]) << std::endl;
return -1;
} else {
for(const AssetEntry& entry : asset.entries) {
if(entry.name == std::string(argv[3])) {
std::vector<char> loaded = asset.Load(entry);
pos->second(argv[5], loaded.data());
}
}
std::cout << std::format("Entry {} not found", argv[3]) << std::endl;
return -1;
}
} else {
std::cout << "Unkown command, use help for help" << std::endl;
}
}

View file

@ -3,41 +3,51 @@
"configurations": [
{
"name": "base",
"standard": "c++26",
"source_files": ["Crafter.Asset-Asset"],
"module_files": ["Crafter.Asset-Asset", "Crafter.Asset"],
"build_dir": "build",
"output_dir": "bin"
"implementations": ["implementations/main"],
"interfaces": ["interfaces/Crafter.Asset", "interfaces/Crafter.Asset-Texture", "interfaces/Crafter.Asset-Mesh"]
},
{
"name": "deps-debug",
"extends": ["base"],
"dependencies": [
{
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git",
"configuration":"lib-debug"
}
]
},
{
"name": "deps",
"extends": ["base"],
"type":"executable",
"dependencies": [
{
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git",
"configuration":"lib"
}
]
},
{
"name": "executable",
"extends": ["base"],
"type":"executable",
"source_files": ["main"]
"extends": ["deps"],
"type": "executable"
},
{
"name": "executable-debug",
"extends": ["executable"],
"extends": ["deps-debug"],
"type": "executable",
"debug": true
},
{
"name": "executable-release",
"extends": ["executable"],
"optimization_level": "3"
},
{
"name": "lib",
"extends": ["base"],
"type":"library"
"extends": ["deps"],
"type": "library"
},
{
"name": "lib-debug",
"extends": ["lib"],
"debug": true
},
{
"name": "lib-release",
"extends": ["lib"],
"extends": ["deps-debug"],
"type": "library",
"debug": true
}
]

File diff suppressed because it is too large Load diff