2852 lines · 130.3 KB · C++
RawDownload
////  Rig.cpp//  libraries/animation/src/////  Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15.//  Copyright (c) 2015 High Fidelity, Inc. All rights reserved.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// #include "Rig.h" #include <glm/gtx/vector_angle.hpp>#include <queue>#include <QWriteLocker>#include <QReadLocker> #include <GeometryUtil.h>#include <NumericalConstants.h>#include <DebugDraw.h>#include <PerfStat.h>#include <ScriptEngine.h>#include <ScriptManager.h>#include <ScriptValueUtils.h>#include <ScriptValue.h>#include <shared/NsightHelpers.h> #include "AnimationLogging.h"#include "AnimClip.h"#include "AnimInverseKinematics.h"#include "AnimOverlay.h"#include "AnimSkeleton.h"#include "AnimStateMachine.h"#include "AnimUtil.h"#include "AvatarConstants.h"#include "IKTarget.h"#include "PathUtils.h" static int nextRigId = 1;static std::map<int, Rig*> rigRegistry;static std::mutex rigRegistryMutex; static bool isEqual(const float p, float q) {    const float EPSILON = 0.00001f;    return fabsf(p - q) <= EPSILON;} static bool isEqual(const glm::vec3& u, const glm::vec3& v) {    return isEqual(u.x, v.x) && isEqual(u.y, v.y) && isEqual(u.z, v.z);} static bool isEqual(const glm::quat& p, const glm::quat& q) {    const float EPSILON = 0.00001f;    return 1.0f - fabsf(glm::dot(p, q)) <= EPSILON;} #define ASSERT(cond) assert(cond) // 2 meter tall dudeconst glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f);const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f);const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f); static const QString LEFT_FOOT_POSITION("leftFootPosition");static const QString LEFT_FOOT_ROTATION("leftFootRotation");static const QString LEFT_FOOT_IK_POSITION_VAR("leftFootIKPositionVar");static const QString LEFT_FOOT_IK_ROTATION_VAR("leftFootIKRotationVar");static const QString MAIN_STATE_MACHINE_LEFT_FOOT_POSITION("mainStateMachineLeftFootPosition");static const QString MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION("mainStateMachineLeftFootRotation"); static const QString RIGHT_FOOT_POSITION("rightFootPosition");static const QString RIGHT_FOOT_ROTATION("rightFootRotation");static const QString RIGHT_FOOT_IK_POSITION_VAR("rightFootIKPositionVar");static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar");static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation");static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); static const QString LEFT_HAND_POSITION("leftHandPosition");static const QString LEFT_HAND_ROTATION("leftHandRotation");static const QString LEFT_HAND_IK_POSITION_VAR("leftHandIKPositionVar");static const QString LEFT_HAND_IK_ROTATION_VAR("leftHandIKRotationVar");static const QString MAIN_STATE_MACHINE_LEFT_HAND_POSITION("mainStateMachineLeftHandPosition");static const QString MAIN_STATE_MACHINE_LEFT_HAND_ROTATION("mainStateMachineLeftHandRotation"); static const QString RIGHT_HAND_POSITION("rightHandPosition");static const QString RIGHT_HAND_ROTATION("rightHandRotation");static const QString RIGHT_HAND_IK_POSITION_VAR("rightHandIKPositionVar");static const QString RIGHT_HAND_IK_ROTATION_VAR("rightHandIKRotationVar");static const QString MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION("mainStateMachineRightHandRotation");static const QString MAIN_STATE_MACHINE_RIGHT_HAND_POSITION("mainStateMachineRightHandPosition");  /*@jsdoc * <p>An <code>AnimStateDictionary</code> object may have the following properties. It may also have other properties, set by * scripts.</p> * <p><strong>Warning:</strong> These properties are subject to change. * <table> *   <thead> *     <tr><th>Name</th><th>Type</th><th>Description</th> *   </thead> *   <tbody> *     <tr><td><code>userAnimNone</code></td><td>boolean</td><td><code>true</code> when no user overrideAnimation is *       playing.</td></tr> *     <tr><td><code>userAnimA</code></td><td>boolean</td><td><code>true</code> when a user overrideAnimation is *       playing.</td></tr> *     <tr><td><code>userAnimB</code></td><td>boolean</td><td><code>true</code> when a user overrideAnimation is *       playing.</td></tr> * *     <tr><td><code>sine</code></td><td>number</td><td>Oscillating sine wave.</td></tr> *     <tr><td><code>moveForwardSpeed</code></td><td>number</td><td>Controls the blend between the various forward walking *       &amp; running animations.</td></tr> *     <tr><td><code>moveBackwardSpeed</code></td><td>number</td><td>Controls the blend between the various backward walking *       &amp; running animations.</td></tr> *     <tr><td><code>moveLateralSpeed</code></td><td>number</td><td>Controls the blend between the various sidestep walking *       &amp; running animations.</td></tr> * *     <tr><td><code>isMovingForward</code></td><td>boolean</td><td><code>true</code> if the avatar is moving *       forward.</td></tr> *     <tr><td><code>isMovingBackward</code></td><td>boolean</td><td><code>true</code> if the avatar is moving *       backward.</td></tr> *     <tr><td><code>isMovingRight</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the *       right.</td></tr> *     <tr><td><code>isMovingLeft</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the *       left.</td></tr> *     <tr><td><code>isMovingRightHmd</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the right *       while the user is in HMD mode.</td></tr> *     <tr><td><code>isMovingLeftHmd</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the left while *       the user is in HMD mode.</td></tr> *     <tr><td><code>isNotMoving</code></td><td>boolean</td><td><code>true</code> if the avatar is stationary.</td></tr> * *     <tr><td><code>isTurningRight</code></td><td>boolean</td><td><code>true</code> if the avatar is turning *       clockwise.</td></tr> *     <tr><td><code>isTurningLeft</code></td><td>boolean</td><td><code>true</code> if the avatar is turning *       counter-clockwise.</td></tr> *     <tr><td><code>isNotTurning</code></td><td>boolean</td><td><code>true</code> if the avatar is not turning.</td></tr> *     <tr><td><code>isFlying</code></td><td>boolean</td><td><code>true</code> if the avatar is flying.</td></tr> *     <tr><td><code>isNotFlying</code></td><td>boolean</td><td><code>true</code> if the avatar is not flying.</td></tr> *     <tr><td><code>isTakeoffStand</code></td><td>boolean</td><td><code>true</code> if the avatar is about to execute a *       standing jump.</td></tr> *     <tr><td><code>isTakeoffRun</code></td><td>boolean</td><td><code>true</code> if the avatar is about to execute a running *       jump.</td></tr> *     <tr><td><code>isNotTakeoff</code></td><td>boolean</td><td><code>true</code> if the avatar is not jumping.</td></tr> *     <tr><td><code>isInAirStand</code></td><td>boolean</td><td><code>true</code> if the avatar is in the air after a standing *       jump.</td></tr> *     <tr><td><code>isInAirRun</code></td><td>boolean</td><td><code>true</code> if the avatar is in the air after a running *       jump.</td></tr> *     <tr><td><code>isNotInAir</code></td><td>boolean</td><td><code>true</code> if the avatar on the ground.</td></tr> * *     <tr><td><code>inAirAlpha</code></td><td>number</td><td>Used to interpolate between the up, apex, and down in-air *       animations.</td></tr> *     <tr><td><code>ikOverlayAlpha</code></td><td>number</td><td>The blend between upper body and spline IK versus the *       underlying animation</td></tr> * *     <tr><td><code>headPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>Head</code> joint in *       rig coordinates.</td></tr> *     <tr><td><code>headRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>Head</code> joint in *       rig coordinates.</td></tr> *     <tr><td><code>headType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the *       head.</td></tr> *     <tr><td><code>headWeight</code></td><td>number</td><td>How strongly the head chain blends with the other IK *       chains.</td></tr> * *     <tr><td><code>leftHandPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>LeftHand</code> *       joint in rig coordinates.</td></tr> *     <tr><td><code>leftHandRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>LeftHand</code> *       joint in rig coordinates.</td></tr> *     <tr><td><code>leftHandType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the *       left arm.</td></tr> *     <tr><td><code>leftHandPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the elbow angle is *       controlled by the <code>rightHandPoleVector</code> property value. Otherwise the elbow direction comes from the *       underlying animation.</td></tr> *     <tr><td><code>leftHandPoleReferenceVector</code></td><td>{@link Vec3}</td><td>The direction of the elbow in the local *       coordinate system of the elbow.</td></tr> *     <tr><td><code>leftHandPoleVector</code></td><td>{@link Vec3}</td><td>The direction the elbow should point in rig *       coordinates.</td></tr> * *     <tr><td><code>rightHandPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>RightHand</code> *       joint in rig coordinates.</td></tr> *     <tr><td><code>rightHandRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the *       <code>RightHand</code> joint in rig coordinates.</td></tr> *     <tr><td><code>rightHandType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for *       the right arm.</td></tr> *     <tr><td><code>rightHandPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the elbow angle is *       controlled by the <code>rightHandPoleVector</code> property value. Otherwise the elbow direction comes from the *       underlying animation.</td></tr> *     <tr><td><code>rightHandPoleReferenceVector</code></td><td>{@link Vec3}</td><td>The direction of the elbow in the local *       coordinate system of the elbow.</td></tr> *     <tr><td><code>rightHandPoleVector</code></td><td>{@link Vec3}</td><td>The direction the elbow should point in rig *       coordinates.</td></tr> * *     <tr><td><code>leftFootIKEnabled</code></td><td>boolean</td><td><code>true</code> if IK is enabled for the left *       foot.</td></tr> *     <tr><td><code>rightFootIKEnabled</code></td><td>boolean</td><td><code>true</code> if IK is enabled for the right *       foot.</td></tr> * *     <tr><td><code>leftFootIKPositionVar</code></td><td>string</td><td>The name of the source for the desired position *       of the <code>LeftFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr> *     <tr><td><code>leftFootIKRotationVar</code></td><td>string</td><td>The name of the source for the desired rotation *       of the <code>LeftFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr> *     <tr><td><code>leftFootPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the knee angle is *       controlled by the <code>leftFootPoleVector</code> property value. Otherwise the knee direction comes from the *       underlying animation.</td></tr> *     <tr><td><code>leftFootPoleVector</code></td><td>{@link Vec3}</td><td>The direction the knee should face in rig *       coordinates.</td></tr> *     <tr><td><code>rightFootIKPositionVar</code></td><td>string</td><td>The name of the source for the desired position *       of the <code>RightFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr> *     <tr><td><code>rightFootIKRotationVar</code></td><td>string</td><td>The name of the source for the desired rotation *       of the <code>RightFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr> *     <tr><td><code>rightFootPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the knee angle is *       controlled by the <code>rightFootPoleVector</code> property value. Otherwise the knee direction comes from the *       underlying animation.</td></tr> *     <tr><td><code>rightFootPoleVector</code></td><td>{@link Vec3}</td><td>The direction the knee should face in rig *       coordinates.</td></tr> * *     <tr><td><code>isTalking</code></td><td>boolean</td><td><code>true</code> if the avatar is talking.</td></tr> *     <tr><td><code>notIsTalking</code></td><td>boolean</td><td><code>true</code> if the avatar is not talking.</td></tr> * *     <tr><td><code>solutionSource</code></td><td>{@link MyAvatar.AnimIKSolutionSource|AnimIKSolutionSource}</td> *       <td>Determines the initial conditions of the IK solver.</td></tr> *     <tr><td><code>defaultPoseOverlayAlpha</code></td><td>number</td><td>Controls the blend between the main animation state *       machine and the default pose. Mostly used during full body tracking so that walking &amp; jumping animations do not *       affect the IK of the figure.</td></tr> *     <tr><td><code>defaultPoseOverlayBoneSet</code></td><td>{@link MyAvatar.AnimOverlayBoneSet|AnimOverlayBoneSet}</td> *       <td>Specifies which bones will be replace by the source overlay.</td></tr> *     <tr><td><code>hipsType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the *       hips.</td></tr> *     <tr><td><code>hipsPosition</code></td><td>{@link Vec3}</td><td>The desired position of <code>Hips</code> joint in rig *       coordinates.</td></tr> *     <tr><td><code>hipsRotation</code></td><td>{@link Quat}</td><td>the desired orientation of the <code>Hips</code> joint in *       rig coordinates.</td></tr> *     <tr><td><code>spine2Type</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the *       <code>Spine2</code> joint.</td></tr> *     <tr><td><code>spine2Position</code></td><td>{@link Vec3}</td><td>The desired position of the <code>Spine2</code> joint *       in rig coordinates.</td></tr> *     <tr><td><code>spine2Rotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>Spine2</code> *       joint in rig coordinates.</td></tr> * *     <tr><td><code>leftFootIKAlpha</code></td><td>number</td><td>Blends between full IK for the leg and the underlying *       animation.</td></tr> *     <tr><td><code>rightFootIKAlpha</code></td><td>number</td><td>Blends between full IK for the leg and the underlying *       animation.</td></tr> *     <tr><td><code>hipsWeight</code></td><td>number</td><td>How strongly the hips target blends with the IK solution for *       other IK chains.</td></tr> *     <tr><td><code>leftHandWeight</code></td><td>number</td><td>How strongly the left hand blends with IK solution of other *        IK chains.</td></tr> *     <tr><td><code>rightHandWeight</code></td><td>number</td><td>How strongly the right hand blends with IK solution of other *       IK chains.</td></tr> *     <tr><td><code>spine2Weight</code></td><td>number</td><td>How strongly the spine2 chain blends with the rest of the IK *       solution.</td></tr> * *     <tr><td><code>leftHandOverlayAlpha</code></td><td>number</td><td>Used to blend in the animated hand gesture poses, such *       as point and thumbs up.</td></tr> *     <tr><td><code>leftHandGraspAlpha</code></td><td>number</td><td>Used to blend between an open hand and a closed hand. *       Usually changed as you squeeze the trigger of the hand controller.</td></tr> *     <tr><td><code>rightHandOverlayAlpha</code></td><td>number</td><td>Used to blend in the animated hand gesture poses, *       such as point and thumbs up.</td></tr> *     <tr><td><code>rightHandGraspAlpha</code></td><td>number</td><td>Used to blend between an open hand and a closed hand. *       Usually changed as you squeeze the trigger of the hand controller.</td></tr> *     <tr><td><code>isLeftIndexPoint</code></td><td>boolean</td><td><code>true</code> if the left hand should be *       pointing.</td></tr> *     <tr><td><code>isLeftThumbRaise</code></td><td>boolean</td><td><code>true</code> if the left hand should be *       thumbs-up.</td></tr> *     <tr><td><code>isLeftIndexPointAndThumbRaise</code></td><td>boolean</td><td><code>true</code> if the left hand should be *       pointing and thumbs-up.</td></tr> *     <tr><td><code>isLeftHandGrasp</code></td><td>boolean</td><td><code>true</code> if the left hand should be at rest, *       grasping the controller.</td></tr> *     <tr><td><code>isRightIndexPoint</code></td><td>boolean</td><td><code>true</code> if the right hand should be *       pointing.</td></tr> *     <tr><td><code>isRightThumbRaise</code></td><td>boolean</td><td><code>true</code> if the right hand should be *       thumbs-up.</td></tr> *     <tr><td><code>isRightIndexPointAndThumbRaise</code></td><td>boolean</td><td><code>true</code> if the right hand should *       be pointing and thumbs-up.</td></tr> *     <tr><td><code>isRightHandGrasp</code></td><td>boolean</td><td><code>true</code> if the right hand should be at rest, *       grasping the controller.</td></tr> * *   </tbody> * </table> * <p>Note: Rig coordinates are <code>+z</code> forward and <code>+y</code> up.</p> * @typedef {object} MyAvatar.AnimStateDictionary */// Note: The following animVars are intentionally not documented:// - leftFootPosition// - leftFootRotation// - rightFooKPosition// - rightFooKRotation// Note: The following items aren't set in the code below but are still intentionally documented:// - leftFootIKAlpha// - rightFootIKAlpha// - hipsWeight// - leftHandWeight// - rightHandWeight// - spine2Weight// - rightHandOverlayAlpha// - rightHandGraspAlpha// - leftHandOverlayAlpha// - leftHandGraspAlpha// - isRightIndexPoint// - isRightThumbRaise// - isRightIndexPointAndThumbRaise// - isRightHandGrasp// - isLeftIndexPoint// - isLeftThumbRaise// - isLeftIndexPointAndThumbRaise// - isLeftHandGraspRig::Rig() : _internalFlow(this), _networkFlow(this) {    // Ensure thread-safe access to the rigRegistry.    std::lock_guard<std::mutex> guard(rigRegistryMutex);     // Insert this newly allocated rig into the rig registry    _rigId = nextRigId;    rigRegistry[_rigId] = this;    nextRigId++;} Rig::~Rig() {    // Ensure thread-safe access to the rigRegstry, but also prevent the rig from being deleted    // while Rig::animationStateHandlerResult is being invoked on a script thread.    std::lock_guard<std::mutex> guard(rigRegistryMutex);    auto iter = rigRegistry.find(_rigId);    if (iter != rigRegistry.end()) {        rigRegistry.erase(iter);    }} void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {     UserAnimState::ClipNodeEnum clipNodeEnum;    if (_userAnimState.clipNodeEnum == UserAnimState::None || _userAnimState.clipNodeEnum == UserAnimState::B) {        clipNodeEnum = UserAnimState::A;    } else {        clipNodeEnum = UserAnimState::B;    }     if (_animNode) {        // find an unused AnimClip clipNode        std::shared_ptr<AnimClip> clip;        if (clipNodeEnum == UserAnimState::A) {            clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimA"));        } else {            clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("userAnimB"));        }         if (clip) {            // set parameters            clip->setLoopFlag(loop);            clip->setStartFrame(firstFrame);            clip->setEndFrame(lastFrame);            const float REFERENCE_FRAMES_PER_SECOND = 30.0f;            float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;            clip->setTimeScale(timeScale);            clip->loadURL(url);        }    }     // store current user anim state.    _userAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };     // notify the userAnimStateMachine the desired state.    _animVars.set("userAnimNone", false);    _animVars.set("userAnimA", clipNodeEnum == UserAnimState::A);    _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B);} void Rig::restoreAnimation() {    if (_userAnimState.clipNodeEnum != UserAnimState::None) {        _userAnimState.clipNodeEnum = UserAnimState::None;         // notify the userAnimStateMachine the desired state.        _animVars.set("userAnimNone", true);        _animVars.set("userAnimA", false);        _animVars.set("userAnimB", false);    }} void Rig::overrideHandAnimation(bool isLeft, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {    HandAnimState::ClipNodeEnum clipNodeEnum;    if (isLeft) {        if (_leftHandAnimState.clipNodeEnum == HandAnimState::None || _leftHandAnimState.clipNodeEnum == HandAnimState::B) {            clipNodeEnum = HandAnimState::A;        } else {            clipNodeEnum = HandAnimState::B;        }    } else {        if (_rightHandAnimState.clipNodeEnum == HandAnimState::None || _rightHandAnimState.clipNodeEnum == HandAnimState::B) {            clipNodeEnum = HandAnimState::A;        } else {            clipNodeEnum = HandAnimState::B;        }    }     if (_animNode) {        std::shared_ptr<AnimClip> clip;        if (isLeft) {            if (clipNodeEnum == HandAnimState::A) {                clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("leftHandAnimA"));            } else {                clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("leftHandAnimB"));            }        } else {            if (clipNodeEnum == HandAnimState::A) {                clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("rightHandAnimA"));            } else {                clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("rightHandAnimB"));            }        }         if (clip) {            // set parameters            clip->setLoopFlag(loop);            clip->setStartFrame(firstFrame);            clip->setEndFrame(lastFrame);            const float REFERENCE_FRAMES_PER_SECOND = 30.0f;            float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;            clip->setTimeScale(timeScale);            clip->loadURL(url);        }    }     // notify the handAnimStateMachine the desired state.    if (isLeft) {        // store current hand anim state.        _leftHandAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };        _animVars.set("leftHandAnimNone", false);        _animVars.set("leftHandAnimA", clipNodeEnum == HandAnimState::A);        _animVars.set("leftHandAnimB", clipNodeEnum == HandAnimState::B);    } else {        // store current hand anim state.        _rightHandAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };        _animVars.set("rightHandAnimNone", false);        _animVars.set("rightHandAnimA", clipNodeEnum == HandAnimState::A);        _animVars.set("rightHandAnimB", clipNodeEnum == HandAnimState::B);    }} void Rig::restoreHandAnimation(bool isLeft) {    if (isLeft) {        if (_leftHandAnimState.clipNodeEnum != HandAnimState::None) {            _leftHandAnimState.clipNodeEnum = HandAnimState::None;             // notify the handAnimStateMachine the desired state.            _animVars.set("leftHandAnimNone", true);            _animVars.set("leftHandAnimA", false);            _animVars.set("leftHandAnimB", false);        }    } else {        if (_rightHandAnimState.clipNodeEnum != HandAnimState::None) {            _rightHandAnimState.clipNodeEnum = HandAnimState::None;             // notify the handAnimStateMachine the desired state.            _animVars.set("rightHandAnimNone", true);            _animVars.set("rightHandAnimA", false);            _animVars.set("rightHandAnimB", false);        }    }} void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {     NetworkAnimState::ClipNodeEnum clipNodeEnum = NetworkAnimState::None;    if (_networkAnimState.clipNodeEnum == NetworkAnimState::None || _networkAnimState.clipNodeEnum == NetworkAnimState::B) {        clipNodeEnum = NetworkAnimState::A;    } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::A) {        clipNodeEnum = NetworkAnimState::B;    }     if (_networkNode) {        // find an unused AnimClip clipNode        std::shared_ptr<AnimClip> clip;        if (clipNodeEnum == NetworkAnimState::A) {            clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("userNetworkAnimA"));        } else {            clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("userNetworkAnimB"));        }        if (clip) {            // set parameters            clip->setLoopFlag(loop);            clip->setStartFrame(firstFrame);            clip->setEndFrame(lastFrame);            const float REFERENCE_FRAMES_PER_SECOND = 30.0f;            float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;            clip->setTimeScale(timeScale);            clip->loadURL(url);        }    }     // store current user anim state.    _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };     // notify the userAnimStateMachine the desired state.    _networkVars.set("transitAnimStateMachine", false);    _networkVars.set("userNetworkAnimA", clipNodeEnum == NetworkAnimState::A);    _networkVars.set("userNetworkAnimB", clipNodeEnum == NetworkAnimState::B);    if (!_computeNetworkAnimation) {        _networkAnimState.blendTime = 0.0f;        _computeNetworkAnimation = true;    }}

Lines 1–500 of 2852.