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