From d1672b510d5aec85145369e862984c36720909b3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 Aug 2014 18:41:07 -0700 Subject: [PATCH 01/10] Add DomainHandler::disconnectedFromDomain signal --- libraries/networking/src/DomainHandler.cpp | 5 ++++- libraries/networking/src/DomainHandler.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index fcd284fd4a..91166129ad 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -36,6 +36,7 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); _isConnected = false; + emit disconnectedFromDomain(); if (_handshakeTimer) { _handshakeTimer->stop(); @@ -129,6 +130,8 @@ void DomainHandler::setIsConnected(bool isConnected) { // we've connected to new domain - time to ask it for global settings requestDomainSettings(); + } else { + emit disconnectedFromDomain(); } } } @@ -196,4 +199,4 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement _sockAddr.setPort(dtlsPort); // initializeDTLSSession(); -} \ No newline at end of file +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 28433c5455..91caddca22 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -70,6 +70,7 @@ private slots: signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); + void disconnectedFromDomain(); void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); From 0296d0deeb77e31b3703b868cbda3e7e2191c280 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 Aug 2014 18:43:26 -0700 Subject: [PATCH 02/10] Add domain connection status to window title --- interface/src/Application.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6f7212e68f..9f14023dfc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -242,6 +242,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); + connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); // hookup VoxelEditSender to PaymentManager so we can pay for octree edits @@ -3320,9 +3322,10 @@ void Application::updateWindowTitle(){ QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); + QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) - + nodeList->getDomainHandler().getHostname() + buildVersion; + + nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion; AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.getAccountInfo().hasBalance()) { From 4852fcc034964dd3027bef184209df6cc24986e2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 Aug 2014 18:43:49 -0700 Subject: [PATCH 03/10] Add red border to main window when not connected to domain --- interface/src/ui/ApplicationOverlay.cpp | 29 +++++++++++++++++++++++++ interface/src/ui/ApplicationOverlay.h | 3 ++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c49873b5f5..7dcb50c314 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -57,6 +57,9 @@ ApplicationOverlay::~ApplicationOverlay() { const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; +const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f }; +const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; + // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(bool renderToTexture) { @@ -115,6 +118,8 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { renderPointers(); + renderDomainConnectionStatusBorder(); + glPopMatrix(); @@ -1234,6 +1239,30 @@ void ApplicationOverlay::renderTexturedHemisphere() { } +void ApplicationOverlay::renderDomainConnectionStatusBorder() { + NodeList* nodeList = NodeList::getInstance(); + + if (nodeList && !nodeList->getDomainHandler().isConnected()) { + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + int right = glWidget->width(); + int bottom = glWidget->height(); + + glColor3f(CONNECTION_STATUS_BORDER_COLOR[0], + CONNECTION_STATUS_BORDER_COLOR[1], + CONNECTION_STATUS_BORDER_COLOR[2]); + glLineWidth(CONNECTION_STATUS_BORDER_LINE_WIDTH); + + glBegin(GL_LINE_LOOP); + + glVertex2i(0, 0); + glVertex2i(0, bottom); + glVertex2i(right, bottom); + glVertex2i(right, 0); + + glEnd(); + } +} + QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { QSize size = Application::getInstance()->getGLWidget()->size(); if (!_framebufferObject || _framebufferObject->size() != size) { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 0c2ccc7b21..a493f6cd1b 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -56,6 +56,7 @@ private: void renderAudioMeter(); void renderStatsAndLogs(); void renderTexturedHemisphere(); + void renderDomainConnectionStatusBorder(); QOpenGLFramebufferObject* _framebufferObject; float _trailingAudioLoudness; @@ -76,4 +77,4 @@ private: GLuint _crosshairTexture; }; -#endif // hifi_ApplicationOverlay_h \ No newline at end of file +#endif // hifi_ApplicationOverlay_h From 46c91052c9edae7be647480c07f36ae1bad189ed Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 Aug 2014 14:29:03 -0700 Subject: [PATCH 04/10] split SkeletonModel and Ragdoll classes apart --- interface/src/avatar/MyAvatar.cpp | 11 +- interface/src/avatar/SkeletonModel.cpp | 200 +++++++---------------- interface/src/avatar/SkeletonModel.h | 19 +-- interface/src/avatar/SkeletonRagdoll.cpp | 148 +++++++++++++++++ interface/src/avatar/SkeletonRagdoll.h | 42 +++++ interface/src/renderer/Model.h | 3 + libraries/shared/src/Ragdoll.h | 2 +- 7 files changed, 263 insertions(+), 162 deletions(-) create mode 100644 interface/src/avatar/SkeletonRagdoll.cpp create mode 100644 interface/src/avatar/SkeletonRagdoll.h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c9d19d4bcc..ac44e1884e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -83,10 +83,11 @@ MyAvatar::MyAvatar() : for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; } - _skeletonModel.setEnableShapes(true); - // The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type. _physicsSimulation.setEntity(&_skeletonModel); - _physicsSimulation.setRagdoll(&_skeletonModel); + + _skeletonModel.setEnableShapes(true); + Ragdoll* ragdoll = _skeletonModel.buildRagdoll(); + _physicsSimulation.setRagdoll(ragdoll); } MyAvatar::~MyAvatar() { @@ -1604,10 +1605,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { if (simulation != &(_physicsSimulation)) { skeleton->setEnableShapes(true); _physicsSimulation.addEntity(skeleton); - _physicsSimulation.addRagdoll(skeleton); + _physicsSimulation.addRagdoll(skeleton->getRagdoll()); } } else if (simulation == &(_physicsSimulation)) { - _physicsSimulation.removeRagdoll(skeleton); + _physicsSimulation.removeRagdoll(skeleton->getRagdoll()); _physicsSimulation.removeEntity(skeleton); skeleton->setEnableShapes(false); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 29c4e2cc52..90145f0af8 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -23,13 +23,21 @@ #include "Menu.h" #include "MuscleConstraint.h" #include "SkeletonModel.h" +#include "SkeletonRagdoll.h" SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : Model(parent), - Ragdoll(), _owningAvatar(owningAvatar), _boundingShape(), - _boundingShapeLocalOffset(0.0f) { + _boundingShapeLocalOffset(0.0f), + _ragdoll(NULL) { +} + +SkeletonModel::~SkeletonModel() { + if (_ragdoll) { + delete _ragdoll; + _ragdoll = NULL; + } } void SkeletonModel::setJointStates(QVector states) { @@ -155,9 +163,6 @@ void SkeletonModel::getBodyShapes(QVector& shapes) const { void SkeletonModel::renderIKConstraints() { renderJointConstraints(getRightHandJointIndex()); renderJointConstraints(getLeftHandJointIndex()); - //if (isActive() && _owningAvatar->isMyAvatar()) { - // renderRagdoll(); - //} } class IndexValue { @@ -489,21 +494,25 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco } void SkeletonModel::renderRagdoll() { + if (!_ragdoll) { + return; + } + const QVector& points = _ragdoll->getRagdollPoints(); const int BALL_SUBDIVISIONS = 6; glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); - int numPoints = _ragdollPoints.size(); + int numPoints = points.size(); float alpha = 0.3f; float radius1 = 0.008f; float radius2 = 0.01f; - glm::vec3 simulationTranslation = getTranslationInSimulationFrame(); + glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame(); for (int i = 0; i < numPoints; ++i) { glPushMatrix(); // NOTE: ragdollPoints are in simulation-frame but we want them to be model-relative - glm::vec3 position = _ragdollPoints[i]._position - simulationTranslation; + glm::vec3 position = points[i]._position - simulationTranslation; glTranslatef(position.x, position.y, position.z); // draw each point as a yellow hexagon with black border glColor4f(0.0f, 0.0f, 0.0f, alpha); @@ -517,109 +526,18 @@ void SkeletonModel::renderRagdoll() { glEnable(GL_LIGHTING); } -// virtual -void SkeletonModel::initRagdollPoints() { - clearRagdollConstraintsAndPoints(); - _muscleConstraints.clear(); - - initRagdollTransform(); - // one point for each joint - int numStates = _jointStates.size(); - _ragdollPoints.fill(VerletPoint(), numStates); - for (int i = 0; i < numStates; ++i) { - const JointState& state = _jointStates.at(i); - // _ragdollPoints start in model-frame - _ragdollPoints[i].initPosition(state.getPosition()); - } -} - -void SkeletonModel::buildRagdollConstraints() { - // NOTE: the length of DistanceConstraints is computed and locked in at this time - // so make sure the ragdoll positions are in a normal configuration before here. - const int numPoints = _ragdollPoints.size(); - assert(numPoints == _jointStates.size()); - - float minBone = FLT_MAX; - float maxBone = -FLT_MAX; - QMultiMap families; - for (int i = 0; i < numPoints; ++i) { - const JointState& state = _jointStates.at(i); - int parentIndex = state.getParentIndex(); - if (parentIndex == -1) { - FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_ragdollPoints[i])); - _fixedConstraints.push_back(anchor); - } else { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex])); - bone->setDistance(state.getDistanceToParent()); - _boneConstraints.push_back(bone); - families.insert(parentIndex, i); - } - float boneLength = glm::length(state.getPositionInParentFrame()); - if (boneLength > maxBone) { - maxBone = boneLength; - } else if (boneLength < minBone) { - minBone = boneLength; - } - } - // Joints that have multiple children effectively have rigid constraints between the children - // in the parent frame, so we add DistanceConstraints between children in the same family. - QMultiMap::iterator itr = families.begin(); - while (itr != families.end()) { - QList children = families.values(itr.key()); - int numChildren = children.size(); - if (numChildren > 1) { - for (int i = 1; i < numChildren; ++i) { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]])); - _boneConstraints.push_back(bone); - } - if (numChildren > 2) { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]])); - _boneConstraints.push_back(bone); - } - } - ++itr; - } - - float MAX_STRENGTH = 0.6f; - float MIN_STRENGTH = 0.05f; - // each joint gets a MuscleConstraint to its parent - for (int i = 1; i < numPoints; ++i) { - const JointState& state = _jointStates.at(i); - int p = state.getParentIndex(); - if (p == -1) { - continue; - } - MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i])); - _muscleConstraints.push_back(constraint); - - // Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length: - // long = weak and short = strong. - constraint->setIndices(p, i); - float boneLength = glm::length(state.getPositionInParentFrame()); - - float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone); - if (!families.contains(i)) { - // Although muscles only pull on the children not parents, nevertheless those joints that have - // parents AND children are more stable than joints at the end such as fingers. For such joints we - // bestow maximum strength which helps reduce wiggle. - strength = MAX_MUSCLE_STRENGTH; - } - constraint->setStrength(strength); - } - -} - void SkeletonModel::updateVisibleJointStates() { - if (_showTrueJointTransforms) { + if (_showTrueJointTransforms || !_ragdoll) { // no need to update visible transforms return; } + const QVector& ragdollPoints = _ragdoll->getRagdollPoints(); QVector points; points.reserve(_jointStates.size()); glm::quat invRotation = glm::inverse(_rotation); for (int i = 0; i < _jointStates.size(); i++) { JointState& state = _jointStates[i]; - points.push_back(_ragdollPoints[i]._position); + points.push_back(ragdollPoints[i]._position); // get the parent state (this is the state that we want to rotate) int parentIndex = state.getParentIndex(); @@ -653,15 +571,14 @@ void SkeletonModel::updateVisibleJointStates() { } } -// virtual -void SkeletonModel::stepRagdollForward(float deltaTime) { - setRagdollTransform(_translation, _rotation); - Ragdoll::stepRagdollForward(deltaTime); - updateMuscles(); - int numConstraints = _muscleConstraints.size(); - for (int i = 0; i < numConstraints; ++i) { - _muscleConstraints[i]->enforce(); +SkeletonRagdoll* SkeletonModel::buildRagdoll() { + if (!_ragdoll) { + _ragdoll = new SkeletonRagdoll(this); + if (_enableShapes) { + buildShapes(); + } } + return _ragdoll; } float DENSITY_OF_WATER = 1000.0f; // kg/m^3 @@ -679,8 +596,13 @@ void SkeletonModel::buildShapes() { return; } - initRagdollPoints(); - float massScale = getMassScale(); + if (!_ragdoll) { + _ragdoll = new SkeletonRagdoll(this); + } + _ragdoll->initRagdollPoints(); + QVector& points = _ragdoll->getRagdollPoints(); + + float massScale = _ragdoll->getMassScale(); float uniformScale = extractUniformScale(_scale); const int numStates = _jointStates.size(); @@ -700,14 +622,14 @@ void SkeletonModel::buildShapes() { Shape* shape = NULL; int parentIndex = joint.parentIndex; if (type == Shape::SPHERE_SHAPE) { - shape = new VerletSphereShape(radius, &(_ragdollPoints[i])); + shape = new VerletSphereShape(radius, &(points[i])); shape->setEntity(this); - _ragdollPoints[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); + points[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); } else if (type == Shape::CAPSULE_SHAPE) { assert(parentIndex != -1); - shape = new VerletCapsuleShape(radius, &(_ragdollPoints[parentIndex]), &(_ragdollPoints[i])); + shape = new VerletCapsuleShape(radius, &(points[parentIndex]), &(points[i])); shape->setEntity(this); - _ragdollPoints[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); + points[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); } if (parentIndex != -1) { // always disable collisions between joint and its parent @@ -717,7 +639,7 @@ void SkeletonModel::buildShapes() { } else { // give the base joint a very large mass since it doesn't actually move // in the local-frame simulation (it defines the origin) - _ragdollPoints[i].setMass(VERY_BIG_MASS); + points[i].setMass(VERY_BIG_MASS); } _shapes.push_back(shape); } @@ -729,17 +651,11 @@ void SkeletonModel::buildShapes() { // joints that are currently colliding. disableCurrentSelfCollisions(); - buildRagdollConstraints(); + _ragdoll->buildRagdollConstraints(); // ... then move shapes back to current joint positions - if (_ragdollPoints.size() == numStates) { - int numStates = _jointStates.size(); - for (int i = 0; i < numStates; ++i) { - // ragdollPoints start in model-frame - _ragdollPoints[i].initPosition(_jointStates.at(i).getPosition()); - } - } - enforceRagdollConstraints(); + _ragdoll->slamPointPositions(); + _ragdoll->enforceRagdollConstraints(); } void SkeletonModel::moveShapesTowardJoints(float deltaTime) { @@ -747,8 +663,9 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) { // unravel a skelton that has become tangled in its constraints. So let's keep this // around for a while just in case. const int numStates = _jointStates.size(); - assert(_jointStates.size() == _ragdollPoints.size()); - if (_ragdollPoints.size() != numStates) { + QVector& ragdollPoints = _ragdoll->getRagdollPoints(); + assert(_jointStates.size() == ragdollPoints.size()); + if (ragdollPoints.size() != numStates) { return; } @@ -757,32 +674,22 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) { float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f); float oneMinusFraction = 1.0f - fraction; - glm::vec3 simulationTranslation = getTranslationInSimulationFrame(); + glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame(); for (int i = 0; i < numStates; ++i) { // ragdollPoints are in simulation-frame but jointStates are in model-frame - _ragdollPoints[i].initPosition(oneMinusFraction * _ragdollPoints[i]._position + + ragdollPoints[i].initPosition(oneMinusFraction * ragdollPoints[i]._position + fraction * (simulationTranslation + _rotation * (_jointStates.at(i).getPosition()))); } } -void SkeletonModel::updateMuscles() { - int numConstraints = _muscleConstraints.size(); - for (int i = 0; i < numConstraints; ++i) { - MuscleConstraint* constraint = _muscleConstraints[i]; - int j = constraint->getParentIndex(); - int k = constraint->getChildIndex(); - assert(j != -1 && k != -1); - // ragdollPoints are in simulation-frame but jointStates are in model-frame - constraint->setChildOffset(_rotation * (_jointStates.at(k).getPosition() - _jointStates.at(j).getPosition())); - } -} - void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { // compute default joint transforms int numStates = _jointStates.size(); QVector transforms; transforms.fill(glm::mat4(), numStates); + QVector& ragdollPoints = _ragdoll->getRagdollPoints(); + // compute the default transforms and slam the ragdoll positions accordingly // (which puts the shapes where we want them) for (int i = 0; i < numStates; i++) { @@ -791,7 +698,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { int parentIndex = joint.parentIndex; if (parentIndex == -1) { transforms[i] = _jointStates[i].getTransform(); - _ragdollPoints[i].initPosition(extractTranslation(transforms[i])); + ragdollPoints[i].initPosition(extractTranslation(transforms[i])); continue; } @@ -799,7 +706,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform; // setting the ragdollPoints here slams the VerletShapes into their default positions - _ragdollPoints[i].initPosition(extractTranslation(transforms[i])); + ragdollPoints[i].initPosition(extractTranslation(transforms[i])); } // compute bounding box that encloses all shapes @@ -918,9 +825,12 @@ const int BALL_SUBDIVISIONS = 10; // virtual void SkeletonModel::renderJointCollisionShapes(float alpha) { + if (!_ragdoll) { + return; + } glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); - glm::vec3 simulationTranslation = getTranslationInSimulationFrame(); + glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame(); for (int i = 0; i < _shapes.size(); i++) { Shape* shape = _shapes[i]; if (!shape) { diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 55fedbcb6b..b0d6ed7325 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -15,18 +15,20 @@ #include "renderer/Model.h" #include -#include +#include "SkeletonRagdoll.h" class Avatar; class MuscleConstraint; +class SkeletonRagdoll; /// A skeleton loaded from a model. -class SkeletonModel : public Model, public Ragdoll { +class SkeletonModel : public Model { Q_OBJECT public: SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL); + ~SkeletonModel(); void setJointStates(QVector states); @@ -96,12 +98,11 @@ public: bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; virtual void updateVisibleJointStates(); - - // virtual overrride from Ragdoll - virtual void stepRagdollForward(float deltaTime); + SkeletonRagdoll* buildRagdoll(); + SkeletonRagdoll* getRagdoll() { return _ragdoll; } + void moveShapesTowardJoints(float fraction); - void updateMuscles(); void computeBoundingShape(const FBXGeometry& geometry); void renderBoundingCollisionShapes(float alpha); @@ -115,10 +116,6 @@ public: protected: - // virtual overrrides from Ragdoll - void initRagdollPoints(); - void buildRagdollConstraints(); - void buildShapes(); /// \param jointIndex index of joint in model @@ -147,7 +144,7 @@ private: CapsuleShape _boundingShape; glm::vec3 _boundingShapeLocalOffset; - QVector _muscleConstraints; + SkeletonRagdoll* _ragdoll; }; #endif // hifi_SkeletonModel_h diff --git a/interface/src/avatar/SkeletonRagdoll.cpp b/interface/src/avatar/SkeletonRagdoll.cpp new file mode 100644 index 0000000000..b63197bd2c --- /dev/null +++ b/interface/src/avatar/SkeletonRagdoll.cpp @@ -0,0 +1,148 @@ +// +// SkeletonRagdoll.cpp +// interface/src/avatar +// +// Created by Andrew Meadows 2014.08.14 +// 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 "SkeletonRagdoll.h" +#include "MuscleConstraint.h" +#include "../renderer/Model.h" + +SkeletonRagdoll::SkeletonRagdoll(Model* model) : Ragdoll(), _model(model) { + assert(_model); +} + +SkeletonRagdoll::~SkeletonRagdoll() { +} + +// virtual +void SkeletonRagdoll::stepRagdollForward(float deltaTime) { + setRagdollTransform(_model->getTranslation(), _model->getRotation()); + Ragdoll::stepRagdollForward(deltaTime); + updateMuscles(); + int numConstraints = _muscleConstraints.size(); + for (int i = 0; i < numConstraints; ++i) { + _muscleConstraints[i]->enforce(); + } +} + +void SkeletonRagdoll::slamPointPositions() { + QVector& jointStates = _model->getJointStates(); + int numStates = jointStates.size(); + for (int i = 0; i < numStates; ++i) { + _ragdollPoints[i].initPosition(jointStates.at(i).getPosition()); + } +} + +// virtual +void SkeletonRagdoll::initRagdollPoints() { + clearRagdollConstraintsAndPoints(); + _muscleConstraints.clear(); + + initRagdollTransform(); + // one point for each joint + QVector& jointStates = _model->getJointStates(); + int numStates = jointStates.size(); + _ragdollPoints.fill(VerletPoint(), numStates); + slamPointPositions(); +} + +// virtual +void SkeletonRagdoll::buildRagdollConstraints() { + QVector& jointStates = _model->getJointStates(); + + // NOTE: the length of DistanceConstraints is computed and locked in at this time + // so make sure the ragdoll positions are in a normal configuration before here. + const int numPoints = _ragdollPoints.size(); + assert(numPoints == jointStates.size()); + + float minBone = FLT_MAX; + float maxBone = -FLT_MAX; + QMultiMap families; + for (int i = 0; i < numPoints; ++i) { + const JointState& state = jointStates.at(i); + int parentIndex = state.getParentIndex(); + if (parentIndex == -1) { + FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_ragdollPoints[i])); + _fixedConstraints.push_back(anchor); + } else { + DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex])); + bone->setDistance(state.getDistanceToParent()); + _boneConstraints.push_back(bone); + families.insert(parentIndex, i); + } + float boneLength = glm::length(state.getPositionInParentFrame()); + if (boneLength > maxBone) { + maxBone = boneLength; + } else if (boneLength < minBone) { + minBone = boneLength; + } + } + // Joints that have multiple children effectively have rigid constraints between the children + // in the parent frame, so we add DistanceConstraints between children in the same family. + QMultiMap::iterator itr = families.begin(); + while (itr != families.end()) { + QList children = families.values(itr.key()); + int numChildren = children.size(); + if (numChildren > 1) { + for (int i = 1; i < numChildren; ++i) { + DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]])); + _boneConstraints.push_back(bone); + } + if (numChildren > 2) { + DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]])); + _boneConstraints.push_back(bone); + } + } + ++itr; + } + + float MAX_STRENGTH = 0.6f; + float MIN_STRENGTH = 0.05f; + // each joint gets a MuscleConstraint to its parent + for (int i = 1; i < numPoints; ++i) { + const JointState& state = jointStates.at(i); + int p = state.getParentIndex(); + if (p == -1) { + continue; + } + MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i])); + _muscleConstraints.push_back(constraint); + + // Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length: + // long = weak and short = strong. + constraint->setIndices(p, i); + float boneLength = glm::length(state.getPositionInParentFrame()); + + float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone); + if (!families.contains(i)) { + // Although muscles only pull on the children not parents, nevertheless those joints that have + // parents AND children are more stable than joints at the end such as fingers. For such joints we + // bestow maximum strength which helps reduce wiggle. + strength = MAX_MUSCLE_STRENGTH; + } + constraint->setStrength(strength); + } +} + +void SkeletonRagdoll::updateMuscles() { + QVector& jointStates = _model->getJointStates(); + int numConstraints = _muscleConstraints.size(); + glm::quat rotation = _model->getRotation(); + for (int i = 0; i < numConstraints; ++i) { + MuscleConstraint* constraint = _muscleConstraints[i]; + int j = constraint->getParentIndex(); + int k = constraint->getChildIndex(); + assert(j != -1 && k != -1); + // ragdollPoints are in simulation-frame but jointStates are in model-frame + constraint->setChildOffset(rotation * (jointStates.at(k).getPosition() - jointStates.at(j).getPosition())); + } +} diff --git a/interface/src/avatar/SkeletonRagdoll.h b/interface/src/avatar/SkeletonRagdoll.h new file mode 100644 index 0000000000..e9638eafac --- /dev/null +++ b/interface/src/avatar/SkeletonRagdoll.h @@ -0,0 +1,42 @@ +// +// SkeletonkRagdoll.h +// interface/src/avatar +// +// Created by Andrew Meadows 2014.08.14 +// 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_SkeletonRagdoll_h +#define hifi_SkeletonRagdoll_h + +#include + +#include + +#include "../renderer/JointState.h" + +class MuscleConstraint; +class Model; + +class SkeletonRagdoll : public Ragdoll { +public: + + SkeletonRagdoll(Model* model); + virtual ~SkeletonRagdoll(); + + void slamPointPositions(); + virtual void stepRagdollForward(float deltaTime); + + virtual void initRagdollPoints(); + virtual void buildRagdollConstraints(); + + void updateMuscles(); +private: + Model* _model; + QVector _muscleConstraints; +}; + +#endif // hifi_SkeletonRagdoll_h diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 7a29b61420..da72d43133 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -164,6 +164,9 @@ public: const QVector& getLocalLights() const { return _localLights; } void setShowTrueJointTransforms(bool show) { _showTrueJointTransforms = show; } + + QVector& getJointStates() { return _jointStates; } + const QVector& getJointStates() const { return _jointStates; } protected: QSharedPointer _geometry; diff --git a/libraries/shared/src/Ragdoll.h b/libraries/shared/src/Ragdoll.h index ec5a561d1a..75f263149e 100644 --- a/libraries/shared/src/Ragdoll.h +++ b/libraries/shared/src/Ragdoll.h @@ -52,11 +52,11 @@ public: void setMassScale(float scale); float getMassScale() const { return _massScale; } -protected: void clearRagdollConstraintsAndPoints(); virtual void initRagdollPoints() = 0; virtual void buildRagdollConstraints() = 0; +protected: float _massScale; glm::vec3 _ragdollTranslation; // world-frame glm::quat _ragdollRotation; // world-frame From 60d411ead50f5701be7f6b88652ccd9ebe9610ba Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 Aug 2014 14:41:51 -0700 Subject: [PATCH 05/10] cleanup Ragdoll API (less "ragdoll" qualifiers) --- interface/src/avatar/SkeletonModel.cpp | 16 ++++---- interface/src/avatar/SkeletonRagdoll.cpp | 30 +++++++-------- interface/src/avatar/SkeletonRagdoll.h | 6 +-- libraries/shared/src/PhysicsSimulation.cpp | 28 +++++++------- libraries/shared/src/Ragdoll.cpp | 44 +++++++++++----------- libraries/shared/src/Ragdoll.h | 26 ++++++------- 6 files changed, 75 insertions(+), 75 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 90145f0af8..ffe711b03b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -497,7 +497,7 @@ void SkeletonModel::renderRagdoll() { if (!_ragdoll) { return; } - const QVector& points = _ragdoll->getRagdollPoints(); + const QVector& points = _ragdoll->getPoints(); const int BALL_SUBDIVISIONS = 6; glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); @@ -531,7 +531,7 @@ void SkeletonModel::updateVisibleJointStates() { // no need to update visible transforms return; } - const QVector& ragdollPoints = _ragdoll->getRagdollPoints(); + const QVector& ragdollPoints = _ragdoll->getPoints(); QVector points; points.reserve(_jointStates.size()); glm::quat invRotation = glm::inverse(_rotation); @@ -599,8 +599,8 @@ void SkeletonModel::buildShapes() { if (!_ragdoll) { _ragdoll = new SkeletonRagdoll(this); } - _ragdoll->initRagdollPoints(); - QVector& points = _ragdoll->getRagdollPoints(); + _ragdoll->initPoints(); + QVector& points = _ragdoll->getPoints(); float massScale = _ragdoll->getMassScale(); @@ -651,11 +651,11 @@ void SkeletonModel::buildShapes() { // joints that are currently colliding. disableCurrentSelfCollisions(); - _ragdoll->buildRagdollConstraints(); + _ragdoll->buildConstraints(); // ... then move shapes back to current joint positions _ragdoll->slamPointPositions(); - _ragdoll->enforceRagdollConstraints(); + _ragdoll->enforceConstraints(); } void SkeletonModel::moveShapesTowardJoints(float deltaTime) { @@ -663,7 +663,7 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) { // unravel a skelton that has become tangled in its constraints. So let's keep this // around for a while just in case. const int numStates = _jointStates.size(); - QVector& ragdollPoints = _ragdoll->getRagdollPoints(); + QVector& ragdollPoints = _ragdoll->getPoints(); assert(_jointStates.size() == ragdollPoints.size()); if (ragdollPoints.size() != numStates) { return; @@ -688,7 +688,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { QVector transforms; transforms.fill(glm::mat4(), numStates); - QVector& ragdollPoints = _ragdoll->getRagdollPoints(); + QVector& ragdollPoints = _ragdoll->getPoints(); // compute the default transforms and slam the ragdoll positions accordingly // (which puts the shapes where we want them) diff --git a/interface/src/avatar/SkeletonRagdoll.cpp b/interface/src/avatar/SkeletonRagdoll.cpp index b63197bd2c..503f38f00f 100644 --- a/interface/src/avatar/SkeletonRagdoll.cpp +++ b/interface/src/avatar/SkeletonRagdoll.cpp @@ -24,9 +24,9 @@ SkeletonRagdoll::~SkeletonRagdoll() { } // virtual -void SkeletonRagdoll::stepRagdollForward(float deltaTime) { - setRagdollTransform(_model->getTranslation(), _model->getRotation()); - Ragdoll::stepRagdollForward(deltaTime); +void SkeletonRagdoll::stepForward(float deltaTime) { + setTransform(_model->getTranslation(), _model->getRotation()); + Ragdoll::stepForward(deltaTime); updateMuscles(); int numConstraints = _muscleConstraints.size(); for (int i = 0; i < numConstraints; ++i) { @@ -38,30 +38,30 @@ void SkeletonRagdoll::slamPointPositions() { QVector& jointStates = _model->getJointStates(); int numStates = jointStates.size(); for (int i = 0; i < numStates; ++i) { - _ragdollPoints[i].initPosition(jointStates.at(i).getPosition()); + _points[i].initPosition(jointStates.at(i).getPosition()); } } // virtual -void SkeletonRagdoll::initRagdollPoints() { - clearRagdollConstraintsAndPoints(); +void SkeletonRagdoll::initPoints() { + clearConstraintsAndPoints(); _muscleConstraints.clear(); - initRagdollTransform(); + initTransform(); // one point for each joint QVector& jointStates = _model->getJointStates(); int numStates = jointStates.size(); - _ragdollPoints.fill(VerletPoint(), numStates); + _points.fill(VerletPoint(), numStates); slamPointPositions(); } // virtual -void SkeletonRagdoll::buildRagdollConstraints() { +void SkeletonRagdoll::buildConstraints() { QVector& jointStates = _model->getJointStates(); // NOTE: the length of DistanceConstraints is computed and locked in at this time // so make sure the ragdoll positions are in a normal configuration before here. - const int numPoints = _ragdollPoints.size(); + const int numPoints = _points.size(); assert(numPoints == jointStates.size()); float minBone = FLT_MAX; @@ -71,10 +71,10 @@ void SkeletonRagdoll::buildRagdollConstraints() { const JointState& state = jointStates.at(i); int parentIndex = state.getParentIndex(); if (parentIndex == -1) { - FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_ragdollPoints[i])); + FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_points[i])); _fixedConstraints.push_back(anchor); } else { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex])); + DistanceConstraint* bone = new DistanceConstraint(&(_points[i]), &(_points[parentIndex])); bone->setDistance(state.getDistanceToParent()); _boneConstraints.push_back(bone); families.insert(parentIndex, i); @@ -94,11 +94,11 @@ void SkeletonRagdoll::buildRagdollConstraints() { int numChildren = children.size(); if (numChildren > 1) { for (int i = 1; i < numChildren; ++i) { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]])); + DistanceConstraint* bone = new DistanceConstraint(&(_points[children[i-1]]), &(_points[children[i]])); _boneConstraints.push_back(bone); } if (numChildren > 2) { - DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]])); + DistanceConstraint* bone = new DistanceConstraint(&(_points[children[numChildren-1]]), &(_points[children[0]])); _boneConstraints.push_back(bone); } } @@ -114,7 +114,7 @@ void SkeletonRagdoll::buildRagdollConstraints() { if (p == -1) { continue; } - MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i])); + MuscleConstraint* constraint = new MuscleConstraint(&(_points[p]), &(_points[i])); _muscleConstraints.push_back(constraint); // Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length: diff --git a/interface/src/avatar/SkeletonRagdoll.h b/interface/src/avatar/SkeletonRagdoll.h index e9638eafac..f9f99395ac 100644 --- a/interface/src/avatar/SkeletonRagdoll.h +++ b/interface/src/avatar/SkeletonRagdoll.h @@ -28,10 +28,10 @@ public: virtual ~SkeletonRagdoll(); void slamPointPositions(); - virtual void stepRagdollForward(float deltaTime); + virtual void stepForward(float deltaTime); - virtual void initRagdollPoints(); - virtual void buildRagdollConstraints(); + virtual void initPoints(); + virtual void buildConstraints(); void updateMuscles(); private: diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index 6dcafc6a54..84f8282c9d 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -47,12 +47,12 @@ PhysicsSimulation::~PhysicsSimulation() { void PhysicsSimulation::setRagdoll(Ragdoll* ragdoll) { if (_ragdoll != ragdoll) { if (_ragdoll) { - _ragdoll->_ragdollSimulation = NULL; + _ragdoll->_simulation = NULL; } _ragdoll = ragdoll; if (_ragdoll) { - assert(!(_ragdoll->_ragdollSimulation)); - _ragdoll->_ragdollSimulation = this; + assert(!(_ragdoll->_simulation)); + _ragdoll->_simulation = this; } } } @@ -144,7 +144,7 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) { // list is full return false; } - if (doll->_ragdollSimulation == this) { + if (doll->_simulation == this) { for (int i = 0; i < numDolls; ++i) { if (doll == _otherRagdolls[i]) { // already in list @@ -153,8 +153,8 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) { } } // add to list - assert(!(doll->_ragdollSimulation)); - doll->_ragdollSimulation = this; + assert(!(doll->_simulation)); + doll->_simulation = this; _otherRagdolls.push_back(doll); // set the massScale of otherRagdolls artificially high @@ -164,7 +164,7 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) { void PhysicsSimulation::removeRagdoll(Ragdoll* doll) { int numDolls = _otherRagdolls.size(); - if (doll->_ragdollSimulation != this) { + if (doll->_simulation != this) { return; } for (int i = 0; i < numDolls; ++i) { @@ -178,7 +178,7 @@ void PhysicsSimulation::removeRagdoll(Ragdoll* doll) { _otherRagdolls.pop_back(); _otherRagdolls[i] = lastDoll; } - doll->_ragdollSimulation = NULL; + doll->_simulation = NULL; doll->setMassScale(1.0f); break; } @@ -199,9 +199,9 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter int numDolls = _otherRagdolls.size(); { PerformanceTimer perfTimer("enforce"); - _ragdoll->enforceRagdollConstraints(); + _ragdoll->enforceConstraints(); for (int i = 0; i < numDolls; ++i) { - _otherRagdolls[i]->enforceRagdollConstraints(); + _otherRagdolls[i]->enforceConstraints(); } } @@ -214,9 +214,9 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter { // enforce constraints PerformanceTimer perfTimer("enforce"); - error = _ragdoll->enforceRagdollConstraints(); + error = _ragdoll->enforceConstraints(); for (int i = 0; i < numDolls; ++i) { - error = glm::max(error, _otherRagdolls[i]->enforceRagdollConstraints()); + error = glm::max(error, _otherRagdolls[i]->enforceConstraints()); } } enforceContactConstraints(); @@ -230,10 +230,10 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter void PhysicsSimulation::moveRagdolls(float deltaTime) { PerformanceTimer perfTimer("integrate"); - _ragdoll->stepRagdollForward(deltaTime); + _ragdoll->stepForward(deltaTime); int numDolls = _otherRagdolls.size(); for (int i = 0; i < numDolls; ++i) { - _otherRagdolls[i]->stepRagdollForward(deltaTime); + _otherRagdolls[i]->stepForward(deltaTime); } } diff --git a/libraries/shared/src/Ragdoll.cpp b/libraries/shared/src/Ragdoll.cpp index 80e3c27e04..7eeaf0b609 100644 --- a/libraries/shared/src/Ragdoll.cpp +++ b/libraries/shared/src/Ragdoll.cpp @@ -19,27 +19,27 @@ #include "PhysicsSimulation.h" #include "SharedUtil.h" // for EPSILON -Ragdoll::Ragdoll() : _massScale(1.0f), _ragdollTranslation(0.0f), _translationInSimulationFrame(0.0f), _ragdollSimulation(NULL) { +Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f), _simulation(NULL) { } Ragdoll::~Ragdoll() { - clearRagdollConstraintsAndPoints(); - if (_ragdollSimulation) { - _ragdollSimulation->removeRagdoll(this); + clearConstraintsAndPoints(); + if (_simulation) { + _simulation->removeRagdoll(this); } } -void Ragdoll::stepRagdollForward(float deltaTime) { - if (_ragdollSimulation) { - updateSimulationTransforms(_ragdollTranslation - _ragdollSimulation->getTranslation(), _ragdollRotation); +void Ragdoll::stepForward(float deltaTime) { + if (_simulation) { + updateSimulationTransforms(_translation - _simulation->getTranslation(), _rotation); } - int numPoints = _ragdollPoints.size(); + int numPoints = _points.size(); for (int i = 0; i < numPoints; ++i) { - _ragdollPoints[i].integrateForward(); + _points[i].integrateForward(); } } -void Ragdoll::clearRagdollConstraintsAndPoints() { +void Ragdoll::clearConstraintsAndPoints() { int numConstraints = _boneConstraints.size(); for (int i = 0; i < numConstraints; ++i) { delete _boneConstraints[i]; @@ -50,10 +50,10 @@ void Ragdoll::clearRagdollConstraintsAndPoints() { delete _fixedConstraints[i]; } _fixedConstraints.clear(); - _ragdollPoints.clear(); + _points.clear(); } -float Ragdoll::enforceRagdollConstraints() { +float Ragdoll::enforceConstraints() { float maxDistance = 0.0f; // enforce the bone constraints first int numConstraints = _boneConstraints.size(); @@ -68,16 +68,16 @@ float Ragdoll::enforceRagdollConstraints() { return maxDistance; } -void Ragdoll::initRagdollTransform() { - _ragdollTranslation = glm::vec3(0.0f); - _ragdollRotation = glm::quat(); +void Ragdoll::initTransform() { + _translation = glm::vec3(0.0f); + _rotation = glm::quat(); _translationInSimulationFrame = glm::vec3(0.0f); _rotationInSimulationFrame = glm::quat(); } -void Ragdoll::setRagdollTransform(const glm::vec3& translation, const glm::quat& rotation) { - _ragdollTranslation = translation; - _ragdollRotation = rotation; +void Ragdoll::setTransform(const glm::vec3& translation, const glm::quat& rotation) { + _translation = translation; + _rotation = rotation; } void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm::quat& rotation) { @@ -93,9 +93,9 @@ void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm glm::quat deltaRotation = rotation * glm::inverse(_rotationInSimulationFrame); // apply the deltas to all ragdollPoints - int numPoints = _ragdollPoints.size(); + int numPoints = _points.size(); for (int i = 0; i < numPoints; ++i) { - _ragdollPoints[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame); + _points[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame); } // remember the current transform @@ -109,9 +109,9 @@ void Ragdoll::setMassScale(float scale) { scale = glm::clamp(glm::abs(scale), MIN_SCALE, MAX_SCALE); if (scale != _massScale) { float rescale = scale / _massScale; - int numPoints = _ragdollPoints.size(); + int numPoints = _points.size(); for (int i = 0; i < numPoints; ++i) { - _ragdollPoints[i].setMass(rescale * _ragdollPoints[i].getMass()); + _points[i].setMass(rescale * _points[i].getMass()); } _massScale = scale; } diff --git a/libraries/shared/src/Ragdoll.h b/libraries/shared/src/Ragdoll.h index 75f263149e..c82295d9a5 100644 --- a/libraries/shared/src/Ragdoll.h +++ b/libraries/shared/src/Ragdoll.h @@ -33,44 +33,44 @@ public: Ragdoll(); virtual ~Ragdoll(); - virtual void stepRagdollForward(float deltaTime); + virtual void stepForward(float deltaTime); /// \return max distance of point movement - float enforceRagdollConstraints(); + float enforceConstraints(); // both const and non-const getPoints() - const QVector& getRagdollPoints() const { return _ragdollPoints; } - QVector& getRagdollPoints() { return _ragdollPoints; } + const QVector& getPoints() const { return _points; } + QVector& getPoints() { return _points; } - void initRagdollTransform(); + void initTransform(); /// set the translation and rotation of the Ragdoll and adjust all VerletPoints. - void setRagdollTransform(const glm::vec3& translation, const glm::quat& rotation); + void setTransform(const glm::vec3& translation, const glm::quat& rotation); const glm::vec3& getTranslationInSimulationFrame() const { return _translationInSimulationFrame; } void setMassScale(float scale); float getMassScale() const { return _massScale; } - void clearRagdollConstraintsAndPoints(); - virtual void initRagdollPoints() = 0; - virtual void buildRagdollConstraints() = 0; + void clearConstraintsAndPoints(); + virtual void initPoints() = 0; + virtual void buildConstraints() = 0; protected: float _massScale; - glm::vec3 _ragdollTranslation; // world-frame - glm::quat _ragdollRotation; // world-frame + glm::vec3 _translation; // world-frame + glm::quat _rotation; // world-frame glm::vec3 _translationInSimulationFrame; glm::quat _rotationInSimulationFrame; - QVector _ragdollPoints; + QVector _points; QVector _boneConstraints; QVector _fixedConstraints; private: void updateSimulationTransforms(const glm::vec3& translation, const glm::quat& rotation); friend class PhysicsSimulation; - PhysicsSimulation* _ragdollSimulation; + PhysicsSimulation* _simulation; }; #endif // hifi_Ragdoll_h From 98d27ad2b53656da0f2b38761f6720d2a0dc45df Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 Aug 2014 15:17:03 -0700 Subject: [PATCH 06/10] more correct names for ContactPoint API renamed (and disabled) the useless enforce() to applyFriction() changed the buildConstraints() method to more correct name: enforce() will eventually change how ContactPoint actually works, but later --- libraries/shared/src/ContactPoint.cpp | 39 ++++++++++++---------- libraries/shared/src/ContactPoint.h | 4 +-- libraries/shared/src/PhysicsSimulation.cpp | 24 ++++++------- libraries/shared/src/PhysicsSimulation.h | 4 +-- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/libraries/shared/src/ContactPoint.cpp b/libraries/shared/src/ContactPoint.cpp index 4c1cf7b842..4f1cf87da5 100644 --- a/libraries/shared/src/ContactPoint.cpp +++ b/libraries/shared/src/ContactPoint.cpp @@ -88,25 +88,7 @@ ContactPoint::ContactPoint(const CollisionInfo& collision, quint32 frame) : } } -// virtual float ContactPoint::enforce() { - for (int i = 0; i < _numPoints; ++i) { - glm::vec3& position = _points[i]->_position; - // TODO: use a fast distance approximation - float newDistance = glm::distance(_contactPoint, position); - float constrainedDistance = _distances[i]; - // NOTE: these "distance" constraints only push OUT, don't pull IN. - if (newDistance > EPSILON && newDistance < constrainedDistance) { - glm::vec3 direction = (_contactPoint - position) / newDistance; - glm::vec3 center = 0.5f * (_contactPoint + position); - _contactPoint = center + (0.5f * constrainedDistance) * direction; - position = center - (0.5f * constrainedDistance) * direction; - } - } - return 0.0f; -} - -void ContactPoint::buildConstraints() { glm::vec3 pointA = _shapeA->getTranslation() + _offsetA; glm::vec3 pointB = _shapeB->getTranslation() + _offsetB; glm::vec3 penetration = pointA - pointB; @@ -153,6 +135,27 @@ void ContactPoint::buildConstraints() { _distances[i] = glm::length(glm::length(_offsets[i])); } } + return 0.0f; +} + +// virtual +void ContactPoint::applyFriction() { + // TODO: Andrew to re-implement this + /* + for (int i = 0; i < _numPoints; ++i) { + glm::vec3& position = _points[i]->_position; + // TODO: use a fast distance approximation + float newDistance = glm::distance(_contactPoint, position); + float constrainedDistance = _distances[i]; + // NOTE: these "distance" constraints only push OUT, don't pull IN. + if (newDistance > EPSILON && newDistance < constrainedDistance) { + glm::vec3 direction = (_contactPoint - position) / newDistance; + glm::vec3 center = 0.5f * (_contactPoint + position); + _contactPoint = center + (0.5f * constrainedDistance) * direction; + position = center - (0.5f * constrainedDistance) * direction; + } + } + */ } void ContactPoint::updateContact(const CollisionInfo& collision, quint32 frame) { diff --git a/libraries/shared/src/ContactPoint.h b/libraries/shared/src/ContactPoint.h index 5257fabee0..d584945970 100644 --- a/libraries/shared/src/ContactPoint.h +++ b/libraries/shared/src/ContactPoint.h @@ -26,8 +26,8 @@ public: ContactPoint(const CollisionInfo& collision, quint32 frame); virtual float enforce(); - - void buildConstraints(); + + void applyFriction(); void updateContact(const CollisionInfo& collision, quint32 frame); quint32 getLastFrame() const { return _lastFrame; } diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index 84f8282c9d..a62b3816af 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -195,7 +195,7 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter quint64 expiry = startTime + maxUsec; moveRagdolls(deltaTime); - buildContactConstraints(); + enforceContacts(); int numDolls = _otherRagdolls.size(); { PerformanceTimer perfTimer("enforce"); @@ -219,7 +219,7 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter error = glm::max(error, _otherRagdolls[i]->enforceConstraints()); } } - enforceContactConstraints(); + applyContactFriction(); ++iterations; now = usecTimestampNow(); @@ -288,16 +288,7 @@ void PhysicsSimulation::resolveCollisions() { } } -void PhysicsSimulation::buildContactConstraints() { - PerformanceTimer perfTimer("contacts"); - QMap::iterator itr = _contacts.begin(); - while (itr != _contacts.end()) { - itr.value().buildConstraints(); - ++itr; - } -} - -void PhysicsSimulation::enforceContactConstraints() { +void PhysicsSimulation::enforceContacts() { PerformanceTimer perfTimer("contacts"); QMap::iterator itr = _contacts.begin(); while (itr != _contacts.end()) { @@ -306,6 +297,15 @@ void PhysicsSimulation::enforceContactConstraints() { } } +void PhysicsSimulation::applyContactFriction() { + PerformanceTimer perfTimer("contacts"); + QMap::iterator itr = _contacts.begin(); + while (itr != _contacts.end()) { + itr.value().applyFriction(); + ++itr; + } +} + void PhysicsSimulation::updateContacts() { PerformanceTimer perfTimer("contacts"); int numCollisions = _collisions.size(); diff --git a/libraries/shared/src/PhysicsSimulation.h b/libraries/shared/src/PhysicsSimulation.h index 61ab1bf177..881007208b 100644 --- a/libraries/shared/src/PhysicsSimulation.h +++ b/libraries/shared/src/PhysicsSimulation.h @@ -56,8 +56,8 @@ protected: void computeCollisions(); void resolveCollisions(); - void buildContactConstraints(); - void enforceContactConstraints(); + void enforceContacts(); + void applyContactFriction(); void updateContacts(); void pruneContacts(); From 432c14408c05d1f99a7b09bed4c4a619f2d98035 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 Aug 2014 15:29:57 -0700 Subject: [PATCH 07/10] removed hackery from ContactPoint enforcement --- libraries/shared/src/ContactPoint.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/shared/src/ContactPoint.cpp b/libraries/shared/src/ContactPoint.cpp index 4f1cf87da5..27a496d445 100644 --- a/libraries/shared/src/ContactPoint.cpp +++ b/libraries/shared/src/ContactPoint.cpp @@ -98,14 +98,6 @@ float ContactPoint::enforce() { // the contact point will be the average of the two points on the shapes _contactPoint = 0.5f * (pointA + pointB); - // TODO: Andrew to compute more correct lagrangian weights that provide a more realistic response. - // - // HACK: since the weights are naively equal for all points (which is what the above TODO is about) we - // don't want to use the full-strength delta because otherwise there can be annoying oscillations. We - // reduce this problem by in the short-term by attenuating the delta that is applied, the tradeoff is - // that this makes it easier for limbs to tunnel through during collisions. - const float HACK_STRENGTH = 0.5f; - if (constraintViolation) { for (int i = 0; i < _numPoints; ++i) { VerletPoint* point = _points[i]; @@ -128,7 +120,7 @@ float ContactPoint::enforce() { glm::vec3 targetPosition = point->_position + delta; _distances[i] = glm::distance(_contactPoint, targetPosition); - point->_position += HACK_STRENGTH * delta; + point->_position += delta; } } else { for (int i = 0; i < _numPoints; ++i) { @@ -140,7 +132,7 @@ float ContactPoint::enforce() { // virtual void ContactPoint::applyFriction() { - // TODO: Andrew to re-implement this + // TODO: Andrew to re-implement this in a different way /* for (int i = 0; i < _numPoints; ++i) { glm::vec3& position = _points[i]->_position; From cac4e5ba6c5d59dc243ac99c53d6b97dd5b4e7fd Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sun, 17 Aug 2014 14:08:07 -0700 Subject: [PATCH 08/10] improved use of MovingMinMaxAvg<> for better performance, added execution timing stats --- tests/jitter/src/main.cpp | 119 ++++++++++++++++++++++++++++++++------ 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/tests/jitter/src/main.cpp b/tests/jitter/src/main.cpp index c27a791d5b..e70435b543 100644 --- a/tests/jitter/src/main.cpp +++ b/tests/jitter/src/main.cpp @@ -16,12 +16,14 @@ #include #include -#include // for MovingMinMaxAvg +#include #include -#include #include // for usecTimestampNow +#include +#include const quint64 MSEC_TO_USEC = 1000; +const quint64 LARGE_STATS_TIME = 500; // we don't expect stats calculation to take more than this many usecs void runSend(const char* addressOption, int port, int gap, int size, int report); void runReceive(const char* addressOption, int port, int gap, int size, int report); @@ -77,25 +79,37 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) servaddr.sin_addr.s_addr = inet_addr(addressOption); servaddr.sin_port = htons(port); - - const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap; + const int SAMPLES_FOR_SECOND = 1000000 / gap; + std::cout << "SAMPLES_FOR_SECOND:" << SAMPLES_FOR_SECOND << "\n"; + const int INTERVALS_PER_30_SECONDS = 30; + std::cout << "INTERVALS_PER_30_SECONDS:" << INTERVALS_PER_30_SECONDS << "\n"; + const int SAMPLES_FOR_30_SECONDS = 30 * SAMPLES_FOR_SECOND; std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n"; + const int REPORTS_FOR_30_SECONDS = 30 * MSECS_PER_SECOND / report; + std::cout << "REPORTS_FOR_30_SECONDS:" << REPORTS_FOR_30_SECONDS << "\n"; - const int SAMPLES_PER_REPORT = report * MSEC_TO_USEC / gap; - std::cout << "SAMPLES_PER_REPORT:" << SAMPLES_PER_REPORT << "\n"; - + int intervalsPerReport = report / MSEC_TO_USEC; + if (intervalsPerReport < 1) { + intervalsPerReport = 1; + } + std::cout << "intervalsPerReport:" << intervalsPerReport << "\n"; + MovingMinMaxAvg timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS); + MovingMinMaxAvg timeGapsPerReport(SAMPLES_FOR_SECOND, intervalsPerReport); char* outputBuffer = new char[size]; memset(outputBuffer, 0, size); quint16 outgoingSequenceNumber = 0; - MovingMinMaxAvg timeGaps(1, SAMPLES_FOR_30_SECONDS); - MovingMinMaxAvg timeGapsPerReport(1, SAMPLES_PER_REPORT); StDev stDevReportInterval; StDev stDev30s; StDev stDev; + + SimpleMovingAverage averageNetworkTime(SAMPLES_FOR_30_SECONDS); + SimpleMovingAverage averageStatsCalcultionTime(SAMPLES_FOR_30_SECONDS); + float lastStatsCalculationTime = 0.0f; // we add out stats calculation time in the next calculation window + bool hasStatsCalculationTime = false; quint64 last = usecTimestampNow(); quint64 lastReport = 0; @@ -111,19 +125,37 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) // pack seq num memcpy(outputBuffer, &outgoingSequenceNumber, sizeof(quint16)); + quint64 networkStart = usecTimestampNow(); int n = sendto(sockfd, outputBuffer, size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); + quint64 networkEnd = usecTimestampNow(); + float networkElapsed = (float)(networkEnd - networkStart); + if (n < 0) { std::cout << "Send error: " << strerror(errno) << "\n"; } outgoingSequenceNumber++; + quint64 statsCalcultionStart = usecTimestampNow(); + int gapDifferece = actualGap - gap; + timeGaps.update(gapDifferece); timeGapsPerReport.update(gapDifferece); stDev.addValue(gapDifferece); stDev30s.addValue(gapDifferece); stDevReportInterval.addValue(gapDifferece); last = now; + + // track out network time and stats calculation times + averageNetworkTime.updateAverage(networkElapsed); + + // for our stats calculation time, we actually delay the updating by one sample. + // we do this so that the calculation of the average timing for the stats calculation + // happen inside of the calculation processing. This ensures that tracking stats on + // stats calculation doesn't side effect the remaining running time. + if (hasStatsCalculationTime) { + averageStatsCalcultionTime.updateAverage(lastStatsCalculationTime); + } if (now - lastReport >= (report * MSEC_TO_USEC)) { @@ -144,6 +176,9 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" + << "Average Execution Times Last 30s:\n" + << " network: " << averageNetworkTime.getAverage() << " usecs average\n" + << " stats: " << averageStatsCalcultionTime.getAverage() << " usecs average" << "\n"; stDevReportInterval.reset(); @@ -153,6 +188,14 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) lastReport = now; } + + quint64 statsCalcultionEnd = usecTimestampNow(); + lastStatsCalculationTime = (float)(statsCalcultionEnd - statsCalcultionStart); + if (lastStatsCalculationTime > LARGE_STATS_TIME) { + qDebug() << "WARNING -- unexpectedly large lastStatsCalculationTime=" << lastStatsCalculationTime; + } + hasStatsCalculationTime = true; + } } delete[] outputBuffer; @@ -184,21 +227,26 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo myaddr.sin_port = htons(port); - const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap; + const int SAMPLES_FOR_SECOND = 1000000 / gap; + std::cout << "SAMPLES_FOR_SECOND:" << SAMPLES_FOR_SECOND << "\n"; + const int INTERVALS_PER_30_SECONDS = 30; + std::cout << "INTERVALS_PER_30_SECONDS:" << INTERVALS_PER_30_SECONDS << "\n"; + const int SAMPLES_FOR_30_SECONDS = 30 * SAMPLES_FOR_SECOND; std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n"; - - const int SAMPLES_PER_REPORT = report * MSEC_TO_USEC / gap; - std::cout << "SAMPLES_PER_REPORT:" << SAMPLES_PER_REPORT << "\n"; - const int REPORTS_FOR_30_SECONDS = 30 * MSECS_PER_SECOND / report; std::cout << "REPORTS_FOR_30_SECONDS:" << REPORTS_FOR_30_SECONDS << "\n"; + int intervalsPerReport = report / MSEC_TO_USEC; + if (intervalsPerReport < 1) { + intervalsPerReport = 1; + } + std::cout << "intervalsPerReport:" << intervalsPerReport << "\n"; + MovingMinMaxAvg timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS); + MovingMinMaxAvg timeGapsPerReport(SAMPLES_FOR_SECOND, intervalsPerReport); char* inputBuffer = new char[size]; memset(inputBuffer, 0, size); - MovingMinMaxAvg timeGaps(1, SAMPLES_FOR_30_SECONDS); - MovingMinMaxAvg timeGapsPerReport(1, SAMPLES_PER_REPORT); SequenceNumberStats seqStats(REPORTS_FOR_30_SECONDS); @@ -206,6 +254,11 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo StDev stDev30s; StDev stDev; + SimpleMovingAverage averageNetworkTime(SAMPLES_FOR_30_SECONDS); + SimpleMovingAverage averageStatsCalcultionTime(SAMPLES_FOR_30_SECONDS); + float lastStatsCalculationTime = 0.0f; // we add out stats calculation time in the next calculation window + bool hasStatsCalculationTime = false; + if (bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { std::cout << "bind failed\n"; return; @@ -213,9 +266,16 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo quint64 last = 0; // first case quint64 lastReport = 0; + + quint64 a,b; while (true) { + + quint64 networkStart = usecTimestampNow(); n = recvfrom(sockfd, inputBuffer, size, 0, NULL, NULL); // we don't care about where it came from + quint64 networkEnd = usecTimestampNow(); + float networkElapsed = (float)(networkEnd - networkStart); + if (n < 0) { std::cout << "Receive error: " << strerror(errno) << "\n"; } @@ -228,8 +288,11 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo last = usecTimestampNow(); std::cout << "first packet received\n"; } else { + + quint64 statsCalcultionStart = usecTimestampNow(); quint64 now = usecTimestampNow(); int actualGap = now - last; + int gapDifferece = actualGap - gap; timeGaps.update(gapDifferece); timeGapsPerReport.update(gapDifferece); @@ -237,6 +300,17 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo stDev30s.addValue(gapDifferece); stDevReportInterval.addValue(gapDifferece); last = now; + + // track out network time and stats calculation times + averageNetworkTime.updateAverage(networkElapsed); + + // for our stats calculation time, we actually delay the updating by one sample. + // we do this so that the calculation of the average timing for the stats calculation + // happen inside of the calculation processing. This ensures that tracking stats on + // stats calculation doesn't side effect the remaining running time. + if (hasStatsCalculationTime) { + averageStatsCalcultionTime.updateAverage(lastStatsCalculationTime); + } if (now - lastReport >= (report * MSEC_TO_USEC)) { @@ -258,9 +332,12 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" + << "Average Execution Times Last 30s:\n" + << " network: " << averageNetworkTime.getAverage() << " usecs average\n" + << " stats: " << averageStatsCalcultionTime.getAverage() << " usecs average" << "\n"; - stDevReportInterval.reset(); + if (stDev30s.getSamples() > SAMPLES_FOR_30_SECONDS) { stDev30s.reset(); } @@ -282,6 +359,14 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo lastReport = now; } + + quint64 statsCalcultionEnd = usecTimestampNow(); + + lastStatsCalculationTime = (float)(statsCalcultionEnd - statsCalcultionStart); + if (lastStatsCalculationTime > LARGE_STATS_TIME) { + qDebug() << "WARNING -- unexpectedly large lastStatsCalculationTime=" << lastStatsCalculationTime; + } + hasStatsCalculationTime = true; } } delete[] inputBuffer; From be94f98a9169eb778b71785d94b321f83fb1a1bd Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sun, 17 Aug 2014 14:10:42 -0700 Subject: [PATCH 09/10] fix warning --- tests/jitter/src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/jitter/src/main.cpp b/tests/jitter/src/main.cpp index e70435b543..8c93b7dbec 100644 --- a/tests/jitter/src/main.cpp +++ b/tests/jitter/src/main.cpp @@ -267,8 +267,6 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo quint64 last = 0; // first case quint64 lastReport = 0; - quint64 a,b; - while (true) { quint64 networkStart = usecTimestampNow(); From 77047b01302b75692a7a751d69ac644c58f8ab0a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 11:25:51 -0700 Subject: [PATCH 10/10] no NULL check on delete SkeletonModel::_ragdoll --- interface/src/avatar/SkeletonModel.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ffe711b03b..4e954af46b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -34,10 +34,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : } SkeletonModel::~SkeletonModel() { - if (_ragdoll) { - delete _ragdoll; - _ragdoll = NULL; - } + delete _ragdoll; + _ragdoll = NULL; } void SkeletonModel::setJointStates(QVector states) {