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