From 6f4fc24fb6082c06c86b528aea97deae3f0b9aab Mon Sep 17 00:00:00 2001 From: Bennett Goble Date: Sun, 30 Aug 2015 12:05:56 -0400 Subject: [PATCH 01/22] NodeConnectionData QDataStream #include --- domain-server/src/NodeConnectionData.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 3c75e8faad..80cb5950be 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -11,6 +11,8 @@ #include "NodeConnectionData.h" +#include + NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr, bool isConnectRequest) { NodeConnectionData newHeader; From a907c5757b63b9da6592f0ee642f727a2356d4ef Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 31 Aug 2015 11:09:28 -0700 Subject: [PATCH 02/22] Do not reset animation frame when restarting while we're fading out. Keeps it smooth if we're oscillating on some theshold between running and not.) --- libraries/animation/src/AnimationHandle.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimationHandle.cpp b/libraries/animation/src/AnimationHandle.cpp index 1f4c886a06..ad09e0736d 100644 --- a/libraries/animation/src/AnimationHandle.cpp +++ b/libraries/animation/src/AnimationHandle.cpp @@ -10,7 +10,7 @@ // #include "AnimationHandle.h" - +#include "AnimationLogging.h" void AnimationHandle::setURL(const QUrl& url) { if (_url != url) { @@ -51,8 +51,8 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { } void AnimationHandle::setRunning(bool running, bool doRestoreJoints) { - if (running && isRunning()) { - // if we're already running, this is the same as a restart + if (running && isRunning() && (getFadePerSecond() >= 0.0f)) { + // if we're already running, this is the same as a restart -- unless we're fading out. setFrameIndex(getFirstFrame()); return; } From 2dbfa5ce9f1d0adeb97e0ce86bdc90ff23eb564c Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 31 Aug 2015 11:10:05 -0700 Subject: [PATCH 03/22] Be more uniform in deciding animation movement, and incorporate HMD standing mode (which sets position, but not velocity). --- libraries/animation/src/Rig.cpp | 40 ++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3e466b94d6..24a86aa81a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -418,9 +418,30 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } bool isMoving = false; glm::vec3 front = worldRotation * IDENTITY_FRONT; - float forwardSpeed = glm::dot(worldVelocity, front); - float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT); - float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; + glm::vec3 right = worldRotation * IDENTITY_RIGHT; + const float PERCEPTIBLE_DELTA = 0.001f; + const float PERCEPTIBLE_SPEED = 0.1f; + // It can be more accurate/smooth to use velocity rather than position, + // but some modes (e.g., hmd standing) update position without updating velocity. + // It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...) + // So, let's create our own workingVelocity from the worldPosition... + glm::vec3 positionDelta = worldPosition - _lastPosition; + glm::vec3 workingVelocity = positionDelta / deltaTime; + // But for smoothest (non-hmd standing) results, go ahead and use velocity: +#if !WANT_DEBUG + // Note: Separately, we've arranged for starting/stopping animations by role (as we've done here) to pick up where they've left off when fading, + // so that you wouldn't notice the start/stop if it happens fast enough (e.g., one frame). But the print below would still be noisy. + if (!positionDelta.x && !positionDelta.y && !positionDelta.z) { + workingVelocity = worldVelocity; + } +#endif + + float forwardSpeed = glm::dot(workingVelocity, front); + float rightLateralSpeed = glm::dot(workingVelocity, right); + float rightTurningDelta = glm::orientedAngle(front, _lastFront, IDENTITY_UP); + float rightTurningSpeed = rightTurningDelta / deltaTime; + bool isTurning = (std::abs(rightTurningDelta) > PERCEPTIBLE_DELTA) && (std::abs(rightTurningSpeed) > PERCEPTIBLE_SPEED); + bool isStrafing = std::abs(rightLateralSpeed) > PERCEPTIBLE_SPEED; auto updateRole = [&](const QString& role, bool isOn) { isMoving = isMoving || isOn; if (isOn) { @@ -435,14 +456,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } } }; - updateRole("walk", forwardSpeed > 0.01f); - updateRole("backup", forwardSpeed < -0.01f); - bool isTurning = std::abs(rightTurningSpeed) > 0.5f; - updateRole("rightTurn", isTurning && (rightTurningSpeed > 0)); - updateRole("leftTurn", isTurning && (rightTurningSpeed < 0)); - bool isStrafing = !isTurning && (std::abs(rightLateralSpeed) > 0.01f); + updateRole("walk", forwardSpeed > PERCEPTIBLE_SPEED); + updateRole("backup", forwardSpeed < -PERCEPTIBLE_SPEED); + updateRole("rightTurn", isTurning && (rightTurningSpeed > 0.0f)); + updateRole("leftTurn", isTurning && (rightTurningSpeed < 0.0f)); + isStrafing = isStrafing && !isMoving; updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f)); - updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f)); + updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f)); updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus. _lastFront = front; _lastPosition = worldPosition; From d003ec9c584433a0ecddc5f7ee7890efbe66eec8 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 31 Aug 2015 15:49:17 -0700 Subject: [PATCH 04/22] Delete collision sounds (and their threads) after use. This has been broken ever since I added avatar collision sounds around https://github.com/highfidelity/hifi/pull/5159 or so. --- libraries/audio/src/AudioInjector.cpp | 11 +++++++++-- libraries/audio/src/AudioInjector.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 8fd7cb9ce5..716ed5d43e 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -313,7 +313,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol QByteArray samples = sound->getByteArray(); if (stretchFactor == 1.0f) { - return playSound(samples, options, NULL); + return playSoundAndDelete(samples, options, NULL); } soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); @@ -333,9 +333,16 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; resampled = samples; } - return playSound(resampled, options, NULL); + return playSoundAndDelete(resampled, options, NULL); } +AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { + AudioInjector* sound = playSound(buffer, options, localInterface); + sound->triggerDeleteAfterFinish(); + return sound; +} + + AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { QThread* injectorThread = new QThread(); injectorThread->setObjectName("Audio Injector Thread"); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index d65925b865..0e98fe1682 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -46,6 +46,7 @@ public: void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } + static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); From 2b17eafef4cefb4fc5324b5b5e9b0129b1c96416 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 1 Sep 2015 10:24:50 -0700 Subject: [PATCH 05/22] Only render display names in front of camera --- interface/src/avatar/Avatar.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e7423336b1..3d3dee3f44 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -784,8 +784,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const { bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats() && !isMyAvatar(); - // If we have nothing to draw, or it's tottaly transparent, return - if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) { + // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return + const float CLIP_DISTANCE = 0.2f; + if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f + || (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) { return; } auto renderer = textRenderer(DISPLAYNAME); From 4a7a384c650f811cb5066470846da3b8a1da6026 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Sep 2015 14:12:14 -0700 Subject: [PATCH 06/22] avoid bad joints when computing bounding capsule --- interface/src/avatar/SkeletonModel.cpp | 109 +++++++++++++++++++------ interface/src/avatar/SkeletonModel.h | 3 +- libraries/animation/src/Rig.cpp | 23 +++--- libraries/fbx/src/FBXReader.cpp | 16 +++- 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index a2e5908477..90c30b41ee 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -86,7 +86,7 @@ void SkeletonModel::initJointStates(QVector states) { _rig->updateJointState(i, rootTransform); } - buildShapes(); + computeBoundingShape(); Extents meshExtents = getMeshExtents(); _headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z); @@ -248,6 +248,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), true, PALM_PRIORITY); } + void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) { return; @@ -346,9 +347,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde } OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex]; - glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; - glm::vec3 pUp = position + orientation * IDENTITY_UP * size; - glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; + glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; + glm::vec3 pUp = position + orientation * IDENTITY_UP * size; + glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; glm::vec3 red(1.0f, 0.0f, 0.0f); geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right); @@ -466,7 +467,7 @@ float MIN_JOINT_MASS = 1.0f; float VERY_BIG_MASS = 1.0e6f; // virtual -void SkeletonModel::buildShapes() { +void SkeletonModel::computeBoundingShape() { if (_geometry == NULL || _rig->jointStatesEmpty()) { return; } @@ -476,36 +477,87 @@ void SkeletonModel::buildShapes() { // rootJointIndex == -1 if the avatar model has no skeleton return; } - computeBoundingShape(geometry); -} -void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { - // compute default joint transforms - int numStates = _rig->getJointStateCount(); - QVector transforms; - transforms.fill(glm::mat4(), numStates); + // BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the + // hands and feet into positions that are more correct than the default pose. + + // Measure limb lengths so we can specify IK targets that will pull hands and feet tight to body + QVector endEffectors; + endEffectors.push_back("RightHand"); + endEffectors.push_back("LeftHand"); + endEffectors.push_back("RightFoot"); + endEffectors.push_back("LeftFoot"); + + QVector baseJoints; + baseJoints.push_back("RightArm"); + baseJoints.push_back("LeftArm"); + baseJoints.push_back("RightUpLeg"); + baseJoints.push_back("LeftUpLeg"); + + for (int i = 0; i < endEffectors.size(); ++i) { + QString tipName = endEffectors[i]; + QString baseName = baseJoints[i]; + float limbLength = 0.0f; + int tipIndex = _rig->indexOfJoint(tipName); + if (tipIndex == -1) { + continue; + } + // save tip's relative rotation for later + glm::quat tipRotation = _rig->getJointState(tipIndex).getRotationInConstrainedFrame(); + + // IK on each endpoint + int jointIndex = tipIndex; + QVector freeLineage; + float priority = 1.0f; + while (jointIndex > -1) { + JointState limbJoint = _rig->getJointState(jointIndex); + freeLineage.push_back(jointIndex); + if (limbJoint.getName() == baseName) { + glm::vec3 targetPosition = limbJoint.getPosition() - glm::vec3(0.0f, 1.5f * limbLength, 0.0f); + // do IK a few times to make sure the endpoint gets close to its target + for (int j = 0; j < 5; ++j) { + _rig->inverseKinematics(tipIndex, + targetPosition, + glm::quat(), + priority, + freeLineage, + glm::mat4()); + } + const JointState& movedState = _rig->getJointState(tipIndex); + break; + } + limbLength += limbJoint.getDistanceToParent(); + jointIndex = limbJoint.getParentIndex(); + } + + // since this IK target is totally bogus we restore the tip's relative rotation + _rig->setJointRotationInConstrainedFrame(tipIndex, tipRotation, priority); + } + + // recompute all joint model-frame transforms + glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + for (int i = 0; i < _rig->getJointStateCount(); i++) { + _rig->updateJointState(i, rootTransform); + } + // END BOUNDING SHAPE HACK // compute bounding box that encloses all shapes Extents totalExtents; totalExtents.reset(); totalExtents.addPoint(glm::vec3(0.0f)); + int numStates = _rig->getJointStateCount(); for (int i = 0; i < numStates; i++) { // compute the default transform of this joint const JointState& state = _rig->getJointState(i); - int parentIndex = state.getParentIndex(); - if (parentIndex == -1) { - transforms[i] = _rig->getJointTransform(i); - } else { - glm::quat modifiedRotation = state.getPreRotation() * state.getDefaultRotation() * state.getPostRotation(); - transforms[i] = transforms[parentIndex] * glm::translate(state.getTranslation()) - * state.getPreTransform() * glm::mat4_cast(modifiedRotation) * state.getPostTransform(); - } - // Each joint contributes a sphere at its position - glm::vec3 axis(state.getBoneRadius()); - glm::vec3 jointPosition = extractTranslation(transforms[i]); - totalExtents.addPoint(jointPosition + axis); - totalExtents.addPoint(jointPosition - axis); + // HACK WORKAROUND: ignore joints that may have bad translation (e.g. have been flagged as such with zero radius) + if (state.getBoneRadius() > 0.0f) { + // Each joint contributes a sphere at its position + glm::vec3 axis(state.getBoneRadius()); + glm::vec3 jointPosition = state.getPosition(); + totalExtents.addPoint(jointPosition + axis); + totalExtents.addPoint(jointPosition - axis); + } } // compute bounding shape parameters @@ -517,6 +569,11 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition(); _boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition; + + // RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose + for (int i = 0; i < numStates; i++) { + _rig->restoreJointRotation(i, 1.0f, 1.0f); + } } void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) { @@ -535,7 +592,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha glm::vec4(0.6f, 0.6f, 0.8f, alpha)); // draw a yellow sphere at the capsule bottom point - glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f); + glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f); glm::vec3 axis = topPoint - bottomPoint; transform.setTranslation(bottomPoint); batch.setModelTransform(transform); diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 4ae615eadd..75ad728d46 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -94,7 +94,6 @@ public: /// \return whether or not the head was found. glm::vec3 getDefaultEyeModelPosition() const; - void computeBoundingShape(const FBXGeometry& geometry); void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha); float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; } float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; } @@ -112,7 +111,7 @@ signals: protected: - void buildShapes(); + void computeBoundingShape(); /// \param jointIndex index of joint in model /// \param position position of joint in model-frame diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d536bcb608..556953e6e7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -9,30 +9,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Rig.h" + #include #include #include "AnimationHandle.h" #include "AnimationLogging.h" -#include "Rig.h" void Rig::HeadParameters::dump() const { qCDebug(animation, "HeadParameters ="); - qCDebug(animation, " leanSideways = %0.5f", leanSideways); - qCDebug(animation, " leanForward = %0.5f", leanForward); - qCDebug(animation, " torsoTwist = %0.5f", torsoTwist); + qCDebug(animation, " leanSideways = %0.5f", (double)leanSideways); + qCDebug(animation, " leanForward = %0.5f", (double)leanForward); + qCDebug(animation, " torsoTwist = %0.5f", (double)torsoTwist); glm::vec3 axis = glm::axis(localHeadOrientation); float theta = glm::angle(localHeadOrientation); - qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); + qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta); axis = glm::axis(worldHeadOrientation); theta = glm::angle(worldHeadOrientation); - qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); + qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta); axis = glm::axis(modelRotation); theta = glm::angle(modelRotation); - qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); - qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", modelTranslation.x, modelTranslation.y, modelTranslation.z); - qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", eyeLookAt.x, eyeLookAt.y, eyeLookAt.z); - qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", eyeSaccade.x, eyeSaccade.y, eyeSaccade.z); + qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta); + qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", (double)modelTranslation.x, (double)modelTranslation.y, (double)modelTranslation.z); + qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", (double)eyeLookAt.x, (double)eyeLookAt.y, (double)eyeLookAt.z); + qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", (double)eyeSaccade.x, (double)eyeSaccade.y, (double)eyeSaccade.z); qCDebug(animation, " leanJointIndex = %.d", leanJointIndex); qCDebug(animation, " neckJointIndex = %.d", neckJointIndex); qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex); @@ -103,7 +104,7 @@ AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QStrin const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/"; if (role == "walk") { standard = base + "walk_fwd.fbx"; - } else if (role == "backup") { + } else if (role == "backup") { standard = base + "walk_bwd.fbx"; } else if (role == "leftTurn") { standard = base + "turn_left.fbx"; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 8bbe8dafd8..6d83a87a1c 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2677,8 +2677,21 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping foreach (const glm::vec3& vertex, extracted.mesh.vertices) { averageRadius += glm::distance(vertex, averageVertex); } - jointShapeInfo.averageRadius = averageRadius * radiusScale; + jointShapeInfo.averageRadius = averageRadius * radiusScale / (float)jointShapeInfo.numVertices; } + + // BUG: the boneBegin and/or boneEnd are incorrect for meshes that are "connected + // under the bone" without weights. Unfortunately we haven't been able to find it yet. + // Although the the mesh vertices are correct in the model-frame, the joint's transform + // in the same frame is just BAD. + // + // HACK WORKAROUND: prevent these shapes from contributing to the collision capsule by setting + // some key members of jointShapeInfo to zero: + jointShapeInfo.numVertices = 0; + jointShapeInfo.sumVertexWeights = 0.0f; + jointShapeInfo.numVertexWeights = 0; + jointShapeInfo.boneBegin = glm::vec3(0.0f); + jointShapeInfo.averageRadius = 0.0f; } extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); @@ -2728,7 +2741,6 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping // the average radius to the average point. if (jointShapeInfo.numVertexWeights == 0 && jointShapeInfo.numVertices > 0) { - jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices; joint.boneRadius = jointShapeInfo.averageRadius; } } From a7d57d7c60d0d865df2ae60ac055a32c11c9785c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 1 Sep 2015 14:31:47 -0700 Subject: [PATCH 07/22] cleanup whitespace --- interface/src/avatar/SkeletonModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 90c30b41ee..567f9b30ac 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -347,9 +347,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde } OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex]; - glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; - glm::vec3 pUp = position + orientation * IDENTITY_UP * size; - glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; + glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; + glm::vec3 pU = position + orientation * IDENTITY_UP * size; + glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; glm::vec3 red(1.0f, 0.0f, 0.0f); geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right); @@ -478,7 +478,7 @@ void SkeletonModel::computeBoundingShape() { return; } - // BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the + // BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the // hands and feet into positions that are more correct than the default pose. // Measure limb lengths so we can specify IK targets that will pull hands and feet tight to body From 2ce225d76c43262a47f0e09140a95892eaebe47c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 1 Sep 2015 15:18:59 -0600 Subject: [PATCH 08/22] fix for sixense not present on OS X --- libraries/input-plugins/src/input-plugins/SixenseManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index c6cc8a404b..052a05b9a9 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -50,8 +50,10 @@ const float CONTROLLER_THRESHOLD = 0.35f; #ifdef __APPLE__ typedef int (*SixenseBaseFunction)(); typedef int (*SixenseTakeIntFunction)(int); +#ifdef HAVE_SIXENSE typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*); #endif +#endif const QString SixenseManager::NAME = "Sixense"; From 9a4d666b0ceea55ed402f1c779fdfe492ef7e39a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 1 Sep 2015 15:19:50 -0600 Subject: [PATCH 09/22] no consts if HAVE_SIXENSE not set --- libraries/input-plugins/src/input-plugins/SixenseManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 052a05b9a9..579fddcd63 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -34,6 +34,8 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") const unsigned int LEFT_MASK = 0; const unsigned int RIGHT_MASK = 1U << 1; +#ifdef HAVE_SIXENSE + const int CALIBRATION_STATE_IDLE = 0; const int CALIBRATION_STATE_X = 1; const int CALIBRATION_STATE_Y = 2; @@ -47,6 +49,8 @@ const float NECK_Z = 0.3f; // meters const float CONTROLLER_THRESHOLD = 0.35f; +#endif + #ifdef __APPLE__ typedef int (*SixenseBaseFunction)(); typedef int (*SixenseTakeIntFunction)(int); From 1e6198cf597fef194fc657ce2a992c01cd705409 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Wed, 2 Sep 2015 11:29:37 -0700 Subject: [PATCH 10/22] update dhydra grab to grab near and far objects, and removed grabbing logic from toybox.js --- examples/controllers/hydra/hydraGrab.js | 524 ++++++++++++------------ examples/controllers/toybox.js | 233 +---------- 2 files changed, 267 insertions(+), 490 deletions(-) diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index 34484eb9e8..dfb0fdcadf 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -1,307 +1,315 @@ -// // hydraGrab.js // examples // -// Created by Clément Brisset on 4/24/14. -// Updated by Eric Levin on 5/14/15. -// Copyright 2014 High Fidelity, Inc. +// Created by Eric Levin on 9/2/15 +// Copyright 2015 High Fidelity, Inc. // -// This script allows you to grab and move/rotate physical objects with the hydra -// -// Using the hydras : -// grab physical entities with the right trigger +// Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); +var rightTriggerAction = RIGHT_HAND_CLICK; +var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); +var leftTriggerAction = LEFT_HAND_CLICK; -var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity; -var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; -var LEFT = 0; -var RIGHT = 1; -var LASER_WIDTH = 3; -var LASER_COLOR = { - red: 50, - green: 150, - blue: 200 -}; -var LASER_HOVER_COLOR = { - red: 200, - green: 50, - blue: 50 -}; - -var DROP_DISTANCE = 5.0; -var DROP_COLOR = { - red: 200, - green: 200, - blue: 200 -}; - -var FULL_STRENGTH = 0.05; -var LASER_LENGTH_FACTOR = 500; -var CLOSE_ENOUGH = 0.001; -var SPRING_RATE = 1.5; -var DAMPING_RATE = 0.8; -var SCREEN_TO_METERS = 0.001; -var DISTANCE_SCALE_FACTOR = 1000 - -var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); -var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); - -function getRayIntersection(pickRay) { - var intersection = Entities.findRayIntersection(pickRay, true); - return intersection; -} - - -function controller(side) { - this.triggerHeld = false; - this.triggerThreshold = 0.9; - this.side = side; - this.palm = 2 * side; - this.tip = 2 * side + 1; - this.trigger = side; - this.originalGravity = { +var ZERO_VEC = { x: 0, y: 0, z: 0 - }; +} +var LINE_LENGTH = 500; +var THICK_LINE_WIDTH = 7; +var THIN_LINE_WIDTH = 2; - this.laser = Overlays.addOverlay("line3d", { - start: { - x: 0, - y: 0, - z: 0 - }, - end: { - x: 0, - y: 0, - z: 0 - }, - color: LASER_COLOR, - alpha: 1, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; - this.dropLine = Overlays.addOverlay("line3d", { - color: DROP_COLOR, - alpha: 1, - visible: false, - lineWidth: 2 - }); +var GRAB_RADIUS = 2; +var GRAB_COLOR = { + red: 250, + green: 10, + blue: 250 +}; +var SHOW_LINE_THRESHOLD = 0.2; +var DISTANCE_HOLD_THRESHOLD = 0.8; - this.update = function(deltaTime) { - this.updateControllerState(); - this.moveLaser(); - this.checkTrigger(); - this.checkEntityIntersection(); - if (this.grabbing) { - this.updateEntity(deltaTime); - } +var right4Action = 18; +var left4Action = 17; - this.oldPalmPosition = this.palmPosition; - this.oldTipPosition = this.tipPosition; - } +var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5; - this.updateEntity = function(deltaTime) { - this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); - this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); - this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); +var RIGHT = 1; +var LEFT = 0; +var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right") +var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left") - this.entityProps = Entities.getEntityProperties(this.grabbedEntity); - this.currentPosition = this.entityProps.position; - this.currentVelocity = this.entityProps.velocity; - - var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); - this.distanceToTarget = Vec3.length(dPosition); - if (this.distanceToTarget > CLOSE_ENOUGH) { - // compute current velocity in the direction we want to move - this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition)); - this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget); - // compute the speed we would like to be going toward the target position - - this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); - // compute how much we want to add to the existing velocity - this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget); - //If target is to far, roll off force as inverse square of distance - if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) { - this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0)); - } - this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity); - this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE)); +function controller(side, triggerAction, pullAction, hand) { + this.hand = hand; + if (hand === "right") { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; } else { - this.newVelocity = { - x: 0, - y: 0, - z: 0 - }; - } - this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip); - this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity); - Entities.editEntity(this.grabbedEntity, { - velocity: this.newVelocity, - angularVelocity: this.transformedAngularVelocity + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + this.triggerAction = triggerAction; + this.pullAction = pullAction; + this.actionID = null; + this.tractorBeamActive = false; + this.distanceHolding = false; + this.triggerValue = 0; + this.prevTriggerValue = 0; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.pointer = Entities.addEntity({ + type: "Line", + name: "pointer", + color: NO_INTERSECT_COLOR, + dimensions: { + x: 1000, + y: 1000, + z: 1000 + }, + visible: false, + }); +} + + +controller.prototype.updateLine = function() { + var handPosition = Controller.getSpatialControlPosition(this.palm); + var direction = Controller.getSpatialControlNormal(this.tip); + + Entities.editEntity(this.pointer, { + position: handPosition, + linePoints: [ + ZERO_VEC, + Vec3.multiply(direction, LINE_LENGTH) + ] }); - this.updateDropLine(this.targetPosition); - - } - - - this.updateControllerState = function() { - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); - this.triggerValue = Controller.getTriggerValue(this.trigger); - } - - this.checkTrigger = function() { - if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) { - this.triggerHeld = true; - } else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) { - this.triggerHeld = false; - if (this.grabbing) { - this.release(); - } + //only check if we havent already grabbed an object + if (this.distanceHolding) { + return; } - } + + //move origin a bit away from hand so nothing gets in way + var origin = Vec3.sum(handPosition, direction); + if (this.checkForIntersections(origin, direction)) { + Entities.editEntity(this.pointer, { + color: INTERSECT_COLOR, + }); + } else { + Entities.editEntity(this.pointer, { + color: NO_INTERSECT_COLOR, + }); + } +} - this.updateDropLine = function(position) { - - Overlays.editOverlay(this.dropLine, { - visible: true, - start: { - x: position.x, - y: position.y + DROP_DISTANCE, - z: position.z - }, - end: { - x: position.x, - y: position.y - DROP_DISTANCE, - z: position.z - } - }); - - } - - this.checkEntityIntersection = function() { +controller.prototype.checkForIntersections = function(origin, direction) { var pickRay = { - origin: this.palmPosition, - direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) + origin: origin, + direction: direction }; - var intersection = getRayIntersection(pickRay, true); - if (intersection.intersects && intersection.properties.collisionsWillMove) { - this.laserWasHovered = true; - if (this.triggerHeld && !this.grabbing) { - this.grab(intersection.entityID); - } - Overlays.editOverlay(this.laser, { - color: LASER_HOVER_COLOR - }); - } else if (this.laserWasHovered) { - this.laserWasHovered = false; - Overlays.editOverlay(this.laser, { - color: LASER_COLOR - }); + + var intersection = Entities.findRayIntersection(pickRay, true); + + if (intersection.intersects) { + this.distanceToEntity = Vec3.distance(origin, intersection.properties.position); + Entities.editEntity(this.pointer, { + linePoints: [ + ZERO_VEC, + Vec3.multiply(direction, this.distanceToEntity) + ] + }); + this.grabbedEntity = intersection.entityID; + return true; } - } + return false; +} - this.grab = function(entityId) { - this.grabbing = true; - this.grabbedEntity = entityId; - this.entityProps = Entities.getEntityProperties(this.grabbedEntity); - this.targetPosition = this.entityProps.position; - this.currentPosition = this.targetPosition; - this.oldPalmPosition = this.palmPosition; - this.originalGravity = this.entityProps.gravity; - Entities.editEntity(this.grabbedEntity, { - gravity: { - x: 0, - y: 0, - z: 0 - } - }); - Overlays.editOverlay(this.laser, { - visible: false - }); - Audio.playSound(grabSound, { - position: this.entityProps.position, - volume: 0.25 - }); - } +controller.prototype.attemptMove = function() { + if (this.tractorBeamActive) { + return; + } + if (this.grabbedEntity || this.distanceHolding) { + var handPosition = Controller.getSpatialControlPosition(this.palm); + var direction = Controller.getSpatialControlNormal(this.tip); - this.release = function() { - this.grabbing = false; + var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity)) + this.distanceHolding = true; + //TO DO : USE SPRING ACTION UPDATE FOR MOVING + if (this.actionID === null) { + this.actionID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: newPosition, + linearTimeScale: 0.1 + }); + } else { + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: newPosition + }); + } + } + +} + +controller.prototype.showPointer = function() { + Entities.editEntity(this.pointer, { + visible: true + }); + +} + +controller.prototype.hidePointer = function() { + Entities.editEntity(this.pointer, { + visible: false + }); +} + + +controller.prototype.letGo = function() { + Entities.deleteAction(this.grabbedEntity, this.actionID); this.grabbedEntity = null; - Overlays.editOverlay(this.laser, { - visible: true - }); - Overlays.editOverlay(this.dropLine, { - visible: false - }); + this.actionID = null; + this.distanceHolding = false; + this.tractorBeamActive = false; + this.checkForEntityArrival = false; +} - Audio.playSound(releaseSound, { - position: this.entityProps.position, - volume: 0.25 - }); - - // only restore the original gravity if it's not zero. This is to avoid... - // 1. interface A grabs an entity and locally saves off its gravity - // 2. interface A sets the entity's gravity to zero - // 3. interface B grabs the entity and saves off its gravity (which is zero) - // 4. interface A releases the entity and puts the original gravity back - // 5. interface B releases the entity and puts the original gravity back (to zero) - if(vectorIsZero(this.originalGravity)) { - Entities.editEntity(this.grabbedEntity, { - gravity: this.originalGravity - }); +controller.prototype.update = function() { + if (this.tractorBeamActive && this.checkForEntityArrival) { + var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity + if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) { + this.letGo(); + } + return; + } + this.triggerValue = Controller.getActionValue(this.triggerAction); + if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) { + //First check if an object is within close range and then run the close grabbing logic + if (this.checkForInRangeObject()) { + this.grabEntity(); + } else { + this.showPointer(); + this.shouldDisplayLine = true; + } + } else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) { + this.hidePointer(); + this.letGo(); + this.shouldDisplayLine = false; } - } - this.moveLaser = function() { - var inverseRotation = Quat.inverse(MyAvatar.orientation); - var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); - // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); - var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); - direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); - var endPosition = Vec3.sum(startPosition, direction); + if (this.shouldDisplayLine) { + this.updateLine(); + } + if (this.triggerValue > DISTANCE_HOLD_THRESHOLD) { + this.attemptMove(); + } - Overlays.editOverlay(this.laser, { - start: startPosition, - end: endPosition + + this.prevTriggerValue = this.triggerValue; +} + +controller.prototype.grabEntity = function() { + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; + var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position; + var offset = Vec3.subtract(objectPosition, handPosition); + var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset); + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + relativePosition: offsetPosition, + relativeRotation: offsetRotation, + hand: this.hand, + timeScale: 0.05 }); - - } - - this.cleanup = function() { - Overlays.deleteOverlay(this.laser); - Overlays.deleteOverlay(this.dropLine); - } } -function update(deltaTime) { - rightController.update(deltaTime); - leftController.update(deltaTime); + +controller.prototype.checkForInRangeObject = function() { + var handPosition = Controller.getSpatialControlPosition(this.palm); + var entities = Entities.findEntities(handPosition, GRAB_RADIUS); + var minDistance = GRAB_RADIUS; + var grabbedEntity = null; + //Get nearby entities and assign nearest + for (var i = 0; i < entities.length; i++) { + var props = Entities.getEntityProperties(entities[i]); + var distance = Vec3.distance(props.position, handPosition); + if (distance < minDistance && props.name !== "pointer") { + grabbedEntity = entities[i]; + minDistance = distance; + } + } + if (grabbedEntity === null) { + return false; + } else { + this.grabbedEntity = grabbedEntity; + return true; + } } -function scriptEnding() { - rightController.cleanup(); - leftController.cleanup(); + +controller.prototype.onActionEvent = function(action, state) { + if (this.pullAction === action && state === 1) { + if (this.actionID !== null) { + var self = this; + this.tractorBeamActive = true; + //We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some + //low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving! + Script.setTimeout(function() { + self.checkForEntityArrival = true; + }, 500); + var handPosition = Controller.getSpatialControlPosition(this.palm); + var direction = Controller.getSpatialControlNormal(this.tip); + //move final destination along line a bit, so it doesnt hit avatar hand + Entities.updateAction(this.grabbedEntity, this.actionID, { + targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction)) + }); + } + } + } -function vectorIsZero(v) { - return v.x === 0 && v.y === 0 && v.z === 0; +controller.prototype.cleanup = function() { + Entities.deleteEntity(this.pointer); + Entities.deleteAction(this.grabbedEntity, this.actionID); } -var rightController = new controller(RIGHT); -var leftController = new controller(LEFT); +function update() { + rightController.update(); + leftController.update(); +} + +function onActionEvent(action, state) { + rightController.onActionEvent(action, state); + leftController.onActionEvent(action, state); + +} -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); +} + + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update) +Controller.actionEvent.connect(onActionEvent); \ No newline at end of file diff --git a/examples/controllers/toybox.js b/examples/controllers/toybox.js index 4c0925f3b6..bf03974fda 100644 --- a/examples/controllers/toybox.js +++ b/examples/controllers/toybox.js @@ -13,32 +13,7 @@ Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.j HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; -var nullActionID = "00000000-0000-0000-0000-000000000000"; -var controllerID; -var controllerActive; -var leftHandObjectID = null; -var rightHandObjectID = null; -var leftHandActionID = nullActionID; -var rightHandActionID = nullActionID; -var TRIGGER_THRESHOLD = 0.2; -var GRAB_RADIUS = 0.15; - -var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); -var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); -var ACTION1 = Controller.findAction("ACTION1"); -var ACTION2 = Controller.findAction("ACTION2"); - -var rightHandGrabAction = RIGHT_HAND_CLICK; -var leftHandGrabAction = LEFT_HAND_CLICK; - -var rightHandGrabValue = 0; -var leftHandGrabValue = 0; -var prevRightHandGrabValue = 0 -var prevLeftHandGrabValue = 0; - -var grabColor = { red: 0, green: 255, blue: 0}; -var releaseColor = { red: 0, green: 0, blue: 255}; var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() { return { @@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", { alpha: 1 }); -var overlays = false; -var leftHandOverlay; -var rightHandOverlay; -if (overlays) { - leftHandOverlay = Overlays.addOverlay("sphere", { - position: MyAvatar.getLeftPalmPosition(), - size: GRAB_RADIUS, - color: releaseColor, - alpha: 0.5, - solid: false - }); - rightHandOverlay = Overlays.addOverlay("sphere", { - position: MyAvatar.getRightPalmPosition(), - size: GRAB_RADIUS, - color: releaseColor, - alpha: 0.5, - solid: false - }); -} var OBJECT_HEIGHT_OFFSET = 0.5; var MIN_OBJECT_SIZE = 0.05; @@ -98,8 +54,6 @@ var GRAVITY = { z: 0.0 } -var LEFT = 0; -var RIGHT = 1; var tableCreated = false; @@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table var VELOCITY_MAG = 0.3; -var entitiesToResize = []; var MODELS = Array( { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" }, @@ -136,196 +89,15 @@ var COLLISION_SOUNDS = Array( var RESIZE_TIMER = 0.0; var RESIZE_WAIT = 0.05; // 50 milliseconds -var leftFist = Entities.addEntity( { - type: "Sphere", - shapeType: 'sphere', - position: MyAvatar.getLeftPalmPosition(), - dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS }, - rotation: MyAvatar.getLeftPalmRotation(), - visible: false, - collisionsWillMove: false, - ignoreForCollisions: true - }); -var rightFist = Entities.addEntity( { - type: "Sphere", - shapeType: 'sphere', - position: MyAvatar.getRightPalmPosition(), - dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS }, - rotation: MyAvatar.getRightPalmRotation(), - visible: false, - collisionsWillMove: false, - ignoreForCollisions: true - }); -function letGo(hand) { - var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID; - var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID; - var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity(); - var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() : - MyAvatar.getRightPalmAngularVelocity(); - if (actionIDToRemove != nullActionID && entityIDToEdit != null) { - Entities.deleteAction(entityIDToEdit, actionIDToRemove); - // TODO: upon successful letGo, restore collision groups - if (hand == LEFT) { - leftHandObjectID = null; - leftHandActionID = nullActionID; - } else { - rightHandObjectID = null; - rightHandActionID = nullActionID; - } - } -} -function setGrabbedObject(hand) { - var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - var entities = Entities.findEntities(handPosition, GRAB_RADIUS); - var objectID = null; - var minDistance = GRAB_RADIUS; - for (var i = 0; i < entities.length; i++) { - // Don't grab the object in your other hands, your fists, or the table - if ((hand == LEFT && entities[i] == rightHandObjectID) || - (hand == RIGHT && entities[i] == leftHandObjectID) || - entities[i] == leftFist || entities[i] == rightFist || - (tableCreated && entities[i] == tableEntities[0])) { - continue; - } else { - var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition); - if (distance <= minDistance) { - objectID = entities[i]; - minDistance = distance; - } - } - } - if (objectID == null) { - return false; - } - if (hand == LEFT) { - leftHandObjectID = objectID; - } else { - rightHandObjectID = objectID; - } - return true; -} - -function grab(hand) { - if (!setGrabbedObject(hand)) { - // If you don't grab an object, make a fist - Entities.editEntity((hand == LEFT) ? leftFist : rightFist, { ignoreForCollisions: false } ); - return; - } - var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID; - var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation(); - var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); - - var objectRotation = Entities.getEntityProperties(objectID).rotation; - var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var objectPosition = Entities.getEntityProperties(objectID).position; - var offset = Vec3.subtract(objectPosition, handPosition); - var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset); - // print(JSON.stringify(offsetPosition)); - var actionID = Entities.addAction("hold", objectID, { - relativePosition: { x: 0, y: 0, z: 0 }, - relativeRotation: offsetRotation, - hand: (hand == LEFT) ? "left" : "right", - timeScale: 0.05 - }); - if (actionID == nullActionID) { - if (hand == LEFT) { - leftHandObjectID = null; - } else { - rightHandObjectID = null; - } - } else { - // TODO: upon successful grab, add to collision group so object doesn't collide with immovable entities - if (hand == LEFT) { - leftHandActionID = actionID; - } else { - rightHandActionID = actionID; - } - } -} - -function resizeModels() { - var newEntitiesToResize = []; - for (var i = 0; i < entitiesToResize.length; i++) { - var naturalDimensions = Entities.getEntityProperties(entitiesToResize[i]).naturalDimensions; - if (naturalDimensions.x != 1.0 || naturalDimensions.y != 1.0 || naturalDimensions.z != 1.0) { - // bigger range of sizes for models - var dimensions = Vec3.multiply(randFloat(MIN_OBJECT_SIZE, 3.0*MAX_OBJECT_SIZE), Vec3.normalize(naturalDimensions)); - Entities.editEntity(entitiesToResize[i], { - dimensions: dimensions, - shapeType: "box" - }); - } else { - newEntitiesToResize.push(entitiesToResize[i]); - } - - } - entitiesToResize = newEntitiesToResize; -} - -function update(deltaTime) { - if (overlays) { - Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() }); - Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() }); - } - - // if (tableCreated && RESIZE_TIMER < RESIZE_WAIT) { - // RESIZE_TIMER += deltaTime; - // } else if (tableCreated) { - // resizeModels(); - // } - - rightHandGrabValue = Controller.getActionValue(rightHandGrabAction); - leftHandGrabValue = Controller.getActionValue(leftHandGrabAction); - - Entities.editEntity(leftFist, { position: MyAvatar.getLeftPalmPosition() }); - Entities.editEntity(rightFist, { position: MyAvatar.getRightPalmPosition() }); - - if (rightHandGrabValue > TRIGGER_THRESHOLD && - prevRightHandGrabValue < TRIGGER_THRESHOLD) { - if (overlays) { - Overlays.editOverlay(rightHandOverlay, { color: grabColor }); - } - grab(RIGHT); - } else if (rightHandGrabValue < TRIGGER_THRESHOLD && - prevRightHandGrabValue > TRIGGER_THRESHOLD) { - Entities.editEntity(rightFist, { ignoreForCollisions: true } ); - if (overlays) { - Overlays.editOverlay(rightHandOverlay, { color: releaseColor }); - } - letGo(RIGHT); - } - - if (leftHandGrabValue > TRIGGER_THRESHOLD && - prevLeftHandGrabValue < TRIGGER_THRESHOLD) { - if (overlays) { - Overlays.editOverlay(leftHandOverlay, { color: grabColor }); - } - grab(LEFT); - } else if (leftHandGrabValue < TRIGGER_THRESHOLD && - prevLeftHandGrabValue > TRIGGER_THRESHOLD) { - Entities.editEntity(leftFist, { ignoreForCollisions: true } ); - if (overlays) { - Overlays.editOverlay(leftHandOverlay, { color: releaseColor }); - } - letGo(LEFT); - } - - prevRightHandGrabValue = rightHandGrabValue; - prevLeftHandGrabValue = leftHandGrabValue; -} function cleanUp() { - letGo(RIGHT); - letGo(LEFT); + print("CLEANUP!!!") if (overlays) { Overlays.deleteOverlay(leftHandOverlay); Overlays.deleteOverlay(rightHandOverlay); } - Entities.deleteEntity(leftFist); - Entities.deleteEntity(rightFist); removeTable(); toolBar.cleanup(); } @@ -405,7 +177,6 @@ function createTable() { density: 0.5, collisionsWillMove: true, color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) }, - // collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)] }); if (type == "Model") { var randModel = randInt(0, MODELS.length); @@ -413,7 +184,6 @@ function createTable() { shapeType: "box", modelURL: MODELS[randModel].modelURL }); - entitiesToResize.push(tableEntities[i]); } } } @@ -426,5 +196,4 @@ function removeTable() { } Script.scriptEnding.connect(cleanUp); -Script.update.connect(update); Controller.mousePressEvent.connect(onClick); \ No newline at end of file From eb78b39bea5de68b060188283d0e2b9391c8110e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 2 Sep 2015 11:31:05 -0700 Subject: [PATCH 11/22] fix typo that broke build and remove warning --- interface/src/avatar/SkeletonModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 567f9b30ac..c7fa674d80 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -348,7 +348,7 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex]; glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; - glm::vec3 pU = position + orientation * IDENTITY_UP * size; + glm::vec3 pUp = position + orientation * IDENTITY_UP * size; glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; glm::vec3 red(1.0f, 0.0f, 0.0f); @@ -523,7 +523,6 @@ void SkeletonModel::computeBoundingShape() { freeLineage, glm::mat4()); } - const JointState& movedState = _rig->getJointState(tipIndex); break; } limbLength += limbJoint.getDistanceToParent(); From 992bd5c9d20e578df833fe7ec406e00e7da0e22b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 2 Sep 2015 11:16:36 -0700 Subject: [PATCH 12/22] fix sixense and vive rotation readings for palms --- interface/src/Application.cpp | 2 +- interface/src/avatar/SkeletonModel.cpp | 4 +--- libraries/avatars/src/HandData.h | 6 +++++- .../src/input-plugins/SixenseManager.cpp | 7 +++++-- .../src/input-plugins/ViveControllerManager.cpp | 13 ++++++------- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a8781ee308..e0726311c9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4919,7 +4919,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float // Store the one fingertip in the palm structure so we can track velocity const float FINGER_LENGTH = 0.3f; // meters - const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); + const glm::vec3 FINGER_VECTOR(0.0f, FINGER_LENGTH, 0.0f); const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; glm::vec3 oldTipPosition = palm->getTipRawPosition(); if (deltaTime > 0.0f) { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index a2e5908477..6a36a41dc4 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -261,9 +261,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { // the palm's position must be transformed into the model-frame glm::quat inverseRotation = glm::inverse(_rotation); glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation); - - // the palm's "raw" rotation is already in the model-frame - glm::quat palmRotation = palm.getRawRotation(); + glm::quat palmRotation = inverseRotation * palm.getRotation(); inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY); } diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 6388c882c7..c87c840399 100644 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -64,12 +64,13 @@ public: bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, const PalmData*& collidingPalm) const; + glm::quat getBaseOrientation() const; + friend class AvatarData; protected: AvatarData* _owningAvatarData; std::vector _palms; - glm::quat getBaseOrientation() const; glm::vec3 getBasePosition() const; float getBaseScale() const; @@ -95,6 +96,7 @@ public: void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; }; glm::quat getRawRotation() const { return _rawRotation; } + glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; } void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; } void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; } const glm::vec3& getRawVelocity() const { return _rawVelocity; } @@ -147,6 +149,7 @@ public: glm::vec3 getNormal() const; private: + // unless marked otherwise, these are all in the model-frame glm::quat _rawRotation; glm::vec3 _rawPosition; glm::vec3 _rawVelocity; @@ -156,6 +159,7 @@ private: glm::vec3 _tipPosition; glm::vec3 _tipVelocity; glm::vec3 _totalPenetration; // accumulator for per-frame penetrations + unsigned int _controllerButtons; unsigned int _lastControllerButtons; float _trigger; diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 579fddcd63..f090db7959 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -481,6 +481,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis) // const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f); + const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f); const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f); const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis); @@ -491,13 +492,15 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis); // Finally, there is a post-offset (same for both hands) to get the hand's rest orientation - // (fingers forward, palm down) aligned properly in the avatar's model-frame. - const glm::quat postOffset = glm::angleAxis(PI / 2.0f, xAxis); + // (fingers forward, palm down) aligned properly in the avatar's model-frame, + // and then a flip about the yAxis to get into model-frame. + const glm::quat postOffset = glm::angleAxis(PI, yAxis) * glm::angleAxis(PI / 2.0f, xAxis); // The total rotation of the hand uses the formula: // // rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh // + // TODO: find a shortcut with fewer rotations. rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index 082c37a837..5410db11a4 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -362,18 +362,17 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { // // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) // - // An approximate offset for the Vive can be obtained by inpection: + // An approximate offset for the Vive can be obtained by inspection: // // Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis)) // - - // Finally there is another flip around the yAxis to re-align from model to Vive space, so the full equation is: + // So the full equation is: // - // Q = yFlip * combinedMeasurement * viveToHand + // Q = combinedMeasurement * viveToHand // - // Q = yFlip * (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) // - // Q = yFlip * (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); @@ -381,7 +380,7 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f)); const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat offset = glm::inverse(signedQuaterZ * eighthX); - rotation = yFlip * rotation * offset * yFlip * quarterX; + rotation = rotation * offset * yFlip * quarterX; position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET); From c61bc190dee217ad5cf3078c74c532bd3dbc962e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 2 Sep 2015 14:16:02 -0700 Subject: [PATCH 13/22] fix finger and palm-normal directions for JS --- libraries/avatars/src/HandData.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index 9a9b51c1c8..1a9b6775d3 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -114,12 +114,14 @@ float HandData::getBaseScale() const { } glm::vec3 PalmData::getFingerDirection() const { - const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f); + // finger points along yAxis in hand-frame + const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f); return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION)); } glm::vec3 PalmData::getNormal() const { - const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f); + // palm normal points along zAxis in hand-frame + const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, 0.0f, 1.0f); return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION)); } From 54b7a063e2d23ab326d03fefa2172f07d81f44cb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 20:31:33 -0700 Subject: [PATCH 14/22] Support HTML colors in overlays --- interface/src/ui/overlays/Overlay.cpp | 11 +---------- libraries/shared/src/RegisteredMetaTypes.cpp | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 7824c0c498..0c909a1bfb 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -66,16 +66,7 @@ Overlay::~Overlay() { void Overlay::setProperties(const QScriptValue& properties) { QScriptValue color = properties.property("color"); - if (color.isValid()) { - QScriptValue red = color.property("red"); - QScriptValue green = color.property("green"); - QScriptValue blue = color.property("blue"); - if (red.isValid() && green.isValid() && blue.isValid()) { - _color.red = red.toVariant().toInt(); - _color.green = green.toVariant().toInt(); - _color.blue = blue.toVariant().toInt(); - } - } + xColorFromScriptValue(properties.property("color"), _color); if (properties.property("alpha").isValid()) { setAlpha(properties.property("alpha").toVariant().toFloat()); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index c4e05a68fb..2c4b213fcb 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { } void xColorFromScriptValue(const QScriptValue &object, xColor& color) { - color.red = object.property("red").toVariant().toInt(); - color.green = object.property("green").toVariant().toInt(); - color.blue = object.property("blue").toVariant().toInt(); + if (!object.isValid()) { + return; + } + if (object.isNumber()) { + color.red = color.green = color.blue = (uint8_t)object.toUInt32(); + } else if (object.isString()) { + QColor qcolor(object.toString()); + if (qcolor.isValid()) { + color.red = (uint8_t)qcolor.red(); + color.blue = (uint8_t)qcolor.blue(); + color.green = (uint8_t)qcolor.green(); + } + } else { + color.red = object.property("red").toVariant().toInt(); + color.green = object.property("green").toVariant().toInt(); + color.blue = object.property("blue").toVariant().toInt(); + } } QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { From 849249d7fec44004ea95d6e6a4c746588bd84872 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 20:39:08 -0700 Subject: [PATCH 15/22] Fixing colors Conflicts: examples/toys/magBalls/handController.js --- examples/libraries/htmlColors.js | 145 +++++++++++++++++++++++ examples/toys/magBalls/handController.js | 1 + examples/toys/magBalls/magBallsMain.js | 1 + 3 files changed, 147 insertions(+) create mode 100644 examples/libraries/htmlColors.js diff --git a/examples/libraries/htmlColors.js b/examples/libraries/htmlColors.js new file mode 100644 index 0000000000..e9ebbba841 --- /dev/null +++ b/examples/libraries/htmlColors.js @@ -0,0 +1,145 @@ + +HTML_COLORS = { + AliceBlue: "#F0F8FF", + AntiqueWhite: "#FAEBD7", + Aqua: "#00FFFF", + Aquamarine: "#7FFFD4", + Azure: "#F0FFFF", + Beige: "#F5F5DC", + Bisque: "#FFE4C4", + Black: "#000000", + BlanchedAlmond: "#FFEBCD", + Blue: "#0000FF", + BlueViolet: "#8A2BE2", + Brown: "#A52A2A", + BurlyWood: "#DEB887", + CadetBlue: "#5F9EA0", + Chartreuse: "#7FFF00", + Chocolate: "#D2691E", + Coral: "#FF7F50", + CornflowerBlue: "#6495ED", + Cornsilk: "#FFF8DC", + Crimson: "#DC143C", + Cyan: "#00FFFF", + DarkBlue: "#00008B", + DarkCyan: "#008B8B", + DarkGoldenRod: "#B8860B", + DarkGray: "#A9A9A9", + DarkGreen: "#006400", + DarkKhaki: "#BDB76B", + DarkMagenta: "#8B008B", + DarkOliveGreen: "#556B2F", + DarkOrange: "#FF8C00", + DarkOrchid: "#9932CC", + DarkRed: "#8B0000", + DarkSalmon: "#E9967A", + DarkSeaGreen: "#8FBC8F", + DarkSlateBlue: "#483D8B", + DarkSlateGray: "#2F4F4F", + DarkTurquoise: "#00CED1", + DarkViolet: "#9400D3", + DeepPink: "#FF1493", + DeepSkyBlue: "#00BFFF", + DimGray: "#696969", + DodgerBlue: "#1E90FF", + FireBrick: "#B22222", + FloralWhite: "#FFFAF0", + ForestGreen: "#228B22", + Fuchsia: "#FF00FF", + Gainsboro: "#DCDCDC", + GhostWhite: "#F8F8FF", + Gold: "#FFD700", + GoldenRod: "#DAA520", + Gray: "#808080", + Green: "#008000", + GreenYellow: "#ADFF2F", + HoneyDew: "#F0FFF0", + HotPink: "#FF69B4", + IndianRed: "#CD5C5C", + Indigo: "#4B0082", + Ivory: "#FFFFF0", + Khaki: "#F0E68C", + Lavender: "#E6E6FA", + LavenderBlush: "#FFF0F5", + LawnGreen: "#7CFC00", + LemonChiffon: "#FFFACD", + LightBlue: "#ADD8E6", + LightCoral: "#F08080", + LightCyan: "#E0FFFF", + LightGoldenRodYellow: "#FAFAD2", + LightGray: "#D3D3D3", + LightGreen: "#90EE90", + LightPink: "#FFB6C1", + LightSalmon: "#FFA07A", + LightSeaGreen: "#20B2AA", + LightSkyBlue: "#87CEFA", + LightSlateGray: "#778899", + LightSteelBlue: "#B0C4DE", + LightYellow: "#FFFFE0", + Lime: "#00FF00", + LimeGreen: "#32CD32", + Linen: "#FAF0E6", + Magenta: "#FF00FF", + Maroon: "#800000", + MediumAquaMarine: "#66CDAA", + MediumBlue: "#0000CD", + MediumOrchid: "#BA55D3", + MediumPurple: "#9370DB", + MediumSeaGreen: "#3CB371", + MediumSlateBlue: "#7B68EE", + MediumSpringGreen: "#00FA9A", + MediumTurquoise: "#48D1CC", + MediumVioletRed: "#C71585", + MidnightBlue: "#191970", + MintCream: "#F5FFFA", + MistyRose: "#FFE4E1", + Moccasin: "#FFE4B5", + NavajoWhite: "#FFDEAD", + Navy: "#000080", + OldLace: "#FDF5E6", + Olive: "#808000", + OliveDrab: "#6B8E23", + Orange: "#FFA500", + OrangeRed: "#FF4500", + Orchid: "#DA70D6", + PaleGoldenRod: "#EEE8AA", + PaleGreen: "#98FB98", + PaleTurquoise: "#AFEEEE", + PaleVioletRed: "#DB7093", + PapayaWhip: "#FFEFD5", + PeachPuff: "#FFDAB9", + Peru: "#CD853F", + Pink: "#FFC0CB", + Plum: "#DDA0DD", + PowderBlue: "#B0E0E6", + Purple: "#800080", + RebeccaPurple: "#663399", + Red: "#FF0000", + RosyBrown: "#BC8F8F", + RoyalBlue: "#4169E1", + SaddleBrown: "#8B4513", + Salmon: "#FA8072", + SandyBrown: "#F4A460", + SeaGreen: "#2E8B57", + SeaShell: "#FFF5EE", + Sienna: "#A0522D", + Silver: "#C0C0C0", + SkyBlue: "#87CEEB", + SlateBlue: "#6A5ACD", + SlateGray: "#708090", + Snow: "#FFFAFA", + SpringGreen: "#00FF7F", + SteelBlue: "#4682B4", + Tan: "#D2B48C", + Teal: "#008080", + Thistle: "#D8BFD8", + Tomato: "#FF6347", + Turquoise: "#40E0D0", + Violet: "#EE82EE", + Wheat: "#F5DEB3", + White: "#FFFFFF", + WhiteSmoke: "#F5F5F5", + Yellow: "#FFFF00", + YellowGreen: "#9ACD32", +} + diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js index 998d22c6f8..4aba43d412 100644 --- a/examples/toys/magBalls/handController.js +++ b/examples/toys/magBalls/handController.js @@ -10,6 +10,7 @@ RIGHT_CONTROLLER = 1; // FIXME add a customizable wand model and a mechanism to switch between wands HandController = function(side) { + this.side = side; this.palm = 2 * side; this.tip = 2 * side + 1; diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js index e54b818e4a..4eb98fab26 100644 --- a/examples/toys/magBalls/magBallsMain.js +++ b/examples/toys/magBalls/magBallsMain.js @@ -6,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("../../libraries/htmlColors.js"); Script.include("constants.js"); Script.include("utils.js"); Script.include("magBalls.js"); From d303c7e119021c3fd438c940e6c56e3197d8003c Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Wed, 2 Sep 2015 16:45:50 -0700 Subject: [PATCH 16/22] only add actions to physical objects --- examples/controllers/hydra/hydraGrab.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index dfb0fdcadf..71e4d2a07e 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -9,6 +9,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -70,6 +72,7 @@ function controller(side, triggerAction, pullAction, hand) { this.actionID = null; this.tractorBeamActive = false; this.distanceHolding = false; + this.closeGrabbing = false; this.triggerValue = 0; this.prevTriggerValue = 0; this.palm = 2 * side; @@ -127,8 +130,7 @@ controller.prototype.checkForIntersections = function(origin, direction) { }; var intersection = Entities.findRayIntersection(pickRay, true); - - if (intersection.intersects) { + if (intersection.intersects && intersection.properties.collisionsWillMove === 1) { this.distanceToEntity = Vec3.distance(origin, intersection.properties.position); Entities.editEntity(this.pointer, { linePoints: [ @@ -156,7 +158,7 @@ controller.prototype.attemptMove = function() { if (this.actionID === null) { this.actionID = Entities.addAction("spring", this.grabbedEntity, { targetPosition: newPosition, - linearTimeScale: 0.1 + linearTimeScale: .1 }); } else { Entities.updateAction(this.grabbedEntity, this.actionID, { @@ -188,6 +190,7 @@ controller.prototype.letGo = function() { this.distanceHolding = false; this.tractorBeamActive = false; this.checkForEntityArrival = false; + this.closeGrabbing = false; } controller.prototype.update = function() { @@ -216,7 +219,7 @@ controller.prototype.update = function() { if (this.shouldDisplayLine) { this.updateLine(); } - if (this.triggerValue > DISTANCE_HOLD_THRESHOLD) { + if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) { this.attemptMove(); } @@ -234,6 +237,7 @@ controller.prototype.grabEntity = function() { var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position; var offset = Vec3.subtract(objectPosition, handPosition); var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset); + this.closeGrabbing = true; this.actionID = Entities.addAction("hold", this.grabbedEntity, { relativePosition: offsetPosition, relativeRotation: offsetRotation, @@ -252,7 +256,7 @@ controller.prototype.checkForInRangeObject = function() { for (var i = 0; i < entities.length; i++) { var props = Entities.getEntityProperties(entities[i]); var distance = Vec3.distance(props.position, handPosition); - if (distance < minDistance && props.name !== "pointer") { + if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) { grabbedEntity = entities[i]; minDistance = distance; } From ea415071b48d1c3060c6b455536e54e951aa90fe Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 22:54:23 -0700 Subject: [PATCH 17/22] OmniTool first pass --- .../{toys/magBalls => libraries}/constants.js | 0 .../magBalls => libraries}/highlighter.js | 6 + examples/libraries/omniTool.js | 300 ++++++++++++++++++ .../libraries/omniTool/models/modelBase.js | 19 ++ examples/libraries/omniTool/models/wand.js | 120 +++++++ examples/libraries/omniTool/modules/test.js | 9 + .../{toys/magBalls => libraries}/utils.js | 8 +- examples/toys/magBalls/magBallsMain.js | 26 -- 8 files changed, 461 insertions(+), 27 deletions(-) rename examples/{toys/magBalls => libraries}/constants.js (100%) rename examples/{toys/magBalls => libraries}/highlighter.js (92%) create mode 100644 examples/libraries/omniTool.js create mode 100644 examples/libraries/omniTool/models/modelBase.js create mode 100644 examples/libraries/omniTool/models/wand.js create mode 100644 examples/libraries/omniTool/modules/test.js rename examples/{toys/magBalls => libraries}/utils.js (93%) delete mode 100644 examples/toys/magBalls/magBallsMain.js diff --git a/examples/toys/magBalls/constants.js b/examples/libraries/constants.js similarity index 100% rename from examples/toys/magBalls/constants.js rename to examples/libraries/constants.js diff --git a/examples/toys/magBalls/highlighter.js b/examples/libraries/highlighter.js similarity index 92% rename from examples/toys/magBalls/highlighter.js rename to examples/libraries/highlighter.js index 149d9ec5b7..b3550b6c8a 100644 --- a/examples/toys/magBalls/highlighter.js +++ b/examples/libraries/highlighter.js @@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) { }); } +Highlighter.prototype.setRotation = function(newRotation) { + Overlays.editOverlay(this.highlightCube, { + rotation: newRotation + }); +} + Highlighter.prototype.updateHighlight = function() { if (this.hightlighted) { var properties = Entities.getEntityProperties(this.hightlighted); diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js new file mode 100644 index 0000000000..643b789a0e --- /dev/null +++ b/examples/libraries/omniTool.js @@ -0,0 +1,300 @@ +// +// Created by Bradley Austin Davis on 2015/09/01 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("constants.js"); +Script.include("utils.js"); +Script.include("highlighter.js"); +Script.include("omniTool/models/modelBase.js"); +Script.include("omniTool/models/wand.js"); + +OmniToolModules = {}; +OmniToolModuleType = null; + +OmniTool = function(side) { + this.OMNI_KEY = "OmniTool"; + this.MAX_FRAMERATE = 30; + this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE + this.SIDE = side; + this.PALM = 2 * side; + this.ACTION = findAction(side ? "ACTION2" : "ACTION1"); + this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2"); + + this.highlighter = new Highlighter(); + this.ignoreEntities = {}; + this.nearestOmniEntity = { + id: null, + inside: false, + position: null, + distance: Infinity, + radius: 0, + omniProperties: {}, + boundingBox: null, + }; + + this.activeOmniEntityId = null; + this.lastUpdateInterval = 0; + this.tipLength = 0.4; + this.active = false; + this.module = null; + this.moduleEntityId = null; + this.lastScanPosition = ZERO_VECTOR; + this.model = new Wand(); + this.model.setLength(this.tipLength); + + // Connect to desired events + var _this = this; + Controller.actionEvent.connect(function(action, state) { + _this.onActionEvent(action, state); + }); + + Script.update.connect(function(deltaTime) { + _this.lastUpdateInterval += deltaTime; + if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) { + _this.onUpdate(_this.lastUpdateInterval); + _this.lastUpdateInterval = 0; + } + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +OmniTool.prototype.onActionEvent = function(action, state) { + // FIXME figure out the issues when only one spatial controller is active + // logDebug("Action: " + action + " " + state); + + if (this.module && this.module.onActionEvent) { + this.module.onActionEvent(action, state); + } + + if (action == this.ACTION) { + if (state) { + this.onClick(); + } else { + this.onRelease(); + } + } + + // FIXME Does not work + //// with only one controller active (listed as 2 here because 'tip' + 'palm') + //// then treat the alt action button as the action button +} + +OmniTool.prototype.getOmniToolData = function(entityId) { + return getEntityCustomData(this.OMNI_KEY, entityId, null); +} + +OmniTool.prototype.setOmniToolData = function(entityId, data) { + setEntityCustomData(this.OMNI_KEY, entityId, data); +} + +OmniTool.prototype.updateOmniToolData = function(entityId, data) { + var currentData = this.getOmniToolData(entityId) || {}; + for (var key in data) { + currentData[key] = data[key]; + } + setEntityCustomData(this.OMNI_KEY, entityId, currentData); +} + +OmniTool.prototype.setActive = function(active) { + if (active === this.active) { + return; + } + logDebug("omnitool changing active state: " + active); + this.active = active; + this.model.setVisible(this.active); + + if (this.module && this.module.onActiveChanged) { + this.module.onActiveChanged(this.side); + } +} + + +OmniTool.prototype.onUpdate = function(deltaTime) { + // FIXME this returns data if either the left or right controller is not on the base + this.position = Controller.getSpatialControlPosition(this.PALM); + // When on the base, hydras report a position of 0 + this.setActive(Vec3.length(this.position) > 0.001); + + var rawRotation = Controller.getSpatialControlRawRotation(this.PALM); + this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); + + this.model.setTransform({ + rotation: this.rotation, + position: this.position, + }); + + this.scan(); + + if (this.module && this.module.onUpdate) { + this.module.onUpdate(deltaTime); + } +} + +OmniTool.prototype.onClick = function() { + // First check to see if the user is switching to a new omni module + if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) { + this.activateNewOmniModule(); + return; + } + + // Next check if there is an active module and if so propagate the click + // FIXME how to I switch to a new module? + if (this.module && this.module.onClick) { + this.module.onClick(); + return; + } +} + +OmniTool.prototype.onRelease = function() { + // FIXME how to I switch to a new module? + if (this.module && this.module.onRelease) { + this.module.onRelease(); + return; + } + logDebug("Base omnitool does nothing on release"); +} + +// FIXME resturn a structure of all nearby entities to distances +OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) { + if (!maxDistance) { + maxDistance = 2.0; + } + var resultDistance = Infinity; + var resultId = null; + var resultProperties = null; + var resultOmniData = null; + var ids = Entities.findEntities(this.model.tipPosition, maxDistance); + for (var i in ids) { + var entityId = ids[i]; + if (this.ignoreEntities[entityId]) { + continue; + } + var omniData = this.getOmniToolData(entityId); + if (!omniData) { + // FIXME find a place to flush this information + this.ignoreEntities[entityId] = true; + continue; + } + + // Let searchers query specifically + if (selector && !selector(entityId, omniData)) { + continue; + } + + var properties = Entities.getEntityProperties(entityId); + var distance = Vec3.distance(this.model.tipPosition, properties.position); + if (distance < resultDistance) { + resultDistance = distance; + resultId = entityId; + } + } + + return resultId; +} + +OmniTool.prototype.onEnterNearestOmniEntity = function() { + this.nearestOmniEntity.inside = true; + this.highlighter.highlight(this.nearestOmniEntity.id); + logDebug("On enter omniEntity " + this.nearestOmniEntity.id); +} + +OmniTool.prototype.onLeaveNearestOmniEntity = function() { + this.nearestOmniEntity.inside = false; + this.highlighter.highlight(null); + logDebug("On leave omniEntity " + this.nearestOmniEntity.id); +} + +OmniTool.prototype.setNearestOmniEntity = function(entityId) { + if (entityId && entityId !== this.nearestOmniEntity.id) { + if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) { + this.onLeaveNearestOmniEntity(); + } + this.nearestOmniEntity.id = entityId; + this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId); + var properties = Entities.getEntityProperties(entityId); + this.nearestOmniEntity.position = properties.position; + // FIXME use a real bounding box, not a sphere + var bbox = properties.boundingBox; + this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn)); + this.highlighter.setRotation(properties.rotation); + this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions)); + } +} + +OmniTool.prototype.scan = function() { + var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition); + + if (scanDistance < 0.005) { + return; + } + + this.lastScanPosition = this.model.tipPosition; + + this.setNearestOmniEntity(this.findNearestOmniEntity()); + if (this.nearestOmniEntity.id) { + var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position); + // track distance on a half centimeter basis + if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) { + this.nearestOmniEntity.distance = distance; + if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) { + this.onEnterNearestOmniEntity(); + } + + if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) { + this.onLeaveNearestOmniEntity(); + } + } + } +} + +OmniTool.prototype.unloadModule = function() { + if (this.module && this.module.onUnload) { + this.module.onUnload(); + } + this.module = null; + this.moduleEntityId = null; +} + +OmniTool.prototype.activateNewOmniModule = function() { + // Support the ability for scripts to just run without replacing the current module + var script = this.nearestOmniEntity.omniProperties.script; + if (script.indexOf("/") < 0) { + script = "omniTool/modules/" + script; + } + + // Reset the tool type + OmniToolModuleType = null; + logDebug("Including script path: " + script); + try { + Script.include(script); + } catch(err) { + logWarn("Failed to include script: " + script + "\n" + err); + return; + } + + // If we're building a new module, unload the old one + if (OmniToolModuleType) { + logDebug("New OmniToolModule: " + OmniToolModuleType); + this.unloadModule(); + + try { + this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id); + this.moduleEntityId = this.nearestOmniEntity.id; + if (this.module.onLoad) { + this.module.onLoad(); + } + } catch(err) { + logWarn("Failed to instantiate new module: " + err); + } + } +} + +// FIXME find a good way to sync the two omni tools +OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ]; diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js new file mode 100644 index 0000000000..7697856d3f --- /dev/null +++ b/examples/libraries/omniTool/models/modelBase.js @@ -0,0 +1,19 @@ + +ModelBase = function() { + this.length = 0.2; +} + +ModelBase.prototype.setVisible = function(visible) { + this.visible = visible; +} + +ModelBase.prototype.setLength = function(length) { + this.length = length; +} + +ModelBase.prototype.setTransform = function(transform) { + this.rotation = transform.rotation; + this.position = transform.position; + this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 }); + this.tipPosition = Vec3.sum(this.position, this.tipVector); +} diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js new file mode 100644 index 0000000000..8f0fe92b53 --- /dev/null +++ b/examples/libraries/omniTool/models/wand.js @@ -0,0 +1,120 @@ + +Wand = function() { + // Max updates fps + this.MAX_FRAMERATE = 30 + this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE + this.DEFAULT_TIP_COLORS = [ { + red: 128, + green: 128, + blue: 128, + }, { + red: 64, + green: 64, + blue: 64, + }]; + this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45); + + // FIXME does this need to be a member of this? + this.lastUpdateInterval = 0; + + this.pointers = [ + Overlays.addOverlay("cube", { + position: ZERO_VECTOR, + color: this.DEFAULT_TIP_COLORS[0], + alpha: 1.0, + solid: true, + visible: false, + }), + Overlays.addOverlay("cube", { + position: ZERO_VECTOR, + color: this.DEFAULT_TIP_COLORS[1], + alpha: 1.0, + solid: true, + visible: false, + }) + ]; + + this.wand = Overlays.addOverlay("cube", { + position: ZERO_VECTOR, + color: COLORS.WHITE, + dimensions: { x: 0.01, y: 0.01, z: 0.2 }, + alpha: 1.0, + solid: true, + visible: false + }); + + var _this = this; + Script.scriptEnding.connect(function() { + Overlays.deleteOverlay(_this.pointers[0]); + Overlays.deleteOverlay(_this.pointers[1]); + Overlays.deleteOverlay(_this.wand); + }); + + Script.update.connect(function(deltaTime) { + _this.lastUpdateInterval += deltaTime; + if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) { + _this.onUpdate(_this.lastUpdateInterval); + _this.lastUpdateInterval = 0; + } + }); +} + +Wand.prototype = Object.create( ModelBase.prototype ); + +Wand.prototype.setVisible = function(visible) { + ModelBase.prototype.setVisible.call(this, visible); + Overlays.editOverlay(this.pointers[0], { + visible: this.visible + }); + Overlays.editOverlay(this.pointers[1], { + visible: this.visible + }); + Overlays.editOverlay(this.wand, { + visible: this.visible + }); +} + +Wand.prototype.setTransform = function(transform) { + ModelBase.prototype.setTransform.call(this, transform); + + var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector)); + Overlays.editOverlay(this.pointers[0], { + position: this.tipPosition, + rotation: this.rotation, + visible: true, + }); + Overlays.editOverlay(this.pointers[1], { + position: this.tipPosition, + rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation), + visible: true, + }); + Overlays.editOverlay(this.wand, { + dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 }, + position: wandPosition, + rotation: this.rotation, + visible: true, + }); +} + +Wand.prototype.setTipColors = function(color1, color2) { + Overlays.editOverlay(this.pointers[0], { + color: color1 || this.DEFAULT_TIP_COLORS[0], + }); + Overlays.editOverlay(this.pointers[1], { + color: color2 || this.DEFAULT_TIP_COLORS[1], + }); +} + +Wand.prototype.onUpdate = function(deltaTime) { + if (this.visible) { + var time = new Date().getTime() / 250; + var scale1 = Math.abs(Math.sin(time)); + var scale2 = Math.abs(Math.cos(time)); + Overlays.editOverlay(this.pointers[0], { + scale: scale1 * 0.01, + }); + Overlays.editOverlay(this.pointers[1], { + scale: scale2 * 0.01, + }); + } +} diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js new file mode 100644 index 0000000000..1ca806affa --- /dev/null +++ b/examples/libraries/omniTool/modules/test.js @@ -0,0 +1,9 @@ + +OmniToolModules.Test = function() { +} + +OmniToolModules.Test.prototype.onClick = function() { + logDebug("Test module onClick"); +} + +OmniToolModuleType = "Test" \ No newline at end of file diff --git a/examples/toys/magBalls/utils.js b/examples/libraries/utils.js similarity index 93% rename from examples/toys/magBalls/utils.js rename to examples/libraries/utils.js index ea1446f858..b15ea6c313 100644 --- a/examples/toys/magBalls/utils.js +++ b/examples/libraries/utils.js @@ -53,11 +53,17 @@ getEntityUserData = function(id) { var results = null; var properties = Entities.getEntityProperties(id); if (properties.userData) { - results = JSON.parse(properties.userData); + try { + results = JSON.parse(properties.userData); + } catch(err) { + logDebug(err); + logDebug(properties.userData); + } } return results ? results : {}; } + // Non-destructively modify the user data of an entity. setEntityCustomData = function(customKey, id, data) { var userData = getEntityUserData(id); diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js deleted file mode 100644 index 4eb98fab26..0000000000 --- a/examples/toys/magBalls/magBallsMain.js +++ /dev/null @@ -1,26 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/25 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -Script.include("../../libraries/htmlColors.js"); -Script.include("constants.js"); -Script.include("utils.js"); -Script.include("magBalls.js"); - -Script.include("ballController.js"); - -var magBalls = new MagBalls(); - -// Clear any previous balls -// magBalls.clear(); - -MenuController = function(side) { - HandController.call(this, side); -} - -// FIXME resolve some of the issues with dual controllers before allowing both controllers active -var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ]; From 5717b7783748ff543403d877e3770f594270f319 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 2 Sep 2015 18:01:47 -0700 Subject: [PATCH 18/22] Updating magballs to omnitool --- examples/libraries/constants.js | 77 -------------- examples/libraries/omniTool.js | 4 + examples/libraries/utils.js | 8 -- examples/toys/magBalls.js | 113 ++++++++++++++++++++ examples/toys/magBalls/ballController.js | 103 ------------------ examples/toys/magBalls/constants.js | 77 ++++++++++++++ examples/toys/magBalls/graph.js | 4 + examples/toys/magBalls/handController.js | 128 ----------------------- examples/toys/magBalls/magBalls.js | 115 +++++++++++++++----- examples/toys/magBalls/menuController.js | 66 ------------ 10 files changed, 289 insertions(+), 406 deletions(-) create mode 100644 examples/toys/magBalls.js delete mode 100644 examples/toys/magBalls/ballController.js create mode 100644 examples/toys/magBalls/constants.js delete mode 100644 examples/toys/magBalls/handController.js delete mode 100644 examples/toys/magBalls/menuController.js diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js index d154910f91..3ed7c02633 100644 --- a/examples/libraries/constants.js +++ b/examples/libraries/constants.js @@ -9,17 +9,6 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx"; -// FIXME make this editable through some script UI, so the user can customize the size of the structure built -SCALE = 0.5; -BALL_SIZE = 0.08 * SCALE; -STICK_LENGTH = 0.24 * SCALE; - -DEBUG_MAGSTICKS = true; - -CUSTOM_DATA_NAME = "magBalls"; -BALL_NAME = "MagBall"; -EDGE_NAME = "MagStick"; - ZERO_VECTOR = { x: 0, y: 0, z: 0 }; COLORS = { @@ -70,71 +59,5 @@ COLORS = { } } -BALL_RADIUS = BALL_SIZE / 2.0; - -BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; - -BALL_DIMENSIONS = { - x: BALL_SIZE, - y: BALL_SIZE, - z: BALL_SIZE -}; - -BALL_COLOR = { - red: 128, - green: 128, - blue: 128 -}; - -STICK_DIMENSIONS = { - x: STICK_LENGTH / 6, - y: STICK_LENGTH / 6, - z: STICK_LENGTH -}; - -BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; - -BALL_PROTOTYPE = { - type: "Sphere", - name: BALL_NAME, - dimensions: BALL_DIMENSIONS, - color: BALL_COLOR, - ignoreCollisions: true, - collisionsWillMove: false -}; - -// 2 millimeters -BALL_EPSILON = (.002) / BALL_DISTANCE; - -LINE_DIMENSIONS = { - x: 5, - y: 5, - z: 5 -} - -LINE_PROTOTYPE = { - type: "Line", - name: EDGE_NAME, - color: COLORS.CYAN, - dimensions: LINE_DIMENSIONS, - lineWidth: 5, - visible: true, - ignoreCollisions: true, - collisionsWillMove: false, -} - -EDGE_PROTOTYPE = LINE_PROTOTYPE; - -// var EDGE_PROTOTYPE = { -// type: "Sphere", -// name: EDGE_NAME, -// color: { red: 0, green: 255, blue: 255 }, -// //dimensions: STICK_DIMENSIONS, -// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, -// rotation: rotation, -// visible: true, -// ignoreCollisions: true, -// collisionsWillMove: false -// } diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js index 643b789a0e..c9f041d672 100644 --- a/examples/libraries/omniTool.js +++ b/examples/libraries/omniTool.js @@ -199,6 +199,10 @@ OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) { return resultId; } +OmniTool.prototype.getPosition = function() { + return this.model.tipPosition; +} + OmniTool.prototype.onEnterNearestOmniEntity = function() { this.nearestOmniEntity.inside = true; this.highlighter.highlight(this.nearestOmniEntity.id); diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index b15ea6c313..6e6012cfe3 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -76,14 +76,6 @@ getEntityCustomData = function(customKey, id, defaultValue) { return userData[customKey] ? userData[customKey] : defaultValue; } -getMagBallsData = function(id) { - return getEntityCustomData(CUSTOM_DATA_NAME, id, {}); -} - -setMagBallsData = function(id, value) { - setEntityCustomData(CUSTOM_DATA_NAME, id, value); -} - mergeObjects = function(proto, custom) { var result = {}; for (var attrname in proto) { diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js new file mode 100644 index 0000000000..8e441901a2 --- /dev/null +++ b/examples/toys/magBalls.js @@ -0,0 +1,113 @@ +// +// Created by Bradley Austin Davis on 2015/08/25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js +Script.include("../toys/magBalls/constants.js"); +Script.include("../toys/magBalls/graph.js"); +Script.include("../toys/magBalls/edgeSpring.js"); +Script.include("../toys/magBalls/magBalls.js"); + +OmniToolModuleType = "MagBallsController" + + +OmniToolModules.MagBallsController = function(omniTool, entityId) { + this.omniTool = omniTool; + this.entityId = entityId; + this.highlighter = new Highlighter(); + this.magBalls = new MagBalls(); + this.highlighter.setSize(BALL_SIZE); + this.ghostEdges = {}; +} + +var MAG_BALLS_DATA_NAME = "magBalls"; + +getMagBallsData = function(id) { + return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {}); +} + +setMagBallsData = function(id, value) { + setEntityCustomData(MAG_BALLS_DATA_NAME, id, value); +} + +//var magBalls = new MagBalls(); +// DEBUGGING ONLY - Clear any previous balls +// magBalls.clear(); + +OmniToolModules.MagBallsController.prototype.onClick = function() { + logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition)); + + this.selected = this.highlighter.hightlighted; + logDebug("This selected: " + this.selected); + if (!this.selected) { + this.selected = this.magBalls.createBall(this.tipPosition); + } + this.magBalls.selectBall(this.selected); + this.highlighter.highlight(null); + logDebug("Selected " + this.selected); +} + +OmniToolModules.MagBallsController.prototype.onRelease = function() { + logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition)); + this.clearGhostEdges(); + if (this.selected) { + this.magBalls.releaseBall(this.selected); + this.selected = null; + } +} + +OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) { + this.tipPosition = this.omniTool.getPosition(); + if (!this.selected) { + // Find the highlight target and set it. + var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); + this.highlighter.highlight(target); + if (!target) { + this.magBalls.onUpdate(deltaTime); + } + return; + } + + this.highlighter.highlight(null); + Entities.editEntity(this.selected, { position: this.tipPosition }); + var targetBalls = this.magBalls.findPotentialEdges(this.selected); + for (var ballId in targetBalls) { + if (!this.ghostEdges[ballId]) { + // create the ovleray + this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { + start: this.magBalls.getNodePosition(ballId), + end: this.tipPosition, + color: COLORS.RED, + alpha: 1, + lineWidth: 5, + visible: true, + }); + } else { + Overlays.editOverlay(this.ghostEdges[ballId], { + end: this.tipPosition, + }); + } + } + for (var ballId in this.ghostEdges) { + if (!targetBalls[ballId]) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } + } +} + +OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() { + for(var ballId in this.ghostEdges) { + Overlays.deleteOverlay(this.ghostEdges[ballId]); + delete this.ghostEdges[ballId]; + } +} + + +BallController.prototype.onUnload = function() { + this.clearGhostEdges(); +} diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js deleted file mode 100644 index 0f178b2804..0000000000 --- a/examples/toys/magBalls/ballController.js +++ /dev/null @@ -1,103 +0,0 @@ -Script.include("handController.js"); -Script.include("highlighter.js"); - -BallController = function(side, magBalls) { - HandController.call(this, side); - this.magBalls = magBalls; - this.highlighter = new Highlighter(); - this.highlighter.setSize(BALL_SIZE); - this.ghostEdges = {}; -} - -BallController.prototype = Object.create( HandController.prototype ); - -BallController.prototype.onUpdate = function(deltaTime) { - HandController.prototype.onUpdate.call(this, deltaTime); - - if (!this.selected) { - // Find the highlight target and set it. - var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(target); - return; - } - this.highlighter.highlight(null); - Entities.editEntity(this.selected, { position: this.tipPosition }); - var targetBalls = this.magBalls.findPotentialEdges(this.selected); - for (var ballId in targetBalls) { - if (!this.ghostEdges[ballId]) { - // create the ovleray - this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { - start: this.magBalls.getNodePosition(ballId), - end: this.tipPosition, - color: COLORS.RED, - alpha: 1, - lineWidth: 5, - visible: true, - }); - } else { - Overlays.editOverlay(this.ghostEdges[ballId], { - end: this.tipPosition, - }); - } - } - for (var ballId in this.ghostEdges) { - if (!targetBalls[ballId]) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } - } -} - -BallController.prototype.onClick = function() { - this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(null); -} - -BallController.prototype.onRelease = function() { - this.clearGhostEdges(); - this.magBalls.releaseBall(this.selected); - this.selected = null; -} - -BallController.prototype.clearGhostEdges = function() { - for(var ballId in this.ghostEdges) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } -} - -BallController.prototype.onCleanup = function() { - HandController.prototype.onCleanup.call(this); - this.clearGhostEdges(); -} - -BallController.prototype.onAltClick = function() { - return; - var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - if (!target) { - logDebug(target); - return; - } - - // FIXME move to delete shape - var toDelete = {}; - var deleteQueue = [ target ]; - while (deleteQueue.length) { - var curNode = deleteQueue.shift(); - if (toDelete[curNode]) { - continue; - } - toDelete[curNode] = true; - for (var nodeId in this.magBalls.getConnectedNodes(curNode)) { - deleteQueue.push(nodeId); - } - } - for (var nodeId in toDelete) { - this.magBalls.destroyNode(nodeId); - } -} - - - -BallController.prototype.onAltRelease = function() { -} diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js new file mode 100644 index 0000000000..b692f0908c --- /dev/null +++ b/examples/toys/magBalls/constants.js @@ -0,0 +1,77 @@ + +// FIXME make this editable through some script UI, so the user can customize the size of the structure built +SCALE = 0.5; +BALL_SIZE = 0.08 * SCALE; +STICK_LENGTH = 0.24 * SCALE; + +DEBUG_MAGSTICKS = true; + +BALL_NAME = "MagBall"; +EDGE_NAME = "MagStick"; + +BALL_RADIUS = BALL_SIZE / 2.0; + +BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; + +BALL_DIMENSIONS = { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE +}; + +BALL_COLOR = { + red: 128, + green: 128, + blue: 128 +}; + +STICK_DIMENSIONS = { + x: STICK_LENGTH / 6, + y: STICK_LENGTH / 6, + z: STICK_LENGTH +}; + +BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; + +BALL_PROTOTYPE = { + type: "Sphere", + name: BALL_NAME, + dimensions: BALL_DIMENSIONS, + color: BALL_COLOR, + ignoreCollisions: true, + collisionsWillMove: false +}; + +// 2 millimeters +BALL_EPSILON = (.002) / BALL_DISTANCE; + +LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false, +} + +EDGE_PROTOTYPE = LINE_PROTOTYPE; + +// var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +// } diff --git a/examples/toys/magBalls/graph.js b/examples/toys/magBalls/graph.js index df02ee3628..198c1b0c16 100644 --- a/examples/toys/magBalls/graph.js +++ b/examples/toys/magBalls/graph.js @@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) { return result; } +Graph.prototype.getNodesForEdge = function(edgeId) { + return Object.keys(this.edges[edgeId]); +} + Graph.prototype.getEdgeLength = function(edgeId) { var nodesInEdge = Object.keys(this.edges[edgeId]); return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js deleted file mode 100644 index 4aba43d412..0000000000 --- a/examples/toys/magBalls/handController.js +++ /dev/null @@ -1,128 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/29 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -LEFT_CONTROLLER = 0; -RIGHT_CONTROLLER = 1; - -// FIXME add a customizable wand model and a mechanism to switch between wands -HandController = function(side) { - - this.side = side; - this.palm = 2 * side; - this.tip = 2 * side + 1; - this.action = findAction(side ? "ACTION2" : "ACTION1"); - this.altAction = findAction(side ? "ACTION1" : "ACTION2"); - this.active = false; - this.tipScale = 1.4; - this.pointer = Overlays.addOverlay("sphere", { - position: ZERO_VECTOR, - size: 0.01, - color: COLORS.YELLOW, - alpha: 1.0, - solid: true, - visible: false, - }); - - // Connect to desired events - var _this = this; - Controller.actionEvent.connect(function(action, state) { - _this.onActionEvent(action, state); - }); - - Script.update.connect(function(deltaTime) { - _this.onUpdate(deltaTime); - }); - - Script.scriptEnding.connect(function() { - _this.onCleanup(); - }); -} - -HandController.prototype.onActionEvent = function(action, state) { - var spatialControlCount = Controller.getNumberOfSpatialControls(); - // If only 2 spacial controls, then we only have one controller active, so use either button - // otherwise, only use the specified action - - if (action == this.action) { - if (state) { - this.onClick(); - } else { - this.onRelease(); - } - } - - if (action == this.altAction) { - if (state) { - this.onAltClick(); - } else { - this.onAltRelease(); - } - } -} - -HandController.prototype.setActive = function(active) { - if (active == this.active) { - return; - } - logDebug("Hand controller changing active state: " + active); - this.active = active; - Overlays.editOverlay(this.pointer, { - visible: this.active - }); - Entities.editEntity(this.wand, { - visible: this.active - }); -} - -HandController.prototype.updateControllerState = function() { - // FIXME this returns data if either the left or right controller is not on the base - this.palmPos = Controller.getSpatialControlPosition(this.palm); - var tipPos = Controller.getSpatialControlPosition(this.tip); - this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale); - // When on the base, hydras report a position of 0 - this.setActive(Vec3.length(this.palmPos) > 0.001); - - //logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1)); - - //if (this.active) { - // logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos)) - //} -} - -HandController.prototype.onCleanup = function() { - Overlays.deleteOverlay(this.pointer); -} - -HandController.prototype.onUpdate = function(deltaTime) { - this.updateControllerState(); - if (this.active) { - Overlays.editOverlay(this.pointer, { - position: this.tipPosition - }); - Entities.editEntity(this.wand, { - position: this.tipPosition - }); - } -} - -HandController.prototype.onClick = function() { - logDebug("Base hand controller does nothing on click"); -} - -HandController.prototype.onRelease = function() { - logDebug("Base hand controller does nothing on release"); -} - -HandController.prototype.onAltClick = function() { - logDebug("Base hand controller does nothing on alt click"); -} - -HandController.prototype.onAltRelease = function() { - logDebug("Base hand controller does nothing on alt click"); -} - - diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js index 187c550073..307be9f5e1 100644 --- a/examples/toys/magBalls/magBalls.js +++ b/examples/toys/magBalls/magBalls.js @@ -8,30 +8,34 @@ var UPDATE_INTERVAL = 0.1; -Script.include("graph.js"); -Script.include("edgeSpring.js"); // A collection of balls and edges connecting them. MagBalls = function() { Graph.call(this); - this.MAX_ADJUST_ITERATIONS = 100; + this.REFRESH_WAIT_TICKS = 10; + this.MAX_VARIANCE = 0.25; this.lastUpdateAge = 0; - this.stable = false; + this.stable = true; this.adjustIterations = 0; this.selectedNodes = {}; this.edgeObjects = {}; + this.unstableEdges = {}; this.refresh(); var _this = this; - Script.update.connect(function(deltaTime) { - _this.onUpdate(deltaTime); - }); + //Script.update.connect(function(deltaTime) { + // _this.onUpdate(deltaTime); + //}); Script.scriptEnding.connect(function() { _this.onCleanup(); }); + + Entities.addingEntity.connect(function(entityId) { + _this.onEntityAdded(entityId); + }); } MagBalls.prototype = Object.create( Graph.prototype ); @@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) { this.lastUpdateAge += deltaTime; if (this.lastUpdateAge > UPDATE_INTERVAL) { this.lastUpdateAge = 0; - if (!this.stable) { + if (this.refreshNeeded) { + if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) { + logDebug("Refreshing"); + this.refresh(); + this.refreshNeeded = 0; + } + } + if (!this.stable && !Object.keys(this.selectedNodes).length) { this.adjustIterations += 1; - // logDebug("Update"); var adjusted = false; var nodeAdjustResults = {}; var fixupEdges = {}; for(var edgeId in this.edges) { + if (!this.unstableEdges[edgeId]) { + continue; + } adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); } for (var nodeId in nodeAdjustResults) { @@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) { }, ((UPDATE_INTERVAL * 1000) / 2)); if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { + if (adjusted) { + logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning"); + } this.adjustIterations = 0; this.stable = true; + this.unstableEdges = {}; } } } @@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) { // Check distance to attempt var distance = this.getNodeDistance(nodeId, otherNodeId); var variance = this.getVariance(distance); - if (Math.abs(variance) > 0.25) { + if (Math.abs(variance) > this.MAX_VARIANCE) { continue; } @@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) { return variances; } -MagBalls.prototype.grabBall = function(position, maxDist) { - var selected = this.findNearestNode(position, maxDist); - if (!selected) { - selected = this.createNode({ position: position }); - } - if (selected) { - this.stable = true; - this.breakEdges(selected); - this.selectedNodes[selected] = true; - } - return selected; +MagBalls.prototype.breakEdges = function(nodeId) { + //var unstableNodes = this.findShape(Object.keys.target); + //for (var node in unstableNodes) { + // this.unstableNodes[node] = true; + //} + Graph.prototype.breakEdges.call(this, nodeId); } +MagBalls.prototype.createBall = function(position) { + var created = this.createNode({ position: position }); + this.selectBall(created); + return created; +} + +MagBalls.prototype.selectBall = function(selected) { + if (!selected) { + return; + } + // stop updating shapes while manipulating + this.stable = true; + this.selectedNodes[selected] = true; + this.breakEdges(selected); +} + + MagBalls.prototype.releaseBall = function(releasedBall) { delete this.selectedNodes[releasedBall]; logDebug("Released ball: " + releasedBall); + var releasePosition = this.getNodePosition(releasedBall); this.stable = false; - var releasePosition = this.getNodePosition(releasedBall); // iterate through the other balls and ensure we don't intersect with // any of them. If we do, just delete this ball and return. @@ -169,10 +198,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) { for (var otherBallId in targets) { this.createEdge(otherBallId, releasedBall); } + + var unstableNodes = this.findShape(releasedBall); + for (var nodeId in unstableNodes) { + for (var edgeId in this.nodes[nodeId]) { + this.unstableEdges[edgeId] = true; + } + } this.validate(); } +MagBalls.prototype.findShape = function(nodeId) { + var result = {}; + var queue = [ nodeId ]; + while (queue.length) { + var curNode = queue.shift(); + if (result[curNode]) { + continue; + } + result[curNode] = true; + for (var otherNodeId in this.getConnectedNodes(curNode)) { + queue.push(otherNodeId); + } + } + return result; +} + + MagBalls.prototype.getVariance = function(distance) { // FIXME different balls or edges might have different ideas of variance... // let something else handle this @@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() { Script.setTimeout(function() { for (var i in deleteEdges) { var edgeId = deleteEdges[i]; - logDebug("deleting invalid edge " + edgeId); - Entities.deleteEntity(edgeId); + //logDebug("deleting invalid edge " + edgeId); + //Entities.deleteEntity(edgeId); + Entities.editEntity(edgeId, { + color: COLORS.RED + }) } }, 1000); } @@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) { Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); } +MagBalls.prototype.onEntityAdded = function(entityId) { + // We already have it + if (this.nodes[entityId] || this.edges[entityId]) { + return; + } + + var properties = Entities.getEntityProperties(entityId); + if (properties.name == BALL_NAME || properties.name == EDGE_NAME) { + this.refreshNeeded = 1; + } +} \ No newline at end of file diff --git a/examples/toys/magBalls/menuController.js b/examples/toys/magBalls/menuController.js deleted file mode 100644 index 0a076d1ff8..0000000000 --- a/examples/toys/magBalls/menuController.js +++ /dev/null @@ -1,66 +0,0 @@ -Script.include("handController.js"); - -MenuController = function(side, magBalls) { - HandController.call(this, side); -} - -MenuController.prototype = Object.create( HandController.prototype ); - -MenuController.prototype.onUpdate = function(deltaTime) { - HandController.prototype.onUpdate.call(this, deltaTime); - if (!this.selected) { - // Find the highlight target and set it. - var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(target); - return; - } - this.highlighter.highlight(null); - Entities.editEntity(this.selected, { position: this.tipPosition }); - var targetBalls = this.magBalls.findPotentialEdges(this.selected); - for (var ballId in targetBalls) { - if (!this.ghostEdges[ballId]) { - // create the ovleray - this.ghostEdges[ballId] = Overlays.addOverlay("line3d", { - start: this.magBalls.getNodePosition(ballId), - end: this.tipPosition, - color: COLORS.RED, - alpha: 1, - lineWidth: 5, - visible: true, - }); - } else { - Overlays.editOverlay(this.ghostEdges[ballId], { - end: this.tipPosition, - }); - } - } - for (var ballId in this.ghostEdges) { - if (!targetBalls[ballId]) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } - } -} - -MenuController.prototype.onClick = function() { - this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS); - this.highlighter.highlight(null); -} - -MenuController.prototype.onRelease = function() { - this.clearGhostEdges(); - this.magBalls.releaseBall(this.selected); - this.selected = null; -} - -MenuController.prototype.clearGhostEdges = function() { - for(var ballId in this.ghostEdges) { - Overlays.deleteOverlay(this.ghostEdges[ballId]); - delete this.ghostEdges[ballId]; - } -} - -MenuController.prototype.onCleanup = function() { - HandController.prototype.onCleanup.call(this); - this.clearGhostEdges(); -} From b6ef2e314ff04f41621bee27d247e5ad26f408e9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 22:48:30 -0700 Subject: [PATCH 19/22] Adding 'web3d' overlay type --- interface/src/ui/overlays/Overlays.cpp | 3 + interface/src/ui/overlays/Web3DOverlay.cpp | 163 +++++++++++++++++++++ interface/src/ui/overlays/Web3DOverlay.h | 50 +++++++ 3 files changed, 216 insertions(+) create mode 100644 interface/src/ui/overlays/Web3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Web3DOverlay.h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 39b8892f13..563d90f6b9 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -30,6 +30,7 @@ #include "Grid3DOverlay.h" #include "TextOverlay.h" #include "Text3DOverlay.h" +#include "Web3DOverlay.h" Overlays::Overlays() : _nextOverlayID(1) { @@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay = std::make_shared(Application::getInstance()->getEntityClipboardRenderer()); } else if (type == ModelOverlay::TYPE) { thisOverlay = std::make_shared(); + } else if (type == Web3DOverlay::TYPE) { + thisOverlay = std::make_shared(); } if (thisOverlay) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp new file mode 100644 index 0000000000..c173c5927f --- /dev/null +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -0,0 +1,163 @@ +// +// Web3DOverlay.cpp +// +// +// Created by Clement on 7/1/14. +// Modified and renamed by Zander Otavka on 8/4/15 +// 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 +// + +#include "Web3DOverlay.h" + +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// #include "Application.h" +// #include "GeometryUtil.h" + +static const float DPI = 30.47f; +static const float METERS_TO_INCHES = 39.3701f; +static const float INCHES_TO_METERS = 1.0f / 39.3701f; + +QString const Web3DOverlay::TYPE = "web3d"; + +Web3DOverlay::Web3DOverlay() : _dpi(DPI) { } + +Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : + Billboard3DOverlay(Web3DOverlay), + _url(Web3DOverlay->_url), + _dpi(Web3DOverlay->_dpi), + _resolution(Web3DOverlay->_resolution) +{ +} + +Web3DOverlay::~Web3DOverlay() { + if (_webSurface) { + _webSurface->pause(); + _webSurface->disconnect(_connection); + // The lifetime of the QML surface MUST be managed by the main thread + // Additionally, we MUST use local variables copied by value, rather than + // member variables, since they would implicitly refer to a this that + // is no longer valid + auto webSurface = _webSurface; + AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { + webSurface->deleteLater(); + }); + } +} + +void Web3DOverlay::update(float deltatime) { + applyTransformTo(_transform); +} + +void Web3DOverlay::render(RenderArgs* args) { + if (!_visible || !getParentVisible()) { + return; + } + + QOpenGLContext * currentContext = QOpenGLContext::currentContext(); + QSurface * currentSurface = currentContext->surface(); + if (!_webSurface) { + _webSurface = new OffscreenQmlSurface(); + _webSurface->create(currentContext); + _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); + _webSurface->load("WebEntity.qml"); + _webSurface->resume(); + _webSurface->getRootItem()->setProperty("url", _url); + _webSurface->resize(QSize(_resolution.x, _resolution.y)); + _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { + _texture = textureId; + }); + currentContext->makeCurrent(currentSurface); + } + + vec2 size = _resolution / _dpi * INCHES_TO_METERS; + vec2 halfSize = size / 2.0f; + vec4 color(toGlm(getColor()), getAlpha()); + + applyTransformTo(_transform, true); + Transform transform = _transform; + if (glm::length2(getDimensions()) != 1.0f) { + transform.postScale(vec3(getDimensions(), 1.0f)); + } + + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + if (_texture) { + batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture); + } else { + batch.setResourceTexture(0, DependencyManager::get()->getWhiteTexture()); + } + + batch.setModelTransform(transform); + DependencyManager::get()->bindSimpleProgram(batch, true, false, false, true); + DependencyManager::get()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color); + batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me +} + +void Web3DOverlay::setProperties(const QScriptValue &properties) { + Billboard3DOverlay::setProperties(properties); + + QScriptValue urlValue = properties.property("url"); + if (urlValue.isValid()) { + QString newURL = urlValue.toVariant().toString(); + if (newURL != _url) { + setURL(newURL); + } + } + + QScriptValue resolution = properties.property("resolution"); + if (resolution.isValid()) { + vec2FromScriptValue(resolution, _resolution); + } + + + QScriptValue dpi = properties.property("dpi"); + if (dpi.isValid()) { + _dpi = dpi.toVariant().toFloat(); + } +} + +QScriptValue Web3DOverlay::getProperty(const QString& property) { + if (property == "url") { + return _url; + } + if (property == "dpi") { + return _dpi; + } + return Billboard3DOverlay::getProperty(property); +} + +void Web3DOverlay::setURL(const QString& url) { + _url = url; + _isLoaded = false; +} + +bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) { + //// Make sure position and rotation is updated. + applyTransformTo(_transform, true); + vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions()); + // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. + return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance); +} + +Web3DOverlay* Web3DOverlay::createClone() const { + return new Web3DOverlay(this); +} diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h new file mode 100644 index 0000000000..59c8fae0fe --- /dev/null +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -0,0 +1,50 @@ +// +// Created by Bradley Austin Davis on 2015/08/31 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Web3DOverlay_h +#define hifi_Web3DOverlay_h + +#include "Billboard3DOverlay.h" + +class OffscreenQmlSurface; + +class Web3DOverlay : public Billboard3DOverlay { + Q_OBJECT + +public: + static QString const TYPE; + virtual QString getType() const { return TYPE; } + + Web3DOverlay(); + Web3DOverlay(const Web3DOverlay* Web3DOverlay); + virtual ~Web3DOverlay(); + + virtual void render(RenderArgs* args); + + virtual void update(float deltatime); + + // setters + void setURL(const QString& url); + + virtual void setProperties(const QScriptValue& properties); + virtual QScriptValue getProperty(const QString& property); + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); + + virtual Web3DOverlay* createClone() const; + +private: + OffscreenQmlSurface* _webSurface{ nullptr }; + QMetaObject::Connection _connection; + uint32_t _texture{ 0 }; + QString _url; + float _dpi; + vec2 _resolution{ 640, 480 }; +}; + +#endif // hifi_Web3DOverlay_h From 1f83d04423d476b25310783ecb1c6a2b5ff2c2e3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 1 Sep 2015 14:55:09 -0700 Subject: [PATCH 20/22] Fix oculus crash on switching display plugin to something else --- interface/src/Application.cpp | 133 +++++++++++------- interface/src/Application.h | 1 + interface/src/PluginContainerProxy.cpp | 12 -- .../src/display-plugins/DisplayPlugin.h | 6 + .../src/display-plugins/NullDisplayPlugin.cpp | 1 + .../src/display-plugins/NullDisplayPlugin.h | 1 + .../display-plugins/OpenGLDisplayPlugin.cpp | 11 +- .../src/display-plugins/OpenGLDisplayPlugin.h | 3 +- .../oculus/OculusDisplayPlugin.cpp | 6 - libraries/shared/src/Finally.h | 27 ++++ 10 files changed, 133 insertions(+), 68 deletions(-) create mode 100644 libraries/shared/src/Finally.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e0726311c9..391ba748d0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -64,6 +64,7 @@ #include #include +#include #include #include #include @@ -1009,6 +1010,16 @@ void Application::paintGL() { if (nullptr == _displayPlugin) { return; } + + // Some plugins process message events, potentially leading to + // re-entering a paint event. don't allow further processing if this + // happens + if (_inPaint) { + return; + } + _inPaint = true; + Finally clearFlagLambda([this] { _inPaint = false; }); + auto displayPlugin = getActiveDisplayPlugin(); displayPlugin->preRender(); _offscreenContext->makeCurrent(); @@ -1203,7 +1214,7 @@ void Application::paintGL() { // Ensure all operations from the previous context are complete before we try to read the fbo glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glDeleteSync(sync); - + { PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); displayPlugin->display(finalTexture, toGlm(size)); @@ -1219,7 +1230,6 @@ void Application::paintGL() { _frameCount++; Stats::getInstance()->setRenderDetails(renderArgs._details); - // Reset the gpu::Context Stages // Back to the default framebuffer; gpu::Batch batch; @@ -4703,53 +4713,68 @@ void Application::updateDisplayMode() { auto offscreenUi = DependencyManager::get(); DisplayPluginPointer oldDisplayPlugin = _displayPlugin; - if (oldDisplayPlugin != newDisplayPlugin) { - if (!_currentDisplayPluginActions.isEmpty()) { - auto menu = Menu::getInstance(); - foreach(auto itemInfo, _currentDisplayPluginActions) { - menu->removeMenuItem(itemInfo.first, itemInfo.second); - } - _currentDisplayPluginActions.clear(); - } - - if (newDisplayPlugin) { - _offscreenContext->makeCurrent(); - _activatingDisplayPlugin = true; - newDisplayPlugin->activate(); - _activatingDisplayPlugin = false; - _offscreenContext->makeCurrent(); - offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); - _offscreenContext->makeCurrent(); - } - - oldDisplayPlugin = _displayPlugin; - _displayPlugin = newDisplayPlugin; - - // If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed - // Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1 - bool newPluginWantsHMDTools = newDisplayPlugin ? - (newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false; - bool oldPluginWantedHMDTools = oldDisplayPlugin ? - (oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false; - - // Only show the hmd tools after the correct plugin has - // been activated so that it's UI is setup correctly - if (newPluginWantsHMDTools) { - _pluginContainer->showDisplayPluginsTools(); - } - - if (oldDisplayPlugin) { - oldDisplayPlugin->deactivate(); - _offscreenContext->makeCurrent(); - - // if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools - if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) { - DependencyManager::get()->hmdTools(false); - } - } - emit activeDisplayPluginChanged(); - resetSensors(); + if (newDisplayPlugin == oldDisplayPlugin) { + return; } + + // Some plugins *cough* Oculus *cough* process message events from inside their + // display function, and we don't want to change the display plugin underneath + // the paintGL call, so we need to guard against that + if (_inPaint) { + qDebug() << "Deferring plugin switch until out of painting"; + // Have the old plugin stop requesting renders + oldDisplayPlugin->stop(); + QCoreApplication::postEvent(this, new LambdaEvent([this] { + updateDisplayMode(); + })); + return; + } + + if (!_currentDisplayPluginActions.isEmpty()) { + auto menu = Menu::getInstance(); + foreach(auto itemInfo, _currentDisplayPluginActions) { + menu->removeMenuItem(itemInfo.first, itemInfo.second); + } + _currentDisplayPluginActions.clear(); + } + + if (newDisplayPlugin) { + _offscreenContext->makeCurrent(); + _activatingDisplayPlugin = true; + newDisplayPlugin->activate(); + _activatingDisplayPlugin = false; + _offscreenContext->makeCurrent(); + offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); + _offscreenContext->makeCurrent(); + } + + oldDisplayPlugin = _displayPlugin; + _displayPlugin = newDisplayPlugin; + + // If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed + // Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1 + bool newPluginWantsHMDTools = newDisplayPlugin ? + (newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false; + bool oldPluginWantedHMDTools = oldDisplayPlugin ? + (oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false; + + // Only show the hmd tools after the correct plugin has + // been activated so that it's UI is setup correctly + if (newPluginWantsHMDTools) { + _pluginContainer->showDisplayPluginsTools(); + } + + if (oldDisplayPlugin) { + oldDisplayPlugin->deactivate(); + _offscreenContext->makeCurrent(); + + // if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools + if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) { + DependencyManager::get()->hmdTools(false); + } + } + emit activeDisplayPluginChanged(); + resetSensors(); Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } @@ -5052,3 +5077,15 @@ void Application::crashApplication() { qCDebug(interfaceapp) << "Intentionally crashed Interface"; } + +void Application::setActiveDisplayPlugin(const QString& pluginName) { + auto menu = Menu::getInstance(); + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + QString name = displayPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (pluginName == name) { + action->setChecked(true); + } + } + updateDisplayMode(); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 9b819eae53..6c3b68cf6f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -673,6 +673,7 @@ private: int _simsPerSecondReport = 0; quint64 _lastSimsPerSecondUpdate = 0; bool _isForeground = true; // starts out assumed to be in foreground + bool _inPaint = false; friend class PluginContainerProxy; }; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index e172dbbd9e..4ef755bd1b 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() { QGLWidget* PluginContainerProxy::getPrimarySurface() { return qApp->_glWidget; } - -void Application::setActiveDisplayPlugin(const QString& pluginName) { - auto menu = Menu::getInstance(); - foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { - QString name = displayPlugin->getName(); - QAction* action = menu->getActionForOption(name); - if (pluginName == name) { - action->setChecked(true); - } - } - updateDisplayMode(); -} diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index a9220d68f6..86cfabe724 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -57,6 +57,11 @@ public: // Rendering support + // Stop requesting renders, but don't do full deactivation + // needed to work around the issues caused by Oculus + // processing messages in the middle of submitFrame + virtual void stop() = 0; + /** * Called by the application before the frame rendering. Can be used for * render timing related calls (for instance, the Oculus begin frame timing @@ -120,6 +125,7 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } + static const QString& MENU_PATH(); signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index e5a96d167e..1f8242f081 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {} void NullDisplayPlugin::activate() {} void NullDisplayPlugin::deactivate() {} +void NullDisplayPlugin::stop() {} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 90e717b5ee..bb1ab2d97f 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -17,6 +17,7 @@ public: void activate() override; void deactivate() override; + void stop() override; virtual glm::uvec2 getRecommendedRenderSize() const override; virtual bool hasFocus() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 4101bebfa8..cfe101e1ab 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -16,7 +16,9 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() { connect(&_timer, &QTimer::timeout, this, [&] { - emit requestRender(); + if (_active) { + emit requestRender(); + } }); } @@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() { } void OpenGLDisplayPlugin::activate() { + _active = true; _timer.start(1); } +void OpenGLDisplayPlugin::stop() { + _active = false; + _timer.stop(); +} + void OpenGLDisplayPlugin::deactivate() { + _active = false; _timer.stop(); makeCurrent(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 0dc94b72f5..52715ebde7 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -25,7 +25,7 @@ public: virtual void activate() override; virtual void deactivate() override; - + virtual void stop() override; virtual bool eventFilter(QObject* receiver, QEvent* event) override; virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; @@ -44,6 +44,7 @@ protected: mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; + bool _active{ false }; bool _vsyncSupported{ false }; }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index ff218987ec..c214b7e627 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -350,11 +350,6 @@ void OculusDisplayPlugin::deactivate() { } void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - static bool inDisplay = false; - if (inDisplay) { - return; - } - inDisplay = true; #if (OVR_MAJOR_VERSION >= 6) using namespace oglplus; // Need to make sure only the display plugin is responsible for @@ -420,7 +415,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi ++_frameIndex; #endif - inDisplay = false; } // Pass input events on to the application diff --git a/libraries/shared/src/Finally.h b/libraries/shared/src/Finally.h new file mode 100644 index 0000000000..59e8be0228 --- /dev/null +++ b/libraries/shared/src/Finally.h @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis on 2015/09/01 +// Copyright 2013-2105 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 +// + +// Simulates a java finally block by executing a lambda when an instance leaves +// scope + +#include + +#pragma once +#ifndef hifi_Finally_h +#define hifi_Finally_h + +class Finally { +public: + template + Finally(F f) : _f(f) {} + ~Finally() { _f(); } +private: + std::function _f; +}; + +#endif From 2901155a7bc1550e9172d38513280cbddeede492 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 1 Sep 2015 15:34:25 -0700 Subject: [PATCH 21/22] Fix Oculus mirroring to window --- .../oculus/OculusDisplayPlugin.cpp | 34 ++++++++----------- .../oculus/OculusDisplayPlugin.h | 2 +- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index c214b7e627..2ed5e69fe7 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -325,19 +325,18 @@ void OculusDisplayPlugin::customizeContext() { //_texture = DependencyManager::get()-> // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); uvec2 mirrorSize = toGlm(_window->geometry().size()); - _mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd)); - _mirrorFbo->Init(mirrorSize); _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); #endif + enableVsync(false); + isVsyncEnabled(); } void OculusDisplayPlugin::deactivate() { #if (OVR_MAJOR_VERSION >= 6) makeCurrent(); _sceneFbo.reset(); - _mirrorFbo.reset(); doneCurrent(); PerformanceTimer::setActive(false); @@ -378,12 +377,14 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi the UI visible in the output window (unlikely). This should be done before _sceneFbo->Increment or we're be using the wrong texture */ - _sceneFbo->Bound(Framebuffer::Target::Read, [&] { - glBlitFramebuffer( - 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, - 0, 0, windowSize.x, windowSize.y, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - }); + if (_enableMirror) { + _sceneFbo->Bound(Framebuffer::Target::Read, [&] { + glBlitFramebuffer( + 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, + 0, 0, windowSize.x, windowSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + }); + } { PerformanceTimer("OculusSubmit"); @@ -404,6 +405,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi The other alternative for mirroring is to use the Oculus mirror texture support, which will contain the post-distorted and fully composited scene regardless of how many layers we send. + Currently generates an error. */ //auto mirrorSize = _mirrorFbo->size; //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { @@ -419,16 +421,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi // Pass input events on to the application bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { -#if (OVR_MAJOR_VERSION >= 6) - if (event->type() == QEvent::Resize) { - QResizeEvent* resizeEvent = static_cast(event); - qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height(); - auto newSize = toGlm(resizeEvent->size()); - makeCurrent(); - _mirrorFbo->Resize(newSize); - doneCurrent(); - } -#endif return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); } @@ -438,7 +430,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { otherwise the swapbuffer delay will interefere with the framerate of the headset */ void OculusDisplayPlugin::finishFrame() { - //swapBuffers(); + if (_enableMirror) { + swapBuffers(); + } doneCurrent(); }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h index 42f8d5763f..d30356daa0 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -58,6 +58,7 @@ private: mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; ovrTrackingState _trackingState; + bool _enableMirror{ false }; #if (OVR_MAJOR_VERSION >= 6) ovrHmd _hmd; @@ -70,7 +71,6 @@ private: ovrLayerEyeFov& getSceneLayer(); ovrHmdDesc _hmdDesc; SwapFboPtr _sceneFbo; - MirrorFboPtr _mirrorFbo; ovrLayerEyeFov _sceneLayer; #endif #if (OVR_MAJOR_VERSION == 7) From 70ffe10057d5a410b74e8c37eb5f148596bc443e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 14:44:12 -0700 Subject: [PATCH 22/22] Use a constant log file and rollover accumulated logs to a new file based on startup, time or size --- interface/src/FileLogger.cpp | 62 ++++++++++++++++++++++++++++-------- interface/src/FileLogger.h | 2 +- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 00da80814b..0b2128cf17 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -10,16 +10,39 @@ // #include "FileLogger.h" -#include "HifiSockAddr.h" -#include -#include -#include -#include -#include -const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; -const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; -const QString LOGS_DIRECTORY = "Logs"; +#include +#include +#include +#include + +#include +#include +#include + +#include "HifiSockAddr.h" + +static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; +static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; +static const QString LOGS_DIRECTORY = "Logs"; +// Max log size is 1 MB +static const uint64_t MAX_LOG_SIZE = 1024 * 1024; +// Max log age is 1 hour +static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600; + +QString getLogRollerFilename() { + QString result = FileUtils::standardPath(LOGS_DIRECTORY); + QHostAddress clientAddress = getLocalAddress(); + QDateTime now = QDateTime::currentDateTime(); + result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); + return result; +} + +const QString& getLogFilename() { + static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt"; + return fileName; +} + class FilePersistThread : public GenericQueueThread < QString > { public: @@ -28,8 +51,22 @@ public: } protected: + void rollFileIfNecessary(QFile& file) { + uint64_t now = usecTimestampNow(); + if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) { + QString newFileName = getLogRollerFilename(); + if (file.copy(newFileName)) { + _lastRollTime = now; + file.open(QIODevice::WriteOnly | QIODevice::Truncate); + file.close(); + qDebug() << "Rolled log file: " << newFileName; + } + } + } + virtual bool processQueueItems(const Queue& messages) { QFile file(_logger._fileName); + rollFileIfNecessary(file); if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { QTextStream out(&file); foreach(const QString& message, messages) { @@ -40,20 +77,17 @@ protected: } private: const FileLogger& _logger; + uint64_t _lastRollTime = 0; }; static FilePersistThread* _persistThreadInstance; FileLogger::FileLogger(QObject* parent) : - AbstractLoggerInterface(parent) + AbstractLoggerInterface(parent), _fileName(getLogFilename()) { _persistThreadInstance = new FilePersistThread(*this); _persistThreadInstance->initialize(true, QThread::LowestPriority); - _fileName = FileUtils::standardPath(LOGS_DIRECTORY); - QHostAddress clientAddress = getLocalAddress(); - QDateTime now = QDateTime::currentDateTime(); - _fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); } FileLogger::~FileLogger() { diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index 549654ca5c..122da20ae7 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -27,7 +27,7 @@ public: virtual void locateLog() override; private: - QString _fileName; + const QString _fileName; friend class FilePersistThread; };