You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5030 lines
125 KiB

#include "util.h"
#define VVM_UNPACK (1u<<31) //animations will be unpacked into individual frames-as-animations (ie: no more framegroups)
#define VVM_ALLPRIVATE (VVM_UNPACK)
bool noext = false; /* not fully working */
bool verbose = false;
bool quiet = false;
bool ext_detected = false;
struct ejoint
{
const char *name;
int parent;
ejoint() : name(NULL), parent(-1) {
}
};
struct triangle { uint vert[3]; triangle() {
}
triangle(uint v0, uint v1, uint v2) {
vert[0] = v0; vert[1] = v1; vert[2] = v2;
}
};
vector<triangle> triangles, neighbors;
struct mesh { uint name, material; uint firstvert, numverts; uint firsttri, numtris; mesh() : name(0), material(0), firstvert(0), numverts(0), firsttri(0), numtris(0) {} };
vector<mesh> meshes;
struct meshprop
{
uint contents;
uint surfaceflags;
uint body;
uint geomset;
uint geomid;
float mindist;
float maxdist;
meshprop() : contents(0x02000000), surfaceflags(0), body(0), geomset(~0u), geomid(0), mindist(0), maxdist(0) {
};
};
vector<meshprop> meshes_fte;
uint modelflags;
struct event_fte
{
uint anim;
float timestamp;
uint evcode;
const char *evdata_str;
uint evdata_idx;
};
vector<event_fte> events_fte;
struct anim { uint name; uint firstframe, numframes; float fps; uint flags; anim() : name(0), firstframe(0), numframes(0), fps(0), flags(0) {
}
};
vector<anim> anims;
struct joint { int group; uint name; int parent; float pos[3], orient[4], scale[3]; joint() : name(0), parent(-1) {
memset(pos, 0, sizeof(pos)); memset(orient, 0, sizeof(orient)); memset(scale, 0, sizeof(scale));
}
};
vector<joint> joints; //for meshes
struct pose { const char *name; int parent; uint flags; float offset[10], scale[10]; pose() : name(NULL), parent(-1), flags(0) {
memset(offset, 0, sizeof(offset)); memset(scale, 0, sizeof(scale));
}
};
vector<pose> poses; //aka: animation joints
struct framebounds { Vec3 bbmin, bbmax; double xyradius, radius; framebounds() : bbmin(0, 0, 0), bbmax(0, 0, 0), xyradius(0), radius(0) {
}
};
vector<framebounds> bounds;
struct transform
{
Vec3 pos;
Quat orient;
Vec3 scale;
transform() {
}
transform(const Vec3 &pos, const Quat &orient, const Vec3 &scale = Vec3(1, 1, 1)) : pos(pos), orient(orient), scale(scale) {
}
};
struct frame
{
struct framepose
{
int remap;
const char *bonename;
int boneparent;
transform tr;
framepose() : bonename(""),boneparent(-1),tr() {
}
framepose(ejoint &j, transform t) : bonename(j.name),boneparent(j.parent),tr(t) {
}
};
vector<framepose> pose;
};
vector<frame> frames;
vector<char> stringdata, commentdata;
uint numfverts; //verts generated so far
struct boneoverride
{
const char *name;
bool used;
struct prop
{
const char *rename;
int group;
prop() : rename(NULL), group(-1) {
}
} props;
boneoverride() : used(false), props(){
}
};
vector<boneoverride> boneoverrides;
struct meshoverride
{
const char *name;
meshprop props;
};
vector<meshoverride> meshoverrides;
struct hitbox
{
int body;
const char *bone;
Vec3 mins, maxs;
};
struct filespec
{
const char *file;
const char *name;
double fps;
uint flags;
int startframe;
int endframe;
meshprop meshprops;
const char *materialprefix;
Quat rotate;
float scale;
Vec3 translate;
bool nomesh;
bool noanim;
vector<event_fte> events;
filespec() {
reset();
}
void reset()
{
file = NULL;
name = NULL;
fps = 24;
flags = 0;
startframe = 0;
endframe = -1;
meshprops = meshprop();
materialprefix = NULL;
rotate = Quat(0, 0, 0, 1);
scale = 1;
translate = Vec3(0,0,0);
nomesh = false;
noanim = false;
events.setsize(0);
}
};
struct sharedstring
{
uint offset;
sharedstring() {
}
sharedstring(const char *s) : offset(stringdata.length()) {
stringdata.put(s, strlen(s)+1);
}
};
static inline bool htcmp(const char *x, const sharedstring &s)
{
return htcmp(x, &stringdata[s.offset]);
}
hashtable<sharedstring, uint> stringoffsets;
uint sharestring(const char *s)
{
if(stringdata.empty()) stringoffsets.access("", 0);
return stringoffsets.access(s ? s : "", stringdata.length());
}
struct blendcombo
{
int sorted;
double weights[4];
uchar bones[4];
blendcombo() : sorted(0) {
}
void reset() {
sorted = 0;
}
void addweight(double weight, int bone)
{
if(weight <= 1e-3) return;
loopk(sorted) if(weight > weights[k])
{
for(int l = min(sorted-1, 2); l >= k; l--)
{
weights[l+1] = weights[l];
bones[l+1] = bones[l];
}
weights[k] = weight;
bones[k] = bone;
if(sorted<4) sorted++;
return;
}
if(sorted>=4) return;
weights[sorted] = weight;
bones[sorted] = bone;
sorted++;
}
void finalize()
{
loopj(4-sorted) {
weights[sorted+j] = 0; bones[sorted+j] = 0;
}
if(sorted <= 0) return;
double total = 0;
loopj(sorted) total += weights[j];
total = 1.0/total;
loopj(sorted) weights[j] *= total;
}
void serialize(uchar *vweights) const
{
int total = 0;
loopk(4) total += (vweights[k] = uchar(0.5 + weights[k]*255));
if(sorted <= 0) return;
while(total > 255)
{
loopk(4) if(vweights[k] > 0 && total > 255) { vweights[k]--; total--; }
}
while(total < 255)
{
loopk(4) if(vweights[k] < 255 && total < 255) { vweights[k]++; total++; }
}
}
bool operator==(const blendcombo &c) {
loopi(4) if(weights[i] != c.weights[i] || bones[i] != c.bones[i]) return false; return true;
}
bool operator!=(const blendcombo &c) {
loopi(4) if(weights[i] != c.weights[i] || bones[i] != c.bones[i]) return true; return false;
}
};
vector<Vec4> mpositions;
vector<blendcombo> mblends;
static bool parseindex(char *&c, int &val)
{
while(isspace(*c)) c++;
char *end = NULL;
int rval = strtol(c, &end, 10);
if(c == end) return false;
val = rval;
c = end;
return true;
}
static double parseattrib(char *&c, double ival = 0)
{
while(isspace(*c)) c++;
char *end = NULL;
double val = strtod(c, &end);
if(c == end) val = ival;
else c = end;
return val;
}
static bool maybeparseattrib(char *&c, double &result)
{
while(isspace(*c)) c++;
char *end = NULL;
double val = strtod(c, &end);
if(c == end) return false;
c = end;
result = val;
return true;
}
#if 0
static bool parsename(char *&c, char *buf, int bufsize = sizeof(string))
{
while(isspace(*c)) c++;
char *end;
if(*c == '"')
{
c++;
end = c;
while(*end && *end != '"') end++;
copystring(buf, c, min(int(end-c+1), bufsize));
if(*end == '"') end++;
}
else
{
end = c;
while(*end && !isspace(*end)) end++;
copystring(buf, c, min(int(end-c+1), bufsize));
}
if(c == end) return false;
c = end;
return true;
}
#endif
static char *trimname(char *&c)
{
while(isspace(*c)) c++;
char *start, *end;
if(*c == '"')
{
c++;
start = end = c;
while(*end && *end != '"') end++;
if(*end) { *end = '\0'; end++; }
}
else
{
start = end = c;
while(*end && !isspace(*end)) end++;
if(*end) { *end = '\0'; end++; }
}
c = end;
return start;
}
static Vec4 parseattribs4(char *&c, const Vec4 &ival = Vec4(0, 0, 0, 0))
{
Vec4 val;
loopk(4) val[k] = parseattrib(c, ival[k]);
return val;
}
static Vec3 parseattribs3(char *&c, const Vec3 &ival = Vec3(0, 0, 0))
{
Vec3 val;
loopk(3) val[k] = parseattrib(c, ival[k]);
return val;
}
static blendcombo parseblends(char *&c)
{
blendcombo b;
int index;
while(parseindex(c, index))
{
double weight = parseattrib(c, 0);
b.addweight(weight, index);
}
b.finalize();
return b;
}
struct eanim
{
const char *name;
int startframe, endframe;
double fps;
uint flags;
eanim() : name(NULL), startframe(0), endframe(INT_MAX), fps(0), flags(0) {
}
};
struct emesh
{
const char *name, *material;
int firsttri;
bool used;
bool hasexplicits;
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) {
}
};
struct evarray
{
string name;
uint type, format, size;
evarray() : type(VVM_POSITION), format(VVM_FLOAT), size(3) {
name[0] = '\0';
}
evarray(uint type, uint format, uint size, const char *initname = "") : type(type), format(format), size(size) {
copystring(name, initname);
}
};
struct esmoothgroup
{
enum
{
F_USED = 1<<0,
F_UVSMOOTH = 1<<1
};
int key;
float angle;
int flags;
esmoothgroup() : key(-1), angle(-1), flags(0) {
}
};
struct etriangle
{
int smoothgroup;
uint vert[3], weld[3];
etriangle()
: smoothgroup(-1)
{
}
etriangle(int v0, int v1, int v2, int smoothgroup = -1)
: smoothgroup(smoothgroup)
{
vert[0] = v0;
vert[1] = v1;
vert[2] = v2;
}
};
vector<Vec4> epositions, etexcoords, etangents, ecolors, ecustom[10];
vector<Vec3> enormals, ebitangents;
vector<blendcombo> eblends;
vector<etriangle> etriangles;
vector<esmoothgroup> esmoothgroups;
vector<int> esmoothindexes;
vector<uchar> esmoothedges;
vector<ejoint> ejoints;
vector<transform> eposes;
vector<Matrix3x4> mjoints;
vector<int> eframes;
vector<eanim> eanims;
vector<emesh> emeshes;
vector<evarray> evarrays;
hashtable<const char *, char *> enames;
const char *getnamekey(const char *name)
{
char **exists = enames.access(name);
if(exists) return *exists;
char *key = newstring(name);
enames[key] = key;
return key;
}
struct weldinfo
{
int tri, vert;
weldinfo *next;
};
void weldvert(const vector<Vec3> &norms, const Vec4 &pos, weldinfo *welds, int &numwelds, unionfind<int> &welder)
{
welder.clear();
int windex = 0;
for(weldinfo *w = welds; w; w = w->next, windex++)
{
etriangle &wt = etriangles[w->tri];
esmoothgroup &wg = esmoothgroups[wt.smoothgroup];
int vindex = windex + 1;
for(weldinfo *v = w->next; v; v = v->next, vindex++)
{
etriangle &vt = etriangles[v->tri];
esmoothgroup &vg = esmoothgroups[vt.smoothgroup];
if(wg.key != vg.key) continue;
if(norms[w->tri].dot(norms[v->tri]) < max(wg.angle, vg.angle)) continue;
if(((wg.flags | vg.flags) & esmoothgroup::F_UVSMOOTH) &&
etexcoords[wt.vert[w->vert]] != etexcoords[vt.vert[v->vert]])
continue;
if(esmoothindexes.length() > max(w->vert, v->vert) && esmoothindexes[w->vert] != esmoothindexes[v->vert])
continue;
if(esmoothedges.length())
{
int w0 = w->vert, w1 = (w->vert+1)%3, w2 = (w->vert+2)%3;
const Vec4 &wp1 = epositions[wt.vert[w1]],
&wp2 = epositions[wt.vert[w2]];
int v0 = v->vert, v1 = (v->vert+1)%3, v2 = (v->vert+2)%3;
const Vec4 &vp1 = epositions[vt.vert[v1]],
&vp2 = epositions[vt.vert[v2]];
int wf = esmoothedges[w->tri], vf = esmoothedges[v->tri];
if((wp1 != vp1 || !(((wf>>w0)|(vf>>v0))&1)) &&
(wp1 != vp2 || !(((wf>>w0)|(vf>>v2))&1)) &&
(wp2 != vp1 || !(((wf>>w2)|(vf>>v0))&1)) &&
(wp2 != vp2 || !(((wf>>w2)|(vf>>v2))&1)))
continue;
}
welder.unite(windex, vindex, -1);
}
}
windex = 0;
for(weldinfo *w = welds; w; w = w->next, windex++)
{
etriangle &wt = etriangles[w->tri];
wt.weld[w->vert] = welder.find(windex, -1, numwelds);
if(wt.weld[w->vert] == uint(numwelds)) numwelds++;
}
}
void smoothverts(bool areaweight = true)
{
if(etriangles.empty()) return;
if(enormals.length())
{
loopv(etriangles)
{
etriangle &t = etriangles[i];
loopk(3) t.weld[k] = t.vert[k];
}
return;
}
if(etexcoords.empty()) loopv(esmoothgroups) esmoothgroups[i].flags &= ~esmoothgroup::F_UVSMOOTH;
if(esmoothedges.length()) while(esmoothedges.length() < etriangles.length()) esmoothedges.add(7);
vector<Vec3> tarea, tnorms;
loopv(etriangles)
{
etriangle &t = etriangles[i];
Vec3 v0(epositions[t.vert[0]]),
v1(epositions[t.vert[1]]),
v2(epositions[t.vert[2]]);
tnorms.add(tarea.add((v2 - v0).cross(v1 - v0)).normalize());
}
int nextalloc = 0;
vector<weldinfo *> allocs;
hashtable<Vec4, weldinfo *> welds(1<<12);
loopv(etriangles)
{
etriangle &t = etriangles[i];
loopk(3)
{
weldinfo **next = &welds.access(epositions[t.vert[k]], NULL);
if(!(nextalloc % 1024)) allocs.add(new weldinfo[1024]);
weldinfo &w = allocs[nextalloc/1024][nextalloc%1024];
nextalloc++;
w.tri = i;
w.vert = k;
w.next = *next;
*next = &w;
}
}
int numwelds = 0;
unionfind<int> welder;
enumerate(welds, Vec4, vpos, weldinfo *, vwelds, weldvert(tnorms, vpos, vwelds, numwelds, welder));
loopv(allocs) delete[] allocs[i];
loopi(numwelds) enormals.add(Vec3(0, 0, 0));
loopv(etriangles)
{
etriangle &t = etriangles[i];
loopk(3) enormals[t.weld[k]]+= areaweight ? tarea[i] : tnorms[i];
}
loopv(enormals) if(enormals[i] != Vec3(0, 0, 0)) enormals[i] = enormals[i].normalize();
}
struct sharedvert
{
int index, weld;
sharedvert() {
}
sharedvert(int index, int weld) : index(index), weld(weld) {
}
};
static inline bool htcmp(const sharedvert &v, const sharedvert &s)
{
if(epositions[v.index] != epositions[s.index]) return false;
if(etexcoords.length() && etexcoords[v.index] != etexcoords[s.index]) return false;
if(enormals.length() && enormals[v.weld] != enormals[s.weld]) return false;
if(eblends.length() && eblends[v.index] != eblends[s.index]) return false;
if(ecolors.length() && ecolors[v.index] != ecolors[s.index]) return false;
loopi(10) if(ecustom[i].length() && ecustom[i][v.index] != ecustom[i][s.index]) return false;
return true;
}
static inline uint hthash(const sharedvert &v)
{
return hthash(epositions[v.index]);
}
const struct vertexarraytype
{
const char *name;
int code;
} vatypes[] =
{
{ "position", VVM_POSITION },
{ "texcoord", VVM_TEXCOORD },
{ "normal", VVM_NORMAL },
{ "tangent", VVM_TANGENT },
{ "blendindexes", VVM_BLENDINDEXES },
{ "blendweights", VVM_BLENDWEIGHTS },
{ "color", VVM_COLOR },
{ "custom0", VVM_CUSTOM + 0 },
{ "custom1", VVM_CUSTOM + 1 },
{ "custom2", VVM_CUSTOM + 2 },
{ "custom3", VVM_CUSTOM + 3 },
{ "custom4", VVM_CUSTOM + 4 },
{ "custom5", VVM_CUSTOM + 5 },
{ "custom6", VVM_CUSTOM + 6 },
{ "custom7", VVM_CUSTOM + 7 },
{ "custom8", VVM_CUSTOM + 8 },
{ "custom9", VVM_CUSTOM + 9 }
};
int findvertexarraytype(const char *name)
{
loopi(sizeof(vatypes)/sizeof(vatypes[0]))
{
if(!strcasecmp(vatypes[i].name, name))
return vatypes[i].code;
}
return -1;
}
const struct vertexarrayformat
{
const char *name;
int code;
int size;
} vaformats[] =
{
{ "byte", VVM_BYTE, 1 },
{ "ubyte", VVM_UBYTE, 1 },
{ "short", VVM_SHORT, 2 },
{ "ushort", VVM_USHORT, 2 },
{ "int", VVM_INT, 4 },
{ "uint", VVM_UINT, 4 },
{ "half", VVM_HALF, 2 },
{ "float", VVM_FLOAT, 4 },
{ "double", VVM_DOUBLE, 8 }
};
int findvertexarrayformat(const char *name)
{
loopi(sizeof(vaformats)/sizeof(vaformats[0]))
{
if(!strcasecmp(vaformats[i].name, name))
return vaformats[i].code;
}
return -1;
}
struct vertexarray
{
uint type, flags, format, size, offset, count;
vector<uchar> vdata;
vertexarray(uint type, uint format, uint size) : type(type), flags(0), format(format), size(size), offset(0), count(0) {
}
int formatsize() const
{
return vaformats[format].size;
}
int bytesize() const
{
return size * vaformats[format].size;
}
};
vector<sharedvert> vmap;
vector<vertexarray> varrays;
vector<uchar> vdata;
struct halfdata
{
ushort val;
halfdata(double d)
{
union
{
ullong i;
double d;
} conv;
conv.d = d;
ushort signbit = ushort((conv.i>>63)&1);
ushort mantissa = ushort((conv.i>>(52-10))&0x3FF);
int exponent = int((conv.i>>52)&0x7FF) - 1023 + 15;
if(exponent <= 0)
{
mantissa |= 0x400;
mantissa >>= min(1-exponent, 10+1);
exponent = 0;
}
else if(exponent >= 0x1F)
{
mantissa = 0;
exponent = 0x1F;
}
val = (signbit<<15) | (ushort(exponent)<<10) | mantissa;
}
};
template<> inline halfdata endianswap<halfdata>(halfdata n) {
n.val = endianswap16(n.val); return n;
}
template<int TYPE> static inline int remapindex(int i, const sharedvert &v) {
return v.index;
}
template<> inline int remapindex<VVM_NORMAL>(int i, const sharedvert &v) {
return v.weld;
}
template<> inline int remapindex<VVM_TANGENT>(int i, const sharedvert &v) {
return i;
}
template<class T, class U>
static inline void putattrib(T &out, const U &val) {
out = T(val);
}
template<class T, class U>
static inline void uroundattrib(T &out, const U &val, double scale) {
out = T(clamp(0.5 + val*scale, 0.0, scale));
}
template<class T, class U>
static inline void sroundattrib(T &out, const U &val, double scale, double low, double high) {
double n = val*scale*0.5; out = T(clamp(n < 0 ? ceil(n - 1) : floor(n), low, high));
}
template<class T, class U>
static inline void scaleattrib(T &out, const U &val) {
putattrib(out, val);
}
template<class U>
static inline void scaleattrib(char &out, const U &val) {
sroundattrib(out, val, 255.0, -128.0, 127.0);
}
template<class U>
static inline void scaleattrib(short &out, const U &val) {
sroundattrib(out, val, 65535.0, -32768.0, 32767.0);
}
template<class U>
static inline void scaleattrib(int &out, const U &val) {
sroundattrib(out, val, 4294967295.0, -2147483648.0, 2147483647.0);
}
template<class U>
static inline void scaleattrib(uchar &out, const U &val) {
uroundattrib(out, val, 255.0);
}
template<class U>
static inline void scaleattrib(ushort &out, const U &val) {
uroundattrib(out, val, 65535.0);
}
template<class U>
static inline void scaleattrib(uint &out, const U &val) {
uroundattrib(out, val, 4294967295.0);
}
template<int T>
static inline bool normalizedattrib() {
return true;
}
template<int TYPE, int FMT, class T, class U>
static inline void serializeattrib(const vertexarray &va, T *data, const U &attrib)
{
if(normalizedattrib<TYPE>()) switch(va.size)
{
case 4: scaleattrib(data[3], attrib.w);
case 3: scaleattrib(data[2], attrib.z);
case 2: scaleattrib(data[1], attrib.y);
case 1: scaleattrib(data[0], attrib.x);
}
else switch(va.size)
{
case 4: putattrib(data[3], attrib.w);
case 3: putattrib(data[2], attrib.z);
case 2: putattrib(data[1], attrib.y);
case 1: putattrib(data[0], attrib.x);
}
lilswap(data, va.size);
}
template<int TYPE, int FMT, class T>
static inline void serializeattrib(const vertexarray &va, T *data, const Vec3 &attrib)
{
if(normalizedattrib<TYPE>()) switch(va.size)
{
case 3: scaleattrib(data[2], attrib.z);
case 2: scaleattrib(data[1], attrib.y);
case 1: scaleattrib(data[0], attrib.x);
}
else switch(va.size)
{
case 3: putattrib(data[2], attrib.z);
case 2: putattrib(data[1], attrib.y);
case 1: putattrib(data[0], attrib.x);
}
lilswap(data, va.size);
}
template<int TYPE, int FMT, class T>
static inline void serializeattrib(const vertexarray &va, T *data, const blendcombo &blend)
{
if(TYPE == VVM_BLENDINDEXES)
{
switch(va.size)
{
case 4: putattrib(data[3], blend.bones[3]);
case 3: putattrib(data[2], blend.bones[2]);
case 2: putattrib(data[1], blend.bones[1]);
case 1: putattrib(data[0], blend.bones[0]);
}
}
else if(FMT == VVM_UBYTE)
{
uchar weights[4];
blend.serialize(weights);
switch(va.size)
{
case 4: putattrib(data[3], weights[3]);
case 3: putattrib(data[2], weights[2]);
case 2: putattrib(data[1], weights[1]);
case 1: putattrib(data[0], weights[0]);
}
}
else
{
switch(va.size)
{
case 4: scaleattrib(data[3], blend.weights[3]);
case 3: scaleattrib(data[2], blend.weights[2]);
case 2: scaleattrib(data[1], blend.weights[1]);
case 1: scaleattrib(data[0], blend.weights[0]);
}
}
lilswap(data, va.size);
}
template<int TYPE, class T>
void setupvertexarray(const vector<T> &attribs, uint type, uint fmt, uint size, uint first)
{
const char *name = "";
loopv(evarrays) if(evarrays[i].type == type)
{
evarray &info = evarrays[i];
fmt = info.format;
size = (uint)clamp((int)info.size, 1, 4);
name = info.name;
break;
}
if(type >= VVM_CUSTOM)
{
if(!name[0])
{
defformatstring(customname, "custom%d", type-VVM_CUSTOM);
type = VVM_CUSTOM + sharestring(customname);
}
else type = VVM_CUSTOM + sharestring(name);
}
int k;
for (k = 0; k < varrays.length(); k++)
{
if (varrays[k].type == type && varrays[k].format == fmt && varrays[k].size == size)
break;
}
if (k == varrays.length())
varrays.add(vertexarray(type, fmt, size));
vertexarray &va = varrays[k];
if (va.count != first)
fatal("count != first"); //gaps are a problem.
va.count += vmap.length();
int totalsize = va.bytesize() * vmap.length();
uchar *data = va.vdata.reserve(totalsize);
va.vdata.advance(totalsize);
loopv(vmap)
{
const T &attrib = attribs[remapindex<TYPE>(i, vmap[i])];
switch(va.format)
{
case VVM_BYTE: serializeattrib<TYPE, VVM_BYTE>(va, (char *)data, attrib); break;
case VVM_UBYTE: serializeattrib<TYPE, VVM_UBYTE>(va, (uchar *)data, attrib); break;
case VVM_SHORT: serializeattrib<TYPE, VVM_SHORT>(va, (short *)data, attrib); break;
case VVM_USHORT: serializeattrib<TYPE, VVM_USHORT>(va, (ushort *)data, attrib); break;
case VVM_INT: serializeattrib<TYPE, VVM_INT>(va, (int *)data, attrib); break;
case VVM_UINT: serializeattrib<TYPE, VVM_UINT>(va, (uint *)data, attrib); break;
case VVM_HALF: serializeattrib<TYPE, VVM_HALF>(va, (halfdata *)data, attrib); break;
case VVM_FLOAT: serializeattrib<TYPE, VVM_FLOAT>(va, (float *)data, attrib); break;
case VVM_DOUBLE: serializeattrib<TYPE, VVM_DOUBLE>(va, (double *)data, attrib); break;
}
data += va.bytesize();
}
}
// linear speed vertex cache optimization from Tom Forsyth
#define MAXVCACHE 32
struct triangleinfo
{
bool used;
float score;
uint vert[3];
triangleinfo() {
}
triangleinfo(uint v0, uint v1, uint v2)
{
vert[0] = v0;
vert[1] = v1;
vert[2] = v2;
}
};
struct vertexcache : listnode<vertexcache>
{
int index, rank;
float score;
int numuses;
triangleinfo **uses;
vertexcache() : index(-1), rank(-1), score(-1.0f), numuses(0), uses(NULL) {
}
void calcscore()
{
if(numuses > 0)
{
score = 2.0f * powf(numuses, -0.5f);
if(rank >= 3) score += powf(1.0f - (rank - 3)/float(MAXVCACHE - 3), 1.5f);
else if(rank >= 0) score += 0.75f;
}
else score = -1.0f;
}
void removeuse(triangleinfo *t)
{
loopi(numuses) if(uses[i] == t)
{
uses[i] = uses[--numuses];
return;
}
}
};
void maketriangles(vector<triangleinfo> &tris, const vector<sharedvert> &mmap)
{
triangleinfo **uses = new triangleinfo *[3*tris.length()];
vertexcache *verts = new vertexcache[mmap.length()];
list<vertexcache> vcache;
loopv(tris)
{
triangleinfo &t = tris[i];
t.used = t.vert[0] == t.vert[1] || t.vert[1] == t.vert[2] || t.vert[2] == t.vert[0];
if(t.used) continue;
loopk(3) verts[t.vert[k]].numuses++;
}
triangleinfo **curuse = uses;
loopvrev(tris)
{
triangleinfo &t = tris[i];
if(t.used) continue;
loopk(3)
{
vertexcache &v = verts[t.vert[k]];
if(!v.uses) { curuse += v.numuses; v.uses = curuse; }
*--v.uses = &t;
}
}
loopv(mmap) verts[i].calcscore();
triangleinfo *besttri = NULL;
float bestscore = -1e16f;
loopv(tris)
{
triangleinfo &t = tris[i];
if(t.used) continue;
t.score = verts[t.vert[0]].score + verts[t.vert[1]].score + verts[t.vert[2]].score;
if(t.score > bestscore) { besttri = &t; bestscore = t.score; }
}
//int reloads = 0;
while(besttri)
{
besttri->used = true;
triangle &t = triangles.add();
loopk(3)
{
vertexcache &v = verts[besttri->vert[k]];
if(v.index < 0) { v.index = vmap.length(); vmap.add(mmap[besttri->vert[k]]); }
t.vert[k] = v.index;
v.removeuse(besttri);
if(v.rank >= 0) vcache.remove(&v)->rank = -1;
// else reloads++;
if(v.numuses <= 0) continue;
vcache.insertfirst(&v);
v.rank = 0;
}
int rank = 0;
for(vertexcache *v = vcache.first(); v != vcache.end(); v = v->next)
{
v->rank = rank++;
v->calcscore();
}
besttri = NULL;
bestscore = -1e16f;
for(vertexcache *v = vcache.first(); v != vcache.end(); v = v->next)
{
loopi(v->numuses)
{
triangleinfo &t = *v->uses[i];
t.score = verts[t.vert[0]].score + verts[t.vert[1]].score + verts[t.vert[2]].score;
if(t.score > bestscore) { besttri = &t; bestscore = t.score; }
}
}
while(vcache.size > MAXVCACHE) vcache.removelast()->rank = -1;
if(!besttri) loopv(tris)
{
triangleinfo &t = tris[i];
if(!t.used && t.score > bestscore) { besttri = &t; bestscore = t.score; }
}
}
// printf("reloads: %d, worst: %d, best: %d\n", reloads, tris.length()*3, mmap.length());
delete[] uses;
delete[] verts;
}
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 (int i = priortris; i < triangles.length(); i++)
{
const triangle &t = triangles[i];
sharedvert &i0 = vmap[t.vert[0]],
&i1 = vmap[t.vert[1]],
&i2 = vmap[t.vert[2]];
Vec3 v0(epositions[i0.index]), e1 = Vec3(epositions[i1.index]) - v0, e2 = Vec3(epositions[i2.index]) - v0;
double u1 = etexcoords[i1.index].x - etexcoords[i0.index].x, v1 = etexcoords[i1.index].y - etexcoords[i0.index].y,
u2 = etexcoords[i2.index].x - etexcoords[i0.index].x, v2 = etexcoords[i2.index].y - etexcoords[i0.index].y;
Vec3 u = e2*v1 - e1*v2,
v = e2*u1 - e1*u2;
if(e2.cross(e1).dot(v.cross(u)) < 0)
{
u = -u;
v = -v;
}
if(!areaweight)
{
u = u.normalize();
v = v.normalize();
}
loopj(3)
{
tangent[t.vert[j]] += u;
bitangent[t.vert[j]] += v;
}
}
loopv(vmap)
{
const Vec3 &n = enormals[vmap[i].weld],
&t = tangent[i],
&bt = bitangent[i];
etangents.add(Vec4((t - n*n.dot(t)).normalize(), n.cross(t).dot(bt) < 0 ? -1 : 1));
}
delete[] tangent;
}
struct neighborkey
{
uint e0, e1;
neighborkey() {
}
neighborkey(uint i0, uint i1)
{
if(epositions[i0] < epositions[i1]) { e0 = i0; e1 = i1; }
else { e0 = i1; e1 = i0; }
}
uint hash() const {
return hthash(epositions[e0]) + hthash(epositions[e1]);
}
bool operator==(const neighborkey &n) const
{
return epositions[e0] == epositions[n.e0] && epositions[e1] == epositions[n.e1] &&
(eblends.empty() || (eblends[e0] == eblends[n.e0] && eblends[e1] == eblends[n.e1]));
}
};
static inline uint hthash(const neighborkey &n) {
return n.hash();
}
static inline bool htcmp(const neighborkey &x, const neighborkey &y) {
return x == y;
}
struct neighborval
{
uint tris[2];
neighborval() {
}
neighborval(uint i) {
tris[0] = i; tris[1] = 0xFFFFFFFFU;
}
void add(uint i)
{
if(tris[1] != 0xFFFFFFFFU) tris[0] = tris[1] = 0xFFFFFFFFU;
else if(tris[0] != 0xFFFFFFFFU) tris[1] = i;
}
int opposite(uint i) const
{
return tris[0] == i ? tris[1] : tris[0];
}
};
void makeneighbors(uint priortris)
{
hashtable<neighborkey, neighborval> nhash;
for(int i = priortris; i<triangles.length(); i++)
{
triangle &t = triangles[i];
for(int j = 0, p = 2; j < 3; p = j, j++)
{
neighborkey key(t.vert[p], t.vert[j]);
neighborval *val = nhash.access(key);
if(val) val->add(i);
else nhash[key] = neighborval(i);
}
}
for(int i = priortris; i<triangles.length(); i++)
{
triangle &t = triangles[i];
triangle &n = neighbors.add();
for(int j = 0, p = 2; j < 3; p = j, j++)
n.vert[p] = nhash[neighborkey(t.vert[p], t.vert[j])].opposite(i);
}
}
Quat erotate;
double escale = 1;
Vec3 emeshtrans(0, 0, 0);
Vec3 ejointtrans(0, 0, 0);
double gscale = 1;
Vec3 gmeshtrans(0,0,0);
void printlastmesh(void)
{
if (quiet)
return;
mesh &m = meshes[meshes.length()-1];
meshprop &fm = meshes_fte[meshes.length()-1];
printf(" %smesh %i:\tname=\"%s\",\tmat=\"%s\",\ttri=%i, vert=%i\n", fm.contents?"c":"r", meshes.length()-1,
&stringdata[m.name], &stringdata[m.material], m.numtris, m.numverts);
if (verbose)
{
if (noext)
printf(" writing mesh properties is disabled\n");
else
printf(" c=%#x sf=%#x b=%i gs=%i gi=%i nd=%g fd=%g\n", fm.contents, fm.surfaceflags, fm.body, fm.geomset, fm.geomid, fm.maxdist, fm.mindist);
}
}
void makemeshes(const filespec &spec)
{
if (spec.nomesh)
return;
/*
if (meshes.length())
return;
meshes.setsize(0);
meshes_fte.setsize(0);
triangles.setsize(0);
neighbors.setsize(0);
varrays.setsize(0);
vdata.setsize(0);
*/
int priorverts = numfverts;
int priortris = triangles.length();
hashtable<sharedvert, uint> mshare(1<<12);
vector<sharedvert> mmap;
vector<triangleinfo> tinfo;
if (!noext)
{
loopv(emeshes)
{
if (!emeshes[i].hasexplicits)
emeshes[i].explicits = spec.meshprops;
loopk(meshoverrides.length())
{
if (!strcmp(meshoverrides[k].name, emeshes[i].name))
{
emeshes[i].explicits = meshoverrides[k].props;
for (; k < meshoverrides.length()-1; k++)
meshoverrides[k] = meshoverrides[k+1];
meshoverrides.drop();
break;
}
}
}
}
loopv(emeshes)
{
emesh &em1 = emeshes[i];
if(em1.used) continue;
for(int j = i; j < emeshes.length(); j++)
{
emesh &em = emeshes[j];
if(strcmp(em.name, em1.name) || strcmp(em.material, em1.material) || memcmp(&em.explicits, &em1.explicits, sizeof(em.explicits))) continue;
int lasttri = emeshes.inrange(j+1) ? emeshes[j+1].firsttri : etriangles.length();
for(int k = em.firsttri; k < lasttri; k++)
{
etriangle &et = etriangles[k];
triangleinfo &t = tinfo.add();
loopl(3)
{
sharedvert v(et.vert[l], et.weld[l]);
t.vert[l] = mshare.access(v, mmap.length());
if(!mmap.inrange(t.vert[l])) mmap.add(v);
}
}
em.used = true;
}
if(tinfo.empty()) continue;
mesh &m = meshes.add();
m.name = sharestring(em1.name);
if (spec.materialprefix)
{
char material[512];
formatstring(material, "%s%s", spec.materialprefix, em1.material);
m.material = sharestring(material);
}
else
m.material = sharestring(em1.material);
m.firsttri = triangles.length();
m.firstvert = numfverts+vmap.length();
maketriangles(tinfo, mmap);
m.numtris = triangles.length() - m.firsttri;
m.numverts = numfverts+vmap.length() - m.firstvert;
meshprop &mf = meshes_fte.add();
mf = em1.explicits;
printlastmesh();
mshare.clear();
mmap.setsize(0);
tinfo.setsize(0);
}
numfverts+=vmap.length();
if(triangles.length()) makeneighbors(priortris);
if(escale != 1) loopv(epositions) epositions[i] *= escale;
if(erotate != Quat(0, 0, 0, 1))
{
loopv(epositions) epositions[i].setxyz(erotate.transform(Vec3(epositions[i])));
loopv(enormals) enormals[i] = erotate.transform(enormals[i]);
loopv(etangents) etangents[i].setxyz(erotate.transform(Vec3(etangents[i])));
loopv(ebitangents) ebitangents[i] = erotate.transform(ebitangents[i]);
}
if(emeshtrans != Vec3(0, 0, 0)) loopv(epositions) epositions[i] += emeshtrans;
if(epositions.length()) setupvertexarray<VVM_POSITION>(epositions, VVM_POSITION, VVM_FLOAT, 3, priorverts);
if(etexcoords.length()) setupvertexarray<VVM_TEXCOORD>(etexcoords, VVM_TEXCOORD, VVM_FLOAT, 2, priorverts);
if(enormals.length()) setupvertexarray<VVM_NORMAL>(enormals, VVM_NORMAL, VVM_FLOAT, 3, priorverts);
if(etangents.length())
{
if(ebitangents.length() && enormals.length())
{
loopv(etangents) if(ebitangents.inrange(i) && enormals.inrange(i))
etangents[i].w = enormals[i].cross(Vec3(etangents[i])).dot(ebitangents[i]) < 0 ? -1 : 1;
}
setupvertexarray<VVM_TANGENT>(etangents, VVM_TANGENT, VVM_FLOAT, 4, priorverts);
}
else if(enormals.length() && etexcoords.length())
{
calctangents(priortris);
setupvertexarray<VVM_TANGENT>(etangents, VVM_TANGENT, VVM_FLOAT, 4, priorverts);
}
if(eblends.length())
{
setupvertexarray<VVM_BLENDINDEXES>(eblends, VVM_BLENDINDEXES, VVM_UBYTE, 4, priorverts);
setupvertexarray<VVM_BLENDWEIGHTS>(eblends, VVM_BLENDWEIGHTS, VVM_UBYTE, 4, priorverts);
}
if(ecolors.length()) setupvertexarray<VVM_COLOR>(ecolors, VVM_COLOR, VVM_UBYTE, 4, priorverts);
loopi(10) if(ecustom[i].length()) setupvertexarray<VVM_CUSTOM>(ecustom[i], VVM_CUSTOM + i, VVM_FLOAT, 4, priorverts);
//make sure we keep this data in a usable form so that we can calc framebounds.
if(epositions.length())
{
Vec4 *o = mpositions.reserve(epositions.length());
mpositions.advance(epositions.length());
loopv(epositions)
o[i] = epositions[i];
}
if(eblends.length())
{
blendcombo *o = mblends.reserve(eblends.length());
mblends.advance(eblends.length());
loopv(eblends)
o[i] = eblends[i];
}
//the generated triangles currently refer to the imported arrays.
//make sure they refer to the final verts
if (priorverts)
for (int i = priortris; i < triangles.length(); i++)
{
triangles[i].vert[0] += priorverts;
triangles[i].vert[1] += priorverts;
triangles[i].vert[2] += priorverts;
}
}
void makebounds(framebounds &bb, Matrix3x4 *invbase, frame &frame)
{
vector<Matrix3x4> buf;
buf.growbuf(joints.length());
buf.setsize(joints.length());
//make sure all final bones have some value, even if its gibberish. should probably ignore verts that depend upon bones not defined in this animation.
//remap<0 means the bone was dropped.
loopv(buf) {
buf[i] = Matrix3x4(Quat(0,0,0,1),Vec3(0,0,0));
}
loopv(frame.pose) if (frame.pose[i].remap>=0)
{
int bone = frame.pose[i].remap;
int jparent = frame.pose[i].boneparent;
if (jparent >= 0) jparent = frame.pose[jparent].remap;
if(jparent >= 0) buf[bone] = buf[jparent] * Matrix3x4(frame.pose[i].tr.orient, frame.pose[i].tr.pos, frame.pose[i].tr.scale);
else buf[bone] = Matrix3x4(frame.pose[i].tr.orient, frame.pose[i].tr.pos, frame.pose[i].tr.scale);
}
loopv(frame.pose) buf[i] *= invbase[i];
loopv(mpositions)
{
const blendcombo &c = mblends[i];
Matrix3x4 m(Vec4(0, 0, 0, 0), Vec4(0, 0, 0, 0), Vec4(0, 0, 0, 0));
loopk(4) if(c.weights[k] > 0)
m += buf[c.bones[k]] * c.weights[k];
Vec3 p = m.transform(Vec3(mpositions[i]));
if(!i) bb.bbmin = bb.bbmax = p;
else
{
bb.bbmin.x = min(bb.bbmin.x, p.x);
bb.bbmin.y = min(bb.bbmin.y, p.y);
bb.bbmin.z = min(bb.bbmin.z, p.z);
bb.bbmax.x = max(bb.bbmax.x, p.x);
bb.bbmax.y = max(bb.bbmax.y, p.y);
bb.bbmax.z = max(bb.bbmax.z, p.z);
}
double xyradius = p.x*p.x + p.y*p.y;
bb.xyradius = max(bb.xyradius, xyradius);
bb.radius = max(bb.radius, xyradius + p.z*p.z);
}
if(bb.xyradius > 0) bb.xyradius = sqrt(bb.xyradius);
if(bb.radius > 0) bb.radius = sqrt(bb.radius);
}
void makerelativebasepose()
{
int numbasejoints = min(ejoints.length(), eframes.length() ? eframes[0] : eposes.length());
for(int i = numbasejoints-1; i >= 0; i--)
{
ejoint &ej = ejoints[i];
if(ej.parent < 0) continue;
transform &parent = eposes[ej.parent], &child = eposes[i];
child.pos = (-parent.orient).transform(child.pos - parent.pos);
child.orient = (-parent.orient)*child.orient;
if(child.orient.w > 0) child.orient.flip();
}
}
bool forcejoints = false;
void printlastanim(void)
{
if (quiet)
return;
anim &a = anims[anims.length()-1];
if (a.numframes == 1)
printf(" frame %i:\tname=\"%s\"\tfps=%g, %s\n", anims.length()-1,
&stringdata[a.name], a.fps, (a.flags & VVM_LOOP)?"looped":"clamped");
else {
printf(" anim %i:\tname=\"%s\",\tframes=%i, fps=%g, %s\n", anims.length()-1,
&stringdata[a.name], a.numframes, a.fps, (a.flags & VVM_LOOP)?"looped":"clamped");
}
loopv(events_fte)
{
if (events_fte[i].anim == (uint)anims.length()-1)
printf(" pose %g: %x \"%s\"\n", events_fte[i].timestamp*a.fps, events_fte[i].evcode, events_fte[i].evdata_str);
}
}
void printbones(int parent = -1, size_t ind = 1)
{
char prefix[256];
if (ind >= sizeof(prefix))
ind = sizeof(prefix)-1;
memset(prefix, ' ', ind);
prefix[ind] = 0;
loopv(joints)
{
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);
printbones(i, ind+1);
}
}
}
void printbonelist()
{
loopv(joints)
{
conoutf("bone %i:\tname=\"%s\"\tparent=%i%s, group=%i", i+1, &stringdata[joints[i].name], joints[i].parent+1, joints[i].parent >= i?"(ERROR)":"", joints[i].group);
}
}
int findjoint(const char *name)
{
loopv(joints)
{
if (!strcmp(&stringdata[joints[i].name], name))
return i;
}
return -1;
}
bool floatcmp(float f1, float f2)
{
if (f1 != f2)
return true;
return false;
}
void makeanims(const filespec &spec)
{
if(escale != 1) loopv(eposes) eposes[i].pos *= escale;
if(erotate != Quat(0, 0, 0, 1)) loopv(ejoints)
{
ejoint &ej = ejoints[i];
if(ej.parent < 0) for(int j = i; j < eposes.length(); j += ejoints.length())
{
transform &p = eposes[j];
p.orient = erotate * p.orient;
p.pos = erotate.transform(p.pos);
}
}
int numbasejoints = eframes.length() ? eframes[0] : eposes.length();
if(forcejoints || emeshes.length())
{
bool warned = false;
int *jr = new int[ejoints.length()];
loopv(ejoints)
{
ejoint &ej = ejoints[i];
jr[i] = findjoint(ej.name);
if (jr[i] >= 0)
{
bool rigmismatch = false;
if (warned || forcejoints)
continue;
joint &j = joints[jr[i]];
loopk(3) if (floatcmp(j.pos[k], eposes[i].pos[k] + (ej.parent>=0?0:ejointtrans[k]))) rigmismatch = true;
loopk(4) if (floatcmp(j.orient[k], eposes[i].orient[k])) rigmismatch = true;
loopk(3) if (floatcmp(j.scale[k], eposes[i].scale[k])) rigmismatch = true;
if (rigmismatch)
{
warned = true;
conoutf("warning: rig mismatch (bone %s)", ej.name);
}
continue;
}
jr[i] = joints.length();
joint &j = joints.add();
Matrix3x4 &m = mjoints.add();
const char *name = ej.name;
int group = -1;
loopvk(boneoverrides)
{
if (!strcmp(boneoverrides[k].name, name))
{
boneoverrides[k].used = true;
if (boneoverrides[k].props.rename)
name = boneoverrides[k].props.rename;
if (boneoverrides[k].props.group >= 0)
group = boneoverrides[k].props.group;
break;
}
}
j.name = sharestring(name);
if (ej.parent >= 0)
j.parent = findjoint(ejoints[ej.parent].name);
else
j.parent = -1;
if (group < 0 && j.parent >= 0)
group = joints[j.parent].group;
if (group < 0)
group = 0;
j.group = group;
if(i < numbasejoints)
{
m.invert(Matrix3x4(eposes[i].orient, eposes[i].pos + (ej.parent>=0?Vec3(0,0,0):ejointtrans), eposes[i].scale));
loopk(3) j.pos[k] = eposes[i].pos[k] + (ej.parent>=0?0:ejointtrans[k]);
loopk(4) j.orient[k] = eposes[i].orient[k];
loopk(3) j.scale[k] = eposes[i].scale[k];
}
else m.invert(Matrix3x4(Quat(0, 0, 0, 1), Vec3(0, 0, 0), Vec3(1, 1, 1)));
if(j.parent >= 0) m *= mjoints[j.parent];
}
loopv(eblends)
{
loopk(eblends[i].sorted)
{
int b = eblends[i].bones[k];
if (b >= ejoints.length())
b = 0;
else
b = jr[b];
eblends[i].bones[k] = b;
}
}
delete[] jr;
}
// loopv(spec.events)
// spec.events[i].evdata_idx = 0;
loopv(eanims)
{
eanim &ea = eanims[i];
if (ea.flags & VVM_UNPACK)
{ //some quake mods suck, and are unable to deal with animations
for (int j = ea.startframe, end = eanims.inrange(i+1) ? eanims[i+1].startframe : eframes.length(); j < end && j < ea.endframe; j++)
{
anim &a = anims.add();
char nname[256];
formatstring(nname, "%s%i", ea.name, j+1-ea.startframe);
a.name = sharestring(nname);
a.firstframe = frames.length();
a.numframes = 0;
a.fps = ea.fps;
a.flags = ea.flags&~VVM_ALLPRIVATE;
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))));
a.numframes++;
printlastanim();
}
}
else
{
anim &a = anims.add();
a.name = sharestring(ea.name);
a.firstframe = frames.length();
a.numframes = 0;
a.fps = ea.fps;
a.flags = ea.flags&~VVM_ALLPRIVATE;
for(int j = ea.startframe, end = eanims.inrange(i+1) ? eanims[i+1].startframe : eframes.length(); j < end && j <= ea.endframe; j++)
{
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()))
{
if (ejoints[k].parent < 0)
eposes[offset+k].pos += ejointtrans;
fr.pose.add(frame::framepose(ejoints[k], eposes[offset + k]));
}
loopk(max(ejoints.length() - range, 0))
{
if (ejoints[k].parent < 0)
fr.pose.add(frame::framepose(ejoints[k], transform(ejointtrans, Quat(0, 0, 0, 1), Vec3(1, 1, 1))));
else
fr.pose.add(frame::framepose(ejoints[k], transform(Vec3(0, 0, 0), Quat(0, 0, 0, 1), Vec3(1, 1, 1))));
}
a.numframes++;
}
loopvj(spec.events)
{
float p;
if (spec.events[j].anim == ~0u)
{
if (spec.events[j].timestamp < ea.startframe || spec.events[j].timestamp >= ea.startframe + a.numframes)
continue;
p = spec.events[j].timestamp - ea.startframe;
}
else
{
if (spec.events[j].anim != (uint)i)
continue;
else
p = spec.events[j].timestamp;
}
event_fte &ev = events_fte.add(spec.events[j]);
ev.anim = anims.length()-1;
ev.timestamp = p / a.fps;
}
printlastanim();
}
}
// loopv(spec.events) if (!spec.events[i].evdata_idx)
// {
// conoutf("event specifies invalid animation from %s", spec.file);
// }
}
bool resetimporter(const filespec &spec, bool reuse = false)
{
if(reuse)
{
ejoints.setsize(0);
evarrays.setsize(0);
return false;
}
vmap.setsize(0);
epositions.setsize(0);
etexcoords.setsize(0);
enormals.setsize(0);
etangents.setsize(0);
ebitangents.setsize(0);
ecolors.setsize(0);
loopi(10) ecustom[i].setsize(0);
eblends.setsize(0);
etriangles.setsize(0);
esmoothindexes.setsize(0);
esmoothedges.setsize(0);
esmoothgroups.setsize(0);
esmoothgroups.add();
ejoints.setsize(0);
eposes.setsize(0);
eframes.setsize(0);
eanims.setsize(0);
emeshes.setsize(0);
evarrays.setsize(0);
emeshtrans = gmeshtrans+spec.translate;
ejointtrans = spec.translate;
erotate = spec.rotate;
escale = gscale*spec.scale;
return true;
}
bool parseiqe(stream *f)
{
const char *curmesh = getnamekey(""), *curmaterial = getnamekey("");
bool needmesh = true;
int fmoffset = 0;
char buf[512];
if(!f->getline(buf, sizeof(buf))) return false;
if(!strchr(buf, '#') || strstr(buf, "# Inter-Quake Export") != strchr(buf, '#')) return false;
while(f->getline(buf, sizeof(buf)))
{
char *c = buf;
while(isspace(*c)) ++c;
if(isalpha(c[0]) && isalnum(c[1]) && (!c[2] || isspace(c[2]))) switch(*c++)
{
case 'v':
switch(*c++)
{
case 'p': epositions.add(parseattribs4(c, Vec4(0, 0, 0, 1))); continue;
case 't': etexcoords.add(parseattribs4(c)); continue;
case 'n': enormals.add(parseattribs3(c)); continue;
case 'x':
{
Vec4 tangent(parseattribs3(c), 0);
Vec3 bitangent(0, 0, 0);
bitangent.x = parseattrib(c);
if(maybeparseattrib(c, bitangent.y))
{
bitangent.z = parseattrib(c);
ebitangents.add(bitangent);
}
else tangent.w = bitangent.x;
etangents.add(tangent);
continue;
}
case 'b': eblends.add(parseblends(c)); continue;
case 'c': ecolors.add(parseattribs4(c, Vec4(0, 0, 0, 1))); continue;
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
{
int n = c[-1] - '0';
ecustom[n].add(parseattribs4(c));
continue;
}
case 's':
parseindex(c, esmoothindexes.add());
continue;
}
break;
case 'p':
{
transform t;
switch(*c++)
{
case 'q':
{
t.pos = parseattribs3(c);
loopk(3) t.orient[k] = parseattrib(c);
t.orient.restorew();
double w = parseattrib(c, t.orient.w);
if(w != t.orient.w)
{
t.orient.w = w;
t.orient.normalize();
// double x2 = f.orient.x*f.orient.x, y2 = f.orient.y*f.orient.y, z2 = f.orient.z*f.orient.z, w2 = f.orient.w*f.orient.w, s2 = x2 + y2 + z2 + w2;
// f.orient.x = keepsign(f.orient.x, sqrt(max(1.0 - (w2 + y2 + z2) / s2, 0.0)));
// f.orient.y = keepsign(f.orient.y, sqrt(max(1.0 - (w2 + x2 + z2) / s2, 0.0)));
// f.orient.z = keepsign(f.orient.z, sqrt(max(1.0 - (w2 + x2 + y2) / s2, 0.0)));
// f.orient.w = keepsign(f.orient.w, sqrt(max(1.0 - (x2 + y2 + z2) / s2, 0.0)));
}
if(t.orient.w > 0) t.orient.flip();
t.scale = parseattribs3(c, Vec3(1, 1, 1));
eposes.add(t);
continue;
}
case 'm':
{
t.pos = parseattribs3(c);
Matrix3x3 m;
m.a = parseattribs3(c);
m.b = parseattribs3(c);
m.c = parseattribs3(c);
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(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 = parseattribs3(c, Vec3(1, 1, 1)) * mscale;
eposes.add(t);
continue;
}
case 'a':
{
t.pos = parseattribs3(c);
Vec3 rot = parseattribs3(c);
t.orient = Quat::fromangles(rot);
t.scale = parseattribs3(c, Vec3(1, 1, 1));
eposes.add(t);
continue;
}
}
break;
}
case 'f':
switch(*c++)
{
case 'a':
{
int i1 = 0, i2 = 0, i3 = 0;
if(!parseindex(c, i1) || !parseindex(c, i2)) continue;
if(needmesh)
{
emeshes.add(emesh(curmesh, curmaterial, etriangles.length()));
needmesh = false;
}
if(i1 < 0) i1 = max(epositions.length() + i1, 0);
if(i2 < 0) i2 = max(epositions.length() + i2, 0);
while(parseindex(c, i3))
{
if(i3 < 0) i3 = max(epositions.length() + i3, 0);
esmoothgroups.last().flags |= esmoothgroup::F_USED;
etriangles.add(etriangle(i1, i2, i3, esmoothgroups.length()-1));
i2 = i3;
}
continue;
}
case 'm':
{
int i1 = 0, i2 = 0, i3 = 0;
if(!parseindex(c, i1) || !parseindex(c, i2)) continue;
if(needmesh)
{
emeshes.add(emesh(curmesh, curmaterial, etriangles.length()));
needmesh = false;
}
i1 = i1 < 0 ? max(epositions.length() + i1, 0) : (fmoffset + i1);
i2 = i2 < 0 ? max(epositions.length() + i2, 0) : (fmoffset + i2);
while(parseindex(c, i3))
{
i3 = i3 < 0 ? max(epositions.length() + i3, 0) : (fmoffset + i3);
esmoothgroups.last().flags |= esmoothgroup::F_USED;
etriangles.add(etriangle(i1, i2, i3, esmoothgroups.length()-1));
i2 = i3;
}
continue;
}
case 's':
{
int i1 = 0, i2 = 0, i3 = 0;
uchar flags = 0;
if(!parseindex(c, i1) || !parseindex(c, i2) || !parseindex(c, i3)) continue;
flags |= clamp(i1, 0, 1);
flags |= clamp(i2, 0, 1)<<1;
flags |= clamp(i3, 0, 1)<<2;
esmoothgroups.last().flags |= esmoothgroup::F_USED;
while(parseindex(c, i3))
{
esmoothedges.add(flags | 4);
flags = 1 | ((flags & 4) >> 1) | (clamp(i3, 0, 1)<<2);
}
esmoothedges.add(flags);
continue;
}
}
break;
}
char *args = c;
while(*args && !isspace(*args)) args++;
if(!strncmp(c, "smoothgroup", max(int(args-c), 11)))
{
if(esmoothgroups.last().flags & esmoothgroup::F_USED) esmoothgroups.dup();
parseindex(args, esmoothgroups.last().key);
}
else if(!strncmp(c, "smoothangle", max(int(args-c), 11)))
{
if(esmoothgroups.last().flags & esmoothgroup::F_USED) esmoothgroups.dup();
double angle = parseattrib(args, 0);
esmoothgroups.last().angle = fabs(cos(clamp(angle, -180.0, 180.0) * M_PI/180));
}
else if(!strncmp(c, "smoothuv", max(int(args-c), 8)))
{
if(esmoothgroups.last().flags & esmoothgroup::F_USED) esmoothgroups.dup();
int val = 1;
if(parseindex(args, val) && val <= 0) esmoothgroups.last().flags &= ~esmoothgroup::F_UVSMOOTH;
else esmoothgroups.last().flags |= esmoothgroup::F_UVSMOOTH;
}
else if(!strncmp(c, "mesh", max(int(args-c), 4)))
{
curmesh = getnamekey(trimname(args));
if(emeshes.empty() || emeshes.last().name != curmesh) needmesh = true;
fmoffset = epositions.length();
#if 0
emesh &m = emeshes.add();
m.firsttri = etriangles.length();
fmoffset = epositions.length();
parsename(args, m.name);
#endif
}
else if(!strncmp(c, "material", max(int(args-c), 8)))
{
curmaterial = getnamekey(trimname(args));
if(emeshes.empty() || emeshes.last().material != curmaterial) needmesh = true;
// if(emeshes.length()) parsename(c, emeshes.last().material);
}
else if(!strncmp(c, "joint", max(int(args-c), 5)))
{
ejoint &j = ejoints.add();
j.name = getnamekey(trimname(args));
parseindex(args, j.parent);
}
else if(!strncmp(c, "vertexarray", max(int(args-c), 11)))
{
evarray &va = evarrays.add();
va.type = findvertexarraytype(trimname(args));
va.format = findvertexarrayformat(trimname(args));
va.size = strtol(args, &args, 10);
copystring(va.name, trimname(args));
}
else if(!strncmp(c, "animation", max(int(args-c), 9)))
{
eanim &a = eanims.add();
a.name = getnamekey(trimname(args));
a.startframe = eframes.length();
if(!eframes.length() || eframes.last() != eposes.length()) eframes.add(eposes.length());
}
else if(!strncmp(c, "frame", max(int(args-c), 5)))
{
if(eanims.length() && eframes.length() && eframes.last() != eposes.length()) eframes.add(eposes.length());
}
else if(!strncmp(c, "framerate", max(int(args-c), 9)))
{
if(eanims.length())
{
double fps = parseattrib(args);
eanims.last().fps = max(fps, 0.0);
}
}
else if(!strncmp(c, "loop", max(int(args-c), 4)))
{
if(eanims.length()) eanims.last().flags |= VVM_LOOP;
}
else if(!strncmp(c, "comment", max(int(args-c), 7)))
{
if(commentdata.length()) break;
for(;;)
{
size_t len = f->read(commentdata.reserve(1024), 1024);
commentdata.advance(len);
if(len < 1024) { commentdata.add('\0'); break; }
}
}
}
return true;
}
bool loadiqe(const char *filename, const filespec &spec)
{
int numfiles = 0;
while(filename)
{
const char *endfile = strchr(filename, ',');
const char *file = endfile ? newstring(filename, endfile-filename) : filename;
stream *f = openfile(file, "r");
if(f)
{
resetimporter(spec, numfiles > 0);
if(parseiqe(f)) numfiles++;
delete f;
}
if(!endfile) break;
delete[] file;
filename = endfile+1;
}
if(!numfiles) return false;
if(eanims.length() == 1)
{
eanim &a = eanims.last();
if(spec.name) a.name = spec.name;
if(spec.fps > 0) a.fps = spec.fps;
a.flags |= spec.flags;
if(spec.endframe >= 0) a.endframe = a.startframe + spec.endframe;
else if(spec.endframe < -1) a.endframe = a.startframe + max(eframes.length() - a.startframe + spec.endframe + 1, 0);
a.startframe += spec.startframe;
}
makeanims(spec);
if(emeshes.length())
{
smoothverts();
makemeshes(spec);
}
return true;
}
struct md5weight
{
int joint;
double bias;
Vec3 pos;
};
struct md5vert
{
double u, v;
uint start, count;
};
struct md5hierarchy
{
const char *name;
int parent, flags, start;
};
vector<md5weight> weightinfo;
vector<md5vert> vertinfo;
void buildmd5verts()
{
loopv(vertinfo)
{
md5vert &v = vertinfo[i];
Vec3 pos(0, 0, 0);
loopk(v.count)
{
md5weight &w = weightinfo[v.start+k];
transform &j = eposes[w.joint];
pos += (j.orient.transform(w.pos) + j.pos)*w.bias;
}
epositions.add(Vec4(pos, 1));
etexcoords.add(Vec4(v.u, v.v, 0, 0));
blendcombo &c = eblends.add();
loopj(v.count)
{
md5weight &w = weightinfo[v.start+j];
c.addweight(w.bias, w.joint);
}
c.finalize();
}
}
void parsemd5mesh(stream *f, char *buf, size_t bufsize)
{
md5weight w;
md5vert v;
etriangle t(0, 0, 0, 0);
int index, firsttri = etriangles.length(), firstvert = vertinfo.length(), firstweight = weightinfo.length(), numtris = 0, numverts = 0, numweights = 0;
emesh m;
while(f->getline(buf, bufsize) && buf[0]!='}')
{
if(strstr(buf, "// meshes:"))
{
char *start = strchr(buf, ':')+1;
if(*start==' ') start++;
char *end = start + strlen(start)-1;
while(end >= start && isspace(*end)) end--;
end[1] = '\0';
m.name = getnamekey(start);
}
else if(strstr(buf, "shader"))
{
char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : NULL;
if(start && end)
{
*end = '\0';
m.material = getnamekey(start+1);
}
}
else if(sscanf(buf, " numverts %d", &numverts)==1)
{
numverts = max(numverts, 0);
if(numverts)
{
vertinfo.reserve(numverts);
vertinfo.advance(numverts);
}
}
else if(sscanf(buf, " numtris %d", &numtris)==1)
{
numtris = max(numtris, 0);
if(numtris)
{
etriangles.reserve(numtris);
etriangles.advance(numtris);
}
m.firsttri = firsttri;
}
else if(sscanf(buf, " numweights %d", &numweights)==1)
{
numweights = max(numweights, 0);
if(numweights)
{
weightinfo.reserve(numweights);
weightinfo.advance(numweights);
}
}
else if(sscanf(buf, " vert %d ( %lf %lf ) %u %u", &index, &v.u, &v.v, &v.start, &v.count)==5)
{
if(index>=0 && index<numverts)
{
v.start += firstweight;
vertinfo[firstvert + index] = v;
}
}
else if(sscanf(buf, " tri %d %u %u %u", &index, &t.vert[0], &t.vert[1], &t.vert[2])==4)
{
if(index>=0 && index<numtris)
{
loopk(3) t.vert[k] += firstvert;
etriangles[firsttri + index] = t;
}
}
else if(sscanf(buf, " weight %d %d %lf ( %lf %lf %lf ) ", &index, &w.joint, &w.bias, &w.pos.x, &w.pos.y, &w.pos.z)==6)
{
if(index>=0 && index<numweights) weightinfo[firstweight + index] = w;
}
}
if(numtris && numverts) emeshes.add(m);
}
bool loadmd5mesh(const char *filename, const filespec &spec)
{
stream *f = openfile(filename, "r");
if(!f) return false;
resetimporter(spec);
esmoothgroups[0].flags |= esmoothgroup::F_UVSMOOTH;
char buf[512];
while(f->getline(buf, sizeof(buf)))
{
int tmp;
if(sscanf(buf, " MD5Version %d", &tmp)==1)
{
if(tmp!=10) { delete f; return false; }
}
else if(sscanf(buf, " numJoints %d", &tmp)==1)
{
if(tmp<1 || (joints.length() && tmp != joints.length())) { delete f; return false; }
}
else if(sscanf(buf, " numMeshes %d", &tmp)==1)
{
if(tmp<1) { delete f; return false; }
}
else if(strstr(buf, "joints {"))
{
ejoint j;
transform p;
while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
{
char *c = buf;
j.name = getnamekey(trimname(c));
if(sscanf(c, " %d ( %lf %lf %lf ) ( %lf %lf %lf )",
&j.parent, &p.pos.x, &p.pos.y, &p.pos.z,
&p.orient.x, &p.orient.y, &p.orient.z)==7)
{
p.orient.restorew();
p.scale = Vec3(1, 1, 1);
ejoints.add(j);
eposes.add(p);
}
}
}
else if(strstr(buf, "mesh {"))
{
parsemd5mesh(f, buf, sizeof(buf));
}
}
delete f;
buildmd5verts();
makeanims(spec);
smoothverts();
makemeshes(spec);
makerelativebasepose();
return true;
}
bool loadmd5anim(const char *filename, const filespec &spec)
{
stream *f = openfile(filename, "r");
if(!f) return false;
resetimporter(spec);
vector<md5hierarchy> hierarchy;
vector<transform> baseframe;
int animdatalen = 0, animframes = 0, frameoffset = eposes.length(), firstframe = eframes.length();
double framerate = 0;
double *animdata = NULL;
char buf[512];
while(f->getline(buf, sizeof(buf)))
{
int tmp;
if(sscanf(buf, " MD5Version %d", &tmp)==1)
{
if(tmp!=10) { delete f; return false; }
}
else if(sscanf(buf, " numJoints %d", &tmp)==1)
{
if(tmp<1) { delete f; return false; }
}
else if(sscanf(buf, " numFrames %d", &animframes)==1)
{
if(animframes<1) { delete f; return false; }
}
else if(sscanf(buf, " frameRate %lf", &framerate)==1);
else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
{
if(animdatalen>0) animdata = new double[animdatalen];
}
else if(strstr(buf, "bounds {"))
{
while(f->getline(buf, sizeof(buf)) && buf[0]!='}');
}
else if(strstr(buf, "hierarchy {"))
{
while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
{
char *c = buf;
md5hierarchy h;
h.name = getnamekey(trimname(c));
if(sscanf(c, " %d %d %d", &h.parent, &h.flags, &h.start)==3)
hierarchy.add(h);
}
if(hierarchy.empty()) { delete f; return false; }
loopv(hierarchy)
{
md5hierarchy &h = hierarchy[i];
ejoint &j = ejoints.add();
j.name = h.name;
j.parent = h.parent;
}
}
else if(strstr(buf, "baseframe {"))
{
while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
{
transform j;
if(sscanf(buf, " ( %lf %lf %lf ) ( %lf %lf %lf )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
{
j.orient.restorew();
j.scale = Vec3(1, 1, 1);
baseframe.add(j);
}
}
if(baseframe.length()!=hierarchy.length()) { delete f; return false; }
eposes.reserve(animframes*baseframe.length());
eposes.advance(animframes*baseframe.length());
}
else if(sscanf(buf, " frame %d", &tmp)==1)
{
for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
{
for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
{
animdata[numdata] = strtod(src, &next);
if(next <= src) break;
}
}
int offset = frameoffset + tmp*baseframe.length();
eframes.add(offset);
loopv(baseframe)
{
md5hierarchy &h = hierarchy[i];
transform j = baseframe[i];
if(h.start < animdatalen && h.flags)
{
double *jdata = &animdata[h.start];
if(h.flags&1) j.pos.x = *jdata++;
if(h.flags&2) j.pos.y = *jdata++;
if(h.flags&4) j.pos.z = *jdata++;
if(h.flags&8) j.orient.x = *jdata++;
if(h.flags&16) j.orient.y = *jdata++;
if(h.flags&32) j.orient.z = *jdata++;
j.orient.restorew();
}
eposes[offset + i] = j;
}
}
}
if(animdata) delete[] animdata;
delete f;
eanim &a = eanims.add();
if(spec.name) a.name = getnamekey(spec.name);
else
{
string name;
copystring(name, filename);
char *end = strrchr(name, '.');
if(end) *end = '\0';
a.name = getnamekey(name);
}
a.startframe = firstframe;
a.fps = spec.fps > 0 ? spec.fps : framerate;
a.flags = spec.flags;
if(spec.endframe >= 0) a.endframe = a.startframe + spec.endframe;
else if(spec.endframe < -1) a.endframe = a.startframe + max(eframes.length() - a.startframe + spec.endframe + 1, 0);
a.startframe += spec.startframe;
makeanims(spec);
return true;
}
namespace smd
{
bool skipcomment(char *&curbuf)
{
while(*curbuf && isspace(*curbuf)) curbuf++;
switch(*curbuf)
{
case '#':
case ';':
case '\r':
case '\n':
case '\0':
return true;
case '/':
if(curbuf[1] == '/') return true;
break;
}
return false;
}
void skipsection(stream *f, char *buf, size_t bufsize)
{
while(f->getline(buf, bufsize))
{
char *curbuf = buf;
if(skipcomment(curbuf)) continue;
if(!strncmp(curbuf, "end", 3)) break;
}
}
void readname(char *&curbuf, char *name, size_t namesize)
{
char *curname = name;
while(*curbuf && isspace(*curbuf)) curbuf++;
bool allowspace = false;
if(*curbuf == '"') { curbuf++; allowspace = true; }
while(*curbuf)
{
char c = *curbuf++;