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:
parent
ea9936a340
commit
8f02c55729
3 changed files with 329 additions and 1 deletions
|
@ -9161,6 +9161,330 @@ static qboolean QDECL Mod_LoadCompositeAnim(model_t *mod, void *buffer, size_t f
|
||||||
|
|
||||||
#endif //MD5MODELS
|
#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)
|
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, "MD5 Mesh/Anim (md5mesh)", "MD5Version", Mod_LoadMD5MeshModel);
|
||||||
Mod_RegisterModelFormatText(NULL, "External Anim", "EXTERNALANIM", Mod_LoadCompositeAnim);
|
Mod_RegisterModelFormatText(NULL, "External Anim", "EXTERNALANIM", Mod_LoadCompositeAnim);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef MODELFMT_OBJ
|
||||||
|
Mod_RegisterModelFormatText(NULL, "Wavefront Object (obj)", ".obj", Mod_LoadObjModel);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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 INTERQUAKEMODELS //Preferred model format, at least from an idealism perspective.
|
||||||
#define MODELFMT_MDX //kingpin's format (for hitboxes+geomsets).
|
#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...).
|
#define RAGDOLL //ragdoll support. requires RBE support (via a plugin...).
|
||||||
|
|
||||||
//Image formats
|
//Image formats
|
||||||
|
|
|
@ -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
|
//look for known extensions first, to try to avoid issues with specific formats
|
||||||
for(i = 0; i < countof(modelloaders); i++)
|
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;
|
break;
|
||||||
}
|
}
|
||||||
//now look to see if we can find one with the right magic header
|
//now look to see if we can find one with the right magic header
|
||||||
|
|
Loading…
Reference in a new issue