#include "model.h"
#include "renderer.h"
#include "../util/array.h"
#include "../util/util.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_HASH_MODELS 1024
static Model *model_hash_table[MAX_HASH_MODELS];
static const int BUFFER_SIZE = 128;
static Model *_model_alloc(const char *name)
{
if(strlen(name) > MAX_PATH_LENGTH)
Util_FatalError("File following model name is too long: %s", name);
if(render.num_models >= MAX_MODELS)
return NULL;
Model *model;
unsigned int hash_ = Util_Hash( name );
hash_ %= MAX_HASH_MODELS;
model = malloc( sizeof(Model) );
memset(model, 0, sizeof(Model) );
render.models[render.num_models] = model;
render.num_models += 1;
strcpy(model->_name, name);
model->_hash_next = model_hash_table[hash_];
model_hash_table[hash_] = model;
model->_hash = hash_;
return model;
}
Model *model_get(const char *name)
{
Model *model;
unsigned int hash_ = Util_Hash( name );
hash_ %= MAX_HASH_MODELS;
if(model_hash_table[hash_] != NULL)
{
for(model = model_hash_table[hash_]; model; model = model->_hash_next)
{
if( model->_hash == hash_ )
return model;
}
}
return NULL;
}
typedef struct
{
vertex_t *data;
GLushort *indices;
Array *positions;
Array *textures;
Array *normals;
Array *tangents;
unsigned int vertex_count, index_count;
unsigned int index_pointer;
GLubyte hasTextCoords, hasNormals;
} OBJ_Mesh;
static vertex_t *search_index(vertex_t *pkey, vertex_t *pelem, unsigned int vertex_count)
{
int i;
for(i = 0; i < vertex_count; i++)
{
if(pelem[i].position.x == pkey->position.x &&
pelem[i].position.y == pkey->position.y &&
pelem[i].position.z == pkey->position.z &&
pelem[i].texCoord.x == pkey->texCoord.x &&
pelem[i].texCoord.y == pkey->texCoord.y &&
pelem[i].normal.x == pkey->normal.x &&
pelem[i].normal.y == pkey->normal.y &&
pelem[i].normal.z == pkey->normal.z)
{
return &pelem[i];
}
}
return NULL;
}
static void calculate_tangents(OBJ_Mesh *mesh)
{
int i;
for(i = 0; i < mesh->index_count; i += 3)
{
int i0 = i;
int i1 = i + 1;
int i2 = i + 2;
Vec3 *v0 = ( (Vec3 *)mesh->positions->data) + i0;
Vec3 *v1 = ( (Vec3 *)mesh->positions->data) + i1;
Vec3 *v2 = ( (Vec3 *)mesh->positions->data) + i2;
Vec2 *uv0 = ( (Vec2 *)mesh->textures->data) + i0;
Vec2 *uv1 = ( (Vec2 *)mesh->textures->data) + i1;
Vec2 *uv2 = ( (Vec2 *)mesh->textures->data) + i2;
Vec3 deltaPos1 = vec3_sub(v1, v0);
Vec3 deltaPos2 = vec3_sub(v2, v0);
Vec2 deltaUV1 = vec2_sub(uv1, uv0);
Vec2 deltaUV2 = vec2_sub(uv2, uv0);
GLfloat r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
deltaPos1 = vec3_scalar_mul(&deltaPos1, deltaUV2.y);
deltaPos2 = vec3_scalar_mul(&deltaPos2, deltaUV1.y);
Vec3 tangent = vec3_sub(&deltaPos1, &deltaPos2);
tangent = vec3_scalar_mul(&tangent, r);
( (Vec3 *)mesh->tangents->data)[i2] = tangent;
( (Vec3 *)mesh->tangents->data)[i1] = tangent;
( (Vec3 *)mesh->tangents->data)[i0] = tangent;
}
/*
We normalize the tangents at the end of the parse_obj_index loop
for(i = 0; i < mesh.index_count; i++)
{
mesh.tangents[i] = vec3_normalize(&mesh.tangents[i]);
}
*/
}
static void parse_obj_index(OBJ_Mesh *mesh, vertex_t *current_vertex)
{
vertex_t *indexOnArray = search_index(current_vertex, mesh->data, mesh->vertex_count);
/* We check if the vertex was already loaded, so the index points to the created vertex instead of repeating data*/
if(indexOnArray == NULL)
{
mesh->data = (vertex_t*) realloc( mesh->data, sizeof(vertex_t) * (++mesh->vertex_count) );
/* We make the index point to the last vertex added */
mesh->indices[mesh->index_pointer] = mesh->vertex_count - 1;
mesh->data[mesh->vertex_count - 1] = *current_vertex;
}
else
{
GLushort index = (GLushort)(indexOnArray - mesh->data);
mesh->data[index].tangent = vec3_add( &mesh->data[index].tangent,
¤t_vertex->tangent );
/* We make the index point to the previus vertex added instead of creating a new one */
mesh->indices[mesh->index_pointer] = index;
}
mesh->index_pointer += 1;
}
Model *model_obj_new(const char *path)
{
Model *model;
model = model_get(path);
if(model)
return model;
OBJ_Mesh obj_mesh;
memset( &obj_mesh, 0, sizeof(OBJ_Mesh) );
obj_mesh.positions = array_create( sizeof(Vec3) );
obj_mesh.normals = array_create( sizeof(Vec3) );
obj_mesh.textures = array_create( sizeof(Vec2) );
Array *positions = array_create( sizeof(Vec3) );
Array *normals = array_create( sizeof(Vec3) );
Array *textures = array_create( sizeof(Vec2) );
vertex_t current_vertex;
Vec3 current_position;
Vec3 current_normal;
Vec2 current_texture;
int count = 0, i;
int texture[3], normal[3], verts[3];
FILE *file = fopen(path, "r");
if(file == NULL)
Util_FatalError("%s file could not be loaded!", path);
char buffer[BUFFER_SIZE];
while( !feof(file) )
{
fgets(buffer, BUFFER_SIZE, file);
switch(buffer[0])
{
case 'v':
if(buffer[1] == 't')
{
count = sscanf(buffer, "vt %f %f\n", ¤t_texture.x, ¤t_texture.y);
array_append(textures, ¤t_texture);
if(count != 2)
Util_FatalError("Bad texture coordinates on .obj file");
}
else if(buffer[1] == 'n')
{
count = sscanf(buffer, "vn %f %f %f\n", ¤t_normal.x,
¤t_normal.y,
¤t_normal.z);
array_append(normals, ¤t_normal);
if(count != 3)
Util_FatalError("Bad normals data on .obj file");
}
else
{
count = sscanf(buffer, "v %f %f %f\n", ¤t_position.x,
¤t_position.y,
¤t_position.z);
array_append(positions, ¤t_position);
if(count != 3)
Util_FatalError("Bad vertices data on .obj file");
}
break;
case 'f':
obj_mesh.index_count += 3;
count = sscanf(buffer, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
&verts[0], &texture[0], &normal[0],
&verts[1], &texture[1], &normal[1],
&verts[2], &texture[2], &normal[2]);
if(count != 9)
Util_FatalError("Bad face data on .obj file");
array_append(obj_mesh.positions, &( (Vec3 *)positions->data )[ verts[0] -1 ] );
array_append(obj_mesh.positions, &( (Vec3 *)positions->data )[ verts[1] -1 ] );
array_append(obj_mesh.positions, &( (Vec3 *)positions->data )[ verts[2] -1 ] );
array_append(obj_mesh.normals, &( (Vec3 *)normals->data )[ normal[0] - 1 ] );
array_append(obj_mesh.normals, &( (Vec3 *)normals->data )[ normal[1] - 1 ] );
array_append(obj_mesh.normals, &( (Vec3 *)normals->data )[ normal[2] - 1 ] );
array_append(obj_mesh.textures, &( (Vec2 *)textures->data )[ texture[0] - 1 ] );
array_append(obj_mesh.textures, &( (Vec2 *)textures->data )[ texture[1] - 1 ] );
array_append(obj_mesh.textures, &( (Vec2 *)textures->data )[ texture[2] - 1 ] );
break;
default:
break;
}
}
obj_mesh.indices = malloc( obj_mesh.index_count * sizeof(GLushort) );
obj_mesh.tangents = array_create_by_size( sizeof(Vec3), obj_mesh.index_count );
calculate_tangents(&obj_mesh);
for(i = 0; i < obj_mesh.index_count; i++)
{
current_vertex.position = ( (Vec3 *)obj_mesh.positions->data )[i];
current_vertex.texCoord = ( (Vec2 *)obj_mesh.textures->data )[i];
current_vertex.normal = ( (Vec3 *)obj_mesh.normals->data )[i];
current_vertex.tangent = ( (Vec3 *)obj_mesh.tangents->data )[i];
parse_obj_index(&obj_mesh, ¤t_vertex);
}
for(i = 0; i < obj_mesh.vertex_count; i++)
{
obj_mesh.data[i].tangent = vec3_normalize(&obj_mesh.data[i].tangent);
}
array_free(obj_mesh.positions);
array_free(obj_mesh.normals);
array_free(obj_mesh.textures);
array_free(obj_mesh.tangents);
array_free(positions);
array_free(textures);
array_free(normals);
fclose(file);
GLsizeiptr vertexBuffersize = obj_mesh.vertex_count * sizeof(vertex_t);
GLsizeiptr indexBuffersize = obj_mesh.index_count * sizeof(GLushort);
model = _model_alloc(path);
model->mesh = mesh_new(obj_mesh.data, vertexBuffersize, obj_mesh.indices, indexBuffersize);
free(obj_mesh.data);
free(obj_mesh.indices);
return model;
}
void model_purge(Model *model)
{
mesh_purge(model->mesh);
}