Line data Source code
1 : #ifndef ANIMMODEL_H_
2 : #define ANIMMODEL_H_
3 :
4 : extern int fullbrightmodels, testtags, debugcolmesh;
5 :
6 : const std::string modelpath = "media/model/";
7 :
8 : /* animmodel: generic class for an animated model object, derived from the very
9 : * general model structure
10 : *
11 : * animmodel provides functionality to implement an animated model consisting of
12 : * multiple mesh groups which are treated as a single model
13 : *
14 : * animmodel is extended by skelmodel to allow multipart objects to be rigged by
15 : * a skeleton/bone type animation methodology
16 : */
17 : class animmodel : public model
18 : {
19 : public:
20 :
21 : struct AnimPos final
22 : {
23 : int anim, fr1, fr2;
24 : float t;
25 :
26 : void setframes(const animinfo &info);
27 :
28 : bool operator==(const AnimPos &a) const;
29 : bool operator!=(const AnimPos &a) const;
30 : };
31 :
32 : class part;
33 :
34 : struct AnimState final
35 : {
36 : const part *owner;
37 : AnimPos cur, prev;
38 : float interp;
39 :
40 : bool operator==(const AnimState &a) const;
41 : bool operator!=(const AnimState &a) const;
42 : };
43 :
44 : class Mesh;
45 :
46 : struct shaderparams
47 : {
48 : float spec, gloss, glow, glowdelta, glowpulse, fullbright, scrollu, scrollv, alphatest;
49 : vec color;
50 :
51 :
52 55 : bool operator==(const animmodel::shaderparams &y) const
53 : {
54 55 : return spec == y.spec
55 55 : && gloss == y.glow
56 0 : && glow == y.glow
57 0 : && glowdelta == y.glowdelta
58 0 : && glowpulse == y.glowpulse
59 0 : && fullbright == y.fullbright
60 0 : && scrollu == y.scrollu
61 0 : && scrollv == y.scrollv
62 0 : && alphatest == y.alphatest
63 110 : && color == y.color;
64 : }
65 17 : shaderparams() : spec(1.0f), gloss(1), glow(3.0f), glowdelta(0), glowpulse(0), fullbright(0), scrollu(0), scrollv(0), alphatest(0.9f), color(1, 1, 1) {}
66 : };
67 :
68 : //An object used to store texture data for a mesh, contained inside a `part` object.
69 : //A `meshgroup` object is paired with a `skin` object to provide texture & geometry
70 : //data for a mesh
71 : class skin final : public shaderparams
72 : {
73 : public:
74 : Texture *tex, *decal;
75 : const Texture *masks, *normalmap;
76 : Shader *shader;
77 : int cullface;
78 :
79 17 : skin(const part *owner, Texture *tex, const Texture *masks) :
80 17 : tex(notexture),
81 17 : decal(nullptr),
82 17 : masks(masks),
83 17 : normalmap(nullptr),
84 17 : shader(nullptr),
85 17 : cullface(1),
86 17 : owner(owner),
87 17 : rsmshader(nullptr),
88 17 : key(nullptr)
89 : {
90 17 : }
91 :
92 : bool alphatested() const;
93 : void setkey();
94 : void cleanup();
95 : void preloadBIH() const;
96 : void preloadshader();
97 : void bind(Mesh &b, const AnimState *as, bool usegpuskel = false, int vweights = 0);
98 : static void invalidateshaderparams();
99 :
100 : private:
101 : const part *owner;
102 : Shader *rsmshader;
103 : class ShaderParamsKey
104 : {
105 : public:
106 : static std::unordered_map<shaderparams, ShaderParamsKey> keys;
107 :
108 11 : ShaderParamsKey() : version(-1) {}
109 :
110 : bool checkversion();
111 :
112 0 : static void invalidate()
113 : {
114 0 : firstversion = lastversion;
115 0 : }
116 : private:
117 : static int firstversion, lastversion;
118 :
119 : int version;
120 : };
121 : ShaderParamsKey *key;
122 :
123 : bool masked() const;
124 : bool bumpmapped() const;
125 : bool decaled() const;
126 : void setshaderparams(Mesh &m, const AnimState *as, bool skinned = true);
127 : Shader *loadshader();
128 : void setshader(Mesh &m, const AnimState *as, bool usegpuskel, int vweights);
129 :
130 : };
131 :
132 : class meshgroup;
133 :
134 : //An object used to store a single geometry mesh inside a `meshgroup` object.
135 : //This object is to be extended to contain the actual geometry of a type of
136 : //model, such as `skelmesh` or `vertmesh`. In its base class form, the object
137 : //contains no geometry data.
138 : class Mesh
139 : {
140 : public:
141 : std::string name;
142 : bool cancollide, canrender, noclip;
143 :
144 3 : virtual ~Mesh()
145 3 : {
146 3 : }
147 :
148 : virtual void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) const = 0;
149 :
150 : virtual void genBIH(BIH::mesh &m) const = 0;
151 :
152 : void genBIH(const skin &s, std::vector<BIH::mesh> &bih, const matrix4x3 &t);
153 :
154 0 : virtual void genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &m) const
155 : {
156 0 : }
157 :
158 0 : virtual void setshader(Shader *s, bool, int, int row = 0)
159 : {
160 0 : if(row)
161 : {
162 0 : s->setvariant(0, row);
163 : }
164 : else
165 : {
166 0 : s->set();
167 : }
168 0 : }
169 :
170 : protected:
171 : meshgroup *group;
172 :
173 0 : Mesh() : cancollide(true), canrender(true), noclip(false), group(nullptr)
174 : {
175 0 : }
176 :
177 4 : Mesh(std::string_view name, meshgroup *m) :
178 4 : name(name),
179 4 : cancollide(true),
180 4 : canrender(true),
181 4 : noclip(false),
182 4 : group(m)
183 : {
184 4 : }
185 :
186 : template<class T>
187 0 : static void smoothnorms(typename T::vert *verts, int numverts, const typename T::tri *tris, int numtris, float limit, bool areaweight)
188 : {
189 0 : if(!numverts)
190 : {
191 0 : return;
192 : }
193 0 : smoothdata *smooth = new smoothdata[numverts];
194 0 : std::unordered_map<vec, int> share;
195 0 : for(int i = 0; i < numverts; ++i)
196 : {
197 0 : const typename T::vert &v = verts[i];
198 0 : const auto itr = share.find(v.pos);
199 0 : if(itr == share.end())
200 : {
201 0 : share[v.pos] = i;
202 : }
203 : else
204 : {
205 0 : smooth[i].next = (*itr).second;
206 0 : (*itr).second = i;
207 : }
208 : }
209 0 : for(int i = 0; i < numtris; ++i)
210 : {
211 0 : const typename T::tri &t = tris[i];
212 0 : const uint v1 = t.vert[0],
213 0 : v2 = t.vert[1],
214 0 : v3 = t.vert[2];
215 0 : vec norm;
216 0 : norm.cross(verts[v1].pos, verts[v2].pos, verts[v3].pos);
217 0 : if(!areaweight)
218 : {
219 0 : norm.normalize();
220 : }
221 0 : smooth[v1].norm.add(norm);
222 0 : smooth[v2].norm.add(norm);
223 0 : smooth[v3].norm.add(norm);
224 : }
225 0 : for(int i = 0; i < numverts; ++i)
226 : {
227 0 : verts[i].norm = vec(0, 0, 0);
228 : }
229 0 : for(int i = 0; i < numverts; ++i)
230 : {
231 0 : const smoothdata &n = smooth[i];
232 0 : verts[i].norm.add(n.norm);
233 0 : if(n.next >= 0)
234 : {
235 0 : const float vlimit = limit*n.norm.magnitude();
236 0 : for(int j = n.next; j >= 0;)
237 : {
238 0 : const smoothdata &o = smooth[j];
239 0 : if(n.norm.dot(o.norm) >= vlimit*o.norm.magnitude())
240 : {
241 0 : verts[i].norm.add(o.norm);
242 0 : verts[j].norm.add(n.norm);
243 : }
244 0 : j = o.next;
245 : }
246 : }
247 : }
248 0 : for(int i = 0; i < numverts; ++i)
249 : {
250 0 : verts[i].norm.normalize();
251 : }
252 0 : delete[] smooth;
253 0 : }
254 :
255 : /**
256 : * @brief Generates normal data for an array of vert objects.
257 : *
258 : * If there are no verts in the verts array, returns with no changes.
259 : *
260 : * Sets all norm fields in the verts array to the zero vector.
261 : * Accesses three elements in verts using a set of indices saved in the tri object.
262 : *
263 : * Each triangle's normal is the cross product of the three elements' positions, normalized if areaweight == false.
264 : * (If areaweight is true, since the cross product's magnitude encodes its area, we don't want to
265 : * normalize because we want to weight normals by area)
266 : * This normal is added to each of the three vertices accessed (individual vertices can have multiple tris).
267 : *
268 : * Then, all vertices are normalized, creating normalized normals using all triangles sharing each vertex.
269 : *
270 : * @param verts An array of vertices
271 : * @param numverts The size of the vertex array
272 : * @param tris An array of triangles, containing indices in verts
273 : * @param numtris The size of the tris array
274 : * @param areaweight If true, weights normals by area of associated triangle
275 : */
276 : template<class T>
277 3 : static void buildnorms(typename T::vert *verts, int numverts, const typename T::tri *tris, int numtris, bool areaweight)
278 : {
279 3 : if(!numverts)
280 : {
281 0 : return;
282 : }
283 449 : for(int i = 0; i < numverts; ++i)
284 : {
285 446 : verts[i].norm = vec(0, 0, 0);
286 : }
287 469 : for(int i = 0; i < numtris; ++i)
288 : {
289 466 : const typename T::tri &t = tris[i];
290 466 : typename T::vert &v1 = verts[t.vert[0]],
291 466 : &v2 = verts[t.vert[1]],
292 466 : &v3 = verts[t.vert[2]];
293 466 : vec norm;
294 466 : norm.cross(v1.pos, v2.pos, v3.pos);
295 466 : if(!areaweight)
296 : {
297 2 : norm.normalize();
298 : }
299 466 : v1.norm.add(norm);
300 466 : v2.norm.add(norm);
301 466 : v3.norm.add(norm);
302 : }
303 449 : for(int i = 0; i < numverts; ++i)
304 : {
305 446 : verts[i].norm.normalize();
306 : }
307 : }
308 :
309 : template<class T>
310 0 : static void buildnorms(typename T::vert *verts, int numverts, const typename T::tri *tris, int numtris, bool areaweight, int numframes)
311 : {
312 0 : if(!numverts)
313 : {
314 0 : return;
315 : }
316 0 : for(int i = 0; i < numframes; ++i)
317 : {
318 0 : buildnorms<T>(&verts[i*numverts], numverts, tris, numtris, areaweight);
319 : }
320 : }
321 :
322 : static void fixqtangent(quat &q, float bt);
323 :
324 : template<class T, class TC>
325 1 : void calctangents(typename T::vert *verts, TC *tcverts, int numverts, typename T::tri *tris, int numtris, bool areaweight)
326 : {
327 877 : vec *tangent = new vec[2*numverts],
328 1 : *bitangent = tangent+numverts;
329 1 : std::memset(static_cast<void *>(tangent), 0, 2*numverts*sizeof(vec));
330 :
331 463 : for(int i = 0; i < numtris; ++i)
332 : {
333 462 : const typename T::tri &t = tris[i];
334 462 : const vec &e0 = verts[t.vert[0]].pos;
335 462 : vec e1 = vec(verts[t.vert[1]].pos).sub(e0),
336 462 : e2 = vec(verts[t.vert[2]].pos).sub(e0);
337 :
338 462 : const vec2 &tc0 = tcverts[t.vert[0]].tc,
339 462 : &tc1 = tcverts[t.vert[1]].tc,
340 462 : &tc2 = tcverts[t.vert[2]].tc;
341 462 : float u1 = tc1.x - tc0.x,
342 462 : v1 = tc1.y - tc0.y,
343 462 : u2 = tc2.x - tc0.x,
344 462 : v2 = tc2.y - tc0.y;
345 462 : vec u(e2), v(e2);
346 462 : u.mul(v1).sub(vec(e1).mul(v2));
347 462 : v.mul(u1).sub(vec(e1).mul(u2));
348 :
349 462 : if(vec().cross(e2, e1).dot(vec().cross(v, u)) >= 0)
350 : {
351 19 : u.neg();
352 19 : v.neg();
353 : }
354 :
355 462 : if(!areaweight)
356 : {
357 0 : u.normalize();
358 0 : v.normalize();
359 : }
360 :
361 1848 : for(int j = 0; j < 3; ++j)
362 : {
363 1386 : tangent[t.vert[j]].sub(u);
364 1386 : bitangent[t.vert[j]].add(v);
365 : }
366 : }
367 439 : for(int i = 0; i < numverts; ++i)
368 : {
369 438 : typename T::vert &v = verts[i];
370 438 : const vec &t = tangent[i],
371 438 : &bt = bitangent[i];
372 438 : matrix3 m;
373 438 : m.c = v.norm;
374 438 : (m.a = t).project(m.c).normalize();
375 438 : m.b.cross(m.c, m.a);
376 438 : quat q(m);
377 438 : fixqtangent(q, m.b.dot(bt));
378 438 : v.tangent = q;
379 : }
380 1 : delete[] tangent;
381 1 : }
382 :
383 : template<class T, class TC>
384 0 : void calctangents(typename T::vert *verts, TC *tcverts, int numverts, typename T::tri *tris, int numtris, bool areaweight, int numframes)
385 : {
386 0 : for(int i = 0; i < numframes; ++i)
387 : {
388 0 : calctangents<T, TC>(&verts[i*numverts], tcverts, numverts, tris, numtris, areaweight);
389 : }
390 0 : }
391 :
392 : private:
393 : struct smoothdata
394 : {
395 : vec norm;
396 : int next;
397 :
398 0 : smoothdata() : norm(0, 0, 0), next(-1) {}
399 : };
400 : };
401 :
402 : //A group of one or more meshes, which are used by a `part` to represent its contained geometry.
403 : //A global (static field) map of meshgroups are kept to cache model geometry; the actual `part`'s
404 : //refered mesh group object is kept as a pointer to a member of the static meshgroup map.
405 : class meshgroup
406 : {
407 : public:
408 : std::vector<Mesh *> meshes;
409 :
410 : virtual ~meshgroup();
411 :
412 : virtual void concattagtransform(int i, const matrix4x3 &m, matrix4x3 &n) const = 0;
413 : virtual std::optional<size_t> findtag(std::string_view name) = 0;
414 : virtual int totalframes() const = 0;
415 : virtual void *animkey() = 0;
416 : virtual void cleanup() = 0;
417 : virtual void render(const AnimState *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) = 0;
418 : virtual void preload() = 0;
419 :
420 : /**
421 : * Returns a list of Mesh iterators corresponding to a given name
422 : * The iterators may be invalidated by other method calls.
423 : */
424 : std::vector<std::vector<animmodel::Mesh *>::const_iterator> getmeshes(std::string_view meshname) const;
425 : /**
426 : * Returns a list of indices corresponding to locations in animmodel::part::skins.
427 : * These indices are invalidated if animmodel::skins is modified after calling.
428 : */
429 : std::vector<size_t> getskins(std::string_view meshname) const;
430 :
431 : void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &t) const;
432 : void genBIH(const std::vector<skin> &skins, std::vector<BIH::mesh> &bih, const matrix4x3 &t) const;
433 : void genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &t) const;
434 : bool hasframe(int i) const;
435 : bool hasframes(int i, int n) const;
436 : int clipframes(int i, int n) const;
437 : const std::string &groupname() const;
438 :
439 : /**
440 : * @brief Returns a list of valid renderable meshes contained within this object.
441 : *
442 : * Returns a vector of std::vector<>::iterator objects which point to valid
443 : * elements of the object's mesh list which can be rendered (the relevant flag
444 : * field is true). Alternatively if the global debugcolmesh is enabled, all
445 : * meshes will be returned.
446 : *
447 : * @return a vector of std::vector iterators pointing to renderable meshes
448 : */
449 : std::vector<std::vector<Mesh *>::const_iterator> getrendermeshes() const;
450 : std::vector<std::vector<Mesh *>::iterator> getrendermeshes();
451 :
452 : #define LOOP_RENDER_MESHES(type, name, body) do { \
453 : for(uint i = 0; i < meshes.size(); i++) \
454 : { \
455 : type &name = *static_cast<type *>(meshes[i]); \
456 : if(name.canrender || debugcolmesh) \
457 : { \
458 : body; \
459 : } \
460 : } \
461 : } while(0)
462 :
463 : protected:
464 : meshgroup();
465 :
466 : std::string name;
467 :
468 : void bindpos(GLuint ebuf, GLuint vbuf, const void *v, int stride, int type, int size);
469 : void bindpos(GLuint ebuf, GLuint vbuf, const vec *v, int stride);
470 : void bindpos(GLuint ebuf, GLuint vbuf, const vec4<half> *v, int stride);
471 : void bindtc(const void *v, int stride);
472 : void bindtangents(const void *v, int stride);
473 : void bindbones(const void *wv, const void *bv, int stride);
474 : //no private-able members
475 : };
476 :
477 : /*
478 : * The meshgroups map stores meshgroups which may be shared between different
479 : * model instantiations (e.g. skeletal models which have other models attached
480 : * as parts).
481 : *
482 : * The animmodel is comprised of parts which each represent instance-specific
483 : * metadata applied to a meshgroup in this map.
484 : */
485 : static std::unordered_map<std::string, meshgroup *> meshgroups;
486 :
487 : /* The `part` object is the highest level of organization in a model object.
488 : * Each `part` is a logically separate part of an overall model, containing
489 : * its own skin(s) (`skin` objects), mesh(es) (`meshgroup` objects), and
490 : * model rendering parameters.
491 : *
492 : * Parts may contain a list of linked parts, which are other `part` objects
493 : * logically dependent on another `part`. An example is an object being held
494 : * in the hand of another model type, which is a separate mesh.
495 : */
496 : class part
497 : {
498 : public:
499 : const animmodel *model;
500 : int index;
501 : meshgroup *meshes; //pointer to a single meshgroup in animmodel::meshgroups
502 :
503 : //a pointer to another part object dependent on this part.
504 : //pointed part may be modified in case it is necessary to link/unlink
505 : //the pointed part to other dependent parts
506 : struct linkedpart
507 : {
508 : part *p;
509 : int tag, anim, basetime;
510 : vec translate;
511 : vec *pos; //a pos pointer from a modelattach object, which is set from within game
512 : matrix4 matrix;
513 :
514 : linkedpart() : p(nullptr), tag(-1), anim(-1), basetime(0), translate(0, 0, 0), pos(nullptr) {}
515 0 : linkedpart(part *p, int tag, int anim, int basetime, vec translate, vec *post, matrix4 matrix) :
516 0 : p(p), tag(tag), anim(anim), basetime(basetime), translate(translate), pos(post), matrix(matrix) {}
517 : };
518 : std::vector<linkedpart> links;
519 : std::vector<skin> skins;
520 : int numanimparts;
521 : float pitchscale, pitchoffset, pitchmin, pitchmax;
522 :
523 : part(const animmodel *model, int index = 0);
524 : virtual ~part();
525 : part(const part& a) = delete;
526 : part &operator=(const part &a) = delete;
527 :
528 : void cleanup();
529 : void disablepitch();
530 : void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m, float modelscale) const;
531 : void genBIH(std::vector<BIH::mesh> &bih, const matrix4x3 &m, float modelscale) const;
532 : void genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &m, float modelscale) const;
533 : bool link(part *p, std::string_view tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = nullptr);
534 : bool unlink(const part *p);
535 : void initskins(Texture *tex = notexture, Texture *masks = notexture, uint limit = 0);
536 : bool alphatested() const;
537 : void preloadBIH() const;
538 : void preloadshaders();
539 : void preloadmeshes();
540 :
541 : void intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, const vec &o, const vec &ray);
542 : void intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, const vec &o, const vec &ray, AnimState *as);
543 : void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d);
544 : void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, AnimState *as);
545 : void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0);
546 : bool animated() const;
547 : virtual void loaded();
548 :
549 : protected:
550 : virtual void getdefaultanim(animinfo &info) const;
551 : bool calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info, int &animinterptime) const;
552 :
553 : private:
554 : struct animspec
555 : {
556 : int frame, range;
557 : float speed;
558 : int priority;
559 : };
560 :
561 : std::vector<animspec> *anims[maxanimparts]; //pointer to array of std::vector<animspec>
562 : };
563 :
564 : std::vector<part *> parts;
565 :
566 : //ordinary methods
567 : ~animmodel();
568 : animmodel(const animmodel& a) = delete;
569 : animmodel &operator=(const animmodel &a) = delete;
570 :
571 : part &addpart();
572 : void initmatrix(matrix4x3 &m) const;
573 : void genBIH(std::vector<BIH::mesh> &bih);
574 : bool link(part *p, std::string_view tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = nullptr) const;
575 : void loaded();
576 : bool unlink(const part *p) const;
577 : void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a) const;
578 :
579 : //virtual methods
580 : virtual bool flipy() const = 0;
581 : virtual bool loadconfig(const std::string &mdlname) = 0;
582 : virtual bool loaddefaultparts() = 0;
583 : virtual void startload() = 0;
584 : virtual void endload() = 0;
585 :
586 : //model object overrides
587 : void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, float roll, dynent *d, modelattach *a, float size, const vec4<float> &color) const override final;
588 : void cleanup() override final;
589 : void genshadowmesh(std::vector<triangle> &tris, const matrix4x3 &orient) override final;
590 : void preloadBIH() override final;
591 : bool setBIH() override final;
592 : bool animated() const override final;
593 : bool pitched() const override final;
594 : bool alphatested() const override final;
595 : bool load() override final;
596 : void preloadshaders() override final;
597 : void preloadmeshes() override final;
598 : void setshader(Shader *shader) override final;
599 : void setspec(float spec) override final;
600 : void setgloss(int gloss) override final;
601 : void setglow(float glow, float delta, float pulse) override final;
602 : void setalphatest(float alphatest) override final;
603 : void setfullbright(float fullbright) override final;
604 : void setcullface(int cullface) override final;
605 : void setcolor(const vec &color) override final;
606 : void settransformation(const std::optional<vec> pos,
607 : const std::optional<vec> rotate,
608 : const std::optional<vec> orient,
609 : const std::optional<float> size) override final;
610 : vec4<float> locationsize() const override final;
611 : void calcbb(vec ¢er, vec &radius) const override final;
612 : void calctransform(matrix4x3 &m) const override final;
613 : void startrender() const override final;
614 : void endrender() const override final;
615 : void boundbox(vec ¢er, vec &radius) override final;
616 : float collisionbox(vec ¢er, vec &radius) override final;
617 : float above() override final;
618 : const std::string &modelname() const override final;
619 : //static methods
620 : static void disablebones();
621 : static void disabletangents();
622 : static void disabletc();
623 : static void disablevbo();
624 :
625 : protected:
626 : enum
627 : {
628 : Link_Tag = 0,
629 : Link_Reuse
630 : };
631 :
632 : animmodel(std::string name);
633 :
634 : virtual int linktype(const animmodel *, const part *) const;
635 : int intersect(int anim, int basetime, int basetime2, const vec &pos, float yaw, float pitch, float roll, dynent *d, modelattach *a, float size, const vec &o, const vec &ray, float &dist) const override final;
636 :
637 : static bool enabletc, enablebones, enabletangents;
638 : static std::stack<matrix4> matrixstack;
639 : static float sizescale;
640 :
641 : private:
642 : void intersect(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a, const vec &o, const vec &ray) const;
643 :
644 : static bool enablecullface, enabledepthoffset;
645 : static vec4<float> colorscale;
646 : static GLuint lastvbuf, lasttcbuf, lastxbuf, lastbbuf, lastebuf;
647 : static const Texture *lasttex,
648 : *lastdecal,
649 : *lastmasks,
650 : *lastnormalmap;
651 : };
652 :
653 : /* modelloader
654 : *
655 : * modelloader is a template for a wrapper to load a model into a model/animmodel
656 : * object from a transactional format, it is intended to be a child template class
657 : * of an animmodel derivative (the BASE template parameter)
658 : *
659 : * skelloader is a specialization of this class which uses modelloader to load
660 : * a skeletal model
661 : *
662 : */
663 : template<class MDL, class BASE>
664 : struct modelloader : BASE
665 : {
666 : static MDL *loading;
667 : static std::string dir;
668 :
669 19 : modelloader(std::string name) : BASE(name)
670 : {
671 19 : }
672 :
673 2 : static bool cananimate()
674 : {
675 2 : return true;
676 : }
677 :
678 6 : static bool multiparted()
679 : {
680 6 : return true;
681 : }
682 :
683 3 : static bool multimeshed()
684 : {
685 3 : return true;
686 : }
687 :
688 17 : void startload() override final
689 : {
690 17 : loading = static_cast<MDL *>(this);
691 17 : }
692 :
693 13 : void endload() override final
694 : {
695 13 : loading = nullptr;
696 13 : }
697 :
698 6 : bool loadconfig(const std::string &mdlname) override final
699 : {
700 6 : dir.clear();
701 6 : dir.append(modelpath).append(mdlname);
702 6 : std::string cfgname;
703 6 : cfgname.append(modelpath).append(mdlname).append("/").append(MDL::formatname()).append(".cfg");
704 :
705 6 : identflags &= ~Idf_Persist;
706 6 : bool success = execfile(cfgname.c_str(), false);
707 6 : identflags |= Idf_Persist;
708 6 : return success;
709 6 : }
710 : };
711 :
712 : template<class MDL, class BASE>
713 : MDL *modelloader<MDL, BASE>::loading = nullptr;
714 :
715 : template<class MDL, class BASE>
716 : std::string modelloader<MDL, BASE>::dir = {""}; // crashes clang if "" is used here
717 :
718 : /* modelcommands
719 : *
720 : * this template class adds a series of commands to the cubescript binding
721 : * adaptable to a specific model type
722 : *
723 : * this template class generates unique command names for each separate model type
724 : * such as objcolor for obj, or md5color for md5 models; they are static and the
725 : * same for any given MDL template parameter
726 : *
727 : * the intended MDL template parameter is one of the model formats (md5, obj, etc)
728 : */
729 : template<class MDL>
730 : struct modelcommands
731 : {
732 : typedef class MDL::part part;
733 : typedef class MDL::skin skin;
734 :
735 69 : static bool checkmdl()
736 : {
737 69 : if(!MDL::loading)
738 : {
739 69 : conoutf(Console_Error, "not loading a model");
740 69 : return false;
741 : }
742 : else
743 : {
744 0 : return true;
745 : }
746 : }
747 :
748 3 : static void mdlcullface(const int *cullface)
749 : {
750 3 : if(!checkmdl())
751 : {
752 3 : return;
753 : }
754 0 : MDL::loading->setcullface(*cullface);
755 : }
756 :
757 3 : static void mdlcolor(const float *r, const float *g, const float *b)
758 : {
759 3 : if(!checkmdl())
760 : {
761 3 : return;
762 : }
763 0 : MDL::loading->setcolor(vec(*r, *g, *b));
764 : }
765 :
766 3 : static void mdlcollide(const int *collide)
767 : {
768 3 : if(!checkmdl())
769 : {
770 3 : return;
771 : }
772 0 : MDL::loading->collide = *collide!=0 ? (MDL::loading->collide ? MDL::loading->collide : Collide_OrientedBoundingBox) : Collide_None;
773 : }
774 :
775 3 : static void mdlellipsecollide(const int *collide)
776 : {
777 3 : if(!checkmdl())
778 : {
779 3 : return;
780 : }
781 0 : MDL::loading->collide = *collide!=0 ? Collide_Ellipse : Collide_None;
782 : }
783 :
784 3 : static void mdltricollide(const char *collide)
785 : {
786 3 : if(!checkmdl())
787 : {
788 3 : return;
789 : }
790 0 : MDL::loading->collidemodel.clear();
791 0 : char *end = nullptr;
792 0 : int val = std::strtol(collide, &end, 0);
793 0 : if(*end)
794 : {
795 0 : val = 1;
796 0 : MDL::loading->collidemodel = std::string(collide);
797 : }
798 0 : MDL::loading->collide = val ? Collide_TRI : Collide_None;
799 : }
800 :
801 3 : static void mdlspec(const float *percent)
802 : {
803 3 : if(!checkmdl())
804 : {
805 3 : return;
806 : }
807 0 : float spec = *percent > 0 ? *percent/100.0f : 0.0f;
808 0 : MDL::loading->setspec(spec);
809 : }
810 :
811 3 : static void mdlgloss(int *gloss)
812 : {
813 3 : if(!checkmdl())
814 : {
815 3 : return;
816 : }
817 0 : MDL::loading->setgloss(std::clamp(*gloss, 0, 2));
818 : }
819 :
820 3 : static void mdlalphatest(const float *cutoff)
821 : {
822 3 : if(!checkmdl())
823 : {
824 3 : return;
825 : }
826 0 : MDL::loading->setalphatest(std::max(0.0f, std::min(1.0f, *cutoff)));
827 : }
828 :
829 3 : static void mdldepthoffset(const int *offset)
830 : {
831 3 : if(!checkmdl())
832 : {
833 3 : return;
834 : }
835 0 : MDL::loading->depthoffset = *offset!=0;
836 : }
837 :
838 3 : static void mdlglow(const float *percent, const float *delta, const float *pulse)
839 : {
840 3 : if(!checkmdl())
841 : {
842 3 : return;
843 : }
844 0 : float glow = *percent > 0 ? *percent/100.0f : 0.0f,
845 0 : glowdelta = *delta/100.0f,
846 0 : glowpulse = *pulse > 0 ? *pulse/1000.0f : 0;
847 0 : glowdelta -= glow;
848 0 : MDL::loading->setglow(glow, glowdelta, glowpulse);
849 : }
850 :
851 3 : static void mdlfullbright(const float *fullbright)
852 : {
853 3 : if(!checkmdl())
854 : {
855 3 : return;
856 : }
857 0 : MDL::loading->setfullbright(*fullbright);
858 : }
859 :
860 :
861 3 : static void mdlshader(const char *shader)
862 : {
863 3 : if(!checkmdl())
864 : {
865 3 : return;
866 : }
867 0 : MDL::loading->setshader(lookupshaderbyname(shader));
868 : }
869 :
870 : //assigns a new spin speed in three euler angles for the model object currently being loaded
871 3 : static void mdlspin(const float *yaw, const float *pitch, const float *roll)
872 : {
873 3 : if(!checkmdl())
874 : {
875 3 : return;
876 : }
877 0 : MDL::loading->settransformation(std::nullopt, vec(*yaw, *pitch, *roll), std::nullopt, std::nullopt);
878 : }
879 :
880 : //assigns a new scale factor in % for the model object currently being loaded
881 3 : static void mdlscale(const float *percent)
882 : {
883 3 : if(!checkmdl())
884 : {
885 3 : return;
886 : }
887 0 : float scale = *percent > 0 ? *percent/100.0f : 1.0f;
888 0 : MDL::loading->settransformation(std::nullopt, std::nullopt, std::nullopt, scale);
889 : }
890 :
891 : //assigns translation in x,y,z in cube units for the model object currently being loaded
892 3 : static void mdltrans(const float *x, const float *y, const float *z)
893 : {
894 3 : if(!checkmdl())
895 : {
896 3 : return;
897 : }
898 0 : MDL::loading->settransformation(vec(*x, *y, *z), std::nullopt, std::nullopt, std::nullopt);
899 : }
900 :
901 : //assigns angle to the offsetyaw field of the model object currently being loaded
902 3 : static void mdlyaw(const float *angle)
903 : {
904 3 : if(!checkmdl())
905 : {
906 3 : return;
907 : }
908 0 : MDL::loading->orientation.x = *angle;
909 : }
910 :
911 :
912 : //assigns angle to the offsetpitch field of the model object currently being loaded
913 3 : static void mdlpitch(const float *angle)
914 : {
915 3 : if(!checkmdl())
916 : {
917 3 : return;
918 : }
919 0 : MDL::loading->orientation.y = *angle;
920 : }
921 :
922 : //assigns angle to the offsetroll field of the model object currently being loaded
923 3 : static void mdlroll(const float *angle)
924 : {
925 3 : if(!checkmdl())
926 : {
927 3 : return;
928 : }
929 0 : MDL::loading->orientation.z = *angle;
930 : }
931 :
932 : //assigns shadow to the shadow field of the model object currently being loaded
933 3 : static void mdlshadow(const int *shadow)
934 : {
935 3 : if(!checkmdl())
936 : {
937 3 : return;
938 : }
939 0 : MDL::loading->shadow = *shadow!=0;
940 : }
941 :
942 : //assigns alphashadow to the alphashadow field of the model object currently being loaded
943 3 : static void mdlalphashadow(const int *alphashadow)
944 : {
945 3 : if(!checkmdl())
946 : {
947 3 : return;
948 : }
949 0 : MDL::loading->alphashadow = *alphashadow!=0;
950 : }
951 :
952 : //assigns rad, h, eyeheight to the fields of the model object currently being loaded
953 3 : static void mdlbb(const float *rad, const float *h, const float *eyeheight)
954 : {
955 3 : if(!checkmdl())
956 : {
957 3 : return;
958 : }
959 0 : MDL::loading->collidexyradius = *rad;
960 0 : MDL::loading->collideheight = *h;
961 0 : MDL::loading->eyeheight = *eyeheight;
962 : }
963 :
964 3 : static void mdlextendbb(const float *x, const float *y, const float *z)
965 : {
966 3 : if(!checkmdl())
967 : {
968 3 : return;
969 : }
970 0 : MDL::loading->bbextend = vec(*x, *y, *z);
971 : }
972 :
973 : /* mdlname
974 : *
975 : * returns the name of the model currently loaded [most recently]
976 : */
977 3 : static void mdlname()
978 : {
979 3 : if(!checkmdl())
980 : {
981 3 : return;
982 : }
983 0 : result(MDL::loading->modelname().c_str());
984 : }
985 :
986 14 : static void setdir(const char *name)
987 : {
988 14 : if(!MDL::loading)
989 : {
990 3 : conoutf("not loading an %s", MDL::formatname());
991 3 : return;
992 : }
993 11 : MDL::dir.clear();
994 11 : MDL::dir.append(modelpath).append(name);
995 : }
996 :
997 : /**
998 : * @brief Returns an iterator vector of meshes with the given name
999 : *
1000 : * Returns a vector of MDL::loading->parts.back()'s meshgroup's mesh vector
1001 : * iterators where those iterator's contents' name field compares equal to the
1002 : * passed string. If the wildcard "*" is passed as `meshname` then all elements
1003 : * will be added regardless of name. If no such mesh vector exists (or there is
1004 : * no loading model) then an empty vector is returned.
1005 : *
1006 : * @param meshname the mesh name to select from the mesh vector
1007 : *
1008 : * @return vector of iterators corresponding to meshes with the given name
1009 : */
1010 3 : static std::vector<std::vector<animmodel::Mesh *>::const_iterator> getmeshes(std::string_view meshname)
1011 : {
1012 3 : std::vector<std::vector<animmodel::Mesh *>::const_iterator> meshlist;
1013 3 : if(!MDL::loading || MDL::loading->parts.empty())
1014 : {
1015 3 : conoutf("not loading an %s", MDL::formatname());
1016 3 : return meshlist; //empty vector
1017 : }
1018 0 : const part &mdl = *MDL::loading->parts.back();
1019 0 : if(!mdl.meshes)
1020 : {
1021 0 : return meshlist; //empty vector
1022 : }
1023 0 : meshlist = mdl.meshes->getmeshes(meshname);
1024 0 : return meshlist;
1025 0 : }
1026 :
1027 : /**
1028 : * @brief Returns an iterator vector of skins associated with the given name
1029 : *
1030 : * Returns a vector of MDL::loading->parts.back()'s meshgroup's skin vector
1031 : * iterators where those iterator's contents' name field compares equal to the
1032 : * passed string. If the wildcard "*" is passed as `meshname` then all elements
1033 : * will be added regardless of name. If no such mesh vector exists (or there is
1034 : * no loading model) then an empty vector is returned.
1035 : *
1036 : * @param meshname the mesh name to select from the skin vector
1037 : *
1038 : * @return vector of iterators corresponding to skins with the given name
1039 : */
1040 44 : static std::vector<std::vector<animmodel::skin>::iterator> getskins(std::string_view meshname)
1041 : {
1042 44 : std::vector<std::vector<animmodel::skin>::iterator> skinlist;
1043 44 : if(!MDL::loading || MDL::loading->parts.empty())
1044 : {
1045 36 : conoutf("not loading an %s", MDL::formatname());
1046 36 : return skinlist;
1047 : }
1048 8 : part &mdl = *MDL::loading->parts.back();
1049 8 : if(!mdl.meshes)
1050 : {
1051 0 : return skinlist;
1052 : }
1053 8 : std::vector<size_t> skinindices = mdl.meshes->getskins(meshname);
1054 16 : for(size_t i : skinindices)
1055 : {
1056 8 : skinlist.push_back(mdl.skins.begin() + i);
1057 : }
1058 8 : return skinlist;
1059 8 : }
1060 :
1061 6 : static void setskin(const char *meshname, const char *tex, const char *masks)
1062 : {
1063 6 : auto skinlist = getskins(meshname);
1064 9 : for(auto s : skinlist)
1065 : {
1066 3 : (*s).tex = textureload(makerelpath(MDL::dir.c_str(), tex), 0, true, false);
1067 3 : if(*masks)
1068 : {
1069 3 : (*s).masks = textureload(makerelpath(MDL::dir.c_str(), masks), 0, true, false);
1070 : }
1071 : }
1072 6 : }
1073 :
1074 3 : static void setspec(const char *meshname, const float *percent)
1075 : {
1076 3 : float spec = *percent > 0 ? *percent/100.0f : 0.0f;
1077 3 : auto skinlist = getskins(meshname);
1078 3 : for(auto s : skinlist)
1079 : {
1080 0 : (*s).spec = spec;
1081 : }
1082 3 : }
1083 :
1084 3 : static void setgloss(const char *meshname, const int *gloss)
1085 : {
1086 3 : auto skinlist = getskins(meshname);
1087 3 : for(auto s : skinlist)
1088 : {
1089 0 : (*s).gloss = std::clamp(*gloss, 0, 2);
1090 : }
1091 3 : }
1092 :
1093 3 : static void setglow(const char *meshname, const float *percent, const float *delta, const float *pulse)
1094 : {
1095 3 : float glow = *percent > 0 ? *percent/100.0f : 0.0f,
1096 3 : glowdelta = *delta/100.0f,
1097 3 : glowpulse = *pulse > 0 ? *pulse/1000.0f : 0;
1098 3 : glowdelta -= glow;
1099 3 : auto skinlist = getskins(meshname);
1100 3 : for(auto s : skinlist)
1101 : {
1102 0 : (*s).glow = glow;
1103 0 : (*s).glowdelta = glowdelta;
1104 0 : (*s).glowpulse = glowpulse;
1105 : }
1106 3 : }
1107 :
1108 3 : static void setalphatest(const char *meshname, const float *cutoff)
1109 : {
1110 3 : auto skinlist = getskins(meshname);
1111 3 : for(auto s : skinlist)
1112 : {
1113 0 : (*s).alphatest = std::max(0.0f, std::min(1.0f, *cutoff));
1114 : }
1115 3 : }
1116 :
1117 3 : static void setcullface(const char *meshname, const int *cullface)
1118 : {
1119 3 : auto skinlist = getskins(meshname);
1120 3 : for(auto s : skinlist)
1121 : {
1122 0 : (*s).cullface = *cullface;
1123 : }
1124 3 : }
1125 :
1126 3 : static void setcolor(const char *meshname, const float *r, const float *g, const float *b)
1127 : {
1128 3 : auto skinlist = getskins(meshname);
1129 3 : for(auto s : skinlist)
1130 : {
1131 0 : (*s).color = vec(*r, *g, *b);
1132 : }
1133 3 : }
1134 :
1135 4 : static void setbumpmap(const char *meshname, const char *normalmapfile)
1136 : {
1137 4 : Texture *normalmaptex = textureload(makerelpath(MDL::dir.c_str(), normalmapfile), 0, true, false);
1138 4 : auto skinlist = getskins(meshname);
1139 5 : for(auto s : skinlist)
1140 : {
1141 1 : (*s).normalmap = normalmaptex;
1142 : }
1143 4 : }
1144 :
1145 4 : static void setdecal(const char *meshname, const char *decal)
1146 : {
1147 4 : auto skinlist = getskins(meshname);
1148 5 : for(auto s : skinlist)
1149 : {
1150 1 : (*s).decal = textureload(makerelpath(MDL::dir.c_str(), decal), 0, true, false);
1151 : }
1152 4 : }
1153 :
1154 3 : static void setfullbright(const char *meshname, const float *fullbright)
1155 : {
1156 3 : auto skinlist = getskins(meshname);
1157 3 : for(auto s : skinlist)
1158 : {
1159 0 : (*s).fullbright = *fullbright;
1160 : }
1161 3 : }
1162 :
1163 3 : static void setshader(const char *meshname, const char *shader)
1164 : {
1165 3 : auto skinlist = getskins(meshname);
1166 3 : for(auto s : skinlist)
1167 : {
1168 0 : (*s).shader = lookupshaderbyname(shader);
1169 : }
1170 3 : }
1171 :
1172 3 : static void setscroll(const char *meshname, const float *scrollu, const float *scrollv)
1173 : {
1174 3 : auto skinlist = getskins(meshname);
1175 3 : for(auto s : skinlist)
1176 : {
1177 0 : (*s).scrollu = *scrollu;
1178 0 : (*s).scrollv = *scrollv;
1179 : }
1180 3 : }
1181 :
1182 3 : static void setnoclip(const char *meshname, const int *noclip)
1183 : {
1184 6 : auto meshlist = getmeshes(std::string(meshname));
1185 3 : if(meshlist.empty())
1186 : {
1187 3 : return;
1188 : }
1189 0 : for(auto &i : meshlist)
1190 : {
1191 0 : (*i)->noclip = *noclip!=0;
1192 : }
1193 3 : }
1194 :
1195 0 : static void settricollide(const char *meshname)
1196 : {
1197 0 : bool init = true;
1198 0 : auto meshlist = getmeshes(std::string(meshname));
1199 0 : if(!meshlist.empty())
1200 : {
1201 0 : return;
1202 : }
1203 : else
1204 : {
1205 0 : for(auto &i : meshlist)
1206 : {
1207 0 : if(!(*i)->cancollide)
1208 : {
1209 0 : init = false;
1210 : }
1211 : }
1212 0 : if(init)
1213 : {
1214 0 : for(auto &i : meshlist)
1215 : {
1216 0 : (*i)->cancollide = false;
1217 : }
1218 : }
1219 0 : for(auto &i : meshlist)
1220 : {
1221 0 : (*i)->cancollide = true;
1222 0 : (*i)->canrender = false;
1223 : }
1224 : }
1225 0 : }
1226 :
1227 3 : static void setlink(const int *parent, const int *child, const char *tagname, const float *x, const float *y, const float *z)
1228 : {
1229 3 : if(!MDL::loading)
1230 : {
1231 3 : conoutf("not loading an %s", MDL::formatname());
1232 3 : return;
1233 : }
1234 0 : if(!(static_cast<int>(MDL::loading->parts.size()) > *parent) || !(static_cast<int>(MDL::loading->parts.size()) > *child))
1235 : {
1236 0 : conoutf("no models loaded to link");
1237 0 : return;
1238 : }
1239 0 : if(!MDL::loading->parts[*parent]->link(MDL::loading->parts[*child], tagname, vec(*x, *y, *z)))
1240 : {
1241 0 : conoutf("could not link model %s", MDL::loading->modelname().c_str());
1242 : }
1243 : }
1244 :
1245 : template<class F>
1246 152 : void modelcommand(F *fun, std::string_view suffix, std::string_view args)
1247 : {
1248 152 : std::string name;
1249 152 : name.append(MDL::formatname()).append(suffix);
1250 152 : addcommand(newstring(name.c_str()), (identfun)fun, args.data());
1251 152 : }
1252 :
1253 3 : modelcommands()
1254 : {
1255 3 : modelcommand(setdir, "dir", "s");//<fmt>dir [name]
1256 3 : if(MDL::multimeshed())
1257 : {
1258 3 : modelcommand(mdlcullface, "cullface", "i");
1259 3 : modelcommand(mdlcolor, "color", "fff");
1260 3 : modelcommand(mdlcollide, "collide", "i");
1261 3 : modelcommand(mdlellipsecollide, "ellipsecollide", "i");
1262 3 : modelcommand(mdltricollide, "tricollide", "s");
1263 3 : modelcommand(mdlspec, "spec", "f");
1264 3 : modelcommand(mdlgloss, "gloss", "i");
1265 3 : modelcommand(mdlalphatest, "alphatest", "f");
1266 3 : modelcommand(mdldepthoffset, "depthoffset", "i");
1267 3 : modelcommand(mdlglow, "glow", "fff");
1268 3 : modelcommand(mdlfullbright, "fullbright", "f");
1269 3 : modelcommand(mdlshader, "shader", "s");
1270 3 : modelcommand(mdlspin, "spin", "fff");
1271 3 : modelcommand(mdlscale, "scale", "f");
1272 3 : modelcommand(mdltrans, "trans", "fff");
1273 3 : modelcommand(mdlyaw, "yaw", "f");
1274 3 : modelcommand(mdlpitch, "pitch", "f");
1275 3 : modelcommand(mdlroll, "roll", "f");
1276 3 : modelcommand(mdlshadow, "shadow", "i");
1277 3 : modelcommand(mdlalphashadow, "alphashadow", "i");
1278 3 : modelcommand(mdlbb, "bb", "fff");
1279 3 : modelcommand(mdlextendbb, "extendbb", "fff");
1280 3 : modelcommand(mdlname, "name", "");
1281 :
1282 3 : modelcommand(setskin, "skin", "sss"); //<fmt>skin [meshname] [tex] [masks]
1283 3 : modelcommand(setspec, "texspec", "sf"); //<fmt>texspec [mesh] [scale]
1284 3 : modelcommand(setgloss, "texgloss", "si"); //<fmt>texgloss [mesh] [type] type ranges 0..2
1285 3 : modelcommand(setglow, "texglow", "sfff"); //<fmt>texglow [mesh] [pct] [del] [pulse]
1286 3 : modelcommand(setalphatest, "meshalphatest", "sf"); //<fmt>meshalphatest [mesh] [cutoff]
1287 3 : modelcommand(setcullface, "meshcullface", "si"); //<fmt>cullface [mesh] [cullface]
1288 3 : modelcommand(setcolor, "meshcolor", "sfff"); //<fmt>meshcolor [mesh] [r] [g] [b]
1289 3 : modelcommand(setbumpmap, "bumpmap", "ss"); //<fmt>bumpmap [mesh] [tex]
1290 3 : modelcommand(setdecal, "decal", "ss"); //<fmt>decal [mesh] [tex]
1291 3 : modelcommand(setfullbright, "meshfullbright", "sf");//<fmt>meshfullbright [mesh] [bright]
1292 3 : modelcommand(setshader, "meshshader", "ss"); //<fmt>meshshader [mesh] [shader]
1293 3 : modelcommand(setscroll, "scroll", "sff"); //<fmt>scroll [mesh] [x] [y]
1294 3 : modelcommand(setnoclip, "noclip", "si"); //<fmt>noclip [mesh] [bool]
1295 3 : modelcommand(settricollide, "tricollide", "s"); //<fmt>settricollide [mesh]
1296 : }
1297 3 : if(MDL::multiparted())
1298 : {
1299 3 : modelcommand(setlink, "link", "iisfff");//<mdl>link [parent] [child] [tag] [x] [y] [z]
1300 : }
1301 3 : }
1302 : };
1303 :
1304 : #endif
|