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

Generated by: LCOV version 2.0-1