From 4e70e8ed42084e480e8be6cd95220d479a55560a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 09:55:49 -0700 Subject: [PATCH 01/29] js call to ray-pick against avatars --- interface/src/avatar/Avatar.cpp | 9 ++++ interface/src/avatar/Avatar.h | 1 + interface/src/avatar/AvatarManager.cpp | 48 ++++++++++++++++++++ interface/src/avatar/AvatarManager.h | 4 ++ libraries/avatars/src/AvatarData.cpp | 23 ++++++++++ libraries/avatars/src/AvatarData.h | 15 ++++++ libraries/script-engine/src/ScriptEngine.cpp | 1 + 7 files changed, 101 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 39bb7eac17..8fd330db19 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1085,6 +1085,15 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset()); } +void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight + start = getPosition() - glm::vec3(0, halfExtents.y, 0); + end = getPosition() + glm::vec3(0, halfExtents.y, 0); + radius = halfExtents.x; +} + void Avatar::setMotionState(AvatarMotionState* motionState) { _motionState = motionState; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 064f0a9533..b9f44613c7 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -154,6 +154,7 @@ public: virtual void rebuildCollisionShape(); virtual void computeShapeInfo(ShapeInfo& shapeInfo); + void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); AvatarMotionState* getMotionState() { return _motionState; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 68d2eca2c0..f08a75ffda 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -398,3 +398,51 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) return findAvatar(sessionID); } + +RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, + const QScriptValue& avatarIdsToInclude, + const QScriptValue& avatarIdsToDiscard) { + if (QThread::currentThread() != thread()) { + RayToAvatarIntersectionResult result; + QMetaObject::invokeMethod(const_cast(this), "findRayIntersection", Qt::BlockingQueuedConnection, + Q_ARG(const QScriptValue&, avatarIdsToInclude), + Q_ARG(const QScriptValue&, avatarIdsToDiscard), + Q_RETURN_ARG(RayToAvatarIntersectionResult, result)); + return result; + } + + RayToAvatarIntersectionResult result; + + QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); + QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); + + glm::vec3 normDirection = glm::normalize(ray.direction); + + for (auto avatarData : _avatarHash) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float distance; + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + + bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance); + if (intersects && (!result.intersects || distance < result.distance)) { + result.intersects = true; + result.avatarID = avatar->getID(); + result.distance = distance; + } + } + + if (result.intersects) { + result.intersection = ray.origin + normDirection * result.distance; + } + + return result; +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9be186301d..c49d566aa8 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -70,6 +70,10 @@ public: void addAvatarToSimulation(Avatar* avatar); + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, + const QScriptValue& avatarIdsToInclude = QScriptValue(), + const QScriptValue& avatarIdsToDiscard = QScriptValue()); + public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 709cc76d01..e73702cd95 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "AvatarLogging.h" @@ -1681,3 +1682,25 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { }); return result; } + +QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); + obj.setProperty("avatarID", avatarIDValue); + obj.setProperty("distance", value.distance); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); + obj.setProperty("intersection", intersection); + return obj; +} + +void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + QScriptValue avatarIDValue = object.property("avatarID"); + quuidFromScriptValue(avatarIDValue, value.avatarID); + value.distance = object.property("distance").toVariant().toFloat(); + QScriptValue intersection = object.property("intersection"); + if (intersection.isValid()) { + vec3FromScriptValue(intersection, value.intersection); + } +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2dd1079b49..14b4f07471 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -495,4 +495,19 @@ public: void registerAvatarTypes(QScriptEngine* engine); +class RayToAvatarIntersectionResult { +public: +RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} + bool intersects; + QUuid avatarID; + float distance; + glm::vec3 intersection; +}; + +Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) + +QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); +void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); + + #endif // hifi_AvatarData_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a5e3be8a43..9642aaf588 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -465,6 +465,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); + qScriptRegisterMetaType(this, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue); qScriptRegisterSequenceMetaType>(this); qScriptRegisterSequenceMetaType>(this); From 8a0d58a0c2410f0fc43f69b4842591ae7143bc87 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 11:15:01 -0700 Subject: [PATCH 02/29] fix invokeMethod in AvatarManager::findRayIntersection --- interface/src/avatar/AvatarManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index f08a75ffda..c5a9efeecc 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -405,6 +405,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& if (QThread::currentThread() != thread()) { RayToAvatarIntersectionResult result; QMetaObject::invokeMethod(const_cast(this), "findRayIntersection", Qt::BlockingQueuedConnection, + Q_ARG(const PickRay&, ray), Q_ARG(const QScriptValue&, avatarIdsToInclude), Q_ARG(const QScriptValue&, avatarIdsToDiscard), Q_RETURN_ARG(RayToAvatarIntersectionResult, result)); From 0f9f4749e7b849e85246639245e8271679dd7de5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 11:18:46 -0700 Subject: [PATCH 03/29] fix invokeMethod in AvatarManager::findRayIntersection --- interface/src/avatar/AvatarManager.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c5a9efeecc..0eba27ee78 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -402,18 +402,16 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude, const QScriptValue& avatarIdsToDiscard) { + RayToAvatarIntersectionResult result; if (QThread::currentThread() != thread()) { - RayToAvatarIntersectionResult result; QMetaObject::invokeMethod(const_cast(this), "findRayIntersection", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(RayToAvatarIntersectionResult, result), Q_ARG(const PickRay&, ray), Q_ARG(const QScriptValue&, avatarIdsToInclude), - Q_ARG(const QScriptValue&, avatarIdsToDiscard), - Q_RETURN_ARG(RayToAvatarIntersectionResult, result)); + Q_ARG(const QScriptValue&, avatarIdsToDiscard)); return result; } - RayToAvatarIntersectionResult result; - QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); From 630d5cfc825eef8026dd94145ae51623f1a6579e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 15:47:17 -0700 Subject: [PATCH 04/29] make trigger more sensitive, print out position and rotation when releasing something --- 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 f86ff158f6..2164340314 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -27,7 +27,7 @@ var WANT_DEBUG_SEARCH_NAME = null; var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab +var TRIGGER_GRAB_VALUE = 0.50; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; @@ -1994,6 +1994,12 @@ function MyController(hand) { joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); + + grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + print("adjusted position: " + vec3toStr(grabbedProperties.localPosition)); + print("adjusted rotation: " + quatToStr(grabbedProperties.localRotation)); + + this.grabbedEntity = null; if (this.triggerSmoothedGrab()) { From b782ae667cc34c1495e28a091a6de775728aa6a6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 16:36:37 -0700 Subject: [PATCH 05/29] when one hand adjusts something equippd in the other, print out the new local position and rotation in case a content-creator wants to update userData --- .../system/controllers/handControllerGrab.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2164340314..8857d6c70c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1970,10 +1970,24 @@ function MyController(hand) { var noVelocity = false; if (this.grabbedEntity !== null) { + + // If this looks like the release after adjusting something still held in the other hand, print the position + // and rotation of the held thing to help content creators set the userData. + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); + if (grabData.refCount > 1) { + grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { + print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + + ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + + ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + + ', "w":' + grabbedProperties.localRotation.w + '}]'); + } + } + if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); // sometimes we want things to stay right where they are when we let go. - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (releaseVelocityData.disableReleaseVelocity === true || // this next line allowed both: @@ -1994,12 +2008,6 @@ function MyController(hand) { joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); - - grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - print("adjusted position: " + vec3toStr(grabbedProperties.localPosition)); - print("adjusted rotation: " + quatToStr(grabbedProperties.localRotation)); - - this.grabbedEntity = null; if (this.triggerSmoothedGrab()) { From 52d017ac743a6f3eeba49e6d8c9c904a19814494 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 28 Jun 2016 19:22:19 -0700 Subject: [PATCH 06/29] undo previous change --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8857d6c70c..c92fb88f54 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -27,7 +27,7 @@ var WANT_DEBUG_SEARCH_NAME = null; var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.50; // Squeezed far enough to complete distant grab +var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; From 03ed36cf3303b41c63d94955b2532da28bb477f4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 29 Jun 2016 09:13:52 -0700 Subject: [PATCH 07/29] take shape offset into account when getting avatar's capsule --- interface/src/avatar/Avatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 8fd330db19..4d9481f002 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1089,8 +1089,8 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight - start = getPosition() - glm::vec3(0, halfExtents.y, 0); - end = getPosition() + glm::vec3(0, halfExtents.y, 0); + start = getPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + end = getPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); radius = halfExtents.x; } From c3dbe5d9c43e23a84392442de5a036a4c0030b8a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 29 Jun 2016 09:45:09 -0700 Subject: [PATCH 08/29] ray pick against avatar meshes rather than capsules --- interface/src/avatar/AvatarManager.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 0eba27ee78..e1fe03ae2e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -425,13 +425,20 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& } float distance; + BoxFace face; + glm::vec3 surfaceNormal; - glm::vec3 start; - glm::vec3 end; - float radius; - avatar->getCapsule(start, end, radius); + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + AABox avatarBounds = avatarModel->getRenderableMeshBound(); + if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) { + // ray doesn't intersect avatar's bounding-box + continue; + } + + QString extraInfo; + bool intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, + distance, face, surfaceNormal, extraInfo, true); - bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance); if (intersects && (!result.intersects || distance < result.distance)) { result.intersects = true; result.avatarID = avatar->getID(); From 9114ebb548aa2384fb16584c197704a272483511 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 29 Jun 2016 09:53:26 -0700 Subject: [PATCH 09/29] remove unused code --- interface/src/avatar/Avatar.cpp | 9 --------- interface/src/avatar/Avatar.h | 1 - 2 files changed, 10 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 4d9481f002..39bb7eac17 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1085,15 +1085,6 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset()); } -void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { - ShapeInfo shapeInfo; - computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight - start = getPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); - end = getPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); - radius = halfExtents.x; -} - void Avatar::setMotionState(AvatarMotionState* motionState) { _motionState = motionState; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index b9f44613c7..064f0a9533 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -154,7 +154,6 @@ public: virtual void rebuildCollisionShape(); virtual void computeShapeInfo(ShapeInfo& shapeInfo); - void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); AvatarMotionState* getMotionState() { return _motionState; } From d53c7ae5d8021ffc11145fceb684539f2f789257 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 29 Jun 2016 12:39:04 -0700 Subject: [PATCH 10/29] experimenting --- libraries/render-utils/src/Model.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0470a238fc..f947474aac 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -456,6 +456,15 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { if (pickAgainstTriangles) { QVector thisMeshTriangles; + + glm::mat4 meshTransform; + if (mesh.clusters.size() > 0) { + int jointIndex = mesh.clusters[0].jointIndex; + meshTransform = _rig->getJointTransform(jointIndex); + } else { + meshTransform = mesh.modelTransform; + } + for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); @@ -474,10 +483,10 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i2 = part.quadIndices[vIndex++]; int i3 = part.quadIndices[vIndex++]; - glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); - glm::vec3 mv3 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + glm::vec3 mv0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 mv1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 mv2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 mv3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { @@ -517,9 +526,9 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; - glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 mv0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 mv1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 mv2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); // track the mesh parts in model space if (!atLeastOnePointInBounds) { From 5f9c7b6ea5cb5df7392777a9976a53b0e9e2480a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 29 Jun 2016 17:02:49 -0700 Subject: [PATCH 11/29] trying to take joint information into account when raypicking against models --- interface/src/avatar/AvatarManager.cpp | 3 + libraries/render-utils/src/Model.cpp | 161 +++++++++++++++++++------ libraries/render-utils/src/Model.h | 5 + 3 files changed, 135 insertions(+), 34 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e1fe03ae2e..a5a02bdaff 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -435,6 +435,9 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& continue; } + avatarModel->invalidCalculatedMeshBoxes(); + avatarModel->recalculateMeshBoxes(true); + QString extraInfo; bool intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, distance, face, surfaceNormal, extraInfo, true); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index f947474aac..4969585af4 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" @@ -441,32 +442,31 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { PROFILE_RANGE(__FUNCTION__); bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; - if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { + if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || + (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _calculatedMeshBoxes.resize(numberOfMeshes); _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); - for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& mesh = geometry.meshes.at(i); + + + + int okCount = 0; + int notOkCount = 0; + + + for (int meshIndex = 0; meshIndex < numberOfMeshes; meshIndex++) { + const FBXMesh& mesh = geometry.meshes.at(meshIndex); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents, _translation, _rotation); - _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); + _calculatedMeshBoxes[meshIndex] = AABox(scaledMeshExtents); if (pickAgainstTriangles) { QVector thisMeshTriangles; - - glm::mat4 meshTransform; - if (mesh.clusters.size() > 0) { - int jointIndex = mesh.clusters[0].jointIndex; - meshTransform = _rig->getJointTransform(jointIndex); - } else { - meshTransform = mesh.modelTransform; - } - - for (int j = 0; j < mesh.parts.size(); j++) { - const FBXMeshPart& part = mesh.parts.at(j); + for (int partIndex = 0; partIndex < mesh.parts.size(); partIndex++) { + const FBXMeshPart& part = mesh.parts.at(partIndex); bool atLeastOnePointInBounds = false; AABox thisPartBounds; @@ -483,10 +483,62 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i2 = part.quadIndices[vIndex++]; int i3 = part.quadIndices[vIndex++]; - glm::vec3 mv0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 mv1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 mv2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); - glm::vec3 mv3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + glm::vec3 v[3]; + int ok = 0; + if (meshIndex < _meshStates.size()) { + int quadPointIndexes[ 4 ] = {i0, i1, i2, i3}; + for (int pointInQuadIndex = 0; pointInQuadIndex < 4; pointInQuadIndex++) { + int vertexIndex = quadPointIndexes[pointInQuadIndex]; + glm::vec4 clusterIndices = mesh.clusterIndices[vertexIndex]; + glm::vec4 clusterWeights = mesh.clusterWeights[vertexIndex]; + + bool vSet = false; + for (int ci = 0; ci < 4; ci++) { + int clusterIndex = (int) clusterIndices[ci]; + float clusterWeight = clusterWeights[ci]; + if (clusterIndex < 0 || + clusterIndex >= mesh.clusters.size() || + clusterWeight == 0.0f) { + continue; + } + const FBXCluster& cluster = mesh.clusters.at(clusterIndex); + auto clusterMatrix = _meshStates[meshIndex].clusterMatrices[cluster.jointIndex]; + glm::vec3 meshVertex = mesh.vertices[vertexIndex]; + glm::vec3 fuh = transformPoint(clusterMatrix, meshVertex); + glm::vec3 tpoint = clusterWeight * (_translation + fuh); + v[pointInQuadIndex] += tpoint; + vSet = true; + } + if (vSet) { + ok++; + } + } + } + + + glm::vec3 v0; + glm::vec3 v1; + glm::vec3 v2; + glm::vec3 v3; + + glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 mv3 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + + if (ok == 4) { + okCount++; + v0 = v[0]; + v1 = v[1]; + v2 = v[2]; + v3 = v[3]; + } else { + notOkCount++; + v0 = calculateScaledOffsetPoint(mv0); + v1 = calculateScaledOffsetPoint(mv1); + v2 = calculateScaledOffsetPoint(mv2); + v3 = calculateScaledOffsetPoint(mv3); + } // track the mesh parts in model space if (!atLeastOnePointInBounds) { @@ -499,11 +551,6 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisPartBounds += mv2; thisPartBounds += mv3; - glm::vec3 v0 = calculateScaledOffsetPoint(mv0); - glm::vec3 v1 = calculateScaledOffsetPoint(mv1); - glm::vec3 v2 = calculateScaledOffsetPoint(mv2); - glm::vec3 v3 = calculateScaledOffsetPoint(mv3); - // Sam's recommended triangle slices Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; @@ -514,7 +561,6 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); - } } @@ -526,9 +572,57 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; - glm::vec3 mv0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); - glm::vec3 mv1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); - glm::vec3 mv2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 v[3]; + int ok = 0; + if (meshIndex < _meshStates.size()) { + int trianglePointIndexes[ 3 ] = {i0, i1, i2}; + for (int pointInTriIndex = 0; pointInTriIndex < 3; pointInTriIndex++) { + int vertexIndex = trianglePointIndexes[pointInTriIndex]; + glm::vec4 clusterIndices = mesh.clusterIndices[vertexIndex]; + glm::vec4 clusterWeights = mesh.clusterWeights[vertexIndex]; + + bool vSet = false; + for (int ci = 0; ci < 4; ci++) { + int clusterIndex = (int) clusterIndices[ci]; + float clusterWeight = clusterWeights[ci]; + if (clusterIndex < 0 || + clusterIndex >= mesh.clusters.size() || + clusterWeight == 0.0f) { + continue; + } + const FBXCluster& cluster = mesh.clusters.at(clusterIndex); + auto clusterMatrix = _meshStates[meshIndex].clusterMatrices[cluster.jointIndex]; + glm::vec3 meshVertex = mesh.vertices[vertexIndex]; + glm::vec3 fuh = transformPoint(clusterMatrix, meshVertex); + glm::vec3 tpoint = clusterWeight * (_translation + fuh); + v[pointInTriIndex] += tpoint; + vSet = true; + } + if (vSet) { + ok++; + } + } + } + + glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + + glm::vec3 v0; + glm::vec3 v1; + glm::vec3 v2; + + if (ok == 3) { + okCount++; + v0 = v[0]; + v1 = v[1]; + v2 = v[2]; + } else { + notOkCount++; + v0 = calculateScaledOffsetPoint(mv0); + v1 = calculateScaledOffsetPoint(mv1); + v2 = calculateScaledOffsetPoint(mv2); + } // track the mesh parts in model space if (!atLeastOnePointInBounds) { @@ -540,21 +634,20 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisPartBounds += mv1; thisPartBounds += mv2; - glm::vec3 v0 = calculateScaledOffsetPoint(mv0); - glm::vec3 v1 = calculateScaledOffsetPoint(mv1); - glm::vec3 v2 = calculateScaledOffsetPoint(mv2); - Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } - _calculatedMeshPartBoxes[QPair(i, j)] = thisPartBounds; + _calculatedMeshPartBoxes[QPair(meshIndex, partIndex)] = thisPartBounds; } - _calculatedMeshTriangles[i] = thisMeshTriangles; + _calculatedMeshTriangles[meshIndex] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } + + qDebug() << "ok = " << okCount << " not-ok =" << notOkCount; + _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6a7c9ec560..66c5eb019e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -316,11 +316,14 @@ protected: float getLimbLength(int jointIndex) const; /// Allow sub classes to force invalidating the bboxes +public: void invalidCalculatedMeshBoxes() { _calculatedMeshBoxesValid = false; _calculatedMeshPartBoxesValid = false; _calculatedMeshTrianglesValid = false; } +protected: + // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; @@ -357,7 +360,9 @@ protected: bool _calculatedMeshTrianglesValid; QMutex _mutex; +public: void recalculateMeshBoxes(bool pickAgainstTriangles = false); +protected: void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes static model::MaterialPointer _collisionHullMaterial; From 218b26b5213cd1bc8af041064b4d5e82644e7334 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Jul 2016 10:58:55 -0700 Subject: [PATCH 12/29] removed snapshot share dialog, ctrl s shouldn't leave you moving --- .../qml/hifi/dialogs/SnapshotShareDialog.qml | 117 ------------------ interface/src/Application.cpp | 12 +- interface/src/ui/Snapshot.cpp | 115 ----------------- interface/src/ui/Snapshot.h | 8 -- interface/src/ui/SnapshotShareDialog.cpp | 47 ------- .../src/input-plugins/KeyboardMouseDevice.cpp | 8 +- 6 files changed, 6 insertions(+), 301 deletions(-) delete mode 100644 interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml delete mode 100644 interface/src/ui/SnapshotShareDialog.cpp diff --git a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml deleted file mode 100644 index f99b770a78..0000000000 --- a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml +++ /dev/null @@ -1,117 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.XmlListModel 2.0 - -import "../../windows" -import "../../js/Utils.js" as Utils -import "../models" - -Window { - id: root - resizable: true - width: 516 - height: 616 - minSize: Qt.vector2d(500, 600); - maxSize: Qt.vector2d(1000, 800); - - property alias source: image.source - - Rectangle { - anchors.fill: parent - color: "white" - - Item { - anchors { fill: parent; margins: 8 } - - Image { - id: image - anchors { top: parent.top; left: parent.left; right: parent.right; bottom: notesLabel.top; bottomMargin: 8 } - fillMode: Image.PreserveAspectFit - } - - Text { - id: notesLabel - anchors { left: parent.left; bottom: notes.top; bottomMargin: 8; } - text: "Notes about this image" - font.pointSize: 14 - font.bold: true - color: "#666" - } - - TextArea { - id: notes - anchors { left: parent.left; bottom: parent.bottom; right: shareButton.left; rightMargin: 8 } - height: 60 - } - - Button { - id: shareButton - anchors { verticalCenter: notes.verticalCenter; right: parent.right; } - width: 120; height: 50 - text: "Share" - - style: ButtonStyle { - background: Rectangle { - implicitWidth: 120 - implicitHeight: 50 - border.width: control.activeFocus ? 2 : 1 - color: "#333" - radius: 9 - } - label: Text { - color: shareButton.enabled ? "white" : "gray" - font.pixelSize: 18 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.fill: parent - text: shareButton.text - } - } - - onClicked: { - enabled = false; - uploadTimer.start(); - } - - Timer { - id: uploadTimer - running: false - interval: 5 - repeat: false - onTriggered: { - var uploaded = SnapshotUploader.uploadSnapshot(root.source.toString()) - console.log("Uploaded result " + uploaded) - if (!uploaded) { - console.log("Upload failed "); - } - } - } - } - } - - Action { - id: shareAction - text: qsTr("OK") - enabled: root.result ? true : false - shortcut: Qt.Key_Return - onTriggered: { - root.destroy(); - } - } - - Action { - id: cancelAction - text: qsTr("Cancel") - shortcut: Qt.Key_Escape - onTriggered: { - root.destroy(); - } - } - } -} - - - - diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1c9ec94dc4..9d177d724d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1517,7 +1517,6 @@ void Application::initializeUi() { // For some reason there is already an "Application" object in the QML context, // though I can't find it. Hence, "ApplicationInterface" - rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader()); rootContext->setContextProperty("ApplicationInterface", this); rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); rootContext->setContextProperty("Controller", DependencyManager::get().data()); @@ -4989,16 +4988,7 @@ void Application::takeSnapshot() { player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); - QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); - - auto accountManager = DependencyManager::get(); - if (!accountManager->isLoggedIn()) { - return; - } - - DependencyManager::get()->load("hifi/dialogs/SnapshotShareDialog.qml", [=](QQmlContext*, QObject* dialog) { - dialog->setProperty("source", QUrl::fromLocalFile(fileName)); - }); + Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); } float Application::getRenderResolutionScale() const { diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index b8be2bb8c4..a3af742f92 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -133,118 +133,3 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { return imageTempFile; } } - -const QString FORUM_URL = "https://alphas.highfidelity.io"; -const QString FORUM_UPLOADS_URL = FORUM_URL + "/uploads"; -const QString FORUM_POST_URL = FORUM_URL + "/posts"; -const QString FORUM_REPLY_TO_TOPIC = "244"; -const QString FORUM_POST_TEMPLATE = "

