/* * 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_normal { float value[3]; } ta_obj_normal; 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_normal *normals = NULL; size_t position_count = 0; size_t normal_count = 0; size_t position_capacity = 0; size_t normal_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] == 'v' && line[1] == 'n' && line[2] == ' ') { ta_obj_normal normal; if (sscanf(line + 3, "%f %f %f", &normal.value[0], &normal.value[1], &normal.value[2]) != 3) { goto done; } if (normal_count == normal_capacity) { size_t next_capacity = normal_capacity == 0 ? 1024 : normal_capacity * 2; ta_obj_normal *grown = realloc(normals, next_capacity * sizeof(*grown)); if (!grown) { goto done; } normals = grown; normal_capacity = next_capacity; } normals[normal_count++] = normal; } else if (line[0] == 'f' && line[1] == ' ') { int position_indices[3]; int normal_indices[3]; bool has_normals = sscanf(line + 2, "%d//%d %d//%d %d//%d", &position_indices[0], &normal_indices[0], &position_indices[1], &normal_indices[1], &position_indices[2], &normal_indices[2]) == 6; if (!has_normals && sscanf(line + 2, "%d %d %d", &position_indices[0], &position_indices[1], &position_indices[2]) != 3) { goto done; } for (int index = 0; index < 3; index++) { if (position_indices[index] <= 0 || (size_t)position_indices[index] > position_count || (has_normals && (normal_indices[index] <= 0 || (size_t)normal_indices[index] > normal_count))) { goto done; } } float face_normal[3] = {0.0f, 0.0f, 1.0f}; if (!has_normals) { const float *first = positions[position_indices[0] - 1].value; const float *second = positions[position_indices[1] - 1].value; const float *third = positions[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]}; face_normal[0] = edge_a[1] * edge_b[2] - edge_a[2] * edge_b[1]; face_normal[1] = edge_a[2] * edge_b[0] - edge_a[0] * edge_b[2]; face_normal[2] = edge_a[0] * edge_b[1] - edge_a[1] * edge_b[0]; float length = sqrtf(face_normal[0] * face_normal[0] + face_normal[1] * face_normal[1] + face_normal[2] * face_normal[2]); if (length > 0.0f) { face_normal[0] /= length; face_normal[1] /= length; face_normal[2] /= length; } } for (int index = 0; index < 3; index++) { ta_obj_vertex vertex; memcpy(vertex.position, positions[position_indices[index] - 1].value, sizeof(vertex.position)); if (has_normals) { memcpy(vertex.normal, normals[normal_indices[index] - 1].value, sizeof(vertex.normal)); } else { memcpy(vertex.normal, face_normal, sizeof(vertex.normal)); } if (!ta_obj_push_vertex(mesh, &vertex_capacity, &vertex)) { goto done; } } } if (!next_line) { break; } line = next_line + 1; } success = mesh->vertex_count > 0; done: if (!success) { ta_obj_free(mesh); } free(positions); free(normals); free(text); return success; } void ta_obj_free(ta_obj_mesh *mesh) { free(mesh->vertices); mesh->vertices = NULL; mesh->vertex_count = 0; }