Merge pull request #3180 from AndrewMeadows/ragdoll

Ragdoll Part4: ragdoll shapes follow target joints, visible joints follow ragdoll shapes
This commit is contained in:
Philip Rosedale 2014-07-17 16:00:02 -07:00
commit 1b265f3adb
10 changed files with 312 additions and 111 deletions

View file

@ -305,13 +305,14 @@ namespace MenuOption {
const QString Attachments = "Attachments...";
const QString AudioNoiseReduction = "Audio Noise Reduction";
const QString AudioScope = "Audio Scope";
const QString AudioScopePause = "Pause Audio Scope";
const QString AudioScopeFrames = "Display Frames";
const QString AudioScopeFiveFrames = "Five";
const QString AudioScopeTwentyFrames = "Twenty";
const QString AudioScopeFiftyFrames = "Fifty";
const QString AudioToneInjection = "Inject Test Tone";
const QString AudioScopeFiveFrames = "Five";
const QString AudioScopeFrames = "Display Frames";
const QString AudioScopePause = "Pause Audio Scope";
const QString AudioScopeTwentyFrames = "Twenty";
const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation";
const QString AudioSpatialProcessing = "Audio Spatial Processing";
const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation";
const QString AudioSpatialProcessingHeadOriented = "Head Oriented";
const QString AudioSpatialProcessingIncludeOriginal = "Includes Network Original";
const QString AudioSpatialProcessingPreDelay = "Add Pre-Delay";
@ -321,14 +322,12 @@ namespace MenuOption {
const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces";
const QString AudioSpatialProcessingStereoSource = "Stereo Source";
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation";
const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation";
const QString AudioToneInjection = "Inject Test Tone";
const QString Avatars = "Avatars";
const QString AvatarsReceiveShadows = "Avatars Receive Shadows";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString BuckyBalls = "Bucky Balls";
const QString StringHair = "String Hair";
const QString CascadedShadows = "Cascaded";
const QString Chat = "Chat...";
const QString ChatCircling = "Chat Circling";
@ -349,14 +348,14 @@ namespace MenuOption {
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";
const QString DisplayModelBounds = "Display Model Bounds";
const QString DisplayModelElementProxy = "Display Model Element Bounds";
const QString DisplayModelElementChildProxies = "Display Model Element Children";
const QString DisplayModelElementProxy = "Display Model Element Bounds";
const QString DisplayTimingDetails = "Display Timing Details";
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
const QString EchoLocalAudio = "Echo Local Audio";
const QString EchoServerAudio = "Echo Server Audio";
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
const QString EnableVRMode = "Enable VR Mode";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
@ -366,7 +365,6 @@ namespace MenuOption {
const QString Faceplus = "Faceplus";
const QString Faceshift = "Faceshift";
const QString FilterSixense = "Smooth Sixense Movement";
const QString LowVelocityFilter = "Low Velocity Filter";
const QString FirstPerson = "First Person";
const QString FocusIndicators = "Focus Indicators";
const QString FrameTimer = "Show Timer";
@ -376,10 +374,9 @@ namespace MenuOption {
const QString GlowMode = "Cycle Glow Mode";
const QString GlowWhenSpeaking = "Glow When Speaking";
const QString GoHome = "Go Home";
const QString GoTo = "Go To...";
const QString GoToDomain = "Go To Domain...";
const QString GoTo = "Go To...";
const QString GoToLocation = "Go To Location...";
const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity";
const QString HandsCollideWithSelf = "Collide With Self";
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";
@ -387,21 +384,23 @@ namespace MenuOption {
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools";
const QString Log = "Log";
const QString Login = "Login";
const QString Log = "Log";
const QString Logout = "Logout";
const QString LookAtVectors = "Look-at Vectors";
const QString LowVelocityFilter = "Low Velocity Filter";
const QString MetavoxelEditor = "Metavoxel Editor...";
const QString Metavoxels = "Metavoxels";
const QString Mirror = "Mirror";
const QString Models = "Models";
const QString ModelOptions = "Model Options";
const QString Models = "Models";
const QString MoveWithLean = "Move with Lean";
const QString MuteAudio = "Mute Microphone";
const QString MuteEnvironment = "Mute Environment";
const QString MyLocations = "My Locations...";
const QString NameLocation = "Name this location";
const QString NewVoxelCullingMode = "New Voxel Culling Mode";
const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity";
const QString OctreeStats = "Voxel and Particle Statistics";
const QString OffAxisProjection = "Off-Axis Projection";
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
@ -421,17 +420,18 @@ namespace MenuOption {
const QString ScriptEditor = "Script Editor...";
const QString SettingsExport = "Export Settings";
const QString SettingsImport = "Import Settings";
const QString SimpleShadows = "Simple";
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
const QString ShowBordersModelNodes = "Show Model Nodes";
const QString ShowBordersParticleNodes = "Show Particle Nodes";
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
const QString ShowIKConstraints = "Show IK Constraints";
const QString SimpleShadows = "Simple";
const QString SixenseMouseInput = "Enable Sixense Mouse Input";
const QString StandOnNearbyFloors = "Stand on nearby floors";
const QString Stars = "Stars";
const QString Stats = "Stats";
const QString StereoAudio = "Stereo Audio";
const QString StopAllScripts = "Stop All Scripts";
const QString StringHair = "String Hair";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString TestPing = "Test Ping";
const QString TransmitterDrive = "Transmitter Drive";

View file

@ -135,6 +135,7 @@ void MyAvatar::simulate(float deltaTime) {
setScale(scale);
Application::getInstance()->getCamera()->setScale(scale);
}
_skeletonModel.setShowTrueJointTransforms(! Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll));
// no extra movement of the hand here any more ...
_handState = HAND_STATE_NULL;
@ -428,6 +429,7 @@ glm::vec3 MyAvatar::getLeftPalmPosition() {
leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation);
return leftHandPosition;
}
glm::vec3 MyAvatar::getRightPalmPosition() {
glm::vec3 rightHandPosition;
getSkeletonModel().getRightHandPosition(rightHandPosition);

View file

@ -10,6 +10,7 @@
//
#include <glm/gtx/transform.hpp>
#include <QMultiMap>
#include <VerletCapsuleShape.h>
#include <VerletSphereShape.h>
@ -69,7 +70,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex);
if (jointIndex != -1) {
JointState& state = _jointStates[jointIndex];
state.setRotationFromBindFrame(prioVR->getJointRotations().at(i), PALM_PRIORITY);
state.setRotationInBindFrame(prioVR->getJointRotations().at(i), PALM_PRIORITY);
}
}
return;
@ -217,7 +218,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
setJointPosition(parentJointIndex, palmPosition + forearm,
glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY);
JointState& parentState = _jointStates[parentJointIndex];
parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY);
parentState.setRotationInBindFrame(palmRotation, PALM_PRIORITY);
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat());
} else {
@ -381,13 +382,13 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c
glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition);
JointState& shoulderState = _jointStates[shoulderJointIndex];
shoulderState.setRotationFromBindFrame(shoulderRotation, PALM_PRIORITY);
shoulderState.setRotationInBindFrame(shoulderRotation, PALM_PRIORITY);
JointState& elbowState = _jointStates[elbowJointIndex];
elbowState.setRotationFromBindFrame(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY);
elbowState.setRotationInBindFrame(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY);
JointState& handState = _jointStates[jointIndex];
handState.setRotationFromBindFrame(rotation, PALM_PRIORITY);
handState.setRotationInBindFrame(rotation, PALM_PRIORITY);
}
bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const {
@ -522,6 +523,7 @@ void SkeletonModel::buildRagdollConstraints() {
const int numPoints = _ragdollPoints.size();
assert(numPoints == _jointStates.size());
QMultiMap<int, int> families;
for (int i = 0; i < numPoints; ++i) {
const JointState& state = _jointStates.at(i);
const FBXJoint& joint = state.getFBXJoint();
@ -532,18 +534,72 @@ void SkeletonModel::buildRagdollConstraints() {
} else {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex]));
_ragdollConstraints.push_back(bone);
families.insert(parentIndex, i);
}
}
// Joints that have multiple children effectively have rigid constraints between the children
// in the parent frame, so we add constraints between children in the same family.
QMultiMap<int, int>::iterator itr = families.begin();
while (itr != families.end()) {
QList<int> children = families.values(itr.key());
if (children.size() > 1) {
for (int i = 1; i < children.size(); ++i) {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]]));
_ragdollConstraints.push_back(bone);
}
}
++itr;
}
}
void SkeletonModel::updateVisibleJointStates() {
Model::updateVisibleJointStates();
// TODO: implement this to move visible joints to agree with joint shape positions
if (_showTrueJointTransforms) {
// no need to update visible transforms
return;
}
QVector<glm::vec3> points;
points.reserve(_jointStates.size());
for (int i = 0; i < _jointStates.size(); i++) {
JointState& state = _jointStates[i];
points.push_back(_ragdollPoints[i]._position);
// get the parent state (this is the state that we want to rotate)
int parentIndex = state.getParentIndex();
if (parentIndex == -1) {
_jointStates[i].slaveVisibleTransform();
continue;
}
JointState& parentState = _jointStates[parentIndex];
// check the grand-parent index (for now we don't want to rotate any root states)
int grandParentIndex = parentState.getParentIndex();
if (grandParentIndex == -1) {
continue;
}
// make sure state's visibleTransform is up to date
const glm::mat4& parentTransform = parentState.getVisibleTransform();
state.computeVisibleTransform(parentTransform);
// we're looking for the rotation that moves visible bone parallel to ragdoll bone
// rotationBetween(jointTip - jointPivot, shapeTip - shapePivot)
glm::quat delta = rotationBetween(state.getVisiblePosition() - extractTranslation(parentTransform),
points[i] - points[parentIndex]);
// apply
parentState.mixVisibleRotationDelta(delta, 0.01f);
// update transforms
parentState.computeVisibleTransform(_jointStates[grandParentIndex].getVisibleTransform());
state.computeVisibleTransform(parentState.getVisibleTransform());
}
}
// virtual
void SkeletonModel::stepRagdollForward(float deltaTime) {
const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.03f;
// NOTE: increasing this timescale reduces vibrations in the ragdoll solution and reduces tunneling
// but makes the shapes slower to follow the body (introduces lag).
const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.05f;
float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f);
moveShapesTowardJoints(fraction);
}
@ -554,7 +610,7 @@ float VERY_BIG_MASS = 1.0e6f;
// virtual
void SkeletonModel::buildShapes() {
if (!_geometry || _rootIndex == -1) {
if (_geometry == NULL || _jointStates.isEmpty()) {
return;
}
@ -610,7 +666,13 @@ void SkeletonModel::buildShapes() {
buildRagdollConstraints();
// ... then move shapes back to current joint positions
moveShapesTowardJoints(1.0f);
if (_ragdollPoints.size() == numStates) {
int numJoints = _jointStates.size();
for (int i = 0; i < numJoints; ++i) {
_ragdollPoints[i]._lastPosition = _ragdollPoints.at(i)._position;
_ragdollPoints[i]._position = _jointStates.at(i).getPosition();
}
}
enforceRagdollConstraints();
}
@ -708,7 +770,7 @@ void SkeletonModel::resetShapePositionsToDefaultPose() {
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.
if (!_geometry || _rootIndex == -1 || _shapes.isEmpty()) {
if (!_geometry || _shapes.isEmpty()) {
// geometry or joints have not yet been created
return;
}

View file

@ -19,6 +19,7 @@
JointState::JointState() :
_animationPriority(0.0f),
_positionInParentFrame(0.0f),
_fbxJoint(NULL),
_constraint(NULL) {
}
@ -27,6 +28,7 @@ JointState::JointState(const JointState& other) : _constraint(NULL) {
_transform = other._transform;
_rotation = other._rotation;
_rotationInConstrainedFrame = other._rotationInConstrainedFrame;
_positionInParentFrame = other._positionInParentFrame;
_animationPriority = other._animationPriority;
_fbxJoint = other._fbxJoint;
// DO NOT copy _constraint
@ -69,6 +71,7 @@ void JointState::copyState(const JointState& state) {
_transform = state._transform;
_rotation = extractRotation(_transform);
_rotationInConstrainedFrame = state._rotationInConstrainedFrame;
_positionInParentFrame = state._positionInParentFrame;
_visibleTransform = state._visibleTransform;
_visibleRotation = extractRotation(_visibleTransform);
@ -76,24 +79,37 @@ void JointState::copyState(const JointState& state) {
// DO NOT copy _fbxJoint or _constraint
}
void JointState::initTransform(const glm::mat4& parentTransform) {
computeTransform(parentTransform);
_positionInParentFrame = glm::inverse(extractRotation(parentTransform)) * (extractTranslation(_transform) - extractTranslation(parentTransform));
}
void JointState::computeTransform(const glm::mat4& parentTransform) {
glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform;
_transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform;
glm::quat rotationInParentFrame = _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
glm::mat4 transformInParentFrame = _fbxJoint->preTransform * glm::mat4_cast(rotationInParentFrame) * _fbxJoint->postTransform;
_transform = parentTransform * glm::translate(_fbxJoint->translation) * transformInParentFrame;
_rotation = extractRotation(_transform);
}
void JointState::computeVisibleTransform(const glm::mat4& parentTransform) {
glm::quat rotationInConstrainedFrame = _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation;
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(rotationInConstrainedFrame) * _fbxJoint->postTransform;
_visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform;
glm::quat rotationInParentFrame = _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation;
glm::mat4 transformInParentFrame = _fbxJoint->preTransform * glm::mat4_cast(rotationInParentFrame) * _fbxJoint->postTransform;
_visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * transformInParentFrame;
_visibleRotation = extractRotation(_visibleTransform);
}
glm::quat JointState::getRotationFromBindToModelFrame() const {
glm::quat JointState::getRotationInBindFrame() const {
return _rotation * _fbxJoint->inverseBindRotation;
}
glm::quat JointState::getRotationInParentFrame() const {
return _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
}
glm::quat JointState::getVisibleRotationInParentFrame() const {
return _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation;
}
void JointState::restoreRotation(float fraction, float priority) {
assert(_fbxJoint != NULL);
if (priority == _animationPriority || _animationPriority == 0.0f) {
@ -102,7 +118,7 @@ void JointState::restoreRotation(float fraction, float priority) {
}
}
void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain) {
void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain) {
// rotation is from bind- to model-frame
assert(_fbxJoint != NULL);
if (priority >= _animationPriority) {
@ -164,12 +180,27 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float
setRotationInConstrainedFrame(targetRotation);
}
void JointState::mixVisibleRotationDelta(const glm::quat& delta, float mixFactor) {
// NOTE: delta is in model-frame
assert(_fbxJoint != NULL);
glm::quat targetRotation = _visibleRotationInConstrainedFrame * glm::inverse(_visibleRotation) * delta * _visibleRotation;
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
//targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor);
targetRotation = safeMix(targetRotation, _rotationInConstrainedFrame, mixFactor);
}
setVisibleRotationInConstrainedFrame(targetRotation);
}
glm::quat JointState::computeParentRotation() const {
// R = Rp * Rpre * r * Rpost
// Rp = R * (Rpre * r * Rpost)^
return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation);
}
glm::quat JointState::computeVisibleParentRotation() const {
return _visibleRotation * glm::inverse(_fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation);
}
void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation) {
glm::quat parentRotation = computeParentRotation();
_rotationInConstrainedFrame = targetRotation;
@ -177,6 +208,12 @@ void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation)
_rotation = parentRotation * _fbxJoint->preRotation * _rotationInConstrainedFrame * _fbxJoint->postRotation;
}
void JointState::setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation) {
glm::quat parentRotation = computeVisibleParentRotation();
_visibleRotationInConstrainedFrame = targetRotation;
_visibleRotation = parentRotation * _fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation;
}
const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const {
assert(_fbxJoint != NULL);
return _fbxJoint->translation;

View file

@ -32,6 +32,7 @@ public:
void updateConstraint();
void copyState(const JointState& state);
void initTransform(const glm::mat4& parentTransform);
void computeTransform(const glm::mat4& parentTransform);
void computeVisibleTransform(const glm::mat4& parentTransform);
@ -45,7 +46,13 @@ public:
glm::vec3 getPosition() const { return extractTranslation(_transform); }
/// \return rotation from bind to model frame
glm::quat getRotationFromBindToModelFrame() const;
glm::quat getRotationInBindFrame() const;
glm::quat getRotationInParentFrame() const;
glm::quat getVisibleRotationInParentFrame() const;
const glm::vec3& getPositionInParentFrame() const { return _positionInParentFrame; }
int getParentIndex() const { return _fbxJoint->parentIndex; }
/// \param rotation rotation of joint in model-frame
void setRotation(const glm::quat& rotation, bool constrain, float priority);
@ -59,6 +66,7 @@ public:
/// \param mixFactor fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
/// \param priority priority level of this animation blend
void mixRotationDelta(const glm::quat& delta, float mixFactor, float priority = 1.0f);
void mixVisibleRotationDelta(const glm::quat& delta, float mixFactor);
/// Blends a fraciton of default pose into joint rotation.
/// \param fraction fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
@ -68,9 +76,10 @@ public:
/// \param rotation is from bind- to model-frame
/// computes and sets new _rotationInConstrainedFrame
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
void setRotationInConstrainedFrame(const glm::quat& targetRotation);
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
const glm::vec3& getDefaultTranslationInConstrainedFrame() const;
@ -82,17 +91,19 @@ public:
float _animationPriority; // the priority of the animation affecting this joint
private:
/// \return parent model-frame rotation
// (used to keep _rotation consistent when modifying _rotationInWorldFrame directly)
glm::quat computeParentRotation() const;
glm::quat computeVisibleParentRotation() const;
private:
/// debug helper function
void loadBindRotation();
glm::mat4 _transform; // joint- to model-frame
glm::quat _rotation; // joint- to model-frame
glm::quat _rotationInConstrainedFrame; // rotation in frame where angular constraints would be applied
glm::vec3 _positionInParentFrame; // only changes when the Model is scaled
glm::mat4 _visibleTransform;
glm::quat _visibleRotation;

View file

@ -39,8 +39,7 @@ Model::Model(QObject* parent) :
_scaledToFit(false),
_snapModelToCenter(false),
_snappedToCenter(false),
_showTrueJointTransforms(false),
_rootIndex(-1),
_showTrueJointTransforms(true),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url("http://invalid.com") {
@ -126,6 +125,7 @@ void Model::setScaleInternal(const glm::vec3& scale) {
const float ONE_PERCENT = 0.01f;
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
_scale = scale;
initJointTransforms();
if (_shapes.size() > 0) {
clearShapes();
buildShapes();
@ -165,24 +165,26 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
state.setFBXJoint(&joint);
jointStates.append(state);
}
return jointStates;
};
void Model::initJointTransforms() {
// compute model transforms
int numJoints = jointStates.size();
int numJoints = _jointStates.size();
for (int i = 0; i < numJoints; ++i) {
JointState& state = jointStates[i];
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
_rootIndex = i;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
// NOTE: in practice geometry.offset has a non-unity scale (rather than a translation)
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform);
state.initTransform(parentTransform);
} else {
const JointState& parentState = jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform());
const JointState& parentState = _jointStates.at(parentIndex);
state.initTransform(parentState.getTransform());
}
}
return jointStates;
}
void Model::init() {
@ -560,6 +562,7 @@ bool Model::updateGeometry() {
// virtual
void Model::setJointStates(QVector<JointState> states) {
_jointStates = states;
initJointTransforms();
int numJoints = _jointStates.size();
float radius = 0.0f;
@ -937,7 +940,6 @@ void Model::simulateInternal(float deltaTime) {
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
updateVisibleJointStates();
_shapesAreDirty = ! _shapes.isEmpty();
@ -1006,10 +1008,12 @@ void Model::updateJointState(int index) {
}
void Model::updateVisibleJointStates() {
if (!_showTrueJointTransforms) {
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
if (_showTrueJointTransforms) {
// no need to update visible transforms
return;
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
}
@ -1037,8 +1041,8 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl
if (useRotation) {
JointState& state = _jointStates[jointIndex];
state.setRotationFromBindFrame(rotation, priority);
endRotation = state.getRotationFromBindToModelFrame();
state.setRotationInBindFrame(rotation, priority);
endRotation = state.getRotationInBindFrame();
}
// then, we go from the joint upwards, rotating the end as close as possible to the target
@ -1209,7 +1213,7 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm:
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
// set final rotation of the end joint
endState.setRotationFromBindFrame(targetRotation, priority, true);
endState.setRotationInBindFrame(targetRotation, priority, true);
_shapesAreDirty = !_shapes.isEmpty();
}
@ -1356,6 +1360,7 @@ void Model::deleteGeometry() {
}
void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows) {
updateVisibleJointStates();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();

View file

@ -148,6 +148,8 @@ public:
void setLocalLightDirection(const glm::vec3& direction, int lightIndex);
void setLocalLightColor(const glm::vec3& color, int lightIndex);
void setNumLocalLights(int numLocalLights);
void setShowTrueJointTransforms(bool show) { _showTrueJointTransforms = show; }
protected:
QSharedPointer<NetworkGeometry> _geometry;
@ -162,7 +164,6 @@ protected:
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
bool _snappedToCenter; /// are we currently snapped to center
bool _showTrueJointTransforms;
int _rootIndex;
glm::vec3 _localLightDirections[MAX_LOCAL_LIGHTS];
glm::vec3 _localLightColors[MAX_LOCAL_LIGHTS];
@ -225,6 +226,7 @@ private:
void deleteGeometry();
void renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows);
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
QSharedPointer<NetworkGeometry> _nextBaseGeometry;

View file

@ -967,17 +967,19 @@ QString getString(const QVariant& value) {
class JointShapeInfo {
public:
JointShapeInfo() : numVertices(0), numProjectedVertices(0), averageVertex(0.f), boneBegin(0.f), averageRadius(0.f) {
extents.reset();
JointShapeInfo() : numVertices(0),
sumVertexWeights(0.0f), sumWeightedRadii(0.0f), numVertexWeights(0),
averageVertex(0.f), boneBegin(0.f), averageRadius(0.f) {
}
// NOTE: the points here are in the "joint frame" which has the "jointEnd" at the origin
int numVertices; // num vertices from contributing meshes
int numProjectedVertices; // num vertices that successfully project onto bone axis
Extents extents; // max and min extents of mesh vertices (in joint frame)
glm::vec3 averageVertex; // average of all mesh vertices (in joint frame)
glm::vec3 boneBegin; // parent joint location (in joint frame)
float averageRadius; // average distance from mesh points to averageVertex
int numVertices; // num vertices from contributing meshes
float sumVertexWeights; // sum of all vertex weights
float sumWeightedRadii; // sum of weighted vertices
int numVertexWeights; // num vertices that contributed to sums
glm::vec3 averageVertex;// average of all mesh vertices (in joint frame)
glm::vec3 boneBegin; // parent joint location (in joint frame)
float averageRadius; // average distance from mesh points to averageVertex
};
class AnimationCurve {
@ -1740,14 +1742,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
const float EXPANSION_WEIGHT_THRESHOLD = 0.25f;
if (weight > EXPANSION_WEIGHT_THRESHOLD) {
const glm::vec3& vertex = extracted.mesh.vertices.at(it.value());
float proj = glm::dot(boneDirection, vertex - boneEnd);
if (proj < 0.0f && proj > -boneLength) {
joint.boneRadius = glm::max(joint.boneRadius,
radiusScale * glm::distance(vertex, boneEnd + boneDirection * proj));
++jointShapeInfo.numProjectedVertices;
float proj = glm::dot(boneDirection, boneEnd - vertex);
if (proj < 0.0f || proj > boneLength) {
weight *= 0.5f;
}
jointShapeInfo.sumVertexWeights += weight;
jointShapeInfo.sumWeightedRadii += weight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj);
++jointShapeInfo.numVertexWeights;
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
jointShapeInfo.extents.addPoint(vertexInJointFrame);
jointShapeInfo.averageVertex += vertexInJointFrame;
++jointShapeInfo.numVertices;
}
@ -1792,13 +1796,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::vec3 averageVertex(0.f);
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
float proj = glm::dot(boneDirection, vertex - boneEnd);
if (proj < 0.0f && proj > -boneLength) {
joint.boneRadius = glm::max(joint.boneRadius, radiusScale * glm::distance(vertex, boneEnd + boneDirection * proj));
++jointShapeInfo.numProjectedVertices;
float weight = 1.0f;
float proj = glm::dot(boneDirection, boneEnd - vertex);
if (proj < 0.0f || proj > boneLength) {
weight *= 0.5f;
}
jointShapeInfo.sumVertexWeights += weight;
jointShapeInfo.sumWeightedRadii += weight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj);
++jointShapeInfo.numVertexWeights;
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
jointShapeInfo.extents.addPoint(vertexInJointFrame);
jointShapeInfo.averageVertex += vertexInJointFrame;
averageVertex += vertex;
}
@ -1832,9 +1839,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
jointShapeInfo.boneBegin = inverseRotation * (extractTranslation(parentJoint.transform) - extractTranslation(joint.transform));
}
// we use a capsule if the joint ANY mesh vertices successfully projected onto the bone
if (jointShapeInfo.sumVertexWeights > 0.0f) {
joint.boneRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights;
}
// we use a capsule if the joint had ANY mesh vertices successfully projected onto the bone
// AND its boneRadius is not too close to zero
bool collideLikeCapsule = jointShapeInfo.numProjectedVertices > 0
bool collideLikeCapsule = jointShapeInfo.numVertexWeights > 0
&& glm::length(jointShapeInfo.boneBegin) > EPSILON;
if (collideLikeCapsule) {
@ -1850,7 +1861,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else {
joint.shapePosition = glm::vec3(0.f);
}
if (jointShapeInfo.numProjectedVertices == 0
if (jointShapeInfo.numVertexWeights == 0
&& jointShapeInfo.numVertices > 0) {
// the bone projection algorithm was not able to compute the joint radius
// so we use an alternative measure

View file

@ -126,22 +126,7 @@ void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
}
}
}
// TODO: Andrew to implement:
// DONE (1) joints pull points (SpecialCapsuleShape would help solve this)
// DONE (2) points slam shapes (SpecialCapsuleShape would help solve this)
// DONE (3) detect collisions
// DONE (4) collisions move points (SpecialCapsuleShape would help solve this)
// DONE (5) enforce constraints
// DONE (6) make sure MyAvatar creates shapes, adds to simulation with ragdoll support
// DONE (7) support for pairwise collision bypass
// DONE (8) process collisions
// DONE (8a) stubbery
// DONE (8b) shapes actually accumulate movement
// DONE (9) verify that avatar shapes self collide
// (10) slave rendered SkeletonModel to physical shapes
// (10a) give SkeletonModel duplicate JointState data
// (10b) figure out how to slave dupe JointStates to physical shapes
// (11) add and enforce angular contraints for joints
void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec) {
quint64 now = usecTimestampNow();
quint64 startTime = now;

View file

@ -343,6 +343,72 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
return false;
}
/// \param lineP point on line
/// \param lineDir normalized direction of line
/// \param cylinderP point on cylinder axis
/// \param cylinderDir normalized direction of cylinder axis
/// \param cylinderRadius radius of cylinder
/// \param hitLow[out] distance from point on line to first intersection with cylinder
/// \param hitHigh[out] distance from point on line to second intersection with cylinder
/// \return true if line hits cylinder
bool lineCylinder(const glm::vec3& lineP, const glm::vec3& lineDir,
const glm::vec3& cylinderP, const glm::vec3& cylinderDir, float cylinderRadius,
float& hitLow, float& hitHigh) {
// first handle parallel case
float uDotV = glm::dot(lineDir, cylinderDir);
if (fabsf(1.0f - fabsf(uDotV)) < EPSILON) {
// line and cylinder are parallel
if (glm::distance2(lineP, cylinderP) <= cylinderRadius * cylinderRadius) {
// line is inside cylinder, which we consider a hit
hitLow = 0.0f;
hitHigh = 0.0f;
return true;
}
return false;
}
// Given a line with point 'p' and normalized direction 'u' and
// a cylinder with axial point 's', radius 'r', and normalized direction 'v'
// the intersection of the two is on the line at distance 't' from 'p'.
//
// Determining the values of t reduces to solving a quadratic equation: At^2 + Bt + C = 0
//
// where:
//
// P = p-s
// w = u-(u.v)v
// Q = P-(P.v)v
//
// A = w^2
// B = 2(w.Q)
// C = Q^2 - r^2
glm::vec3 P = lineP - cylinderP;
glm::vec3 w = lineDir - uDotV * cylinderDir;
glm::vec3 Q = P - glm::dot(P, cylinderDir) * cylinderDir;
// we save a few multiplies by storing 2*A rather than just A
float A2 = 2.0f * glm::dot(w, w);
float B = 2.0f * glm::dot(w, Q);
// since C is only ever used once (in the determinant) we compute it inline
float determinant = B * B - 2.0f * A2 * (glm::dot(Q, Q) - cylinderRadius * cylinderRadius);
if (determinant < 0.0f) {
return false;
}
hitLow = (-B - sqrtf(determinant)) / A2;
hitHigh = -(hitLow + 2.0f * B / A2);
if (hitLow > hitHigh) {
// re-arrange so hitLow is always the smaller value
float temp = hitHigh;
hitHigh = hitLow;
hitLow = temp;
}
return true;
}
bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions) {
glm::vec3 axisA;
capsuleA->computeNormalizedAxis(axisA);
@ -358,23 +424,43 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
float denominator = 1.0f - aDotB * aDotB;
float totalRadius = capsuleA->getRadius() + capsuleB->getRadius();
if (denominator > EPSILON) {
// distances to points of closest approach
float distanceA = glm::dot((centerB - centerA), (axisA - (aDotB) * axisB)) / denominator;
float distanceB = glm::dot((centerA - centerB), (axisB - (aDotB) * axisA)) / denominator;
// clamp the distances to the ends of the capsule line segments
float absDistanceA = fabs(distanceA);
if (absDistanceA > capsuleA->getHalfHeight() + capsuleA->getRadius()) {
float signA = distanceA < 0.0f ? -1.0f : 1.0f;
distanceA = signA * capsuleA->getHalfHeight();
}
float absDistanceB = fabs(distanceB);
if (absDistanceB > capsuleB->getHalfHeight() + capsuleB->getRadius()) {
float signB = distanceB < 0.0f ? -1.0f : 1.0f;
distanceB = signB * capsuleB->getHalfHeight();
// perform line-cylinder intesection test between axis of cylinderA and cylinderB with exanded radius
float hitLow = 0.0f;
float hitHigh = 0.0f;
if (!lineCylinder(centerA, axisA, centerB, axisB, totalRadius, hitLow, hitHigh)) {
return false;
}
// collide like spheres at closest approaches (do most of the math relative to B)
float halfHeightA = capsuleA->getHalfHeight();
if (hitLow > halfHeightA || hitHigh < -halfHeightA) {
// the intersections are off the ends of capsuleA
return false;
}
// compute nearest approach on axisA of axisB
float distanceA = glm::dot((centerB - centerA), (axisA - (aDotB) * axisB)) / denominator;
// clamp to intersection zone
if (distanceA > hitLow) {
if (distanceA > hitHigh) {
distanceA = hitHigh;
}
} else {
distanceA = hitLow;
}
// clamp to capsule segment
distanceA = glm::clamp(distanceA, -halfHeightA, halfHeightA);
// find the closest point on capsuleB to sphere on capsuleA
float distanceB = glm::dot(centerA + distanceA * axisA - centerB, axisB);
float halfHeightB = capsuleB->getHalfHeight();
if (fabsf(distanceB) > halfHeightB) {
// we must clamp distanceB...
distanceB = glm::clamp(distanceB, -halfHeightB, halfHeightB);
// ...and therefore must recompute distanceA
distanceA = glm::clamp(glm::dot(centerB + distanceB * axisB - centerA, axisA), -halfHeightA, halfHeightA);
}
// collide like two spheres (do most of the math relative to B)
glm::vec3 BA = (centerB + distanceB * axisB) - (centerA + distanceA * axisA);
float distanceSquared = glm::dot(BA, BA);
if (distanceSquared < totalRadius * totalRadius) {