summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-13 08:46:44 +0100
committerskal <pascal.massimino@gmail.com>2026-02-13 08:46:44 +0100
commit6a726a83d534e338e9badcfc26cb13cbc003967b (patch)
tree20bee709cf26240dd60fc2cd0223a0e2df47cca2
parent65f7d74cbd4b34640dda73100c45f25ad468349d (diff)
Refactor: Consolidate asset_packer.cc helpers
Extract repeated logic into focused helper functions: - ParseProceduralParams() - eliminates 2× duplicate parameter parsing - ParseProceduralFunction() - unifies PROC() and PROC_GPU() handling - ProcessMeshFile() - encapsulates 164-line mesh processing - ProcessImageFile() - encapsulates image loading Reduces 598→568 lines (-5%), improves readability, preserves behavior. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> EOF
-rw-r--r--tools/asset_packer.cc562
1 files changed, 268 insertions, 294 deletions
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc
index 5646716..e7f678b 100644
--- a/tools/asset_packer.cc
+++ b/tools/asset_packer.cc
@@ -35,12 +35,8 @@ static const std::map<std::string, ProcGenFunc> kAssetPackerProcGenFuncMap = {
static bool HasImageExtension(const std::string& filename) {
std::string ext = filename.substr(filename.find_last_of(".") + 1);
- // simple case-insensitive check (assuming lowercase for simplicity or just
- // basic checks)
- if (ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "tga" ||
- ext == "bmp")
- return true;
- return false;
+ return ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "tga" ||
+ ext == "bmp";
}
static bool HasMeshExtension(const std::string& filename) {
@@ -48,6 +44,35 @@ static bool HasMeshExtension(const std::string& filename) {
return ext == "obj";
}
+// Forward declaration
+struct AssetBuildInfo;
+
+static bool ParseProceduralParams(const std::string& params_str,
+ std::vector<float>* out_params,
+ const std::string& asset_name) {
+ size_t current_pos = 0;
+ while (current_pos < params_str.length()) {
+ size_t comma_pos = params_str.find(',', current_pos);
+ std::string param_val_str =
+ (comma_pos == std::string::npos)
+ ? params_str.substr(current_pos)
+ : params_str.substr(current_pos, comma_pos - current_pos);
+ param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n"));
+ param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1);
+ try {
+ out_params->push_back(std::stof(param_val_str));
+ } catch (...) {
+ fprintf(stderr, "Error: Invalid proc param for %s: %s\n",
+ asset_name.c_str(), param_val_str.c_str());
+ return false;
+ }
+ if (comma_pos == std::string::npos)
+ break;
+ current_pos = comma_pos + 1;
+ }
+ return true;
+}
+
// Helper struct to hold all information about an asset during parsing
struct AssetBuildInfo {
std::string name;
@@ -63,35 +88,256 @@ struct AssetBuildInfo {
std::string func_name_str_name; // ASSET_PROC_FUNC_STR_xxx for procedural
};
+static bool ParseProceduralFunction(const std::string& compression_type_str,
+ AssetBuildInfo* info, bool is_gpu) {
+ const char* prefix = is_gpu ? "PROC_GPU(" : "PROC(";
+ size_t prefix_len = is_gpu ? 9 : 5;
+
+ size_t open_paren = compression_type_str.find('(');
+ size_t close_paren = compression_type_str.rfind(')');
+ if (open_paren == std::string::npos || close_paren == std::string::npos) {
+ fprintf(stderr, "Error: Invalid %s syntax for asset: %s, string: [%s]\n",
+ prefix, info->name.c_str(), compression_type_str.c_str());
+ return false;
+ }
+
+ std::string func_and_params_str =
+ compression_type_str.substr(open_paren + 1, close_paren - open_paren - 1);
+ size_t params_start = func_and_params_str.find(',');
+
+ if (params_start != std::string::npos) {
+ info->proc_func_name = func_and_params_str.substr(0, params_start);
+ std::string params_str = func_and_params_str.substr(params_start + 1);
+ if (!ParseProceduralParams(params_str, &info->proc_params, info->name)) {
+ return false;
+ }
+ } else {
+ info->proc_func_name = func_and_params_str;
+ }
+
+ if (is_gpu) {
+ if (info->proc_func_name != "gen_noise" &&
+ info->proc_func_name != "gen_perlin" &&
+ info->proc_func_name != "gen_grid") {
+ fprintf(stderr,
+ "Error: PROC_GPU only supports gen_noise, gen_perlin, gen_grid, "
+ "got: %s for asset: %s\n",
+ info->proc_func_name.c_str(), info->name.c_str());
+ return false;
+ }
+ } else {
+ if (kAssetPackerProcGenFuncMap.find(info->proc_func_name) ==
+ kAssetPackerProcGenFuncMap.end()) {
+ fprintf(stderr,
+ "Warning: Unknown procedural function: %s for asset: %s "
+ "(Runtime error will occur)\n",
+ info->proc_func_name.c_str(), info->name.c_str());
+ }
+ }
+
+ return true;
+}
+
struct Vec3 {
float x, y, z;
- Vec3 operator+(const Vec3& o) const {
- return {x + o.x, y + o.y, z + o.z};
- }
+ Vec3 operator+(const Vec3& o) const { return {x + o.x, y + o.y, z + o.z}; }
Vec3 operator+=(const Vec3& o) {
x += o.x;
y += o.y;
z += o.z;
return *this;
}
- Vec3 operator-(const Vec3& o) const {
- return {x - o.x, y - o.y, z - o.z};
- }
- Vec3 operator*(float s) const {
- return {x * s, y * s, z * s};
- }
+ Vec3 operator-(const Vec3& o) const { return {x - o.x, y - o.y, z - o.z}; }
+ Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
static Vec3 cross(const Vec3& a, const Vec3& b) {
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x};
}
Vec3 normalize() const {
float len = std::sqrt(x * x + y * y + z * z);
- if (len > 1e-6f)
- return {x / len, y / len, z / len};
- return {0, 0, 0};
+ return (len > 1e-6f) ? Vec3{x / len, y / len, z / len} : Vec3{0, 0, 0};
}
};
+struct Vertex {
+ float p[3], n[3], u[2];
+};
+
+static bool ProcessMeshFile(const std::string& full_path,
+ std::vector<uint8_t>* buffer,
+ const std::string& asset_name) {
+ std::ifstream obj_file(full_path);
+ if (!obj_file.is_open()) {
+ fprintf(stderr, "Error: Could not open mesh file: %s\n", full_path.c_str());
+ return false;
+ }
+
+ std::vector<float> v_pos, v_norm, v_uv;
+ struct RawFace {
+ int v[3], vt[3], vn[3];
+ };
+ std::vector<RawFace> raw_faces;
+
+ std::string obj_line;
+ while (std::getline(obj_file, obj_line)) {
+ if (obj_line.compare(0, 2, "v ") == 0) {
+ float x, y, z;
+ std::sscanf(obj_line.c_str(), "v %f %f %f", &x, &y, &z);
+ v_pos.push_back(x);
+ v_pos.push_back(y);
+ v_pos.push_back(z);
+ } else if (obj_line.compare(0, 3, "vn ") == 0) {
+ float x, y, z;
+ std::sscanf(obj_line.c_str(), "vn %f %f %f", &x, &y, &z);
+ v_norm.push_back(x);
+ v_norm.push_back(y);
+ v_norm.push_back(z);
+ } else if (obj_line.compare(0, 3, "vt ") == 0) {
+ float u, v;
+ std::sscanf(obj_line.c_str(), "vt %f %f", &u, &v);
+ v_uv.push_back(u);
+ v_uv.push_back(v);
+ } else if (obj_line.compare(0, 2, "f ") == 0) {
+ char s1[64], s2[64], s3[64];
+ if (std::sscanf(obj_line.c_str(), "f %s %s %s", s1, s2, s3) == 3) {
+ std::string parts[3] = {s1, s2, s3};
+ RawFace face = {};
+ for (int i = 0; i < 3; ++i) {
+ int v_idx = 0, vt_idx = 0, vn_idx = 0;
+ if (parts[i].find("//") != std::string::npos) {
+ std::sscanf(parts[i].c_str(), "%d//%d", &v_idx, &vn_idx);
+ } else if (std::count(parts[i].begin(), parts[i].end(), '/') == 2) {
+ std::sscanf(parts[i].c_str(), "%d/%d/%d", &v_idx, &vt_idx, &vn_idx);
+ } else if (std::count(parts[i].begin(), parts[i].end(), '/') == 1) {
+ std::sscanf(parts[i].c_str(), "%d/%d", &v_idx, &vt_idx);
+ } else {
+ std::sscanf(parts[i].c_str(), "%d", &v_idx);
+ }
+ face.v[i] = v_idx;
+ face.vt[i] = vt_idx;
+ face.vn[i] = vn_idx;
+ }
+ raw_faces.push_back(face);
+ }
+ }
+ }
+
+ // Generate normals if missing
+ if (v_norm.empty() && !v_pos.empty()) {
+ printf("Generating normals for %s...\n", asset_name.c_str());
+ std::vector<Vec3> temp_normals(v_pos.size() / 3, {0, 0, 0});
+ for (auto& face : raw_faces) {
+ int idx0 = face.v[0] - 1;
+ int idx1 = face.v[1] - 1;
+ int idx2 = face.v[2] - 1;
+
+ if (idx0 >= 0 && idx1 >= 0 && idx2 >= 0) {
+ Vec3 p0 = {v_pos[idx0 * 3], v_pos[idx0 * 3 + 1], v_pos[idx0 * 3 + 2]};
+ Vec3 p1 = {v_pos[idx1 * 3], v_pos[idx1 * 3 + 1], v_pos[idx1 * 3 + 2]};
+ Vec3 p2 = {v_pos[idx2 * 3], v_pos[idx2 * 3 + 1], v_pos[idx2 * 3 + 2]};
+
+ Vec3 normal = Vec3::cross(p1 - p0, p2 - p0).normalize();
+ temp_normals[idx0] += normal;
+ temp_normals[idx1] += normal;
+ temp_normals[idx2] += normal;
+ }
+ }
+
+ for (const auto& n : temp_normals) {
+ Vec3 normalized = n.normalize();
+ v_norm.push_back(normalized.x);
+ v_norm.push_back(normalized.y);
+ v_norm.push_back(normalized.z);
+ }
+
+ for (auto& face : raw_faces) {
+ face.vn[0] = face.v[0];
+ face.vn[1] = face.v[1];
+ face.vn[2] = face.v[2];
+ }
+ }
+
+ // Build final vertices
+ std::vector<Vertex> final_vertices;
+ std::vector<uint32_t> final_indices;
+ std::map<std::string, uint32_t> vertex_map;
+
+ for (const auto& face : raw_faces) {
+ for (int i = 0; i < 3; ++i) {
+ char key_buf[128];
+ std::snprintf(key_buf, sizeof(key_buf), "%d/%d/%d", face.v[i],
+ face.vt[i], face.vn[i]);
+ std::string key = key_buf;
+
+ if (vertex_map.find(key) == vertex_map.end()) {
+ vertex_map[key] = (uint32_t)final_vertices.size();
+
+ Vertex v = {};
+ if (face.v[i] > 0) {
+ v.p[0] = v_pos[(face.v[i] - 1) * 3];
+ v.p[1] = v_pos[(face.v[i] - 1) * 3 + 1];
+ v.p[2] = v_pos[(face.v[i] - 1) * 3 + 2];
+ }
+ if (face.vn[i] > 0) {
+ v.n[0] = v_norm[(face.vn[i] - 1) * 3];
+ v.n[1] = v_norm[(face.vn[i] - 1) * 3 + 1];
+ v.n[2] = v_norm[(face.vn[i] - 1) * 3 + 2];
+ }
+ if (face.vt[i] > 0) {
+ v.u[0] = v_uv[(face.vt[i] - 1) * 2];
+ v.u[1] = v_uv[(face.vt[i] - 1) * 2 + 1];
+ }
+ final_vertices.push_back(v);
+ }
+ final_indices.push_back(vertex_map[key]);
+ }
+ }
+
+ // Format: [num_vertices][Vertex*N][num_indices][uint32_t*N]
+ buffer->resize(sizeof(uint32_t) + final_vertices.size() * sizeof(Vertex) +
+ sizeof(uint32_t) + final_indices.size() * sizeof(uint32_t));
+ uint8_t* out_ptr = buffer->data();
+ *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_vertices.size();
+ out_ptr += sizeof(uint32_t);
+ std::memcpy(out_ptr, final_vertices.data(),
+ final_vertices.size() * sizeof(Vertex));
+ out_ptr += final_vertices.size() * sizeof(Vertex);
+ *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_indices.size();
+ out_ptr += sizeof(uint32_t);
+ std::memcpy(out_ptr, final_indices.data(),
+ final_indices.size() * sizeof(uint32_t));
+
+ printf("Processed mesh asset %s: %zu vertices, %zu indices\n",
+ asset_name.c_str(), final_vertices.size(), final_indices.size());
+
+ return true;
+}
+
+static bool ProcessImageFile(const std::string& full_path,
+ std::vector<uint8_t>* buffer,
+ const std::string& asset_name) {
+ int w, h, channels;
+ unsigned char* img_data =
+ stbi_load(full_path.c_str(), &w, &h, &channels, 4); // Force RGBA
+ if (!img_data) {
+ fprintf(stderr, "Error: Could not load image file: %s (Reason: %s)\n",
+ full_path.c_str(), stbi_failure_reason());
+ return false;
+ }
+
+ // Format: [Width(4)][Height(4)][Pixels...]
+ buffer->resize(sizeof(uint32_t) * 2 + w * h * 4);
+ uint32_t* header = reinterpret_cast<uint32_t*>(buffer->data());
+ header[0] = (uint32_t)w;
+ header[1] = (uint32_t)h;
+ std::memcpy(buffer->data() + sizeof(uint32_t) * 2, img_data, w * h * 4);
+
+ stbi_image_free(img_data);
+ printf("Processed image asset %s: %dx%d RGBA\n", asset_name.c_str(), w, h);
+
+ return true;
+}
+
int main(int argc, char* argv[]) {
if (argc != 4) {
fprintf(stderr,
@@ -189,110 +435,15 @@ int main(int argc, char* argv[]) {
if (compression_type_str.rfind("PROC_GPU(", 0) == 0) {
info.is_procedural = true;
info.is_gpu_procedural = true;
- size_t open_paren = compression_type_str.find('(');
- size_t close_paren = compression_type_str.rfind(')');
- if (open_paren == std::string::npos ||
- close_paren == std::string::npos) {
- fprintf(stderr,
- "Error: Invalid PROC_GPU() syntax for asset: %s, string: [%s]\n",
- info.name.c_str(), compression_type_str.c_str());
- return 1;
- }
- std::string func_and_params_str = compression_type_str.substr(
- open_paren + 1, close_paren - open_paren - 1);
-
- size_t params_start = func_and_params_str.find(',');
- if (params_start != std::string::npos) {
- std::string params_str = func_and_params_str.substr(params_start + 1);
- info.proc_func_name = func_and_params_str.substr(0, params_start);
-
- size_t current_pos = 0;
- while (current_pos < params_str.length()) {
- size_t comma_pos = params_str.find(',', current_pos);
- std::string param_val_str =
- (comma_pos == std::string::npos)
- ? params_str.substr(current_pos)
- : params_str.substr(current_pos, comma_pos - current_pos);
- param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n"));
- param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1);
- try {
- info.proc_params.push_back(std::stof(param_val_str));
- } catch (...) {
- fprintf(stderr, "Error: Invalid proc param for %s: %s\n",
- info.name.c_str(), param_val_str.c_str());
- return 1;
- }
- if (comma_pos == std::string::npos)
- break;
- current_pos = comma_pos + 1;
- }
- } else {
- info.proc_func_name = func_and_params_str;
- }
-
- // Validate GPU procedural function name
- if (info.proc_func_name != "gen_noise" &&
- info.proc_func_name != "gen_perlin" &&
- info.proc_func_name != "gen_grid") {
- fprintf(stderr,
- "Error: PROC_GPU only supports gen_noise, gen_perlin, gen_grid, got: %s for asset: %s\n",
- info.proc_func_name.c_str(), info.name.c_str());
+ if (!ParseProceduralFunction(compression_type_str, &info, true)) {
return 1;
}
} else if (compression_type_str.rfind("PROC(", 0) == 0) {
info.is_procedural = true;
info.is_gpu_procedural = false;
- size_t open_paren = compression_type_str.find('(');
- size_t close_paren = compression_type_str.rfind(')');
- if (open_paren == std::string::npos ||
- close_paren == std::string::npos) {
- fprintf(stderr,
- "Error: Invalid PROC() syntax for asset: %s, string: [%s]\n",
- info.name.c_str(), compression_type_str.c_str());
+ if (!ParseProceduralFunction(compression_type_str, &info, false)) {
return 1;
}
- std::string func_and_params_str = compression_type_str.substr(
- open_paren + 1, close_paren - open_paren - 1);
-
- size_t params_start = func_and_params_str.find(',');
- if (params_start != std::string::npos) {
- std::string params_str = func_and_params_str.substr(params_start + 1);
- info.proc_func_name = func_and_params_str.substr(0, params_start);
-
- size_t current_pos = 0;
- while (current_pos < params_str.length()) {
- size_t comma_pos = params_str.find(',', current_pos);
- std::string param_val_str =
- (comma_pos == std::string::npos)
- ? params_str.substr(current_pos)
- : params_str.substr(current_pos, comma_pos - current_pos);
- param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n"));
- param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1);
- try {
- info.proc_params.push_back(std::stof(param_val_str));
- } catch (...) {
- fprintf(stderr, "Error: Invalid proc param for %s: %s\n",
- info.name.c_str(), param_val_str.c_str());
- return 1;
- }
- if (comma_pos == std::string::npos)
- break;
- current_pos = comma_pos + 1;
- }
- } else {
- info.proc_func_name = func_and_params_str;
- }
-
- // Validate procedural function name
- // kAssetPackerProcGenFuncMap is defined globally for validation
- if (kAssetPackerProcGenFuncMap.find(info.proc_func_name) ==
- kAssetPackerProcGenFuncMap.end()) {
- fprintf(stderr,
- "Warning: Unknown procedural function: %s for asset: %s "
- "(Runtime error will occur)\n",
- info.proc_func_name.c_str(), info.name.c_str());
- // return 1; // Allow unknown functions for testing runtime handling
- }
}
asset_build_infos.push_back(info);
@@ -330,190 +481,13 @@ int main(int argc, char* argv[]) {
bool is_mesh = HasMeshExtension(info.filename);
if (is_image) {
- int w, h, channels;
- unsigned char* img_data = stbi_load(
- full_path.c_str(), &w, &h, &channels, 4); // Force 4 channels (RGBA)
- if (!img_data) {
- fprintf(stderr, "Error: Could not load image file: %s (Reason: %s)\n",
- full_path.c_str(), stbi_failure_reason());
+ if (!ProcessImageFile(full_path, &buffer, info.name)) {
return 1;
}
-
- // Format: [Width(4)][Height(4)][Pixels...]
- buffer.resize(sizeof(uint32_t) * 2 + w * h * 4);
- uint32_t* header = reinterpret_cast<uint32_t*>(buffer.data());
- header[0] = (uint32_t)w;
- header[1] = (uint32_t)h;
- std::memcpy(buffer.data() + sizeof(uint32_t) * 2, img_data, w * h * 4);
-
- stbi_image_free(img_data);
- printf("Processed image asset %s: %dx%d RGBA\n", info.name.c_str(), w,
- h);
} else if (is_mesh) {
- std::ifstream obj_file(full_path);
- if (!obj_file.is_open()) {
- fprintf(stderr, "Error: Could not open mesh file: %s\n",
- full_path.c_str());
+ if (!ProcessMeshFile(full_path, &buffer, info.name)) {
return 1;
}
-
- std::vector<float> v_pos;
- std::vector<float> v_norm;
- std::vector<float> v_uv;
-
- struct RawFace {
- int v[3];
- int vt[3];
- int vn[3];
- };
- std::vector<RawFace> raw_faces;
-
- struct Vertex {
- float p[3], n[3], u[2];
- };
- std::vector<Vertex> final_vertices;
- std::vector<uint32_t> final_indices;
- std::map<std::string, uint32_t> vertex_map;
-
- std::string obj_line;
- while (std::getline(obj_file, obj_line)) {
- if (obj_line.compare(0, 2, "v ") == 0) {
- float x, y, z;
- std::sscanf(obj_line.c_str(), "v %f %f %f", &x, &y, &z);
- v_pos.push_back(x);
- v_pos.push_back(y);
- v_pos.push_back(z);
- } else if (obj_line.compare(0, 3, "vn ") == 0) {
- float x, y, z;
- std::sscanf(obj_line.c_str(), "vn %f %f %f", &x, &y, &z);
- v_norm.push_back(x);
- v_norm.push_back(y);
- v_norm.push_back(z);
- } else if (obj_line.compare(0, 3, "vt ") == 0) {
- float u, v;
- std::sscanf(obj_line.c_str(), "vt %f %f", &u, &v);
- v_uv.push_back(u);
- v_uv.push_back(v);
- } else if (obj_line.compare(0, 2, "f ") == 0) {
- char s1[64], s2[64], s3[64];
- if (std::sscanf(obj_line.c_str(), "f %s %s %s", s1, s2, s3) == 3) {
- std::string parts[3] = {s1, s2, s3};
- RawFace face = {};
- for (int i = 0; i < 3; ++i) {
- int v_idx = 0, vt_idx = 0, vn_idx = 0;
- if (parts[i].find("//") != std::string::npos) {
- std::sscanf(parts[i].c_str(), "%d//%d", &v_idx, &vn_idx);
- } else if (std::count(parts[i].begin(), parts[i].end(), '/') ==
- 2) {
- std::sscanf(parts[i].c_str(), "%d/%d/%d", &v_idx, &vt_idx,
- &vn_idx);
- } else if (std::count(parts[i].begin(), parts[i].end(), '/') ==
- 1) {
- std::sscanf(parts[i].c_str(), "%d/%d", &v_idx, &vt_idx);
- } else {
- std::sscanf(parts[i].c_str(), "%d", &v_idx);
- }
- face.v[i] = v_idx;
- face.vt[i] = vt_idx;
- face.vn[i] = vn_idx;
- }
- raw_faces.push_back(face);
- }
- }
- }
-
- // Generate normals if missing
- if (v_norm.empty() && !v_pos.empty()) {
- printf("Generating normals for %s...\n", info.name.c_str());
- std::vector<Vec3> temp_normals(v_pos.size() / 3, {0, 0, 0});
- for (auto& face : raw_faces) {
- // Indices are 1-based in OBJ
- int idx0 = face.v[0] - 1;
- int idx1 = face.v[1] - 1;
- int idx2 = face.v[2] - 1;
-
- if (idx0 >= 0 && idx1 >= 0 && idx2 >= 0) {
- Vec3 p0 = {v_pos[idx0 * 3], v_pos[idx0 * 3 + 1],
- v_pos[idx0 * 3 + 2]};
- Vec3 p1 = {v_pos[idx1 * 3], v_pos[idx1 * 3 + 1],
- v_pos[idx1 * 3 + 2]};
- Vec3 p2 = {v_pos[idx2 * 3], v_pos[idx2 * 3 + 1],
- v_pos[idx2 * 3 + 2]};
-
- Vec3 normal = Vec3::cross(p1 - p0, p2 - p0).normalize();
- temp_normals[idx0] += normal;
- temp_normals[idx1] += normal;
- temp_normals[idx2] += normal;
- }
- }
-
- for (const auto& n : temp_normals) {
- Vec3 normalized = n.normalize();
- v_norm.push_back(normalized.x);
- v_norm.push_back(normalized.y);
- v_norm.push_back(normalized.z);
- }
-
- // Assign generated normals to faces
- for (auto& face : raw_faces) {
- face.vn[0] = face.v[0];
- face.vn[1] = face.v[1];
- face.vn[2] = face.v[2];
- }
- }
-
- // Build final vertices
- for (const auto& face : raw_faces) {
- for (int i = 0; i < 3; ++i) {
- // Reconstruct key string for uniqueness check
- char key_buf[128];
- std::snprintf(key_buf, sizeof(key_buf), "%d/%d/%d", face.v[i],
- face.vt[i], face.vn[i]);
- std::string key = key_buf;
-
- if (vertex_map.find(key) == vertex_map.end()) {
- vertex_map[key] = (uint32_t)final_vertices.size();
-
- Vertex v = {};
- if (face.v[i] > 0) {
- v.p[0] = v_pos[(face.v[i] - 1) * 3];
- v.p[1] = v_pos[(face.v[i] - 1) * 3 + 1];
- v.p[2] = v_pos[(face.v[i] - 1) * 3 + 2];
- }
- if (face.vn[i] > 0) {
- v.n[0] = v_norm[(face.vn[i] - 1) * 3];
- v.n[1] = v_norm[(face.vn[i] - 1) * 3 + 1];
- v.n[2] = v_norm[(face.vn[i] - 1) * 3 + 2];
- }
- if (face.vt[i] > 0) {
- v.u[0] = v_uv[(face.vt[i] - 1) * 2];
- v.u[1] = v_uv[(face.vt[i] - 1) * 2 + 1];
- }
- final_vertices.push_back(v);
- }
- final_indices.push_back(vertex_map[key]);
- }
- }
-
- // Format: [num_vertices(u32)][Vertex *
- // num_vertices][num_indices(u32)][uint32_t
- // * num_indices]
- buffer.resize(
- sizeof(uint32_t) + final_vertices.size() * sizeof(Vertex) +
- sizeof(uint32_t) + final_indices.size() * sizeof(uint32_t));
- uint8_t* out_ptr = buffer.data();
- *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_vertices.size();
- out_ptr += sizeof(uint32_t);
- std::memcpy(out_ptr, final_vertices.data(),
- final_vertices.size() * sizeof(Vertex));
- out_ptr += final_vertices.size() * sizeof(Vertex);
- *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_indices.size();
- out_ptr += sizeof(uint32_t);
- std::memcpy(out_ptr, final_indices.data(),
- final_indices.size() * sizeof(uint32_t));
-
- printf("Processed mesh asset %s: %zu vertices, %zu indices\n",
- info.name.c_str(), final_vertices.size(), final_indices.size());
} else {
std::ifstream asset_file(full_path, std::ios::binary);
if (!asset_file.is_open()) {