%2

"; -const QString SHARE_DEFAULT_ERROR = "The server isn't responding. Please try again in a few minutes."; -const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...
%1"; - - -QString SnapshotUploader::uploadSnapshot(const QUrl& fileUrl) { - auto accountManager = DependencyManager::get(); - if (accountManager->getAccountInfo().getDiscourseApiKey().isEmpty()) { - OffscreenUi::warning(nullptr, "", "Your Discourse API key is missing, you cannot share snapshots. Please try to relog."); - return QString(); - } - - QHttpPart apiKeyPart; - apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"api_key\"")); - apiKeyPart.setBody(accountManager->getAccountInfo().getDiscourseApiKey().toLatin1()); - - QString filename = fileUrl.toLocalFile(); - qDebug() << filename; - QFile* file = new QFile(filename); - Q_ASSERT(file->exists()); - file->open(QIODevice::ReadOnly); - - QHttpPart imagePart; - imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg")); - imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file\"; filename=\"" + file->fileName() + "\"")); - imagePart.setBodyDevice(file); - - QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart - multiPart->append(apiKeyPart); - multiPart->append(imagePart); - - QUrl url(FORUM_UPLOADS_URL); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - - QString result; - QEventLoop loop; - - QSharedPointer reply(NetworkAccessManager::getInstance().post(request, multiPart)); - QObject::connect(reply.data(), &QNetworkReply::finished, [&] { - loop.quit(); - - qDebug() << reply->errorString(); - for (const auto& header : reply->rawHeaderList()) { - qDebug() << "Header " << QString(header); - } - auto replyResult = reply->readAll(); - qDebug() << QString(replyResult); - QJsonDocument jsonResponse = QJsonDocument::fromJson(replyResult); - const QJsonObject& responseObject = jsonResponse.object(); - if (!responseObject.contains("url")) { - OffscreenUi::warning(this, "", SHARE_DEFAULT_ERROR); - return; - } - result = responseObject["url"].toString(); - }); - loop.exec(); - return result; -} - -QString SnapshotUploader::sendForumPost(const QString& snapshotPath, const QString& notes) { - // post to Discourse forum - QNetworkRequest request; - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QUrl forumUrl(FORUM_POST_URL); - - QUrlQuery query; - query.addQueryItem("api_key", DependencyManager::get()->getAccountInfo().getDiscourseApiKey()); - query.addQueryItem("topic_id", FORUM_REPLY_TO_TOPIC); - query.addQueryItem("raw", FORUM_POST_TEMPLATE.arg(snapshotPath, notes)); - forumUrl.setQuery(query); - - QByteArray postData = forumUrl.toEncoded(QUrl::RemoveFragment); - request.setUrl(forumUrl); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QNetworkReply* requestReply = NetworkAccessManager::getInstance().post(request, postData); - - QEventLoop loop; - QString result; - connect(requestReply, &QNetworkReply::finished, [&] { - loop.quit(); - QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); - requestReply->deleteLater(); - const QJsonObject& responseObject = jsonResponse.object(); - - if (!responseObject.contains("id")) { - QString errorMessage(SHARE_DEFAULT_ERROR); - if (responseObject.contains("errors")) { - QJsonArray errorArray = responseObject["errors"].toArray(); - if (!errorArray.first().toString().isEmpty()) { - errorMessage = errorArray.first().toString(); - } - } - OffscreenUi::warning(this, "", errorMessage); - return; - } - - const QString urlTemplate = "%1/t/%2/%3/%4"; - result = urlTemplate.arg(FORUM_URL, - responseObject["topic_slug"].toString(), - QString::number(responseObject["topic_id"].toDouble()), - QString::number(responseObject["post_number"].toDouble())); - }); - loop.exec(); - return result; -} - diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index d87a70255f..5856743141 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -43,12 +43,4 @@ private: static QFile* savedFileForSnapshot(QImage & image, bool isTemporary); }; -class SnapshotUploader : public QObject{ - Q_OBJECT -public: - SnapshotUploader(QObject* parent = nullptr) : QObject(parent) {} - Q_INVOKABLE QString uploadSnapshot(const QUrl& fileUrl); - Q_INVOKABLE QString sendForumPost(const QString& snapshotPath, const QString& notes); -}; - #endif // hifi_Snapshot_h diff --git a/interface/src/ui/SnapshotShareDialog.cpp b/interface/src/ui/SnapshotShareDialog.cpp deleted file mode 100644 index 94f89641e2..0000000000 --- a/interface/src/ui/SnapshotShareDialog.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// SnapshotShareDialog.cpp -// interface/src/ui -// -// Created by Stojce Slavkovski on 2/16/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#if 0 - - -#include - -const int NARROW_SNAPSHOT_DIALOG_SIZE = 500; -const int WIDE_SNAPSHOT_DIALOG_WIDTH = 650; -const int SUCCESS_LABEL_HEIGHT = 140; - -const QString SHARE_BUTTON_STYLE = "border-width:0;border-radius:9px;border-radius:9px;font-family:Arial;font-size:18px;" - "font-weight:100;color:#FFFFFF;width: 120px;height: 50px;"; -const QString SHARE_BUTTON_ENABLED_STYLE = "background-color: #333;"; -const QString SHARE_BUTTON_DISABLED_STYLE = "background-color: #999;"; - -Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) - -SnapshotShareDialog::SnapshotShareDialog(QString fileName, QWidget* parent) : - QDialog(parent), - _fileName(fileName) -{ - - - _ui.snapshotWidget->setPixmap(snaphsotPixmap); - _ui.snapshotWidget->adjustSize(); -} - -void SnapshotShareDialog::accept() { - // prevent multiple clicks on share button - _ui.shareButton->setEnabled(false); - // gray out share button - _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_DISABLED_STYLE); - uploadSnapshot(); -} - - -#endif diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 915ec1db87..ebe80f12cf 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -49,9 +49,11 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() { void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { auto input = _inputDevice->makeInput((Qt::Key) event->key()); - auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); - if (!result.second) { - // key pressed again ? without catching the release event ? + if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) { + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); + if (result.second) { + // key pressed again ? without catching the release event ? + } } } From 04184ee1fba636a42e6844334d6b7e11980c5cc4 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Jul 2016 17:21:57 -0700 Subject: [PATCH 13/29] added snapshot notification in desktop mode --- interface/src/Application.cpp | 4 +- .../src/scripting/WindowScriptingInterface.h | 1 + scripts/system/notifications.js | 83 +++++++++++-------- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9d177d724d..d00c966da1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4988,7 +4988,9 @@ void Application::takeSnapshot() { player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); - Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); + QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); + + emit DependencyManager::get()->snapshotTaken(path); } float Application::getRenderResolutionScale() const { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index b92114c1bf..4f26ccd057 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -46,6 +46,7 @@ signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reasonMessage, int reasonCode); + void snapshotTaken(const QString& path); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 7d97470b8a..667d87271d 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -49,10 +49,10 @@ // 2. Declare a text string. // 3. Call createNotifications(text, NotificationType) parsing the text. // example: -// if (key.text === "s") { +// if (key.text === "o") { // if (ctrlIsPressed === true) { -// noteString = "Snapshot taken."; -// createNotification(noteString, NotificationType.SNAPSHOT); +// noteString = "Open script"; +// createNotification(noteString, NotificationType.OPEN_SCRIPT); // } // } @@ -233,8 +233,9 @@ function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { // Pushes data to each array and sets up data for 2nd dimension array // to handle auxiliary data not carried by the overlay class // specifically notification "heights", "times" of creation, and . -function notify(notice, button, height) { - var noticeWidth, +function notify(notice, button, height, imageProperties, image) { + var notificationText, + noticeWidth, noticeHeight, positions, last; @@ -269,9 +270,13 @@ function notify(notice, button, height) { height: noticeHeight }); } else { - var notificationText = Overlays.addOverlay("text", notice); - notifications.push((notificationText)); - buttons.push((Overlays.addOverlay("image", button))); + if (!image) { + notificationText = Overlays.addOverlay("text", notice); + notifications.push((notificationText)); + } else { + notifications.push(Overlays.addOverlay("image", notice)); + } + buttons.push(Overlays.addOverlay("image", button)); } height = height + 1.0; @@ -281,11 +286,24 @@ function notify(notice, button, height) { last = notifications.length - 1; createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); fadeIn(notifications[last], buttons[last]); + + if (imageProperties && !image) { + var imageHeight = notice.width / imageProperties.aspectRatio; + notice = { + x: notice.x, + y: notice.y + height, + width: notice.width, + height: imageHeight, + imageURL: imageProperties.path + }; + notify(notice, button, imageHeight, imageProperties, true); + } + return notificationText; } // This function creates and sizes the overlays -function createNotification(text, notificationType) { +function createNotification(text, notificationType, imageProperties) { var count = (text.match(/\n/g) || []).length, breakPoint = 43.0, // length when new line is added extraLine = 0, @@ -308,6 +326,11 @@ function createNotification(text, notificationType) { level = (stack + 20.0); height = height + extraLine; + + if (imageProperties && imageProperties.path) { + imageProperties.path = Script.resolvePath(imageProperties.path); + } + noticeProperties = { x: overlayLocationX, y: level, @@ -336,12 +359,11 @@ function createNotification(text, notificationType) { }; if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) - { + Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { randomSounds.playRandom(); } - return notify(noticeProperties, buttonProperties, height); + return notify(noticeProperties, buttonProperties, height, imageProperties); } function deleteNotification(index) { @@ -362,21 +384,12 @@ function deleteNotification(index) { // wraps whole word to newline function stringDivider(str, slotWidth, spaceReplacer) { - var p, - left, - right; + var left, right; - if (str.length > slotWidth) { - p = slotWidth; - while (p > 0 && str[p] !== ' ') { - p -= 1; - } - - if (p > 0) { - left = str.substring(0, p); - right = str.substring(p + 1); - return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); - } + if (str.length > slotWidth && slotWidth > 0) { + left = str.substring(0, slotWidth); + right = str.substring(slotWidth + 1); + return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); } return str; } @@ -504,7 +517,15 @@ function onMuteStateChanged() { } function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED ); + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); +} + +function onSnapshotTaken(path) { + var imageProperties = { + path: path, + aspectRatio: Window.innerWidth / Window.innerHeight + } + createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); } // handles mouse clicks on buttons @@ -541,13 +562,6 @@ function keyPressEvent(key) { if (key.key === 16777249) { ctrlIsPressed = true; } - - if (key.text === "s") { - if (ctrlIsPressed === true) { - noteString = "Snapshot taken."; - createNotification(noteString, NotificationType.SNAPSHOT); - } - } } function setup() { @@ -615,5 +629,6 @@ Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); Menu.menuItemEvent.connect(menuItemEvent); Window.domainConnectionRefused.connect(onDomainConnectionRefused); +Window.snapshotTaken.connect(onSnapshotTaken); setup(); From 4eb94f8ccbd48d6b86f2ca23945c34fea2122d8e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Jul 2016 17:40:33 -0700 Subject: [PATCH 14/29] added support for snapshot notification in HMD, although 3d image overlays seem to be broken in master --- scripts/system/notifications.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 667d87271d..988d2998d7 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -251,16 +251,23 @@ function notify(notice, button, height, imageProperties, image) { notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; notice.bottomMargin = 0; notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; - notice.isFacingAvatar = false; + + positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + + if (!image) { + notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.isFacingAvatar = false; + + notificationText = Overlays.addOverlay("text3d", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image3d", notice)); + } button.url = button.imageURL; button.scale = button.width * NOTIFICATION_3D_SCALE; button.isFacingAvatar = false; - positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); - - notifications.push((Overlays.addOverlay("text3d", notice))); buttons.push((Overlays.addOverlay("image3d", button))); overlay3DDetails.push({ notificationOrientation: positions.notificationOrientation, @@ -294,6 +301,8 @@ function notify(notice, button, height, imageProperties, image) { y: notice.y + height, width: notice.width, height: imageHeight, + topMargin: 0, + leftMargin: 0, imageURL: imageProperties.path }; notify(notice, button, imageHeight, imageProperties, true); From b3513b543c50899edc63c9fda74bd386e4916e27 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Jul 2016 18:23:59 -0700 Subject: [PATCH 15/29] fixed snapshot path setting --- .../hifi/dialogs/AvatarPreferencesDialog.qml | 2 +- interface/src/ui/PreferencesDialog.cpp | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml index 86f195612c..45414cfaf8 100644 --- a/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AvatarPreferencesDialog.qml @@ -7,7 +7,7 @@ PreferencesDialog { id: root objectName: "AvatarPreferencesDialog" title: "Avatar Settings" - showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ] + showCategories: [ "Avatar Basics", "Snapshots", "Avatar Tuning", "Avatar Camera" ] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c1705da206..146df549dd 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -35,7 +35,7 @@ void setupPreferences() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); static const QString AVATAR_BASICS { "Avatar Basics" }; { - auto getter = [=]()->QString {return myAvatar->getDisplayName(); }; + auto getter = [=]()->QString { return myAvatar->getDisplayName(); }; auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar display name (optional)", getter, setter); preference->setPlaceholderText("Not showing a name"); @@ -43,7 +43,7 @@ void setupPreferences() { } { - auto getter = [=]()->QString {return myAvatar->getCollisionSoundURL(); }; + auto getter = [=]()->QString { return myAvatar->getCollisionSoundURL(); }; auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar collision sound URL (optional)", getter, setter); preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); @@ -56,20 +56,24 @@ void setupPreferences() { auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } + { - auto getter = [=]()->bool {return myAvatar->getSnapTurn(); }; + auto getter = [=]()->bool { return myAvatar->getSnapTurn(); }; auto setter = [=](bool value) { myAvatar->setSnapTurn(value); }; preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } { - auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); }; + auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); }; auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } + + // Snapshots + static const QString SNAPSHOTS { "Snapshots" }; { - auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; - auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; - auto preference = new BrowsePreference("Snapshots", "Put my snapshots here", getter, setter); + auto getter = [=]()->QString { return Snapshot::snapshotsLocation.get(); }; + auto setter = [=](const QString& value) { Snapshot::snapshotsLocation.set(value); }; + auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } @@ -85,7 +89,7 @@ void setupPreferences() { })); { - auto getter = []()->bool {return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; + auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; preferences->addPreference(new CheckPreference("Privacy", "Send data", getter, setter)); } @@ -184,7 +188,7 @@ void setupPreferences() { static const QString AUDIO("Audio"); { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getDynamicJitterBuffers(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setDynamicJitterBuffers(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Enable dynamic jitter buffers", getter, setter)); } @@ -207,7 +211,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getUseStDevForJitterCalc(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setUseStDevForJitterCalc(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Use standard deviation for dynamic jitter calc", getter, setter)); } @@ -236,7 +240,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; + auto getter = []()->bool { return DependencyManager::get()->getReceivedAudioStream().getRepetitionWithFade(); }; auto setter = [](bool value) { DependencyManager::get()->getReceivedAudioStream().setRepetitionWithFade(value); }; preferences->addPreference(new CheckPreference(AUDIO, "Repetition with fade", getter, setter)); } @@ -250,7 +254,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = []()->bool {return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; + auto getter = []()->bool { return DependencyManager::get()->getOutputStarveDetectionEnabled(); }; auto setter = [](bool value) { DependencyManager::get()->setOutputStarveDetectionEnabled(value); }; auto preference = new CheckPreference(AUDIO, "Output starve detection (automatic buffer size increase)", getter, setter); preferences->addPreference(preference); From 79864df08ea91abf042bbf20ffc02068d91c9693 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 5 Jul 2016 18:53:39 -0700 Subject: [PATCH 16/29] didn't mean to do that --- interface/src/ui/PreferencesDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 146df549dd..4007c940c3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -71,8 +71,8 @@ void setupPreferences() { // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { - auto getter = [=]()->QString { return Snapshot::snapshotsLocation.get(); }; - auto setter = [=](const QString& value) { Snapshot::snapshotsLocation.set(value); }; + auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; + auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } From 6a44ea76e541b22bd9bfdaec392f4a5e193bdf95 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Jul 2016 11:57:29 -0700 Subject: [PATCH 17/29] fixed snapshot preview on windows --- scripts/system/notifications.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 988d2998d7..98a31d708c 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -247,14 +247,14 @@ function notify(notice, button, height, imageProperties, image) { noticeHeight = notice.height * NOTIFICATION_3D_SCALE; notice.size = { x: noticeWidth, y: noticeHeight }; - notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; - notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; - notice.bottomMargin = 0; - notice.rightMargin = 0; positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); if (!image) { + notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; + notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; + notice.bottomMargin = 0; + notice.rightMargin = 0; notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; notice.isFacingAvatar = false; @@ -289,8 +289,8 @@ function notify(notice, button, height, imageProperties, image) { height = height + 1.0; heights.push(height); times.push(new Date().getTime() / 1000); - myAlpha.push(0); last = notifications.length - 1; + myAlpha.push(notifications[last].alpha); createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); fadeIn(notifications[last], buttons[last]); @@ -301,9 +301,11 @@ function notify(notice, button, height, imageProperties, image) { y: notice.y + height, width: notice.width, height: imageHeight, - topMargin: 0, - leftMargin: 0, - imageURL: imageProperties.path + subImage: { x: 0, y: 0 }, + color: { red: 255, green: 255, blue: 255}, + visible: true, + imageURL: imageProperties.path, + alpha: backgroundAlpha }; notify(notice, button, imageHeight, imageProperties, true); } @@ -336,10 +338,6 @@ function createNotification(text, notificationType, imageProperties) { level = (stack + 20.0); height = height + extraLine; - if (imageProperties && imageProperties.path) { - imageProperties.path = Script.resolvePath(imageProperties.path); - } - noticeProperties = { x: overlayLocationX, y: level, @@ -397,7 +395,7 @@ function stringDivider(str, slotWidth, spaceReplacer) { if (str.length > slotWidth && slotWidth > 0) { left = str.substring(0, slotWidth); - right = str.substring(slotWidth + 1); + right = str.substring(slotWidth); return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); } return str; @@ -531,7 +529,7 @@ function onDomainConnectionRefused(reason) { function onSnapshotTaken(path) { var imageProperties = { - path: path, + path: Script.resolvePath("file:///" + path), aspectRatio: Window.innerWidth / Window.innerHeight } createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); From 890b22426c44b5773257c483d17e00e86809aab9 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Jul 2016 12:15:34 -0700 Subject: [PATCH 18/29] added folder selection dialog on first snapshot --- interface/src/ui/Snapshot.cpp | 10 +++++++++- interface/src/ui/Snapshot.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index a3af742f92..aaf11d14a4 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -44,6 +44,7 @@ const QString URL = "highfidelity_url"; Setting::Handle Snapshot::snapshotsLocation("snapshotsLocation", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); +Setting::Handle Snapshot::hasSetSnapshotsLocation("hasSetSnapshotsLocation", false); SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { @@ -103,7 +104,14 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { const int IMAGE_QUALITY = 100; if (!isTemporary) { - QString snapshotFullPath = snapshotsLocation.get(); + QString snapshotFullPath; + if (!hasSetSnapshotsLocation.get()) { + snapshotFullPath = QFileDialog::getExistingDirectory(nullptr, "Choose Snapshots Directory", snapshotsLocation.get()); + hasSetSnapshotsLocation.set(true); + snapshotsLocation.set(snapshotFullPath); + } else { + snapshotFullPath = snapshotsLocation.get(); + } if (!snapshotFullPath.endsWith(QDir::separator())) { snapshotFullPath.append(QDir::separator()); diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 5856743141..2e7986a5c0 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -39,6 +39,7 @@ public: static SnapshotMetaData* parseSnapshotData(QString snapshotPath); static Setting::Handle snapshotsLocation; + static Setting::Handle hasSetSnapshotsLocation; private: static QFile* savedFileForSnapshot(QImage & image, bool isTemporary); }; From aba6924dc7b9c844da3586776478763d62b43fc8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 7 Jul 2016 13:33:36 -0700 Subject: [PATCH 19/29] Fix MyAvatar name and url accessors to JS. Q_INVOKABLE does not support returning references. MyAvatar.getFullAvatarURLFromPreferences() now returns a QUrl instead of a reference. MyAvatar.getFullAvatarModelName() now returns a QString instead of reference. This should fix the JS API for these methods, they will now receive a JS string instead of undefined. --- interface/src/avatar/MyAvatar.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 96fa999de5..a21922f1b1 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -212,8 +212,8 @@ public: virtual void clearJointsData() override; Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); - Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } - Q_INVOKABLE const QString& getFullAvatarModelName() const { return _fullAvatarModelName; } + Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } + Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; } void resetFullAvatarURL(); From 11542aeca2b9b01b6e2d6fb9ec58abd8c41d1dcb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 7 Jul 2016 16:36:25 -0700 Subject: [PATCH 20/29] pick against avatar's capsule and then against the T-pose mesh --- interface/src/avatar/Avatar.cpp | 9 ++ interface/src/avatar/Avatar.h | 1 + interface/src/avatar/AvatarManager.cpp | 32 ++++-- libraries/render-utils/src/Model.cpp | 137 ++++--------------------- libraries/render-utils/src/Model.h | 5 - 5 files changed, 52 insertions(+), 132 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 39bb7eac17..4d9481f002 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1085,6 +1085,15 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset()); } +void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight + start = getPosition() - glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + end = getPosition() + glm::vec3(0, halfExtents.y, 0) + shapeInfo.getOffset(); + radius = halfExtents.x; +} + void Avatar::setMotionState(AvatarMotionState* motionState) { _motionState = motionState; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 064f0a9533..b9f44613c7 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -154,6 +154,7 @@ public: virtual void rebuildCollisionShape(); virtual void computeShapeInfo(ShapeInfo& shapeInfo); + void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); AvatarMotionState* getMotionState() { return _motionState; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index a5a02bdaff..31a77df0cf 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -429,18 +429,34 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& glm::vec3 surfaceNormal; SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); - AABox avatarBounds = avatarModel->getRenderableMeshBound(); - if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) { - // ray doesn't intersect avatar's bounding-box + + // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + // AABox avatarBounds = avatarModel->getRenderableMeshBound(); + // if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) { + // // ray doesn't intersect avatar's bounding-box + // continue; + // } + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance); + if (!intersects) { + // ray doesn't intersect avatar's capsule continue; } - avatarModel->invalidCalculatedMeshBoxes(); - avatarModel->recalculateMeshBoxes(true); - QString extraInfo; - bool intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, - distance, face, surfaceNormal, extraInfo, true); + intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, + distance, face, surfaceNormal, extraInfo, true); if (intersects && (!result.intersects || distance < result.distance)) { result.intersects = true; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4969585af4..43eced3107 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -442,31 +442,23 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { PROFILE_RANGE(__FUNCTION__); bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; - if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || - (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { + if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) { const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _calculatedMeshBoxes.resize(numberOfMeshes); _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); - - - - int okCount = 0; - int notOkCount = 0; - - - for (int meshIndex = 0; meshIndex < numberOfMeshes; meshIndex++) { - const FBXMesh& mesh = geometry.meshes.at(meshIndex); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents, _translation, _rotation); - _calculatedMeshBoxes[meshIndex] = AABox(scaledMeshExtents); + _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); if (pickAgainstTriangles) { QVector thisMeshTriangles; - for (int partIndex = 0; partIndex < mesh.parts.size(); partIndex++) { - const FBXMeshPart& part = mesh.parts.at(partIndex); + for (int j = 0; j < mesh.parts.size(); j++) { + const FBXMeshPart& part = mesh.parts.at(j); bool atLeastOnePointInBounds = false; AABox thisPartBounds; @@ -483,63 +475,11 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i2 = part.quadIndices[vIndex++]; int i3 = part.quadIndices[vIndex++]; - glm::vec3 v[3]; - int ok = 0; - if (meshIndex < _meshStates.size()) { - int quadPointIndexes[ 4 ] = {i0, i1, i2, i3}; - for (int pointInQuadIndex = 0; pointInQuadIndex < 4; pointInQuadIndex++) { - int vertexIndex = quadPointIndexes[pointInQuadIndex]; - glm::vec4 clusterIndices = mesh.clusterIndices[vertexIndex]; - glm::vec4 clusterWeights = mesh.clusterWeights[vertexIndex]; - - bool vSet = false; - for (int ci = 0; ci < 4; ci++) { - int clusterIndex = (int) clusterIndices[ci]; - float clusterWeight = clusterWeights[ci]; - if (clusterIndex < 0 || - clusterIndex >= mesh.clusters.size() || - clusterWeight == 0.0f) { - continue; - } - const FBXCluster& cluster = mesh.clusters.at(clusterIndex); - auto clusterMatrix = _meshStates[meshIndex].clusterMatrices[cluster.jointIndex]; - glm::vec3 meshVertex = mesh.vertices[vertexIndex]; - glm::vec3 fuh = transformPoint(clusterMatrix, meshVertex); - glm::vec3 tpoint = clusterWeight * (_translation + fuh); - v[pointInQuadIndex] += tpoint; - vSet = true; - } - if (vSet) { - ok++; - } - } - } - - - glm::vec3 v0; - glm::vec3 v1; - glm::vec3 v2; - glm::vec3 v3; - glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); glm::vec3 mv3 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f)); - if (ok == 4) { - okCount++; - v0 = v[0]; - v1 = v[1]; - v2 = v[2]; - v3 = v[3]; - } else { - notOkCount++; - v0 = calculateScaledOffsetPoint(mv0); - v1 = calculateScaledOffsetPoint(mv1); - v2 = calculateScaledOffsetPoint(mv2); - v3 = calculateScaledOffsetPoint(mv3); - } - // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); @@ -551,6 +491,11 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisPartBounds += mv2; thisPartBounds += mv3; + glm::vec3 v0 = calculateScaledOffsetPoint(mv0); + glm::vec3 v1 = calculateScaledOffsetPoint(mv1); + glm::vec3 v2 = calculateScaledOffsetPoint(mv2); + glm::vec3 v3 = calculateScaledOffsetPoint(mv3); + // Sam's recommended triangle slices Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; @@ -561,6 +506,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisMeshTriangles.push_back(tri1); thisMeshTriangles.push_back(tri2); + } } @@ -572,58 +518,10 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { int i1 = part.triangleIndices[vIndex++]; int i2 = part.triangleIndices[vIndex++]; - glm::vec3 v[3]; - int ok = 0; - if (meshIndex < _meshStates.size()) { - int trianglePointIndexes[ 3 ] = {i0, i1, i2}; - for (int pointInTriIndex = 0; pointInTriIndex < 3; pointInTriIndex++) { - int vertexIndex = trianglePointIndexes[pointInTriIndex]; - glm::vec4 clusterIndices = mesh.clusterIndices[vertexIndex]; - glm::vec4 clusterWeights = mesh.clusterWeights[vertexIndex]; - - bool vSet = false; - for (int ci = 0; ci < 4; ci++) { - int clusterIndex = (int) clusterIndices[ci]; - float clusterWeight = clusterWeights[ci]; - if (clusterIndex < 0 || - clusterIndex >= mesh.clusters.size() || - clusterWeight == 0.0f) { - continue; - } - const FBXCluster& cluster = mesh.clusters.at(clusterIndex); - auto clusterMatrix = _meshStates[meshIndex].clusterMatrices[cluster.jointIndex]; - glm::vec3 meshVertex = mesh.vertices[vertexIndex]; - glm::vec3 fuh = transformPoint(clusterMatrix, meshVertex); - glm::vec3 tpoint = clusterWeight * (_translation + fuh); - v[pointInTriIndex] += tpoint; - vSet = true; - } - if (vSet) { - ok++; - } - } - } - glm::vec3 mv0 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f)); glm::vec3 mv1 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f)); glm::vec3 mv2 = glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f)); - glm::vec3 v0; - glm::vec3 v1; - glm::vec3 v2; - - if (ok == 3) { - okCount++; - v0 = v[0]; - v1 = v[1]; - v2 = v[2]; - } else { - notOkCount++; - v0 = calculateScaledOffsetPoint(mv0); - v1 = calculateScaledOffsetPoint(mv1); - v2 = calculateScaledOffsetPoint(mv2); - } - // track the mesh parts in model space if (!atLeastOnePointInBounds) { thisPartBounds.setBox(mv0, 0.0f); @@ -634,20 +532,21 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { thisPartBounds += mv1; thisPartBounds += mv2; + glm::vec3 v0 = calculateScaledOffsetPoint(mv0); + glm::vec3 v1 = calculateScaledOffsetPoint(mv1); + glm::vec3 v2 = calculateScaledOffsetPoint(mv2); + Triangle tri = { v0, v1, v2 }; thisMeshTriangles.push_back(tri); } } - _calculatedMeshPartBoxes[QPair(meshIndex, partIndex)] = thisPartBounds; + _calculatedMeshPartBoxes[QPair(i, j)] = thisPartBounds; } - _calculatedMeshTriangles[meshIndex] = thisMeshTriangles; + _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; } } - - qDebug() << "ok = " << okCount << " not-ok =" << notOkCount; - _calculatedMeshBoxesValid = true; _calculatedMeshTrianglesValid = pickAgainstTriangles; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 66c5eb019e..6a7c9ec560 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -316,14 +316,11 @@ protected: float getLimbLength(int jointIndex) const; /// Allow sub classes to force invalidating the bboxes -public: void invalidCalculatedMeshBoxes() { _calculatedMeshBoxesValid = false; _calculatedMeshPartBoxesValid = false; _calculatedMeshTrianglesValid = false; } -protected: - // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; @@ -360,9 +357,7 @@ protected: bool _calculatedMeshTrianglesValid; QMutex _mutex; -public: void recalculateMeshBoxes(bool pickAgainstTriangles = false); -protected: void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes static model::MaterialPointer _collisionHullMaterial; From 31948bce2d4a2a871dce7480e92cfd16db7b1063 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 8 Jul 2016 16:35:03 -0700 Subject: [PATCH 21/29] Support alpha and color gradients in circle overlays --- interface/src/ui/overlays/Circle3DOverlay.cpp | 313 +++++++++--------- interface/src/ui/overlays/Circle3DOverlay.h | 48 +-- libraries/render-utils/src/GeometryCache.cpp | 52 ++- libraries/render-utils/src/GeometryCache.h | 4 +- 4 files changed, 219 insertions(+), 198 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 2896ce711e..22995cbc35 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -17,28 +17,7 @@ QString const Circle3DOverlay::TYPE = "circle3d"; -Circle3DOverlay::Circle3DOverlay() : - _startAt(0.0f), - _endAt(360.0f), - _outerRadius(1.0f), - _innerRadius(0.0f), - _hasTickMarks(false), - _majorTickMarksAngle(0.0f), - _minorTickMarksAngle(0.0f), - _majorTickMarksLength(0.0f), - _minorTickMarksLength(0.0f), - _quadVerticesID(GeometryCache::UNKNOWN_ID), - _lineVerticesID(GeometryCache::UNKNOWN_ID), - _majorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _minorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _lastStartAt(-1.0f), - _lastEndAt(-1.0f), - _lastOuterRadius(-1.0f), - _lastInnerRadius(-1.0f) -{ - _majorTickMarksColor.red = _majorTickMarksColor.green = _majorTickMarksColor.blue = (unsigned char)0; - _minorTickMarksColor.red = _minorTickMarksColor.green = _minorTickMarksColor.blue = (unsigned char)0; -} +Circle3DOverlay::Circle3DOverlay() { } Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : Planar3DOverlay(circle3DOverlay), @@ -56,11 +35,7 @@ Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : _quadVerticesID(GeometryCache::UNKNOWN_ID), _lineVerticesID(GeometryCache::UNKNOWN_ID), _majorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _minorTicksVerticesID(GeometryCache::UNKNOWN_ID), - _lastStartAt(-1.0f), - _lastEndAt(-1.0f), - _lastOuterRadius(-1.0f), - _lastInnerRadius(-1.0f) + _minorTicksVerticesID(GeometryCache::UNKNOWN_ID) { } @@ -70,36 +45,25 @@ void Circle3DOverlay::render(RenderArgs* args) { } float alpha = getAlpha(); - if (alpha == 0.0f) { return; // do nothing if our alpha is 0, we're not visible } - // Create the circle in the coordinates origin - float outerRadius = getOuterRadius(); - float innerRadius = getInnerRadius(); // only used in solid case - float startAt = getStartAt(); - float endAt = getEndAt(); - - bool geometryChanged = (startAt != _lastStartAt || endAt != _lastEndAt || - innerRadius != _lastInnerRadius || outerRadius != _lastOuterRadius); - + bool geometryChanged = _dirty; + _dirty = false; const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; - - xColor colorX = getColor(); const float MAX_COLOR = 255.0f; - glm::vec4 color(colorX.red / MAX_COLOR, colorX.green / MAX_COLOR, colorX.blue / MAX_COLOR, alpha); - - bool colorChanged = colorX.red != _lastColor.red || colorX.green != _lastColor.green || colorX.blue != _lastColor.blue; - _lastColor = colorX; auto geometryCache = DependencyManager::get(); Q_ASSERT(args->_batch); auto& batch = *args->_batch; + if (args->_pipeline) { + batch.setPipeline(args->_pipeline->pipeline); + } // FIXME: THe line width of _lineWidth is not supported anymore, we ll need a workaround @@ -110,81 +74,89 @@ void Circle3DOverlay::render(RenderArgs* args) { // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise // we just draw a line... if (getIsSolid()) { - if (_quadVerticesID == GeometryCache::UNKNOWN_ID) { + if (!_quadVerticesID) { _quadVerticesID = geometryCache->allocateID(); } - if (geometryChanged || colorChanged) { - + if (geometryChanged) { QVector points; - - float angle = startAt; - float angleInRadians = glm::radians(angle); - glm::vec2 mostRecentInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 mostRecentOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); - - while (angle < endAt) { - angleInRadians = glm::radians(angle); - glm::vec2 thisInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 thisOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); - - points << mostRecentInnerPoint << mostRecentOuterPoint << thisOuterPoint; // first triangle - points << mostRecentInnerPoint << thisInnerPoint << thisOuterPoint; // second triangle - - angle += SLICE_ANGLE; + QVector colors; - mostRecentInnerPoint = thisInnerPoint; - mostRecentOuterPoint = thisOuterPoint; + float pulseLevel = updatePulse(); + vec4 pulseModifier = vec4(1); + if (_alphaPulse != 0.0) { + pulseModifier.a = (_alphaPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); } - - // get the last slice portion.... - angle = endAt; - angleInRadians = glm::radians(angle); - glm::vec2 lastInnerPoint(cosf(angleInRadians) * innerRadius, sinf(angleInRadians) * innerRadius); - glm::vec2 lastOuterPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + if (_colorPulse != 0.0) { + float pulseValue = (_colorPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); + pulseModifier = vec4(vec3(pulseValue), pulseModifier.a); + } + vec4 innerStartColor = vec4(toGlm(_innerStartColor), _innerStartAlpha) * pulseModifier; + vec4 outerStartColor = vec4(toGlm(_outerStartColor), _outerStartAlpha) * pulseModifier; + vec4 innerEndColor = vec4(toGlm(_innerEndColor), _innerEndAlpha) * pulseModifier; + vec4 outerEndColor = vec4(toGlm(_outerEndColor), _outerEndAlpha) * pulseModifier; - points << mostRecentInnerPoint << mostRecentOuterPoint << lastOuterPoint; // first triangle - points << mostRecentInnerPoint << lastInnerPoint << lastOuterPoint; // second triangle - - geometryCache->updateVertices(_quadVerticesID, points, color); + if (_innerRadius <= 0) { + _solidPrimitive = gpu::TRIANGLE_FAN; + points << vec2(); + colors << innerStartColor; + for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { + float range = (angle - _startAt) / (_endAt - _startAt); + float angleRadians = glm::radians(angle); + points << glm::vec2(cos(angleRadians) * _outerRadius, sin(angleRadians) * _outerRadius); + colors << glm::mix(outerStartColor, outerEndColor, range); + } + } else { + _solidPrimitive = gpu::TRIANGLE_STRIP; + for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { + float range = (angle - _startAt) / (_endAt - _startAt); + + float angleRadians = glm::radians(angle); + points << glm::vec2(cos(angleRadians) * _innerRadius, sin(angleRadians) * _innerRadius); + colors << glm::mix(innerStartColor, innerEndColor, range); + + points << glm::vec2(cos(angleRadians) * _outerRadius, sin(angleRadians) * _outerRadius); + colors << glm::mix(outerStartColor, outerEndColor, range); + } + } + geometryCache->updateVertices(_quadVerticesID, points, colors); } - geometryCache->renderVertices(batch, gpu::TRIANGLES, _quadVerticesID); + geometryCache->renderVertices(batch, _solidPrimitive, _quadVerticesID); } else { - if (_lineVerticesID == GeometryCache::UNKNOWN_ID) { + if (!_lineVerticesID) { _lineVerticesID = geometryCache->allocateID(); } - if (geometryChanged || colorChanged) { + if (geometryChanged) { QVector points; - float angle = startAt; + float angle = _startAt; float angleInRadians = glm::radians(angle); - glm::vec2 firstPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 firstPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << firstPoint; - while (angle < endAt) { + while (angle < _endAt) { angle += SLICE_ANGLE; angleInRadians = glm::radians(angle); - glm::vec2 thisPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 thisPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << thisPoint; if (getIsDashedLine()) { angle += SLICE_ANGLE / 2.0f; // short gap angleInRadians = glm::radians(angle); - glm::vec2 dashStartPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 dashStartPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << dashStartPoint; } } // get the last slice portion.... - angle = endAt; + angle = _endAt; angleInRadians = glm::radians(angle); - glm::vec2 lastPoint(cosf(angleInRadians) * outerRadius, sinf(angleInRadians) * outerRadius); + glm::vec2 lastPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << lastPoint; - - geometryCache->updateVertices(_lineVerticesID, points, color); + geometryCache->updateVertices(_lineVerticesID, points, vec4(toGlm(getColor()), getAlpha())); } if (getIsDashedLine()) { @@ -214,13 +186,13 @@ void Circle3DOverlay::render(RenderArgs* args) { if (getMajorTickMarksAngle() > 0.0f && getMajorTickMarksLength() != 0.0f) { float tickMarkAngle = getMajorTickMarksAngle(); - float angle = startAt - fmodf(startAt, tickMarkAngle) + tickMarkAngle; + float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMajorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; - while (angle <= endAt) { + while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); @@ -236,13 +208,13 @@ void Circle3DOverlay::render(RenderArgs* args) { if (getMinorTickMarksAngle() > 0.0f && getMinorTickMarksLength() != 0.0f) { float tickMarkAngle = getMinorTickMarksAngle(); - float angle = startAt - fmodf(startAt, tickMarkAngle) + tickMarkAngle; + float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMinorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; - while (angle <= endAt) { + while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); @@ -269,17 +241,10 @@ void Circle3DOverlay::render(RenderArgs* args) { geometryCache->renderVertices(batch, gpu::LINES, _minorTicksVerticesID); } - - if (geometryChanged) { - _lastStartAt = startAt; - _lastEndAt = endAt; - _lastInnerRadius = innerRadius; - _lastOuterRadius = outerRadius; - } } const render::ShapeKey Circle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace(); + auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit(); if (getAlpha() != 1.0f) { builder.withTranslucent(); } @@ -289,72 +254,102 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { return builder.build(); } +template T fromVariant(const QVariant& v, bool& valid) { + valid = v.isValid(); + return qvariant_cast(v); +} + +template<> xColor fromVariant(const QVariant& v, bool& valid) { + return xColorFromVariant(v, valid); +} + +template +bool updateIfValid(const QVariantMap& properties, const char* key, T& output) { + bool valid; + T result = fromVariant(properties[key], valid); + if (!valid) { + return false; + } + + // Don't signal updates if the value was already set + if (result == output) { + return false; + } + + output = result; + return true; +} + +// Multicast, many outputs +template +bool updateIfValid(const QVariantMap& properties, const char* key, std::initializer_list> outputs) { + bool valid; + T value = fromVariant(properties[key], valid); + if (!valid) { + return false; + } + bool updated = false; + for (T& output : outputs) { + if (output != value) { + output = value; + updated = true; + } + } + return updated; +} + +// Multicast, multiple possible inputs, in order of preference +template +bool updateIfValid(const QVariantMap& properties, const std::initializer_list keys, T& output) { + for (const char* key : keys) { + if (updateIfValid(properties, key, output)) { + return true; + } + } + return false; +} + + void Circle3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); + _dirty |= updateIfValid(properties, "alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "Alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "startAlpha", { _innerStartAlpha, _outerStartAlpha }); + _dirty |= updateIfValid(properties, "endAlpha", { _innerEndAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "innerAlpha", { _innerStartAlpha, _innerEndAlpha }); + _dirty |= updateIfValid(properties, "outerAlpha", { _outerStartAlpha, _outerEndAlpha }); + _dirty |= updateIfValid(properties, "innerStartAlpha", _innerStartAlpha); + _dirty |= updateIfValid(properties, "innerEndAlpha", _innerEndAlpha); + _dirty |= updateIfValid(properties, "outerStartAlpha", _outerStartAlpha); + _dirty |= updateIfValid(properties, "outerEndAlpha", _outerEndAlpha); - QVariant startAt = properties["startAt"]; - if (startAt.isValid()) { - setStartAt(startAt.toFloat()); - } + _dirty |= updateIfValid(properties, "color", { _innerStartColor, _innerEndColor, _outerStartColor, _outerEndColor }); + _dirty |= updateIfValid(properties, "startColor", { _innerStartColor, _outerStartColor } ); + _dirty |= updateIfValid(properties, "endColor", { _innerEndColor, _outerEndColor } ); + _dirty |= updateIfValid(properties, "innerColor", { _innerStartColor, _innerEndColor } ); + _dirty |= updateIfValid(properties, "outerColor", { _outerStartColor, _outerEndColor } ); + _dirty |= updateIfValid(properties, "innerStartColor", _innerStartColor); + _dirty |= updateIfValid(properties, "innerEndColor", _innerEndColor); + _dirty |= updateIfValid(properties, "outerStartColor", _outerStartColor); + _dirty |= updateIfValid(properties, "outerEndColor", _outerEndColor); - QVariant endAt = properties["endAt"]; - if (endAt.isValid()) { - setEndAt(endAt.toFloat()); - } + _dirty |= updateIfValid(properties, "startAt", _startAt); + _dirty |= updateIfValid(properties, "endAt", _endAt); - QVariant outerRadius = properties["radius"]; - if (!outerRadius.isValid()) { - outerRadius = properties["outerRadius"]; - } - if (outerRadius.isValid()) { - setOuterRadius(outerRadius.toFloat()); - } + _dirty |= updateIfValid(properties, { "radius", "outerRadius" }, _outerRadius); + _dirty |= updateIfValid(properties, "innerRadius", _innerRadius); + _dirty |= updateIfValid(properties, "hasTickMarks", _hasTickMarks); + _dirty |= updateIfValid(properties, "majorTickMarksAngle", _majorTickMarksAngle); + _dirty |= updateIfValid(properties, "minorTickMarksAngle", _minorTickMarksAngle); + _dirty |= updateIfValid(properties, "majorTickMarksLength", _majorTickMarksLength); + _dirty |= updateIfValid(properties, "minorTickMarksLength", _minorTickMarksLength); + _dirty |= updateIfValid(properties, "majorTickMarksColor", _majorTickMarksColor); + _dirty |= updateIfValid(properties, "minorTickMarksColor", _minorTickMarksColor); - QVariant innerRadius = properties["innerRadius"]; - if (innerRadius.isValid()) { - setInnerRadius(innerRadius.toFloat()); - } - - QVariant hasTickMarks = properties["hasTickMarks"]; - if (hasTickMarks.isValid()) { - setHasTickMarks(hasTickMarks.toBool()); - } - - QVariant majorTickMarksAngle = properties["majorTickMarksAngle"]; - if (majorTickMarksAngle.isValid()) { - setMajorTickMarksAngle(majorTickMarksAngle.toFloat()); - } - - QVariant minorTickMarksAngle = properties["minorTickMarksAngle"]; - if (minorTickMarksAngle.isValid()) { - setMinorTickMarksAngle(minorTickMarksAngle.toFloat()); - } - - QVariant majorTickMarksLength = properties["majorTickMarksLength"]; - if (majorTickMarksLength.isValid()) { - setMajorTickMarksLength(majorTickMarksLength.toFloat()); - } - - QVariant minorTickMarksLength = properties["minorTickMarksLength"]; - if (minorTickMarksLength.isValid()) { - setMinorTickMarksLength(minorTickMarksLength.toFloat()); - } - - bool valid; - auto majorTickMarksColor = properties["majorTickMarksColor"]; - if (majorTickMarksColor.isValid()) { - auto color = xColorFromVariant(majorTickMarksColor, valid); - if (valid) { - _majorTickMarksColor = color; - } - } - - auto minorTickMarksColor = properties["minorTickMarksColor"]; - if (minorTickMarksColor.isValid()) { - auto color = xColorFromVariant(majorTickMarksColor, valid); - if (valid) { - _minorTickMarksColor = color; - } + if (_innerStartAlpha < 1.0f || _innerEndAlpha < 1.0f || _outerStartAlpha < 1.0f || _outerEndAlpha < 1.0f) { + // Force the alpha to 0.5, since we'll ignore it in the presence of these other values, but we need + // it to be non-1 in order to get the right pipeline and non-0 in order to render at all. + _alpha = 0.5f; } } diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index c0e84ef1c6..39f1ade7e9 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -59,28 +59,34 @@ public: virtual Circle3DOverlay* createClone() const override; protected: - float _startAt; - float _endAt; - float _outerRadius; - float _innerRadius; - bool _hasTickMarks; - float _majorTickMarksAngle; - float _minorTickMarksAngle; - float _majorTickMarksLength; - float _minorTickMarksLength; - xColor _majorTickMarksColor; - xColor _minorTickMarksColor; - - int _quadVerticesID; - int _lineVerticesID; - int _majorTicksVerticesID; - int _minorTicksVerticesID; + float _startAt { 0 }; + float _endAt { 360 }; + float _outerRadius { 1 }; + float _innerRadius { 0 }; - xColor _lastColor; - float _lastStartAt; - float _lastEndAt; - float _lastOuterRadius; - float _lastInnerRadius; + xColor _innerStartColor; + xColor _innerEndColor; + xColor _outerStartColor; + xColor _outerEndColor; + float _innerStartAlpha; + float _innerEndAlpha; + float _outerStartAlpha; + float _outerEndAlpha; + + bool _hasTickMarks { false }; + float _majorTickMarksAngle { 0 }; + float _minorTickMarksAngle { 0 }; + float _majorTickMarksLength { 0 }; + float _minorTickMarksLength { 0 }; + xColor _majorTickMarksColor {}; + xColor _minorTickMarksColor {}; + gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN }; + int _quadVerticesID { 0 }; + int _lineVerticesID { 0 }; + int _majorTicksVerticesID { 0 }; + int _minorTicksVerticesID { 0 }; + + bool _dirty { true }; }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index fd06272fa9..8be1c478be 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -432,7 +432,7 @@ void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, co renderQuad(batch, minCorner, maxCorner, MIN_TEX_COORD, MAX_TEX_COORD, color, id); } -void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { +void GeometryCache::updateVertices(int id, const QVector& points, const QVector& colors) { BatchItemDetails& details = _registeredVertices[id]; if (details.isCreated) { @@ -469,11 +469,6 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - float* vertexData = new float[details.vertices * FLOATS_PER_VERTEX]; float* vertex = vertexData; @@ -481,16 +476,25 @@ void GeometryCache::updateVertices(int id, const QVector& points, con int* colorDataAt = colorData; const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - foreach(const glm::vec2& point, points) { + auto pointCount = points.size(); + auto colorCount = colors.size(); + int compactColor; + for (auto i = 0; i < pointCount; ++i) { + const auto& point = points[i]; *(vertex++) = point.x; *(vertex++) = point.y; *(vertex++) = NORMAL.x; *(vertex++) = NORMAL.y; *(vertex++) = NORMAL.z; - + if (i < colorCount) { + const auto& color = colors[i]; + compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + } *(colorDataAt++) = compactColor; } - details.verticesBuffer->append(sizeof(float) * FLOATS_PER_VERTEX * details.vertices, (gpu::Byte*) vertexData); details.colorBuffer->append(sizeof(int) * details.vertices, (gpu::Byte*) colorData); delete[] vertexData; @@ -501,7 +505,11 @@ void GeometryCache::updateVertices(int id, const QVector& points, con #endif } -void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { +void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { + updateVertices(id, points, QVector({ color })); +} + +void GeometryCache::updateVertices(int id, const QVector& points, const QVector& colors) { BatchItemDetails& details = _registeredVertices[id]; if (details.isCreated) { details.clear(); @@ -537,11 +545,8 @@ void GeometryCache::updateVertices(int id, const QVector& points, con details.vertices = points.size(); details.vertexSize = FLOATS_PER_VERTEX; - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - + // Default to white + int compactColor = 0xFFFFFFFF; float* vertexData = new float[details.vertices * FLOATS_PER_VERTEX]; float* vertex = vertexData; @@ -549,14 +554,23 @@ void GeometryCache::updateVertices(int id, const QVector& points, con int* colorDataAt = colorData; const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); - foreach(const glm::vec3& point, points) { + auto pointCount = points.size(); + auto colorCount = colors.size(); + for (auto i = 0; i < pointCount; ++i) { + const glm::vec3& point = points[i]; + if (i < colorCount) { + const glm::vec4& color = colors[i]; + compactColor = ((int(color.x * 255.0f) & 0xFF)) | + ((int(color.y * 255.0f) & 0xFF) << 8) | + ((int(color.z * 255.0f) & 0xFF) << 16) | + ((int(color.w * 255.0f) & 0xFF) << 24); + } *(vertex++) = point.x; *(vertex++) = point.y; *(vertex++) = point.z; *(vertex++) = NORMAL.x; *(vertex++) = NORMAL.y; *(vertex++) = NORMAL.z; - *(colorDataAt++) = compactColor; } @@ -570,6 +584,10 @@ void GeometryCache::updateVertices(int id, const QVector& points, con #endif } +void GeometryCache::updateVertices(int id, const QVector& points, const glm::vec4& color) { + updateVertices(id, points, QVector({ color })); +} + void GeometryCache::updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color) { BatchItemDetails& details = _registeredVertices[id]; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 647fa9889c..1bfdc1798e 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -283,7 +283,9 @@ public: const glm::vec4& color1, const glm::vec4& color2, int id = UNKNOWN_ID); void updateVertices(int id, const QVector& points, const glm::vec4& color); + void updateVertices(int id, const QVector& points, const QVector& colors); void updateVertices(int id, const QVector& points, const glm::vec4& color); + void updateVertices(int id, const QVector& points, const QVector& colors); void updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color); void renderVertices(gpu::Batch& batch, gpu::Primitive primitiveType, int id); @@ -360,7 +362,7 @@ private: QHash _coneVBOs; - int _nextID{ 0 }; + int _nextID{ 1 }; QHash _lastRegisteredQuad3DTexture; QHash _quad3DTextures; From 2c72037e81872d61e055218aadb5773b908d5f2b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 8 Jul 2016 18:35:50 -0700 Subject: [PATCH 22/29] Fixing warnings --- interface/src/ui/overlays/Circle3DOverlay.cpp | 16 +++++++++------- interface/src/ui/overlays/Circle3DOverlay.h | 4 ++-- libraries/render-utils/src/GeometryCache.cpp | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 22995cbc35..6ebfd5c71c 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -14,10 +14,12 @@ #include #include - QString const Circle3DOverlay::TYPE = "circle3d"; -Circle3DOverlay::Circle3DOverlay() { } +Circle3DOverlay::Circle3DOverlay() { + memset(&_minorTickMarksColor, 0, sizeof(_minorTickMarksColor)); + memset(&_majorTickMarksColor, 0, sizeof(_majorTickMarksColor)); +} Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : Planar3DOverlay(circle3DOverlay), @@ -84,10 +86,10 @@ void Circle3DOverlay::render(RenderArgs* args) { float pulseLevel = updatePulse(); vec4 pulseModifier = vec4(1); - if (_alphaPulse != 0.0) { + if (_alphaPulse != 0.0f) { pulseModifier.a = (_alphaPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); } - if (_colorPulse != 0.0) { + if (_colorPulse != 0.0f) { float pulseValue = (_colorPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); pulseModifier = vec4(vec3(pulseValue), pulseModifier.a); } @@ -103,7 +105,7 @@ void Circle3DOverlay::render(RenderArgs* args) { for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { float range = (angle - _startAt) / (_endAt - _startAt); float angleRadians = glm::radians(angle); - points << glm::vec2(cos(angleRadians) * _outerRadius, sin(angleRadians) * _outerRadius); + points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); colors << glm::mix(outerStartColor, outerEndColor, range); } } else { @@ -112,10 +114,10 @@ void Circle3DOverlay::render(RenderArgs* args) { float range = (angle - _startAt) / (_endAt - _startAt); float angleRadians = glm::radians(angle); - points << glm::vec2(cos(angleRadians) * _innerRadius, sin(angleRadians) * _innerRadius); + points << glm::vec2(cosf(angleRadians) * _innerRadius, sinf(angleRadians) * _innerRadius); colors << glm::mix(innerStartColor, innerEndColor, range); - points << glm::vec2(cos(angleRadians) * _outerRadius, sin(angleRadians) * _outerRadius); + points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); colors << glm::mix(outerStartColor, outerEndColor, range); } } diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 39f1ade7e9..82c7c47dc7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -78,8 +78,8 @@ protected: float _minorTickMarksAngle { 0 }; float _majorTickMarksLength { 0 }; float _minorTickMarksLength { 0 }; - xColor _majorTickMarksColor {}; - xColor _minorTickMarksColor {}; + xColor _majorTickMarksColor; + xColor _minorTickMarksColor; gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN }; int _quadVerticesID { 0 }; int _lineVerticesID { 0 }; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 8be1c478be..4558b68af9 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -478,7 +478,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); auto pointCount = points.size(); auto colorCount = colors.size(); - int compactColor; + int compactColor = 0; for (auto i = 0; i < pointCount; ++i) { const auto& point = points[i]; *(vertex++) = point.x; From 207ddcea3863805597f066c23efe32192fcfa02d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Sat, 9 Jul 2016 22:25:28 -0700 Subject: [PATCH 23/29] wrap hull about each mesh part --- .../src/RenderableModelEntityItem.cpp | 84 ++++++++++++++++--- libraries/physics/src/ShapeFactory.cpp | 58 ++++++++++++- libraries/shared/src/ShapeInfo.h | 6 +- 3 files changed, 133 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 416a38d27c..9f4ff63548 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -690,8 +691,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. - for (int i = 0; i < pointCollection.size(); i++) { - for (int j = 0; j < pointCollection[i].size(); j++) { + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { // compensate for registration pointCollection[i][j] += _model->getOffset(); // scale so the collision points match the model points @@ -708,9 +709,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // compute meshPart local transforms QVector localTransforms; const FBXGeometry& fbxGeometry = _model->getFBXGeometry(); - int numberOfMeshes = fbxGeometry.meshes.size(); - int totalNumVertices = 0; - for (int i = 0; i < numberOfMeshes; i++) { + int32_t numMeshes = (int32_t)fbxGeometry.meshes.size(); + int32_t totalNumVertices = 0; + for (int32_t i = 0; i < numMeshes; i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.clusters.size() > 0) { const FBXCluster& cluster = mesh.clusters.at(0); @@ -722,7 +723,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } totalNumVertices += mesh.vertices.size(); } - const int MAX_VERTICES_PER_STATIC_MESH = 1e6; + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; info.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); @@ -730,7 +731,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); - int32_t numMeshes = (int32_t)(meshes.size()); + + // the render geometry's mesh count should match that of the FBXGeometry + assert(numMeshes == (int32_t)(meshes.size())); ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); pointCollection.clear(); @@ -741,8 +744,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } Extents extents; - int meshCount = 0; - int pointListIndex = 0; + int32_t meshCount = 0; + int32_t pointListIndex = 0; for (auto& mesh : meshes) { const gpu::BufferView& vertices = mesh->getVertexBuffer(); const gpu::BufferView& indices = mesh->getIndexBuffer(); @@ -775,6 +778,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { if (type == SHAPE_TYPE_STATIC_MESH) { // copy into triangleIndices ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + triangleIndices.clear(); triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements())); gpu::BufferView::Iterator partItr = parts.cbegin(); while (partItr != parts.cend()) { @@ -823,6 +827,64 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } ++partItr; } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + triangleIndices.clear(); + gpu::BufferView::Iterator partItr = parts.cbegin(); + while (partItr != parts.cend()) { + // collect unique list of indices for this part + std::set uniqueIndices; + if (partItr->_topology == model::Mesh::TRIANGLES) { + assert(partItr->_numIndices % 3 == 0); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + partItr->_numIndices; + while (indexItr != indexEnd) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + } else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) { + assert(partItr->_numIndices > 2); + auto indexItr = indices.cbegin() + partItr->_startIndex; + auto indexEnd = indexItr + (partItr->_numIndices - 2); + + // first triangle uses the first three indices + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + uniqueIndices.insert(*(indexItr++)); + + // the rest use previous and next index + uint32_t triangleCount = 1; + while (indexItr != indexEnd) { + if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) { + if (triangleCount % 2 == 0) { + // even triangles use first two indices in order + uniqueIndices.insert(*(indexItr - 2)); + uniqueIndices.insert(*(indexItr - 1)); + } else { + // odd triangles swap order of first two indices + uniqueIndices.insert(*(indexItr - 1)); + uniqueIndices.insert(*(indexItr - 2)); + } + uniqueIndices.insert(*indexItr); + ++triangleCount; + } + ++indexItr; + } + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + + ++partItr; + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); } ++meshCount; } @@ -830,13 +892,13 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // scale and shift glm::vec3 extentsSize = extents.size(); glm::vec3 scaleToFit = dimensions / extentsSize; - for (int i = 0; i < 3; ++i) { + for (int32_t i = 0; i < 3; ++i) { if (extentsSize[i] < 1.0e-6f) { scaleToFit[i] = 1.0f; } } for (auto points : pointCollection) { - for (int i = 0; i < points.size(); ++i) { + for (int32_t i = 0; i < points.size(); ++i) { points[i] = (points[i] * scaleToFit); } } diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index f71711eccd..e47a0fe8a2 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -69,6 +69,7 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = { // util method btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { + //std::cout << "adebug createConvexHull() points.size() = " << points.size() << std::endl; // adebug assert(points.size() > 0); btConvexHullShape* hull = new btConvexHullShape(); @@ -240,6 +241,7 @@ void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); + //std::cout << "adebug createShapeFromInfo() type = " << type << std::endl; // adebug switch(type) { case SHAPE_TYPE_BOX: { shape = new btBoxShape(glmToBullet(info.getHalfExtents())); @@ -258,8 +260,7 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } break; case SHAPE_TYPE_COMPOUND: - case SHAPE_TYPE_SIMPLE_HULL: - case SHAPE_TYPE_SIMPLE_COMPOUND: { + case SHAPE_TYPE_SIMPLE_HULL: { const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); uint32_t numSubShapes = info.getNumSubShapes(); if (numSubShapes == 1) { @@ -270,12 +271,63 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { trans.setIdentity(); foreach (const ShapeInfo::PointList& hullPoints, pointCollection) { btConvexHullShape* hull = createConvexHull(hullPoints); - compound->addChildShape (trans, hull); + compound->addChildShape(trans, hull); } shape = compound; } } break; + case SHAPE_TYPE_SIMPLE_COMPOUND: { + const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); + const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); + uint32_t numIndices = triangleIndices.size(); + uint32_t numMeshes = info.getNumSubShapes(); + const uint32_t MIN_NUM_SIMPLE_COMPOUND_INDICES = 2; // END_OF_MESH_PART + END_OF_MESH + if (numMeshes > 0 && numIndices > MIN_NUM_SIMPLE_COMPOUND_INDICES) { + uint32_t i = 0; + std::vector hulls; + for (auto& points : pointCollection) { + // build a hull around each part + while (i < numIndices) { + ShapeInfo::PointList hullPoints; + hullPoints.reserve(points.size()); + while (i < numIndices) { + int32_t j = triangleIndices[i]; + ++i; + if (j == END_OF_MESH_PART) { + // end of part + break; + } + hullPoints.push_back(points[j]); + } + if (hullPoints.size() > 0) { + btConvexHullShape* hull = createConvexHull(hullPoints); + hulls.push_back(hull); + } + + assert(i < numIndices); + if (triangleIndices[i] == END_OF_MESH) { + // end of mesh + ++i; + break; + } + } + } + uint32_t numHulls = hulls.size(); + if (numHulls == 1) { + shape = hulls[0]; + } else { + auto compound = new btCompoundShape(); + btTransform trans; + trans.setIdentity(); + for (auto hull : hulls) { + compound->addChildShape(trans, hull); + } + shape = compound; + } + } + } + break; case SHAPE_TYPE_STATIC_MESH: { btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info); shape = new StaticMeshShape(dataArray); diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 7bf145412a..a6ff8d6d4a 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -26,6 +26,10 @@ const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored // trim convex hulls with many points down to only 42 points. const int MAX_HULL_POINTS = 42; + +const int32_t END_OF_MESH_PART = -1; // bogus vertex index at end of mesh part +const int32_t END_OF_MESH = -2; // bogus vertex index at end of mesh + enum ShapeType { SHAPE_TYPE_NONE, SHAPE_TYPE_BOX, @@ -50,7 +54,7 @@ public: using PointList = QVector; using PointCollection = QVector; - using TriangleIndices = QVector; + using TriangleIndices = QVector; void clear(); From 72175ae09e1510de3adef4fcb2b90e515cc05a0a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Sat, 9 Jul 2016 22:55:55 -0700 Subject: [PATCH 24/29] fix warnings about implicit casts --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- libraries/physics/src/ShapeFactory.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9f4ff63548..d63361538a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -874,7 +874,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } // store uniqueIndices in triangleIndices - triangleIndices.reserve(triangleIndices.size() + uniqueIndices.size()); + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); for (auto index : uniqueIndices) { triangleIndices.push_back(index); } diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index e47a0fe8a2..944e05e571 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -313,7 +313,7 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } } } - uint32_t numHulls = hulls.size(); + uint32_t numHulls = (uint32_t)hulls.size(); if (numHulls == 1) { shape = hulls[0]; } else { From 0efc684992f44fadd687de16a90984b5225f7671 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 11 Jul 2016 14:19:27 -0700 Subject: [PATCH 25/29] Add glow to the termination point of UI pointers --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 253 ++++++++++++++++-- .../display-plugins/hmd/HmdDisplayPlugin.h | 18 ++ 2 files changed, 244 insertions(+), 27 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index c2497e5740..2dc7df341a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include #include @@ -34,6 +36,9 @@ static const QString REPROJECTION = "Allow Reprojection"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; static const QString DEVELOPER_MENU_PATH = "Developer>" + DisplayPlugin::MENU_PATH(); static const bool DEFAULT_MONO_VIEW = true; +static const int NUMBER_OF_HANDS = 2; +static const glm::mat4 IDENTITY_MATRIX; + glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; @@ -241,6 +246,9 @@ void main() { )VS"; +static const char * LASER_GS = R"GS( +)GS"; + static const char * LASER_FS = R"FS(#version 410 core uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); @@ -254,6 +262,85 @@ void main() { )FS"; + +static const char * LASER_GLOW_VS = R"VS(#version 410 core + +uniform mat4 mvp = mat4(1); + +in vec3 Position; +in vec2 TexCoord; + +out vec3 vPosition; +out vec2 vTexCoord; + +void main() { + gl_Position = mvp * vec4(Position, 1); + vTexCoord = TexCoord; + vPosition = Position; +} + +)VS"; + +static const char * LASER_GLOW_FS = R"FS(#version 410 core +#line 286 +uniform sampler2D sampler; +uniform float alpha = 1.0; +uniform vec4 glowPoints = vec4(-1); +uniform vec4 glowColor1 = vec4(0.01, 0.7, 0.9, 1.0); +uniform vec4 glowColor2 = vec4(0.9, 0.7, 0.01, 1.0); +uniform vec2 resolution = vec2(3960.0, 1188.0); +uniform float radius = 0.005; + +in vec3 vPosition; +in vec2 vTexCoord; +in vec4 vGlowPoints; + +out vec4 FragColor; + +float easeInOutCubic(float f) { + const float d = 1.0; + const float b = 0.0; + const float c = 1.0; + float t = f; + if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; + return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; +} + +void main() { + vec2 aspect = resolution; + aspect /= resolution.x; + FragColor = texture(sampler, vTexCoord); + + float glowIntensity = 0.0; + float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); + float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); + float dist = min(dist1, dist2); + vec3 glowColor = glowColor1.rgb; + if (dist2 < dist1) { + glowColor = glowColor2.rgb; + } + + if (dist <= radius) { + glowIntensity = 1.0 - (dist / radius); + glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); + glowIntensity = easeInOutCubic(glowIntensity); + glowIntensity = pow(glowIntensity, 0.5); + } + + if (alpha <= 0.0) { + if (glowIntensity <= 0.0) { + discard; + } + + FragColor = vec4(glowColor, glowIntensity); + return; + } + + FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); + FragColor.a *= alpha; +} +)FS"; + void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled @@ -263,7 +350,6 @@ void HmdDisplayPlugin::customizeContext() { #endif _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); - using namespace oglplus; if (!_enablePreview) { const std::string version("#version 410 core\n"); @@ -271,6 +357,7 @@ void HmdDisplayPlugin::customizeContext() { PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); } + updateGlowProgram(); compileProgram(_laserProgram, LASER_VS, LASER_FS); _laserGeometry = loadLaser(_laserProgram); @@ -280,7 +367,67 @@ void HmdDisplayPlugin::customizeContext() { PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); } +#if 0 +static QString readFile(const char* filename) { + QFile file(filename); + file.open(QFile::Text | QFile::ReadOnly); + QString result; + result.append(QTextStream(&file).readAll()); + return result; +} +#endif + + +void HmdDisplayPlugin::updateGlowProgram() { +#if 0 + static qint64 vsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + static const char* vsFile = "H:/glow_vert.glsl"; + static const char* fsFile = "H:/glow_frag.glsl"; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + if (!_overlayProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + ProgramPtr newOverlayProgram; + compileProgram(newOverlayProgram, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (newOverlayProgram) { + using namespace oglplus; + _overlayProgram = newOverlayProgram; + auto uniforms = _overlayProgram->ActiveUniforms(); + _overlayUniforms.mvp = Uniform(*_overlayProgram, "mvp").Location(); + _overlayUniforms.alpha = Uniform(*_overlayProgram, "alpha").Location(); + _overlayUniforms.glowPoints = Uniform(*_overlayProgram, "glowPoints").Location(); + _overlayUniforms.resolution = Uniform(*_overlayProgram, "resolution").Location(); + _overlayUniforms.radius = Uniform(*_overlayProgram, "radius").Location(); + useProgram(_overlayProgram); + Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); + } + vsBuiltAge = vsAge; + fsBuiltAge = fsAge; + + } +#else + if (!_overlayProgram) { + compileProgram(_overlayProgram, LASER_GLOW_VS, LASER_GLOW_FS); + using namespace oglplus; + auto uniforms = _overlayProgram->ActiveUniforms(); + _overlayUniforms.mvp = Uniform(*_overlayProgram, "mvp").Location(); + _overlayUniforms.alpha = Uniform(*_overlayProgram, "alpha").Location(); + _overlayUniforms.glowPoints = Uniform(*_overlayProgram, "glowPoints").Location(); + _overlayUniforms.resolution = Uniform(*_overlayProgram, "resolution").Location(); + _overlayUniforms.radius = Uniform(*_overlayProgram, "radius").Location(); + useProgram(_overlayProgram); + Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); + } + +#endif +} + void HmdDisplayPlugin::uncustomizeContext() { + _overlayProgram.reset(); _sphereSection.reset(); _compositeFramebuffer.reset(); _previewProgram.reset(); @@ -327,19 +474,83 @@ void HmdDisplayPlugin::compositeOverlay() { auto compositorHelper = DependencyManager::get(); glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - useProgram(_program); - // set the alpha - Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); + withPresentThreadLock([&] { + _presentHandLasers = _handLasers; + _presentHandPoses = _handPoses; + _presentUiModelTransform = _uiModelTransform; + }); + std::array handGlowPoints { { vec2(-1), vec2(-1) } }; + + // compute the glow point interesections + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { + if (_presentHandPoses[i] == IDENTITY_MATRIX) { + continue; + } + const auto& handLaser = _presentHandLasers[i]; + if (!handLaser.valid()) { + continue; + } + + const auto& laserDirection = handLaser.direction; + auto model = _presentHandPoses[i]; + auto castDirection = glm::quat_cast(model) * laserDirection; + if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { + castDirection = glm::normalize(castDirection); + castDirection = glm::inverse(_presentUiModelTransform.getRotation()) * castDirection; + } + + // FIXME fetch the actual UI radius from... somewhere? + float uiRadius = 1.0f; + + // Find the intersection of the laser with he UI and use it to scale the model matrix + float distance; + if (!glm::intersectRaySphere(vec3(_presentHandPoses[i][3]), castDirection, _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { + continue; + } + + vec3 intersectionPosition = vec3(_presentHandPoses[i][3]) + (castDirection * distance) - _presentUiModelTransform.getTranslation(); + intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; + + // Take the interesection normal and convert it to a texture coordinate + vec2 yawPitch; + { + vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); + yawPitch.x = glm::atan(xdir.x, xdir.y); + yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + M_PI_2; + } + vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; + + // Are we out of range + if (glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { + continue; + } + + yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; + yawPitch += 0.5f; + handGlowPoints[i] = yawPitch; + } + + updateGlowProgram(); + useProgram(_overlayProgram); + // Setup the uniforms + { + if (_overlayUniforms.alpha >= 0) { + Uniform(*_overlayProgram, _overlayUniforms.alpha).Set(_compositeOverlayAlpha); + } + if (_overlayUniforms.glowPoints >= 0) { + vec4 glowPoints(handGlowPoints[0], handGlowPoints[1]); + Uniform(*_overlayProgram, _overlayUniforms.glowPoints).Set(glowPoints); + } + } + _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; auto mvp = _eyeProjections[eye] * modelView; - Uniform(*_program, _mvpUniform).Set(mvp); + Uniform(*_overlayProgram, _overlayUniforms.mvp).Set(mvp); _sphereSection->Draw(); }); - // restore the alpha - Uniform(*_program, _alphaUniform).Set(1.0); } void HmdDisplayPlugin::compositePointer() { @@ -478,23 +689,12 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } void HmdDisplayPlugin::compositeExtra() { - const int NUMBER_OF_HANDS = 2; - std::array handLasers; - std::array renderHandPoses; - Transform uiModelTransform; - withPresentThreadLock([&] { - handLasers = _handLasers; - renderHandPoses = _handPoses; - uiModelTransform = _uiModelTransform; - }); - // If neither hand laser is activated, exit - if (!handLasers[0].valid() && !handLasers[1].valid()) { + if (!_presentHandLasers[0].valid() && !_presentHandLasers[1].valid()) { return; } - static const glm::mat4 identity; - if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) { + if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX) { return; } @@ -505,16 +705,16 @@ void HmdDisplayPlugin::compositeExtra() { std::array handLaserModelMatrices; for (int i = 0; i < NUMBER_OF_HANDS; ++i) { - if (renderHandPoses[i] == identity) { + if (_presentHandPoses[i] == IDENTITY_MATRIX) { continue; } - const auto& handLaser = handLasers[i]; + const auto& handLaser = _presentHandLasers[i]; if (!handLaser.valid()) { continue; } const auto& laserDirection = handLaser.direction; - auto model = renderHandPoses[i]; + auto model = _presentHandPoses[i]; auto castDirection = glm::quat_cast(model) * laserDirection; if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { castDirection = glm::normalize(castDirection); @@ -525,7 +725,7 @@ void HmdDisplayPlugin::compositeExtra() { // Find the intersection of the laser with he UI and use it to scale the model matrix float distance; - if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { + if (!glm::intersectRaySphere(vec3(_presentHandPoses[i][3]), castDirection, _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { continue; } @@ -545,13 +745,12 @@ void HmdDisplayPlugin::compositeExtra() { auto view = glm::inverse(eyePose); const auto& projection = _eyeProjections[eye]; for (int i = 0; i < NUMBER_OF_HANDS; ++i) { - if (handLaserModelMatrices[i] == identity) { + if (handLaserModelMatrices[i] == IDENTITY_MATRIX) { continue; } Uniform(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]); - Uniform(*_laserProgram, "color").Set(handLasers[i].color); + Uniform(*_laserProgram, "color").Set(_presentHandLasers[i].color); _laserGeometry->Draw(); - // TODO render some kind of visual indicator at the intersection point with the UI. } }); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 8e48690fd1..be2811076d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -61,13 +61,29 @@ protected: } }; + ProgramPtr _overlayProgram; + struct OverlayUniforms { + int32_t mvp { -1 }; + int32_t alpha { -1 }; + int32_t glowPoints { -1 }; + int32_t resolution { -1 }; + int32_t radius { -1 }; + } _overlayUniforms; + Transform _uiModelTransform; std::array _handLasers; std::array _handPoses; + + Transform _presentUiModelTransform; + std::array _presentHandLasers; + std::array _presentHandPoses; + std::array _eyeOffsets; std::array _eyeProjections; std::array _eyeInverseProjections; + + glm::mat4 _cullingProjection; glm::uvec2 _renderTargetSize; float _ipd { 0.064f }; @@ -87,6 +103,8 @@ protected: FrameInfo _currentRenderFrameInfo; private: + void updateGlowProgram(); + bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; From 6242816f68b47c74f20479bcc5fdabac5188f21f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 11 Jul 2016 18:59:22 -0700 Subject: [PATCH 26/29] Adding glow to hand lasers --- .../resources/shaders/hmd_hand_lasers.frag | 35 ++ .../resources/shaders/hmd_hand_lasers.geom | 70 +++ .../resources/shaders/hmd_hand_lasers.vert | 15 + .../resources/shaders/hmd_reproject.frag | 78 ++++ .../resources/shaders/hmd_reproject.vert | 20 + interface/resources/shaders/hmd_ui_glow.frag | 65 +++ interface/resources/shaders/hmd_ui_glow.vert | 23 + .../display-plugins/hmd/HmdDisplayPlugin.cpp | 425 ++++++------------ .../display-plugins/hmd/HmdDisplayPlugin.h | 39 +- libraries/gl/src/gl/OglplusHelpers.cpp | 30 ++ libraries/gl/src/gl/OglplusHelpers.h | 2 + 11 files changed, 493 insertions(+), 309 deletions(-) create mode 100644 interface/resources/shaders/hmd_hand_lasers.frag create mode 100644 interface/resources/shaders/hmd_hand_lasers.geom create mode 100644 interface/resources/shaders/hmd_hand_lasers.vert create mode 100644 interface/resources/shaders/hmd_reproject.frag create mode 100644 interface/resources/shaders/hmd_reproject.vert create mode 100644 interface/resources/shaders/hmd_ui_glow.frag create mode 100644 interface/resources/shaders/hmd_ui_glow.vert diff --git a/interface/resources/shaders/hmd_hand_lasers.frag b/interface/resources/shaders/hmd_hand_lasers.frag new file mode 100644 index 0000000000..6fffb1c521 --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.frag @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); + +layout(location = 0) in vec3 inLineDistance; + +out vec4 FragColor; + +void main() { + vec2 d = inLineDistance.xy; + d.y = abs(d.y); + d.x = abs(d.x); + if (d.x > 1.0) { + d.x = (d.x - 1.0) / 0.02; + } else { + d.x = 0.0; + } + float alpha = 1.0 - length(d); + if (alpha <= 0.0) { + discard; + } + alpha = pow(alpha, 10.0); + if (alpha < 0.05) { + discard; + } + FragColor = vec4(color.rgb, alpha); +} diff --git a/interface/resources/shaders/hmd_hand_lasers.geom b/interface/resources/shaders/hmd_hand_lasers.geom new file mode 100644 index 0000000000..16b5dafadd --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.geom @@ -0,0 +1,70 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// +#version 410 core +#extension GL_EXT_geometry_shader4 : enable + +layout(location = 0) out vec3 outLineDistance; + +layout(lines) in; +layout(triangle_strip, max_vertices = 24) out; + +vec3[2] getOrthogonals(in vec3 n, float scale) { + float yDot = abs(dot(n, vec3(0, 1, 0))); + + vec3 result[2]; + if (yDot < 0.9) { + result[0] = normalize(cross(n, vec3(0, 1, 0))); + } else { + result[0] = normalize(cross(n, vec3(1, 0, 0))); + } + // The cross of result[0] and n is orthogonal to both, which are orthogonal to each other + result[1] = cross(result[0], n); + result[0] *= scale; + result[1] *= scale; + return result; +} + + +vec2 orthogonal(vec2 v) { + vec2 result = v.yx; + result.y *= -1.0; + return result; +} + +void main() { + vec2 endpoints[2]; + for (int i = 0; i < 2; ++i) { + endpoints[i] = gl_PositionIn[i].xy / gl_PositionIn[i].w; + } + vec2 lineNormal = normalize(endpoints[1] - endpoints[0]); + vec2 lineOrthogonal = orthogonal(lineNormal); + lineNormal *= 0.02; + lineOrthogonal *= 0.02; + + gl_Position = gl_PositionIn[0]; + gl_Position.xy -= lineOrthogonal; + outLineDistance = vec3(-1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[0]; + gl_Position.xy += lineOrthogonal; + outLineDistance = vec3(-1.02, 1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy -= lineOrthogonal; + outLineDistance = vec3(1.02, -1, gl_Position.z); + EmitVertex(); + + gl_Position = gl_PositionIn[1]; + gl_Position.xy += lineOrthogonal; + outLineDistance = vec3(1.02, 1, gl_Position.z); + EmitVertex(); + + EndPrimitive(); +} diff --git a/interface/resources/shaders/hmd_hand_lasers.vert b/interface/resources/shaders/hmd_hand_lasers.vert new file mode 100644 index 0000000000..db5c7c1ecd --- /dev/null +++ b/interface/resources/shaders/hmd_hand_lasers.vert @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// +#version 410 core +uniform mat4 mvp = mat4(1); + +in vec3 Position; + +void main() { + gl_Position = mvp * vec4(Position, 1); +} diff --git a/interface/resources/shaders/hmd_reproject.frag b/interface/resources/shaders/hmd_reproject.frag new file mode 100644 index 0000000000..adda0315a3 --- /dev/null +++ b/interface/resources/shaders/hmd_reproject.frag @@ -0,0 +1,78 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform sampler2D sampler; +uniform mat3 reprojection = mat3(1); +uniform mat4 inverseProjections[2]; +uniform mat4 projections[2]; + +in vec2 vTexCoord; +in vec3 vPosition; + +out vec4 FragColor; + +void main() { + vec2 uv = vTexCoord; + + mat4 eyeInverseProjection; + mat4 eyeProjection; + + float xoffset = 1.0; + vec2 uvmin = vec2(0.0); + vec2 uvmax = vec2(1.0); + // determine the correct projection and inverse projection to use. + if (vTexCoord.x < 0.5) { + uvmax.x = 0.5; + eyeInverseProjection = inverseProjections[0]; + eyeProjection = projections[0]; + } else { + xoffset = -1.0; + uvmin.x = 0.5; + uvmax.x = 1.0; + eyeInverseProjection = inverseProjections[1]; + eyeProjection = projections[1]; + } + + // Account for stereo in calculating the per-eye NDC coordinates + vec4 ndcSpace = vec4(vPosition, 1.0); + ndcSpace.x *= 2.0; + ndcSpace.x += xoffset; + + // Convert from NDC to eyespace + vec4 eyeSpace = eyeInverseProjection * ndcSpace; + eyeSpace /= eyeSpace.w; + + // Convert to a noramlized ray + vec3 ray = eyeSpace.xyz; + ray = normalize(ray); + + // Adjust the ray by the rotation + ray = reprojection * ray; + + // Project back on to the texture plane + ray *= eyeSpace.z / ray.z; + + // Update the eyespace vector + eyeSpace.xyz = ray; + + // Reproject back into NDC + ndcSpace = eyeProjection * eyeSpace; + ndcSpace /= ndcSpace.w; + ndcSpace.x -= xoffset; + ndcSpace.x /= 2.0; + + // Calculate the new UV coordinates + uv = (ndcSpace.xy / 2.0) + 0.5; + if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { + FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } else { + FragColor = texture(sampler, uv); + } +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_reproject.vert b/interface/resources/shaders/hmd_reproject.vert new file mode 100644 index 0000000000..923375613a --- /dev/null +++ b/interface/resources/shaders/hmd_reproject.vert @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core +in vec3 Position; +in vec2 TexCoord; + +out vec3 vPosition; +out vec2 vTexCoord; + +void main() { + gl_Position = vec4(Position, 1); + vTexCoord = TexCoord; + vPosition = Position; +} diff --git a/interface/resources/shaders/hmd_ui_glow.frag b/interface/resources/shaders/hmd_ui_glow.frag new file mode 100644 index 0000000000..733f32d718 --- /dev/null +++ b/interface/resources/shaders/hmd_ui_glow.frag @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform sampler2D sampler; +uniform float alpha = 1.0; +uniform vec4 glowPoints = vec4(-1); +uniform vec4 glowColors[2]; +uniform vec2 resolution = vec2(3960.0, 1188.0); +uniform float radius = 0.005; + +in vec3 vPosition; +in vec2 vTexCoord; +in vec4 vGlowPoints; + +out vec4 FragColor; + +float easeInOutCubic(float f) { + const float d = 1.0; + const float b = 0.0; + const float c = 1.0; + float t = f; + if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; + return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; +} + +void main() { + vec2 aspect = resolution; + aspect /= resolution.x; + FragColor = texture(sampler, vTexCoord); + + float glowIntensity = 0.0; + float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); + float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); + float dist = min(dist1, dist2); + vec3 glowColor = glowColors[0].rgb; + if (dist2 < dist1) { + glowColor = glowColors[1].rgb; + } + + if (dist <= radius) { + glowIntensity = 1.0 - (dist / radius); + glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); + glowIntensity = easeInOutCubic(glowIntensity); + glowIntensity = pow(glowIntensity, 0.5); + } + + if (alpha <= 0.0) { + if (glowIntensity <= 0.0) { + discard; + } + + FragColor = vec4(glowColor, glowIntensity); + return; + } + + FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); + FragColor.a *= alpha; +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui_glow.vert b/interface/resources/shaders/hmd_ui_glow.vert new file mode 100644 index 0000000000..5defec085f --- /dev/null +++ b/interface/resources/shaders/hmd_ui_glow.vert @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 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 +// + +#version 410 core + +uniform mat4 mvp = mat4(1); + +in vec3 Position; +in vec2 TexCoord; + +out vec3 vPosition; +out vec2 vTexCoord; + +void main() { + gl_Position = mvp * vec4(Position, 1); + vTexCoord = TexCoord; + vPosition = Position; +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 2dc7df341a..1bfa6c7921 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -95,252 +95,6 @@ void HmdDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); } - -static const char * REPROJECTION_VS = R"VS(#version 410 core -in vec3 Position; -in vec2 TexCoord; - -out vec3 vPosition; -out vec2 vTexCoord; - -void main() { - gl_Position = vec4(Position, 1); - vTexCoord = TexCoord; - vPosition = Position; -} - -)VS"; - -static GLint REPROJECTION_MATRIX_LOCATION = -1; -static GLint INVERSE_PROJECTION_MATRIX_LOCATION = -1; -static GLint PROJECTION_MATRIX_LOCATION = -1; -static const char * REPROJECTION_FS = R"FS(#version 410 core - -uniform sampler2D sampler; -uniform mat3 reprojection = mat3(1); -uniform mat4 inverseProjections[2]; -uniform mat4 projections[2]; - -in vec2 vTexCoord; -in vec3 vPosition; - -out vec4 FragColor; - -void main() { - vec2 uv = vTexCoord; - - mat4 eyeInverseProjection; - mat4 eyeProjection; - - float xoffset = 1.0; - vec2 uvmin = vec2(0.0); - vec2 uvmax = vec2(1.0); - // determine the correct projection and inverse projection to use. - if (vTexCoord.x < 0.5) { - uvmax.x = 0.5; - eyeInverseProjection = inverseProjections[0]; - eyeProjection = projections[0]; - } else { - xoffset = -1.0; - uvmin.x = 0.5; - uvmax.x = 1.0; - eyeInverseProjection = inverseProjections[1]; - eyeProjection = projections[1]; - } - - // Account for stereo in calculating the per-eye NDC coordinates - vec4 ndcSpace = vec4(vPosition, 1.0); - ndcSpace.x *= 2.0; - ndcSpace.x += xoffset; - - // Convert from NDC to eyespace - vec4 eyeSpace = eyeInverseProjection * ndcSpace; - eyeSpace /= eyeSpace.w; - - // Convert to a noramlized ray - vec3 ray = eyeSpace.xyz; - ray = normalize(ray); - - // Adjust the ray by the rotation - ray = reprojection * ray; - - // Project back on to the texture plane - ray *= eyeSpace.z / ray.z; - - // Update the eyespace vector - eyeSpace.xyz = ray; - - // Reproject back into NDC - ndcSpace = eyeProjection * eyeSpace; - ndcSpace /= ndcSpace.w; - ndcSpace.x -= xoffset; - ndcSpace.x /= 2.0; - - // Calculate the new UV coordinates - uv = (ndcSpace.xy / 2.0) + 0.5; - if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { - FragColor = vec4(0.0, 0.0, 0.0, 1.0); - } else { - FragColor = texture(sampler, uv); - } -} -)FS"; - -#ifdef DEBUG_REPROJECTION_SHADER -#include -#include -#include -#include - -static const QString REPROJECTION_FS_FILE = "c:/Users/bdavis/Git/hifi/interface/resources/shaders/reproject.frag"; - -static ProgramPtr getReprojectionProgram() { - static ProgramPtr _currentProgram; - uint64_t now = usecTimestampNow(); - static uint64_t _lastFileCheck = now; - - bool modified = false; - if ((now - _lastFileCheck) > USECS_PER_MSEC * 100) { - QFileInfo info(REPROJECTION_FS_FILE); - QDateTime lastModified = info.lastModified(); - static QDateTime _lastModified = lastModified; - qDebug() << lastModified.toTime_t(); - qDebug() << _lastModified.toTime_t(); - if (lastModified > _lastModified) { - _lastModified = lastModified; - modified = true; - } - } - - if (!_currentProgram || modified) { - _currentProgram.reset(); - try { - QFile shaderFile(REPROJECTION_FS_FILE); - shaderFile.open(QIODevice::ReadOnly); - QString fragment = shaderFile.readAll(); - compileProgram(_currentProgram, REPROJECTION_VS, fragment.toLocal8Bit().data()); - } catch (const std::runtime_error& error) { - qDebug() << "Failed to build: " << error.what(); - } - if (!_currentProgram) { - _currentProgram = loadDefaultShader(); - } - } - return _currentProgram; -} -#endif - -static GLint PREVIEW_TEXTURE_LOCATION = -1; - -static const char * LASER_VS = R"VS(#version 410 core -uniform mat4 mvp = mat4(1); - -in vec3 Position; - -out vec3 vPosition; - -void main() { - gl_Position = mvp * vec4(Position, 1); - vPosition = Position; -} - -)VS"; - -static const char * LASER_GS = R"GS( -)GS"; - -static const char * LASER_FS = R"FS(#version 410 core - -uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); -in vec3 vPosition; - -out vec4 FragColor; - -void main() { - FragColor = color; -} - -)FS"; - - -static const char * LASER_GLOW_VS = R"VS(#version 410 core - -uniform mat4 mvp = mat4(1); - -in vec3 Position; -in vec2 TexCoord; - -out vec3 vPosition; -out vec2 vTexCoord; - -void main() { - gl_Position = mvp * vec4(Position, 1); - vTexCoord = TexCoord; - vPosition = Position; -} - -)VS"; - -static const char * LASER_GLOW_FS = R"FS(#version 410 core -#line 286 -uniform sampler2D sampler; -uniform float alpha = 1.0; -uniform vec4 glowPoints = vec4(-1); -uniform vec4 glowColor1 = vec4(0.01, 0.7, 0.9, 1.0); -uniform vec4 glowColor2 = vec4(0.9, 0.7, 0.01, 1.0); -uniform vec2 resolution = vec2(3960.0, 1188.0); -uniform float radius = 0.005; - -in vec3 vPosition; -in vec2 vTexCoord; -in vec4 vGlowPoints; - -out vec4 FragColor; - -float easeInOutCubic(float f) { - const float d = 1.0; - const float b = 0.0; - const float c = 1.0; - float t = f; - if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; - return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; -} - -void main() { - vec2 aspect = resolution; - aspect /= resolution.x; - FragColor = texture(sampler, vTexCoord); - - float glowIntensity = 0.0; - float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); - float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); - float dist = min(dist1, dist2); - vec3 glowColor = glowColor1.rgb; - if (dist2 < dist1) { - glowColor = glowColor2.rgb; - } - - if (dist <= radius) { - glowIntensity = 1.0 - (dist / radius); - glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); - glowIntensity = easeInOutCubic(glowIntensity); - glowIntensity = pow(glowIntensity, 0.5); - } - - if (alpha <= 0.0) { - if (glowIntensity <= 0.0) { - discard; - } - - FragColor = vec4(glowColor, glowIntensity); - return; - } - - FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); - FragColor.a *= alpha; -} -)FS"; - void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled @@ -354,76 +108,139 @@ void HmdDisplayPlugin::customizeContext() { if (!_enablePreview) { const std::string version("#version 410 core\n"); compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); - PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); + _previewUniforms.previewTexture = Uniform(*_previewProgram, "colorMap").Location(); } - updateGlowProgram(); - compileProgram(_laserProgram, LASER_VS, LASER_FS); + updateReprojectionProgram(); + updateOverlayProgram(); + updateLaserProgram(); + _laserGeometry = loadLaser(_laserProgram); - - compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); - REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); - INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); - PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); } +//#define LIVE_SHADER_RELOAD 1 -#if 0 -static QString readFile(const char* filename) { +static QString readFile(const QString& filename) { QFile file(filename); file.open(QFile::Text | QFile::ReadOnly); QString result; result.append(QTextStream(&file).readAll()); return result; } -#endif - -void HmdDisplayPlugin::updateGlowProgram() { -#if 0 +void HmdDisplayPlugin::updateReprojectionProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.frag"; +#if LIVE_SHADER_RELOAD + static qint64 vsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + if (!_reprojectionProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { + vsBuiltAge = vsAge; + fsBuiltAge = fsAge; +#else + if (!_reprojectionProgram) { +#endif + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _reprojectionUniforms.reprojectionMatrix = Uniform(*program, "reprojection").Location(); + _reprojectionUniforms.inverseProjectionMatrix = Uniform(*program, "inverseProjections").Location(); + _reprojectionUniforms.projectionMatrix = Uniform(*program, "projections").Location(); + _reprojectionProgram = program; + } + } catch (std::runtime_error& error) { + qWarning() << "Error building reprojection shader " << error.what(); + } + } + +} + +void HmdDisplayPlugin::updateLaserProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.vert"; + static const QString gsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.geom"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.frag"; + +#if LIVE_SHADER_RELOAD + static qint64 vsBuiltAge = 0; + static qint64 gsBuiltAge = 0; + static qint64 fsBuiltAge = 0; + QFileInfo vsInfo(vsFile); + QFileInfo fsInfo(fsFile); + QFileInfo gsInfo(fsFile); + auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); + auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); + auto gsAge = gsInfo.lastModified().toMSecsSinceEpoch(); + if (!_laserProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge || gsAge > gsBuiltAge) { + vsBuiltAge = vsAge; + gsBuiltAge = gsAge; + fsBuiltAge = fsAge; +#else + if (!_laserProgram) { +#endif + + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + QString gsSource = readFile(gsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), gsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _laserUniforms.color = Uniform(*program, "color").Location(); + _laserUniforms.mvp = Uniform(*program, "mvp").Location(); + _laserProgram = program; + } + } catch (std::runtime_error& error) { + qWarning() << "Error building hand laser composite shader " << error.what(); + } + } +} + +void HmdDisplayPlugin::updateOverlayProgram() { + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.frag"; + +#if LIVE_SHADER_RELOAD static qint64 vsBuiltAge = 0; static qint64 fsBuiltAge = 0; - static const char* vsFile = "H:/glow_vert.glsl"; - static const char* fsFile = "H:/glow_frag.glsl"; QFileInfo vsInfo(vsFile); QFileInfo fsInfo(fsFile); auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); if (!_overlayProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { - QString vsSource = readFile(vsFile); - QString fsSource = readFile(fsFile); - ProgramPtr newOverlayProgram; - compileProgram(newOverlayProgram, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); - if (newOverlayProgram) { - using namespace oglplus; - _overlayProgram = newOverlayProgram; - auto uniforms = _overlayProgram->ActiveUniforms(); - _overlayUniforms.mvp = Uniform(*_overlayProgram, "mvp").Location(); - _overlayUniforms.alpha = Uniform(*_overlayProgram, "alpha").Location(); - _overlayUniforms.glowPoints = Uniform(*_overlayProgram, "glowPoints").Location(); - _overlayUniforms.resolution = Uniform(*_overlayProgram, "resolution").Location(); - _overlayUniforms.radius = Uniform(*_overlayProgram, "radius").Location(); - useProgram(_overlayProgram); - Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); - } vsBuiltAge = vsAge; fsBuiltAge = fsAge; - - } #else if (!_overlayProgram) { - compileProgram(_overlayProgram, LASER_GLOW_VS, LASER_GLOW_FS); - using namespace oglplus; - auto uniforms = _overlayProgram->ActiveUniforms(); - _overlayUniforms.mvp = Uniform(*_overlayProgram, "mvp").Location(); - _overlayUniforms.alpha = Uniform(*_overlayProgram, "alpha").Location(); - _overlayUniforms.glowPoints = Uniform(*_overlayProgram, "glowPoints").Location(); - _overlayUniforms.resolution = Uniform(*_overlayProgram, "resolution").Location(); - _overlayUniforms.radius = Uniform(*_overlayProgram, "radius").Location(); - useProgram(_overlayProgram); - Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); - } - #endif + QString vsSource = readFile(vsFile); + QString fsSource = readFile(fsFile); + ProgramPtr program; + try { + compileProgram(program, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString()); + if (program) { + using namespace oglplus; + _overlayUniforms.mvp = Uniform(*program, "mvp").Location(); + _overlayUniforms.alpha = Uniform(*program, "alpha").Location(); + _overlayUniforms.glowColors = Uniform(*program, "glowColors").Location(); + _overlayUniforms.glowPoints = Uniform(*program, "glowPoints").Location(); + _overlayUniforms.resolution = Uniform(*program, "resolution").Location(); + _overlayUniforms.radius = Uniform(*program, "radius").Location(); + _overlayProgram = program; + useProgram(_overlayProgram); + Uniform(*_overlayProgram, _overlayUniforms.resolution).Set(CompositorHelper::VIRTUAL_SCREEN_SIZE); + } + } catch (std::runtime_error& error) { + qWarning() << "Error building overlay composite shader " << error.what(); + } + } } void HmdDisplayPlugin::uncustomizeContext() { @@ -459,12 +276,12 @@ void HmdDisplayPlugin::compositeScene() { using namespace oglplus; Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); - Uniform(*_reprojectionProgram, REPROJECTION_MATRIX_LOCATION).Set(_currentPresentFrameInfo.presentReprojection); + Uniform(*_reprojectionProgram, _reprojectionUniforms.reprojectionMatrix).Set(_currentPresentFrameInfo.presentReprojection); //Uniform(*_reprojectionProgram, PROJECTION_MATRIX_LOCATION).Set(_eyeProjections); //Uniform(*_reprojectionProgram, INVERSE_PROJECTION_MATRIX_LOCATION).Set(_eyeInverseProjections); // FIXME what's the right oglplus mechanism to do this? It's not that ^^^ ... better yet, switch to a uniform buffer - glUniformMatrix4fv(INVERSE_PROJECTION_MATRIX_LOCATION, 2, GL_FALSE, &(_eyeInverseProjections[0][0][0])); - glUniformMatrix4fv(PROJECTION_MATRIX_LOCATION, 2, GL_FALSE, &(_eyeProjections[0][0][0])); + glUniformMatrix4fv(_reprojectionUniforms.inverseProjectionMatrix, 2, GL_FALSE, &(_eyeInverseProjections[0][0][0])); + glUniformMatrix4fv(_reprojectionUniforms.projectionMatrix, 2, GL_FALSE, &(_eyeProjections[0][0][0])); _plane->UseInProgram(*_reprojectionProgram); _plane->Draw(); } @@ -530,7 +347,11 @@ void HmdDisplayPlugin::compositeOverlay() { handGlowPoints[i] = yawPitch; } - updateGlowProgram(); + updateOverlayProgram(); + if (!_overlayProgram) { + return; + } + useProgram(_overlayProgram); // Setup the uniforms { @@ -541,6 +362,12 @@ void HmdDisplayPlugin::compositeOverlay() { vec4 glowPoints(handGlowPoints[0], handGlowPoints[1]); Uniform(*_overlayProgram, _overlayUniforms.glowPoints).Set(glowPoints); } + if (_overlayUniforms.glowColors >= 0) { + std::array glowColors; + glowColors[0] = _presentHandLasers[0].color; + glowColors[1] = _presentHandLasers[1].color; + glProgramUniform4fv(GetName(*_overlayProgram), _overlayUniforms.glowColors, 2, &glowColors[0].r); + } } _sphereSection->Use(); @@ -634,7 +461,7 @@ void HmdDisplayPlugin::internalPresent() { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(targetViewportPosition.x, targetViewportPosition.y, targetViewportSize.x, targetViewportSize.y); - glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); + glUniform1i(_previewUniforms.previewTexture, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _previewTextureID); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -698,6 +525,8 @@ void HmdDisplayPlugin::compositeExtra() { return; } + updateLaserProgram(); + // Render hand lasers using namespace oglplus; useProgram(_laserProgram); @@ -739,6 +568,7 @@ void HmdDisplayPlugin::compositeExtra() { handLaserModelMatrices[i] = model; } + glEnable(GL_BLEND); for_each_eye([&](Eye eye) { eyeViewport(eye); auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); @@ -753,4 +583,5 @@ void HmdDisplayPlugin::compositeExtra() { _laserGeometry->Draw(); } }); + glDisable(GL_BLEND); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index be2811076d..f168ec9607 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -61,15 +61,6 @@ protected: } }; - ProgramPtr _overlayProgram; - struct OverlayUniforms { - int32_t mvp { -1 }; - int32_t alpha { -1 }; - int32_t glowPoints { -1 }; - int32_t resolution { -1 }; - int32_t radius { -1 }; - } _overlayUniforms; - Transform _uiModelTransform; std::array _handLasers; std::array _handPoses; @@ -82,8 +73,6 @@ protected: std::array _eyeProjections; std::array _eyeInverseProjections; - - glm::mat4 _cullingProjection; glm::uvec2 _renderTargetSize; float _ipd { 0.064f }; @@ -103,23 +92,49 @@ protected: FrameInfo _currentRenderFrameInfo; private: - void updateGlowProgram(); + void updateOverlayProgram(); + void updateLaserProgram(); + void updateReprojectionProgram(); bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; bool _firstPreview { true }; + ProgramPtr _overlayProgram; + struct OverlayUniforms { + int32_t mvp { -1 }; + int32_t alpha { -1 }; + int32_t glowColors { -1 }; + int32_t glowPoints { -1 }; + int32_t resolution { -1 }; + int32_t radius { -1 }; + } _overlayUniforms; + ProgramPtr _previewProgram; + struct PreviewUniforms { + int32_t previewTexture { -1 }; + } _previewUniforms; + float _previewAspect { 0 }; GLuint _previewTextureID { 0 }; glm::uvec2 _prevWindowSize { 0, 0 }; qreal _prevDevicePixelRatio { 0 }; ProgramPtr _reprojectionProgram; + struct ReprojectionUniforms { + int32_t reprojectionMatrix { -1 }; + int32_t inverseProjectionMatrix { -1 }; + int32_t projectionMatrix { -1 }; + } _reprojectionUniforms; + ShapeWrapperPtr _sphereSection; ProgramPtr _laserProgram; + struct LaserUniforms { + int32_t mvp { -1 }; + int32_t color { -1 }; + } _laserUniforms; ShapeWrapperPtr _laserGeometry; }; diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 7a535a806d..1154042b4a 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -87,6 +87,36 @@ ProgramPtr loadCubemapShader() { return result; } +void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& gs, const std::string& fs) { + using namespace oglplus; + try { + result = std::make_shared(); + // attach the shaders to the program + result->AttachShader( + VertexShader() + .Source(GLSLSource(vs)) + .Compile() + ); + result->AttachShader( + GeometryShader() + .Source(GLSLSource(gs)) + .Compile() + ); + result->AttachShader( + FragmentShader() + .Source(GLSLSource(fs)) + .Compile() + ); + result->Link(); + } catch (ProgramBuildError& err) { + Q_UNUSED(err); + qWarning() << err.Log().c_str(); + Q_ASSERT_X(false, "compileProgram", "Failed to build shader program"); + qFatal("%s", (const char*)err.Message); + result.reset(); + } +} + void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs) { using namespace oglplus; try { diff --git a/libraries/gl/src/gl/OglplusHelpers.h b/libraries/gl/src/gl/OglplusHelpers.h index 8940205b21..c453fbad28 100644 --- a/libraries/gl/src/gl/OglplusHelpers.h +++ b/libraries/gl/src/gl/OglplusHelpers.h @@ -62,6 +62,8 @@ using Mat4Uniform = oglplus::Uniform; ProgramPtr loadDefaultShader(); ProgramPtr loadCubemapShader(); void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs); +void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& gs, const std::string& fs); + ShapeWrapperPtr loadSkybox(ProgramPtr program); ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f); ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128); From 87ba1ffecabef45a7bd390ec1f4acc1ca5da996e Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Tue, 12 Jul 2016 16:05:46 -0700 Subject: [PATCH 27/29] Fix orientation being reset to 0,0,0,1 --- libraries/networking/src/AddressManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index df9b4094b0..7ed3888be0 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -564,10 +564,10 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { - glm::quat newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(), - orientationRegex.cap(1).toFloat(), - orientationRegex.cap(2).toFloat(), - orientationRegex.cap(3).toFloat())); + newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(), + orientationRegex.cap(1).toFloat(), + orientationRegex.cap(2).toFloat(), + orientationRegex.cap(3).toFloat())); if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z) && !isNaN(newOrientation.w)) { From f2220288fb17e0fad0d582a290e1dc00975047c3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 Jul 2016 14:54:12 -0700 Subject: [PATCH 28/29] Expand frame decoration when mouse is over a window in HMD mode --- .../resources/qml/hifi/toolbars/Toolbar.qml | 14 ++- .../resources/qml/windows/Decoration.qml | 47 +++++++ .../resources/qml/windows/DefaultFrame.qml | 100 +-------------- .../qml/windows/DefaultFrameDecoration.qml | 115 ++++++++++++++++++ interface/resources/qml/windows/Frame.qml | 16 ++- interface/resources/qml/windows/ToolFrame.qml | 78 +----------- .../qml/windows/ToolFrameDecoration.qml | 98 +++++++++++++++ interface/resources/qml/windows/Window.qml | 8 ++ tests/ui/qml/main.qml | 33 ++++- 9 files changed, 325 insertions(+), 184 deletions(-) create mode 100644 interface/resources/qml/windows/Decoration.qml create mode 100644 interface/resources/qml/windows/DefaultFrameDecoration.qml create mode 100644 interface/resources/qml/windows/ToolFrameDecoration.qml diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 75c06e4199..c5c15a6406 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -21,7 +21,19 @@ Window { height: content.height visible: true // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' - activator: Item {} + activator: MouseArea { + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 + propagateComposedEvents: true + acceptedButtons: Qt.AllButtons + enabled: window.visible + hoverEnabled: true + onPressed: mouse.accepted = false; + onEntered: window.mouseEntered(); + onExited: window.mouseExited(); + } property bool horizontal: true property real buttonSize: 50; property var buttons: [] diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml new file mode 100644 index 0000000000..628a4d3370 --- /dev/null +++ b/interface/resources/qml/windows/Decoration.qml @@ -0,0 +1,47 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Rectangle { + HifiConstants { id: hifi } + + property int frameMargin: 9 + property int frameMarginLeft: frameMargin + property int frameMarginRight: frameMargin + property int frameMarginTop: 2 * frameMargin + iconSize + property int frameMarginBottom: iconSize + 11 + + anchors { + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius + + // Enable dragging of the window, + // detect mouseover of the window (including decoration) + MouseArea { + anchors.fill: parent + drag.target: window + } +} + diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index 242209dbe0..33c2818849 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -16,104 +16,6 @@ import "../styles-uit" Frame { HifiConstants { id: hifi } - - Rectangle { - // Dialog frame - id: frameContent - - readonly property int iconSize: hifi.dimensions.frameIconSize - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 - - anchors { - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - anchors.fill: parent - color: hifi.colors.baseGrayHighlight40 - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.faintGray50 - } - radius: hifi.dimensions.borderRadius - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } - - Row { - id: controlsRow - anchors { - right: parent.right; - top: parent.top; - topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameContent.frameMarginRight; - } - spacing: frameContent.iconSize / 4 - - HiFiGlyphs { - // "Pin" button - visible: window.pinnable - text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin - color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: pinClickArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - onClicked: window.pinned = !window.pinned; - } - } - - HiFiGlyphs { - // "Close" button - visible: window ? window.closable : false - text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close - color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: closeClickArea - anchors.fill: parent - hoverEnabled: true - onClicked: window.shown = false; - } - } - } - - RalewayRegular { - // Title - id: titleText - anchors { - left: parent.left - leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x - right: controlsRow.left - rightMargin: frameContent.iconSize - top: parent.top - topMargin: frameContent.frameMargin - } - text: window ? window.title : "" - color: hifi.colors.white - size: hifi.fontSizes.overlayTitle - } - - DropShadow { - source: titleText - anchors.fill: titleText - horizontalOffset: 2 - verticalOffset: 2 - samples: 2 - color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) - cached: true - } - } + DefaultFrameDecoration {} } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml new file mode 100644 index 0000000000..ecb1760717 --- /dev/null +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -0,0 +1,115 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + + +Decoration { + HifiConstants { id: hifi } + + // Dialog frame + id: root + + property int iconSize: hifi.dimensions.frameIconSize + frameMargin: 9 + frameMarginLeft: frameMargin + frameMarginRight: frameMargin + frameMarginTop: 2 * frameMargin + iconSize + frameMarginBottom: iconSize + 11 + + Connections { + target: window + onMouseEntered: { + if (!HMD.active) { + return; + } + root.frameMargin = 18 + titleText.size = hifi.fontSizes.overlayTitle * 2 + root.iconSize = hifi.dimensions.frameIconSize * 2 + } + onMouseExited: { + root.frameMargin = 9 + titleText.size = hifi.fontSizes.overlayTitle + root.iconSize = hifi.dimensions.frameIconSize + } + } + + Row { + id: controlsRow + anchors { + right: parent.right; + top: parent.top; + topMargin: root.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: root.frameMarginRight; + } + spacing: root.iconSize / 4 + + HiFiGlyphs { + // "Pin" button + visible: window.pinnable + text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin + color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white + size: root.iconSize + MouseArea { + id: pinClickArea + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + onClicked: window.pinned = !window.pinned; + } + } + + HiFiGlyphs { + // "Close" button + visible: window ? window.closable : false + text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close + color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white + size: root.iconSize + MouseArea { + id: closeClickArea + anchors.fill: parent + hoverEnabled: true + onClicked: window.shown = false; + } + } + } + + RalewayRegular { + // Title + id: titleText + anchors { + left: parent.left + leftMargin: root.frameMarginLeft + hifi.dimensions.contentMargin.x + right: controlsRow.left + rightMargin: root.iconSize + top: parent.top + topMargin: root.frameMargin + } + text: window ? window.title : "" + color: hifi.colors.white + size: hifi.fontSizes.overlayTitle + } + + DropShadow { + source: titleText + anchors.fill: titleText + horizontalOffset: 2 + verticalOffset: 2 + samples: 2 + color: hifi.colors.baseGrayShadow60 + visible: (window && window.focus) + cached: true + } +} + diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 88d8c3ad41..030af974f6 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -22,10 +22,10 @@ Item { property bool gradientsSupported: desktop.gradientsSupported - readonly property int frameMarginLeft: frameContent.frameMarginLeft - readonly property int frameMarginRight: frameContent.frameMarginRight - readonly property int frameMarginTop: frameContent.frameMarginTop - readonly property int frameMarginBottom: frameContent.frameMarginBottom + readonly property int frameMarginLeft: frame.decoration ? frame.decoration.frameMarginLeft : 0 + readonly property int frameMarginRight: frame.decoration ? frame.decoration.frameMarginRight : 0 + readonly property int frameMarginTop: frame.decoration ? frame.decoration.frameMarginTop : 0 + readonly property int frameMarginBottom: frame.decoration ? frame.decoration.frameMarginBottom : 0 // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes @@ -103,16 +103,14 @@ Item { } onReleased: { if (hid) { - pane.visible = true - frameContent.visible = true + window.content.visible = true hid = false; } } onPositionChanged: { if (pressed) { - if (pane.visible) { - pane.visible = false; - frameContent.visible = false + if (window.content.visible) { + window.content.visible = false; hid = true; } var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index eff5fc0377..20c86afb5e 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -16,81 +16,11 @@ import "../styles-uit" Frame { HifiConstants { id: hifi } - property bool horizontalSpacers: false - property bool verticalSpacers: false + property alias horizontalSpacers: decoration.horizontalSpacers + property alias verticalSpacers: decoration.verticalSpacers - Rectangle { - // Dialog frame - id: frameContent - readonly property int frameMargin: 6 - readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0) - readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0) - readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0) - readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0) - - Rectangle { - visible: horizontalSpacers - anchors.left: parent.left - anchors.leftMargin: 6 - anchors.verticalCenter: parent.verticalCenter - width: 8 - height: window.height - color: "gray"; - radius: 4 - } - - Rectangle { - visible: horizontalSpacers - anchors.right: parent.right - anchors.rightMargin: 6 - anchors.verticalCenter: parent.verticalCenter - width: 8 - height: window.height - color: "gray"; - radius: 4 - } - - Rectangle { - visible: verticalSpacers - anchors.top: parent.top - anchors.topMargin: 6 - anchors.horizontalCenter: parent.horizontalCenter - height: 8 - width: window.width - color: "gray"; - radius: 4 - } - - Rectangle { - visible: verticalSpacers - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - anchors.horizontalCenter: parent.horizontalCenter - height: 8 - width: window.width - color: "gray"; - radius: 4 - } - - anchors { - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - topMargin: -frameMarginTop - bottomMargin: -frameMarginBottom - } - anchors.fill: parent - color: hifi.colors.baseGrayHighlight40 - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.faintGray50 - } - radius: hifi.dimensions.borderRadius / 2 - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } + ToolFrameDecoration { + id: decoration } } diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml new file mode 100644 index 0000000000..a7068183c1 --- /dev/null +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -0,0 +1,98 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Decoration { + id: root + HifiConstants { id: hifi } + + property bool horizontalSpacers: false + property bool verticalSpacers: false + + // Dialog frame + property int spacerWidth: 8 + property int spacerRadius: 4 + property int spacerMargin: 12 + frameMargin: 6 + frameMarginLeft: frameMargin + (horizontalSpacers ? spacerMargin : 0) + frameMarginRight: frameMargin + (horizontalSpacers ? spacerMargin : 0) + frameMarginTop: frameMargin + (verticalSpacers ? spacerMargin : 0) + frameMarginBottom: frameMargin + (verticalSpacers ? spacerMargin : 0) + radius: hifi.dimensions.borderRadius / 2 + + Connections { + target: window + onMouseEntered: { + if (!HMD.active) { + return; + } + root.frameMargin = 18 + root.spacerWidth = 16 + root.spacerRadius = 8 + root.spacerMargin = 8 + } + onMouseExited: { + root.frameMargin = 6 + root.spacerWidth = 8 + root.spacerRadius = 4 + root.spacerMargin = 12 + } + } + + Rectangle { + visible: horizontalSpacers + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: root.spacerWidth + height: decoration.height - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: horizontalSpacers + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: root.spacerWidth + height: decoration.height - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: verticalSpacers + anchors.top: parent.top + anchors.topMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: root.spacerWidth + width: decoration.width - 12 + color: "gray"; + radius: root.spacerRadius + } + + Rectangle { + visible: verticalSpacers + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: root.spacerWidth + width: decoration.width - 12 + color: "gray"; + radius: root.spacerRadius + } +} + diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 82bcf011e9..ca37c55f4d 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -31,6 +31,8 @@ Fadable { // Signals // signal windowDestroyed(); + signal mouseEntered(); + signal mouseExited(); // // Native properties @@ -113,11 +115,14 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible + hoverEnabled: true onPressed: { //console.log("Pressed on activator area"); window.raise(); mouse.accepted = false; } + onEntered: window.mouseEntered(); + onExited: window.mouseExited(); } // This mouse area serves to swallow mouse events while the mouse is over the window @@ -287,4 +292,7 @@ Fadable { break; } } + + onMouseEntered: console.log("Mouse entered " + window) + onMouseExited: console.log("Mouse exited " + window) } diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 47d0f6d601..a23a1e3ade 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -28,6 +28,11 @@ ApplicationWindow { property var toolbar; property var lastButton; + Button { + text: HMD.active ? "Disable HMD" : "Enable HMD" + onClicked: HMD.active = !HMD.active + } + // Window visibility Button { text: "toggle desktop" @@ -340,13 +345,13 @@ ApplicationWindow { } */ - /* Window { id: blue closable: true visible: true resizable: true destroyOnHidden: false + title: "Blue" width: 100; height: 100 x: 1280 / 2; y: 720 / 2 @@ -366,7 +371,33 @@ ApplicationWindow { } } + Window { + id: green + closable: true + visible: true + resizable: true + title: "Green" + destroyOnHidden: false + width: 100; height: 100 + x: 1280 / 2; y: 720 / 2 + Settings { + category: "TestWindow.Green" + property alias x: green.x + property alias y: green.y + property alias width: green.width + property alias height: green.height + } + + Rectangle { + anchors.fill: parent + visible: true + color: "green" + Component.onDestruction: console.log("Green destroyed") + } + } + +/* Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } Window { From 6336362d40fbd2e5e76c7d0000f83ee07da68934 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 Jul 2016 17:23:13 -0700 Subject: [PATCH 29/29] Fixing decoration inflation --- interface/resources/qml/desktop/Desktop.qml | 1 + .../resources/qml/windows/Decoration.qml | 24 +++++++++++++++ .../qml/windows/DefaultFrameDecoration.qml | 28 ++++++++--------- .../qml/windows/ToolFrameDecoration.qml | 30 +++++++++---------- .../src/scripting/HMDScriptingInterface.cpp | 7 ++++- tests/ui/qml/main.qml | 5 ++++ 6 files changed, 63 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 59d5b435ba..e5ff849df8 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -25,6 +25,7 @@ FocusScope { property rect recommendedRect: Qt.rect(0,0,0,0); property var expectedChildren; property bool repositionLocked: true + property bool hmdHandMouseActive: false onRepositionLockedChanged: { if (!repositionLocked) { diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index 628a4d3370..edfb369c0f 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -17,6 +17,9 @@ import "../styles-uit" Rectangle { HifiConstants { id: hifi } + signal inflateDecorations(); + signal deflateDecorations(); + property int frameMargin: 9 property int frameMarginLeft: frameMargin property int frameMarginRight: frameMargin @@ -43,5 +46,26 @@ Rectangle { anchors.fill: parent drag.target: window } + Connections { + target: window + onMouseEntered: { + if (desktop.hmdHandMouseActive) { + root.inflateDecorations() + } + } + onMouseExited: root.deflateDecorations(); + } + Connections { + target: desktop + onHmdHandMouseActiveChanged: { + if (desktop.hmdHandMouseActive) { + if (window.activator.containsMouse) { + root.inflateDecorations(); + } + } else { + root.deflateDecorations(); + } + } + } } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index ecb1760717..ce47b818b1 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -14,7 +14,6 @@ import QtGraphicalEffects 1.0 import "." import "../styles-uit" - Decoration { HifiConstants { id: hifi } @@ -28,23 +27,22 @@ Decoration { frameMarginTop: 2 * frameMargin + iconSize frameMarginBottom: iconSize + 11 - Connections { - target: window - onMouseEntered: { - if (!HMD.active) { - return; - } - root.frameMargin = 18 - titleText.size = hifi.fontSizes.overlayTitle * 2 - root.iconSize = hifi.dimensions.frameIconSize * 2 - } - onMouseExited: { - root.frameMargin = 9 - titleText.size = hifi.fontSizes.overlayTitle - root.iconSize = hifi.dimensions.frameIconSize + onInflateDecorations: { + if (!HMD.active) { + return; } + root.frameMargin = 18 + titleText.size = hifi.fontSizes.overlayTitle * 2 + root.iconSize = hifi.dimensions.frameIconSize * 2 } + onDeflateDecorations: { + root.frameMargin = 9 + titleText.size = hifi.fontSizes.overlayTitle + root.iconSize = hifi.dimensions.frameIconSize + } + + Row { id: controlsRow anchors { diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml index a7068183c1..ba36a2a38c 100644 --- a/interface/resources/qml/windows/ToolFrameDecoration.qml +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -32,23 +32,21 @@ Decoration { frameMarginBottom: frameMargin + (verticalSpacers ? spacerMargin : 0) radius: hifi.dimensions.borderRadius / 2 - Connections { - target: window - onMouseEntered: { - if (!HMD.active) { - return; - } - root.frameMargin = 18 - root.spacerWidth = 16 - root.spacerRadius = 8 - root.spacerMargin = 8 - } - onMouseExited: { - root.frameMargin = 6 - root.spacerWidth = 8 - root.spacerRadius = 4 - root.spacerMargin = 12 + onInflateDecorations: { + if (!HMD.active) { + return; } + root.frameMargin = 18 + root.spacerWidth = 16 + root.spacerRadius = 8 + root.spacerMargin = 8 + } + + onDeflateDecorations: { + root.frameMargin = 6 + root.spacerWidth = 8 + root.spacerRadius = 4 + root.spacerMargin = 12 } Rectangle { diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index e9677cc3c8..36cde378f8 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "Application.h" @@ -110,13 +111,17 @@ QString HMDScriptingInterface::preferredAudioOutput() const { } bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([offscreenUi, enabled] { + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); + }); return qApp->getActiveDisplayPlugin()->setHandLaser(hands, enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, color, direction); } void HMDScriptingInterface::disableHandLasers(int hands) const { - qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None); + setHandLasers(hands, false, vec4(0), vec3(0)); } bool HMDScriptingInterface::suppressKeyboard() { diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index a23a1e3ade..8ca9399b74 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -33,6 +33,11 @@ ApplicationWindow { onClicked: HMD.active = !HMD.active } + Button { + text: desktop.hmdHandMouseActive ? "Disable HMD HandMouse" : "Enable HMD HandMouse" + onClicked: desktop.hmdHandMouseActive = !desktop.hmdHandMouseActive + } + // Window visibility Button { text: "toggle desktop"