/* 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 normal; Vector tangent; Vector uv; }; struct VertexNormalTangentUV { Vector normal; Vector tangent; Vector uv; }; template struct MeshAsset { std::vector> vertexes; std::vector indexes; std::vector 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(&vertexCount), sizeof(vertexCount)); file.write(reinterpret_cast(&indexCount), sizeof(indexCount)); file.write(reinterpret_cast(&dataCount), sizeof(dataCount)); file.write(reinterpret_cast(vertexes.data()), vertexCount * sizeof(Vector)); file.write(reinterpret_cast(indexes.data()), indexCount * sizeof(std::uint32_t)); file.write(reinterpret_cast(datas.data()), dataCount * sizeof(T)); } static MeshAsset Load(fs::path path) { MeshAsset 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(&vertexCount), sizeof(vertexCount)); file.read(reinterpret_cast(&indexCount), sizeof(indexCount)); file.read(reinterpret_cast(&dataCount), sizeof(dataCount)); mesh.vertexes.resize(vertexCount); mesh.indexes.resize(indexCount); mesh.datas.resize(dataCount); file.read(reinterpret_cast(mesh.vertexes.data()), vertexCount * sizeof(Vector)); file.read(reinterpret_cast(mesh.indexes.data()), indexCount * sizeof(std::uint32_t)); file.read(reinterpret_cast(mesh.datas.data()), dataCount * sizeof(T)); return mesh; } static MeshAsset LoadOBJ(fs::path path) requires (std::same_as || std::same_as) { std::ifstream file(path); MeshAsset mesh; std::string line; std::vector> positions; std::vector> normals; std::vector> uvs; while (std::getline(file, line)) { if (line.substr(0, 2) == "vt") { std::istringstream iss(line.substr(3)); Vector uv; iss >> uv.x >> uv.y; uvs.push_back(uv); } else if (line.substr(0, 2) == "vn") { std::istringstream iss(line.substr(3)); Vector 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 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 v = positions[std::stoi(vIndex) - 1]; Vector vt = uvs[std::stoi(vtIndex) - 1]; Vector 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 edge1 = mesh.vertexes[i1] - mesh.vertexes[i0]; Vector edge2 = mesh.vertexes[i2] - mesh.vertexes[i0]; // Texture coordinate deltas Vector deltaUV1 = mesh.datas[i1].uv - mesh.datas[i0].uv; Vector deltaUV2 = mesh.datas[i2].uv - mesh.datas[i0].uv; // Tangent calculation float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); Vector 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 { std::vector> vertexes; std::vector 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(&vertexCount), sizeof(vertexCount)); file.write(reinterpret_cast(&indexCount), sizeof(indexCount)); file.write(reinterpret_cast(vertexes.data()), vertexCount * sizeof(Vector)); file.write(reinterpret_cast(indexes.data()), indexCount * sizeof(std::uint32_t)); } static MeshAsset Load(fs::path path) { MeshAsset mesh; std::ifstream file(path, std::ios::binary); std::uint32_t vertexCount = 0; std::uint32_t indexCount = 0; file.read(reinterpret_cast(&vertexCount), sizeof(vertexCount)); file.read(reinterpret_cast(&indexCount), sizeof(indexCount)); mesh.vertexes.resize(vertexCount); mesh.indexes.resize(indexCount); file.read(reinterpret_cast(mesh.vertexes.data()), vertexCount * sizeof(Vector)); file.read(reinterpret_cast(mesh.indexes.data()), indexCount * sizeof(std::uint32_t)); return mesh; } static MeshAsset LoadOBJ(fs::path path) { std::ifstream file(path); MeshAsset mesh; std::string line; while (std::getline(file, line)) { if (line.rfind("v ", 0) == 0) { std::istringstream iss(line.substr(2)); Vector 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; } }; }