From 1b2cd2e144f81e47046046ea87ac0e643e44b050 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 5 Jun 2014 11:07:35 -0700 Subject: [PATCH] adding RagDoll scaffold --- interface/src/avatar/SkeletonModel.cpp | 48 +++++++++ interface/src/avatar/SkeletonModel.h | 8 +- interface/src/renderer/JointState.cpp | 101 +++++++++++++++++++ interface/src/renderer/JointState.h | 66 +++++++++++++ interface/src/renderer/Model.cpp | 95 ++---------------- interface/src/renderer/Model.h | 48 +-------- interface/src/renderer/RagDoll.cpp | 131 +++++++++++++++++++++++++ interface/src/renderer/RagDoll.h | 78 +++++++++++++++ 8 files changed, 441 insertions(+), 134 deletions(-) create mode 100644 interface/src/renderer/JointState.cpp create mode 100644 interface/src/renderer/JointState.h create mode 100644 interface/src/renderer/RagDoll.cpp create mode 100644 interface/src/renderer/RagDoll.h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 58151d25ab..f5ca1ab218 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -21,6 +21,11 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } +void SkeletonModel::setJointStates(QVector states) { + Model::setJointStates(states); + _ragDoll.init(_jointStates); +} + const float PALM_PRIORITY = 3.0f; void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { @@ -78,6 +83,21 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]); applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]); } + + simulateRagDoll(deltaTime); +} + +void SkeletonModel::simulateRagDoll(float deltaTime) { + _ragDoll.slaveToSkeleton(_jointStates, 0.5f); + + float MIN_CONSTRAINT_ERROR = 0.005f; // 5mm + int MAX_ITERATIONS = 4; + int iterations = 0; + float delta = 0.0f; + do { + delta = _ragDoll.enforceConstraints(); + ++iterations; + } while (delta > MIN_CONSTRAINT_ERROR && iterations < MAX_ITERATIONS); } void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { @@ -121,6 +141,7 @@ void SkeletonModel::getBodyShapes(QVector& shapes) const { void SkeletonModel::renderIKConstraints() { renderJointConstraints(getRightHandJointIndex()); renderJointConstraints(getLeftHandJointIndex()); + renderRagDoll(); } class IndexValue { @@ -452,3 +473,30 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco return false; } +void SkeletonModel::renderRagDoll() { + const int BALL_SUBDIVISIONS = 6; + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glPushMatrix(); + + Application::getInstance()->loadTranslatedViewMatrix(_translation); + QVector points = _ragDoll.getPoints(); + int numPoints = points.size(); + float alpha = 0.3f; + float radius1 = 0.008f; + float radius2 = 0.01f; + for (int i = 0; i < numPoints; ++i) { + glPushMatrix(); + // draw each point as a yellow hexagon with black border + glm::vec3 position = _rotation * points[i]; + glTranslatef(position.x, position.y, position.z); + glColor4f(0.0f, 0.0f, 0.0f, alpha); + glutSolidSphere(radius2, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + glColor4f(1.0f, 1.0f, 0.0f, alpha); + glutSolidSphere(radius1, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + glPopMatrix(); + } + glPopMatrix(); + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 4335dfc3ff..d733d937ee 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -13,6 +13,7 @@ #define hifi_SkeletonModel_h #include "renderer/Model.h" +#include "renderer/RagDoll.h" class Avatar; @@ -23,8 +24,11 @@ class SkeletonModel : public Model { public: SkeletonModel(Avatar* owningAvatar); - + + void setJointStates(QVector states); + void simulate(float deltaTime, bool fullUpdate = true); + void simulateRagDoll(float deltaTime); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes @@ -89,6 +93,7 @@ public: /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + void renderRagDoll(); protected: /// \param jointIndex index of joint in model @@ -114,6 +119,7 @@ private: void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation); Avatar* _owningAvatar; + RagDoll _ragDoll; }; #endif // hifi_SkeletonModel_h diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp new file mode 100644 index 0000000000..e66a2f44e9 --- /dev/null +++ b/interface/src/renderer/JointState.cpp @@ -0,0 +1,101 @@ +// +// JointState.cpp +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +//#include +#include + +#include "JointState.h" + +JointState::JointState() : + _animationPriority(0.0f), + _fbxJoint(NULL) { +} + +void JointState::setFBXJoint(const FBXJoint* joint) { + assert(joint != NULL); + _rotationInParentFrame = joint->rotation; + // NOTE: JointState does not own the FBXJoint to which it points. + _fbxJoint = joint; +} + +void JointState::copyState(const JointState& state) { + _rotationInParentFrame = state._rotationInParentFrame; + _transform = state._transform; + _rotation = extractRotation(_transform); + _animationPriority = state._animationPriority; + // DO NOT copy _fbxJoint +} + +void JointState::computeTransform(const glm::mat4& parentTransform) { + glm::quat modifiedRotation = _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; + glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; + _transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; + _rotation = extractRotation(_transform); +} + +glm::quat JointState::getRotationFromBindToModelFrame() const { + return _rotation * _fbxJoint->inverseBindRotation; +} + +void JointState::restoreRotation(float fraction, float priority) { + assert(_fbxJoint != NULL); + if (priority == _animationPriority || _animationPriority == 0.0f) { + _rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction); + _animationPriority = 0.0f; + } +} + +void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) { + assert(_fbxJoint != NULL); + if (priority >= _animationPriority) { + // rotation is from bind- to model-frame + _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + _animationPriority = priority; + } +} + +void JointState::clearTransformTranslation() { + _transform[3][0] = 0.0f; + _transform[3][1] = 0.0f; + _transform[3][2] = 0.0f; +} + +void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) { + applyRotationDelta(rotation * glm::inverse(_rotation), true, priority); +} + +void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { + // NOTE: delta is in jointParent-frame + assert(_fbxJoint != NULL); + if (priority < _animationPriority) { + return; + } + _animationPriority = priority; + if (!constrain || (_fbxJoint->rotationMin == glm::vec3(-PI, -PI, -PI) && + _fbxJoint->rotationMax == glm::vec3(PI, PI, PI))) { + // no constraints + _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; + _rotation = delta * _rotation; + return; + } + glm::quat targetRotation = delta * _rotation; + glm::vec3 eulers = safeEulerAngles(_rotationInParentFrame * glm::inverse(_rotation) * targetRotation); + glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax)); + _rotation = _rotation * glm::inverse(_rotationInParentFrame) * newRotation; + _rotationInParentFrame = newRotation; +} + +const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { + assert(_fbxJoint != NULL); + return _fbxJoint->translation; +} diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h new file mode 100644 index 0000000000..b1a584d4ec --- /dev/null +++ b/interface/src/renderer/JointState.h @@ -0,0 +1,66 @@ +// +// JointState.h +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_JointState_h +#define hifi_JointState_h + +#include +#include +#include + +#include + +class JointState { +public: + JointState(); + + void setFBXJoint(const FBXJoint* joint); + const FBXJoint& getFBXJoint() const { return *_fbxJoint; } + + void copyState(const JointState& state); + + void computeTransform(const glm::mat4& parentTransform); + const glm::mat4& getTransform() const { return _transform; } + + glm::quat getRotation() const { return _rotation; } + glm::vec3 getPosition() const { return extractTranslation(_transform); } + + /// \return rotation from bind to model frame + glm::quat getRotationFromBindToModelFrame() const; + + /// \param rotation rotation of joint in model-frame + void setRotation(const glm::quat& rotation, bool constrain, float priority); + + /// \param delta is in the jointParent-frame + void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); + + const glm::vec3& getDefaultTranslationInParentFrame() const; + + void restoreRotation(float fraction, float priority); + + /// \param rotation is from bind- to model-frame + /// computes and sets new _rotationInParentFrame + /// NOTE: the JointState's model-frame transform/rotation are NOT updated! + void setRotationFromBindFrame(const glm::quat& rotation, float priority); + + void clearTransformTranslation(); + + glm::quat _rotationInParentFrame; // joint- to parentJoint-frame + float _animationPriority; // the priority of the animation affecting this joint + +private: + glm::mat4 _transform; // joint- to model-frame + glm::quat _rotation; // joint- to model-frame + + const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint +}; + +#endif // hifi_JointState_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2881d96d88..105301054b 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -510,12 +510,12 @@ bool Model::updateGeometry() { deleteGeometry(); _dilatedTextures.clear(); _geometry = geometry; - _jointStates = newJointStates; + setJointStates(newJointStates); needToRebuild = true; } else if (_jointStates.isEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { - _jointStates = createJointStates(fbxGeometry); + setJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } } else if (!geometry->isLoaded()) { @@ -557,6 +557,11 @@ bool Model::updateGeometry() { return needFullUpdate; } +// virtual +void Model::setJointStates(QVector states) { + _jointStates = states; +} + bool Model::render(float alpha, RenderMode mode, bool receiveShadows) { // render the attachments foreach (Model* attachment, _attachments) { @@ -1974,89 +1979,3 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) { } } -// ---------------------------------------------------------------------------- -// JointState TODO: move this class to its own files -// ---------------------------------------------------------------------------- -JointState::JointState() : - _animationPriority(0.0f), - _fbxJoint(NULL) { -} - -void JointState::setFBXJoint(const FBXJoint* joint) { - assert(joint != NULL); - _rotationInParentFrame = joint->rotation; - // NOTE: JointState does not own the FBXJoint to which it points. - _fbxJoint = joint; -} - -void JointState::copyState(const JointState& state) { - _rotationInParentFrame = state._rotationInParentFrame; - _transform = state._transform; - _rotation = extractRotation(_transform); - _animationPriority = state._animationPriority; - // DO NOT copy _fbxJoint -} - -void JointState::computeTransform(const glm::mat4& parentTransform) { - glm::quat modifiedRotation = _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; - glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; - _transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; - _rotation = extractRotation(_transform); -} - -glm::quat JointState::getRotationFromBindToModelFrame() const { - return _rotation * _fbxJoint->inverseBindRotation; -} - -void JointState::restoreRotation(float fraction, float priority) { - assert(_fbxJoint != NULL); - if (priority == _animationPriority || _animationPriority == 0.0f) { - _rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction); - _animationPriority = 0.0f; - } -} - -void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) { - assert(_fbxJoint != NULL); - if (priority >= _animationPriority) { - // rotation is from bind- to model-frame - _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); - _animationPriority = priority; - } -} - -void JointState::clearTransformTranslation() { - _transform[3][0] = 0.0f; - _transform[3][1] = 0.0f; - _transform[3][2] = 0.0f; -} - -void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) { - applyRotationDelta(rotation * glm::inverse(_rotation), true, priority); -} - -void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { - // NOTE: delta is in jointParent-frame - assert(_fbxJoint != NULL); - if (priority < _animationPriority) { - return; - } - _animationPriority = priority; - if (!constrain || (_fbxJoint->rotationMin == glm::vec3(-PI, -PI, -PI) && - _fbxJoint->rotationMax == glm::vec3(PI, PI, PI))) { - // no constraints - _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation; - _rotation = delta * _rotation; - return; - } - glm::quat targetRotation = delta * _rotation; - glm::vec3 eulers = safeEulerAngles(_rotationInParentFrame * glm::inverse(_rotation) * targetRotation); - glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax)); - _rotation = _rotation * glm::inverse(_rotationInParentFrame) * newRotation; - _rotationInParentFrame = newRotation; -} - -const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { - assert(_fbxJoint != NULL); - return _fbxJoint->translation; -} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 4bab2451c4..3bc261ed44 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -22,6 +22,7 @@ #include "GeometryCache.h" #include "InterfaceConfig.h" +#include "JointState.h" #include "ProgramObject.h" #include "TextureCache.h" @@ -30,51 +31,6 @@ class Shape; typedef QSharedPointer AnimationHandlePointer; typedef QWeakPointer WeakAnimationHandlePointer; - -class JointState { -public: - JointState(); - - void setFBXJoint(const FBXJoint* joint); - const FBXJoint& getFBXJoint() const { return *_fbxJoint; } - - void copyState(const JointState& state); - - void computeTransform(const glm::mat4& parentTransform); - const glm::mat4& getTransform() const { return _transform; } - - glm::quat getRotation() const { return _rotation; } - glm::vec3 getPosition() const { return extractTranslation(_transform); } - - /// \return rotation from bind to model frame - glm::quat getRotationFromBindToModelFrame() const; - - /// \param rotation rotation of joint in model-frame - void setRotation(const glm::quat& rotation, bool constrain, float priority); - - /// \param delta is in the jointParent-frame - void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); - - const glm::vec3& getDefaultTranslationInParentFrame() const; - - void restoreRotation(float fraction, float priority); - - /// \param rotation is from bind- to model-frame - /// computes and sets new _rotationInParentFrame - /// NOTE: the JointState's model-frame transform/rotation are NOT updated! - void setRotationFromBindFrame(const glm::quat& rotation, float priority); - - void clearTransformTranslation(); - - glm::quat _rotationInParentFrame; // joint- to parentJoint-frame - float _animationPriority; // the priority of the animation affecting this joint - -private: - glm::mat4 _transform; // joint- to model-frame - glm::quat _rotation; // joint- to model-frame - - const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint -}; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject { @@ -250,6 +206,8 @@ protected: // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); + + virtual void setJointStates(QVector states); void setScaleInternal(const glm::vec3& scale); void scaleToFit(); diff --git a/interface/src/renderer/RagDoll.cpp b/interface/src/renderer/RagDoll.cpp new file mode 100644 index 0000000000..54fb776552 --- /dev/null +++ b/interface/src/renderer/RagDoll.cpp @@ -0,0 +1,131 @@ +// +// RagDoll.cpp +// interface/src/avatar +// +// Created by Andrew Meadows 2014.05.30 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include +#include + +#include "RagDoll.h" + +// ---------------------------------------------------------------------------- +// FixedConstraint +// ---------------------------------------------------------------------------- +FixedConstraint::FixedConstraint() : _point(NULL), _anchor(0.0f, 0.0f, 0.0f) { +} + +float FixedConstraint::enforce() { + assert(_point != NULL); + float distance = glm::distance(_anchor, *_point); + *_point = _anchor; + return distance; +} + +void FixedConstraint::setPoint(glm::vec3* point) { + _point = point; +} + +void FixedConstraint::setAnchor(const glm::vec3& anchor) { + _anchor = anchor; +} + +// ---------------------------------------------------------------------------- +// DistanceConstraint +// ---------------------------------------------------------------------------- +DistanceConstraint::DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB) : _distance(-1.0f) { + _points[0] = pointA; + _points[1] = pointB; + _distance = glm::distance(*(_points[0]), *(_points[1])); +} + +DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) { + _distance = other._distance; + _points[0] = other._points[0]; + _points[1] = other._points[1]; +} + +void DistanceConstraint::setDistance(float distance) { + _distance = fabsf(distance); +} + +float DistanceConstraint::enforce() { + float newDistance = glm::distance(*(_points[0]), *(_points[1])); + glm::vec3 direction(0.0f, 1.0f, 0.0f); + if (newDistance > EPSILON) { + direction = (*(_points[0]) - *(_points[1])) / newDistance; + } + glm::vec3 center = 0.5f * (*(_points[0]) + *(_points[1])); + *(_points[0]) = center + (0.5f * _distance) * direction; + *(_points[1]) = center - (0.5f * _distance) * direction; + return glm::abs(newDistance - _distance); +} + +// ---------------------------------------------------------------------------- +// RagDoll +// ---------------------------------------------------------------------------- + +RagDoll::RagDoll() { +} + +RagDoll::~RagDoll() { + clear(); +} + +void RagDoll::init(const QVector& states) { + clear(); + const int numStates = states.size(); + _points.reserve(numStates); + for (int i = 0; i < numStates; ++i) { + const JointState& state = states[i]; + _points.push_back(state.getPosition()); + int parentIndex = state.getFBXJoint().parentIndex; + assert(parentIndex < i); + if (parentIndex != -1) { + DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex])); + _constraints.push_back(stick); + } + } +} +/// Delete all data. +void RagDoll::clear() { + int numConstraints = _constraints.size(); + for (int i = 0; i < numConstraints; ++i) { + delete _constraints[i]; + } + _constraints.clear(); + _points.clear(); +} + +float RagDoll::slaveToSkeleton(const QVector& states, float fraction) { + const int numStates = states.size(); + assert(numStates == _points.size()); + fraction = glm::clamp(fraction, 0.0f, 1.0f); + float maxDistance = 0.0f; + for (int i = 0; i < numStates; ++i) { + glm::vec3 oldPoint = _points[i]; + _points[i] = (1.0f - fraction) * _points[i] + fraction * states[i].getPosition(); + maxDistance = glm::max(maxDistance, glm::distance(oldPoint, _points[i])); + } + return maxDistance; +} + +float RagDoll::enforceConstraints() { + float maxDistance = 0.0f; + const int numConstraints = _constraints.size(); + for (int i = 0; i < numConstraints; ++i) { + DistanceConstraint* c = static_cast(_constraints[i]); + //maxDistance = glm::max(maxDistance, _constraints[i]->enforce()); + maxDistance = glm::max(maxDistance, c->enforce()); + } + return maxDistance; +} diff --git a/interface/src/renderer/RagDoll.h b/interface/src/renderer/RagDoll.h new file mode 100644 index 0000000000..1d23973827 --- /dev/null +++ b/interface/src/renderer/RagDoll.h @@ -0,0 +1,78 @@ +// +// RagDoll.h +// interface/src/avatar +// +// Created by Andrew Meadows 2014.05.30 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RagDoll_h +#define hifi_RagDoll_h + +#include "renderer/Model.h" + +class Constraint { +public: + Constraint() {} + virtual ~Constraint() {} + + /// Enforce contraint by moving relevant points. + /// \return max distance of point movement + virtual float enforce() = 0; +}; + +class FixedConstraint : public Constraint { +public: + FixedConstraint(); + float enforce(); + void setPoint(glm::vec3* point); + void setAnchor(const glm::vec3& anchor); +private: + glm::vec3* _point; + glm::vec3 _anchor; +}; + +class DistanceConstraint : public Constraint { +public: + DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB); + DistanceConstraint(const DistanceConstraint& other); + float enforce(); + void setDistance(float distance); +private: + float _distance; + glm::vec3* _points[2]; +}; + +class RagDoll { +public: + + RagDoll(); + virtual ~RagDoll(); + + /// Create points and constraints based on topology of collection of joints + /// \param joints list of connected joint states + void init(const QVector& states); + + /// Delete all data. + void clear(); + + /// \param states list of joint states + /// \param fraction range from 0.0 (no movement) to 1.0 (use joint locations) + /// \return max distance of point movement + float slaveToSkeleton(const QVector& states, float fraction); + + /// Enforce contraints. + /// \return max distance of point movement + float enforceConstraints(); + + const QVector& getPoints() const { return _points; } + +private: + QVector _constraints; + QVector _points; +}; + +#endif // hifi_RagDoll_h