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