Integrate gltf2 support without external plugin.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5605 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2020-01-20 18:36:45 +00:00
parent 8e656b4af8
commit ffda35fae4
9 changed files with 211 additions and 60 deletions

View file

@ -1,3 +1,6 @@
#ifndef COM_MESH_H
#define COM_MESH_H
#ifdef __cplusplus
extern "C" {
#endif
@ -91,7 +94,6 @@ typedef struct
//we can't be bothered with animating skins.
//We'll load up to four of them but after that you're on your own
#ifndef SERVERONLY
typedef struct
{
shader_t *shader;
@ -100,7 +102,7 @@ typedef struct
char shadername[MAX_QPATH];
texnums_t texnums;
} skinframe_t;
typedef struct
struct galiasskin_s
{
int skinwidth;
int skinheight;
@ -108,7 +110,7 @@ typedef struct
int numframes;
skinframe_t *frame;
char name[MAX_QPATH];
} galiasskin_t;
};
typedef struct
{
@ -121,9 +123,8 @@ typedef struct
unsigned int subframe;
bucket_t bucket;
} galiascolourmapped_t;
#else
typedef void galiasskin_t;
#endif
typedef struct galiasskin_s galiasskin_t;
typedef struct
{
@ -276,4 +277,5 @@ void R_Generate_Mesh_ST_Vectors(mesh_t *mesh);
#ifdef __cplusplus
};
#endif
#endif
#endif //COM_MESH_H

View file

@ -186,6 +186,7 @@
//#define IMAGEFMT_EXR //openexr, via Industrial Light & Magic's rgba api, giving half-float data.
//#define MODELFMT_MDX
//#define MODELFMT_OBJ
//#define MODELFMT_GLTF //khronos 'transmission format'. .gltf or .glb extension. PBR. Version 2 only, for now.
//#define AVAIL_STBI //make use of Sean T. Barrett's lightweight public domain stb_image[_write] single-file-library, to avoid libpng/libjpeg dependancies.

View file

@ -85,6 +85,7 @@
#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 MODELFMT_GLTF //khronos 'transmission format'. .gltf or .glb extension. PBR. Version 2 only, for now.
#define RAGDOLL //ragdoll support. requires RBE support (via a plugin...).
//Image formats

View file

@ -87,6 +87,7 @@
//#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 MODELFMT_GLTF //khronos 'transmission format'. .gltf or .glb extension. PBR. Version 2 only, for now.
//#define RAGDOLL //ragdoll support. requires RBE support (via a plugin...).
//Image formats

View file

@ -85,6 +85,7 @@
#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 MODELFMT_GLTF //khronos 'transmission format'. .gltf or .glb extension. PBR. Version 2 only, for now.
#define RAGDOLL //ragdoll support. requires RBE support (via a plugin...).
//Image formats

View file

@ -194,6 +194,7 @@
//#define HAVE_HTTPSV
//#define MODELFMT_MDX
//#define MODELFMT_OBJ
//#define MODELFMT_GLTF //khronos 'transmission format'. .gltf or .glb extension. PBR. Version 2 only, for now.
#ifdef COMPILE_OPTS
//things to configure qclib, which annoyingly doesn't include this file itself

View file

