From 127a014b4694fdbd8dac2d16799249b82d49a203 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 15 Feb 2017 15:21:08 -0800 Subject: [PATCH 01/40] Clear caches on domain switch --- interface/src/Application.cpp | 7 +++++++ libraries/networking/src/ResourceCache.cpp | 6 +++--- libraries/networking/src/ResourceCache.h | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..1b83419db7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5152,6 +5152,7 @@ void Application::updateWindowTitle() const { #endif _window->setWindowTitle(title); } + void Application::clearDomainOctreeDetails() { // if we're about to quit, we really don't need to do any of these things... @@ -5181,6 +5182,12 @@ void Application::clearDomainOctreeDetails() { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); _recentlyClearedDomain = true; + + DependencyManager::get()->clearOtherAvatars(); + DependencyManager::get()->clearUnusedResources(); + DependencyManager::get()->clearUnusedResources(); + DependencyManager::get()->clearUnusedResources(); + DependencyManager::get()->clearUnusedResources(); } void Application::domainChanged(const QString& domainHostname) { diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index d95c6f140f..0396e0ed94 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -221,7 +221,7 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { } ResourceCache::~ResourceCache() { - clearUnusedResource(); + clearUnusedResources(); } void ResourceCache::clearATPAssets() { @@ -265,7 +265,7 @@ void ResourceCache::clearATPAssets() { void ResourceCache::refreshAll() { // Clear all unused resources so we don't have to reload them - clearUnusedResource(); + clearUnusedResources(); resetResourceCounters(); QHash> resources; @@ -418,7 +418,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { } } -void ResourceCache::clearUnusedResource() { +void ResourceCache::clearUnusedResources() { // the unused resources may themselves reference resources that will be added to the unused // list on destruction, so keep clearing until there are no references left QWriteLocker locker(&_unusedResourcesLock); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index a369416ebe..8f1f1baed2 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -249,6 +249,7 @@ public: void refreshAll(); void refresh(const QUrl& url); + void clearUnusedResources(); signals: void dirty(); @@ -298,7 +299,7 @@ protected: void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); - + /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading /// \return true if the resource began loading, otherwise false if the resource is in the pending queue static bool attemptRequest(QSharedPointer resource); @@ -309,7 +310,6 @@ private: friend class Resource; void reserveUnusedResource(qint64 resourceSize); - void clearUnusedResource(); void resetResourceCounters(); void removeResource(const QUrl& url, qint64 size = 0); From 6d7fab40530c781ffed6f6b6506352c678435033 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 21 Feb 2017 16:59:08 -0800 Subject: [PATCH 02/40] Agent Avatars sending loudness in AvatarData --- assignment-client/src/Agent.cpp | 53 +++++++++++++++++++++++++-------- assignment-client/src/Agent.h | 1 + 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index bea677aeb6..cd8c8189b2 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -88,9 +88,9 @@ void Agent::playAvatarSound(SharedSoundPointer sound) { QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound)); return; } else { - // TODO: seems to add occasional artifact in tests. I believe it is + // TODO: seems to add occasional artifact in tests. I believe it is // correct to do this, but need to figure out for sure, so commenting this - // out until I verify. + // out until I verify. // _numAvatarSoundSentBytes = 0; setAvatarSound(sound); } @@ -105,7 +105,7 @@ void Agent::handleOctreePacket(QSharedPointer message, SharedNo if (message->getSize() > statsMessageLength) { // pull out the piggybacked packet and create a new QSharedPointer for it int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength; - + auto buffer = std::unique_ptr(new char[piggyBackedSizeWithHeader]); memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader); @@ -284,7 +284,7 @@ void Agent::selectAudioFormat(const QString& selectedCodecName) { for (auto& plugin : codecPlugins) { if (_selectedCodecName == plugin->getName()) { _codec = plugin; - _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); + _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); qDebug() << "Selected Codec Plugin:" << _codec.get(); break; @@ -424,16 +424,16 @@ void Agent::executeScript() { entityScriptingInterface->setEntityTree(_entityViewer.getTree()); DependencyManager::set(_entityViewer.getTree()); - + // 100Hz timer for audio AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer(); audioTimerWorker->moveToThread(&_avatarAudioTimerThread); connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio); connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start); connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop); - connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); + connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); _avatarAudioTimerThread.start(); - + // 60Hz timer for avatar QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatar); _scriptEngine->run(); @@ -448,14 +448,14 @@ QUuid Agent::getSessionUUID() const { return DependencyManager::get()->getSessionUUID(); } -void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { +void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { // this must happen on Agent's main thread if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream)); return; } if (_isListeningToAudioStream) { - // have to tell just the audio mixer to KillAvatar. + // have to tell just the audio mixer to KillAvatar. auto nodeList = DependencyManager::get(); nodeList->eachMatchingNode( @@ -471,7 +471,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { }); } - _isListeningToAudioStream = isListeningToAudioStream; + _isListeningToAudioStream = isListeningToAudioStream; } void Agent::setIsAvatar(bool isAvatar) { @@ -552,6 +552,7 @@ void Agent::processAgentAvatar() { nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); } } + void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { _flushEncoder = false; static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0); @@ -562,6 +563,23 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } } +void Agent::computeLoudness(QByteArray* decodedBuffer) { + float loudness = 0.0f; + auto scriptedAvatar = DependencyManager::get(); + if (decodedBuffer) { + auto soundData = reinterpret_cast(decodedBuffer->constData()); + auto numFrames = decodedBuffer->size() / sizeof(int16_t); + // now iterate and come up with average + if (numFrames > 0) { + for(int i = 0; i < numFrames; i++) { + loudness += (float) std::abs(soundData[i]); + } + loudness /= numFrames; + } + } + scriptedAvatar->setAudioLoudness(loudness); +} + void Agent::processAgentAvatarAudio() { auto recordingInterface = DependencyManager::get(); bool isPlayingRecording = recordingInterface->isPlaying(); @@ -611,6 +629,9 @@ void Agent::processAgentAvatarAudio() { audioPacket->seek(sizeof(quint16)); if (silentFrame) { + // no matter what, the loudness should be set to 0 + computeLoudness(nullptr); + if (!_isListeningToAudioStream) { // if we have a silent frame and we're not listening then just send nothing and break out of here return; @@ -618,7 +639,7 @@ void Agent::processAgentAvatarAudio() { // write the codec audioPacket->writeString(_selectedCodecName); - + // write the number of silent samples so the audio-mixer can uphold timing audioPacket->writePrimitive(numAvailableSamples); @@ -628,8 +649,9 @@ void Agent::processAgentAvatarAudio() { audioPacket->writePrimitive(headOrientation); audioPacket->writePrimitive(scriptedAvatar->getPosition()); audioPacket->writePrimitive(glm::vec3(0)); + } else if (nextSoundOutput) { - + // write the codec audioPacket->writeString(_selectedCodecName); @@ -646,6 +668,8 @@ void Agent::processAgentAvatarAudio() { QByteArray encodedBuffer; if (_flushEncoder) { encodeFrameOfZeros(encodedBuffer); + // loudness is 0 + computeLoudness(nullptr); } else { QByteArray decodedBuffer(reinterpret_cast(nextSoundOutput), numAvailableSamples*sizeof(int16_t)); if (_encoder) { @@ -654,10 +678,15 @@ void Agent::processAgentAvatarAudio() { } else { encodedBuffer = decodedBuffer; } + computeLoudness(&decodedBuffer); } audioPacket->write(encodedBuffer.constData(), encodedBuffer.size()); } + // we should never have both nextSoundOutput being null and silentFrame being false, but lets + // assert on it in case things above change in a bad way + assert(nextSoundOutput || silentFrame); + // write audio packet to AudioMixer nodes auto nodeList = DependencyManager::get(); nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node) { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index c9b1707101..7f04b4746f 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -82,6 +82,7 @@ private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); void encodeFrameOfZeros(QByteArray& encodedZeros); + void computeLoudness(QByteArray* decodedBuffer); std::unique_ptr _scriptEngine; EntityEditPacketSender _entityEditSender; From 397eb89c143d2e746ca6e5ecd3980c210ea95e04 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 22 Feb 2017 08:38:49 -0800 Subject: [PATCH 03/40] compiler warning - odd --- assignment-client/src/Agent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cd8c8189b2..2f91571d8b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -568,7 +568,7 @@ void Agent::computeLoudness(QByteArray* decodedBuffer) { auto scriptedAvatar = DependencyManager::get(); if (decodedBuffer) { auto soundData = reinterpret_cast(decodedBuffer->constData()); - auto numFrames = decodedBuffer->size() / sizeof(int16_t); + int numFrames = decodedBuffer->size() / sizeof(int16_t); // now iterate and come up with average if (numFrames > 0) { for(int i = 0; i < numFrames; i++) { From d9a716bf3de4bc95ddd0d98f5ecd0e5a3487cc10 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 24 Feb 2017 18:42:52 +1300 Subject: [PATCH 04/40] If someone else is grabbing entity you want to grab show their grab beam --- .../system/controllers/handControllerGrab.js | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ea76490b7b..5064705f78 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -419,10 +419,10 @@ function entityIsGrabbedByOther(entityID) { } if (tag.slice(0, 5) == "grab-") { // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; + return tag.slice(5, 42); } } - return false; + return null; } function propsArePhysical(props) { @@ -790,10 +790,10 @@ function MyController(hand) { // for visualizations this.overlayLine = null; - - // for lights - this.overlayLine = null; this.searchSphere = null; + this.otherGrabbingLine = null; + + this.otherGrabbingUUID = null; this.waitForTriggerRelease = false; @@ -1050,6 +1050,29 @@ function MyController(hand) { } }; + this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) { + if (this.otherGrabbingLine === null) { + var lineProperties = { + lineWidth: 5, + start: avatarPosition, + end: entityPosition, + color: color, + glow: 1.0, + ignoreRayIntersection: true, + drawInFront: true, + visible: true, + alpha: 1 + }; + this.otherGrabbingLine = Overlays.addOverlay("line3d", lineProperties); + } else { + Overlays.editOverlay(this.otherGrabbingLine, { + start: avatarPosition, + end: entityPosition, + color: color + }); + } + }; + this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -1093,14 +1116,20 @@ function MyController(hand) { } }; - this.turnOffVisualizations = function() { + this.otherGrabbingLineOff = function() { + if (this.otherGrabbingLine !== null) { + Overlays.deleteOverlay(this.otherGrabbingLine); + } + this.otherGrabbingLine = null; + }; + this.turnOffVisualizations = function() { this.overlayLineOff(); this.grabPointSphereOff(); this.lineOff(); this.searchSphereOff(); + this.otherGrabbingLineOff(); restore2DMode(); - }; this.triggerPress = function(value) { @@ -1504,7 +1533,8 @@ function MyController(hand) { return false; } - if (entityIsGrabbedByOther(entityID)) { + this.otherGrabbingUUID = entityIsGrabbedByOther(entityID); + if (this.otherGrabbingUUID !== null) { // don't distance grab something that is already grabbed. if (debug) { print("distance grab is skipping '" + props.name + "': already grabbed by another."); @@ -1683,6 +1713,7 @@ function MyController(hand) { } else { // potentialFarTriggerEntity = entity; } + this.otherGrabbingLineOff(); } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { this.grabbedEntity = entity; @@ -1692,7 +1723,21 @@ function MyController(hand) { } else { // potentialFarGrabEntity = entity; } + this.otherGrabbingLineOff(); + } else if (this.otherGrabbingUUID !== null) { + if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { + var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); + var IN_FRONT_OF_AVATAR = { x: 0, y: 0, z: 0.2 }; + var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); + this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD); + } else { + this.otherGrabbingLineOff(); + } + } else { + this.otherGrabbingLineOff(); } + } else { + this.otherGrabbingLineOff(); } this.updateEquipHaptics(potentialEquipHotspot, handPosition); @@ -2268,6 +2313,7 @@ function MyController(hand) { this.lineOff(); this.overlayLineOff(); this.searchSphereOff(); + this.otherGrabbingLineOff(); this.dropGestureReset(); this.clearEquipHaptics(); From a98824f48300179684c5e3817545cbf67de1bfc6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 25 Feb 2017 08:13:15 +1300 Subject: [PATCH 05/40] Code review --- scripts/system/controllers/handControllerGrab.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 5064705f78..fe0fe19ae3 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -417,9 +417,11 @@ function entityIsGrabbedByOther(entityID) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; } - if (tag.slice(0, 5) == "grab-") { + var GRAB_PREFIX_LENGTH = 5; + var UUID_LENGTH = 38; + if (tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") { // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return tag.slice(5, 42); + return tag.slice(GRAB_PREFIX_LENGTH, GRAB_PREFIX_LENGTH + UUID_LENGTH - 1); } } return null; From f4a3627b767294133ef060ad5e74630721d29093 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 09:47:36 +1300 Subject: [PATCH 06/40] Make other avatar's grab beam start at more natural position --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index fe0fe19ae3..1ab06927ac 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1729,7 +1729,7 @@ function MyController(hand) { } else if (this.otherGrabbingUUID !== null) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); - var IN_FRONT_OF_AVATAR = { x: 0, y: 0, z: 0.2 }; + var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 }; // Up from hips and in front of avatar. var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD); } else { From 08cae1d3f23c74b34a4bcb9349b0a8b68a3fc61e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 10:02:06 +1300 Subject: [PATCH 07/40] Make other avatar's grab beam finish at entity's centroid --- scripts/system/controllers/handControllerGrab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 1ab06927ac..cdfb33ee06 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1731,7 +1731,11 @@ function MyController(hand) { var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 }; // Up from hips and in front of avatar. var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); - this.otherGrabbingLineOn(startPosition, rayPickInfo.properties.position, COLORS_GRAB_DISTANCE_HOLD); + var finishPisition = Vec3.sum(rayPickInfo.properties.position, // Entity's centroid. + Vec3.multiplyQbyV(rayPickInfo.properties.rotation , + Vec3.multiplyVbyV(rayPickInfo.properties.dimensions, + Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayPickInfo.properties.registrationPoint)))); + this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD); } else { this.otherGrabbingLineOff(); } From 6fcc096bcf57cd2d3429906e3ceec3e16dd5e140 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 10:14:38 +1300 Subject: [PATCH 08/40] Fix JavaScript error --- scripts/system/controllers/handControllerGrab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index cdfb33ee06..3df7b91b6a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -413,13 +413,13 @@ function entityIsGrabbedByOther(entityID) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); var tag = actionArguments.tag; - if (tag == getTag()) { + if (tag === getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; } var GRAB_PREFIX_LENGTH = 5; var UUID_LENGTH = 38; - if (tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") { + if (tag && tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") { // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. return tag.slice(GRAB_PREFIX_LENGTH, GRAB_PREFIX_LENGTH + UUID_LENGTH - 1); } From 5b6f953ac270d60aab93ae08cfa2302dafee1161 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 17:35:13 +1300 Subject: [PATCH 09/40] Add hand controller state of distance-rotating for second grab hand --- .../system/controllers/handControllerGrab.js | 138 ++++++++++++++---- 1 file changed, 109 insertions(+), 29 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d313d1cfa1..0bc0600c81 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -205,14 +205,15 @@ var HARDWARE_MOUSE_ID = 0; // Value reserved for hardware mouse. var STATE_OFF = 0; var STATE_SEARCHING = 1; var STATE_DISTANCE_HOLDING = 2; -var STATE_NEAR_GRABBING = 3; -var STATE_NEAR_TRIGGER = 4; -var STATE_FAR_TRIGGER = 5; -var STATE_HOLD = 6; -var STATE_ENTITY_STYLUS_TOUCHING = 7; -var STATE_ENTITY_LASER_TOUCHING = 8; -var STATE_OVERLAY_STYLUS_TOUCHING = 9; -var STATE_OVERLAY_LASER_TOUCHING = 10; +var STATE_DISTANCE_ROTATING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_NEAR_TRIGGER = 5; +var STATE_FAR_TRIGGER = 6; +var STATE_HOLD = 7; +var STATE_ENTITY_STYLUS_TOUCHING = 8; +var STATE_ENTITY_LASER_TOUCHING = 9; +var STATE_OVERLAY_STYLUS_TOUCHING = 10; +var STATE_OVERLAY_LASER_TOUCHING = 11; var CONTROLLER_STATE_MACHINE = {}; @@ -231,6 +232,11 @@ CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding" }; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_ROTATING] = { + name: "distance_rotating", + enterMethod: "distanceRotatingEnter", + updateMethod: "distanceRotating" +}; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", enterMethod: "nearGrabbingEnter", @@ -869,7 +875,8 @@ function MyController(hand) { newState !== STATE_OVERLAY_LASER_TOUCHING)) { return; } - setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING)); + setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_DISTANCE_ROTATING) + || (newState === STATE_NEAR_GRABBING)); if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -1439,9 +1446,10 @@ function MyController(hand) { var props = entityPropertiesCache.getProps(hotspot.entityID); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || - this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && - this.getOtherHandController().grabbedThingID == hotspot.entityID); + var otherHandControllerState = this.getOtherHandController().state; + var okToEquipFromOtherHand = ((otherHandControllerState === STATE_NEAR_GRABBING + || otherHandControllerState === STATE_DISTANCE_HOLDING || otherHandControllerState === STATE_DISTANCE_ROTATING) + && this.getOtherHandController().grabbedThingID === hotspot.entityID); var hasParent = true; if (props.parentID === NULL_UUID) { hasParent = false; @@ -1731,7 +1739,11 @@ function MyController(hand) { this.grabbedThingID = entity; this.grabbedIsOverlay = false; this.grabbedDistance = rayPickInfo.distance; - this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) { + this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'"); + } else { + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + } return; } else { // potentialFarGrabEntity = entity; @@ -2036,6 +2048,19 @@ function MyController(hand) { return (dimensions.x * dimensions.y * dimensions.z) * density; }; + this.ensureDynamic = function () { + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); + if (props.dynamic && props.parentID == NULL_UUID) { + var velocity = props.velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z: 0.0 }; + Entities.editEntity(this.grabbedThingID, { velocity: velocity }); + } + } + }; + this.distanceHoldingEnter = function() { this.clearEquipHaptics(); this.grabPointSphereOff(); @@ -2102,25 +2127,20 @@ function MyController(hand) { this.previousRoomControllerPosition = roomControllerPosition; }; - this.ensureDynamic = function() { - // if we distance hold something and keep it very still before releasing it, it ends up - // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID == NULL_UUID) { - var velocity = props.velocity; - if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - velocity = { x: 0.0, y: 0.2, z:0.0 }; - Entities.editEntity(this.grabbedThingID, { velocity: velocity }); - } - } - }; - this.distanceHolding = function(deltaTime, timestamp) { if (!this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); this.ensureDynamic(); this.setState(STATE_OFF, "trigger released"); + if (this.getOtherHandController().state === STATE_DISTANCE_ROTATING) { + this.getOtherHandController().setState(STATE_SEARCHING, "trigger released on holding controller"); + // Can't set state of other controller to STATE_DISTANCE_HOLDING because then either: + // (a) The entity would jump to line up with the formerly rotating controller's orientation, or + // (b) The grab beam would need an orientation offset to the controller's true orientation. + // Neither of these options is good, so instead set STATE_SEARCHING and subsequently let the formerly distance + // rotating controller start distance holding the entity if it happens to be pointing at the entity. + } return; } @@ -2209,11 +2229,11 @@ function MyController(hand) { } this.maybeScale(grabbedProperties); + // visualizations - var rayPickInfo = this.calcRayPickInfo(this.hand); - - this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); + this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), + COLORS_GRAB_DISTANCE_HOLD); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); var success = Entities.updateAction(this.grabbedThingID, this.actionID, { @@ -2232,6 +2252,66 @@ function MyController(hand) { this.previousRoomControllerPosition = roomControllerPosition; }; + this.distanceRotatingEnter = function() { + this.clearEquipHaptics(); + this.grabPointSphereOff(); + + this.shouldScale = false; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.currentCameraOrientation = Camera.orientation; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // TODO + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.turnOffVisualizations(); + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.distanceRotating = function(deltaTime, timestamp) { + + if (!this.triggerClicked) { + this.callEntityMethodOnGrabbed("releaseGrab"); + this.ensureDynamic(); + this.setState(STATE_OFF, "trigger released"); + return; + } + + // TODO + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + + // TODO + + var rayPickInfo = this.calcRayPickInfo(this.hand); + this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), + COLORS_GRAB_DISTANCE_HOLD); + + // TODO + } + this.setupHoldAction = function() { this.actionID = Entities.addAction("hold", this.grabbedThingID, { hand: this.hand === RIGHT_HAND ? "right" : "left", From 28768d4a781a267e15bc51ecca84c13be7748702 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 22:22:34 +1300 Subject: [PATCH 10/40] Rotate the entity by twice the rotating controller's rotation --- .../system/controllers/handControllerGrab.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0bc0600c81..2910092293 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2283,7 +2283,8 @@ function MyController(hand) { targetPosition = Vec3.sum(targetPosition, worldControllerPosition); this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - // TODO + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.turnOffVisualizations(); @@ -2299,17 +2300,24 @@ function MyController(hand) { return; } - // TODO - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - // TODO + // Delta rotation of grabbing controller since last update. + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + this.getOtherHandController().currentObjectRotation = Quat.multiply(controllerRotationDelta, + this.getOtherHandController().currentObjectRotation); var rayPickInfo = this.calcRayPickInfo(this.hand); this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); - // TODO + this.previousWorldControllerRotation = worldControllerRotation; } this.setupHoldAction = function() { From e3f2f3c5bc830064d6aef54f8259bffca4f6cd2d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 23:04:23 +1300 Subject: [PATCH 11/40] Tidying --- .../system/controllers/handControllerGrab.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2910092293..2dfa7e6948 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2256,29 +2256,15 @@ function MyController(hand) { this.clearEquipHaptics(); this.grabPointSphereOff(); - this.shouldScale = false; - var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; - // transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.currentCameraOrientation = Camera.orientation; - this.grabRadius = this.grabbedDistance; - this.grabRadialVelocity = 0.0; - // offset between controller vector at the grab radius and the entity position + // Offset between controller vector at the grab radius and the entity position. var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); targetPosition = Vec3.sum(targetPosition, worldControllerPosition); this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); @@ -2288,7 +2274,6 @@ function MyController(hand) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.turnOffVisualizations(); - this.previousRoomControllerPosition = roomControllerPosition; }; this.distanceRotating = function(deltaTime, timestamp) { From d8d17be0e06206ce94839765d747259feba505a3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 23:22:07 +1300 Subject: [PATCH 12/40] Rotate about translation grab position --- scripts/system/controllers/handControllerGrab.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2dfa7e6948..08877e2b10 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2298,6 +2298,11 @@ function MyController(hand) { this.getOtherHandController().currentObjectRotation = Quat.multiply(controllerRotationDelta, this.getOtherHandController().currentObjectRotation); + // Rotate about the translation controller's target position. + this.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.offsetPosition); + this.getOtherHandController().offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, + this.getOtherHandController().offsetPosition); + var rayPickInfo = this.calcRayPickInfo(this.hand); this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); From 2635657456a097bfc99d28eb44711f3b7a6b0797 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 28 Feb 2017 13:24:25 -0700 Subject: [PATCH 13/40] zappoman's feedback, plus added const corrrectness to computeLoudness --- assignment-client/src/Agent.cpp | 4 +++- assignment-client/src/Agent.h | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 5211c3beaa..9c830ef391 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -380,6 +380,8 @@ void Agent::executeScript() { audioTransform.setTranslation(scriptedAvatar->getPosition()); audioTransform.setRotation(headOrientation); + computeLoudness(&audio); + QByteArray encodedBuffer; if (_encoder) { _encoder->encode(audio, encodedBuffer); @@ -571,7 +573,7 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } } -void Agent::computeLoudness(QByteArray* decodedBuffer) { +void Agent::computeLoudness(const QByteArray* decodedBuffer) { float loudness = 0.0f; auto scriptedAvatar = DependencyManager::get(); if (decodedBuffer) { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 7f04b4746f..ce7393011f 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -68,10 +68,10 @@ private slots: void handleAudioPacket(QSharedPointer message); void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); void handleJurisdictionPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleSelectedAudioFormat(QSharedPointer message); + void handleSelectedAudioFormat(QSharedPointer message); void nodeActivated(SharedNodePointer activatedNode); - + void processAgentAvatar(); void processAgentAvatarAudio(); @@ -82,7 +82,7 @@ private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); void encodeFrameOfZeros(QByteArray& encodedZeros); - void computeLoudness(QByteArray* decodedBuffer); + void computeLoudness(const QByteArray* decodedBuffer); std::unique_ptr _scriptEngine; EntityEditPacketSender _entityEditSender; @@ -104,10 +104,10 @@ private: bool _isAvatar = false; QTimer* _avatarIdentityTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; - + CodecPluginPointer _codec; QString _selectedCodecName; - Encoder* _encoder { nullptr }; + Encoder* _encoder { nullptr }; QThread _avatarAudioTimerThread; bool _flushEncoder { false }; }; From 01abb4bdb61ed9e6eff63da995cd74400f4cbad1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 24 Feb 2017 15:34:20 -0800 Subject: [PATCH 14/40] Exposed DebugDraw interface to Java Script --- libraries/script-engine/src/Quat.h | 10 ++++ libraries/script-engine/src/ScriptEngine.cpp | 3 + libraries/script-engine/src/Vec3.h | 9 +++ libraries/shared/src/DebugDraw.cpp | 8 +-- libraries/shared/src/DebugDraw.h | 63 ++++++++++++++++---- tools/jsdoc/plugins/hifi.js | 1 + 6 files changed, 80 insertions(+), 14 deletions(-) diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index bb81f24586..b51f1cb47e 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -19,6 +19,16 @@ #include #include +/**jsdoc + * A Quaternion + * + * @typedef Quat + * @property {float} x imaginary component i. + * @property {float} y imaginary component j. + * @property {float} z imaginary component k. + * @property {float} w real component. + */ + /// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API class Quat : public QObject { Q_OBJECT diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 83f2f5ccc0..2147374367 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -630,6 +631,8 @@ void ScriptEngine::init() { registerGlobalObject("Tablet", DependencyManager::get().data()); registerGlobalObject("Assets", &_assetScriptingInterface); registerGlobalObject("Resources", DependencyManager::get().data()); + + registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 5f524eaf74..b3a3dc3035 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -37,6 +37,15 @@ * @property {float} z Z-coordinate of the vector. */ +/**jsdoc + * A 4-dimensional vector. + * + * @typedef Vec4 + * @property {float} x X-coordinate of the vector. + * @property {float} y Y-coordinate of the vector. + * @property {float} z Z-coordinate of the vector. + * @property {float} w W-coordinate of the vector. + */ /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API class Vec3 : public QObject { diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp index 04759e6187..549dbb7293 100644 --- a/libraries/shared/src/DebugDraw.cpp +++ b/libraries/shared/src/DebugDraw.cpp @@ -28,19 +28,19 @@ void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm: _rays.push_back(Ray(start, end, color)); } -void DebugDraw::addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { +void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { _markers[key] = MarkerInfo(rotation, position, color); } -void DebugDraw::removeMarker(const std::string& key) { +void DebugDraw::removeMarker(const QString& key) { _markers.erase(key); } -void DebugDraw::addMyAvatarMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { +void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { _myAvatarMarkers[key] = MarkerInfo(rotation, position, color); } -void DebugDraw::removeMyAvatarMarker(const std::string& key) { +void DebugDraw::removeMyAvatarMarker(const QString& key) { _myAvatarMarkers.erase(key); } diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index f77e281e06..ac7e8b3cbc 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -17,26 +17,69 @@ #include #include -class DebugDraw { +#include +#include + +/**jsdoc + * Helper functions to render ephemeral debug markers and lines. + * DebugDraw markers and lines are only visible locally, they are not visible by other users. + * @namespace DebugDraw + */ +class DebugDraw : public QObject { + Q_OBJECT public: static DebugDraw& getInstance(); DebugDraw(); ~DebugDraw(); - // world space line, drawn only once - void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color); + /**jsdoc + * Draws a line in world space, but it will only be visible for a single frame. + * @function DebugDraw.drawRay + * @param {Vec3} start - start position of line in world space. + * @param {Vec3} end - end position of line in world space. + * @param {Vec4} color - color of line, each component should be in the zero to one range. x = red, y = blue, z = green, w = alpha. + */ + Q_INVOKABLE void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color); - // world space maker, marker drawn every frame until it is removed. - void addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); - void removeMarker(const std::string& key); + /**jsdoc + * Adds a debug marker to the world. This marker will be drawn every frame until it is removed with DebugDraw.removeMarker. + * This can be called repeatedly to change the position of the marker. + * @function DebugDraw.addMarker + * @param {string} key - name to uniquely identify this marker, later used for DebugDraw.removeMarker. + * @param {Quat} rotation - start position of line in world space. + * @param {Vec3} position - position of the marker in world space. + * @param {Vec4} color - color of the marker. + */ + Q_INVOKABLE void addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); - // myAvatar relative marker, maker is drawn every frame until it is removed. - void addMyAvatarMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); - void removeMyAvatarMarker(const std::string& key); + /**jsdoc + * Removes debug marker from the world. Once a marker is removed, it will no longer be visible. + * @function DebugDraw.removeMarker + * @param {string} key - name of marker to remove. + */ + Q_INVOKABLE void removeMarker(const QString& key); + + /**jsdoc + * Adds a debug marker to the world, this marker will be drawn every frame until it is removed with DebugDraw.removeMyAvatarMarker. + * This can be called repeatedly to change the position of the marker. + * @function DebugDraw.addMyAvatarMarker + * @param {string} key - name to uniquely identify this marker, later used for DebugDraw.removeMyAvatarMarker. + * @param {Quat} rotation - start position of line in avatar space. + * @param {Vec3} position - position of the marker in avatar space. + * @param {Vec4} color - color of the marker. + */ + Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); + + /**jsdoc + * Removes debug marker from the world. Once a marker is removed, it will no longer be visible + * @function DebugDraw.removeMyAvatarMarker + * @param {string} key - name of marker to remove. + */ + Q_INVOKABLE void removeMyAvatarMarker(const QString& key); using MarkerInfo = std::tuple; - using MarkerMap = std::unordered_map; + using MarkerMap = std::map; using Ray = std::tuple; using Rays = std::vector; diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 8a6d2bf0f2..8be15c4103 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -21,6 +21,7 @@ exports.handlers = { '../../libraries/networking/src', '../../libraries/animation/src', '../../libraries/entities/src', + '../../libraries/shared/src' ]; var exts = ['.h', '.cpp']; From f41845710042c935d078ef0d04111ca99480053b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 24 Feb 2017 15:50:41 -0800 Subject: [PATCH 15/40] Index finger touch support for the tablet-ui. --- .../system/controllers/handControllerGrab.js | 215 ++++++++++++++---- 1 file changed, 169 insertions(+), 46 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d313d1cfa1..5f854cd5ab 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -28,7 +28,7 @@ Script.include("/~/system/libraries/controllers.js"); // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; var FORCE_IGNORE_IK = false; @@ -46,6 +46,8 @@ var BUMPER_ON_VALUE = 0.5; var THUMB_ON_VALUE = 0.5; +var USE_FINGER_AS_STYLUS = true; + var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; var HAPTIC_TEXTURE_STRENGTH = 0.1; @@ -74,6 +76,10 @@ var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative numbe var WEB_TOUCH_TOO_CLOSE = 0.03; // if the stylus is pushed far though the web surface, don't consider it touching var WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE = 0.01; +var FINGER_TOUCH_Y_OFFSET = -0.02; +var FINGER_TOUCH_MIN = -0.01 - FINGER_TOUCH_Y_OFFSET; +var FINGER_TOUCH_MAX = 0.01 - FINGER_TOUCH_Y_OFFSET; + // // distant manipulation // @@ -252,20 +258,51 @@ CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { updateMethod: "farTrigger" }; CONTROLLER_STATE_MACHINE[STATE_ENTITY_STYLUS_TOUCHING] = { - name: "entityTouching", + name: "entityStylusTouching", + enterMethod: "entityTouchingEnter", + exitMethod: "entityTouchingExit", + updateMethod: "entityTouching" +}; +CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = { + name: "entityLaserTouching", enterMethod: "entityTouchingEnter", exitMethod: "entityTouchingExit", updateMethod: "entityTouching" }; -CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_ENTITY_STYLUS_TOUCHING]; CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = { - name: "overlayTouching", + name: "overlayStylusTouching", + enterMethod: "overlayTouchingEnter", + exitMethod: "overlayTouchingExit", + updateMethod: "overlayTouching" +}; +CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = { + name: "overlayLaserTouching", enterMethod: "overlayTouchingEnter", exitMethod: "overlayTouchingExit", updateMethod: "overlayTouching" }; -CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING]; +function getFingerWorldLocation(hand) { + var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; + + var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); + var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); + var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); + + // local y offset. + var localYOffset = Vec3.multiplyQbyV(worldFingerRotation, {x: 0, y: FINGER_TOUCH_Y_OFFSET, z: 0}); + + var offsetWorldFingerPosition = Vec3.sum(worldFingerPosition, localYOffset); + + return { + position: offsetWorldFingerPosition, + orientation: worldFingerRotation, + rotation: worldFingerRotation, + valid: true + }; +} function distanceBetweenPointAndEntityBoundingBox(point, entityProps) { var entityXform = new Xform(entityProps.rotation, entityProps.position); @@ -347,6 +384,7 @@ function handLaserIntersectItem(position, rotation, start) { direction: rayDirection, length: PICK_MAX_DISTANCE }; + return intersectionInfo; } else { // entity has been destroyed? or is no longer in cache @@ -816,6 +854,8 @@ function MyController(hand) { this.tabletStabbedPos2D = null; this.tabletStabbedPos3D = null; + this.useFingerInsteadOfStylus = false; + var _this = this; var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; @@ -829,10 +869,17 @@ function MyController(hand) { this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); + if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) { + this.useFingerInsteadOfStylus = true; + } else { + this.useFingerInsteadOfStylus = false; + } + if (this.ignoreInput()) { // Most hand input is disabled, because we are interacting with the 2d hud. // However, we still should check for collisions of the stylus with the web overlay. + var controllerLocation = getControllerWorldLocation(this.handToController(), true); this.processStylus(controllerLocation.position); @@ -1174,30 +1221,54 @@ function MyController(hand) { }; this.processStylus = function(worldHandPosition) { - // see if the hand is near a tablet or web-entity - var candidateEntities = Entities.findEntities(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); - entityPropertiesCache.addEntities(candidateEntities); - var nearWeb = false; - for (var i = 0; i < candidateEntities.length; i++) { - var props = entityPropertiesCache.getProps(candidateEntities[i]); - if (props && (props.type == "Web" || this.isTablet(candidateEntities[i]))) { - nearWeb = true; - break; + + var performRayTest = false; + if (this.useFingerInsteadOfStylus) { + this.hideStylus(); + performRayTest = true; + } else { + var i; + + // see if the hand is near a tablet or web-entity + var candidateEntities = Entities.findEntities(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); + entityPropertiesCache.addEntities(candidateEntities); + for (i = 0; i < candidateEntities.length; i++) { + var props = entityPropertiesCache.getProps(candidateEntities[i]); + if (props && (props.type == "Web" || this.isTablet(candidateEntities[i]))) { + performRayTest = true; + break; + } + } + + if (!performRayTest) { + var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); + for (i = 0; i < candidateOverlays.length; i++) { + if (this.isTablet(candidateOverlays[i])) { + performRayTest = true; + break; + } + } + } + + if (performRayTest) { + this.showStylus(); + } else { + this.hideStylus(); } } - var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); - for (var j = 0; j < candidateOverlays.length; j++) { - if (this.isTablet(candidateOverlays[j])) { - nearWeb = true; + if (performRayTest) { + var rayPickInfo = this.calcRayPickInfo(this.hand, this.useFingerInsteadOfStylus); + var max, min; + if (this.useFingerInsteadOfStylus) { + max = FINGER_TOUCH_MAX; + min = FINGER_TOUCH_MIN; + } else { + max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET; + min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; } - } - if (nearWeb) { - this.showStylus(); - var rayPickInfo = this.calcRayPickInfo(this.hand); - if (rayPickInfo.distance < WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET && - rayPickInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE) { + if (rayPickInfo.distance < max && rayPickInfo.distance > min) { this.handleStylusOnHomeButton(rayPickInfo); if (this.handleStylusOnWebEntity(rayPickInfo)) { return; @@ -1206,10 +1277,8 @@ function MyController(hand) { return; } } else { - this.homeButtonTouched = false; - } - } else { - this.hideStylus(); + this.homeButtonTouched = false; + } } }; @@ -1324,10 +1393,17 @@ function MyController(hand) { // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + // @param {bool} if true use the world position/orientation of the index finger to cast the ray from. // @returns {object} returns object with two keys entityID and distance // - this.calcRayPickInfo = function(hand) { - var controllerLocation = getControllerWorldLocation(this.handToController(), true); + this.calcRayPickInfo = function(hand, useFingerInsteadOfController) { + + var controllerLocation; + if (useFingerInsteadOfController) { + controllerLocation = getFingerWorldLocation(hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } var worldHandPosition = controllerLocation.position; var worldHandRotation = controllerLocation.orientation; @@ -2783,8 +2859,13 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, - getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2820,8 +2901,13 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, - getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { var pointerEvent; if (this.deadspotExpired) { @@ -2861,12 +2947,26 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, - getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { + var max, min; + if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { + max = FINGER_TOUCH_MAX; + min = FINGER_TOUCH_MIN; + } else { + max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET; + min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; + } + if (this.state == STATE_ENTITY_STYLUS_TOUCHING && - intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET) { + intersectInfo.distance > max) { this.setState(STATE_OFF, "pulled away from web entity"); return; } @@ -2909,8 +3009,13 @@ function MyController(hand) { this.overlayTouchingEnter = function () { // Test for intersection between controller laser and Web overlay plane. - var intersectInfo = - handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2945,8 +3050,13 @@ function MyController(hand) { this.overlayTouchingExit = function () { // Test for intersection between controller laser and Web overlay plane. - var intersectInfo = - handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { var pointerEvent; @@ -3003,12 +3113,25 @@ function MyController(hand) { } // Test for intersection between controller laser and Web overlay plane. - var intersectInfo = - handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true)); + var controllerLocation; + if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { + controllerLocation = getFingerWorldLocation(this.hand); + } else { + controllerLocation = getControllerWorldLocation(this.handToController(), true); + } + var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { - if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && - intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) { + var max, min; + if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { + max = FINGER_TOUCH_MAX; + min = FINGER_TOUCH_MIN; + } else { + max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE; + min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; + } + + if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && intersectInfo.distance > max) { this.grabbedThingID = null; this.setState(STATE_OFF, "pulled away from overlay"); return; @@ -3019,7 +3142,7 @@ function MyController(hand) { if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && !this.tabletStabbed && - intersectInfo.distance < WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE) { + intersectInfo.distance < min) { // they've stabbed the tablet, don't send events until they pull back this.tabletStabbed = true; this.tabletStabbedPos2D = pos2D; From d142c3d69bfd851b57f6901bf24440b3c0ec4697 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Mar 2017 13:29:24 -0800 Subject: [PATCH 16/40] eslint fix --- scripts/system/controllers/handControllerGrab.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 5f854cd5ab..dbc2bebd31 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2956,13 +2956,11 @@ function MyController(hand) { var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { - var max, min; + var max; if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { max = FINGER_TOUCH_MAX; - min = FINGER_TOUCH_MIN; } else { max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET; - min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; } if (this.state == STATE_ENTITY_STYLUS_TOUCHING && From 465a9e2008f18e893b27e33e87e4c58c5c475bd2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Mar 2017 14:31:04 -0800 Subject: [PATCH 17/40] Responsiveness improvement to tablet PointerEvents sent from handControllerGrab.js to the Qml window are now direct connections, instead of queued. This should reduce latency of laser and finger pressed onto the tablet. --- interface/src/ui/overlays/Web3DOverlay.cpp | 33 ++++++++++++++-------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index bfc37ccf60..7b9e075d64 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -198,18 +198,27 @@ void Web3DOverlay::render(RenderArgs* args) { _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); currentContext->makeCurrent(currentSurface); + auto selfOverlayID = getOverlayID(); + std::weak_ptr weakSelf = std::dynamic_pointer_cast(qApp->getOverlays().getOverlay(selfOverlayID)); auto forwardPointerEvent = [=](OverlayID overlayID, const PointerEvent& event) { - if (overlayID == getOverlayID()) { - handlePointerEvent(event); + auto self = weakSelf.lock(); + if (!self) { + return; + } + if (overlayID == selfOverlayID) { + self->handlePointerEvent(event); } }; - _mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, forwardPointerEvent); - _mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, forwardPointerEvent); - _mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, forwardPointerEvent); - _hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay, - [=](OverlayID overlayID, const PointerEvent& event) { - if (this->_pressed && this->getOverlayID() == overlayID) { + _mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); + _mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); + _mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, this, forwardPointerEvent, Qt::DirectConnection); + _hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay, this, [=](OverlayID overlayID, const PointerEvent& event) { + auto self = weakSelf.lock(); + if (!self) { + return; + } + if (self->_pressed && overlayID == selfOverlayID) { // If the user mouses off the overlay while the button is down, simulate a touch end. QTouchEvent::TouchPoint point; point.setId(event.getID()); @@ -222,12 +231,12 @@ void Web3DOverlay::render(RenderArgs* args) { touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints); - touchEvent->setWindow(_webSurface->getWindow()); + touchEvent->setWindow(self->_webSurface->getWindow()); touchEvent->setDevice(&_touchDevice); - touchEvent->setTarget(_webSurface->getRootItem()); - QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); + touchEvent->setTarget(self->_webSurface->getRootItem()); + QCoreApplication::postEvent(self->_webSurface->getWindow(), touchEvent); } - }); + }, Qt::DirectConnection); _emitScriptEventConnection = connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); _webEventReceivedConnection = connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); From 81451191c1fef266e2b78b9549b1bb855710caea Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 1 Mar 2017 18:06:03 -0700 Subject: [PATCH 18/40] no need to get the ScriptableAvatar from DependencyManager every time --- assignment-client/src/Agent.cpp | 14 ++++++-------- assignment-client/src/Agent.h | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 9c830ef391..d20ef2e687 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -43,7 +43,6 @@ #include #include // TODO: consider moving to scriptengine.h -#include "avatars/ScriptableAvatar.h" #include "entities/AssignmentParentFinder.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" @@ -380,7 +379,7 @@ void Agent::executeScript() { audioTransform.setTranslation(scriptedAvatar->getPosition()); audioTransform.setRotation(headOrientation); - computeLoudness(&audio); + computeLoudness(&audio, scriptedAvatar); QByteArray encodedBuffer; if (_encoder) { @@ -573,9 +572,8 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } } -void Agent::computeLoudness(const QByteArray* decodedBuffer) { +void Agent::computeLoudness(const QByteArray* decodedBuffer, QSharedPointer scriptableAvatar) { float loudness = 0.0f; - auto scriptedAvatar = DependencyManager::get(); if (decodedBuffer) { auto soundData = reinterpret_cast(decodedBuffer->constData()); int numFrames = decodedBuffer->size() / sizeof(int16_t); @@ -587,7 +585,7 @@ void Agent::computeLoudness(const QByteArray* decodedBuffer) { loudness /= numFrames; } } - scriptedAvatar->setAudioLoudness(loudness); + scriptableAvatar->setAudioLoudness(loudness); } void Agent::processAgentAvatarAudio() { @@ -640,7 +638,7 @@ void Agent::processAgentAvatarAudio() { if (silentFrame) { // no matter what, the loudness should be set to 0 - computeLoudness(nullptr); + computeLoudness(nullptr, scriptedAvatar); if (!_isListeningToAudioStream) { // if we have a silent frame and we're not listening then just send nothing and break out of here @@ -679,7 +677,7 @@ void Agent::processAgentAvatarAudio() { if (_flushEncoder) { encodeFrameOfZeros(encodedBuffer); // loudness is 0 - computeLoudness(nullptr); + computeLoudness(nullptr, scriptedAvatar); } else { QByteArray decodedBuffer(reinterpret_cast(nextSoundOutput), numAvailableSamples*sizeof(int16_t)); if (_encoder) { @@ -688,7 +686,7 @@ void Agent::processAgentAvatarAudio() { } else { encodedBuffer = decodedBuffer; } - computeLoudness(&decodedBuffer); + computeLoudness(&decodedBuffer, scriptedAvatar); } audioPacket->write(encodedBuffer.constData(), encodedBuffer.size()); } diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index ce7393011f..0ce7b71d5d 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -30,6 +30,7 @@ #include #include "MixedAudioStream.h" +#include "avatars/ScriptableAvatar.h" class Agent : public ThreadedAssignment { Q_OBJECT @@ -82,7 +83,7 @@ private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); void encodeFrameOfZeros(QByteArray& encodedZeros); - void computeLoudness(const QByteArray* decodedBuffer); + void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer); std::unique_ptr _scriptEngine; EntityEditPacketSender _entityEditSender; From 4f03c06a948ce37283f07247e73ffc793fa24f2e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Mar 2017 18:07:53 -0800 Subject: [PATCH 19/40] Added General Preference to control stylus vs finger usage By default the finger is preferred over the stylus. --- interface/src/Application.cpp | 6 ++++++ interface/src/Application.h | 3 +++ interface/src/ui/PreferencesDialog.cpp | 6 ++++++ scripts/system/controllers/handControllerGrab.js | 7 +++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d48fe19a99..d37c1a259e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -549,6 +549,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false; +const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), @@ -572,6 +573,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), _tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS), + _preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _scaleMirror(1.0f), _rotateMirror(0.0f), @@ -2362,6 +2364,10 @@ void Application::setTabletVisibleToOthersSetting(bool value) { updateSystemTabletMode(); } +void Application::setPreferAvatarFingerOverStylus(bool value) { + _preferAvatarFingerOverStylusSetting.set(value); +} + void Application::setSettingConstrainToolbarPosition(bool setting) { _constrainToolbarPosition.set(setting); DependencyManager::get()->setConstrainToolbarToCenterX(setting); diff --git a/interface/src/Application.h b/interface/src/Application.h index 13c1458aee..ec6d9b19f7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -220,6 +220,8 @@ public: void setHmdTabletBecomesToolbarSetting(bool value); bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); } void setTabletVisibleToOthersSetting(bool value); + bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); } + void setPreferAvatarFingerOverStylus(bool value); float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); @@ -565,6 +567,7 @@ private: Setting::Handle _desktopTabletBecomesToolbarSetting; Setting::Handle _hmdTabletBecomesToolbarSetting; Setting::Handle _tabletVisibleToOthersSetting; + Setting::Handle _preferAvatarFingerOverStylusSetting; Setting::Handle _constrainToolbarPosition; float _scaleMirror; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d291510556..c2caf91045 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -107,6 +107,12 @@ void setupPreferences() { auto setter = [](bool value) { qApp->setTabletVisibleToOthersSetting(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Tablet Is Visible To Others", getter, setter)); } + { + auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); }; + auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); + } + // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index dbc2bebd31..e52c3344e6 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -46,8 +46,6 @@ var BUMPER_ON_VALUE = 0.5; var THUMB_ON_VALUE = 0.5; -var USE_FINGER_AS_STYLUS = true; - var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; var HAPTIC_TEXTURE_STRENGTH = 0.1; @@ -869,6 +867,11 @@ function MyController(hand) { this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); + var DEFAULT_USE_FINGER_AS_STYLUS = true; + var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); + if (USE_FINGER_AS_STYLUS === "") { + USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS; + } if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) { this.useFingerInsteadOfStylus = true; } else { From 29f263a29668e5901865c8d60aadf772183b4026 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 2 Mar 2017 12:43:41 -0700 Subject: [PATCH 20/40] oh man, this was hard to find --- assignment-client/src/Agent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d20ef2e687..be23dcfa25 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -637,8 +637,6 @@ void Agent::processAgentAvatarAudio() { audioPacket->seek(sizeof(quint16)); if (silentFrame) { - // no matter what, the loudness should be set to 0 - computeLoudness(nullptr, scriptedAvatar); if (!_isListeningToAudioStream) { // if we have a silent frame and we're not listening then just send nothing and break out of here @@ -658,6 +656,8 @@ void Agent::processAgentAvatarAudio() { audioPacket->writePrimitive(scriptedAvatar->getPosition()); audioPacket->writePrimitive(glm::vec3(0)); + // no matter what, the loudness should be set to 0 + computeLoudness(nullptr, scriptedAvatar); } else if (nextSoundOutput) { // write the codec From 644e29a43d63bf8029e4f71bf4d6d576d693eaab Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 2 Mar 2017 14:15:05 -0800 Subject: [PATCH 21/40] disable WANT_STATE_DEBUG in handControllerGrab.js --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index e52c3344e6..33254e9da2 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -28,7 +28,7 @@ Script.include("/~/system/libraries/controllers.js"); // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; var FORCE_IGNORE_IK = false; From bc256f3e8f01ce07b5740f00099e7dac59c1b4ef Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 2 Mar 2017 14:42:06 -0800 Subject: [PATCH 22/40] Fix for multi-threaded access to maps in DebugDraw. --- libraries/shared/src/DebugDraw.cpp | 30 ++++++++++++++++++++++++++++++ libraries/shared/src/DebugDraw.h | 10 ++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp index 549dbb7293..f17671da4d 100644 --- a/libraries/shared/src/DebugDraw.cpp +++ b/libraries/shared/src/DebugDraw.cpp @@ -10,6 +10,8 @@ #include "DebugDraw.h" #include "SharedUtil.h" +using Lock = std::unique_lock; + DebugDraw& DebugDraw::getInstance() { static DebugDraw* instance = globalInstance("com.highfidelity.DebugDraw"); return *instance; @@ -25,22 +27,50 @@ DebugDraw::~DebugDraw() { // world space line, drawn only once void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color) { + Lock lock(_mapMutex); _rays.push_back(Ray(start, end, color)); } void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { + Lock lock(_mapMutex); _markers[key] = MarkerInfo(rotation, position, color); } void DebugDraw::removeMarker(const QString& key) { + Lock lock(_mapMutex); _markers.erase(key); } void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { + Lock lock(_mapMutex); _myAvatarMarkers[key] = MarkerInfo(rotation, position, color); } void DebugDraw::removeMyAvatarMarker(const QString& key) { + Lock lock(_mapMutex); _myAvatarMarkers.erase(key); } +// +// accessors used by renderer +// + +DebugDraw::MarkerMap DebugDraw::getMarkerMap() const { + Lock lock(_mapMutex); + return _markers; +} + +DebugDraw::MarkerMap DebugDraw::getMyAvatarMarkerMap() const { + Lock lock(_mapMutex); + return _myAvatarMarkers; +} + +DebugDraw::Rays DebugDraw::getRays() const { + Lock lock(_mapMutex); + return _rays; +} + +void DebugDraw::clearRays() { + Lock lock(_mapMutex); + _rays.clear(); +} diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index ac7e8b3cbc..64327585fb 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -10,6 +10,7 @@ #ifndef hifi_DebugDraw_h #define hifi_DebugDraw_h +#include #include #include #include @@ -87,16 +88,17 @@ public: // accessors used by renderer // - const MarkerMap& getMarkerMap() const { return _markers; } - const MarkerMap& getMyAvatarMarkerMap() const { return _myAvatarMarkers; } + MarkerMap getMarkerMap() const; + MarkerMap getMyAvatarMarkerMap() const; void updateMyAvatarPos(const glm::vec3& pos) { _myAvatarPos = pos; } const glm::vec3& getMyAvatarPos() const { return _myAvatarPos; } void updateMyAvatarRot(const glm::quat& rot) { _myAvatarRot = rot; } const glm::quat& getMyAvatarRot() const { return _myAvatarRot; } - const Rays getRays() const { return _rays; } - void clearRays() { _rays.clear(); } + Rays getRays() const; + void clearRays(); protected: + mutable std::mutex _mapMutex; MarkerMap _markers; MarkerMap _myAvatarMarkers; glm::quat _myAvatarRot; From 388a144af19afe92d238475e46e1ede68143cfb9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 3 Mar 2017 18:44:50 +1300 Subject: [PATCH 23/40] Tidying after merge --- scripts/system/controllers/handControllerGrab.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 07190a09f2..c584e777e3 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1055,17 +1055,17 @@ function MyController(hand) { } else { if (farParentID && farParentID != NULL_UUID) { - Overlays.editOverlay(this.overlayLine, { - color: color, - endParentID: farParentID - }); + Overlays.editOverlay(this.overlayLine, { + color: color, + endParentID: farParentID + }); } else { Overlays.editOverlay(this.overlayLine, { length: Vec3.distance(farPoint, closePoint), color: color, endParentID: farParentID }); - } + } } }; @@ -2624,9 +2624,9 @@ function MyController(hand) { var grabEquipCheck = function () { if (_this.state == STATE_NEAR_GRABBING) { _this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_HOLD - _this.callEntityMethodOnGrabbed("startEquip"); - } + } else { // this.state == STATE_HOLD + _this.callEntityMethodOnGrabbed("startEquip"); + } _this.currentHandControllerTipPosition = (_this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; From e56f02d94f8c499acb50b099830d4dce05be0875 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 3 Mar 2017 14:12:04 -0800 Subject: [PATCH 24/40] Changed default for preferFingerOverStylus to false. --- interface/src/Application.cpp | 2 +- scripts/system/controllers/handControllerGrab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0335743360..56b93e57e4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -549,7 +549,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false; -const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true; +const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index db678044b5..f53512b7a7 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -896,7 +896,7 @@ function MyController(hand) { this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); - var DEFAULT_USE_FINGER_AS_STYLUS = true; + var DEFAULT_USE_FINGER_AS_STYLUS = false; var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); if (USE_FINGER_AS_STYLUS === "") { USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS; From 15d680d4f9fd1ed08ccddd7396ad4a18051fe5b1 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Mar 2017 16:56:12 -0800 Subject: [PATCH 25/40] preserve audio data during pal refresh --- scripts/system/pal.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 70b2739c96..4914cbe34c 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -248,12 +248,16 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } break; case 'refresh': + data = {}; + ExtendedOverlay.some(function (overlay) { // capture the audio data + data[overlay.key] = overlay; + }); removeOverlays(); // If filter is specified from .qml instead of through settings, update the settings. if (message.params.filter !== undefined) { Settings.setValue('pal/filtered', !!message.params.filter); } - populateUserList(message.params.selected); + populateUserList(message.params.selected, data); UserActivityLogger.palAction("refresh", ""); break; case 'displayNameUpdate': @@ -285,7 +289,7 @@ function addAvatarNode(id) { } // Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter. var avatarsOfInterest = {}; -function populateUserList(selectData) { +function populateUserList(selectData, oldAudioData) { var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')}; var data = [], avatars = AvatarList.getAvatarIdentifiers(); avatarsOfInterest = {}; @@ -317,12 +321,13 @@ function populateUserList(selectData) { if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) { return; } + var oldAudio = oldAudioData && oldAudioData[id]; var avatarPalDatum = { displayName: name, userName: '', sessionId: id || '', - audioLevel: 0.0, - avgAudioLevel: 0.0, + audioLevel: (oldAudio && oldAudio.audioLevel) || 0.0, + avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0, admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id) // ditto From 6d4abca0c122d855825fdb2cf3212ab89ba8220f Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 4 Mar 2017 11:12:19 +0200 Subject: [PATCH 26/40] Base line progress --- .../tutorials/entity_scripts/magneticBlock.js | 68 +++++++++++++++++++ scripts/tutorials/makeBlocks.js | 0 2 files changed, 68 insertions(+) create mode 100644 scripts/tutorials/entity_scripts/magneticBlock.js create mode 100644 scripts/tutorials/makeBlocks.js diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js new file mode 100644 index 0000000000..6eb0900db5 --- /dev/null +++ b/scripts/tutorials/entity_scripts/magneticBlock.js @@ -0,0 +1,68 @@ +(function(){ + + // Helper for detecting nearby objects + function findEntitiesInRange(releasedProperties) { + var dimensions = releasedProperties.dimensions; + return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) *1.5); + } + function getNearestValidEntityProperties(id) { + var releasedProperties = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]); + var entities = findEntitiesInRange(releasedProperties); + var nearestEntity = null; + var nearest = -1; + var releaseSize = Vec3.length(releasedProperties.dimensions); + entities.forEach(function(entityId) { + print('ftest ' + entityId); + var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']); + var distance = Vec3.distance(releasedProperties.position, entity.position); + var scale = releaseSize/Vec3.length(entity.dimensions); + if ((nearest === -1 || distance < nearest && scale >= 0.5 && scale <= 2 ) && entity.id !== entityId) { + nearestEntity = entity; + dnearest = distance; + } + }) + return nearestEntity; + + } + + // Create the 'class' + function MagneticBlock () { } + // Bind pre-emptive events + MagneticBlock.prototype = { + // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor + preload: function (id) { + /* + We will now override any existing userdata with the grabbable property. + Only retrieving userData + */ + var val = Entities.getEntityProperties(id, ['userData']) + var userData = {}; + if (val.userData && val.userData.length > 0 ) { + try { + userData = JSON.parse(val.userData); + } catch (e) {} + } + // Object must be triggerable inorder to bind events. + userData.grabbableKey = {grabbable: true}; + // Apply the new properties to entity of id + Entities.editEntity(id, {userData: JSON.stringify(userData)}); + this.held = false; + + // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context + var t = this; + this.callbacks = {}; + this.callbacks["releaseGrab"] = function () { + var nearest = getNearestValidEntityProperties(id); + + print(JSON.stringify(nearest)); + } + + this.releaseGrab = this.callbacks["releaseGrab"]; + + Script.scriptEnding.connect( function () { + Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab + }) + } + } + return new MagneticBlock(); +}) diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js new file mode 100644 index 0000000000..e69de29bb2 From ea3f7f02749856c9ca7bde0fc7a77f2464b13ffe Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 4 Mar 2017 18:50:30 +0200 Subject: [PATCH 27/40] Initial Magnetic block --- scripts/system/assets/sounds/entitySnap.wav | Bin 0 -> 30858 bytes .../tutorials/entity_scripts/magneticBlock.js | 79 ++++++++++++++---- scripts/tutorials/makeBlocks.js | 15 ++++ 3 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 scripts/system/assets/sounds/entitySnap.wav diff --git a/scripts/system/assets/sounds/entitySnap.wav b/scripts/system/assets/sounds/entitySnap.wav new file mode 100644 index 0000000000000000000000000000000000000000..4584f3dcaa7469e2efd170c68a30ed306674ca91 GIT binary patch literal 30858 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 0.5 && scale <= 2 ) && entity.id !== entityId) { - nearestEntity = entity; - dnearest = distance; + if(entityId !== releasedProperties.id) { + var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']); + var distance = Vec3.distance(releasedProperties.position, entity.position); + var scale = releaseSize/Vec3.length(entity.dimensions); + + if (distance < nearest && (scale >= 0.5 && scale <= 2)) { + nearestEntity = entity; + nearest = distance; + } } }) return nearestEntity; - } - // Create the 'class' function MagneticBlock () { } // Bind pre-emptive events @@ -52,13 +56,56 @@ var t = this; this.callbacks = {}; this.callbacks["releaseGrab"] = function () { - var nearest = getNearestValidEntityProperties(id); + var released = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]); + var target = getNearestValidEntityProperties(released); + if (target !== null) { + // We found nearest, now lets do the snap calculations + // Plays the snap sound between the two objects. + Audio.playSound(SNAPSOUND, { + volume: 1, + position: Vec3.mix(target.position, released.position, 0.5) + }); + // Check Nearest Axis + var difference = Vec3.subtract(released.position, target.position); + var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference); - print(JSON.stringify(nearest)); + var abs = { + x: Math.abs(relativeDifference.x), + y: Math.abs(relativeDifference.y), + z: Math.abs(relativeDifference.z) + }; + + if (abs.x >= abs.y && abs.x >= abs.z) { + relativeDifference.y = 0; + relativeDifference.z = 0; + if (relativeDifference.x > 0) { + relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2; + } else { + relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2; + } + } else if (abs.y >= abs.x && abs.y >= abs.z) { + relativeDifference.x = 0; + relativeDifference.z = 0; + if (relativeDifference.y > 0) { + relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2; + } else { + relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2; + } + } else if (abs.z >= abs.x && abs.z >= abs.y ) { + relativeDifference.x = 0; + relativeDifference.y = 0; + if (relativeDifference.z > 0) { + relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2; + } else { + relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2; + } + } + var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference); + Entities.editEntity(id, {rotation: target.rotation, position: Vec3.sum(target.position, newPosition)}) + } } this.releaseGrab = this.callbacks["releaseGrab"]; - Script.scriptEnding.connect( function () { Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab }) diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js index e69de29bb2..6a8b95bb3f 100644 --- a/scripts/tutorials/makeBlocks.js +++ b/scripts/tutorials/makeBlocks.js @@ -0,0 +1,15 @@ +// Toy +const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum. +const MIN_COLOR_VALUE = 127; +function newColor() { + color = { + red: randomPastelRGBComponent(), + green: randomPastelRGBComponent(), + blue: randomPastelRGBComponent() + }; + return color; + } +// Helper functions. +function randomPastelRGBComponent() { + return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE; +} From fe19b5511ce5b1f04c42e6db7369c1c9f075c289 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 4 Mar 2017 21:55:21 +0200 Subject: [PATCH 28/40] Fixed up blocks scripts --- .../tutorials/entity_scripts/magneticBlock.js | 240 ++++++++++-------- scripts/tutorials/makeBlocks.js | 79 ++++-- 2 files changed, 193 insertions(+), 126 deletions(-) diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js index 73f317e3b2..a375025671 100644 --- a/scripts/tutorials/entity_scripts/magneticBlock.js +++ b/scripts/tutorials/entity_scripts/magneticBlock.js @@ -1,115 +1,133 @@ -(function(){ +// +// magneticBlock.js +// +// Created by Matti Lahtinen 4/3/2017 +// Copyright 2017 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Makes the entity the script is bound to connect to nearby, similarly sized entities, like a magnet. +(function() { + const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs")); + // Preload trick for faster playback + const RANGE_MULTIPLER = 1.5; - const SNAPSOUND = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs")); - const RANGE_MULTIPLER = 1.5; - - // Helper for detecting nearby objects - function findEntitiesInRange(releasedProperties) { - var dimensions = releasedProperties.dimensions; - return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); - } - - function getNearestValidEntityProperties(releasedProperties) { - var entities = findEntitiesInRange(releasedProperties); - var nearestEntity = null; - var nearest = 9999999999999; - var releaseSize = Vec3.length(releasedProperties.dimensions); - entities.forEach(function(entityId) { - if(entityId !== releasedProperties.id) { - var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']); - var distance = Vec3.distance(releasedProperties.position, entity.position); - var scale = releaseSize/Vec3.length(entity.dimensions); - - if (distance < nearest && (scale >= 0.5 && scale <= 2)) { - nearestEntity = entity; - nearest = distance; - } - } - }) - return nearestEntity; - } - // Create the 'class' - function MagneticBlock () { } - // Bind pre-emptive events - MagneticBlock.prototype = { - // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor - preload: function (id) { - /* - We will now override any existing userdata with the grabbable property. - Only retrieving userData - */ - var val = Entities.getEntityProperties(id, ['userData']) - var userData = {}; - if (val.userData && val.userData.length > 0 ) { - try { - userData = JSON.parse(val.userData); - } catch (e) {} - } - // Object must be triggerable inorder to bind events. - userData.grabbableKey = {grabbable: true}; - // Apply the new properties to entity of id - Entities.editEntity(id, {userData: JSON.stringify(userData)}); - this.held = false; - - // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context - var t = this; - this.callbacks = {}; - this.callbacks["releaseGrab"] = function () { - var released = Entities.getEntityProperties(id,["position", "rotation", "dimensions"]); - var target = getNearestValidEntityProperties(released); - if (target !== null) { - // We found nearest, now lets do the snap calculations - // Plays the snap sound between the two objects. - Audio.playSound(SNAPSOUND, { - volume: 1, - position: Vec3.mix(target.position, released.position, 0.5) - }); - // Check Nearest Axis - var difference = Vec3.subtract(released.position, target.position); - var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference); - - var abs = { - x: Math.abs(relativeDifference.x), - y: Math.abs(relativeDifference.y), - z: Math.abs(relativeDifference.z) - }; - - if (abs.x >= abs.y && abs.x >= abs.z) { - relativeDifference.y = 0; - relativeDifference.z = 0; - if (relativeDifference.x > 0) { - relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2; - } else { - relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2; - } - } else if (abs.y >= abs.x && abs.y >= abs.z) { - relativeDifference.x = 0; - relativeDifference.z = 0; - if (relativeDifference.y > 0) { - relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2; - } else { - relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2; - } - } else if (abs.z >= abs.x && abs.z >= abs.y ) { - relativeDifference.x = 0; - relativeDifference.y = 0; - if (relativeDifference.z > 0) { - relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2; - } else { - relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2; - } - } - var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference); - Entities.editEntity(id, {rotation: target.rotation, position: Vec3.sum(target.position, newPosition)}) - } - } - - this.releaseGrab = this.callbacks["releaseGrab"]; - Script.scriptEnding.connect( function () { - Script.removeEventHandler(id, "releaseGrab", this.callbacks["releaseGrab"]); //continueNearGrab - }) + // Helper for detecting nearby objects + function findEntitiesInRange(releasedProperties) { + var dimensions = releasedProperties.dimensions; + return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); } - } - return new MagneticBlock(); + + function getNearestValidEntityProperties(releasedProperties) { + var entities = findEntitiesInRange(releasedProperties); + var nearestEntity = null; + var nearest = 9999999999999; + var releaseSize = Vec3.length(releasedProperties.dimensions); + entities.forEach(function(entityId) { + if (entityId !== releasedProperties.id) { + var entity = Entities.getEntityProperties(entityId, ['position', 'rotation', 'dimensions']); + var distance = Vec3.distance(releasedProperties.position, entity.position); + var scale = releaseSize / Vec3.length(entity.dimensions); + + if (distance < nearest && (scale >= 0.5 && scale <= 2)) { + nearestEntity = entity; + nearest = distance; + } + } + }) + return nearestEntity; + } + // Create the 'class' + function MagneticBlock() {} + // Bind pre-emptive events + MagneticBlock.prototype = { + // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor + preload: function(id) { + /* + We will now override any existing userdata with the grabbable property. + Only retrieving userData + */ + var val = Entities.getEntityProperties(id, ['userData']) + var userData = {grabbableKey: {}}; + + if (val.userData && val.userData.length > 0) { + try { + userData = JSON.parse(val.userData); + } catch (e) { + } + } + // Object must be triggerable inorder to bind events. + userData.grabbableKey.grabbable = true; + + // Apply the new properties to entity of id + Entities.editEntity(id, { + userData: JSON.stringify(userData) + }); + this.held = false; + // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context + var t = this; + this.callbacks = {}; + this.releaseGrab = function() { + var released = Entities.getEntityProperties(id, ["position", "rotation", "dimensions"]); + var target = getNearestValidEntityProperties(released); + if (target !== null) { + // We found nearest, now lets do the snap calculations + // Plays the snap sound between the two objects. + Audio.playSound(SNAPSOUND_SOURCE, { + volume: 1, + position: Vec3.mix(target.position, released.position, 0.5) + }); + // Check Nearest Axis + var difference = Vec3.subtract(released.position, target.position); + var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference); + + var abs = { + x: Math.abs(relativeDifference.x), + y: Math.abs(relativeDifference.y), + z: Math.abs(relativeDifference.z) + }; + // Check what value is greater. Simplified. + if (abs.x >= abs.y && abs.x >= abs.z) { + relativeDifference.y = 0; + relativeDifference.z = 0; + if (relativeDifference.x > 0) { + relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2; + } else { + relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2; + } + } else if (abs.y >= abs.x && abs.y >= abs.z) { + relativeDifference.x = 0; + relativeDifference.z = 0; + if (relativeDifference.y > 0) { + relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2; + } else { + relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2; + } + } else if (abs.z >= abs.x && abs.z >= abs.y) { + relativeDifference.x = 0; + relativeDifference.y = 0; + if (relativeDifference.z > 0) { + relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2; + } else { + relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2; + } + } + // Can be expanded upon to work in nearest rotation as well, but was not in spec. + var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference); + Entities.editEntity(id, { + rotation: target.rotation, + position: Vec3.sum(target.position, newPosition) + }) + } + } + + Script.scriptEnding.connect(function() { + Script.removeEventHandler(id, "releaseGrab", this.releaseGrab); + }) + } + } + return new MagneticBlock(); }) diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js index 6a8b95bb3f..bb4974498c 100644 --- a/scripts/tutorials/makeBlocks.js +++ b/scripts/tutorials/makeBlocks.js @@ -1,15 +1,64 @@ -// Toy -const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum. -const MIN_COLOR_VALUE = 127; -function newColor() { - color = { - red: randomPastelRGBComponent(), - green: randomPastelRGBComponent(), - blue: randomPastelRGBComponent() - }; - return color; - } -// Helper functions. -function randomPastelRGBComponent() { - return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE; -} +// +// makeBlocks.js +// +// Created by Matti Lahtinen 4/3/2017 +// Copyright 2017 High Fidelity, Inc. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Creates multiple "magnetic" blocks with random colors that users clones of and snap together. + + +(function() { + const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum. + const MIN_COLOR_VALUE = 127; + const SIZE = 0.3; + const LIFETIME = 600; + + // Random Pastel Generator based on Piper's script + function newColor() { + color = { + red: randomPastelRGBComponent(), + green: randomPastelRGBComponent(), + blue: randomPastelRGBComponent() + }; + return color; + } + // Helper functions. + function randomPastelRGBComponent() { + return Math.floor(Math.random() * MAX_RGB_COMPONENT_VALUE) + MIN_COLOR_VALUE; + } + + var SCRIPT_URL = Script.resolvePath("./entity_scripts/magneticBlock.js"); + + var frontVector = Quat.getFront(MyAvatar.orientation); + frontVector.y -=.25; + for(var x =0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + + var frontOffset = { + x: 0, + y: SIZE * y + SIZE, + z: SIZE * x + SIZE + }; + + Entities.addEntity({ + type: "Box", + name: "MagneticBlock-" + y +'-' + x, + dimensions: { + x: SIZE, + y: SIZE, + z: SIZE + }, + userData: JSON.stringify({grabbableKey: { cloneable: true, grabbable: true, cloneLifetime : LIFETIME, cloneLimit: 9999}}), + position: Vec3.sum(MyAvatar.position, Vec3.sum(frontOffset, frontVector)), + color: newColor(), + script: SCRIPT_URL + }); + } + } + + Script.stop(); +})(); From 0bababf1f5866dff5f73c4650a87648cb427b5a1 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 14:53:07 -0800 Subject: [PATCH 29/40] Safe replacement of glm_mat4_mul() for unaligned arguments instead of __m128 --- libraries/shared/src/GLMHelpers.h | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 4aac913768..609c3ab08b 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -245,4 +245,53 @@ inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value glm::mat4 orthoInverse(const glm::mat4& m); +// +// Safe replacement of glm_mat4_mul() for unaligned arguments instead of __m128 +// +inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r) { + +#if GLM_ARCH & GLM_ARCH_SSE2_BIT + __m128 u0 = _mm_loadu_ps((float*)&m1[0][0]); + __m128 u1 = _mm_loadu_ps((float*)&m1[1][0]); + __m128 u2 = _mm_loadu_ps((float*)&m1[2][0]); + __m128 u3 = _mm_loadu_ps((float*)&m1[3][0]); + + __m128 v0 = _mm_loadu_ps((float*)&m2[0][0]); + __m128 v1 = _mm_loadu_ps((float*)&m2[1][0]); + __m128 v2 = _mm_loadu_ps((float*)&m2[2][0]); + __m128 v3 = _mm_loadu_ps((float*)&m2[3][0]); + + __m128 t0 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(0,0,0,0)), u0); + __m128 t1 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(1,1,1,1)), u1); + __m128 t2 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(2,2,2,2)), u2); + __m128 t3 = _mm_mul_ps(_mm_shuffle_ps(v0, v0, _MM_SHUFFLE(3,3,3,3)), u3); + v0 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3)); + + t0 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(0,0,0,0)), u0); + t1 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(1,1,1,1)), u1); + t2 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2,2,2,2)), u2); + t3 = _mm_mul_ps(_mm_shuffle_ps(v1, v1, _MM_SHUFFLE(3,3,3,3)), u3); + v1 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3)); + + t0 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(0,0,0,0)), u0); + t1 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(1,1,1,1)), u1); + t2 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(2,2,2,2)), u2); + t3 = _mm_mul_ps(_mm_shuffle_ps(v2, v2, _MM_SHUFFLE(3,3,3,3)), u3); + v2 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3)); + + t0 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(0,0,0,0)), u0); + t1 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(1,1,1,1)), u1); + t2 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(2,2,2,2)), u2); + t3 = _mm_mul_ps(_mm_shuffle_ps(v3, v3, _MM_SHUFFLE(3,3,3,3)), u3); + v3 = _mm_add_ps(_mm_add_ps(t0, t1), _mm_add_ps(t2, t3)); + + _mm_storeu_ps((float*)&r[0][0], v0); + _mm_storeu_ps((float*)&r[1][0], v1); + _mm_storeu_ps((float*)&r[2][0], v2); + _mm_storeu_ps((float*)&r[3][0], v3); +#else + r = m1 * m2; +#endif +} + #endif // hifi_GLMHelpers_h From 117bba8b6a79473190220e15b77617f32eb7171b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 15:17:09 -0800 Subject: [PATCH 30/40] redo unsafe optimization --- interface/src/avatar/CauterizedModel.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 0c3d863649..7faf89dec5 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -110,13 +110,7 @@ void CauterizedModel::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) - glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; - glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); - state.clusterMatrices[j] = out; -#else - state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix; -#endif + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } // Once computed the cluster matrices, update the buffer(s) From 46c5f961130a42e0434927f33d75470ce7b8323e Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 15:24:03 -0800 Subject: [PATCH 31/40] redo unsafe optimization --- interface/src/avatar/CauterizedModel.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 7faf89dec5..1ca87a498a 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -143,13 +143,7 @@ void CauterizedModel::updateClusterMatrices() { if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) { jointMatrix = cauterizeMatrix; } -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) - glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; - glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); - state.clusterMatrices[j] = out; -#else - state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix; -#endif + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } if (!_cauterizeBoneSet.empty() && (state.clusterMatrices.size() > 1)) { From 50f92cb934034767416cc5946e886b1cdb4048e8 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 15:29:23 -0800 Subject: [PATCH 32/40] redo unsafe optimization --- interface/src/avatar/SoftAttachmentModel.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp index 6ed54afb27..0521f7a893 100644 --- a/interface/src/avatar/SoftAttachmentModel.cpp +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -60,13 +60,7 @@ void SoftAttachmentModel::updateClusterMatrices() { } else { jointMatrix = _rig->getJointTransform(cluster.jointIndex); } -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) - glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; - glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); - state.clusterMatrices[j] = out; -#else - state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix; -#endif + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } // Once computed the cluster matrices, update the buffer(s) From 44c1f8500dfafc8de6ee0338baaeee40790f1451 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 15:55:53 -0800 Subject: [PATCH 33/40] redo unsafe optimization --- libraries/animation/src/AnimPose.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 5638cacabc..e1c8528e0b 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -50,15 +50,9 @@ glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { } AnimPose AnimPose::operator*(const AnimPose& rhs) const { -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 result; - glm::mat4 lhsMat = *this; - glm::mat4 rhsMat = rhs; - glm_mat4_mul((glm_vec4*)&lhsMat, (glm_vec4*)&rhsMat, (glm_vec4*)&result); + glm_mat4u_mul(*this, rhs, result); return AnimPose(result); -#else - return AnimPose(static_cast(*this) * static_cast(rhs)); -#endif } AnimPose AnimPose::inverse() const { From a5571bd49dbd4e71f236e95d8bf637aa9574f67c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 16:02:39 -0800 Subject: [PATCH 34/40] redo unsafe optimization --- libraries/render-utils/src/Model.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index adfffe2614..d4de05c84d 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1178,13 +1178,7 @@ void Model::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) - glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; - glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); - state.clusterMatrices[j] = out; -#else - state.clusterMatrices[j] = jointMatrix * cluster.inverseBindMatrix; -#endif + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } // Once computed the cluster matrices, update the buffer(s) From 818425707b182343ecbd0739fcc2d9df5b5cd689 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sat, 4 Mar 2017 16:14:31 -0800 Subject: [PATCH 35/40] update unit tests --- tests/shared/src/GLMHelpersTests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index 8d26d35c69..b4af4729a3 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -115,8 +115,8 @@ void GLMHelpersTests::testSimd() { a1 = a * b; b1 = b * a; - glm_mat4_mul((glm_vec4*)&a, (glm_vec4*)&b, (glm_vec4*)&a2); - glm_mat4_mul((glm_vec4*)&b, (glm_vec4*)&a, (glm_vec4*)&b2); + glm_mat4u_mul(a, b, a2); + glm_mat4u_mul(b, a, b2); { @@ -133,8 +133,8 @@ void GLMHelpersTests::testSimd() { QElapsedTimer timer; timer.start(); for (size_t i = 0; i < LOOPS; ++i) { - glm_mat4_mul((glm_vec4*)&a, (glm_vec4*)&b, (glm_vec4*)&a2); - glm_mat4_mul((glm_vec4*)&b, (glm_vec4*)&a, (glm_vec4*)&b2); + glm_mat4u_mul(a, b, a2); + glm_mat4u_mul(b, a, b2); } qDebug() << "SIMD " << timer.elapsed(); } From 04d3bf0c3802e1a77dee971eca94a915ca6c1dc6 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 6 Mar 2017 10:55:47 +0200 Subject: [PATCH 36/40] Cleanup and safeguards - RegistrationPoint can no longer be set for magnetic blocks, will set it to 0.5 on snap. - Simplified axis lock logic --- .../tutorials/entity_scripts/magneticBlock.js | 147 ++++++++++-------- 1 file changed, 80 insertions(+), 67 deletions(-) diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js index a375025671..911e9c0eb5 100644 --- a/scripts/tutorials/entity_scripts/magneticBlock.js +++ b/scripts/tutorials/entity_scripts/magneticBlock.js @@ -12,13 +12,14 @@ (function() { const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs")); - // Preload trick for faster playback const RANGE_MULTIPLER = 1.5; + const MAX_SCALE = 2; + const MIN_SCALE = 0.5; - // Helper for detecting nearby objects - function findEntitiesInRange(releasedProperties) { - var dimensions = releasedProperties.dimensions; - return Entities.findEntities(releasedProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); + // Helper for detecting nearby objects near entityProperties, with the scale calculated by the dimensions of the object. + function findEntitiesInRange(entityProperties) { + var dimensions = entityProperties.dimensions; + return Entities.findEntities(entityProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); } function getNearestValidEntityProperties(releasedProperties) { @@ -32,7 +33,7 @@ var distance = Vec3.distance(releasedProperties.position, entity.position); var scale = releaseSize / Vec3.length(entity.dimensions); - if (distance < nearest && (scale >= 0.5 && scale <= 2)) { + if (distance < nearest && (scale >= MIN_SCALE && scale <= MAX_SCALE)) { nearestEntity = entity; nearest = distance; } @@ -51,82 +52,94 @@ Only retrieving userData */ var val = Entities.getEntityProperties(id, ['userData']) - var userData = {grabbableKey: {}}; - + var userData = { + grabbableKey: {} + }; + // Check if existing userData field exists. if (val.userData && val.userData.length > 0) { try { userData = JSON.parse(val.userData); + if (!userData.grabbableKey) { + userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData. + } } catch (e) { + // if user data is not valid json, we will simply overwrite it. } } - // Object must be triggerable inorder to bind events. + // Object must be triggerable inorder to bind releaseGrabEvent userData.grabbableKey.grabbable = true; // Apply the new properties to entity of id Entities.editEntity(id, { userData: JSON.stringify(userData) }); - this.held = false; - // We will now create a custom binding, to keep the 'current' context as these are callbacks called without context - var t = this; - this.callbacks = {}; - this.releaseGrab = function() { - var released = Entities.getEntityProperties(id, ["position", "rotation", "dimensions"]); - var target = getNearestValidEntityProperties(released); - if (target !== null) { - // We found nearest, now lets do the snap calculations - // Plays the snap sound between the two objects. - Audio.playSound(SNAPSOUND_SOURCE, { - volume: 1, - position: Vec3.mix(target.position, released.position, 0.5) - }); - // Check Nearest Axis - var difference = Vec3.subtract(released.position, target.position); - var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference); - - var abs = { - x: Math.abs(relativeDifference.x), - y: Math.abs(relativeDifference.y), - z: Math.abs(relativeDifference.z) - }; - // Check what value is greater. Simplified. - if (abs.x >= abs.y && abs.x >= abs.z) { - relativeDifference.y = 0; - relativeDifference.z = 0; - if (relativeDifference.x > 0) { - relativeDifference.x = target.dimensions.x / 2 + released.dimensions.x / 2; - } else { - relativeDifference.x = -target.dimensions.x / 2 - released.dimensions.x / 2; - } - } else if (abs.y >= abs.x && abs.y >= abs.z) { - relativeDifference.x = 0; - relativeDifference.z = 0; - if (relativeDifference.y > 0) { - relativeDifference.y = target.dimensions.y / 2 + released.dimensions.y / 2; - } else { - relativeDifference.y = -target.dimensions.y / 2 - released.dimensions.y / 2; - } - } else if (abs.z >= abs.x && abs.z >= abs.y) { - relativeDifference.x = 0; - relativeDifference.y = 0; - if (relativeDifference.z > 0) { - relativeDifference.z = target.dimensions.z / 2 + released.dimensions.z / 2; - } else { - relativeDifference.z = -target.dimensions.z / 2 - released.dimensions.z / 2; - } - } - // Can be expanded upon to work in nearest rotation as well, but was not in spec. - var newPosition = Vec3.multiplyQbyV(target.rotation, relativeDifference); - Entities.editEntity(id, { - rotation: target.rotation, - position: Vec3.sum(target.position, newPosition) - }) - } - } - Script.scriptEnding.connect(function() { Script.removeEventHandler(id, "releaseGrab", this.releaseGrab); }) + }, + releaseGrab: function(entityId) { + // Release grab is called with entityId, + var released = Entities.getEntityProperties(entityId, ["position", "rotation", "dimensions"]); + var target = getNearestValidEntityProperties(released); + if (target !== null) { + // We found nearest, now lets do the snap calculations + // Plays the snap sound between the two objects. + Audio.playSound(SNAPSOUND_SOURCE, { + volume: 1, + position: Vec3.mix(target.position, released.position, 0.5) + }); + // Check Nearest Axis + var difference = Vec3.subtract(released.position, target.position); + var relativeDifference = Vec3.multiplyQbyV(Quat.inverse(target.rotation), difference); + + var abs = { + x: Math.abs(relativeDifference.x), + y: Math.abs(relativeDifference.y), + z: Math.abs(relativeDifference.z) + }; + // Check what value is greater. and lock down to that axis. + var newRelative = { + x: 0, + y: 0, + z: 0 + } + if (abs.x >= abs.y && abs.x >= abs.z) { + newRelative.x = target.dimensions.x / 2 + released.dimensions.x / 2; + if (relativeDifference.x < 0) { + newRelative.x = -newRelative.x; + } + } else if (abs.y >= abs.x && abs.y >= abs.z) { + newRelative.y = target.dimensions.y / 2 + released.dimensions.y / 2; + if (relativeDifference.y < 0) { + newRelative.y = -newRelative.y; + } + } else if (abs.z >= abs.x && abs.z >= abs.y) { + newRelative.z = target.dimensions.z / 2 + released.dimensions.z / 2; + if (relativeDifference.z < 0) { + newRelative.z = -newRelative.z; + } + } + // Can be expanded upon to work in nearest 90 degree rotation as well, but was not in spec. + var newPosition = Vec3.multiplyQbyV(target.rotation, newRelative); + Entities.editEntity(entityId, { + // Script relies on the registrationPoint being at the very center of the object. Thus override. + registrationPoint: { + x: 0.5, + y: 0.5, + z: 0.5 + }, + rotation: target.rotation, + position: Vec3.sum(target.position, newPosition) + }); + // Script relies on the registrationPoint being at the very center of the object. Thus override. + Entities.editEntity(target.id, { + registrationPoint: { + x: 0.5, + y: 0.5, + z: 0.5 + } + }) + } } } return new MagneticBlock(); From 19a31d76304393f5488196a3c70bf6475eaf410b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 6 Mar 2017 10:15:53 -0800 Subject: [PATCH 37/40] don't automatically unhook overlays from hands unless they were grabbable overlays --- scripts/system/controllers/handControllerGrab.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index aa0a3d9abd..febeea0c8e 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3570,13 +3570,21 @@ function MyController(hand) { } _this.previouslyUnhooked[childID] = now; - // we don't know if it's an entity or an overlay + if (Overlays.getProperty(childID, "grabbable")) { + // only auto-unhook overlays that were flagged as grabbable. this avoids unhooking overlays + // used in tutorial. + Overlays.editOverlay(childID, { + parentID: previousParentID, + parentJointIndex: previousParentJointIndex + }); + } Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); - Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); } else { Entities.editEntity(childID, { parentID: NULL_UUID }); - Overlays.editOverlay(childID, { parentID: NULL_UUID }); + if (Overlays.getProperty(childID, "grabbable")) { + Overlays.editOverlay(childID, { parentID: NULL_UUID }); + } } } }); From d50a0e33a99b6cb0181186f8fae1dd496dd1f3f2 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 6 Mar 2017 21:07:16 +0200 Subject: [PATCH 38/40] CR. Run through Lint ECMAScript 5 standard Meaning all const are out, non-variable definitions are out, etc --- .../tutorials/entity_scripts/magneticBlock.js | 37 +++++++++++-------- scripts/tutorials/makeBlocks.js | 32 ++++++++++------ 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js index 911e9c0eb5..7771a3668d 100644 --- a/scripts/tutorials/entity_scripts/magneticBlock.js +++ b/scripts/tutorials/entity_scripts/magneticBlock.js @@ -11,21 +11,23 @@ // Makes the entity the script is bound to connect to nearby, similarly sized entities, like a magnet. (function() { - const SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs")); - const RANGE_MULTIPLER = 1.5; - const MAX_SCALE = 2; - const MIN_SCALE = 0.5; + var SNAPSOUND_SOURCE = SoundCache.getSound(Script.resolvePath("../../system/assets/sounds/entitySnap.wav?xrs")); + var RANGE_MULTIPLER = 1.5; + var MAX_SCALE = 2; + var MIN_SCALE = 0.5; // Helper for detecting nearby objects near entityProperties, with the scale calculated by the dimensions of the object. function findEntitiesInRange(entityProperties) { var dimensions = entityProperties.dimensions; - return Entities.findEntities(entityProperties.position, ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); + // Average of the dimensions instead of full value. + return Entities.findEntities(entityProperties.position, + ((dimensions.x + dimensions.y + dimensions.z) / 3) * RANGE_MULTIPLER); } function getNearestValidEntityProperties(releasedProperties) { var entities = findEntitiesInRange(releasedProperties); var nearestEntity = null; - var nearest = 9999999999999; + var nearest = Number.MAX_SAFE_INTEGER; var releaseSize = Vec3.length(releasedProperties.dimensions); entities.forEach(function(entityId) { if (entityId !== releasedProperties.id) { @@ -38,27 +40,30 @@ nearest = distance; } } - }) + }); return nearestEntity; } // Create the 'class' function MagneticBlock() {} // Bind pre-emptive events MagneticBlock.prototype = { - // When script is bound to an entity, preload is the first callback called with the entityID. It will behave as the constructor + /* + When script is bound to an entity, preload is the first callback called with the entityID. + It will behave as the constructor + */ preload: function(id) { /* We will now override any existing userdata with the grabbable property. Only retrieving userData */ - var val = Entities.getEntityProperties(id, ['userData']) + var entity = Entities.getEntityProperties(id, ['userData']); var userData = { grabbableKey: {} }; // Check if existing userData field exists. - if (val.userData && val.userData.length > 0) { + if (entity.userData && entity.userData.length > 0) { try { - userData = JSON.parse(val.userData); + userData = JSON.parse(entity.userData); if (!userData.grabbableKey) { userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData. } @@ -75,7 +80,7 @@ }); Script.scriptEnding.connect(function() { Script.removeEventHandler(id, "releaseGrab", this.releaseGrab); - }) + }); }, releaseGrab: function(entityId) { // Release grab is called with entityId, @@ -102,7 +107,7 @@ x: 0, y: 0, z: 0 - } + }; if (abs.x >= abs.y && abs.x >= abs.z) { newRelative.x = target.dimensions.x / 2 + released.dimensions.x / 2; if (relativeDifference.x < 0) { @@ -138,9 +143,9 @@ y: 0.5, z: 0.5 } - }) + }); } } - } + }; return new MagneticBlock(); -}) +}); diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js index bb4974498c..54bdead792 100644 --- a/scripts/tutorials/makeBlocks.js +++ b/scripts/tutorials/makeBlocks.js @@ -12,19 +12,20 @@ (function() { - const MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum. - const MIN_COLOR_VALUE = 127; - const SIZE = 0.3; - const LIFETIME = 600; - + var MAX_RGB_COMPONENT_VALUE = 256 / 2; // Limit the values to half the maximum. + var MIN_COLOR_VALUE = 127; + var SIZE = 0.3; + var LIFETIME = 600; + var VERTICAL_OFFSET = -0.25; + var ROWS = 3; + var COLUMNS = 3; // Random Pastel Generator based on Piper's script function newColor() { - color = { + return { red: randomPastelRGBComponent(), green: randomPastelRGBComponent(), blue: randomPastelRGBComponent() }; - return color; } // Helper functions. function randomPastelRGBComponent() { @@ -34,9 +35,9 @@ var SCRIPT_URL = Script.resolvePath("./entity_scripts/magneticBlock.js"); var frontVector = Quat.getFront(MyAvatar.orientation); - frontVector.y -=.25; - for(var x =0; x < 3; x++) { - for (var y = 0; y < 3; y++) { + frontVector.y += VERTICAL_OFFSET; + for (var x = 0; x < COLUMNS; x++) { + for (var y = 0; y < ROWS; y++) { var frontOffset = { x: 0, @@ -46,13 +47,20 @@ Entities.addEntity({ type: "Box", - name: "MagneticBlock-" + y +'-' + x, + name: "MagneticBlock-" + y + '-' + x, dimensions: { x: SIZE, y: SIZE, z: SIZE }, - userData: JSON.stringify({grabbableKey: { cloneable: true, grabbable: true, cloneLifetime : LIFETIME, cloneLimit: 9999}}), + userData: JSON.stringify({ + grabbableKey: { + cloneable: true, + grabbable: true, + cloneLifetime: LIFETIME, + cloneLimit: 9999 + } + }), position: Vec3.sum(MyAvatar.position, Vec3.sum(frontOffset, frontVector)), color: newColor(), script: SCRIPT_URL From 01ba44c572d66be6c86d79390918bda798576ba6 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 6 Mar 2017 21:32:27 +0200 Subject: [PATCH 39/40] Fixed print --- scripts/tutorials/entity_scripts/magneticBlock.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/tutorials/entity_scripts/magneticBlock.js b/scripts/tutorials/entity_scripts/magneticBlock.js index 7771a3668d..1ec5f2a6c6 100644 --- a/scripts/tutorials/entity_scripts/magneticBlock.js +++ b/scripts/tutorials/entity_scripts/magneticBlock.js @@ -27,7 +27,7 @@ function getNearestValidEntityProperties(releasedProperties) { var entities = findEntitiesInRange(releasedProperties); var nearestEntity = null; - var nearest = Number.MAX_SAFE_INTEGER; + var nearest = Number.MAX_VALUE - 1; var releaseSize = Vec3.length(releasedProperties.dimensions); entities.forEach(function(entityId) { if (entityId !== releasedProperties.id) { @@ -56,14 +56,14 @@ We will now override any existing userdata with the grabbable property. Only retrieving userData */ - var entity = Entities.getEntityProperties(id, ['userData']); + var entityProperties = Entities.getEntityProperties(id, ['userData']); var userData = { grabbableKey: {} }; // Check if existing userData field exists. - if (entity.userData && entity.userData.length > 0) { + if (entityProperties.userData && entityProperties.userData.length > 0) { try { - userData = JSON.parse(entity.userData); + userData = JSON.parse(entityProperties.userData); if (!userData.grabbableKey) { userData.grabbableKey = {}; // If by random change there is no grabbableKey in the userData. } From a2d2c41f02876e90711e1c222044af048a5d4c84 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 6 Mar 2017 11:45:49 -0800 Subject: [PATCH 40/40] remove debug print --- scripts/system/controllers/handControllerGrab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index febeea0c8e..7e9aae17af 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3552,7 +3552,6 @@ function MyController(hand) { // we appear to be holding something and this script isn't in a state that would be holding something. // unhook it. if we previously took note of this entity's parent, put it back where it was. This // works around some problems that happen when more than one hand or avatar is passing something around. - print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); if (_this.previousParentID[childID]) { var previousParentID = _this.previousParentID[childID]; var previousParentJointIndex = _this.previousParentJointIndex[childID];