LCOV - code coverage report
Current view: top level - engine/model - skelmodel.h (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 51.6 % 314 162
Test Date: 2026-09-21 06:08:11 Functions: 86.7 % 45 39

            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
        

Generated by: LCOV version 2.0-1