From 7256502afa0babe60fcafbd2888cd3e33c3f9b6b Mon Sep 17 00:00:00 2001 From: xolatile Date: Wed, 16 Jul 2025 23:07:43 +0200 Subject: Source code, broken... --- src/engine/animmodel.h | 1617 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1617 insertions(+) create mode 100644 src/engine/animmodel.h (limited to 'src/engine/animmodel.h') diff --git a/src/engine/animmodel.h b/src/engine/animmodel.h new file mode 100644 index 0000000..16d5189 --- /dev/null +++ b/src/engine/animmodel.h @@ -0,0 +1,1617 @@ +VARFP(envmapmodels, 0, 1, 1, preloadmodelshaders(true)); +VARFP(bumpmodels, 0, 1, 1, preloadmodelshaders(true)); +VARP(fullbrightmodels, 0, 0, 200); + +struct animmodel : model +{ + struct animspec + { + int frame, range; + float speed; + int priority; + }; + + struct animpos + { + int anim, fr1, fr2; + float t; + + void setframes(const animinfo &info) + { + anim = info.anim; + if(info.range<=1) + { + fr1 = 0; + t = 0; + } + else + { + int time = info.anim&ANIM_SETTIME ? info.basetime : lastmillis-info.basetime; + fr1 = (int)(time/info.speed); // round to full frames + t = (time-fr1*info.speed)/info.speed; // progress of the frame, value from 0.0f to 1.0f + } + if(info.anim&ANIM_LOOP) + { + fr1 = fr1%info.range+info.frame; + fr2 = fr1+1; + if(fr2>=info.frame+info.range) fr2 = info.frame; + } + else + { + fr1 = min(fr1, info.range-1)+info.frame; + fr2 = min(fr1+1, info.frame+info.range-1); + } + if(info.anim&ANIM_REVERSE) + { + fr1 = (info.frame+info.range-1)-(fr1-info.frame); + fr2 = (info.frame+info.range-1)-(fr2-info.frame); + } + } + + bool operator==(const animpos &a) const { return fr1==a.fr1 && fr2==a.fr2 && (fr1==fr2 || t==a.t); } + bool operator!=(const animpos &a) const { return fr1!=a.fr1 || fr2!=a.fr2 || (fr1!=fr2 && t!=a.t); } + }; + + struct part; + + struct animstate + { + part *owner; + animpos cur, prev; + float interp; + + bool operator==(const animstate &a) const { return cur==a.cur && (interp<1 ? interp==a.interp && prev==a.prev : a.interp>=1); } + bool operator!=(const animstate &a) const { return cur!=a.cur || (interp<1 ? interp!=a.interp || prev!=a.prev : a.interp<1); } + }; + + struct linkedpart; + struct mesh; + + struct shaderparams + { + float spec, ambient, glow, glowdelta, glowpulse, specglare, glowglare, fullbright, envmapmin, envmapmax, scrollu, scrollv, alphatest; + + shaderparams() : spec(1.0f), ambient(0.3f), glow(3.0f), glowdelta(0), glowpulse(0), specglare(1), glowglare(1), fullbright(0), envmapmin(0), envmapmax(0), scrollu(0), scrollv(0), alphatest(0.9f) {} + }; + + struct shaderparamskey + { + static hashtable keys; + static int firstversion, lastversion; + + int version; + + shaderparamskey() : version(-1) {} + + bool checkversion() + { + if(version >= firstversion) return true; + version = lastversion; + if(++lastversion <= 0) + { + enumerate(keys, shaderparamskey, key, key.version = -1); + firstversion = 0; + lastversion = 1; + version = 0; + } + return false; + } + + static inline void invalidate() + { + firstversion = lastversion; + } + }; + + struct skin : shaderparams + { + part *owner; + Texture *tex, *masks, *envmap, *normalmap; + Shader *shader; + bool alphablend, cullface; + shaderparamskey *key; + + skin() : owner(0), tex(notexture), masks(notexture), envmap(NULL), normalmap(NULL), shader(NULL), alphablend(true), cullface(true), key(NULL) {} + + bool masked() const { return masks != notexture; } + bool envmapped() { return envmapmax>0 && envmapmodels; } + bool bumpmapped() { return normalmap && bumpmodels; } + bool tangents() { return bumpmapped(); } + bool alphatested() const { return alphatest > 0 && tex->type&Texture::ALPHA; } + + void setkey() + { + key = &shaderparamskey::keys[*this]; + } + + void setshaderparams(mesh *m, const animstate *as) + { + if(!Shader::lastshader) return; + + float mincolor = as->cur.anim&ANIM_FULLBRIGHT ? fullbrightmodels/100.0f : 0.0f; + if(fullbright) + { + gle::colorf(fullbright/2, fullbright/2, fullbright/2, transparent); + } + else + { + gle::color(vec(lightcolor).max(mincolor), transparent); + } + + if(key->checkversion() && Shader::lastshader->owner == key) return; + Shader::lastshader->owner = key; + + if(alphatested()) LOCALPARAMF(alphatest, alphatest); + + if(fullbright) + { + LOCALPARAMF(lightscale, 0, 0, 2); + } + else + { + float bias = max(mincolor-1.0f, 0.2f), scale = 0.5f*max(0.8f-bias, 0.0f), + minshade = scale*max(ambient, mincolor); + LOCALPARAMF(lightscale, scale - minshade, scale, minshade + bias); + } + float curglow = glow; + if(glowpulse > 0) + { + float curpulse = lastmillis*glowpulse; + curpulse -= floor(curpulse); + curglow += glowdelta*2*fabs(curpulse - 0.5f); + } + LOCALPARAMF(maskscale, 0.5f*spec, 0.5f*curglow, 16*specglare, 4*glowglare); + LOCALPARAMF(texscroll, scrollu*lastmillis/1000.0f, scrollv*lastmillis/1000.0f); + if(envmapped()) LOCALPARAMF(envmapscale, envmapmin-envmapmax, envmapmax); + } + + Shader *loadshader() + { + #define DOMODELSHADER(name, body) \ + do { \ + static Shader *name##shader = NULL; \ + if(!name##shader) name##shader = useshaderbyname(#name); \ + body; \ + } while(0) + #define LOADMODELSHADER(name) DOMODELSHADER(name, return name##shader) + #define SETMODELSHADER(m, name) DOMODELSHADER(name, (m)->setshader(name##shader)) + if(shader) return shader; + + string opts; + int optslen = 0; + if(alphatested()) opts[optslen++] = 'a'; + if(owner->tangents()) opts[optslen++] = 'q'; + if(bumpmapped()) opts[optslen++] = 'n'; + if(envmapped()) opts[optslen++] = 'e'; + if(masked()) opts[optslen++] = 'm'; + if(!fullbright && (masked() || spec>=0.01f)) opts[optslen++] = 's'; + opts[optslen++] = '\0'; + + defformatstring(name, "model%s", opts); + shader = generateshader(name, "modelshader \"%s\"", opts); + return shader; + } + + void cleanup() + { + if(shader && shader->standard) shader = NULL; + } + + void preloadBIH() + { + if(tex->type&Texture::ALPHA && !tex->alphamask) loadalphamask(tex); + } + + void preloadshader(bool force) + { + if(force) cleanup(); + loadshader(); + } + + void setshader(mesh *m, const animstate *as) + { + m->setshader(loadshader()); + } + + void bind(mesh *b, const animstate *as) + { + if(!cullface && enablecullface) { glDisable(GL_CULL_FACE); enablecullface = false; } + else if(cullface && !enablecullface) { glEnable(GL_CULL_FACE); enablecullface = true; } + + if(as->cur.anim&ANIM_NOSKIN) + { + if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } + if(shadowmapping) SETMODELSHADER(b, shadowmapcaster); + else /*if(as->cur.anim&ANIM_SHADOW)*/ SETMODELSHADER(b, notexturemodel); + return; + } + setshader(b, as); + setshaderparams(b, as); + int activetmu = 0; + if(tex!=lasttex) + { + glBindTexture(GL_TEXTURE_2D, tex->id); + lasttex = tex; + } + if(bumpmapped() && normalmap !=lastnormalmap) + { + glActiveTexture_(GL_TEXTURE3); + activetmu = 3; + glBindTexture(GL_TEXTURE_2D, normalmap->id); + lastnormalmap = normalmap; + } + if(tex->type&Texture::ALPHA) + { + if(alphablend) + { + if(!enablealphablend && !reflecting && !refracting) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + enablealphablend = true; + } + } + else if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } + } + else if(enablealphablend && transparent>=1) { glDisable(GL_BLEND); enablealphablend = false; } + if(masked() && masks!=lastmasks) + { + glActiveTexture_(GL_TEXTURE1); + activetmu = 1; + glBindTexture(GL_TEXTURE_2D, masks->id); + lastmasks = masks; + } + if(envmapped()) + { + GLuint emtex = envmap ? envmap->id : closestenvmaptex; + if(lastenvmaptex!=emtex) + { + glActiveTexture_(GL_TEXTURE2); + activetmu = 2; + glBindTexture(GL_TEXTURE_CUBE_MAP, emtex); + lastenvmaptex = emtex; + } + } + if(activetmu != 0) glActiveTexture_(GL_TEXTURE0); + } + }; + + struct meshgroup; + + struct mesh + { + meshgroup *group; + char *name; + bool noclip; + + mesh() : group(NULL), name(NULL), noclip(false) + { + } + + virtual ~mesh() + { + DELETEA(name); + } + + virtual void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) {} + + virtual void genBIH(BIH::mesh &m) {} + void genBIH(skin &s, vector &bih, const matrix4x3 &t) + { + BIH::mesh &m = bih.add(); + m.xform = t; + m.tex = s.tex; + if(s.tex->type&Texture::ALPHA) m.flags |= BIH::MESH_ALPHA; + if(noclip) m.flags |= BIH::MESH_NOCLIP; + if(s.cullface) m.flags |= BIH::MESH_CULLFACE; + genBIH(m); + while(bih.last().numtris > BIH::mesh::MAXTRIS) + { + BIH::mesh &overflow = bih.dup(); + overflow.tris += BIH::mesh::MAXTRIS; + overflow.numtris -= BIH::mesh::MAXTRIS; + bih[bih.length()-2].numtris = BIH::mesh::MAXTRIS; + } + } + + virtual void setshader(Shader *s) + { + if(glaring) s->setvariant(0, 1); + else s->set(); + } + + template void smoothnorms(V *verts, int numverts, T *tris, int numtris, float limit, bool areaweight) + { + hashtable share; + int *next = new int[numverts]; + memset(next, -1, numverts*sizeof(int)); + loopi(numverts) + { + V &v = verts[i]; + v.norm = vec(0, 0, 0); + int idx = share.access(v.pos, i); + if(idx != i) { next[i] = next[idx]; next[idx] = i; } + } + loopi(numtris) + { + T &t = tris[i]; + V &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]]; + vec norm; + norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos)); + if(!areaweight) norm.normalize(); + v1.norm.add(norm); + v2.norm.add(norm); + v3.norm.add(norm); + } + vec *norms = new vec[numverts]; + memclear(norms, numverts); + loopi(numverts) + { + V &v = verts[i]; + norms[i].add(v.norm); + if(next[i] >= 0) + { + float vlimit = limit*v.norm.magnitude(); + for(int j = next[i]; j >= 0; j = next[j]) + { + V &o = verts[j]; + if(v.norm.dot(o.norm) >= vlimit*o.norm.magnitude()) + { + norms[i].add(o.norm); + norms[j].add(v.norm); + } + } + } + } + loopi(numverts) verts[i].norm = norms[i].normalize(); + delete[] next; + delete[] norms; + } + + template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight) + { + loopi(numverts) verts[i].norm = vec(0, 0, 0); + loopi(numtris) + { + T &t = tris[i]; + V &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]]; + vec norm; + norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos)); + if(!areaweight) norm.normalize(); + v1.norm.add(norm); + v2.norm.add(norm); + v3.norm.add(norm); + } + loopi(numverts) verts[i].norm.normalize(); + } + + template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight, int numframes) + { + if(!numverts) return; + loopi(numframes) buildnorms(&verts[i*numverts], numverts, tris, numtris, areaweight); + } + + static inline void fixqtangent(quat &q, float bt) + { + static const float bias = -1.5f/65535, biasscale = sqrtf(1 - bias*bias); + if(bt < 0) + { + if(q.w >= 0) q.neg(); + if(q.w > bias) { q.mul3(biasscale); q.w = bias; } + } + else if(q.w < 0) q.neg(); + } + + template static inline void calctangent(V &v, const vec &n, const vec &t, float bt) + { + matrix3 m; + m.c = n; + m.a = t; + m.b.cross(m.c, m.a); + quat q(m); + fixqtangent(q, bt); + v.tangent = q; + } + + template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight) + { + vec *tangent = new vec[2*numverts], *bitangent = tangent+numverts; + memclear(tangent, 2*numverts); + loopi(numtris) + { + const T &t = tris[i]; + const vec &e0 = verts[t.vert[0]].pos; + vec e1 = vec(verts[t.vert[1]].pos).sub(e0), e2 = vec(verts[t.vert[2]].pos).sub(e0); + + const vec2 &tc0 = tcverts[t.vert[0]].tc, + &tc1 = tcverts[t.vert[1]].tc, + &tc2 = tcverts[t.vert[2]].tc; + float u1 = tc1.x - tc0.x, v1 = tc1.y - tc0.y, + u2 = tc2.x - tc0.x, v2 = tc2.y - tc0.y; + vec u(e2), v(e2); + u.mul(v1).sub(vec(e1).mul(v2)); + v.mul(u1).sub(vec(e1).mul(u2)); + + if(vec().cross(e2, e1).dot(vec().cross(v, u)) >= 0) + { + u.neg(); + v.neg(); + } + + if(!areaweight) + { + u.normalize(); + v.normalize(); + } + + loopj(3) + { + tangent[t.vert[j]].sub(u); + bitangent[t.vert[j]].add(v); + } + } + loopi(numverts) + { + const vec &n = verts[i].norm, + &t = tangent[i], + &bt = bitangent[i]; + B &bv = bumpverts[i]; + matrix3 m; + m.c = n; + (m.a = t).project(m.c).normalize(); + m.b.cross(m.c, m.a); + quat q(m); + fixqtangent(q, m.b.dot(bt)); + bv.tangent = q; + } + delete[] tangent; + } + + template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight, int numframes) + { + loopi(numframes) calctangents(&bumpverts[i*numverts], &verts[i*numverts], tcverts, numverts, tris, numtris, areaweight); + } + }; + + struct meshgroup + { + meshgroup *next; + int shared; + char *name; + vector meshes; + + meshgroup() : next(NULL), shared(0), name(NULL) + { + } + + virtual ~meshgroup() + { + DELETEA(name); + meshes.deletecontents(); + DELETEP(next); + } + + virtual int findtag(const char *name) { return -1; } + virtual void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) {} + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + loopv(meshes) meshes[i]->calcbb(bbmin, bbmax, m); + } + + void genBIH(vector &skins, vector &bih, const matrix4x3 &t) + { + loopv(meshes) meshes[i]->genBIH(skins[i], bih, t); + } + + virtual void *animkey() { return this; } + virtual int totalframes() const { return 1; } + bool hasframe(int i) const { return i>=0 && i=0 && i+n<=totalframes(); } + int clipframes(int i, int n) const { return min(n, totalframes() - i); } + + virtual void cleanup() {} + virtual void preload(part *p) {} + virtual void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) {} + + void bindpos(GLuint ebuf, GLuint vbuf, void *v, int stride) + { + if(lastebuf!=ebuf) + { + gle::bindebo(ebuf); + lastebuf = ebuf; + } + if(lastvbuf!=vbuf) + { + gle::bindvbo(vbuf); + if(!lastvbuf) gle::enablevertex(); + gle::vertexpointer(stride, v); + lastvbuf = vbuf; + } + } + + void bindtc(void *v, int stride) + { + if(!enabletc) + { + gle::enabletexcoord0(); + enabletc = true; + } + if(lasttcbuf!=lastvbuf) + { + gle::texcoord0pointer(stride, v); + lasttcbuf = lastvbuf; + } + } + + void bindnormals(void *v, int stride) + { + if(!enablenormals) + { + gle::enablenormal(); + enablenormals = true; + } + if(lastnbuf!=lastvbuf) + { + gle::normalpointer(stride, v); + lastnbuf = lastvbuf; + } + } + + void bindtangents(void *v, int stride) + { + if(!enabletangents) + { + gle::enabletangent(); + enabletangents = true; + } + if(lastxbuf!=lastvbuf) + { + gle::tangentpointer(stride, v, GL_SHORT); + lastxbuf = lastvbuf; + } + } + + void bindbones(void *wv, void *bv, int stride) + { + if(!enablebones) + { + gle::enableboneweight(); + gle::enableboneindex(); + enablebones = true; + } + if(lastbbuf!=lastvbuf) + { + gle::boneweightpointer(stride, wv); + gle::boneindexpointer(stride, bv); + lastbbuf = lastvbuf; + } + } + }; + + virtual meshgroup *loadmeshes(const char *name, va_list args) { return NULL; } + + meshgroup *sharemeshes(const char *name, ...) + { + static hashnameset meshgroups; + if(!meshgroups.access(name)) + { + va_list args; + va_start(args, name); + meshgroup *group = loadmeshes(name, args); + va_end(args); + if(!group) return NULL; + meshgroups.add(group); + } + return meshgroups[name]; + } + + struct linkedpart + { + part *p; + int tag, anim, basetime; + vec translate; + vec *pos; + matrix4 matrix; + + linkedpart() : p(NULL), tag(-1), anim(-1), basetime(0), translate(0, 0, 0), pos(NULL) {} + }; + + struct part + { + animmodel *model; + int index; + meshgroup *meshes; + vector links; + vector skins; + vector *anims[MAXANIMPARTS]; + int numanimparts; + float pitchscale, pitchoffset, pitchmin, pitchmax; + vec translate; + + part(animmodel *model, int index = 0) : model(model), index(index), meshes(NULL), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0), translate(0, 0, 0) + { + loopk(MAXANIMPARTS) anims[k] = NULL; + } + virtual ~part() + { + loopk(MAXANIMPARTS) DELETEA(anims[k]); + } + + virtual void cleanup() + { + if(meshes) meshes->cleanup(); + loopv(skins) skins[i].cleanup(); + } + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + matrix4x3 t = m; + t.scale(model->scale); + t.translate(translate); + meshes->calcbb(bbmin, bbmax, t); + loopv(links) + { + matrix4x3 n; + meshes->concattagtransform(this, links[i].tag, m, n); + n.translate(links[i].translate, model->scale); + links[i].p->calcbb(bbmin, bbmax, n); + } + } + + void genBIH(vector &bih, const matrix4x3 &m) + { + matrix4x3 t = m; + t.scale(model->scale); + t.translate(translate); + meshes->genBIH(skins, bih, t); + loopv(links) + { + matrix4x3 n; + meshes->concattagtransform(this, links[i].tag, m, n); + n.translate(links[i].translate, model->scale); + links[i].p->genBIH(bih, n); + } + } + + bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) + { + int i = meshes ? meshes->findtag(tag) : -1; + if(i<0) + { + loopv(links) if(links[i].p && links[i].p->link(p, tag, translate, anim, basetime, pos)) return true; + return false; + } + linkedpart &l = links.add(); + l.p = p; + l.tag = i; + l.anim = anim; + l.basetime = basetime; + l.translate = translate; + l.pos = pos; + return true; + } + + bool unlink(part *p) + { + loopvrev(links) if(links[i].p==p) { links.remove(i, 1); return true; } + loopv(links) if(links[i].p && links[i].p->unlink(p)) return true; + return false; + } + + void initskins(Texture *tex = notexture, Texture *masks = notexture, int limit = 0) + { + if(!limit) + { + if(!meshes) return; + limit = meshes->meshes.length(); + } + while(skins.length() < limit) + { + skin &s = skins.add(); + s.owner = this; + s.tex = tex; + s.masks = masks; + } + } + + bool envmapped() + { + loopv(skins) if(skins[i].envmapped()) return true; + return false; + } + + bool tangents() + { + loopv(skins) if(skins[i].tangents()) return true; + return false; + } + + void preloadBIH() + { + loopv(skins) skins[i].preloadBIH(); + } + + void preloadshaders(bool force) + { + loopv(skins) skins[i].preloadshader(force); + } + + void preloadmeshes() + { + if(meshes) meshes->preload(this); + } + + virtual void getdefaultanim(animinfo &info, int anim, uint varseed, dynent *d) + { + info.frame = 0; + info.range = 1; + } + + bool calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info, int &aitime) + { + uint varseed = uint((size_t)d); + info.anim = anim; + info.basetime = basetime; + info.varseed = varseed; + info.speed = anim&ANIM_SETSPEED ? basetime2 : 100.0f; + if((anim&ANIM_INDEX)==ANIM_ALL) + { + info.frame = 0; + info.range = meshes->totalframes(); + } + else + { + animspec *spec = NULL; + if(anims[animpart]) + { + int primaryidx = anim&ANIM_INDEX; + if(primaryidx < NUMANIMS) + { + vector &primary = anims[animpart][primaryidx]; + if(primary.length()) spec = &primary[uint(varseed + basetime)%primary.length()]; + } + if((anim>>ANIM_SECONDARY)&(ANIM_INDEX|ANIM_DIR)) + { + int secondaryidx = (anim>>ANIM_SECONDARY)&ANIM_INDEX; + if(secondaryidx < NUMANIMS) + { + vector &secondary = anims[animpart][secondaryidx]; + if(secondary.length()) + { + animspec &spec2 = secondary[uint(varseed + basetime2)%secondary.length()]; + if(!spec || spec2.priority > spec->priority) + { + spec = &spec2; + info.anim >>= ANIM_SECONDARY; + info.basetime = basetime2; + } + } + } + } + } + if(spec) + { + info.frame = spec->frame; + info.range = spec->range; + if(spec->speed>0) info.speed = 1000.0f/spec->speed; + } + else getdefaultanim(info, anim, uint(varseed + info.basetime), d); + } + + info.anim &= (1<hasframes(info.frame, info.range)) + { + if(!meshes->hasframe(info.frame)) return false; + info.range = meshes->clipframes(info.frame, info.range); + } + + if(d && interp>=0) + { + animinterpinfo &ai = d->animinterp[interp]; + if((info.anim&ANIM_CLAMP)==ANIM_CLAMP) aitime = min(aitime, int(info.range*info.speed*0.5e-3f)); + void *ak = meshes->animkey(); + if(d->ragdoll && !(anim&ANIM_RAGDOLL)) + { + ai.prev.range = ai.cur.range = 0; + ai.lastswitch = -1; + } + else if(ai.lastmodel!=ak || ai.lastswitch<0 || lastmillis-d->lastrendered>aitime) + { + ai.prev = ai.cur = info; + ai.lastswitch = lastmillis-aitime*2; + } + else if(ai.cur!=info) + { + if(lastmillis-ai.lastswitch>aitime/2) ai.prev = ai.cur; + ai.cur = info; + ai.lastswitch = lastmillis; + } + else if(info.anim&ANIM_SETTIME) ai.cur.basetime = info.basetime; + ai.lastmodel = ak; + } + return true; + } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d) + { + animstate as[MAXANIMPARTS]; + render(anim, basetime, basetime2, pitch, axis, forward, d, as); + } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, animstate *as) + { + if(!(anim&ANIM_REUSE)) loopi(numanimparts) + { + animinfo info; + int interp = d && index+numanimparts<=MAXANIMPARTS ? index+i : -1, aitime = animationinterpolationtime; + if(!calcanim(i, anim, basetime, basetime2, d, interp, info, aitime)) return; + animstate &p = as[i]; + p.owner = this; + p.cur.setframes(info); + p.interp = 1; + if(interp>=0 && d->animinterp[interp].prev.range>0) + { + int diff = lastmillis-d->animinterp[interp].lastswitch; + if(diffaniminterp[interp].prev); + p.interp = diff/float(aitime); + } + } + } + + vec oaxis, oforward; + matrixstack[matrixpos].transposedtransformnormal(axis, oaxis); + float pitchamount = pitchscale*pitch + pitchoffset; + if((pitchmin || pitchmax) && pitchmin <= pitchmax) pitchamount = clamp(pitchamount, pitchmin, pitchmax); + if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH)) + pitchamount *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp); + if(pitchamount) + { + ++matrixpos; + matrixstack[matrixpos] = matrixstack[matrixpos-1]; + matrixstack[matrixpos].rotate(pitchamount*RAD, oaxis); + } + matrixstack[matrixpos].transposedtransformnormal(forward, oforward); + + if(!(anim&ANIM_NORENDER)) + { + matrix4 modelmatrix; + modelmatrix.mul(shadowmapping ? shadowmatrix : camprojmatrix, matrixstack[matrixpos]); + if(model->scale!=1) modelmatrix.scale(model->scale); + if(!translate.iszero()) modelmatrix.translate(translate); + GLOBALPARAM(modelmatrix, modelmatrix); + + if(!(anim&ANIM_NOSKIN)) + { + if(envmapped()) GLOBALPARAM(modelworld, matrix3(matrixstack[matrixpos])); + + vec odir, ocampos; + matrixstack[matrixpos].transposedtransformnormal(lightdir, odir); + GLOBALPARAM(lightdir, odir); + matrixstack[matrixpos].transposedtransform(camera1->o, ocampos); + ocampos.div(model->scale).sub(translate); + GLOBALPARAM(modelcamera, ocampos); + } + } + + meshes->render(as, pitch, oaxis, oforward, d, this); + + if(!(anim&ANIM_REUSE)) + { + loopv(links) + { + linkedpart &link = links[i]; + link.matrix.translate(links[i].translate, model->scale); + + matrixpos++; + matrixstack[matrixpos].mul(matrixstack[matrixpos-1], link.matrix); + + if(link.pos) *link.pos = matrixstack[matrixpos].gettranslation(); + + if(!link.p) + { + matrixpos--; + continue; + } + + int nanim = anim, nbasetime = basetime, nbasetime2 = basetime2; + if(link.anim>=0) + { + nanim = link.anim | (anim&ANIM_FLAGS); + nbasetime = link.basetime; + nbasetime2 = 0; + } + link.p->render(nanim, nbasetime, nbasetime2, pitch, axis, forward, d); + + matrixpos--; + } + } + + if(pitchamount) matrixpos--; + } + + void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0) + { + if(animpart<0 || animpart>=MAXANIMPARTS) return; + if(frame<0 || range<=0 || !meshes || !meshes->hasframes(frame, range)) + { + conoutf(CON_ERROR, "invalid frame %d, range %d in model %s", frame, range, model->name); + return; + } + if(!anims[animpart]) anims[animpart] = new vector[NUMANIMS]; + animspec &spec = anims[animpart][num].add(); + spec.frame = frame; + spec.range = range; + spec.speed = speed; + spec.priority = priority; + } + + virtual void loaded() + { + meshes->shared++; + loopv(skins) skins[i].setkey(); + } + }; + + enum + { + LINK_TAG = 0, + LINK_COOP, + LINK_REUSE + }; + + virtual int linktype(animmodel *m) const { return LINK_TAG; } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a) + { + int numtags = 0; + if(a) + { + int index = parts.last()->index + parts.last()->numanimparts; + for(int i = 0; a[i].tag; i++) + { + numtags++; + + animmodel *m = (animmodel *)a[i].m; + if(!m) + { + if(a[i].pos) link(NULL, a[i].tag, vec(0, 0, 0), 0, 0, a[i].pos); + continue; + } + part *p = m->parts[0]; + switch(linktype(m)) + { + case LINK_TAG: + p->index = link(p, a[i].tag, vec(0, 0, 0), a[i].anim, a[i].basetime, a[i].pos) ? index : -1; + break; + + case LINK_COOP: + p->index = index; + break; + + default: + continue; + } + index += p->numanimparts; + } + } + + animstate as[MAXANIMPARTS]; + parts[0]->render(anim, basetime, basetime2, pitch, axis, forward, d, as); + + if(a) for(int i = numtags-1; i >= 0; i--) + { + animmodel *m = (animmodel *)a[i].m; + if(!m) + { + if(a[i].pos) unlink(NULL); + continue; + } + part *p = m->parts[0]; + switch(linktype(m)) + { + case LINK_TAG: + if(p->index >= 0) unlink(p); + p->index = 0; + break; + + case LINK_COOP: + p->render(anim, basetime, basetime2, pitch, axis, forward, d); + p->index = 0; + break; + + case LINK_REUSE: + p->render(anim | ANIM_REUSE, basetime, basetime2, pitch, axis, forward, d, as); + break; + } + } + } + + void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, const vec &color, const vec &dir, float trans) + { + yaw += spinyaw*lastmillis/1000.0f; + pitch += offsetpitch + spinpitch*lastmillis/1000.0f; + + vec axis(0, -1, 0), forward(1, 0, 0); + + matrixpos = 0; + matrixstack[0].identity(); + if(!d || !d->ragdoll || anim&ANIM_RAGDOLL) + { + matrixstack[0].settranslation(o); + matrixstack[0].rotate_around_z(yaw*RAD); + matrixstack[0].transformnormal(vec(axis), axis); + matrixstack[0].transformnormal(vec(forward), forward); + if(offsetyaw) matrixstack[0].rotate_around_z(offsetyaw*RAD); + } + else pitch = 0; + + if(anim&ANIM_NORENDER) + { + render(anim, basetime, basetime2, pitch, axis, forward, d, a); + if(d) d->lastrendered = lastmillis; + return; + } + + if(!(anim&ANIM_NOSKIN)) + { + transparent = trans; + lightdir = dir; + lightcolor = color; + + if(envmapped()) + { + setupenvmap: + closestenvmaptex = lookupenvmap(closestenvmap(o)); + GLOBALPARAM(lightdirworld, dir); + } + else if(a) for(int i = 0; a[i].tag; i++) if(a[i].m && a[i].m->envmapped()) goto setupenvmap; + } + + if(depthoffset && !enabledepthoffset) + { + enablepolygonoffset(GL_POLYGON_OFFSET_FILL); + enabledepthoffset = true; + } + + if(transparent<1) + { + if(anim&ANIM_GHOST) + { + glDepthFunc(GL_GREATER); + glDepthMask(GL_FALSE); + } + else if(alphadepth) + { + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + render(anim|ANIM_NOSKIN, basetime, basetime2, pitch, axis, forward, d, a); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE); + + glDepthFunc(GL_LEQUAL); + } + + if(!enablealphablend) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + enablealphablend = true; + } + } + + render(anim, basetime, basetime2, pitch, axis, forward, d, a); + + if(transparent<1 && (alphadepth || anim&ANIM_GHOST)) + { + glDepthFunc(GL_LESS); + if(anim&ANIM_GHOST) glDepthMask(GL_TRUE); + } + + if(d) d->lastrendered = lastmillis; + } + + vector parts; + + animmodel(const char *name) : model(name) + { + } + + ~animmodel() + { + parts.deletecontents(); + } + + void cleanup() + { + loopv(parts) parts[i]->cleanup(); + } + + virtual void flushpart() {} + + part &addpart() + { + flushpart(); + part *p = new part(this, parts.length()); + parts.add(p); + return *p; + } + + void initmatrix(matrix4x3 &m) + { + m.identity(); + if(offsetyaw) m.rotate_around_z(offsetyaw*RAD); + if(offsetpitch) m.rotate_around_y(-offsetpitch*RAD); + } + + void genBIH(vector &bih) + { + if(parts.empty()) return; + matrix4x3 m; + initmatrix(m); + parts[0]->genBIH(bih, m); + } + + void preloadBIH() + { + model::preloadBIH(); + if(bih) loopv(parts) parts[i]->preloadBIH(); + } + + BIH *setBIH() + { + if(bih) return bih; + vector meshes; + genBIH(meshes); + bih = new BIH(meshes); + return bih; + } + + bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) + { + if(parts.empty()) return false; + return parts[0]->link(p, tag, translate, anim, basetime, pos); + } + + bool unlink(part *p) + { + if(parts.empty()) return false; + return parts[0]->unlink(p); + } + + bool envmapped() + { + loopv(parts) if(parts[i]->envmapped()) return true; + return false; + } + + virtual bool flipy() const { return false; } + virtual bool loadconfig() { return false; } + virtual bool loaddefaultparts() { return false; } + virtual void startload() {} + virtual void endload() {} + + bool load() + { + startload(); + bool success = loadconfig() && parts.length(); // configured model, will call the model commands below + if(!success) + success = loaddefaultparts(); // model without configuration, try default tris and skin + flushpart(); + endload(); + if(flipy()) translate.y = -translate.y; + + if(!success) return false; + loopv(parts) if(!parts[i]->meshes) return false; + + loaded(); + return true; + } + + void preloadshaders(bool force) + { + loopv(parts) parts[i]->preloadshaders(force); + } + + void preloadmeshes() + { + loopv(parts) parts[i]->preloadmeshes(); + } + + void setshader(Shader *shader) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].shader = shader; + } + + void setenvmap(float envmapmin, float envmapmax, Texture *envmap) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + if(envmapmax) + { + s.envmapmin = envmapmin; + s.envmapmax = envmapmax; + } + if(envmap) s.envmap = envmap; + } + } + + void setspec(float spec) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].spec = spec; + } + + void setambient(float ambient) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].ambient = ambient; + } + + void setglow(float glow, float delta, float pulse) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + s.glow = glow; + s.glowdelta = delta; + s.glowpulse = pulse; + } + } + + void setglare(float specglare, float glowglare) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + s.specglare = specglare; + s.glowglare = glowglare; + } + } + + void setalphatest(float alphatest) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphatest = alphatest; + } + + void setalphablend(bool alphablend) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphablend = alphablend; + } + + void setfullbright(float fullbright) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].fullbright = fullbright; + } + + void setcullface(bool cullface) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].cullface = cullface; + } + + void calcbb(vec ¢er, vec &radius) + { + if(parts.empty()) return; + vec bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f); + matrix4x3 m; + initmatrix(m); + parts[0]->calcbb(bbmin, bbmax, m); + radius = bbmax; + radius.sub(bbmin); + radius.mul(0.5f); + center = bbmin; + center.add(radius); + } + + virtual void loaded() + { + scale /= 4; + if(parts.length()) parts[0]->translate = translate; + loopv(parts) parts[i]->loaded(); + } + + static bool enabletc, enablealphablend, enablecullface, enablenormals, enabletangents, enablebones, enabledepthoffset; + static vec lightdir, lightcolor; + static float transparent, lastalphatest; + static GLuint lastvbuf, lasttcbuf, lastnbuf, lastxbuf, lastbbuf, lastebuf, lastenvmaptex, closestenvmaptex; + static Texture *lasttex, *lastmasks, *lastnormalmap; + static int matrixpos; + static matrix4 matrixstack[64]; + + void startrender() + { + enabletc = enablealphablend = enablenormals = enabletangents = enablebones = enabledepthoffset = false; + enablecullface = true; + lastalphatest = -1; + lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = lastenvmaptex = closestenvmaptex = 0; + lasttex = lastmasks = lastnormalmap = NULL; + transparent = 1; + shaderparamskey::invalidate(); + } + + static void disablebones() + { + gle::disableboneweight(); + gle::disableboneindex(); + enablebones = false; + } + + static void disabletangents() + { + gle::disabletangent(); + enabletangents = false; + } + + static void disabletc() + { + gle::disabletexcoord0(); + enabletc = false; + } + + static void disablenormals() + { + gle::disablenormal(); + enablenormals = false; + } + + static void disablevbo() + { + if(lastebuf) gle::clearebo(); + if(lastvbuf) + { + gle::clearvbo(); + gle::disablevertex(); + } + if(enabletc) disabletc(); + if(enablenormals) disablenormals(); + if(enabletangents) disabletangents(); + if(enablebones) disablebones(); + lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = 0; + } + + void endrender() + { + if(lastvbuf || lastebuf) disablevbo(); + if(enablealphablend) glDisable(GL_BLEND); + if(!enablecullface) glEnable(GL_CULL_FACE); + if(enabledepthoffset) disablepolygonoffset(GL_POLYGON_OFFSET_FILL); + } +}; + +bool animmodel::enabletc = false, animmodel::enablealphablend = false, + animmodel::enablecullface = true, + animmodel::enablenormals = false, animmodel::enabletangents = false, animmodel::enablebones = false, animmodel::enabledepthoffset = false; +vec animmodel::lightdir(0, 0, 1), animmodel::lightcolor(1, 1, 1); +float animmodel::transparent = 1, animmodel::lastalphatest = -1; +GLuint animmodel::lastvbuf = 0, animmodel::lasttcbuf = 0, animmodel::lastnbuf = 0, animmodel::lastxbuf = 0, animmodel::lastbbuf = 0, + animmodel::lastebuf = 0, animmodel::lastenvmaptex = 0, animmodel::closestenvmaptex = 0; +Texture *animmodel::lasttex = NULL, *animmodel::lastmasks = NULL, *animmodel::lastnormalmap = NULL; +int animmodel::matrixpos = 0; +matrix4 animmodel::matrixstack[64]; + +static inline uint hthash(const animmodel::shaderparams &k) +{ + return memhash(&k, sizeof(k)); +} + +static inline bool htcmp(const animmodel::shaderparams &x, const animmodel::shaderparams &y) +{ + return !memcmp(&x, &y, sizeof(animmodel::shaderparams)); +} + +hashtable animmodel::shaderparamskey::keys; +int animmodel::shaderparamskey::firstversion = 0, animmodel::shaderparamskey::lastversion = 1; + +template struct modelloader : BASE +{ + static MDL *loading; + static string dir; + + modelloader(const char *name) : BASE(name) {} + + static bool animated() { return true; } + static bool multiparted() { return true; } + static bool multimeshed() { return true; } + + void startload() + { + loading = (MDL *)this; + } + + void endload() + { + loading = NULL; + } + + bool loadconfig() + { + formatstring(dir, "packages/models/%s", BASE::name); + defformatstring(cfgname, "packages/models/%s/%s.cfg", BASE::name, MDL::formatname()); + + identflags &= ~IDF_PERSIST; + bool success = execfile(cfgname, false); + identflags |= IDF_PERSIST; + return success; + } +}; + +template MDL *modelloader::loading = NULL; +template string modelloader::dir = {'\0'}; // crashes clang if "" is used here + +template struct modelcommands +{ + typedef struct MDL::part part; + typedef struct MDL::skin skin; + + static void setdir(char *name) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + formatstring(MDL::dir, "packages/models/%s", name); + } + + #define loopmeshes(meshname, m, body) \ + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } \ + part &mdl = *MDL::loading->parts.last(); \ + if(!mdl.meshes) return; \ + loopv(mdl.meshes->meshes) \ + { \ + MESH &m = *(MESH *)mdl.meshes->meshes[i]; \ + if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \ + { \ + body; \ + } \ + } + + #define loopskins(meshname, s, body) loopmeshes(meshname, m, { skin &s = mdl.skins[i]; body; }) + + static void setskin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin) + { + loopskins(meshname, s, + s.tex = textureload(makerelpath(MDL::dir, tex), 0, true, false); + if(*masks) + { + s.masks = textureload(makerelpath(MDL::dir, masks), 0, true, false); + s.envmapmax = *envmapmax; + s.envmapmin = *envmapmin; + } + ); + } + + static void setspec(char *meshname, int *percent) + { + float spec = 1.0f; + if(*percent>0) spec = *percent/100.0f; + else if(*percent<0) spec = 0.0f; + loopskins(meshname, s, s.spec = spec); + } + + static void setambient(char *meshname, int *percent) + { + float ambient = 0.3f; + if(*percent>0) ambient = *percent/100.0f; + else if(*percent<0) ambient = 0.0f; + loopskins(meshname, s, s.ambient = ambient); + } + + static void setglow(char *meshname, int *percent, int *delta, float *pulse) + { + float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; + if(*percent>0) glow = *percent/100.0f; + else if(*percent<0) glow = 0.0f; + glowdelta -= glow; + loopskins(meshname, s, { s.glow = glow; s.glowdelta = glowdelta; s.glowpulse = glowpulse; }); + } + + static void setglare(char *meshname, float *specglare, float *glowglare) + { + loopskins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; }); + } + + static void setalphatest(char *meshname, float *cutoff) + { + loopskins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff))); + } + + static void setalphablend(char *meshname, int *blend) + { + loopskins(meshname, s, s.alphablend = *blend!=0); + } + + static void setcullface(char *meshname, int *cullface) + { + loopskins(meshname, s, s.cullface = *cullface!=0); + } + + static void setenvmap(char *meshname, char *envmap) + { + Texture *tex = cubemapload(envmap); + loopskins(meshname, s, s.envmap = tex); + } + + static void setbumpmap(char *meshname, char *normalmapfile) + { + Texture *normalmaptex = textureload(makerelpath(MDL::dir, normalmapfile), 0, true, false); + loopskins(meshname, s, s.normalmap = normalmaptex); + } + + static void setfullbright(char *meshname, float *fullbright) + { + loopskins(meshname, s, s.fullbright = *fullbright); + } + + static void setshader(char *meshname, char *shader) + { + loopskins(meshname, s, s.shader = lookupshaderbyname(shader)); + } + + static void setscroll(char *meshname, float *scrollu, float *scrollv) + { + loopskins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; }); + } + + static void setnoclip(char *meshname, int *noclip) + { + loopmeshes(meshname, m, m.noclip = *noclip!=0); + } + + static void setlink(int *parent, int *child, char *tagname, float *x, float *y, float *z) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + if(!MDL::loading->parts.inrange(*parent) || !MDL::loading->parts.inrange(*child)) { conoutf(CON_ERROR, "no models loaded to link"); return; } + if(!MDL::loading->parts[*parent]->link(MDL::loading->parts[*child], tagname, vec(*x, *y, *z))) conoutf(CON_ERROR, "could not link model %s", MDL::loading->name); + } + + template void modelcommand(F *fun, const char *suffix, const char *args) + { + defformatstring(name, "%s%s", MDL::formatname(), suffix); + addcommand(newstring(name), (void (*)())fun, args); + } + + modelcommands() + { + modelcommand(setdir, "dir", "s"); + if(MDL::multimeshed()) + { + modelcommand(setskin, "skin", "sssff"); + modelcommand(setspec, "spec", "si"); + modelcommand(setambient, "ambient", "si"); + modelcommand(setglow, "glow", "siif"); + modelcommand(setglare, "glare", "sff"); + modelcommand(setalphatest, "alphatest", "sf"); + modelcommand(setalphablend, "alphablend", "si"); + modelcommand(setcullface, "cullface", "si"); + modelcommand(setenvmap, "envmap", "ss"); + modelcommand(setbumpmap, "bumpmap", "ss"); + modelcommand(setfullbright, "fullbright", "sf"); + modelcommand(setshader, "shader", "ss"); + modelcommand(setscroll, "scroll", "sff"); + modelcommand(setnoclip, "noclip", "si"); + } + if(MDL::multiparted()) modelcommand(setlink, "link", "iisfff"); + } +}; + -- cgit v1.2.3