From 74c163d04797ba9ecfa969c1b1402b95d919a960 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 25 May 2017 10:25:29 -0700 Subject: [PATCH 01/66] snapshot of safeLanding. Works as long as entities don't intersect. --- interface/src/avatar/MyAvatar.cpp | 88 +++++++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 89 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9996df2afc..bc8b4c33c3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2180,6 +2180,94 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, emit transformChanged(); } +bool MyAvatar::safeLanding(const glm::vec3& position) { + // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point, regardless of "face direction". + // There to be a need for a "landing" if the closest above and the closest below are either: + // a) less than the avatar capsule height apart, or + // b) on the same entity (thus enclosing the point). + // If no landing is required, we go to that point directly and return false; + // When a landing is required, we the entity that has the nearest "above" intersection, and find the highest intersection on the same entity + // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. + // We then recurse with that, and when it bottoms out, the last candidate point is where we place the avatar's feet, and return true; + // FIXME test: MyAvatar.safeLanding({x: 100, y: 100, z: 100}) + //qDebug() << "FIXME avatar position:" << getPosition() << "halfHeight:" << _characterController.getCapsuleHalfHeight() << "height:" << _skeletonModel->getBoundingCapsuleHeight() << "offset:" << _characterController.getCapsuleLocalOffset(); + auto direct = [&](QString label) { + qDebug() << "Already safe" << label << position; + goToLocation(position); + return false; + }; + auto halfHeight = _characterController.getCapsuleHalfHeight(); + if (halfHeight == 0) { + return direct("zero height avatar"); + } + auto entityTree = DependencyManager::get()->getTree(); + if (!entityTree) { + return direct("no entity tree"); + } + // FIXME: capsule has an offset from position! + + QVector include{}; + QVector ignore{}; + OctreeElementPointer element; + EntityItemPointer intersectedEntity = NULL; + bool intersects; + float distance; + BoxFace face; + glm::vec3 surfaceNormal; + const bool visibleOnly = false; + const bool collidableOnly = true; + const bool precisionPicking = true; + const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? + bool* accurateResult = NULL; + // FIXME DRY this, and get rid of the extra vector arithmetic. + intersects = entityTree->findRayIntersection(position, Vectors::UNIT_Y, + include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, surfaceNormal, + (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return direct("no above"); + } + glm::vec3 upperIntersection = position + (Vectors::UNIT_Y * distance); + auto upperId = intersectedEntity->getEntityItemID(); + auto upperY = surfaceNormal.y; + + intersects = entityTree->findRayIntersection(position, Vectors::UNIT_NEG_Y, + include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, surfaceNormal, + (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return direct("no below"); + } + glm::vec3 lowerIntersection = position + (Vectors::UNIT_NEG_Y * distance); + auto lowerId = intersectedEntity->getEntityItemID(); + auto lowerY = surfaceNormal.y; + + auto delta = glm::distance(upperIntersection, lowerIntersection); + //qDebug() << "FIXME position:" << position << "upper:" << upperId << upperIntersection << "lower:" << lowerId << lowerIntersection << "distance:" << delta << "height:" << (2 * _characterController.getCapsuleHalfHeight()); + if ((upperId != lowerId) && (delta > (2 * halfHeight))) { + qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperY << "lower:" << lowerId << lowerIntersection << " n:" << lowerY << "delta:" << delta << "halfHeight:" << halfHeight; + return direct("enough room"); + } + qDebug() << "FIXME need to compute safe landing for" << position; + const float big = (float)TREE_SCALE; + const auto skyHigh = Vectors::UNIT_Y * big; + include.push_back(upperId); + auto fromAbove = position + skyHigh; + intersects = entityTree->findRayIntersection(fromAbove, Vectors::UNIT_NEG_Y, + include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, surfaceNormal, + (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return direct("no landing"); + } + // Bottom of capsule is at intersection, so capsule center is above that, which also avoids looping on the same intersection. + auto newFloor = fromAbove + (Vectors::UNIT_NEG_Y * distance); + auto newTarget = newFloor + (Vectors::UNIT_Y * halfHeight); + qDebug() << "FIXME newFloor:" << newFloor << "newTarget:" << newTarget; + safeLanding(newTarget); + return true; +} + void MyAvatar::updateMotionBehaviorFromMenu() { if (QThread::currentThread() != thread()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index fde350a43e..b41483d761 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -506,6 +506,7 @@ public slots: bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); From 9d73d0c931d0db4397ca198e7cdcbccc99722259 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 25 May 2017 12:09:22 -0700 Subject: [PATCH 02/66] works with nested enclosing entities --- interface/src/avatar/MyAvatar.cpp | 60 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bc8b4c33c3..2a70a3dab3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2199,12 +2199,16 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { auto halfHeight = _characterController.getCapsuleHalfHeight(); if (halfHeight == 0) { return direct("zero height avatar"); - } + } auto entityTree = DependencyManager::get()->getTree(); if (!entityTree) { return direct("no entity tree"); } // FIXME: capsule has an offset from position! + // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. + // However: + // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes. + // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. QVector include{}; QVector ignore{}; @@ -2225,8 +2229,12 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { element, distance, face, surfaceNormal, (void**)&intersectedEntity, lockType, accurateResult); if (!intersects || !intersectedEntity) { + // We currently believe that physics will reliably push us out if our feet are embedded, + // as long as our capsule center is out and there's room above us. Here we have those + // conditions, so no need to check our feet below. return direct("no above"); } + // FIXME: use _worldUpDirection and negative instead of UNIT_Y? If we use normalMumble.y, will need to also dot against _worldUpDirection. glm::vec3 upperIntersection = position + (Vectors::UNIT_Y * distance); auto upperId = intersectedEntity->getEntityItemID(); auto upperY = surfaceNormal.y; @@ -2236,19 +2244,55 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { element, distance, face, surfaceNormal, (void**)&intersectedEntity, lockType, accurateResult); if (!intersects || !intersectedEntity) { + // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. return direct("no below"); } glm::vec3 lowerIntersection = position + (Vectors::UNIT_NEG_Y * distance); auto lowerId = intersectedEntity->getEntityItemID(); auto lowerY = surfaceNormal.y; - auto delta = glm::distance(upperIntersection, lowerIntersection); - //qDebug() << "FIXME position:" << position << "upper:" << upperId << upperIntersection << "lower:" << lowerId << lowerIntersection << "distance:" << delta << "height:" << (2 * _characterController.getCapsuleHalfHeight()); - if ((upperId != lowerId) && (delta > (2 * halfHeight))) { - qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperY << "lower:" << lowerId << lowerIntersection << " n:" << lowerY << "delta:" << delta << "halfHeight:" << halfHeight; - return direct("enough room"); + if ((upperY < 0) && (lowerY > 0)) { + // The surface above us is the bottom of something, and the surface below us it the top of something. + // I.e., we are in a clearing between two objects. + auto delta = glm::distance(upperIntersection, lowerIntersection); + //qDebug() << "FIXME position:" << position << "upper:" << upperId << upperIntersection << "lower:" << lowerId << lowerIntersection << "distance:" << delta << "height:" << (2 * _characterController.getCapsuleHalfHeight()); + if (delta > (2 * halfHeight)) { + // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. + if (upperId != lowerId) { // An optimization over what follows: the simplest case of not being inside an entity. + // We're going to iterate upwards through successive roofIntersections, testing to see if we're contained within the top surface of some entity. + // There will be one of two outcomes: + // a) We're not contained, so we have enough room and our position is good. + // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. + auto entityAbove = upperId; + auto roofIntersection = upperIntersection; + while (1) { + ignore.push_back(entityAbove); + intersects = entityTree->findRayIntersection(roofIntersection, Vectors::UNIT_Y, + include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, surfaceNormal, + (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! + qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperY << " lower:" << lowerId << lowerIntersection << " n:" << lowerY << " delta:" << delta << " halfHeight:" << halfHeight; + return direct("enough room"); + } + roofIntersection = roofIntersection + (Vectors::UNIT_Y * distance); + if (surfaceNormal.y > 0) { + // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. + // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top + // of the enclosing entity, let's put our feet at roofIntersection and start over. + safeLanding(roofIntersection + (Vectors::UNIT_Y * halfHeight)); + return true; + } + // We found a new bottom surface, which we're not interested in. + // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. + entityAbove = intersectedEntity->getEntityItemID(); + } + ignore.clear(); // We didn't find anything, but in what happens below, don't ignore the entities we've encountered. + } + } } - qDebug() << "FIXME need to compute safe landing for" << position; + qDebug() << "FIXME need to compute safe landing for" << position << " based on " << upperIntersection << "@" << upperId << " and " << lowerIntersection << "@" << lowerId; const float big = (float)TREE_SCALE; const auto skyHigh = Vectors::UNIT_Y * big; include.push_back(upperId); @@ -2263,7 +2307,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // Bottom of capsule is at intersection, so capsule center is above that, which also avoids looping on the same intersection. auto newFloor = fromAbove + (Vectors::UNIT_NEG_Y * distance); auto newTarget = newFloor + (Vectors::UNIT_Y * halfHeight); - qDebug() << "FIXME newFloor:" << newFloor << "newTarget:" << newTarget; + qDebug() << "FIXME newFloor:" << newFloor << " newTarget:" << newTarget; safeLanding(newTarget); return true; } From ac8cb6341928c1d5e728889b46ddb8af35f7ef97 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 25 May 2017 14:34:24 -0700 Subject: [PATCH 03/66] clean and DRY --- interface/src/avatar/MyAvatar.cpp | 152 ++++++++++++++---------------- 1 file changed, 70 insertions(+), 82 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2a70a3dab3..808d2730a9 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2181,19 +2181,24 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } bool MyAvatar::safeLanding(const glm::vec3& position) { - // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point, regardless of "face direction". - // There to be a need for a "landing" if the closest above and the closest below are either: - // a) less than the avatar capsule height apart, or - // b) on the same entity (thus enclosing the point). + // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. + // There needs to be a "landing" if: + // a) the closest above and the closest below are less than the avatar capsule height apart, or + // b) the above point is the top surface of an entity, indicating that we are inside it. // If no landing is required, we go to that point directly and return false; - // When a landing is required, we the entity that has the nearest "above" intersection, and find the highest intersection on the same entity + // When a landing is required by a, we find the highest intersection on that closest-above entity // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. - // We then recurse with that, and when it bottoms out, the last candidate point is where we place the avatar's feet, and return true; - // FIXME test: MyAvatar.safeLanding({x: 100, y: 100, z: 100}) - //qDebug() << "FIXME avatar position:" << getPosition() << "halfHeight:" << _characterController.getCapsuleHalfHeight() << "height:" << _skeletonModel->getBoundingCapsuleHeight() << "offset:" << _characterController.getCapsuleLocalOffset(); - auto direct = [&](QString label) { - qDebug() << "Already safe" << label << position; - goToLocation(position); + // For b, use that top surface point. + // We then place our feet there, recurse with new capsule center point, and return true; + + const auto offset = _characterController.getCapsuleLocalOffset(); + const auto capsuleCenter = position + offset; + // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: + // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. + // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. + auto direct = [&](const char* label) { // position is good to go, or at least, we cannot do better + //qDebug() << "Already safe" << label << position; + goToLocation(position); // I.e., capsuleCenter - offset return false; }; auto halfHeight = _characterController.getCapsuleHalfHeight(); @@ -2204,58 +2209,58 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { if (!entityTree) { return direct("no entity tree"); } - // FIXME: capsule has an offset from position! - // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. - // However: - // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes. - // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. + glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; + EntityItemID upperId, lowerId; + const auto up = _worldUpDirection, down = -up; QVector include{}; QVector ignore{}; - OctreeElementPointer element; - EntityItemPointer intersectedEntity = NULL; - bool intersects; - float distance; - BoxFace face; - glm::vec3 surfaceNormal; - const bool visibleOnly = false; - const bool collidableOnly = true; - const bool precisionPicking = true; - const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? - bool* accurateResult = NULL; - // FIXME DRY this, and get rid of the extra vector arithmetic. - intersects = entityTree->findRayIntersection(position, Vectors::UNIT_Y, - include, ignore, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, - (void**)&intersectedEntity, lockType, accurateResult); - if (!intersects || !intersectedEntity) { + auto recurse = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. + safeLanding(upperIntersection + (up * halfHeight) - offset); + return true; + }; + auto isUp = [&](const glm::vec3& normal) { return glm::dot(normal, up) > 0.0f; }; // true iff normal points up + auto isDown = [&](const glm::vec3& normal) { return glm::dot(normal, up) < 0.0f; }; + auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { + OctreeElementPointer element; + EntityItemPointer intersectedEntity = NULL; + float distance; + BoxFace face; + const bool visibleOnly = false; + const bool collidableOnly = true; + const bool precisionPicking = true; + const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? + bool* accurateResult = NULL; + + bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult); + if (!intersects || !intersectedEntity) { + return false; + } + intersectionOut = startPointIn + (directionIn * distance); + entityIdOut = intersectedEntity->getEntityItemID(); + return true; + }; + + // The Algorithm, in four parts: + + if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those // conditions, so no need to check our feet below. - return direct("no above"); + return direct("nothing above"); } - // FIXME: use _worldUpDirection and negative instead of UNIT_Y? If we use normalMumble.y, will need to also dot against _worldUpDirection. - glm::vec3 upperIntersection = position + (Vectors::UNIT_Y * distance); - auto upperId = intersectedEntity->getEntityItemID(); - auto upperY = surfaceNormal.y; - intersects = entityTree->findRayIntersection(position, Vectors::UNIT_NEG_Y, - include, ignore, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, - (void**)&intersectedEntity, lockType, accurateResult); - if (!intersects || !intersectedEntity) { + if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. - return direct("no below"); + return direct("nothing below"); } - glm::vec3 lowerIntersection = position + (Vectors::UNIT_NEG_Y * distance); - auto lowerId = intersectedEntity->getEntityItemID(); - auto lowerY = surfaceNormal.y; - if ((upperY < 0) && (lowerY > 0)) { + // See if we have room between entities above and below, but that we are not contained. + if (isDown(upperNormal) && isUp(lowerNormal)) { // The surface above us is the bottom of something, and the surface below us it the top of something. // I.e., we are in a clearing between two objects. auto delta = glm::distance(upperIntersection, lowerIntersection); - //qDebug() << "FIXME position:" << position << "upper:" << upperId << upperIntersection << "lower:" << lowerId << lowerIntersection << "distance:" << delta << "height:" << (2 * _characterController.getCapsuleHalfHeight()); if (delta > (2 * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. if (upperId != lowerId) { // An optimization over what follows: the simplest case of not being inside an entity. @@ -2263,53 +2268,36 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // There will be one of two outcomes: // a) We're not contained, so we have enough room and our position is good. // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. - auto entityAbove = upperId; - auto roofIntersection = upperIntersection; - while (1) { - ignore.push_back(entityAbove); - intersects = entityTree->findRayIntersection(roofIntersection, Vectors::UNIT_Y, - include, ignore, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, - (void**)&intersectedEntity, lockType, accurateResult); - if (!intersects || !intersectedEntity) { + for (;;) { + ignore.push_back(upperId); + if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! - qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperY << " lower:" << lowerId << lowerIntersection << " n:" << lowerY << " delta:" << delta << " halfHeight:" << halfHeight; + //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; return direct("enough room"); } - roofIntersection = roofIntersection + (Vectors::UNIT_Y * distance); - if (surfaceNormal.y > 0) { + if (isUp(upperNormal)) { // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top - // of the enclosing entity, let's put our feet at roofIntersection and start over. - safeLanding(roofIntersection + (Vectors::UNIT_Y * halfHeight)); - return true; + // of the enclosing entity, let's put our feet at upperIntersection and start over. + return recurse(); } // We found a new bottom surface, which we're not interested in. // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. - entityAbove = intersectedEntity->getEntityItemID(); } - ignore.clear(); // We didn't find anything, but in what happens below, don't ignore the entities we've encountered. } } } - qDebug() << "FIXME need to compute safe landing for" << position << " based on " << upperIntersection << "@" << upperId << " and " << lowerIntersection << "@" << lowerId; + + //qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << upperIntersection << "@" << upperId << " and " << lowerIntersection << "@" << lowerId; const float big = (float)TREE_SCALE; - const auto skyHigh = Vectors::UNIT_Y * big; - include.push_back(upperId); - auto fromAbove = position + skyHigh; - intersects = entityTree->findRayIntersection(fromAbove, Vectors::UNIT_NEG_Y, - include, ignore, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, - (void**)&intersectedEntity, lockType, accurateResult); - if (!intersects || !intersectedEntity) { - return direct("no landing"); + const auto skyHigh = up * big; + auto fromAbove = capsuleCenter + skyHigh; + include.push_back(upperId); // We're looking for the intersection from above onto this entity. + if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { + return direct("Unable to find a landing"); } - // Bottom of capsule is at intersection, so capsule center is above that, which also avoids looping on the same intersection. - auto newFloor = fromAbove + (Vectors::UNIT_NEG_Y * distance); - auto newTarget = newFloor + (Vectors::UNIT_Y * halfHeight); - qDebug() << "FIXME newFloor:" << newFloor << " newTarget:" << newTarget; - safeLanding(newTarget); - return true; + // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. + return recurse(); } void MyAvatar::updateMotionBehaviorFromMenu() { From 905b5261d1a57bc1051b76d93735db487cabf58e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 25 May 2017 16:32:44 -0700 Subject: [PATCH 04/66] If collisions are enabled, turn them off in safeLanding, and restore. --- interface/src/avatar/MyAvatar.cpp | 13 ++++++++++++- interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 808d2730a9..d72b96828a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2180,6 +2180,10 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, emit transformChanged(); } +void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. + goToLocation(position); + QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); +} bool MyAvatar::safeLanding(const glm::vec3& position) { // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. // There needs to be a "landing" if: @@ -2198,7 +2202,14 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. auto direct = [&](const char* label) { // position is good to go, or at least, we cannot do better //qDebug() << "Already safe" << label << position; - goToLocation(position); // I.e., capsuleCenter - offset + if (!getCollisionsEnabled()) { + goToLocation(position); + } else { // If you try to go while stuck, physics will keep you stuck. + setCollisionsEnabled(false); + // Don't goToLocation just yet. Yield so that physics can act on the above. + QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick + Q_ARG(glm::vec3, position)); // I.e., capsuleCenter - offset + } return false; }; auto halfHeight = _characterController.getCapsuleHalfHeight(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b41483d761..00f2cdf6bd 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -506,6 +506,7 @@ public slots: bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + void goToLocationAndEnableCollisions(const glm::vec3& newPosition); bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); From 4be31f1b1830c0de0acc521c8b1016f098a0d8e6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 26 May 2017 15:31:59 -0700 Subject: [PATCH 05/66] thread safety --- interface/src/avatar/MyAvatar.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 43fffe7e0b..ee83975142 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2233,6 +2233,11 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. // For b, use that top surface point. // We then place our feet there, recurse with new capsule center point, and return true; + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); + return result; + } const auto offset = _characterController.getCapsuleLocalOffset(); const auto capsuleCenter = position + offset; From f694691efbc30a146fedf654cc7f266578dc53e0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 26 May 2017 16:15:18 -0700 Subject: [PATCH 06/66] add fixme comments and make isUp/isDown ordinary methods. --- interface/src/avatar/MyAvatar.cpp | 11 +++++++---- interface/src/avatar/MyAvatar.h | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ee83975142..422053dfa5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2220,6 +2220,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. + // FIXME: Doesn't work 100% of time. Need to figure out what isn't happening fast enough. E.g., don't goToLocation until confirmed removed from physics? goToLocation(position); QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } @@ -2232,14 +2233,17 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // When a landing is required by a, we find the highest intersection on that closest-above entity // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. // For b, use that top surface point. - // We then place our feet there, recurse with new capsule center point, and return true; + // We then place our feet there, recurse with new capsule center point, and return true. + + // I'm not sure the true/false return is useful, and it does make things more complicated. Might get rid of it. + // E.g., Q_RETURN_ARG, and direct() could be a normal method. if (QThread::currentThread() != thread()) { bool result; QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); return result; } - const auto offset = _characterController.getCapsuleLocalOffset(); + const auto offset = _characterController.getCapsuleLocalOffset(); // FIXME: correct space. const auto capsuleCenter = position + offset; // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. @@ -2274,14 +2278,13 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { safeLanding(upperIntersection + (up * halfHeight) - offset); return true; }; - auto isUp = [&](const glm::vec3& normal) { return glm::dot(normal, up) > 0.0f; }; // true iff normal points up - auto isDown = [&](const glm::vec3& normal) { return glm::dot(normal, up) < 0.0f; }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; float distance; BoxFace face; const bool visibleOnly = false; + // FIXME: this doesn't do what we want here. findRayIntersection always works on mesh, skipping entirely based on collidable. What we really want is to use the collision hull! See CharacterGhostObject::rayTest? const bool collidableOnly = true; const bool precisionPicking = true; const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a458e36f0d..08c3d9c67c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -509,6 +509,10 @@ public: // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; + Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up. + Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; }; + + public slots: void increaseSize(); void decreaseSize(); From 92aeadd70f4477077d89066840c8b6b7ad5f876e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 26 May 2017 16:34:08 -0700 Subject: [PATCH 07/66] rotation of local capsule offset --- interface/src/avatar/MyAvatar.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 422053dfa5..1d9009e129 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -294,6 +294,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { CameraMode mode = qApp->getCamera().getMode(); _globalPosition = getPosition(); + // This might not be right! Isn't the capsule local offset in avatar space? -HR 5/26/17 _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); @@ -448,6 +449,7 @@ void MyAvatar::update(float deltaTime) { setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); + // This might not be right! Isn't the capsule local offset in avatar space? -HR 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), @@ -2230,7 +2232,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // a) the closest above and the closest below are less than the avatar capsule height apart, or // b) the above point is the top surface of an entity, indicating that we are inside it. // If no landing is required, we go to that point directly and return false; - // When a landing is required by a, we find the highest intersection on that closest-above entity + // When a landing is required by a, we find the highest intersection on that closest-agbove entity // (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point. // For b, use that top surface point. // We then place our feet there, recurse with new capsule center point, and return true. @@ -2243,7 +2245,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { return result; } - const auto offset = _characterController.getCapsuleLocalOffset(); // FIXME: correct space. + const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); const auto capsuleCenter = position + offset; // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. From fc12d7547a45434f784412c029acedc1e127acc9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 24 May 2017 19:06:49 -0700 Subject: [PATCH 08/66] Addition of CubicHermiteSpline helper classes. --- .../animation/src/AnimInverseKinematics.cpp | 67 +++++++++++++- .../animation/src/AnimInverseKinematics.h | 1 + libraries/shared/src/CubicHermiteSpline.h | 90 +++++++++++++++++++ tests/shared/src/CubicHermiteSplineTests.cpp | 77 ++++++++++++++++ tests/shared/src/CubicHermiteSplineTests.h | 23 +++++ 5 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 libraries/shared/src/CubicHermiteSpline.h create mode 100644 tests/shared/src/CubicHermiteSplineTests.cpp create mode 100644 tests/shared/src/CubicHermiteSplineTests.h diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d613e42866..0684cdbd8e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -20,6 +20,7 @@ #include "ElbowConstraint.h" #include "SwingTwistConstraint.h" #include "AnimationLogging.h" +#include "CubicHermiteSpline.h" AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : @@ -457,10 +458,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); - if (context.getEnableDebugDrawIKConstraints()) { - debugDrawConstraints(context); - } - const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; @@ -581,6 +578,11 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } + if (context.getEnableDebugDrawIKConstraints()) { + debugDrawConstraints(context); + } + debugDrawSpineSpline(context); + return _relativePoses; } @@ -1316,3 +1318,60 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s break; } } + +void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) const { + AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); + + AnimPose hipsPose = geomToWorldPose * _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); + AnimPose headPose = geomToWorldPose * _skeleton->getAbsolutePose(_headIndex, _relativePoses); + + float d = glm::length(hipsPose.trans() - headPose.trans()); + + const float BACK_GAIN = 1.0f; + const float NECK_GAIN = 0.33f; + glm::vec3 cp0 = hipsPose.trans(); + glm::vec3 cm0 = BACK_GAIN * d * (hipsPose.rot() * Vectors::UNIT_Y); + glm::vec3 cp1 = headPose.trans(); + glm::vec3 cm1 = NECK_GAIN * d * (headPose.rot() * Vectors::UNIT_Y); + + CubicHermiteSplineFunctorWithArcLength hermiteFunc(cp0, cm0, cp1, cm1); + + const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); + const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const glm::vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + + int NUM_SUBDIVISIONS = 20; + float totalArcLength = hermiteFunc.arcLength(1.0f); + const float dArcLength = totalArcLength / NUM_SUBDIVISIONS; + float arcLength = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + float prevT = hermiteFunc.arcLengthInverse(arcLength); + float nextT = hermiteFunc.arcLengthInverse(arcLength + dArcLength); + DebugDraw::getInstance().drawRay(hermiteFunc(prevT), hermiteFunc(nextT), (i % 2) == 0 ? RED : WHITE); + arcLength += dArcLength; + } + + /* + AnimPose p0 = hipsPose; + AnimPose p1 = AnimPose(glm::vec3(1.0f), glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), 0.25f)), hermiteSpline(cp0, cm0, cp1, cm1, 0.25f)); + AnimPose p2 = AnimPose(glm::vec3(1.0f), glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), 0.75f)), hermiteSpline(cp0, cm0, cp1, cm1, 0.75f)); + AnimPose p3 = headPose; + + DebugDraw::getInstance().drawRay(cp0, cp0 + cm0, GREEN); + DebugDraw::getInstance().drawRay(cp0 + cm0, cp1, CYAN); + DebugDraw::getInstance().drawRay(cp1, cp1 + cm1, GREEN); + + // draw the spline itself + int NUM_SUBDIVISIONS = 20; + float D_ALPHA = 1.0f / NUM_SUBDIVISIONS; + float alpha = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + DebugDraw::getInstance().drawRay(hermiteSpline(cp0, cm0, cp1, cm1, alpha), + hermiteSpline(cp0, cm0, cp1, cm1, alpha + D_ALPHA), + (i % 2) == 0 ? RED : WHITE); + alpha += D_ALPHA; + } + */ +} diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 0267f14650..22ebee4dec 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -70,6 +70,7 @@ protected: void debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; + void debugDrawSpineSpline(const AnimContext& context) const; void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h new file mode 100644 index 0000000000..310dedb1af --- /dev/null +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -0,0 +1,90 @@ +// +// CubicHermiteSpline.h +// +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CubicHermiteSpline_h +#define hifi_CubicHermiteSpline_h + +#include "GLMHelpers.h" + +class CubicHermiteSplineFunctor { +public: + CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {} + + CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {} + + virtual ~CubicHermiteSplineFunctor() {} + + // evalute the hermite curve at parameter t (0..1) + glm::vec3 operator()(float t) const { + float t3 = t * t * t; + float t2 = t * t; + float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f; + float w1 = t3 - 2.0f * t2 + t; + float w2 = -2.0f * t3 + 3.0f * t2; + float w3 = t3 - t2; + return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; + } + +protected: + glm::vec3 _p0; + glm::vec3 _m0; + glm::vec3 _p1; + glm::vec3 _m1; +}; + +class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor { +public: + enum Constants { NUM_SUBDIVISIONS = 30 }; + + CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) { + // initialize _values with the accumulated arcLength along the spline. + const float DELTA = 1.0f / NUM_SUBDIVISIONS; + float alpha = 0.0f; + float accum = 0.0f; + _values[0] = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + accum += glm::distance(this->operator()(alpha), + this->operator()(alpha + DELTA)); + alpha += DELTA; + _values[i + 1] = accum; + } + } + + CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) { + memcpy(_values, orig._values, sizeof(float) * NUM_SUBDIVISIONS + 1); + } + + // given the spline parameter (0..1) output the arcLength of the spline up to that point. + float arcLength(float t) const { + float index = t * NUM_SUBDIVISIONS; + int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS); + int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS); + float alpha = glm::fract(index); + return lerp(_values[prevIndex], _values[nextIndex], alpha); + } + + // given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength. + float arcLengthInverse(float s) const { + // find first item in _values that is > s. + int nextIndex; + for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) { + if (_values[nextIndex] > s) { + break; + } + } + int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS); + float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f); + const float DELTA = 1.0f / NUM_SUBDIVISIONS; + return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha); + } +protected: + float _values[NUM_SUBDIVISIONS + 1]; +}; + +#endif // hifi_CubicHermiteSpline_h diff --git a/tests/shared/src/CubicHermiteSplineTests.cpp b/tests/shared/src/CubicHermiteSplineTests.cpp new file mode 100644 index 0000000000..53a4ba8f6b --- /dev/null +++ b/tests/shared/src/CubicHermiteSplineTests.cpp @@ -0,0 +1,77 @@ +// +// CubicHermiteSplineTests.cpp +// tests/shared/src +// +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CubicHermiteSplineTests.h" +#include "../QTestExtensions.h" +#include +#include "CubicHermiteSpline.h" + +QTEST_MAIN(CubicHermiteSplineTests) + +void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() { + glm::vec3 p0(0.0f, 0.0f, 0.0f); + glm::vec3 m0(1.0f, 0.0f, 0.0f); + glm::vec3 p1(1.0f, 1.0f, 0.0f); + glm::vec3 m1(2.0f, 0.0f, 0.0f); + CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1); + + const float EPSILON = 0.0001f; + + QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON); + QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON); + + // these values were computed offline. + const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f); + const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f); + const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f); + + QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON); + QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON); + QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON); +} + +void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() { + glm::vec3 p0(0.0f, 0.0f, 0.0f); + glm::vec3 m0(1.0f, 0.0f, 0.0f); + glm::vec3 p1(1.0f, 1.0f, 0.0f); + glm::vec3 m1(2.0f, 0.0f, 0.0f); + CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1); + + const float EPSILON = 0.001f; + + float arcLengths[5] = { + hermiteSpline.arcLength(0.0f), + hermiteSpline.arcLength(0.25f), + hermiteSpline.arcLength(0.5f), + hermiteSpline.arcLength(0.75f), + hermiteSpline.arcLength(1.0f) + }; + + // these values were computed offline + float referenceArcLengths[5] = { + 0.0f, + 0.268317f, + 0.652788f, + 1.07096f, + 1.50267f + }; + + QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON); + QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON); + QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON); + QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON); + QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON); + + QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON); + QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON); + QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON); + QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON); + QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON); +} diff --git a/tests/shared/src/CubicHermiteSplineTests.h b/tests/shared/src/CubicHermiteSplineTests.h new file mode 100644 index 0000000000..1828d1aa02 --- /dev/null +++ b/tests/shared/src/CubicHermiteSplineTests.h @@ -0,0 +1,23 @@ +// +// CubicHermiteSplineTests.h +// tests/shared/src +// +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CubicHermiteSplineTests_h +#define hifi_CubicHermiteSplineTests_h + +#include + +class CubicHermiteSplineTests : public QObject { + Q_OBJECT +private slots: + void testCubicHermiteSplineFunctor(); + void testCubicHermiteSplineFunctorWithArcLength(); +}; + +#endif // hifi_TransformTests_h From 39c23bfe2f60721bf8d251927cfc553f93935f47 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 26 May 2017 17:09:30 -0700 Subject: [PATCH 09/66] revision of spine spline rotation calculation --- .../animation/src/AnimInverseKinematics.cpp | 68 +++++++++++-------- libraries/shared/src/CubicHermiteSpline.h | 19 ++++++ libraries/shared/src/GLMHelpers.cpp | 2 +- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0684cdbd8e..69a224c686 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1319,6 +1319,13 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s } } +struct SplineJointInfo { + int index; + float ratio; + AnimPose defaultPose; + AnimPose offsetPose; +}; + void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) const { AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); @@ -1327,21 +1334,22 @@ void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) con float d = glm::length(hipsPose.trans() - headPose.trans()); - const float BACK_GAIN = 1.0f; - const float NECK_GAIN = 0.33f; - glm::vec3 cp0 = hipsPose.trans(); - glm::vec3 cm0 = BACK_GAIN * d * (hipsPose.rot() * Vectors::UNIT_Y); - glm::vec3 cp1 = headPose.trans(); - glm::vec3 cm1 = NECK_GAIN * d * (headPose.rot() * Vectors::UNIT_Y); + const float BACK_GAIN = 0.5f; + const float NECK_GAIN = 1.0f; + glm::vec3 p0 = hipsPose.trans(); + glm::vec3 m0 = BACK_GAIN * d * (hipsPose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = headPose.trans(); + glm::vec3 m1 = NECK_GAIN * d * (headPose.rot() * Vectors::UNIT_Y); - CubicHermiteSplineFunctorWithArcLength hermiteFunc(cp0, cm0, cp1, cm1); + CubicHermiteSplineFunctorWithArcLength hermiteFunc(p0, m0, p1, m1); - const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); - const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); + const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); const glm::vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); - const glm::vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + // draw red and white stripped spline, parameterized by arc length. + // i.e. each stripe should be the same length. int NUM_SUBDIVISIONS = 20; float totalArcLength = hermiteFunc.arcLength(1.0f); const float dArcLength = totalArcLength / NUM_SUBDIVISIONS; @@ -1353,25 +1361,27 @@ void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) con arcLength += dArcLength; } - /* - AnimPose p0 = hipsPose; - AnimPose p1 = AnimPose(glm::vec3(1.0f), glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), 0.25f)), hermiteSpline(cp0, cm0, cp1, cm1, 0.25f)); - AnimPose p2 = AnimPose(glm::vec3(1.0f), glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), 0.75f)), hermiteSpline(cp0, cm0, cp1, cm1, 0.75f)); - AnimPose p3 = headPose; + AnimPose defaultHipsPose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + AnimPose defaultHeadPose = _skeleton->getAbsoluteDefaultPose(_headIndex); + float hipsToHeadHeight = defaultHeadPose.trans().y - defaultHipsPose.trans().y; - DebugDraw::getInstance().drawRay(cp0, cp0 + cm0, GREEN); - DebugDraw::getInstance().drawRay(cp0 + cm0, cp1, CYAN); - DebugDraw::getInstance().drawRay(cp1, cp1 + cm1, GREEN); - - // draw the spline itself - int NUM_SUBDIVISIONS = 20; - float D_ALPHA = 1.0f / NUM_SUBDIVISIONS; - float alpha = 0.0f; - for (int i = 0; i < NUM_SUBDIVISIONS; i++) { - DebugDraw::getInstance().drawRay(hermiteSpline(cp0, cm0, cp1, cm1, alpha), - hermiteSpline(cp0, cm0, cp1, cm1, alpha + D_ALPHA), - (i % 2) == 0 ? RED : WHITE); - alpha += D_ALPHA; + // iterate down the chain and build the splineJointInfoVec + std::vector splineJointInfoVec; + int index = _headIndex; + int endIndex = _skeleton->getParentIndex(_hipsIndex); + while (index != endIndex) { + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); + float ratio = (defaultPose.trans().y - defaultHipsPose.trans().y) / hipsToHeadHeight; + SplineJointInfo splineJointInfo = { index, ratio, defaultPose, AnimPose() }; + splineJointInfoVec.push_back(splineJointInfo); + index = _skeleton->getParentIndex(index); } - */ + + for (auto& splineJointInfo : splineJointInfoVec) { + float t = hermiteFunc.arcLengthInverse(splineJointInfo.ratio * totalArcLength); + glm::vec3 trans = hermiteFunc(t); + glm::quat rot = glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), t)); + DebugDraw::getInstance().addMarker(QString("splineJoint%1").arg(splineJointInfo.index), rot, trans, BLUE); + } + } diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index 310dedb1af..e563fc2b73 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -31,6 +31,25 @@ public: return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; } + // evaulate the first derivative of the hermite curve at parameter t (0..1) + glm::vec3 d(float t) const { + float t2 = t * t; + float w0 = -6.0f * t + 6.0f * t2; + float w1 = 1.0f - 4.0f * t + 3.0f * t2; + float w2 = 6.0 * t - 6.0f * t2; + float w3 = -2.0f * t + 3.0f * t2; + return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; + } + + // evaulate the second derivative of the hermite curve at paramter t (0..1) + glm::vec3 d2(float t) const { + float w0 = -6.0f + 12.0f * t; + float w1 = -4.0f + 6.0f * t; + float w2 = 6.0f - 12.0f * t; + float w3 = -2.0f + 6.0f * t; + return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; + } + protected: glm::vec3 _p0; glm::vec3 _m0; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 70237e8ff6..f4989d61f5 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -540,7 +540,7 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda // if secondaryAxis is parallel with the primaryAxis, pick another axis. const float EPSILON = 1.0e-4f; - if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { + if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { // pick a better secondaryAxis. normSecondary = glm::vec3(1.0f, 0.0f, 0.0f); if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { From d4dbd94a3564c2bc020fceeb5723eaaff8547f17 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 30 May 2017 14:22:03 -0700 Subject: [PATCH 10/66] Compute defaultSpineSplineto defaultPose offset --- .../animation/src/AnimInverseKinematics.cpp | 110 +++++++++++++----- .../animation/src/AnimInverseKinematics.h | 11 +- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 69a224c686..03f1208600 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -576,12 +576,12 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars _hipsOffset = Vectors::ZERO; } } - } - if (context.getEnableDebugDrawIKConstraints()) { - debugDrawConstraints(context); + if (context.getEnableDebugDrawIKConstraints()) { + debugDrawConstraints(context); + } + debugDrawSpineSpline(context, targets); } - debugDrawSpineSpline(context); return _relativePoses; } @@ -1319,14 +1319,77 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s } } -struct SplineJointInfo { - int index; - float ratio; - AnimPose defaultPose; - AnimPose offsetPose; -}; +void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, int targetIndex, const IKTarget& target) { + std::vector splineJointInfoVec; + + // build default spline + AnimPose geomToRigPose(context.getGeometryToRigMatrix()); + AnimPose tipPose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(target.getIndex()); + AnimPose basePose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(_hipsIndex); + + const float BASE_GAIN = 0.5f; + const float TIP_GAIN = 1.0f; + float d = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); + + // AJT: FIXME: TODO: this won't work for horizontal splines... + float baseToTipHeight = tipPose.trans().y - basePose.trans().y; + + CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); + float totalArcLength = spline.arcLength(1.0f); + + int index = _headIndex; + int endIndex = _skeleton->getParentIndex(_hipsIndex); + while (index != endIndex) { + AnimPose defaultPose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(index); + + // AJT: FIXME: TODO: this wont work for horizontal splines... + float ratio = (defaultPose.trans().y - basePose.trans().y) / baseToTipHeight; + + // compute offset from default spline pose to default pose. + float t = spline.arcLengthInverse(ratio * totalArcLength); + AnimPose pose(glm::vec3(1.0f), glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)), spline(t)); + AnimPose offset = pose.inverse() * defaultPose; + + SplineJointInfo splineJointInfo = { index, ratio, defaultPose, offset }; + splineJointInfoVec.push_back(splineJointInfo); + index = _skeleton->getParentIndex(index); + } + + _splineJointInfoMap[targetIndex] = splineJointInfoVec; +} + +void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context, const std::vector& targets) { + + // find head target + int headTargetIndex = -1; + for (int i = 0; i < (int)targets.size(); i++) { + if (targets[i].getIndex() == _headIndex) { + headTargetIndex = i; + break; + } + } + + // find or create splineJointInfo for the head target + const std::vector* splineJointInfoVec = nullptr; + if (headTargetIndex != -1) { + auto iter = _splineJointInfoMap.find(headTargetIndex); + if (iter != _splineJointInfoMap.end()) { + splineJointInfoVec = &(iter->second); + } else { + computeSplineJointInfosForIKTarget(context, headTargetIndex, targets[headTargetIndex]); + auto iter = _splineJointInfoMap.find(headTargetIndex); + if (iter != _splineJointInfoMap.end()) { + splineJointInfoVec = &(iter->second); + } + } + } + + // AJT: TODO: render using splineJointInfoVec offset -void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) const { AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); AnimPose hipsPose = geomToWorldPose * _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); @@ -1365,23 +1428,14 @@ void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context) con AnimPose defaultHeadPose = _skeleton->getAbsoluteDefaultPose(_headIndex); float hipsToHeadHeight = defaultHeadPose.trans().y - defaultHipsPose.trans().y; - // iterate down the chain and build the splineJointInfoVec - std::vector splineJointInfoVec; - int index = _headIndex; - int endIndex = _skeleton->getParentIndex(_hipsIndex); - while (index != endIndex) { - AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); - float ratio = (defaultPose.trans().y - defaultHipsPose.trans().y) / hipsToHeadHeight; - SplineJointInfo splineJointInfo = { index, ratio, defaultPose, AnimPose() }; - splineJointInfoVec.push_back(splineJointInfo); - index = _skeleton->getParentIndex(index); - } - - for (auto& splineJointInfo : splineJointInfoVec) { - float t = hermiteFunc.arcLengthInverse(splineJointInfo.ratio * totalArcLength); - glm::vec3 trans = hermiteFunc(t); - glm::quat rot = glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), t)); - DebugDraw::getInstance().addMarker(QString("splineJoint%1").arg(splineJointInfo.index), rot, trans, BLUE); + if (splineJointInfoVec) { + for (auto& splineJointInfo : *splineJointInfoVec) { + float t = hermiteFunc.arcLengthInverse(splineJointInfo.ratio * totalArcLength); + glm::vec3 trans = hermiteFunc(t); + glm::quat rot = glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), t)); + AnimPose pose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + DebugDraw::getInstance().addMarker(QString("splineJoint%1").arg(splineJointInfo.jointIndex), pose.rot(), pose.trans(), BLUE); + } } } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 22ebee4dec..2b2535ac79 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -70,7 +70,8 @@ protected: void debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; - void debugDrawSpineSpline(const AnimContext& context) const; + void debugDrawSpineSpline(const AnimContext& context, const std::vector& targets); + void computeSplineJointInfosForIKTarget(const AnimContext& context, int targetIndex, const IKTarget& target); void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); @@ -112,6 +113,14 @@ protected: AnimPoseVec _relativePoses; // current relative poses AnimPoseVec _limitCenterPoses; // relative + struct SplineJointInfo { + int jointIndex; + float ratio; + AnimPose defaultPose; + AnimPose offsetPose; + }; + std::map> _splineJointInfoMap; + // experimental data for moving hips during IK glm::vec3 _hipsOffset { Vectors::ZERO }; float _maxHipsOffsetLength{ FLT_MAX }; From 07ea2e271bc1c0ca0e23b9768fe6b75a8b43fd78 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 30 May 2017 14:59:19 -0700 Subject: [PATCH 11/66] hook into location changes --- interface/src/avatar/MyAvatar.cpp | 35 ++++++++++++++++++++++--------- interface/src/avatar/MyAvatar.h | 5 +++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1d9009e129..e10327b7c7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -435,6 +435,11 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); + _physicsSafetyPending = true; + } + if (_physicsSafetyPending && qApp->isPhysicsEnabled()) { + _physicsSafetyPending = false; + safeLanding(_goToPosition); // no-op if already safe } Head* head = getHead(); @@ -2244,22 +2249,30 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); return result; } + glm::vec3 better; + if (requiresSafeLanding(position, better)) { + qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); + if (!getCollisionsEnabled()) { + goToLocation(better); // recurses + } else { // If you try to go while stuck, physics will keep you stuck. + setCollisionsEnabled(false); + // Don't goToLocation just yet. Yield so that physics can act on the above. + QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick + Q_ARG(glm::vec3, better)); // I.e., capsuleCenter - offset + } + return true; + } + return false; +} +bool MyAvatar::requiresSafeLanding(const glm::vec3& position, glm::vec3& betterPosition) { const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); const auto capsuleCenter = position + offset; // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. auto direct = [&](const char* label) { // position is good to go, or at least, we cannot do better - //qDebug() << "Already safe" << label << position; - if (!getCollisionsEnabled()) { - goToLocation(position); - } else { // If you try to go while stuck, physics will keep you stuck. - setCollisionsEnabled(false); - // Don't goToLocation just yet. Yield so that physics can act on the above. - QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick - Q_ARG(glm::vec3, position)); // I.e., capsuleCenter - offset - } + qDebug() << "Already safe" << label << position << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); return false; }; auto halfHeight = _characterController.getCapsuleHalfHeight(); @@ -2277,7 +2290,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { QVector include{}; QVector ignore{}; auto recurse = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. - safeLanding(upperIntersection + (up * halfHeight) - offset); + betterPosition = upperIntersection + (up * halfHeight) - offset; return true; }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { @@ -2287,6 +2300,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { BoxFace face; const bool visibleOnly = false; // FIXME: this doesn't do what we want here. findRayIntersection always works on mesh, skipping entirely based on collidable. What we really want is to use the collision hull! See CharacterGhostObject::rayTest? + // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders const bool collidableOnly = true; const bool precisionPicking = true; const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? @@ -2378,6 +2392,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } + qDebug() << "FIXME updateMotionBehaviorFromMenu collisions:" << menu->isOptionChecked(MenuOption::EnableAvatarCollisions) << "physics:" << qApp->isPhysicsEnabled(); setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 08c3d9c67c..efab177a04 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -571,7 +571,7 @@ private: glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; - + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; @@ -722,7 +722,8 @@ private: }; FollowHelper _follow; - bool _goToPending; + bool _goToPending { false }; + bool _physicsSafetyPending { false }; glm::vec3 _goToPosition; glm::quat _goToOrientation; From cff42ab9b0a5b92aac8275d7db20850f3b23f98b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 30 May 2017 19:01:52 -0700 Subject: [PATCH 12/66] Working spline spline. --- .../animation/src/AnimInverseKinematics.cpp | 142 ++++++++++++++---- .../animation/src/AnimInverseKinematics.h | 13 +- libraries/animation/src/IKTarget.cpp | 3 + libraries/animation/src/IKTarget.h | 1 + libraries/animation/src/Rig.cpp | 8 +- .../animation/src/RotationAccumulator.cpp | 4 +- .../animation/src/TranslationAccumulator.cpp | 34 +++++ .../animation/src/TranslationAccumulator.h | 42 ++++++ 8 files changed, 207 insertions(+), 40 deletions(-) create mode 100644 libraries/animation/src/TranslationAccumulator.cpp create mode 100644 libraries/animation/src/TranslationAccumulator.h diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 03f1208600..31f7bc63d2 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -60,7 +60,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN AnimInverseKinematics::~AnimInverseKinematics() { clearConstraints(); - _accumulators.clear(); + _rotationAccumulators.clear(); + _translationAccumulators.clear(); _targetVarVec.clear(); } @@ -73,10 +74,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) { assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); if (_skeleton->getNumJoints() == (int)poses.size()) { _relativePoses = poses; - _accumulators.resize(_relativePoses.size()); + _rotationAccumulators.resize(_relativePoses.size()); + _translationAccumulators.resize(_relativePoses.size()); } else { _relativePoses.clear(); - _accumulators.clear(); + _rotationAccumulators.clear(); + _translationAccumulators.clear(); } } @@ -176,14 +179,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } } -void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector& targets) { +void AnimInverseKinematics::solve(const AnimContext& context, const std::vector& targets) { // compute absolute poses that correspond to relative target poses AnimPoseVec absolutePoses; absolutePoses.resize(_relativePoses.size()); computeAbsolutePoses(absolutePoses); // clear the accumulators before we start the IK solver - for (auto& accumulator: _accumulators) { + for (auto& accumulator : _rotationAccumulators) { + accumulator.clearAndClean(); + } + for (auto& accumulator : _translationAccumulators) { accumulator.clearAndClean(); } @@ -198,14 +204,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& // solve all targets for (auto& target: targets) { - solveTargetWithCCD(context, target, absolutePoses, debug); + if (target.getType() == IKTarget::Type::Spline) { + solveTargetWithSpline(context, target, absolutePoses, debug); + } else { + solveTargetWithCCD(context, target, absolutePoses, debug); + } } // harvest accumulated rotations and apply the average for (int i = 0; i < (int)_relativePoses.size(); ++i) { - if (_accumulators[i].size() > 0) { - _relativePoses[i].rot() = _accumulators[i].getAverage(); - _accumulators[i].clear(); + if (_rotationAccumulators[i].size() > 0) { + _relativePoses[i].rot() = _rotationAccumulators[i].getAverage(); + _rotationAccumulators[i].clear(); + } + if (_translationAccumulators[i].size() > 0) { + _relativePoses[i].trans() = _translationAccumulators[i].getAverage(); + _translationAccumulators[i].clear(); } } @@ -237,7 +251,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& int parentIndex = _skeleton->getParentIndex(tipIndex); // update rotationOnly targets that don't lie on the ik chain of other ik targets. - if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { + if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { const glm::quat& targetRotation = target.getRotation(); // compute tip's new parent-relative rotation // Q = Qp * q --> q' = Qp^ * Q @@ -312,10 +326,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } // store the relative rotation change in the accumulator - _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); + _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); + + glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans(); + _translationAccumulators[tipIndex].add(tipRelativeTranslation); if (debug) { - debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained); + debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained); } } @@ -423,10 +440,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } // store the relative rotation change in the accumulator - _accumulators[pivotIndex].add(newRot, target.getWeight()); + _rotationAccumulators[pivotIndex].add(newRot, target.getWeight()); + + glm::vec3 newTrans = _relativePoses[pivotIndex].trans(); + _translationAccumulators[pivotIndex].add(newTrans); if (debug) { - debugJointMap[pivotIndex] = DebugJoint(newRot, constrained); + debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained); } // keep track of tip's new transform as we descend towards root @@ -445,6 +465,68 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } } +void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { + + std::map debugJointMap; + + const int baseIndex = _hipsIndex; + + // build spline + AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose basePose = absolutePoses[baseIndex]; + + const float BASE_GAIN = 0.5f; + const float TIP_GAIN = 1.0f; + float d = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); + + CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); + float totalArcLength = spline.arcLength(1.0f); + + // find or create splineJointInfo for the head target + const std::vector* splineJointInfoVec = nullptr; + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + splineJointInfoVec = &(iter->second); + } else { + computeSplineJointInfosForIKTarget(context, target); + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + splineJointInfoVec = &(iter->second); + } + } + + if (splineJointInfoVec && splineJointInfoVec->size() > 0) { + const int baseParentIndex = _skeleton->getParentIndex(baseIndex); + AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); + + // go thru splineJointInfoVec backwards (base to tip) + for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) { + const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; + float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); + glm::vec3 trans = spline(t); + glm::quat rot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)); + AnimPose absPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + AnimPose relPose = parentAbsPose.inverse() * absPose; + _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); + _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight()); + + if (debug) { + debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), false); + } + + parentAbsPose = absPose; + } + } + + if (debug) { + debugDrawIKChain(debugJointMap, context); + } +} + //virtual const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) { // don't call this function, call overlay() instead @@ -566,7 +648,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); - solveWithCyclicCoordinateDescent(context, targets); + solve(context, targets); } if (_hipsTargetIndex < 0) { @@ -1047,7 +1129,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele _maxTargetIndex = -1; - for (auto& accumulator: _accumulators) { + for (auto& accumulator: _rotationAccumulators) { + accumulator.clearAndClean(); + } + for (auto& accumulator: _translationAccumulators) { accumulator.clearAndClean(); } @@ -1119,6 +1204,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map& debugJoi // copy debug joint rotations into the relative poses for (auto& debugJoint : debugJointMap) { poses[debugJoint.first].rot() = debugJoint.second.relRot; + poses[debugJoint.first].trans() = debugJoint.second.relTrans; } // convert relative poses to absolute @@ -1284,7 +1370,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A int numJoints = (int)_relativePoses.size(); for (int i = 0; i < numJoints; ++i) { float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); - if (_accumulators[i].isDirty()) { + if (_rotationAccumulators[i].isDirty()) { // this joint is affected by IK --> blend toward the targetPoses rotation _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); } else { @@ -1319,13 +1405,12 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s } } -void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, int targetIndex, const IKTarget& target) { +void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { std::vector splineJointInfoVec; // build default spline - AnimPose geomToRigPose(context.getGeometryToRigMatrix()); - AnimPose tipPose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(target.getIndex()); - AnimPose basePose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(_hipsIndex); + AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); const float BASE_GAIN = 0.5f; const float TIP_GAIN = 1.0f; @@ -1341,10 +1426,10 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); float totalArcLength = spline.arcLength(1.0f); - int index = _headIndex; + int index = target.getIndex(); int endIndex = _skeleton->getParentIndex(_hipsIndex); while (index != endIndex) { - AnimPose defaultPose = geomToRigPose * _skeleton->getAbsoluteDefaultPose(index); + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); // AJT: FIXME: TODO: this wont work for horizontal splines... float ratio = (defaultPose.trans().y - basePose.trans().y) / baseToTipHeight; @@ -1352,18 +1437,19 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext // compute offset from default spline pose to default pose. float t = spline.arcLengthInverse(ratio * totalArcLength); AnimPose pose(glm::vec3(1.0f), glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)), spline(t)); - AnimPose offset = pose.inverse() * defaultPose; + AnimPose offsetPose = pose.inverse() * defaultPose; - SplineJointInfo splineJointInfo = { index, ratio, defaultPose, offset }; + SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; splineJointInfoVec.push_back(splineJointInfo); index = _skeleton->getParentIndex(index); } - _splineJointInfoMap[targetIndex] = splineJointInfoVec; + _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context, const std::vector& targets) { + /* // find head target int headTargetIndex = -1; for (int i = 0; i < (int)targets.size(); i++) { @@ -1388,8 +1474,6 @@ void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context, con } } - // AJT: TODO: render using splineJointInfoVec offset - AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); AnimPose hipsPose = geomToWorldPose * _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); @@ -1437,5 +1521,5 @@ void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context, con DebugDraw::getInstance().addMarker(QString("splineJoint%1").arg(splineJointInfo.jointIndex), pose.rot(), pose.trans(), BLUE); } } - + */ } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 2b2535ac79..42e84530cc 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -19,6 +19,7 @@ #include "IKTarget.h" #include "RotationAccumulator.h" +#include "TranslationAccumulator.h" class RotationConstraint; @@ -58,20 +59,22 @@ public: protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); - void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector& targets); + void solve(const AnimContext& context, const std::vector& targets); void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); + void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; struct DebugJoint { DebugJoint() : relRot(), constrained(false) {} - DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {} + DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {} glm::quat relRot; + glm::vec3 relTrans; bool constrained; }; void debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; void debugDrawSpineSpline(const AnimContext& context, const std::vector& targets); - void computeSplineJointInfosForIKTarget(const AnimContext& context, int targetIndex, const IKTarget& target); + void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); @@ -107,7 +110,8 @@ protected: }; std::map _constraints; - std::vector _accumulators; + std::vector _rotationAccumulators; + std::vector _translationAccumulators; std::vector _targetVarVec; AnimPoseVec _defaultRelativePoses; // poses of the relaxed state AnimPoseVec _relativePoses; // current relative poses @@ -116,7 +120,6 @@ protected: struct SplineJointInfo { int jointIndex; float ratio; - AnimPose defaultPose; AnimPose offsetPose; }; std::map> _splineJointInfoMap; diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index 2fe767b08d..41cac62fa3 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -44,6 +44,9 @@ void IKTarget::setType(int type) { case (int)Type::HipsRelativeRotationAndPosition: _type = Type::HipsRelativeRotationAndPosition; break; + case (int)Type::Spline: + _type = Type::Spline; + break; default: _type = Type::Unknown; } diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 4f464c103c..011175aedf 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -21,6 +21,7 @@ public: RotationOnly, HmdHead, HipsRelativeRotationAndPosition, + Spline, Unknown }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index add3a461af..dd98993688 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1041,7 +1041,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { } if (params.spine2Enabled) { - _animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("spine2Type", (int)IKTarget::Type::Spline); _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); } else { @@ -1101,9 +1101,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) { _animVars.set("headPosition", params.rigHeadPosition); _animVars.set("headRotation", params.rigHeadOrientation); if (params.hipsEnabled) { - // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. - // this will allow the spine to bend more, ensuring that it can reach the head target position. - _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); + // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type. + // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position. + _animVars.set("headType", (int)IKTarget::Type::Spline); _animVars.unset("headWeight"); // use the default weight for this target. } else { // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, diff --git a/libraries/animation/src/RotationAccumulator.cpp b/libraries/animation/src/RotationAccumulator.cpp index a4940e1989..2b8fc7d66a 100644 --- a/libraries/animation/src/RotationAccumulator.cpp +++ b/libraries/animation/src/RotationAccumulator.cpp @@ -1,5 +1,5 @@ // -// RotationAccumulator.h +// RotationAccumulator.cpp // // Copyright 2015 High Fidelity, Inc. // @@ -27,7 +27,7 @@ void RotationAccumulator::clear() { _numRotations = 0; } -void RotationAccumulator::clearAndClean() { +void RotationAccumulator::clearAndClean() { clear(); _isDirty = false; } diff --git a/libraries/animation/src/TranslationAccumulator.cpp b/libraries/animation/src/TranslationAccumulator.cpp new file mode 100644 index 0000000000..4b110b1564 --- /dev/null +++ b/libraries/animation/src/TranslationAccumulator.cpp @@ -0,0 +1,34 @@ +// +// TranslationAccumulator.cpp +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TranslationAccumulator.h" + +void TranslationAccumulator::add(const glm::vec3& translation, float weight) { + _accum += weight * translation; + _totalWeight += weight; + _isDirty = true; +} + +glm::vec3 TranslationAccumulator::getAverage() { + if (_totalWeight > 0.0f) { + return _accum / _totalWeight; + } else { + return glm::vec3(); + } +} + +void TranslationAccumulator::clear() { + _accum *= 0.0f; + _totalWeight = 0.0f; +} + +void TranslationAccumulator::clearAndClean() { + clear(); + _isDirty = false; +} diff --git a/libraries/animation/src/TranslationAccumulator.h b/libraries/animation/src/TranslationAccumulator.h new file mode 100644 index 0000000000..65f8a0dc97 --- /dev/null +++ b/libraries/animation/src/TranslationAccumulator.h @@ -0,0 +1,42 @@ +// +// TranslationAccumulator.h +// +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_TranslationAccumulator_h +#define hifi_TranslationAccumulator_h + +#include + +class TranslationAccumulator { +public: + TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { } + + int size() const { return _totalWeight > 0.0f; } + + /// \param rotation rotation to add + /// \param weight contribution factor of this rotation to total accumulation + void add(const glm::vec3& translation, float weight = 1.0f); + + glm::vec3 getAverage(); + + /// \return true if any rotations were accumulated + bool isDirty() const { return _isDirty; } + + /// \brief clear accumulated rotation but don't change _isDirty + void clear(); + + /// \brief clear accumulated rotation and set _isDirty to false + void clearAndClean(); + +private: + glm::vec3 _accum; + float _totalWeight; + bool _isDirty; +}; + +#endif // hifi_TranslationAccumulator_h From d3ca34956d23b65a60f1ff9bb460cec5de09af4d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 31 May 2017 16:58:17 -0700 Subject: [PATCH 13/66] Fix spline interpolation the wrong way when bending backward. --- .../animation/src/AnimInverseKinematics.cpp | 118 +++++++----------- .../animation/src/AnimInverseKinematics.h | 2 +- 2 files changed, 46 insertions(+), 74 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 31f7bc63d2..911d966057 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -499,6 +499,13 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } } + // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way) + // when the head is arched backwards very far. + glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f)); + if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) { + tipPose.rot() = -tipPose.rot(); + } + if (splineJointInfoVec && splineJointInfoVec->size() > 0) { const int baseParentIndex = _skeleton->getParentIndex(baseIndex); AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); @@ -662,7 +669,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars if (context.getEnableDebugDrawIKConstraints()) { debugDrawConstraints(context); } - debugDrawSpineSpline(context, targets); } return _relativePoses; @@ -1447,79 +1453,45 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; } -void AnimInverseKinematics::debugDrawSpineSpline(const AnimContext& context, const std::vector& targets) { +void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const { - /* - // find head target - int headTargetIndex = -1; - for (int i = 0; i < (int)targets.size(); i++) { - if (targets[i].getIndex() == _headIndex) { - headTargetIndex = i; - break; + for (auto& target : targets) { + + if (target.getType() != IKTarget::Type::Spline) { + continue; + } + + const int baseIndex = _hipsIndex; + + // build spline + AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); + + const float BASE_GAIN = 0.5f; + const float TIP_GAIN = 1.0f; + float d = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); + + CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); + float totalArcLength = spline.arcLength(1.0f); + + const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); + + // draw red and white stripped spline, parameterized by arc length. + // i.e. each stripe should be the same length. + AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); + int NUM_SUBDIVISIONS = 20; + const float dArcLength = totalArcLength / NUM_SUBDIVISIONS; + float arcLength = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + float prevT = spline.arcLengthInverse(arcLength); + float nextT = spline.arcLengthInverse(arcLength + dArcLength); + DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE); + arcLength += dArcLength; } } - - // find or create splineJointInfo for the head target - const std::vector* splineJointInfoVec = nullptr; - if (headTargetIndex != -1) { - auto iter = _splineJointInfoMap.find(headTargetIndex); - if (iter != _splineJointInfoMap.end()) { - splineJointInfoVec = &(iter->second); - } else { - computeSplineJointInfosForIKTarget(context, headTargetIndex, targets[headTargetIndex]); - auto iter = _splineJointInfoMap.find(headTargetIndex); - if (iter != _splineJointInfoMap.end()) { - splineJointInfoVec = &(iter->second); - } - } - } - - AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); - - AnimPose hipsPose = geomToWorldPose * _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); - AnimPose headPose = geomToWorldPose * _skeleton->getAbsolutePose(_headIndex, _relativePoses); - - float d = glm::length(hipsPose.trans() - headPose.trans()); - - const float BACK_GAIN = 0.5f; - const float NECK_GAIN = 1.0f; - glm::vec3 p0 = hipsPose.trans(); - glm::vec3 m0 = BACK_GAIN * d * (hipsPose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = headPose.trans(); - glm::vec3 m1 = NECK_GAIN * d * (headPose.rot() * Vectors::UNIT_Y); - - CubicHermiteSplineFunctorWithArcLength hermiteFunc(p0, m0, p1, m1); - - const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); - const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f); - const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); - const glm::vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); - - // draw red and white stripped spline, parameterized by arc length. - // i.e. each stripe should be the same length. - int NUM_SUBDIVISIONS = 20; - float totalArcLength = hermiteFunc.arcLength(1.0f); - const float dArcLength = totalArcLength / NUM_SUBDIVISIONS; - float arcLength = 0.0f; - for (int i = 0; i < NUM_SUBDIVISIONS; i++) { - float prevT = hermiteFunc.arcLengthInverse(arcLength); - float nextT = hermiteFunc.arcLengthInverse(arcLength + dArcLength); - DebugDraw::getInstance().drawRay(hermiteFunc(prevT), hermiteFunc(nextT), (i % 2) == 0 ? RED : WHITE); - arcLength += dArcLength; - } - - AnimPose defaultHipsPose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); - AnimPose defaultHeadPose = _skeleton->getAbsoluteDefaultPose(_headIndex); - float hipsToHeadHeight = defaultHeadPose.trans().y - defaultHipsPose.trans().y; - - if (splineJointInfoVec) { - for (auto& splineJointInfo : *splineJointInfoVec) { - float t = hermiteFunc.arcLengthInverse(splineJointInfo.ratio * totalArcLength); - glm::vec3 trans = hermiteFunc(t); - glm::quat rot = glm::normalize(glm::lerp(hipsPose.rot(), headPose.rot(), t)); - AnimPose pose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; - DebugDraw::getInstance().addMarker(QString("splineJoint%1").arg(splineJointInfo.jointIndex), pose.rot(), pose.trans(), BLUE); - } - } - */ } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 42e84530cc..459b37d0b6 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -73,7 +73,7 @@ protected: void debugDrawIKChain(std::map& debugJointMap, const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; - void debugDrawSpineSpline(const AnimContext& context, const std::vector& targets); + void debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const; void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); From 1e1dd3a633aca6aa4657832c8eafdbcea9d27865 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 31 May 2017 17:10:37 -0700 Subject: [PATCH 14/66] checkpoint with working detector --- interface/src/avatar/MyAvatar.cpp | 71 ++++++++++--------- libraries/physics/src/CharacterController.cpp | 18 +++-- libraries/physics/src/CharacterController.h | 2 + 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e10327b7c7..e8552df7e9 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -435,9 +435,9 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); - _physicsSafetyPending = true; + _physicsSafetyPending = getCollisionsEnabled(); // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. } - if (_physicsSafetyPending && qApp->isPhysicsEnabled()) { + if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // fix only when needed and ready _physicsSafetyPending = false; safeLanding(_goToPosition); // no-op if already safe } @@ -1555,6 +1555,11 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); + if (_characterController.isStuck()) { + _physicsSafetyPending = true; + _goToPosition = getPosition(); + qDebug() << "FIXME setting safety test at:" << _goToPosition; + } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); } @@ -2242,55 +2247,53 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // For b, use that top surface point. // We then place our feet there, recurse with new capsule center point, and return true. - // I'm not sure the true/false return is useful, and it does make things more complicated. Might get rid of it. - // E.g., Q_RETURN_ARG, and direct() could be a normal method. if (QThread::currentThread() != thread()) { bool result; QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position)); return result; } glm::vec3 better; - if (requiresSafeLanding(position, better)) { - qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); - if (!getCollisionsEnabled()) { - goToLocation(better); // recurses - } else { // If you try to go while stuck, physics will keep you stuck. - setCollisionsEnabled(false); - // Don't goToLocation just yet. Yield so that physics can act on the above. - QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick - Q_ARG(glm::vec3, better)); // I.e., capsuleCenter - offset - } - return true; + if (!requiresSafeLanding(position, better)) { + return false; } - return false; + qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); + if (!getCollisionsEnabled()) { + goToLocation(better); // recurses + } else { // If you try to go while stuck, physics will keep you stuck. + setCollisionsEnabled(false); + // Don't goToLocation just yet. Yield so that physics can act on the above. + QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick + Q_ARG(glm::vec3, better)); // I.e., capsuleCenter - offset + } + return true; } -bool MyAvatar::requiresSafeLanding(const glm::vec3& position, glm::vec3& betterPosition) { - const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); - const auto capsuleCenter = position + offset; +// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. - auto direct = [&](const char* label) { // position is good to go, or at least, we cannot do better - qDebug() << "Already safe" << label << position << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); + auto ok = [&](const char* label) { // position is good to go, or at least, we cannot do better + qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); return false; }; auto halfHeight = _characterController.getCapsuleHalfHeight(); if (halfHeight == 0) { - return direct("zero height avatar"); + return ok("zero height avatar"); } auto entityTree = DependencyManager::get()->getTree(); if (!entityTree) { - return direct("no entity tree"); + return ok("no entity tree"); } + const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); + const auto capsuleCenter = positionIn + offset; + const auto up = _worldUpDirection, down = -up; glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; EntityItemID upperId, lowerId; - const auto up = _worldUpDirection, down = -up; - QVector include{}; - QVector ignore{}; - auto recurse = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. - betterPosition = upperIntersection + (up * halfHeight) - offset; + QVector include{}, ignore{}; + auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. + betterPositionOut = upperIntersection + (up * halfHeight) - offset; return true; }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { @@ -2322,12 +2325,12 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& position, glm::vec3& betterP // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those // conditions, so no need to check our feet below. - return direct("nothing above"); + return ok("nothing above"); } if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. - return direct("nothing below"); + return ok("nothing below"); } // See if we have room between entities above and below, but that we are not contained. @@ -2347,13 +2350,13 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& position, glm::vec3& betterP if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; - return direct("enough room"); + return ok("enough room"); } if (isUp(upperNormal)) { // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top // of the enclosing entity, let's put our feet at upperIntersection and start over. - return recurse(); + return mustMove(); } // We found a new bottom surface, which we're not interested in. // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. @@ -2368,10 +2371,10 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& position, glm::vec3& betterP auto fromAbove = capsuleCenter + skyHigh; include.push_back(upperId); // We're looking for the intersection from above onto this entity. if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { - return direct("Unable to find a landing"); + return ok("Unable to find a landing"); } // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. - return recurse(); + return mustMove(); } void MyAvatar::updateMotionBehaviorFromMenu() { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index ee240a6aac..88eff1a0fc 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -152,6 +152,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { btDispatcher* dispatcher = collisionWorld->getDispatcher(); int numManifolds = dispatcher->getNumManifolds(); bool hasFloor = false; + bool isStuck = false; btTransform rotation = _rigidBody->getWorldTransform(); rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part @@ -169,10 +170,18 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp); + // If there's non-trivial penetration with a big impulse for several steps, we're probably stuck. + // Note it here in the controller, and let MyAvatar figure out what to do about it. + const float STUCK_PENETRATION = -0.1f; // always negative into the object. + const float STUCK_IMPULSE = 500.0f; + const int STUCK_LIFETIME = 50; + if ((contact.getDistance() < -STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { + isStuck = true; // latch on + } if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { hasFloor = true; - if (!pushing) { - // we're not pushing against anything so we can early exit + if (!pushing && isStuck) { + // we're not pushing against anything and we're stuck so we can early exit // (all we need to know is that there is a floor) break; } @@ -182,7 +191,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { if (!_stepUpEnabled || hitHeight > _maxStepHeight) { // this manifold is invalidated by point that is too high stepContactIndex = -1; - break; + qDebug() << "FIXME breaking early"; break; } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) { highestStep = hitHeight; stepContactIndex = j; @@ -198,12 +207,13 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { _stepHeight = highestStep; _stepPoint = rotation * pointOnCharacter; // rotate into world-frame } - if (hasFloor && !(pushing && _stepUpEnabled)) { + if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) { // early exit since all we need to know is that we're on a floor break; } } } + _isStuck = isStuck; return hasFloor; } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 6790495ff8..bf84d318d4 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -110,6 +110,7 @@ public: void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale); bool isEnabledAndReady() const { return _dynamicsWorld; } + bool isStuck() const { return _isStuck; } void setCollisionless(bool collisionless); int16_t computeCollisionGroup() const; @@ -192,6 +193,7 @@ protected: State _state; bool _isPushingUp; + bool _isStuck { false }; btDynamicsWorld* _dynamicsWorld { nullptr }; btRigidBody* _rigidBody { nullptr }; From 95aab28e911949ee7804b8fc6f144ece80af8512 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 1 Jun 2017 18:13:57 +1200 Subject: [PATCH 15/66] Disable unused code Keep for future experimentation per other commented-out code in method --- 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 d613e42866..5e89093b23 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -722,8 +722,10 @@ void AnimInverseKinematics::initConstraints() { loadDefaultPoses(_skeleton->getRelativeBindPoses()); - // compute corresponding absolute poses int numJoints = (int)_defaultRelativePoses.size(); + + /* KEEP THIS CODE for future experimentation + // compute corresponding absolute poses AnimPoseVec absolutePoses; absolutePoses.resize(numJoints); for (int i = 0; i < numJoints; ++i) { @@ -734,6 +736,7 @@ void AnimInverseKinematics::initConstraints() { absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i]; } } + */ clearConstraints(); for (int i = 0; i < numJoints; ++i) { From 47e65e942e1abbed8d081208862d46b63371b464 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 1 Jun 2017 23:42:08 +1200 Subject: [PATCH 16/66] Gradually relax hands from controlled positions when lose tracking --- .../animation/src/AnimInverseKinematics.cpp | 20 ++++- .../animation/src/AnimInverseKinematics.h | 10 +++ libraries/animation/src/Rig.cpp | 85 ++++++++++++++++--- libraries/animation/src/Rig.h | 9 ++ 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5e89093b23..5b5d70be01 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -453,7 +453,6 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { - // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); @@ -581,6 +580,16 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } + if (_leftHandIndex > -1) { + _uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses); + } + if (_rightHandIndex > -1) { + _uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses); + } + if (_hipsIndex > -1) { + _uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + } + return _relativePoses; } @@ -1064,12 +1073,21 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele } else { _hipsParentIndex = -1; } + + _leftHandIndex = _skeleton->nameToJointIndex("LeftHand"); + _rightHandIndex = _skeleton->nameToJointIndex("RightHand"); } else { clearConstraints(); _headIndex = -1; _hipsIndex = -1; _hipsParentIndex = -1; + _leftHandIndex = -1; + _rightHandIndex = -1; } + + _uncontrolledLeftHandPose = AnimPose(); + _uncontrolledRightHandPose = AnimPose(); + _uncontrolledHipsPose = AnimPose(); } static glm::vec3 sphericalToCartesian(float phi, float theta) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 0267f14650..35845224e2 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -56,6 +56,10 @@ public: void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } + const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; } + const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; } + const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; } + protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector& targets); @@ -118,6 +122,8 @@ protected: int _hipsIndex { -1 }; int _hipsParentIndex { -1 }; int _hipsTargetIndex { -1 }; + int _leftHandIndex { -1 }; + int _rightHandIndex { -1 }; // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses // during the the cyclic coordinate descent algorithm @@ -127,6 +133,10 @@ protected: bool _previousEnableDebugIKTargets { false }; SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; QString _solutionSourceVar; + + AnimPose _uncontrolledLeftHandPose { AnimPose() }; + AnimPose _uncontrolledRightHandPose { AnimPose() }; + AnimPose _uncontrolledHipsPose { AnimPose() }; }; #endif // hifi_AnimInverseKinematics_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index add3a461af..6b478da5fc 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1172,10 +1172,10 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f // TODO: add isHipsEnabled bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; + const float RELAX_DURATION = 0.6f; + if (params.isLeftEnabled) { - glm::vec3 handPosition = params.leftPosition; - if (!bodySensorTrackingEnabled) { // prevent the hand IK targets from intersecting the body capsule glm::vec3 displacement; @@ -1187,16 +1187,46 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.set("leftHandPosition", handPosition); _animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); + + _isLeftHandControlled = true; + _lastLeftHandControlledPosition = handPosition; + _lastLeftHandControlledOrientation = params.leftOrientation; } else { - _animVars.unset("leftHandPosition"); - _animVars.unset("leftHandRotation"); - _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + if (_isLeftHandControlled) { + _leftHandRelaxDuration = RELAX_DURATION; + _isLeftHandControlled = false; + } + + if (_leftHandRelaxDuration > 0) { + // Move hand from controlled position to non-controlled position. + _leftHandRelaxDuration = std::max(_leftHandRelaxDuration - dt, 0.0f); + + auto ikNode = getAnimInverseKinematicsNode(); + if (ikNode) { + float alpha = _leftHandRelaxDuration / RELAX_DURATION; + auto uncontrolledHandPose = ikNode->getUncontrolledLeftHandPose(); + auto uncontrolledHipsPose = ikNode->getUncontrolledHipPose(); + + glm::vec3 relaxedPosition = _geometryOffset * (uncontrolledHandPose.trans() - uncontrolledHipsPose.trans()); + glm::vec3 handPosition = alpha * _lastLeftHandControlledPosition + (1.0f - alpha) * relaxedPosition; + + const AnimPose geometryToRigPose(_geometryToRigTransform); + glm::quat handOrientation = geometryToRigPose.rot() * uncontrolledHandPose.rot(); + handOrientation = slerp(handOrientation, _lastLeftHandControlledOrientation, alpha); + + _animVars.set("leftHandPosition", handPosition); + _animVars.set("leftHandRotation", handOrientation); + _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); + } + } else { + _animVars.unset("leftHandPosition"); + _animVars.unset("leftHandRotation"); + _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + } } if (params.isRightEnabled) { - glm::vec3 handPosition = params.rightPosition; - if (!bodySensorTrackingEnabled) { // prevent the hand IK targets from intersecting the body capsule glm::vec3 displacement; @@ -1208,10 +1238,42 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.set("rightHandPosition", handPosition); _animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); + + _isRightHandControlled = true; + _lastRightHandControlledPosition = handPosition; + _lastRightHandControlledOrientation = params.rightOrientation; } else { - _animVars.unset("rightHandPosition"); - _animVars.unset("rightHandRotation"); - _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + if (_isRightHandControlled) { + _rightHandRelaxDuration = RELAX_DURATION; + _isRightHandControlled = false; + } + + if (_rightHandRelaxDuration > 0) { + // Move hand from controlled position to non-controlled position. + _rightHandRelaxDuration = std::max(_rightHandRelaxDuration - dt, 0.0f); + + auto ikNode = getAnimInverseKinematicsNode(); + if (ikNode) { + float alpha = _rightHandRelaxDuration / RELAX_DURATION; + auto uncontrolledHandPose = ikNode->getUncontrolledRightHandPose(); + auto uncontrolledHipsPose = ikNode->getUncontrolledHipPose(); + + glm::vec3 relaxedPosition = _geometryOffset * (uncontrolledHandPose.trans() - uncontrolledHipsPose.trans()); + glm::vec3 handPosition = alpha * _lastRightHandControlledPosition + (1.0f - alpha) * relaxedPosition; + + const AnimPose geometryToRigPose(_geometryToRigTransform); + glm::quat handOrientation = geometryToRigPose.rot() * uncontrolledHandPose.rot(); + handOrientation = slerp(handOrientation, _lastRightHandControlledOrientation, alpha); + + _animVars.set("rightHandPosition", handPosition); + _animVars.set("rightHandRotation", handOrientation); + _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); + } + } else { + _animVars.unset("rightHandPosition"); + _animVars.unset("rightHandRotation"); + _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + } } if (params.isLeftFootEnabled) { @@ -1233,7 +1295,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.unset("rightFootRotation"); _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); } - } } @@ -1509,5 +1570,3 @@ void Rig::computeAvatarBoundingCapsule( glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum))); localOffsetOut = rigCenter - (geometryToRig * rootPosition); } - - diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c9d52d8c72..1c7e40a04a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -355,6 +355,15 @@ private: QMap _stateHandlers; int _nextStateHandlerId { 0 }; QMutex _stateMutex; + + bool _isLeftHandControlled { false }; + bool _isRightHandControlled { false }; + float _leftHandRelaxDuration { 0.0f }; + float _rightHandRelaxDuration { 0.0f }; + glm::vec3 _lastLeftHandControlledPosition; + glm::vec3 _lastRightHandControlledPosition; + glm::quat _lastLeftHandControlledOrientation; + glm::quat _lastRightHandControlledOrientation; }; #endif /* defined(__hifi__Rig__) */ From 813feeb8fdeb8f6414703b8e73ed5f3a454813c9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 08:56:42 -0700 Subject: [PATCH 17/66] Limit spine spline compression/stretch to 15% --- .../animation/src/AnimInverseKinematics.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 911d966057..4fd769bec6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -519,10 +519,26 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co AnimPose absPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; AnimPose relPose = parentAbsPose.inverse() * absPose; _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); + + // constrain the amount the spine can stretch or compress + float length = glm::length(relPose.trans()); + float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); + const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; + const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); + const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); + bool constrained = false; + if (length > MAX_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; + constrained = true; + } else if (length < MIN_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; + constrained = true; + } + _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight()); if (debug) { - debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), false); + debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained); } parentAbsPose = absPose; From 226855b2b922f72440f1b10c5b3979356d38f038 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 13:20:50 -0700 Subject: [PATCH 18/66] Bug fixes and cleanup * Bug fix for problem when chest target is enabled but hips target is not. * centralized the two computeSplineFromTipAndBase functions into one. * Removed dead code --- .../animation/src/AnimInverseKinematics.cpp | 100 +++++++++++------- libraries/animation/src/Rig.cpp | 14 +-- libraries/animation/src/Rig.h | 4 - libraries/shared/src/CubicHermiteSpline.h | 4 + 4 files changed, 65 insertions(+), 57 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 4fd769bec6..5c0127334b 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -465,6 +465,16 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const } } +static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) { + float linearDistance = glm::length(basePose.trans() - tipPose.trans()); + glm::vec3 p0 = basePose.trans(); + glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y); + glm::vec3 p1 = tipPose.trans(); + glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y); + + return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); +} + void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { std::map debugJointMap; @@ -475,15 +485,15 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[baseIndex]; - const float BASE_GAIN = 0.5f; - const float TIP_GAIN = 1.0f; - float d = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); - - CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } float totalArcLength = spline.arcLength(1.0f); // find or create splineJointInfo for the head target @@ -515,24 +525,32 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i]; float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); glm::vec3 trans = spline(t); - glm::quat rot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)); + + // for head splines, preform most rotation toward the tip by using ease in function. t^2 + float rotT = t; + if (target.getIndex() == _headIndex) { + rotT = t * t; + } + glm::quat rot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT)); AnimPose absPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; AnimPose relPose = parentAbsPose.inverse() * absPose; _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); - // constrain the amount the spine can stretch or compress - float length = glm::length(relPose.trans()); - float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); - const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; - const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); - const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); bool constrained = false; - if (length > MAX_LENGTH) { - relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; - constrained = true; - } else if (length < MIN_LENGTH) { - relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; - constrained = true; + if (splineJointInfo.jointIndex != _hipsIndex) { + // constrain the amount the spine can stretch or compress + float length = glm::length(relPose.trans()); + float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); + const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; + const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); + const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); + if (length > MAX_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; + constrained = true; + } else if (length < MIN_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; + constrained = true; + } } _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight()); @@ -1434,20 +1452,20 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); - const float BASE_GAIN = 0.5f; - const float TIP_GAIN = 1.0f; - float d = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } + float totalArcLength = spline.arcLength(1.0f); // AJT: FIXME: TODO: this won't work for horizontal splines... float baseToTipHeight = tipPose.trans().y - basePose.trans().y; - CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); - float totalArcLength = spline.arcLength(1.0f); - int index = target.getIndex(); int endIndex = _skeleton->getParentIndex(_hipsIndex); while (index != endIndex) { @@ -1483,15 +1501,15 @@ void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, co AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); - const float BASE_GAIN = 0.5f; - const float TIP_GAIN = 1.0f; - float d = glm::length(basePose.trans() - tipPose.trans()); - glm::vec3 p0 = basePose.trans(); - glm::vec3 m0 = BASE_GAIN * d * (basePose.rot() * Vectors::UNIT_Y); - glm::vec3 p1 = tipPose.trans(); - glm::vec3 m1 = TIP_GAIN * d * (tipPose.rot() * Vectors::UNIT_Y); - - CubicHermiteSplineFunctorWithArcLength spline(p0, m0, p1, m1); + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } float totalArcLength = spline.arcLength(1.0f); const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index dd98993688..175c834d5d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -401,16 +401,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo } } -void Rig::restoreJointRotation(int index, float fraction, float priority) { - // AJT: DEAD CODE? - ASSERT(false); -} - -void Rig::restoreJointTranslation(int index, float fraction, float priority) { - // AJT: DEAD CODE? - ASSERT(false); -} - bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { if (isIndexValid(jointIndex)) { position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; @@ -1040,7 +1030,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } - if (params.spine2Enabled) { + if (params.hipsEnabled && params.spine2Enabled) { _animVars.set("spine2Type", (int)IKTarget::Type::Spline); _animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); @@ -1051,7 +1041,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { if (params.leftArmEnabled) { _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("leftArmPosition", params.leftArmPosition); - _animVars.set("leftArmRotation", params.leftArmRotation); + _animVars.set("leftArmRotation", params.leftArmRotation); } else { _animVars.set("leftArmType", (int)IKTarget::Type::Unknown); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c9d52d8c72..7166717792 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -134,10 +134,6 @@ public: void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); - // legacy - void restoreJointRotation(int index, float fraction, float priority); - void restoreJointTranslation(int index, float fraction, float priority); - // if translation and rotation is identity, position will be in rig space bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index e563fc2b73..17b4fda991 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -14,6 +14,7 @@ class CubicHermiteSplineFunctor { public: + CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {} CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {} CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {} @@ -61,6 +62,9 @@ class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor public: enum Constants { NUM_SUBDIVISIONS = 30 }; + CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() { + memset(_values, 0, sizeof(float) * NUM_SUBDIVISIONS + 1); + } CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) { // initialize _values with the accumulated arcLength along the spline. const float DELTA = 1.0f / NUM_SUBDIVISIONS; From 1068a4a9bd2248823a14c3c35a5935659c86e26f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 1 Jun 2017 13:21:24 -0700 Subject: [PATCH 19/66] remove the optimization for upperId == lowerId. Doesn't work in zaru tunnels. --- interface/src/avatar/MyAvatar.cpp | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e8552df7e9..bbac271450 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2340,32 +2340,31 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette auto delta = glm::distance(upperIntersection, lowerIntersection); if (delta > (2 * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. - if (upperId != lowerId) { // An optimization over what follows: the simplest case of not being inside an entity. - // We're going to iterate upwards through successive roofIntersections, testing to see if we're contained within the top surface of some entity. - // There will be one of two outcomes: - // a) We're not contained, so we have enough room and our position is good. - // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. - for (;;) { - ignore.push_back(upperId); - if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { - // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! - //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; - return ok("enough room"); - } - if (isUp(upperNormal)) { - // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. - // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top - // of the enclosing entity, let's put our feet at upperIntersection and start over. - return mustMove(); - } - // We found a new bottom surface, which we're not interested in. - // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. + // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. + // There will be one of two outcomes: + // a) We're not contained, so we have enough room and our position is good. + // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. + for (;;) { + ignore.push_back(upperId); + if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { + // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! + //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; + return ok("enough room"); } + if (isUp(upperNormal)) { + // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. + // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top + // of the enclosing entity, let's put our feet at upperIntersection and start over. + qDebug() << "FIXME inside above:" << upperId << " below:" << lowerId; + return mustMove(); + } + // We found a new bottom surface, which we're not interested in. + // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. } } } - //qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << upperIntersection << "@" << upperId << " and " << lowerIntersection << "@" << lowerId; + qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << (isDown(upperNormal) ? "down " : "up ") << upperIntersection << "@" << upperId << " and " << (isUp(lowerNormal) ? "up " : "down ") << lowerIntersection << "@" << lowerId; const float big = (float)TREE_SCALE; const auto skyHigh = up * big; auto fromAbove = capsuleCenter + skyHigh; From b9bf6f4c054a459fca4265c98e9bdbce3b300390 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 13:30:35 -0700 Subject: [PATCH 20/66] whitespace --- libraries/shared/src/GLMHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index f4989d61f5..70237e8ff6 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -540,7 +540,7 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda // if secondaryAxis is parallel with the primaryAxis, pick another axis. const float EPSILON = 1.0e-4f; - if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { + if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { // pick a better secondaryAxis. normSecondary = glm::vec3(1.0f, 0.0f, 0.0f); if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { From f99b579c14ed38ac0a4dff551c42b2bd2925e3b3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 13:38:18 -0700 Subject: [PATCH 21/66] added some docs --- libraries/animation/src/AnimInverseKinematics.cpp | 11 +++++++---- libraries/animation/src/AnimInverseKinematics.h | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5c0127334b..524d08de5b 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1445,10 +1445,11 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s } } +// pre-compute information about each joint influeced by this spline IK target. void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { std::vector splineJointInfoVec; - // build default spline + // build spline between the default poses. AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); @@ -1461,9 +1462,11 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext } else { spline = computeSplineFromTipAndBase(tipPose, basePose); } + + // measure the total arc length along the spline float totalArcLength = spline.arcLength(1.0f); - // AJT: FIXME: TODO: this won't work for horizontal splines... + // FIXME: TODO: this won't work for horizontal splines... float baseToTipHeight = tipPose.trans().y - basePose.trans().y; int index = target.getIndex(); @@ -1471,10 +1474,10 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); - // AJT: FIXME: TODO: this wont work for horizontal splines... + // FIXME: TODO: this wont work for horizontal splines... float ratio = (defaultPose.trans().y - basePose.trans().y) / baseToTipHeight; - // compute offset from default spline pose to default pose. + // compute offset from spline to the default pose. float t = spline.arcLengthInverse(ratio * totalArcLength); AnimPose pose(glm::vec3(1.0f), glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)), spline(t)); AnimPose offsetPose = pose.inverse() * defaultPose; diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 459b37d0b6..ff1ab9115f 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -117,10 +117,11 @@ protected: AnimPoseVec _relativePoses; // current relative poses AnimPoseVec _limitCenterPoses; // relative + // used to pre-compute information about each joint influeced by a spline IK target. struct SplineJointInfo { - int jointIndex; - float ratio; - AnimPose offsetPose; + int jointIndex; // joint in the skeleton that this information pertains to. + float ratio; // percentage (0..1) along the spline for this joint. + AnimPose offsetPose; // local offset from the spline to the joint. }; std::map> _splineJointInfoMap; From 551426f46e1d645049e344dbb1c5309fc0800771 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 13:56:06 -0700 Subject: [PATCH 22/66] Made computeSplineJointInfosForIKTarget more general It should now work for non-vertical oriented splines. --- libraries/animation/src/AnimInverseKinematics.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 524d08de5b..12d480a578 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1466,16 +1466,16 @@ void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext // measure the total arc length along the spline float totalArcLength = spline.arcLength(1.0f); - // FIXME: TODO: this won't work for horizontal splines... - float baseToTipHeight = tipPose.trans().y - basePose.trans().y; + glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; int index = target.getIndex(); int endIndex = _skeleton->getParentIndex(_hipsIndex); while (index != endIndex) { AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); - // FIXME: TODO: this wont work for horizontal splines... - float ratio = (defaultPose.trans().y - basePose.trans().y) / baseToTipHeight; + float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; // compute offset from spline to the default pose. float t = spline.arcLengthInverse(ratio * totalArcLength); From 3710637254a01e8b48e83f720d26973de9430338 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 1 Jun 2017 15:17:02 -0700 Subject: [PATCH 23/66] warning fix --- libraries/shared/src/CubicHermiteSpline.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index 17b4fda991..d03136867a 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -37,7 +37,7 @@ public: float t2 = t * t; float w0 = -6.0f * t + 6.0f * t2; float w1 = 1.0f - 4.0f * t + 3.0f * t2; - float w2 = 6.0 * t - 6.0f * t2; + float w2 = 6.0f * t - 6.0f * t2; float w3 = -2.0f * t + 3.0f * t2; return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1; } From 6b2e4c5abca650a627d53462aabe6c04f030a869 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 1 Jun 2017 17:08:22 -0700 Subject: [PATCH 24/66] zaru stuff --- interface/src/avatar/MyAvatar.cpp | 14 +++++++++----- interface/src/avatar/MyAvatar.h | 4 ++-- libraries/physics/src/CharacterController.cpp | 8 +++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bbac271450..d9b5309a77 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -439,7 +439,7 @@ void MyAvatar::update(float deltaTime) { } if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // fix only when needed and ready _physicsSafetyPending = false; - safeLanding(_goToPosition); // no-op if already safe + safeLanding(_goToPosition, _characterController.isStuck()); // no-op if already safe } Head* head = getHead(); @@ -2236,7 +2236,7 @@ void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // S goToLocation(position); QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } -bool MyAvatar::safeLanding(const glm::vec3& position) { +bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. // There needs to be a "landing" if: // a) the closest above and the closest below are less than the avatar capsule height apart, or @@ -2253,7 +2253,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { return result; } glm::vec3 better; - if (!requiresSafeLanding(position, better)) { + if (!requiresSafeLanding(position, better, force)) { return false; } qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); @@ -2269,7 +2269,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { } // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. -bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut, bool force) { // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. @@ -2324,7 +2324,11 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those - // conditions, so no need to check our feet below. + // conditions, so no need to check our feet below, unless forced. + if (force) { + upperIntersection = capsuleCenter; + return mustMove(); + } return ok("nothing above"); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index efab177a04..d9a2c03c38 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -523,7 +523,7 @@ public slots: bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); void goToLocationAndEnableCollisions(const glm::vec3& newPosition); - bool safeLanding(const glm::vec3& position); + bool safeLanding(const glm::vec3& position, bool force = false); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); @@ -571,7 +571,7 @@ private: glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; - bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut, bool force = false); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 88eff1a0fc..06c211e2f3 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -172,10 +172,12 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp); // If there's non-trivial penetration with a big impulse for several steps, we're probably stuck. // Note it here in the controller, and let MyAvatar figure out what to do about it. - const float STUCK_PENETRATION = -0.1f; // always negative into the object. + const float STUCK_PENETRATION = -0.05f; // always negative into the object. const float STUCK_IMPULSE = 500.0f; - const int STUCK_LIFETIME = 50; - if ((contact.getDistance() < -STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { + const int STUCK_LIFETIME = 3; + //if (contact.getDistance() < STUCK_PENETRATION) qDebug() << "FIXME checking contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); + if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { + qDebug() << "FIXME stuck contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); isStuck = true; // latch on } if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { From 580c2548e9fcf89327150138ba2848cb6e1f1e82 Mon Sep 17 00:00:00 2001 From: NeetBhagat Date: Fri, 2 Jun 2017 23:04:58 +0530 Subject: [PATCH 25/66] Initial Commit : 1) Resolve a bug when 2 different entity is grabbed in 2 hands. And move your left hand and entity of left hand grow/shrink when you move right/left hand. 2) Resolve right hand-left hand grow-shrink functionality --- scripts/system/controllers/handControllerGrab.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 993cf22d83..ba30b88113 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3330,11 +3330,17 @@ function MyController(hand) { return; } + var isSameEntityInBothControllers = false; + if (leftController.grabbedThingID && rightController.grabbedThingID && + leftController.grabbedThingID == rightController.grabbedThingID) { + isSameEntityInBothControllers = true; + } + if (!this.shouldScale) { // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling if (this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed() && - this.getOtherHandController().state === STATE_OFF) { + isSameEntityInBothControllers) { this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), this.getOtherHandController().getHandPosition())); this.scalingStartDimensions = props.dimensions; From 2422c7e1bb73e76b3a1a53bb106aff74b4c17d03 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Jun 2017 15:47:51 -0700 Subject: [PATCH 26/66] code review feedback --- .../animation/src/AnimInverseKinematics.cpp | 31 +++++++++++-------- .../animation/src/TranslationAccumulator.h | 10 +++--- libraries/shared/src/CubicHermiteSpline.h | 6 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 12d480a578..c155f4733f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -540,16 +540,21 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co if (splineJointInfo.jointIndex != _hipsIndex) { // constrain the amount the spine can stretch or compress float length = glm::length(relPose.trans()); - float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); - const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; - const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); - const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); - if (length > MAX_LENGTH) { - relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; - constrained = true; - } else if (length < MIN_LENGTH) { - relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; - constrained = true; + const float EPSILON = 0.0001f; + if (length > EPSILON) { + float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans()); + const float STRETCH_COMPRESS_PERCENTAGE = 0.15f; + const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE); + const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE); + if (length > MAX_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MAX_LENGTH; + constrained = true; + } else if (length < MIN_LENGTH) { + relPose.trans() = (relPose.trans() / length) * MIN_LENGTH; + constrained = true; + } + } else { + relPose.trans() = glm::vec3(0.0f); } } @@ -1521,10 +1526,10 @@ void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, co // draw red and white stripped spline, parameterized by arc length. // i.e. each stripe should be the same length. AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix()); - int NUM_SUBDIVISIONS = 20; - const float dArcLength = totalArcLength / NUM_SUBDIVISIONS; + const int NUM_SEGMENTS = 20; + const float dArcLength = totalArcLength / NUM_SEGMENTS; float arcLength = 0.0f; - for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + for (int i = 0; i < NUM_SEGMENTS; i++) { float prevT = spline.arcLengthInverse(arcLength); float nextT = spline.arcLengthInverse(arcLength + dArcLength); DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE); diff --git a/libraries/animation/src/TranslationAccumulator.h b/libraries/animation/src/TranslationAccumulator.h index 65f8a0dc97..18cac5ec7a 100644 --- a/libraries/animation/src/TranslationAccumulator.h +++ b/libraries/animation/src/TranslationAccumulator.h @@ -18,19 +18,19 @@ public: int size() const { return _totalWeight > 0.0f; } - /// \param rotation rotation to add - /// \param weight contribution factor of this rotation to total accumulation + /// \param translation translation to add + /// \param weight contribution factor of this translation to total accumulation void add(const glm::vec3& translation, float weight = 1.0f); glm::vec3 getAverage(); - /// \return true if any rotations were accumulated + /// \return true if any translation were accumulated bool isDirty() const { return _isDirty; } - /// \brief clear accumulated rotation but don't change _isDirty + /// \brief clear accumulated translation but don't change _isDirty void clear(); - /// \brief clear accumulated rotation and set _isDirty to false + /// \brief clear accumulated translation and set _isDirty to false void clearAndClean(); private: diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index d03136867a..a393e4c51f 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -23,8 +23,8 @@ public: // evalute the hermite curve at parameter t (0..1) glm::vec3 operator()(float t) const { - float t3 = t * t * t; float t2 = t * t; + float t3 = t2 * t; float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f; float w1 = t3 - 2.0f * t2 + t; float w2 = -2.0f * t3 + 3.0f * t2; @@ -63,7 +63,7 @@ public: enum Constants { NUM_SUBDIVISIONS = 30 }; CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() { - memset(_values, 0, sizeof(float) * NUM_SUBDIVISIONS + 1); + memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1)); } CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) { // initialize _values with the accumulated arcLength along the spline. @@ -80,7 +80,7 @@ public: } CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) { - memcpy(_values, orig._values, sizeof(float) * NUM_SUBDIVISIONS + 1); + memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1)); } // given the spline parameter (0..1) output the arcLength of the spline up to that point. From 1a8f6b8de00c379f6efb35167382b7734afd8ea8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 2 Jun 2017 18:16:14 -0700 Subject: [PATCH 27/66] Readability improvement --- libraries/shared/src/CubicHermiteSpline.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index a393e4c51f..da2ed26de4 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -71,11 +71,11 @@ public: float alpha = 0.0f; float accum = 0.0f; _values[0] = 0.0f; - for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { accum += glm::distance(this->operator()(alpha), this->operator()(alpha + DELTA)); alpha += DELTA; - _values[i + 1] = accum; + _values[i] = accum; } } From 02c46080f7fb3e624523c5f72caeca53f78c600a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 3 Jun 2017 15:43:14 -0700 Subject: [PATCH 28/66] we don't need to force, but we do need some extra room for confined spaces --- interface/src/avatar/MyAvatar.cpp | 28 +++++++++++++--------------- interface/src/avatar/MyAvatar.h | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5a3c00b1ec..3c7f1e7d8d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -439,7 +439,7 @@ void MyAvatar::update(float deltaTime) { } if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // fix only when needed and ready _physicsSafetyPending = false; - safeLanding(_goToPosition, _characterController.isStuck()); // no-op if already safe + safeLanding(_goToPosition); // no-op if already safe } Head* head = getHead(); @@ -1555,11 +1555,11 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); - if (_characterController.isStuck()) { + /*FIXME if (_characterController.isStuck()) { _physicsSafetyPending = true; _goToPosition = getPosition(); qDebug() << "FIXME setting safety test at:" << _goToPosition; - } + }*/ } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); } @@ -2236,7 +2236,7 @@ void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // S goToLocation(position); QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } -bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { +bool MyAvatar::safeLanding(const glm::vec3& position) { // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. // There needs to be a "landing" if: // a) the closest above and the closest below are less than the avatar capsule height apart, or @@ -2253,7 +2253,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { return result; } glm::vec3 better; - if (!requiresSafeLanding(position, better, force)) { + if (!requiresSafeLanding(position, better)) { return false; } qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); @@ -2269,7 +2269,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { } // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. -bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut, bool force) { +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. @@ -2302,7 +2302,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette float distance; BoxFace face; const bool visibleOnly = false; - // FIXME: this doesn't do what we want here. findRayIntersection always works on mesh, skipping entirely based on collidable. What we really want is to use the collision hull! See CharacterGhostObject::rayTest? + // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable. + // What we really want is to use the collision hull! // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders const bool collidableOnly = true; const bool precisionPicking = true; @@ -2324,11 +2325,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those - // conditions, so no need to check our feet below, unless forced. - if (force) { - upperIntersection = capsuleCenter; - return mustMove(); - } + // conditions, so no need to check our feet below. return ok("nothing above"); } @@ -2342,7 +2339,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // The surface above us is the bottom of something, and the surface below us it the top of something. // I.e., we are in a clearing between two objects. auto delta = glm::distance(upperIntersection, lowerIntersection); - if (delta > (2 * halfHeight)) { + const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + if (delta > (halfHeightFactor * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. // There will be one of two outcomes: @@ -2368,11 +2366,11 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette } } - qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << (isDown(upperNormal) ? "down " : "up ") << upperIntersection << "@" << upperId << " and " << (isUp(lowerNormal) ? "up " : "down ") << lowerIntersection << "@" << lowerId; + include.push_back(upperId); // We're now looking for the intersection from above onto this entity. const float big = (float)TREE_SCALE; const auto skyHigh = up * big; auto fromAbove = capsuleCenter + skyHigh; - include.push_back(upperId); // We're looking for the intersection from above onto this entity. + qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << (isDown(upperNormal) ? "down " : "up ") << upperIntersection << "@" << upperId << " and " << (isUp(lowerNormal) ? "up " : "down ") << lowerIntersection << "@" << lowerId; if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { return ok("Unable to find a landing"); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d9a2c03c38..efab177a04 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -523,7 +523,7 @@ public slots: bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); void goToLocationAndEnableCollisions(const glm::vec3& newPosition); - bool safeLanding(const glm::vec3& position, bool force = false); + bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); @@ -571,7 +571,7 @@ private: glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; - bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut, bool force = false); + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; From 556dcd69ac1bbb934ecc3eb77ddad4337cd00b29 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 3 Jun 2017 15:48:48 -0700 Subject: [PATCH 29/66] wording change regarding use of exact for floors --- interface/resources/qml/AssetServer.qml | 2 +- interface/resources/qml/hifi/dialogs/TabletAssetServer.qml | 2 +- interface/resources/qml/hifi/tablet/NewModelDialog.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index bf46ba121c..eb47afc951 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -177,7 +177,7 @@ ScrollingWindow { SHAPE_TYPE_STATIC_MESH ], checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } }); diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 85f8a2f59e..6e0263787b 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -179,7 +179,7 @@ Rectangle { SHAPE_TYPE_STATIC_MESH ], checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } }); diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 5dbb733872..2d9d121209 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -118,7 +118,7 @@ Rectangle { id: text2 width: 160 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") wrapMode: Text.WordWrap font.pixelSize: 12 } From fd01258c76705bbd04cfb091db9e7a77689fa9d3 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 3 Jun 2017 16:02:58 -0700 Subject: [PATCH 30/66] cleanup --- interface/src/avatar/MyAvatar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3c7f1e7d8d..a14fd71f9b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1555,11 +1555,11 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isEnabledAndReady()) { setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); - /*FIXME if (_characterController.isStuck()) { + if (_characterController.isStuck()) { _physicsSafetyPending = true; _goToPosition = getPosition(); qDebug() << "FIXME setting safety test at:" << _goToPosition; - }*/ + } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); } @@ -2258,12 +2258,12 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { } qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); if (!getCollisionsEnabled()) { - goToLocation(better); // recurses + goToLocation(better); // recurses on next update } else { // If you try to go while stuck, physics will keep you stuck. setCollisionsEnabled(false); // Don't goToLocation just yet. Yield so that physics can act on the above. QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick - Q_ARG(glm::vec3, better)); // I.e., capsuleCenter - offset + Q_ARG(glm::vec3, better)); } return true; } From 3d62900daf5840ec5ede6c6c6f16a4c0d247875d Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 3 Jun 2017 16:49:16 -0700 Subject: [PATCH 31/66] When we do precision detailed picking on a model, check the back faces, too. In other words, precision picking from inside a model works. --- .../src/RenderableModelEntityItem.cpp | 2 +- libraries/render-utils/src/Model.cpp | 4 ++-- libraries/render-utils/src/Model.h | 2 +- libraries/shared/src/GeometryUtil.cpp | 4 ++-- libraries/shared/src/GeometryUtil.h | 6 +++--- libraries/shared/src/TriangleSet.cpp | 10 +++++----- libraries/shared/src/TriangleSet.h | 6 +++--- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 36273c1f07..eab0e3011e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori QString extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, - face, surfaceNormal, extraInfo, precisionPicking); + face, surfaceNormal, extraInfo, precisionPicking, precisionPicking); } void RenderableModelEntityItem::getCollisionGeometryResource() { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e5a25d733e..447d0e37bd 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -332,7 +332,7 @@ void Model::initJointStates() { bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles) { + QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; @@ -381,7 +381,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; - if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles)) { + if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d718145d66..53d446d306 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -156,7 +156,7 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles = false); + QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index c137ebd438..f853240fe3 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -290,12 +290,12 @@ glm::vec3 Triangle::getNormal() const { } bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { glm::vec3 firstSide = v0 - v1; glm::vec3 secondSide = v2 - v1; glm::vec3 normal = glm::cross(secondSide, firstSide); float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); - if (dividend > 0.0f) { + if (!allowBackface && dividend > 0.0f) { return false; // origin below plane } float divisor = glm::dot(normal, direction); diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 2fdc1aa25f..857d423896 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -83,7 +83,7 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire const glm::vec3& position, const glm::vec2& dimensions, float& distance); bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance); + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose @@ -104,8 +104,8 @@ public: }; inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const Triangle& triangle, float& distance) { - return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance); + const Triangle& triangle, float& distance, bool allowBackface = false) { + return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); } diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index aa21aa5cc0..61ad811a4d 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -31,7 +31,7 @@ void TriangleSet::clear() { } bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) { // reset our distance to be the max possible, lower level tests will store best distance here distance = std::numeric_limits::max(); @@ -95,7 +95,7 @@ void TriangleSet::balanceOctree() { // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; float boxDistance = distance; @@ -114,7 +114,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec const auto& triangle = _allTriangles[triangleIndex]; float thisTriangleDistance; trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { + if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; intersectedSomething = true; @@ -203,7 +203,7 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { } bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect @@ -247,7 +247,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } } // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 6cedc4da7e..3b0b33d7d5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -27,7 +27,7 @@ class TriangleSet { void clear(); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); const AABox& getBounds() const { return _bounds; } @@ -38,7 +38,7 @@ class TriangleSet { // checks our internal list of triangles bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); std::vector& _allTriangles; std::map _children; @@ -60,7 +60,7 @@ public: void insert(const Triangle& t); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false); void balanceOctree(); From a0ea306aedb88c9a3d10d51fb4487922ffd51028 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 4 Jun 2017 07:55:23 -0700 Subject: [PATCH 32/66] cleanup, and include missing allowBackface parameter --- interface/src/avatar/MyAvatar.cpp | 7 +------ libraries/physics/src/CharacterController.cpp | 4 +--- libraries/shared/src/TriangleSet.cpp | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a14fd71f9b..c12b6ebdee 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1558,7 +1558,6 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isStuck()) { _physicsSafetyPending = true; _goToPosition = getPosition(); - qDebug() << "FIXME setting safety test at:" << _goToPosition; } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); @@ -2232,7 +2231,6 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. - // FIXME: Doesn't work 100% of time. Need to figure out what isn't happening fast enough. E.g., don't goToLocation until confirmed removed from physics? goToLocation(position); QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } @@ -2274,7 +2272,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. auto ok = [&](const char* label) { // position is good to go, or at least, we cannot do better - qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); + //qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); return false; }; auto halfHeight = _characterController.getCapsuleHalfHeight(); @@ -2350,14 +2348,12 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette ignore.push_back(upperId); if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! - //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; return ok("enough room"); } if (isUp(upperNormal)) { // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top // of the enclosing entity, let's put our feet at upperIntersection and start over. - qDebug() << "FIXME inside above:" << upperId << " below:" << lowerId; return mustMove(); } // We found a new bottom surface, which we're not interested in. @@ -2370,7 +2366,6 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette const float big = (float)TREE_SCALE; const auto skyHigh = up * big; auto fromAbove = capsuleCenter + skyHigh; - qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << (isDown(upperNormal) ? "down " : "up ") << upperIntersection << "@" << upperId << " and " << (isUp(lowerNormal) ? "up " : "down ") << lowerIntersection << "@" << lowerId; if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { return ok("Unable to find a landing"); } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 06c211e2f3..bd4d1201c7 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -175,9 +175,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { const float STUCK_PENETRATION = -0.05f; // always negative into the object. const float STUCK_IMPULSE = 500.0f; const int STUCK_LIFETIME = 3; - //if (contact.getDistance() < STUCK_PENETRATION) qDebug() << "FIXME checking contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { - qDebug() << "FIXME stuck contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); isStuck = true; // latch on } if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { @@ -193,7 +191,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { if (!_stepUpEnabled || hitHeight > _maxStepHeight) { // this manifold is invalidated by point that is too high stepContactIndex = -1; - qDebug() << "FIXME breaking early"; break; + break; } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) { highestStep = hitHeight; stepContactIndex = j; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index 61ad811a4d..68c99a9753 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& } int trianglesTouched = 0; - auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); + auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface); #if WANT_DEBUGGING if (precision) { From 05f41fb4f81c65db3687f4a518b720afc769e5c9 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sun, 4 Jun 2017 08:13:12 -0700 Subject: [PATCH 33/66] cleanup --- interface/src/avatar/MyAvatar.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c12b6ebdee..3ee43def87 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -435,9 +435,11 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); - _physicsSafetyPending = getCollisionsEnabled(); // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + _physicsSafetyPending = getCollisionsEnabled(); } - if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // fix only when needed and ready + if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { + // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; safeLanding(_goToPosition); // no-op if already safe } @@ -2268,9 +2270,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { - // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: - // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. - // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. + // We begin with utilities and tests. The Algorithm in four parts is below. auto ok = [&](const char* label) { // position is good to go, or at least, we cannot do better //qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); return false; @@ -2283,7 +2283,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette if (!entityTree) { return ok("no entity tree"); } - + // More utilities. const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); const auto capsuleCenter = positionIn + offset; const auto up = _worldUpDirection, down = -up; @@ -2391,7 +2391,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - qDebug() << "FIXME updateMotionBehaviorFromMenu collisions:" << menu->isOptionChecked(MenuOption::EnableAvatarCollisions) << "physics:" << qApp->isPhysicsEnabled(); setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } From 2ce72d554bd34200259ef9d8ed517c28963c92e5 Mon Sep 17 00:00:00 2001 From: NeetBhagat Date: Mon, 5 Jun 2017 14:11:53 +0530 Subject: [PATCH 34/66] Resolve 2 issues: 1) Entity can grow/shrink if it is grabbed by first right hand then left hand. 2) Stop unwanted grow/shrink of an entity. --- scripts/system/controllers/handControllerGrab.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ba30b88113..04921fe14d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3330,17 +3330,12 @@ function MyController(hand) { return; } - var isSameEntityInBothControllers = false; - if (leftController.grabbedThingID && rightController.grabbedThingID && - leftController.grabbedThingID == rightController.grabbedThingID) { - isSameEntityInBothControllers = true; - } - if (!this.shouldScale) { // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling if (this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed() && - isSameEntityInBothControllers) { + this.grabbedThingID && this.getOtherHandController().grabbedThingID && + this.grabbedThingID == this.getOtherHandController().grabbedThingID) { this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), this.getOtherHandController().getHandPosition())); this.scalingStartDimensions = props.dimensions; From ca526e07765cd316d91d7e50a87d1a9544a257de Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 5 Jun 2017 07:11:25 -0700 Subject: [PATCH 35/66] git rid of a lambda --- interface/src/avatar/MyAvatar.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3ee43def87..0370acaf05 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2271,17 +2271,13 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { // We begin with utilities and tests. The Algorithm in four parts is below. - auto ok = [&](const char* label) { // position is good to go, or at least, we cannot do better - //qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); - return false; - }; auto halfHeight = _characterController.getCapsuleHalfHeight(); if (halfHeight == 0) { - return ok("zero height avatar"); + return false; // zero height avatar } auto entityTree = DependencyManager::get()->getTree(); if (!entityTree) { - return ok("no entity tree"); + return false; // no entity tree } // More utilities. const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); @@ -2324,12 +2320,12 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those // conditions, so no need to check our feet below. - return ok("nothing above"); + return false; // nothing above } if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. - return ok("nothing below"); + return false; // nothing below } // See if we have room between entities above and below, but that we are not contained. @@ -2348,7 +2344,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette ignore.push_back(upperId); if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! - return ok("enough room"); + return false; // enough room } if (isUp(upperNormal)) { // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. @@ -2367,7 +2363,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette const auto skyHigh = up * big; auto fromAbove = capsuleCenter + skyHigh; if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { - return ok("Unable to find a landing"); + return false; // Unable to find a landing } // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. return mustMove(); From 5cb1918b807bedd1b8f6e7634ec50b01dfadbbad Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 6 Jun 2017 13:26:16 +1200 Subject: [PATCH 36/66] Simplify code --- libraries/animation/src/Rig.cpp | 49 ++++++++++++--------------------- libraries/animation/src/Rig.h | 6 ++-- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0e48cd8bd1..6a53f6c0f2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -28,6 +28,7 @@ #include "AnimClip.h" #include "AnimInverseKinematics.h" #include "AnimSkeleton.h" +#include "AnimUtil.h" #include "IKTarget.h" static bool isEqual(const glm::vec3& u, const glm::vec3& v) { @@ -1189,8 +1190,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); _isLeftHandControlled = true; - _lastLeftHandControlledPosition = handPosition; - _lastLeftHandControlledOrientation = params.leftOrientation; + _lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition); } else { if (_isLeftHandControlled) { _leftHandRelaxDuration = RELAX_DURATION; @@ -1200,22 +1200,15 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f if (_leftHandRelaxDuration > 0) { // Move hand from controlled position to non-controlled position. _leftHandRelaxDuration = std::max(_leftHandRelaxDuration - dt, 0.0f); - auto ikNode = getAnimInverseKinematicsNode(); if (ikNode) { - float alpha = _leftHandRelaxDuration / RELAX_DURATION; - auto uncontrolledHandPose = ikNode->getUncontrolledLeftHandPose(); - auto uncontrolledHipsPose = ikNode->getUncontrolledHipPose(); - - glm::vec3 relaxedPosition = _geometryOffset * (uncontrolledHandPose.trans() - uncontrolledHipsPose.trans()); - glm::vec3 handPosition = alpha * _lastLeftHandControlledPosition + (1.0f - alpha) * relaxedPosition; - - const AnimPose geometryToRigPose(_geometryToRigTransform); - glm::quat handOrientation = geometryToRigPose.rot() * uncontrolledHandPose.rot(); - handOrientation = slerp(handOrientation, _lastLeftHandControlledOrientation, alpha); - - _animVars.set("leftHandPosition", handPosition); - _animVars.set("leftHandRotation", handOrientation); + float alpha = 1.0f - _leftHandRelaxDuration / RELAX_DURATION; + const AnimPose geometryToRigTransform(_geometryToRigTransform); + AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose(); + AnimPose handPose; + ::blend(1, &_lastLeftHandControlledPose, &uncontrolledHandPose, alpha, &handPose); + _animVars.set("leftHandPosition", handPose.trans()); + _animVars.set("leftHandRotation", handPose.rot()); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } } else { @@ -1240,8 +1233,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); _isRightHandControlled = true; - _lastRightHandControlledPosition = handPosition; - _lastRightHandControlledOrientation = params.rightOrientation; + _lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition); } else { if (_isRightHandControlled) { _rightHandRelaxDuration = RELAX_DURATION; @@ -1251,22 +1243,15 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f if (_rightHandRelaxDuration > 0) { // Move hand from controlled position to non-controlled position. _rightHandRelaxDuration = std::max(_rightHandRelaxDuration - dt, 0.0f); - auto ikNode = getAnimInverseKinematicsNode(); if (ikNode) { - float alpha = _rightHandRelaxDuration / RELAX_DURATION; - auto uncontrolledHandPose = ikNode->getUncontrolledRightHandPose(); - auto uncontrolledHipsPose = ikNode->getUncontrolledHipPose(); - - glm::vec3 relaxedPosition = _geometryOffset * (uncontrolledHandPose.trans() - uncontrolledHipsPose.trans()); - glm::vec3 handPosition = alpha * _lastRightHandControlledPosition + (1.0f - alpha) * relaxedPosition; - - const AnimPose geometryToRigPose(_geometryToRigTransform); - glm::quat handOrientation = geometryToRigPose.rot() * uncontrolledHandPose.rot(); - handOrientation = slerp(handOrientation, _lastRightHandControlledOrientation, alpha); - - _animVars.set("rightHandPosition", handPosition); - _animVars.set("rightHandRotation", handOrientation); + float alpha = 1.0f - _rightHandRelaxDuration / RELAX_DURATION; + const AnimPose geometryToRigTransform(_geometryToRigTransform); + AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose(); + AnimPose handPose; + ::blend(1, &_lastRightHandControlledPose, &uncontrolledHandPose, alpha, &handPose); + _animVars.set("rightHandPosition", handPose.trans()); + _animVars.set("rightHandRotation", handPose.rot()); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } } else { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 1c7e40a04a..0911f330f0 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -360,10 +360,8 @@ private: bool _isRightHandControlled { false }; float _leftHandRelaxDuration { 0.0f }; float _rightHandRelaxDuration { 0.0f }; - glm::vec3 _lastLeftHandControlledPosition; - glm::vec3 _lastRightHandControlledPosition; - glm::quat _lastLeftHandControlledOrientation; - glm::quat _lastRightHandControlledOrientation; + AnimPose _lastLeftHandControlledPose; + AnimPose _lastRightHandControlledPose; }; #endif /* defined(__hifi__Rig__) */ From 3de243b093dd370fb2fdf00909bbb3f603590f32 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 5 Jun 2017 18:48:11 -0700 Subject: [PATCH 37/66] Include the radius of the sphere at the avatar's feet. --- interface/src/avatar/MyAvatar.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0370acaf05..a79aa2a50d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -294,7 +294,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { CameraMode mode = qApp->getCamera().getMode(); _globalPosition = getPosition(); - // This might not be right! Isn't the capsule local offset in avatar space? -HR 5/26/17 + // This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17 _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); _globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight(); _globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius(); @@ -456,7 +456,7 @@ void MyAvatar::update(float deltaTime) { setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); - // This might not be right! Isn't the capsule local offset in avatar space? -HR 5/26/17 + // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), @@ -2271,7 +2271,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { // We begin with utilities and tests. The Algorithm in four parts is below. - auto halfHeight = _characterController.getCapsuleHalfHeight(); + auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); if (halfHeight == 0) { return false; // zero height avatar } From 9bc1bc47a2012abb73afc937b9a892c2301f9b63 Mon Sep 17 00:00:00 2001 From: seefo Date: Tue, 6 Jun 2017 14:57:42 -0700 Subject: [PATCH 38/66] Added CLI to Oven tool --- tools/oven/src/BakerCLI.cpp | 69 +++++++++++++++++++++++++++++++++++++ tools/oven/src/BakerCLI.h | 32 +++++++++++++++++ tools/oven/src/Oven.cpp | 32 +++++++++++++---- 3 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 tools/oven/src/BakerCLI.cpp create mode 100644 tools/oven/src/BakerCLI.h diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp new file mode 100644 index 0000000000..cb23eff224 --- /dev/null +++ b/tools/oven/src/BakerCLI.cpp @@ -0,0 +1,69 @@ +// +// BakerCLI.cpp +// tools/oven/src +// +// Created by Robbie Uvanni on 6/6/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include "ModelBakingLoggingCategory.h" + +#include "Oven.h" +#include "BakerCLI.h" + +#include "FBXBaker.h" +#include "TextureBaker.h" + +void BakerCLI::bakeFile(const QString inputFilename, const QString outputFilename) { + QUrl inputUrl(inputFilename); + + // if the URL doesn't have a scheme, assume it is a local file + if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") { + inputUrl.setScheme("file"); + } + + static const QString MODEL_EXTENSION { ".fbx" }; + + // check what kind of baker we should be creating + bool isFBX = inputFilename.endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); + bool isSupportedImage = false; + + for (QByteArray format : QImageReader::supportedImageFormats()) { + isSupportedImage |= inputFilename.endsWith(format, Qt::CaseInsensitive); + } + + // create our appropiate baker + Baker* baker; + + if (isFBX) { + baker = new FBXBaker(inputUrl, outputFilename, []() -> QThread* { + return qApp->getNextWorkerThread(); + }); + baker->moveToThread(qApp->getFBXBakerThread()); + } else if (isSupportedImage) { + baker = new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputFilename); + baker->moveToThread(qApp->getNextWorkerThread()); + } else { + qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; + return; + } + + // invoke the bake method on the baker thread + QMetaObject::invokeMethod(baker, "bake"); + + // make sure we hear about the results of this baker when it is done + connect(baker, &Baker::finished, this, &BakerCLI::handleFinishedBaker); +} + +void BakerCLI::handleFinishedBaker() { + qCDebug(model_baking) << "Finished baking file."; + sender()->deleteLater(); + QApplication::quit(); +} \ No newline at end of file diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h new file mode 100644 index 0000000000..6e613aeefc --- /dev/null +++ b/tools/oven/src/BakerCLI.h @@ -0,0 +1,32 @@ +// +// BakerCLI.h +// tools/oven/src +// +// Created by Robbie Uvanni on 6/6/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BakerCLI_h +#define hifi_BakerCLI_h + +#include +#include + +#include +#include + +class BakerCLI : public QObject { + Q_OBJECT + +public: + void bakeFile(const QString inputFilename, const QString outputFilename); + +private slots: + void handleFinishedBaker(); + +}; + +#endif // hifi_BakerCLI_h \ No newline at end of file diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index af660e9795..57252b4cf9 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -11,13 +11,16 @@ #include #include +#include +#include #include #include #include "ui/OvenMainWindow.h" - +#include "ModelBakingLoggingCategory.h" #include "Oven.h" +#include "BakerCli.h" static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; @@ -30,24 +33,39 @@ Oven::Oven(int argc, char* argv[]) : // init the settings interface so we can save and load settings Setting::init(); + // parse the command line parameters + QCommandLineParser parser; + + parser.addOptions({ + { "i", "Input filename.", "input" }, + { "o", "Output filename.", "output" } + }); + parser.addHelpOption(); + + parser.process(*this); + // enable compression in image library, except for cube maps image::setColorTexturesCompressionEnabled(true); image::setGrayscaleTexturesCompressionEnabled(true); image::setNormalTexturesCompressionEnabled(true); image::setCubeTexturesCompressionEnabled(true); - // check if we were passed any command line arguments that would tell us just to run without the GUI - - // setup the GUI - _mainWindow = new OvenMainWindow; - _mainWindow->show(); - // setup our worker threads setupWorkerThreads(QThread::idealThreadCount() - 1); // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process // setup the FBX Baker thread setupFBXBakerThread(); + + // check if we were passed any command line arguments that would tell us just to run without the GUI + if (parser.isSet("i") && parser.isSet("o")) { + BakerCLI* cli = new BakerCLI(); + cli->bakeFile(parser.value("i"), parser.value("o")); + } else { + // setup the GUI + _mainWindow = new OvenMainWindow; + _mainWindow->show(); + } } Oven::~Oven() { From a36727732fafe6a75d3c79f5743cbf067bed3218 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 6 Jun 2017 09:28:50 -0700 Subject: [PATCH 39/66] Fix bad initializer --- plugins/openvr/src/ViveControllerManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index c32579b0d8..f674fad50b 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -143,7 +143,7 @@ private: int _trackedControllers { 0 }; vr::IVRSystem*& _system; - quint64 _timeTilCalibration { 0.0f }; + quint64 _timeTilCalibration { 0 }; float _leftHapticStrength { 0.0f }; float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; From fc6a278217d632c5aa74cd845be1036d8658cbbd Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 6 Jun 2017 12:42:28 -0700 Subject: [PATCH 40/66] Fixing dangling thread issues, consolidating thread management --- assignment-client/src/Agent.cpp | 6 +- assignment-client/src/AssignmentClient.cpp | 18 +--- .../src/scripts/EntityScriptServer.cpp | 6 +- gvr-interface/src/RenderingClient.cpp | 12 +-- interface/src/Application.cpp | 43 ++------- interface/src/networking/CloseEventSender.cpp | 7 +- interface/src/networking/CloseEventSender.h | 1 + ...lessVoiceRecognitionScriptingInterface.cpp | 9 +- ...itlessVoiceRecognitionScriptingInterface.h | 1 - interface/src/ui/ModelsBrowser.cpp | 8 +- libraries/audio-client/src/AudioClient.cpp | 94 ++++--------------- libraries/audio-client/src/AudioClient.h | 4 +- libraries/networking/src/MessagesClient.cpp | 12 +-- libraries/networking/src/MessagesClient.h | 2 +- libraries/networking/src/NodeList.cpp | 6 ++ libraries/networking/src/NodeList.h | 1 + libraries/shared/src/ThreadHelpers.cpp | 39 ++++++++ libraries/shared/src/ThreadHelpers.h | 12 ++- tests/qt59/CMakeLists.txt | 15 +++ tests/qt59/src/main.cpp | 78 +++++++++++++++ tools/ac-client/src/ACClientApp.cpp | 15 +-- tools/atp-get/src/ATPGetApp.cpp | 16 +--- 22 files changed, 205 insertions(+), 200 deletions(-) create mode 100644 libraries/shared/src/ThreadHelpers.cpp create mode 100644 tests/qt59/CMakeLists.txt create mode 100644 tests/qt59/src/main.cpp diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f517716b72..1dc3aefb61 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -166,11 +166,7 @@ void Agent::run() { // Setup MessagesClient auto messagesClient = DependencyManager::set(); - QThread* messagesThread = new QThread; - messagesThread->setObjectName("Messages Client Thread"); - messagesClient->moveToThread(messagesThread); - connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); - messagesThread->start(); + messagesClient->startThread(); // make sure we hear about connected nodes so we can grab an ATP script if a request is pending connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 0869329d68..abfc66ac55 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -69,17 +69,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set(); DependencyManager::set(); - // setup a thread for the NodeList and its PacketReceiver - QThread* nodeThread = new QThread(this); - nodeThread->setObjectName("NodeList Thread"); - nodeThread->start(); - - // make sure the node thread is given highest priority - nodeThread->setPriority(QThread::TimeCriticalPriority); - - // put the NodeList on the node thread - nodeList->moveToThread(nodeThread); - + nodeList->startThread(); // set the logging target to the the CHILD_TARGET_NAME LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); @@ -166,14 +156,8 @@ void AssignmentClient::stopAssignmentClient() { } AssignmentClient::~AssignmentClient() { - QThread* nodeThread = DependencyManager::get()->thread(); - // remove the NodeList from the DependencyManager DependencyManager::destroy(); - - // ask the node thread to quit and wait until it is done - nodeThread->quit(); - nodeThread->wait(); } void AssignmentClient::aboutToQuit() { diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 270a22e17b..1b226ab642 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -236,11 +236,7 @@ void EntityScriptServer::run() { // Setup MessagesClient auto messagesClient = DependencyManager::set(); - QThread* messagesThread = new QThread; - messagesThread->setObjectName("Messages Client Thread"); - messagesClient->moveToThread(messagesThread); - connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); - messagesThread->start(); + messagesClient->startThread(); DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); diff --git a/gvr-interface/src/RenderingClient.cpp b/gvr-interface/src/RenderingClient.cpp index e6d2f6b585..b7c6f30a73 100644 --- a/gvr-interface/src/RenderingClient.cpp +++ b/gvr-interface/src/RenderingClient.cpp @@ -37,20 +37,12 @@ RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString DependencyManager::set(); // get our audio client setup on its own thread - QThread* audioThread = new QThread(); auto audioClient = DependencyManager::set(); - audioClient->setPositionGetter(getPositionForAudio); audioClient->setOrientationGetter(getOrientationForAudio); + audioClient->startThread(); - audioClient->moveToThread(audioThread); - connect(audioThread, &QThread::started, audioClient.data(), &AudioClient::start); - connect(audioClient.data(), &AudioClient::destroyed, audioThread, &QThread::quit); - connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); - - audioThread->start(); - - + connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket); _avatarTimer.setInterval(16); // 60 FPS _avatarTimer.start(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c4cd4aa0a..5b7f5afb98 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -651,6 +651,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us auto nodeList = DependencyManager::get(); + nodeList->startThread(); // Set up a watchdog thread to intentionally crash the application on deadlocks _deadlockWatchdogThread = new DeadlockWatchdogThread(); @@ -676,25 +677,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); - // start the nodeThread so its event loop is running - QThread* nodeThread = new QThread(this); - nodeThread->setObjectName("NodeList Thread"); - nodeThread->start(); - - // make sure the node thread is given highest priority - nodeThread->setPriority(QThread::TimeCriticalPriority); - // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(nodeList.data()); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - // put the NodeList and datagram processing on the node thread - nodeList->moveToThread(nodeThread); - - // put the audio processing on a separate thread - QThread* audioThread = new QThread(); - audioThread->setObjectName("Audio Thread"); auto audioIO = DependencyManager::get(); audioIO->setPositionGetter([]{ @@ -710,7 +697,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; }); - audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { audioIO->handleRecordedAudioInput(frame->data); }); @@ -724,9 +710,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); auto audioScriptingInterface = DependencyManager::set(); - connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); - connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); - connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); @@ -744,19 +727,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - audioThread->start(); + audioIO->startThread(); ResourceManager::init(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); // Setup MessagesClient - auto messagesClient = DependencyManager::get(); - QThread* messagesThread = new QThread; - messagesThread->setObjectName("Messages Client Thread"); - messagesClient->moveToThread(messagesThread); - connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); - messagesThread->start(); + DependencyManager::get()->startThread(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); @@ -1648,12 +1626,7 @@ void Application::aboutToQuit() { getActiveDisplayPlugin()->deactivate(); // use the CloseEventSender via a QThread to send an event that says the user asked for the app to close - auto closeEventSender = DependencyManager::get(); - QThread* closureEventThread = new QThread(this); - closeEventSender->moveToThread(closureEventThread); - // sendQuitEventAsync will bail immediately if the UserActivityLogger is not enabled - connect(closureEventThread, &QThread::started, closeEventSender.data(), &CloseEventSender::sendQuitEventAsync); - closureEventThread->start(); + DependencyManager::get()->startThread(); // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get()->hide("RunningScripts"); @@ -1741,6 +1714,8 @@ void Application::cleanupBeforeQuit() { // stop QML DependencyManager::destroy(); + DependencyManager::destroy(); + if (_snapshotSoundInjector != nullptr) { _snapshotSoundInjector->stop(); } @@ -1800,15 +1775,9 @@ Application::~Application() { ResourceManager::cleanup(); - QThread* nodeThread = DependencyManager::get()->thread(); - // remove the NodeList from the DependencyManager DependencyManager::destroy(); - // ask the node thread to quit and wait until it is done - nodeThread->quit(); - nodeThread->wait(); - Leapmotion::destroy(); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { diff --git a/interface/src/networking/CloseEventSender.cpp b/interface/src/networking/CloseEventSender.cpp index 8c3d6ae888..de8bd897b2 100644 --- a/interface/src/networking/CloseEventSender.cpp +++ b/interface/src/networking/CloseEventSender.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -87,4 +88,8 @@ bool CloseEventSender::hasTimedOutQuitEvent() { && QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS; } - +void CloseEventSender::startThread() { + moveToNewNamedThread(this, "CloseEvent Logger Thread", [this] { + sendQuitEventAsync(); + }); +} diff --git a/interface/src/networking/CloseEventSender.h b/interface/src/networking/CloseEventSender.h index 05e6f81ad4..b74412c41c 100644 --- a/interface/src/networking/CloseEventSender.h +++ b/interface/src/networking/CloseEventSender.h @@ -24,6 +24,7 @@ class CloseEventSender : public QObject, public Dependency { SINGLETON_DEPENDENCY public: + void startThread(); bool hasTimedOutQuitEvent(); bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; } diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp index 1352630f84..ebb5ca9280 100644 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp +++ b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp @@ -9,9 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + +#include #include #include -#include + #include "LimitlessVoiceRecognitionScriptingInterface.h" const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; @@ -24,9 +27,7 @@ LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingI connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); - _connection.moveToThread(&_connectionThread); - _connectionThread.setObjectName("Limitless Connection"); - _connectionThread.start(); + moveToNewNamedThread(&_connection, "Limitless Connection"); } void LimitlessVoiceRecognitionScriptingInterface::update() { diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h index d1b1139695..2a35c37ab0 100644 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h +++ b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h @@ -41,7 +41,6 @@ private: static const int _voiceTimeoutDuration; QTimer _voiceTimer; - QThread _connectionThread; LimitlessConnection _connection; void voiceTimeout(); diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 430cc805ed..44089119c6 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -84,12 +85,7 @@ ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) : _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); // Setup and launch update thread - QThread* thread = new QThread(); - thread->setObjectName("Models Browser"); - thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit())); - thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); - _handler->moveToThread(thread); - thread->start(); + moveToNewNamedThread(_handler, "Models Browser"); emit startDownloading(); // Initialize the view diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e03ca83131..6caa4fb159 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -31,10 +31,13 @@ #include #endif +#include +#include #include #include #include +#include #include #include #include @@ -76,59 +79,6 @@ using Mutex = std::mutex; using Lock = std::unique_lock; static Mutex _deviceMutex; -class BackgroundThread : public QThread { -public: - BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {} - virtual void join() = 0; -protected: - AudioClient* _client; -}; - -// background thread continuously polling device changes -class CheckDevicesThread : public BackgroundThread { -public: - CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {} - - void join() override { - _shouldQuit = true; - std::unique_lock lock(_joinMutex); - _joinCondition.wait(lock, [&]{ return !_isRunning; }); - } - -protected: - void run() override { - while (!_shouldQuit) { - _client->checkDevices(); - - const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; - QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); - } - std::lock_guard lock(_joinMutex); - _isRunning = false; - _joinCondition.notify_one(); - } - -private: - std::atomic _shouldQuit { false }; - bool _isRunning { true }; - std::mutex _joinMutex; - std::condition_variable _joinCondition; -}; - -// background thread buffering local injectors -class LocalInjectorsThread : public BackgroundThread { - Q_OBJECT -public: - LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {} - - void join() override { return; } - -private slots: - void prepare() { _client->prepareLocalAudioInjectors(); } -}; - -#include "AudioClient.moc" - static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) { for (int i = 0; i < numSamples/2; i++) { @@ -223,16 +173,15 @@ AudioClient::AudioClient() : _inputDevices = getDeviceNames(QAudio::AudioInput); _outputDevices = getDeviceNames(QAudio::AudioOutput); - // start a thread to detect any device changes - _checkDevicesThread = new CheckDevicesThread(this); - _checkDevicesThread->setObjectName("AudioClient CheckDevices Thread"); - _checkDevicesThread->setPriority(QThread::LowPriority); - _checkDevicesThread->start(); - // start a thread to process local injectors - _localInjectorsThread = new LocalInjectorsThread(this); - _localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread"); - _localInjectorsThread->start(); + // start a thread to detect any device changes + _checkDevicesTimer = new QTimer(this); + connect(_checkDevicesTimer, &QTimer::timeout, [this] { + QtConcurrent::run(QThreadPool::globalInstance(), [this] { + checkDevices(); + }); + }); + configureReverb(); @@ -263,15 +212,7 @@ void AudioClient::cleanupBeforeQuit() { stop(); - if (_checkDevicesThread) { - static_cast(_checkDevicesThread)->join(); - delete _checkDevicesThread; - } - - if (_localInjectorsThread) { - static_cast(_localInjectorsThread)->join(); - delete _localInjectorsThread; - } + _checkDevicesTimer->stop(); } void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { @@ -1369,10 +1310,8 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { if (!_activeLocalAudioInjectors.contains(injector)) { qCDebug(audioclient) << "adding new injector"; _activeLocalAudioInjectors.append(injector); - // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) injectorBuffer->setParent(nullptr); - injectorBuffer->moveToThread(_localInjectorsThread); // update the flag _localInjectorsAvailable.exchange(true, std::memory_order_release); @@ -1782,7 +1721,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { } // prepare injectors for the next callback - QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection); + QtConcurrent::run(QThreadPool::globalInstance(), [this] { + _audio->prepareLocalAudioInjectors(); + }); int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped); int framesPopped = samplesPopped / AudioConstants::STEREO; @@ -1855,3 +1796,8 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca avatarBoundingBoxCorner = corner; avatarBoundingBoxScale = scale; } + + +void AudioClient::startThread() { + moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); +} diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index b79cee238c..bec2fd2cc9 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -104,6 +104,7 @@ public: int _unfulfilledReads; }; + void startThread(); void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); @@ -386,8 +387,7 @@ private: RateCounter<> _silentInbound; RateCounter<> _audioInbound; - QThread* _checkDevicesThread { nullptr }; - QThread* _localInjectorsThread { nullptr }; + QTimer* _checkDevicesTimer { nullptr }; }; diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index 333552db4e..2302c22a48 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include "NetworkLogging.h" #include "NodeList.h" #include "PacketReceiver.h" @@ -30,12 +32,6 @@ MessagesClient::MessagesClient() { connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); } -void MessagesClient::init() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection); - } -} - void MessagesClient::decodeMessagesPacket(QSharedPointer receivedMessage, QString& channel, bool& isText, QString& message, QByteArray& data, QUuid& senderID) { quint16 channelLength; @@ -185,3 +181,7 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) { } } } + +void MessagesClient::startThread() { + moveToNewNamedThread(this, "Messages Client Thread"); +} diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index 51b468d646..6d0483fe9d 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -28,7 +28,7 @@ class MessagesClient : public QObject, public Dependency { public: MessagesClient(); - Q_INVOKABLE void init(); + void startThread(); Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); Q_INVOKABLE void sendLocalMessage(QString channel, QString message); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 868128f093..2aa30b84aa 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -1115,3 +1116,8 @@ void NodeList::setRequestsDomainListData(bool isRequesting) { }); _requestsDomainListData = isRequesting; } + + +void NodeList::startThread() { + moveToNewNamedThread(this, "NodeList Thread", QThread::TimeCriticalPriority); +} \ No newline at end of file diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 293b0942d6..ee836fa94b 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -52,6 +52,7 @@ class NodeList : public LimitedNodeList { SINGLETON_DEPENDENCY public: + void startThread(); NodeType_t getOwnerType() const { return _ownerType.load(); } void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); } diff --git a/libraries/shared/src/ThreadHelpers.cpp b/libraries/shared/src/ThreadHelpers.cpp new file mode 100644 index 0000000000..14ae35762b --- /dev/null +++ b/libraries/shared/src/ThreadHelpers.cpp @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2017/06/06 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ThreadHelpers.h" + +#include + + +void moveToNewNamedThread(QObject* object, const QString& name, std::function startCallback, QThread::Priority priority) { + Q_ASSERT(QThread::currentThread() == object->thread()); + // setup a thread for the NodeList and its PacketReceiver + QThread* thread = new QThread(); + thread->setObjectName(name); + + if (priority != QThread::InheritPriority) { + thread->setPriority(priority); + } + + QString tempName = name; + QObject::connect(thread, &QThread::started, [startCallback] { + startCallback(); + }); + // Make sure the thread will be destroyed and cleaned up + QObject::connect(object, &QObject::destroyed, thread, &QThread::quit); + QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); + + // put the object on the thread + object->moveToThread(thread); + thread->start(); +} + +void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority) { + moveToNewNamedThread(object, name, [] {}, priority); +} diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h index 0a92d36b80..6461fa5724 100644 --- a/libraries/shared/src/ThreadHelpers.h +++ b/libraries/shared/src/ThreadHelpers.h @@ -12,8 +12,13 @@ #define hifi_ThreadHelpers_h #include -#include -#include +#include + +#include +#include +#include +#include +#include template void withLock(L lock, F function) { @@ -26,4 +31,7 @@ void withLock(QMutex& lock, F function) { function(); } +void moveToNewNamedThread(QObject* object, const QString& name, std::function startCallback, QThread::Priority priority = QThread::InheritPriority); +void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority = QThread::InheritPriority); + #endif diff --git a/tests/qt59/CMakeLists.txt b/tests/qt59/CMakeLists.txt new file mode 100644 index 0000000000..32cc125ecf --- /dev/null +++ b/tests/qt59/CMakeLists.txt @@ -0,0 +1,15 @@ + +set(TARGET_NAME qt59) + +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Gui) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared networking) + +package_libraries_for_deployment() diff --git a/tests/qt59/src/main.cpp b/tests/qt59/src/main.cpp new file mode 100644 index 0000000000..c66a5e6f9a --- /dev/null +++ b/tests/qt59/src/main.cpp @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2017/06/06 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include + +#include +#include +#include +#include + +#include + + +class Qt59TestApp : public QCoreApplication { + Q_OBJECT +public: + Qt59TestApp(int argc, char* argv[]); + ~Qt59TestApp(); + +private: + void finish(int exitCode); +}; + + + +Qt59TestApp::Qt59TestApp(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + + Setting::init(); + DependencyManager::registerInheritance(); + DependencyManager::set([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); }); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, 0); + auto nodeList = DependencyManager::get(); + nodeList->startThread(); + auto messagesClient = DependencyManager::set(); + messagesClient->startThread(); + QTimer::singleShot(1000, [this] { finish(0); }); +} + +Qt59TestApp::~Qt59TestApp() { +} + + +void Qt59TestApp::finish(int exitCode) { + auto nodeList = DependencyManager::get(); + + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); + nodeList->getPacketReceiver().setShouldDropPackets(true); + + // remove the NodeList from the DependencyManager + DependencyManager::destroy(); + QCoreApplication::exit(exitCode); +} + + +int main(int argc, char * argv[]) { + QCoreApplication::setApplicationName("Qt59Test"); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + Qt59TestApp app(argc, argv); + + return app.exec(); +} + +#include "main.moc" \ No newline at end of file diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 4c12a09388..b81d092662 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -90,23 +90,14 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : auto nodeList = DependencyManager::get(); - // start the nodeThread so its event loop is running - QThread* nodeThread = new QThread(this); - nodeThread->setObjectName("NodeList Thread"); - nodeThread->start(); - - // make sure the node thread is given highest priority - nodeThread->setPriority(QThread::TimeCriticalPriority); + nodeList->startThread(); // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(nodeList.data()); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - // put the NodeList and datagram processing on the node thread - nodeList->moveToThread(nodeThread); - const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); @@ -239,12 +230,8 @@ void ACClientApp::finish(int exitCode) { // tell the packet receiver we're shutting down, so it can drop packets nodeList->getPacketReceiver().setShouldDropPackets(true); - QThread* nodeThread = DependencyManager::get()->thread(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); - // ask the node thread to quit and wait until it is done - nodeThread->quit(); - nodeThread->wait(); printFailedServers(); QCoreApplication::exit(exitCode); diff --git a/tools/atp-get/src/ATPGetApp.cpp b/tools/atp-get/src/ATPGetApp.cpp index 30054fffea..4125582c21 100644 --- a/tools/atp-get/src/ATPGetApp.cpp +++ b/tools/atp-get/src/ATPGetApp.cpp @@ -111,23 +111,13 @@ ATPGetApp::ATPGetApp(int argc, char* argv[]) : auto nodeList = DependencyManager::get(); - - // start the nodeThread so its event loop is running - QThread* nodeThread = new QThread(this); - nodeThread->setObjectName("NodeList Thread"); - nodeThread->start(); - - // make sure the node thread is given highest priority - nodeThread->setPriority(QThread::TimeCriticalPriority); + nodeList->startThread(); // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(nodeList.data()); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - // put the NodeList and datagram processing on the node thread - nodeList->moveToThread(nodeThread); - const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); @@ -258,12 +248,8 @@ void ATPGetApp::finish(int exitCode) { // tell the packet receiver we're shutting down, so it can drop packets nodeList->getPacketReceiver().setShouldDropPackets(true); - QThread* nodeThread = DependencyManager::get()->thread(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); - // ask the node thread to quit and wait until it is done - nodeThread->quit(); - nodeThread->wait(); QCoreApplication::exit(exitCode); } From b4bbf98fe3633eeed0e921f8062ae0130c326845 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 6 Jun 2017 13:55:24 -0700 Subject: [PATCH 41/66] Suppress TBB warnings --- .../src/audio/AudioMixerSlavePool.h | 4 +-- .../src/avatars/AvatarMixerSlavePool.h | 3 +- libraries/networking/src/LimitedNodeList.cpp | 2 -- libraries/networking/src/LimitedNodeList.h | 5 ++-- libraries/networking/src/Node.h | 2 +- libraries/networking/src/NodeList.h | 2 +- libraries/shared/src/TBBHelpers.h | 28 +++++++++++++++++++ tools/oven/src/Oven.h | 2 +- 8 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 libraries/shared/src/TBBHelpers.h diff --git a/assignment-client/src/audio/AudioMixerSlavePool.h b/assignment-client/src/audio/AudioMixerSlavePool.h index 4082ea18d9..25047faa89 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.h +++ b/assignment-client/src/audio/AudioMixerSlavePool.h @@ -16,10 +16,10 @@ #include #include -#include - #include +#include + #include "AudioMixerSlave.h" class AudioMixerSlavePool; diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index e6ac2a1f4e..15bd681b2c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -16,10 +16,9 @@ #include #include -#include - #include +#include #include #include "AvatarMixerSlave.h" diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index cba1e664ab..93ae941f1e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -22,8 +22,6 @@ #include #include -#include - #include #include #include diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 056a4d16cf..554386f786 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include @@ -68,9 +68,8 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges"; -using namespace tbb; typedef std::pair UUIDNodePair; -typedef concurrent_unordered_map NodeHash; +typedef tbb::concurrent_unordered_map NodeHash; typedef quint8 PingType_t; namespace PingType { diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index d1bbffd817..1092fcc7fa 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include "HifiSockAddr.h" #include "NetworkPeer.h" diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index ee836fa94b..6db760b3ca 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -20,7 +20,7 @@ #include // not on windows, not needed for mac or windows #endif -#include +#include #include #include diff --git a/libraries/shared/src/TBBHelpers.h b/libraries/shared/src/TBBHelpers.h new file mode 100644 index 0000000000..6b5c4d416b --- /dev/null +++ b/libraries/shared/src/TBBHelpers.h @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis on 2017/06/06 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_TBBHelpers_h +#define hifi_TBBHelpers_h + +#ifdef _WIN32 +#pragma warning( push ) +#pragma warning( disable : 4334 ) +#endif + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#pragma warning( pop ) +#endif + +#endif // hifi_TBBHelpers_h diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 350c615ce0..569b73a3e2 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -14,7 +14,7 @@ #include -#include +#include #include From 46400f41222ddd5ecea1995f8cf7ffb7666a670d Mon Sep 17 00:00:00 2001 From: seefo Date: Tue, 6 Jun 2017 15:05:26 -0700 Subject: [PATCH 42/66] Cleaned up oven CLI --- tools/oven/src/BakerCLI.cpp | 3 --- tools/oven/src/BakerCLI.h | 4 ---- tools/oven/src/Oven.cpp | 7 ++----- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index cb23eff224..3a0d1eeef7 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -9,15 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - #include #include #include "ModelBakingLoggingCategory.h" #include "Oven.h" #include "BakerCLI.h" - #include "FBXBaker.h" #include "TextureBaker.h" diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 6e613aeefc..7803aa6c86 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -12,10 +12,6 @@ #ifndef hifi_BakerCLI_h #define hifi_BakerCLI_h -#include -#include - -#include #include class BakerCLI : public QObject { diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 57252b4cf9..3d356a5f1e 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -12,13 +12,11 @@ #include #include #include -#include #include #include #include "ui/OvenMainWindow.h" -#include "ModelBakingLoggingCategory.h" #include "Oven.h" #include "BakerCli.h" @@ -37,11 +35,10 @@ Oven::Oven(int argc, char* argv[]) : QCommandLineParser parser; parser.addOptions({ - { "i", "Input filename.", "input" }, - { "o", "Output filename.", "output" } + { "i", "Path to file that you would like to bake.", "input" }, + { "o", "Path to folder that will be used as output.", "output" } }); parser.addHelpOption(); - parser.process(*this); // enable compression in image library, except for cube maps From a3d2fa2630b713b31297e221284a1efe4184b90a Mon Sep 17 00:00:00 2001 From: seefo Date: Tue, 6 Jun 2017 16:13:18 -0700 Subject: [PATCH 43/66] Made requested changed to Oven CLI --- tools/oven/src/BakerCLI.cpp | 12 ++++++++---- tools/oven/src/BakerCLI.h | 5 ++++- tools/oven/src/Oven.cpp | 13 ++++++++----- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 3a0d1eeef7..04d43853da 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -9,16 +9,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include -#include "ModelBakingLoggingCategory.h" +#include "ModelBakingLoggingCategory.h" #include "Oven.h" #include "BakerCLI.h" #include "FBXBaker.h" #include "TextureBaker.h" -void BakerCLI::bakeFile(const QString inputFilename, const QString outputFilename) { +BakerCLI::BakerCLI(Oven* parent) : QObject() { +} + +void BakerCLI::bakeFile(const QString inputFilename, const QString outputPath) { QUrl inputUrl(inputFilename); // if the URL doesn't have a scheme, assume it is a local file @@ -40,12 +44,12 @@ void BakerCLI::bakeFile(const QString inputFilename, const QString outputFilenam Baker* baker; if (isFBX) { - baker = new FBXBaker(inputUrl, outputFilename, []() -> QThread* { + baker = new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }); baker->moveToThread(qApp->getFBXBakerThread()); } else if (isSupportedImage) { - baker = new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputFilename); + baker = new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath); baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 7803aa6c86..88ea028b9f 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -14,11 +14,14 @@ #include +#include "Oven.h" + class BakerCLI : public QObject { Q_OBJECT public: - void bakeFile(const QString inputFilename, const QString outputFilename); + BakerCLI(Oven* parent); + void bakeFile(const QString inputFilename, const QString outputPath); private slots: void handleFinishedBaker(); diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 3d356a5f1e..7431863ba5 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -22,6 +22,9 @@ static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; +static const QString CLI_INPUT_PARAMETER = "i"; +static const QString CLI_OUTPUT_PARAMETER = "o"; + Oven::Oven(int argc, char* argv[]) : QApplication(argc, argv) { @@ -35,8 +38,8 @@ Oven::Oven(int argc, char* argv[]) : QCommandLineParser parser; parser.addOptions({ - { "i", "Path to file that you would like to bake.", "input" }, - { "o", "Path to folder that will be used as output.", "output" } + { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, + { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" } }); parser.addHelpOption(); parser.process(*this); @@ -55,9 +58,9 @@ Oven::Oven(int argc, char* argv[]) : setupFBXBakerThread(); // check if we were passed any command line arguments that would tell us just to run without the GUI - if (parser.isSet("i") && parser.isSet("o")) { - BakerCLI* cli = new BakerCLI(); - cli->bakeFile(parser.value("i"), parser.value("o")); + if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { + BakerCLI* cli = new BakerCLI(this); + cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER)); } else { // setup the GUI _mainWindow = new OvenMainWindow; From 608becef7dd7f3af88bc7f81ecc90c30ae816980 Mon Sep 17 00:00:00 2001 From: Mohammed Nafees Date: Wed, 7 Jun 2017 15:51:27 +0530 Subject: [PATCH 44/66] Make the back button work again, uses the browser history directly now. --- scripts/system/html/js/marketplacesInject.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index d4937ac9db..3b3d4b4937 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -25,9 +25,7 @@ var canWriteAssets = false; var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - - var lastPage = "https://metaverse.highfidelity.com/marketplace?"; - + function injectCommonCode(isDirectoryPage) { // Supporting styles from marketplaces.css. @@ -67,7 +65,7 @@ // Footer actions. $("#back-button").on("click", function () { - window.location = lastPage; + window.history.back(); }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(GOTO_DIRECTORY); @@ -344,12 +342,7 @@ } } - function locationChanged() { - lastPage = location.href; - } - // Load / unload. window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - window.addEventListener("hashchange", locationChanged); }()); From 99e9e108829eb60369212620b816470dea5f91d3 Mon Sep 17 00:00:00 2001 From: seefo Date: Wed, 7 Jun 2017 10:35:20 -0700 Subject: [PATCH 45/66] Made requested changes to OvenCLI constructor --- tools/oven/src/BakerCLI.cpp | 9 ++++----- tools/oven/src/BakerCLI.h | 2 +- tools/oven/src/Oven.cpp | 11 ++++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 04d43853da..b76e00269f 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -19,11 +19,10 @@ #include "FBXBaker.h" #include "TextureBaker.h" -BakerCLI::BakerCLI(Oven* parent) : QObject() { +BakerCLI::BakerCLI(Oven* parent) : QObject(parent) { } -void BakerCLI::bakeFile(const QString inputFilename, const QString outputPath) { - QUrl inputUrl(inputFilename); +void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { // if the URL doesn't have a scheme, assume it is a local file if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") { @@ -33,11 +32,11 @@ void BakerCLI::bakeFile(const QString inputFilename, const QString outputPath) { static const QString MODEL_EXTENSION { ".fbx" }; // check what kind of baker we should be creating - bool isFBX = inputFilename.endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); + bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); bool isSupportedImage = false; for (QByteArray format : QImageReader::supportedImageFormats()) { - isSupportedImage |= inputFilename.endsWith(format, Qt::CaseInsensitive); + isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive); } // create our appropiate baker diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 88ea028b9f..5935151bb5 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -21,7 +21,7 @@ class BakerCLI : public QObject { public: BakerCLI(Oven* parent); - void bakeFile(const QString inputFilename, const QString outputPath); + void bakeFile(QUrl inputUrl, const QString outputPath); private slots: void handleFinishedBaker(); diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 7431863ba5..a38aaa2b97 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -58,9 +58,14 @@ Oven::Oven(int argc, char* argv[]) : setupFBXBakerThread(); // check if we were passed any command line arguments that would tell us just to run without the GUI - if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { - BakerCLI* cli = new BakerCLI(this); - cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER)); + if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) { + if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) { + BakerCLI* cli = new BakerCLI(this); + cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER)); + } else { + parser.showHelp(); + QApplication::quit(); + } } else { // setup the GUI _mainWindow = new OvenMainWindow; From a654bfa692ca3fc89ddcd0057d371f954f8edcb1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 Jun 2017 11:20:36 -0700 Subject: [PATCH 46/66] Update server-console to watch interface via pid rather than marker --- interface/src/main.cpp | 2 +- libraries/networking/src/SandboxUtils.cpp | 8 ++++---- libraries/networking/src/SandboxUtils.h | 2 +- server-console/src/main.js | 22 ++++++++++++++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 63738d2d91..a22130377a 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -199,7 +199,7 @@ int main(int argc, const char* argv[]) { bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); if (runServer) { - SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); + SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater); } Application app(argc, const_cast(argv), startupTime, runningMarkerExisted); diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp index d816f7ebee..c62bd8f982 100644 --- a/libraries/networking/src/SandboxUtils.cpp +++ b/libraries/networking/src/SandboxUtils.cpp @@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) { return false; } -void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { +void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) { QString serverPath = "./server-console/server-console.exe"; - qCDebug(networking) << "Running marker path is: " << runningMarkerName; qCDebug(networking) << "Server path is: " << serverPath; qCDebug(networking) << "autoShutdown: " << autoShutdown; qCDebug(networking) << "noUpdater: " << noUpdater; @@ -74,8 +73,9 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark } if (autoShutdown) { - QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); - args << "--shutdownWatcher" << interfaceRunningStateFile; + auto pid = qApp->applicationPid(); + qCDebug(networking) << "autoShutdown pid is" << pid; + args << "--watchProcessShutdown" << QString::number(pid); } if (noUpdater) { diff --git a/libraries/networking/src/SandboxUtils.h b/libraries/networking/src/SandboxUtils.h index 42484b8edf..370b28e1b0 100644 --- a/libraries/networking/src/SandboxUtils.h +++ b/libraries/networking/src/SandboxUtils.h @@ -21,7 +21,7 @@ namespace SandboxUtils { QNetworkReply* getStatus(); bool readStatus(QByteArray statusData); - void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); + void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater); }; #endif // hifi_SandboxUtils_h diff --git a/server-console/src/main.js b/server-console/src/main.js index 98bda9a10f..725c6ca0c8 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -821,6 +821,15 @@ for (var key in trayIcons) { const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); +function isProcessRunning(pid) { + try { + running = process.kill(pid, 0); + return true; + } catch (e) { + } + return false; +} + function onContentLoaded() { // Disable splash window for now. // maybeShowSplash(); @@ -882,6 +891,19 @@ function onContentLoaded() { startInterface(); } + if (argv.watchProcessShutdown) { + let pid = argv.watchProcessShutdown; + console.log("Watching process: ", pid); + let watchProcessInterval = setInterval(function() { + let isRunning = isProcessRunning(pid); + if (!isRunning) { + log.debug("Watched process is no longer running, shutting down"); + clearTimeout(watchProcessInterval); + forcedShutdown(); + } + }, 5000); + } + // If we were launched with the shutdownWatcher option, then we need to watch for the interface app // shutting down. The interface app will regularly update a running state file which we will check. // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. From 34630f839f05d773db1e4d8f6947e1ea08cad84c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 Jun 2017 12:48:42 -0700 Subject: [PATCH 47/66] Fix isProcessRunning error in strict mode --- server-console/src/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server-console/src/main.js b/server-console/src/main.js index 725c6ca0c8..55a45a4991 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -823,8 +823,7 @@ const notificationIcon = path.join(__dirname, '../resources/console-notification function isProcessRunning(pid) { try { - running = process.kill(pid, 0); - return true; + return process.kill(pid, 0); } catch (e) { } return false; From 37321814b6c2bc5164128a08a21a78bd028478d8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 Jun 2017 18:13:27 -0700 Subject: [PATCH 48/66] Rename watchProcessShutdown to shutdownWith --- libraries/networking/src/SandboxUtils.cpp | 3 +-- server-console/src/main.js | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp index c62bd8f982..f6c7634ce5 100644 --- a/libraries/networking/src/SandboxUtils.cpp +++ b/libraries/networking/src/SandboxUtils.cpp @@ -74,8 +74,7 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) { if (autoShutdown) { auto pid = qApp->applicationPid(); - qCDebug(networking) << "autoShutdown pid is" << pid; - args << "--watchProcessShutdown" << QString::number(pid); + args << "--shutdownWith" << QString::number(pid); } if (noUpdater) { diff --git a/server-console/src/main.js b/server-console/src/main.js index 55a45a4991..6667a570c8 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -890,18 +890,18 @@ function onContentLoaded() { startInterface(); } - if (argv.watchProcessShutdown) { - let pid = argv.watchProcessShutdown; - console.log("Watching process: ", pid); - let watchProcessInterval = setInterval(function() { + // If we were launched with the shutdownWith option, then we need to shutdown when that process (pid) + // is no longer running. + if (argv.shutdownWith) { + let pid = argv.shutdownWith; + console.log("Shutting down with process: ", pid); + let checkProcessInterval = setInterval(function() { let isRunning = isProcessRunning(pid); if (!isRunning) { log.debug("Watched process is no longer running, shutting down"); - clearTimeout(watchProcessInterval); + clearTimeout(checkProcessInterval); forcedShutdown(); } - }, 5000); - } // If we were launched with the shutdownWatcher option, then we need to watch for the interface app // shutting down. The interface app will regularly update a running state file which we will check. From ccd473ad0cfccefa4833b03f544f0a5f2b7faf81 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 Jun 2017 18:13:40 -0700 Subject: [PATCH 49/66] Remove shutdownWatcher option inside sandbox --- server-console/src/main.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/server-console/src/main.js b/server-console/src/main.js index 6667a570c8..46cc472e04 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -902,32 +902,6 @@ function onContentLoaded() { clearTimeout(checkProcessInterval); forcedShutdown(); } - - // If we were launched with the shutdownWatcher option, then we need to watch for the interface app - // shutting down. The interface app will regularly update a running state file which we will check. - // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. - if (argv.shutdownWatcher) { - log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); - var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates - var firstAttemptToCheck = new Date().getTime(); - var shutdownWatchInterval = setInterval(function(){ - var stats = fs.stat(argv.shutdownWatcher, function(err, stats) { - if (err) { - var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; - if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { - log.debug("Running state file is missing, assume interface has shutdown... shutting down snadbox."); - forcedShutdown(); - clearTimeout(shutdownWatchInterval); - } - } else { - var sinceEdit = new Date().getTime() - stats.mtime.getTime(); - if (sinceEdit > MAX_TIME_SINCE_EDIT) { - log.debug("Running state of interface hasn't updated in MAX time... shutting down."); - forcedShutdown(); - clearTimeout(shutdownWatchInterval); - } - } - }); }, 1000); } } From 3888385a7294556c617283baa839dd2e99474b0f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 Jun 2017 18:19:13 -0700 Subject: [PATCH 50/66] Remove the runningMarker timer --- interface/src/main.cpp | 5 +--- libraries/shared/src/RunningMarker.cpp | 35 +------------------------- libraries/shared/src/RunningMarker.h | 12 +-------- 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index a22130377a..83cac6d9bb 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -190,7 +190,7 @@ int main(int argc, const char* argv[]) { int exitCode; { - RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); + RunningMarker runningMarker(RUNNING_MARKER_FILENAME); bool runningMarkerExisted = runningMarker.fileExists(); runningMarker.writeRunningMarkerFile(); @@ -204,9 +204,6 @@ int main(int argc, const char* argv[]) { Application app(argc, const_cast(argv), startupTime, runningMarkerExisted); - // Now that the main event loop is setup, launch running marker thread - runningMarker.startRunningMarker(); - // If we failed the OpenGLVersion check, log it. if (override) { auto accountManager = DependencyManager::get(); diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp index 0c1fd06df8..cb7b39320c 100644 --- a/libraries/shared/src/RunningMarker.cpp +++ b/libraries/shared/src/RunningMarker.cpp @@ -13,44 +13,16 @@ #include #include -#include -#include -#include "NumericalConstants.h" #include "PathUtils.h" -RunningMarker::RunningMarker(QObject* parent, QString name) : - _parent(parent), +RunningMarker::RunningMarker(QString name) : _name(name) { } -void RunningMarker::startRunningMarker() { - static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND; - - // start the nodeThread so its event loop is running - _runningMarkerThread = new QThread(_parent); - _runningMarkerThread->setObjectName("Running Marker Thread"); - _runningMarkerThread->start(); - - writeRunningMarkerFile(); // write the first file, even before timer - - _runningMarkerTimer = new QTimer(); - QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){ - writeRunningMarkerFile(); - }); - _runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); - - // put the time on the thread - _runningMarkerTimer->moveToThread(_runningMarkerThread); -} - RunningMarker::~RunningMarker() { deleteRunningMarkerFile(); - QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection); - _runningMarkerThread->quit(); - _runningMarkerTimer->deleteLater(); - _runningMarkerThread->deleteLater(); } bool RunningMarker::fileExists() const { @@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() { QString RunningMarker::getFilePath() const { return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; } - -QString RunningMarker::getMarkerFilePath(QString name) { - return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name; -} - diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h index f9c8e72d37..ae7c550660 100644 --- a/libraries/shared/src/RunningMarker.h +++ b/libraries/shared/src/RunningMarker.h @@ -12,21 +12,14 @@ #ifndef hifi_RunningMarker_h #define hifi_RunningMarker_h -#include #include -class QThread; -class QTimer; - class RunningMarker { public: - RunningMarker(QObject* parent, QString name); + RunningMarker(QString name); ~RunningMarker(); - void startRunningMarker(); - QString getFilePath() const; - static QString getMarkerFilePath(QString name); bool fileExists() const; @@ -34,10 +27,7 @@ public: void deleteRunningMarkerFile(); private: - QObject* _parent { nullptr }; QString _name; - QThread* _runningMarkerThread { nullptr }; - QTimer* _runningMarkerTimer { nullptr }; }; #endif // hifi_RunningMarker_h From f10f717ea205917873b721a4caa23c1758b340a8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 2 Jun 2017 08:58:35 -0700 Subject: [PATCH 51/66] Add clarifying comment to use of kill in isProcessRunning --- server-console/src/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server-console/src/main.js b/server-console/src/main.js index 46cc472e04..408a17bd56 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -823,6 +823,9 @@ const notificationIcon = path.join(__dirname, '../resources/console-notification function isProcessRunning(pid) { try { + // Sending a signal of 0 is effectively a NOOP. + // If sending the signal is successful, kill will return true. + // If the process is not running, an exception will be thrown. return process.kill(pid, 0); } catch (e) { } From 2146f96091a4b0462eb61d0ee3e33a47b72ee61d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 2 Jun 2017 13:51:39 -0700 Subject: [PATCH 52/66] Replace static use of qApp with QCoreApplication --- libraries/networking/src/SandboxUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp index f6c7634ce5..4a348b0662 100644 --- a/libraries/networking/src/SandboxUtils.cpp +++ b/libraries/networking/src/SandboxUtils.cpp @@ -73,7 +73,7 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) { } if (autoShutdown) { - auto pid = qApp->applicationPid(); + auto pid = QCoreApplication::applicationPid(); args << "--shutdownWith" << QString::number(pid); } From 9cf027a68c3facf258f6fea15123f7675aae312e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 7 Jun 2017 16:44:13 -0700 Subject: [PATCH 53/66] pr feedback --- interface/src/avatar/MyAvatar.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 811c0ae55e..bc621543e3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2261,7 +2261,6 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { if (!requiresSafeLanding(position, better)) { return false; } - qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); if (!getCollisionsEnabled()) { goToLocation(better); // recurses on next update } else { // If you try to go while stuck, physics will keep you stuck. @@ -2334,18 +2333,19 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette } // See if we have room between entities above and below, but that we are not contained. + // First check if the surface above us is the bottom of something, and the surface below us it the top of something. + // I.e., we are in a clearing between two objects. if (isDown(upperNormal) && isUp(lowerNormal)) { - // The surface above us is the bottom of something, and the surface below us it the top of something. - // I.e., we are in a clearing between two objects. - auto delta = glm::distance(upperIntersection, lowerIntersection); + auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. - if (delta > (halfHeightFactor * halfHeight)) { + if (spaceBetween > (halfHeightFactor * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. // There will be one of two outcomes: // a) We're not contained, so we have enough room and our position is good. // b) We are contained, so we'll bail out of this but try again at a position above the containing entity. - for (;;) { + const int iterationLimit = 1000; + for (int counter = 0; counter < iterationLimit; counter++) { ignore.push_back(upperId); if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! @@ -2360,6 +2360,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // We found a new bottom surface, which we're not interested in. // But there could still be a top surface above us for an entity we haven't seen, so keep looking upward. } + qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense."; } } From 0c7ffa0ac6416ac040590ed61940416a2e1b640c Mon Sep 17 00:00:00 2001 From: seefo Date: Thu, 8 Jun 2017 09:47:01 -0700 Subject: [PATCH 54/66] Replaced baker in OvenCLI with an std::unique_ptr --- tools/oven/src/BakerCLI.cpp | 17 ++++++----------- tools/oven/src/BakerCLI.h | 5 ++++- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index b76e00269f..7bdd221514 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -40,30 +40,25 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { } // create our appropiate baker - Baker* baker; - if (isFBX) { - baker = new FBXBaker(inputUrl, outputPath, []() -> QThread* { - return qApp->getNextWorkerThread(); - }); - baker->moveToThread(qApp->getFBXBakerThread()); + _baker = std::unique_ptr { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) }; + _baker->moveToThread(qApp->getFBXBakerThread()); } else if (isSupportedImage) { - baker = new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath); - baker->moveToThread(qApp->getNextWorkerThread()); + _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; + _baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; return; } // invoke the bake method on the baker thread - QMetaObject::invokeMethod(baker, "bake"); + QMetaObject::invokeMethod(_baker.get(), "bake"); // make sure we hear about the results of this baker when it is done - connect(baker, &Baker::finished, this, &BakerCLI::handleFinishedBaker); + connect(_baker.get(), &Baker::finished, this, &BakerCLI::handleFinishedBaker); } void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; - sender()->deleteLater(); QApplication::quit(); } \ No newline at end of file diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 5935151bb5..cb2b908059 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -14,6 +14,7 @@ #include +#include "Baker.h" #include "Oven.h" class BakerCLI : public QObject { @@ -24,8 +25,10 @@ public: void bakeFile(QUrl inputUrl, const QString outputPath); private slots: - void handleFinishedBaker(); + void handleFinishedBaker(); +private: + std::unique_ptr _baker; }; #endif // hifi_BakerCLI_h \ No newline at end of file From 3b471425926daf1a7bd8cc201fe89b882aaa107b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Jun 2017 10:28:38 -0700 Subject: [PATCH 55/66] fix bug that causes hmd avatar eyes to be all the way to the side when there's not another avatar to look at --- interface/src/Application.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ea5a9c0a89..44b0a36f7a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4163,7 +4163,7 @@ void Application::updateMyAvatarLookAtPosition() { lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + - (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, TREE_SCALE)); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bc621543e3..dea2404cee 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2664,8 +2664,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - _desiredBodyMatrix = desiredBodyMatrix; +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, bool hasDriveInput) { if (myAvatar.getHMDLeanRecenterEnabled()) { if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { @@ -2679,7 +2679,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } - glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; + glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix; glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; AnimPose followWorldPose(currentWorldMatrix); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3e2581382d..fb11705a9c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -702,7 +702,6 @@ private: Vertical, NumFollowTypes }; - glm::mat4 _desiredBodyMatrix; float _timeRemaining[NumFollowTypes]; void deactivate(); From 110a2f5d037058d55db92ec311ff3f74da8b47dc Mon Sep 17 00:00:00 2001 From: seefo Date: Thu, 8 Jun 2017 10:35:50 -0700 Subject: [PATCH 56/66] Resolved FB#4399 --- domain-server/resources/web/content/index.shtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index c7eb765878..e1ba5499b6 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -21,7 +21,7 @@

