838 lines
28 KiB
C++
838 lines
28 KiB
C++
struct skelbih
|
|
{
|
|
struct node
|
|
{
|
|
short split[2];
|
|
ushort child[2];
|
|
|
|
int axis() const { return child[0]>>14; }
|
|
int childindex(int which) const { return child[which]&0x3FFF; }
|
|
bool isleaf(int which) const { return (child[1]&(1<<(14+which)))!=0; }
|
|
};
|
|
|
|
struct tri : skelmodel::tri
|
|
{
|
|
uchar mesh, id;
|
|
};
|
|
|
|
node *nodes;
|
|
int numnodes;
|
|
tri *tris;
|
|
|
|
vec bbmin, bbmax;
|
|
|
|
skelbih(skelmodel::skelmeshgroup *m, int numtris, tri *tris);
|
|
|
|
~skelbih()
|
|
{
|
|
DELETEA(nodes);
|
|
}
|
|
|
|
bool triintersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, int tidx, const vec &o, const vec &ray);
|
|
|
|
void build(skelmodel::skelmeshgroup *m, ushort *indices, int numindices, const vec &vmin, const vec &vmax);
|
|
|
|
void intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const vec &o, const vec &ray, const vec &invray, node *curnode, float tmin, float tmax);
|
|
void intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const vec &o, const vec &ray);
|
|
|
|
vec calccenter() const
|
|
{
|
|
return vec(bbmin).add(bbmax).mul(0.5f);
|
|
}
|
|
|
|
float calcradius() const
|
|
{
|
|
return vec(bbmax).sub(bbmin).mul(0.5f).magnitude();
|
|
}
|
|
};
|
|
|
|
#define SKELTRIINTERSECT(a, b, c) \
|
|
vec eb = vec(b).sub(a), ec = vec(c).sub(a); \
|
|
vec p; \
|
|
p.cross(ray, ec); \
|
|
float det = eb.dot(p); \
|
|
if(det == 0) return false; \
|
|
vec r = vec(o).sub(a); \
|
|
float u = r.dot(p) / det; \
|
|
if(u < 0 || u > 1) return false; \
|
|
vec q; \
|
|
q.cross(r, eb); \
|
|
float v = ray.dot(q) / det; \
|
|
if(v < 0 || u + v > 1) return false; \
|
|
float f = ec.dot(q) / det; \
|
|
if(f < 0 || f*skelmodel::intersectscale > skelmodel::intersectdist) return false; \
|
|
if(!(skelmodel::intersectmode&RAY_SHADOW) && tm->noclip) return false; \
|
|
if((skelmodel::intersectmode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY) \
|
|
{ \
|
|
Texture *tex = s[t.mesh].tex; \
|
|
if(tex->type&Texture::ALPHA && (tex->alphamask || loadalphamask(tex))) \
|
|
{ \
|
|
int si = clamp(int(tex->xs * (va.tc.x + u*(vb.tc.x - va.tc.x) + v*(vc.tc.x - va.tc.x))), 0, tex->xs-1), \
|
|
ti = clamp(int(tex->ys * (va.tc.y + u*(vb.tc.y - va.tc.y) + v*(vc.tc.y - va.tc.y))), 0, tex->ys-1); \
|
|
if(!(tex->alphamask[ti*((tex->xs+7)/8) + si/8] & (1<<(si%8)))) return false; \
|
|
} \
|
|
} \
|
|
skelmodel::intersectdist = f*skelmodel::intersectscale; \
|
|
skelmodel::intersectresult = t.id&0x80 ? -1 : t.id; \
|
|
return true;
|
|
|
|
bool skelbih::triintersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, int tidx, const vec &o, const vec &ray)
|
|
{
|
|
const tri &t = tris[tidx];
|
|
skelmodel::skelmesh *tm = (skelmodel::skelmesh *)m->meshes[t.mesh];
|
|
const skelmodel::vert &va = tm->verts[t.vert[0]], &vb = tm->verts[t.vert[1]], &vc = tm->verts[t.vert[2]];
|
|
SKELTRIINTERSECT(va.pos, vb.pos, vc.pos)
|
|
}
|
|
|
|
struct skelbihstack
|
|
{
|
|
skelbih::node *node;
|
|
float tmin, tmax;
|
|
};
|
|
|
|
inline void skelbih::intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const vec &o, const vec &ray, const vec &invray, node *curnode, float smin, float smax)
|
|
{
|
|
skelbihstack stack[128];
|
|
int stacksize = 0;
|
|
ivec order(ray.x>0 ? 0 : 1, ray.y>0 ? 0 : 1, ray.z>0 ? 0 : 1);
|
|
float tmin = smin, tmax = smax;
|
|
for(;;)
|
|
{
|
|
int axis = curnode->axis();
|
|
int nearidx = order[axis], faridx = nearidx^1;
|
|
float nearsplit = (curnode->split[nearidx] - o[axis])*invray[axis],
|
|
farsplit = (curnode->split[faridx] - o[axis])*invray[axis];
|
|
|
|
if(nearsplit <= tmin)
|
|
{
|
|
if(farsplit < tmax)
|
|
{
|
|
if(!curnode->isleaf(faridx))
|
|
{
|
|
curnode += curnode->childindex(faridx);
|
|
tmin = max(tmin, farsplit);
|
|
continue;
|
|
}
|
|
else if(triintersect(m, s, curnode->childindex(faridx), o, ray))
|
|
smax = min(smax, skelmodel::intersectdist/skelmodel::intersectscale);
|
|
}
|
|
}
|
|
else if(curnode->isleaf(nearidx))
|
|
{
|
|
if(triintersect(m, s, curnode->childindex(nearidx), o, ray))
|
|
{
|
|
smax = min(smax, skelmodel::intersectdist/skelmodel::intersectscale);
|
|
tmax = min(tmax, smax);
|
|
}
|
|
if(farsplit < tmax)
|
|
{
|
|
if(!curnode->isleaf(faridx))
|
|
{
|
|
curnode += curnode->childindex(faridx);
|
|
tmin = max(tmin, farsplit);
|
|
continue;
|
|
}
|
|
else if(triintersect(m, s, curnode->childindex(faridx), o, ray))
|
|
smax = min(smax, skelmodel::intersectdist/skelmodel::intersectscale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(farsplit < tmax)
|
|
{
|
|
if(!curnode->isleaf(faridx))
|
|
{
|
|
if(stacksize < int(sizeof(stack)/sizeof(stack[0])))
|
|
{
|
|
skelbihstack &save = stack[stacksize++];
|
|
save.node = curnode + curnode->childindex(faridx);
|
|
save.tmin = max(tmin, farsplit);
|
|
save.tmax = tmax;
|
|
}
|
|
else
|
|
{
|
|
intersect(m, s, o, ray, invray, curnode + curnode->childindex(nearidx), tmin, tmax);
|
|
curnode += curnode->childindex(faridx);
|
|
tmin = max(tmin, farsplit);
|
|
}
|
|
}
|
|
else if(triintersect(m, s, curnode->childindex(faridx), o, ray))
|
|
{
|
|
smax = min(smax, skelmodel::intersectdist/skelmodel::intersectscale);
|
|
tmax = min(tmax, smax);
|
|
}
|
|
}
|
|
curnode += curnode->childindex(nearidx);
|
|
tmax = min(tmax, nearsplit);
|
|
continue;
|
|
}
|
|
if(stacksize <= 0) return;
|
|
skelbihstack &restore = stack[--stacksize];
|
|
curnode = restore.node;
|
|
tmin = restore.tmin;
|
|
tmax = min(restore.tmax, smax);
|
|
}
|
|
}
|
|
|
|
void skelbih::intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const vec &o, const vec &ray)
|
|
{
|
|
vec invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f);
|
|
float tmin, tmax;
|
|
float t1 = (bbmin.x - o.x)*invray.x,
|
|
t2 = (bbmax.x - o.x)*invray.x;
|
|
if(invray.x > 0) { tmin = t1; tmax = t2; } else { tmin = t2; tmax = t1; }
|
|
t1 = (bbmin.y - o.y)*invray.y;
|
|
t2 = (bbmax.y - o.y)*invray.y;
|
|
if(invray.y > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); }
|
|
t1 = (bbmin.z - o.z)*invray.z;
|
|
t2 = (bbmax.z - o.z)*invray.z;
|
|
if(invray.z > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); }
|
|
tmax = min(tmax, skelmodel::intersectdist/skelmodel::intersectscale);
|
|
if(tmin >= tmax) return;
|
|
|
|
if(nodes) intersect(m, s, o, ray, invray, nodes, tmin, tmax);
|
|
else triintersect(m, s, 0, o, ray);
|
|
}
|
|
|
|
void skelbih::build(skelmodel::skelmeshgroup *m, ushort *indices, int numindices, const vec &vmin, const vec &vmax)
|
|
{
|
|
int axis = 2;
|
|
loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k;
|
|
|
|
vec leftmin, leftmax, rightmin, rightmax;
|
|
float splitleft, splitright;
|
|
int left, right;
|
|
loopk(3)
|
|
{
|
|
leftmin = rightmin = vec(1e16f, 1e16f, 1e16f);
|
|
leftmax = rightmax = vec(-1e16f, -1e16f, -1e16f);
|
|
float split = 0.5f*(vmax[axis] + vmin[axis]);
|
|
for(left = 0, right = numindices, splitleft = SHRT_MIN, splitright = SHRT_MAX; left < right;)
|
|
{
|
|
tri &tri = tris[indices[left]];
|
|
skelmodel::skelmesh *tm = (skelmodel::skelmesh *)m->meshes[tri.mesh];
|
|
const vec &ta = tm->verts[tri.vert[0]].pos, &tb = tm->verts[tri.vert[1]].pos, &tc = tm->verts[tri.vert[2]].pos;
|
|
vec trimin = vec(ta).min(tb).min(tc), trimax = vec(ta).max(tb).max(tc);
|
|
float amin = trimin[axis], amax = trimax[axis];
|
|
if(max(split - amin, 0.0f) > max(amax - split, 0.0f))
|
|
{
|
|
++left;
|
|
splitleft = max(splitleft, amax);
|
|
leftmin.min(trimin);
|
|
leftmax.max(trimax);
|
|
}
|
|
else
|
|
{
|
|
--right;
|
|
swap(indices[left], indices[right]);
|
|
splitright = min(splitright, amin);
|
|
rightmin.min(trimin);
|
|
rightmax.max(trimax);
|
|
}
|
|
}
|
|
if(left > 0 && right < numindices) break;
|
|
axis = (axis+1)%3;
|
|
}
|
|
|
|
if(!left || right==numindices)
|
|
{
|
|
leftmin = rightmin = vec(1e16f, 1e16f, 1e16f);
|
|
leftmax = rightmax = vec(-1e16f, -1e16f, -1e16f);
|
|
left = right = numindices/2;
|
|
splitleft = SHRT_MIN;
|
|
splitright = SHRT_MAX;
|
|
loopi(numindices)
|
|
{
|
|
tri &tri = tris[indices[i]];
|
|
skelmodel::skelmesh *tm = (skelmodel::skelmesh *)m->meshes[tri.mesh];
|
|
const vec &ta = tm->verts[tri.vert[0]].pos, &tb = tm->verts[tri.vert[1]].pos, &tc = tm->verts[tri.vert[2]].pos;
|
|
vec trimin = vec(ta).min(tb).min(tc), trimax = vec(ta).max(tb).max(tc);
|
|
if(i < left)
|
|
{
|
|
splitleft = max(splitleft, trimax[axis]);
|
|
leftmin.min(trimin);
|
|
leftmax.max(trimax);
|
|
}
|
|
else
|
|
{
|
|
splitright = min(splitright, trimin[axis]);
|
|
rightmin.min(trimin);
|
|
rightmax.max(trimax);
|
|
}
|
|
}
|
|
}
|
|
|
|
int offset = numnodes++;
|
|
node &curnode = nodes[offset];
|
|
curnode.split[0] = short(ceil(splitleft));
|
|
curnode.split[1] = short(floor(splitright));
|
|
|
|
if(left==1) curnode.child[0] = (axis<<14) | indices[0];
|
|
else
|
|
{
|
|
curnode.child[0] = (axis<<14) | (numnodes - offset);
|
|
build(m, indices, left, leftmin, leftmax);
|
|
}
|
|
|
|
if(numindices-right==1) curnode.child[1] = (1<<15) | (left==1 ? 1<<14 : 0) | indices[right];
|
|
else
|
|
{
|
|
curnode.child[1] = (left==1 ? 1<<14 : 0) | (numnodes - offset);
|
|
build(m, &indices[right], numindices-right, rightmin, rightmax);
|
|
}
|
|
}
|
|
|
|
skelbih::skelbih(skelmodel::skelmeshgroup *m, int numtris, tri *tris)
|
|
: nodes(NULL), numnodes(0), tris(tris), bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f)
|
|
{
|
|
loopi(numtris)
|
|
{
|
|
tri &tri = tris[i];
|
|
skelmodel::skelmesh *tm = (skelmodel::skelmesh *)m->meshes[tri.mesh];
|
|
const vec &ta = tm->verts[tri.vert[0]].pos, &tb = tm->verts[tri.vert[1]].pos, &tc = tm->verts[tri.vert[2]].pos;
|
|
bbmin.min(ta).min(tb).min(tc);
|
|
bbmax.max(ta).max(tb).max(tc);
|
|
}
|
|
if(numtris > 1)
|
|
{
|
|
nodes = new node[numtris];
|
|
ushort *indices = new ushort[numtris];
|
|
loopi(numtris) indices[i] = i;
|
|
build(m, indices, numtris, bbmin, bbmax);
|
|
delete[] indices;
|
|
}
|
|
}
|
|
|
|
struct skelhitzone
|
|
{
|
|
typedef skelbih::tri tri;
|
|
|
|
int numparents, numchildren;
|
|
skelhitzone **parents, **children;
|
|
vec center, animcenter;
|
|
float radius;
|
|
int visited;
|
|
union
|
|
{
|
|
int blend;
|
|
int numtris;
|
|
};
|
|
union
|
|
{
|
|
tri *tris;
|
|
skelbih *bih;
|
|
};
|
|
|
|
skelhitzone() : numparents(0), numchildren(0), parents(NULL), children(NULL), center(0, 0, 0), animcenter(0, 0, 0), radius(0), visited(-1)
|
|
{
|
|
blend = -1;
|
|
bih = NULL;
|
|
}
|
|
|
|
~skelhitzone()
|
|
{
|
|
if(!numchildren) { DELETEP(bih); }
|
|
else { DELETEA(tris); }
|
|
}
|
|
|
|
static bool triintersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const dualquat *bdata1, const dualquat *bdata2, int numblends, const tri &t, const vec &o, const vec &ray);
|
|
|
|
bool shellintersect(const vec &o, const vec &ray)
|
|
{
|
|
vec c(animcenter);
|
|
c.sub(o);
|
|
float v = c.dot(ray), inside = radius*radius - c.squaredlen();
|
|
if(inside < 0 && v < 0) return false;
|
|
float d = inside + v*v;
|
|
if(d < 0) return false;
|
|
v -= skelmodel::intersectdist/skelmodel::intersectscale;
|
|
return v < 0 || d >= v*v;
|
|
}
|
|
|
|
void intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const dualquat *bdata1, const dualquat *bdata2, int numblends, const vec &o, const vec &ray)
|
|
{
|
|
if(!numchildren)
|
|
{
|
|
if(bih)
|
|
{
|
|
const dualquat &b = blend < numblends ? bdata2[blend] : bdata1[m->skel->bones[blend - numblends].interpindex];
|
|
vec bo = b.transposedtransform(o), bray = b.transposedtransformnormal(ray);
|
|
bih->intersect(m, s, bo, bray);
|
|
}
|
|
}
|
|
else if(shellintersect(o, ray))
|
|
{
|
|
loopi(numtris) triintersect(m, s, bdata1, bdata2, numblends, tris[i], o, ray);
|
|
loopi(numchildren) if(children[i]->visited != visited)
|
|
{
|
|
children[i]->visited = visited;
|
|
children[i]->intersect(m, s, bdata1, bdata2, numblends, o, ray);
|
|
}
|
|
}
|
|
}
|
|
|
|
void propagate(skelmodel::skelmeshgroup *m, const dualquat *bdata1, const dualquat *bdata2, int numblends)
|
|
{
|
|
if(!numchildren)
|
|
{
|
|
const dualquat &b = blend < numblends ? bdata2[blend] : bdata1[m->skel->bones[blend - numblends].interpindex];
|
|
animcenter = b.transform(center);
|
|
}
|
|
else
|
|
{
|
|
animcenter = children[numchildren-1]->animcenter;
|
|
radius = children[numchildren-1]->radius;
|
|
loopi(numchildren-1)
|
|
{
|
|
skelhitzone *child = children[i];
|
|
vec n = child->animcenter;
|
|
n.sub(animcenter);
|
|
float dist = n.magnitude();
|
|
if(child->radius >= dist + radius)
|
|
{
|
|
animcenter = child->animcenter;
|
|
radius = child->radius;
|
|
}
|
|
else if(radius < dist + child->radius)
|
|
{
|
|
float newradius = 0.5f*(radius + dist + child->radius);
|
|
animcenter.add(n.mul((newradius - radius)/dist));
|
|
radius = newradius;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
bool skelhitzone::triintersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const dualquat *bdata1, const dualquat *bdata2, int numblends, const tri &t, const vec &o, const vec &ray)
|
|
{
|
|
skelmodel::skelmesh *tm = (skelmodel::skelmesh *)m->meshes[t.mesh];
|
|
const skelmodel::vert &va = tm->verts[t.vert[0]], &vb = tm->verts[t.vert[1]], &vc = tm->verts[t.vert[2]];
|
|
vec a = (va.blend < numblends ? bdata2[va.blend] : bdata1[m->blendcombos[va.blend].interpbones[0]]).transform(va.pos),
|
|
b = (vb.blend < numblends ? bdata2[vb.blend] : bdata1[m->blendcombos[vb.blend].interpbones[0]]).transform(vb.pos),
|
|
c = (vc.blend < numblends ? bdata2[vc.blend] : bdata1[m->blendcombos[vc.blend].interpbones[0]]).transform(vc.pos);
|
|
SKELTRIINTERSECT(a, b, c);
|
|
}
|
|
|
|
struct skelzonekey
|
|
{
|
|
int blend;
|
|
uchar bones[12];
|
|
|
|
skelzonekey() : blend(-1) { memset(bones, 0xFF, sizeof(bones)); }
|
|
skelzonekey(int bone) : blend(INT_MAX) { bones[0] = bone; memset(&bones[1], 0xFF, sizeof(bones)-1); }
|
|
skelzonekey(skelmodel::skelmesh *m, const skelmodel::tri &t)
|
|
: blend(-1)
|
|
{
|
|
memset(bones, 0xFF, sizeof(bones));
|
|
addbones(m, t);
|
|
}
|
|
|
|
bool includes(const skelzonekey &o)
|
|
{
|
|
int j = 0;
|
|
loopi(sizeof(bones))
|
|
{
|
|
if(bones[i] > o.bones[j]) return false;
|
|
if(bones[i] == o.bones[j]) j++;
|
|
}
|
|
return j < (int)sizeof(bones) ? o.bones[j] == 0xFF : blend < 0 || blend == o.blend;
|
|
}
|
|
|
|
void subtract(const skelzonekey &o)
|
|
{
|
|
int len = 0, j = 0;
|
|
loopi(sizeof(bones))
|
|
{
|
|
retry:
|
|
if(j >= (int)sizeof(o.bones) || bones[i] < o.bones[j]) { bones[len++] = bones[i]; continue; }
|
|
if(bones[i] == o.bones[j]) { j++; continue; }
|
|
do j++; while(j < (int)sizeof(o.bones) && bones[i] > o.bones[j]);
|
|
goto retry;
|
|
}
|
|
memset(&bones[len], 0xFF, sizeof(bones) - len);
|
|
}
|
|
|
|
bool hasbone(int n)
|
|
{
|
|
loopi(sizeof(bones))
|
|
{
|
|
if(bones[i] == n) return true;
|
|
if(bones[i] == 0xFF) break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int numbones()
|
|
{
|
|
loopi(sizeof(bones)) if(bones[i] == 0xFF) return i;
|
|
return sizeof(bones);
|
|
}
|
|
|
|
void addbone(int n)
|
|
{
|
|
loopi(sizeof(bones))
|
|
{
|
|
if(n <= bones[i])
|
|
{
|
|
if(n < bones[i])
|
|
{
|
|
memmove(&bones[i+1], &bones[i], sizeof(bones) - (i+1));
|
|
bones[i] = n;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void addbones(skelmodel::skelmesh *m, const skelmodel::tri &t)
|
|
{
|
|
skelmodel::skelmeshgroup *g = (skelmodel::skelmeshgroup *)m->group;
|
|
int b0 = m->verts[t.vert[0]].blend, b1 = m->verts[t.vert[1]].blend, b2 = m->verts[t.vert[1]].blend;
|
|
const skelmodel::blendcombo &c0 = g->blendcombos[b0];
|
|
loopi(4) if(c0.weights[i]) addbone(c0.bones[i]);
|
|
if(b0 != b1 || b0 != b2)
|
|
{
|
|
const skelmodel::blendcombo &c1 = g->blendcombos[b1];
|
|
loopi(4) if(c1.weights[i]) addbone(c1.bones[i]);
|
|
const skelmodel::blendcombo &c2 = g->blendcombos[b2];
|
|
loopi(4) if(c2.weights[i]) addbone(c2.bones[i]);
|
|
}
|
|
else blend = b0;
|
|
}
|
|
};
|
|
|
|
struct skelzonebounds
|
|
{
|
|
int owner;
|
|
vec bbmin, bbmax;
|
|
|
|
skelzonebounds() : owner(-1), bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f) {}
|
|
|
|
void addvert(const vec &p)
|
|
{
|
|
bbmin.x = min(bbmin.x, p.x);
|
|
bbmin.y = min(bbmin.y, p.y);
|
|
bbmin.z = min(bbmin.z, p.z);
|
|
bbmax.x = max(bbmax.x, p.x);
|
|
bbmax.y = max(bbmax.y, p.y);
|
|
bbmax.z = max(bbmax.z, p.z);
|
|
}
|
|
|
|
bool empty() const { return bbmin.x > bbmax.x; }
|
|
|
|
vec calccenter() const
|
|
{
|
|
return vec(bbmin).add(bbmax).mul(0.5f);
|
|
}
|
|
|
|
float calcradius() const
|
|
{
|
|
return vec(bbmax).sub(bbmin).mul(0.5f).magnitude();
|
|
}
|
|
};
|
|
|
|
struct skelzoneinfo
|
|
{
|
|
int index, parents, conflicts;
|
|
skelzonekey key;
|
|
vector<skelzoneinfo *> children;
|
|
vector<skelhitzone::tri> tris;
|
|
|
|
skelzoneinfo() : index(-1), parents(0), conflicts(0) {}
|
|
skelzoneinfo(const skelzonekey &key) : index(-1), parents(0), conflicts(0), key(key) {}
|
|
};
|
|
|
|
static inline bool htcmp(const skelzonekey &x, const skelzoneinfo &y) { return !memcmp(x.bones, y.key.bones, sizeof(x.bones)) && (x.bones[1] == 0xFF || x.blend == y.key.blend); }
|
|
|
|
static inline uint hthash(const skelzonekey &k)
|
|
{
|
|
union { uint i[3]; uchar b[12]; } conv;
|
|
memcpy(conv.b, k.bones, sizeof(conv.b));
|
|
return conv.i[0]^conv.i[1]^conv.i[2];
|
|
}
|
|
|
|
struct skelhitdata
|
|
{
|
|
int numzones, rootzones, visited;
|
|
skelhitzone *zones;
|
|
skelhitzone **links;
|
|
skelhitzone::tri *tris;
|
|
int numblends;
|
|
skelmodel::blendcacheentry blendcache;
|
|
|
|
skelhitdata() : numzones(0), rootzones(0), visited(0), zones(NULL), links(NULL), tris(NULL), numblends(0) {}
|
|
~skelhitdata()
|
|
{
|
|
DELETEA(zones);
|
|
DELETEA(links);
|
|
DELETEA(tris);
|
|
DELETEA(blendcache.bdata);
|
|
}
|
|
|
|
uchar chooseid(skelmodel::skelmeshgroup *g, skelmodel::skelmesh *m, const skelmodel::tri &t, const uchar *ids);
|
|
void build(skelmodel::skelmeshgroup *g, const uchar *ids);
|
|
|
|
void cleanup()
|
|
{
|
|
blendcache.owner = -1;
|
|
}
|
|
|
|
void propagate(skelmodel::skelmeshgroup *m, const dualquat *bdata1, dualquat *bdata2)
|
|
{
|
|
visited = 0;
|
|
loopi(numzones)
|
|
{
|
|
zones[i].visited = -1;
|
|
zones[i].propagate(m, bdata1, bdata2, numblends);
|
|
}
|
|
}
|
|
|
|
void intersect(skelmodel::skelmeshgroup *m, skelmodel::skin *s, const dualquat *bdata1, dualquat *bdata2, const vec &o, const vec &ray)
|
|
{
|
|
if(++visited < 0)
|
|
{
|
|
visited = 0;
|
|
loopi(numzones) zones[i].visited = -1;
|
|
}
|
|
for(int i = numzones - rootzones; i < numzones; i++)
|
|
{
|
|
zones[i].visited = visited;
|
|
zones[i].intersect(m, s, bdata1, bdata2, numblends, o, ray);
|
|
}
|
|
}
|
|
};
|
|
|
|
void skelmodel::skelmeshgroup::cleanuphitdata()
|
|
{
|
|
if(hitdata) hitdata->cleanup();
|
|
}
|
|
|
|
void skelmodel::skelmeshgroup::deletehitdata()
|
|
{
|
|
DELETEP(hitdata);
|
|
}
|
|
|
|
void skelmodel::skelmeshgroup::intersect(skelhitdata *z, part *p, const skelmodel::skelcacheentry &sc, const vec &o, const vec &ray)
|
|
{
|
|
int owner = &sc - &skel->skelcache[0];
|
|
skelmodel::blendcacheentry &bc = z->blendcache;
|
|
if(bc.owner != owner || bc != sc)
|
|
{
|
|
bc.owner = owner;
|
|
bc.millis = lastmillis;
|
|
(animcacheentry &)bc = sc;
|
|
blendbones(sc.bdata, bc.bdata, blendcombos.getbuf(), z->numblends);
|
|
z->propagate(this, sc.bdata, bc.bdata);
|
|
}
|
|
z->intersect(this, p->skins.getbuf(), sc.bdata, bc.bdata, o, ray);
|
|
}
|
|
|
|
uchar skelhitdata::chooseid(skelmodel::skelmeshgroup *g, skelmodel::skelmesh *m, const skelmodel::tri &t, const uchar *ids)
|
|
{
|
|
int numused = 0;
|
|
uchar used[12];
|
|
float weights[12];
|
|
loopk(3)
|
|
{
|
|
const skelmodel::vert &v = m->verts[t.vert[k]];
|
|
const skelmodel::blendcombo &c = g->blendcombos[v.blend];
|
|
loopl(4) if(c.weights[l])
|
|
{
|
|
uchar id = ids[c.bones[l]];
|
|
loopi(numused) if(used[i] == id) { weights[i] += c.weights[l]; goto nextbone; }
|
|
used[numused] = id;
|
|
weights[numused] = c.weights[l];
|
|
numused++;
|
|
nextbone:;
|
|
}
|
|
}
|
|
uchar bestid = 0xFF;
|
|
float bestweight = 0;
|
|
loopi(numused) if(weights[i] > bestweight || (weights[i] == bestweight && used[i] < bestid))
|
|
{
|
|
bestid = used[i];
|
|
bestweight = weights[i];
|
|
}
|
|
return bestid;
|
|
}
|
|
|
|
void skelhitdata::build(skelmodel::skelmeshgroup *g, const uchar *ids)
|
|
{
|
|
if(numzones) return;
|
|
|
|
hashset<skelzoneinfo> infomap(256);
|
|
vector<skelzoneinfo *> info;
|
|
skelzonebounds *bounds = new skelzonebounds[g->skel->numbones];
|
|
numblends = g->blendcombos.length();
|
|
loopv(g->blendcombos) if(!g->blendcombos[i].weights[1]) { numblends = i; break; }
|
|
blendcache.bdata = numblends > 0 ? new dualquat[numblends] : NULL;
|
|
loopi(min(g->meshes.length(), 0x100))
|
|
{
|
|
skelmodel::skelmesh *m = (skelmodel::skelmesh *)g->meshes[i];
|
|
loopj(m->numtris)
|
|
{
|
|
const skelmodel::tri &t = m->tris[j];
|
|
loopk(3)
|
|
{
|
|
const skelmodel::vert &v = m->verts[t.vert[k]];
|
|
const skelmodel::blendcombo &c = g->blendcombos[v.blend];
|
|
loopl(4) if(c.weights[l]) bounds[c.bones[l]].addvert(v.pos);
|
|
}
|
|
skelzonekey key(m, t);
|
|
skelzoneinfo &zi = infomap.access(key, key);
|
|
if(key.blend >= numblends && zi.index < 0)
|
|
{
|
|
bounds[key.bones[0]].owner = zi.index = info.length();
|
|
info.add(&zi);
|
|
}
|
|
skelhitzone::tri &zt = zi.tris.add();
|
|
zt.mesh = i;
|
|
zt.id = chooseid(g, m, t, ids);
|
|
memcpy(zt.vert, t.vert, sizeof(zt.vert));
|
|
}
|
|
}
|
|
loopi(g->skel->numbones)
|
|
{
|
|
if(bounds[i].empty() || bounds[i].owner >= 0) continue;
|
|
skelzonekey key(i);
|
|
skelzoneinfo &zi = infomap.access(key, key);
|
|
zi.index = info.length();
|
|
info.add(&zi);
|
|
}
|
|
int leafzones = info.length();
|
|
enumerate(infomap, skelzoneinfo, zi, { if(zi.index < 0) info.add(&zi); });
|
|
for(int i = leafzones; i < info.length(); i++)
|
|
{
|
|
skelzoneinfo &zi = *info[i];
|
|
if(zi.key.blend >= 0) continue;
|
|
loopvj(info) if(i != j && zi.key.includes(info[j]->key))
|
|
{
|
|
skelzoneinfo &zj = *info[j];
|
|
loopvk(zi.children)
|
|
{
|
|
skelzoneinfo &zk = *zi.children[k];
|
|
if(zk.key.includes(zj.key)) goto nextzone;
|
|
if(zj.key.includes(zk.key))
|
|
{
|
|
zk.parents--;
|
|
zj.parents++;
|
|
zi.children[k] = &zj;
|
|
while(++k < zi.children.length()) if(zj.key.includes(zi.children[k]->key)) { zi.children[k]->parents--; zi.children.removeunordered(k--); }
|
|
goto nextzone;
|
|
}
|
|
}
|
|
zj.parents++;
|
|
zi.children.add(&zj);
|
|
nextzone:;
|
|
}
|
|
skelzonekey deps = zi.key;
|
|
loopvj(zi.children)
|
|
{
|
|
skelzoneinfo &zj = *zi.children[j];
|
|
if(zj.key.blend < 0 || zj.key.blend >= numblends) deps.subtract(zj.key);
|
|
}
|
|
loopj(sizeof(deps.bones))
|
|
{
|
|
if(deps.bones[j]==0xFF) break;
|
|
skelzonekey dep(deps.bones[j]);
|
|
skelzoneinfo &zj = infomap.access(dep, dep);
|
|
zj.parents++;
|
|
zi.children.add(&zj);
|
|
}
|
|
}
|
|
for(int i = leafzones; i < info.length(); i++)
|
|
{
|
|
skelzoneinfo &zi = *info[i];
|
|
loopvj(zi.children)
|
|
{
|
|
skelzoneinfo &zj = *zi.children[j];
|
|
if(zj.tris.length() <= 2 && zj.parents == 1)
|
|
{
|
|
zi.tris.put(zj.tris.getbuf(), zj.tris.length());
|
|
zj.tris.setsize(0);
|
|
if(zj.index < 0)
|
|
{
|
|
zj.parents = 0;
|
|
zi.children.removeunordered(j--);
|
|
}
|
|
zi.children.put(zj.children.getbuf(), zj.children.length());
|
|
zj.children.setsize(0);
|
|
}
|
|
}
|
|
}
|
|
int numlinks = 0, numtris = 0;
|
|
loopvrev(info)
|
|
{
|
|
skelzoneinfo &zi = *info[i];
|
|
if(zi.parents || zi.tris.empty()) info.removeunordered(i);
|
|
zi.conflicts = zi.parents;
|
|
numlinks += zi.parents + zi.children.length();
|
|
numtris += zi.tris.length();
|
|
}
|
|
rootzones = info.length();
|
|
loopv(info)
|
|
{
|
|
skelzoneinfo &zi = *info[i];
|
|
zi.index = i;
|
|
loopvj(zi.children)
|
|
{
|
|
skelzoneinfo &zj = *zi.children[j];
|
|
if(!--zj.conflicts) info.add(&zj);
|
|
}
|
|
}
|
|
numzones = info.length();
|
|
zones = new skelhitzone[numzones];
|
|
links = numlinks ? new skelhitzone *[numlinks] : NULL;
|
|
tris = new skelhitzone::tri[numtris];
|
|
skelhitzone **curlink = links;
|
|
skelhitzone::tri *curtris = tris;
|
|
loopi(numzones)
|
|
{
|
|
skelhitzone &z = zones[i];
|
|
skelzoneinfo &zi = *info[info.length()-1 - i];
|
|
memcpy(curtris, zi.tris.getbuf(), zi.tris.length()*sizeof(skelhitzone::tri));
|
|
if(zi.key.blend >= numblends)
|
|
{
|
|
z.blend = zi.key.bones[0] + numblends;
|
|
if(zi.tris.length()) z.bih = new skelbih(g, zi.tris.length(), curtris);
|
|
const skelzonebounds &b = bounds[zi.key.bones[0]];
|
|
z.center = b.calccenter();
|
|
z.radius = b.calcradius();
|
|
}
|
|
else if(zi.key.blend >= 0)
|
|
{
|
|
z.blend = zi.key.blend;
|
|
z.bih = new skelbih(g, zi.tris.length(), curtris);
|
|
z.center = z.bih->calccenter();
|
|
z.radius = z.bih->calcradius();
|
|
}
|
|
else
|
|
{
|
|
z.numtris = zi.tris.length();
|
|
z.tris = curtris;
|
|
}
|
|
curtris += zi.tris.length();
|
|
z.parents = curlink;
|
|
curlink += zi.parents;
|
|
z.numchildren = zi.children.length();
|
|
z.children = curlink;
|
|
loopvj(zi.children) z.children[j] = &zones[info.length()-1 - zi.children[j]->index];
|
|
curlink += zi.children.length();
|
|
}
|
|
loopi(numzones)
|
|
{
|
|
skelhitzone &z = zones[i];
|
|
loopj(z.numchildren) z.children[j]->parents[z.children[j]->numparents++] = &z;
|
|
}
|
|
delete[] bounds;
|
|
}
|
|
|
|
void skelmodel::skelmeshgroup::buildhitdata(const uchar *hitzones)
|
|
{
|
|
if(hitdata) return;
|
|
hitdata = new skelhitdata;
|
|
hitdata->build(this, hitzones);
|
|
}
|
|
|