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 3 : skelmeshgroup() : skel(nullptr), edata(nullptr), ebuf(0), vweights(0), vlen(0), vertsize(0), vblends(0), vdata(nullptr)
890 : {
891 3 : numblends.fill(0);
892 3 : }
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 :
1038 : //override methods
1039 :
1040 : /**
1041 : * @brief Returns the link type of an animmodel relative to a part
1042 : *
1043 : * If `this` model's zeroth part's mesh's skel is the same as the passed part's
1044 : * mesh's skel, returns Link_Reuse
1045 : * If the passed model parameter is not linkable, or does not meet the criteria above,
1046 : * returns Link_Tag.
1047 : *
1048 : * @param m must point to a valid animmodel object
1049 : * @param p must point to a valid part object which points to a valid skeleton.
1050 : */
1051 : int linktype(const animmodel *m, const part *p) const final;
1052 : bool skeletal() const final;
1053 :
1054 : protected:
1055 : //virtual methods
1056 : virtual skelmeshgroup *newmeshes() = 0;
1057 :
1058 : };
1059 :
1060 : class skeladjustment final
1061 : {
1062 : public:
1063 0 : skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
1064 : void adjust(dualquat &dq) const;
1065 :
1066 : private:
1067 : float yaw, pitch, roll;
1068 : vec translate;
1069 : };
1070 :
1071 : template<class MDL>
1072 : struct skelloader : modelloader<MDL, skelmodel>
1073 : {
1074 : static std::vector<skeladjustment> adjustments;
1075 : static std::vector<uchar> hitzones;
1076 :
1077 18 : skelloader(std::string name) : modelloader<MDL, skelmodel>(name) {}
1078 : };
1079 :
1080 : template<class MDL>
1081 : std::vector<skeladjustment> skelloader<MDL>::adjustments;
1082 :
1083 : template<class MDL>
1084 : std::vector<uchar> skelloader<MDL>::hitzones;
1085 :
1086 : /**
1087 : * @brief Defines skeletal commands for a chosen type of skeletal model format.
1088 : *
1089 : * this template structure defines a series of commands for a model object (or
1090 : * child of the model object) which can be used to set its dynamically modifiable
1091 : * properties
1092 : *
1093 : */
1094 : template<class MDL>
1095 : struct skelcommands : modelcommands<MDL>
1096 : {
1097 : typedef modelcommands<MDL> commands;
1098 : typedef class MDL::skeleton skeleton;
1099 : typedef class MDL::skelmeshgroup meshgroup;
1100 : typedef class MDL::skelpart part;
1101 : typedef struct MDL::skelanimspec animspec;
1102 : typedef struct MDL::skeleton::pitchtarget pitchtarget;
1103 : typedef struct MDL::skeleton::pitchcorrect pitchcorrect;
1104 :
1105 : //unused second param
1106 13 : static void loadpart(const char *meshfile, const char *, const float *smooth)
1107 : {
1108 13 : if(!MDL::loading)
1109 : {
1110 2 : conoutf("not loading an %s", MDL::formatname());
1111 2 : return;
1112 : }
1113 11 : std::string filename;
1114 11 : filename.append(MDL::dir).append("/").append(meshfile ? meshfile : "");
1115 11 : part &mdl = MDL::loading->addpart();
1116 11 : mdl.meshes = MDL::loading->sharemeshes(path(filename), *smooth > 0 ? std::cos(std::clamp(*smooth, 0.0f, 180.0f)/RAD) : 2);
1117 11 : if(!mdl.meshes)
1118 : {
1119 0 : conoutf("could not load %s", filename.c_str());
1120 : }
1121 : else
1122 : {
1123 11 : if(mdl.meshes && static_cast<meshgroup *>(mdl.meshes)->skel->numbones > 0)
1124 : {
1125 11 : mdl.disablepitch();
1126 : }
1127 11 : mdl.initanimparts();
1128 11 : mdl.initskins();
1129 : }
1130 11 : }
1131 :
1132 : /**
1133 : * @brief Adds a tag corresponding to a bone
1134 : *
1135 : * @param name the name of the bone in the skeletal model
1136 : * @param name the new name to assign the associated tag
1137 : *
1138 : * @param tx/ty/tz translation parameters
1139 : * @param rx/ry/rz rotation parameters
1140 : */
1141 4 : static void settag(const char *name, const char *tagname,
1142 : const float *tx, const float *ty, const float *tz,
1143 : const float *rx, const float *ry, const float *rz)
1144 : {
1145 4 : if(!MDL::loading || MDL::loading->parts.empty())
1146 : {
1147 2 : conoutf("not loading an %s", MDL::formatname());
1148 4 : return;
1149 : }
1150 2 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1151 2 : std::optional<size_t> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1152 2 : if(i)
1153 : {
1154 2 : float cx = *rx ? std::cos(*rx/(2*RAD)) : 1, sx = *rx ? std::sin(*rx/(2*RAD)) : 0,
1155 2 : cy = *ry ? std::cos(*ry/(2*RAD)) : 1, sy = *ry ? std::sin(*ry/(2*RAD)) : 0,
1156 2 : cz = *rz ? std::cos(*rz/(2*RAD)) : 1, sz = *rz ? std::sin(*rz/(2*RAD)) : 0;
1157 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)),
1158 2 : vec(*tx, *ty, *tz));
1159 2 : static_cast<meshgroup *>(mdl.meshes)->skel->addtag(tagname, *i, m);
1160 2 : return;
1161 : }
1162 0 : conoutf("could not find bone %s for tag %s", name, tagname);
1163 : }
1164 :
1165 : //attempts to set the pitch of a named bone within a MDL object, within the bounds set
1166 : //prints to console failure messages if no model or no bone with name passed
1167 2 : static void setpitch(const char *name, const float *pitchscale,
1168 : const float *pitchoffset, const float *pitchmin, const float *pitchmax)
1169 : {
1170 2 : if(!MDL::loading || MDL::loading->parts.empty())
1171 : {
1172 2 : conoutf("not loading an %s", MDL::formatname());
1173 2 : return;
1174 : }
1175 0 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1176 :
1177 0 : if(name[0])
1178 : {
1179 0 : std::optional<size_t> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1180 0 : if(i)
1181 : {
1182 0 : float newpitchmin = 0.f,
1183 0 : newpitchmax = 0.f;
1184 0 : if(*pitchmin || *pitchmax)
1185 : {
1186 0 : newpitchmin = *pitchmin;
1187 0 : newpitchmax = *pitchmax;
1188 : }
1189 : else
1190 : {
1191 0 : newpitchmin = -360*std::fabs(*pitchscale) + *pitchoffset;
1192 0 : newpitchmax = 360*std::fabs(*pitchscale) + *pitchoffset;
1193 : }
1194 0 : static_cast<meshgroup *>(mdl.meshes)->skel->setbonepitch(*i, *pitchscale, *pitchoffset, newpitchmin, newpitchmax);
1195 0 : return;
1196 : }
1197 0 : conoutf("could not find bone %s to pitch", name);
1198 0 : return;
1199 : }
1200 :
1201 0 : mdl.pitchscale = *pitchscale;
1202 0 : mdl.pitchoffset = *pitchoffset;
1203 0 : if(*pitchmin || *pitchmax)
1204 : {
1205 0 : mdl.pitchmin = *pitchmin;
1206 0 : mdl.pitchmax = *pitchmax;
1207 : }
1208 : else
1209 : {
1210 0 : mdl.pitchmin = -360*std::fabs(mdl.pitchscale) + mdl.pitchoffset;
1211 0 : mdl.pitchmax = 360*std::fabs(mdl.pitchscale) + mdl.pitchoffset;
1212 : }
1213 : }
1214 :
1215 3 : static void setpitchtarget(const char *name, const char *animfile, const int *frameoffset,
1216 : const float *pitchmin, const float *pitchmax)
1217 : {
1218 3 : if(!MDL::loading || MDL::loading->parts.empty())
1219 : {
1220 2 : conoutf("not loading an %s", MDL::formatname());
1221 2 : return;
1222 : }
1223 1 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1224 1 : if(!mdl.meshes)
1225 : {
1226 0 : return;
1227 : }
1228 1 : std::string filename = std::format("{}/{}", MDL::dir, animfile);
1229 1 : const animspec *sa = static_cast<meshgroup *>(mdl.meshes)->loadanim(path(filename));
1230 1 : if(!sa)
1231 : {
1232 0 : conoutf("could not load %s anim file %s", MDL::formatname(), filename.c_str());
1233 0 : return;
1234 : }
1235 1 : skeleton *skel = static_cast<meshgroup *>(mdl.meshes)->skel;
1236 1 : std::optional<size_t> bone = skel ? skel->findbone(name) : std::nullopt;
1237 1 : if(!bone)
1238 : {
1239 0 : conoutf("could not find bone %s to pitch target", name);
1240 0 : return;
1241 : }
1242 1 : for(const pitchtarget &i : skel->pitchtargets)
1243 : {
1244 0 : if(i.bone == *bone)
1245 : {
1246 0 : return;
1247 : }
1248 : }
1249 1 : pitchtarget t;
1250 1 : t.bone = *bone;
1251 1 : t.frame = sa->frame + std::clamp(*frameoffset, 0, sa->range-1);
1252 1 : t.pitchmin = *pitchmin;
1253 1 : t.pitchmax = *pitchmax;
1254 1 : skel->pitchtargets.push_back(t);
1255 1 : }
1256 :
1257 : /**
1258 : * @brief Adds a pitch correction to this model's skeleton
1259 : *
1260 : * @param name the name of the bone to pitch correct
1261 : * @param targetname the name of the bone to target
1262 : * @param scale the scale to apply to the pitchcorrect
1263 : * @param pitchmin the minimum pitch to apply
1264 : * @param pitchmax the maximum pitch to apply
1265 : */
1266 2 : static void setpitchcorrect(const char *name, const char *targetname,
1267 : const float *scale, const float *pitchmin, const float *pitchmax)
1268 : {
1269 2 : if(!MDL::loading || MDL::loading->parts.empty())
1270 : {
1271 2 : conoutf("not loading an %s", MDL::formatname());
1272 2 : return;
1273 : }
1274 0 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1275 0 : if(!mdl.meshes)
1276 : {
1277 0 : return;
1278 : }
1279 0 : skeleton *skel = static_cast<meshgroup *>(mdl.meshes)->skel;
1280 0 : std::optional<int> bone = skel ? skel->findbone(name) : std::nullopt;
1281 0 : if(!bone)
1282 : {
1283 0 : conoutf("could not find bone %s to pitch correct", name);
1284 0 : return;
1285 : }
1286 0 : if(skel->findpitchcorrect(*bone) >= 0)
1287 : {
1288 0 : return;
1289 : }
1290 0 : std::optional<size_t> targetbone = skel->findbone(targetname),
1291 0 : target = std::nullopt;
1292 0 : if(targetbone)
1293 : {
1294 0 : for(size_t i = 0; i < skel->pitchtargets.size(); i++)
1295 : {
1296 0 : if(skel->pitchtargets[i].bone == *targetbone)
1297 : {
1298 0 : target = i;
1299 0 : break;
1300 : }
1301 : }
1302 : }
1303 0 : if(!target)
1304 : {
1305 0 : conoutf("could not find pitch target %s to pitch correct %s", targetname, name);
1306 0 : return;
1307 : }
1308 0 : pitchcorrect c(*bone, *target, *pitchmin, *pitchmax, *scale);
1309 0 : size_t pos = skel->pitchcorrects.size();
1310 0 : for(size_t i = 0; i < skel->pitchcorrects.size(); i++)
1311 : {
1312 0 : if(bone <= skel->pitchcorrects[i].bone)
1313 : {
1314 0 : pos = i;
1315 0 : break;
1316 : }
1317 : }
1318 0 : skel->pitchcorrects.insert(skel->pitchcorrects.begin() + pos, c);
1319 : }
1320 :
1321 : /**
1322 : * @param Assigns an animation to the currently loaded model.
1323 : *
1324 : * Attempts to give a model object an animation by the name of anim parameter
1325 : * loaded from animfile with speed/priority/offsets to determine how fast
1326 : * and what frames play.
1327 : *
1328 : * The name of the anim being loaded (anim param) must be in the global animnames vector.
1329 : *
1330 : * The MDL::loading static field must be set by calling startload() for the
1331 : * relevant model object.
1332 : *
1333 : * The animation will be applied to the most recent loaded part (with loadpart()).
1334 : */
1335 4 : static void setanim(const char *anim, const char *animfile, const float *speed,
1336 : const int *priority, const int *startoffset, const int *endoffset)
1337 : {
1338 4 : if(!MDL::loading || MDL::loading->parts.empty())
1339 : {
1340 2 : conoutf("not loading an %s", MDL::formatname());
1341 2 : return;
1342 : }
1343 2 : std::vector<size_t> anims = findanims(anim);
1344 2 : if(anims.empty())
1345 : {
1346 0 : conoutf("could not find animation %s", anim);
1347 : }
1348 : else
1349 : {
1350 2 : part *p = static_cast<part *>(MDL::loading->parts.back());
1351 2 : if(!p->meshes)
1352 : {
1353 0 : return;
1354 : }
1355 2 : std::string filename = std::format("{}/{}", MDL::dir, animfile);
1356 2 : const animspec *sa = static_cast<meshgroup *>(p->meshes)->loadanim(path(filename));
1357 2 : if(!sa)
1358 : {
1359 0 : conoutf("could not load %s anim file %s", MDL::formatname(), filename.c_str());
1360 : }
1361 : else
1362 : {
1363 4 : for(size_t i = 0; i < anims.size(); i++)
1364 : {
1365 2 : int start = sa->frame,
1366 2 : end = sa->range;
1367 2 : if(*startoffset > 0)
1368 : {
1369 0 : start += std::min(*startoffset, end-1);
1370 : }
1371 2 : else if(*startoffset < 0)
1372 : {
1373 0 : start += std::max(end + *startoffset, 0);
1374 : }
1375 2 : end -= start - sa->frame;
1376 2 : if(*endoffset > 0)
1377 : {
1378 0 : end = std::min(end, *endoffset);
1379 : }
1380 2 : else if(*endoffset < 0)
1381 : {
1382 0 : end = std::max(end + *endoffset, 1);
1383 : }
1384 2 : MDL::loading->parts.back()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority);
1385 : }
1386 : }
1387 2 : }
1388 2 : }
1389 :
1390 : /**
1391 : * @brief Assigns a subtree of bones to a bone mask.
1392 : *
1393 : * This bone mask is used to separate a part into two (and no more than two)
1394 : * distinct groups of subtrees which can be animated independently.
1395 : *
1396 : * These bones which make up subtree(s) under the specified bones are saved
1397 : * in the partmask vector of the relevant skelpart.
1398 : *
1399 : */
1400 3 : static void setanimpart(const char *maskstr)
1401 : {
1402 3 : if(!MDL::loading || MDL::loading->parts.empty())
1403 : {
1404 2 : conoutf("not loading an %s", MDL::formatname());
1405 2 : return;
1406 : }
1407 1 : part *p = static_cast<part *>(MDL::loading->parts.back());
1408 :
1409 1 : std::vector<std::string> bonestrs;
1410 1 : explodelist(maskstr, bonestrs);
1411 1 : std::vector<uint> bonemask;
1412 2 : for(size_t i = 0; i < bonestrs.size(); i++)
1413 : {
1414 1 : const std::string &bonestr = bonestrs[i];
1415 1 : std::optional<int> bone = p->meshes ? static_cast<meshgroup *>(p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr.substr(1) : bonestr) : std::nullopt;
1416 1 : if(!bone)
1417 : {
1418 0 : conoutf("could not find bone %s for anim part mask [%s]", bonestr.c_str(), maskstr);
1419 0 : return;
1420 : }
1421 1 : bonemask.push_back(*bone | (bonestr[0]=='!' ? Bonemask_Not : 0));
1422 : }
1423 1 : std::sort(bonemask.begin(), bonemask.end());
1424 1 : if(bonemask.size())
1425 : {
1426 1 : bonemask.push_back(Bonemask_End);
1427 : }
1428 1 : if(!p->addanimpart(bonemask))
1429 : {
1430 0 : conoutf("too many animation parts");
1431 : }
1432 1 : }
1433 :
1434 2 : static void setadjust(const char *name, const float *yaw, const float *pitch, const float *roll,
1435 : const float *tx, const float *ty, const float *tz)
1436 : {
1437 2 : if(!MDL::loading || MDL::loading->parts.empty())
1438 : {
1439 2 : conoutf("not loading an %s", MDL::formatname());
1440 2 : return;
1441 : }
1442 0 : part &mdl = *static_cast<part *>(MDL::loading->parts.back());
1443 0 : if(!name[0])
1444 : {
1445 0 : return;
1446 : }
1447 0 : std::optional<int> i = mdl.meshes ? static_cast<meshgroup *>(mdl.meshes)->skel->findbone(name) : std::nullopt;
1448 0 : if(!i)
1449 : {
1450 0 : conoutf("could not find bone %s to adjust", name);
1451 0 : return;
1452 : }
1453 0 : while(!(static_cast<int>(MDL::adjustments.size()) > *i))
1454 : {
1455 0 : MDL::adjustments.push_back(skeladjustment(0, 0, 0, vec(0, 0, 0)));
1456 : }
1457 0 : MDL::adjustments[*i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
1458 : }
1459 :
1460 2 : static void sethitzone(const int *id, const char *maskstr)
1461 : {
1462 2 : if(!MDL::loading || MDL::loading->parts.empty())
1463 : {
1464 2 : conoutf("not loading an %s", MDL::formatname());
1465 2 : return;
1466 : }
1467 0 : if(*id >= 0x80)
1468 : {
1469 0 : conoutf("invalid hit zone id %d", *id);
1470 0 : return;
1471 : }
1472 0 : part *p = static_cast<part *>(MDL::loading->parts.back());
1473 0 : meshgroup *m = static_cast<meshgroup *>(p->meshes);
1474 0 : if(!m)
1475 : {
1476 0 : return;
1477 : }
1478 0 : std::vector<std::string> bonestrs;
1479 0 : explodelist(maskstr, bonestrs);
1480 0 : std::vector<uint> bonemask;
1481 0 : for(size_t i = 0; i < bonestrs.size(); i++)
1482 : {
1483 0 : const std::string &bonestr = bonestrs[i];
1484 0 : std::optional<int> bone = p->meshes ? static_cast<meshgroup *>(p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr.substr(1) : bonestr) : std::nullopt;
1485 0 : if(!bone)
1486 : {
1487 0 : conoutf("could not find bone %s for hit zone mask [%s]", bonestr.c_str(), maskstr);
1488 0 : return;
1489 : }
1490 0 : bonemask.push_back(*bone | (bonestr[0]=='!' ? Bonemask_Not : 0));
1491 : }
1492 0 : if(bonemask.empty())
1493 : {
1494 0 : return;
1495 : }
1496 0 : std::sort(bonemask.begin(), bonemask.end());
1497 0 : bonemask.push_back(Bonemask_End);
1498 :
1499 0 : while(MDL::hitzones.size() < m->skel->numbones)
1500 : {
1501 0 : MDL::hitzones.emplace_back(0xFF);
1502 : }
1503 0 : m->skel->applybonemask(bonemask, MDL::hitzones, *id < 0 ? 0xFF : *id);
1504 0 : }
1505 :
1506 : /**
1507 : * @brief Checks for an available ragdoll to modify.
1508 : *
1509 : * If a skeletal model is being loaded, and meets the criteria for a ragdoll,
1510 : * returns the pointer to that ragdollskel (new one made if necessary), returns nullptr otherwise
1511 : *
1512 : * @return pointer to the loading skelmodel's ragdoll, or nullptr if no such object available
1513 : */
1514 14 : static ragdollskel *checkragdoll()
1515 : {
1516 14 : if(!MDL::loading)
1517 : {
1518 14 : conoutf(Console_Error, "not loading a model");
1519 14 : return nullptr;
1520 : }
1521 0 : if(!MDL::loading->skeletal())
1522 : {
1523 0 : conoutf(Console_Error, "not loading a skeletal model");
1524 0 : return nullptr;
1525 : }
1526 0 : skelmodel *m = static_cast<skelmodel *>(MDL::loading);
1527 0 : if(m->parts.empty())
1528 : {
1529 0 : return nullptr;
1530 : }
1531 0 : skelmodel::skelmeshgroup *meshes = static_cast<skelmodel::skelmeshgroup *>(m->parts.back()->meshes);
1532 0 : if(!meshes)
1533 : {
1534 0 : return nullptr;
1535 : }
1536 0 : skelmodel::skeleton *skel = meshes->skel;
1537 0 : ragdollskel *ragdoll = skel->trycreateragdoll();
1538 0 : if(ragdoll->loaded)
1539 : {
1540 0 : return nullptr;
1541 : }
1542 0 : return ragdoll;
1543 : }
1544 :
1545 : /**
1546 : * @brief Adds a vertex to the working ragdoll vert list
1547 : *
1548 : * @param x the x position of the new vert
1549 : * @param y the y position of the new vert
1550 : * @param z the z position of the new vert
1551 : * @param radius the effect radius of the ragdoll vert
1552 : */
1553 2 : static void rdvert(const float *x, const float *y, const float *z, const float *radius)
1554 : {
1555 2 : ragdollskel *ragdoll = checkragdoll();
1556 2 : if(!ragdoll)
1557 : {
1558 2 : return;
1559 : }
1560 0 : ragdoll->verts.push_back({vec(*x, *y, *z), *radius > 0 ? *radius : 1, 0.f});
1561 : }
1562 :
1563 : /**
1564 : * @brief sets the ragdoll eye level
1565 : *
1566 : * Sets the ragdoll's eye point to the level passed
1567 : * implicitly modifies the ragdoll selected by CHECK_RAGDOLL
1568 : *
1569 : * @param v the level to set the eye at
1570 : */
1571 2 : static void rdeye(const int *v)
1572 : {
1573 2 : ragdollskel *ragdoll = checkragdoll();
1574 2 : if(!ragdoll)
1575 : {
1576 2 : return;
1577 : }
1578 0 : ragdoll->eye = *v;
1579 : }
1580 :
1581 :
1582 : /**
1583 : * @brief adds a ragdoll tri
1584 : *
1585 : * Adds a triangle to the current ragdoll with the specified indices.
1586 : * No effect if there is no current ragdoll.
1587 : *
1588 : * @param v1 first vertex index
1589 : * @param v2 second vertex index
1590 : * @param v3 third vertex index.
1591 : */
1592 2 : static void rdtri(const int *v1, const int *v2, const int *v3)
1593 : {
1594 2 : ragdollskel *ragdoll = checkragdoll();
1595 2 : if(!ragdoll)
1596 : {
1597 2 : return;
1598 : }
1599 0 : ragdoll->tris.push_back({*v1, *v2, *v3});
1600 : }
1601 :
1602 2 : static void rdjoint(const int *n, const int *t, const int *v1, const int *v2, const int *v3)
1603 : {
1604 2 : ragdollskel *ragdoll = checkragdoll();
1605 2 : if(!ragdoll)
1606 : {
1607 2 : return;
1608 : }
1609 0 : const skelmodel *m = static_cast<skelmodel *>(MDL::loading);
1610 0 : const skelmodel::skelmeshgroup *meshes = static_cast<const skelmodel::skelmeshgroup *>(m->parts.back()->meshes);
1611 0 : const skelmodel::skeleton *skel = meshes->skel;
1612 0 : if(*n < 0 || *n >= static_cast<int>(skel->numbones))
1613 : {
1614 0 : return;
1615 : }
1616 0 : ragdoll->joints.push_back({*n, *t, {*v1, *v2, *v3}, 0.f, matrix4x3()});
1617 : }
1618 :
1619 2 : static void rdlimitdist(const int *v1, const int *v2, const float *mindist, const float *maxdist)
1620 : {
1621 2 : ragdollskel *ragdoll = checkragdoll();
1622 2 : if(!ragdoll)
1623 : {
1624 2 : return;
1625 : }
1626 0 : ragdoll->distlimits.push_back({*v1, *v2, *mindist, std::max(*maxdist, *mindist)});
1627 : }
1628 :
1629 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)
1630 : {
1631 2 : ragdollskel *ragdoll = checkragdoll();
1632 2 : if(!ragdoll)
1633 : {
1634 2 : return;
1635 : }
1636 0 : float rmaxangle = *maxangle / RAD;
1637 0 : ragdoll->rotlimits.push_back({*t1,
1638 : *t2,
1639 : rmaxangle,
1640 0 : 1 + 2*std::cos(rmaxangle),
1641 0 : matrix3(quat(*qx, *qy, *qz, *qw))});
1642 : }
1643 :
1644 2 : static void rdanimjoints(const int *on)
1645 : {
1646 2 : ragdollskel *ragdoll = checkragdoll();
1647 2 : if(!ragdoll)
1648 : {
1649 2 : return;
1650 : }
1651 0 : ragdoll->animjoints = *on!=0;
1652 : }
1653 :
1654 2 : skelcommands()
1655 2 : {
1656 2 : if(MDL::multiparted())
1657 : {
1658 2 : this->modelcommand(loadpart, "load", "ssf"); //<fmt>load [mesh] [skel] [smooth]
1659 : }
1660 2 : this->modelcommand(settag, "tag", "ssffffff"); //<fmt>tag [name] [tag] [tx] [ty] [tz] [rx] [ry] [rz]
1661 2 : this->modelcommand(setpitch, "pitchbone", "sffff"); //<fmt>pitchbone [name] [target] [scale] [min] [max]
1662 2 : this->modelcommand(setpitchtarget, "pitchtarget", "ssiff"); //<fmt>pitchtarget [name] [anim] [offset] [min] [max]
1663 2 : this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff"); //<fmt>pitchcorrect [name] [target] [scale] [min] [max]
1664 2 : this->modelcommand(sethitzone, "hitzone", "is"); //<fmt>hitzone [id] [mask]
1665 2 : if(MDL::cananimate())
1666 : {
1667 2 : this->modelcommand(setanim, "anim", "ssfiii"); //<fmt>anim [anim] [animfile] [speed] [priority] [startoffset] [endoffset]
1668 2 : this->modelcommand(setanimpart, "animpart", "s"); //<fmt>animpart [maskstr]
1669 2 : this->modelcommand(setadjust, "adjust", "sffffff"); //<fmt>adjust [name] [yaw] [pitch] [tx] [ty] [tz]
1670 : }
1671 :
1672 2 : this->modelcommand(rdvert, "rdvert", "ffff");
1673 2 : this->modelcommand(rdeye, "rdeye", "i");
1674 2 : this->modelcommand(rdtri, "rdtri", "iii");
1675 2 : this->modelcommand(rdjoint, "rdjoint", "iibbb");
1676 2 : this->modelcommand(rdlimitdist, "rdlimitdist", "iiff");
1677 2 : this->modelcommand(rdlimitrot, "rdlimitrot", "iifffff");
1678 2 : this->modelcommand(rdanimjoints, "rdanimjoints", "i");
1679 2 : }
1680 : };
1681 :
1682 : #endif
|