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