mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:44:02 +02:00
Merge pull request #3180 from AndrewMeadows/ragdoll
Ragdoll Part4: ragdoll shapes follow target joints, visible joints follow ragdoll shapes
This commit is contained in:
commit
1b265f3adb
10 changed files with 312 additions and 111 deletions
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue