From eb48342d07da948743c91626a9d0e98ba640dcd5 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 Nov 2015 09:10:04 -0800 Subject: [PATCH 01/21] add inversiona nd spatialkey --- examples/toybox/bubblewand/createWand.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/toybox/bubblewand/createWand.js b/examples/toybox/bubblewand/createWand.js index 4f4bc39e2c..a0e6ce5918 100644 --- a/examples/toybox/bubblewand/createWand.js +++ b/examples/toybox/bubblewand/createWand.js @@ -43,5 +43,14 @@ var wand = Entities.addEntity({ //must be enabled to be grabbable in the physics engine collisionsWillMove: true, compoundShapeURL: WAND_COLLISION_SHAPE, - script: WAND_SCRIPT_URL + script: WAND_SCRIPT_URL, + userData:JSON.stringify({ + grabbableKey:{ + invertSolidWhileHeld:true, + spatialKey:{ + relativePosition:{x:0,y:1,z:0}, + relativeRotation:Quat.fromPitchYawRollDegrees(0,90,0) + } + } + }) }); \ No newline at end of file From de953ce14d4da3bbad14e151b1e5317456c62edb Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 Nov 2015 10:41:53 -0800 Subject: [PATCH 02/21] better spatialkey --- examples/toybox/bubblewand/createWand.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/toybox/bubblewand/createWand.js b/examples/toybox/bubblewand/createWand.js index a0e6ce5918..faea908c81 100644 --- a/examples/toybox/bubblewand/createWand.js +++ b/examples/toybox/bubblewand/createWand.js @@ -12,8 +12,8 @@ Script.include("../../libraries/utils.js"); -var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/wand.fbx'; -var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/actual_no_top_collision_hull.obj'; +var WAND_MODEL = 'http://hifi-content.s3.amazonaws.com/james/bubblewand/wand.fbx'; +var WAND_COLLISION_SHAPE = 'http://hifi-content.s3.amazonaws.com/james/bubblewand/wand_collision_hull.obj'; var WAND_SCRIPT_URL = Script.resolvePath("wand.js"); @@ -48,8 +48,8 @@ var wand = Entities.addEntity({ grabbableKey:{ invertSolidWhileHeld:true, spatialKey:{ - relativePosition:{x:0,y:1,z:0}, - relativeRotation:Quat.fromPitchYawRollDegrees(0,90,0) + relativePosition:{x:0,y:0.1,z:0}, + relativeRotation:Quat.fromPitchYawRollDegrees(0,0,90) } } }) From d3ee0d4998e19a2368177b363cee6e52641b43f1 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 Nov 2015 10:44:24 -0800 Subject: [PATCH 03/21] handle bubble solidity --- examples/toybox/bubblewand/wand.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/toybox/bubblewand/wand.js b/examples/toybox/bubblewand/wand.js index 8036d9ead6..354a88c43f 100644 --- a/examples/toybox/bubblewand/wand.js +++ b/examples/toybox/bubblewand/wand.js @@ -113,6 +113,7 @@ Entities.editEntity(this.currentBubble, { velocity: velocity, lifetime: lifetime, + ignoreForCollisions:false, gravity: this.randomizeBubbleGravity() }); @@ -162,7 +163,7 @@ position: this.getWandTipPosition(properties), dimensions: BUBBLE_INITIAL_DIMENSIONS, collisionsWillMove: false, - ignoreForCollisions: false, + ignoreForCollisions: true, linearDamping: BUBBLE_LINEAR_DAMPING, shapeType: "sphere" }); From 5f3e3ed9d1c760c9489fa0befe1a730151ced109 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 Nov 2015 11:06:48 -0800 Subject: [PATCH 04/21] cleanup --- examples/toybox/bubblewand/createWand.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/toybox/bubblewand/createWand.js b/examples/toybox/bubblewand/createWand.js index faea908c81..6b4f9717ec 100644 --- a/examples/toybox/bubblewand/createWand.js +++ b/examples/toybox/bubblewand/createWand.js @@ -44,12 +44,16 @@ var wand = Entities.addEntity({ collisionsWillMove: true, compoundShapeURL: WAND_COLLISION_SHAPE, script: WAND_SCRIPT_URL, - userData:JSON.stringify({ - grabbableKey:{ - invertSolidWhileHeld:true, - spatialKey:{ - relativePosition:{x:0,y:0.1,z:0}, - relativeRotation:Quat.fromPitchYawRollDegrees(0,0,90) + userData: JSON.stringify({ + grabbableKey: { + invertSolidWhileHeld: true, + spatialKey: { + relativePosition: { + x: 0, + y: 0.1, + z: 0 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 90) } } }) From 12e2178dd3f2deabba02dc5ce57d70184cc9726b Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 Nov 2015 11:28:07 -0800 Subject: [PATCH 05/21] cleanup --- examples/toybox/bubblewand/wand.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/toybox/bubblewand/wand.js b/examples/toybox/bubblewand/wand.js index 354a88c43f..f429ef6d55 100644 --- a/examples/toybox/bubblewand/wand.js +++ b/examples/toybox/bubblewand/wand.js @@ -12,7 +12,7 @@ /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -(function () { +(function() { Script.include("../../libraries/utils.js"); @@ -58,23 +58,23 @@ BubbleWand.prototype = { timePassed: null, currentBubble: null, - preload: function (entityID) { + preload: function(entityID) { this.entityID = entityID; }, - getWandTipPosition: function (properties) { + getWandTipPosition: function(properties) { //the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position var upVector = Quat.getUp(properties.rotation); var upOffset = Vec3.multiply(upVector, WAND_TIP_OFFSET); var wandTipPosition = Vec3.sum(properties.position, upOffset); return wandTipPosition; }, - addCollisionsToBubbleAfterCreation: function (bubble) { + addCollisionsToBubbleAfterCreation: function(bubble) { //if the bubble collide immediately, we get weird effects. so we add collisions after release Entities.editEntity(bubble, { collisionsWillMove: true }); }, - randomizeBubbleGravity: function () { + randomizeBubbleGravity: function() { //change up the gravity a little bit for variation in floating effects var randomNumber = randFloat(BUBBLE_GRAVITY_MIN, BUBBLE_GRAVITY_MAX); var gravity = { @@ -84,7 +84,7 @@ }; return gravity; }, - growBubbleWithWandVelocity: function (properties, deltaTime) { + growBubbleWithWandVelocity: function(properties, deltaTime) { //get the wand and tip position for calculations var wandPosition = properties.position; this.getWandTipPosition(properties); @@ -113,7 +113,6 @@ Entities.editEntity(this.currentBubble, { velocity: velocity, lifetime: lifetime, - ignoreForCollisions:false, gravity: this.randomizeBubbleGravity() }); @@ -146,7 +145,7 @@ dimensions: dimensions }); }, - createBubbleAtTipOfWand: function () { + createBubbleAtTipOfWand: function() { //create a new bubble at the tip of the wand var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); @@ -169,18 +168,17 @@ }); }, - startNearGrab: function () { + startNearGrab: function() { //create a bubble to grow at the start of the grab if (this.currentBubble === null) { this.createBubbleAtTipOfWand(); } }, - continueNearGrab: function () { + continueNearGrab: function() { var deltaTime = checkInterval(); //only get the properties that we need var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); - var wandTipPosition = this.getWandTipPosition(properties); //update the bubble to stay with the wand tip @@ -190,7 +188,7 @@ this.growBubbleWithWandVelocity(properties, deltaTime); }, - releaseGrab: function () { + releaseGrab: function() { //delete the current buble and reset state when the wand is released Entities.deleteEntity(this.currentBubble); this.currentBubble = null; From b4f9c4158fe423e94fa73a9dd776ad61c38da9dc Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Thu, 19 Nov 2015 09:44:25 -0800 Subject: [PATCH 06/21] cleanup directory + bug fix --- examples/entityScripts/recordingMaster.js | 1 - .../entityScripts/synchronizerEntityScript.js | 90 -------------- examples/entityScripts/synchronizerMaster.js | 117 ------------------ 3 files changed, 208 deletions(-) delete mode 100644 examples/entityScripts/synchronizerEntityScript.js delete mode 100644 examples/entityScripts/synchronizerMaster.js diff --git a/examples/entityScripts/recordingMaster.js b/examples/entityScripts/recordingMaster.js index 680d364eb1..27f84f44c2 100644 --- a/examples/entityScripts/recordingMaster.js +++ b/examples/entityScripts/recordingMaster.js @@ -27,7 +27,6 @@ var toolBar = null; var recordIcon; var isRecording = false; var channel = "groupRecordingChannel"; -Messages.subscribe(channel); setupToolBar(); function setupToolBar() { diff --git a/examples/entityScripts/synchronizerEntityScript.js b/examples/entityScripts/synchronizerEntityScript.js deleted file mode 100644 index 82dd954381..0000000000 --- a/examples/entityScripts/synchronizerEntityScript.js +++ /dev/null @@ -1,90 +0,0 @@ -// -// synchronizerEntityScript.js -// examples/entityScripts -// -// Created by Alessandro Signa on 11/12/15. -// Copyright 2015 High Fidelity, Inc. -// - -// This script shows how to create a synchronized event between avatars trhough an entity. -// It works using the entity's userData: the master change its value and every client checks it every frame -// This entity prints a message when the event starts and when it ends. -// The client running synchronizerMaster.js is the event master and it decides when the event starts/ends by pressing a button. -// All the avatars in the area when the master presses the button will receive a message. -// - -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - - - - -(function() { - var insideArea = false; - var isJoiningTheEvent = false; - var _this; - - - - function ParamsEntity() { - _this = this; - return; - } - - - ParamsEntity.prototype = { - update: function(){ - var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData); - var valueToCheck = userData.myKey.valueToCheck; - if(valueToCheck && !isJoiningTheEvent){ - _this.sendMessage(); - }else if((!valueToCheck && isJoiningTheEvent) || (isJoiningTheEvent && !insideArea)){ - _this.stopMessage(); - } - }, - preload: function(entityID) { - print('entity loaded') - this.entityID = entityID; - Script.update.connect(_this.update); - }, - enterEntity: function(entityID) { - print("enterEntity("+entityID+")"); - var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData); - var valueToCheck = userData.myKey.valueToCheck; - if(!valueToCheck){ - //i'm in the area in time (before the event starts) - insideArea = true; - } - change(entityID); - }, - leaveEntity: function(entityID) { - print("leaveEntity("+entityID+")"); - Entities.editEntity(entityID, { color: { red: 255, green: 190, blue: 20} }); - insideArea = false; - }, - - sendMessage: function(myID){ - if(insideArea && !isJoiningTheEvent){ - print("The event started"); - isJoiningTheEvent = true; - } - }, - - stopMessage: function(myID){ - if(isJoiningTheEvent){ - print("The event ended"); - isJoiningTheEvent = false; - } - }, - clean: function(entityID) { - Script.update.disconnect(_this.update); - } - } - - function change(entityID) { - Entities.editEntity(entityID, { color: { red: 255, green: 100, blue: 220} }); - } - - - return new ParamsEntity(); -}); diff --git a/examples/entityScripts/synchronizerMaster.js b/examples/entityScripts/synchronizerMaster.js deleted file mode 100644 index 8b6c8c2b8b..0000000000 --- a/examples/entityScripts/synchronizerMaster.js +++ /dev/null @@ -1,117 +0,0 @@ -// -// synchronizerMaster.js -// examples/entityScripts -// -// Created by Alessandro Signa on 11/12/15. -// Copyright 2015 High Fidelity, Inc. -// -// Run this script to spawn a box (synchronizer) and drive the start/end of the event for anyone who is inside the box -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -var PARAMS_SCRIPT_URL = Script.resolvePath('synchronizerEntityScript.js'); - - -HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; -Script.include("../libraries/toolBars.js"); -Script.include("../libraries/utils.js"); - - - -var rotation = Quat.safeEulerAngles(Camera.getOrientation()); -rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0); -var center = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(rotation))); - -var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/"; -var ALPHA_ON = 1.0; -var ALPHA_OFF = 0.7; -var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 }; - -var toolBar = null; -var recordIcon; - - - -var isHappening = false; - -var testEntity = Entities.addEntity({ - name: 'paramsTestEntity', - dimensions: { - x: 2, - y: 1, - z: 2 - }, - type: 'Box', - position: center, - color: { - red: 255, - green: 255, - blue: 255 - }, - visible: true, - ignoreForCollisions: true, - script: PARAMS_SCRIPT_URL, - - userData: JSON.stringify({ - myKey: { - valueToCheck: false - } - }) -}); - - -setupToolBar(); - -function setupToolBar() { - if (toolBar != null) { - print("Multiple calls to setupToolBar()"); - return; - } - Tool.IMAGE_HEIGHT /= 2; - Tool.IMAGE_WIDTH /= 2; - - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); //put the button in the up-left corner - - toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); - - recordIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "recording-record.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - x: 0, y: 0, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: MyAvatar.isPlaying() ? ALPHA_OFF : ALPHA_ON, - visible: true - }, true, isHappening); - -} - -function mousePressEvent(event) { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (recordIcon === toolBar.clicked(clickedOverlay, false)) { - if (!isHappening) { - print("I'm the event master. I want the event starts"); - isHappening = true; - setEntityCustomData("myKey", testEntity, {valueToCheck: true}); - - } else { - print("I want the event stops"); - isHappening = false; - setEntityCustomData("myKey", testEntity, {valueToCheck: false}); - - } - } -} - - -function cleanup() { - toolBar.cleanup(); - Entities.callEntityMethod(testEntity, 'clean'); //have to call this before deleting to avoid the JSON warnings - Entities.deleteEntity(testEntity); -} - - - - Script.scriptEnding.connect(cleanup); - Controller.mousePressEvent.connect(mousePressEvent); \ No newline at end of file From 2a05ec650b0bd1a878a620115b4c7876552fa7f0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 14:17:48 -0800 Subject: [PATCH 07/21] fix locking in AvatarHashMap/AvatarManager --- interface/src/avatar/AvatarManager.cpp | 62 ++++++++++++++----------- interface/src/avatar/AvatarManager.h | 4 +- libraries/avatars/src/AvatarHashMap.cpp | 59 ++++++++++++++--------- libraries/avatars/src/AvatarHashMap.h | 5 +- 4 files changed, 78 insertions(+), 52 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4b9bfd21a3..ae4c978877 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -119,6 +119,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); // simulate avatars + QWriteLocker writeLock(&_hashLock); + AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::dynamic_pointer_cast(avatarIterator.value()); @@ -128,10 +130,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // DO NOT update or fade out uninitialized Avatars ++avatarIterator; } else if (avatar->shouldDie()) { - removeAvatarMotionState(avatar); - _avatarFades.push_back(avatarIterator.value()); - QWriteLocker locker(&_hashLock); + auto removedAvatar = avatarIterator.value(); avatarIterator = _avatarHash.erase(avatarIterator); + handleRemovedAvatar(removedAvatar); } else { avatar->startUpdate(); avatar->simulate(deltaTime); @@ -139,6 +140,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { ++avatarIterator; } } + + writeLock.unlock(); // simulate avatar fades simulateAvatarFades(deltaTime); @@ -173,8 +176,8 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() { } // virtual -AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { - auto avatar = std::dynamic_pointer_cast(AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer)); +AvatarSharedPointer AvatarManager::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + auto avatar = std::dynamic_pointer_cast(AvatarHashMap::newOrExistingAvatar(sessionUUID, mixerWeakPointer)); render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; if (DependencyManager::get()->shouldRenderAvatars()) { @@ -200,20 +203,28 @@ void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) { // virtual void AvatarManager::removeAvatar(const QUuid& sessionUUID) { - AvatarHash::iterator avatarIterator = _avatarHash.find(sessionUUID); - if (avatarIterator != _avatarHash.end()) { - std::shared_ptr avatar = std::dynamic_pointer_cast(avatarIterator.value()); - if (avatar != _myAvatar && avatar->isInitialized()) { - removeAvatarMotionState(avatar); - _avatarFades.push_back(avatarIterator.value()); - QWriteLocker locker(&_hashLock); - _avatarHash.erase(avatarIterator); - } + QWriteLocker locker(&_hashLock); + + auto removedAvatar = _avatarHash.take(sessionUUID); + if (removedAvatar) { + handleRemovedAvatar(removedAvatar); } } +void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { + qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) + << "from AvatarManager"; + + AvatarHashMap::handleRemovedAvatar(removedAvatar); + + removeAvatarMotionState(removedAvatar); + _avatarFades.push_back(removedAvatar); +} + void AvatarManager::clearOtherAvatars() { // clear any avatars that came from an avatar-mixer + QWriteLocker locker(&_hashLock); + AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); @@ -221,10 +232,10 @@ void AvatarManager::clearOtherAvatars() { // don't remove myAvatar or uninitialized avatars from the list ++avatarIterator; } else { - removeAvatarMotionState(avatar); - _avatarFades.push_back(avatarIterator.value()); - QWriteLocker locker(&_hashLock); + auto removedAvatar = avatarIterator.value(); avatarIterator = _avatarHash.erase(avatarIterator); + + handleRemovedAvatar(removedAvatar); } } _myAvatar->clearLookAtTargetAvatar(); @@ -318,9 +329,11 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) { - AvatarHash::iterator avatarItr = _avatarHash.find(id); - if (avatarItr != _avatarHash.end()) { - auto avatar = std::static_pointer_cast(avatarItr.value()); + auto avatarData = findAvatar(id); + + if (avatarData) { + auto avatar = std::dynamic_pointer_cast(avatarData); + AvatarMotionState* motionState = avatar->getMotionState(); if (motionState) { motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); @@ -363,11 +376,6 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) if (sessionID == _myAvatar->getSessionUUID()) { return std::static_pointer_cast(_myAvatar); } - QReadLocker locker(&_hashLock); - auto iter = _avatarHash.find(sessionID); - if (iter != _avatarHash.end()) { - return iter.value(); - } else { - return AvatarSharedPointer(); - } + + return findAvatar(sessionID); } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index fa0593368b..71f579abc2 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -77,9 +77,11 @@ private: // virtual overrides virtual AvatarSharedPointer newSharedAvatar(); - virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + virtual AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); void removeAvatarMotionState(AvatarSharedPointer avatar); + virtual void removeAvatar(const QUuid& sessionUUID); + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar); QVector _avatarFades; std::shared_ptr _myAvatar; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 342f995de7..cc83ddf678 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -42,18 +42,30 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } -AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { - qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - - AvatarSharedPointer avatar = newSharedAvatar(); - avatar->setSessionUUID(sessionUUID); - avatar->setOwningAvatarMixer(mixerWeakPointer); +AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); - _avatarHash.insert(sessionUUID, avatar); - emit avatarAddedEvent(sessionUUID); + + auto avatar = _avatarHash.value(sessionUUID); + + if (!avatar) { + qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; + + avatar = newSharedAvatar(); + avatar->setSessionUUID(sessionUUID); + avatar->setOwningAvatarMixer(mixerWeakPointer); + + _avatarHash.insert(sessionUUID, avatar); + emit avatarAddedEvent(sessionUUID); + } + return avatar; } +AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) { + QReadLocker locker(&_hashLock); + return _avatarHash.value(sessionUUID); +} + void AvatarHashMap::processAvatarDataPacket(QSharedPointer packet, SharedNodePointer sendingNode) { // enumerate over all of the avatars in this packet @@ -66,10 +78,7 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer packet, Sha QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead()); if (sessionUUID != _lastOwnerSessionUUID) { - AvatarSharedPointer avatar = _avatarHash.value(sessionUUID); - if (!avatar) { - avatar = addAvatar(sessionUUID, sendingNode); - } + auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); @@ -97,10 +106,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer packet, identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer avatar = _avatarHash.value(sessionUUID); - if (!avatar) { - avatar = addAvatar(sessionUUID, sendingNode); - } + auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); + if (avatar->getFaceModelURL() != faceMeshURL) { avatar->setFaceModelURL(faceMeshURL); } @@ -122,10 +129,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer packet, void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer packet, SharedNodePointer sendingNode) { QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - AvatarSharedPointer avatar = _avatarHash.value(sessionUUID); - if (!avatar) { - avatar = addAvatar(sessionUUID, sendingNode); - } + auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); QByteArray billboard = packet->read(packet->bytesLeftToRead()); if (avatar->getBillboard() != billboard) { @@ -137,13 +141,22 @@ void AvatarHashMap::processKillAvatar(QSharedPointer packet, SharedNod // read the node id QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); removeAvatar(sessionUUID); - } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); - _avatarHash.remove(sessionUUID); - emit avatarRemovedEvent(sessionUUID); + + auto removedAvatar = _avatarHash.take(sessionUUID); + + if (removedAvatar) { + handleRemovedAvatar(removedAvatar); + } +} + +void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { + qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) + << "from AvatarHashMap"; + emit avatarRemovedEvent(removedAvatar->getSessionUUID()); } void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index a125784cd8..0f230a7022 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -54,8 +54,11 @@ protected: AvatarHashMap(); virtual AvatarSharedPointer newSharedAvatar(); - virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + virtual AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID); + + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar); AvatarHash _avatarHash; // "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock. From 5140a480a15556bbd7b52ea77810fef74dd6d396 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 15:35:41 -0800 Subject: [PATCH 08/21] fix for recursive write then read lock --- interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 32 +++++++++++--------------- interface/src/avatar/AvatarManager.h | 2 +- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b979334383..bfa32b8223 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1187,7 +1187,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { // virtual void Avatar::rebuildSkeletonBody() { - DependencyManager::get()->updateAvatarPhysicsShape(getSessionUUID()); + DependencyManager::get()->updateAvatarPhysicsShape(this); } glm::vec3 Avatar::getLeftPalmPosition() { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index ae4c978877..af5937d3eb 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -328,25 +328,19 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } } -void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) { - auto avatarData = findAvatar(id); - - if (avatarData) { - auto avatar = std::dynamic_pointer_cast(avatarData); - - AvatarMotionState* motionState = avatar->getMotionState(); - if (motionState) { - motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); - } else { - ShapeInfo shapeInfo; - avatar->computeShapeInfo(shapeInfo); - btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); - if (shape) { - AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape); - avatar->setMotionState(motionState); - _motionStatesToAdd.insert(motionState); - _avatarMotionStates.insert(motionState); - } +void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) { + AvatarMotionState* motionState = avatar->getMotionState(); + if (motionState) { + motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); + } else { + ShapeInfo shapeInfo; + avatar->computeShapeInfo(shapeInfo); + btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + if (shape) { + AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + avatar->setMotionState(motionState); + _motionStatesToAdd.insert(motionState); + _avatarMotionStates.insert(motionState); } } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 71f579abc2..35e37656d8 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -63,7 +63,7 @@ public: void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); - void updateAvatarPhysicsShape(const QUuid& id); + void updateAvatarPhysicsShape(Avatar* avatar); public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } From 6398a922c656316c001c3990cb71f1f450cb9776 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 15:43:29 -0800 Subject: [PATCH 09/21] leverage COW for AvatarHash --- interface/src/avatar/AvatarManager.cpp | 14 ++++++-------- interface/src/avatar/MyAvatar.cpp | 6 ++---- libraries/avatars/src/AvatarHashMap.cpp | 4 ---- libraries/avatars/src/AvatarHashMap.h | 2 +- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index af5937d3eb..4a69e9010a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -119,10 +119,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); // simulate avatars - QWriteLocker writeLock(&_hashLock); + auto hashCopy = _avatarHash; - AvatarHash::iterator avatarIterator = _avatarHash.begin(); - while (avatarIterator != _avatarHash.end()) { + AvatarHash::iterator avatarIterator = hashCopy.begin(); + while (avatarIterator != hashCopy.end()) { auto avatar = std::dynamic_pointer_cast(avatarIterator.value()); if (avatar == _myAvatar || !avatar->isInitialized()) { @@ -130,9 +130,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // DO NOT update or fade out uninitialized Avatars ++avatarIterator; } else if (avatar->shouldDie()) { - auto removedAvatar = avatarIterator.value(); - avatarIterator = _avatarHash.erase(avatarIterator); - handleRemovedAvatar(removedAvatar); + removeAvatar(avatarIterator.key()); + ++avatarIterator; } else { avatar->startUpdate(); avatar->simulate(deltaTime); @@ -140,8 +139,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { ++avatarIterator; } } - - writeLock.unlock(); // simulate avatar fades simulateAvatarFades(deltaTime); @@ -329,6 +326,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) { + qDebug() << "Update physics state called for" << avatar; AvatarMotionState* motionState = avatar->getMotionState(); if (motionState) { motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1c151bcd3f..6521a0c891 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -982,10 +982,8 @@ void MyAvatar::updateLookAtTargetAvatar() { const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f; const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; - AvatarHash hash; - DependencyManager::get()->withAvatarHash([&] (const AvatarHash& locked) { - hash = locked; // make a shallow copy and operate on that, to minimize lock time - }); + AvatarHash hash = DependencyManager::get()->getCopy(); + foreach (const AvatarSharedPointer& avatarPointer, hash) { auto avatar = static_pointer_cast(avatarPointer); bool isCurrentTarget = avatar->getIsLookAtTarget(); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index cc83ddf678..1da9abacdd 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -22,10 +22,6 @@ AvatarHashMap::AvatarHashMap() { connect(DependencyManager::get().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } -void AvatarHashMap::withAvatarHash(std::function callback) { - QReadLocker locker(&_hashLock); - callback(_avatarHash); -} bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { QReadLocker locker(&_hashLock); foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 0f230a7022..c3c9160f12 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -31,7 +31,7 @@ class AvatarHashMap : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - void withAvatarHash(std::function); + AvatarHash getCopy() { return _avatarHash; } int size() { return _avatarHash.size(); } signals: From 40397add433f25c0dfa4baf39b711adf2dd673bc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 16:15:21 -0800 Subject: [PATCH 10/21] fix for extra adds in AvatarManager --- interface/src/avatar/AvatarManager.cpp | 24 +++++++++++------------- interface/src/avatar/AvatarManager.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 22 ++++++++++++++-------- libraries/avatars/src/AvatarHashMap.h | 3 ++- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4a69e9010a..a64263aec2 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -123,7 +123,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarHash::iterator avatarIterator = hashCopy.begin(); while (avatarIterator != hashCopy.end()) { - auto avatar = std::dynamic_pointer_cast(avatarIterator.value()); + auto avatar = std::static_pointer_cast(avatarIterator.value()); if (avatar == _myAvatar || !avatar->isInitialized()) { // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. @@ -169,19 +169,21 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { } AvatarSharedPointer AvatarManager::newSharedAvatar() { - return AvatarSharedPointer(std::make_shared(std::make_shared())); + return std::make_shared(std::make_shared()); } -// virtual -AvatarSharedPointer AvatarManager::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { - auto avatar = std::dynamic_pointer_cast(AvatarHashMap::newOrExistingAvatar(sessionUUID, mixerWeakPointer)); +AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); + auto rawRenderableAvatar = std::static_pointer_cast(newAvatar); + render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; if (DependencyManager::get()->shouldRenderAvatars()) { - avatar->addToScene(avatar, scene, pendingChanges); + rawRenderableAvatar->addToScene(rawRenderableAvatar, scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); - return avatar; + + return newAvatar; } // protected @@ -209,9 +211,6 @@ void AvatarManager::removeAvatar(const QUuid& sessionUUID) { } void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { - qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) - << "from AvatarManager"; - AvatarHashMap::handleRemovedAvatar(removedAvatar); removeAvatarMotionState(removedAvatar); @@ -326,7 +325,6 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents } void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) { - qDebug() << "Update physics state called for" << avatar; AvatarMotionState* motionState = avatar->getMotionState(); if (motionState) { motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); @@ -346,7 +344,7 @@ void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) { void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { if (DependencyManager::get()->shouldRenderAvatars()) { for (auto avatarData : _avatarHash) { - auto avatar = std::dynamic_pointer_cast(avatarData); + auto avatar = std::static_pointer_cast(avatarData); render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; avatar->addToScene(avatar, scene, pendingChanges); @@ -354,7 +352,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { } } else { for (auto avatarData : _avatarHash) { - auto avatar = std::dynamic_pointer_cast(avatarData); + auto avatar = std::static_pointer_cast(avatarData); render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; avatar->removeFromScene(avatar, scene, pendingChanges); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 35e37656d8..f911dacc4d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -77,7 +77,7 @@ private: // virtual overrides virtual AvatarSharedPointer newSharedAvatar(); - virtual AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); void removeAvatarMotionState(AvatarSharedPointer avatar); virtual void removeAvatar(const QUuid& sessionUUID); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 1da9abacdd..8f2ea82373 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -38,20 +38,26 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } +AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; + + auto avatar = newSharedAvatar(); + avatar->setSessionUUID(sessionUUID); + avatar->setOwningAvatarMixer(mixerWeakPointer); + + _avatarHash.insert(sessionUUID, avatar); + emit avatarAddedEvent(sessionUUID); + + return avatar; +} + AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); auto avatar = _avatarHash.value(sessionUUID); if (!avatar) { - qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - - avatar = newSharedAvatar(); - avatar->setSessionUUID(sessionUUID); - avatar->setOwningAvatarMixer(mixerWeakPointer); - - _avatarHash.insert(sessionUUID, avatar); - emit avatarAddedEvent(sessionUUID); + avatar = addAvatar(sessionUUID, mixerWeakPointer); } return avatar; diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index c3c9160f12..c2fecf5bd9 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -54,7 +54,8 @@ protected: AvatarHashMap(); virtual AvatarSharedPointer newSharedAvatar(); - virtual AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID); From 3f89d73fc651c5553ceb6956912f03b013517080 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 19 Nov 2015 16:15:34 -0800 Subject: [PATCH 11/21] add grasp to equip and example --- examples/controllers/handControllerGrab.js | 237 +++++++++++++------ examples/example/avatarcontrol/graspHands.js | 68 ++++++ 2 files changed, 239 insertions(+), 66 deletions(-) create mode 100644 examples/example/avatarcontrol/graspHands.js diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index dd3a9a4b96..abacb819ff 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -37,9 +37,21 @@ var BUMPER_ON_VALUE = 0.5; var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did -var NO_INTERSECT_COLOR = { red: 10, green: 10, blue: 255}; // line color when pick misses -var INTERSECT_COLOR = { red: 250, green: 10, blue: 10}; // line color when pick hits -var LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000,z: 1000}; +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray @@ -84,12 +96,13 @@ var ACTION_TTL_REFRESH = 5; var PICKS_PER_SECOND_PER_HAND = 5; var MSECS_PER_SEC = 1000.0; var GRABBABLE_PROPERTIES = ["position", - "rotation", - "gravity", - "ignoreForCollisions", - "collisionsWillMove", - "locked", - "name"]; + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js @@ -100,7 +113,7 @@ var DEFAULT_GRABBABLE_DATA = { invertSolidWhileHeld: false }; -var disabledHand ='none'; +var disabledHand = 'none'; // states for the state machine @@ -125,40 +138,40 @@ var STATE_EQUIP_SPRING = 16; function stateToName(state) { switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP_SEARCHING: - return "equip_searching"; - case STATE_EQUIP: - return "equip"; - case STATE_CONTINUE_EQUIP_BD: - return "continue_equip_bd"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_BUMPER_RELEASE: - return "waiting_for_bumper_release"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_DISTANCE_HOLDING: + return "distance_holding"; + case STATE_CONTINUE_DISTANCE_HOLDING: + return "continue_distance_holding"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_WAITING_FOR_BUMPER_RELEASE: + return "waiting_for_bumper_release"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; } return "unknown"; @@ -188,6 +201,12 @@ function entityIsGrabbedByOther(entityID) { } + +//make sure to clean this up when the script ends so we don't get stuck. +Script.scriptEnding.connect(function() { + MyController.endHandGrasp(); +}) + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -211,8 +230,17 @@ function MyController(hand) { this.rawTriggerValue = 0; this.rawBumperValue = 0; - this.offsetPosition = { x: 0.0, y: 0.0, z: 0.0 }; - this.offsetRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; + this.offsetPosition = { + x: 0.0, + y: 0.0, + z: 0.0 + }; + this.offsetRotation = { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0 + }; var _this = this; @@ -277,7 +305,7 @@ function MyController(hand) { this.state = newState; } - this.debugLine = function(closePoint, farPoint, color){ + this.debugLine = function(closePoint, farPoint, color) { Entities.addEntity({ type: "Line", name: "Grab Debug Entity", @@ -321,16 +349,16 @@ function MyController(hand) { this.pointer = null; }; - this.triggerPress = function (value) { + this.triggerPress = function(value) { _this.rawTriggerValue = value; }; - this.bumperPress = function (value) { + this.bumperPress = function(value) { _this.rawBumperValue = value; }; - this.updateSmoothedTrigger = function () { + this.updateSmoothedTrigger = function() { var triggerValue = this.rawTriggerValue; // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + @@ -401,7 +429,7 @@ function MyController(hand) { this.lastPickTime = now; } - for (var index=0; index < pickRays.length; ++index) { + for (var index = 0; index < pickRays.length; ++index) { var pickRay = pickRays[index]; var directionNormalized = Vec3.normalize(pickRay.direction); var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); @@ -466,10 +494,9 @@ function MyController(hand) { } return; } - } else if (! entityIsGrabbedByOther(intersection.entityID)) { + } else if (!entityIsGrabbedByOther(intersection.entityID)) { // don't allow two people to distance grab the same object - if (intersection.properties.collisionsWillMove - && !intersection.properties.locked) { + if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { // the hand is far from the intersected object. go into distance-holding mode this.grabbedEntity = intersection.entityID; if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { @@ -494,10 +521,18 @@ function MyController(hand) { Entities.addEntity({ type: "Sphere", name: "Grab Debug Entity", - dimensions: {x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS}, + dimensions: { + x: GRAB_RADIUS, + y: GRAB_RADIUS, + z: GRAB_RADIUS + }, visible: true, position: handPosition, - color: { red: 0, green: 255, blue: 0}, + color: { + red: 0, + green: 255, + blue: 0 + }, lifetime: 0.1 }); } @@ -604,6 +639,7 @@ function MyController(hand) { } else { Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); } @@ -639,7 +675,7 @@ function MyController(hand) { // the action was set up on a previous call. update the targets. var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * - DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); + DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); // how far did avatar move this timestep? var currentPosition = MyAvatar.position; var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); @@ -688,9 +724,9 @@ function MyController(hand) { // this doubles hand rotation var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, - handRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.handPreviousRotation)); + handRotation, + DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + Quat.inverse(this.handPreviousRotation)); this.handPreviousRotation = handRotation; this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); @@ -773,6 +809,8 @@ function MyController(hand) { this.setState(STATE_CONTINUE_NEAR_GRABBING); } else { // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); this.setState(STATE_CONTINUE_EQUIP_BD); } @@ -781,6 +819,9 @@ function MyController(hand) { } else { Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); } @@ -807,6 +848,7 @@ function MyController(hand) { } if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) { this.setState(STATE_CONTINUE_EQUIP_BD); + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); return; } @@ -827,6 +869,10 @@ function MyController(hand) { this.currentObjectTime = now; Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl Entities.updateAction(this.grabbedEntity, this.actionID, { @@ -846,6 +892,8 @@ function MyController(hand) { if (this.bumperReleased()) { this.setState(STATE_RELEASE); Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + Entities.callEntityMethod(this.grabbedEntity, "unequip"); + this.endHandGrasp(); } }; @@ -856,8 +904,17 @@ function MyController(hand) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); // use a spring to pull the object to where it will be when equipped - var relativeRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; - var relativePosition = { x: 0.0, y: 0.0, z: 0.0 }; + var relativeRotation = { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0 + }; + var relativePosition = { + x: 0.0, + y: 0.0, + z: 0.0 + }; if (grabbableData.spatialKey.relativePosition) { relativePosition = grabbableData.spatialKey.relativePosition; } @@ -913,6 +970,9 @@ function MyController(hand) { } else { Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); this.setState(STATE_CONTINUE_NEAR_TRIGGER); }; @@ -929,6 +989,7 @@ function MyController(hand) { } else { Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); this.setState(STATE_CONTINUE_FAR_TRIGGER); }; @@ -1040,7 +1101,7 @@ function MyController(hand) { this.release = function() { - if(this.hand !== disabledHand){ + if (this.hand !== disabledHand) { //release the disabled hand when we let go with the main one disabledHand = 'none'; } @@ -1075,9 +1136,15 @@ function MyController(hand) { data["gravity"] = grabbedProperties.gravity; data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions; data["collisionsWillMove"] = grabbedProperties.collisionsWillMove; - var whileHeldProperties = {gravity: {x:0, y:0, z:0}}; + var whileHeldProperties = { + gravity: { + x: 0, + y: 0, + z: 0 + } + }; if (invertSolidWhileHeld) { - whileHeldProperties["ignoreForCollisions"] = ! grabbedProperties.ignoreForCollisions; + whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions; } Entities.editEntity(entityID, whileHeldProperties); } @@ -1103,6 +1170,44 @@ function MyController(hand) { } setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + } + + this.graspHandler = null + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + } + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + } + } var rightController = new MyController(RIGHT_HAND); @@ -1132,4 +1237,4 @@ function cleanup() { } Script.scriptEnding.connect(cleanup); -Script.update.connect(update); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/example/avatarcontrol/graspHands.js b/examples/example/avatarcontrol/graspHands.js new file mode 100644 index 0000000000..266ffe6866 --- /dev/null +++ b/examples/example/avatarcontrol/graspHands.js @@ -0,0 +1,68 @@ +// graspHands.js +// +// Created by James B. Pollack @imgntn -- 11/19/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Shows how to use the animation API to grasp an Avatar's hands. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +//choose a hand. set it programatically if you'd like +var handToGrasp = 'LEFT_HAND'; + +//this is our handler, where we do the actual work of changing animation settings +function graspHand(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (handToGrasp === 'RIGHT_HAND') { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (handToGrasp === 'LEFT_HAND') { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; +} + +//keep a reference to this so we can clear it +var handler; + +//register our handler with the animation system +function startHandGrasp() { + if (handToGrasp === 'RIGHT_HAND') { + handler = MyAvatar.addAnimationStateHandler(graspHand, ['isRightHandGrab']); + } else if (handToGrasp === 'LEFT_HAND') { + handler = MyAvatar.addAnimationStateHandler(graspHand, ['isLeftHandGrab']); + } +} + +function endHandGrasp() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(handler); +} + +//make sure to clean this up when the script ends so we don't get stuck. +Script.scriptEnding.connect(function() { + Script.clearInterval(graspInterval); + endHandGrasp(); +}) + +//set an interval and toggle grasping +var isGrasping = false; +var graspInterval = Script.setInterval(function() { + if (isGrasping === false) { + startHandGrasp(); + isGrasping = true; + } else { + endHandGrasp(); + isGrasping = false + } +}, 1000) \ No newline at end of file From 6f76f0cbc04e2a4ba36a361b8ebf43ba779c9ba2 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 19 Nov 2015 16:22:54 -0800 Subject: [PATCH 12/21] release grasp at cleanup --- examples/controllers/handControllerGrab.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index abacb819ff..f8a2eeefa5 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -200,13 +200,6 @@ function entityIsGrabbedByOther(entityID) { return false; } - - -//make sure to clean this up when the script ends so we don't get stuck. -Script.scriptEnding.connect(function() { - MyController.endHandGrasp(); -}) - function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -1122,6 +1115,7 @@ function MyController(hand) { this.cleanup = function() { this.release(); + this.endHandGrasp(); }; this.activateEntity = function(entityID, grabbedProperties) { From 430cb7876dd99cfb7832620b2da65e40f0bf004f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 16:22:56 -0800 Subject: [PATCH 13/21] remove a couple of read lockers for AvatarHashMap --- interface/src/avatar/AvatarManager.cpp | 1 - libraries/avatars/src/AvatarHashMap.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index a64263aec2..bd45561b38 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -256,7 +256,6 @@ QVector AvatarManager::getLocalLights() const { } QVector AvatarManager::getAvatarIdentifiers() { - QReadLocker locker(&_hashLock); return _avatarHash.keys().toVector(); } AvatarData* AvatarManager::getAvatar(QUuid avatarID) { diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 8f2ea82373..035d29f344 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -23,8 +23,8 @@ AvatarHashMap::AvatarHashMap() { } bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { - QReadLocker locker(&_hashLock); - foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) { + auto hashCopy = _avatarHash; + foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getPosition(); float distance = glm::distance(avatarPosition, position); if (distance < range) { From 22f5d4df6dece4e1c9f3c42d1cdced28e4b2dfa9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 Nov 2015 16:25:33 -0800 Subject: [PATCH 14/21] change signature of copy return --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/avatars/src/AvatarHashMap.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6521a0c891..a2102bd010 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -982,7 +982,7 @@ void MyAvatar::updateLookAtTargetAvatar() { const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f; const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; - AvatarHash hash = DependencyManager::get()->getCopy(); + AvatarHash hash = DependencyManager::get()->getHashCopy(); foreach (const AvatarSharedPointer& avatarPointer, hash) { auto avatar = static_pointer_cast(avatarPointer); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index c2fecf5bd9..7795072ec2 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -31,7 +31,7 @@ class AvatarHashMap : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - AvatarHash getCopy() { return _avatarHash; } + AvatarHash getHashCopy() { return _avatarHash; } int size() { return _avatarHash.size(); } signals: From f80a765a296caa309f9484f07c12ed3de5f8985e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 19 Nov 2015 17:26:59 -0800 Subject: [PATCH 15/21] add Assets.uploadData() and Assets.downloadData() --- examples/example/assetsExample.js | 11 ++++ libraries/networking/src/AssetClient.cpp | 62 ++++++++++++++++++++ libraries/networking/src/AssetClient.h | 13 ++++ libraries/script-engine/src/ScriptEngine.cpp | 3 + libraries/script-engine/src/ScriptEngine.h | 3 + 5 files changed, 92 insertions(+) create mode 100644 examples/example/assetsExample.js diff --git a/examples/example/assetsExample.js b/examples/example/assetsExample.js new file mode 100644 index 0000000000..decebbcfa3 --- /dev/null +++ b/examples/example/assetsExample.js @@ -0,0 +1,11 @@ +var data = "this is some data"; +var extension = "txt"; +var uploadedFile; + +Assets.uploadData(data, extension, function (url) { + print("data uploaded to:" + url); + uploadedFile = url; + Assets.downloadData(url, function (data) { + print("data downloaded from:" + url + " the data is:" + data); + }); +}); diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 6a1b46340c..75b4ca04e8 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -365,3 +365,65 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { } } } + +void AssetScriptingInterface::uploadData(QString data, QString extension, QScriptValue callback) { + QByteArray dataByteArray = data.toUtf8(); + auto upload = DependencyManager::get()->createUpload(dataByteArray, extension); + QObject::connect(upload, &AssetUpload::finished, this, [callback, extension](AssetUpload* upload, const QString& hash) mutable { + if (callback.isFunction()) { + QString url = "atp://" + hash + "." + extension; + QScriptValueList args { url }; + callback.call(QScriptValue(), args); + } + }); + + // start the upload now + upload->start(); +} + +void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) { + const QString ATP_SCHEME { "atp://" }; + + if (!urlString.startsWith(ATP_SCHEME)) { + return; + } + + // Make request to atp + auto path = urlString.right(urlString.length() - ATP_SCHEME.length()); + auto parts = path.split(".", QString::SkipEmptyParts); + auto hash = parts.length() > 0 ? parts[0] : ""; + auto extension = parts.length() > 1 ? parts[1] : ""; + + if (hash.length() != SHA256_HASH_HEX_LENGTH) { + return; + } + + auto assetClient = DependencyManager::get(); + auto assetRequest = assetClient->createRequest(hash, extension); + + if (!assetRequest) { + return; + } + + _pendingRequests << assetRequest; + + connect(assetRequest, &AssetRequest::finished, [this, callback](AssetRequest* request) mutable { + Q_ASSERT(request->getState() == AssetRequest::Finished); + + if (request->getError() == AssetRequest::Error::NoError) { + if (callback.isFunction()) { + QString data = QString::fromUtf8(request->getData()); + QScriptValueList args { data }; + callback.call(QScriptValue(), args); + } + } + + request->deleteLater(); + _pendingRequests.remove(request); + }); + + assetRequest->start(); +} + + + diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 22933ea30b..f1bb210614 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -14,6 +14,7 @@ #define hifi_AssetClient_h #include +#include #include @@ -21,6 +22,7 @@ #include "LimitedNodeList.h" #include "NLPacket.h" #include "Node.h" +#include "ResourceCache.h" class AssetRequest; class AssetUpload; @@ -68,4 +70,15 @@ private: friend class AssetUpload; }; + +class AssetScriptingInterface : public QObject { + Q_OBJECT +public: + Q_INVOKABLE void uploadData(QString data, QString extension, QScriptValue callback); + Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete); +protected: + QSet _pendingRequests; +}; + + #endif diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1b0fb80a05..5326090723 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -381,6 +381,9 @@ void ScriptEngine::init() { auto recordingInterface = DependencyManager::get(); registerGlobalObject("Recording", recordingInterface.data()); + + registerGlobalObject("Assets", &_assetScriptingInterface); + } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index c957b0c3b4..1412ba7aaf 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -195,6 +196,8 @@ private: ArrayBufferClass* _arrayBufferClass; + AssetScriptingInterface _assetScriptingInterface; + QHash _registeredHandlers; void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); From fb90ffec5c6fdebb9997c507819e65be9455c766 Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Tue, 17 Nov 2015 19:42:18 -0800 Subject: [PATCH 16/21] Migration of AC playback --- examples/acScripts/playbackAgents.js | 141 +++++++++++++++ examples/acScripts/playbackMaster.js | 259 +++++++++++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 examples/acScripts/playbackAgents.js create mode 100644 examples/acScripts/playbackMaster.js diff --git a/examples/acScripts/playbackAgents.js b/examples/acScripts/playbackAgents.js new file mode 100644 index 0000000000..9912e00010 --- /dev/null +++ b/examples/acScripts/playbackAgents.js @@ -0,0 +1,141 @@ +// +// playbackAgents.js +// acScripts +// +// Created by Edgar Pironti on 11/17/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set the following variables to the values needed +var channel = "PlaybackChannel1"; +var clip_url = null; +var playFromCurrentLocation = true; +var useDisplayName = true; +var useAttachments = true; +var useAvatarModel = true; + +// ID of the agent. Two agents can't have the same ID. +var id = 0; + +// Set position/orientation/scale here if playFromCurrentLocation is true +Avatar.position = { x:0, y: 0, z: 0 }; +Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); +Avatar.scale = 1.0; + +var totalTime = 0; +var subscribed = false; +var WAIT_FOR_AUDIO_MIXER = 1; + +// Script. DO NOT MODIFY BEYOND THIS LINE. +var DO_NOTHING = 0; +var PLAY = 1; +var PLAY_LOOP = 2; +var STOP = 3; +var SHOW = 4; +var HIDE = 5; +var LOAD = 6; + +Recording.setPlayFromCurrentLocation(playFromCurrentLocation); +Recording.setPlayerUseDisplayName(useDisplayName); +Recording.setPlayerUseAttachments(useAttachments); +Recording.setPlayerUseHeadModel(false); +Recording.setPlayerUseSkeletonModel(useAvatarModel); + +function getAction(channel, message, senderID) { + + if(subscribed) { + var command = JSON.parse(message); + print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key); + + if (command.id_key == id || command.id_key == -1) { + if (command.action_key === 6) + clip_url = command.clip_url_key; + + // If the id is -1 (broadcast) and the action is 6, in the url should be the performance file + // with all the clips recorded in a session (not just the single clip url). + // It has to be computed here in order to retrieve the url for the single agent. + // Checking the id we can assign the correct url to the correct agent. + + action = command.action_key; + } else { + action = DO_NOTHING; + } + + switch(action) { + case PLAY: + print("Play"); + if (!Agent.isAvatar) { + Agent.isAvatar = true; + } + if (!Recording.isPlaying()) { + Recording.startPlaying(); + } + Recording.setPlayerLoop(false); + break; + case PLAY_LOOP: + print("Play loop"); + if (!Agent.isAvatar) { + Agent.isAvatar = true; + } + if (!Recording.isPlaying()) { + Recording.startPlaying(); + } + Recording.setPlayerLoop(true); + break; + case STOP: + print("Stop"); + if (Recording.isPlaying()) { + Recording.stopPlaying(); + } + break; + case SHOW: + print("Show"); + if (!Agent.isAvatar) { + Agent.isAvatar = true; + } + break; + case HIDE: + print("Hide"); + if (Recording.isPlaying()) { + Recording.stopPlaying(); + } + Agent.isAvatar = false; + break; + case LOAD: + print("Load"); + if(clip_url !== null) { + Recording.loadRecording(clip_url); + } + break; + case DO_NOTHING: + break; + default: + print("Unknown action: " + action); + break; + + } + + if (Recording.isPlaying()) { + Recording.play(); + } + } +} + + +function update(deltaTime) { + + totalTime += deltaTime; + + if (totalTime > WAIT_FOR_AUDIO_MIXER && !subscribed) { + Messages.subscribe(channel); + subscribed = true; + print("I'm the agent and I am ready to receive!") + } +} + +Script.update.connect(update); +Messages.messageReceived.connect(getAction); + diff --git a/examples/acScripts/playbackMaster.js b/examples/acScripts/playbackMaster.js new file mode 100644 index 0000000000..f1f1b44768 --- /dev/null +++ b/examples/acScripts/playbackMaster.js @@ -0,0 +1,259 @@ +// +// playbackMaster.js +// acScripts +// +// Created by Edgar Pironti on 11/17/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + + +var ac_number = 1; // This is the default number of ACs. Their ID need to be unique and between 0 (included) and ac_number (excluded) +var names = new Array(); // It is possible to specify the name of the ACs in this array. ACs names ordered by IDs (Default name is "ACx", x = ID + 1)) +var channel = "PlaybackChannel1"; +var subscribed = false; +var clip_url = null; +var input_text = null; + +// Script. DO NOT MODIFY BEYOND THIS LINE. +Script.include("../libraries/toolBars.js"); + +var DO_NOTHING = 0; +var PLAY = 1; +var PLAY_LOOP = 2; +var STOP = 3; +var SHOW = 4; +var HIDE = 5; +var LOAD = 6; + +var windowDimensions = Controller.getViewportDimensions(); +var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/"; +var ALPHA_ON = 1.0; +var ALPHA_OFF = 0.7; +var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 }; +var COLOR_MASTER = { red: 0, green: 0, blue: 0 }; +var TEXT_HEIGHT = 12; +var TEXT_MARGIN = 3; + +var toolBars = new Array(); +var nameOverlays = new Array(); +var onOffIcon = new Array(); +var playIcon = new Array(); +var playLoopIcon = new Array(); +var stopIcon = new Array(); +var loadIcon = new Array(); + +setupPlayback(); + +function setupPlayback() { + ac_number = Window.prompt("Insert number of agents: ","1"); + if (ac_number === "" || ac_number === null) + ac_number = 1; + Messages.subscribe(channel); + subscribed = true; + setupToolBars(); +} + +function setupToolBars() { + if (toolBars.length > 0) { + print("Multiple calls to Recorder.js:setupToolBars()"); + return; + } + Tool.IMAGE_HEIGHT /= 2; + Tool.IMAGE_WIDTH /= 2; + + for (i = 0; i <= ac_number; i++) { + toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL)); + toolBars[i].setBack((i == ac_number) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF); + + onOffIcon.push(toolBars[i].addTool({ + imageURL: TOOL_ICON_URL + "ac-on-off.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + x: 0, y: 0, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, true, true)); + + playIcon[i] = toolBars[i].addTool({ + imageURL: TOOL_ICON_URL + "play.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + var playLoopWidthFactor = 1.65; + playLoopIcon[i] = toolBars[i].addTool({ + imageURL: TOOL_ICON_URL + "play-and-loop.svg", + subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: playLoopWidthFactor * Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + stopIcon[i] = toolBars[i].addTool({ + imageURL: TOOL_ICON_URL + "recording-stop.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + loadIcon[i] = toolBars[i].addTool({ + imageURL: TOOL_ICON_URL + "recording-upload.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + nameOverlays.push(Overlays.addOverlay("text", { + backgroundColor: { red: 0, green: 0, blue: 0 }, + font: { size: TEXT_HEIGHT }, + text: (i == ac_number) ? "Master" : i + ". " + + ((i < names.length) ? names[i] : + "AC" + i), + x: 0, y: 0, + width: toolBars[i].width + ToolBar.SPACING, + height: TEXT_HEIGHT + TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + alpha: ALPHA_OFF, + backgroundAlpha: ALPHA_OFF, + visible: true + })); + } +} + +function sendCommand(id, action) { + + if (action === SHOW) { + toolBars[id].selectTool(onOffIcon[id], false); + toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); + toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]); + toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]); + toolBars[id].setAlpha(ALPHA_ON, loadIcon[id]); + } else if (action === HIDE) { + toolBars[id].selectTool(onOffIcon[id], true); + toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]); + toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]); + toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]); + toolBars[id].setAlpha(ALPHA_OFF, loadIcon[id]); + } else if (toolBars[id].toolSelected(onOffIcon[id])) { + return; + } + + if (id == (toolBars.length - 1)) + id = -1; // Master command becomes broadcast. + + var message = { + id_key: id, + action_key: action, + clip_url_key: clip_url + }; + + if(subscribed){ + Messages.sendMessage(channel, JSON.stringify(message)); + print("Message sent!"); + } +} + +function mousePressEvent(event) { + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + // Check master control + var i = toolBars.length - 1; + if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + if (toolBars[i].toolSelected(onOffIcon[i])) { + sendCommand(i, SHOW); + } else { + sendCommand(i, HIDE); + } + } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, PLAY); + } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, PLAY_LOOP); + } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, STOP); + } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + input_text = Window.prompt("Insert the url of the clip: ",""); + if (!(input_text === "" || input_text === null)) { + clip_url = input_text; + sendCommand(i, LOAD); + } + } else { + // Check individual controls + for (i = 0; i < ac_number; i++) { + if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + if (toolBars[i].toolSelected(onOffIcon[i], false)) { + sendCommand(i, SHOW); + } else { + sendCommand(i, HIDE); + } + } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, PLAY); + } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, PLAY_LOOP); + } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + sendCommand(i, STOP); + } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + input_text = Window.prompt("Insert the url of the clip: ",""); + if (!(input_text === "" || input_text === null)) { + clip_url = input_text; + sendCommand(i, LOAD); + } + } else { + + } + } + } +} + +function moveUI() { + var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; + var relative = { x: 70, y: 75 + (ac_number) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; + + for (i = 0; i <= ac_number; i++) { + toolBars[i].move(relative.x, + windowDimensions.y - relative.y + + i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)); + + Overlays.editOverlay(nameOverlays[i], { + x: toolBars[i].x - ToolBar.SPACING, + y: toolBars[i].y - textSize + }); + } +} + +function update() { + var newDimensions = Controller.getViewportDimensions(); + if (windowDimensions.x != newDimensions.x || + windowDimensions.y != newDimensions.y) { + windowDimensions = newDimensions; + moveUI(); + } +} + +function scriptEnding() { + for (i = 0; i <= ac_number; i++) { + toolBars[i].cleanup(); + Overlays.deleteOverlay(nameOverlays[i]); + } + + if(subscribed) + Messages.unsubscribe(channel); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + +moveUI(); \ No newline at end of file From d75209cb3783c071c18d097db199345612e86ea2 Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Wed, 18 Nov 2015 12:30:39 -0800 Subject: [PATCH 17/21] Fixes to clip_url overwriting --- examples/acScripts/playbackAgents.js | 2 ++ examples/acScripts/playbackMaster.js | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/acScripts/playbackAgents.js b/examples/acScripts/playbackAgents.js index 9912e00010..93b563d497 100644 --- a/examples/acScripts/playbackAgents.js +++ b/examples/acScripts/playbackAgents.js @@ -60,6 +60,8 @@ function getAction(channel, message, senderID) { // Checking the id we can assign the correct url to the correct agent. action = command.action_key; + print("That command was for me!"); + print("My clip is: " + clip_url); } else { action = DO_NOTHING; } diff --git a/examples/acScripts/playbackMaster.js b/examples/acScripts/playbackMaster.js index f1f1b44768..e3448c0256 100644 --- a/examples/acScripts/playbackMaster.js +++ b/examples/acScripts/playbackMaster.js @@ -163,6 +163,7 @@ function sendCommand(id, action) { if(subscribed){ Messages.sendMessage(channel, JSON.stringify(message)); print("Message sent!"); + clip_url = null; } } From e267926725fa9e03f6b398398eb3212946d3588b Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Thu, 19 Nov 2015 20:03:27 -0800 Subject: [PATCH 18/21] JSON computing --- examples/acScripts/playbackAgents.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/acScripts/playbackAgents.js b/examples/acScripts/playbackAgents.js index 93b563d497..c50c727277 100644 --- a/examples/acScripts/playbackAgents.js +++ b/examples/acScripts/playbackAgents.js @@ -51,13 +51,25 @@ function getAction(channel, message, senderID) { print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key); if (command.id_key == id || command.id_key == -1) { - if (command.action_key === 6) + if (command.action_key === 6) { clip_url = command.clip_url_key; - - // If the id is -1 (broadcast) and the action is 6, in the url should be the performance file - // with all the clips recorded in a session (not just the single clip url). - // It has to be computed here in order to retrieve the url for the single agent. - // Checking the id we can assign the correct url to the correct agent. + + // If the id is -1 (broadcast) and the action is 6, in the url should be the performance file + // with all the clips recorded in a session (not just the single clip url). + // It has to be computed here in order to retrieve the url for the single agent. + // Checking the id we can assign the correct url to the correct agent. + + if (command.id_key == -1) { + Assets.downloadData(clip_url, function (data) { + var myJSONObject = JSON.parse(data); + var hash = myJSONObject.results[id].hashATP; + }); + + Assets.downloadData(hash, function (data) { + clip_url = JSON.parse(data); + }); + } + } action = command.action_key; print("That command was for me!"); From 8ba6dfe721cc9c0369e0ceec677daba77a3dfd1b Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 20 Nov 2015 09:29:36 -0800 Subject: [PATCH 19/21] add deprication warning about binary SVOs --- libraries/octree/src/Octree.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index fe92fe7745..6f73be360f 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1841,6 +1841,7 @@ bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStream) { + qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon."; bool fileOk = false; @@ -2062,6 +2063,8 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element, } void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) { + qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon."; + std::ofstream file(fileName, std::ios::out|std::ios::binary); if(file.is_open()) { From 19d3d80ff97ec6e2aafff260fcc14b47482beaff Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 20 Nov 2015 11:03:52 -0800 Subject: [PATCH 20/21] fixes for some incorrect assumptions --- interface/src/avatar/AvatarManager.cpp | 10 +++++++++- libraries/avatars/src/AvatarHashMap.cpp | 2 +- libraries/avatars/src/AvatarHashMap.h | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bd45561b38..c17590c4ac 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -110,16 +110,22 @@ void AvatarManager::updateMyAvatar(float deltaTime) { } void AvatarManager::updateOtherAvatars(float deltaTime) { + // lock the hash for read to check the size + QReadLocker lock(&_hashLock); + if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) { return; } + + lock.unlock(); + bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); // simulate avatars - auto hashCopy = _avatarHash; + auto hashCopy = getHashCopy(); AvatarHash::iterator avatarIterator = hashCopy.begin(); while (avatarIterator != hashCopy.end()) { @@ -256,8 +262,10 @@ QVector AvatarManager::getLocalLights() const { } QVector AvatarManager::getAvatarIdentifiers() { + QReadLocker lock(&_hashLock); return _avatarHash.keys().toVector(); } + AvatarData* AvatarManager::getAvatar(QUuid avatarID) { QReadLocker locker(&_hashLock); return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar. diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 035d29f344..c195ab4c32 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -23,7 +23,7 @@ AvatarHashMap::AvatarHashMap() { } bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { - auto hashCopy = _avatarHash; + auto hashCopy = getHashCopy(); foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getPosition(); float distance = glm::distance(avatarPosition, position); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 7795072ec2..5881b779a1 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -31,7 +31,7 @@ class AvatarHashMap : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - AvatarHash getHashCopy() { return _avatarHash; } + AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; } int size() { return _avatarHash.size(); } signals: From 9b5bfd45bc4a822ff365d646b4d65c449655f649 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 20 Nov 2015 11:04:46 -0800 Subject: [PATCH 21/21] change lock back to locker to remove change --- interface/src/avatar/AvatarManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c17590c4ac..769b1d56a2 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -262,7 +262,7 @@ QVector AvatarManager::getLocalLights() const { } QVector AvatarManager::getAvatarIdentifiers() { - QReadLocker lock(&_hashLock); + QReadLocker locker(&_hashLock); return _avatarHash.keys().toVector(); }