mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 20:36:38 +02:00
fix bug: allow IK to iterate more than once
also changed name of parentTransform to be rootTransform for more correctness
This commit is contained in:
parent
fba7478c52
commit
bdcc68ce36
7 changed files with 47 additions and 33 deletions
|
@ -41,7 +41,7 @@ SkeletonModel::~SkeletonModel() {
|
||||||
|
|
||||||
void SkeletonModel::initJointStates(QVector<JointState> states) {
|
void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||||
|
|
||||||
int rootJointIndex = geometry.rootJointIndex;
|
int rootJointIndex = geometry.rootJointIndex;
|
||||||
int leftHandJointIndex = geometry.leftHandJointIndex;
|
int leftHandJointIndex = geometry.leftHandJointIndex;
|
||||||
|
@ -51,7 +51,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
int rightElbowJointIndex = rightHandJointIndex >= 0 ? geometry.joints.at(rightHandJointIndex).parentIndex : -1;
|
||||||
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
int rightShoulderJointIndex = rightElbowJointIndex >= 0 ? geometry.joints.at(rightElbowJointIndex).parentIndex : -1;
|
||||||
|
|
||||||
_boundingRadius = _rig->initJointStates(states, parentTransform,
|
_boundingRadius = _rig->initJointStates(states, rootTransform,
|
||||||
rootJointIndex,
|
rootJointIndex,
|
||||||
leftHandJointIndex,
|
leftHandJointIndex,
|
||||||
leftElbowJointIndex,
|
leftElbowJointIndex,
|
||||||
|
@ -83,7 +83,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
||||||
// of its root joint and we need that done before we try to build shapes hence we
|
// of its root joint and we need that done before we try to build shapes hence we
|
||||||
// recompute all joint transforms at this time.
|
// recompute all joint transforms at this time.
|
||||||
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
||||||
_rig->updateJointState(i, parentTransform);
|
_rig->updateJointState(i, rootTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildShapes();
|
buildShapes();
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "AvatarRig.h"
|
#include "AvatarRig.h"
|
||||||
|
|
||||||
/// Updates the state of the joint at the specified index.
|
/// Updates the state of the joint at the specified index.
|
||||||
void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) {
|
void AvatarRig::updateJointState(int index, glm::mat4 rootTransform) {
|
||||||
if (index < 0 && index >= _jointStates.size()) {
|
if (index < 0 && index >= _jointStates.size()) {
|
||||||
return; // bail
|
return; // bail
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) {
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
if (index == _rootJointIndex) {
|
if (index == _rootJointIndex) {
|
||||||
// we always zero-out the translation part of an avatar's root join-transform.
|
// we always zero-out the translation part of an avatar's root join-transform.
|
||||||
state.computeTransform(parentTransform);
|
state.computeTransform(rootTransform);
|
||||||
clearJointTransformTranslation(index);
|
clearJointTransformTranslation(index);
|
||||||
} else {
|
} else {
|
||||||
// guard against out-of-bounds access to _jointStates
|
// guard against out-of-bounds access to _jointStates
|
||||||
|
|
|
@ -21,7 +21,7 @@ class AvatarRig : public Rig {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~AvatarRig() {}
|
~AvatarRig() {}
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform);
|
virtual void updateJointState(int index, glm::mat4 rootTransform);
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority);
|
float scale, float priority);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,13 +12,13 @@
|
||||||
#include "EntityRig.h"
|
#include "EntityRig.h"
|
||||||
|
|
||||||
/// Updates the state of the joint at the specified index.
|
/// Updates the state of the joint at the specified index.
|
||||||
void EntityRig::updateJointState(int index, glm::mat4 parentTransform) {
|
void EntityRig::updateJointState(int index, glm::mat4 rootTransform) {
|
||||||
JointState& state = _jointStates[index];
|
JointState& state = _jointStates[index];
|
||||||
|
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
state.computeTransform(parentTransform);
|
state.computeTransform(rootTransform);
|
||||||
} else {
|
} else {
|
||||||
// guard against out-of-bounds access to _jointStates
|
// guard against out-of-bounds access to _jointStates
|
||||||
if (parentIndex >= 0 && parentIndex < _jointStates.size()) {
|
if (parentIndex >= 0 && parentIndex < _jointStates.size()) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class EntityRig : public Rig {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~EntityRig() {}
|
~EntityRig() {}
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform);
|
virtual void updateJointState(int index, glm::mat4 rootTransform);
|
||||||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||||
float scale, float priority) {}
|
float scale, float priority) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -185,7 +185,7 @@ void Rig::deleteAnimations() {
|
||||||
_animationHandles.clear();
|
_animationHandles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
float Rig::initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
||||||
int rootJointIndex,
|
int rootJointIndex,
|
||||||
int leftHandJointIndex,
|
int leftHandJointIndex,
|
||||||
int leftElbowJointIndex,
|
int leftElbowJointIndex,
|
||||||
|
@ -203,7 +203,7 @@ float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform
|
||||||
_rightElbowJointIndex = rightElbowJointIndex;
|
_rightElbowJointIndex = rightElbowJointIndex;
|
||||||
_rightShoulderJointIndex = rightShoulderJointIndex;
|
_rightShoulderJointIndex = rightShoulderJointIndex;
|
||||||
|
|
||||||
initJointTransforms(parentTransform);
|
initJointTransforms(rootTransform);
|
||||||
|
|
||||||
int numStates = _jointStates.size();
|
int numStates = _jointStates.size();
|
||||||
float radius = 0.0f;
|
float radius = 0.0f;
|
||||||
|
@ -233,14 +233,14 @@ int Rig::indexOfJoint(const QString& jointName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Rig::initJointTransforms(glm::mat4 parentTransform) {
|
void Rig::initJointTransforms(glm::mat4 rootTransform) {
|
||||||
// compute model transforms
|
// compute model transforms
|
||||||
int numStates = _jointStates.size();
|
int numStates = _jointStates.size();
|
||||||
for (int i = 0; i < numStates; ++i) {
|
for (int i = 0; i < numStates; ++i) {
|
||||||
JointState& state = _jointStates[i];
|
JointState& state = _jointStates[i];
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
state.initTransform(parentTransform);
|
state.initTransform(rootTransform);
|
||||||
} else {
|
} else {
|
||||||
const JointState& parentState = _jointStates.at(parentIndex);
|
const JointState& parentState = _jointStates.at(parentIndex);
|
||||||
state.initTransform(parentState.getTransform());
|
state.initTransform(parentState.getTransform());
|
||||||
|
@ -448,7 +448,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
||||||
_lastPosition = worldPosition;
|
_lastPosition = worldPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
||||||
|
|
||||||
// First normalize the fades so that they sum to 1.0.
|
// First normalize the fades so that they sum to 1.0.
|
||||||
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
||||||
|
@ -494,7 +494,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
updateJointState(i, parentTransform);
|
updateJointState(i, rootTransform);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
_jointStates[i].resetTransformChanged();
|
_jointStates[i].resetTransformChanged();
|
||||||
|
@ -503,7 +503,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
||||||
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
|
const QVector<int>& freeLineage, glm::mat4 rootTransform) {
|
||||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +555,7 @@ bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm:
|
||||||
glm::vec3 positionSum;
|
glm::vec3 positionSum;
|
||||||
for (int k = j - 1; k > 0; k--) {
|
for (int k = j - 1; k > 0; k--) {
|
||||||
int index = freeLineage.at(k);
|
int index = freeLineage.at(k);
|
||||||
updateJointState(index, parentTransform);
|
updateJointState(index, rootTransform);
|
||||||
positionSum += extractTranslation(_jointStates.at(index).getTransform());
|
positionSum += extractTranslation(_jointStates.at(index).getTransform());
|
||||||
}
|
}
|
||||||
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
|
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
|
||||||
|
@ -578,14 +578,14 @@ bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm:
|
||||||
|
|
||||||
// now update the joint states from the top
|
// now update the joint states from the top
|
||||||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||||
updateJointState(freeLineage.at(j), parentTransform);
|
updateJointState(freeLineage.at(j), rootTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
|
const QVector<int>& freeLineage, glm::mat4 rootTransform) {
|
||||||
// NOTE: targetRotation is from in model-frame
|
// NOTE: targetRotation is from in model-frame
|
||||||
|
|
||||||
if (endIndex == -1 || _jointStates.isEmpty()) {
|
if (endIndex == -1 || _jointStates.isEmpty()) {
|
||||||
|
@ -604,12 +604,27 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
const JointState& state = _jointStates.at(index);
|
const JointState& state = _jointStates.at(index);
|
||||||
int parentIndex = state.getParentIndex();
|
int parentIndex = state.getParentIndex();
|
||||||
if (parentIndex == -1) {
|
if (parentIndex == -1) {
|
||||||
topParentTransform = parentTransform;
|
topParentTransform = rootTransform;
|
||||||
} else {
|
} else {
|
||||||
topParentTransform = _jointStates[parentIndex].getTransform();
|
topParentTransform = _jointStates[parentIndex].getTransform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// relax toward default rotation
|
||||||
|
// NOTE: ideally this should use dt and a relaxation timescale to compute how much to relax
|
||||||
|
for (int j = 0; j < numFree; j++) {
|
||||||
|
int nextIndex = freeLineage.at(j);
|
||||||
|
JointState& nextState = _jointStates[nextIndex];
|
||||||
|
if (! nextState.getIsFree()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the zero rotationDelta, but use mixRotationDelta() which blends a bit of the default pose
|
||||||
|
// in the process. This provides stability to the IK solution for most models.
|
||||||
|
float mixFactor = 0.08f;
|
||||||
|
nextState.mixRotationDelta(glm::quat(), mixFactor, priority);
|
||||||
|
}
|
||||||
|
|
||||||
// this is a cyclic coordinate descent algorithm: see
|
// this is a cyclic coordinate descent algorithm: see
|
||||||
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
|
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
|
||||||
|
|
||||||
|
@ -618,7 +633,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
glm::vec3 endPosition = endState.getPosition();
|
glm::vec3 endPosition = endState.getPosition();
|
||||||
float distanceToGo = glm::distance(targetPosition, endPosition);
|
float distanceToGo = glm::distance(targetPosition, endPosition);
|
||||||
|
|
||||||
const int MAX_ITERATION_COUNT = 2;
|
const int MAX_ITERATION_COUNT = 3;
|
||||||
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
|
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
|
||||||
int numIterations = 0;
|
int numIterations = 0;
|
||||||
do {
|
do {
|
||||||
|
@ -655,7 +670,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
glm::cross(worldAlignment, leverArm));
|
glm::cross(worldAlignment, leverArm));
|
||||||
|
|
||||||
float gravityAngle = glm::angle(gravityDelta);
|
float gravityAngle = glm::angle(gravityDelta);
|
||||||
const float MIN_GRAVITY_ANGLE = 0.1f;
|
const float MIN_GRAVITY_ANGLE = 0.5f;
|
||||||
float mixFactor = 0.5f;
|
float mixFactor = 0.5f;
|
||||||
if (gravityAngle < MIN_GRAVITY_ANGLE) {
|
if (gravityAngle < MIN_GRAVITY_ANGLE) {
|
||||||
// the final rotation is a mix of the two
|
// the final rotation is a mix of the two
|
||||||
|
@ -664,11 +679,10 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
|
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
|
// Apply the rotation delta.
|
||||||
// in the process. This provides stability to the IK solution for most models.
|
|
||||||
glm::quat oldNextRotation = nextState.getRotation();
|
glm::quat oldNextRotation = nextState.getRotation();
|
||||||
float mixFactor = 0.03f;
|
float mixFactor = 0.05f;
|
||||||
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
|
nextState.applyRotationDelta(deltaRotation, mixFactor, priority);
|
||||||
|
|
||||||
// measure the result of the rotation which may have been modified by
|
// measure the result of the rotation which may have been modified by
|
||||||
// blending and constraints
|
// blending and constraints
|
||||||
|
@ -687,7 +701,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
||||||
// measure our success
|
// measure our success
|
||||||
endPosition = endState.getPosition();
|
endPosition = endState.getPosition();
|
||||||
distanceToGo = glm::distance(targetPosition, endPosition);
|
distanceToGo = glm::distance(targetPosition, endPosition);
|
||||||
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
|
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo > ACCEPTABLE_IK_ERROR);
|
||||||
|
|
||||||
// set final rotation of the end joint
|
// set final rotation of the end joint
|
||||||
endState.setRotationInModelFrame(targetRotation, priority, true);
|
endState.setRotationInModelFrame(targetRotation, priority, true);
|
||||||
|
|
|
@ -92,7 +92,7 @@ public:
|
||||||
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
|
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
|
||||||
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
|
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
|
||||||
|
|
||||||
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform,
|
float initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
||||||
int rootJointIndex,
|
int rootJointIndex,
|
||||||
int leftHandJointIndex,
|
int leftHandJointIndex,
|
||||||
int leftElbowJointIndex,
|
int leftElbowJointIndex,
|
||||||
|
@ -104,7 +104,7 @@ public:
|
||||||
int getJointStateCount() const { return _jointStates.size(); }
|
int getJointStateCount() const { return _jointStates.size(); }
|
||||||
int indexOfJoint(const QString& jointName) ;
|
int indexOfJoint(const QString& jointName) ;
|
||||||
|
|
||||||
void initJointTransforms(glm::mat4 parentTransform);
|
void initJointTransforms(glm::mat4 rootTransform);
|
||||||
void clearJointTransformTranslation(int jointIndex);
|
void clearJointTransformTranslation(int jointIndex);
|
||||||
void reset(const QVector<FBXJoint>& fbxJoints);
|
void reset(const QVector<FBXJoint>& fbxJoints);
|
||||||
bool getJointStateRotation(int index, glm::quat& rotation) const;
|
bool getJointStateRotation(int index, glm::quat& rotation) const;
|
||||||
|
@ -135,12 +135,12 @@ public:
|
||||||
// Start or stop animations as needed.
|
// Start or stop animations as needed.
|
||||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation);
|
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation);
|
||||||
// Regardless of who started the animations or how many, update the joints.
|
// Regardless of who started the animations or how many, update the joints.
|
||||||
void updateAnimations(float deltaTime, glm::mat4 parentTransform);
|
void updateAnimations(float deltaTime, glm::mat4 rootTransform);
|
||||||
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
|
||||||
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform);
|
const QVector<int>& freeLineage, glm::mat4 rootTransform);
|
||||||
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
||||||
const QVector<int>& freeLineage, glm::mat4 parentTransform);
|
const QVector<int>& freeLineage, glm::mat4 rootTransform);
|
||||||
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
|
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
|
||||||
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
||||||
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
|
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
|
||||||
|
@ -152,7 +152,7 @@ public:
|
||||||
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
|
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
|
||||||
void updateVisibleJointStates();
|
void updateVisibleJointStates();
|
||||||
|
|
||||||
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
|
virtual void updateJointState(int index, glm::mat4 rootTransform) = 0;
|
||||||
|
|
||||||
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue