/* * Tuxánci 2 - A first person shooter * Copyright (C) 2007-2011 Tuxánci Development Team * Copyright (C) 2025-2026 Connor Thomson * * 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 . */ #include #include #include #include #include #include "obj.h" typedef struct ta_obj_position { float value[3]; } ta_obj_position; typedef struct ta_obj_face { int position_indices[3]; } ta_obj_face; static bool ta_obj_push_vertex(ta_obj_mesh *mesh, size_t *capacity, const ta_obj_vertex *vertex) { if (mesh->vertex_count == *capacity) { size_t next_capacity = *capacity == 0 ? 1024 : *capacity * 2; ta_obj_vertex *vertices = realloc(mesh->vertices, next_capacity * sizeof(*vertices)); if (!vertices) { return false; } mesh->vertices = vertices; *capacity = next_capacity; } mesh->vertices[mesh->vertex_count++] = *vertex; return true; } bool ta_obj_parse(const unsigned char *data, size_t size, ta_obj_mesh *mesh) { ta_obj_position *positions = NULL; ta_obj_face *faces = NULL; float *normal_accum = NULL; size_t position_count = 0; size_t face_count = 0; size_t position_capacity = 0; size_t face_capacity = 0; size_t vertex_capacity = 0; bool success = false; memset(mesh, 0, sizeof(*mesh)); char *text = malloc(size + 1); if (!text) { return false; } memcpy(text, data, size); text[size] = '\0'; char *line = text; while (line && *line) { char *next_line = strchr(line, '\n'); if (next_line) { *next_line = '\0'; } if (line[0] == 'v' && line[1] == ' ') { ta_obj_position position; if (sscanf(line + 2, "%f %f %f", &position.value[0], &position.value[1], &position.value[2]) != 3) { goto done; } if (position_count == position_capacity) { size_t next_capacity = position_capacity == 0 ? 1024 : position_capacity * 2; ta_obj_position *grown = realloc(positions, next_capacity * sizeof(*grown)); if (!grown) { goto done; } positions = grown; position_capacity = next_capacity; } positions[position_count++] = position; } else if (line[0] == 'f' && line[1] == ' ') { ta_obj_face face; int discarded_normal_indices[3]; bool has_normals = sscanf(line + 2, "%d//%d %d//%d %d//%d", &face.position_indices[0], &discarded_normal_indices[0], &face.position_indices[1], &discarded_normal_indices[1], &face.position_indices[2], &discarded_normal_indices[2]) == 6; if (!has_normals && sscanf(line + 2, "%d %d %d", &face.position_indices[0], &face.position_indices[1], &face.position_indices[2]) != 3) { goto done; } for (int index = 0; index < 3; index++) { if (face.position_indices[index] <= 0 || (size_t)face.position_indices[index] > position_count) { goto done; } } if (face_count == face_capacity) { size_t next_capacity = face_capacity == 0 ? 1024 : face_capacity * 2; ta_obj_face *grown = realloc(faces, next_capacity * sizeof(*grown)); if (!grown) { goto done; } faces = grown; face_capacity = next_capacity; } faces[face_count++] = face; } if (!next_line) { break; } line = next_line + 1; } if (position_count == 0 || face_count == 0) { goto done; } normal_accum = calloc(position_count * 3, sizeof(*normal_accum)); if (!normal_accum) { goto done; } for (size_t index = 0; index < face_count; index++) { const float *first = positions[faces[index].position_indices[0] - 1].value; const float *second = positions[faces[index].position_indices[1] - 1].value; const float *third = positions[faces[index].position_indices[2] - 1].value; float edge_a[3] = {second[0] - first[0], second[1] - first[1], second[2] - first[2]}; float edge_b[3] = {third[0] - first[0], third[1] - first[1], third[2] - first[2]}; float face_normal[3] = { edge_a[1] * edge_b[2] - edge_a[2] * edge_b[1], edge_a[2] * edge_b[0] - edge_a[0] * edge_b[2], edge_a[0] * edge_b[1] - edge_a[1] * edge_b[0], }; for (int corner = 0; corner < 3; corner++) { size_t position_index = (size_t)(faces[index].position_indices[corner] - 1); normal_accum[position_index * 3 + 0] += face_normal[0]; normal_accum[position_index * 3 + 1] += face_normal[1]; normal_accum[position_index * 3 + 2] += face_normal[2]; } } for (size_t index = 0; index < position_count; index++) { float *normal = &normal_accum[index * 3]; float length = sqrtf(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]); if (length > 0.0f) { normal[0] /= length; normal[1] /= length; normal[2] /= length; } else { normal[0] = 0.0f; normal[1] = 0.0f; normal[2] = 1.0f; } } for (size_t index = 0; index < face_count; index++) { for (int corner = 0; corner < 3; corner++) { size_t position_index = (size_t)(faces[index].position_indices[corner] - 1); ta_obj_vertex vertex; memcpy(vertex.position, positions[position_index].value, sizeof(vertex.position)); memcpy(vertex.normal, &normal_accum[position_index * 3], sizeof(vertex.normal)); if (!ta_obj_push_vertex(mesh, &vertex_capacity, &vertex)) { goto done; } } } success = mesh->vertex_count > 0; done: if (!success) { ta_obj_free(mesh); } free(positions); free(faces); free(normal_accum); free(text); return success; } void ta_obj_free(ta_obj_mesh *mesh) { free(mesh->vertices); mesh->vertices = NULL; mesh->vertex_count = 0; }