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