Improve gltf->iqm frame timings, update iqmtool's help text.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5905 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-06-21 13:46:31 +00:00
parent 26d9d89f24
commit ac427ff76c
3 changed files with 196 additions and 87 deletions

View file

@ -2037,7 +2037,7 @@ m-dbg:
m-profile:
@$(MAKE) m-tmp TYPE=_clsv-profile OUT_DIR="$(PROFILE_DIR)/$(NCDIRPREFIX)$(MB_DIR)"
.PHONY: m-tmp mcl-tmp mingl-tmp glcl-tmp gl-tmp sv-tmp _clsv-dbg _clsv-rel _cl-dbg _cl-rel _out-rel _out-dbg reldir debugdir makelibs wel-rel web-dbg httpserver iqm imgtool
.PHONY: m-tmp mcl-tmp mingl-tmp glcl-tmp gl-tmp sv-tmp _clsv-dbg _clsv-rel _cl-dbg _cl-rel _out-rel _out-dbg reldir debugdir makelibs wel-rel web-dbg httpserver iqmtool imgtool
_qcc-tmp: $(REQDIR)
@ -2381,15 +2381,16 @@ $(RELEASE_DIR)/httpserver$(BITS)$(EXEPOSTFIX): $(HTTP_OBJECTS)
httpserver: $(RELEASE_DIR)/httpserver$(BITS)$(EXEPOSTFIX)
IQM_OBJECTS=../iqm/iqm.cpp ../imgtool.c client/image.c ../plugins/models/gltf.c
$(RELEASE_DIR)/iqm$(BITS)$(EXEPOSTFIX): $(IQM_OBJECTS)
$(RELEASE_DIR)/iqmtool$(BITS)$(EXEPOSTFIX): $(IQM_OBJECTS)
ifdef windir
$(CC) -o $@ $(IQM_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver $(ALL_CFLAGS) $(CLIENTLIBFLAGS) -DIQMTOOL $(BASELDFLAGS) $(CLIENTLDDEPS) -static -lstdc++ -lm -Os
$(CC) -o $@ $(IQM_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver $(ALL_CFLAGS) $(CLIENTLIBFLAGS) -DIQMTOOL $(BASELDFLAGS) $(CLIENTLDDEPS) --static -static-libgcc -static-libstdc++ -lstdc++ -lm -Os
else
$(CC) -o $@ $(IQM_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver $(ALL_CFLAGS) $(CLIENTLIBFLAGS) -DIQMTOOL $(BASELDFLAGS) $(CLIENTLDDEPS) -lstdc++ -lm -Os
$(CC) -o $@ $(IQM_OBJECTS) -Icommon -Iclient -Iqclib -Igl -Iserver $(ALL_CFLAGS) $(CLIENTLIBFLAGS) -DIQMTOOL $(BASELDFLAGS) $(CLIENTLDDEPS) --static -static-libgcc -static-libstdc++ -lstdc++ -lm -Os
endif
iqm-rel: $(RELEASE_DIR)/iqm$(BITS)$(EXEPOSTFIX)
iqm-rel: $(RELEASE_DIR)/iqmtool$(BITS)$(EXEPOSTFIX)
iqmtool-rel: $(RELEASE_DIR)/iqmtool$(BITS)$(EXEPOSTFIX)
iqm: iqm-rel
iqmtool: iqm-rel
iqmtool: iqmtool-rel
IMGTOOL_OBJECTS=../imgtool.c client/image.c
$(RELEASE_DIR)/imgtool$(BITS)$(EXEPOSTFIX): $(IMGTOOL_OBJECTS)

View file

@ -1398,7 +1398,10 @@ 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 (%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]);
if (parent == -1)
conoutf("%sbone %i:\tname=\"%s\"\tparent=NONE, group=%i (%f %f %f)", prefix, i+1, &stringdata[joints[i].name], joints[i].group, joints[i].pos[0], joints[i].pos[1], joints[i].pos[2]);
else
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);
}
}
@ -1528,7 +1531,7 @@ void makeanims(const filespec &spec)
{
anim &a = anims.add();
char nname[256];
formatstring(nname, "%s%i", ea.name, j+1-ea.startframe);
formatstring(nname, "%s_%i", ea.name, j+1-ea.startframe);
a.name = sharestring(nname);
a.firstframe = frames.length();
a.numframes = 0;
@ -1538,8 +1541,8 @@ void makeanims(const filespec &spec)
int offset = eframes[j], range = (eframes.inrange(j+1) ? eframes[j+1] : eposes.length()) - offset;
if(range <= 0) continue;
frame &fr = frames.add();
loopk(min(range, ejoints.length())) fr.pose.add(frame::framepose(ejoints[i], eposes[offset + k]));
loopk(max(ejoints.length() - range, 0)) fr.pose.add(frame::framepose(ejoints[i], transform(Vec3(0, 0, 0), Quat(0, 0, 0, 1), Vec3(1, 1, 1))));
loopk(min(range, ejoints.length())) fr.pose.add(frame::framepose(ejoints[k], eposes[offset + k]));
loopk(max(ejoints.length() - range, 0)) fr.pose.add(frame::framepose(ejoints[k], transform(Vec3(0, 0, 0), Quat(0, 0, 0, 1), Vec3(1, 1, 1))));
a.numframes++;
printlastanim();
@ -3874,11 +3877,29 @@ bool loadfbx(const char *filename, const filespec &spec)
namespace fte
{
static vector<cvar_t*> cvars;
static plugfsfuncs_t cppfsfuncs;
static plugmodfuncs_t cppmodfuncs;
static plugcorefuncs_t cppplugfuncs;
static plugcvarfuncs_t cppcvarfuncs;
static cvar_t *Cvar_Create(const char *name, const char *defaultval, unsigned int flags, const char *description, const char *groupname)
{ //could maybe fill with environment settings perhaps? yuck.
cvar_t *v = NULL;
for (int i = 0; i < cvars.length(); i++)
if (!strcmp(cvars[i]->name, name))
return cvars[i];
if (!v)
{
v = cvars.add() = new cvar_t();
v->name = strdup(name);
v->string = strdup(defaultval);
v->value = atof(v->string);
v->ival = atoi(v->string);
}
return v;
};
static void SetupFTEPluginFuncs(void)
{
cppfsfuncs.OpenVFS = [](const char *filename, const char *mode, enum fs_relative relativeto)
@ -4019,15 +4040,7 @@ namespace fte
return ret;
};
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;
};
cppcvarfuncs.GetNVFDG = Cvar_Create;
}
extern "C"
{ //our plugin-style stuff has a few external dependancies not provided via pointers...
@ -4164,6 +4177,7 @@ namespace fte
a.startframe = firstframe;
a.fps = anim.rate;
a.flags = anim.loop?IQM_LOOP:0;
a.flags |= spec.flags;
a.endframe = eframes.length();
}
#endif
@ -4232,6 +4246,9 @@ namespace fte
else
materialname = surf->surfacename;
if (surf->nummorphs)
printf("Morph targets on input surface \"%s\" \"%s\" cannot be supported\n", surf->surfacename, materialname);
emesh mesh(surf->surfacename, materialname, etriangles.length());
//add in some extra surface properties.
@ -5313,70 +5330,114 @@ static bool writemd3(const char *filename)
return false;
}
void help(bool exitstatus = EXIT_SUCCESS)
static void help(bool exitstatus, bool fullhelp)
{
fprintf(exitstatus != EXIT_SUCCESS ? stderr : stdout,
"-- FTE's Fork of Lee Salzman's iqm exporter --\n"
"Usage:\n"
"\n"
"./iqm cmdfile.cmd\n"
"./iqm [options] output.iqm mesh.iqe anim1.iqe ... animN.iqe\n"
"./iqm [options] output.iqm mesh.md5mesh anim1.md5anim ... animN.md5anim\n"
"./iqm [options] output.iqm mesh.smd anim1.smd ... animN.smd\n"
"./iqm [options] output.iqm mesh.fbx anim1.fbx ... animN.fbx\n"
"./iqm [options] output.iqm mesh.obj\n"
"./iqmtool cmdfile.cmd\n"
"./iqmtool [options] output.iqm mesh.iqe anim1.iqe ... animN.iqe\n"
"./iqmtool [options] output.iqm mesh.md5mesh anim1.md5anim ... animN.md5anim\n"
"./iqmtool [options] output.iqm mesh.smd anim1.smd ... animN.smd\n"
"./iqmtool [options] output.iqm mesh.fbx anim1.fbx ... animN.fbx\n"
"./iqmtool [options] output.iqm mesh.obj\n"
"./iqmtool [options] output.iqm source.gltf\n"
"\n"
"Basic commandline options:\n"
" --help Show full help.\n"
" -v Verbose\n"
" -q Quiet operation\n"
" -n Disable output of fte's iqm extensions\n"
);
if (fullhelp)
fprintf(exitstatus != EXIT_SUCCESS ? stderr : stdout,
"\n"
"For certain formats, IQE, OBJ, and FBX, it is possible to combine multiple mesh\n"
"files of the exact same vertex layout and skeleton by supplying them as\n"
"\"mesh1.iqe,mesh2.iqe,mesh3.iqe\", that is, a comma-separated list of the mesh\n"
"files (with no spaces) in place of the usual mesh filename.\n"
"\n"
"Options can be any of the following command-line switches:\n"
"Legacy commandline options that affect mesh import for the next file:\n"
"\n"
" -s N\n"
" --scale N\n"
" Sets the output scale to N (float).\n"
" -s N Sets the output scale to N (float).\n"
" --meshtrans Z\n"
" --meshtrans X,Y,Z Translates a mesh by X,Y,Z (floats).\n"
" This does not affect the skeleton.\n"
" -j\n"
" --forcejoints Forces the exporting of joint information in animation"
" files without meshes.\n"
"\n"
" --meshtrans Z\n"
" --meshtrans X,Y,Z\n"
" Translates a mesh by X,Y,Z (floats). This does not affect the skeleton.\n"
"Legacy commandline options that affect the following animation file:\n"
"\n"
" -j\n"
" --forcejoints\n"
" Forces the exporting of joint information in animation files without\n"
" meshes.\n"
"\n"
" -q\n"
" Quiet. Only display warnings or errors.\n"
"\n"
" -v\n"
" Verbose. Print lots of extra info.\n"
"\n"
" -n\n"
" No Extensions. Disables the use of fte-specific iqm extensions.\n"
"\n"
"Each animation file can be preceded by any combination of the following command-\n"
"line switches:\n"
"\n"
" --name A\n"
" Sets the name of the animation to A.\n"
" --fps N\n"
" Sets the FPS of the animation to N (float).\n"
" --loop\n"
" Sets the loop flag for the animation.\n"
" --start N\n"
" Sets the first frame of the animation to N (integer).\n"
" --end N\n"
" Sets the last frame of the animation to N (integer).\n"
" --zup\n"
" Source model is in quake's orientation.\n"
" --name A Sets the name of the animation to A.\n"
" --fps N Sets the FPS of the animation to N (float).\n"
" --loop Sets the loop flag for the animation.\n"
" --start N Sets the first frame of the animation to N (integer).\n"
" --end N Sets the last frame of the animation to N (integer).\n"
" --zup Source model is in quake's orientation.\n"
"\n"
"You can supply either a mesh file, animation files, or both.\n"
"Note that if an input mesh file is supplied, it must come before the animation\n"
"files in the file list.\n"
"The output IQM file will contain the supplied mesh and any supplied animations.\n"
"If no mesh is provided,the IQM file will simply contain the supplied animations.\n"
);
"If no mesh is provided,the IQM file will simply contain the supplied animations\n"
"\n"
"Command script commands:\n"
" hitbox BODYNUM BONENAME x y z x y z\n"
" Attaches a hitbox surface around the specified bone\n"
" (in base pose). Gamecode knows which part of the model\n"
" was hit via the body value.\n"
" exec FILENAME Reads additional commands from the specified file.\n"
" Handy for shared animations.\n"
" modelflags FLAGS Specifies the model's flags, mostly for trail effects.\n"
" Known values are rocket, grenade, gib, rotate, tracer1,\n"
" zomgib, tracer2, tracer3, or 0xXXXXXXXX\n"
" mesh NAME MESHPROPS Overrides properties for a specific source surface.\n"
" bone NAME BONEPROPS Overrides properties for a specific bone.\n"
" ANIMPROPS Provides default values for following imported files.\n"
" import FILENAME [PROPS]\n"
" Loads the specified file, with per-file properties.\n"
" output FILENAME Specifies the iqm filename to write.\n"
" output_mdl FILENAME Specifies the (q1) mdl filename to write.\n"
"\n"
"Bone Properties:\n"
" rename NEWNAME Renames the bone accordingly.\n"
" group GROUPID Inherited from parents this allows resorting bones.\n"
"\n"
"Mesh Properties:\n"
" contents a[,b,c] Override surface content values for collisons\n"
" Accepted values are empty, solid, lava, slime, water,\n"
" fluid, fte_ladder, playerclip, monsterclip, body,\n"
" corpse, q2_ladder, fte_sky, q3_nodrop,\n"
" or 0xXXXXXXXX for custom values.\n"
" surfaceflags a[,b] Specifies explicit surface flags values.\n"
" Accepted values are nodraw, or custom 0xXXXXXXXX values.\n"
" body BODYNUM Specifies mod-specific body numbers.\n"
" geomset GROUP IDX Controls which geomset this surface is part of\n"
" lodrange min max Specifies a distance range within which to draw this lod\n"
"\n"
"Anim Properties:\n"
" name NAME Defines the output's name for this animation.\n"
" fps RATE Controls the playback rate.\n"
" loop Forces the animation(s) to loop.\n"
" clamp Forces the animation(s) to not loop.\n"
" unpack Extract each pose into its own single-pose 'animation'.\n"
" pack Undoes the effect of 'unpack'.\n"
" nomesh 1 Skips importing of meshes, getting animations only.\n"
" noanim 1 Skips importing of animations, for mesh import only.\n"
" materialprefix PRE Prefixes material names with the specified string.\n"
" ignoresurfname 1 Ignores source surface names.\n"
" start FRAME The Imported animation starts on this frame...\n"
" end FRAME ... and ends on this one.\n"
" rotate X Y Z Rotates the input model by the specified angles.\n"
" scale FACTOR Scales the input model by some value\n"
" origin X Y Z Translates the input model.\n"
" event reset Discard previously specified events\n"
" event [ANIMIDX:]TIME CODE \"VALUE\"\n"
" Inserts a model event into the relevant animation index.\n"
);
exit(exitstatus);
}
@ -5778,7 +5839,7 @@ void parsecommands(char *filename, const char *outfiles[countof(outputtypes)], v
int main(int argc, char **argv)
{
if(argc <= 1) help(EXIT_FAILURE);
if(argc <= 1) help(EXIT_FAILURE, false);
vector<filespec> infiles;
vector<hitbox> hitboxes;
@ -5790,11 +5851,13 @@ int main(int argc, char **argv)
{
if(argv[i][1] == '-')
{
if(!strcasecmp(&argv[i][2], "cmd")) { if(i + 1 < argc) parsecommands(argv[++i], outfiles, infiles, hitboxes); }
if(!strcasecmp(&argv[i][2], "set")) { if(i + 2 < argc) fte::Cvar_Create(argv[i+1], argv[i+2], 0, NULL, "cmdline");i+=2;}
else if(!strcasecmp(&argv[i][2], "cmd")) { if(i + 1 < argc) parsecommands(argv[++i], outfiles, infiles, hitboxes); }
else if(!strcasecmp(&argv[i][2], "noext")) noext = true;
else if(!strcasecmp(&argv[i][2], "fps")) { if(i + 1 < argc) inspec.fps = atof(argv[++i]); }
else if(!strcasecmp(&argv[i][2], "name")) { if(i + 1 < argc) inspec.name = argv[++i]; }
else if(!strcasecmp(&argv[i][2], "loop")) { inspec.flags |= IQM_LOOP; }
else if(!strcasecmp(&argv[i][2], "unpack")) { inspec.flags |= IQM_UNPACK; }
else if(!strcasecmp(&argv[i][2], "start")) { if(i + 1 < argc) inspec.startframe = max(atoi(argv[++i]), 0); }
else if(!strcasecmp(&argv[i][2], "end")) { if(i + 1 < argc) inspec.endframe = atoi(argv[++i]); }
else if(!strcasecmp(&argv[i][2], "scale")) { if(i + 1 < argc) inspec.scale = clamp(atof(argv[++i]), 1e-8, 1e8); }
@ -5802,7 +5865,7 @@ int main(int argc, char **argv)
else if(!strcasecmp(&argv[i][2], "yup")) inspec.rotate = Quat::fromangles(Vec3(0,-M_PI,0));
else if(!strcasecmp(&argv[i][2], "zup")) inspec.rotate = Quat::fromdegrees(Vec3(0,-90,-90));
else if(!strcasecmp(&argv[i][2], "ignoresurfname")) inspec.ignoresurfname = true;
else if(!strcasecmp(&argv[i][2], "help")) help();
else if(!strcasecmp(&argv[i][2], "help")) help(EXIT_SUCCESS,true);
else if(!strcasecmp(&argv[i][2], "forcejoints")) forcejoints = true;
else if(!strcasecmp(&argv[i][2], "meshtrans"))
{
@ -5815,7 +5878,7 @@ int main(int argc, char **argv)
else switch(argv[i][1])
{
case 'h':
help();
help(EXIT_SUCCESS,true);
break;
case 's':
if(i + 1 < argc) gscale = clamp(atof(argv[++i]), 1e-8, 1e8);
@ -5934,9 +5997,12 @@ int main(int argc, char **argv)
calcanimdata();
conoutf("bone list:");
printbones();
// printbonelist();
if (!quiet)
{
conoutf("bone list:");
printbones();
// printbonelist();
}
if (!quiet)
conoutf("");

View file

@ -10,6 +10,9 @@
#ifdef IQMTOOL
#define Con_Printf printf
#define Con_DPrintf printf
#undef CON_WARNING
#define CON_WARNING
#endif
#ifdef SKELETALMODELS
@ -49,10 +52,12 @@
static plugmodfuncs_t *modfuncs;
static plugfsfuncs_t *filefuncs;
static cvar_t *mod_gltf_loop;
static cvar_t *mod_gltf_scale;
static cvar_t *mod_gltf_fixbuggyanims;
static cvar_t *mod_gltf_privatematerials;
static cvar_t *mod_gltf_ignoretechniques;
static cvar_t *mod_gltf_standardorientation;
#include <stdarg.h>
void VARGS Q_snprintfcat (char *dest, size_t size, const char *fmt, ...)
@ -2712,7 +2717,7 @@ static qboolean GLTF_ProcessNode(gltf_t *gltf, json_t *nodeid, double pmatrix[16
skinmatrix[i] = JSON_GetIndexedFloat(bdsm, i, ((i%5)==0)?1.0:0.0);
}
}
JSON_FlagAsUsed(node, "name");
JSON_FlagAsUsed(skin, "name");
}
if (gltf->ver <= 1)
@ -3127,10 +3132,11 @@ 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];
float toquake[12]={
0,0,mod_gltf_scale->value, 0,
mod_gltf_scale->value,0,0, 0,
0,mod_gltf_scale->value,0, 0};
float toquake[12]={0};
if (mod_gltf_standardorientation->ival)
toquake[2] = toquake[4] = toquake[9] = mod_gltf_scale->value;
else
toquake[0] = toquake[5] = toquake[10] = mod_gltf_scale->value;
memcpy(fnar, bonematrix, sizeof(fnar));
modfuncs->ConcatTransforms((void*)toquake, (void*)fnar, (void*)bonematrix);
}
@ -3250,11 +3256,16 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize,
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] = mod_gltf_scale->value; rootmatrix[15] = 1;
#else
rootmatrix[0] = rootmatrix[5] = rootmatrix[10] = 1; rootmatrix[15] = 1;
#endif
if (mod_gltf_standardorientation->ival) //transform from gltf to quake. mostly only needed for the base pose.
{
rootmatrix[2] = rootmatrix[4] = rootmatrix[9] = mod_gltf_scale->value;
rootmatrix[15] = 1;
}
else //identity orientation that violates gltf standards.
{
rootmatrix[0] = rootmatrix[5] = rootmatrix[10] = mod_gltf_scale->value;
rootmatrix[15] = 1;
}
n = JSON_FindChild(gltf.r, "nodes");
for (j = 0; ; j++)
@ -3354,7 +3365,7 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize,
else
Q_snprintf(fg->name, sizeof(fg->name), "anim%u", (unsigned int)k);
}
fg->loop = true;
fg->loop = !!mod_gltf_loop->ival;
fg->skeltype = SKEL_RELATIVE;
for(chan = JSON_FindIndexedChild(anim, "channels", 0); chan; chan = chan->sibling)
{
@ -3414,11 +3425,10 @@ static qboolean GLTF_LoadModel(struct model_s *mod, char *json, size_t jsonsize,
if (!maxtime)
maxtime = 1.0/30; //some stuff doesn't like 0-length animations. divisions by 0 are not nice.
a->duration = maxtime;
//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).
if (maxtime&&fg->numposes>1)
fg->rate = (fg->numposes-1)/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;
@ -3460,11 +3470,40 @@ 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, mod_gltf_scale->value, mod->mins);
VectorScale(mod->maxs, mod_gltf_scale->value, mod->maxs);
if (!mod->meshinfo)
if (!mod->meshinfo && numframegroups>0)
Con_Printf("%s: Doesn't contain any meshes...\n", mod->name);
else if (!mod_gltf_loop->ival)
{ //gltf doesn't specify if things loop or not.
//our lerping is between previous+next frames, we don't actually handle wrapping here, and our 'numframes' is entirely arbitrary.
//if the last frame and first frame are identical then its safe to say that it MAY loop, otherwise there'll be a judder when trying to do so.
//and if it does seem to loop okay, don't hold that last frame for an extra 1/fps when the repeats starts, just go snap straight to the first frame.
float *first = malloc(sizeof(*first)*12*gltf.numbones*2);
float *last = first+12*gltf.numbones;
galiasanimation_t *fg;
struct galiasanimation_gltf_s *a;
surf = mod->meshinfo;
for (k = 0; k < numframegroups; k++)
{
fg = &framegroups[k];
a = fg->boneofs;
fg->GetRawBones(surf, fg, 0, first, gltf.numbones);
fg->GetRawBones(surf, fg, a->duration, last, gltf.numbones); //should fall on an exact frame.
for (j = 0; j < 12*gltf.numbones; j++)
if (first[j] != last[j])
break;
if (j == 12*gltf.numbones)
{
fg->loop = true;
fg->numposes--;
}
}
free(first);
}
JSON_WarnUnused(gltf.r, &gltf.warnlimit);
}
else
@ -3541,11 +3580,14 @@ qboolean Plug_GLTF_Init(void)
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_loop = cvarfuncs->GetNVFDG("mod_gltf_loop", "0", CVAR_RENDERERLATCH, "This forces all gltf models to loop, instead of only when the last frame matches the first.", "GLTF Models");
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_loop = cvarfuncs->GetNVFDG("mod_gltf_loop", "1", CVAR_RENDERERLATCH, "This forces all gltf models to loop, instead of only when the last frame matches the first.", "GLTF Models");
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");
mod_gltf_ignoretechniques = cvarfuncs->GetNVFDG("mod_gltf_ignoretechniques", "1", CVAR_RENDERERLATCH, "Ignore the GLTF-1 model-specific glsl. This is enabled by default because it just doesn't work very well.", "GLTF Models");
mod_gltf_standardorientation = cvarfuncs->GetNVFDG("mod_gltf_standardorientation", "1", CVAR_RENDERERLATCH, "Transform GLTF files from standard y-up to Quake's z-up orientation. Set to 0 to violate the GLTF specification.", "GLTF Models");
if (modfuncs && filefuncs)
{