From 11d2f9a8fcb52608764cd5ee8a4d4ef6e23f6fbf Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 26 Jan 2016 14:02:21 -0800 Subject: [PATCH 01/19] equip via parenting rather than action. change name of a messaging channel. minor cleanups. --- examples/controllers/handControllerGrab.js | 117 ++++++++------------- 1 file changed, 46 insertions(+), 71 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 9dade9191f..a3c629bdc9 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -160,7 +160,6 @@ var STATE_EQUIP = 12 var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down var STATE_CONTINUE_EQUIP = 14; var STATE_WAITING_FOR_BUMPER_RELEASE = 15; -var STATE_EQUIP_SPRING = 16; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -201,8 +200,6 @@ function stateToName(state) { return "continue_equip"; case STATE_WAITING_FOR_BUMPER_RELEASE: return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; } return "unknown"; @@ -343,9 +340,6 @@ function MyController(hand) { case STATE_WAITING_FOR_BUMPER_RELEASE: this.waitingForBumperRelease(); break; - case STATE_EQUIP_SPRING: - this.pullTowardEquipPosition() - break; case STATE_CONTINUE_NEAR_GRABBING: case STATE_CONTINUE_EQUIP_BD: case STATE_CONTINUE_EQUIP: @@ -786,7 +780,9 @@ function MyController(hand) { var distantPickRay = { origin: PICK_WITH_HAND_RAY ? handPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(this.getHandRotation()) : Vec3.mix(Quat.getUp(this.getHandRotation()), Quat.getFront(Camera.orientation), HAND_HEAD_MIX_RATIO), + direction: PICK_WITH_HAND_RAY ? Quat.getUp(this.getHandRotation()) : Vec3.mix(Quat.getUp(this.getHandRotation()), + Quat.getFront(Camera.orientation), + HAND_HEAD_MIX_RATIO), length: PICK_MAX_DISTANCE }; @@ -836,7 +832,7 @@ function MyController(hand) { candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; - var forbiddenTyes = ['Unknown', 'Light', 'ParticleEffect', 'PolyLine', 'Zone']; + var forbiddenTypes = ['Unknown', 'Light', 'ParticleEffect', 'PolyLine', 'Zone']; var minDistance = PICK_MAX_DISTANCE; var i, props, distance, grabbableData; @@ -849,7 +845,7 @@ function MyController(hand) { if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { continue; } - if (forbiddenTyes.indexOf(propsForCandidate.type) >= 0) { + if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) { continue; } if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { @@ -1243,7 +1239,7 @@ function MyController(hand) { } var isPhysical = this.propsArePhysical(grabbedProperties); - if (isPhysical) { + if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { return; @@ -1321,8 +1317,7 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); } - //// jbp::: SEND UPDATE MESSAGE TO WEARABLES MANAGER - Messages.sendMessage('Hifi-Wearables-Manager', JSON.stringify({ + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'update', grabbedEntity: this.grabbedEntity })) @@ -1357,60 +1352,6 @@ function MyController(hand) { } }; - this.pullTowardEquipPosition = function() { - - this.turnOffVisualizations(); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - // use a spring to pull the object to where it will be when equipped - var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - var ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var targetRotation = Quat.multiply(handRotation, relativeRotation); - var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); - var targetPosition = Vec3.sum(handPosition, offset); - - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_UUID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (this.equipSpringID === NULL_UUID) { - this.equipSpringID = null; - this.setState(STATE_OFF); - return; - } - } else { - var success = Entities.updateAction(this.grabbedEntity, this.equipSpringID, { - targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, - targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, - ttl: ACTION_TTL, - ignoreIK: ignoreIK - }); - if (!success) { - print("pullTowardEquipPosition -- updateActionfailed"); - } - } - - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; - this.setState(STATE_EQUIP); - } - }; - this.nearTrigger = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); @@ -1595,12 +1536,10 @@ function MyController(hand) { this.actionID = null; this.setState(STATE_OFF); - //// jbp::: SEND RELEASE MESSAGE TO WEARABLES MANAGER - - Messages.sendMessage('Hifi-Wearables-Manager', JSON.stringify({ - action: 'checkIfWearable', + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', grabbedEntity: this.grabbedEntity - })) + })); this.grabbedEntity = null; }; @@ -1670,6 +1609,24 @@ function MyController(hand) { } setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; + + this.checkNewlyLoaded = function(loadedEntityID) { + if (this.state == STATE_OFF || + this.state == STATE_SEARCHING || + this.state == STATE_EQUIP_SEARCHING) { + var loadedProps = Entities.getEntityProperties(loadedEntityID, ["parentID", "parentJointIndex"]); + if (loadedProps.parentID != MyAvatar.sessionUUID) { + return; + } + var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + if (loadedProps.parentJointIndex != handJointIndex) { + return; + } + // an entity has been loaded and it's where this script would have equipped something, so switch states. + this.grabbedEntity = loadedEntityID; + this.setState(STATE_EQUIP); + } + } }; var rightController = new MyController(RIGHT_HAND); @@ -1701,6 +1658,7 @@ function update() { Messages.subscribe('Hifi-Hand-Disabler'); Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); +Messages.subscribe('Hifi-Object-Manipulation'); handleHandMessages = function(channel, message, sender) { if (sender === MyAvatar.sessionUUID) { @@ -1741,6 +1699,23 @@ handleHandMessages = function(channel, message, sender) { } } catch (e) {} + } else if (channel === 'Hifi-Object-Manipulation') { + if (sender !== MyAvatar.sessionUUID) { + return; + } + + var parsedMessage = null; + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing Hifi-Object-Manipulation message'); + return; + } + + if (parsedMessage.action === 'loaded') { + rightController.checkNewlyLoaded(parsedMessage['grabbedEntity']); + leftController.checkNewlyLoaded(parsedMessage['grabbedEntity']); + } } } } From 3b7f8ef77d36b4f639b04e61368f3e7a2d5820b2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 26 Jan 2016 15:44:26 -0800 Subject: [PATCH 02/19] honor hardcoded equip offsets --- examples/controllers/handControllerGrab.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index a3c629bdc9..cc35cac807 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -365,7 +365,8 @@ function MyController(hand) { this.setState = function(newState) { if (WANT_DEBUG || WANT_DEBUG_STATE) { - print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + + stateToName(newState) + ", hand: " + this.hand); } this.state = newState; }; @@ -1249,7 +1250,9 @@ function MyController(hand) { var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); Entities.editEntity(this.grabbedEntity, { parentID: MyAvatar.sessionUUID, - parentJointIndex: handJointIndex + parentJointIndex: handJointIndex, + localPosition: this.offsetPosition, + localRotation: this.offsetRotation }); } From 5cbb5000d2ddcc64a9b028dbffb26cfac8b1fdea Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 28 Jan 2016 11:42:54 -0800 Subject: [PATCH 03/19] Render the correct maxDrawnItems --- libraries/render/src/render/DrawTask.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 9bf9a9acd3..455a420355 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -155,7 +155,10 @@ void render::renderShapes(const SceneContextPointer& sceneContext, const RenderC auto& scene = sceneContext->_scene; RenderArgs* args = renderContext->args; - auto numItemsToDraw = glm::max((int)inItems.size(), maxDrawnItems); + int numItemsToDraw = (int)inItems.size(); + if (maxDrawnItems != -1) { + numItemsToDraw = glm::min(numItemsToDraw, maxDrawnItems); + } for (auto i = 0; i < numItemsToDraw; ++i) { auto& item = scene->getItem(inItems[i].id); renderShape(args, shapeContext, item); @@ -246,4 +249,4 @@ void DepthSortShapes::run(const SceneContextPointer& sceneContext, const RenderC depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems->second); } -} \ No newline at end of file +} From 7b3f688a17241893d563947fa6a904d2822ac08f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 Jan 2016 13:18:03 -0800 Subject: [PATCH 04/19] pull CCD IK solution pass into protected method --- .../animation/src/AnimInverseKinematics.cpp | 334 ++++++++++-------- .../animation/src/AnimInverseKinematics.h | 1 + 2 files changed, 183 insertions(+), 152 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index f9c8873779..e616b2ef09 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -105,7 +105,12 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); - targets.push_back(target); + if (target.getType() == IKTarget::Type::HmdHead) { + // HmdHead target always goes to beginning of the list + targets.insert(targets.begin(), target); + } else { + targets.push_back(target); + } if (targetVar.jointIndex > _maxTargetIndex) { _maxTargetIndex = targetVar.jointIndex; } @@ -137,6 +142,18 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector= (int)_relativePoses.size()) { continue; } - int tipIndex = target.getIndex(); - int pivotIndex = _skeleton->getParentIndex(tipIndex); - if (pivotIndex == -1 || pivotIndex == _hipsIndex) { - continue; - } - int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); - if (pivotsParentIndex == -1) { - // TODO?: handle case where tip's parent is root? - continue; - } - - // cache tip's absolute orientation - glm::quat tipOrientation = absolutePoses[tipIndex].rot; - - // also cache tip's parent's absolute orientation so we can recompute - // the tip's parent-relative as we proceed up the chain - glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot; - - if (targetType == IKTarget::Type::HmdHead) { - // rotate tip directly to target orientation - tipOrientation = target.getRotation(); - - // enforce tip's constraint - RotationConstraint* constraint = getConstraint(tipIndex); - if (constraint) { - glm::quat tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); - bool constrained = constraint->apply(tipRelativeRotation); - if (constrained) { - tipOrientation = glm::normalize(tipRelativeRotation * tipParentOrientation); - } + // harvest accumulated rotations and apply the average + const int numJoints = (int)_accumulators.size(); + for (int i = 0; i < numJoints; ++i) { + if (_accumulators[i].size() > 0) { + _relativePoses[i].rot = _accumulators[i].getAverage(); + _accumulators[i].clear(); } } - // cache tip absolute position - glm::vec3 tipPosition = absolutePoses[tipIndex].trans; - - // descend toward root, pivoting each joint to get tip closer to target - while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { - // compute the two lines that should be aligned - glm::vec3 jointPosition = absolutePoses[pivotIndex].trans; - glm::vec3 leverArm = tipPosition - jointPosition; - - glm::quat deltaRotation; - if (targetType == IKTarget::Type::RotationAndPosition || - targetType == IKTarget::Type::HipsRelativeRotationAndPosition) { - // compute the swing that would get get tip closer - glm::vec3 targetLine = target.getTranslation() - jointPosition; - glm::vec3 axis = glm::cross(leverArm, targetLine); - float axisLength = glm::length(axis); - const float MIN_AXIS_LENGTH = 1.0e-4f; - if (axisLength > MIN_AXIS_LENGTH) { - // compute deltaRotation for alignment (swings tip closer to target) - axis /= axisLength; - float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); - - // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is - // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. - const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; - if (angle > MIN_ADJUSTMENT_ANGLE) { - // reduce angle by a fraction (for stability) - const float fraction = 0.5f; - angle *= fraction; - deltaRotation = glm::angleAxis(angle, axis); - - // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's - // new orientation and its target. This is the final parent-relative orientation that the tip joint have - // make to achieve its target orientation. - glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentOrientation) * target.getRotation(); - - // enforce tip's constraint - RotationConstraint* constraint = getConstraint(tipIndex); - if (constraint) { - bool constrained = constraint->apply(tipRelativeRotation); - if (constrained) { - // The tip's final parent-relative rotation would violate its constraint - // so we try to pre-twist this pivot to compensate. - glm::quat constrainedTipRotation = deltaRotation * tipParentOrientation * tipRelativeRotation; - glm::quat missingRotation = target.getRotation() * glm::inverse(constrainedTipRotation); - glm::quat swingPart; - glm::quat twistPart; - glm::vec3 axis = glm::normalize(deltaRotation * leverArm); - swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); - float dotSign = copysignf(1.0f, twistPart.w); - deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, fraction)) * deltaRotation; - } - } - } - } - } else if (targetType == IKTarget::Type::HmdHead) { - // An HmdHead target slaves the orientation of the end-effector by distributing rotation - // deltas up the hierarchy. Its target position is enforced later by shifting the hips. - deltaRotation = target.getRotation() * glm::inverse(tipOrientation); - float dotSign = copysignf(1.0f, deltaRotation.w); - const float ANGLE_DISTRIBUTION_FACTOR = 0.45f; - deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * deltaRotation, ANGLE_DISTRIBUTION_FACTOR)); + // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex + for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { + auto parentIndex = _skeleton->getParentIndex((int)i); + if (parentIndex != -1) { + absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; } - - // compute joint's new parent-relative rotation after swing - // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q - glm::quat newRot = glm::normalize(glm::inverse( - absolutePoses[pivotsParentIndex].rot) * - deltaRotation * - absolutePoses[pivotIndex].rot); - - // enforce pivot's constraint - RotationConstraint* constraint = getConstraint(pivotIndex); - if (constraint) { - bool constrained = constraint->apply(newRot); - if (constrained) { - // the constraint will modify the local rotation of the tip so we must - // compute the corresponding model-frame deltaRotation - // Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^ - deltaRotation = absolutePoses[pivotsParentIndex].rot * - newRot * glm::inverse(absolutePoses[pivotIndex].rot); - } - } - - // store the rotation change in the accumulator - _accumulators[pivotIndex].add(newRot, target.getWeight()); - - // this joint has been changed so we check to see if it has the lowest index - if (pivotIndex < lowestMovedIndex) { - lowestMovedIndex = pivotIndex; - } - - // keep track of tip's new transform as we descend towards root - tipPosition = jointPosition + deltaRotation * leverArm; - tipOrientation = glm::normalize(deltaRotation * tipOrientation); - tipParentOrientation = glm::normalize(deltaRotation * tipParentOrientation); - - pivotIndex = pivotsParentIndex; - pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); } } - ++numLoops; - - // harvest accumulated rotations and apply the average - const int numJoints = (int)_accumulators.size(); - for (int i = 0; i < numJoints; ++i) { - if (_accumulators[i].size() > 0) { - _relativePoses[i].rot = _accumulators[i].getAverage(); - _accumulators[i].clear(); - } - } - - // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex - for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { - auto parentIndex = _skeleton->getParentIndex((int)i); - if (parentIndex != -1) { - absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; - } - } - } while (numLoops < MAX_IK_LOOPS); + } // finally set the relative rotation of each tip to agree with absolute target rotation for (auto& target: targets) { @@ -329,6 +212,153 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(tipIndex); + if (pivotIndex == -1 || pivotIndex == _hipsIndex) { + return lowestMovedIndex; + } + int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); + if (pivotsParentIndex == -1) { + // TODO?: handle case where tip's parent is root? + return lowestMovedIndex; + } + + // cache tip's absolute orientation + glm::quat tipOrientation = absolutePoses[tipIndex].rot; + + // also cache tip's parent's absolute orientation so we can recompute + // the tip's parent-relative as we proceed up the chain + glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot; + + if (targetType == IKTarget::Type::HmdHead) { + // rotate tip directly to target orientation + tipOrientation = target.getRotation(); + glm::quat tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + + // enforce tip's constraint + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { + bool constrained = constraint->apply(tipRelativeRotation); + if (constrained) { + tipOrientation = glm::normalize(tipRelativeRotation * tipParentOrientation); + tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + } + } + // store the relative rotation change in the accumulator + _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); + } + + // cache tip absolute position + glm::vec3 tipPosition = absolutePoses[tipIndex].trans; + + // descend toward root, pivoting each joint to get tip closer to target position + while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { + // compute the two lines that should be aligned + glm::vec3 jointPosition = absolutePoses[pivotIndex].trans; + glm::vec3 leverArm = tipPosition - jointPosition; + + glm::quat deltaRotation; + if (targetType == IKTarget::Type::RotationAndPosition || + targetType == IKTarget::Type::HipsRelativeRotationAndPosition) { + // compute the swing that would get get tip closer + glm::vec3 targetLine = target.getTranslation() - jointPosition; + glm::vec3 axis = glm::cross(leverArm, targetLine); + float axisLength = glm::length(axis); + const float MIN_AXIS_LENGTH = 1.0e-4f; + if (axisLength > MIN_AXIS_LENGTH) { + // compute deltaRotation for alignment (swings tip closer to target) + axis /= axisLength; + float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); + + // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is + // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. + const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; + if (angle > MIN_ADJUSTMENT_ANGLE) { + // reduce angle by a fraction (for stability) + const float fraction = 0.5f; + angle *= fraction; + deltaRotation = glm::angleAxis(angle, axis); + + // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's + // new orientation and its target. This is the final parent-relative orientation that the tip joint have + // make to achieve its target orientation. + glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentOrientation) * target.getRotation(); + + // enforce tip's constraint + RotationConstraint* constraint = getConstraint(tipIndex); + if (constraint) { + bool constrained = constraint->apply(tipRelativeRotation); + if (constrained) { + // The tip's final parent-relative rotation would violate its constraint + // so we try to pre-twist this pivot to compensate. + glm::quat constrainedTipRotation = deltaRotation * tipParentOrientation * tipRelativeRotation; + glm::quat missingRotation = target.getRotation() * glm::inverse(constrainedTipRotation); + glm::quat swingPart; + glm::quat twistPart; + glm::vec3 axis = glm::normalize(deltaRotation * leverArm); + swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); + float dotSign = copysignf(1.0f, twistPart.w); + deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, fraction)) * deltaRotation; + } + } + } + } + } else if (targetType == IKTarget::Type::HmdHead) { + // An HmdHead target slaves the orientation of the end-effector by distributing rotation + // deltas up the hierarchy. Its target position is enforced later (by shifting the hips). + deltaRotation = target.getRotation() * glm::inverse(tipOrientation); + float dotSign = copysignf(1.0f, deltaRotation.w); + const float ANGLE_DISTRIBUTION_FACTOR = 0.45f; + deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * deltaRotation, ANGLE_DISTRIBUTION_FACTOR)); + } + + // compute joint's new parent-relative rotation after swing + // Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q + glm::quat newRot = glm::normalize(glm::inverse( + absolutePoses[pivotsParentIndex].rot) * + deltaRotation * + absolutePoses[pivotIndex].rot); + + // enforce pivot's constraint + RotationConstraint* constraint = getConstraint(pivotIndex); + if (constraint) { + bool constrained = constraint->apply(newRot); + if (constrained) { + // the constraint will modify the local rotation of the tip so we must + // compute the corresponding model-frame deltaRotation + // Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^ + deltaRotation = absolutePoses[pivotsParentIndex].rot * newRot * glm::inverse(absolutePoses[pivotIndex].rot); + } + } + + // store the relative rotation change in the accumulator + _accumulators[pivotIndex].add(newRot, target.getWeight()); + + // this joint has been changed so we check to see if it has the lowest index + if (pivotIndex < lowestMovedIndex) { + lowestMovedIndex = pivotIndex; + } + + // keep track of tip's new transform as we descend towards root + tipPosition = jointPosition + deltaRotation * leverArm; + tipOrientation = glm::normalize(deltaRotation * tipOrientation); + tipParentOrientation = glm::normalize(deltaRotation * tipParentOrientation); + + pivotIndex = pivotsParentIndex; + pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); + } + return lowestMovedIndex; +} + //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) { // don't call this function, call overlay() instead diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 825577b1ae..aeb718668a 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -40,6 +40,7 @@ public: protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); + int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; // for AnimDebugDraw rendering From 48f6a9c05f69db5060d557ad68077111979de27d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 29 Jan 2016 10:19:12 -0800 Subject: [PATCH 05/19] fix IK looping error after minor refactor --- .../animation/src/AnimInverseKinematics.cpp | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index e616b2ef09..6558ba5f1d 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -142,18 +142,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector= (int)_relativePoses.size()) { - continue; + // harvest accumulated rotations and apply the average + const int numJoints = (int)_accumulators.size(); + for (int i = 0; i < numJoints; ++i) { + if (_accumulators[i].size() > 0) { + _relativePoses[i].rot = _accumulators[i].getAverage(); + _accumulators[i].clear(); } + } - // harvest accumulated rotations and apply the average - const int numJoints = (int)_accumulators.size(); - for (int i = 0; i < numJoints; ++i) { - if (_accumulators[i].size() > 0) { - _relativePoses[i].rot = _accumulators[i].getAverage(); - _accumulators[i].clear(); - } - } - - // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex - for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { - auto parentIndex = _skeleton->getParentIndex((int)i); - if (parentIndex != -1) { - absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; - } + // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex + for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { + auto parentIndex = _skeleton->getParentIndex((int)i); + if (parentIndex != -1) { + absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; } } } @@ -371,7 +358,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (_relativePoses.size() != underPoses.size()) { loadPoses(underPoses); } else { - // relax toward underpose + // relax toward underPoses // HACK: this relaxation needs to be constant per-frame rather than per-realtime // in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts // of this relaxation will be FPS dependent (low FPS will make the limbs align slower @@ -382,8 +369,10 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars for (int i = 0; i < numJoints; ++i) { float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot, underPoses[i].rot)); if (_accumulators[i].isDirty()) { + // this joint is affected by IK --> blend toward underPose rotation _relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * underPoses[i].rot, blend)); } else { + // this joint is NOT affected by IK --> slam to underPose rotation _relativePoses[i].rot = underPoses[i].rot; } _relativePoses[i].trans = underPoses[i].trans; @@ -406,7 +395,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars ++constraintItr; } } else { - // shift the everything according to the _hipsOffset from the previous frame + // shift hips according to the _hipsOffset from the previous frame float offsetLength = glm::length(_hipsOffset); const float MIN_HIPS_OFFSET_LENGTH = 0.03f; if (offsetLength > MIN_HIPS_OFFSET_LENGTH) { @@ -423,14 +412,14 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars hipsFrameRotation *= _relativePoses[index].rot; index = _skeleton->getParentIndex(index); } - _relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + _relativePoses[_hipsIndex].trans = underPoses[_hipsIndex].trans + glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset); } } solveWithCyclicCoordinateDescent(targets); - // compute the new target hips offset (for next frame) + // measure new _hipsOffset for next frame // by looking for discrepancies between where a targeted endEffector is // and where it wants to be (after IK solutions are done) glm::vec3 newHipsOffset = Vectors::ZERO; @@ -439,7 +428,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (targetIndex == _headIndex && _headIndex != -1) { // special handling for headTarget if (target.getType() == IKTarget::Type::RotationOnly) { - // we want to shift the hips to bring the underpose closer + // we want to shift the hips to bring the underPose closer // to where the head happens to be (overpose) glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans; glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans; From 449d566d2aa13a5a34a7a052bf3b3887686ababe Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 29 Jan 2016 10:31:00 -0800 Subject: [PATCH 06/19] properly track lowestMovedIndex --- libraries/animation/src/AnimInverseKinematics.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 6558ba5f1d..6a6abff84c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -156,7 +156,10 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector Date: Fri, 29 Jan 2016 10:36:22 -0800 Subject: [PATCH 07/19] minor IK optimization: changed accumulators only --- libraries/animation/src/AnimInverseKinematics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 6a6abff84c..d46cb5b60a 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -164,14 +164,14 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector 0) { _relativePoses[i].rot = _accumulators[i].getAverage(); _accumulators[i].clear(); } } - // only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex + // update the absolutePoses that need it (from lowestMovedIndex to _maxTargetIndex) for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) { auto parentIndex = _skeleton->getParentIndex((int)i); if (parentIndex != -1) { From 14ec1b62954073b8d218da2af34ed9d768c22149 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 29 Jan 2016 14:26:50 -0800 Subject: [PATCH 08/19] reduce hand IK coupling to hip position --- .../animation/src/AnimInverseKinematics.cpp | 29 +++++++++++++++---- libraries/animation/src/RotationConstraint.h | 3 ++ .../animation/src/SwingTwistConstraint.cpp | 6 ++-- .../animation/src/SwingTwistConstraint.h | 24 ++++++++------- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d46cb5b60a..b62eaca7fd 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -163,7 +163,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector 0) { _relativePoses[i].rot = _accumulators[i].getAverage(); @@ -261,16 +260,32 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe targetType == IKTarget::Type::HipsRelativeRotationAndPosition) { // compute the swing that would get get tip closer glm::vec3 targetLine = target.getTranslation() - jointPosition; + + const float MIN_AXIS_LENGTH = 1.0e-4f; + RotationConstraint* constraint = getConstraint(pivotIndex); + if (constraint && constraint->isLowerSpine()) { + // for these types of targets we only allow twist at the lower-spine + // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) + glm::vec3 twistAxis = absolutePoses[pivotIndex].trans - absolutePoses[pivotsParentIndex].trans; + float twistAxisLength = glm::length(twistAxis); + if (twistAxisLength > MIN_AXIS_LENGTH) { + // project leverArm and targetLine to the plane + twistAxis /= twistAxisLength; + leverArm -= glm::dot(leverArm, twistAxis) * twistAxis; + targetLine -= glm::dot(targetLine, twistAxis) * twistAxis; + } else { + leverArm = Vectors::ZERO; + targetLine = Vectors::ZERO; + } + } + glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); - const float MIN_AXIS_LENGTH = 1.0e-4f; if (axisLength > MIN_AXIS_LENGTH) { - // compute deltaRotation for alignment (swings tip closer to target) + // compute angle of rotation that brings tip closer to target axis /= axisLength; float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine))); - // NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is - // still possible for the angle to be zero so we also check that to avoid unnecessary calculations. const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; if (angle > MIN_ADJUSTMENT_ANGLE) { // reduce angle by a fraction (for stability) @@ -663,6 +678,10 @@ void AnimInverseKinematics::initConstraints() { const float MAX_SPINE_SWING = PI / 14.0f; minDots.push_back(cosf(MAX_SPINE_SWING)); stConstraint->setSwingLimits(minDots); + if (0 == baseName.compare("Spine1", Qt::CaseInsensitive) + || 0 == baseName.compare("Spine", Qt::CaseInsensitive)) { + stConstraint->setLowerSpine(true); + } constraint = static_cast(stConstraint); } else if (baseName.startsWith("Hips2", Qt::CaseInsensitive)) { diff --git a/libraries/animation/src/RotationConstraint.h b/libraries/animation/src/RotationConstraint.h index 6926302ec8..0745500582 100644 --- a/libraries/animation/src/RotationConstraint.h +++ b/libraries/animation/src/RotationConstraint.h @@ -28,6 +28,9 @@ public: /// \return true if rotation is clamped virtual bool apply(glm::quat& rotation) const = 0; + /// \return true if this constraint is part of lower spine + virtual bool isLowerSpine() const { return false; } + protected: glm::quat _referenceRotation = glm::quat(); }; diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index c29f75202c..7386fb2bcd 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -123,7 +123,7 @@ void SwingTwistConstraint::setSwingLimits(const std::vector& swungDir // sort limits by theta std::sort(limits.begin(), limits.end()); - + // extrapolate evenly distributed limits for fast lookup table float deltaTheta = TWO_PI / (float)(numLimits); uint32_t rightIndex = 0; @@ -219,7 +219,7 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { } else { _lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY; } - + // clamp the swing // The swingAxis is always perpendicular to the reference axis (yAxis in the constraint's frame). glm::vec3 swungY = swingRotation * yAxis; @@ -232,7 +232,7 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const { float theta = atan2f(-swingAxis.z, swingAxis.x); float minDot = _swingLimitFunction.getMinDot(theta); if (glm::dot(swungY, yAxis) < minDot) { - // The swing limits are violated so we extract the angle from midDot and + // The swing limits are violated so we extract the angle from midDot and // use it to supply a new rotation. swingAxis /= axisLength; swingRotation = glm::angleAxis(acosf(minDot), swingAxis); diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index 155f72a518..f36dc851ea 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -18,20 +18,20 @@ class SwingTwistConstraint : public RotationConstraint { public: - // The SwingTwistConstraint starts in the "referenceRotation" and then measures an initial twist + // The SwingTwistConstraint starts in the "referenceRotation" and then measures an initial twist // about the yAxis followed by a swing about some axis that lies in the XZ plane, such that the twist - // and swing combine to produce the rotation. Each partial rotation is constrained within limits + // and swing combine to produce the rotation. Each partial rotation is constrained within limits // then used to construct the new final rotation. SwingTwistConstraint(); /// \param minDots vector of minimum dot products between the twist and swung axes - /// \brief The values are minimum dot-products between the twist axis and the swung axes + /// \brief The values are minimum dot-products between the twist axis and the swung axes /// that correspond to swing axes equally spaced around the XZ plane. Another way to - /// think about it is that the dot-products correspond to correspond to angles (theta) - /// about the twist axis ranging from 0 to 2PI-deltaTheta (Note: the cyclic boundary + /// think about it is that the dot-products correspond to correspond to angles (theta) + /// about the twist axis ranging from 0 to 2PI-deltaTheta (Note: the cyclic boundary /// conditions are handled internally, so don't duplicate the dot-product at 2PI). - /// See the paper by Quang Liu and Edmond C. Prakash mentioned below for a more detailed + /// See the paper by Quang Liu and Edmond C. Prakash mentioned below for a more detailed /// description of how this works. void setSwingLimits(std::vector minDots); @@ -50,21 +50,24 @@ public: /// \return true if rotation is changed virtual bool apply(glm::quat& rotation) const override; + void setLowerSpine(bool lowerSpine) { _lowerSpine = lowerSpine; } + virtual bool isLowerSpine() const { return _lowerSpine; } + // SwingLimitFunction is an implementation of the constraint check described in the paper: // "The Parameterization of Joint Rotation with the Unit Quaternion" by Quang Liu and Edmond C. Prakash class SwingLimitFunction { public: SwingLimitFunction(); - + /// \brief use a uniform conical swing limit void setCone(float maxAngle); - + /// \brief use a vector of lookup values for swing limits void setMinDots(const std::vector& minDots); - + /// \return minimum dotProduct between reference and swung axes float getMinDot(float theta) const; - + protected: // the limits are stored in a lookup table with cyclic boundary conditions std::vector _minDots; @@ -84,6 +87,7 @@ protected: // We want to remember the LAST clamped boundary, so we an use it even when the far boundary is closer. // This reduces "pops" when the input twist angle goes far beyond and wraps around toward the far boundary. mutable int _lastTwistBoundary; + bool _lowerSpine { false }; }; #endif // hifi_SwingTwistConstraint_h From de54a0ac4bc86c867b693b168eb6702af2853d3a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 29 Jan 2016 14:51:24 -0800 Subject: [PATCH 09/19] remove cruft --- libraries/animation/src/AnimInverseKinematics.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index b62eaca7fd..48287d5c7e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -105,12 +105,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); - if (target.getType() == IKTarget::Type::HmdHead) { - // HmdHead target always goes to beginning of the list - targets.insert(targets.begin(), target); - } else { - targets.push_back(target); - } + targets.push_back(target); if (targetVar.jointIndex > _maxTargetIndex) { _maxTargetIndex = targetVar.jointIndex; } @@ -142,7 +137,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector Date: Fri, 29 Jan 2016 15:17:46 -0800 Subject: [PATCH 10/19] change grab script to equip with parenting rather than action. various other changes to messaging. added initial version of attachedEntitiesManager.js. updated toybox scripts to track messaging changes --- examples/attachedEntitiesManager.js | 303 +++++++++++++++ examples/controllers/handControllerGrab.js | 363 ++++++++++-------- examples/toybox/bow/bow.js | 6 +- examples/toybox/bow/createBow.js | 33 +- examples/toybox/bubblewand/createWand.js | 26 +- examples/toybox/bubblewand/wand.js | 9 + examples/toybox/doll/doll.js | 9 + .../toybox/flashlight/createFlashlight.js | 16 +- examples/toybox/flashlight/flashlight.js | 12 +- .../toybox/ping_pong_gun/createPingPongGun.js | 24 +- examples/toybox/ping_pong_gun/pingPongGun.js | 6 +- examples/toybox/pistol/createPistol.js | 24 +- examples/toybox/pistol/pistol.js | 4 +- 13 files changed, 610 insertions(+), 225 deletions(-) create mode 100644 examples/attachedEntitiesManager.js diff --git a/examples/attachedEntitiesManager.js b/examples/attachedEntitiesManager.js new file mode 100644 index 0000000000..cbffb41f9a --- /dev/null +++ b/examples/attachedEntitiesManager.js @@ -0,0 +1,303 @@ +// +// attachedEntitiesManager.js +// +// Created by Seth Alves on 2016-1-20 +// Copyright 2016 High Fidelity, Inc. +// +// This script handles messages from the grab script related to wearables, and interacts with a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + +var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +var DEFAULT_WEARABLE_DATA = { + joints: {} +}; + + +var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.4; +var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; +var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; +var DRESSING_ROOM_DISTANCE = 2.0; + +// tool bar + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var BUTTON_SIZE = 32; +var PADDING = 3; +Script.include(["libraries/toolBars.js"]); +var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) { + return { + x: (BUTTON_SIZE + PADDING), + y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING) + }; +}); +var saveButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: "http://headache.hungry.com/~seth/hifi/save.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); +var loadButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: "http://headache.hungry.com/~seth/hifi/load.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + if (clickedOverlay == saveButton) { + manager.saveAttachedEntities(); + } else if (clickedOverlay == loadButton) { + manager.loadAttachedEntities(); + } +} + +function scriptEnding() { + toolBar.cleanup(); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Script.scriptEnding.connect(scriptEnding); + + + + +// attached entites + + +function AttachedEntitiesManager() { + this.subscribeToMessages = function() { + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(this.handleWearableMessages); + } + + this.handleWearableMessages = function(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + // if (sender !== MyAvatar.sessionUUID) { + // print('wearablesManager got message from wrong sender'); + // return; + // } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + return; + } + + if (parsedMessage.action === 'update' || + parsedMessage.action === 'loaded') { + // ignore + } else if (parsedMessage.action === 'release') { + manager.checkIfWearable(parsedMessage.grabbedEntity, parsedMessage.joint) + // manager.saveAttachedEntities(); + } else if (parsedMessage.action === 'shared-release') { + // manager.saveAttachedEntities(); + } else if (parsedMessage.action === 'equip') { + // manager.saveAttachedEntities(); + } else { + print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action); + } + } + + this.avatarIsInDressingRoom = function() { + // return true if MyAvatar is near the dressing room + var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE); + for (i = 0; i < possibleDressingRoom.length; i++) { + var entityID = possibleDressingRoom[i]; + var props = Entities.getEntityProperties(entityID); + if (props.name == 'Hifi-Dressing-Room-Base') { + return true; + } + } + return false; + } + + this.checkIfWearable = function(grabbedEntity, releasedFromJoint) { + var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; + + var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID"]); + if (props.parentID === NULL_UUID || props.parentID === MyAvatar.sessionUUID) { + var bestJointName = ""; + var bestJointIndex = -1; + var bestJointDistance = 0; + var bestJointOffset = null; + for (var jointName in allowedJoints) { + if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") && + (jointName == "LeftHand" || jointName == "RightHand")) { + // don't auto-attach to a hand if a hand just dropped something + continue; + } + var jointIndex = MyAvatar.getJointIndex(jointName); + if (jointIndex > 0) { + var jointPosition = MyAvatar.getJointPosition(jointIndex); + var distanceFromJoint = Vec3.distance(jointPosition, props.position); + if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) { + if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) { + bestJointName = jointName; + bestJointIndex = jointIndex; + bestJointDistance = distanceFromJoint; + bestJointOffset = allowedJoints[jointName]; + } + } + } + } + + if (bestJointIndex != -1) { + var wearProps = { + parentID: MyAvatar.sessionUUID, + parentJointIndex: bestJointIndex + }; + + if (!this.avatarIsInDressingRoom() && + bestJointOffset && bestJointOffset.constructor === Array && bestJointOffset.length > 1) { + // don't snap the entity to the preferred position if the avatar is in the dressing room. + wearProps.localPosition = bestJointOffset[0]; + wearProps.localRotation = bestJointOffset[1]; + } + Entities.editEntity(grabbedEntity, wearProps); + } else if (props.parentID != NULL_UUID) { + // drop the entity with no parent (not on the avatar) + Entities.editEntity(grabbedEntity, { + parentID: NULL_UUID + }); + } + } + } + + this.updateRelativeOffsets = function(entityID, props) { + // save the preferred (current) relative position and rotation into the user-data of the entity + var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA); + var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex]; + wearableData.joints[currentJointName] = [props.localPosition, props.localRotation]; + setEntityCustomData('wearable', entityID, wearableData); + } + + this.saveAttachedEntities = function() { + print("--- saving attached entities ---"); + saveData = []; + var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + for (i = 0; i < nearbyEntities.length; i++) { + var entityID = nearbyEntities[i]; + var props = Entities.getEntityProperties(entityID); + if (props.parentID == MyAvatar.sessionUUID) { + grabData = getEntityCustomData('grabKey', entityID, {}); + grabbableData = getEntityCustomData('grabbableKey', entityID, {}); + this.updateRelativeOffsets(entityID, props); + props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + + // if an entity is currently being held or equipped, its original properties are saved in + // the userData under "grabKey". Save with these original properties rather than the + // ones currently on the entity. + if (grabData.refCount > 0) { + if ("gravity" in grabData) { + props.gravity = grabData.gravity; + } + if ("collidesWith" in grabData) { + props.collidesWith = grabData.collidesWith; + } + if ("dynamic" in grabData) { + props.dynamic = grabData.dynamic; + } + if ("collisionless" in grabData) { + if ("invertSolidWhileHeld" in grabbableData && grabbableData.invertSolidWhileHeld) { + props.collisionless = !grabData.collisionless; + } else { + props.collisionless = grabData.collisionless; + } + } + // if ("parentID" in grabData) { + // props.parentID = grabData.parentID; + // } + // if ("parentJointIndex" in grabData) { + // props.parentJointIndex = grabData.parentJointIndex; + // } + } + + this.scrubProperties(props); + saveData.push(props); + } + } + Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + } + + this.scrubProperties = function(props) { + var toScrub = ["queryAACube", "position", "rotation", + "created", "ageAsText", "naturalDimensions", + "naturalPosition", "velocity", "acceleration", + "angularVelocity", "boundingBox"]; + toScrub.forEach(function(propertyName) { + delete props[propertyName]; + }); + // if the userData has a grabKey, strip it out + if ("userData" in props) { + try { + parsedUserData = JSON.parse(props.userData); + if ("grabKey" in parsedUserData) { + delete parsedUserData["grabKey"]; + props["userData"] = JSON.stringify(parsedUserData); + } + } catch (e) { + } + } + } + + this.loadAttachedEntities = function(grabbedEntity) { + print("--- loading attached entities ---"); + jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + var loadData = []; + try { + loadData = JSON.parse(jsonAttachmentData); + } catch (e) { + print('error parsing saved attachment data'); + return; + } + + for (i = 0; i < loadData.length; i ++) { + var savedProps = loadData[ i ]; + var currentProps = Entities.getEntityProperties(savedProps.id); + if (currentProps.id == savedProps.id && + // TODO -- also check that parentJointIndex matches? + currentProps.parentID == MyAvatar.sessionUUID) { + // entity is already in-world. TODO -- patch it up? + continue; + } + this.scrubProperties(savedProps); + delete savedProps["id"]; + savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + var loadedEntityID = Entities.addEntity(savedProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'loaded', + grabbedEntity: loadedEntityID + })); + } + } +} + +var manager = new AttachedEntitiesManager(); +manager.subscribeToMessages(); diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index cc35cac807..5f2cdcf89a 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -18,7 +18,7 @@ Script.include("../libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; // // these tune time-averaging and "on" value for analog trigger @@ -109,7 +109,8 @@ var GRABBABLE_PROPERTIES = [ "rotation", "gravity", "collidesWith", - "collisionsWillMove", + "dynamic", + "collisionless", "locked", "name", "shapeType", @@ -228,48 +229,6 @@ function entityIsGrabbedByOther(entityID) { return false; } -function getSpatialOffsetPosition(hand, spatialKey) { - var position = Vec3.ZERO; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) { - position = spatialKey.leftRelativePosition; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) { - position = spatialKey.rightRelativePosition; - } - if (spatialKey.relativePosition) { - position = spatialKey.relativePosition; - } - - // add the relative hand center offset - var handSizeRatio = calculateHandSizeRatio(); - position = Vec3.multiply(position, handSizeRatio); - return position; -} - -var yFlip = Quat.angleAxis(180, Vec3.UNIT_Y); - -function getSpatialOffsetRotation(hand, spatialKey) { - var rotation = Quat.IDENTITY; - - if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) { - rotation = spatialKey.leftRelativeRotation; - } - if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) { - rotation = spatialKey.rightRelativeRotation; - } - if (spatialKey.relativeRotation) { - rotation = spatialKey.relativeRotation; - } - - // Flip left hand - if (hand !== RIGHT_HAND) { - rotation = Quat.multiply(yFlip, rotation); - } - - return rotation; -} - function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -280,9 +239,6 @@ function MyController(hand) { this.getHandRotation = MyAvatar.getLeftPalmRotation; } - var SPATIAL_CONTROLLERS_PER_PALM = 2; - var TIP_CONTROLLER_OFFSET = 1; - this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. this.state = STATE_OFF; @@ -363,6 +319,15 @@ function MyController(hand) { } }; + this.callEntityMethodOnGrabbed = function(entityMethodName, args) { + print("Entity Method: " + entityMethodName + ", hand: " + this.hand); + if (args.length > 0) { + Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); + } else { + Entities.callEntityMethod(this.grabbedEntity, entityMethodName); + } + } + this.setState = function(newState) { if (WANT_DEBUG || WANT_DEBUG_STATE) { print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + @@ -381,7 +346,7 @@ function MyController(hand) { linePoints: [ZERO_VEC, farPoint], color: color, lifetime: 0.1, - collisionsWillMove: false, + dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { @@ -403,7 +368,7 @@ function MyController(hand) { linePoints: [ZERO_VEC, farPoint], color: color, lifetime: LIFETIME, - collisionsWillMove: false, + dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { @@ -767,6 +732,7 @@ function MyController(hand) { this.search = function() { this.grabbedEntity = null; + this.isInitialGrab = false; if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { this.setState(STATE_RELEASE); @@ -813,7 +779,7 @@ function MyController(hand) { direction: pickRay.direction }; - Messages.sendMessage('Hifi-Light-Overlay-Ray-Check', JSON.stringify(pickRayBacked)); + // Messages.sendMessage('Hifi-Light-Overlay-Ray-Check', JSON.stringify(pickRayBacked)); var intersection; @@ -861,6 +827,11 @@ function MyController(hand) { // too far away, don't grab continue; } + if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_EQUIP_SEARCHING) { + // don't allow a double-equip + continue; + } + if (distance < minDistance) { this.grabbedEntity = candidateEntities[i]; minDistance = distance; @@ -870,7 +841,7 @@ function MyController(hand) { } if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0); + var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; var isPhysical = this.propsArePhysical(props); // near or far trigger @@ -890,11 +861,14 @@ function MyController(hand) { return; } this.temporaryPositionOffset = null; - if (typeof grabbableData.spatialKey === 'undefined') { + if (!this.hasPresetOffsets()) { // We want to give a temporary position offset to this object so it is pulled close to hand var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, intersection.properties.position)); - this.temporaryPositionOffset = Vec3.normalize(Vec3.subtract(intersection.properties.position, handPosition)); + var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var handJointPosition = MyAvatar.getJointPosition(handJointIndex); + this.temporaryPositionOffset = + Vec3.normalize(Vec3.subtract(intersection.properties.position, handJointPosition)); this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, intersectionPointToCenterDistance * FAR_TO_NEAR_GRAB_PADDING_FACTOR); @@ -919,13 +893,17 @@ function MyController(hand) { if (this.intersectionDistance > 0) { // If we hit something with our pick ray, move the search sphere toward that distance - this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); + this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); } - var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); - this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + var searchSphereLocation = Vec3.sum(distantPickRay.origin, + Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); + this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, + (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); if ((USE_OVERLAY_LINES_FOR_SEARCHING === true) && PICK_WITH_HAND_RAY) { - this.overlayLineOn(handPosition, searchSphereLocation, (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + this.overlayLineOn(handPosition, searchSphereLocation, + (this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR); } }; @@ -966,14 +944,8 @@ function MyController(hand) { if (this.actionID !== null) { this.setState(STATE_CONTINUE_DISTANCE_HOLDING); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); + this.activateEntity(this.grabbedEntity, grabbedProperties, false); + this.callSetupEntityMethods("startDistanceGrab"); } this.currentAvatarPosition = MyAvatar.position; @@ -985,7 +957,9 @@ function MyController(hand) { this.continueDistanceHolding = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + if (this.isInitialGrab) { + this.callEntityMethodOnGrabbed("releaseGrab", []); + } return; } @@ -997,7 +971,7 @@ function MyController(hand) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() && - typeof grabbableData.spatialKey !== 'undefined') { + this.hasPresetOffsets()) { var saveGrabbedID = this.grabbedEntity; this.release(); this.setState(STATE_EQUIP); @@ -1067,7 +1041,7 @@ function MyController(hand) { this.handPreviousRotation = handRotation; this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); - Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); + this.callEntityMethodOnGrabbed("continueDistantGrab", []); var defaultMoveWithHeadData = { disableMoveWithHead: false @@ -1167,40 +1141,69 @@ function MyController(hand) { }; this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { - var aPrime = Vec3.subtract(position, axisStart); - - var bPrime = Vec3.subtract(axisEnd, axisStart); - - var bPrimeMagnitude = Vec3.length(bPrime); - var dotProduct = Vec3.dot(aPrime, bPrime); - - var scalar = dotProduct / bPrimeMagnitude; - if (scalar < 0) { scalar = 0; } - if (scalar > 1) { scalar = 1; } - var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); - return projection }; + this.callSetupEntityMethods = function(entityMethodName) { + if (this.isInitialGrab) { + if (this.hand === RIGHT_HAND) { + this.callEntityMethodOnGrabbed("setRightHand", []); + } else { + this.callEntityMethodOnGrabbed("setLeftHand", []); + } + this.callEntityMethodOnGrabbed("setHand", [this.hand]); + this.callEntityMethodOnGrabbed(entityMethodName, [JSON.stringify(this.hand)]); + } + } + + this.hasPresetOffsets = function() { + var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); + var allowedJoints = wearableData.joints; + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (handJointName in allowedJoints) { + return true; + } + } + + this.getPresetPosition = function() { + var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); + var allowedJoints = wearableData.joints; + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (handJointName in allowedJoints) { + return allowedJoints[handJointName][0]; + } + } + + this.getPresetRotation = function() { + var wearableData = getEntityCustomData('wearable', this.grabbedEntity, {joints: {}}); + var allowedJoints = wearableData.joints; + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (handJointName in allowedJoints) { + return allowedJoints[handJointName][1]; + } + } + this.nearGrabbing = function() { var now = Date.now(); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + if (this.isInitialGrab) { + this.callEntityMethodOnGrabbed("releaseGrab", []); + } return; } @@ -1208,10 +1211,10 @@ function MyController(hand) { this.overlayLineOff(); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + this.activateEntity(this.grabbedEntity, grabbedProperties, false); + if (grabbedProperties.dynamic && NEAR_GRABBING_KINEMATIC) { Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false + dynamic: false }); } @@ -1220,11 +1223,13 @@ function MyController(hand) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + var hasPresetPosition = false; + if (this.state != STATE_NEAR_GRABBING && this.hasPresetOffsets()) { + // if an object is "equipped" and has a predefined offset, use it. + this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + this.offsetPosition = this.getPresetPosition(); + this.offsetRotation = this.getPresetRotation(); + hasPresetPosition = true; } else { this.ignoreIK = false; @@ -1236,6 +1241,7 @@ function MyController(hand) { this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); if (this.temporaryPositionOffset && this.state != STATE_NEAR_GRABBING) { this.offsetPosition = this.temporaryPositionOffset; + hasPresetPosition = true; } } @@ -1247,42 +1253,44 @@ function MyController(hand) { } } else { // grab entity via parenting + this.actionID = null; var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - Entities.editEntity(this.grabbedEntity, { + reparentProps = { parentID: MyAvatar.sessionUUID, - parentJointIndex: handJointIndex, - localPosition: this.offsetPosition, - localRotation: this.offsetRotation - }); + parentJointIndex: handJointIndex + } + if (hasPresetPosition) { + reparentProps["localPosition"] = this.offsetPosition; + reparentProps["localRotation"] = this.offsetRotation; + } + Entities.editEntity(this.grabbedEntity, reparentProps); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'equip', + grabbedEntity: this.grabbedEntity + })); } + this.callSetupEntityMethods(this.state == STATE_NEAR_GRABBING ? "startNearGrab" : "startEquip"); + if (this.state == STATE_NEAR_GRABBING) { + // near grabbing this.setState(STATE_CONTINUE_NEAR_GRABBING); } else { // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); this.setState(STATE_CONTINUE_EQUIP_BD); } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - this.currentObjectTime = Date.now(); }; this.continueNearGrabbing = function() { if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + if (this.isInitialGrab) { + this.callEntityMethodOnGrabbed("releaseGrab", []); + } return; } if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) { @@ -1291,11 +1299,15 @@ function MyController(hand) { } if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) { this.setState(STATE_WAITING_FOR_BUMPER_RELEASE); + this.callEntityMethodOnGrabbed("releaseEquip", [JSON.stringify(this.hand)]); return; } if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { this.setState(STATE_CONTINUE_EQUIP_BD); - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + if (this.isInitialGrab) { + this.callEntityMethodOnGrabbed("releaseGrab", [JSON.stringify(this.hand)]); + this.callEntityMethodOnGrabbed("startEquip", [JSON.stringify(this.hand)]); + } return; } @@ -1314,16 +1326,23 @@ function MyController(hand) { this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; - Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); - if (this.state === STATE_CONTINUE_EQUIP_BD) { - Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); + if (this.isInitialGrab) { + if (this.state === STATE_CONTINUE_EQUIP) { + // this.callEntityMethodOnGrabbed("continueEquip", []); + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + // this.callEntityMethodOnGrabbed("continueNearGrab", []); + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + } } - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'update', - grabbedEntity: this.grabbedEntity - })) + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'update', + // grabbedEntity: this.grabbedEntity + // })) if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl @@ -1350,60 +1369,48 @@ function MyController(hand) { this.waitingForBumperRelease = function() { if (this.bumperReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - Entities.callEntityMethod(this.grabbedEntity, "unequip"); + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); + if (this.isInitialGrab) { + // TODO -- only one of these should be sent + this.callEntityMethodOnGrabbed("releaseGrab", []); + this.callEntityMethodOnGrabbed("unequip", []); + } } }; this.nearTrigger = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + this.callEntityMethodOnGrabbed("stopNearTrigger", []); return; } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.callSetupEntityMethods("startNearTrigger"); this.setState(STATE_CONTINUE_NEAR_TRIGGER); }; this.farTrigger = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + this.callEntityMethodOnGrabbed("stopFarTrigger", []); return; } - - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.callSetupEntityMethods("startFarTrigger"); this.setState(STATE_CONTINUE_FAR_TRIGGER); }; this.continueNearTrigger = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + this.callEntityMethodOnGrabbed("stopNearTrigger", []); return; } - - Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + this.callEntityMethodOnGrabbed("continueNearTrigger", []); }; this.continueFarTrigger = function() { if (this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + this.callEntityMethodOnGrabbed("stopFarTrigger", []); return; } @@ -1419,7 +1426,7 @@ function MyController(hand) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + this.callEntityMethodOnGrabbed("stopFarTrigger", []); return; } } @@ -1427,8 +1434,7 @@ function MyController(hand) { if (USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - - Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + this.callEntityMethodOnGrabbed("continueFarTrigger", []); }; _this.allTouchedIDs = {}; @@ -1488,15 +1494,16 @@ function MyController(hand) { }; this.startTouch = function(entityID) { - Entities.callEntityMethod(entityID, "startTouch"); + this.callEntityMethodOnGrabbed("startTouch", []); }; this.continueTouch = function(entityID) { - Entities.callEntityMethod(entityID, "continueTouch"); + // this.callEntityMethodOnGrabbed("continueTouch", []); + Entities.callEntityMethod(this.grabbedEntity, "continueTouch"); }; this.stopTouch = function(entityID) { - Entities.callEntityMethod(entityID, "stopTouch"); + this.callEntityMethodOnGrabbed("stopTouch", []); }; this.release = function() { @@ -1506,13 +1513,10 @@ function MyController(hand) { if (this.grabbedEntity !== null) { if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); //sometimes we want things to stay right where they are when we let go. - var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, - this.grabbedEntity, - DEFAULT_GRABBABLE_DATA); - if (releaseVelocityData.disableReleaseVelocity === true) { - Entities.deleteAction(this.grabbedEntity, this.actionID); - + var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + if (releaseVelocityData.disableReleaseVelocity === true || !this.isInitialGrab) { Entities.editEntity(this.grabbedEntity, { velocity: { x: 0, @@ -1524,25 +1528,28 @@ function MyController(hand) { y: 0, z: 0 } - }) - Entities.deleteAction(this.grabbedEntity, this.actionID); - - } else { - //don't make adjustments - Entities.deleteAction(this.grabbedEntity, this.actionID); + }); } } } this.deactivateEntity(this.grabbedEntity); - this.actionID = null; this.setState(STATE_OFF); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.grabbedEntity - })); + if (this.isInitialGrab) { + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + } else { + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'shared-release', + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + } this.grabbedEntity = null; }; @@ -1554,19 +1561,30 @@ function MyController(hand) { Entities.deleteEntity(this.pointLight); }; - this.activateEntity = function(entityID, grabbedProperties) { + this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); data["activated"] = true; data["avatarId"] = MyAvatar.sessionUUID; - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + if (wasLoaded) { + data["refCount"] = 1; + } else { + data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + } // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done if (data["refCount"] == 1) { + this.isInitialGrab = true; data["gravity"] = grabbedProperties.gravity; data["collidesWith"] = grabbedProperties.collidesWith; - data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - data["parentID"] = grabbedProperties.parentID; + data["collisionless"] = grabbedProperties.collisionless; + data["dynamic"] = grabbedProperties.dynamic; + data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; data["parentJointIndex"] = grabbedProperties.parentJointIndex; + + if ("invertSolidWhileHeld" in grabbableData && grabbableData.invertSolidWhileHeld) { + data["collisionless"] = !data["collisionless"]; + } + var whileHeldProperties = { gravity: { x: 0, @@ -1580,6 +1598,7 @@ function MyController(hand) { }; Entities.editEntity(entityID, whileHeldProperties); } else if (data["refCount"] > 1) { + this.isInitialGrab = false; // if an object is being grabbed by more than one person (or the same person twice, but nevermind), switch // the collision groups so that it wont collide with "other" avatars. This avoids a situation where two // people are holding something and one of them will be able (if the other releases at the right time) to @@ -1600,8 +1619,8 @@ function MyController(hand) { Entities.editEntity(entityID, { gravity: data["gravity"], collidesWith: data["collidesWith"], - collisionsWillMove: data["collisionsWillMove"], - ignoreForCollisions: data["ignoreForCollisions"], + collisionless: data["collisionless"], + dynamic: data["dynamic"], parentID: data["parentID"], parentJointIndex: data["parentJointIndex"] }); @@ -1617,7 +1636,7 @@ function MyController(hand) { if (this.state == STATE_OFF || this.state == STATE_SEARCHING || this.state == STATE_EQUIP_SEARCHING) { - var loadedProps = Entities.getEntityProperties(loadedEntityID, ["parentID", "parentJointIndex"]); + var loadedProps = Entities.getEntityProperties(loadedEntityID); if (loadedProps.parentID != MyAvatar.sessionUUID) { return; } @@ -1625,9 +1644,13 @@ function MyController(hand) { if (loadedProps.parentJointIndex != handJointIndex) { return; } + print("--- handControllerGrab found loaded entity ---"); // an entity has been loaded and it's where this script would have equipped something, so switch states. this.grabbedEntity = loadedEntityID; - this.setState(STATE_EQUIP); + this.activateEntity(this.grabbedEntity, loadedProps, true); + this.isInitialGrab = true; + this.callSetupEntityMethods("startEquip"); + this.setState(STATE_CONTINUE_EQUIP); } } }; diff --git a/examples/toybox/bow/bow.js b/examples/toybox/bow/bow.js index 0ac571276e..489cc6341e 100644 --- a/examples/toybox/bow/bow.js +++ b/examples/toybox/bow/bow.js @@ -139,7 +139,7 @@ this.hand = 'right'; }, - startNearGrab: function() { + startEquip: function() { print('START BOW GRAB') if (this.isGrabbed === true) { @@ -159,7 +159,7 @@ setEntityCustomData('grabbableKey', this.entityID, data); }, - continueNearGrab: function() { + continueEquip: function() { this.deltaTime = checkInterval(); //debounce during debugging -- maybe we're updating too fast? @@ -552,4 +552,4 @@ }; return new Bow(); -}); \ No newline at end of file +}); diff --git a/examples/toybox/bow/createBow.js b/examples/toybox/bow/createBow.js index ba4994ab1a..eb6d8bb81b 100644 --- a/examples/toybox/bow/createBow.js +++ b/examples/toybox/bow/createBow.js @@ -64,21 +64,22 @@ function makeBow() { script: SCRIPT_URL, userData: JSON.stringify({ grabbableKey: { - invertSolidWhileHeld: true, - spatialKey: { - leftRelativePosition: { - x: -0.02, - y: 0.08, - z: 0.09 - }, - relativePosition: { - x: 0.02, - y: 0.08, - z: 0.09 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90) - } - } + invertSolidWhileHeld: true + }, + wearable:{joints:{RightHand:[{x:0.03960523009300232, + y:0.01979270577430725, + z:0.03294898942112923}, + {x:-0.7257906794548035, + y:-0.4611682891845703, + z:0.4436084032058716, + w:-0.25251442193984985}], + LeftHand:[{x:0.0055799782276153564, + y:0.04354757443070412, + z:0.05119767785072327}, + {x:-0.14914104342460632, + y:0.6448180079460144, + z:-0.2888556718826294, + w:-0.6917579770088196}]}} }) }; @@ -147,4 +148,4 @@ function cleanup() { Entities.deleteEntity(preNotchString); } -Script.scriptEnding.connect(cleanup); \ No newline at end of file +Script.scriptEnding.connect(cleanup); diff --git a/examples/toybox/bubblewand/createWand.js b/examples/toybox/bubblewand/createWand.js index d859d7cb9f..d0b1372755 100644 --- a/examples/toybox/bubblewand/createWand.js +++ b/examples/toybox/bubblewand/createWand.js @@ -46,16 +46,22 @@ var wand = Entities.addEntity({ script: WAND_SCRIPT_URL, userData: JSON.stringify({ grabbableKey: { - invertSolidWhileHeld: true, - spatialKey: { - relativePosition: { - x: 0, - y: 0.1, - z: 0 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, -90) - } - } + invertSolidWhileHeld: true + }, + "wearable":{"joints":{"RightHand":[{"x":0.11421211808919907, + "y":0.06508062779903412, + "z":0.06317152827978134}, + {"x":-0.7886992692947388, + "y":-0.6108893156051636, + "z":-0.05003821849822998, + "w":0.047579944133758545}], + "LeftHand":[{"x":0.03530977666378021, + "y":0.11278322339057922, + "z":0.049768272787332535}, + {"x":-0.050609711557626724, + "y":-0.11595471203327179, + "z":0.3554558753967285, + "w":0.9260908961296082}]}} }) }); diff --git a/examples/toybox/bubblewand/wand.js b/examples/toybox/bubblewand/wand.js index fe1d917e69..d0ef2871f8 100644 --- a/examples/toybox/bubblewand/wand.js +++ b/examples/toybox/bubblewand/wand.js @@ -174,6 +174,9 @@ this.createBubbleAtTipOfWand(); } }, + startEquip: function(id, params) { + this.startNearGrab(id, params); + }, continueNearGrab: function() { var deltaTime = checkInterval(); //only get the properties that we need @@ -188,11 +191,17 @@ this.growBubbleWithWandVelocity(properties, deltaTime); }, + continueEquip: function() { + this.continueNearGrab(); + }, releaseGrab: function() { //delete the current buble and reset state when the wand is released Entities.deleteEntity(this.currentBubble); this.currentBubble = null; }, + releaseEquip: function() { + this.releaseGrab(); + }, }; diff --git a/examples/toybox/doll/doll.js b/examples/toybox/doll/doll.js index 04712f0e1d..c7661af610 100644 --- a/examples/toybox/doll/doll.js +++ b/examples/toybox/doll/doll.js @@ -49,6 +49,9 @@ this.isGrabbed = true; this.initialHand = this.hand; }, + startEquip: function(id, params) { + this.startNearGrab(id, params); + }, continueNearGrab: function() { var props = Entities.getEntityProperties(this.entityID, ["position"]); @@ -57,6 +60,9 @@ }; this.audioInjector.options = audioOptions; }, + continueEquip: function() { + this.continueNearGrab(); + }, releaseGrab: function() { if (this.isGrabbed === true && this.hand === this.initialHand) { @@ -73,6 +79,9 @@ this.isGrabbed = false; } }, + releaseEquip: function() { + this.releaseGrab(); + }, preload: function(entityID) { this.entityID = entityID; diff --git a/examples/toybox/flashlight/createFlashlight.js b/examples/toybox/flashlight/createFlashlight.js index 897ba516e1..be91516e63 100644 --- a/examples/toybox/flashlight/createFlashlight.js +++ b/examples/toybox/flashlight/createFlashlight.js @@ -39,6 +39,20 @@ var flashlight = Entities.addEntity({ userData: JSON.stringify({ grabbableKey: { invertSolidWhileHeld: true - } + }, + wearable:{joints:{RightHand:[{x:0.0717092975974083, + y:0.1166968047618866, + z:0.07085515558719635}, + {x:-0.7195770740509033, + y:0.175227552652359, + z:0.5953742265701294, + w:0.31150275468826294}], + LeftHand:[{x:0.0806504637002945, + y:0.09710478782653809, + z:0.08610185235738754}, + {x:0.5630447864532471, + y:-0.2545935809612274, + z:0.7855332493782043, + w:0.033170729875564575}]}} }) }); diff --git a/examples/toybox/flashlight/flashlight.js b/examples/toybox/flashlight/flashlight.js index 251ca71b7a..1a85af695d 100644 --- a/examples/toybox/flashlight/flashlight.js +++ b/examples/toybox/flashlight/flashlight.js @@ -86,6 +86,7 @@ }, startNearGrab: function(entityID) { + print("FLASHLIGHT startNearGrab"); if (!this.hasSpotlight) { var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); @@ -144,6 +145,9 @@ } }, + startEquip: function(id, params) { + this.startNearGrab(id, params); + }, setWhichHand: function() { this.whichHand = this.hand; @@ -157,6 +161,9 @@ this.changeLightWithTriggerPressure(this.whichHand); } }, + continueEquip: function() { + this.continueNearGrab(); + }, releaseGrab: function() { //delete the lights and reset state @@ -170,6 +177,9 @@ this.lightOn = false; } }, + releaseEquip: function() { + this.releaseGrab(); + }, changeLightWithTriggerPressure: function(flashLightHand) { @@ -258,4 +268,4 @@ // entity scripts always need to return a newly constructed object of our type return new Flashlight(); -}); \ No newline at end of file +}); diff --git a/examples/toybox/ping_pong_gun/createPingPongGun.js b/examples/toybox/ping_pong_gun/createPingPongGun.js index c8f7093b7e..d9f47eda37 100644 --- a/examples/toybox/ping_pong_gun/createPingPongGun.js +++ b/examples/toybox/ping_pong_gun/createPingPongGun.js @@ -38,16 +38,22 @@ var pingPongGun = Entities.addEntity({ collisionSoundURL: COLLISION_SOUND_URL, userData: JSON.stringify({ grabbableKey: { - spatialKey: { - relativePosition: { - x: -0.05, - y: 0, - z: 0.0 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, -90) - }, invertSolidWhileHeld: true - } + }, + wearable:{joints:{RightHand:[{x:0.1177130937576294, + y:0.12922893464565277, + z:0.08307232707738876}, + {x:0.4934672713279724, + y:0.3605862259864807, + z:0.6394805908203125, + w:-0.4664038419723511}], + LeftHand:[{x:0.09151676297187805, + y:0.13639454543590546, + z:0.09354984760284424}, + {x:-0.19628101587295532, + y:0.6418180465698242, + z:0.2830369472503662, + w:0.6851521730422974}]}} }) }); diff --git a/examples/toybox/ping_pong_gun/pingPongGun.js b/examples/toybox/ping_pong_gun/pingPongGun.js index 5b928719fa..bcb793746b 100644 --- a/examples/toybox/ping_pong_gun/pingPongGun.js +++ b/examples/toybox/ping_pong_gun/pingPongGun.js @@ -66,7 +66,7 @@ this.hand = 0; }, - startNearGrab: function() { + startEquip: function() { this.setWhichHand(); }, @@ -74,7 +74,7 @@ this.whichHand = this.hand; }, - continueNearGrab: function() { + continueEquip: function() { if (this.whichHand === null) { //only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten this.setWhichHand(); @@ -86,7 +86,7 @@ } }, - releaseGrab: function() { + releaseEquip: function() { var _this = this; if (this.whichHand === this.hand) { diff --git a/examples/toybox/pistol/createPistol.js b/examples/toybox/pistol/createPistol.js index e5f9391c65..1608c2fa4b 100644 --- a/examples/toybox/pistol/createPistol.js +++ b/examples/toybox/pistol/createPistol.js @@ -30,16 +30,22 @@ var pistol = Entities.addEntity({ collisionSoundURL: "http://hifi-content.s3.amazonaws.com/james/pistol/sounds/drop.wav", userData: JSON.stringify({ grabbableKey: { - spatialKey: { - relativePosition: { - x: 0, - y: 0.05, - z: -0.08 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0) - }, invertSolidWhileHeld: true - } + }, + wearable:{joints:{RightHand:[{x:0.07079616189002991, + y:0.20177987217903137, + z:0.06374628841876984}, + {x:-0.5863648653030396, + y:-0.46007341146469116, + z:0.46949487924575806, + w:-0.4733745753765106}], + LeftHand:[{x:0.1802254319190979, + y:0.13442856073379517, + z:0.08504903316497803}, + {x:0.2198076844215393, + y:-0.7377811074256897, + z:0.2780133783817291, + w:0.574519157409668}]}} }) }); diff --git a/examples/toybox/pistol/pistol.js b/examples/toybox/pistol/pistol.js index c2456d4b7d..87e2f57780 100644 --- a/examples/toybox/pistol/pistol.js +++ b/examples/toybox/pistol/pistol.js @@ -52,7 +52,7 @@ this.hand = JSON.parse(params[0]); }, - continueNearGrab: function() { + continueEquip: function() { if (!this.equipped) { return; } @@ -61,8 +61,6 @@ this.updateLaser(); } this.toggleWithTriggerPressure(); - - }, updateProps: function() { From ceed571c82267d3ba8056a42dff2458f91cb36d8 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 29 Jan 2016 16:02:58 -0800 Subject: [PATCH 11/19] Look harder for head, log if not found, and compute what we need regardless. --- interface/src/avatar/SkeletonModel.cpp | 28 +++++++++++++------------- libraries/fbx/src/FBXReader.cpp | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ad434f6b61..969773c3d3 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -46,21 +46,21 @@ void SkeletonModel::initJointStates() { // Determine the default eye position for avatar scale = 1.0 int headJointIndex = _geometry->getFBXGeometry().headJointIndex; - if (0 <= headJointIndex && headJointIndex < _rig->getJointStateCount()) { - - glm::vec3 leftEyePosition, rightEyePosition; - getEyeModelPositions(leftEyePosition, rightEyePosition); - glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f; - - int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex; - glm::vec3 rootModelPosition; - getJointPosition(rootJointIndex, rootModelPosition); - - _defaultEyeModelPosition = midEyePosition - rootModelPosition; - - // Skeleton may have already been scaled so unscale it - _defaultEyeModelPosition = _defaultEyeModelPosition / _scale; + if (0 > headJointIndex || headJointIndex >= _rig->getJointStateCount()) { + qCWarning(interfaceapp) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount(); } + glm::vec3 leftEyePosition, rightEyePosition; + getEyeModelPositions(leftEyePosition, rightEyePosition); + glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f; + + int rootJointIndex = _geometry->getFBXGeometry().rootJointIndex; + glm::vec3 rootModelPosition; + getJointPosition(rootJointIndex, rootModelPosition); + + _defaultEyeModelPosition = midEyePosition - rootModelPosition; + + // Skeleton may have already been scaled so unscale it + _defaultEyeModelPosition = _defaultEyeModelPosition / _scale; computeBoundingShape(); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1be3bbb5f6..b1507b79b0 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -600,7 +600,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } else if (name == jointLeanName) { jointLeanID = getID(object.properties); - } else if (name == jointHeadName) { + } else if (name == jointHeadName || name == "head" || name == "Head" || name == "HEAD" || name == "joint_head") { jointHeadID = getID(object.properties); } else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand") { From 464f74fc1ba413e8a7f6b4717379e757713db932 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jan 2016 16:37:19 -0800 Subject: [PATCH 12/19] don't attempt to distance-grab non-physical objects --- examples/controllers/handControllerGrab.js | 33 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 5f2cdcf89a..0a1482829b 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -18,7 +18,7 @@ Script.include("../libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; // // these tune time-averaging and "on" value for analog trigger @@ -122,7 +122,6 @@ var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js var DEFAULT_GRABBABLE_DATA = { - grabbable: true, disableReleaseVelocity: false }; @@ -320,7 +319,7 @@ function MyController(hand) { }; this.callEntityMethodOnGrabbed = function(entityMethodName, args) { - print("Entity Method: " + entityMethodName + ", hand: " + this.hand); + // print("Entity Method: " + entityMethodName + ", hand: " + this.hand); if (args.length > 0) { Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); } else { @@ -663,6 +662,9 @@ function MyController(hand) { }; this.propsArePhysical = function(props) { + if (!props.dynamic) { + return false; + } var isPhysical = (props.shapeType && props.shapeType != 'none'); return isPhysical; } @@ -808,7 +810,21 @@ function MyController(hand) { var grabbableDataForCandidate = getEntityCustomData(GRABBABLE_DATA_KEY, candidateEntities[i], DEFAULT_GRABBABLE_DATA); var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); - var grabbable = (typeof grabbableDataForCandidate.grabbable === 'undefined' || grabbableDataForCandidate.grabbable); + + var isPhysical = this.propsArePhysical(propsForCandidate); + var grabbable; + if (isPhysical) { + // physical things default to grabbable + grabbable = true; + } else { + // non-physical things default to non-grabbable + grabbable = false; + } + if ("grabbable" in grabbableDataForCandidate) { + // if userData indicates that this is grabbable or not, override the default. + grabbable = grabbableDataForCandidate.grabbable; + } + if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { continue; } @@ -832,6 +848,11 @@ function MyController(hand) { continue; } + if (this.state == STATE_SEARCHING && !isPhysical && distance > NEAR_PICK_MAX_DISTANCE) { + // we can't distance-grab non-physical + continue; + } + if (distance < minDistance) { this.grabbedEntity = candidateEntities[i]; minDistance = distance; @@ -855,7 +876,7 @@ function MyController(hand) { return; } // far grab or equip with action - if (isPhysical && !near) { + if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) { if (entityIsGrabbedByOther(intersection.entityID)) { // don't distance grab something that is already grabbed. return; @@ -879,7 +900,7 @@ function MyController(hand) { } // else this thing isn't physical. grab it by reparenting it. - this.setState(STATE_NEAR_GRABBING); + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; } From 0c5c682887f66b96f42e0a13ac08d1feb0060684 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jan 2016 17:38:24 -0800 Subject: [PATCH 13/19] unmuddle saved state in Settings and userData --- examples/attachedEntitiesManager.js | 34 +---------- examples/controllers/handControllerGrab.js | 69 +++++++++++----------- 2 files changed, 36 insertions(+), 67 deletions(-) diff --git a/examples/attachedEntitiesManager.js b/examples/attachedEntitiesManager.js index cbffb41f9a..f44564d6dc 100644 --- a/examples/attachedEntitiesManager.js +++ b/examples/attachedEntitiesManager.js @@ -209,35 +209,6 @@ function AttachedEntitiesManager() { grabbableData = getEntityCustomData('grabbableKey', entityID, {}); this.updateRelativeOffsets(entityID, props); props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them - - // if an entity is currently being held or equipped, its original properties are saved in - // the userData under "grabKey". Save with these original properties rather than the - // ones currently on the entity. - if (grabData.refCount > 0) { - if ("gravity" in grabData) { - props.gravity = grabData.gravity; - } - if ("collidesWith" in grabData) { - props.collidesWith = grabData.collidesWith; - } - if ("dynamic" in grabData) { - props.dynamic = grabData.dynamic; - } - if ("collisionless" in grabData) { - if ("invertSolidWhileHeld" in grabbableData && grabbableData.invertSolidWhileHeld) { - props.collisionless = !grabData.collisionless; - } else { - props.collisionless = grabData.collisionless; - } - } - // if ("parentID" in grabData) { - // props.parentID = grabData.parentID; - // } - // if ("parentJointIndex" in grabData) { - // props.parentJointIndex = grabData.parentJointIndex; - // } - } - this.scrubProperties(props); saveData.push(props); } @@ -253,12 +224,13 @@ function AttachedEntitiesManager() { toScrub.forEach(function(propertyName) { delete props[propertyName]; }); - // if the userData has a grabKey, strip it out + // if the userData has a grabKey, clear old state if ("userData" in props) { try { parsedUserData = JSON.parse(props.userData); if ("grabKey" in parsedUserData) { - delete parsedUserData["grabKey"]; + parsedUserData.grabKey.refCount = 0; + delete parsedUserData.grabKey["avatarId"]; props["userData"] = JSON.stringify(parsedUserData); } } catch (e) { diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 0a1482829b..653b759839 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -913,7 +913,7 @@ function MyController(hand) { var SEARCH_SPHERE_FOLLOW_RATE = 0.50; if (this.intersectionDistance > 0) { - // If we hit something with our pick ray, move the search sphere toward that distance + // If we hit something with our pick ray, move the search sphere toward that distance this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); } @@ -1215,7 +1215,7 @@ function MyController(hand) { return allowedJoints[handJointName][1]; } } - + this.nearGrabbing = function() { var now = Date.now(); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); @@ -1589,45 +1589,42 @@ function MyController(hand) { data["avatarId"] = MyAvatar.sessionUUID; if (wasLoaded) { data["refCount"] = 1; + data["avatarId"] = MyAvatar.sessionUUID; } else { data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; - } - // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - this.isInitialGrab = true; - data["gravity"] = grabbedProperties.gravity; - data["collidesWith"] = grabbedProperties.collidesWith; - data["collisionless"] = grabbedProperties.collisionless; - data["dynamic"] = grabbedProperties.dynamic; - data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; - data["parentJointIndex"] = grabbedProperties.parentJointIndex; - if ("invertSolidWhileHeld" in grabbableData && grabbableData.invertSolidWhileHeld) { - data["collisionless"] = !data["collisionless"]; + // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done + if (data["refCount"] == 1) { + this.isInitialGrab = true; + data["gravity"] = grabbedProperties.gravity; + data["collidesWith"] = grabbedProperties.collidesWith; + data["collisionless"] = grabbedProperties.collisionless; + data["dynamic"] = grabbedProperties.dynamic; + data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; + data["parentJointIndex"] = grabbedProperties.parentJointIndex; + + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + }, + // bummer, it isn't easy to do bitwise collisionMask operations like this: + //"collisionMask": COLLISION_MASK_WHILE_GRABBED | grabbedProperties.collisionMask + // when using string values + "collidesWith": COLLIDES_WITH_WHILE_GRABBED + }; + Entities.editEntity(entityID, whileHeldProperties); + } else if (data["refCount"] > 1) { + this.isInitialGrab = false; + // if an object is being grabbed by more than one person (or the same person twice, but nevermind), switch + // the collision groups so that it wont collide with "other" avatars. This avoids a situation where two + // people are holding something and one of them will be able (if the other releases at the right time) to + // bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in + // the collision mask hinges on who the physics simulation owner is. + Entities.editEntity(entityID, {"collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED}); } - - var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - }, - // bummer, it isn't easy to do bitwise collisionMask operations like this: - //"collisionMask": COLLISION_MASK_WHILE_GRABBED | grabbedProperties.collisionMask - // when using string values - "collidesWith": COLLIDES_WITH_WHILE_GRABBED - }; - Entities.editEntity(entityID, whileHeldProperties); - } else if (data["refCount"] > 1) { - this.isInitialGrab = false; - // if an object is being grabbed by more than one person (or the same person twice, but nevermind), switch - // the collision groups so that it wont collide with "other" avatars. This avoids a situation where two - // people are holding something and one of them will be able (if the other releases at the right time) to - // bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in - // the collision mask hinges on who the physics simulation owner is. - Entities.editEntity(entityID, {"collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED}); } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); return data; }; From 9a16bc7d22df8ed688bc5384d0821957a6725ef4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jan 2016 18:01:57 -0800 Subject: [PATCH 14/19] when a parented equip or grab releases and the object should fall, give it a little velocity so bullet doesn't consider it static --- examples/controllers/handControllerGrab.js | 29 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 653b759839..efd04e07ca 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -1532,6 +1532,7 @@ function MyController(hand) { this.turnLightsOff(); this.turnOffVisualizations(); + var noVelocity = false; if (this.grabbedEntity !== null) { if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -1550,11 +1551,12 @@ function MyController(hand) { z: 0 } }); + noVelocity = true; } } } - this.deactivateEntity(this.grabbedEntity); + this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; this.setState(STATE_OFF); @@ -1629,19 +1631,38 @@ function MyController(hand) { return data; }; - this.deactivateEntity = function(entityID) { + this.deactivateEntity = function(entityID, noVelocity) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); if (data && data["refCount"]) { data["refCount"] = data["refCount"] - 1; if (data["refCount"] < 1) { - Entities.editEntity(entityID, { + var deactiveProps = { gravity: data["gravity"], collidesWith: data["collidesWith"], collisionless: data["collisionless"], dynamic: data["dynamic"], parentID: data["parentID"], parentJointIndex: data["parentJointIndex"] - }); + }; + + // things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If + // it looks like the dropped thing should fall, give it a little velocity. + var parentID = Entities.getEntityProperties(entityID, ["parentID"]).parentID; + var forceVelocity = false; + if (!noVelocity && + parentID == MyAvatar.sessionUUID && + Vec3.length(data["gravity"]) > 0.0 && + data["dynamic"] && + data["parentID"] == NULL_UUID && + !data["collisionless"]) { + forceVelocity = true; + } + + Entities.editEntity(entityID, deactiveProps); + + if (forceVelocity) { + Entities.editEntity(entityID, {velocity:{x:0, y:0.1, z:0}}); + } data = null; } } else { From 6697b5ae5fc8cb8af1b9876a69d6db75677d17cb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 29 Jan 2016 18:36:22 -0800 Subject: [PATCH 15/19] get multi-near-grabs working again --- examples/controllers/handControllerGrab.js | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index efd04e07ca..b0081a5d0f 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -662,7 +662,8 @@ function MyController(hand) { }; this.propsArePhysical = function(props) { - if (!props.dynamic) { + if (!props.dynamic && props.parentID != MyAvatar.sessionUUID) { + // if we have parented something, don't do this check on dynamic. return false; } var isPhysical = (props.shapeType && props.shapeType != 'none'); @@ -809,6 +810,7 @@ function MyController(hand) { for (i = 0; i < candidateEntities.length; i++) { var grabbableDataForCandidate = getEntityCustomData(GRABBABLE_DATA_KEY, candidateEntities[i], DEFAULT_GRABBABLE_DATA); + var grabDataForCandidate = getEntityCustomData(GRAB_USER_DATA_KEY, candidateEntities[i], {}); var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var isPhysical = this.propsArePhysical(propsForCandidate); @@ -817,8 +819,12 @@ function MyController(hand) { // physical things default to grabbable grabbable = true; } else { - // non-physical things default to non-grabbable - grabbable = false; + // non-physical things default to non-grabbable unless they are already grabbed + if ("refCount" in grabDataForCandidate && grabDataForCandidate.refCount > 0) { + grabbable = true; + } else { + grabbable = false; + } } if ("grabbable" in grabbableDataForCandidate) { // if userData indicates that this is grabbable or not, override the default. @@ -871,7 +877,7 @@ function MyController(hand) { return; } // near grab or equip with action - if (isPhysical && near) { + if (near) { this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; } @@ -899,9 +905,12 @@ function MyController(hand) { return; } - // else this thing isn't physical. grab it by reparenting it. - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); - return; + // else this thing isn't physical. grab it by reparenting it (but not if we've already + // grabbed it). + if (grabbableData.refCount < 1) { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); + return; + } } //search line visualizations From 7767dfb2138f99ba30e5ae36e47542dcf4e048ec Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 30 Jan 2016 19:59:56 -0800 Subject: [PATCH 16/19] remove high freq messages --- examples/controllers/handControllerGrab.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index d6fd4bce27..33c67d40e4 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -817,8 +817,6 @@ function MyController(hand) { direction: pickRay.direction }; - Messages.sendMessage('Hifi-Light-Overlay-Ray-Check', JSON.stringify(pickRayBacked)); - var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { @@ -1341,12 +1339,6 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); } - //// jbp::: SEND UPDATE MESSAGE TO WEARABLES MANAGER - Messages.sendMessage('Hifi-Wearables-Manager', JSON.stringify({ - action: 'update', - grabbedEntity: this.grabbedEntity - })) - if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl var success = Entities.updateAction(this.grabbedEntity, this.actionID, { @@ -1615,8 +1607,6 @@ function MyController(hand) { this.actionID = null; this.setState(STATE_OFF); - //// jbp::: SEND RELEASE MESSAGE TO WEARABLES MANAGER - Messages.sendMessage('Hifi-Wearables-Manager', JSON.stringify({ action: 'checkIfWearable', grabbedEntity: this.grabbedEntity From 499e6d980471dfa6111e6e2de8f8aaf36d74b2e7 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Sat, 30 Jan 2016 20:04:51 -0800 Subject: [PATCH 17/19] update readme --- examples/light_modifier/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/light_modifier/README.md b/examples/light_modifier/README.md index f23f22148a..f23bd25dda 100644 --- a/examples/light_modifier/README.md +++ b/examples/light_modifier/README.md @@ -1,3 +1,5 @@ +*Temporarily Deprecated - needs a better way to know when 'grab beams' intersect with 'light overlays'. Sending messages containing the ray from the hand grab script to the overlay intersection test doesn't seem to be sustainable. * + This PR demonstrates one way in-world editing of objects might work. Running this script will show light overlay icons in-world. Enter edit mode by running your distance beam through a light overlay. Exit using the red X. From 71664dffae19d6703ce02035c38ed6b617b444d3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 1 Feb 2016 09:50:01 -0800 Subject: [PATCH 18/19] change string comparisons to be CaseSensitive --- .../animation/src/AnimInverseKinematics.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 48287d5c7e..9f08ce455a 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -542,16 +542,16 @@ void AnimInverseKinematics::initConstraints() { for (int i = 0; i < numJoints; ++i) { // compute the joint's baseName and remember whether its prefix was "Left" or not QString baseName = _skeleton->getJointName(i); - bool isLeft = baseName.startsWith("Left", Qt::CaseInsensitive); + bool isLeft = baseName.startsWith("Left", Qt::CaseSensitive); float mirror = isLeft ? -1.0f : 1.0f; if (isLeft) { baseName.remove(0, 4); - } else if (baseName.startsWith("Right", Qt::CaseInsensitive)) { + } else if (baseName.startsWith("Right", Qt::CaseSensitive)) { baseName.remove(0, 5); } RotationConstraint* constraint = nullptr; - if (0 == baseName.compare("Arm", Qt::CaseInsensitive)) { + if (0 == baseName.compare("Arm", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f); @@ -585,7 +585,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (0 == baseName.compare("UpLeg", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("UpLeg", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); @@ -611,7 +611,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(swungDirections); constraint = static_cast(stConstraint); - } else if (0 == baseName.compare("Hand", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_HAND_TWIST = 3.0f * PI / 5.0f; @@ -650,7 +650,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (baseName.startsWith("Shoulder", Qt::CaseInsensitive)) { + } else if (baseName.startsWith("Shoulder", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_SHOULDER_TWIST = PI / 20.0f; @@ -662,7 +662,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (baseName.startsWith("Spine", Qt::CaseInsensitive)) { + } else if (baseName.startsWith("Spine", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_SPINE_TWIST = PI / 12.0f; @@ -672,13 +672,13 @@ void AnimInverseKinematics::initConstraints() { const float MAX_SPINE_SWING = PI / 14.0f; minDots.push_back(cosf(MAX_SPINE_SWING)); stConstraint->setSwingLimits(minDots); - if (0 == baseName.compare("Spine1", Qt::CaseInsensitive) - || 0 == baseName.compare("Spine", Qt::CaseInsensitive)) { + if (0 == baseName.compare("Spine1", Qt::CaseSensitive) + || 0 == baseName.compare("Spine", Qt::CaseSensitive)) { stConstraint->setLowerSpine(true); } constraint = static_cast(stConstraint); - } else if (baseName.startsWith("Hips2", Qt::CaseInsensitive)) { + } else if (baseName.startsWith("Hips2", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_SPINE_TWIST = PI / 8.0f; @@ -690,7 +690,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (0 == baseName.compare("Neck", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("Neck", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_NECK_TWIST = PI / 9.0f; @@ -702,7 +702,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (0 == baseName.compare("Head", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); const float MAX_HEAD_TWIST = PI / 9.0f; @@ -714,7 +714,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); - } else if (0 == baseName.compare("ForeArm", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("ForeArm", Qt::CaseSensitive)) { // The elbow joint rotates about the parent-frame's zAxis (-zAxis) for the Right (Left) arm. ElbowConstraint* eConstraint = new ElbowConstraint(); glm::quat referenceRotation = _defaultRelativePoses[i].rot; @@ -745,7 +745,7 @@ void AnimInverseKinematics::initConstraints() { eConstraint->setAngleLimits(minAngle, maxAngle); constraint = static_cast(eConstraint); - } else if (0 == baseName.compare("Leg", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("Leg", Qt::CaseSensitive)) { // The knee joint rotates about the parent-frame's -xAxis. ElbowConstraint* eConstraint = new ElbowConstraint(); glm::quat referenceRotation = _defaultRelativePoses[i].rot; @@ -776,7 +776,7 @@ void AnimInverseKinematics::initConstraints() { eConstraint->setAngleLimits(minAngle, maxAngle); constraint = static_cast(eConstraint); - } else if (0 == baseName.compare("Foot", Qt::CaseInsensitive)) { + } else if (0 == baseName.compare("Foot", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f); From 24f53492f570e76bf213115a78efa7ef7be086d3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 1 Feb 2016 09:52:03 -0800 Subject: [PATCH 19/19] Back out changes to reader. --- libraries/fbx/src/FBXReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index b1507b79b0..1be3bbb5f6 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -600,7 +600,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } else if (name == jointLeanName) { jointLeanID = getID(object.properties); - } else if (name == jointHeadName || name == "head" || name == "Head" || name == "HEAD" || name == "joint_head") { + } else if (name == jointHeadName) { jointHeadID = getID(object.properties); } else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand") {