Line data Source code
1 : #ifndef SKELMODEL_H_
2 : #define SKELMODEL_H_
3 :
4 : enum Bonemask
5 : {
6 : Bonemask_Not = 0x8000,
7 : Bonemask_End = 0xFFFF,
8 : Bonemask_Bone = 0x7FFF
9 : };
10 :
11 : /* skelmodel: implementation of model object for a skeletally rigged model
12 : *
13 : * skelmodel implements most of what is required to render a skeletally rigged
14 : * and animated model, using animmodel's specialization of model to implement
15 : * animations.
16 : *
17 : * extending skelmodel for a specific file format allows a program to use these
18 : * formats with skeletal animation support
19 : *
20 : * arrows indicate pointer fields pointing towards an object of the pointed type
21 : * arrows point to base or derived type specifically (which share a box)
22 : *
23 : * /-------------------------------\
24 : * | skelmodel : animmodel : model |
25 : * \--------------|---Λ------------/
26 : * | \_______________
27 : * | \ ____________
28 : * | | / \
29 : * | /------------V--V-\ /----|-------\
30 : * | | skelpart : part-------->linkedpart |
31 : * | \-------------|-Λ-/ \------------/
32 : * \ _____________/ \___
33 : * | / \
34 : * /------------------V----V---\ /---V----\
35 : * | skelmeshgroup : meshgroup | | skin |
36 : * \-|------|--|-Λ----------Λ--/ \--|---|-/
37 : * | | | | | | |
38 : * /-----------V-\ | | | | | |
39 : * |vbocacheentry| | | | | | |
40 : * \-|-----------/ | | | | | |
41 : * | / | | | | |
42 : * | | | | | | |
43 : * | /-------------V-\ | | | | \_____
44 : * | |blendcacheentry| | | \_____________ | \
45 : * | \--|------------/ | \ \ /-V----\ |
46 : * | | / \ | |shader| |
47 : * | | / \ | \------/ /---V---\
48 : * | | | | | |texture|
49 : * | | /--------V-\ /--V-----\ /------------V----\ \-------/
50 : * | | |blendcombo| |skeleton| | skelmesh : Mesh |
51 : * | | \----------/ \-|---|--/ \--|----|---------/
52 : * \____\_________ | | | |
53 : * \ | | | |
54 : * /----------------\ | /------V-\ | /--V-\ |
55 : * |dynent : physent| | |boneinfo| | |vert| |
56 : * \---|------------/ | \--------/ | \----/ |
57 : * \____________ | / |
58 : * \ | / /-V-\
59 : * /--V-V------\ | |tri|
60 : * |ragdolldata| | \---/
61 : * \-|-|-----|-/ |
62 : * | | | |
63 : * ____/ | /--V----V---\
64 : * / | |ragdollskel|
65 : * | | \-----------/
66 : * /-V-\ /--V-\
67 : * |tri| |vert|
68 : * \---/ \----/
69 : */
70 : struct skelmodel : animmodel
71 : {
72 : struct vert final
73 : {
74 : vec pos, norm;
75 : vec2 tc;
76 : quat tangent;
77 : int blend, interpindex;
78 : };
79 :
80 : struct vvertg
81 : {
82 : vec4<half> pos;
83 : GenericVec2<half> tc;
84 : squat tangent;
85 : };
86 :
87 : struct vvertgw final : vvertg
88 : {
89 : std::array<uchar, 4> weights,
90 : bones;
91 : };
92 :
93 : struct tri final
94 : {
95 : std::array<uint, 3> vert;
96 : };
97 :
98 : /**
99 : * @brief An object representing a set of weights for a vertex.
100 : *
101 : * A blendcombo object stores a set of weights, which when finalized should total
102 : * to a total weight quantity of 1. The weights are stored in descending order,
103 : * and should only assume to be normalized once finalize() is called.
104 : */
105 : class blendcombo final
106 : {
107 : public:
108 : int uses, interpindex;
109 :
110 : struct BoneData
111 : {
112 : float weight;
113 : uchar bone;
114 : uchar interpbone;
115 : };
116 : std::array<BoneData, 4> bonedata;
117 :
118 : blendcombo();
119 :
120 : /**
121 : * @brief Compares two blendcombos' weights and bones.
122 : *
123 : * Returns true if all bone indices and weight values in the Bone Data
124 : * array match. Checks no other values in the BoneData or other fields in
125 : * the blendcombo object
126 : *
127 : * @param c the blendcombo to compare
128 : *
129 : * @return true if the bones and weights match, false otherwise
130 : */
131 : bool operator==(const blendcombo &c) const;
132 :
133 : /**
134 : * @brief Returns the number of assigned weights in the blendcomb object.
135 : *
136 : * Assumes that all weights assigned are in the order created by addweight().
137 : *
138 : * @return the number of weights assigned in the bonedata
139 : */
140 : size_t size() const;
141 :
142 : /**
143 : * @brief Returns whether the first blendcombo has more weights than the second
144 : *
145 : * Returns true if `x` has a weight set at an index which `y` does not (reading from
146 : * left to right). Does not compare the actual values of the weights.
147 : * If both blendcombos have the same number of weights, returns false
148 : *
149 : * @param x the first blendcombo to compare
150 : * @param y the second blendcombo to compare
151 : *
152 : * @return true if x has more weights than y set, false if equal or less weights
153 : */
154 : static bool sortcmp(const blendcombo &x, const blendcombo &y);
155 :
156 : /**
157 : * @brief Attempts to assign a weight to one of the bonedata slots.
158 : *
159 : * Attempts to add the passed weight/bone combination to this blendcombo.
160 : * If the weight passed is less than 1e-3, the weight will not be added
161 : * regardless of the status of the object.
162 : *
163 : * If a weight/bone combo with a weight larger than any of the existing
164 : * members of the object are stored, the smaller weights are shifted
165 : * (and the smallest one removed) to make space for it. The stored
166 : * blends are assumed to be stored in descending order, and if added,
167 : * the inserted object will also be inserted to preserve descending
168 : * order.
169 : *
170 : * The inserted object will only be inserted at a depth inside the
171 : * weights buffer as deep as `sorted`. If this occurs, the descending
172 : * order of the buffer may not be maintained, and future operations
173 : * depending on this may not function properly.
174 : *
175 : * The returned value sorted indicates the depth into the object at
176 : * which the object should attempt to add an element. If an element
177 : * is successfully added, and if the bone data is not filled, then
178 : * sorted is returned incremented by one. Otherwise, the same value
179 : * passed as sorted will be returned.
180 : *
181 : * @param sorted the depth to add a weight in this object
182 : * @param weight the weight to attempt to add
183 : * @param bone the corresponding bone index
184 : *
185 : * @return the resulting number of allocated weights
186 : */
187 : int addweight(int sorted, float weight, int bone);
188 :
189 : /**
190 : * @brief Normalizes elements in the bonedata array.
191 : * Normalizes the elements in the weights part of the bonedata array
192 : * (up to `output` number of bones to normalize).
193 : *
194 : * The normalization critera for the weight data is the condition where
195 : * the sum of all the weights (up to the number sorted) adds to 1.
196 : *
197 : * @param sorted number of elements to normalize (must be <= 4)
198 : */
199 : void finalize(int sorted);
200 :
201 : /**
202 : * @brief Assigns unsigned character values to a vvertgw using the data in the blendcombo object
203 : *
204 : * If interpindex >=0:
205 : * Sets the zeroth weight to 255 (1.f) and the others to zero
206 : * Sets all of the bone values to 2*interpindex
207 : * Note that none of the blendcombo's saved weights/bones values impact this operation
208 : *
209 : * If interpindex <0:
210 : * Sets the passed vvertgw's weight values using floating point values ranging from 0..1
211 : * converted to an unsigned character value ranging from 0..255
212 : *
213 : * While the sum of the weights is greater than 1 (255), for each nonzero weight, remove
214 : * 1/255 from that weight, until the sum of weights is 1
215 : *
216 : * Otherwise, while the sum of the weights is less than 1 (255), for each weight < 1, add
217 : * 1/255 from that weight, until the sum of the weights is 1
218 : *
219 : * Assigns the passed object's bones to be equal to two times this objects' respective interpbones index
220 : *
221 : * @param v the vvertgw object to set weight/bone values to
222 : */
223 : void serialize(skelmodel::vvertgw &v) const;
224 :
225 : /**
226 : * @brief Creates a dual quaternion representation from the bone data of a blendcombo.
227 : *
228 : * Accumulates the set of dual quaternions pointed to by the bonedata object, scaled
229 : * by their respective weights. The resulting dual quaternion should be normalized,
230 : * if the blendcombo was normalized (by calling finalize()).
231 : *
232 : * @param bdata an array of dualquats, to which the BoneData.interpbones field points to
233 : *
234 : * @return a dual quaternion created from the blendcombo object's bone data
235 : */
236 : dualquat blendbones(const dualquat *bdata) const;
237 :
238 : /**
239 : * @brief Assigns interpbones values to the specified bonedata.
240 : *
241 : * @param val value to set to the indicated bone
242 : * @param i the index of the bonedata to set
243 : */
244 : void setinterpbones(int val, size_t i);
245 :
246 : /**
247 : * @brief Gets the bone stored at index.
248 : *
249 : * Gets the bone index associated with one of the bonedata's stored
250 : * bones. This index represents an index of the skeleton's boneinfo array.
251 : *
252 : * @param index the index of the bonedata to access
253 : */
254 : int getbone(size_t index);
255 :
256 : };
257 :
258 : struct animcacheentry
259 : {
260 : std::array<AnimState, maxanimparts> as;
261 : float pitch;
262 : int millis;
263 : const std::vector<uchar> * partmask;
264 : const ragdolldata *ragdoll;
265 :
266 : animcacheentry();
267 :
268 : /**
269 : * @brief Returns whether two animcacheentries compare equal
270 : *
271 : * Checks that all AnimStates in the animcacheentry::as field compare equal, as
272 : * well as the pitch, partmask, and ragdoll fields.
273 : *
274 : * If there are ragdolls in both objects, checks that neither
275 : * object's timestamp is less than this object's lastmove timestamp
276 : *
277 : * @param c the animcacheentry to compare
278 : *
279 : * @return true if the animcacheentries compare equal
280 : */
281 : bool operator==(const animcacheentry &c) const;
282 :
283 : /**
284 : * @brief Returns the opposite of animcacheentry::operator==
285 : *
286 : * @param c the animcacheentry to compare
287 : *
288 : * @return true if the animcacheentries compare unequal
289 : */
290 : bool operator!=(const animcacheentry &c) const;
291 : };
292 :
293 : struct vbocacheentry final : animcacheentry
294 : {
295 : GLuint vbuf; //GL_ARRAY_BUFFER (gle::bindvbo)
296 : int owner;
297 :
298 : bool check() const;
299 : vbocacheentry();
300 : };
301 :
302 : struct skelcacheentry : animcacheentry
303 : {
304 : dualquat *bdata; //array of size numinterpbones
305 : int version; //caching version
306 :
307 : skelcacheentry();
308 : void nextversion();
309 : };
310 :
311 : struct blendcacheentry final : skelcacheentry
312 : {
313 : int owner;
314 :
315 : blendcacheentry();
316 : };
317 :
318 : class skelmeshgroup;
319 :
320 : class skelmesh : public Mesh
321 : {
322 : public:
323 : skelmesh();
324 :
325 : /**
326 : * @brief Constructs a skelmesh object.
327 : *
328 : * @param name name of the underlying Mesh object
329 : * @param verts a heap-allocated array of vertices
330 : * @param numverts size of verts array
331 : * @param tris a heap-allocated array of tris
332 : * @param numtris size of tris array
333 : */
334 : skelmesh(std::string_view name, vert *verts, uint numverts, tri *tris, uint numtris, meshgroup *m);
335 :
336 : virtual ~skelmesh();
337 :
338 : int addblendcombo(const blendcombo &c);
339 :
340 : void smoothnorms(float limit = 0, bool areaweight = true);
341 : void buildnorms(bool areaweight = true);
342 : void calctangents(bool areaweight = true);
343 : void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) const final;
344 : void genBIH(BIH::mesh &m) const final;
345 : void genshadowmesh(std::vector<triangle> &out, const matrix4x3 &m) const final;
346 : //assignvert() functions are used externally in test code
347 : static void assignvert(vvertg &vv, const vert &v);
348 : static void assignvert(vvertgw &vv, const vert &v, const blendcombo &c);
349 :
350 : /*
351 : * these two genvbo() functions are used for different cases
352 : * of skelmodel rendering paths:
353 : *
354 : * genvbo(const std::vector<blendcombo>&, std::vector<GLuint>&, int, std::vector<vvertgw>) is for skeleton with animation frames
355 : * genvbo(std::vector<GLuint>&, int, std::vector<vvertg>&, int, int) is for no animation frames
356 : */
357 : int genvbo(const std::vector<blendcombo> &bcs, std::vector<GLuint> &idxs, int offset, std::vector<vvertgw> &vverts);
358 : int genvbo(std::vector<GLuint> &idxs, int offset, std::vector<vvertg> &vverts, int *htdata, int htlen);
359 :
360 : void setshader(Shader *s, bool usegpuskel, int vweights, int row) const final;
361 : void render() const;
362 : /**
363 : * @brief Assigns indices from the remap parameter to the object's verts
364 : *
365 : * Assigns the vector of remap blend indices to the verts array. Assumes
366 : * that the vector passed is at least as large as the verts array.
367 : *
368 : * @param remap a vector of new indices to assign
369 : */
370 : void remapverts(const std::vector<int> remap);
371 : /**
372 : * @brief Returns the number of verts represented by the object.
373 : *
374 : * This function is used by the testing code.
375 : *
376 : * @return the number of vertices represented
377 : */
378 : int vertcount() const;
379 :
380 : /**
381 : * @brief Returns the number of tris represented by the object.
382 : *
383 : * This function is used by the testing code.
384 : *
385 : * @return the number of triangles represented
386 : */
387 : int tricount() const;
388 :
389 : /**
390 : * @brief Returns a const reference to a vert object inside this skelmesh
391 : *
392 : * This function is intended for testing and not to be used in other parts
393 : * of the model code.
394 : *
395 : * @param index the index of the verts array to get
396 : *
397 : * @return a reference to a skelmodel::vert corresponding to the index
398 : */
399 : const vert &getvert(size_t index) const;
400 :
401 : protected:
402 : tri *tris;
403 : int numtris;
404 : vert *verts;
405 : int numverts;
406 :
407 : private:
408 : int maxweights;
409 : int voffset, eoffset, elen;
410 : GLuint minvert, maxvert;
411 : };
412 :
413 : struct skelanimspec final
414 : {
415 : std::string name;
416 : int frame, range;
417 : };
418 :
419 : class skeleton
420 : {
421 : public:
422 : size_t numbones;
423 : int numgpubones;
424 : size_t numframes;
425 : dualquat *framebones; //array of quats, size equal to anim frames * bones in model
426 : std::vector<skelanimspec> skelanims;
427 : ragdollskel *ragdoll; //optional ragdoll object if ragdoll is in effect
428 :
429 : struct pitchtarget final
430 : {
431 : size_t bone; //an index in skeleton::bones
432 : int frame, corrects, deps;
433 : float pitchmin, pitchmax, deviated;
434 : dualquat pose;
435 : };
436 : std::vector<pitchtarget> pitchtargets; //vector of pitch target objects, added to models via pitchtarget command
437 :
438 : struct pitchcorrect final
439 : {
440 : int bone, parent;
441 : size_t target; //an index in skeleton::pitchtargets vector
442 : float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal;
443 :
444 : pitchcorrect(int bone, size_t target, float pitchscale, float pitchmin, float pitchmax);
445 : pitchcorrect();
446 : };
447 : std::vector<pitchcorrect> pitchcorrects; //vector pitch correct objects, added to models via pitchcorrect command
448 :
449 : std::vector<skelcacheentry> skelcache;
450 :
451 : skeleton(skelmeshgroup * const group);
452 : ~skeleton();
453 :
454 : /**
455 : * @brief Finds a skelanimspec in skeleton::skelanims
456 : *
457 : * Searches for the first skelanimspec in skeleton::skelanims with a
458 : * nonnull name field equalling the name passed. The first such entry
459 : * is returned by const pointer (or nullptr if none is found)
460 : *
461 : * @param name the skelanimspec::name to query
462 : *
463 : * @return pointer to element of skeleton::skelanims
464 : */
465 : const skelanimspec *findskelanim(std::string_view name) const;
466 :
467 : /**
468 : * @brief Adds a skelanimspec to the end of skelanims()
469 : *
470 : * @param name the name to set in the skelanimspec
471 : * @param numframes the number of frames to set in the skelanimspec
472 : * @param amimframes the number of animation frames to set in the skelanimspec
473 : *
474 : * @return a reference to the added skelanimspec
475 : */
476 : skelanimspec &addskelanim(std::string_view name, int numframes, int animframes);
477 :
478 : /**
479 : * @brief Returns the first bone index in skeleton::bones with matching name field
480 : *
481 : * @param name the name to search for
482 : *
483 : * @return the index in skeleton::bones if found, nullopt if not
484 : */
485 : std::optional<size_t> findbone(std::string_view name) const;
486 :
487 : /**
488 : * @brief Returns the first tag index in skeleton::tags with matching name field
489 : *
490 : * @param name the name to search for
491 : *
492 : * @return the index in skeleton::tags if found, nullopt if not
493 : */
494 : std::optional<size_t> findtag(std::string_view name) const;
495 :
496 : /**
497 : * @brief Modifies or sets a tag in the `skeleton::tags`
498 : *
499 : * If there is a tag in `skeleton::tags` with a matching name to `name`,
500 : * sets the value of `bone` and `matrix` in that object.
501 : *
502 : * If there is no such tag, creates a new one at the end of `skeleton:tags`
503 : * with `name`, `bone` and `matrix` set
504 : *
505 : * @param name name string to search for
506 : * @param bone bone value to set
507 : * @param matrix matrix value to set
508 : */
509 : bool addtag(std::string_view name, int bone, const matrix4x3 &matrix);
510 :
511 : /**
512 : * @brief Returns the first pitchcorrect index in skeleton::pitchcorrects with matching bone
513 : *
514 : * @param bone the bone to search for
515 : *
516 : * @return the index in skeleton::pitchcorrects if found, nullopt if not
517 : */
518 : std::optional<size_t> findpitchcorrect(int bone) const;
519 : void optimize();
520 :
521 : /**
522 : * @brief Applies bone mask values to the given partmask.
523 : *
524 : * No effect if mask is empty or starts with end enum value (Bonemask_End).
525 : * The partmask will not be changed in size, and is implied to be of size
526 : * `numbones`.
527 : * Sets `partindex` value to elements in `partmask` according to `expandbonemask()`,
528 : * applied to a copy of `mask`.
529 : * Partindex will be downcast from int -> unsigned char.
530 : *
531 : * @param mask vector of mask values to determine setting with, size numbones
532 : * @param partmask vector of values to conditionally set
533 : * @param partindex value to conditionally set to partmask
534 : */
535 : void applybonemask(const std::vector<uint> &mask, std::vector<uchar> &partmask, int partindex) const;
536 :
537 : /**
538 : * @brief Links this skeleton's children (boneinfo elements)
539 : *
540 : * Invalidates each element's child index and then resets it, if another boneinfo
541 : * read later (later implies position lower on tree) indicates it as a parent. The
542 : * value previously pointed to as the child in that parent object is set to the child's
543 : * `next` field.
544 : *
545 : * Invalidates the `next` element in the boneinfo's linkedlist if there is no valid parent
546 : * for a bone (is the top of the tree).
547 : */
548 : void linkchildren();
549 :
550 : /**
551 : * @brief Returns the number of bones available to be accelerated
552 : *
553 : * Returns the lesser of the `maxvsuniforms` (max vertex shader uniforms)
554 : * and `maxskelanimdata` (divided by two).
555 : *
556 : * @return number of bones that can be accelerated
557 : */
558 : static int availgpubones();
559 :
560 : /**
561 : * @brief Sets up the ragdolldata passed using the metadata of this skeleton.
562 : *
563 : * The ragdolldata, which is an object that acts as an instatiation of the ragdollskel
564 : * contained in skeleton->ragdoll, has its joints, animjoints, verts, and reljoints
565 : * set up by the ragdollskel associated with this model's skeleton.
566 : *
567 : * These values are modified by the array of dualquat transformations stored in the
568 : * skelcacheentry pointed to by `sc`.
569 : *
570 : * @param d the ragdolldata to set up
571 : * @param sc the location of the dualquat transformations to apply
572 : * @param scale scale factor for the vertex coordinates
573 : */
574 : void initragdoll(ragdolldata &d, const skelcacheentry &sc, float scale) const;
575 :
576 : /**
577 : * @brief Sets n to the product of m, the i'th bone's base matrix, and the i'th tag's matrix
578 : *
579 : * The contents of the matrix n have no effect on the resulting value stored in n.
580 : *
581 : * @param i the tag index in skeleton::tags to use
582 : * @param m the matrix to start the transform with
583 : * @param n the matrix to set
584 : */
585 : void concattagtransform(int i, const matrix4x3 &m, matrix4x3 &n) const;
586 : void calctags(part *p, const skelcacheentry *sc = nullptr) const;
587 : void cleanup(bool full = true);
588 :
589 : /**
590 : * @brief Gets a skelcacheentry from skeleton::skelcache
591 : *
592 : * Returns the first skelcacheentry matching the specified pitch, partmask (from `as` parent part),
593 : * ragdolldata. If no such element exists, creates one and adds it to the back of the skelcache,
594 : * modifying the ragdollbones and calling interpbones() to update the model's bones
595 : *
596 : * @param pos position to set in interpbones() if entry added
597 : * @param scale scale to add to new skelcache entry if added
598 : * @param as array of animstates, size numanimparts, from which metadata is queried
599 : * @param pitch pitch value to check against and conditionally set
600 : * @param axis value to pass to interpbones() if new entry added
601 : * @param forward value to pass to interpbones() if new entry added
602 : * @param rdata ragdoll data to check against and conditionally set
603 : *
604 : * @return the skelcache entry which was either found or added
605 : */
606 : const skelcacheentry &checkskelcache(const vec &pos, float scale, const AnimState *as, float pitch, const vec &axis, const vec &forward, const ragdolldata * const rdata);
607 : void setgpubones(const skelcacheentry &sc, const blendcacheentry *bc, int count);
608 : bool shouldcleanup() const;
609 :
610 : /**
611 : * @brief Sets the pitch information for the index'th bone in the skeleton's bones
612 : *
613 : * If no bone exists at `index` returns false; otherwise returns true
614 : * If no bone exists, only effect is to return false.
615 : *
616 : * @param index the index in `skeleton::bones`
617 : * @param scale scale factor to set
618 : * @param offset pitch offset value to set
619 : * @param min pitch minimum value to set
620 : * @param max pitch maximum value to set
621 : *
622 : * @return true if index within bounds, false if outside
623 : */
624 : bool setbonepitch(size_t index, float scale, float offset, float min, float max);
625 :
626 : /**
627 : * @brief Returns the dualquaternion base transform from the specified bone
628 : *
629 : * Returns the dualquat base field from skeleton::bones; if index is out
630 : * of bounds, returns nullopt.
631 : *
632 : * @param index index in skeleton::bones
633 : *
634 : * @return value of bone's dualquat base
635 : */
636 : std::optional<dualquat> getbonebase(size_t index) const;
637 :
638 : /**
639 : * @brief Assigns the vector of dual quaternion bases to skeleton:bones
640 : *
641 : * Assigns the vector of dual quaternion bases to the skeleton::bones
642 : * field. If skeleton::bones and bases are not the same length, returns
643 : * false and performs no operation; returns true otherwise.
644 : *
645 : * @param bases the vector of bases to assign
646 : */
647 : bool setbonebases(const std::vector<dualquat> &bases);
648 :
649 : /**
650 : * @brief Sets a boneinfo's name in skeleton::bones
651 : *
652 : * Only sets name if no name is present (null string). Does not apply
653 : * any effect if the index is out of bounds.
654 : *
655 : * @param index the element of skeleton::bones to modify
656 : * @param name the new name to set
657 : *
658 : * @return true if the name was set, false if a name already existed or invalid index
659 : */
660 : bool setbonename(size_t index, std::string_view name);
661 :
662 : /**
663 : * @brief Sets a boneinfo's parent in skeleton::bones
664 : *
665 : * Does not apply any effect if the index or parent is out of bounds
666 : * (if either value is larger than numbones)
667 : *
668 : * @param index the element of skeleton::bones to modify
669 : * @param name the new name to set
670 : *
671 : * @return true if the name was set, false if either value was an invalid index
672 : */
673 : bool setboneparent(size_t index, size_t parent);
674 :
675 : /**
676 : * @brief Creates a boneinfo array and assigns it to skeleton::bones
677 : *
678 : * Also sets the value of numbones to the size passed.
679 : * Will cause a memory leak if skeleton::bones is already heap-allocated.
680 : *
681 : * @param num the number of array elements in the new array
682 : */
683 : void createbones(size_t num);
684 :
685 : /**
686 : * @brief Creates a ragdoll if none is defined; returns the skeleton's ragdoll
687 : *
688 : * @return a pointer to this model's ragdoll
689 : */
690 : ragdollskel *trycreateragdoll();
691 :
692 : private:
693 : skelmeshgroup * const owner;
694 : size_t numinterpbones;
695 :
696 : struct BoneInfo final
697 : {
698 : std::string name;
699 : int parent, //parent node in boneinfo
700 : children, //first index of child bone list in boneinfo
701 : next, //next adjacent sibling bone in boneinfo, last bone in sibling list has next = 0
702 : group,
703 : scheduled,
704 : interpindex,
705 : interpparent,
706 : ragdollindex,
707 : correctindex;
708 : float pitchscale, pitchoffset, pitchmin, pitchmax;
709 : dualquat base;
710 :
711 : BoneInfo();
712 : };
713 : /**
714 : * nodes in boneinfo tree, node relations in the tree are indicated
715 : * by boneinfo's fields
716 : *
717 : * n-leaf tree (nodes can have 0...INT_MAX children)
718 : *
719 : * size equal to numbones
720 : */
721 : BoneInfo *bones;
722 :
723 : struct pitchdep
724 : {
725 : int bone, parent;
726 : dualquat pose;
727 : };
728 : std::vector<pitchdep> pitchdeps;
729 :
730 : struct antipode
731 : {
732 : int parent, child;
733 :
734 0 : antipode(int parent, int child) : parent(parent), child(child) {}
735 : };
736 : std::vector<antipode> antipodes;
737 :
738 : struct tag
739 : {
740 : std::string name;
741 : int bone;
742 : matrix4x3 matrix;
743 :
744 6 : tag(std::string_view name, int bone, matrix4x3 matrix) : name(name), bone(bone), matrix(matrix) {}
745 : };
746 : std::vector<tag> tags;
747 :
748 : /**
749 : * @brief Cache used by skeleton::getblendoffset() to cache glGetUniformLocation queries
750 : */
751 : std::unordered_map<GLuint, GLint> blendoffsets;
752 :
753 : /**
754 : * @brief Creates a new antipode array
755 : *
756 : * Clears the existing skeleton::antipode vector and adds new bones
757 : * corresponding to boneinfo objects from the skeleton::bones array.
758 : *
759 : * The number of antipodes in the created array is no larger than the
760 : * number of values in the skeleton::bones array (skeleton::numbones)
761 : * multiplied by the number of bones with their group field set to
762 : * a value larger than numbones.
763 : */
764 : void calcantipodes();
765 : void remapbones();
766 :
767 : struct framedata
768 : {
769 : const dualquat *fr1, *fr2, //frame data
770 : *pfr1, *pfr2; //part frame data
771 : };
772 :
773 : /**
774 : * @brief Gets the location of the uniform specified
775 : *
776 : * Helper function for setglslbones().
777 : *
778 : * Gets the uniform location of the uniform in `u`, at index
779 : * 2*`skeleton::numgpubones`. Adds the shader program in `shader::lastshader`
780 : * to `blendoffsets` if it is not already there.
781 : *
782 : * Once a shader program has been added to the `skeleton::blendoffsets`
783 : * map, further calls of this function while that shader program is
784 : * assigned to `shader::lastshader` will return the first uniformloc
785 : * location query value associated with that program, and the parameter
786 : * will be ignored.
787 : *
788 : * Returns the array element at 2*skeleton::numgpubones, skipping
789 : * the values used in `setglslbones()` to set the `sc` skelcacheentry values.
790 : *
791 : * @param u the uniformloc to query
792 : *
793 : * @return a GLint location of the uniform array at a position skipping the "sc" elements
794 : */
795 : GLint getblendoffset(const UniformLoc &u);
796 :
797 : /**
798 : * @brief Sets uniform values from skelcacheentries to the uniform at the specified UniformLoc
799 : *
800 : * Uses glUniform4fv to copy 4 dimensional quaternion values from the specified
801 : * skelcacheentries into the GL uniform array pointed to by the UniformLoc u.
802 : * The number of values copied from sc will be skeleton::numgpubones*2, and
803 : * the number of values copied from bc will be `count`. Only the real component
804 : * of the dual quaternions are copied.
805 : *
806 : * Sets the version and data values of the UniformLoc to that of the bc parameter,
807 : * to cache this operation only to occur when there is a mismatch between those
808 : * two objects.
809 : *
810 : * @param u the uniform location object to modify corresponding uniforms of
811 : * @param sc the skelcacheentry from which to set
812 : * @param bc the skelcacheentry from which to set the trailing values from
813 : * @param count the number of entries from bc to place in
814 : */
815 : void setglslbones(UniformLoc &u, const skelcacheentry &sc, const skelcacheentry &bc, int count);
816 : dualquat interpbone(int bone, const std::array<framedata, maxanimparts> &partframes, const AnimState *as, const uchar *partmask) const;
817 : void addpitchdep(int bone, int frame);
818 : static float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2);
819 :
820 : /**
821 : * @brief Searches for a pitchdep in the pitchdeps field
822 : *
823 : * Searches the pitchdeps vector field in ascending order. For each pitchdep
824 : * in the pitchdeps vector, if the bone stored in that pitchdep is at least as
825 : * high of an index (lower on the tree) then searching is stopped and the
826 : * function will either return that index (if the pitchdep's bone field exactly
827 : * matches) or nullopt if the bone at that pitchdep is lower on the tree.
828 : *
829 : * If the bone passed is larger than any bone data in any pitchep in the
830 : * pitchdeps vector, returns nullopt.
831 : *
832 : * @param bone the bone to search for
833 : *
834 : * @return nullopt if no such valid pitchdep exists
835 : * @return index of pitchdep if found
836 : */
837 : std::optional<size_t> findpitchdep(int bone) const;
838 : void initpitchdeps();
839 :
840 : /**
841 : * @brief Recursively applies the specified mask value to the bone mask array passed.
842 : *
843 : * The expansion array should be equal to the number of bones (which may be greater
844 : * than the bone parameter).
845 : *
846 : * Assigns the value val to the bone'th element in the expansion array, then calls
847 : * this function recursively for all children pointed to by the bone's index in
848 : * the boneinfo array `bones`.
849 : *
850 : * Applies the value val to children in boneinfo depth-first. All nodes traversed
851 : * will have the same value assigned (in the expansion array).
852 : *
853 : * @param expansion mask array to assign values to
854 : * @param bone the root bone to assign values to
855 : * @param val the value to set
856 : */
857 : void expandbonemask(uchar *expansion, int bone, int val) const;
858 : void calcpitchcorrects(float pitch, const vec &axis, const vec &forward);
859 : void interpbones(const AnimState *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc);
860 :
861 : /**
862 : * @brief Sets up a skelcacheentry's bone transformations.
863 : *
864 : * Uses the model data at `d` and the translation/positon information passed
865 : * to set up the dual quaternion transformation array in the passed skelcacheentry.
866 : * Creates a new array of size `skeleton::numinterpbones` if no array exists, allocated
867 : * on the heap.
868 : *
869 : * @param d the ragdolldata to use vertex/tri information from
870 : * @param sc the skelcacheentry to set up
871 : * @param translate the position of the model
872 : * @param scale the scale factor of the model's vertices
873 : */
874 : void genragdollbones(const ragdolldata &d, skelcacheentry &sc, const vec &translate, float scale) const;
875 :
876 : };
877 :
878 : class skelmeshgroup : public meshgroup
879 : {
880 : public:
881 : skeleton *skel;
882 :
883 : std::vector<blendcombo> blendcombos;
884 :
885 : GLuint *edata;
886 :
887 3 : skelmeshgroup() : skel(nullptr), edata(nullptr), ebuf(0), vweights(0), vlen(0), vertsize(0), vblends(0), vdata(nullptr)
888 : {
889 3 : numblends.fill(0);
890 3 : }
891 :
892 : virtual ~skelmeshgroup();
893 :
894 : std::optional<size_t> findtag(std::string_view) final;
895 :
896 : /**
897 : * @brief Returns the skelmodel::skeleton object this skelmeshgroup points to.
898 : *
899 : * Returns the pointer to the skeleton object associated with this object.
900 : */
901 : void *animkey() final;
902 : int totalframes() const final;
903 : void concattagtransform(int i, const matrix4x3 &m, matrix4x3 &n) const final;
904 : void preload() final;
905 : void render(const AnimState *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) final;
906 :
907 : //for vvert, vvertg and vvertgw (also for vvertgw see below function),
908 : //disable bones if active
909 : //must have same const-qualification to properly interact with bindbones() below
910 : template<class T>
911 0 : void bindbones(const T *)
912 : {
913 0 : if(enablebones)
914 : {
915 0 : disablebones();
916 : }
917 0 : }
918 :
919 : /* this function is only called if `bindbones(vvertgw *)` is used to call it;
920 : * if you call bindbones<vvertgw>(), that will call the above template
921 : * (this function can be called if no <> specifier is provided, because
922 : * of partial ordering rules -- see C++20 N4849 13.10.2.4)
923 : */
924 : void bindbones(const vvertgw *vverts);
925 :
926 : template<class T>
927 0 : void bindvbo(const AnimState *as, const part *p, const vbocacheentry &vc)
928 : {
929 0 : T *vverts = nullptr;
930 0 : bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize);
931 0 : if(as->cur.anim & Anim_NoSkin)
932 : {
933 0 : if(enabletangents)
934 : {
935 0 : disabletangents();
936 : }
937 0 : if(p->alphatested())
938 : {
939 0 : bindtc(&vverts->tc, vertsize);
940 : }
941 0 : else if(enabletc)
942 : {
943 0 : disabletc();
944 : }
945 : }
946 : else
947 : {
948 0 : bindtangents(&vverts->tangent, vertsize);
949 :
950 0 : bindtc(&vverts->tc, vertsize);
951 : }
952 0 : bindbones(vverts);
953 0 : }
954 :
955 : void makeskeleton();
956 : /**
957 : * @brief Generates a vertex buffer object for an associated vbocache entry
958 : * the vbocacheentry passed will have its vbuf assigned to a GL buffer,
959 : * and if there is no ebuf (element array buffer) the following will
960 : * occur (summarized):
961 : *
962 : * - vweights will be set depending animation presence and gpuskel
963 : * - vlen will be set to the sum of all the encapsulated meshes' vertices
964 : * - vdata will be deleted and re-allocated as an array of size vlen*sizeof(vert object)
965 : * - vdata will be filled with values using fillverts() (which gets data from skelmesh::verts array)
966 : * - ebuf will be filled with data from skelmesh::genvbo, with ebuf size being equal to all of the respective meshes' tri counts summed
967 : *
968 : * @param vc the vbocacheentry to modify
969 : */
970 : void genvbo(vbocacheentry &vc);
971 : void bindvbo(const AnimState *as, const part *p, const vbocacheentry &vc);
972 : int addblendcombo(const blendcombo &c);
973 : //sorts the blendcombos by its comparison function, then applies this new order to associated skelmesh verts
974 : void sortblendcombos();
975 : void blendbones(const skelcacheentry &sc, blendcacheentry &bc) const;
976 : void cleanup() final;
977 :
978 : virtual bool load(std::string_view meshfile, float smooth, part &p) = 0;
979 : virtual const skelanimspec *loadanim(const std::string &filename) = 0;
980 : private:
981 : std::array<int, 4> numblends;
982 :
983 : static constexpr size_t maxblendcache = 16; //number of entries in the blendcache entry array
984 : static constexpr size_t maxvbocache = 16; //number of entries in the vertex buffer object array
985 :
986 : std::array<blendcacheentry, maxblendcache> blendcache;
987 : std::array<vbocacheentry, maxvbocache> vbocache;
988 : /*
989 : * ebuf, vbo variables are all initialized by genvbo(vbocacheentry), if render() has an ebuf
990 : * present then vbo variables will not be modified in render()
991 : */
992 : GLuint ebuf; //GL_ELEMENT_ARRAY_BUFFER gluint handle
993 : int vweights, //number of vbo weights, values 0...4
994 : vlen, //sum of this skelmeshgroup's renderable meshes' vertex counts
995 : vertsize, //sizeof vvert, if skeleton has animation frames & gpuskel; sizeof vvertgw if animation frames and no gpuskel, sizeof vvertg if neither
996 : vblends; //number of blendcombos (= number of verts in e.g. md5)
997 : uchar *vdata; //vertex data drawn in the render() stage. It is filled by genvbo() and then used as a GL_ARRAY_BUFFER in the render() stage.
998 :
999 : blendcacheentry &checkblendcache(const skelcacheentry &sc, int owner);
1000 : };
1001 :
1002 : class skelpart : public part
1003 : {
1004 : public:
1005 : std::vector<uchar> partmask;
1006 :
1007 : skelpart(animmodel *model, int index = 0);
1008 : virtual ~skelpart();
1009 :
1010 : void initanimparts();
1011 : bool addanimpart(const std::vector<uint> &bonemask);
1012 : void loaded() final;
1013 : private:
1014 : std::vector<uchar> buildingpartmask;
1015 :
1016 : /**
1017 : * @brief Manages caching of part masking data.
1018 : *
1019 : * Attempts to match the passed vector of uchar with one in the internal static vector.
1020 : * If a matching entry is found, empties the passed vector returns the entry in the cache.
1021 : *
1022 : * @param o a vector of uchar to delete or insert into the internal cache
1023 : * @return the passed value o, or the equivalent entry in the internal cache
1024 : */
1025 : std::vector<uchar> &sharepartmask(std::vector<uchar> &o);
1026 : std::vector<uchar> newpartmask();
1027 : void endanimparts();
1028 : };
1029 :
1030 : //ordinary methods
1031 : skelmodel(std::string name);
1032 : skelpart &addpart();
1033 : meshgroup *loadmeshes(const std::string &name, float smooth = 2);
1034 : meshgroup *sharemeshes(const std::string &name, float smooth = 2);
1035 :
1036 : //override methods
1037 :
1038 : /**
1039 : * @brief Returns the link type of an animmodel relative to a part
1040 : *
1041 : * If `this` model's zeroth part's mesh's skel is the same as the passed part's
1042 : * mesh's skel, returns Link_Reuse
1043 : * If the passed model parameter is not linkable, or does not meet the criteria above,
1044 : * returns Link_Tag.
1045 : *
1046 : * @param m must point to a valid animmodel object
1047 : * @param p must point to a valid part object which points to a valid skeleton.
1048 : */
1049 : int linktype(const animmodel *m, const part *p) const final;
1050 : bool skeletal() const final;
1051 :
1052 : protected:
1053 : //virtual methods
1054 : virtual skelmeshgroup *newmeshes() = 0;
1055 :
1056 : };
1057 :
1058 : class skeladjustment final
1059 : {
1060 : public:
1061 0 : skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
1062 : void adjust(dualquat &dq) const;
1063 :
1064 : private:
1065 : float yaw, pitch, roll;
1066 : vec translate;
1067 : };
1068 :
1069 : template<class MDL>
1070 : struct skelloader : modelloader<MDL, skelmodel>
1071 : {
1072 : static std::vector<skeladjustment> adjustments;
1073 : static std::vector<uchar> hitzones;
1074 :
1075 18 : skelloader(std::string name) : modelloader<MDL, skelmodel>(name) {}
1076 : };
1077 :
1078 : template<class MDL>
1079 : std::vector<skeladjustment> skelloader<MDL>::adjustments;
1080 :
1081 : template<class MDL>
1082 : std::vector<uchar> skelloader<MDL>::hitzones;
1083 :
1084 : /**
1085 : * @brief Defines skeletal commands for a chosen type of skeletal model format.
1086 : *
1087 : * this template structure defines a series of commands for a model object (or
1088 : * child of the model object) which can be used to set its dynamically modifiable
1089 : * properties
1090 : *
1091 : */
1092 : template<class MDL>
1093 : struct skelcommands : modelcommands<MDL>
1094 : {
1095 : typedef modelcommands<MDL> commands;
1096 : typedef class MDL::skeleton skeleton;
1097 : typedef class MDL::skelmeshgroup meshgroup;
1098 : typedef class MDL::skelpart part;
1099 : typedef struct MDL::skelanimspec animspec;
1100 : typedef struct MDL::skeleton::pitchtarget pitchtarget;
1101 : typedef struct MDL::skeleton::pitchcorrect pitchcorrect;
1102 :
1103 : //unused second param
1104 13 : static void loadpart(const char *meshfile, const char *, const float *smooth)
1105 : {
1106 13 : if(!MDL::loading)
1107 : {
1108 2 : conoutf("not loading an %s", MDL::formatname());
1109 2 : return;
1110 : }
1111 11 : std::string filename;
1112 11 : filename.append(MDL::dir).append("/").append(meshfile ? meshfile : "");
1113 11 : part &mdl = MDL::loading->addpart();
1114 11 : mdl.meshes = MDL::loading->sharemeshes(path(filename), *smooth > 0 ? std::cos(std::clamp(*smooth, 0.0f, 180.0f)/RAD) : 2);
1115 11 : if(!mdl.meshes)
1116 : {
1117 0 : conoutf("could not load %s", filename.c_str());
1118 : }
1119 : else
1120 : {
1121 11 : if(mdl.meshes && static_cast<meshgroup *>(mdl.meshes)->skel->numbones > 0)
1122 : {
1123 11 : mdl.disablepitch();
1124 : }
1125 11 : mdl.initanimparts();
1126 11 : mdl.initskins();
1127 : }
1128 11 : }
1129 :
1130 : /**
1131 : * @brief Adds a tag corresponding to a bone
1132 : *
1133 : * @param name the name of the bone in the skeletal model
1134 : * @param name the new name to assign the associated tag
1135 : *
1136 : * @param tx/ty/tz translation parameters
1137 : * @param rx/ry/rz rotation parameters
1138 : */
1139 4 : static void settag(const char *name, const char *tagname,
1140 : const float *tx, const float *ty, const float *tz,
1141 : const float *rx, const float *ry, const float *rz)
1142 : {
1143 4 : if(!MDL::loading || MDL::loading->parts.empty())
1144 : {
1145 2 : conoutf("not loading an %s", MDL::formatname());
1146 4 : return;
1147 : }
1148 2 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1149 2 : std::optional<size_t> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1150 2 : if(i)
1151 : {
1152 2 : float cx = *rx ? std::cos(*rx/(2*RAD)) : 1, sx = *rx ? std::sin(*rx/(2*RAD)) : 0,
1153 2 : cy = *ry ? std::cos(*ry/(2*RAD)) : 1, sy = *ry ? std::sin(*ry/(2*RAD)) : 0,
1154 2 : cz = *rz ? std::cos(*rz/(2*RAD)) : 1, sz = *rz ? std::sin(*rz/(2*RAD)) : 0;
1155 2 : matrix4x3 m(matrix3(quat(sx*cy*cz - cx*sy*sz, cx*sy*cz + sx*cy*sz, cx*cy*sz - sx*sy*cz, cx*cy*cz + sx*sy*sz)),
1156 2 : vec(*tx, *ty, *tz));
1157 2 : static_cast<meshgroup *>(mdl.meshes)->skel->addtag(tagname, *i, m);
1158 2 : return;
1159 : }
1160 0 : conoutf("could not find bone %s for tag %s", name, tagname);
1161 : }
1162 :
1163 : //attempts to set the pitch of a named bone within a MDL object, within the bounds set
1164 : //prints to console failure messages if no model or no bone with name passed
1165 2 : static void setpitch(const char *name, const float *pitchscale,
1166 : const float *pitchoffset, const float *pitchmin, const float *pitchmax)
1167 : {
1168 2 : if(!MDL::loading || MDL::loading->parts.empty())
1169 : {
1170 2 : conoutf("not loading an %s", MDL::formatname());
1171 2 : return;
1172 : }
1173 0 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1174 :
1175 0 : if(name[0])
1176 : {
1177 0 : std::optional<size_t> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1178 0 : if(i)
1179 : {
1180 0 : float newpitchmin = 0.f,
1181 0 : newpitchmax = 0.f;
1182 0 : if(*pitchmin || *pitchmax)
1183 : {
1184 0 : newpitchmin = *pitchmin;
1185 0 : newpitchmax = *pitchmax;
1186 : }
1187 : else
1188 : {
1189 0 : newpitchmin = -360*std::fabs(*pitchscale) + *pitchoffset;
1190 0 : newpitchmax = 360*std::fabs(*pitchscale) + *pitchoffset;
1191 : }
1192 0 : static_cast<meshgroup *>(mdl.meshes)->skel->setbonepitch(*i, *pitchscale, *pitchoffset, newpitchmin, newpitchmax);
1193 0 : return;
1194 : }
1195 0 : conoutf("could not find bone %s to pitch", name);
1196 0 : return;
1197 : }
1198 :
1199 0 : mdl.pitchscale = *pitchscale;
1200 0 : mdl.pitchoffset = *pitchoffset;
1201 0 : if(*pitchmin || *pitchmax)
1202 : {
1203 0 : mdl.pitchmin = *pitchmin;
1204 0 : mdl.pitchmax = *pitchmax;
1205 : }
1206 : else
1207 : {
1208 0 : mdl.pitchmin = -360*std::fabs(mdl.pitchscale) + mdl.pitchoffset;
1209 0 : mdl.pitchmax = 360*std::fabs(mdl.pitchscale) + mdl.pitchoffset;
1210 : }
1211 : }
1212 :
1213 3 : static void setpitchtarget(const char *name, const char *animfile, const int *frameoffset,
1214 : const float *pitchmin, const float *pitchmax)
1215 : {
1216 3 : if(!MDL::loading || MDL::loading->parts.empty())
1217 : {
1218 2 : conoutf("not loading an %s", MDL::formatname());
1219 2 : return;
1220 : }
1221 1 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1222 1 : if(!mdl.meshes)
1223 : {
1224 0 : return;
1225 : }
1226 1 : std::string filename = std::format("{}/{}", MDL::dir, animfile);
1227 1 : const animspec *sa = static_cast<meshgroup *>(mdl.meshes)->loadanim(path(filename));
1228 1 : if(!sa)
1229 : {
1230 0 : conoutf("could not load %s anim file %s", MDL::formatname(), filename.c_str());
1231 0 : return;
1232 : }
1233 1 : skeleton *skel = static_cast<meshgroup *>(mdl.meshes)->skel;
1234 1 : std::optional<size_t> bone = skel ? skel->findbone(name) : std::nullopt;
1235 1 : if(!bone)
1236 : {
1237 0 : conoutf("could not find bone %s to pitch target", name);
1238 0 : return;
1239 : }
1240 1 : for(const pitchtarget &i : skel->pitchtargets)
1241 : {
1242 0 : if(i.bone == *bone)
1243 : {
1244 0 : return;
1245 : }
1246 : }
1247 1 : pitchtarget t;
1248 1 : t.bone = *bone;
1249 1 : t.frame = sa->frame + std::clamp(*frameoffset, 0, sa->range-1);
1250 1 : t.pitchmin = *pitchmin;
1251 1 : t.pitchmax = *pitchmax;
1252 1 : skel->pitchtargets.push_back(t);
1253 1 : }
1254 :
1255 : /**
1256 : * @brief Adds a pitch correction to this model's skeleton
1257 : *
1258 : * @param name the name of the bone to pitch correct
1259 : * @param targetname the name of the bone to target
1260 : * @param scale the scale to apply to the pitchcorrect
1261 : * @param pitchmin the minimum pitch to apply
1262 : * @param pitchmax the maximum pitch to apply
1263 : */
1264 2 : static void setpitchcorrect(const char *name, const char *targetname,
1265 : const float *scale, const float *pitchmin, const float *pitchmax)
1266 : {
1267 2 : if(!MDL::loading || MDL::loading->parts.empty())
1268 : {
1269 2 : conoutf("not loading an %s", MDL::formatname());
1270 2 : return;
1271 : }
1272 0 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1273 0 : if(!mdl.meshes)
1274 : {
1275 0 : return;
1276 : }
1277 0 : skeleton *skel = static_cast<meshgroup *>(mdl.meshes)->skel;
1278 0 : std::optional<int> bone = skel ? skel->findbone(name) : std::nullopt;
1279 0 : if(!bone)
1280 : {
1281 0 : conoutf("could not find bone %s to pitch correct", name);
1282 0 : return;
1283 : }
1284 0 : if(skel->findpitchcorrect(*bone) >= 0)
1285 : {
1286 0 : return;
1287 : }
1288 0 : std::optional<size_t> targetbone = skel->findbone(targetname),
1289 0 : target = std::nullopt;
1290 0 : if(targetbone)
1291 : {
1292 0 : for(size_t i = 0; i < skel->pitchtargets.size(); i++)
1293 : {
1294 0 : if(skel->pitchtargets[i].bone == *targetbone)
1295 : {
1296 0 : target = i;
1297 0 : break;
1298 : }
1299 : }
1300 : }
1301 0 : if(!target)
1302 : {
1303 0 : conoutf("could not find pitch target %s to pitch correct %s", targetname, name);
1304 0 : return;
1305 : }
1306 0 : pitchcorrect c(*bone, *target, *pitchmin, *pitchmax, *scale);
1307 0 : size_t pos = skel->pitchcorrects.size();
1308 0 : for(size_t i = 0; i < skel->pitchcorrects.size(); i++)
1309 : {
1310 0 : if(bone <= skel->pitchcorrects[i].bone)
1311 : {
1312 0 : pos = i;
1313 0 : break;
1314 : }
1315 : }
1316 0 : skel->pitchcorrects.insert(skel->pitchcorrects.begin() + pos, c);
1317 : }
1318 :
1319 : /**
1320 : * @param Assigns an animation to the currently loaded model.
1321 : *
1322 : * Attempts to give a model object an animation by the name of anim parameter
1323 : * loaded from animfile with speed/priority/offsets to determine how fast
1324 : * and what frames play.
1325 : *
1326 : * The name of the anim being loaded (anim param) must be in the global animnames vector.
1327 : *
1328 : * The MDL::loading static field must be set by calling startload() for the
1329 : * relevant model object.
1330 : *
1331 : * The animation will be applied to the most recent loaded part (with loadpart()).
1332 : */
1333 4 : static void setanim(const char *anim, const char *animfile, const float *speed,
1334 : const int *priority, const int *startoffset, const int *endoffset)
1335 : {
1336 4 : if(!MDL::loading || MDL::loading->parts.empty())
1337 : {
1338 2 : conoutf("not loading an %s", MDL::formatname());
1339 2 : return;
1340 : }
1341 2 : std::vector<size_t> anims = findanims(anim);
1342 2 : if(anims.empty())
1343 : {
1344 0 : conoutf("could not find animation %s", anim);
1345 : }
1346 : else
1347 : {
1348 2 : part *p = static_cast<part *>(MDL::loading->parts.back());
1349 2 : if(!p->meshes)
1350 : {
1351 0 : return;
1352 : }
1353 2 : std::string filename = std::format("{}/{}", MDL::dir, animfile);
1354 2 : const animspec *sa = static_cast<meshgroup *>(p->meshes)->loadanim(path(filename));
1355 2 : if(!sa)
1356 : {
1357 0 : conoutf("could not load %s anim file %s", MDL::formatname(), filename.c_str());
1358 : }
1359 : else
1360 : {
1361 4 : for(size_t i = 0; i < anims.size(); i++)
1362 : {
1363 2 : int start = sa->frame,
1364 2 : end = sa->range;
1365 2 : if(*startoffset > 0)
1366 : {
1367 0 : start += std::min(*startoffset, end-1);
1368 : }
1369 2 : else if(*startoffset < 0)
1370 : {
1371 0 : start += std::max(end + *startoffset, 0);
1372 : }
1373 2 : end -= start - sa->frame;
1374 2 : if(*endoffset > 0)
1375 : {
1376 0 : end = std::min(end, *endoffset);
1377 : }
1378 2 : else if(*endoffset < 0)
1379 : {
1380 0 : end = std::max(end + *endoffset, 1);
1381 : }
1382 2 : MDL::loading->parts.back()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority);
1383 : }
1384 : }
1385 2 : }
1386 2 : }
1387 :
1388 : /**
1389 : * @brief Assigns a subtree of bones to a bone mask.
1390 : *
1391 : * This bone mask is used to separate a part into two (and no more than two)
1392 : * distinct groups of subtrees which can be animated independently.
1393 : *
1394 : * These bones which make up subtree(s) under the specified bones are saved
1395 : * in the partmask vector of the relevant skelpart.
1396 : *
1397 : */
1398 3 : static void setanimpart(const char *maskstr)
1399 : {
1400 3 : if(!MDL::loading || MDL::loading->parts.empty())
1401 : {
1402 2 : conoutf("not loading an %s", MDL::formatname());
1403 2 : return;
1404 : }
1405 1 : part *p = static_cast<part *>(MDL::loading->parts.back());
1406 :
1407 1 : std::vector<std::string> bonestrs;
1408 1 : explodelist(maskstr, bonestrs);
1409 1 : std::vector<uint> bonemask;
1410 2 : for(size_t i = 0; i < bonestrs.size(); i++)
1411 : {
1412 1 : const std::string &bonestr = bonestrs[i];
1413 1 : std::optional<int> bone = p->meshes ? static_cast<meshgroup *>(p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr.substr(1) : bonestr) : std::nullopt;
1414 1 : if(!bone)
1415 : {
1416 0 : conoutf("could not find bone %s for anim part mask [%s]", bonestr.c_str(), maskstr);
1417 0 : return;
1418 : }
1419 1 : bonemask.push_back(*bone | (bonestr[0]=='!' ? Bonemask_Not : 0));
1420 : }
1421 1 : std::sort(bonemask.begin(), bonemask.end());
1422 1 : if(bonemask.size())
1423 : {
1424 1 : bonemask.push_back(Bonemask_End);
1425 : }
1426 1 : if(!p->addanimpart(bonemask))
1427 : {
1428 0 : conoutf("too many animation parts");
1429 : }
1430 1 : }
1431 :
1432 2 : static void setadjust(const char *name, const float *yaw, const float *pitch, const float *roll,
1433 : const float *tx, const float *ty, const float *tz)
1434 : {
1435 2 : if(!MDL::loading || MDL::loading->parts.empty())
1436 : {
1437 2 : conoutf("not loading an %s", MDL::formatname());
1438 2 : return;
1439 : }
1440 0 : const part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1441 0 : if(!name[0])
1442 : {
1443 0 : return;
1444 : }
1445 0 : std::optional<int> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1446 0 : if(!i)
1447 : {
1448 0 : conoutf("could not find bone %s to adjust", name);
1449 0 : return;
1450 : }
1451 0 : while(!(static_cast<int>(MDL::adjustments.size()) > *i))
1452 : {
1453 0 : MDL::adjustments.push_back(skeladjustment(0, 0, 0, vec(0, 0, 0)));
1454 : }
1455 0 : MDL::adjustments[*i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
1456 : }
1457 :
1458 2 : static void sethitzone(const int *id, const char *maskstr)
1459 : {
1460 2 : if(!MDL::loading || MDL::loading->parts.empty())
1461 : {
1462 2 : conoutf("not loading an %s", MDL::formatname());
1463 2 : return;
1464 : }
1465 0 : if(*id >= 0x80)
1466 : {
1467 0 : conoutf("invalid hit zone id %d", *id);
1468 0 : return;
1469 : }
1470 0 : const part *p = static_cast<part *>(MDL::loading->parts.back());
1471 0 : const meshgroup *m = static_cast<meshgroup *>(p->meshes);
1472 0 : if(!m)
1473 : {
1474 0 : return;
1475 : }
1476 0 : std::vector<std::string> bonestrs;
1477 0 : explodelist(maskstr, bonestrs);
1478 0 : std::vector<uint> bonemask;
1479 0 : for(size_t i = 0; i < bonestrs.size(); i++)
1480 : {
1481 0 : const std::string &bonestr = bonestrs[i];
1482 0 : std::optional<int> bone = p->meshes ? static_cast<meshgroup *>(p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr.substr(1) : bonestr) : std::nullopt;
1483 0 : if(!bone)
1484 : {
1485 0 : conoutf("could not find bone %s for hit zone mask [%s]", bonestr.c_str(), maskstr);
1486 0 : return;
1487 : }
1488 0 : bonemask.push_back(*bone | (bonestr[0]=='!' ? Bonemask_Not : 0));
1489 : }
1490 0 : if(bonemask.empty())
1491 : {
1492 0 : return;
1493 : }
1494 0 : std::sort(bonemask.begin(), bonemask.end());
1495 0 : bonemask.push_back(Bonemask_End);
1496 :
1497 0 : while(MDL::hitzones.size() < m->skel->numbones)
1498 : {
1499 0 : MDL::hitzones.emplace_back(0xFF);
1500 : }
1501 0 : m->skel->applybonemask(bonemask, MDL::hitzones, *id < 0 ? 0xFF : *id);
1502 0 : }
1503 :
1504 : /**
1505 : * @brief Checks for an available ragdoll to modify.
1506 : *
1507 : * If a skeletal model is being loaded, and meets the criteria for a ragdoll,
1508 : * returns the pointer to that ragdollskel (new one made if necessary), returns nullptr otherwise
1509 : *
1510 : * @return pointer to the loading skelmodel's ragdoll, or nullptr if no such object available
1511 : */
1512 14 : static ragdollskel *checkragdoll()
1513 : {
1514 14 : if(!MDL::loading)
1515 : {
1516 14 : conoutf(Console_Error, "not loading a model");
1517 14 : return nullptr;
1518 : }
1519 0 : if(!MDL::loading->skeletal())
1520 : {
1521 0 : conoutf(Console_Error, "not loading a skeletal model");
1522 0 : return nullptr;
1523 : }
1524 0 : skelmodel *m = static_cast<skelmodel *>(MDL::loading);
1525 0 : if(m->parts.empty())
1526 : {
1527 0 : return nullptr;
1528 : }
1529 0 : const skelmodel::skelmeshgroup *meshes = static_cast<skelmodel::skelmeshgroup *>(m->parts.back()->meshes);
1530 0 : if(!meshes)
1531 : {
1532 0 : return nullptr;
1533 : }
1534 0 : skelmodel::skeleton *skel = meshes->skel;
1535 0 : ragdollskel *ragdoll = skel->trycreateragdoll();
1536 0 : if(ragdoll->loaded)
1537 : {
1538 0 : return nullptr;
1539 : }
1540 0 : return ragdoll;
1541 : }
1542 :
1543 : /**
1544 : * @brief Adds a vertex to the working ragdoll vert list
1545 : *
1546 : * @param x the x position of the new vert
1547 : * @param y the y position of the new vert
1548 : * @param z the z position of the new vert
1549 : * @param radius the effect radius of the ragdoll vert
1550 : */
1551 2 : static void rdvert(const float *x, const float *y, const float *z, const float *radius)
1552 : {
1553 2 : ragdollskel *ragdoll = checkragdoll();
1554 2 : if(!ragdoll)
1555 : {
1556 2 : return;
1557 : }
1558 0 : ragdoll->verts.push_back({vec(*x, *y, *z), *radius > 0 ? *radius : 1, 0.f});
1559 : }
1560 :
1561 : /**
1562 : * @brief sets the ragdoll eye level
1563 : *
1564 : * Sets the ragdoll's eye point to the level passed
1565 : * implicitly modifies the ragdoll selected by CHECK_RAGDOLL
1566 : *
1567 : * @param v the level to set the eye at
1568 : */
1569 2 : static void rdeye(const int *v)
1570 : {
1571 2 : ragdollskel *ragdoll = checkragdoll();
1572 2 : if(!ragdoll)
1573 : {
1574 2 : return;
1575 : }
1576 0 : ragdoll->eye = *v;
1577 : }
1578 :
1579 :
1580 : /**
1581 : * @brief adds a ragdoll tri
1582 : *
1583 : * Adds a triangle to the current ragdoll with the specified indices.
1584 : * No effect if there is no current ragdoll.
1585 : *
1586 : * @param v1 first vertex index
1587 : * @param v2 second vertex index
1588 : * @param v3 third vertex index.
1589 : */
1590 2 : static void rdtri(const int *v1, const int *v2, const int *v3)
1591 : {
1592 2 : ragdollskel *ragdoll = checkragdoll();
1593 2 : if(!ragdoll)
1594 : {
1595 2 : return;
1596 : }
1597 0 : ragdoll->tris.push_back({*v1, *v2, *v3});
1598 : }
1599 :
1600 2 : static void rdjoint(const int *n, const int *t, const int *v1, const int *v2, const int *v3)
1601 : {
1602 2 : ragdollskel *ragdoll = checkragdoll();
1603 2 : if(!ragdoll)
1604 : {
1605 2 : return;
1606 : }
1607 0 : const skelmodel *m = static_cast<skelmodel *>(MDL::loading);
1608 0 : const skelmodel::skelmeshgroup *meshes = static_cast<const skelmodel::skelmeshgroup *>(m->parts.back()->meshes);
1609 0 : const skelmodel::skeleton *skel = meshes->skel;
1610 0 : if(*n < 0 || *n >= static_cast<int>(skel->numbones))
1611 : {
1612 0 : return;
1613 : }
1614 0 : ragdoll->joints.push_back({*n, *t, {*v1, *v2, *v3}, 0.f, matrix4x3()});
1615 : }
1616 :
1617 2 : static void rdlimitdist(const int *v1, const int *v2, const float *mindist, const float *maxdist)
1618 : {
1619 2 : ragdollskel *ragdoll = checkragdoll();
1620 2 : if(!ragdoll)
1621 : {
1622 2 : return;
1623 : }
1624 0 : ragdoll->distlimits.push_back({*v1, *v2, *mindist, std::max(*maxdist, *mindist)});
1625 : }
1626 :
1627 2 : static void rdlimitrot(const int *t1, const int *t2, const float *maxangle, const float *qx, const float *qy, const float *qz, const float *qw)
1628 : {
1629 2 : ragdollskel *ragdoll = checkragdoll();
1630 2 : if(!ragdoll)
1631 : {
1632 2 : return;
1633 : }
1634 0 : float rmaxangle = *maxangle / RAD;
1635 0 : ragdoll->rotlimits.push_back({*t1,
1636 : *t2,
1637 : rmaxangle,
1638 0 : 1 + 2*std::cos(rmaxangle),
1639 0 : matrix3(quat(*qx, *qy, *qz, *qw))});
1640 : }
1641 :
1642 2 : static void rdanimjoints(const int *on)
1643 : {
1644 2 : ragdollskel *ragdoll = checkragdoll();
1645 2 : if(!ragdoll)
1646 : {
1647 2 : return;
1648 : }
1649 0 : ragdoll->animjoints = *on!=0;
1650 : }
1651 :
1652 2 : skelcommands()
1653 2 : {
1654 2 : if(MDL::multiparted())
1655 : {
1656 2 : this->modelcommand(loadpart, "load", "ssf"); //<fmt>load [mesh] [skel] [smooth]
1657 : }
1658 2 : this->modelcommand(settag, "tag", "ssffffff"); //<fmt>tag [name] [tag] [tx] [ty] [tz] [rx] [ry] [rz]
1659 2 : this->modelcommand(setpitch, "pitchbone", "sffff"); //<fmt>pitchbone [name] [target] [scale] [min] [max]
1660 2 : this->modelcommand(setpitchtarget, "pitchtarget", "ssiff"); //<fmt>pitchtarget [name] [anim] [offset] [min] [max]
1661 2 : this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff"); //<fmt>pitchcorrect [name] [target] [scale] [min] [max]
1662 2 : this->modelcommand(sethitzone, "hitzone", "is"); //<fmt>hitzone [id] [mask]
1663 2 : if(MDL::cananimate())
1664 : {
1665 2 : this->modelcommand(setanim, "anim", "ssfiii"); //<fmt>anim [anim] [animfile] [speed] [priority] [startoffset] [endoffset]
1666 2 : this->modelcommand(setanimpart, "animpart", "s"); //<fmt>animpart [maskstr]
1667 2 : this->modelcommand(setadjust, "adjust", "sffffff"); //<fmt>adjust [name] [yaw] [pitch] [tx] [ty] [tz]
1668 : }
1669 :
1670 2 : this->modelcommand(rdvert, "rdvert", "ffff");
1671 2 : this->modelcommand(rdeye, "rdeye", "i");
1672 2 : this->modelcommand(rdtri, "rdtri", "iii");
1673 2 : this->modelcommand(rdjoint, "rdjoint", "iibbb");
1674 2 : this->modelcommand(rdlimitdist, "rdlimitdist", "iiff");
1675 2 : this->modelcommand(rdlimitrot, "rdlimitrot", "iifffff");
1676 2 : this->modelcommand(rdanimjoints, "rdanimjoints", "i");
1677 2 : }
1678 : };
1679 :
1680 : #endif
|