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