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