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