#ifndef GLQUAKE #define GLQUAKE //this is shit. #endif #include "quakedef.h" #include "../plugin.h" #include "com_mesh.h" extern modplugfuncs_t *modfuncs; #define GLTFMODELS #ifdef GLTFMODELS typedef struct json_s { const char *bodystart; const char *bodyend; struct json_s *parent; struct json_s *child; struct json_s *sibling; struct json_s **childlink; qboolean used; //set to say when something actually read/walked it, so we can flag unsupported things gracefully char name[1]; } json_t; //node destruction static void JSON_Orphan(json_t *t) { if (t->parent) { json_t *p = t->parent, **l = &p->child; while (*l) { if (*l == t) { *l = t->sibling; if (*l) p->childlink = l; break; } l = &(*l)->sibling; } t->parent = NULL; t->sibling = NULL; } } static void JSON_Destroy(json_t *t) { while(t->child) JSON_Destroy(t->child); JSON_Orphan(t); free(t); } //node creation static json_t *JSON_CreateNode(json_t *parent, const char *namestart, const char *nameend, const char *bodystart, const char *bodyend) { json_t *j; qboolean dupbody = false; if (namestart && !nameend) nameend = namestart+strlen(namestart); if (bodystart && !bodyend) { dupbody = true; bodyend = bodystart+strlen(bodystart); } j = malloc(sizeof(*j) + nameend-namestart + (dupbody?1+bodyend-bodystart:0)); memcpy(j->name, namestart, nameend-namestart); j->name[nameend-namestart] = 0; j->bodystart = bodystart; j->bodyend = bodyend; j->child = NULL; j->sibling = NULL; j->childlink = &j->child; j->parent = parent; if (parent) { *parent->childlink = j; parent->childlink = &j->sibling; j->used = false; } else j->used = true; if (dupbody) { char *bod = j->name + (nameend-namestart)+1; j->bodystart = bod; j->bodyend = j->bodystart + (bodyend-bodystart); memcpy(bod, bodystart, bodyend-bodystart); bod[bodyend-bodystart] = 0; } return j; } //node parsing static void JSON_SkipWhite(const char *msg, int *pos, int max) { while (*pos < max && ( msg[*pos] == ' ' || msg[*pos] == '\t' || msg[*pos] == '\r' || msg[*pos] == '\n' )) *pos+=1; } static qboolean JSON_ParseString(char const*msg, int *pos, int max, char const**start, char const** end) { if (*pos < max && msg[*pos] == '\"') { *pos+=1; *start = msg+*pos; while (*pos < max && msg[*pos] != '\"') *pos+=1; if (*pos < max && msg[*pos] == '\"') { *end = msg+*pos; *pos+=1; return true; } } else { *start = msg+*pos; while (*pos < max && msg[*pos] != ' ' && msg[*pos] != '\t' && msg[*pos] != '\r' && msg[*pos] != '\n' && msg[*pos] != ':' && msg[*pos] != ',' && msg[*pos] != '}' && msg[*pos] != '{' && msg[*pos] != '[' && msg[*pos] != ']') { *pos+=1; } *end = msg+*pos; if (*start != *end) return true; } *end = *start; return false; } static json_t *JSON_Parse(json_t *t, const char *namestart, const char *nameend, const char *json, int *jsonpos, int jsonlen) { const char *childstart, *childend; JSON_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen) { if (json[*jsonpos] == '{') { *jsonpos+=1; JSON_SkipWhite(json, jsonpos, jsonlen); t = JSON_CreateNode(t, namestart, nameend, NULL, NULL); while (*jsonpos < jsonlen && json[*jsonpos] == '\"') { if (!JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) break; JSON_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen && json[*jsonpos] == ':') { *jsonpos+=1; if (!JSON_Parse(t, childstart, childend, json, jsonpos, jsonlen)) break; } JSON_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen && json[*jsonpos] == ',') { *jsonpos+=1; JSON_SkipWhite(json, jsonpos, jsonlen); continue; } break; } if (*jsonpos < jsonlen && json[*jsonpos] == '}') { *jsonpos+=1; return t; } JSON_Destroy(t); } else if (json[*jsonpos] == '[') { char idxname[MAX_QPATH]; unsigned int idx = 0; *jsonpos+=1; JSON_SkipWhite(json, jsonpos, jsonlen); t = JSON_CreateNode(t, namestart, nameend, NULL, NULL); for(;;) { Q_snprintf(idxname, sizeof(idxname), "%u", idx++); if (!JSON_Parse(t, idxname, NULL, json, jsonpos, jsonlen)) break; if (*jsonpos < jsonlen && json[*jsonpos] == ',') { *jsonpos+=1; JSON_SkipWhite(json, jsonpos, jsonlen); continue; } break; } JSON_SkipWhite(json, jsonpos, jsonlen); if (*jsonpos < jsonlen && json[*jsonpos] == ']') { *jsonpos+=1; return t; } JSON_Destroy(t); } else { if (JSON_ParseString(json, jsonpos, jsonlen, &childstart, &childend)) return JSON_CreateNode(t, namestart, nameend, childstart, childend); } } return NULL; } static json_t *JSON_FindChild(json_t *t, const char *child) { if (t) { size_t nl; const char *dot = strchr(child, '.'); if (dot) nl = dot-child; else nl = strlen(child); for (t = t->child; t; t = t->sibling) { if (!strncmp(t->name, child, nl) && (t->name[nl] == '.' || !t->name[nl])) { child+=nl; t->used = true; if (*child == '.') return JSON_FindChild(t, child+1); if (!*child) return t; break; } } } return NULL; } static json_t *JSON_FindIndexedChild(json_t *t, const char *child, unsigned int idx) { char idxname[MAX_QPATH]; if (child) Q_snprintf(idxname, sizeof(idxname), "%s.%u", child, idx); else Q_snprintf(idxname, sizeof(idxname), "%u", idx); return JSON_FindChild(t, idxname); } static qboolean JSON_Equals(json_t *t, const char *child, const char *expected) { if (child) t = JSON_FindChild(t, child); if (t && t->bodyend-t->bodystart == strlen(expected)) return !strncmp(t->bodystart, expected, t->bodyend-t->bodystart); return false; } #include static qintptr_t JSON_GetInteger(json_t *t, const char *child, int fallback) { if (child) t = JSON_FindChild(t, child); if (t) { //copy it to another buffer. can probably skip that tbh. char tmp[MAX_QPATH]; size_t l = t->bodyend-t->bodystart; if (l > MAX_QPATH-1) l = MAX_QPATH-1; memcpy(tmp, t->bodystart, l); tmp[l] = 0; return (qintptr_t)strtoll(tmp, NULL, 0); } return fallback; } 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); } static double JSON_GetFloat(json_t *t, const char *child, double fallback) { if (child) t = JSON_FindChild(t, child); if (t) { //copy it to another buffer. can probably skip that tbh. char tmp[MAX_QPATH]; size_t l = t->bodyend-t->bodystart; if (l > MAX_QPATH-1) l = MAX_QPATH-1; memcpy(tmp, t->bodystart, l); tmp[l] = 0; return atof(tmp); } return fallback; } static double JSON_GetIndexedFloat(json_t *t, unsigned int idx, double fallback) { char idxname[MAX_QPATH]; Q_snprintf(idxname, sizeof(idxname), "%u", idx); return JSON_GetFloat(t, idxname, fallback); } static void JSON_GetPath(json_t *t, qboolean ignoreroot, char *buffer, size_t buffersize) { if (t->parent && (t->parent->parent || !ignoreroot)) { JSON_GetPath(t->parent, ignoreroot, buffer, buffersize); Q_strlcat(buffer, ".", buffersize); } Q_strlcat(buffer, t->name, buffersize); } static void JSON_WarnUnused(json_t *t, int *warnlimit) { if (!t) return; if (t->used) { for (t = t->child; t; t = t->sibling) JSON_WarnUnused(t, warnlimit); } else { char path[8192]; *path = 0; JSON_GetPath(t, false, path, sizeof(path)); if ((*warnlimit) --> 0) Con_DPrintf(CON_WARNING"GLTF property %s was not used\n", path); } } static void JSON_FlagAsUsed(json_t *t, const char *child) { if (child) { t = JSON_FindChild(t, child); if (!t) return; } t->used = true; for (t = t->child; t; t = t->sibling) JSON_FlagAsUsed(t, NULL); } static void JSON_WarnIfChild(json_t *t, const char *child, int *warnlimit) { t = JSON_FindChild(t, child); if (t) { char path[8192]; *path = 0; JSON_GetPath(t, false, path, sizeof(path)); if ((*warnlimit) --> 0) Con_Printf(CON_WARNING"Standard feature %s is not supported\n", path); JSON_FlagAsUsed(t, NULL); } } static unsigned int FromBase64(char c) { if (c >= 'A' && c <= 'Z') return 0+(c-'A'); if (c >= 'a' && c <= 'z') return 26+(c-'a'); if (c >= '0' && c <= '9') return 52+(c-'0'); if (c == '+') return 62; if (c == '/') return 63; return 64; } //fancy parsing of content static void *JSON_MallocDataURI(json_t *t, size_t *outlen) { size_t bl = t->bodyend-t->bodystart; if (bl >= 5 && !strncmp(t->bodystart, "data:", 5)) { const char *mimestart = t->bodystart+5; const char *mimeend; const char *encstart; const char *encend; const char *in; char *out, *r; for (mimeend = mimestart; *mimeend && mimeend < t->bodyend; mimeend++) { if (*mimeend == ';') //start of encoding break; if (*mimeend == ',') //start of data break; } if (*mimeend == ';') { for (encend = encstart = mimeend+1; *encend && encend < t->bodyend; encend++) { if (*encend == ',') //start of data break; } } else encstart = encend = mimeend; if (*encend == ',' && encend < t->bodyend) { in = encend+1; if (encend-encstart == 6 && !strncmp(encstart, "base64", 6)) { //base64 r = out = malloc(((t->bodyend-in)*3)/4 + 1); while (in+3 < t->bodyend) { unsigned int c1, c2, c3, c4; c1 = FromBase64(*in++); c2 = FromBase64(*in++); if (c1 >= 64 || c2 >= 64) break; *out++ = (c1<<2) | (c2>>4); c3 = FromBase64(*in++); if (c3 >= 64) break; *out++ = (c2<<4) | (c3>>2); c4 = FromBase64(*in++); if (c3 >= 64) break; *out++ = (c3<<6) | (c4>>0); } *outlen = out-r; *out = 0; return r; } else if (encend == encstart) { //url encoding. yuck, sod off. } } } return NULL; } static size_t JSON_ReadBody(json_t *t, char *out, size_t outsize) { size_t bodysize; if (!t) { if (out) *out = 0; return 0; } if (out) { bodysize = t->bodyend-t->bodystart; if (bodysize > outsize-1) bodysize = outsize-1; memcpy(out, t->bodystart, bodysize); out[bodysize] = 0; } return t->bodyend-t->bodystart; } //glTF 1.0 and 2.0 differ in that 1 uses names and 2 uses indexes. There's also some significant differences with materials. //we only support 2.0 //FTE does not support articulated models. we might be able to convert them to skeletal though. //we don't support skeletal models either right now. //buffers are raw blobs that can come from multiple different sources struct gltf_buffer { qboolean loaded; qboolean malloced; void *data; size_t length; }; typedef struct gltf_s { struct model_s *mod; unsigned int numsurfaces; json_t *r; int bonemap[MAX_BONES]; //remap skinned bones. I hate that we have to do this. struct gltfbone_s { char name[32]; int parent; double amatrix[16]; double inverse[16]; struct { double rmatrix[16]; //gah double quat[4], scale[3], trans[3]; //annoying smeg } rel; struct { struct gltf_accessor *input; struct gltf_accessor *output; } *rot, *scale, *translation; } bones[MAX_BONES]; unsigned int numbones; int warnlimit; //don't spam warnings. this is a loader, not a spammer struct gltf_buffer buffers[64]; } gltf_t; static void GLTF_RelativePath(const char *base, const char *relative, char *out, size_t outsize) { size_t t; const char *sep; const char *end = base; if (*relative == '/') { relative++; } else { for (sep = end; *sep; sep++) { if (*sep == '/' || *sep == '\\') end = sep+1; } } while (!strncmp(relative, "../", 3)) { if (end > base) { end--; while (end > base) { end--; if (*end == '/' || *end == '\\') { relative += 3; end++; break; } } } else break; } outsize--; //for the null t = end-base; if (t > outsize) t = outsize; memcpy(out, base, t); out += t; outsize -= t; t = strlen(relative); if (t > outsize) t = outsize; memcpy(out, relative, t); out += t; outsize -= t; *out = 0; } static struct gltf_buffer *GLTF_GetBufferData(gltf_t *gltf, int bufferidx) { json_t *b = JSON_FindIndexedChild(gltf->r, "buffers", bufferidx); json_t *uri = JSON_FindChild(b, "uri"); size_t length = JSON_GetInteger(b, "byteLength", 0); struct gltf_buffer *out; // JSON_WarnIfChild(b, "name"); // JSON_WarnIfChild(b, "extensions"); // JSON_WarnIfChild(b, "extras"); if (bufferidx < 0 || bufferidx >= countof(gltf->buffers)) return NULL; out = &gltf->buffers[bufferidx]; //we may have been through here before... if (out->loaded) return out->data?out:NULL; out->loaded = true; if (uri) { out->malloced = true; out->data = JSON_MallocDataURI(uri, &out->length); if (!out->data) { //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 = modfuncs->OpenVFS(filename, "rb", FS_GAME); if (f) { out->length = VFS_GETLEN(f); out->length = min(out->length, length); out->data = malloc(length); VFS_READ(f, out->data, length); VFS_CLOSE(f); } } } return out->data?out:NULL; } //buffer views are aka VBOs. each has its own VBO data type (vbo/ebo), and can be uploaded as-is. struct gltf_bufferview { void *data; size_t length; int bytestride; }; static qboolean GLTF_GetBufferViewData(gltf_t *gltf, int bufferview, struct gltf_bufferview *view) { struct gltf_buffer *buf; json_t *bv = JSON_FindIndexedChild(gltf->r, "bufferViews", bufferview); size_t offset; if (!bv) return false; buf = GLTF_GetBufferData(gltf, JSON_GetInteger(bv, "buffer", 0)); if (!buf) return false; offset = JSON_GetInteger(bv, "byteOffset", 0); view->data = (char*)buf->data + offset; view->length = JSON_GetInteger(bv, "byteLength", 0); //required view->bytestride = 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"); return true; } //accessors are basically VAs blocks that refer inside a bufferview/VBO. struct gltf_accessor { void *data; size_t length; size_t bytestride; int componentType; //5120 BYTE, 5121 UNSIGNED_BYTE, 5122 SHORT, 5123 UNSIGNED_SHORT, 5125 UNSIGNED_INT, 5126 FLOAT qboolean normalized; int 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) { struct gltf_bufferview bv; json_t *a, *mins, *maxs; size_t offset; int j; memset(out, 0, sizeof(*out)); a = JSON_FindIndexedChild(gltf->r, "accessors", accessorid); if (!a) return false; if (!GLTF_GetBufferViewData(gltf, JSON_GetInteger(a, "bufferView", 0), &bv)) return false; offset = JSON_GetInteger(a, "byteOffset", 0); if (offset > bv.length) return false; out->length = bv.length - offset; 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); if (JSON_Equals(a, "type", "SCALAR")) out->type = (1<<8) | 1; else if (JSON_Equals(a, "type", "VEC2")) out->type = (1<<8) | 2; else if (JSON_Equals(a, "type", "VEC3")) out->type = (1<<8) | 3; else if (JSON_Equals(a, "type", "VEC4")) out->type = (1<<8) | 4; else if (JSON_Equals(a, "type", "MAT2")) out->type = (2<<8) | 2; else if (JSON_Equals(a, "type", "MAT3")) out->type = (3<<8) | 3; else if (JSON_Equals(a, "type", "MAT4")) out->type = (4<<8) | 4; else { if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"%s: glTF2 unsupported type\n", gltf->mod->name); out->type = 1; } if (!out->bytestride) { out->bytestride = (out->type & 0xff) * (out->type>>8); switch(out->componentType) { default: if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"GLTF_GetAccessor: %s: glTF2 unsupported componentType (%i)\n", gltf->mod->name, out->componentType); case 5120: //BYTE case 5121: //UNSIGNED_BYTE break; case 5122: //SHORT case 5123: //UNSIGNED_SHORT out->bytestride *= 2; break; case 5125: //UNSIGNED_INT case 5126: //FLOAT out->bytestride *= 4; break; } } mins = JSON_FindChild(a, "min"); maxs = JSON_FindChild(a, "max"); for (j = 0; j < (out->type>>8)*(out->type&0xff); j++) { //'must' be set in various situations. out->mins[j] = JSON_GetIndexedInteger(mins, j, 0); out->maxs[j] = JSON_GetIndexedInteger(maxs, j, 0); } // JSON_WarnIfChild(a, "sparse"); // JSON_WarnIfChild(a, "name"); // JSON_WarnIfChild(a, "extensions"); // JSON_WarnIfChild(a, "extras"); 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) { //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); char *in = a->data; size_t v, c; *sdir = os; *tdir = ot; if ((a->type&0xff) != 4) return; switch(a->componentType) { default: if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"GLTF_AccessorToTangents: %s: glTF2 unsupported componentType (%i)\n", gltf->mod->name, a->componentType); case 0: memset(os, 0, sizeof(*os) * outverts); memset(ot, 0, sizeof(*ot) * outverts); break; // case 5120: //BYTE // case 5121: //UNSIGNED_BYTE // case 5122: //SHORT // case 5123: //UNSIGNED_SHORT // case 5125: //UNSIGNED_INT case 5126: //FLOAT for (v = 0; v < outverts; v++) { for (c = 0; c < 3; c++) os[v][c] = ((float*)in)[c]; //bitangent = cross(normal, tangent.xyz) * tangent.w ot[v][0] = (norm[v][1]*os[v][2] - norm[v][2]*os[v][1]) * ((float*)in)[3]; ot[v][1] = (norm[v][2]*os[v][0] - norm[v][0]*os[v][2]) * ((float*)in)[3]; ot[v][2] = (norm[v][0]*os[v][1] - norm[v][1]*os[v][0]) * ((float*)in)[3]; in += a->bytestride; } break; } } static void *GLTF_AccessorToDataF(gltf_t *gltf, size_t outverts, unsigned int outcomponents, struct gltf_accessor *a) { float *ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts), *o; char *in = a->data; int c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; o = ret; switch(a->componentType) { default: if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"GLTF_AccessorToDataF: %s: glTF2 unsupported componentType (%i)\n", gltf->mod->name, a->componentType); case 0: memset(ret, 0, sizeof(*ret) * outcomponents * outverts); break; case 5120: //BYTE while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = max(-1.0, ((char*)in)[c] / 127.0); //negative values are larger, but we want to allow 1.0 for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; case 5121: //UNSIGNED_BYTE while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((unsigned char*)in)[c] / 255.0; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; case 5122: //SHORT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = max(-1.0, ((signed short*)in)[c] / 32767.0); //negative values are larger, but we want to allow 1.0 for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; case 5123: //UNSIGNED_SHORT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((unsigned short*)in)[c] / 65535.0; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; case 5125: //UNSIGNED_INT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((unsigned int*)in)[c] / (double)~0u; //stupid format to use. will be lossy. for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; case 5126: //FLOAT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((float*)in)[c]; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; } return ret; } static void *GLTF_AccessorToDataUB(gltf_t *gltf, size_t outverts, unsigned int outcomponents, struct gltf_accessor *a) { //only used for colour, with fallback to float, so only UNSIGNED_BYTE needs to work. unsigned char *ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts), *o; char *in = a->data; int c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; o = ret; switch(a->componentType) { default: if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"GLTF_AccessorToDataUB: %s: glTF2 unsupported componentType (%i)\n", gltf->mod->name, a->componentType); case 0: memset(ret, 0, sizeof(*ret) * outcomponents * outverts); break; // case 5120: //BYTE case 5121: //UNSIGNED_BYTE while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((unsigned char*)in)[c]; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break; // case 5122: //SHORT // case 5123: //UNSIGNED_SHORT // case 5125: //UNSIGNED_INT /* case 5126: //FLOAT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((float*)in)[c]; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break;*/ } return ret; } static void *GLTF_AccessorToDataBone(gltf_t *gltf, size_t outverts, struct gltf_accessor *a) { //input should only be ubytes||ushorts. const unsigned int outcomponents = 4; unsigned char *ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret) * outcomponents * outverts), *o; char *in = a->data; int c, ic = a->type&0xff; if (ic > outcomponents) ic = outcomponents; o = ret; switch(a->componentType) { default: if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"GLTF_AccessorToDataUB: %s: glTF2 unsupported componentType (%i)\n", gltf->mod->name, a->componentType); case 0: memset(ret, 0, sizeof(*ret) * outcomponents * outverts); break; // case 5120: //BYTE case 5121: //UNSIGNED_BYTE while(outverts --> 0) { unsigned char v; for (c = 0; c < ic; c++) { v = ((unsigned char*)in)[c]; o[c] = gltf->bonemap[v]; } for (; c < outcomponents; c++) o[c] = gltf->bonemap[0]; o += outcomponents; in += a->bytestride; } break; case 5122: //SHORT case 5123: //UNSIGNED_SHORT while(outverts --> 0) { unsigned short v; for (c = 0; c < ic; c++) { v = ((unsigned short*)in)[c]; if (v > 255) v = 0; o[c] = gltf->bonemap[v]; } for (; c < outcomponents; c++) o[c] = gltf->bonemap[0]; o += outcomponents; in += a->bytestride; } break; // case 5125: //UNSIGNED_INT /* case 5126: //FLOAT while(outverts --> 0) { for (c = 0; c < ic; c++) o[c] = ((float*)in)[c]; for (; c < outcomponents; c++) o[c] = 0; o += outcomponents; in += a->bytestride; } break;*/ } return ret; } void TransformArrayD(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++; } } void TransformArrayA(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)); //scaling is bad for axis. mag = DotProduct(t,t); if (mag) { mag = 1/sqrt(mag); VectorScale(t, mag, t); } VectorCopy(t, (*data)); data++; } } static texid_t GLTF_LoadImage(gltf_t *gltf, int imageidx, 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); char uritext[MAX_QPATH]; char filename[MAX_QPATH]; void *mem; struct gltf_bufferview view; //potentially valid mime types: //image/png //image/vnd-ms.dds (MSFT_texture_dds) (void)mimeType; *uritext = 0; if (uri) { mem = JSON_MallocDataURI(uri, &size); if (mem) { JSON_GetPath(image, false, uritext, sizeof(uritext)); ret = modfuncs->GetTexture(uritext, NULL, flags, mem, NULL, size, 0, TF_INVALID); free(mem); } else { JSON_ReadBody(uri, uritext, sizeof(uritext)); GLTF_RelativePath(gltf->mod->name, uritext, filename, sizeof(filename)); ret = modfuncs->GetTexture(filename, NULL, flags, NULL, NULL, 0, 0, TF_INVALID); } } else if (bufferView >= 0) { if (GLTF_GetBufferViewData(gltf, bufferView, &view)) { JSON_GetPath(image, false, uritext, sizeof(uritext)); ret = modfuncs->GetTexture(uritext, NULL, flags, view.data, NULL, view.length, 0, TF_INVALID); } } return ret; } static texid_t GLTF_LoadTexture(gltf_t *gltf, int texture, 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)); 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_FlagAsUsed(sampler, "name"); JSON_FlagAsUsed(sampler, "extensions"); (void)minFilter; switch(magFilter) { default: break; case 9728: //NEAREST flags |= IF_NOMIPMAP|IF_NEAREST; break; case 9729: //LINEAR flags |= IF_NOMIPMAP|IF_LINEAR; break; case 9984: // NEAREST_MIPMAP_NEAREST case 9986: // NEAREST_MIPMAP_LINEAR flags |= IF_NEAREST; break; case 9985: // LINEAR_MIPMAP_NEAREST case 9987: // LINEAR_MIPMAP_LINEAR flags |= IF_LINEAR; break; } if (wrapS == 33071 || wrapT == 33071) flags |= IF_CLAMP; 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); } static galiasskin_t *GLTF_LoadMaterial(gltf_t *gltf, int material, qboolean vertexcolours) { qboolean doubleSided; int alphaMode; //double alphaCutoff; char shader[8192]; json_t *mat = JSON_FindIndexedChild(gltf->r, "materials", material); galiasskin_t *ret; json_t *nam, *unlit, *pbrsg, *pbrmr, *blinn; nam = JSON_FindChild(mat, "name"); unlit = JSON_FindChild(mat, "extensions.KHR_materials_unlit"); pbrsg = JSON_FindChild(mat, "extensions.KHR_materials_pbrSpecularGlossiness"); pbrmr = JSON_FindChild(mat, "pbrMetallicRoughness"); blinn = JSON_FindChild(mat, "extensions.KHR_materials_cmnBlinnPhong"); /* JSON_WarnIfChild(mat, "name"); JSON_WarnIfChild(pbrsg, "diffuseFactor"); JSON_WarnIfChild(pbrsg, "diffuseTexture"); JSON_WarnIfChild(pbrsg, "specularFactor"); JSON_WarnIfChild(pbrsg, "glossinessFactor"); JSON_WarnIfChild(pbrsg, "specularGlossinessTexture"); JSON_WarnIfChild(mat, "normalTexture"); JSON_WarnIfChild(mat, "occlusionTexture"); JSON_WarnIfChild(mat, "emissiveTexture"); JSON_WarnIfChild(mat, "emissiveFactor"); //0,0,0 */ doubleSided = JSON_GetInteger(mat, "doubleSided", false); //alphaCutoff = JSON_GetInteger(mat, "alphaCutoff", 0.5); if (JSON_Equals(mat, "alphaMode", "MASK")) alphaMode = 1; else if (JSON_Equals(mat, "alphaMode", "BLEND")) alphaMode = 2; else //if (JSON_Equals(mat, "alphaMode", "OPAQUE")) alphaMode = 0; ret = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(*ret)); ret->numframes = 1; 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 Q_snprintf(ret->frame->shadername, sizeof(ret->frame->shadername), "%i", material); if (unlit) { //if this extension was present, then we don't get ANY lighting info. int albedo = JSON_GetInteger(pbrmr, "baseColorTexture.index", -1); //.rgba ret->frame->texnums.base = GLTF_LoadTexture(gltf, albedo, 0); Q_snprintf(shader, sizeof(shader), "{\n" "surfaceparm nodlight\n" "%s"//cull "program default2d\n" //fixme: there's no gpu skeletal stuff with this prog "{\n" "map $diffuse\n" "%s" //blend "%s" //rgbgen "}\n" "fte_basefactor %f %f %f %f\n" "}\n", doubleSided?"cullface disable\n":"", (alphaMode==1)?"alphamask\n":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", JSON_GetFloat(pbrmr, "baseColorFactor.0", 1), JSON_GetFloat(pbrmr, "baseColorFactor.1", 1), JSON_GetFloat(pbrmr, "baseColorFactor.2", 1), JSON_GetFloat(pbrmr, "baseColorFactor.3", 1) ); } else if (blinn) { 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); //you wouldn't normally want this, but we have separate factors so lack of a texture is technically valid. 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); if (!ret->frame->texnums.specular) ret->frame->texnums.specular = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); Q_snprintf(shader, sizeof(shader), "{\n" "%s"//cull "program defaultskin#VC\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 1.0\n" "}\n", doubleSided?"cullface disable\n":"", (alphaMode==1)?"alphamask\n":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", JSON_GetFloat(pbrsg, "diffuseFactor.0", 1), JSON_GetFloat(pbrsg, "diffuseFactor.1", 1), JSON_GetFloat(pbrsg, "diffuseFactor.2", 1), JSON_GetFloat(pbrsg, "diffuseFactor.3", 1), JSON_GetFloat(pbrsg, "specularFactor.0", 1), JSON_GetFloat(pbrsg, "specularFactor.1", 1), JSON_GetFloat(pbrsg, "specularFactor.2", 1), JSON_GetFloat(pbrsg, "shininessFactor", 1), JSON_GetFloat(mat, "emissiveFactor.0", 1), JSON_GetFloat(mat, "emissiveFactor.1", 1), JSON_GetFloat(mat, "emissiveFactor.2", 1) ); } else if (pbrsg) { //if this extension was used, then we can use rgb gloss instead of metalness stuff. 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); Q_snprintf(shader, sizeof(shader), "{\n" "%s"//cull "program defaultskin#VC\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 1.0\n" "}\n", doubleSided?"cullface disable\n":"", (alphaMode==1)?"alphamask\n":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", JSON_GetFloat(pbrsg, "diffuseFactor.0", 1), JSON_GetFloat(pbrsg, "diffuseFactor.1", 1), JSON_GetFloat(pbrsg, "diffuseFactor.2", 1), JSON_GetFloat(pbrsg, "diffuseFactor.3", 1), JSON_GetFloat(pbrsg, "specularFactor.0", 1), JSON_GetFloat(pbrsg, "specularFactor.1", 1), JSON_GetFloat(pbrsg, "specularFactor.2", 1), JSON_GetFloat(pbrsg, "glossinessFactor", 1)*32, //this is fucked. JSON_GetFloat(mat, "emissiveFactor.0", 1), JSON_GetFloat(mat, "emissiveFactor.1", 1), JSON_GetFloat(mat, "emissiveFactor.2", 1) ); } else if (pbrmr) { //this is the standard lighting model for gltf2 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 //now work around potential lame exporters. occ = JSON_GetInteger(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index", occ); mrt = JSON_GetInteger(mat, "extensions.MSFT_packing_occlusionRoughnessMetallic.occlusionRoughnessMetallicTexture.index", mrt); if (occ != mrt && occ != -1) //if its -1 then the mrt should have an unused channel set to 1. however, this isn't guarenteed... if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING"%s: Separate occlusion and metallicRoughness textures are not supported\n", gltf->mod->name); //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, 0); Q_snprintf(shader, sizeof(shader), "{\n" "%s"//cull "program defaultskin#PBR_ORM#VC\n" "{\n" "map $diffuse\n" "%s" //blend "%s" //rgbgen "}\n" "fte_basefactor %f %f %f %f\n" "fte_specularfactor 1.0 %f %f 1.0\n" "fte_fullbrightfactor %f %f %f 1.0\n" "}\n", doubleSided?"cullface disable\n":"", (alphaMode==1)?"alphamask\n":(alphaMode==2)?"blendfunc blend\n":"", vertexcolours?"rgbgen vertex\nalphagen vertex\n":"", JSON_GetFloat(pbrmr, "baseColorFactor.0", 1), JSON_GetFloat(pbrmr, "baseColorFactor.1", 1), JSON_GetFloat(pbrmr, "baseColorFactor.2", 1), JSON_GetFloat(pbrmr, "baseColorFactor.3", 1), JSON_GetFloat(pbrmr, "metallicFactor", 1), JSON_GetFloat(pbrmr, "roughnessFactor", 1), JSON_GetFloat(mat, "emissiveFactor.0", 1), JSON_GetFloat(mat, "emissiveFactor.1", 1), JSON_GetFloat(mat, "emissiveFactor.2", 1) ); } 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.base) ret->frame->texnums.base = modfuncs->GetTexture("$whiteimage", NULL, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA, NULL, NULL, 0, 0, TF_INVALID); ret->frame->defaultshader = memcpy(modfuncs->ZG_Malloc(&gltf->mod->memgroup, strlen(shader)+1), shader, strlen(shader)+1); Q_strlcpy(ret->name, ret->frame->shadername, sizeof(ret->name)); return ret; } static qboolean GLTF_ProcessMesh(gltf_t *gltf, int meshidx, int basebone, double pmatrix[]) { model_t *mod = gltf->mod; json_t *mesh = JSON_FindIndexedChild(gltf->r, "meshes", meshidx); json_t *prim; json_t *meshname = JSON_FindChild(mesh, "name"); JSON_WarnIfChild(mesh, "weights", &gltf->warnlimit); JSON_WarnIfChild(mesh, "extensions", &gltf->warnlimit); // JSON_WarnIfChild(mesh, "extras", &gltf->warnlimit); for(prim = JSON_FindIndexedChild(mesh, "primitives", 0); prim; prim = prim->sibling) { int mat = JSON_GetInteger(prim, "material", -1); int mode = JSON_GetInteger(prim, "mode", 4); json_t *attr = JSON_FindChild(prim, "attributes"); struct gltf_accessor tc_0, tc_1, norm, tang, vpos, col0, idx, sidx, swgt; galiasinfo_t *surf; size_t i, j; prim->used = true; if (mode != 4) { Con_Printf("Primitive mode %i not supported\n", mode); 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); 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 if (JSON_GetInteger(attr, "JOINTS_1", -1) != -1 || JSON_GetInteger(attr, "WEIGHTS_1", -1) != -1) if (gltf->warnlimit --> 0) Con_Printf(CON_WARNING "%s: only 4 bones supported per vert\n", gltf->mod->name); //in case a model tries supplying more. we ought to renormalise the weights in this case. if (!vpos.count) continue; surf = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*surf)); surf->surfaceid = meshidx; surf->contents = FTECONTENTS_BODY; surf->csurface.flags = 0; surf->shares_bones = gltf->numsurfaces; surf->shares_verts = gltf->numsurfaces; JSON_ReadBody(meshname, surf->surfacename, sizeof(surf->surfacename)); surf->numverts = vpos.count; if (idx.data) { surf->numindexes = idx.count; surf->ofs_indexes = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*surf->ofs_indexes) * idx.count); if (idx.componentType == 5123) { //unsigned shorts for (i = 0; i < idx.count; i++) surf->ofs_indexes[i] = *(unsigned short *)((char*)idx.data + i*idx.bytestride); } else if (idx.componentType == 5121) { //unsigned bytes for (i = 0; i < idx.count; i++) surf->ofs_indexes[i] = *(unsigned char *)((char*)idx.data + i*idx.bytestride); } else if (idx.componentType == 5125) { //unsigned ints for (i = 0; i < idx.count; i++) surf->ofs_indexes[i] = *(unsigned int *)((char*)idx.data + i*idx.bytestride); //FIXME: bounds check. } else continue; } else { surf->numindexes = surf->numverts; surf->ofs_indexes = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*surf->ofs_indexes) * surf->numverts); for (i = 0; i < surf->numverts; i++) surf->ofs_indexes[i] = i; } //swap winding order. we cull wrongly. for (i = 0; i < idx.count; i+=3) { index_t t = surf->ofs_indexes[i+0]; surf->ofs_indexes[i+0] = surf->ofs_indexes[i+2]; 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); if (tc_1.data) surf->ofs_lmst_array = GLTF_AccessorToDataF(gltf, surf->numverts, countof(surf->ofs_lmst_array[0]), &tc_1); 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); 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); for (i = 0; i < surf->numverts; i++) { float len = surf->ofs_skel_weight[i][0]+surf->ofs_skel_weight[i][1]+surf->ofs_skel_weight[i][2]+surf->ofs_skel_weight[i][3]; if (len) Vector4Scale(surf->ofs_skel_weight[i], 1/len, surf->ofs_skel_weight[i]); else Vector4Set(surf->ofs_skel_weight[i], 0.5, 0.5, 0.5, 0.5); } } else { surf->ofs_skel_idx = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(surf->ofs_skel_idx[0]) * surf->numverts); surf->ofs_skel_weight = modfuncs->ZG_Malloc(&gltf->mod->memgroup, sizeof(surf->ofs_skel_weight[0]) * surf->numverts); for (i = 0; i < surf->numverts; i++) { Vector4Set(surf->ofs_skel_idx[i], basebone, 0, 0, 0); Vector4Set(surf->ofs_skel_weight[i], 1, 0, 0, 0); } } // TransformArrayD(surf->ofs_skel_xyz, surf->numverts, pmatrix); // TransformArrayA(surf->ofs_skel_norm, surf->numverts, pmatrix); // TransformArrayA(surf->ofs_skel_svect, surf->numverts, pmatrix); for (i = 0; i < surf->numverts; i++) { // VectorScale(surf->ofs_skel_xyz[i], 32, surf->ofs_skel_xyz[i]); for (j = 0; j < 3; j++) { if (mod->maxs[j] < surf->ofs_skel_xyz[i][j]) mod->maxs[j] = surf->ofs_skel_xyz[i][j]; if (mod->mins[j] > surf->ofs_skel_xyz[i][j]) mod->mins[j] = surf->ofs_skel_xyz[i][j]; } } surf->numskins = 1; surf->ofsskins = GLTF_LoadMaterial(gltf, mat, surf->ofs_rgbaub||surf->ofs_rgbaf); gltf->numsurfaces++; surf->nextsurf = mod->meshinfo; mod->meshinfo = surf; } return true; } static void Matrix4D_Multiply(const double *a, const double *b, double *out) { out[0] = a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3]; out[1] = a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3]; out[2] = a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3]; out[3] = a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3]; out[4] = a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7]; out[5] = a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7]; out[6] = a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7]; out[7] = a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7]; out[8] = a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11]; out[9] = a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11]; out[10] = a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11]; out[11] = a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11]; out[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15]; out[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15]; out[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15]; out[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]; } static void GenMatrixPosQuat4ScaleDouble(const double pos[3], const double quat[4], const double scale[3], double result[16]) { float xx, xy, xz, xw, yy, yz, yw, zz, zw; float x2, y2, z2; float s; x2 = quat[0] + quat[0]; y2 = quat[1] + quat[1]; z2 = quat[2] + quat[2]; xx = quat[0] * x2; xy = quat[0] * y2; xz = quat[0] * z2; yy = quat[1] * y2; yz = quat[1] * z2; zz = quat[2] * z2; xw = quat[3] * x2; yw = quat[3] * y2; zw = quat[3] * z2; s = scale[0]; result[0*4+0] = s*(1.0f - (yy + zz)); result[1*4+0] = s*(xy + zw); result[2*4+0] = s*(xz - yw); result[3*4+0] = 0; s = scale[1]; result[0*4+1] = s*(xy - zw); result[1*4+1] = s*(1.0f - (xx + zz)); result[2*4+1] = s*(yz + xw); result[3*4+1] = 0; s = scale[2]; result[0*4+2] = s*(xz + yw); result[1*4+2] = s*(yz - xw); result[2*4+2] = s*(1.0f - (xx + yy)); result[3*4+2] = 0; result[0*4+3] = pos[0]; result[1*4+3] = pos[1]; result[2*4+3] = pos[2]; result[3*4+3] = 1; } static qboolean GLTF_ProcessNode(gltf_t *gltf, int nodeidx, double pmatrix[16], int parentidx, qboolean isjoint) { json_t *c; json_t *node; json_t *t; json_t *skin; int mesh; int skinidx; struct gltfbone_s *b; if (nodeidx < 0 || nodeidx >= gltf->numbones) return false; node = JSON_FindIndexedChild(gltf->r, "nodes", nodeidx); if (!node) return false; b = &gltf->bones[nodeidx]; b->parent = parentidx; t = JSON_FindChild(node, "matrix"); if (t) { b->rel.rmatrix[0*4+0] = JSON_GetIndexedFloat(t, 0, 1.0); b->rel.rmatrix[1*4+0] = JSON_GetIndexedFloat(t, 1, 0.0); b->rel.rmatrix[2*4+0] = JSON_GetIndexedFloat(t, 2, 0.0); b->rel.rmatrix[3*4+0] = JSON_GetIndexedFloat(t, 3, 0.0); b->rel.rmatrix[0*4+1] = JSON_GetIndexedFloat(t, 4, 0.0); b->rel.rmatrix[1*4+1] = JSON_GetIndexedFloat(t, 5, 1.0); b->rel.rmatrix[2*4+1] = JSON_GetIndexedFloat(t, 6, 0.0); b->rel.rmatrix[3*4+1] = JSON_GetIndexedFloat(t, 7, 0.0); b->rel.rmatrix[0*4+2] = JSON_GetIndexedFloat(t, 8, 0.0); b->rel.rmatrix[1*4+2] = JSON_GetIndexedFloat(t, 9, 0.0); b->rel.rmatrix[2*4+2] = JSON_GetIndexedFloat(t, 10,1.0); b->rel.rmatrix[3*4+2] = JSON_GetIndexedFloat(t, 11,0.0); b->rel.rmatrix[0*4+3] = JSON_GetIndexedFloat(t, 12,0.0); b->rel.rmatrix[1*4+3] = JSON_GetIndexedFloat(t, 13,0.0); b->rel.rmatrix[2*4+3] = JSON_GetIndexedFloat(t, 14,0.0); b->rel.rmatrix[3*4+3] = JSON_GetIndexedFloat(t, 15,1.0); Vector4Set(b->rel.quat, 0,0,0,1); VectorSet(b->rel.scale,1,1,1); VectorSet(b->rel.trans,0,0,0); } else { double rot[4]; double scale[3]; double trans[3]; t = JSON_FindChild(node, "rotation"); rot[0] = JSON_GetIndexedFloat(t, 0, 0.0); rot[1] = JSON_GetIndexedFloat(t, 1, 0.0); rot[2] = JSON_GetIndexedFloat(t, 2, 0.0); rot[3] = JSON_GetIndexedFloat(t, 3, 1.0); t = JSON_FindChild(node, "scale"); scale[0] = JSON_GetIndexedFloat(t, 0, 1.0); scale[1] = JSON_GetIndexedFloat(t, 1, 1.0); scale[2] = JSON_GetIndexedFloat(t, 2, 1.0); t = JSON_FindChild(node, "translation"); trans[0] = JSON_GetIndexedFloat(t, 0, 0.0); trans[1] = JSON_GetIndexedFloat(t, 1, 0.0); trans[2] = JSON_GetIndexedFloat(t, 2, 0.0); Vector4Copy(rot, b->rel.quat); VectorCopy(scale, b->rel.scale); VectorCopy(trans, b->rel.trans); //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) { // double identity[16]; int 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); inversef = inverse.data; if (inverse.componentType != 5126/*FLOAT*/ || inverse.type != ((4<<8) | 4)/*mat4x4*/) inverse.count = 0; for (j = 0; j < countof(gltf->bonemap); j++, inversef+=inverse.bytestride/sizeof(float)) { int b = JSON_GetIndexedInteger(joints, j, -1); if (b < 0) break; gltf->bonemap[j] = b; if (j < inverse.count) { gltf->bones[b].inverse[0] = inversef[0*4+0]; gltf->bones[b].inverse[1] = inversef[1*4+0]; gltf->bones[b].inverse[2] = inversef[2*4+0]; gltf->bones[b].inverse[3] = inversef[3*4+0]; gltf->bones[b].inverse[4] = inversef[0*4+1]; gltf->bones[b].inverse[5] = inversef[1*4+1]; gltf->bones[b].inverse[6] = inversef[2*4+1]; gltf->bones[b].inverse[7] = inversef[3*4+1]; gltf->bones[b].inverse[8] = inversef[0*4+2]; gltf->bones[b].inverse[9] = inversef[1*4+2]; gltf->bones[b].inverse[10]= inversef[2*4+2]; gltf->bones[b].inverse[11]= inversef[3*4+2]; gltf->bones[b].inverse[12]= inversef[0*4+3]; gltf->bones[b].inverse[13]= inversef[1*4+3]; gltf->bones[b].inverse[14]= inversef[2*4+3]; gltf->bones[b].inverse[15]= inversef[3*4+3]; } else { gltf->bones[b].inverse[0] = 1; gltf->bones[b].inverse[1] = 0; gltf->bones[b].inverse[2] = 0; gltf->bones[b].inverse[3] = 0; gltf->bones[b].inverse[4] = 0; gltf->bones[b].inverse[5] = 1; gltf->bones[b].inverse[6] = 0; gltf->bones[b].inverse[7] = 0; gltf->bones[b].inverse[8] = 0; gltf->bones[b].inverse[9] = 0; gltf->bones[b].inverse[10]= 1; gltf->bones[b].inverse[11]= 0; gltf->bones[b].inverse[12]= 0; gltf->bones[b].inverse[13]= 0; gltf->bones[b].inverse[14]= 0; gltf->bones[b].inverse[15]= 1; } } // GLTF_ProcessNode(gltf, JSON_GetInteger(skin, "skeleton", -1), identity, nodeidx, true); JSON_FlagAsUsed(node, "name"); } mesh = JSON_GetInteger(node, "mesh", -1); if (mesh >= 0) GLTF_ProcessMesh(gltf, mesh, nodeidx, b->amatrix); 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); } JSON_FlagAsUsed(node, "camera"); JSON_WarnIfChild(node, "weights", &gltf->warnlimit); //default value for morph weight animations JSON_WarnIfChild(node, "extensions", &gltf->warnlimit); // JSON_WarnIfChild(node, "extras", &gltf->warnlimit); return true; } struct gltf_animsampler { struct gltf_accessor input; struct gltf_accessor output; }; static struct gltf_animsampler GLTF_AnimationSampler(gltf_t *gltf, json_t *samplers, int sampleridx, int elems) { struct gltf_animsampler r; json_t *sampler = JSON_FindIndexedChild(samplers, NULL, sampleridx); GLTF_GetAccessor(gltf, JSON_GetInteger(sampler, "input", -1), &r.input); GLTF_GetAccessor(gltf, JSON_GetInteger(sampler, "output", -1), &r.output); if (!r.input.data || !r.output.data || r.input.count != r.output.count) memset(&r, 0, sizeof(r)); return r; } static float Anim_GetTime(struct gltf_accessor *in, int index) { //read the input sampler (to get timestamps) switch(in->componentType) { case 5120: //BYTE return max(-1, (*(signed char*)((qbyte*)in->data + in->bytestride*index)) / 127.0); case 5121: //UNSIGNED_BYTE return (*(unsigned char*)((qbyte*)in->data + in->bytestride*index)) / 255.0; case 5122: //SHORT return max(-1, (*(signed short*)((qbyte*)in->data + in->bytestride*index)) / 32767.0); case 5123: //UNSIGNED_SHORT return (*(unsigned short*)((qbyte*)in->data + in->bytestride*index)) / 65535.0; case 5125: //UNSIGNED_INT return (*(unsigned int*)((qbyte*)in->data + in->bytestride*index)) / (double)~0u; case 5126: //FLOAT return *(float*)((qbyte*)in->data + in->bytestride*index); default: Con_Printf("Unsupported input component type\n"); return 0; } } static void Anim_GetVal(struct gltf_accessor *in, int index, float *result, int elems) { //read the input sampler (to get timestamps) switch(in->componentType) { case 5120: //BYTE while (elems --> 0) result[elems] = max(-1, ((signed char*)((qbyte*)in->data + in->bytestride*index))[elems] / 127.0); break; case 5121: //UNSIGNED_BYTE while (elems --> 0) result[elems] = ((unsigned char*)((qbyte*)in->data + in->bytestride*index))[elems] / 255.0; break; case 5122: //SHORT while (elems --> 0) result[elems] = max(-1, ((signed short*)((qbyte*)in->data + in->bytestride*index))[elems] / 32767.0); break; case 5123: //UNSIGNED_SHORT while (elems --> 0) result[elems] = ((unsigned short*)((qbyte*)in->data + in->bytestride*index))[elems] / 65535.0; break; case 5125: //UNSIGNED_INT while (elems --> 0) result[elems] = ((unsigned int*)((qbyte*)in->data + in->bytestride*index))[elems] / (double)~0u; break; case 5126: //FLOAT while (elems --> 0) result[elems] = ((float*)((qbyte*)in->data + in->bytestride*index))[elems]; break; default: Con_Printf("Unsupported output component type\n"); break; } } static void LerpAnimData(gltf_t *gltf, struct gltf_animsampler *samp, float time, float *result, int elems) { float t1, t2; float w1, w2; float v1[4], v2[4]; int f1 = 0, f2, c; struct gltf_accessor *in = &samp->input; struct gltf_accessor *out = &samp->output; t1 = t2 = Anim_GetTime(in, 0); for (f2 = 1 ; f2 < in->count; f2++) { t2 = Anim_GetTime(in, f2); if (t2 > time) break; //now have before and after t1 = t2; f1 = f2; } //assume linear if (f1==f2 || t1==t2) { Anim_GetVal(out, f1, result, elems); return; } w2 = (time-t1)/(t2-t1); w1 = 1-w2; Anim_GetVal(out, f1, v1, elems); Anim_GetVal(out, f2, v2, elems); for (c = 0; c < elems; c++) result[c] = v1[c]*w1 + w2*v2[c]; } static void GLTF_RemapBone(gltf_t *gltf, int *nextidx, int b) { //potentially needs to walk to the root before the child. recursion sucks. if (gltf->bonemap[b] >= 0) return; //already got remapped GLTF_RemapBone(gltf, nextidx, gltf->bones[b].parent); gltf->bonemap[b] = (*nextidx)++; } static void GLTF_RewriteBoneTree(gltf_t *gltf) { galiasinfo_t *surf; int j, n; struct gltfbone_s *tmpbones; for (j = 0; j < gltf->numbones; j++) { if (gltf->bones[j].parent >= j) break; } if (j == gltf->numbones) { for (j = 0; j < gltf->numbones; j++) gltf->bonemap[j] = j; return; //all are ordered okay } for (j = 0; j < gltf->numbones; j++) gltf->bonemap[j] = -1; for ( ; j < MAX_BONES; j++) gltf->bonemap[j] = 0; n = 0; for (j = 0; j < gltf->numbones; j++) GLTF_RemapBone(gltf, &n, j); tmpbones = malloc(sizeof(*tmpbones)*gltf->numbones); memcpy(tmpbones, gltf->bones, sizeof(*tmpbones)*gltf->numbones); for (j = 0; j < gltf->numbones; j++) gltf->bones[gltf->bonemap[j]] = tmpbones[j]; for (j = 0; j < gltf->numbones; j++) if (gltf->bones[j].parent >= 0) gltf->bones[j].parent = gltf->bonemap[gltf->bones[j].parent]; for(surf = gltf->mod->meshinfo; surf; surf = surf->nextsurf) { for (j = 0; j < surf->numverts; j++) for (n = 0; n < countof(surf->ofs_skel_idx[j]); n++) surf->ofs_skel_idx[j][n] = gltf->bonemap[surf->ofs_skel_idx[j][n]]; } } //okay, so gltf is some weird scene thing. //mostly there should be some default scene, so we'll just use that. //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) { gltf_t gltf; int pos=0, j, k; json_t *scene, *n, *anim; double rootmatrix[16]; double gltfver; galiasinfo_t *surf; galiasbone_t *bone; galiasanimation_t *framegroups = NULL; unsigned int numframegroups = 0; float *baseframe; memset(&gltf, 0, sizeof(gltf)); gltf.r = JSON_Parse(NULL, mod->name, NULL, json, &pos, jsonsize); gltf.mod = mod; gltf.buffers[0].data = buffer; gltf.buffers[0].length = buffersize; gltf.warnlimit = 5; //asset.version must exist, supposedly. gltfver = JSON_GetFloat(gltf.r, "asset.version", 2.0); if (gltfver == 2.0) { JSON_FlagAsUsed(gltf.r, "asset.copyright"); JSON_FlagAsUsed(gltf.r, "asset.generator"); JSON_WarnIfChild(gltf.r, "asset.minVersion", &gltf.warnlimit); JSON_WarnIfChild(gltf.r, "asset.extensions", &gltf.warnlimit); for(n = JSON_FindIndexedChild(gltf.r, "extensionsRequired", 0); n; n = n->sibling) { char extname[256]; JSON_ReadBody(n, extname, sizeof(extname)); Con_Printf(CON_ERROR "%s: Required gltf2 extension \"%s\" not supported\n", mod->name, extname); JSON_Destroy(gltf.r); return false; } for(n = JSON_FindIndexedChild(gltf.r, "extensionsUsed", 0); n; n = n->sibling) { //must be a superset of the above. char extname[256]; JSON_ReadBody(n, extname, sizeof(extname)); if (!strcmp(extname, "KHR_materials_pbrSpecularGlossiness")) ; else if (!strcmp(extname, "KHR_texture_transform")) ; else Con_Printf(CON_WARNING "%s: gltf2 extension \"%s\" not known\n", mod->name, extname); } //we don't really care about cameras. JSON_FlagAsUsed(gltf.r, "cameras"); scene = JSON_FindIndexedChild(gltf.r, "scenes", JSON_GetInteger(gltf.r, "scene", 0)); 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] = 1; rootmatrix[15] = 1; #else rootmatrix[0] = rootmatrix[5] = rootmatrix[10] = 1; rootmatrix[15] = 1; #endif for (j = 0; j < countof(gltf.bones); j++) { n = JSON_FindIndexedChild(gltf.r, "nodes", j); if (!n) break; 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); } gltf.bones[j].parent = -1; gltf.bones[j].amatrix[0] = gltf.bones[j].amatrix[5] = gltf.bones[j].amatrix[10] = gltf.bones[j].amatrix[15] = 1; gltf.bones[j].inverse[0] = gltf.bones[j].inverse[5] = gltf.bones[j].inverse[10] = gltf.bones[j].inverse[15] = 1; gltf.bones[j].rel.rmatrix[0] = gltf.bones[j].rel.rmatrix[5] = gltf.bones[j].rel.rmatrix[10] = gltf.bones[j].rel.rmatrix[15] = 1; } gltf.numbones = j; JSON_FlagAsUsed(scene, "name"); JSON_WarnIfChild(scene, "extensions", &gltf.warnlimit); // JSON_WarnIfChild(scene, "extras"); for (j = 0; ; j++) { n = JSON_FindIndexedChild(scene, "nodes", j); if (!n) break; n->used = true; if (!GLTF_ProcessNode(&gltf, JSON_GetInteger(n, NULL, -1), rootmatrix, -1, false)) break; } GLTF_RewriteBoneTree(&gltf); bone = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*bone)*gltf.numbones); baseframe = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(float)*12*gltf.numbones); for (j = 0; j < gltf.numbones; j++) { Q_strlcpy(bone[j].name, gltf.bones[j].name, sizeof(bone[j].name)); bone[j].parent = gltf.bones[j].parent; for(k = 0; k < 12; k++) { baseframe[j*12+k] = gltf.bones[j].amatrix[k]; bone[j].inverse[k] = gltf.bones[j].inverse[k]; } } for(anim = JSON_FindIndexedChild(gltf.r, "animations", 0); anim; anim = anim->sibling) numframegroups++; if (numframegroups) { framegroups = modfuncs->ZG_Malloc(&mod->memgroup, sizeof(*framegroups)*numframegroups); for (k = 0; k < numframegroups; k++) { 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; float maxtime = 0; struct { struct gltf_animsampler rot,scale,trans; } b[MAX_BONES]; memset(b, 0, sizeof(b)); if (!JSON_ReadBody(JSON_FindChild(anim, "name"), fg->name, sizeof(fg->name))) { if (anim) JSON_GetPath(anim, true, fg->name, sizeof(fg->name)); else Q_snprintf(fg->name, sizeof(fg->name), "anim%i", k); } fg->loop = true; fg->skeltype = SKEL_RELATIVE; for(chan = JSON_FindIndexedChild(anim, "channels", 0); chan; chan = chan->sibling) { 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 *path = JSON_FindChild(targ, "path"); 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); 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")) b[bone].rot = s; else if (JSON_Equals(path, NULL, "scale")) b[bone].scale = s; else if (JSON_Equals(path, NULL, "translation")) b[bone].trans = s; else if (gltf.warnlimit --> 0) { //these are unsupported if (JSON_Equals(path, NULL, "weights")) //morph weights Con_Printf("%s: morph animations are not supported\n", mod->name); else Con_Printf("%s: undocumented animation type\n", mod->name); } } //TODO: make a guess at the number of frames+framerate //(input samplers have min+max values). fg->rate = 30; fg->numposes = max(1, maxtime*fg->rate); 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). fg->skeltype = SKEL_RELATIVE; 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) { 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(&gltf, &b[j].rot, time, rot, 4); if (b[j].scale.input.data) LerpAnimData(&gltf, &b[j].scale, time, scale, 3); if (b[j].trans.input.data) LerpAnimData(&gltf, &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,1,0,1,0,0,0,0,1,0,0}; memcpy(fnar, bonematrix, sizeof(fnar)); modfuncs->ConcatTransforms((void*)toquake, (void*)fnar, (void*)bonematrix); } } } } } for(surf = mod->meshinfo; surf; surf = surf->nextsurf) { surf->shares_bones = 0; surf->numbones = gltf.numbones; surf->ofsbones = bone; surf->baseframeofs = baseframe; surf->ofsanimations = framegroups; surf->numanimations = numframegroups; surf->contents = FTECONTENTS_BODY; surf->csurface.flags = 0; surf->geomset = ~0; //invalid set = always visible. FIXME: set this according to scene numbers? surf->geomid = 0; } JSON_WarnUnused(gltf.r, &gltf.warnlimit); } else Con_Printf("%s: unsupported gltf version (%.2f)\n", mod->name, gltfver); JSON_Destroy(gltf.r); mod->type = mod_alias; return !!mod->meshinfo; } qboolean QDECL Mod_LoadGLTFModel (struct model_s *mod, void *buffer, size_t fsize) { //just straight json. return GLTF_LoadModel(mod, buffer, fsize, NULL, 0); } //glb files are some binary header, a lump with json data, and optionally a lump with binary data 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 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); } #endif