2012-07-15 08:15:47 +02:00
|
|
|
/*
|
|
|
|
* This software is licensed under the terms of the MIT-License
|
2017-02-11 04:52:08 +01:00
|
|
|
* See COPYING for further information.
|
2012-07-15 08:15:47 +02:00
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
|
2012-07-15 08:15:47 +02:00
|
|
|
*/
|
|
|
|
|
2017-11-25 20:45:11 +01:00
|
|
|
#include "taisei.h"
|
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
#include "model.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "resource.h"
|
|
|
|
#include "renderer/api.h"
|
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
// TODO: Rewrite all of this mess, maybe even consider a different format
|
|
|
|
// IQM for instance: http://sauerbraten.org/iqm/
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
ResourceHandler model_res_handler = {
|
|
|
|
.type = RES_MODEL,
|
|
|
|
.typename = "model",
|
|
|
|
.subdir = MDL_PATH_PREFIX,
|
|
|
|
|
|
|
|
.procs = {
|
|
|
|
.find = model_path,
|
|
|
|
.check = check_model_path,
|
|
|
|
.begin_load = load_model_begin,
|
|
|
|
.end_load = load_model_end,
|
|
|
|
.unload = unload_model,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
static bool parse_obj(const char *filename, ObjFileData *data);
|
2017-03-02 11:23:30 +01:00
|
|
|
static void free_obj(ObjFileData *data);
|
|
|
|
|
|
|
|
char* model_path(const char *name) {
|
2017-03-07 02:13:06 +01:00
|
|
|
return strjoin(MDL_PATH_PREFIX, name, MDL_EXTENSION, NULL);
|
2017-03-02 11:23:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool check_model_path(const char *path) {
|
|
|
|
return strendswith(path, MDL_EXTENSION);
|
|
|
|
}
|
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
typedef struct ModelLoadData {
|
2018-04-12 16:08:48 +02:00
|
|
|
GenericModelVertex *verts;
|
2018-10-02 22:14:24 +02:00
|
|
|
uint *indices;
|
|
|
|
uint icount;
|
2017-03-13 06:44:39 +01:00
|
|
|
} ModelLoadData;
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
void* load_model_begin(const char *path, uint flags) {
|
2017-03-13 06:44:39 +01:00
|
|
|
ObjFileData *data = malloc(sizeof(ObjFileData));
|
2018-04-12 16:08:48 +02:00
|
|
|
GenericModelVertex *verts;
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
if(!parse_obj(path, data)) {
|
|
|
|
free(data);
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
uint *indices = calloc(data->icount, sizeof(uint));
|
|
|
|
uint icount = data->icount;
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
verts = calloc(data->icount, sizeof(GenericModelVertex));
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
#define BADREF(filename,aux,n) { \
|
|
|
|
log_warn("OBJ file '%s': Index %d: bad %s index reference\n", filename, n, aux); \
|
2017-03-25 01:31:10 +01:00
|
|
|
goto fail; \
|
2017-03-13 06:44:39 +01:00
|
|
|
}
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
memset(verts, 0, data->icount*sizeof(GenericModelVertex));
|
|
|
|
|
|
|
|
for(uint i = 0; i < data->icount; i++) {
|
2017-03-02 11:23:30 +01:00
|
|
|
int xi, ni, ti;
|
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
xi = data->indices[i][0]-1;
|
|
|
|
if(xi < 0 || xi >= data->xcount)
|
2017-03-02 11:23:30 +01:00
|
|
|
BADREF(path, "vertex", i);
|
|
|
|
|
2018-08-01 20:09:18 +02:00
|
|
|
memcpy(verts[i].position, data->xs[xi], sizeof(vec3_noalign));
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
if(data->tcount) {
|
|
|
|
ti = data->indices[i][1]-1;
|
|
|
|
if(ti < 0 || ti >= data->tcount)
|
2017-03-02 11:23:30 +01:00
|
|
|
BADREF(path, "texcoord", i);
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
verts[i].uv.s = data->texcoords[ti][0];
|
|
|
|
verts[i].uv.t = data->texcoords[ti][1];
|
2017-03-02 11:23:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
if(data->ncount) {
|
|
|
|
ni = data->indices[i][2]-1;
|
|
|
|
if(ni < 0 || ni >= data->ncount)
|
2017-03-02 11:23:30 +01:00
|
|
|
BADREF(path, "normal", ni);
|
|
|
|
|
2018-08-01 20:09:18 +02:00
|
|
|
memcpy(verts[i].normal, data->normals[ni], sizeof(vec3_noalign));
|
2017-03-02 11:23:30 +01:00
|
|
|
}
|
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
indices[i] = i;
|
2017-03-02 11:23:30 +01:00
|
|
|
}
|
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
free_obj(data);
|
|
|
|
free(data);
|
|
|
|
|
2017-03-02 11:23:30 +01:00
|
|
|
#undef BADREF
|
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
ModelLoadData *ldata = malloc(sizeof(ModelLoadData));
|
|
|
|
ldata->verts = verts;
|
2018-10-02 22:14:24 +02:00
|
|
|
ldata->indices = indices;
|
|
|
|
ldata->icount = icount;
|
2017-03-13 06:44:39 +01:00
|
|
|
|
|
|
|
return ldata;
|
2017-03-25 01:31:10 +01:00
|
|
|
|
|
|
|
fail:
|
2018-10-02 22:14:24 +02:00
|
|
|
free(indices);
|
2017-03-25 01:31:10 +01:00
|
|
|
free(verts);
|
|
|
|
free_obj(data);
|
|
|
|
free(data);
|
|
|
|
return NULL;
|
2017-03-13 06:44:39 +01:00
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
void* load_model_end(void *opaque, const char *path, uint flags) {
|
2017-03-13 06:44:39 +01:00
|
|
|
ModelLoadData *ldata = opaque;
|
|
|
|
|
|
|
|
if(!ldata) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
Model *model = calloc(1, sizeof(Model));
|
|
|
|
r_model_add_static(model, PRIM_TRIANGLES, ldata->icount, ldata->verts, ldata->indices);
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
free(ldata->verts);
|
2018-10-02 22:14:24 +02:00
|
|
|
free(ldata->indices);
|
2017-03-13 06:44:39 +01:00
|
|
|
free(ldata);
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2018-10-02 22:14:24 +02:00
|
|
|
return model;
|
2017-03-02 11:23:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void unload_model(void *model) { // Does not delete elements from the VBO, so doing this at runtime is leaking VBO space
|
|
|
|
free(model);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_obj(ObjFileData *data) {
|
|
|
|
free(data->xs);
|
|
|
|
free(data->normals);
|
|
|
|
free(data->texcoords);
|
|
|
|
free(data->indices);
|
|
|
|
}
|
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
static bool parse_obj(const char *filename, ObjFileData *data) {
|
2017-04-18 21:48:18 +02:00
|
|
|
SDL_RWops *rw = vfs_open(filename, VFS_MODE_READ);
|
2017-03-12 22:22:43 +01:00
|
|
|
|
|
|
|
if(!rw) {
|
2017-04-18 21:48:18 +02:00
|
|
|
log_warn("VFS error: %s", vfs_get_error());
|
2019-01-25 01:52:00 +01:00
|
|
|
return false;
|
2017-03-12 22:22:43 +01:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
char line[256], *save;
|
2018-08-01 20:09:18 +02:00
|
|
|
vec3_noalign buf;
|
2012-07-15 08:15:47 +02:00
|
|
|
char mode;
|
|
|
|
int linen = 0;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
memset(data, 0, sizeof(ObjFileData));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-03-12 22:22:43 +01:00
|
|
|
while(SDL_RWgets(rw, line, sizeof(line))) {
|
2012-07-15 08:15:47 +02:00
|
|
|
linen++;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
char *first;
|
2017-03-13 06:44:39 +01:00
|
|
|
first = strtok_r(line, " \n", &save);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
if(strcmp(first, "v") == 0)
|
|
|
|
mode = 'v';
|
|
|
|
else if(strcmp(first, "vt") == 0)
|
|
|
|
mode = 't';
|
|
|
|
else if(strcmp(first, "vn") == 0)
|
|
|
|
mode = 'n';
|
|
|
|
else if(strcmp(first, "f") == 0)
|
|
|
|
mode = 'f';
|
|
|
|
else
|
|
|
|
mode = 0;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
|
|
|
if(mode != 0 && mode != 'f') {
|
2017-03-13 06:44:39 +01:00
|
|
|
buf[0] = atof(strtok_r(NULL, " \n", &save));
|
|
|
|
char *wtf = strtok_r(NULL, " \n", &save);
|
|
|
|
buf[1] = atof(wtf);
|
2012-07-15 08:15:47 +02:00
|
|
|
if(mode != 't')
|
2017-03-13 06:44:39 +01:00
|
|
|
buf[2] = atof(strtok_r(NULL, " \n", &save));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
switch(mode) {
|
|
|
|
case 'v':
|
2018-08-01 20:09:18 +02:00
|
|
|
data->xs = realloc(data->xs, sizeof(vec3_noalign)*(++data->xcount));
|
|
|
|
memcpy(data->xs[data->xcount-1], buf, sizeof(vec3_noalign));
|
2012-07-15 08:15:47 +02:00
|
|
|
break;
|
|
|
|
case 't':
|
2018-08-01 20:09:18 +02:00
|
|
|
data->texcoords = realloc(data->texcoords, sizeof(vec3_noalign)*(++data->tcount));
|
|
|
|
memcpy(data->texcoords[data->tcount-1], buf, sizeof(vec3_noalign));
|
2012-07-15 08:15:47 +02:00
|
|
|
break;
|
|
|
|
case 'n':
|
2018-08-01 20:09:18 +02:00
|
|
|
data->normals = realloc(data->normals, sizeof(vec3_noalign)*(++data->ncount));
|
|
|
|
memcpy(data->normals[data->ncount-1], buf, sizeof(vec3_noalign));
|
2012-07-15 08:15:47 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if(mode == 'f') {
|
|
|
|
char *segment, *seg;
|
|
|
|
int j = 0, jj;
|
2018-08-01 20:09:18 +02:00
|
|
|
ivec3_noalign ibuf;
|
2012-07-15 08:15:47 +02:00
|
|
|
memset(ibuf, 0, sizeof(ibuf));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-03-13 06:44:39 +01:00
|
|
|
while((segment = strtok_r(NULL, " \n", &save))) {
|
2012-07-18 13:42:04 +02:00
|
|
|
seg = segment;
|
2017-02-11 04:52:08 +01:00
|
|
|
j++;
|
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
jj = 0;
|
2012-07-18 13:42:04 +02:00
|
|
|
while(jj < 3) {
|
2012-07-15 08:15:47 +02:00
|
|
|
ibuf[jj] = atoi(seg);
|
|
|
|
jj++;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-18 13:42:04 +02:00
|
|
|
while(*seg != '\0' && *(++seg) != '/');
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-18 13:42:04 +02:00
|
|
|
if(*seg == '\0')
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
seg++;
|
2012-07-15 08:15:47 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
if(strstr(segment, "//")) {
|
|
|
|
ibuf[2] = ibuf[1];
|
|
|
|
ibuf[1] = 0;
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
if(jj == 0 || jj > 3 || segment[0] == '/') {
|
|
|
|
log_warn("OBJ file '%s:%d': Parsing error: Corrupt face definition", filename, linen);
|
|
|
|
goto fail;
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-08-01 20:09:18 +02:00
|
|
|
data->indices = realloc(data->indices, sizeof(ivec3_noalign)*(++data->icount));
|
|
|
|
memcpy(data->indices[data->icount-1], ibuf, sizeof(ivec3_noalign));
|
2012-07-15 08:15:47 +02:00
|
|
|
}
|
2012-07-18 13:42:04 +02:00
|
|
|
|
2012-07-15 08:15:47 +02:00
|
|
|
if(data->fverts == 0)
|
|
|
|
data->fverts = j;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
if(data->fverts != j) {
|
|
|
|
log_warn("OBJ file '%s:%d': Parsing error: face vertex count must stay the same in the whole file", filename, linen);
|
|
|
|
goto fail;
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-01-25 01:52:00 +01:00
|
|
|
if(data->fverts != 3) {
|
|
|
|
log_warn("OBJ file '%s:%d': Parsing error: face vertex count must be 3", filename, linen);
|
|
|
|
goto fail;
|
|
|
|
}
|
2012-07-15 08:15:47 +02:00
|
|
|
}
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-03-12 22:22:43 +01:00
|
|
|
SDL_RWclose(rw);
|
2019-01-25 01:52:00 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
SDL_RWclose(rw);
|
|
|
|
free(data->indices);
|
|
|
|
free(data->normals);
|
|
|
|
free(data->xs);
|
|
|
|
return false;
|
2012-07-15 08:15:47 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-03-02 11:23:30 +01:00
|
|
|
Model* get_model(const char *name) {
|
2018-04-12 16:08:48 +02:00
|
|
|
return get_resource(RES_MODEL, name, RESF_DEFAULT)->data;
|
2012-07-15 08:15:47 +02:00
|
|
|
}
|
|
|
|
|