Crafter.Asset/interfaces/Crafter.Asset-Mesh.cppm
2026-02-19 03:00:13 +01:00

244 lines
No EOL
9.6 KiB
C++

/*
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;
}
};
}