@ -9,6 +9,13 @@
#ifdef PLUGINS
#ifdef MODELFMT_GLTF
#define Q_snprintf Q_snprintfz
#define Q_strlcpy Q_strncpyz
#define Q_strlcat Q_strncatz
#include "../plugins/models/gltf.c"
#endif
cvar_t plug_sbar = CVARD("plug_sbar", "3", "Controls whether plugins are allowed to draw the hud, rather than the engine (when allowed by csqc). This is typically used to permit the ezhud plugin without needing to bother unloading it.\n=0: never use hud plugins.\n&1: Use hud plugins in deathmatch.\n&2: Use hud plugins in singleplayer/coop.\n=3: Always use hud plugins (when loaded).");
cvar_t plug_loaddefault = CVARD("plug_loaddefault", "1", "0: Load plugins only via explicit plug_load commands\n1: Load built-in plugins and those selected via the package manager\n2: Scan for misc plugins, loading all that can be found, but not built-ins.\n3: Scan for plugins, and then load any built-ins");
@ -23,6 +30,9 @@ static struct
#endif
#if defined(USE_INTERNAL_ODE)
{"ODE_internal", Plug_ODE_Init},
#endif
#if defined(MODELFMT_GLTF)
{"GLTF", Plug_GLTF_Init},
#endif
{NULL}
};
@ -236,6 +246,7 @@ static plugin_t *Plug_Load(const char *file)
Con_DPrintf("Activated module %s\n", file);
newplug->lib = NULL;
Q_strncpyz(newplug->filename, staticplugins[u].name, sizeof(newplug->filename));
currentplug = newplug;
success = staticplugins[u].initfunction();
break;
@ -1614,6 +1625,7 @@ int QDECL Plug_List_Print(const char *fname, qofs_t fsize, time_t modtime, void
//lots of awkward logic so we hide modules for other cpus.
size_t nl = strlen(fname);
size_t u;
char *arch_ext = ARCH_DL_POSTFIX;
static const char *knownarch[] =
{
"x32", "x64", "amd64", "x86", //various x86 ABIs
@ -1625,9 +1637,14 @@ int QDECL Plug_List_Print(const char *fname, qofs_t fsize, time_t modtime, void
while ((mssuck=strchr(fname, '\\')))
*mssuck = '/';
#endif
if (nl >= strlen(ARCH_DL_POSTFIX) && !Q_strcasecmp(fname+nl-strlen(ARCH_DL_POSTFIX), ARCH_DL_POSTFIX))
if (!parm)
{
nl -= strlen(ARCH_DL_POSTFIX);
parm = "";
arch_ext = ""; //static plugins have no extension.
}
if (nl >= strlen(arch_ext) && !Q_strcasecmp(fname+nl-strlen(arch_ext), arch_ext))
{
nl -= strlen(arch_ext);
for (u = 0; u < countof(knownarch); u++)
{
size_t al = strlen(knownarch[u]);
@ -1664,10 +1681,18 @@ void Plug_List_f(void)
char rootpath[MAX_OSPATH];
unsigned int u;
plugin_t *plug;
Con_Printf("Loaded plugins:\n");
for (plug = plugs; plug; plug = plug->next)
Con_Printf("^[^2%s\\type\\plug_close %s\\^]: loaded\n", plug->filename, plug->name);
if (staticplugins[0].name)
{
Con_DPrintf("Internal plugins:\n");
for (u = 0; staticplugins[u].name; u++)
Plug_List_Print(staticplugins[u].name, 0, 0, NULL, NULL);
}
if (FS_NativePath("", FS_BINARYPATH, binarypath, sizeof(binarypath)))
{
#ifdef _WIN32
@ -1675,7 +1700,7 @@ void Plug_List_f(void)
while ((mssuck=strchr(binarypath, '\\')))
*mssuck = '/';
#endif
Con_Printf("Scanning for plugins at %s:\n", binarypath);
Con_DPrintf("Scanning for plugins at %s:\n", binarypath);
Sys_EnumerateFiles(binarypath, PLUGINPREFIX"*" ARCH_DL_POSTFIX, Plug_List_Print, binarypath, NULL);
}
if (FS_NativePath("", FS_ROOT, rootpath, sizeof(rootpath)))
@ -1687,13 +1712,12 @@ void Plug_List_f(void)
#endif
if (strcmp(binarypath, rootpath))
{
Con_Printf("Scanning for plugins at %s:\n", rootpath);
Con_DPrintf("Scanning for plugins at %s:\n", rootpath);
Sys_EnumerateFiles(rootpath, PLUGINPREFIX"*" ARCH_DL_POSTFIX, Plug_List_Print, rootpath, NULL);
}
}
for (u = 0; staticplugins[u].name; u++)
Plug_List_Print(staticplugins[u].name, 0, 0, "", NULL);
//should probably check downloadables too.
}
void Plug_Shutdown(qboolean preliminary)

View file

@ -4,8 +4,8 @@
#include "quakedef.h"
#include "../plugin.h"
#include "com_mesh.h"
extern plugmodfuncs_t *modfuncs;
extern plugfsfuncs_t *filefuncs;
static plugmodfuncs_t *modfuncs;
static plugfsfuncs_t *filefuncs;
#ifdef SKELETALMODELS
#define GLTFMODELS
@ -76,10 +76,13 @@ static void JSON_Orphan(json_t *t)
}
static void JSON_Destroy(json_t *t)
{
while(t->child)
JSON_Destroy(t->child);
JSON_Orphan(t);
free(t);
if (t)
{
while(t->child)
JSON_Destroy(t->child);
JSON_Orphan(t);
free(t);
}
}
//node creation
@ -127,22 +130,142 @@ static json_t *JSON_CreateNode(json_t *parent, const char *namestart, const char
//node parsing
static void JSON_SkipWhite(const char *msg, int *pos, int max)
{
while (*pos < max && (
msg[*pos] == ' ' ||
msg[*pos] == '\t' ||
msg[*pos] == '\r' ||
msg[*pos] == '\n'
))
*pos+=1;
while (*pos < max)
{
//if its simple whitespace then keep skipping over it
if (msg[*pos] == ' ' ||
msg[*pos] == '\t' ||
msg[*pos] == '\r' ||
msg[*pos] == '\n' )
{
*pos+=1;
continue;
}
//BEGIN NON-STANDARD - Note that comments are NOT part of json, but people insist on using them anyway (c-style, like javascript).
else if (msg[*pos] == '/' && *pos+1 < max)
{
if (msg[*pos+1] == '/')
{ //C++ style single-line comments that continue till the next line break
*pos+=2;
while (*pos < max)
{
if (msg[*pos] == '\r' || msg[*pos] == '\n')
break; //ends on first line break (the break is then whitespace will will be skipped naturally)
*pos+=1; //not yet
}
continue;
}
else if (msg[*pos+1] == '*')
{ /*C style multi-line comment*/
*pos+=2;
while (*pos+1 < max)
{
if (msg[*pos] == '*' && msg[*pos+1] == '/')
{
*pos+=2; //skip past the terminator ready for whitespace or trailing comments directly after
break;
}
*pos+=1; //not yet
}
continue;
}
}
//END NON-STANDARD
break; //not whitespace/comment/etc.
}
}
//writes the body to a null-terminated string, handling escapes as needed.
//returns required body length (without terminator) (NOTE: return value is not escape-aware, so this is an over-estimate).
static size_t JSON_ReadBody(json_t *t, char *out, size_t outsize)
{
// size_t bodysize;
if (!t)
{
if (out)
*out = 0;
return 0;
}
if (out && outsize)
{
char *outend = out+outsize-1; //compensate for null terminator
const char *in = t->bodystart;
while (in < t->bodyend && out < outend)
{
if (*in == '\\')
{
if (++in < t->bodyend)
{
switch(*in++)
{
case '\"': *out++ = '\"'; break;
case '\\': *out++ = '\\'; break;
case '/': *out++ = '/'; break; //json is not C...
case 'b': *out++ = '\b'; break;
case 'f': *out++ = '\f'; break;
case 'n': *out++ = '\n'; break;
case 'r': *out++ = '\r'; break;
case 't': *out++ = '\t'; break;
// case 'u':
// out += utf8_encode(out, code, outend-out);
// break;
default:
//unknown escape. will warn when actually reading it.
*out++ = '\\';
if (out < outend)
*out++ = in[-1];
break;
}
}
else
*out++ = '\\'; //error...
}
else
*out++ = *in++;
}
*out = 0;
}
return t->bodyend-t->bodystart;
}
static qboolean JSON_ParseString(char const*msg, int *pos, int max, char const**start, char const** end)
{
if (*pos < max && msg[*pos] == '\"')
{
{ //quoted string
//FIXME: no handling of backslash followed by one of "\/bfnrtu
*pos+=1;
*start = msg+*pos;
while (*pos < max && msg[*pos] != '\"')
*pos+=1;
while (*pos < max)
{
if (msg[*pos] == '\"')
break;
if (msg[*pos] == '\\')
{ //escapes are expanded elsewhere, we're just skipping over them here.
switch(msg[*pos+1])
{
case '\"':
case '\\':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
*pos+=2;
break;
case 'u':
*pos+=2;
//*pos+=4; //4 hex digits, not escapes so just wait till later before parsing them properly.
break;
default:
//unknown escape. will warn when actually reading it.
*pos+=1;
break;
}
}
else
*pos+=1;
}
if (*pos < max && msg[*pos] == '\"')
{
*end = msg+*pos;
@ -151,7 +274,7 @@ static qboolean JSON_ParseString(char const*msg, int *pos, int max, char const**
}
}
else
{
{ //name
*start = msg+*pos;
while (*pos < max
&& msg[*pos] != ' '
@ -258,6 +381,7 @@ static json_t *JSON_Parse(json_t *t, const char *namestart, const char *nameend,
return NULL;
}
//we don't understand arrays here (we just treat them as tables) so eg "foo.0.bar" to find t->foo[0]->bar
static json_t *JSON_FindChild(json_t *t, const char *child)
{
if (t)
@ -384,11 +508,7 @@ static const char *JSON_GetString(json_t *t, const char *child, char *buffer, si
t = JSON_FindChild(t, child);
if (t)
{ //copy it to another buffer. can probably skip that tbh.
size_t l = t->bodyend-t->bodystart;
if (l > buffersize-1)
l = buffersize-1;
memcpy(buffer, t->bodystart, l);
buffer[l] = 0;
JSON_ReadBody(t, buffer, buffersize);
return buffer;
}
return fallback;
@ -461,7 +581,7 @@ static unsigned int FromBase64(char c)
return 63;
return 64;
}
//fancy parsing of content
//fancy parsing of content. NOTE: doesn't bother to handle escape codes, which shouldn't be present (\u for ascii chars is horribly wasteful).
static void *JSON_MallocDataURI(json_t *t, size_t *outlen)
{
size_t bl = t->bodyend-t->bodystart;
@ -528,27 +648,6 @@ static void *JSON_MallocDataURI(json_t *t, size_t *outlen)
return NULL;
}
static size_t JSON_ReadBody(json_t *t, char *out, size_t outsize)
{
size_t bodysize;
if (!t)
{
if (out)
*out = 0;
return 0;
}
if (out)
{
bodysize = t->bodyend-t->bodystart;
if (bodysize > outsize-1)
bodysize = outsize-1;
memcpy(out, t->bodystart, bodysize);
out[bodysize] = 0;
}
return t->bodyend-t->bodystart;
}
@ -1217,6 +1316,7 @@ void TransformArrayA(vec3_t *data, size_t vcount, double matrix[])
data++;
}
}
#ifndef SERVERONLY
static texid_t GLTF_LoadImage(gltf_t *gltf, int imageidx, unsigned int flags)
{
size_t size;
@ -1567,6 +1667,7 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert
Q_strlcpy(ret->name, ret->frame->shadername, sizeof(ret->name));
return ret;
}
#endif
static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double pmatrix[])
{
model_t *mod = gltf->mod;
@ -1580,7 +1681,6 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double
for(prim = JSON_FindIndexedChild(mesh, "primitives", 0); prim; prim = prim->sibling)
{
int mat = JSON_GetInteger(prim, "material", -1);
int mode = JSON_GetInteger(prim, "mode", 4);
json_t *attr = JSON_FindChild(prim, "attributes");
struct gltf_accessor tc_0, tc_1, norm, tang, vpos, col0, idx, sidx, swgt;
@ -1721,8 +1821,10 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double
}
}
#ifndef SERVERONLY
surf->numskins = 1;
surf->ofsskins = GLTF_LoadMaterial(gltf, mat, surf->ofs_rgbaub||surf->ofs_rgbaf);
surf->ofsskins = GLTF_LoadMaterial(gltf, JSON_GetInteger(prim, "material", -1), surf->ofs_rgbaub||surf->ofs_rgbaf);
#endif
if (!tang.data)
{
@ -2661,5 +2763,21 @@ qboolean QDECL Mod_LoadGLBModel (struct model_s *mod, void *buffer, size_t fsize
return GLTF_LoadModel(mod, json, jsonlen, bin, binlen);
}
qboolean Plug_GLTF_Init(void)
{
filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs));
modfuncs = plugfuncs->GetEngineInterface(plugmodfuncs_name, sizeof(*modfuncs));
if (modfuncs && modfuncs->version < MODPLUGFUNCS_VERSION)
modfuncs = NULL;
if (modfuncs && filefuncs)
{
modfuncs->RegisterModelFormatText("glTF2 models (glTF)", ".gltf", Mod_LoadGLTFModel);
modfuncs->RegisterModelFormatMagic("glTF2 models (glb)", (('F'<<24)+('T'<<16)+('l'<<8)+'g'), Mod_LoadGLBModel);
return true;
}
return false;
}
#endif

View file

@ -11,6 +11,7 @@ plugfsfuncs_t *filefuncs;
//#define LWOMODELS //not working
#ifdef SKELETALMODELS
#define GLTFMODELS //FIXME: not yet working properly.
extern qboolean Plug_GLTF_Init(void);
#endif
#ifdef ASEMODELS
@ -821,6 +822,7 @@ qboolean Mod_ExecuteCommand(qboolean isinsecure)
{
char tok[128];
cmdfuncs->Argv(0, tok, sizeof(tok));
#ifndef SERVERONLY
if (!strcmp(tok, "exportiqm"))
{
model_t *mod;
@ -835,6 +837,7 @@ qboolean Mod_ExecuteCommand(qboolean isinsecure)
}
return true;
}
#endif
return false;
}
@ -854,8 +857,7 @@ qboolean Plug_Init(void)
modfuncs->RegisterModelFormatMagic("LWO models (lwo)", (('M'<<24)+('R'<<16)+('O'<<8)+'F'), Mod_LoadLWOModel);
#endif
#ifdef GLTFMODELS
modfuncs->RegisterModelFormatText("glTF2 models (glTF)", ".gltf", Mod_LoadGLTFModel);
modfuncs->RegisterModelFormatMagic("glTF2 models (glb)", (('F'<<24)+('T'<<16)+('l'<<8)+'g'), Mod_LoadGLBModel);
Plug_GLTF_Init();
#endif
plugfuncs->ExportFunction("ExecuteCommand", Mod_ExecuteCommand);