/* * model.c * * Basic functions to handle models * * Copyright (c) 2008 Thomas White * * This file is part of Thrust3D - a silly game * * Thrust3D 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. * * Thrust3D 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 Thrust3D. If not, see . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "types.h" #include "model.h" #include "utils.h" #include "texture.h" #include "render.h" /* Maximum number of vertices per primitive */ #define MAX_VERTICES 100000 /* Prototype */ static Model *model_lookup(ModelContext *ctx, const char *name); ModelContext *model_init() { ModelContext *ctx; ctx = malloc(sizeof(ModelContext)); if ( ctx == NULL ) return NULL; ctx->num_models = 0; ctx->models = NULL; return ctx; } static int model_add(ModelContext *ctx, Model *model) { ctx->models = realloc(ctx->models, (ctx->num_models+1)*sizeof(Model *)); if ( ctx->models == NULL ) { return 1; } ctx->models[ctx->num_models] = model; ctx->num_models++; return 0; } static Model *model_new(const char *name) { Model *model; model = malloc(sizeof(Model)); if ( model == NULL ) return NULL; model->num_primitives = 0; model->attrib_total = ATTRIB_NONE; model->primitives = NULL; model->name = strdup(name); return model; } static Primitive *model_add_primitive(Model *model, GLenum type, GLfloat *vertices, GLfloat *normals, GLfloat *texcoords, int n, PrimitiveAttrib attribs, GLfloat r, GLfloat g, GLfloat b, char *texture, GLfloat radius, GLfloat shininess, int vbos, int coll, GLfloat colspec) { Primitive *p; p = malloc(sizeof(Primitive)); if ( p == NULL ) return NULL; p->vertices = malloc(n * sizeof(GLfloat) * 3); if ( p->vertices == NULL ) { free(p); return NULL; } p->normals = malloc(n * sizeof(GLfloat) * 3); if ( p->normals == NULL ) { free(p); free(p->vertices); return NULL; } p->texcoords = malloc(n * sizeof(GLfloat) * 2); if ( p->texcoords == NULL ) { free(p); free(p->vertices); free(p->normals); return NULL; } /* Copy the vertices into memory */ memcpy(p->vertices, vertices, 3*n*sizeof(GLfloat)); memcpy(p->normals, normals, 3*n*sizeof(GLfloat)); memcpy(p->texcoords, texcoords, 2*n*sizeof(GLfloat)); p->num_vertices = n; p->type = type; p->attribs = attribs; p->col_r = r; p->col_g = g; p->col_b = b; p->colspec = colspec; p->texture = texture; p->radius = radius; p->shininess = shininess; if ( !coll && vbos ) { /* FIXME: Move this snippet to render.c in some tidy way */ glGenBuffers(1, &p->vertices_buffer); glBindBuffer(GL_ARRAY_BUFFER, p->vertices_buffer); glBufferData(GL_ARRAY_BUFFER, 3*p->num_vertices*sizeof(GLfloat), p->vertices, GL_STATIC_DRAW); glGenBuffers(1, &p->normals_buffer); glBindBuffer(GL_ARRAY_BUFFER, p->normals_buffer); glBufferData(GL_ARRAY_BUFFER, 3*p->num_vertices*sizeof(GLfloat), p->normals, GL_STATIC_DRAW); glGenBuffers(1, &p->texcoords_buffer); glBindBuffer(GL_ARRAY_BUFFER, p->texcoords_buffer); glBufferData(GL_ARRAY_BUFFER, 2*p->num_vertices*sizeof(GLfloat), p->texcoords, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } model->attrib_total = model->attrib_total | attribs; if ( !coll ) { model->primitives = realloc(model->primitives, sizeof(Primitive *) * (model->num_primitives+1)); model->primitives[model->num_primitives] = p; model->num_primitives++; } else { model->coll_primitives = realloc(model->coll_primitives, sizeof(Primitive *) * (model->num_coll_primitives+1)); model->coll_primitives[model->num_coll_primitives] = p; model->num_coll_primitives++; } return 0; } static void model_calculate_normals(GLfloat *vertices, GLfloat *normals, int first, int last, int centre, int v1, int v2) { GLfloat ax, ay, az; GLfloat bx, by, bz; GLfloat nx, ny, nz, n; unsigned int i; ax = vertices[3*v1+0] - vertices[3*centre+0]; ay = vertices[3*v1+1] - vertices[3*centre+1]; az = vertices[3*v1+2] - vertices[3*centre+2]; bx = vertices[3*v2+0] - vertices[3*centre+0]; by = vertices[3*v2+1] - vertices[3*centre+1]; bz = vertices[3*v2+2] - vertices[3*centre+2]; nx = ay*bz - az*by; ny = - ax*bz + az*bx; nz = ax*by - ay*bx; n = sqrtf(nx*nx + ny*ny + nz*nz); nx = nx / n; ny = ny / n; nz = nz / n; for ( i=first; i<=last; i++ ) { normals[3*i+0] = nx; normals[3*i+1] = ny; normals[3*i+2] = nz; } } static int model_load(ModelContext *ctx, const char *name, RenderContext *render, int coll) { FILE *fh; char tmp[64]; Model *model; PrimitiveType type; int num_vertices; GLfloat *vertices; GLfloat *normals; GLfloat *texcoords; GLfloat col_r = 0.0; GLfloat col_g = 0.0; GLfloat col_b = 0.0; GLfloat colspec = 0.0; GLfloat radius = 0.0; GLfloat shininess = 100.0; PrimitiveAttrib attribs; char *texture; int subdiv; /* Attempt to open the file */ if ( !coll ) { model = model_new(name); } else { model = model_lookup(ctx, name); if ( model == NULL ) { fprintf(stderr, "Collision geometry must be loaded AFTER rendering geometry (%s)\n", name); return -1; } model->num_coll_primitives = model->num_primitives; model->coll_primitives = model->primitives; } /* Don't subdivide the geometry if we're doing per-fragment lighting anyway */ if ( render->shaders ) { subdiv = 0; } else { subdiv = 1; } if ( !coll ) { snprintf(tmp, 63, "%s/models/%s", DATADIR, name); } else { snprintf(tmp, 63, "%s/models/%s-coll", DATADIR, name); } fh = fopen(tmp, "r"); if ( fh == NULL ) { if ( coll ) { /* Couldn't open a collision model with name -coll. * Try again using the original model */ snprintf(tmp, 63, "%s/models/%s", DATADIR, name); fh = fopen(tmp, "r"); if ( fh == NULL ) { return 1; } subdiv = 0; } else { return 1; } } if ( coll ) { model->num_coll_primitives = 0; model->coll_primitives = NULL; } vertices = malloc(MAX_VERTICES*3*sizeof(GLfloat)); normals = malloc(MAX_VERTICES*3*sizeof(GLfloat)); texcoords = malloc(MAX_VERTICES*2*sizeof(GLfloat)); num_vertices = 0; type = PRIMITIVE_TRIANGLES; attribs = ATTRIB_NONE; texture = NULL; while ( !feof(fh) ) { char line[1024]; GLfloat x, y, z; GLfloat r, g, b; GLfloat nx, ny, nz; int have_normals; GLfloat texx, texy, forget; texx = 0.0; texy = 0.0; /* Default texture coordinates */ have_normals = 0; /* Calculate the normals by default */ fgets(line, 1023, fh); if ( line[0] == '#' ) { continue; } if ( line[0] == '\n' ) { if ( num_vertices > 0 ) { model_add_primitive(model, type, vertices, normals, texcoords, num_vertices, attribs, col_r, col_g, col_b, texture, radius, shininess, render->vbos, coll, colspec); num_vertices = 0; type = PRIMITIVE_TRIANGLES; attribs = ATTRIB_NONE; texture = NULL; } } if ( strncmp(line, "QUADS", 5) == 0 ) { type = PRIMITIVE_QUADS; } if ( strncmp(line, "TRIANGLES", 9) == 0 ) { type = PRIMITIVE_TRIANGLES; } if ( sscanf(line, "%f %f %f %f %f", &forget, &forget, &forget, &x, &y) == 5 ) { texx = x; texy = y; } if ( sscanf(line, "%f %f %f %f %f %f %f %f", &forget, &forget, &forget, &forget, &forget, &nx, &ny, &nz) == 8 ) { have_normals = 1; } if ( sscanf(line, "%f %f %f", &x, &y, &z) == 3 ) { vertices[3*num_vertices+0] = x; vertices[3*num_vertices+1] = y; vertices[3*num_vertices+2] = z; texcoords[2*num_vertices+0] = texx; texcoords[2*num_vertices+1] = texy; if ( have_normals ) { normals[3*num_vertices+0] = nx; normals[3*num_vertices+1] = ny; normals[3*num_vertices+2] = nz; } num_vertices++; if ( (type == PRIMITIVE_QUADS) && ((num_vertices % 4)==0) ) { if ( !have_normals ) { model_calculate_normals(vertices, normals, num_vertices-4, num_vertices-1, num_vertices-4, num_vertices-3, num_vertices-2); } } if ( (type == PRIMITIVE_TRIANGLES) && ((num_vertices % 3)==0) ) { if ( !have_normals ) { model_calculate_normals(vertices, normals, num_vertices-3, num_vertices-1, num_vertices-3, num_vertices-2, num_vertices-1); } } if ( num_vertices > MAX_VERTICES ) { fprintf(stderr, "Too many vertices in primitive\n"); return 1; } } /* Subdivide the previous face if requested */ if ( subdiv && (sscanf(line, "subdivide %f %f", &x, &y) == 2) ) { if ( type == PRIMITIVE_QUADS ) { if ( (num_vertices > 0) && ((num_vertices % 4)==0) ) { GLfloat u, v; GLfloat v1x, v1y, v1z; GLfloat v2x, v2y, v2z; GLfloat v3x, v3y, v3z; GLfloat v4x, v4y, v4z; GLfloat n1x, n1y, n1z; GLfloat n2x, n2y, n2z; GLfloat n3x, n3y, n3z; GLfloat n4x, n4y, n4z; GLfloat t1x, t1y; GLfloat t2x, t2y; GLfloat t3x, t3y; GLfloat t4x, t4y; n4x = normals[3*(num_vertices-1) + 0]; n4y = normals[3*(num_vertices-1) + 1]; n4z = normals[3*(num_vertices-1) + 2]; n3x = normals[3*(num_vertices-2) + 0]; n3y = normals[3*(num_vertices-2) + 1]; n3z = normals[3*(num_vertices-2) + 2]; n2x = normals[3*(num_vertices-3) + 0]; n2y = normals[3*(num_vertices-3) + 1]; n2z = normals[3*(num_vertices-3) + 2]; n1x = normals[3*(num_vertices-4) + 0]; n1y = normals[3*(num_vertices-4) + 1]; n1z = normals[3*(num_vertices-4) + 2]; v4x = vertices[3*(num_vertices-1) + 0]; v4y = vertices[3*(num_vertices-1) + 1]; v4z = vertices[3*(num_vertices-1) + 2]; v3x = vertices[3*(num_vertices-2) + 0]; v3y = vertices[3*(num_vertices-2) + 1]; v3z = vertices[3*(num_vertices-2) + 2]; v2x = vertices[3*(num_vertices-3) + 0]; v2y = vertices[3*(num_vertices-3) + 1]; v2z = vertices[3*(num_vertices-3) + 2]; v1x = vertices[3*(num_vertices-4) + 0]; v1y = vertices[3*(num_vertices-4) + 1]; v1z = vertices[3*(num_vertices-4) + 2]; t4x = texcoords[2*(num_vertices-1) + 0]; t4y = texcoords[2*(num_vertices-1) + 1]; t3x = texcoords[2*(num_vertices-2) + 0]; t3y = texcoords[2*(num_vertices-2) + 1]; t2x = texcoords[2*(num_vertices-3) + 0]; t2y = texcoords[2*(num_vertices-3) + 1]; t1x = texcoords[2*(num_vertices-4) + 0]; t1y = texcoords[2*(num_vertices-4) + 1]; if ( num_vertices - 4 + x*y*4 > MAX_VERTICES ) { fprintf(stderr, "Not enough free vertex space to subdivide face\n"); } num_vertices -= 4; for ( u=0; unum_models; i++ ) { if ( strcmp(ctx->models[i]->name, name) == 0 ) { found = 1; break; } } if ( found == 0 ) { return NULL; } return ctx->models[i]; } ModelInstance *model_instance_new(ModelContext *ctx, const char *name, RenderContext *render) { ModelInstance *instance; instance = malloc(sizeof(ModelInstance)); instance->model = model_lookup(ctx, name); if ( instance->model == NULL ) { /* Couldn't find model, so try to load it */ if ( model_load(ctx, name, render, 0) != 0 ) { fprintf(stderr, "Couldn't load model '%s'\n", name); return NULL; } model_load(ctx, name, render, 1); instance->model = model_lookup(ctx, name); } instance->vx = 0.0; instance->vy = 0.0; instance->vz = 0.0; instance->yaw = 0.0; instance->yawspeed = 0.0; instance->landed = 0; instance->recharging = 0; return instance; }