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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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 e187aaedcbf60fb72f5d475da2e373d63db6ccf6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 19 Nov 2015 15:56:37 -0800 Subject: [PATCH 07/10] Load recorded clips from URLs, not file paths --- libraries/recording/CMakeLists.txt | 2 +- .../recording/src/recording/ClipCache.cpp | 40 +++++ libraries/recording/src/recording/ClipCache.h | 57 ++++++ .../recording/src/recording/impl/FileClip.cpp | 137 +-------------- .../recording/src/recording/impl/FileClip.h | 27 +-- .../src/recording/impl/PointerClip.cpp | 163 ++++++++++++++++++ .../src/recording/impl/PointerClip.h | 61 +++++++ .../src/RecordingScriptingInterface.cpp | 18 +- .../src/RecordingScriptingInterface.h | 4 +- 9 files changed, 338 insertions(+), 171 deletions(-) create mode 100644 libraries/recording/src/recording/ClipCache.cpp create mode 100644 libraries/recording/src/recording/ClipCache.h create mode 100644 libraries/recording/src/recording/impl/PointerClip.cpp create mode 100644 libraries/recording/src/recording/impl/PointerClip.h diff --git a/libraries/recording/CMakeLists.txt b/libraries/recording/CMakeLists.txt index a0beae4496..b42a4018f8 100644 --- a/libraries/recording/CMakeLists.txt +++ b/libraries/recording/CMakeLists.txt @@ -4,6 +4,6 @@ set(TARGET_NAME recording) setup_hifi_library(Script) # use setup_hifi_library macro to setup our project and link appropriate Qt modules -link_hifi_libraries(shared) +link_hifi_libraries(shared networking) GroupSources("src/recording") diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp new file mode 100644 index 0000000000..fb09245bf9 --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ClipCache.h" +#include "impl/PointerClip.h" + +using namespace recording; +NetworkClipLoader::NetworkClipLoader(const QUrl& url, bool delayLoad) + : Resource(url, delayLoad), _clip(std::make_shared(url)) +{ + +} + + +void NetworkClip::init(const QByteArray& clipData) { + _clipData = clipData; + PointerClip::init((uchar*)_clipData.data(), _clipData.size()); +} + +void NetworkClipLoader::downloadFinished(const QByteArray& data) { + _clip->init(data); +} + +ClipCache& ClipCache::instance() { + static ClipCache _instance; + return _instance; +} + +NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { + return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast(); +} + +QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { + return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared); +} + diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h new file mode 100644 index 0000000000..c72d45648d --- /dev/null +++ b/libraries/recording/src/recording/ClipCache.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2015/11/19 +// 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 +// +#pragma once +#ifndef hifi_Recording_ClipCache_h +#define hifi_Recording_ClipCache_h + +#include + +#include "Forward.h" +#include "impl/PointerClip.h" + +namespace recording { + +class NetworkClip : public PointerClip { +public: + using Pointer = std::shared_ptr; + + NetworkClip(const QUrl& url) : _url(url) {} + virtual void init(const QByteArray& clipData); + virtual QString getName() const override { return _url.toString(); } + +private: + QByteArray _clipData; + QUrl _url; +}; + +class NetworkClipLoader : public Resource { +public: + NetworkClipLoader(const QUrl& url, bool delayLoad); + virtual void downloadFinished(const QByteArray& data) override; + ClipPointer getClip() { return _clip; } + bool completed() { return _failedToLoad || isLoaded(); } + +private: + const NetworkClip::Pointer _clip; +}; + +using NetworkClipLoaderPointer = QSharedPointer; + +class ClipCache : public ResourceCache { +public: + static ClipCache& instance(); + + NetworkClipLoaderPointer getClipLoader(const QUrl& url); + +protected: + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) override; +}; + +} + +#endif diff --git a/libraries/recording/src/recording/impl/FileClip.cpp b/libraries/recording/src/recording/impl/FileClip.cpp index fcc22452e0..ce2705a76c 100644 --- a/libraries/recording/src/recording/impl/FileClip.cpp +++ b/libraries/recording/src/recording/impl/FileClip.cpp @@ -23,63 +23,6 @@ using namespace recording; -static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); -static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes"); -static const QString FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); - -using FrameTranslationMap = QMap; - -FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { - FrameTranslationMap results; - auto headerObj = doc.object(); - if (headerObj.contains(FRAME_TYPE_MAP)) { - auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject(); - auto currentFrameTypes = Frame::getFrameTypes(); - for (auto frameTypeName : frameTypeObj.keys()) { - qDebug() << frameTypeName; - if (!currentFrameTypes.contains(frameTypeName)) { - continue; - } - FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; - FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); - results[storedTypeEnum] = currentTypeEnum; - } - } - return results; -} - - -FileFrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) { - FileFrameHeaderList results; - auto current = start; - auto end = current + size; - // Read all the frame headers - // FIXME move to Frame::readHeader? - while (end - current >= MINIMUM_FRAME_SIZE) { - FileFrameHeader header; - memcpy(&(header.type), current, sizeof(FrameType)); - current += sizeof(FrameType); - memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); - current += sizeof(Frame::Time); - memcpy(&(header.size), current, sizeof(FrameSize)); - current += sizeof(FrameSize); - header.fileOffset = current - start; - if (end - current < header.size) { - current = end; - break; - } - current += header.size; - results.push_back(header); - } - qDebug() << "Parsed source data into " << results.size() << " frames"; -// int i = 0; -// for (const auto& frameHeader : results) { -// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type; -// } - return results; -} - - FileClip::FileClip(const QString& fileName) : _file(fileName) { auto size = _file.size(); qDebug() << "Opening file of size: " << size; @@ -88,58 +31,8 @@ FileClip::FileClip(const QString& fileName) : _file(fileName) { qCWarning(recordingLog) << "Unable to open file " << fileName; return; } - _map = _file.map(0, size, QFile::MapPrivateOption); - if (!_map) { - qCWarning(recordingLog) << "Unable to map file " << fileName; - return; - } - - auto parsedFrameHeaders = parseFrameHeaders(_map, size); - - // Verify that at least one frame exists and that the first frame is a header - if (0 == parsedFrameHeaders.size()) { - qWarning() << "No frames found, invalid file"; - return; - } - - // Grab the file header - { - auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); - parsedFrameHeaders.pop_front(); - if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { - qWarning() << "Missing header frame, invalid file"; - return; - } - - QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); - _fileHeader = QJsonDocument::fromBinaryData(fileHeaderData); - } - - // Check for compression - { - _compressed = _fileHeader.object()[FRAME_COMREPSSION_FLAG].toBool(); - } - - // Find the type enum translation map and fix up the frame headers - { - FrameTranslationMap translationMap = parseTranslationMap(_fileHeader); - if (translationMap.empty()) { - qWarning() << "Header missing frame type map, invalid file"; - return; - } - qDebug() << translationMap; - - // Update the loaded headers with the frame data - _frames.reserve(parsedFrameHeaders.size()); - for (auto& frameHeader : parsedFrameHeaders) { - if (!translationMap.contains(frameHeader.type)) { - continue; - } - frameHeader.type = translationMap[frameHeader.type]; - _frames.push_back(frameHeader); - } - } - + auto mappedFile = _file.map(0, size, QFile::MapPrivateOption); + init(mappedFile, size); } @@ -228,31 +121,9 @@ bool FileClip::write(const QString& fileName, Clip::Pointer clip) { FileClip::~FileClip() { Locker lock(_mutex); - _file.unmap(_map); - _map = nullptr; + _file.unmap(_data); if (_file.isOpen()) { _file.close(); } -} - -// Internal only function, needs no locking -FrameConstPointer FileClip::readFrame(size_t frameIndex) const { - FramePointer result; - if (frameIndex < _frames.size()) { - result = std::make_shared(); - const auto& header = _frames[frameIndex]; - result->type = header.type; - result->timeOffset = header.timeOffset; - if (header.size) { - result->data.insert(0, reinterpret_cast(_map)+header.fileOffset, header.size); - if (_compressed) { - result->data = qUncompress(result->data); - } - } - } - return result; -} - -void FileClip::addFrame(FrameConstPointer) { - throw std::runtime_error("File clips are read only"); + reset(); } diff --git a/libraries/recording/src/recording/impl/FileClip.h b/libraries/recording/src/recording/impl/FileClip.h index f103a9aca6..71ae414721 100644 --- a/libraries/recording/src/recording/impl/FileClip.h +++ b/libraries/recording/src/recording/impl/FileClip.h @@ -10,27 +10,13 @@ #ifndef hifi_Recording_Impl_FileClip_h #define hifi_Recording_Impl_FileClip_h -#include "ArrayClip.h" - -#include +#include "PointerClip.h" #include -#include - -#include "../Frame.h" namespace recording { -struct FileFrameHeader : public FrameHeader { - FrameType type; - Frame::Time timeOffset; - uint16_t size; - quint64 fileOffset; -}; - -using FileFrameHeaderList = std::list; - -class FileClip : public ArrayClip { +class FileClip : public PointerClip { public: using Pointer = std::shared_ptr; @@ -38,20 +24,11 @@ public: virtual ~FileClip(); virtual QString getName() const override; - virtual void addFrame(FrameConstPointer) override; - - const QJsonDocument& getHeader() { - return _fileHeader; - } static bool write(const QString& filePath, Clip::Pointer clip); private: - virtual FrameConstPointer readFrame(size_t index) const override; - QJsonDocument _fileHeader; QFile _file; - uchar* _map { nullptr }; - bool _compressed { true }; }; } diff --git a/libraries/recording/src/recording/impl/PointerClip.cpp b/libraries/recording/src/recording/impl/PointerClip.cpp new file mode 100644 index 0000000000..48132c066d --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.cpp @@ -0,0 +1,163 @@ +// +// Created by Bradley Austin Davis 2015/11/04 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "PointerClip.h" + +#include + +#include +#include +#include + +#include + +#include "../Frame.h" +#include "../Logging.h" +#include "BufferClip.h" + + +using namespace recording; + +const QString PointerClip::FRAME_TYPE_MAP = QStringLiteral("frameTypes"); +const QString PointerClip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed"); + +using FrameTranslationMap = QMap; + +FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) { + FrameTranslationMap results; + auto headerObj = doc.object(); + if (headerObj.contains(PointerClip::FRAME_TYPE_MAP)) { + auto frameTypeObj = headerObj[PointerClip::FRAME_TYPE_MAP].toObject(); + auto currentFrameTypes = Frame::getFrameTypes(); + for (auto frameTypeName : frameTypeObj.keys()) { + qDebug() << frameTypeName; + if (!currentFrameTypes.contains(frameTypeName)) { + continue; + } + FrameType currentTypeEnum = currentFrameTypes[frameTypeName]; + FrameType storedTypeEnum = static_cast(frameTypeObj[frameTypeName].toInt()); + results[storedTypeEnum] = currentTypeEnum; + } + } + return results; +} + + +PointerFrameHeaderList parseFrameHeaders(uchar* const start, const size_t& size) { + PointerFrameHeaderList results; + auto current = start; + auto end = current + size; + // Read all the frame headers + // FIXME move to Frame::readHeader? + while (end - current >= PointerClip::MINIMUM_FRAME_SIZE) { + PointerFrameHeader header; + memcpy(&(header.type), current, sizeof(FrameType)); + current += sizeof(FrameType); + memcpy(&(header.timeOffset), current, sizeof(Frame::Time)); + current += sizeof(Frame::Time); + memcpy(&(header.size), current, sizeof(FrameSize)); + current += sizeof(FrameSize); + header.fileOffset = current - start; + if (end - current < header.size) { + current = end; + break; + } + current += header.size; + results.push_back(header); + } + qDebug() << "Parsed source data into " << results.size() << " frames"; +// int i = 0; +// for (const auto& frameHeader : results) { +// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type; +// } + return results; +} + +void PointerClip::reset() { + _frames.clear(); + _data = nullptr; + _size = 0; + _header = QJsonDocument(); +} + +void PointerClip::init(uchar* data, size_t size) { + reset(); + + _data = data; + _size = size; + + auto parsedFrameHeaders = parseFrameHeaders(data, size); + // Verify that at least one frame exists and that the first frame is a header + if (0 == parsedFrameHeaders.size()) { + qWarning() << "No frames found, invalid file"; + reset(); + return; + } + + // Grab the file header + { + auto fileHeaderFrameHeader = *parsedFrameHeaders.begin(); + parsedFrameHeaders.pop_front(); + if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) { + qWarning() << "Missing header frame, invalid file"; + reset(); + return; + } + + QByteArray fileHeaderData((char*)_data + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size); + _header = QJsonDocument::fromBinaryData(fileHeaderData); + } + + // Check for compression + { + _compressed = _header.object()[FRAME_COMREPSSION_FLAG].toBool(); + } + + // Find the type enum translation map and fix up the frame headers + { + FrameTranslationMap translationMap = parseTranslationMap(_header); + if (translationMap.empty()) { + qWarning() << "Header missing frame type map, invalid file"; + reset(); + return; + } + + // Update the loaded headers with the frame data + _frames.reserve(parsedFrameHeaders.size()); + for (auto& frameHeader : parsedFrameHeaders) { + if (!translationMap.contains(frameHeader.type)) { + continue; + } + frameHeader.type = translationMap[frameHeader.type]; + _frames.push_back(frameHeader); + } + } + +} + +// Internal only function, needs no locking +FrameConstPointer PointerClip::readFrame(size_t frameIndex) const { + FramePointer result; + if (frameIndex < _frames.size()) { + result = std::make_shared(); + const auto& header = _frames[frameIndex]; + result->type = header.type; + result->timeOffset = header.timeOffset; + if (header.size) { + result->data.insert(0, reinterpret_cast(_data)+header.fileOffset, header.size); + if (_compressed) { + result->data = qUncompress(result->data); + } + } + } + return result; +} + +void PointerClip::addFrame(FrameConstPointer) { + throw std::runtime_error("Pointer clips are read only, use duplicate to create a read/write clip"); +} diff --git a/libraries/recording/src/recording/impl/PointerClip.h b/libraries/recording/src/recording/impl/PointerClip.h new file mode 100644 index 0000000000..5a7a3499fe --- /dev/null +++ b/libraries/recording/src/recording/impl/PointerClip.h @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis 2015/11/05 +// 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 +// + +#pragma once +#ifndef hifi_Recording_Impl_PointerClip_h +#define hifi_Recording_Impl_PointerClip_h + +#include "ArrayClip.h" + +#include + +#include + +#include "../Frame.h" + +namespace recording { + +struct PointerFrameHeader : public FrameHeader { + FrameType type; + Frame::Time timeOffset; + uint16_t size; + quint64 fileOffset; +}; + +using PointerFrameHeaderList = std::list; + +class PointerClip : public ArrayClip { +public: + using Pointer = std::shared_ptr; + + PointerClip() {}; + PointerClip(uchar* data, size_t size) { init(data, size); } + + void init(uchar* data, size_t size); + virtual void addFrame(FrameConstPointer) override; + const QJsonDocument& getHeader() const { + return _header; + } + + // FIXME move to frame? + static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize); + static const QString FRAME_TYPE_MAP; + static const QString FRAME_COMREPSSION_FLAG; + +protected: + void reset(); + virtual FrameConstPointer readFrame(size_t index) const override; + QJsonDocument _header; + uchar* _data { nullptr }; + size_t _size { 0 }; + bool _compressed { true }; +}; + +} + +#endif diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 33e67e1b43..d82d471d79 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -8,14 +8,15 @@ #include "RecordingScriptingInterface.h" -#include +#include +#include +#include #include #include #include #include -#include -#include +#include #include "ScriptEngineLogging.h" @@ -43,20 +44,17 @@ float RecordingScriptingInterface::playerLength() const { return _player->length(); } -void RecordingScriptingInterface::loadRecording(const QString& filename) { +void RecordingScriptingInterface::loadRecording(const QString& url) { using namespace recording; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, - Q_ARG(QString, filename)); + Q_ARG(QString, url)); return; } - ClipPointer clip = Clip::fromFile(filename); - if (!clip) { - qWarning() << "Unable to load clip data from " << filename; - } - _player->queueClip(clip); + // FIXME make blocking and force off main thread? + _player->queueClip(ClipCache::instance().getClipLoader(url)->getClip()); } void RecordingScriptingInterface::startPlaying() { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 483ead3ca3..3834089177 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -25,7 +25,7 @@ public: RecordingScriptingInterface(); public slots: - void loadRecording(const QString& filename); + void loadRecording(const QString& url); void startPlaying(); void pausePlayer(); From 3f89d73fc651c5553ceb6956912f03b013517080 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 19 Nov 2015 16:15:34 -0800 Subject: [PATCH 08/10] 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 09/10] 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 f80a765a296caa309f9484f07c12ed3de5f8985e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 19 Nov 2015 17:26:59 -0800 Subject: [PATCH 10/10] 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);