If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:
-

C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz
+
C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz

From a4f4d49bec82425be82abca9b44e17528e0d445d Mon Sep 17 00:00:00 2001 From: seefo Date: Thu, 8 Jun 2017 11:50:23 -0700 Subject: [PATCH 57/66] Oven will now give proper return codes when used via CLI --- tools/oven/src/BakerCLI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 7bdd221514..14eb9de150 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -48,7 +48,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { _baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; - return; + QApplication::exit(1); } // invoke the bake method on the baker thread @@ -60,5 +60,5 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; - QApplication::quit(); + QApplication::exit(_baker.get()->hasErrors()); } \ No newline at end of file From fce3badd1d985eddc0e5fa1ee2bee19169eff8ee Mon Sep 17 00:00:00 2001 From: seefo Date: Thu, 8 Jun 2017 10:48:38 -0700 Subject: [PATCH 58/66] Resolved FB#5005 --- libraries/networking/src/UserActivityLogger.cpp | 10 ++++++++++ libraries/networking/src/UserActivityLogger.h | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 28117c0933..51445dce2a 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -20,6 +20,10 @@ #include #include "AddressManager.h" +UserActivityLogger::UserActivityLogger() { + _timer.start(); +} + UserActivityLogger& UserActivityLogger::getInstance() { static UserActivityLogger sharedInstance; return sharedInstance; @@ -42,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); actionPart.setBody(QByteArray().append(action)); multipart->append(actionPart); + + // Log the local-time that this event was logged + QHttpPart elapsedPart; + elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\""); + elapsedPart.setBody(QByteArray().append(_timer.elapsed())); + multipart->append(elapsedPart); // If there are action details, add them to the multipart if (!details.isEmpty()) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 9fad498b86..179e8e6e66 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "AddressManager.h" @@ -51,8 +52,10 @@ private slots: void requestError(QNetworkReply& errorReply); private: - UserActivityLogger() {}; + UserActivityLogger(); Setting::Handle _disabled { "UserActivityLoggerDisabled", false }; + + QElapsedTimer _timer; }; #endif // hifi_UserActivityLogger_h From 0e27ad767ce36c88d12d1c94523ffbd3459f9a51 Mon Sep 17 00:00:00 2001 From: Liv Date: Thu, 8 Jun 2017 13:07:01 -0700 Subject: [PATCH 59/66] Move chat app to be included as part of the default scripts --- scripts/defaultScripts.js | 3 ++- .../marketplace/chat/Chat.js => scripts/system/chat.js | 2 +- .../marketplace/chat => scripts/system/html}/ChatPage.html | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename unpublishedScripts/marketplace/chat/Chat.js => scripts/system/chat.js (99%) rename {unpublishedScripts/marketplace/chat => scripts/system/html}/ChatPage.html (100%) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 81ce72d901..fa6300270c 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -29,7 +29,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/tablet-ui/tabletUI.js" + "system/tablet-ui/tabletUI.js", + "system/chat.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js" diff --git a/unpublishedScripts/marketplace/chat/Chat.js b/scripts/system/chat.js similarity index 99% rename from unpublishedScripts/marketplace/chat/Chat.js rename to scripts/system/chat.js index 33bfcfeb4d..be6600a874 100644 --- a/unpublishedScripts/marketplace/chat/Chat.js +++ b/scripts/system/chat.js @@ -9,7 +9,7 @@ (function() { - var webPageURL = "ChatPage.html"; // URL of tablet web page. + var webPageURL = Script.resolvePath("html/ChatPage.html");; // URL of tablet web page. var randomizeWebPageURL = true; // Set to true for debugging. var lastWebPageURL = ""; // Last random URL of tablet web page. var onChatPage = false; // True when chat web page is opened. diff --git a/unpublishedScripts/marketplace/chat/ChatPage.html b/scripts/system/html/ChatPage.html similarity index 100% rename from unpublishedScripts/marketplace/chat/ChatPage.html rename to scripts/system/html/ChatPage.html From 03edb06ecb9868da944156a5fb486312e171f135 Mon Sep 17 00:00:00 2001 From: Liv Date: Thu, 8 Jun 2017 13:46:33 -0700 Subject: [PATCH 60/66] change design to run chat separately so it can be removed if users prefer to not use it --- scripts/defaultScripts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index fa6300270c..aef8d9d85b 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -29,11 +29,11 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/tablet-ui/tabletUI.js", - "system/chat.js" + "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js", + "system/chat.js" ]; // add a menu item for debugging From ce4c3e1601278f2ac5159ea549173c12604f815f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Jun 2017 13:55:30 -0700 Subject: [PATCH 61/66] fix bug that causes hmd avatar eyes to be all the way to the side when there's not another avatar to look at --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 44b0a36f7a..eec82d0537 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4160,7 +4160,7 @@ void Application::updateMyAvatarLookAtPosition() { if (isHMD) { glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHeadControllerPoseInSensorFrame().getMatrix(); - lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, TREE_SCALE)); From 8334dff610f1c206a1ef4c1f9b731a272bf03786 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 8 Jun 2017 15:00:12 -0700 Subject: [PATCH 62/66] compute rotation from derivative of spline This should fix bad rotation values for the spine during bowing/touching toes. --- .../resources/avatar/avatar-animation.json | 6 +- .../animation/src/AnimInverseKinematics.cpp | 159 +++++++++++------- .../animation/src/AnimInverseKinematics.h | 16 +- 3 files changed, 106 insertions(+), 75 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 35f2d4b9af..1412b45968 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -103,8 +103,8 @@ "rotationVar": "spine2Rotation", "typeVar": "spine2Type", "weightVar": "spine2Weight", - "weight": 1.0, - "flexCoefficients": [1.0, 0.5, 0.5] + "weight": 2.0, + "flexCoefficients": [1.0, 0.5, 0.25] }, { "jointName": "Head", @@ -113,7 +113,7 @@ "typeVar": "headType", "weightVar": "headWeight", "weight": 4.0, - "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25] + "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] }, { "jointName": "LeftArm", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index c155f4733f..ed61058f6c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -21,6 +21,7 @@ #include "SwingTwistConstraint.h" #include "AnimationLogging.h" #include "CubicHermiteSpline.h" +#include "AnimUtil.h" AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : @@ -475,16 +476,85 @@ static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1); } +// pre-compute information about each joint influeced by this spline IK target. +void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { + std::vector splineJointInfoVec; + + // build spline between the default poses. + AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); + AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); + + CubicHermiteSplineFunctorWithArcLength spline; + if (target.getIndex() == _headIndex) { + // set gain factors so that more curvature occurs near the tip of the spline. + const float HIPS_GAIN = 0.5f; + const float HEAD_GAIN = 1.0f; + spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); + } else { + spline = computeSplineFromTipAndBase(tipPose, basePose); + } + + // measure the total arc length along the spline + float totalArcLength = spline.arcLength(1.0f); + + glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); + float baseToTipLength = glm::length(baseToTip); + glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; + + int index = target.getIndex(); + int endIndex = _skeleton->getParentIndex(_hipsIndex); + while (index != endIndex) { + AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); + + float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; + + // compute offset from spline to the default pose. + float t = spline.arcLengthInverse(ratio * totalArcLength); + + // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose offsetPose = pose.inverse() * defaultPose; + + SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; + splineJointInfoVec.push_back(splineJointInfo); + index = _skeleton->getParentIndex(index); + } + + _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; +} + +const std::vector* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) { + // find or create splineJointInfo for this target + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } else { + computeSplineJointInfosForIKTarget(context, target); + auto iter = _splineJointInfoMap.find(target.getIndex()); + if (iter != _splineJointInfoMap.end()) { + return &(iter->second); + } + } + + return nullptr; +} + void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) { std::map debugJointMap; const int baseIndex = _hipsIndex; - // build spline + // build spline from tip to base AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[baseIndex]; - CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { // set gain factors so that more curvature occurs near the tip of the spline. @@ -496,19 +566,6 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } float totalArcLength = spline.arcLength(1.0f); - // find or create splineJointInfo for the head target - const std::vector* splineJointInfoVec = nullptr; - auto iter = _splineJointInfoMap.find(target.getIndex()); - if (iter != _splineJointInfoMap.end()) { - splineJointInfoVec = &(iter->second); - } else { - computeSplineJointInfosForIKTarget(context, target); - auto iter = _splineJointInfoMap.find(target.getIndex()); - if (iter != _splineJointInfoMap.end()) { - splineJointInfoVec = &(iter->second); - } - } - // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way) // when the head is arched backwards very far. glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f)); @@ -516,6 +573,9 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co tipPose.rot() = -tipPose.rot(); } + // find or create splineJointInfo for this target + const std::vector* splineJointInfoVec = findOrCreateSplineJointInfo(context, target); + if (splineJointInfoVec && splineJointInfoVec->size() > 0) { const int baseParentIndex = _skeleton->getParentIndex(baseIndex); AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose(); @@ -526,14 +586,28 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength); glm::vec3 trans = spline(t); - // for head splines, preform most rotation toward the tip by using ease in function. t^2 + // for head splines, preform most twist toward the tip by using ease in function. t^2 float rotT = t; if (target.getIndex() == _headIndex) { rotT = t * t; } - glm::quat rot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT)); - AnimPose absPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; - AnimPose relPose = parentAbsPose.inverse() * absPose; + glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT)); + + // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis + glm::vec3 y = glm::normalize(spline.d(t)); + glm::vec3 x = twistRot * Vectors::UNIT_X; + glm::vec3 u, v, w; + generateBasisVectors(y, x, v, u, w); + glm::mat3 m(u, v, glm::cross(u, v)); + glm::quat rot = glm::normalize(glm::quat_cast(m)); + + AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + + // apply flex coefficent + AnimPose flexedAbsPose; + ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose); + + AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose; _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight()); bool constrained = false; @@ -564,7 +638,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained); } - parentAbsPose = absPose; + parentAbsPose = flexedAbsPose; } } @@ -1450,51 +1524,6 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s } } -// pre-compute information about each joint influeced by this spline IK target. -void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) { - std::vector splineJointInfoVec; - - // build spline between the default poses. - AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex()); - AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex); - - CubicHermiteSplineFunctorWithArcLength spline; - if (target.getIndex() == _headIndex) { - // set gain factors so that more curvature occurs near the tip of the spline. - const float HIPS_GAIN = 0.5f; - const float HEAD_GAIN = 1.0f; - spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN); - } else { - spline = computeSplineFromTipAndBase(tipPose, basePose); - } - - // measure the total arc length along the spline - float totalArcLength = spline.arcLength(1.0f); - - glm::vec3 baseToTip = tipPose.trans() - basePose.trans(); - float baseToTipLength = glm::length(baseToTip); - glm::vec3 baseToTipNormal = baseToTip / baseToTipLength; - - int index = target.getIndex(); - int endIndex = _skeleton->getParentIndex(_hipsIndex); - while (index != endIndex) { - AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index); - - float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength; - - // compute offset from spline to the default pose. - float t = spline.arcLengthInverse(ratio * totalArcLength); - AnimPose pose(glm::vec3(1.0f), glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), t)), spline(t)); - AnimPose offsetPose = pose.inverse() * defaultPose; - - SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; - splineJointInfoVec.push_back(splineJointInfo); - index = _skeleton->getParentIndex(index); - } - - _splineJointInfoMap[target.getIndex()] = splineJointInfoVec; -} - void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const { for (auto& target : targets) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index ff1ab9115f..352238d1e4 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -74,10 +74,18 @@ protected: void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const; void debugDrawSpineSplines(const AnimContext& context, const std::vector& targets) const; - void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); + // used to pre-compute information about each joint influeced by a spline IK target. + struct SplineJointInfo { + int jointIndex; // joint in the skeleton that this information pertains to. + float ratio; // percentage (0..1) along the spline for this joint. + AnimPose offsetPose; // local offset from the spline to the joint. + }; + + void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target); + const std::vector* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } @@ -117,12 +125,6 @@ protected: AnimPoseVec _relativePoses; // current relative poses AnimPoseVec _limitCenterPoses; // relative - // used to pre-compute information about each joint influeced by a spline IK target. - struct SplineJointInfo { - int jointIndex; // joint in the skeleton that this information pertains to. - float ratio; // percentage (0..1) along the spline for this joint. - AnimPose offsetPose; // local offset from the spline to the joint. - }; std::map> _splineJointInfoMap; // experimental data for moving hips during IK From 6657c4d997302e100edf15262697630eaff7a28f Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 8 Jun 2017 17:22:18 -0700 Subject: [PATCH 63/66] Update chat.js remove stray semicolon --- scripts/system/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/chat.js b/scripts/system/chat.js index be6600a874..d03c6aae98 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -9,7 +9,7 @@ (function() { - var webPageURL = Script.resolvePath("html/ChatPage.html");; // URL of tablet web page. + var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. var randomizeWebPageURL = true; // Set to true for debugging. var lastWebPageURL = ""; // Last random URL of tablet web page. var onChatPage = false; // True when chat web page is opened. From dc02e3478298e9b92b55a4915881fd512f0daeef Mon Sep 17 00:00:00 2001 From: seefo Date: Thu, 8 Jun 2017 16:22:52 -0700 Subject: [PATCH 64/66] Resolved FB#5009 --- interface/src/AvatarBookmarks.cpp | 24 ++++++++++++++++++++++-- interface/src/AvatarBookmarks.h | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 5cdfc8213f..db2a240b92 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -21,15 +21,35 @@ #include "MainWindow.h" #include "Menu.h" - #include "AvatarBookmarks.h" +#include "InterfaceLogging.h" + #include AvatarBookmarks::AvatarBookmarks() { - _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; + _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME; readFromFile(); } +void AvatarBookmarks::readFromFile() { + // migrate old avatarbookmarks.json, used to be in 'local' folder on windows + QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; + QFile oldConfig(oldConfigPath); + + // I imagine that in a year from now, this code for migrating (as well as the two lines above) + // may be removed since all bookmarks should have been migrated by then + // - Robbie Uvanni (6.8.2017) + if (oldConfig.exists()) { + if (QDir().rename(oldConfigPath, _bookmarksFilename)) { + qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME; + } else { + qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME; + } + } + + Bookmarks::readFromFile(); +} + void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { // Add menus/actions auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 725af88b0d..dc5a0aee6e 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -29,6 +29,7 @@ public slots: protected: void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; + void readFromFile(); private: const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; From 5b62c9e83b1261146bf3edc292c36ab41c61a358 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 9 Jun 2017 10:45:04 -0700 Subject: [PATCH 65/66] try again to get forward not-looking-at-anyone gaze to work right --- interface/src/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4821b98e3f..890b5cb455 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4126,13 +4126,13 @@ void Application::updateMyAvatarLookAtPosition() { } } else { // I am not looking at anyone else, so just look forward - if (isHMD) { - glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * - myAvatar->getHeadControllerPoseInSensorFrame().getMatrix(); + auto headPose = myAvatar->getHeadControllerPoseInSensorFrame(); + if (headPose.isValid()) { + glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix(); lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + - (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, TREE_SCALE)); + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } } From 06a5d0970c5916d7bcc78cc35a6e46cfed00f7c7 Mon Sep 17 00:00:00 2001 From: seefo Date: Fri, 9 Jun 2017 10:58:07 -0700 Subject: [PATCH 66/66] Fixed issue with invalid time being sent when logging --- libraries/networking/src/UserActivityLogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 51445dce2a..0cfd1e09e7 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -50,7 +50,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall // Log the local-time that this event was logged QHttpPart elapsedPart; elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\""); - elapsedPart.setBody(QByteArray().append(_timer.elapsed())); + elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit()); multipart->append(elapsedPart); // If there are action details, add them to the multipart