Initial sauer
[SauerbratenRemote.git] / src / engine / rendermodel.cpp
blob7cf15d73549b1fb25ac7a565809e962ba0c7f245
1 #include "pch.h"
2 #include "engine.h"
4 VARP(oqdynent, 0, 1, 1);
5 VARP(animationinterpolationtime, 0, 150, 1000);
6 VARP(orientinterpolationtime, 0, 75, 1000);
8 model *loadingmodel = NULL;
10 #include "vertmodel.h"
11 #include "md2.h"
12 #include "md3.h"
14 #define checkmdl if(!loadingmodel) { conoutf("not loading a model"); return; }
16 void mdlcullface(int *cullface)
18 checkmdl;
19 loadingmodel->cullface = *cullface!=0;
22 COMMAND(mdlcullface, "i");
24 void mdlcollide(int *collide)
26 checkmdl;
27 loadingmodel->collide = *collide!=0;
30 COMMAND(mdlcollide, "i");
32 void mdlellipsecollide(int *collide)
34 checkmdl;
35 loadingmodel->ellipsecollide = *collide!=0;
38 COMMAND(mdlellipsecollide, "i");
40 void mdlspec(int *percent)
42 checkmdl;
43 float spec = 1.0f;
44 if(*percent>0) spec = *percent/100.0f;
45 else if(*percent<0) spec = 0.0f;
46 loadingmodel->setspec(spec);
49 COMMAND(mdlspec, "i");
51 void mdlambient(int *percent)
53 checkmdl;
54 float ambient = 0.3f;
55 if(*percent>0) ambient = *percent/100.0f;
56 else if(*percent<0) ambient = 0.0f;
57 loadingmodel->setambient(ambient);
60 COMMAND(mdlambient, "i");
62 void mdlalphatest(float *cutoff)
64 checkmdl;
65 loadingmodel->setalphatest(max(0, min(1, *cutoff)));
68 COMMAND(mdlalphatest, "f");
70 void mdlalphablend(int *blend)
72 checkmdl;
73 loadingmodel->setalphablend(*blend!=0);
76 COMMAND(mdlalphablend, "i");
78 void mdlglow(int *percent)
80 checkmdl;
81 float glow = 3.0f;
82 if(*percent>0) glow = *percent/100.0f;
83 else if(*percent<0) glow = 0.0f;
84 loadingmodel->setglow(glow);
87 COMMAND(mdlglow, "i");
89 void mdlenvmap(int *envmapmax, int *envmapmin, char *envmap)
91 checkmdl;
92 loadingmodel->setenvmap(*envmapmin, *envmapmax, envmap[0] ? cubemapload(envmap) : NULL);
95 COMMAND(mdlenvmap, "iis");
97 void mdltranslucent(float *translucency)
99 checkmdl;
100 loadingmodel->settranslucency(*translucency);
103 COMMAND(mdltranslucent, "f");
105 void mdlfullbright(float *fullbright)
107 checkmdl;
108 loadingmodel->setfullbright(*fullbright);
111 COMMAND(mdlfullbright, "f");
113 void mdlshader(char *shader)
115 checkmdl;
116 loadingmodel->setshader(lookupshaderbyname(shader));
119 COMMAND(mdlshader, "s");
121 void mdlspin(float *rate)
123 checkmdl;
124 loadingmodel->spin = *rate;
127 COMMAND(mdlspin, "f");
129 void mdlscale(int *percent)
131 checkmdl;
132 float scale = 0.3f;
133 if(*percent>0) scale = *percent/100.0f;
134 else if(*percent<0) scale = 0.0f;
135 loadingmodel->scale = scale;
138 COMMAND(mdlscale, "i");
140 void mdltrans(float *x, float *y, float *z)
142 checkmdl;
143 loadingmodel->translate = vec(*x, *y, *z);
146 COMMAND(mdltrans, "fff");
148 void mdlshadow(int *shadow)
150 checkmdl;
151 loadingmodel->shadow = *shadow!=0;
154 COMMAND(mdlshadow, "i");
156 void mdlbb(float *rad, float *h, float *eyeheight)
158 checkmdl;
159 loadingmodel->collideradius = *rad;
160 loadingmodel->collideheight = *h;
161 loadingmodel->eyeheight = *eyeheight;
164 COMMAND(mdlbb, "fff");
166 void mdlname()
168 checkmdl;
169 result(loadingmodel->name());
172 COMMAND(mdlname, "");
174 // mapmodels
176 vector<mapmodelinfo> mapmodels;
178 void mmodel(char *name, int *tex)
180 mapmodelinfo &mmi = mapmodels.add();
181 s_strcpy(mmi.name, name);
182 mmi.tex = *tex;
183 mmi.m = NULL;
186 void mapmodelcompat(int *rad, int *h, int *tex, char *name, char *shadow)
188 mmodel(name, tex);
191 void mapmodelreset() { mapmodels.setsize(0); }
193 mapmodelinfo &getmminfo(int i) { return mapmodels.inrange(i) ? mapmodels[i] : *(mapmodelinfo *)0; }
194 const char *mapmodelname(int i) { return mapmodels.inrange(i) ? mapmodels[i].name : NULL; }
196 COMMAND(mmodel, "si");
197 COMMANDN(mapmodel, mapmodelcompat, "iiiss");
198 COMMAND(mapmodelreset, "");
200 // model registry
202 hashtable<const char *, model *> mdllookup;
204 model *loadmodel(const char *name, int i, bool msg)
206 if(!name)
208 if(!mapmodels.inrange(i)) return NULL;
209 mapmodelinfo &mmi = mapmodels[i];
210 if(mmi.m) return mmi.m;
211 name = mmi.name;
213 model **mm = mdllookup.access(name);
214 model *m;
215 if(mm) m = *mm;
216 else
218 if(msg)
220 s_sprintfd(filename)("packages/models/%s", name);
221 show_out_of_renderloop_progress(0, filename);
223 m = new md2(name);
224 loadingmodel = m;
225 if(!m->load())
227 delete m;
228 m = new md3(name);
229 loadingmodel = m;
230 if(!m->load())
232 delete m;
233 loadingmodel = NULL;
234 return NULL;
237 loadingmodel = NULL;
238 mdllookup.access(m->name(), &m);
240 if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m;
241 return m;
244 void clear_mdls()
246 enumerate(mdllookup, model *, m, delete m);
249 bool modeloccluded(const vec &center, float radius)
251 int br = int(radius*2)+1;
252 return bboccluded(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br), worldroot, ivec(0, 0, 0), hdr.worldsize/2);
255 VAR(showboundingbox, 0, 0, 2);
257 void render2dbox(vec &o, float x, float y, float z)
259 glBegin(GL_LINE_LOOP);
260 glVertex3f(o.x, o.y, o.z);
261 glVertex3f(o.x, o.y, o.z+z);
262 glVertex3f(o.x+x, o.y+y, o.z+z);
263 glVertex3f(o.x+x, o.y+y, o.z);
264 glEnd();
267 void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius)
269 if(yradius<=0) yradius = xradius;
270 vec c = o;
271 c.sub(vec(xradius, yradius, tofloor));
272 float xsz = xradius*2, ysz = yradius*2;
273 float h = tofloor+toceil;
274 notextureshader->set();
275 glColor3f(1, 1, 1);
276 render2dbox(c, xsz, 0, h);
277 render2dbox(c, 0, ysz, h);
278 c.add(vec(xsz, ysz, 0));
279 render2dbox(c, -xsz, 0, h);
280 render2dbox(c, 0, -ysz, h);
281 xtraverts += 16;
284 void renderellipse(vec &o, float xradius, float yradius, float yaw)
286 notextureshader->set();
287 glColor3f(0.5f, 0.5f, 0.5f);
288 glBegin(GL_LINE_LOOP);
289 loopi(16)
291 vec p(xradius*cosf(2*M_PI*i/16.0f), yradius*sinf(2*M_PI*i/16.0f), 0);
292 p.rotate_around_z((yaw+90)*RAD);
293 p.add(o);
294 glVertex3fv(p.v);
296 glEnd();
299 void setshadowmatrix(const plane &p, const vec &dir)
301 float d = p.dot(dir);
302 GLfloat m[16] =
304 d-dir.x*p.x, -dir.y*p.x, -dir.z*p.x, 0,
305 -dir.x*p.y, d-dir.y*p.y, -dir.z*p.y, 0,
306 -dir.x*p.z, -dir.y*p.z, d-dir.z*p.z, 0,
307 -dir.x*p.offset, -dir.y*p.offset, -dir.z*p.offset, d
309 glMultMatrixf(m);
312 VARP(bounddynshadows, 0, 1, 1);
313 VARP(dynshadow, 0, 60, 100);
315 void rendershadow(vec &dir, model *m, int anim, int varseed, const vec &o, vec center, float radius, float yaw, float pitch, float speed, int basetime, dynent *d, int cull, modelattach *a)
317 vec floor;
318 float dist = rayfloor(center, floor, 0, center.z);
319 if(dist<=0 || dist>=center.z) return;
320 center.z -= dist;
321 if((cull&MDL_CULL_VFC) && refracting && center.z>=refracting) return;
322 if(vec(center).sub(camera1->o).dot(floor)>0) return;
324 vec shaddir;
325 if(cull&MDL_DYNSHADOW)
327 extern vec shadowdir;
328 shaddir = shadowdir;
329 shaddir.normalize();
331 else
333 shaddir = dir;
334 shaddir.z = 0;
335 if(!shaddir.iszero()) shaddir.normalize();
336 shaddir.z = 1.5f*(dir.z*0.5f+1);
337 shaddir.normalize();
340 glDisable(GL_TEXTURE_2D);
341 glDepthMask(GL_FALSE);
343 if(!hasFBO || !reflecting || hasDS) glEnable(GL_STENCIL_TEST);
345 if((!hasFBO || !reflecting || hasDS) && bounddynshadows)
347 nocolorshader->set();
348 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
349 glStencilFunc(GL_ALWAYS, 1, 1);
350 glStencilOp(GL_KEEP, GL_REPLACE, GL_ZERO);
352 vec below(center);
353 below.z -= 1.0f;
354 glPushMatrix();
355 setshadowmatrix(plane(floor, -floor.dot(below)), shaddir);
356 glBegin(GL_QUADS);
357 loopi(6) if((shaddir[dimension(i)]>0)==dimcoord(i)) loopj(4)
359 const ivec &cc = cubecoords[fv[i][j]];
360 glVertex3f(center.x + (cc.x ? 1.5f : -1.5f)*radius,
361 center.y + (cc.y ? 1.5f : -1.5f)*radius,
362 cc.z ? center.z + dist + radius : below.z);
363 xtraverts += 4;
365 glEnd();
366 glPopMatrix();
368 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, refracting && renderpath!=R_FIXEDFUNCTION ? GL_FALSE : GL_TRUE);
371 float intensity = dynshadow/100.0f;
372 if(refracting)
374 if(renderpath!=R_FIXEDFUNCTION) setfogplane(0, max(0.1f, refracting-center.z));
375 else if(refractfog) intensity *= 1 - max(0, min(1, (refracting - center.z)/waterfog));
377 glColor4f(0, 0, 0, intensity);
379 static Shader *dynshadowshader = NULL;
380 if(!dynshadowshader) dynshadowshader = lookupshaderbyname("dynshadow");
381 dynshadowshader->set();
383 if(!hasFBO || !reflecting || hasDS)
385 glStencilFunc(GL_NOTEQUAL, bounddynshadows ? 0 : 1, 1);
386 glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
389 vec above(center);
390 above.z += 0.25f;
392 glPushMatrix();
393 setshadowmatrix(plane(floor, -floor.dot(above)), shaddir);
394 m->render(anim|ANIM_NOSKIN|ANIM_SHADOW, varseed, speed, basetime, o, yaw, pitch, d, a);
395 glPopMatrix();
397 glEnable(GL_TEXTURE_2D);
398 glDepthMask(GL_TRUE);
400 if(!hasFBO || !reflecting || hasDS) glDisable(GL_STENCIL_TEST);
403 struct batchedmodel
405 vec pos, color, dir;
406 int anim, varseed, tex;
407 float yaw, pitch, speed;
408 int basetime, cull;
409 dynent *d;
410 int attached;
411 occludequery *query;
413 struct modelbatch
415 model *m;
416 vector<batchedmodel> batched;
418 static vector<modelbatch *> batches;
419 static vector<modelattach> modelattached;
420 static int numbatches = -1;
421 static occludequery *modelquery = NULL;
423 void startmodelbatches()
425 numbatches = 0;
426 modelattached.setsizenodelete(0);
429 batchedmodel &addbatchedmodel(model *m)
431 modelbatch *b = NULL;
432 if(m->batch>=0 && m->batch<numbatches && batches[m->batch]->m==m) b = batches[m->batch];
433 else
435 if(numbatches<batches.length())
437 b = batches[numbatches];
438 b->batched.setsizenodelete(0);
440 else b = batches.add(new modelbatch);
441 b->m = m;
442 m->batch = numbatches++;
444 batchedmodel &bm = b->batched.add();
445 bm.query = modelquery;
446 return bm;
449 void renderbatchedmodel(model *m, batchedmodel &b)
451 modelattach *a = NULL;
452 if(b.attached>=0) a = &modelattached[b.attached];
453 if((!shadowmap || renderpath==R_FIXEDFUNCTION) && (b.cull&(MDL_SHADOW|MDL_DYNSHADOW)) && dynshadow && hasstencil && (!reflecting || refracting))
455 vec center;
456 float radius = m->boundsphere(0/*frame*/, center, a); // FIXME
457 center.add(b.pos);
458 rendershadow(b.dir, m, b.anim, b.varseed, b.pos, center, radius, b.yaw, b.pitch, b.speed, b.basetime, b.d, b.cull, a);
459 if((b.cull&MDL_CULL_VFC) && refracting && center.z-radius>=refracting) return;
462 int anim = b.anim;
463 if(shadowmapping) anim |= ANIM_NOSKIN;
464 else if(b.cull&MDL_TRANSLUCENT) anim |= ANIM_TRANSLUCENT;
466 m->setskin(b.tex);
467 m->render(anim, b.varseed, b.speed, b.basetime, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir);
470 struct translucentmodel
472 model *m;
473 batchedmodel *batched;
474 float dist;
477 static int sorttranslucentmodels(const translucentmodel *x, const translucentmodel *y)
479 if(x->dist > y->dist) return -1;
480 if(x->dist < y->dist) return 1;
481 return 0;
484 void endmodelbatches()
486 vector<translucentmodel> translucent;
487 loopi(numbatches)
489 modelbatch &b = *batches[i];
490 if(b.batched.empty()) continue;
491 bool rendered = false;
492 occludequery *query = NULL;
493 loopvj(b.batched)
495 batchedmodel &bm = b.batched[j];
496 if(bm.query!=query)
498 if(query) endquery(query);
499 query = bm.query;
500 if(query) startquery(query);
502 if(bm.cull&MDL_TRANSLUCENT && (!query || query->owner==bm.d))
504 translucentmodel &tm = translucent.add();
505 tm.m = b.m;
506 tm.batched = &bm;
507 tm.dist = camera1->o.dist(bm.pos);
508 continue;
510 if(!rendered) { b.m->startrender(); rendered = true; }
511 renderbatchedmodel(b.m, bm);
513 if(query) endquery(query);
514 if(rendered) b.m->endrender();
516 if(translucent.length())
518 translucent.sort(sorttranslucentmodels);
519 model *lastmodel = NULL;
520 occludequery *query = NULL;
521 loopv(translucent)
523 translucentmodel &tm = translucent[i];
524 if(lastmodel!=tm.m)
526 if(lastmodel) lastmodel->endrender();
527 (lastmodel = tm.m)->startrender();
529 if(query!=tm.batched->query)
531 if(query) endquery(query);
532 query = tm.batched->query;
533 if(query) startquery(query);
535 renderbatchedmodel(tm.m, *tm.batched);
537 if(query) endquery(query);
538 if(lastmodel) lastmodel->endrender();
540 numbatches = -1;
543 void startmodelquery(occludequery *query)
545 modelquery = query;
548 void endmodelquery()
550 int querybatches = 0;
551 loopi(numbatches)
553 modelbatch &b = *batches[i];
554 if(b.batched.empty() || b.batched.last().query!=modelquery) continue;
555 querybatches++;
557 if(querybatches<=1)
559 if(!querybatches) modelquery->fragments = 0;
560 modelquery = NULL;
561 return;
563 int minattached = modelattached.length();
564 startquery(modelquery);
565 loopi(numbatches)
567 modelbatch &b = *batches[i];
568 if(b.batched.empty() || b.batched.last().query!=modelquery) continue;
569 b.m->startrender();
572 batchedmodel &bm = b.batched.pop();
573 if(bm.attached>=0) minattached = min(minattached, bm.attached);
574 renderbatchedmodel(b.m, bm);
576 while(b.batched.length() && b.batched.last().query==modelquery);
577 b.m->endrender();
579 endquery(modelquery);
580 modelquery = NULL;
581 modelattached.setsizenodelete(minattached);
584 VARP(maxmodelradiusdistance, 10, 100, 1000);
586 void rendermodelquery(model *m, dynent *d, const vec &center, float radius)
588 d->query = newquery(d);
589 if(!d->query) return;
590 nocolorshader->set();
591 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
592 glDepthMask(GL_FALSE);
593 startquery(d->query);
594 int br = int(radius*2)+1;
595 drawbb(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br));
596 endquery(d->query);
597 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, refracting && renderpath!=R_FIXEDFUNCTION ? GL_FALSE : GL_TRUE);
598 glDepthMask(GL_TRUE);
601 void rendermodel(vec &color, vec &dir, const char *mdl, int anim, int varseed, int tex, const vec &o, float yaw, float pitch, float speed, int basetime, dynent *d, int cull, modelattach *a)
603 if(shadowmapping && !(cull&(MDL_SHADOW|MDL_DYNSHADOW))) return;
604 model *m = loadmodel(mdl);
605 if(!m) return;
606 vec center;
607 float radius = 0;
608 bool shadow = (!shadowmap || renderpath==R_FIXEDFUNCTION) && (cull&(MDL_SHADOW|MDL_DYNSHADOW)) && dynshadow && hasstencil;
609 if(cull)
611 radius = m->boundsphere(0/*frame*/, center, a); // FIXME
612 center.add(o);
613 if(cull&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return;
614 if(cull&MDL_CULL_VFC)
616 if(reflecting)
618 if(refracting)
620 if(center.z+radius<refracting-waterfog || (!shadow && center.z-radius>=refracting)) return;
622 else if(center.z+radius<=reflecting) return;
623 if(center.dist(camera1->o)-radius>reflectdist) return;
625 if(isvisiblesphere(radius, center) >= VFC_FOGGED) return;
626 if(shadowmapping && !isshadowmapcaster(center, radius)) return;
628 if(shadowmapping)
630 if(d)
632 if(cull&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return;
633 if(cull&MDL_CULL_QUERY && hasOQ && oqdynent && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return;
635 if(!addshadowmapcaster(center, radius, radius)) return;
637 else if(cull&MDL_CULL_OCCLUDED && modeloccluded(center, radius))
639 if(!reflecting && !refracting && d)
641 d->occluded = OCCLUDE_PARENT;
642 if(cull&MDL_CULL_QUERY && hasOQ && oqdynent) rendermodelquery(m, d, center, radius);
644 return;
646 else if(cull&MDL_CULL_QUERY && hasOQ && oqdynent && d && d->query && d->query->owner==d && checkquery(d->query))
648 if(!reflecting && !refracting)
650 if(d->occluded<OCCLUDE_BB) d->occluded++;
651 rendermodelquery(m, d, center, radius);
653 return;
656 if(showboundingbox && !shadowmapping)
658 if(d && showboundingbox==1)
660 render3dbox(d->o, d->eyeheight, d->aboveeye, d->radius);
661 renderellipse(d->o, d->xradius, d->yradius, d->yaw);
663 else
665 vec center, radius;
666 if(showboundingbox==1) m->collisionbox(0, center, radius);
667 else m->boundbox(0, center, radius);
668 rotatebb(center, radius, int(yaw));
669 center.add(o);
670 render3dbox(center, radius.z, radius.z, radius.x, radius.y);
674 if(d && !shadowmapping)
676 if(!reflecting && !refracting) d->occluded = OCCLUDE_NOTHING;
677 lightreaching(d->o, color, dir);
678 cl->lighteffects(d, color, dir);
680 vec dyncolor(color), dyndir(dir);
681 if(!shadowmapping) dynlightreaching(o, dyncolor, dyndir);
682 if(a) for(int i = 0; a[i].name; i++)
684 a[i].m = loadmodel(a[i].name);
685 if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL;
688 bool doOQ = cull&MDL_CULL_QUERY && !reflecting && !refracting && !shadowmapping && hasOQ && oqdynent && d;
690 if(numbatches>=0)
692 batchedmodel &b = addbatchedmodel(m);
693 b.pos = o;
694 b.color = dyncolor;
695 b.dir = dyndir;
696 b.anim = anim;
697 b.varseed = varseed;
698 b.tex = tex;
699 b.yaw = yaw;
700 b.pitch = pitch;
701 b.speed = speed;
702 b.basetime = basetime;
703 b.cull = cull;
704 b.d = d;
705 b.attached = a ? modelattached.length() : -1;
706 if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].name) break; }
707 if(doOQ) d->query = b.query = newquery(d);
708 return;
711 m->startrender();
713 if(shadow && (!reflecting || refracting))
715 rendershadow(dyndir, m, anim, varseed, o, center, radius, yaw, pitch, speed, basetime, d, cull, a);
716 if((cull&MDL_CULL_VFC) && refracting && center.z-radius>=refracting) { m->endrender(); return; }
719 if(shadowmapping) anim |= ANIM_NOSKIN;
720 else if(cull&MDL_TRANSLUCENT) anim |= ANIM_TRANSLUCENT;
722 if(doOQ)
724 d->query = newquery(d);
725 if(d->query) startquery(d->query);
728 m->setskin(tex);
729 m->render(anim, varseed, speed, basetime, o, yaw, pitch, d, a, dyncolor, dyndir);
731 if(doOQ && d->query) endquery(d->query);
733 m->endrender();
736 void abovemodel(vec &o, const char *mdl)
738 model *m = loadmodel(mdl);
739 if(!m) return;
740 o.z += m->above(0/*frame*/);
743 bool matchanim(const char *name, const char *pattern)
745 for(;; pattern++)
747 const char *s = name;
748 char c;
749 for(;; pattern++)
751 c = *pattern;
752 if(!c || c=='|') break;
753 else if(c=='*')
755 if(!*s || isspace(*s)) break;
756 do s++; while(*s && !isspace(*s));
758 else if(c!=*s) break;
759 else s++;
761 if(!*s && (!c || c=='|')) return true;
762 pattern = strchr(pattern, '|');
763 if(!pattern) break;
765 return false;
768 void findanims(const char *pattern, vector<int> &anims)
770 static const char *names[] =
772 "dead", "dying", "idle",
773 "forward", "backward", "left", "right",
774 "punch", "shoot", "pain",
775 "jump", "sink", "swim",
776 "edit", "lag", "taunt", "win", "lose",
777 "gun shoot", "gun idle",
778 "vwep", "shield", "powerup",
779 "mapmodel", "trigger"
781 loopi(sizeof(names)/sizeof(names[0])) if(matchanim(names[i], pattern)) anims.add(i);
784 void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks) // model skin sharing
786 #define ifnoload(tex, path) if((tex = textureload(path, 0, true, false))==notexture)
787 #define tryload(tex, path, prefix, name) \
788 s_sprintfd(path)("%spackages/models/%s/%s.jpg", prefix, dir, name); \
789 ifnoload(tex, path) \
791 strcpy(path+strlen(path)-3, "png"); \
792 ifnoload(tex, path) \
794 s_sprintf(path)("%spackages/models/%s/%s.jpg", prefix, altdir, name); \
795 ifnoload(tex, path) \
797 strcpy(path+strlen(path)-3, "png"); \
798 ifnoload(tex, path) return; \
803 masks = notexture;
804 tryload(skin, skinpath, "", "skin");
805 if(renderpath!=R_FIXEDFUNCTION) { tryload(masks, maskspath, "", "masks"); }
806 else { tryload(masks, maskspath, "<ffmask:25>", "masks"); }
809 // convenient function that covers the usual anims for players/monsters/npcs
811 VAR(animoverride, 0, 0, NUMANIMS-1);
812 VAR(testanims, 0, 0, 1);
814 void interpolateorientation(dynent *d, float &interpyaw, float &interppitch)
816 if(!orientinterpolationtime) { interpyaw = d->yaw; interppitch = d->pitch; return; }
817 if(d->orientmillis!=lastmillis)
819 float yaw = d->yaw, pitch = d->pitch;
820 if(yaw-d->lastyaw>=180) yaw -= 360;
821 else if(d->lastyaw-yaw>=180) yaw += 360;
822 d->lastyaw += (yaw-d->lastyaw)*min(orientinterpolationtime, lastmillis-d->orientmillis)/float(orientinterpolationtime);
823 d->lastpitch += (pitch-d->lastpitch)*min(orientinterpolationtime, lastmillis-d->orientmillis)/float(orientinterpolationtime);
824 d->orientmillis = lastmillis;
826 interpyaw = d->lastyaw;
827 interppitch = d->lastpitch;
830 void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int attack, int attackdelay, int lastaction, int lastpain, float sink)
832 int anim = ANIM_IDLE|ANIM_LOOP;
833 float yaw = d->yaw, pitch = d->pitch;
834 if(d->type==ENT_PLAYER && d!=player && orientinterpolationtime) interpolateorientation(d, yaw, pitch);
835 vec o(d->o);
836 o.z -= d->eyeheight + sink;
837 int varseed = (int)(size_t)d, basetime = 0;
838 if(animoverride) anim = animoverride|ANIM_LOOP;
839 else if(d->state==CS_DEAD)
841 pitch = 0;
842 anim = ANIM_DYING;
843 basetime = lastpain;
844 varseed += lastpain;
845 int t = lastmillis-lastpain;
846 if(t<0 || t>20000) return;
847 if(t>500) { anim = ANIM_DEAD|ANIM_LOOP; if(t>1600) { t -= 1600; o.z -= t*t/10000000000.0f*t/16.0f; } }
848 if(o.z<-1000) return;
850 else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP;
851 else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP;
852 else
854 if(lastmillis-lastpain<300)
856 anim = ANIM_PAIN;
857 basetime = lastpain;
858 varseed += lastpain;
860 else if(attack<0 || (d->type!=ENT_AI && lastmillis-lastaction<attackdelay))
862 anim = attack<0 ? -attack : attack;
863 basetime = lastaction;
864 varseed += lastaction;
867 if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((cl->allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->gravity.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<<ANIM_SECONDARY;
868 else if(d->timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<<ANIM_SECONDARY;
869 else if(cl->allowmove(d))
871 if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<<ANIM_SECONDARY;
872 else if(d->strafe) anim |= ((d->strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<<ANIM_SECONDARY;
873 else if(d->move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<<ANIM_SECONDARY;
876 if((anim&ANIM_INDEX)==ANIM_IDLE && (anim>>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY;
878 if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<<ANIM_SECONDARY;
879 int flags = MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY;
880 if(d->type!=ENT_PLAYER) flags |= MDL_CULL_DIST;
881 if((anim&ANIM_INDEX)!=ANIM_DEAD) flags |= MDL_DYNSHADOW;
882 if(d->state==CS_LAGGED) flags |= MDL_TRANSLUCENT;
883 vec color, dir;
884 rendermodel(color, dir, mdlname, anim, varseed, 0, o, testanims && d==player ? 0 : yaw+90, pitch, 0, basetime, d, flags, attachments);
887 void setbbfrommodel(dynent *d, const char *mdl)
889 model *m = loadmodel(mdl);
890 if(!m) return;
891 vec center, radius;
892 m->collisionbox(0, center, radius);
893 if(d->type==ENT_INANIMATE && !m->ellipsecollide)
895 d->collidetype = COLLIDE_AABB;
896 rotatebb(center, radius, int(d->yaw));
898 d->xradius = radius.x + fabs(center.x);
899 d->yradius = radius.y + fabs(center.y);
900 d->radius = max(d->xradius, d->yradius);
901 if(d->collidetype!=COLLIDE_ELLIPSE) d->xradius = d->yradius = d->radius;
902 d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight;
903 d->aboveeye = radius.z*2*(1.0f-m->eyeheight);