Load .obj meshes (yuck). They should at least be useful for loading the odd q3 .map file.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5543 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2019-09-15 15:20:20 +00:00
parent ea9936a340
commit 8f02c55729
3 changed files with 329 additions and 1 deletions

View file

@ -9161,6 +9161,330 @@ static qboolean QDECL Mod_LoadCompositeAnim(model_t *mod, void *buffer, size_t f
#endif //MD5MODELS
#ifdef MODELFMT_OBJ
#include <ctype.h>
struct objvert { size_t attrib[3]; };
struct objbuf_s { char *ptr; char *end; };
static char *obj_getline(struct objbuf_s *vf, char *buffer, size_t buflen)
{
char in;
char *out = buffer;
size_t len;
if (buflen <= 1)
return NULL;
len = buflen-1;
while (len > 0)
{
if (vf->ptr == vf->end)
{
if (len == buflen-1)
return NULL;
*out = '\0';
return buffer;
}
in = *vf->ptr++;
if (in == '\n')
break;
*out++ = in;
len--;
}
*out = '\0';
//if there's a trailing \r, strip it.
if (out > buffer)
if (out[-1] == '\r')
out[-1] = 0;
return buffer;
}
struct objattrib_s {
size_t length;
size_t maxlength;
float *data;
};
static qboolean parseobjvert(char *s, struct objattrib_s *out)
{
int i;
float *v;
char *n;
if (out->length == out->maxlength)
Z_ReallocElements((void**)&out->data,&out->maxlength,out->length+1024,3*sizeof(float));
v = out->data+out->length*3;
out->length++;
while(isalpha(*s)) s++;
for (i = 0; i < 3;)
{
v[i] = strtod(s, &n);
if (n==s)
return false;
s = n;
while(isspace(*s)) s++;
i++;
if(!*s) break;
}
for (; i < 3; i++)
v[i] = 0;
return true;
}
static galiasinfo_t *Obj_FinishFace(model_t *mod, galiasinfo_t *m, struct objattrib_s *attribs, struct objvert *vert, size_t numverts, index_t *indexes, size_t *numelements)
{ //this is really lame. not optimised and I don't care.
qboolean calcnorms = false;
size_t i;
if (m && numverts < MAX_INDICIES)
{
m->ofs_skel_xyz = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_skel_xyz)*numverts);
m->ofs_st_array = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_st_array)*numverts);
m->ofs_skel_norm = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_skel_norm)*numverts);
m->ofs_skel_svect = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_skel_svect)*numverts);
m->ofs_skel_tvect = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_skel_tvect)*numverts);
for (i = 0; i < numverts; i++)
{
if (vert[i].attrib[0] >= attribs[0].length)
VectorClear(m->ofs_skel_xyz[i]);
else
VectorCopy(attribs[0].data+3*vert[i].attrib[0], m->ofs_skel_xyz[i]);
AddPointToBounds(m->ofs_skel_xyz[i], mod->mins, mod->maxs);
if (vert[i].attrib[1] >= attribs[1].length)
Vector2Clear(m->ofs_st_array[i]);
else
Vector2Copy(attribs[1].data+3*vert[i].attrib[1], m->ofs_st_array[i]);
if (vert[i].attrib[2] >= attribs[2].length)
{
VectorClear(m->ofs_skel_norm[i]);
calcnorms = true;
}
else
VectorCopy(attribs[2].data+3*vert[i].attrib[2], m->ofs_skel_norm[i]);
}
m->numverts = i;
m->ofs_indexes = ZG_Malloc(&mod->memgroup, sizeof(*m->ofs_indexes)**numelements);
memcpy(m->ofs_indexes, indexes, sizeof(*m->ofs_indexes)**numelements);
m->numindexes = *numelements;
//calc tangents.
Mod_AccumulateTextureVectors(m->ofs_skel_xyz, m->ofs_st_array, m->ofs_skel_norm, m->ofs_skel_svect, m->ofs_skel_tvect, m->ofs_indexes, m->numindexes, calcnorms);
Mod_NormaliseTextureVectors(m->ofs_skel_norm, m->ofs_skel_svect, m->ofs_skel_tvect, m->numverts, calcnorms);
*numelements = 0;
}
return NULL;
}
static qboolean QDECL Mod_LoadObjModel(model_t *mod, void *buffer, size_t fsize)
{
struct objbuf_s f = {buffer, buffer+fsize};
struct objattrib_s attrib[3] = {{},{},{}};
char buf[512];
char *meshname = NULL, *matname = NULL;
galiasinfo_t *m = NULL, **link = (galiasinfo_t**)&mod->meshinfo;
size_t numverts = 0;
size_t maxverts = 0;
struct objvert *vert = NULL, defaultvert={{-1,-1,-1}};
size_t numelems = 0;
size_t maxelems = 0;
index_t *elem = NULL;
qboolean badinput = false;
int meshidx = 0;
ClearBounds(mod->mins, mod->maxs);
while(!badinput && obj_getline(&f, buf, sizeof(buf)))
{
char *c = buf;
while(isspace(*c)) c++;
switch(*c)
{
case '#': continue;
case 'v':
if(isspace(c[1])) badinput |= !parseobjvert(c, &attrib[0]);
else if(c[1]=='t') badinput |= !parseobjvert(c, &attrib[1]);
else if(c[1]=='n') badinput |= !parseobjvert(c, &attrib[2]);
break;
case 'g':
{
char *name;
size_t namelen;
while(isalpha(*c)) c++;
while(isspace(*c)) c++;
name = c;
namelen = strlen(name);
while(namelen > 0 && isspace(name[namelen-1])) namelen--;
Z_Free(meshname);
meshname = Z_Malloc(namelen+1);
memcpy(meshname, name, namelen);
meshname[namelen] = 0;
m = Obj_FinishFace(mod, m, attrib, vert, numverts, elem, &numelems);
break;
}
case 'u':
{
char *name;
size_t namelen;
if(strncmp(c, "usemtl", 6)) continue;
while(isalpha(*c)) c++;
while(isspace(*c)) c++;
name = c;
namelen = strlen(name);
while(namelen > 0 && isspace(name[namelen-1])) namelen--;
Z_Free(matname);
matname = Z_Malloc(namelen+1);
memcpy(matname, name, namelen);
matname[namelen] = 0;
m = Obj_FinishFace(mod, m, attrib, vert, numverts, elem, &numelems);
break;
}
case 's':
{
if(!isspace(c[1])) continue;
while(isalpha(*c)) c++;
while(isspace(*c)) c++;
int key = strtol(c, &c, 10);
//make sure that these verts are not merged, ensuring that they get smoothed.
//different texture coords will still have discontinuities though.
defaultvert.attrib[2] = -2-key;
break;
}
case 'f':
{
size_t i, v = 0;
struct objvert vkey={};
index_t first=0, prev=0, cur=0;
//only generate a new mesh if something actually changed.
if (!m)
{
#ifdef HAVE_CLIENT
galiasskin_t *skin;
skinframe_t *sframe;
m = ZG_Malloc(&mod->memgroup, sizeof(*m)+sizeof(*skin)+sizeof(*sframe));
#else
m = ZG_Malloc(&mod->memgroup, sizeof(*m));
#endif
*link = m;
link = &m->nextsurf;
m->shares_verts = meshidx;
Mod_DefaultMesh(m, COM_SkipPath(mod->name), meshidx++);
Q_strncpyz(m->surfacename, meshname?meshname:"", sizeof(m->surfacename));
#ifdef HAVE_CLIENT
skin = (void*)(m+1);
sframe = (void*)(skin+1);
skin->frame = sframe;
skin->numframes = 1;
skin->skinspeed = 10;
Q_strncpyz(skin->name, matname?matname:"", sizeof(skin->name));
Q_strncpyz(sframe->shadername, matname?matname:"", sizeof(sframe->shadername));
sframe->shader = NULL;
m->ofsskins = skin;
m->numskins = 1;
#endif
}
while(isalpha(*c)) c++;
for(;;)
{
while(isspace(*c)) c++;
if(!*c) break;
for (i = 0; i < countof(vkey.attrib); )
{
char *n;
long v;
v = strtol(c, &n, 10);
if (c == n) {badinput = true; break;} //not a number if we read nothing!
if (v < 0)
vkey.attrib[i] = attrib[i].length + v;
else
vkey.attrib[i] = v - 1; //0 is index-not-specified.
i++;
c = n;
if(*c!='/') break;
c++;
}
for (; i < countof(vkey.attrib); i++)
vkey.attrib[i] = defaultvert.attrib[i];
//figure out the verts, to avoid dupes
for (cur = 0; cur < numverts; cur++)
{
if (vert[cur].attrib[0] == vkey.attrib[0] &&
vert[cur].attrib[1] == vkey.attrib[1] &&
vert[cur].attrib[2] == vkey.attrib[2])
break;
}
if (cur == numverts)
{
if (numverts == maxverts)
Z_ReallocElements((void**)&vert,&maxverts,numverts+1024,sizeof(*vert));
vert[numverts++] = vkey;
}
//spew out the trifan
if (v == 0)
first = cur;
else if (v > 1)
{
if (numelems+3 >= maxelems)
{
if (numelems >= 65535)
{
badinput = true;
break; //don't depend upon the OOM killer... it kills everything else too
}
Z_ReallocElements((void**)&elem,&maxelems,numelems+1024,sizeof(*elem));
}
elem[numelems++] = cur;
elem[numelems++] = prev;
elem[numelems++] = first;
}
prev = cur;
v++;
}
break;
}
}
}
m = Obj_FinishFace(mod, m, attrib, vert, numverts, elem, &numelems);
Z_Free(vert);
Z_Free(elem);
Z_Free(attrib[0].data);
Z_Free(attrib[1].data);
Z_Free(attrib[2].data);
if (badinput)
{ //fail the load.
Con_Printf(CON_WARNING "File \"%s\" with .obj extension does not appear to be an .obj file\n", mod->name);
mod->meshinfo = NULL;
return false;
}
Mod_ClampModelSize(mod);
Mod_ParseModelEvents(mod, NULL, 0);
mod->flags = 0;
mod->type = mod_alias;
mod->numframes = 0;
mod->funcs.NativeTrace = Mod_Trace;
return !!mod->meshinfo;
}
#endif
void Alias_Register(void)
{
@ -9203,4 +9527,7 @@ void Alias_Register(void)
Mod_RegisterModelFormatText(NULL, "MD5 Mesh/Anim (md5mesh)", "MD5Version", Mod_LoadMD5MeshModel);
Mod_RegisterModelFormatText(NULL, "External Anim", "EXTERNALANIM", Mod_LoadCompositeAnim);
#endif
#ifdef MODELFMT_OBJ
Mod_RegisterModelFormatText(NULL, "Wavefront Object (obj)", ".obj", Mod_LoadObjModel);
#endif
}

View file

@ -84,6 +84,7 @@
#define HALFLIFEMODELS //horrible format that doesn't interact well with the rest of FTE's code. Unusable tools (due to license reasons).
#define INTERQUAKEMODELS //Preferred model format, at least from an idealism perspective.
#define MODELFMT_MDX //kingpin's format (for hitboxes+geomsets).
#define MODELFMT_OBJ //lame mesh-only format that needs far too much processing and even lacks a proper magic identifier too
#define RAGDOLL //ragdoll support. requires RBE support (via a plugin...).
//Image formats

View file

@ -1434,7 +1434,7 @@ static void Mod_LoadModelWorker (void *ctx, void *data, size_t a, size_t b)
//look for known extensions first, to try to avoid issues with specific formats
for(i = 0; i < countof(modelloaders); i++)
{
if (modelloaders[i].load && modelloaders[i].ident && *modelloaders[i].ident == '.' && !Q_strcasecmp(modelloaders[i].ident+1, ext))
if (modelloaders[i].load && modelloaders[i].ident && *modelloaders[i].ident == '.' && !Q_strcasecmp(modelloaders[i].ident, COM_GetFileExtension(mod->name, NULL)))
break;
}
//now look to see if we can find one with the right magic header