From 5501fa249dbe7242077039dedbe08724498a3da0 Mon Sep 17 00:00:00 2001 From: Spoike Date: Sat, 13 Jun 2020 00:05:47 +0000 Subject: [PATCH] makefile: attempt to fix freetype when not using makelibs (should make it slightly easier for people to compile with msys2 without needing to resort to cmake). emenu: clean up hexen2's maplist options slightly. emenu: modelviewer should now be slightly more friendly (click+wasd to move around). particles: fix up randomised s coords. csqc: try to fix issue with applycustomskin not refcounting properly. client: [s_]precache and (new) mod_precache cvars can be set to 2 to precache the resources after load, for faster loading at the expense of some early stutter, without risking later mid-game stuttering. gltf: add support for morphweights in a cpu-fallback path. don't expect good performance on surfaces with morphtargets for now. gtlf: add some support for gltf1 files. far from perfect. shaders: gltf1 semantics handling shaders: const correctness iqmtool: fix up mdl skin export. iqmtool: integrate the engine's gltf2 loader. works with animated models, but unanimated ones suffer from basepose-different-from-bindpose issues. q3bsp: hopefully fixed bih traces. still disabled for now. qc: change default value of pr_gc_threaded to 1. qcext: add the '__deprecated' keyword to various symbols in fteextensions.qc, now that fteqcc supports it. ssqc: spit out a more readable error for WriteByte(MSG_CSQC,...) outside of SendEntity. ssqc: add registercommand builtin, for consistency with menuqc and csqc (though only one can register any single command). sv: report userinfo/serverinfo sizes (some clients still have arbitrary limits, plus its nice to see how abusive things are) sv: try to optimise sv_cullentities_trace a little. movechain: relink moved ents. csqc: add spriteframe builtin, for freecs to use instead of more ugly less reliable hacks. menuqc: fopen("tls://host:port", FILE_STREAM) should now open a tls stream. tcp:// should also work. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5704 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- CMakeLists.txt | 89 +- imgtool.c | 86 +- iqm/iqm.cpp | 951 +++++++++++++++--- iqm/util.h | 17 +- plugins/models/gltf.c | 1365 ++++++++++++++++++++------ plugins/openxr.c | 2 +- quakec/menusys/menusys/mitem_grid.qc | 11 +- 7 files changed, 2046 insertions(+), 475 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e76e386..639d8f8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -796,9 +796,13 @@ ELSE() ADD_EXECUTABLE(iqmtool iqm/iqm.cpp + plugins/models/gltf.c + engine/client/image.c + imgtool.c iqm/iqm.h ) - SET_TARGET_PROPERTIES(iqmtool PROPERTIES COMPILE_DEFINITIONS "${FTE_REVISON}") + SET_TARGET_PROPERTIES(iqmtool PROPERTIES COMPILE_DEFINITIONS "IQMTOOL;${FTE_REVISON}") + TARGET_LINK_LIBRARIES(iqmtool ${CMAKE_DL_LIBS}) SET(INSTALLTARGS ${INSTALLTARGS} iqmtool) ADD_EXECUTABLE(imgtool @@ -958,8 +962,10 @@ SET(INSTALLTARGS ${INSTALLTARGS} qi) #ODE Physics library plugin FIND_PATH(LIBODE_INCLUDE_DIR ode/ode.h) -FIND_LIBRARY(LIBODE_LIBRARY ode) IF (LIBODE_INCLUDE_DIR) + FIND_LIBRARY(LIBODE_LIBRARY ode) +ENDIF() +IF (LIBODE_LIBRARY) ADD_LIBRARY(ode MODULE plugins/plugin.c engine/common/com_phys_ode.c @@ -1157,41 +1163,44 @@ INSTALL(TARGETS ${INSTALLTARGS} LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}" ) -ADD_CUSTOM_TARGET(menusys ALL - VERBATIM - COMMAND fteqcc -srcfile "${CMAKE_CURRENT_SOURCE_DIR}/quakec/menusys/menu.src" -o "${CMAKE_CURRENT_BINARY_DIR}/menu.dat" - BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/menu.dat" "${CMAKE_CURRENT_BINARY_DIR}/menu.lno" - SOURCES - quakec/menusys/menu.src - quakec/menusys/menusys/mitems.qc - quakec/menusys/menusys/mitems_common.qc - quakec/menusys/menusys/mitem_frame.qc - quakec/menusys/menusys/mitem_desktop.qc - quakec/menusys/menusys/mitem_exmenu.qc - quakec/menusys/menusys/mitem_edittext.qc - quakec/menusys/menusys/mitem_tabs.qc - quakec/menusys/menusys/mitem_colours.qc - quakec/menusys/menusys/mitem_checkbox.qc - quakec/menusys/menusys/mitem_slider.qc - quakec/menusys/menusys/mitem_combo.qc - quakec/menusys/menusys/mitem_bind.qc - quakec/menusys/menusys/mitem_spinnymodel.qc - quakec/menusys/menu/loadsave.qc - quakec/menusys/menu/newgame.qc - quakec/menusys/menu/options_basic.qc - quakec/menusys/menu/options_effects.qc - quakec/menusys/menu/options_keys.qc - quakec/menusys/menu/options.qc - quakec/menusys/menu/presets.qc - quakec/menusys/menu/servers.qc - quakec/menusys/menu/main.qc - quakec/menusys/menu/mods.qc - quakec/menusys/menu/cvars.qc - quakec/menusys/menu/updates.qc - quakec/menusys/menu/options_audio.qc - quakec/menusys/menu/options_configs.qc - quakec/menusys/menu/options_hud.qc - quakec/menusys/menu/options_particles.qc - quakec/menusys/menu/options_video.qc - quakec/menusys/menu/quit.qc -) +IF (1) + ADD_CUSTOM_TARGET(menusys ALL + VERBATIM + COMMAND fteqcc -srcfile "${CMAKE_CURRENT_SOURCE_DIR}/quakec/menusys/menu.src" -o "${CMAKE_CURRENT_BINARY_DIR}/menu.dat" + BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/menu.dat" "${CMAKE_CURRENT_BINARY_DIR}/menu.lno" + SOURCES + quakec/menusys/menu.src + quakec/menusys/fteextensions.qc + quakec/menusys/menusys/mitems.qc + quakec/menusys/menusys/mitems_common.qc + quakec/menusys/menusys/mitem_frame.qc + quakec/menusys/menusys/mitem_desktop.qc + quakec/menusys/menusys/mitem_exmenu.qc + quakec/menusys/menusys/mitem_edittext.qc + quakec/menusys/menusys/mitem_tabs.qc + quakec/menusys/menusys/mitem_colours.qc + quakec/menusys/menusys/mitem_checkbox.qc + quakec/menusys/menusys/mitem_slider.qc + quakec/menusys/menusys/mitem_combo.qc + quakec/menusys/menusys/mitem_bind.qc + quakec/menusys/menusys/mitem_spinnymodel.qc + quakec/menusys/menu/loadsave.qc + quakec/menusys/menu/newgame.qc + quakec/menusys/menu/options_basic.qc + quakec/menusys/menu/options_effects.qc + quakec/menusys/menu/options_keys.qc + quakec/menusys/menu/options.qc + quakec/menusys/menu/presets.qc + quakec/menusys/menu/servers.qc + quakec/menusys/menu/main.qc + quakec/menusys/menu/mods.qc + quakec/menusys/menu/cvars.qc + quakec/menusys/menu/updates.qc + quakec/menusys/menu/options_audio.qc + quakec/menusys/menu/options_configs.qc + quakec/menusys/menu/options_hud.qc + quakec/menusys/menu/options_particles.qc + quakec/menusys/menu/options_video.qc + quakec/menusys/menu/quit.qc + ) +ENDIF() \ No newline at end of file diff --git a/imgtool.c b/imgtool.c index 19c11fe9..82989e7b 100644 --- a/imgtool.c +++ b/imgtool.c @@ -86,6 +86,59 @@ void Z_Free(void *p) { free(p); } +#ifdef _WIN32 +// don't use these functions in MSVC8 +#if (_MSC_VER < 1400) +int QDECL linuxlike_snprintf(char *buffer, int size, const char *format, ...) +{ +#undef _vsnprintf + int ret; + va_list argptr; + + if (size <= 0) + return 0; + size--; + + va_start (argptr, format); + ret = _vsnprintf (buffer,size, format,argptr); + va_end (argptr); + + buffer[size] = '\0'; + + return ret; +} +int QDECL linuxlike_vsnprintf(char *buffer, int size, const char *format, va_list argptr) +{ +#undef _vsnprintf + int ret; + + if (size <= 0) + return 0; + size--; + + ret = _vsnprintf (buffer,size, format,argptr); + + buffer[size] = '\0'; + + return ret; +} +#elif (_MSC_VER < 1900) +int VARGS linuxlike_snprintf_vc8(char *buffer, int size, const char *format, ...) +{ + int ret; + va_list argptr; + + va_start (argptr, format); + ret = vsnprintf_s (buffer,size, _TRUNCATE, format,argptr); + va_end (argptr); + + return ret; +} +#endif +#endif + + + #include @@ -349,7 +402,10 @@ qbyte GetPaletteIndexNoFB(int red, int green, int blue) } return best; } -static void ImgTool_SetupPalette(void) + +sh_config_t sh_config; +viddef_t vid; +void ImgTool_SetupPalette(void) { int i; //we ought to try to read gfx/palette.lmp, but its probably in a pak @@ -359,7 +415,20 @@ static void ImgTool_SetupPalette(void) d_8to24rgbtable[i] = (host_basepal[i*3+0]<<0)|(host_basepal[i*3+1]<<8)|(host_basepal[i*3+2]<<16); d_8to24bgrtable[i] = (host_basepal[i*3+0]<<16)|(host_basepal[i*3+1]<<8)|(host_basepal[i*3+2]<<0); } + + sh_config.texture2d_maxsize = 1u<<31; + sh_config.texture3d_maxsize = 1u<<31; + sh_config.texture2darray_maxlayers = 1u<<31; + sh_config.texturecube_maxsize = 8192; + sh_config.texture_non_power_of_two = true; + sh_config.texture_non_power_of_two_pic = true; + sh_config.texture_allow_block_padding = true; + sh_config.npot_rounddown = true; //shouldn't be relevant + sh_config.havecubemaps = true; //I don't think this matters. + + Image_Init(); } +#ifdef IMGTOOL static void ImgTool_FreeMips(struct pendingtextureinfo *mips) { size_t i; @@ -374,9 +443,6 @@ static void ImgTool_FreeMips(struct pendingtextureinfo *mips) } } -sh_config_t sh_config; -viddef_t vid; - typedef struct { unsigned int offset; // Position of the entry in WAD @@ -2136,18 +2202,7 @@ int main(int argc, const char **argv) args.defaultext = NULL; args.width = args.height = 0; - sh_config.texture2d_maxsize = 1u<<31; - sh_config.texture3d_maxsize = 1u<<31; - sh_config.texture2darray_maxlayers = 1u<<31; - sh_config.texturecube_maxsize = 8192; - sh_config.texture_non_power_of_two = true; - sh_config.texture_non_power_of_two_pic = true; - sh_config.texture_allow_block_padding = true; - sh_config.npot_rounddown = true; //shouldn't be relevant - sh_config.havecubemaps = true; //I don't think this matters. - ImgTool_SetupPalette(); - Image_Init(); if (argc==1) goto showhelp; @@ -2394,3 +2449,4 @@ showhelp: } return EXIT_SUCCESS; } +#endif diff --git a/iqm/iqm.cpp b/iqm/iqm.cpp index 28c69b45..feaa543f 100644 --- a/iqm/iqm.cpp +++ b/iqm/iqm.cpp @@ -1,3 +1,8 @@ +#define FTEPLUGIN +#define GLQUAKE //this is shit, but ensures index sizes come out the right size +#include "../plugins/plugin.h" +#include "com_mesh.h" + #include "util.h" #define IQM_UNPACK (1u<<31) //animations will be unpacked into individual frames-as-animations (ie: no more framegroups) @@ -362,7 +367,7 @@ struct emesh meshprop explicits; emesh() : name(NULL), material(NULL), firsttri(0), used(false), hasexplicits(false) {} - emesh(const char *name, const char *material, int firsttri = 0) : name(name), material(material), firsttri(firsttri), used(false) {} + emesh(const char *name, const char *material, int firsttri = 0) : name(name), material(material), firsttri(firsttri), used(false), hasexplicits(false) {} }; struct evarray @@ -988,7 +993,8 @@ void calctangents(uint priortris, bool areaweight = true) { uint numverts = vmap.length(); Vec3 *tangent = new Vec3[2*numverts], *bitangent = tangent+numverts; - memset(tangent, 0, 2*numverts*sizeof(Vec3)); + for (uint i = 0; i < 2*numverts; i++) + tangent[i] = Vec3(0,0,0); for (int i = priortris; i < triangles.length(); i++) { const triangle &t = triangles[i]; @@ -1245,7 +1251,12 @@ void makemeshes(const filespec &spec) } if(eblends.length()) { - setupvertexarray(eblends, IQM_BLENDINDEXES, IQM_UBYTE, 4, priorverts); + if (ejoints.length() > 65535) + setupvertexarray(eblends, IQM_BLENDINDEXES, IQM_UINT, 4, priorverts); + else if (ejoints.length() > 255) + setupvertexarray(eblends, IQM_BLENDINDEXES, IQM_USHORT, 4, priorverts); + else + setupvertexarray(eblends, IQM_BLENDINDEXES, IQM_UBYTE, 4, priorverts); setupvertexarray(eblends, IQM_BLENDWEIGHTS, IQM_UBYTE, 4, priorverts); } if(ecolors.length()) setupvertexarray(ecolors, IQM_COLOR, IQM_UBYTE, 4, priorverts); @@ -1334,6 +1345,7 @@ void makerelativebasepose() transform &parent = eposes[ej.parent], &child = eposes[i]; child.pos = (-parent.orient).transform(child.pos - parent.pos); child.orient = (-parent.orient)*child.orient; + child.scale = child.scale / parent.scale; if(child.orient.w > 0) child.orient.flip(); } } @@ -1372,7 +1384,7 @@ void printbones(int parent = -1, size_t ind = 1) { if (joints[i].parent == parent) { //show as 1-based for consistency with quake. - conoutf("%sbone %i:\tname=\"%s\"\tparent=%i, group=%i", prefix, i+1, &stringdata[joints[i].name], joints[i].parent+1, joints[i].group); + conoutf("%sbone %i:\tname=\"%s\"\tparent=%i, group=%i (%f %f %f)", prefix, i+1, &stringdata[joints[i].name], joints[i].parent+1, joints[i].group, joints[i].pos[0], joints[i].pos[1], joints[i].pos[2]); printbones(i, ind+1); } } @@ -3753,7 +3765,437 @@ bool loadfbx(const char *filename, const filespec &spec) return true; } +namespace fte +{ + static plugfsfuncs_t cppfsfuncs = + { + .OpenVFS = [](const char *filename, const char *mode, enum fs_relative relativeto) + { + stream *f = openfile(filename, "rb"); + if (!f) + return (vfsfile_t*)nullptr; + struct cppfile_t : public vfsfile_t + { + static int ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread) + { + auto c = static_cast(file); + return c->f->read(buffer, bytestoread); + } + static qboolean Seek (struct vfsfile_s *file, qofs_t pos) + { + auto c = static_cast(file); + return c->f->seek(pos)?qtrue:qfalse; + } + static qofs_t Tell (struct vfsfile_s *file) + { + auto c = static_cast(file); + return c->f->tell(); + } + static qofs_t GetLen (struct vfsfile_s *file) + { + auto c = static_cast(file); + return c->f->size(); + } + static qboolean Close (struct vfsfile_s *file) + { + auto c = static_cast(file); + c->f->close(); + delete c; + return qtrue; + } + cppfile_t(stream *sourcefile):f(sourcefile) + { + vfsfile_t::ReadBytes = ReadBytes; + vfsfile_t::Seek = Seek; + vfsfile_t::Tell = Tell; + vfsfile_t::GetLen = GetLen; + vfsfile_t::Close = Close; + } + stream *f; + }; + cppfile_t *c = new cppfile_t(f); + return static_cast(c); + }, + }; + static plugmodfuncs_t cppmodfuncs = + { + .version = MODPLUGFUNCS_VERSION, + .RegisterModelFormatText = [](const char *formatname, char *magictext, qboolean (QDECL *load) (struct model_s *mod, void *buffer, size_t fsize)) + { //called explicitly because we're lame. + return 0; + }, + .RegisterModelFormatMagic = [](const char *formatname, unsigned int magic, qboolean (QDECL *load) (struct model_s *mod, void *buffer, size_t fsize)) + { //called explicitly because we're lame. + return 0; + }, + .ZG_Malloc = [](zonegroup_t *ctx, size_t size) + { + /*leak the memory, because we're lazy*/ + void *ret = malloc(size); + memset(ret, 0, size); + return ret; + }, + .ConcatTransforms = [](const float in1[3][4], const float in2[3][4], float out[3][4]) + { + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; + }, + + .GenMatrixPosQuat4Scale = [](const vec3_t pos, const vec4_t quat, const vec3_t scale, float result[12]) + { + Matrix3x4 m = Matrix3x4(Quat(quat), Vec3(pos), Vec3(scale)); + result[0] = m.a.x; + result[1] = m.b.x; + result[2] = m.c.x; + result[3] = m.a.w; + + result[4] = m.a.y; + result[5] = m.b.y; + result[6] = m.c.y; + result[7] = m.b.w; + + result[8] = m.a.z; + result[9] = m.b.z; + result[10] = m.c.z; + result[11] = m.c.w; + }, + + .GetTexture = [](const char *identifier, const char *subpath, unsigned int flags, void *fallbackdata, void *fallbackpalette, int fallbackwidth, int fallbackheight, uploadfmt_t fallbackfmt) + { + image_t *img = (image_t*)cppmodfuncs.ZG_Malloc(NULL, sizeof(*img)+strlen(identifier)+1); + img->ident = (char*)(img+1); + strcpy(img->ident, identifier); + img->flags = flags; + return img; + }, + + .AccumulateTextureVectors = [](vecV_t *const vc, vec2_t *const tc, vec3_t *nv, vec3_t *sv, vec3_t *tv, const index_t *idx, int numidx, qboolean calcnorms) + { //once per surface that shares the set of verts + }, + .NormaliseTextureVectors = [](vec3_t *n, vec3_t *s, vec3_t *t, int v, qboolean calcnorms) + { //once per shared set of verts. + }, + }; + static plugcorefuncs_t cppplugfuncs = + { + .GetEngineInterface = [](const char *interfacename, size_t structsize) + { + void *ret = nullptr; + if (!strcmp(interfacename, plugfsfuncs_name)) + ret = &cppfsfuncs; + if (!strcmp(interfacename, plugmodfuncs_name)) + ret = &cppmodfuncs; + return ret; + }, + }; + static plugcvarfuncs_t cppcvarfuncs = + { + .GetNVFDG = [](const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname) + { //could maybe fill with environment settings perhaps? yuck. + auto v = new cvar_t(); + v->name = strdup(name); + v->string = strdup(defaultval); + v->value = atof(v->string); + v->ival = atoi(v->string); + return v; + }, + }; + extern "C" + { //our plugin-style stuff has a few external dependancies not provided via pointers... + void ImgTool_SetupPalette(void); + qboolean QDECL Mod_LoadGLTFModel (struct model_s *mod, void *buffer, size_t fsize); + qboolean QDECL Mod_LoadGLBModel (struct model_s *mod, void *buffer, size_t fsize); + qboolean Plug_GLTF_Init(void); + plugcorefuncs_t *plugfuncs = &cppplugfuncs; + plugcmdfuncs_t *cmdfuncs; + plugcvarfuncs_t *cvarfuncs = &cppcvarfuncs; + void Q_strlcpy(char *d, const char *s, int n) + { + int i; + n--; + if (n < 0) + return; //this could be an error + + for (i=0; *s; i++) + { + if (i == n) + break; + *d++ = *s++; + } + *d='\0'; + } + void Q_strlcat(char *d, const char *s, int n) + { + if (n) + { + int dlen = strlen(d); + int slen = strlen(s)+1; + if (slen > (n-1)-dlen) + slen = (n-1)-dlen; + memcpy(d+dlen, s, slen); + d[n - 1] = 0; + } + } + } + + transform ftetransform(float bm[12], bool invert) + { + Matrix3x3 m(Vec3(bm[0], bm[1], bm[2]), Vec3(bm[4], bm[5], bm[6]), Vec3(bm[8], bm[9], bm[10])); + m.transpose(); + Vec3 pos(bm[3], bm[7], bm[11]); + transform t; + + Vec3 mscale(Vec3(m.a.x, m.b.x, m.c.x).magnitude(), Vec3(m.a.y, m.b.y, m.c.y).magnitude(), Vec3(m.a.z, m.b.z, m.c.z).magnitude()); + // check determinant for sign of scaling + if(Matrix3x3(m).determinant() < 0) mscale = -mscale; + m.a /= mscale; + m.b /= mscale; + m.c /= mscale; + t.orient = Quat(m); + if(t.orient.w > 0) t.orient.flip(); + t.scale = mscale; + + if (invert) + { + // invert the translate + t.pos[0] = -(pos[0] * m.a[0] + pos[1] * m.a[1] + pos[2] * m.a[2]); + t.pos[1] = -(pos[0] * m.b[0] + pos[1] * m.b[1] + pos[2] * m.b[2]); + t.pos[2] = -(pos[0] * m.c[0] + pos[1] * m.c[1] + pos[2] * m.c[2]); + } + else + t.pos = pos; + return t; + } + + bool loadfte(model_t *mod, const filespec &spec) + { //import from fte's structs and convert to iqmtool's c++isms + if (mod->type != mod_alias) + return false; //err... + galiasinfo_t *surf = (galiasinfo_t*)mod->meshinfo; + if (surf) + { + resetimporter(spec); + + if (surf->baseframeofs) + { + for (int b = 0; b < surf->numbones; b++) + { + transform p(ftetransform(surf->ofsbones[b].inverse, true)); + + //spit out the joint info + ejoint &j = ejoints.add(); + j.name = surf->ofsbones[b].name; + j.parent = surf->ofsbones[b].parent; + + //and the base pose + eposes.add(p); + } + makerelativebasepose(); + +#if 1 //import the animations + for (int animidx = 0; animidx < surf->numanimations; animidx++) + { + auto &anim = surf->ofsanimations[animidx]; + int firstframe = eframes.length(); + vector bonebuf; + + for (int f = 0; f < anim.numposes; f++) + { + skeltype_t sk = anim.skeltype; + float time = f/anim.rate; + float *bonedata; + if (anim.GetRawBones) + bonedata = anim.GetRawBones(surf, &anim, time, bonebuf.reserve(surf->numbones)[0], surf->numbones); + else if (anim.boneofs) + bonedata = (float*)anim.boneofs; + else + bonedata = (float*)surf->baseframeofs, sk = SKEL_ABSOLUTE; //abs... + + if (sk == SKEL_RELATIVE) + ; + else + printf("Unusable skeletal type for import - %i\n", (int)sk); + + eframes.add(eposes.length()); + for (int b = 0; b < surf->numbones; b++, bonedata += 12) + eposes.add(ftetransform(bonedata, false)); + } + + + eanim &a = eanims.add(); + if(spec.name) a.name = getnamekey(spec.name); + else + { + string name; + copystring(name, mod->name); + char *end = strrchr(name, '.'); + if(end) *end = '\0'; + a.name = getnamekey(name); + } + a.startframe = firstframe; + a.fps = anim.rate; + a.flags = anim.loop?IQM_LOOP:0; + a.endframe = eframes.length(); + } +#endif + } + + for(; surf; surf = surf->nextsurf) + { + if (surf->shares_bones != 0) + continue; + + if (surf->numindexes) + { + unsigned int firstvert=epositions.length(); + for (int v = 0; v < surf->numverts; v++) + { + Vec3 pos(surf->ofs_skel_xyz[v][0], surf->ofs_skel_xyz[v][1], surf->ofs_skel_xyz[v][2]); + Vec3 norm; + if (surf->ofs_skel_norm) + norm = Vec3(surf->ofs_skel_norm[v][0], surf->ofs_skel_norm[v][1], surf->ofs_skel_norm[v][2]); + else + norm = Vec3(0,0,0); + + etexcoords.add(Vec4(surf->ofs_st_array[v][0], surf->ofs_st_array[v][1], 0, 0)); + if (surf->ofs_rgbaf) + ecolors.add(Vec4(surf->ofs_rgbaf[v][0], surf->ofs_rgbaf[v][1], surf->ofs_rgbaf[v][2], surf->ofs_rgbaf[v][3])); + else if (surf->ofs_rgbaub) + ecolors.add(Vec4(surf->ofs_rgbaub[v][0]/255.0, surf->ofs_rgbaub[v][1]/255.0, surf->ofs_rgbaub[v][2]/255.0, surf->ofs_rgbaub[v][3]/255.0)); + +// if (surf->ofs_skel_svect) +// etangents.add (Vec4(surf->ofs_skel_svect[v][0], surf->ofs_skel_svect[v][1], surf->ofs_skel_svect[v][2], 0)); +// if (surf->ofs_skel_tvect) +// ebitangents.add(Vec3(surf->ofs_skel_tvect[v][0], surf->ofs_skel_tvect[v][1], surf->ofs_skel_tvect[v][2])); + + if (surf->shares_bones == 0 && surf->ofs_skel_weight && surf->ofs_skel_idx) + { + blendcombo b = {}; + //Vec3 newpos(0,0,0); + //Vec3 newnorm(0,0,0); + for (size_t w = 0; w < 4; w++) + { + //newpos += bonerepositions[surf->ofs_skel_idx[v][w]].transform(pos) * surf->ofs_skel_weight[v][w]; + //newnorm += bonerepositions[surf->ofs_skel_idx[v][w]].transform3(norm) * surf->ofs_skel_weight[v][w]; + if (surf->ofs_skel_weight[v][w] > 0) + b.addweight(surf->ofs_skel_weight[v][w], surf->ofs_skel_idx[v][w]); + } + b.finalize(); + eblends.add(b); + //pos = newpos; + //norm = newnorm; + } + epositions.add(Vec4(pos)); + enormals.add(norm); + } + + //iqms don't support skins/skingroups themselves. + //we have only surface name and texture(aka material) name. + //so use the diffuse texture's name where we can + // a) its already processed properly so no ''path/model.gltf/sectionthatdoesntevenexistondisk' locations. + // b) its more likely to show something without needing to synthesize shaders/textures. + //we should probably cvar this. + const char *materialname; + if (surf->numskins && surf->ofsskins[0].numframes && surf->ofsskins[0].frame[0].texnums.base && *surf->ofsskins[0].frame[0].texnums.base->ident != '$') + materialname = surf->ofsskins[0].frame[0].texnums.base->ident; + else if (surf->numskins) + materialname = surf->ofsskins[0].name; + else + materialname = surf->surfacename; + + emesh mesh(surf->surfacename, materialname, etriangles.length()); + + //add in some extra surface properties. + mesh.hasexplicits = true; + mesh.explicits.contents = surf->contents; + mesh.explicits.surfaceflags = surf->csurface.flags; + mesh.explicits.body = surf->surfaceid; + mesh.explicits.geomset = surf->geomset; + mesh.explicits.geomid = surf->geomid; + mesh.explicits.mindist = surf->mindist; + mesh.explicits.maxdist = surf->maxdist; + + emeshes.add(mesh); + for (int idx = 0; idx+2 < surf->numindexes; idx+=3) + etriangles.add(etriangle(surf->ofs_indexes[idx+0]+firstvert, surf->ofs_indexes[idx+1]+firstvert, surf->ofs_indexes[idx+2]+firstvert)); + } + } + makeanims(spec); + if (emeshes.length()) + { + smoothverts(); + makemeshes(spec); + } + return true; + } + return false; + } + bool loadglb(const char *filename, const filespec &spec) + { + bool ret = false; + model_t mod={}; + stream *f = openfile(filename, "rb"); + Q_strlcpy(mod.name, filename, sizeof(mod.name)); + if (f) + { + size_t sz = f->size(); + auto filebuf = new char[sz]; + if (sz == f->read(filebuf, sz)) + { + if (Plug_GLTF_Init()) + if (Mod_LoadGLBModel(&mod, filebuf, sz)) + ret = loadfte(&mod, spec); + } + delete[] filebuf; + delete f; + } + return ret; + } + bool loadgltf(const char *filename, const filespec &spec) + { + bool ret = false; + model_t mod={}; + stream *f = openfile(filename, "rb"); + Q_strlcpy(mod.name, filename, sizeof(mod.name)); + if (f) + { + size_t sz = f->size(); + auto filebuf = new char[sz]; + if (sz == f->read(filebuf, sz)) + { + if (Plug_GLTF_Init()) + if (Mod_LoadGLTFModel(&mod, filebuf, sz)) + ret = loadfte(&mod, spec); + } + delete[] filebuf; + delete f; + } + return ret; + } +} void genhitboxes(vector &hitboxes) { @@ -3785,18 +4227,18 @@ void genhitboxes(vector &hitboxes) formatstring(tmp, "hitbox%i", hitboxes[i].body); m.name = newstring(tmp); m.hasexplicits = true; - memset(&m.explicits, 0, sizeof(m.explicits)); + m.explicits = {}; m.explicits.contents = 0x02000000; m.explicits.surfaceflags = 0x80; m.explicits.body = hitboxes[i].body; m.explicits.geomset = ~0u; //spit out some verts + Matrix3x4 bm(mjoints[bone]); + bm.invert(); for (int j = 0; j < 8; j++) { Vec3 p = Vec3((j&1)?hb.mins[0]:hb.maxs[0], (j&2)?hb.mins[1]:hb.maxs[1], (j&4)?hb.mins[2]:hb.maxs[2]); - Matrix3x4 bm(mjoints[bone]); - bm.invert(); p = bm.transform(p); epositions.add(Vec4(p, 0)); enormals.add(p); @@ -4140,7 +4582,7 @@ bool writeiqm(const char *filename) } } - if(stringdata.length()) hdr.ofs_text = hdr.filesize; hdr.num_text = stringdata.length(); hdr.filesize += hdr.num_text; + if(stringdata.length()) hdr.ofs_text = hdr.filesize, hdr.num_text = stringdata.length(), hdr.filesize += hdr.num_text; hdr.num_meshes = meshes.length(); if(meshes.length()) hdr.ofs_meshes = hdr.filesize; hdr.filesize += meshes.length() * sizeof(iqmmesh); uint voffset = hdr.filesize + varrays.length() * sizeof(iqmvertexarray); hdr.num_vertexarrays = varrays.length(); if(varrays.length()) hdr.ofs_vertexarrays = hdr.filesize; hdr.filesize += varrays.length() * sizeof(iqmvertexarray); @@ -4149,17 +4591,17 @@ bool writeiqm(const char *filename) hdr.filesize += valign + vdata.length(); hdr.num_vertexes = numfverts; hdr.num_triangles = triangles.length(); if(triangles.length()) hdr.ofs_triangles = hdr.filesize; hdr.filesize += triangles.length() * sizeof(iqmtriangle); - if(neighbors.length()) hdr.ofs_adjacency = hdr.filesize; hdr.filesize += neighbors.length() * sizeof(iqmtriangle); + if(neighbors.length()) hdr.ofs_adjacency = hdr.filesize, hdr.filesize += neighbors.length() * sizeof(iqmtriangle); hdr.num_joints = joints.length(); if(joints.length()) hdr.ofs_joints = hdr.filesize; hdr.filesize += joints.length() * sizeof(iqmjoint); hdr.num_poses = poses.length(); if(poses.length()) hdr.ofs_poses = hdr.filesize; hdr.filesize += poses.length() * sizeof(iqmpose); hdr.num_anims = anims.length(); if(anims.length()) hdr.ofs_anims = hdr.filesize; hdr.filesize += anims.length() * sizeof(iqmanim); hdr.num_frames = frames.length(); hdr.num_framechannels = framesize; - if(animdata.length()) hdr.ofs_frames = hdr.filesize; hdr.filesize += animdata.length() * sizeof(ushort); - if(bounds.length()) hdr.ofs_bounds = hdr.filesize; hdr.filesize += bounds.length() * sizeof(float[8]); - if(commentdata.length()) hdr.ofs_comment = hdr.filesize; hdr.num_comment = commentdata.length(); hdr.filesize += hdr.num_comment; - if (extensions.length()) hdr.ofs_extensions = hdr.filesize; hdr.num_extensions = extensions.length(); hdr.filesize += sizeof(iqmextension) * hdr.num_extensions; - if (ext_meshes_fte) {ext_meshes_fte->ofs_data = hdr.filesize; ext_meshes_fte->num_data = meshes_fte.length()*sizeof(iqmext_fte_mesh); hdr.filesize += ext_meshes_fte->num_data;} - if (ext_events_fte) {ext_events_fte->ofs_data = hdr.filesize; ext_events_fte->num_data = events_fte.length()*sizeof(iqmext_fte_events); hdr.filesize += ext_events_fte->num_data;} + if(animdata.length()) hdr.ofs_frames = hdr.filesize, hdr.filesize += animdata.length() * sizeof(ushort); + if(bounds.length()) hdr.ofs_bounds = hdr.filesize, hdr.filesize += bounds.length() * sizeof(float[8]); + if(commentdata.length()) hdr.ofs_comment = hdr.filesize, hdr.num_comment = commentdata.length(), hdr.filesize += hdr.num_comment; + if (extensions.length()) hdr.ofs_extensions = hdr.filesize, hdr.num_extensions = extensions.length(), hdr.filesize += sizeof(iqmextension) * hdr.num_extensions; + if (ext_meshes_fte) ext_meshes_fte->ofs_data = hdr.filesize, ext_meshes_fte->num_data = meshes_fte.length()*sizeof(iqmext_fte_mesh), hdr.filesize += ext_meshes_fte->num_data; + if (ext_events_fte) ext_events_fte->ofs_data = hdr.filesize, ext_events_fte->num_data = events_fte.length()*sizeof(iqmext_fte_events), hdr.filesize += ext_events_fte->num_data; lilswap(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint)); @@ -4283,83 +4725,249 @@ bool writeiqm(const char *filename) } -uchar qmdl_bestnorm(Vec3 &v) +static uchar qmdl_bestnorm(Vec3 &v) { - //FIXME - return 0; + #define NUMVERTEXNORMALS 162 + static float r_avertexnormals[NUMVERTEXNORMALS][3] = { + #include "anorms.h" + }; + uchar best = 0; + float bestdot = -FLT_MAX, dot; + for (size_t i = 0; i < countof(r_avertexnormals); i++) + { + dot = DotProduct(v, r_avertexnormals[i]); + if (dot > bestdot) + { + bestdot = dot; + best = i; + } + } + return best; } struct qmdl_vertex_t { unsigned char v[3]; unsigned char normalIndex; }; -template bool writemdl(const char *filename) +static bool writemdl(const char *filename, bool md16) { if (meshes.length() != 1) { conoutf("warning: mdl output requires exactly one mesh"); - return false; //must have ONE mesh only. + if (meshes.length() < 0) + return false; //must have ONE mesh only. + else + conoutf("using first..."); } - auto mesh = meshes[0]; + auto mesh = meshes[0]; //should probably favour the mesh with the most verts or something. vertexarray *texcoords = NULL; vertexarray *vertcoords = NULL; vertexarray *vertnorm = NULL; + vertexarray *vertbones = NULL; + vertexarray *vertweights = NULL; uint skinwidth = 0; uint skinheight = 0; Vec3 offset={0,0,0}; Vec3 scale={1,1,1}; uint numskins = 0; vector skindata; + unsigned char *paletteddata; loopv(varrays) { - if(varrays[i].type == IQM_TEXCOORD && varrays[i].format == IQM_FLOAT && varrays[i].count == 2) + if(varrays[i].type == IQM_TEXCOORD && varrays[i].format == IQM_FLOAT && varrays[i].size == 2) texcoords = &varrays[i]; - if(varrays[i].type == IQM_POSITION && varrays[i].format == IQM_FLOAT && varrays[i].count == 3) + if(varrays[i].type == IQM_POSITION && varrays[i].format == IQM_FLOAT && varrays[i].size == 3) vertcoords = &varrays[i]; - if(varrays[i].type == IQM_NORMAL && varrays[i].format == IQM_FLOAT && varrays[i].count == 3) + if(varrays[i].type == IQM_NORMAL && varrays[i].format == IQM_FLOAT && varrays[i].size == 3) vertnorm = &varrays[i]; -// if(varrays[i].type == IQM_BLENDINDEXES && varrays[i].format == IQM_BYTE && varrays[i].count == 4) -// vertbones = &varrays[i]; -// if(varrays[i].type == IQM_BLENDWEIGHTS && varrays[i].format == IQM_FLOAT && varrays[i].count == 4) -// vertweights = &varrays[i]; + if(varrays[i].type == IQM_BLENDINDEXES && varrays[i].format == IQM_UBYTE && varrays[i].size == 4) + vertbones = &varrays[i]; + if(varrays[i].type == IQM_BLENDWEIGHTS && varrays[i].format == IQM_UBYTE && varrays[i].size == 4) + vertweights = &varrays[i]; } if (!texcoords) { conoutf("warning: mdl output requires a float texcoord array"); return false; //must have some vertex coords... } + if (!vertcoords) + { + conoutf("warning: mdl output requires a suitable vertex positions array..."); + return false; //must have some vertex coords... + } + if (!vertnorm) + { + conoutf("warning: mdl output requires a suitable vertex normals array..."); + return false; //must have some vertex coords... + } float *tcdata = (float*)texcoords->vdata.getbuf(); - skinwidth = 4; - skinheight = 4; - memset(skindata.reserve(skinwidth*skinheight), 15, skinwidth*skinheight); + //the actual mdl limit is really annoying to calculate. + if (mesh.numverts >= 1024) + conoutf("Writing mdl %s with %u verts exceeds regular limit of %u", filename, mesh.numverts, 1024); + + //read the skin... + size_t filesize=0; + qbyte *filedata = NULL; + auto s = openfile(&stringdata[mesh.material], "rb"); + if (s) + { + filesize = s->size(); + filedata = (qbyte*)malloc(filesize); + s->read(filedata, filesize); + delete s; + } + //decode it... + fte::ImgTool_SetupPalette(); + struct pendingtextureinfo *tex = NULL; + if (filedata) + tex = Image_LoadMipsFromMemory(IF_NOMIPMAP, &stringdata[mesh.material], &stringdata[mesh.material], filedata, filesize); + else + conoutf("could not open file %s", &stringdata[mesh.material]); + if (tex) + { //okay, we have a valid image! +#if 1 + //downsize it to work around glquake's limitations. square textures will generally end up 256*256 instead of 512*512 due to that stupid 480 height limit + int newwidth = tex->mip[0].width; + int newheight = tex->mip[0].height; + auto npotup = [](unsigned val) + { //convert to npot, rounding up. + unsigned scaled = 1; + while(scaled < val) + scaled<<=1; + return scaled; + }; + while (newwidth*newheight > 640*480/*GL_Upload8 limit*/ || npotup(newwidth)*npotup(newheight) > 1024*512/*GL_Upload32 limit, may be higher thanks to gl_max_size or gl_picmip but really that sucks*/ || newheight > 480/*Mod_LoadAliasModel limit -- weird MAX_LBM_HEIGHT check*/) + { + newwidth >>= 1; + newheight >>= 1; + } + auto resized = (tex->mip[0].width == newwidth&&tex->mip[0].height == newheight)?NULL:Image_ResampleTexture(tex->encoding, tex->mip[0].data, tex->mip[0].width, tex->mip[0].height, NULL, newwidth, newheight); + if (resized) + { + tex->mip[0].data = resized; + tex->mip[0].datasize = 0; /*o.O*/ + tex->mip[0].width = newwidth; + tex->mip[0].height = newheight; + } +#endif + + //palettize it, to match the q1 palette. + qboolean allowedformats[PTI_MAX] = {}; + allowedformats[PTI_P8]=qtrue; + //FIXME: add hexen2's alpha stuff? + conoutf("Palettizing \"%s\" (%u*%u)", &stringdata[mesh.material], tex->mip[0].width, tex->mip[0].height); + Image_ChangeFormat(tex, allowedformats, PTI_INVALID, "foo"); + + skinwidth = tex->mip[0].width; + skinheight = tex->mip[0].height; + } + else + { //texture coords are ints. if we don't have a large enough texture then we don't have much texture coord precision either, so use something reasonable. + skinwidth = 128; + skinheight = 128; + } + paletteddata = skindata.reserve(skinwidth*skinheight); + if (tex) + { + memcpy(paletteddata, tex->mip[0].data, skinwidth*skinheight); + *paletteddata^=1; //try to work around glquake's flood fill... + } + else + { //fill with some sort of grey. + memset(paletteddata, 8, skinwidth*skinheight); + paletteddata[0] = 7; //work around flood filling... + } skindata.advance(skinwidth*skinheight); numskins++; //we're going to need the transformed pose data, without any bone weights getting in the way. vector vpos, vnorm; Vec3 min={FLT_MAX,FLT_MAX,FLT_MAX}, max={-FLT_MAX,-FLT_MAX,-FLT_MAX}; - loopv(anims) + if (!anims.length()) + { + Vec3 *outv = vpos.reserve(mesh.numverts); + Vec3 *outn = vnorm.reserve(mesh.numverts); + + auto invert = (float*)vertcoords->vdata.getbuf(); + auto innorm = (float*)vertnorm->vdata.getbuf(); +// auto inbones = (uchar*)vertbones->vdata.getbuf(); +// auto inweights = (uchar*)vertweights->vdata.getbuf(); + + //FIXME: generate bone matricies from base pose? or just use the vertex data as-is... + for (uint i = mesh.firstvert; i < mesh.firstvert+mesh.numverts; i++, outv++, outn++) + { + //FIXME: generate vert's matrix + + //transform each vert + *outv = Vec3(invert[i*3+0], invert[i*3+1], invert[i*3+2]); + + //bound it to find the model's extents + for (uint c = 0; c < 3; c++) + { + if (min.v[c] > outv->v[c]) + min.v[c] = outv->v[c]; + if (max.v[c] < outv->v[c]) + max.v[c] = outv->v[c]; + } + + *outn = Vec3(innorm[i*3+0], innorm[i*3+1], innorm[i*3+2]); + } + vpos.advance(mesh.numverts); + vnorm.advance(mesh.numverts); + } + else loopv(anims) { anim &a = anims[i]; Vec3 *outv = vpos.reserve(mesh.numverts*a.numframes); Vec3 *outn = vnorm.reserve(mesh.numverts*a.numframes); - Vec3 *invert = (Vec3*)vertcoords->vdata.getbuf(); - Vec3 *innorm = (Vec3*)vertnorm->vdata.getbuf(); -// uchar *inbones = (uchar*)vertbones->vdata.getbuf(); -// Vec4 *inweights = (Vec4*)vertweights->vdata.getbuf(); +// Matrix3x4 bonepose[joints.length()]; + vector bonepose; + bonepose.reserve(joints.length()); - for (int j = 0; j < a.numframes; j++) + auto invert = (float*)vertcoords->vdata.getbuf(); + auto innorm = (float*)vertnorm->vdata.getbuf(); + auto inbones = vertbones?(uchar*)vertbones->vdata.getbuf():NULL; + auto inweights = vertweights?(uchar*)vertweights->vdata.getbuf():NULL; + + if (!inbones || !inweights) + printf("no bone indexes\n"); + + for (uint j = 0; j < a.numframes; j++) { - //FIXME: generate bone matricies - for (int i = mesh.firstvert; i < mesh.numverts; i++, outv++, outn++) + //build absolute poses. + frame &fr = frames[a.firstframe+j]; + for (int b = 0; b < fr.pose.length(); b++) { - //FIXME: generate vert's matrix + auto &frpose = fr.pose[b]; + bonepose[b] = Matrix3x4(Quat(frpose.tr.orient), Vec3(frpose.tr.pos), Vec3(frpose.tr.scale)); + if (frpose.boneparent >= 0) + bonepose[b] = bonepose[frpose.boneparent] * bonepose[b]; + } + //done with parents... now we want to invert them + for (int b = 0; b < fr.pose.length(); b++) + { + Matrix3x4 invbind = Matrix3x4(mjoints[b]); + //invbind.invert(); + bonepose[b] = bonepose[b] * invbind; + } + for (uint i = mesh.firstvert; i < mesh.numverts; i++, outv++, outn++) + { + //generate per-vert matrix... + Matrix3x4 blend; + blend *= 0; + if (inweights && inbones) + { + if (inweights[i*4+0]) blend += bonepose[inbones[i*4+0]] * (inweights[i*4+0]/255.0); + if (inweights[i*4+1]) blend += bonepose[inbones[i*4+1]] * (inweights[i*4+1]/255.0); + if (inweights[i*4+2]) blend += bonepose[inbones[i*4+2]] * (inweights[i*4+2]/255.0); + if (inweights[i*4+3]) blend += bonepose[inbones[i*4+3]] * (inweights[i*4+3]/255.0); + } //transform each vert - *outv = invert[i]; + *outv = blend.transform(Vec3(invert[i*3+0], invert[i*3+1], invert[i*3+2])); //bound it to find the model's extents for (uint c = 0; c < 3; c++) @@ -4370,14 +4978,14 @@ template bool writemdl(const char *filename) max.v[c] = outv->v[c]; } - *outn = innorm[i]; + *outn = blend.transform3(Vec3(innorm[i*3+0], innorm[i*3+1], innorm[i*3+2])); } vpos.advance(mesh.numverts); vnorm.advance(mesh.numverts); } } - offset = -min; + offset = min; scale = (max-min)/255; //ignore low order info here stream *f = openfile(filename, "wb"); @@ -4405,27 +5013,36 @@ template bool writemdl(const char *filename) f->putlil((uint)mesh.numverts); f->putlil((uint)mesh.numtris); - f->putlil((uint)anims.length()); //numanims + if (!anims.length()) + f->putlil((uint)1); //numanims + else + f->putlil((uint)anims.length()); //numanims f->putlil((uint)0); //synctype f->putlil((uint)modelflags); //flags f->putlil(0.f); //size //skins - for (int i = 0; i < numskins; i++) + for (uint i = 0; i < numskins; i++) { f->putlil((uint)0); //ALIAS_SKIN_SINGLE f->write(skindata.getbuf()+i*skinwidth*skinheight, skinwidth*skinheight); } //texcoords - for (int i = mesh.firstvert; i < mesh.numverts; i++) + for (uint i = mesh.firstvert; i < mesh.firstvert+mesh.numverts; i++) { + float s = (tcdata[i*2+0])*skinwidth; + float t = (tcdata[i*2+1])*skinheight; + s -= 0.5; //glquake has some annoying half-texel offset thing. + t -= 0.5; + s = bound(0, s, skinwidth); + t = bound(0, t, skinwidth); f->putlil((uint)(0?32:0)); //onseam. no verts are ever onseam for us, as we don't do that nonsense here. - f->putlil((int)((tcdata[i*2+0]+.5)*skinwidth)); //mdl texcoords are ints, in texels. which sucks, but what can you do... - f->putlil((int)((tcdata[i*2+1]+.5)*skinheight)); + f->putlil((int)s); //mdl texcoords are ints, in texels. which sucks, but what can you do... + f->putlil((int)t); } //tris - for (int i = mesh.firsttri; i < mesh.firsttri+mesh.numtris; i++) + for (uint i = mesh.firsttri; i < mesh.firsttri+mesh.numtris; i++) { f->putlil((uint)1); //faces front. All are effectively front-facing for us. This avoids annoying tc additions. f->putlil((uint)triangles[i].vert[0]); @@ -4435,90 +5052,158 @@ template bool writemdl(const char *filename) //animations vector high, low; size_t voffset = 0; - loopv(anims) - { - anim &a = anims[i]; - for (int j = 0; j < a.numframes; j++) - { - qmdl_vertex_t *th=high.reserve(mesh.numverts),*tl=low.reserve(mesh.numverts); - for (int i = mesh.firstvert; i < mesh.numverts; i++, th++, tl++) - { - int l; - for (uint c = 0; c < 3; c++) - { - l = (((vpos[voffset][c]-offset[c])*256) / scale[c]); - if (l<0) l = 0; - if (l > 0xff00) l = 0xff00; //0xffff would exceed the bounds values, so don't use it. - th->v[c] = l>>8; - tl->v[c] = l&0xff; - } - tl->normalIndex = th->normalIndex = qmdl_bestnorm(vnorm[voffset]); - voffset++; + if (!anims.length()) + { + qmdl_vertex_t *th=high.reserve(mesh.numverts),*tl=low.reserve(mesh.numverts); + for (uint i = mesh.firstvert; i < mesh.numverts; i++, th++, tl++) + { + int l; + for (uint c = 0; c < 3; c++) + { + l = (((vpos[voffset][c]-offset[c])*256) / scale[c]); + if (l<0) l = 0; + if (l > 0xff00) l = 0xff00; //0xffff would exceed the bounds values, so don't use it. + th->v[c] = l>>8; + tl->v[c] = l&0xff; } - high.advance(mesh.numverts); - low.advance(mesh.numverts); + tl->normalIndex = th->normalIndex = qmdl_bestnorm(vnorm[voffset]); + + voffset++; } + high.advance(mesh.numverts); + low.advance(mesh.numverts); + + voffset = 0; + f->putlil((uint)0); //single-pose type + + char name[16]="base"; + qmdl_vertex_t min={{255,255,255}}, max={{0,0,0}}; + for (uint k = 0; k < mesh.numverts; k++) + { + for (uint c = 0; c < 3; c++) + { + if (min.v[c] > high[voffset+k].v[c]) + min.v[c] = high[voffset+k].v[c]; + if (max.v[c] < high[voffset+k].v[c]) + max.v[c] = high[voffset+k].v[c]; + } + } + f->put(min); + f->put(max); + + name[countof(name)-1] = 0; + for (uint k = 0; k < countof(name); k++) + f->put(name[k]); + + f->write(&high[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); + if (md16) + f->write(&low[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); + voffset += mesh.numverts; } - voffset = 0; - loopv(anims) + else { - anim &a = anims[i]; - if (a.numframes == 1) - f->putlil((uint)0); //single-pose type - else + loopv(anims) { - f->putlil((uint)1); //anim type - f->putlil((uint)a.numframes); - - qmdl_vertex_t min={{255,255,255}}, max={{0,0,0}}; - for (uint k = 0; k < mesh.numverts*a.numframes; k++) + anim &a = anims[i]; + for (uint j = 0; j < a.numframes; j++) { - for (uint c = 0; c < 3; c++) + qmdl_vertex_t *th=high.reserve(mesh.numverts),*tl=low.reserve(mesh.numverts); + + for (uint i = mesh.firstvert; i < mesh.numverts; i++, th++, tl++) { - if (min.v[c] > high[voffset+k].v[c]) - min.v[c] = high[voffset+k].v[c]; - if (max.v[c] < high[voffset+k].v[c]) - max.v[c] = high[voffset+k].v[c]; + int l; + for (uint c = 0; c < 3; c++) + { + l = (((vpos[voffset][c]-offset[c])*256) / scale[c]); + if (l<0) l = 0; + if (l > 0xff00) l = 0xff00; //0xffff would exceed the bounds values, so don't use it. + th->v[c] = l>>8; + tl->v[c] = l&0xff; + } + tl->normalIndex = th->normalIndex = qmdl_bestnorm(vnorm[voffset]); + + voffset++; } + high.advance(mesh.numverts); + low.advance(mesh.numverts); } - f->put(min); - f->put(max); - for (int j = 0; j < a.numframes; j++) - f->putlil(1.0f/a.fps); //intervals. we use the same value for each } - - for (int j = 0; j < a.numframes; j++) + voffset = 0; + loopv(anims) { - char name[16]={0}; - qmdl_vertex_t min={{255,255,255}}, max={{0,0,0}}; - for (uint k = 0; k < mesh.numverts; k++) + anim &a = anims[i]; + if (a.numframes == 1) + f->putlil((uint)0); //single-pose type + else { - for (uint c = 0; c < 3; c++) + f->putlil((uint)1); //anim type + f->putlil((uint)a.numframes); + + qmdl_vertex_t min={{255,255,255}}, max={{0,0,0}}; + for (uint k = 0; k < mesh.numverts*a.numframes; k++) { - if (min.v[c] > high[voffset+k].v[c]) - min.v[c] = high[voffset+k].v[c]; - if (max.v[c] < high[voffset+k].v[c]) - max.v[c] = high[voffset+k].v[c]; + for (uint c = 0; c < 3; c++) + { + if (min.v[c] > high[voffset+k].v[c]) + min.v[c] = high[voffset+k].v[c]; + if (max.v[c] < high[voffset+k].v[c]) + max.v[c] = high[voffset+k].v[c]; + } } + f->put(min); + f->put(max); + for (uint j = 0; j < a.numframes; j++) + f->putlil(1.0f/a.fps); //intervals. we use the same value for each } - f->put(min); - f->put(max); - strncpy(name, &stringdata[a.name], sizeof(name)); - f->put(name); + for (uint j = 0; j < a.numframes; j++) + { + char name[16]={0}; + qmdl_vertex_t min={{255,255,255}}, max={{0,0,0}}; + for (uint k = 0; k < mesh.numverts; k++) + { + for (uint c = 0; c < 3; c++) + { + if (min.v[c] > high[voffset+k].v[c]) + min.v[c] = high[voffset+k].v[c]; + if (max.v[c] < high[voffset+k].v[c]) + max.v[c] = high[voffset+k].v[c]; + } + } + f->put(min); + f->put(max); - f->write(&high[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); - if (md16) - f->write(&low[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); - voffset += mesh.numverts; + strncpy(name, &stringdata[a.name], sizeof(name)); + name[countof(name)-1] = 0; + for (uint k = 0; k < countof(name); k++) + f->put(name[k]); + + f->write(&high[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); + if (md16) + f->write(&low[voffset], sizeof(qmdl_vertex_t)*mesh.numverts); + voffset += mesh.numverts; + } } } delete f; return true; } +static bool writeqmdl(const char *filename) +{ + return writemdl(filename, false); +} +static bool writemd16(const char *filename) +{ + return writemdl(filename, true); +} +static bool writemd3(const char *filename) +{ + fprintf(stderr, "writemd3 is not implemented yet\n"); + return false; +} void help(bool exitstatus = EXIT_SUCCESS) { @@ -4808,15 +5493,17 @@ bool parseanimfield(const char *tok, char **line, filespec &spec, bool defaults) struct { + const char *extname; bool (*write)(const char *filename); const char *cmdname; const char *altcmdname; } outputtypes[] = { - {writeiqm, "output_iqm"}, - {writemdl<0>, "output_qmdl"}, - {writemdl<1>, "output_md16"}, -// {writemd3, "output_md3"}, + {".vvm", writeiqm, "output_vvm"}, + {".iqm", writeiqm, "output_iqm"}, + {".mdl", writeqmdl, "output_qmdl"}, + {".md16", writemd16, "output_md16"}, + {".md3", writemd3, "output_md3"}, }; void parsecommands(char *filename, const char *outfiles[countof(outputtypes)], vector &infiles, vector &hitboxes) @@ -5036,12 +5723,24 @@ int main(int argc, char **argv) const char *type = strrchr(argv[i], '.'); if (type && (!strcasecmp(type, ".cmd")||!strcasecmp(type, ".cfg")||!strcasecmp(type, ".txt")||!strcasecmp(type, ".qc"))) //.qc to humour halflife fanboys parsecommands(argv[i], outfiles, infiles, hitboxes); - else if(!outfiles[0] && !outfiles[1] && !outfiles[2]) - outfiles[0] = argv[i]; //first arg is the output name, if its not an export script thingie. else { - infiles.add(inspec).file = argv[i]; - inspec.reset(); + size_t j; + for (j = 0; j < countof(outfiles); j++) + if (outfiles[j]) + break; + if(j == countof(outfiles)) + { + for (j = countof(outfiles); j --> 0; ) + if (type && !strcasecmp(type, outputtypes[j].extname)) + break; + outfiles[j] = argv[i]; //first arg is the output name, if its not an export script thingie. + } + else + { + infiles.add(inspec).file = argv[i]; + inspec.reset(); + } } } } @@ -5049,7 +5748,17 @@ int main(int argc, char **argv) size_t n; for (n = 0; n < countof(outputtypes) && !outfiles[n]; n++); if(n == countof(outfiles)) fatal("no output file specified"); - if(infiles.empty()) fatal("no input files specified"); + if(infiles.empty()) + { + if (outfiles[0]) + { + inspec.reset(); + infiles.add(inspec).file = outfiles[0]; + outfiles[0] = NULL; + } + else + fatal("no input files specified"); + } if(gscale != 1) printf("scale: %f\n", escale); if(gmeshtrans != Vec3(0, 0, 0)) printf("mesh translate: %f, %f, %f\n", gmeshtrans.x, gmeshtrans.y, gmeshtrans.z); @@ -5085,6 +5794,14 @@ int main(int argc, char **argv) { if(!loadobj(infile, inspec)) fatal("failed reading: %s", infile); } + else if(!strcasecmp(type, ".glb")) + { + if(!fte::loadglb(infile, inspec)) fatal("failed reading: %s", infile); + } + else if(!strcasecmp(type, ".gltf")) + { + if(!fte::loadgltf(infile, inspec)) fatal("failed reading: %s", infile); + } else fatal("unknown file type: %s", type); } diff --git a/iqm/util.h b/iqm/util.h index a9196afb..08eca805 100644 --- a/iqm/util.h +++ b/iqm/util.h @@ -20,9 +20,13 @@ #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif +#ifndef strcasecmp #define strcasecmp _stricmp +#endif +#ifndef strncasecmp #define strncasecmp _strnicmp #endif +#endif typedef unsigned char uchar; typedef unsigned short ushort; @@ -30,7 +34,7 @@ typedef unsigned int uint; typedef signed long long int llong; typedef unsigned long long int ullong; -inline void *operator new(size_t size) +/*inline void *operator new(size_t size) { void *p = malloc(size); if(!p) abort(); @@ -44,7 +48,9 @@ inline void *operator new[](size_t size) } inline void operator delete(void *p) { if(p) free(p); } inline void operator delete[](void *p) { if(p) free(p); } - +inline void operator delete(void *p, size_t sz) { if(p) free(p); } +inline void operator delete[](void *p, size_t sz) { if(p) free(p); } +*/ inline void *operator new(size_t, void *p) { return p; } inline void *operator new[](size_t, void *p) { return p; } inline void operator delete(void *, void *) {} @@ -77,7 +83,9 @@ static inline T min(T a, T b) return a < b ? a : b; } +#ifndef countof #define countof(n) (sizeof(n)/sizeof(n[0])) +#endif #define clamp(a,b,c) (max(b, min(a, c))) #define loop(v,m) for(int v = 0; v +void VARGS Q_snprintfcat (char *dest, size_t size, const char *fmt, ...) +{ + va_list argptr; + + size_t skip = strlen(dest); + dest += skip; + size -= skip; + + va_start (argptr, fmt); + vsnprintf(dest, size, fmt, argptr); + va_end (argptr); +} + + typedef struct json_s { const char *bodystart; @@ -363,7 +391,7 @@ static json_t *JSON_Parse(json_t *t, const char *namestart, const char *nameend, continue; } break; - } + } JSON_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen && json[*jsonpos] == ']') @@ -475,12 +503,14 @@ static qintptr_t JSON_GetInteger(json_t *t, const char *child, int fallback) } return fallback; } +#ifndef SERVERONLY//ffs static qintptr_t JSON_GetIndexedInteger(json_t *t, unsigned int idx, int fallback) { char idxname[MAX_QPATH]; Q_snprintf(idxname, sizeof(idxname), "%u", idx); return JSON_GetInteger(t, idxname, fallback); } +#endif static double JSON_GetFloat(json_t *t, const char *child, double fallback) { if (child) @@ -556,7 +586,7 @@ static void JSON_FlagAsUsed(json_t *t, const char *child) } static void JSON_WarnIfChild(json_t *t, const char *child, int *warnlimit) { - t = JSON_FindChild(t, child); + t = JSON_FindChild(t, child); if (t) { char path[8192]; @@ -580,7 +610,7 @@ static unsigned int FromBase64(char c) return 62; if (c == '/') return 63; - return 64; + return 64; //'=' for no-more/padding. } //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) @@ -633,7 +663,7 @@ static void *JSON_MallocDataURI(json_t *t, size_t *outlen) break; *out++ = (c2<<4) | (c3>>2); c4 = FromBase64(*in++); - if (c3 >= 64) + if (c4 >= 64) break; *out++ = (c3<<6) | (c4>>0); } @@ -676,11 +706,13 @@ typedef struct gltf_s struct model_s *mod; unsigned int numsurfaces; json_t *r; + int ver; int *bonemap;//[MAX_BONES]; //remap skinned bones. I hate that we have to do this. struct gltfbone_s { char name[32]; + char jointname[32]; //gltf1 only int parent; int camera; double amatrix[16]; @@ -699,6 +731,55 @@ typedef struct gltf_s struct gltf_buffer buffers[64]; } gltf_t; +static void GLTF_FlagExtras(json_t *node) +{ + JSON_FindChild(node, "extensions"); //warn about child extensions, but not the extensions table itself. + JSON_FlagAsUsed(node, "extras"); //don't warn about application-specific extras +} + +static json_t *GLTF_FindJSONIDParent(struct gltf_s *gltf, json_t *parent, json_t *id, quintptr_t *idx) +{ + if (gltf->ver == 1) + { //gltf1 uses string-based names + char name[64]; + JSON_ReadBody(id, name, sizeof(name)); + id = parent; + if (idx) + *idx = 0; + if (id) + for (id = id->child; id; id = id->sibling) + { + if (!strcmp(id->name, name)) + { + id->used = true; + return id; + } + if (idx) + *idx += 1; + } + return NULL; + } + else + { //gltf2 uses array indexes. + quintptr_t num; + num = id?JSON_GetInteger(id, NULL, -1):-1; + if (idx) + *idx = num; + return JSON_FindIndexedChild(parent, NULL, num); + } +} +static json_t *GLTF_FindJSONID(struct gltf_s *gltf, const char *restype, json_t *id, quintptr_t *idx) +{ //if there's no scene info, treat it as "-1" + return GLTF_FindJSONIDParent(gltf, JSON_FindChild(gltf->r, restype), id, idx); +} +static json_t *GLTF_FindJSONID_First(struct gltf_s *gltf, const char *restype, json_t *id, quintptr_t *idx) +{ //if there's no scene info, treat it as "0" + + if (!id && gltf->ver > 1) + return JSON_FindIndexedChild(gltf->r, restype, 0); + return GLTF_FindJSONIDParent(gltf, JSON_FindChild(gltf->r, restype), id, idx); +} + static void GLTF_RelativePath(const char *base, const char *relative, char *out, size_t outsize) { size_t t; @@ -755,18 +836,30 @@ static void GLTF_RelativePath(const char *base, const char *relative, char *out, *out = 0; } -static struct gltf_buffer *GLTF_GetBufferData(gltf_t *gltf, int bufferidx) +static struct gltf_buffer *GLTF_GetBufferData(gltf_t *gltf, json_t *bufferid) { - json_t *b = JSON_FindIndexedChild(gltf->r, "buffers", bufferidx); + quintptr_t bufferidx; + json_t *b = GLTF_FindJSONID(gltf, "buffers", bufferid, &bufferidx); json_t *uri = JSON_FindChild(b, "uri"); size_t length = JSON_GetUInteger(b, "byteLength", 0); struct gltf_buffer *out; -// JSON_WarnIfChild(b, "name"); -// JSON_WarnIfChild(b, "extensions"); -// JSON_WarnIfChild(b, "extras"); + if (gltf->ver <= 1) + { + char body[64]; + JSON_ReadBody(bufferid, body, sizeof(body)); + if (!strcmp(body, "binary_glTF") && !gltf->buffers[0].malloced && gltf->buffers[0].data) + return &gltf->buffers[0]; + else + bufferidx++; - if (bufferidx < 0 || bufferidx >= countof(gltf->buffers)) + JSON_FlagAsUsed(b, "type"); //default: "arraybuffer"... yeah, not relevant to anything. + } + JSON_FlagAsUsed(b, "name"); + GLTF_FlagExtras(b); + + + if (bufferidx >= countof(gltf->buffers)) return NULL; out = &gltf->buffers[bufferidx]; @@ -809,28 +902,27 @@ struct gltf_bufferview size_t length; int bytestride; }; -static qboolean GLTF_GetBufferViewData(gltf_t *gltf, int bufferview, struct gltf_bufferview *view) +static qboolean GLTF_GetBufferViewData(gltf_t *gltf, json_t *bufferviewid, struct gltf_bufferview *view) { struct gltf_buffer *buf; - json_t *bv = JSON_FindIndexedChild(gltf->r, "bufferViews", bufferview); + json_t *bv = GLTF_FindJSONID(gltf, "bufferViews", bufferviewid, NULL); size_t offset; if (!bv) return false; - buf = GLTF_GetBufferData(gltf, JSON_GetInteger(bv, "buffer", 0)); + buf = GLTF_GetBufferData(gltf, JSON_FindChild(bv, "buffer")); if (!buf) return false; offset = JSON_GetUInteger(bv, "byteOffset", 0); view->data = (char*)buf->data + offset; view->length = JSON_GetUInteger(bv, "byteLength", 0); //required - view->bytestride = JSON_GetInteger(bv, "byteStride", 0); + view->bytestride = (gltf->ver<=1)?0:JSON_GetInteger(bv, "byteStride", 0); if (offset + view->length > buf->length) return false; JSON_FlagAsUsed(bv, "target"); //required, but not useful for us. JSON_FlagAsUsed(bv, "name"); -// JSON_WarnIfChild(bv, "extensions"); -// JSON_WarnIfChild(bv, "extras"); + GLTF_FlagExtras(bv); return true; } //accessors are basically VAs blocks that refer inside a bufferview/VBO. @@ -842,13 +934,13 @@ struct gltf_accessor int componentType; //5120 BYTE, 5121 UNSIGNED_BYTE, 5122 SHORT, 5123 UNSIGNED_SHORT, 5125 UNSIGNED_INT, 5126 FLOAT qboolean normalized; - int count; + size_t count; int type; //1,2,3,4 says component count, 256|(4,9,16) for square matricies... double mins[16]; double maxs[16]; }; -static qboolean GLTF_GetAccessor(gltf_t *gltf, int accessorid, struct gltf_accessor *out) +static qboolean GLTF_GetAccessor(gltf_t *gltf, json_t *accessorid, struct gltf_accessor *out) { struct gltf_bufferview bv; json_t *a, *mins, *maxs; @@ -856,17 +948,22 @@ static qboolean GLTF_GetAccessor(gltf_t *gltf, int accessorid, struct gltf_acces int j; memset(out, 0, sizeof(*out)); - a = JSON_FindIndexedChild(gltf->r, "accessors", accessorid); + a = GLTF_FindJSONID(gltf, "accessors", accessorid, NULL); if (!a) return false; - if (!GLTF_GetBufferViewData(gltf, JSON_GetInteger(a, "bufferView", 0), &bv)) + JSON_FlagAsUsed(a, "name"); + + if (!GLTF_GetBufferViewData(gltf, JSON_FindChild(a, "bufferView"), &bv)) return false; offset = JSON_GetUInteger(a, "byteOffset", 0); if (offset > bv.length) return false; out->length = bv.length - offset; - out->bytestride = bv.bytestride; + if (gltf->ver <= 1) + out->bytestride = JSON_GetInteger(a, "byteStride", 0); + else + out->bytestride = bv.bytestride; out->componentType = JSON_GetInteger(a, "componentType", 0); out->normalized = JSON_GetInteger(a, "normalized", false); out->count = JSON_GetInteger(a, "count", 0); @@ -924,24 +1021,21 @@ static qboolean GLTF_GetAccessor(gltf_t *gltf, int accessorid, struct gltf_acces // JSON_WarnIfChild(a, "sparse"); // JSON_WarnIfChild(a, "name"); -// JSON_WarnIfChild(a, "extensions"); -// JSON_WarnIfChild(a, "extras"); + GLTF_FlagExtras(a); out->data = (char*)bv.data + offset; return true; } -static void GLTF_AccessorToTangents(gltf_t *gltf, vec3_t *norm, vec3_t **sdir, vec3_t **tdir, size_t outverts, struct gltf_accessor *a) +static void GLTF_AccessorToTangents(gltf_t *gltf, const vec3_t *norm, size_t outverts, const struct gltf_accessor *a, vec3_t *sdir, vec3_t *tdir) { //input MUST be a single float4 //output is two vec3s. wasteful perhaps. - vec3_t *os = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*os) * 3 * outverts); - vec3_t *ot = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ot) * 3 * outverts); + vec3_t *os = sdir; + vec3_t *ot = tdir; char *in = a->data; size_t v, c; float side; - *sdir = os; - *tdir = ot; if ((a->type&0xff) != 4) return; switch(a->componentType) @@ -1004,14 +1098,16 @@ static void GLTF_AccessorToTangents(gltf_t *gltf, vec3_t *norm, vec3_t **sdir, v } } -static void *GLTF_AccessorToDataF(gltf_t *gltf, size_t outverts, unsigned int outcomponents, struct gltf_accessor *a) +static void *GLTF_AccessorToDataF(gltf_t *gltf, size_t outverts, unsigned int outcomponents, const struct gltf_accessor *a, void *out) { - float *ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts), *o; + float *ret = out, *o; char *in = a->data; - int c, ic = a->type&0xff; + size_t c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; + if (!ret) + ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts); o = ret; switch(a->componentType) { @@ -1170,7 +1266,7 @@ static void *GLTF_AccessorToDataUB(gltf_t *gltf, size_t outverts, unsigned int o unsigned char *ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts), *o; char *in = a->data; - int c, ic = a->type&0xff; + size_t c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; o = ret; @@ -1218,7 +1314,7 @@ static void *GLTF_AccessorToDataBone(gltf_t *gltf, size_t outverts, struct gltf_ char *in = a->data; - int c, ic = a->type&0xff; + size_t c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; o = ret; @@ -1241,6 +1337,8 @@ static void *GLTF_AccessorToDataBone(gltf_t *gltf, size_t outverts, struct gltf_ for (c = 0; c < ic; c++) { v = ((unsigned char*)in)[c]; + if ((unsigned int)v >= MAX_BONES) + v = 0; o[c] = gltf->bonemap[v]; } for (; c < outcomponents; c++) @@ -1257,7 +1355,7 @@ static void *GLTF_AccessorToDataBone(gltf_t *gltf, size_t outverts, struct gltf_ for (c = 0; c < ic; c++) { v = ((unsigned short*)in)[c]; - if (v > MAX_BONES) + if (v >= MAX_BONES) v = 0; o[c] = gltf->bonemap[v]; } @@ -1268,42 +1366,66 @@ static void *GLTF_AccessorToDataBone(gltf_t *gltf, size_t outverts, struct gltf_ } break; //the spec doesn't require these. -// case 5125: //UNSIGNED_INT -/* case 5126: //FLOAT + case 5125: //UNSIGNED_INT while(outverts --> 0) { + unsigned int v; for (c = 0; c < ic; c++) - o[c] = ((float*)in)[c]; + { + v = ((unsigned short*)in)[c]; + if (v >= MAX_BONES) + v = 0; + o[c] = gltf->bonemap[v]; + } for (; c < outcomponents; c++) - o[c] = 0; + o[c] = gltf->bonemap[0]; o += outcomponents; in += a->bytestride; } - break;*/ + break; + case 5126: //FLOAT. for bone indexes. wtf? + while(outverts --> 0) + { + unsigned int v; + for (c = 0; c < ic; c++) + { + v = ((float*)in)[c]; + if (v >= MAX_BONES) + v = 0; + o[c] = gltf->bonemap[v]; + } + for (; c < outcomponents; c++) + o[c] = gltf->bonemap[0]; + o += outcomponents; + in += a->bytestride; + } + break; } return ret; } -void TransformArrayD(vecV_t *data, size_t vcount, double matrix[]) +static void TransformPosArray(vecV_t *data, size_t vcount, double matrix[]) { while (vcount --> 0) { vec3_t t; VectorCopy((*data), t); - (*data)[0] = DotProduct(t, (matrix+0)) + matrix[0+3]; - (*data)[1] = DotProduct(t, (matrix+4)) + matrix[4+3]; - (*data)[2] = DotProduct(t, (matrix+8)) + matrix[8+3]; + + (*data)[0] = matrix[0]*t[0] + matrix[4]*t[1] + matrix[8]*t[2] + matrix[12]; + (*data)[1] = matrix[1]*t[0] + matrix[5]*t[1] + matrix[9]*t[2] + matrix[13]; + (*data)[2] = matrix[2]*t[0] + matrix[6]*t[1] + matrix[10]*t[2] + matrix[14]; + //1 = matrix[3]*t[0] + matrix[7]*t[1] + matrix[11]*t[2] + matrix[14]; //hopefully... data++; } } -void TransformArrayA(vec3_t *data, size_t vcount, double matrix[]) +static void TransformDirArray(vec3_t *data, size_t vcount, double matrix[]) { vec3_t t; float mag; while (vcount --> 0) { - t[0] = DotProduct((*data), (matrix+0)); - t[1] = DotProduct((*data), (matrix+4)); - t[2] = DotProduct((*data), (matrix+8)); + t[0] = matrix[0]*(*data)[0] + matrix[4]*(*data)[1] + matrix[8]*(*data)[2]; + t[1] = matrix[1]*(*data)[0] + matrix[5]*(*data)[1] + matrix[9]*(*data)[2]; + t[2] = matrix[2]*(*data)[0] + matrix[6]*(*data)[1] + matrix[10]*(*data)[2]; //scaling is bad for axis. mag = DotProduct(t,t); @@ -1318,19 +1440,34 @@ void TransformArrayA(vec3_t *data, size_t vcount, double matrix[]) } } #ifndef SERVERONLY -static texid_t GLTF_LoadImage(gltf_t *gltf, int imageidx, unsigned int flags) +static texid_t GLTF_LoadImage(gltf_t *gltf, json_t *imageid, unsigned int flags) { size_t size; texid_t ret = r_nulltex; - json_t *image = JSON_FindIndexedChild(gltf->r, "images", imageidx); - json_t *uri = JSON_FindChild(image, "uri"); - json_t *mimeType = JSON_FindChild(image, "mimeType"); - int bufferView = JSON_GetInteger(image, "bufferView", -1); + json_t *image = GLTF_FindJSONID(gltf, "images", imageid, NULL); + json_t *uri = JSON_FindChild(image, "uri"); + json_t *mimeType = JSON_FindChild(image, "mimeType"); + json_t *bufferViewid = JSON_FindChild(image, "bufferView"); char uritext[MAX_QPATH]; char filename[MAX_QPATH]; void *mem; struct gltf_bufferview view; + JSON_FlagAsUsed(image, "name"); + + if (gltf->ver <= 1) + { + json_t *binary_glTF = JSON_FindChild(image, "extensions.KHR_binary_glTF"); + if (binary_glTF) + { + bufferViewid = JSON_FindChild(binary_glTF, "bufferView"); + mimeType = JSON_FindChild(binary_glTF, "mimeType"); + JSON_FlagAsUsed(binary_glTF, "width"); + JSON_FlagAsUsed(binary_glTF, "height"); + uri = NULL; + } + } + //potentially valid mime types: //image/png //image/vnd-ms.dds (MSFT_texture_dds) @@ -1353,9 +1490,9 @@ static texid_t GLTF_LoadImage(gltf_t *gltf, int imageidx, unsigned int flags) ret = modfuncs->GetTexture(filename, NULL, flags, NULL, NULL, 0, 0, TF_INVALID); } } - else if (bufferView >= 0) + else if (bufferViewid) { - if (GLTF_GetBufferViewData(gltf, bufferView, &view)) + if (GLTF_GetBufferViewData(gltf, bufferViewid, &view)) { JSON_GetPath(image, false, uritext, sizeof(uritext)); ret = modfuncs->GetTexture(uritext, NULL, flags, view.data, NULL, view.length, 0, TF_INVALID); @@ -1364,16 +1501,16 @@ static texid_t GLTF_LoadImage(gltf_t *gltf, int imageidx, unsigned int flags) return ret; } -static texid_t GLTF_LoadTexture(gltf_t *gltf, int texture, unsigned int flags) +static texid_t GLTF_LoadTexture(gltf_t *gltf, json_t *textureid, unsigned int flags) { - json_t *tex = JSON_FindIndexedChild(gltf->r, "textures", texture); - json_t *sampler = JSON_FindIndexedChild(gltf->r, "samplers", JSON_GetInteger(tex, "sampler", -1)); + json_t *tex = GLTF_FindJSONID(gltf, "textures", textureid, NULL); + json_t *sampler = GLTF_FindJSONID(gltf, "samplers", JSON_FindChild(tex, "sampler"), NULL); int magFilter = JSON_GetInteger(sampler, "magFilter", 0); int minFilter = JSON_GetInteger(sampler, "minFilter", 0); int wrapS = JSON_GetInteger(sampler, "wrapS", 10497); int wrapT = JSON_GetInteger(sampler, "wrapT", 10497); - int source; + json_t *sourceid; JSON_FlagAsUsed(sampler, "name"); JSON_FlagAsUsed(sampler, "extensions"); @@ -1436,18 +1573,398 @@ static texid_t GLTF_LoadTexture(gltf_t *gltf, int texture, unsigned int flags) flags |= IF_NOREPLACE; - source = JSON_GetInteger(tex, "source", -1); - source = JSON_GetInteger(tex, "extensions.MSFT_texture_dds.source", source); //load a dds instead, if one is available. - return GLTF_LoadImage(gltf, source, flags); + sourceid = JSON_FindChild(tex, "extensions.MSFT_texture_dds.source"); //load a dds instead, if one is available. + if (!sourceid) + sourceid = JSON_FindChild(tex, "source"); //fall back on the normal source + return GLTF_LoadImage(gltf, sourceid, flags); } -static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vertexcolours) +static char *GLTF1_LoadShader(gltf_t *gltf, json_t *shaderid) +{ //reads a vertex or fragment shader blob + json_t *shader = GLTF_FindJSONID(gltf, "shaders", shaderid, NULL); + json_t *uri = JSON_FindChild(shader, "uri"); + char *out = NULL; + json_t *bufferviewid = JSON_FindChild(shader, "extensions.KHR_binary_glTF.bufferView"); + struct gltf_bufferview view; + if (bufferviewid && GLTF_GetBufferViewData(gltf, bufferviewid, &view) && view.data && view.length) + { + out = malloc(view.length+1); + memcpy(out, view.data, view.length); + out[view.length] = 0; + } + else + { + JSON_FlagAsUsed(shader, "type"); //don't care + + if (uri) + { + size_t length; + out = JSON_MallocDataURI(uri, &length); //try and decode data schemes... + if (!out) + { + //read a file from disk. + vfsfile_t *f; + char uritext[MAX_QPATH]; + char filename[MAX_QPATH]; + JSON_ReadBody(uri, uritext, sizeof(uritext)); + GLTF_RelativePath(gltf->mod->name, uritext, filename, sizeof(filename)); + f = filefuncs->OpenVFS(filename, "rb", FS_GAME); + if (f) + { + length = VFS_GETLEN(f); + length = min(length, length); + out = malloc(length+1); + out[length] = 0; + VFS_READ(f, out, length); + VFS_CLOSE(f); + } + else + Con_Printf(CON_WARNING"%s: Unable to read buffer file %s\n", gltf->mod->name, filename); + } + } + } + + //if it starts with a precision modifier then just strip that out... gl doesn't like gles's precision modifiers and we tend to try to provide our own too, which doesn't help things. + if (out && !strncmp(out, "precision ", 10)) + { + char *le = strchr(out, '\n'); + if (le++) + memmove(out, le, strlen(le)+1); + } + return out; +} +static qboolean GLTF1_LoadMaterial(gltf_t *gltf, json_t *mat, texnums_t *texnums, char *shadertext, size_t shadertextsize) +{ + json_t *technique = GLTF_FindJSONID(gltf, "techniques", JSON_FindChild(mat, "technique"), NULL); + json_t *parameters = JSON_FindChild(technique, "parameters"); + json_t *values = JSON_FindChild(mat, "values"); + json_t *common = JSON_FindChild(mat, "extensions.KHR_materials_common"); + json_t *v; + json_t *p; + char header[8192]; + char samplers[8192]; + char attributes[8192]; + char uniforms[8192]; + char *vertshader = NULL; + char *fragshader = NULL; + int type; + char semantic[64]; + int sampidx = 0; + texid_t tex; + + if (common) + { + json_t *values = JSON_FindChild(common, "values"); +// char techniquebuf[64]; +// const char *technique = JSON_GetString(common, "technique", techniquebuf, sizeof(techniquebuf), ""); + qboolean doubleSided = JSON_GetInteger(common, "doubleSided", false); +// qboolean transparent = JSON_GetInteger(common, "transparent", false); +// vec4_t ambient; + json_t *diffusename = JSON_FindChild(values, "diffuse"); + vec4_t diffusetint = {1,1,1,1}; + json_t *emissionname = JSON_FindChild(values, "emission"); + vec4_t emissiontint = {1,1,1,1}; + json_t *specularname = JSON_FindChild(values, "emission"); + vec4_t speculartint = {1,1,1,1}; + float shininess = JSON_GetFloat(values, "shininess", 0); + + if (emissionname) + { + if (!emissionname->child) //a string, so a texture id + texnums->fullbright = GLTF_LoadTexture(gltf, emissionname, IF_NOALPHA); + else + { //child nodes means its a vec4. + emissiontint[0] = JSON_GetIndexedFloat(emissionname, 0, 0); + emissiontint[1] = JSON_GetIndexedFloat(emissionname, 1, 0); + emissiontint[2] = JSON_GetIndexedFloat(emissionname, 2, 0); + emissiontint[3] = JSON_GetIndexedFloat(emissionname, 3, 1); + + if (emissiontint[0] || emissiontint[1] || emissiontint[2]) + texnums->fullbright = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); + } + } + if (diffusename) + { + if (!diffusename->child) //a string, so a texture id + texnums->base = GLTF_LoadTexture(gltf, diffusename, 0); + else + { //child nodes means its a vec4. + texnums->base = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); + diffusetint[0] = JSON_GetIndexedFloat(diffusename, 0, 0); + diffusetint[1] = JSON_GetIndexedFloat(diffusename, 1, 0); + diffusetint[2] = JSON_GetIndexedFloat(diffusename, 2, 0); + diffusetint[3] = JSON_GetIndexedFloat(diffusename, 3, 1); + } + } + if (specularname) + { + if (!specularname->child) //a string, so a texture id + texnums->specular = GLTF_LoadTexture(gltf, specularname, IF_NOALPHA); + else + { //child nodes means its a vec4. + texnums->specular = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); + speculartint[0] = JSON_GetIndexedFloat(specularname, 0, 0); + speculartint[1] = JSON_GetIndexedFloat(specularname, 1, 0); + speculartint[2] = JSON_GetIndexedFloat(specularname, 2, 0); + speculartint[3] = JSON_GetIndexedFloat(specularname, 3, 1); + } + } + + Q_snprintf(shadertext, shadertextsize, + "{\n" + "%s"//cull + "program defaultskin#VC%s#FTE_SPECULAR_EXPONENT=%f\n" + "{\n" + "map $diffuse\n" + "%s" //blend + "%s" //rgbgen + "}\n" + "fte_basefactor %f %f %f %f\n" + "fte_specularfactor %f %f %f %f\n" + "fte_fullbrightfactor %f %f %f %f\n" + "}\n", + doubleSided?"cull disable\n":"", + "",//alphaCutoffmodifier, + shininess, + "",//(alphaMode==1)?"":(alphaMode==2)?"blendfunc blend\n":"", + "",//vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", + diffusetint[0],diffusetint[1],diffusetint[2],diffusetint[3], + speculartint[0],speculartint[1],speculartint[2],speculartint[3], + emissiontint[0],emissiontint[1],emissiontint[2],emissiontint[3]); + return true; + } + + //mat->values.diffuse tends to be quite common. make an executive descision... + texnums->base = GLTF_LoadTexture(gltf, JSON_FindChild(values, "diffuse"), 0); + if (mod_gltf_ignoretechniques->ival) + { //mat->values.diffuse tends to be quite common + return false; + } + if (!technique) + { + //missing technique is supposed to result in a greyscale model + Q_snprintf(shadertext, shadertextsize, + "{\n" + "program defaultskin\n" + "diffusemap $whiteimage\n" + "{\n" + "map $diffuse\n" + "rgbgen const 0.5 0.5 0.5\n" + "alphagen const 1.0\n" + "}\n" + "}\n" + ); + return true; + } + + *header = 0; + *samplers = 0; + *attributes = 0; + *uniforms = 0; + + //this is supposed to be glessl 100. lets try to do our best to get something compatible. + Q_snprintfcat(header, sizeof(header), "!!ver 100 120\n"); + //and reduce conflicts with fte's normal symbols. + Q_snprintfcat(header, sizeof(header), "!!explicit\n"); + + v = JSON_FindChild(technique, "uniforms"); + if (v) + for (v = v->child; v; v = v->sibling) + { + enum { + GLTF_BYTE = 5120, + GLTF_UNSIGNED_BYTE = 5121, + GLTF_SHORT = 5122, + GLTF_UNSIGNED_SHORT = 5123, + GLTF_INT = 5124, + GLTF_UNSIGNED_INT = 5125, + GLTF_FLOAT = 5126, + GLTF_FLOAT_VEC2 = 35664, + GLTF_FLOAT_VEC3 = 35665, + GLTF_FLOAT_VEC4 = 35666, + GLTF_INT_VEC2 = 35667, + GLTF_INT_VEC3 = 35668, + GLTF_INT_VEC4 = 35669, + GLTF_BOOL = 35670, + GLTF_BOOL_VEC2 = 35671, + GLTF_BOOL_VEC3 = 35672, + GLTF_BOOL_VEC4 = 35673, + GLTF_FLOAT_MAT2 = 35674, + GLTF_FLOAT_MAT3 = 35675, + GLTF_FLOAT_MAT4 = 35676, + GLTF_SAMPLER_2D = 35678, + }; + v->used = true; + p = GLTF_FindJSONIDParent(gltf, parameters, v, NULL); + + if (1 != JSON_GetInteger(p, "count", 1)) + { + if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"%s: Unsupported parameter->count for uniform %s\n", gltf->mod->name, v->name); + return false; + } + if (JSON_GetString(p, "node", semantic, sizeof(semantic), NULL)) + { + if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"%s: Unsupported parameter->node for uniform %s\n", gltf->mod->name, v->name); + return false; + } + + if (!JSON_GetString(p, "semantic", semantic, sizeof(semantic), NULL)) + *semantic = 0; + type = JSON_GetInteger(p, "type", 0); + + if (!strcasecmp(semantic, "MODEL") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_model\n", v->name); + else if (!strcasecmp(semantic, "VIEW") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_view\n", v->name); + else if (!strcasecmp(semantic, "PROJECTION") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_projection\n", v->name); + else if (!strcasecmp(semantic, "MODELVIEW") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_modelview\n", v->name); + else if (!strcasecmp(semantic, "MODELVIEWPROJECTION") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_modelviewprojection\n", v->name); + else if (!strcasecmp(semantic, "MODELINVERSE") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invmodel\n", v->name); + else if (!strcasecmp(semantic, "VIEWINVERSE") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invviewprojection\n", v->name); + else if (!strcasecmp(semantic, "PROJECTIONINVERSE") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invprojection\n", v->name); + else if (!strcasecmp(semantic, "MODELVIEWINVERSE") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invmodelview\n", v->name); + else if (!strcasecmp(semantic, "MODELVIEWPROJECTIONINVERSE") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invmodelviewprojection\n", v->name); + else if (!strcasecmp(semantic, "MODELINVERSETRANSPOSE") && type == GLTF_FLOAT_MAT3) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invmodeltranspose\n", v->name); + else if (!strcasecmp(semantic, "MODELVIEWINVERSETRANSPOSE") && type == GLTF_FLOAT_MAT3) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_invmodelviewtranspose\n", v->name); +// else if (!strcasecmp(semantic, "VIEWPORT") && type == GLTF_FLOAT_VEC4) +// Q_snprintfcat(header, sizeof(header), "!!semantic %s UNSUPPORTED\n", v->name); + else if (!strcasecmp(semantic, "JOINTMATRIX") && type == GLTF_FLOAT_MAT4) + Q_snprintfcat(header, sizeof(header), "!!semantic %s m_bones_mat4\n", v->name); +// else if (!strcasecmp(semantic, "LOCAL") && type == GLTF_FLOAT_MAT4) +// Q_snprintfcat(header, sizeof(header), "!!semantic %s UNSUPPORTED\n", v->name); + else if (!strcasecmp(semantic, "")) + { + json_t *val = GLTF_FindJSONIDParent(gltf, values, v, NULL); + switch(type) + { + case GLTF_SAMPLER_2D: + Q_snprintfcat(header, sizeof(header), "!!constt %s %i\n", v->name, sampidx++); + tex = GLTF_LoadTexture(gltf, val, 0); + Q_snprintfcat(samplers, sizeof(samplers), "{\nmap %s\n}\n", tex?tex->ident:"$whiteimage"); + break; + + case GLTF_FLOAT: Q_snprintfcat(header, sizeof(header), "!!const1f %s %f\n", v->name, JSON_GetFloat(val, NULL, 0)); break; + case GLTF_FLOAT_VEC2: Q_snprintfcat(header, sizeof(header), "!!const2f %s %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0)); break; + case GLTF_FLOAT_VEC3: Q_snprintfcat(header, sizeof(header), "!!const3f %s %f %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0), JSON_GetIndexedFloat(val, 2, 0)); break; + case GLTF_FLOAT_VEC4: Q_snprintfcat(header, sizeof(header), "!!const4f %s %f %f %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0), JSON_GetIndexedFloat(val, 2, 0), JSON_GetIndexedFloat(val, 3, 0)); break; + + case GLTF_BOOL: //glsl's bool/bvecN types can be ininitialised as floats or ints. lets just use ints here. + case GLTF_BYTE: //FIXME: it is not specified whether these are meant to be normalized or not. are they always float/vecN or int/ivecN? the spec doesn't say. + case GLTF_SHORT: + case GLTF_INT: Q_snprintfcat(header, sizeof(header), "!!const1i %s %i\n", v->name, JSON_GetInteger(val, NULL, 0)); break; + case GLTF_BOOL_VEC2: + case GLTF_INT_VEC2: Q_snprintfcat(header, sizeof(header), "!!const2i %s %i %i\n", v->name, JSON_GetIndexedInteger(val, 0, 0), JSON_GetIndexedInteger(val, 1, 0)); break; + case GLTF_BOOL_VEC3: + case GLTF_INT_VEC3: Q_snprintfcat(header, sizeof(header), "!!const3i %s %i %i %i\n", v->name, JSON_GetIndexedInteger(val, 0, 0), JSON_GetIndexedInteger(val, 1, 0), JSON_GetIndexedInteger(val, 2, 0)); break; + case GLTF_BOOL_VEC4: + case GLTF_INT_VEC4: Q_snprintfcat(header, sizeof(header), "!!const4i %s %i %i %i %i\n", v->name, JSON_GetIndexedInteger(val, 0, 0), JSON_GetIndexedInteger(val, 1, 0), JSON_GetIndexedInteger(val, 2, 0), JSON_GetIndexedInteger(val, 3, 0)); break; + + case GLTF_UNSIGNED_BYTE://FIXME: it is not specified whether these are meant to be normalized or not. are they always float/vecN or int/ivecN? the spec doesn't say. + case GLTF_UNSIGNED_SHORT: + case GLTF_UNSIGNED_INT: Q_snprintfcat(header, sizeof(header), "!!const1u %s %f\n", v->name, JSON_GetFloat(val, NULL, 0)); break; + //curiously no uvecs listed by the spec + + case GLTF_FLOAT_MAT2: Q_snprintfcat(header, sizeof(header), "!!const2m %s %f %f %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0), JSON_GetIndexedFloat(val, 2, 0), JSON_GetIndexedFloat(val, 3, 0)); break; + case GLTF_FLOAT_MAT3: Q_snprintfcat(header, sizeof(header), "!!const3m %s %f %f %f %f %f %f %f %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0), JSON_GetIndexedFloat(val, 2, 0), JSON_GetIndexedFloat(val, 3, 0), JSON_GetIndexedFloat(val, 4, 0), JSON_GetIndexedFloat(val, 5, 0), JSON_GetIndexedFloat(val, 6, 0), JSON_GetIndexedFloat(val, 7, 0), JSON_GetIndexedFloat(val, 8, 0)); break; + case GLTF_FLOAT_MAT4: Q_snprintfcat(header, sizeof(header), "!!const4m %s %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n", v->name, JSON_GetIndexedFloat(val, 0, 0), JSON_GetIndexedFloat(val, 1, 0), JSON_GetIndexedFloat(val, 2, 0), JSON_GetIndexedFloat(val, 3, 0), JSON_GetIndexedFloat(val, 4, 0), JSON_GetIndexedFloat(val, 5, 0), JSON_GetIndexedFloat(val, 6, 0), JSON_GetIndexedFloat(val, 7, 0), JSON_GetIndexedFloat(val, 8, 0), JSON_GetIndexedFloat(val, 9, 0), JSON_GetIndexedFloat(val, 10, 0), JSON_GetIndexedFloat(val, 11, 0), JSON_GetIndexedFloat(val, 12, 0), JSON_GetIndexedFloat(val, 13, 0), JSON_GetIndexedFloat(val, 14, 0), JSON_GetIndexedFloat(val, 15, 0)); break; + + default: + if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"%s: Unsupported constant uniform type %i for uniform %s\n", gltf->mod->name, type, v->name); + return false; + } + } + else + { + if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"%s: Unknown/Unsupported semantic %s for uniform %s\n", gltf->mod->name, semantic, v->name); + return false; + } + } + v = JSON_FindChild(technique, "attributes"); + if (v) + for (v = v->child; v; v = v->sibling) + { + v->used = true; + p = GLTF_FindJSONIDParent(gltf, parameters, v, NULL); + if (!JSON_GetString(p, "semantic", semantic, sizeof(semantic), NULL)) + *semantic = 0; + type = JSON_GetInteger(p, "type", 0); + + if (!strcasecmp(semantic, "POSITION")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_position\n", v->name); + else if (!strcasecmp(semantic, "NORMAL")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_normal\n", v->name); + else if (!strcasecmp(semantic, "TEXCOORD_0")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_texcoord\n", v->name); + else if (!strcasecmp(semantic, "TEXCOORD_1")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_lmcoord\n", v->name); + else if (!strcasecmp(semantic, "JOINT")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_bone\n", v->name); + else if (!strcasecmp(semantic, "WEIGHT")) + Q_snprintfcat(attributes, sizeof(attributes), "#define %s fte_v_weight\n", v->name); + else + { + if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"%s: Unknown semantic %s for attribute %s\n", gltf->mod->name, semantic, v->name); + return false; + } + } + + p = GLTF_FindJSONID(gltf, "programs", JSON_FindChild(technique, "program"), NULL); + vertshader = GLTF1_LoadShader(gltf, JSON_FindChild(p, "vertexShader")); + fragshader = GLTF1_LoadShader(gltf, JSON_FindChild(p, "fragmentShader")); + + Q_snprintf(shadertext, shadertextsize, + "{\n" + "surfaceparm nodlight\n" //o.O + "surfaceparm noshadows\n" //no surprises please. + "glslprogram\n" + "{\n" + "%s" //header + "%s" //uniformmaps + "#ifdef VERTEX_SHADER\n" + "%s" //attributemaps + "%s\n" //vertexshader + "#endif\n" + "#ifdef FRAGMENT_SHADER\n" + "%s\n" //fragmentshader + "#endif\n" + "}\n" + "%s" //{map foo} {map}... + "}\n", + header, + uniforms, + attributes, + vertshader, + fragshader, + samplers + ); + free(vertshader); + free(fragshader); + return true; +} + +static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, json_t *materialid, qboolean vertexcolours) { qboolean doubleSided; int alphaMode; double alphaCutoff; - char shader[8192]; + char shader[65536]; char alphaCutoffmodifier[128]; - json_t *mat = JSON_FindIndexedChild(gltf->r, "materials", material); + quintptr_t materialidx; + json_t *mat = GLTF_FindJSONID(gltf, "materials", materialid, &materialidx); galiasskin_t *ret; char tmp[64]; const char *t; @@ -1481,23 +1998,49 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert ret->skinspeed = 0.1; ret->frame = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret->frame)); - if (nam) - JSON_ReadBody(nam, ret->frame->shadername, sizeof(ret->frame->shadername)); - else if (mat) - JSON_GetPath(mat, false, ret->frame->shadername, sizeof(ret->frame->shadername)); - else if (material == -1) //explicit invalid material - Q_snprintf(ret->frame->shadername, sizeof(ret->frame->shadername), "%s", gltf->mod->name); - else - Q_snprintf(ret->frame->shadername, sizeof(ret->frame->shadername), "%.100s/%i", gltf->mod->name, material); + { + int skip; + if (nam) + JSON_ReadBody(nam, shader, sizeof(shader)); + else if (mat && *mat->name) + Q_snprintf(shader, sizeof(shader), "%s", mat->name); + else if (!mat) //explicit invalid material + Q_snprintf(shader, sizeof(shader), ""); + else + Q_snprintf(shader, sizeof(shader), "%i", (int)materialidx); + skip = sizeof(ret->frame->shadername)-32 - strlen(shader); + if (skip > 0) + skip = 0; + if (mod_gltf_privatematerials->ival && !strchr(shader, '/')) + { + Q_snprintf(ret->frame->shadername, sizeof(ret->frame->shadername), "%s", gltf->mod->name-skip); + Q_strncatz(ret->frame->shadername, "/", sizeof(ret->frame->shadername)); + } + else + *ret->frame->shadername = 0; + + Q_strncatz(ret->frame->shadername, shader, sizeof(ret->frame->shadername)); + } if (alphaMode == 1) Q_snprintf(alphaCutoffmodifier, sizeof(alphaCutoffmodifier), "#ALPHATEST=>%f", alphaCutoff); else *alphaCutoffmodifier = 0; - if (unlit) + if (gltf->ver <= 1) + { //fixme: break + if (!GLTF1_LoadMaterial(gltf, mat, &ret->frame->texnums, shader, sizeof(shader))) + { //some lame placeholder/fallback. + Q_snprintf(shader, sizeof(shader), + "{\n" + "program defaultskin\n" + "}\n" + ); + } + } + else if (unlit) { //if this extension was present, then we don't get ANY lighting info. - int albedo = JSON_GetInteger(pbrmr, "baseColorTexture.index", -1); //.rgba + json_t *albedo = JSON_FindChild(pbrmr, "baseColorTexture.index"); //.rgba ret->frame->texnums.base = GLTF_LoadTexture(gltf, albedo, 0); Q_snprintf(shader, sizeof(shader), @@ -1526,8 +2069,8 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert { Con_DPrintf(CON_WARNING"%s: KHR_materials_cmnBlinnPhong implemented according to draft spec\n", gltf->mod->name); - ret->frame->texnums.base = GLTF_LoadTexture(gltf, JSON_GetInteger(pbrsg, "diffuseTexture.index", -1), 0); - ret->frame->texnums.specular = GLTF_LoadTexture(gltf, JSON_GetInteger(pbrsg, "specularGlossinessTexture.index", -1), 0); + ret->frame->texnums.base = GLTF_LoadTexture(gltf, JSON_FindChild(pbrsg, "diffuseTexture.index"), 0); + ret->frame->texnums.specular = GLTF_LoadTexture(gltf, JSON_FindChild(pbrsg, "specularGlossinessTexture.index"), 0); //you wouldn't normally want this, but we have separate factors so lack of a texture is technically valid. if (!ret->frame->texnums.base) @@ -1567,10 +2110,10 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert } else if (pbrsg) { //if this extension was used, then we can use rgb gloss instead of metalness stuff. - int occ = JSON_GetInteger(mat, "occlusionTexture.index", -1); //.r - ret->frame->texnums.base = GLTF_LoadTexture(gltf, JSON_GetInteger(pbrsg, "diffuseTexture.index", -1), 0); - ret->frame->texnums.specular = GLTF_LoadTexture(gltf, JSON_GetInteger(pbrsg, "specularGlossinessTexture.index", -1), 0); - if (occ != -1) + json_t *occ = JSON_FindChild(mat, "occlusionTexture.index"); //.r + ret->frame->texnums.base = GLTF_LoadTexture(gltf, JSON_FindChild(pbrsg, "diffuseTexture.index"), 0); + ret->frame->texnums.specular = GLTF_LoadTexture(gltf, JSON_FindChild(pbrsg, "specularGlossinessTexture.index"), 0); + if (occ) ret->frame->texnums.occlusion = GLTF_LoadTexture(gltf, occ, IF_NOSRGB); Q_snprintf(shader, sizeof(shader), @@ -1588,7 +2131,7 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert "bemode rtlight rtlight_sg\n" "}\n", doubleSided?"cull disable\n":"", - (occ!=-1)?"#OCCLUDE":"", + (occ)?"#OCCLUDE":"", alphaCutoffmodifier, (alphaMode==1)?"":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", @@ -1608,23 +2151,44 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert else// if (pbrmr) { //this is the standard lighting model for gltf2 //'When not specified, all the default values of pbrMetallicRoughness apply' - int albedo = JSON_GetInteger(pbrmr, "baseColorTexture.index", -1); //.rgba - int mrt = JSON_GetInteger(pbrmr, "metallicRoughnessTexture.index", -1); //.r = unused, .g = roughness, .b = metalic, .a = unused - int occ = JSON_GetInteger(mat, "occlusionTexture.index", -1); //.r + json_t *albedo = JSON_FindChild(pbrmr, "baseColorTexture.index"); //.rgba + json_t *mrt = JSON_FindChild(pbrmr, "metallicRoughnessTexture.index"); //.r = unused, .g = roughness, .b = metalic, .a = unused + json_t *occ = JSON_FindChild(mat, "occlusionTexture.index"); //.r + json_t *n; + char occname[MAX_QPATH]; + char mrtname[MAX_QPATH]; + + if (JSON_GetInteger(pbrmr, "baseColorTexture.texCoord", 0) != 0) + if (gltf->warnlimit --> 0) + Con_Printf("%s: Unsupported baseColorTexture texCoord value\n", gltf->mod->name); + if (JSON_GetInteger(pbrmr, "metallicRoughnessTexture.texCoord", 0) != 0) + if (gltf->warnlimit --> 0) + Con_Printf("%s: Unsupported metallicRoughnessTexture texCoord value\n", gltf->mod->name); + if (JSON_GetInteger(mat, "occlusionTexture.texCoord", 0) != 0) + if (gltf->warnlimit --> 0) + Con_Printf("%s: Unsupported occlusionTexture texCoord value\n", gltf->mod->name); //now work around potential lame exporters (yay dds?). - occ = JSON_GetInteger(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index", occ); - mrt = JSON_GetInteger(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index", mrt); + n = JSON_FindChild(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index"); + if (n) + occ = n; + n = JSON_FindChild(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index"); + if (n) + mrt = n; //ideally we use the ORM.r for the occlusion map, but some people just love being annoying. - if (occ != mrt && occ != -1) + JSON_ReadBody(occ, occname, sizeof(occname)); + JSON_ReadBody(mrt, mrtname, sizeof(mrtname)); + if (strcmp(occname,mrtname) && occ) ret->frame->texnums.occlusion = GLTF_LoadTexture(gltf, occ, IF_NOSRGB); //note: extensions.MSFT_packing_normalRoughnessMetallic.normalRoughnessMetallicTexture.index gives rg=normalxy, b=roughness, .a=metalic //(would still need an ao map, and probably wouldn't work well as bc3 either) - ret->frame->texnums.base = GLTF_LoadTexture(gltf, albedo, 0); - ret->frame->texnums.specular = GLTF_LoadTexture(gltf, mrt, IF_NOSRGB); + if (albedo) + ret->frame->texnums.base = GLTF_LoadTexture(gltf, albedo, 0); + if (mrt) + ret->frame->texnums.specular = GLTF_LoadTexture(gltf, mrt, IF_NOSRGB); Q_snprintf(shader, sizeof(shader), "{\n" @@ -1641,7 +2205,7 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert "bemode rtlight rtlight_orm\n" "}\n", doubleSided?"cull disable\n":"", - (occ==-1)?"#NOOCCLUDE":((occ!=mrt)?"#OCCLUDE":""), + (!occ)?"#NOOCCLUDE":(strcmp(occname,mrtname)?"#OCCLUDE":""), alphaCutoffmodifier, (alphaMode==1)?"":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", @@ -1657,8 +2221,10 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert JSON_GetFloat(mat, "emissiveFactor.2", 0) ); } - ret->frame->texnums.bump = GLTF_LoadTexture(gltf, JSON_GetInteger(mat, "normalTexture.index", -1), IF_NOSRGB|IF_TRYBUMP); - ret->frame->texnums.fullbright = GLTF_LoadTexture(gltf, JSON_GetInteger(mat, "emissiveTexture.index", -1), 0); + if (!ret->frame->texnums.bump) + ret->frame->texnums.bump = GLTF_LoadTexture(gltf, JSON_FindChild(mat, "normalTexture.index"), IF_NOSRGB|IF_TRYBUMP); + if (!ret->frame->texnums.fullbright) + ret->frame->texnums.fullbright = GLTF_LoadTexture(gltf, JSON_FindChild(mat, "emissiveTexture.index"), 0); if (!ret->frame->texnums.base) ret->frame->texnums.base = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); @@ -1669,22 +2235,30 @@ static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vert return ret; } #endif -static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double pmatrix[]) +static const float *QDECL GLTF_AnimateMorphs(const galiasinfo_t *surf, const framestate_t *framestate); +static qboolean GLTF_ProcessMesh(gltf_t *gltf, json_t *meshid, int basebone, double skinmatrix[]) { model_t *mod = gltf->mod; - json_t *mesh = JSON_FindIndexedChild(gltf->r, "meshes", meshidx); + quintptr_t meshidx; + json_t *mesh = GLTF_FindJSONID(gltf, "meshes", meshid, &meshidx); json_t *prim; json_t *meshname = JSON_FindChild(mesh, "name"); + json_t *target = NULL; + float morphweights[MAX_MORPHWEIGHTS]; + size_t morphtargets; - JSON_WarnIfChild(mesh, "weights", &gltf->warnlimit); - JSON_WarnIfChild(mesh, "extensions", &gltf->warnlimit); -// JSON_WarnIfChild(mesh, "extras", &gltf->warnlimit); + target = JSON_FindChild(mesh, "weights"); + for (morphtargets = 0; morphtargets < MAX_MORPHWEIGHTS; morphtargets++) + morphweights[morphtargets] = JSON_GetIndexedFloat(target, morphtargets, 0); + morphtargets = ~0; + GLTF_FlagExtras(mesh); for(prim = JSON_FindIndexedChild(mesh, "primitives", 0); prim; prim = prim->sibling) { 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; + struct gltf_accessor morph_vpos[MAX_MORPHWEIGHTS], morph_norm[MAX_MORPHWEIGHTS], morph_tang[MAX_MORPHWEIGHTS]; galiasinfo_t *surf; size_t i, j; @@ -1696,20 +2270,48 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double continue; } - JSON_WarnIfChild(prim, "targets", &gltf->warnlimit); //morph targets... - JSON_FindChild(prim, "extensions"); -// JSON_WarnIfChild(prim, "extensions", &gltf->warnlimit); -// JSON_WarnIfChild(prim, "extras", &gltf->warnlimit); + for (i = 0; ; i++) + { + target = JSON_FindIndexedChild(prim, "targets", i); + if (!target) + break; + GLTF_GetAccessor(gltf, JSON_FindChild(target, "POSITION"), &morph_vpos[i]); + GLTF_GetAccessor(gltf, JSON_FindChild(target, "NORMAL"), &morph_norm[i]); + GLTF_GetAccessor(gltf, JSON_FindChild(target, "TANGENT"), &morph_tang[i]); + } + if (i != morphtargets) + { + if (morphtargets == ~0) + morphtargets = i; + else if (gltf->warnlimit --> 0) + Con_Printf(CON_WARNING"morphtargets count changed between primitives\n"); + for (; i < morphtargets; i++) + { + memset(&morph_vpos[i], 0, sizeof(morph_vpos[i])); + memset(&morph_norm[i], 0, sizeof(morph_norm[i])); + memset(&morph_tang[i], 0, sizeof(morph_tang[i])); + } + } - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "TEXCOORD_0", -1), &tc_0); //float, ubyte, ushort - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "TEXCOORD_1", -1), &tc_1); //float, ubyte, ushort - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "NORMAL", -1), &norm); //float - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "TANGENT", -1), &tang); //float - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "POSITION", -1), &vpos); //float - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "COLOR_0", -1), &col0); //float, ubyte, ushort - GLTF_GetAccessor(gltf, JSON_GetInteger(prim, "indices", -1), &idx); - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "JOINTS_0", -1), &sidx); //ubyte, ushort - GLTF_GetAccessor(gltf, JSON_GetInteger(attr, "WEIGHTS_0", -1), &swgt); //float, ubyte, ushort + GLTF_FlagExtras(prim); + + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "TEXCOORD_0"), &tc_0); //float, ubyte, ushort + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "TEXCOORD_1"), &tc_1); //float, ubyte, ushort + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "NORMAL"), &norm); //float + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "TANGENT"), &tang); //float + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "POSITION"), &vpos); //float + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "COLOR_0"), &col0); //float, ubyte, ushort + GLTF_GetAccessor(gltf, JSON_FindChild(prim, "indices"), &idx); + if (gltf->ver <= 1) + { + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "JOINT"), &sidx); //ubyte, ushort + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "WEIGHT"), &swgt); //float, ubyte, ushort + } + else + { //potentially multiple, each a vec4. + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "JOINTS_0"), &sidx); //ubyte, ushort + GLTF_GetAccessor(gltf, JSON_FindChild(attr, "WEIGHTS_0"), &swgt); //float, ubyte, ushort + } if (JSON_GetInteger(attr, "JOINTS_1", -1) != -1 || JSON_GetInteger(attr, "WEIGHTS_1", -1) != -1) if (gltf->warnlimit --> 0) @@ -1718,9 +2320,9 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double if (!vpos.count) continue; - surf = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*surf)); + surf = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*surf) + (morphtargets*sizeof(float))); - surf->surfaceid = surf->contents = JSON_GetInteger(prim, "extras.fte.surfaceid", meshidx); + surf->surfaceid = JSON_GetInteger(prim, "extras.fte.surfaceid", meshidx); surf->contents = JSON_GetInteger(prim, "extras.fte.contents", FTECONTENTS_BODY); surf->csurface.flags = JSON_GetInteger(prim, "extras.fte.surfaceflags", 0); surf->geomset = JSON_GetInteger(prim, "extras.fte.geomset", ~0u); @@ -1771,20 +2373,42 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double surf->ofs_indexes[i+2] = t; } - surf->ofs_skel_xyz = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_xyz[0]), &vpos); - surf->ofs_skel_norm = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_norm[0]), &norm); - GLTF_AccessorToTangents(gltf, surf->ofs_skel_norm, &surf->ofs_skel_svect, &surf->ofs_skel_tvect, surf->numverts, &tang); - surf->ofs_st_array = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_st_array[0]), &tc_0); + surf->AnimateMorphs = GLTF_AnimateMorphs; + memcpy((float*)(surf+1), morphweights, sizeof(float)*morphtargets); + surf->nummorphs = morphtargets; + surf->ofs_skel_xyz = modfuncs->ZG_Malloc(&mod->memgroup, (sizeof(*surf->ofs_skel_xyz)+sizeof(*surf->ofs_skel_norm)+sizeof(*surf->ofs_skel_svect)+sizeof(*surf->ofs_skel_tvect)) * surf->numverts * (1+morphtargets)); + surf->ofs_skel_norm = (vec3_t*)(surf->ofs_skel_xyz+surf->numverts*(1+morphtargets)); + surf->ofs_skel_svect = (vec3_t*)(surf->ofs_skel_norm+surf->numverts*(1+morphtargets)); + surf->ofs_skel_tvect = (vec3_t*)(surf->ofs_skel_svect+surf->numverts*(1+morphtargets)); + + surf->ofs_skel_xyz = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_xyz[0]), &vpos, surf->ofs_skel_xyz); + surf->ofs_skel_norm = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_norm[0]), &norm, surf->ofs_skel_norm); //if no normals, normals should be flat (fragment shader or unwelding the verts...) + GLTF_AccessorToTangents(gltf, surf->ofs_skel_norm, surf->numverts, &tang, surf->ofs_skel_svect, surf->ofs_skel_tvect); + + for (i = 0; i < morphtargets; i++) + { + size_t offset = (i+1) * surf->numverts; + GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_xyz[0]), &morph_vpos[i], surf->ofs_skel_xyz+offset); + GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_norm[0]), &morph_norm[i], surf->ofs_skel_norm+offset); //if no normals, normals should be flat (fragment shader or unwelding the verts...) + GLTF_AccessorToTangents(gltf, surf->ofs_skel_norm+offset, surf->numverts, &morph_tang[i], surf->ofs_skel_svect+offset, surf->ofs_skel_tvect+offset); + } + + surf->ofs_st_array = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_st_array[0]), &tc_0, NULL); if (tc_1.data) - surf->ofs_lmst_array = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_lmst_array[0]), &tc_1); + surf->ofs_lmst_array = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_lmst_array[0]), &tc_1, NULL); if (col0.data && col0.componentType == 5121) //UNSIGNED_BYTE surf->ofs_rgbaub = GLTF_AccessorToDataUB(gltf, surf->numverts, countof(surf->ofs_rgbaub[0]), &col0); else if (col0.data) - surf->ofs_rgbaf = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_rgbaf[0]), &col0); + surf->ofs_rgbaf = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_rgbaf[0]), &col0, NULL); + /*else + { + surf->ofs_rgbaub = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*surf->ofs_rgbaub) * surf->numverts); + memset(surf->ofs_rgbaub, 0xff, sizeof(*surf->ofs_rgbaub) * surf->numverts); + }*/ if (sidx.data && swgt.data) { surf->ofs_skel_idx = GLTF_AccessorToDataBone(gltf,surf->numverts, &sidx); - surf->ofs_skel_weight = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_weight[0]), &swgt); + surf->ofs_skel_weight = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_skel_weight[0]), &swgt, NULL); for (i = 0; i < surf->numverts; i++) { @@ -1806,9 +2430,17 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double } } -// TransformArrayD(surf->ofs_skel_xyz, surf->numverts, pmatrix); -// TransformArrayA(surf->ofs_skel_norm, surf->numverts, pmatrix); -// TransformArrayA(surf->ofs_skel_svect, surf->numverts, pmatrix); + if (skinmatrix) + { + TransformPosArray(surf->ofs_skel_xyz, surf->numverts, skinmatrix); + if (norm.data) + TransformDirArray(surf->ofs_skel_norm, surf->numverts, skinmatrix); + if (tang.data) + { + TransformDirArray(surf->ofs_skel_svect, surf->numverts, skinmatrix); + TransformDirArray(surf->ofs_skel_tvect, surf->numverts, skinmatrix); + } + } for (i = 0; i < surf->numverts; i++) { @@ -1824,7 +2456,7 @@ static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double #ifndef SERVERONLY surf->numskins = 1; - surf->ofsskins = GLTF_LoadMaterial(gltf, JSON_GetInteger(prim, "material", -1), surf->ofs_rgbaub||surf->ofs_rgbaf); + surf->ofsskins = GLTF_LoadMaterial(gltf, JSON_FindChild(prim, "material"), surf->ofs_rgbaub||surf->ofs_rgbaf); #endif if (!tang.data) @@ -1900,24 +2532,26 @@ static void GenMatrixPosQuat4ScaleDouble(const double pos[3], const double quat[ result[3*4+3] = 1; } -static qboolean GLTF_ProcessNode(gltf_t *gltf, int nodeidx, double pmatrix[16], int parentidx, qboolean isjoint) +static qboolean GLTF_ProcessNode(gltf_t *gltf, json_t *nodeid, double pmatrix[16], int parentidx, qboolean isjoint) { + double skinmatrix[16], *skinmatrixptr=NULL; json_t *c; json_t *node; json_t *t; json_t *skin; - int mesh; - int skinidx; + json_t *meshid; + quintptr_t nodeidx; struct gltfbone_s *b; - if (nodeidx < 0 || nodeidx >= gltf->numbones) + node = GLTF_FindJSONID(gltf, "nodes", nodeid, &nodeidx); + if (nodeidx >= gltf->numbones) { - Con_Printf(CON_WARNING"%s: Invalid node index %i\n", gltf->mod->name, nodeidx); + if (nodeidx < MAX_BONES) //don't spam if its detected elsewhere. + Con_Printf(CON_WARNING"%s: Invalid node index %i\n", gltf->mod->name, (int)nodeidx); return false; } - node = JSON_FindIndexedChild(gltf->r, "nodes", nodeidx); if (!node) { - Con_Printf(CON_WARNING"%s: Invalid node index %i\n", gltf->mod->name, nodeidx); + Con_Printf(CON_WARNING"%s: Invalid node index %i\n", gltf->mod->name, (int)nodeidx); return false; } @@ -1973,41 +2607,50 @@ static qboolean GLTF_ProcessNode(gltf_t *gltf, int nodeidx, double pmatrix[16], //T * R * S GenMatrixPosQuat4ScaleDouble(trans, rot, scale, b->rel.rmatrix); -/* - memset(mmatrix, 0, sizeof(mmatrix)); - mmatrix[0] = 1; - mmatrix[5] = 1; - (void)rot,(void)scale; - mmatrix[10] = 1; - mmatrix[15] = 1; - mmatrix[3] = trans[0]; - mmatrix[7] = trans[1]; - mmatrix[11] = trans[2]; -*/ } Matrix4D_Multiply(b->rel.rmatrix, pmatrix, b->amatrix); - skinidx = JSON_GetInteger(node, "skin", -1); - if (skinidx >= 0) + skin = GLTF_FindJSONID(gltf, "skins", JSON_FindChild(node, "skin"), NULL); + if (skin) { -// double identity[16]; - int j; + quintptr_t j; json_t *joints; struct gltf_accessor inverse; float *inversef; - skin = JSON_FindIndexedChild(gltf->r, "skins", skinidx); - - joints = JSON_FindChild(skin, "joints"); - GLTF_GetAccessor(gltf, JSON_GetInteger(skin, "inverseBindMatrices", -1), &inverse); + if (gltf->ver <= 1) + joints = JSON_FindChild(skin, "jointNames"); + else + joints = JSON_FindChild(skin, "joints"); + if (joints) + joints = joints->child; + GLTF_GetAccessor(gltf, JSON_FindChild(skin, "inverseBindMatrices"), &inverse); inversef = inverse.data; if (inverse.componentType != 5126/*FLOAT*/ || inverse.type != ((4<<8) | 4)/*mat4x4*/) inverse.count = 0; - for (j = 0; j < MAX_BONES; j++, inversef+=inverse.bytestride/sizeof(float)) + memset(gltf->bonemap, 0, sizeof(*gltf->bonemap)*MAX_BONES); //avoid unexpected surprises... + for (j = 0; j < MAX_BONES && joints; j++, inversef+=inverse.bytestride/sizeof(float), joints=joints->sibling) { - int b = JSON_GetIndexedInteger(joints, j, -1); - if (b < 0) - break; + quintptr_t b; + joints->used = true; + if (gltf->ver <= 1) + { //urgh + char jointname[64]; + JSON_ReadBody(joints, jointname, sizeof(jointname)); //this is matched to nodes[b].jointName rather than (textual) b, so we can't use our helpers. + for (b = 0; b < gltf->numbones; b++) + { + if (!strcmp(gltf->bones[b].jointname, jointname)) + break; + } + if (b == gltf->numbones) + break; + } + else + { + b = JSON_GetUInteger(joints, NULL, ~0); + if (b >= gltf->numbones) + break; + } gltf->bonemap[j] = b; if (j < inverse.count) { @@ -2055,26 +2698,46 @@ static qboolean GLTF_ProcessNode(gltf_t *gltf, int nodeidx, double pmatrix[16], } } -// GLTF_ProcessNode(gltf, JSON_GetInteger(skin, "skeleton", -1), identity, nodeidx, true); +// GLTF_ProcessNode(gltf, JSON_FindChild(skin, "skeleton"), identity, nodeidx, true); + if (gltf->ver <= 1) + { + int i; + json_t *bdsm = JSON_FindChild(skin, "bindShapeMatrix"); + if (bdsm) + { + skinmatrixptr = skinmatrix; + for (i = 0; i < 16; i++) + skinmatrix[i] = JSON_GetIndexedFloat(bdsm, i, ((i%5)==0)?1.0:0.0); + } + } JSON_FlagAsUsed(node, "name"); } - - mesh = JSON_GetInteger(node, "mesh", -1); - if (mesh >= 0) - GLTF_ProcessMesh(gltf, mesh, nodeidx, b->amatrix); + + if (gltf->ver <= 1) + { //multiple in gltf1 + meshid = JSON_FindChild(node, "meshes"); + if (meshid) + for (meshid = meshid->child; meshid; meshid = meshid->sibling) + GLTF_ProcessMesh(gltf, meshid, nodeidx, skinmatrixptr); + } + else + { //gltf2 moved to only one. + meshid = JSON_FindChild(node, "mesh"); + if (meshid) + GLTF_ProcessMesh(gltf, meshid, nodeidx, skinmatrixptr); + } for(c = JSON_FindIndexedChild(node, "children", 0); c; c = c->sibling) { c->used = true; - GLTF_ProcessNode(gltf, JSON_GetInteger(c, NULL, -1), b->amatrix, nodeidx, isjoint); + GLTF_ProcessNode(gltf, c, b->amatrix, nodeidx, isjoint); } b->camera = JSON_GetInteger(node, "camera", -1); JSON_WarnIfChild(node, "weights", &gltf->warnlimit); //default value for morph weight animations - JSON_WarnIfChild(node, "extensions", &gltf->warnlimit); -// JSON_WarnIfChild(node, "extras", &gltf->warnlimit); + GLTF_FlagExtras(node); return true; } @@ -2088,6 +2751,7 @@ struct gltf_animsampler } interptype; struct gltf_accessor input; //timestamps struct gltf_accessor output; //values + int outputs; }; static void GLTF_Animation_Persist(gltf_t *gltf, struct gltf_accessor *accessor) { @@ -2096,11 +2760,11 @@ static void GLTF_Animation_Persist(gltf_t *gltf, struct gltf_accessor *accessor) memcpy(newdata, accessor->data, accessor->length); accessor->data = newdata; } -static struct gltf_animsampler GLTF_AnimationSampler(gltf_t *gltf, json_t *samplers, int sampleridx, int elems) +static struct gltf_animsampler GLTF_AnimationSampler(gltf_t *gltf, json_t *samplers, json_t *params, json_t *samplerid, int elems) { int outsperinput=1; struct gltf_animsampler r; - json_t *sampler = JSON_FindIndexedChild(samplers, NULL, sampleridx); + json_t *sampler = GLTF_FindJSONIDParent(gltf, samplers, samplerid, NULL); char t[32]; const char *lerptype = JSON_GetString(sampler, "interpolation", t, sizeof(t), "LINEAR"); @@ -2119,10 +2783,22 @@ static struct gltf_animsampler GLTF_AnimationSampler(gltf_t *gltf, json_t *sampl r.interptype = AINTERP_LINEAR; } - GLTF_GetAccessor(gltf, JSON_GetInteger(sampler, "input", -1), &r.input); - GLTF_GetAccessor(gltf, JSON_GetInteger(sampler, "output", -1), &r.output); + if (gltf->ver <= 1) + { + GLTF_GetAccessor(gltf, GLTF_FindJSONIDParent(gltf, params, JSON_FindChild(sampler, "input"), NULL), &r.input); + GLTF_GetAccessor(gltf, GLTF_FindJSONIDParent(gltf, params, JSON_FindChild(sampler, "output"), NULL), &r.output); + } + else + { + GLTF_GetAccessor(gltf, JSON_FindChild(sampler, "input"), &r.input); + GLTF_GetAccessor(gltf, JSON_FindChild(sampler, "output"), &r.output); + } - if (!r.input.data || !r.output.data || r.input.count*outsperinput != r.output.count) + if (!r.input.count) + r.input.count = 1; + else + r.outputs = r.output.count / (r.input.count*outsperinput); + if (!r.input.data || !r.output.data || r.input.count*outsperinput*r.outputs != r.output.count) memset(&r, 0, sizeof(r)); else { @@ -2237,7 +2913,7 @@ static void QuaternionSlerp_(const vec4_t p, const vec4_t q, float t, vec4_t qt) } } } -static void LerpAnimData(const struct gltf_animsampler *samp, float time, float *result, int elems) +static void LerpAnimData(const struct gltf_animsampler *samp, float time, float *result, int elems, qboolean slerp) { float t0, t1; float w0, w1; @@ -2256,6 +2932,9 @@ static void LerpAnimData(const struct gltf_animsampler *samp, float time, float t1 = Anim_GetTime(in, f1); } + f0 *= samp->outputs; + f1 *= samp->outputs; + if (samp->interptype == AINTERP_CUBICSPLINE) { float step=t1-t0; @@ -2282,7 +2961,7 @@ static void LerpAnimData(const struct gltf_animsampler *samp, float time, float result[c] = m0*v0[c] + mb*b[c] + m1*v1[c] + ma*a[c]; //quats must be normalized. - if (elems == 4) + if (slerp) { float len = sqrt(DotProduct4(result,result)); Vector4Scale(result, 1/len, result); @@ -2306,7 +2985,7 @@ static void LerpAnimData(const struct gltf_animsampler *samp, float time, float { Anim_GetVal(out, f0, v0, elems); Anim_GetVal(out, f1, v1, elems); - if (elems == 4) + if (slerp) QuaternionSlerp_(v0, v1, w1, result); else { @@ -2317,9 +2996,9 @@ static void LerpAnimData(const struct gltf_animsampler *samp, float time, float } } -static void GLTF_RemapBone(gltf_t *gltf, int *nextidx, int b) +static void GLTF_RemapBone(gltf_t *gltf, size_t *nextidx, size_t b) { //potentially needs to walk to the root before the child. recursion sucks. - if (gltf->bonemap[b] >= 0) + if (b == -1 || gltf->bonemap[b] >= 0) return; //already got remapped GLTF_RemapBone(gltf, nextidx, gltf->bones[b].parent); gltf->bonemap[b] = (*nextidx)++; @@ -2327,7 +3006,7 @@ static void GLTF_RemapBone(gltf_t *gltf, int *nextidx, int b) static void GLTF_RewriteBoneTree(gltf_t *gltf) { galiasinfo_t *surf; - int j, n; + size_t j, n; struct gltfbone_s *tmpbones; for (j = 0; j < gltf->numbones; j++) @@ -2371,9 +3050,45 @@ struct galiasanimation_gltf_s float duration; struct { - struct gltf_animsampler rot,scale,trans; + struct gltf_animsampler rot,scale,trans,morph; } bone[1]; }; +cvar_t temp1; +static const float *QDECL GLTF_AnimateMorphs(const galiasinfo_t *surf, const framestate_t *framestate) +{ + static float morphs[MAX_MORPHWEIGHTS]; + float imorphs[MAX_MORPHWEIGHTS], *src; + size_t influence, m; + int bone = temp1.ival; + const struct galiasanimation_gltf_s *a; + const struct framestateregion_s *fg = &framestate->g[FS_REG]; + memset(morphs, 0, sizeof(morphs[0])*surf->nummorphs); + for (influence = 0; influence < countof(framestate->g[FS_REG].frame); influence++) + { + m = 0; + if (!fg->lerpweight[influence]) + continue; //mneh, don't care. + if (surf->ofsanimations && (unsigned int)fg->frame[influence] < (unsigned int)surf->numanimations) + { + a = surf->ofsanimations[fg->frame[influence]].boneofs; + if (a && a->bone[bone].morph.input.count) + { + int asz = min(a->bone[bone].morph.outputs, surf->nummorphs); + double time = fg->frametime[influence]; + if (surf->ofsanimations[fg->frame[influence]].loop && time >= a->duration) + time = time - a->duration*floor(time/a->duration); + LerpAnimData(&a->bone[bone].morph, time, imorphs, asz, false); + for (; m < surf->nummorphs && m < asz; m++) + morphs[m] += fg->lerpweight[influence] * imorphs[m]; + } + } + + src = (float*)(surf+1); //our static morphs are stuck on the end of our surf struct.; + for (; m < surf->nummorphs; m++) + morphs[m] += fg->lerpweight[influence]*src[m]; + } + return morphs; +} static float *QDECL GLTF_AnimateBones(const galiasinfo_t *surf, const galiasanimation_t *anim, float time, float *bonematrix, int numbones) { const struct galiasbone_gltf_s *defbone = surf->ctx; @@ -2396,11 +3111,11 @@ static float *QDECL GLTF_AnimateBones(const galiasinfo_t *surf, const galiasanim VectorCopy(defbone[j].trans, trans); if (a->bone[j].rot.input.data) - LerpAnimData(&a->bone[j].rot, time, rot, 4); + LerpAnimData(&a->bone[j].rot, time, rot, 4, true); if (a->bone[j].scale.input.data) - LerpAnimData(&a->bone[j].scale, time, scale, 3); + LerpAnimData(&a->bone[j].scale, time, scale, 3, false); if (a->bone[j].trans.input.data) - LerpAnimData(&a->bone[j].trans, time, trans, 3); + LerpAnimData(&a->bone[j].trans, time, trans, 3, false); //figure out the bone matrix... modfuncs->GenMatrixPosQuat4Scale(trans, rot, scale, bonematrix); } @@ -2412,10 +3127,10 @@ static float *QDECL GLTF_AnimateBones(const galiasinfo_t *surf, const galiasanim if (surf->ofsbones[j].parent < 0) { //rotate any root bones from gltf to quake's orientation. float fnar[12]; - static float toquake[12]={ - 0,0,GLTFSCALE, 0, - GLTFSCALE,0,0, 0, - 0,GLTFSCALE,0, 0}; + float toquake[12]={ + 0,0,mod_gltf_scale->value, 0, + mod_gltf_scale->value,0,0, 0, + 0,mod_gltf_scale->value,0, 0}; memcpy(fnar, bonematrix, sizeof(fnar)); modfuncs->ConcatTransforms((void*)toquake, (void*)fnar, (void*)bonematrix); } @@ -2428,12 +3143,17 @@ static float *QDECL GLTF_AnimateBones(const galiasinfo_t *surf, const galiasanim //we do NOT supported nested nodes right now... static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, void *buffer, size_t buffersize) { - static struct + static struct knowngltfextensions_s { const char *name; qboolean supported; //unsupported extensions don't really need to be listed, but they do prevent warnings from unknown-but-used extensions qboolean draft; //true when our implementation is probably buggy on account of the spec maybe changing. - } extensions[] = + } extensions_v1[] = + { + {"KHR_binary_glTF", true, false}, + {"KHR_materials_common", true, false}, + {NULL} + }, extensions_v2[] = { {"KHR_materials_pbrSpecularGlossiness", true, false}, // {"KHR_materials_cmnBlinnPhong", true, true}, @@ -2443,9 +3163,11 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, {"KHR_mesh_quantization", true, true}, {"MSFT_texture_dds", true, false}, {"MSFT_packing_occlusionRoughnessMetallic", true, false}, - }; + {NULL} + }, *extensions; gltf_t gltf; - int pos=0, j, k; + int pos=0; + quintptr_t j,k; json_t *scene, *n, *anim; double rootmatrix[16]; double gltfver; @@ -2470,7 +3192,19 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, if (gltfver != 2.0) gltfver = JSON_GetFloat(gltf.r, "asset.version", 0.0); if (gltfver == 2.0) + gltf.ver = 2; + if (gltfver == 1.0) //we load gltf1 models. we don't get the materials right but gltf1 sucks for that anyway. + gltf.ver = 1; + if (gltf.ver) { + if (gltf.ver <= 1) + { + JSON_FlagAsUsed(gltf.r, "asset.profile"); + JSON_FlagAsUsed(gltf.r, "asset.premultipliedAlpha"); + extensions = extensions_v1; + } + else + extensions = extensions_v2; JSON_FlagAsUsed(gltf.r, "asset.copyright"); JSON_FlagAsUsed(gltf.r, "asset.generator"); JSON_WarnIfChild(gltf.r, "asset.minVersion", &gltf.warnlimit); @@ -2480,14 +3214,14 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, { char extname[256]; JSON_ReadBody(n, extname, sizeof(extname)); - for (j = 0; j < countof(extensions); j++) + for (j = 0; extensions[j].name; j++) { if (!strcmp(extname, extensions[j].name)) break; } - if (j==countof(extensions) || !extensions[j].supported) + if (!extensions[j].supported) { - Con_Printf(CON_ERROR "%s: Required gltf2 extension \"%s\" not supported\n", mod->name, extname); + Con_Printf(CON_ERROR "%s: Required gltf%i extension \"%s\" not supported\n", mod->name, gltf.ver, extname); goto abort; } } @@ -2496,15 +3230,15 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, { //must be a superset of the above. char extname[256]; JSON_ReadBody(n, extname, sizeof(extname)); - for (j = 0; j < countof(extensions); j++) + for (j = 0; extensions[j].name; j++) { if (!strcmp(extname, extensions[j].name)) break; } - if (j==countof(extensions) || !extensions[j].supported) - Con_Printf(CON_WARNING "%s: gltf2 extension \"%s\" not known\n", mod->name, extname); + if (!extensions[j].supported) + Con_Printf(CON_WARNING "%s: gltf%i extension \"%s\" not known\n", mod->name, gltf.ver, extname); else if (extensions[j].draft) - Con_Printf(CON_WARNING "%s: gltf2 extension \"%s\" follows draft implementation, and may be non-standard/buggy\n", mod->name, extname); + Con_Printf(CON_WARNING "%s: gltf%i extension \"%s\" follows draft implementation, and may be non-standard/buggy\n", mod->name, gltf.ver, extname); } VectorClear(mod->maxs); @@ -2513,18 +3247,22 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, //we don't really care about cameras. JSON_FlagAsUsed(gltf.r, "cameras"); - scene = JSON_FindIndexedChild(gltf.r, "scenes", JSON_GetInteger(gltf.r, "scene", 0)); + scene = GLTF_FindJSONID_First(&gltf, "scenes", JSON_FindChild(gltf.r, "scene"), NULL); memset(&rootmatrix, 0, sizeof(rootmatrix)); #if 1 //transform from gltf to quake. mostly only needed for the base pose. - rootmatrix[2] = rootmatrix[4] = rootmatrix[9] = GLTFSCALE; rootmatrix[15] = 1; + rootmatrix[2] = rootmatrix[4] = rootmatrix[9] = mod_gltf_scale->value; rootmatrix[15] = 1; #else rootmatrix[0] = rootmatrix[5] = rootmatrix[10] = 1; rootmatrix[15] = 1; #endif + n = JSON_FindChild(gltf.r, "nodes"); for (j = 0; ; j++) { - n = JSON_FindIndexedChild(gltf.r, "nodes", j); + if (gltf.ver <= 1) + n = j?n->sibling:n->child; + else + n = JSON_FindIndexedChild(gltf.r, "nodes", j); if (!n) break; if (j == MAX_BONES) @@ -2532,12 +3270,13 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, Con_Printf(CON_WARNING"%s: too many nodes (max %i)\n", mod->name, MAX_BONES); break; } + JSON_ReadBody(JSON_FindChild(n, "jointName"), gltf.bones[j].jointname, sizeof(gltf.bones[j].jointname)); if (!JSON_ReadBody(JSON_FindChild(n, "name"), gltf.bones[j].name, sizeof(gltf.bones[j].name))) { if (n) JSON_GetPath(n, true, gltf.bones[j].name, sizeof(gltf.bones[j].name)); else - Q_snprintf(gltf.bones[j].name, sizeof(gltf.bones[j].name), "bone%i", j); + Q_snprintf(gltf.bones[j].name, sizeof(gltf.bones[j].name), "bone%u", (unsigned)j); } gltf.bones[j].camera = -1; gltf.bones[j].parent = -1; @@ -2548,8 +3287,7 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, gltf.numbones = j; JSON_FlagAsUsed(scene, "name"); - JSON_WarnIfChild(scene, "extensions", &gltf.warnlimit); -// JSON_WarnIfChild(scene, "extras"); + GLTF_FlagExtras(scene); for (j = 0; ; j++) { n = JSON_FindIndexedChild(scene, "nodes", j); @@ -2557,7 +3295,7 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, break; n->used = true; // if (! - GLTF_ProcessNode(&gltf, JSON_GetInteger(n, NULL, -1), rootmatrix, -1, false); + GLTF_ProcessNode(&gltf, n, rootmatrix, -1, false); // break; } @@ -2582,27 +3320,39 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, gltfbone[j] = gltf.bones[j].rel; } - for(anim = JSON_FindIndexedChild(gltf.r, "animations", 0); anim; anim = anim->sibling) - numframegroups++; + anim = JSON_FindChild(gltf.r, "animations"); + if (anim) + for(anim = anim->child; anim; anim = anim->sibling) + numframegroups++; if (numframegroups) { + struct galiasanimation_gltf_s **mergeanims = NULL; + if (mod_gltf_fixbuggyanims->ival) + { + mergeanims = alloca(sizeof(*mergeanims)*gltf.numbones); + memset(mergeanims, 0, sizeof(mergeanims)*gltf.numbones); + } framegroups = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*framegroups)*numframegroups); - for (k = 0; k < numframegroups; k++) + anim = JSON_FindChild(gltf.r, "animations")->child; + for (k = 0; k < numframegroups; k++, anim = anim->sibling) { galiasanimation_t *fg = &framegroups[k]; - json_t *anim = JSON_FindIndexedChild(gltf.r, "animations", k); json_t *chan; json_t *samps = JSON_FindChild(anim, "samplers"); -// int f, l; + json_t *params = gltf.ver<=1?JSON_FindChild(anim, "parameters"):0; //gltf1 float maxtime = 0; + unsigned maxposes = 0; struct galiasanimation_gltf_s *a = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*a)+sizeof(a->bone[0])*(gltf.numbones-1)); + anim->used = true; if (!JSON_ReadBody(JSON_FindChild(anim, "name"), fg->name, sizeof(fg->name))) { - if (anim) + if (gltf.ver <= 1) + Q_snprintf(fg->name, sizeof(fg->name), "%s", anim->name); + else if (anim) JSON_GetPath(anim, true, fg->name, sizeof(fg->name)); else - Q_snprintf(fg->name, sizeof(fg->name), "anim%i", k); + Q_snprintf(fg->name, sizeof(fg->name), "anim%u", (unsigned int)k); } fg->loop = true; fg->skeltype = SKEL_RELATIVE; @@ -2610,89 +3360,89 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, { struct gltf_animsampler s; json_t *targ = JSON_FindChild(chan, "target"); - int sampler = JSON_GetInteger(chan, "sampler", -1); - int bone = JSON_GetInteger(targ, "node", -2); + json_t *samplerid = JSON_FindChild(chan, "sampler"); + qintptr_t bone; json_t *path = JSON_FindChild(targ, "path"); - if (bone == -2) - continue; //'When node isn't defined, channel should be ignored' + chan->used = true; + + if (gltf.ver <= 1) + GLTF_FindJSONID(&gltf, "nodes", JSON_FindChild(targ, "id"), (quintptr_t*)&bone); + else + { + bone = JSON_GetInteger(targ, "node", -2); + if (bone == -2) + continue; //'When node isn't defined, channel should be ignored' + } if (bone < 0 || bone >= gltf.numbones) { if (gltf.warnlimit --> 0) - Con_Printf("%s: invalid node index %i\n", mod->name, bone); + Con_Printf("%s: invalid node index %i\n", mod->name, (int)bone); continue; //error... } bone = gltf.bonemap[bone]; - s = GLTF_AnimationSampler(&gltf, samps, sampler, 4); - maxtime = max(maxtime, s.input.maxs[0]); - if (JSON_Equals(path, NULL, "rotation")) - a->bone[bone].rot = s; - else if (JSON_Equals(path, NULL, "scale")) - a->bone[bone].scale = s; - else if (JSON_Equals(path, NULL, "translation")) - a->bone[bone].trans = s; - else if (gltf.warnlimit --> 0) - { //these are unsupported - if (JSON_Equals(path, NULL, "weights")) //morph weights - Con_Printf(CON_WARNING"%s: morph animations are not supported\n", mod->name); + if (mergeanims) + { + if (mergeanims[bone] && mergeanims[bone] != a) + mergeanims = NULL; //was some other animation... which means two animations are fighting over the same joint. which means we don't need to use this hack! yay! else - Con_Printf("%s: undocumented animation type\n", mod->name); + mergeanims[bone] = a; + } + s = GLTF_AnimationSampler(&gltf, samps, params, samplerid, 4); + if (!s.input.maxs[0] && s.input.count) + s.input.maxs[0] = Anim_GetTime(&s.input, s.input.count-1); + maxtime = max(maxtime, s.input.maxs[0]); + maxposes = max(maxposes, s.input.count); + if (JSON_Equals(path, NULL, "rotation") && s.outputs==1) + a->bone[bone].rot = s; + else if (JSON_Equals(path, NULL, "scale") && s.outputs==1) + a->bone[bone].scale = s; + else if (JSON_Equals(path, NULL, "translation") && s.outputs==1) + a->bone[bone].trans = s; + else if (JSON_Equals(path, NULL, "weights") && s.outputs>=1) + a->bone[bone].morph = s; + else + { + if (gltf.warnlimit --> 0) + { + char buf[64]; + JSON_ReadBody(path, buf, sizeof(buf)); + Con_Printf("%s: unknown animation data - %s\n", mod->name, buf); + } } } + if (!maxtime) + maxtime = 1.0/30; //some stuff doesn't like 0-length animations. divisions by 0 are not nice. a->duration = maxtime; - //TODO: make a guess at the framerate according to sampler intervals - fg->rate = 60; - fg->numposes = max(1, maxtime*fg->rate); + //calc average framerate + fg->numposes = maxposes; if (maxtime) fg->rate = fg->numposes/maxtime; //fix up the rate so we hit the exact end of the animation (so it doesn't have to be quite so exact). + else + fg->rate = 60; fg->skeltype = SKEL_RELATIVE; fg->GetRawBones = GLTF_AnimateBones; fg->boneofs = a; -#if 0 - fg->boneofs = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*fg->boneofs)*12*gltf.numbones*fg->numposes); + } - for (f = 0; f < fg->numposes; f++) - { - float *bonematrix = &fg->boneofs[f*gltf.numbones*12]; - float time = f/fg->rate; - for (j = 0; j < gltf.numbones; j++, bonematrix+=12) + if (mergeanims) + { //if we got this far then no two animations referenced the same bone. + //this is a common issue with various sample models, I'm going to call it an exporter bug, and work around it by merging them into a single animation. + for (k = 1; k < numframegroups && framegroups[k].rate == framegroups[0].rate && framegroups[k].numposes == framegroups[0].numposes; k++) + ; + if (k == numframegroups) + { //yup, all have the same duration+posecount. lets call it a valid fix. + struct galiasanimation_gltf_s *merged = framegroups[0].boneofs; + Q_snprintf(framegroups->name, sizeof(framegroups->name), "allbones"); + numframegroups = 1; + for (k = 0; k < gltf.numbones; k++) { - float scale[3]; - float rot[4]; - float trans[3]; - //eww, weird inheritance crap. - if (b[j].rot.input.data || b[j].scale.input.data || b[j].trans.input.data) - { - VectorCopy(gltf.bones[j].rel.scale, scale); - Vector4Copy(gltf.bones[j].rel.quat, rot); - VectorCopy(gltf.bones[j].rel.trans, trans); - - if (b[j].rot.input.data) - LerpAnimData(&b[j].rot, time, rot, 4); - if (b[j].scale.input.data) - LerpAnimData(&b[j].scale, time, scale, 3); - if (b[j].trans.input.data) - LerpAnimData(&b[j].trans, time, trans, 3); - //figure out the bone matrix... - modfuncs->GenMatrixPosQuat4Scale(trans, rot, scale, bonematrix); - } - else - { //nothing animated, use what we calculated earlier. - for (l = 0; l < 12; l++) - bonematrix[l] = gltf.bones[j].rel.rmatrix[l]; - } - if (gltf.bones[j].parent < 0) - { //rotate any root bones from gltf to quake's orientation. - float fnar[12]; - static float toquake[12]={0,0,GLTFSCALE,0,GLTFSCALE,0,0,0,0,GLTFSCALE,0,0}; - memcpy(fnar, bonematrix, sizeof(fnar)); - modfuncs->ConcatTransforms((void*)toquake, (void*)fnar, (void*)bonematrix); - } + if (mergeanims[k]) + merged->bone[k] = mergeanims[k]->bone[k]; //copy over that bone. } } -#endif } } @@ -2710,8 +3460,8 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize, surf->geomset = ~0; //invalid set = always visible. FIXME: set this according to scene numbers? surf->geomid = 0; } - VectorScale(mod->mins, GLTFSCALE, mod->mins); - VectorScale(mod->maxs, GLTFSCALE, mod->maxs); + VectorScale(mod->mins, mod_gltf_scale->value, mod->mins); + VectorScale(mod->maxs, mod_gltf_scale->value, mod->maxs); if (!mod->meshinfo) Con_Printf("%s: Doesn't contain any meshes...\n", mod->name); @@ -2724,7 +3474,6 @@ abort: free(gltf.bones); free(gltf.bonemap); - mod->type = mod_alias; return !!mod->meshinfo; } @@ -2737,32 +3486,50 @@ qboolean QDECL Mod_LoadGLTFModel (struct model_s *mod, void *buffer, size_t fsiz qboolean QDECL Mod_LoadGLBModel (struct model_s *mod, void *buffer, size_t fsize) { unsigned char *header = buffer; - unsigned int magic = header[0]|(header[1]<<8)|(header[2]<<16)|(header[3]<<24); - unsigned int version = header[4]|(header[5]<<8)|(header[6]<<16)|(header[7]<<24); - unsigned int length = header[8]|(header[9]<<8)|(header[10]<<16)|(header[11]<<24); + unsigned int magic = header[0]|(header[1]<<8)|(header[2]<<16)|(header[3]<<24); //gltf + unsigned int version = header[4]|(header[5]<<8)|(header[6]<<16)|(header[7]<<24); //2 + unsigned int length = header[8]|(header[9]<<8)|(header[10]<<16)|(header[11]<<24); //fsize unsigned int jsonlen = header[12]|(header[13]<<8)|(header[14]<<16)|(header[15]<<24); unsigned int jsontype = header[16]|(header[17]<<8)|(header[18]<<16)|(header[19]<<24); char *json = (char*)(header+20); - unsigned int binlen = header[20+jsonlen]|(header[21+jsonlen]<<8)|(header[22+jsonlen]<<16)|(header[23+jsonlen]<<24); - unsigned int bintype = header[24+jsonlen]|(header[25+jsonlen]<<8)|(header[26+jsonlen]<<16)|(header[27+jsonlen]<<24); - unsigned char *bin = header+28+jsonlen; - if (fsize < 28) return false; if (magic != (('F'<<24)+('T'<<16)+('l'<<8)+'g')) return false; - if (version != 2) - return false; - if (jsontype != 0x4E4F534A) //'JSON' - return false; - if (length != 28+jsonlen+binlen) - return false; - if (bintype != 0x004E4942) //'BIN\0' - return false; - - return GLTF_LoadModel(mod, json, jsonlen, bin, binlen); + if (fsize < length) + return false; //allow padding on the end, but not truncation + if (version == 1) + { + unsigned int binlen = (length-20) - jsonlen; + unsigned char *bin = header+20+jsonlen; + + if (jsonlen&3) + return false; //exporter is expected to pad with spaces. + if (jsontype != 0) //'JSON' + return false; + if (length != 20+jsonlen+binlen) + return false; + + return GLTF_LoadModel(mod, json, jsonlen, bin, binlen); + } + else if (version == 2) + { + unsigned int binlen = header[20+jsonlen]|(header[21+jsonlen]<<8)|(header[22+jsonlen]<<16)|(header[23+jsonlen]<<24); + unsigned int bintype = header[24+jsonlen]|(header[25+jsonlen]<<8)|(header[26+jsonlen]<<16)|(header[27+jsonlen]<<24); + unsigned char *bin = header+28+jsonlen; + + if (jsontype != 0x4E4F534A) //'JSON' + return false; + if (length != 28+jsonlen+binlen) + return false; + if (bintype != 0x004E4942) //'BIN\0' + return false; + + return GLTF_LoadModel(mod, json, jsonlen, bin, binlen); + } + return false; } qboolean Plug_GLTF_Init(void) @@ -2771,11 +3538,19 @@ qboolean Plug_GLTF_Init(void) modfuncs = plugfuncs->GetEngineInterface(plugmodfuncs_name, sizeof(*modfuncs)); if (modfuncs && modfuncs->version < MODPLUGFUNCS_VERSION) modfuncs = NULL; + mod_gltf_scale = cvarfuncs->GetNVFDG("mod_gltf_scale", "30", CVAR_RENDERERLATCH, "This defines the number of units per metre, in order to correctly load standard-scale gltf models.", "GLTF Models"); + mod_gltf_fixbuggyanims = cvarfuncs->GetNVFDG("mod_gltf_fixbuggyanims", "1", CVAR_RENDERERLATCH, "Work around buggy exporters by merging animations that affect only a single bone.", "GLTF Models"); +#ifdef IQMTOOL + mod_gltf_privatematerials = cvarfuncs->GetNVFDG("mod_gltf_privatematerials", "0", CVAR_RENDERERLATCH, "Add the model path to material names, to isolate them between different models.", "GLTF Models"); +#else + mod_gltf_privatematerials = cvarfuncs->GetNVFDG("mod_gltf_privatematerials", "1", CVAR_RENDERERLATCH, "Add the model path to material names, to isolate them between different models.", "GLTF Models"); +#endif + mod_gltf_ignoretechniques = cvarfuncs->GetNVFDG("mod_gltf_ignoretechniques", "1", CVAR_RENDERERLATCH, "Ignore the gltf1 model-specific glsl. This is enabled by default because it just doesn't work very well.", "GLTF Models"); 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); + modfuncs->RegisterModelFormatText("glTF models (glTF)", ".gltf", Mod_LoadGLTFModel); + modfuncs->RegisterModelFormatMagic("glTF models (glb)", (('F'<<24)+('T'<<16)+('l'<<8)+'g'), Mod_LoadGLBModel); return true; } return false; diff --git a/plugins/openxr.c b/plugins/openxr.c index ad3e7cac..bd8998d4 100644 --- a/plugins/openxr.c +++ b/plugins/openxr.c @@ -388,7 +388,7 @@ static qboolean XR_PreInit(vrsetup_t *qreqs) } } - xr.instance = NULL; + xr.instance = XR_NULL_HANDLE; //create our instance { diff --git a/quakec/menusys/menusys/mitem_grid.qc b/quakec/menusys/menusys/mitem_grid.qc index 4e9a2100..4295615f 100644 --- a/quakec/menusys/menusys/mitem_grid.qc +++ b/quakec/menusys/menusys/mitem_grid.qc @@ -156,6 +156,7 @@ void(vector pos) mitem_grid::item_draw = clientpos = pos; clientsize = this.item_size; +/* if (vslider) { //scroll+shrink the client area to fit the slider on it. @@ -185,10 +186,12 @@ void(vector pos) mitem_grid::item_draw = if (pos_x+clientsize_x < ui.drawrectmax[0]) ui.drawrectmax[0] = pos_x+clientsize_x; if (pos_y+clientsize_y < ui.drawrectmax[1]) - ui.drawrectmax[1] = pos_y+clientsize[1]; - if (ui.drawrectmax[0] > ui.drawrectmin[0] && ui.drawrectmax[1] > ui.drawrectmin[1]) + ui.drawrectmax[1] = pos_y+clientsize[1];*/ +// if (ui.drawrectmax[0] > ui.drawrectmin[0] && ui.drawrectmax[1] > ui.drawrectmin[1]) { ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]); +//ui.setcliparea(0, 0, 0, 0); + float c = max(0,floor((ui.drawrectmin[1]-clientpos_y)/item_scale)); //calculate how many we can skip for (clientpos_y += item_scale * c; c < grid_numchildren; c++, clientpos_y += item_scale) { @@ -204,7 +207,7 @@ void(vector pos) mitem_grid::item_draw = vslider.item_draw(pos + [clientsize[0], 0]); } } - ui.drawrectmin = omin; - ui.drawrectmax = omax; +// ui.drawrectmin = omin; +// ui.drawrectmax = omax; }; #endif