Merge branch 'master' of https://github.com/highfidelity/hifi into ambient

This commit is contained in:
Sam Cake 2017-04-16 23:31:06 -07:00
commit 2d35f95930
36 changed files with 404 additions and 200 deletions

View file

@ -33,6 +33,7 @@
#include <SoundCache.h> #include <SoundCache.h>
#include <UUID.h> #include <UUID.h>
#include <recording/ClipCache.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <recording/Frame.h> #include <recording/Frame.h>
@ -53,7 +54,8 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
Agent::Agent(ReceivedMessage& message) : Agent::Agent(ReceivedMessage& message) :
ThreadedAssignment(message), ThreadedAssignment(message),
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) { _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES)
{
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender); DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
ResourceManager::init(); ResourceManager::init();
@ -64,12 +66,16 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCache>();
DependencyManager::set<AudioScriptingInterface>(); DependencyManager::set<AudioScriptingInterface>();
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<recording::Deck>(); DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>(); DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>(); DependencyManager::set<recording::ClipCache>();
DependencyManager::set<ScriptCache>(); DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT); DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
DependencyManager::set<RecordingScriptingInterface>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes( packetReceiver.registerListenerForTypes(
@ -327,6 +333,8 @@ void Agent::executeScript() {
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload)); _scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine.get());
// setup an Avatar for the script to use // setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
@ -470,6 +478,8 @@ void Agent::executeScript() {
Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
Frame::clearFrameHandler(AVATAR_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE);
DependencyManager::destroy<RecordingScriptingInterface>();
setFinished(true); setFinished(true);
} }
@ -753,8 +763,19 @@ void Agent::aboutToFinish() {
// cleanup the AudioInjectorManager (and any still running injectors) // cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>(); DependencyManager::destroy<AudioInjectorManager>();
// destroy all other created dependencies
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ScriptEngines>(); DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AudioScriptingInterface>();
DependencyManager::destroy<recording::Deck>();
DependencyManager::destroy<recording::Recorder>();
DependencyManager::destroy<recording::ClipCache>();
emit stopAvatarAudioTimer(); emit stopAvatarAudioTimer();
_avatarAudioTimerThread.quit(); _avatarAudioTimerThread.quit();

View file

@ -46,9 +46,6 @@ class Agent : public ThreadedAssignment {
public: public:
Agent(ReceivedMessage& message); Agent(ReceivedMessage& message);
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
bool isPlayingAvatarSound() const { return _avatarSound != NULL; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
bool isListeningToAudioStream() const { return _isListeningToAudioStream; } bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
@ -65,6 +62,9 @@ public:
public slots: public slots:
void run() override; void run() override;
void playAvatarSound(SharedSoundPointer avatarSound); void playAvatarSound(SharedSoundPointer avatarSound);
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
private slots: private slots:
void requestScript(); void requestScript();

View file

@ -67,15 +67,13 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) {
int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
int bytesSent = 0;
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious
bytesSent += individualData.size(); auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
identityPacket->write(individualData); identityPackets->write(individualData);
DependencyManager::get<NodeList>()->sendPacket(std::move(identityPacket), *destinationNode); DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPackets), *destinationNode);
_stats.numIdentityPackets++; _stats.numIdentityPackets++;
return bytesSent; return individualData.size();
} }
static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
@ -265,8 +263,16 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
// make sure we haven't already sent this data from this sender to this receiver // make sure we haven't already sent this data from this sender to this receiver
// or that somehow we haven't sent // or that somehow we haven't sent
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
++numAvatarsHeldBack; // don't ignore this avatar if we haven't sent any update for a long while
shouldIgnore = true; // in an effort to prevent other interfaces from deleting a stale avatar instance
uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID());
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND;
if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() &&
lastBroadcastTime + AVATAR_UPDATE_STALE > startIgnoreCalculation) {
++numAvatarsHeldBack;
shouldIgnore = true;
}
} else if (lastSeqFromSender - lastSeqToReceiver > 1) { } else if (lastSeqFromSender - lastSeqToReceiver > 1) {
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening // this is a skip - we still send the packet but capture the presence of the skip so we see it happening
++numAvatarsWithSkippedFrames; ++numAvatarsWithSkippedFrames;
@ -325,7 +331,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
_stats.overBudgetAvatars++; _stats.overBudgetAvatars++;
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData;
} else if (!isInView) { } else if (!isInView) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
nodeData->incrementAvatarOutOfView(); nodeData->incrementAvatarOutOfView();
} else { } else {
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -91,7 +91,7 @@ Item {
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: (selected || isMyCard) && activeTab == "nearbyTab"; enabled: (selected && activeTab == "nearbyTab") || isMyCard;
hoverEnabled: enabled hoverEnabled: enabled
onClicked: { onClicked: {
userInfoViewer.url = defaultBaseUrl + "/users/" + userName; userInfoViewer.url = defaultBaseUrl + "/users/" + userName;

View file

@ -1401,6 +1401,13 @@ Rectangle {
var selectedIDs = getSelectedConnectionsUserNames(); var selectedIDs = getSelectedConnectionsUserNames();
connectionsUserModelData.sort(function (a, b) { connectionsUserModelData.sort(function (a, b) {
var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase(); var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase();
if (!aValue && !bValue) {
return 0;
} else if (!aValue) {
return after;
} else if (!bValue) {
return before;
}
switch (true) { switch (true) {
case (aValue < bValue): return before; case (aValue < bValue): return before;
case (aValue > bValue): return after; case (aValue > bValue): return after;

View file

@ -118,6 +118,7 @@
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
#include <UsersScriptingInterface.h> #include <UsersScriptingInterface.h>
#include <recording/ClipCache.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <shared/StringHelpers.h> #include <shared/StringHelpers.h>
@ -467,6 +468,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<StatTracker>(); DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT); DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
DependencyManager::set<Preferences>(); DependencyManager::set<Preferences>();
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<recording::Deck>(); DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>(); DependencyManager::set<recording::Recorder>();
DependencyManager::set<AddressManager>(); DependencyManager::set<AddressManager>();
@ -5439,6 +5441,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// AvatarManager has some custom types // AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine); AvatarManager::registerMetaTypes(scriptEngine);
// give the script engine to the RecordingScriptingInterface for its callbacks
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
if (property(hifi::properties::TEST).isValid()) { if (property(hifi::properties::TEST).isValid()) {
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
} }
@ -6417,7 +6422,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
// If we're not doing an animated snapshot as well... // If we're not doing an animated snapshot as well...
if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) { if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) {
// Tell the dependency manager that the capture of the still snapshot has taken place. // Tell the dependency manager that the capture of the still snapshot has taken place.
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path, "", notify); emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
} else { } else {
// Get an animated GIF snapshot and save it // Get an animated GIF snapshot and save it
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>()); SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());

View file

@ -77,7 +77,7 @@ void Head::simulate(float deltaTime, bool isMine) {
float audioLoudness = 0.0f; float audioLoudness = 0.0f;
if (_owningAvatar) { if (_owningAvatar) {
_owningAvatar->getAudioLoudness(); audioLoudness = _owningAvatar->getAudioLoudness();
} }
// Update audio trailing average for rendering facial animations // Update audio trailing average for rendering facial animations

View file

@ -240,8 +240,8 @@ void SkeletonModel::updateAttitude() {
// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed), // Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed),
// but just before head has been simulated. // but just before head has been simulated.
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
updateAttitude();
if (fullUpdate) { if (fullUpdate) {
updateAttitude();
setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients());
Model::simulate(deltaTime, fullUpdate); Model::simulate(deltaTime, fullUpdate);

View file

@ -71,9 +71,10 @@ signals:
void domainChanged(const QString& domainHostname); void domainChanged(const QString& domainHostname);
void svoImportRequested(const QString& url); void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo);
void snapshotTaken(const QString& pathStillSnapshot, const QString& pathAnimatedSnapshot, bool notify); void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
void snapshotShared(const QString& error); void snapshotShared(const QString& error);
void processingGif(); void processingGifStarted(const QString& pathStillSnapshot);
void processingGifCompleted(const QString& pathAnimatedSnapshot);
void connectionAdded(const QString& connectionName); void connectionAdded(const QString& connectionName);
void connectionError(const QString& errorString); void connectionError(const QString& errorString);

View file

@ -86,7 +86,8 @@ void SnapshotAnimated::captureFrames() {
SnapshotAnimated::snapshotAnimatedTimerRunning = false; SnapshotAnimated::snapshotAnimatedTimerRunning = false;
// Notify the user that we're processing the snapshot // Notify the user that we're processing the snapshot
emit SnapshotAnimated::snapshotAnimatedDM->processingGif(); // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath);
// Kick off the thread that'll pack the frames into the GIF // Kick off the thread that'll pack the frames into the GIF
QtConcurrent::run(processFrames); QtConcurrent::run(processFrames);
@ -132,7 +133,7 @@ void SnapshotAnimated::processFrames() {
// Reset the current frame timestamp // Reset the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = 0; SnapshotAnimated::snapshotAnimatedTimestamp = 0;
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
// Let the window scripting interface know that the snapshots have been taken. // Update the "Share" dialog with the processed GIF.
emit SnapshotAnimated::snapshotAnimatedDM->snapshotTaken(SnapshotAnimated::snapshotStillPath, SnapshotAnimated::snapshotAnimatedPath, false); emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath);
} }

View file

@ -110,6 +110,8 @@ const char LEFT_HAND_POINTING_FLAG = 1;
const char RIGHT_HAND_POINTING_FLAG = 2; const char RIGHT_HAND_POINTING_FLAG = 2;
const char IS_FINGER_POINTING_FLAG = 4; const char IS_FINGER_POINTING_FLAG = 4;
const qint64 AVATAR_UPDATE_TIMEOUT = 5 * USECS_PER_SECOND;
// AvatarData state flags - we store the details about the packet encoding in the first byte, // AvatarData state flags - we store the details about the packet encoding in the first byte,
// before the "header" structure // before the "header" structure
const char AVATARDATA_FLAGS_MINIMUM = 0; const char AVATARDATA_FLAGS_MINIMUM = 0;
@ -599,10 +601,7 @@ public:
} }
bool shouldDie() const { bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; }
const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND;
return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS;
}
static const float OUT_OF_VIEW_PENALTY; static const float OUT_OF_VIEW_PENALTY;

View file

@ -19,7 +19,6 @@ QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
QNetworkAccessManager& NetworkAccessManager::getInstance() { QNetworkAccessManager& NetworkAccessManager::getInstance() {
if (!networkAccessManagers.hasLocalData()) { if (!networkAccessManagers.hasLocalData()) {
networkAccessManagers.setLocalData(new QNetworkAccessManager()); networkAccessManagers.setLocalData(new QNetworkAccessManager());
} }
return *networkAccessManagers.localData(); return *networkAccessManagers.localData();

View file

@ -5,8 +5,12 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QThread>
#include "ClipCache.h" #include "ClipCache.h"
#include "impl/PointerClip.h" #include "impl/PointerClip.h"
#include "Logging.h"
using namespace recording; using namespace recording;
NetworkClipLoader::NetworkClipLoader(const QUrl& url) : NetworkClipLoader::NetworkClipLoader(const QUrl& url) :
@ -21,18 +25,28 @@ void NetworkClip::init(const QByteArray& clipData) {
void NetworkClipLoader::downloadFinished(const QByteArray& data) { void NetworkClipLoader::downloadFinished(const QByteArray& data) {
_clip->init(data); _clip->init(data);
finishedLoading(true); finishedLoading(true);
emit clipLoaded();
} }
ClipCache& ClipCache::instance() { ClipCache::ClipCache(QObject* parent) :
static ClipCache _instance; ResourceCache(parent)
return _instance; {
} }
NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) {
return ResourceCache::getResource(url, QUrl(), nullptr).staticCast<NetworkClipLoader>(); if (QThread::currentThread() != thread()) {
NetworkClipLoaderPointer result;
QMetaObject::invokeMethod(this, "getClipLoader", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(NetworkClipLoaderPointer, result), Q_ARG(const QUrl&, url));
return result;
}
return getResource(url).staticCast<NetworkClipLoader>();
} }
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) { QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) {
qCDebug(recordingLog) << "Loading recording at" << url;
return QSharedPointer<Resource>(new NetworkClipLoader(url), &Resource::deleter); return QSharedPointer<Resource>(new NetworkClipLoader(url), &Resource::deleter);
} }

View file

@ -30,26 +30,34 @@ private:
}; };
class NetworkClipLoader : public Resource { class NetworkClipLoader : public Resource {
Q_OBJECT
public: public:
NetworkClipLoader(const QUrl& url); NetworkClipLoader(const QUrl& url);
virtual void downloadFinished(const QByteArray& data) override; virtual void downloadFinished(const QByteArray& data) override;
ClipPointer getClip() { return _clip; } ClipPointer getClip() { return _clip; }
bool completed() { return _failedToLoad || isLoaded(); } bool completed() { return _failedToLoad || isLoaded(); }
signals:
void clipLoaded();
private: private:
const NetworkClip::Pointer _clip; const NetworkClip::Pointer _clip;
}; };
using NetworkClipLoaderPointer = QSharedPointer<NetworkClipLoader>; using NetworkClipLoaderPointer = QSharedPointer<NetworkClipLoader>;
class ClipCache : public ResourceCache { class ClipCache : public ResourceCache, public Dependency {
public: Q_OBJECT
static ClipCache& instance(); SINGLETON_DEPENDENCY
public slots:
NetworkClipLoaderPointer getClipLoader(const QUrl& url); NetworkClipLoaderPointer getClipLoader(const QUrl& url);
protected: protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) override; virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) override;
private:
ClipCache(QObject* parent = nullptr);
}; };
} }

View file

@ -52,32 +52,49 @@ float RecordingScriptingInterface::playerLength() const {
return _player->length(); return _player->length();
} }
bool RecordingScriptingInterface::loadRecording(const QString& url) { void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue callback) {
using namespace recording; auto clipLoader = DependencyManager::get<recording::ClipCache>()->getClipLoader(url);
auto loader = ClipCache::instance().getClipLoader(url); // hold a strong pointer to the loading clip so that it has a chance to load
if (!loader) { _clipLoaders.insert(clipLoader);
qWarning() << "Clip failed to load from " << url;
return false;
}
if (!loader->isLoaded()) { auto weakClipLoader = clipLoader.toWeakRef();
QEventLoop loop;
QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit);
QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit);
loop.exec();
}
if (!loader->isLoaded()) { // when clip loaded, call the callback with the URL and success boolean
qWarning() << "Clip failed to load from " << url; connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this,
return false; [this, weakClipLoader, url, callback]() mutable {
}
_player->queueClip(loader->getClip()); if (auto clipLoader = weakClipLoader.toStrongRef()) {
return true; qCDebug(scriptengine) << "Loaded recording from" << url;
_player->queueClip(clipLoader->getClip());
if (callback.isFunction()) {
QScriptValueList args { true, url };
callback.call(_scriptEngine->globalObject(), args);
}
// drop our strong pointer to this clip so it is cleaned up
_clipLoaders.remove(clipLoader);
}
});
// when clip load fails, call the callback with the URL and failure boolean
connect(clipLoader.data(), &recording::NetworkClipLoader::failed, this, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable {
qCDebug(scriptengine) << "Failed to load recording from" << url;
if (callback.isFunction()) {
QScriptValueList args { false, url };
callback.call(_scriptEngine->currentContext()->thisObject(), args);
}
if (auto clipLoader = weakClipLoader.toStrongRef()) {
// drop out strong pointer to this clip so it is cleaned up
_clipLoaders.remove(clipLoader);
}
});
} }
void RecordingScriptingInterface::startPlaying() { void RecordingScriptingInterface::startPlaying() {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);

View file

@ -15,9 +15,11 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <recording/ClipCache.h>
#include <recording/Forward.h> #include <recording/Forward.h>
#include <recording/Frame.h> #include <recording/Frame.h>
class QScriptEngine;
class QScriptValue; class QScriptValue;
class RecordingScriptingInterface : public QObject, public Dependency { class RecordingScriptingInterface : public QObject, public Dependency {
@ -26,8 +28,10 @@ class RecordingScriptingInterface : public QObject, public Dependency {
public: public:
RecordingScriptingInterface(); RecordingScriptingInterface();
void setScriptEngine(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; }
public slots: public slots:
bool loadRecording(const QString& url); void loadRecording(const QString& url, QScriptValue callback = QScriptValue());
void startPlaying(); void startPlaying();
void pausePlayer(); void pausePlayer();
@ -79,6 +83,9 @@ protected:
Flag _useAttachments { false }; Flag _useAttachments { false };
Flag _useSkeletonModel { false }; Flag _useSkeletonModel { false };
recording::ClipPointer _lastClip; recording::ClipPointer _lastClip;
QScriptEngine* _scriptEngine;
QSet<recording::NetworkClipLoaderPointer> _clipLoaders;
}; };
#endif // hifi_RecordingScriptingInterface_h #endif // hifi_RecordingScriptingInterface_h

View file

@ -21,7 +21,7 @@
#include <typeinfo> #include <typeinfo>
#define SINGLETON_DEPENDENCY \ #define SINGLETON_DEPENDENCY \
friend class DependencyManager; friend class ::DependencyManager;
class Dependency { class Dependency {
public: public:

View file

@ -769,7 +769,7 @@ bool similarStrings(const QString& stringA, const QString& stringB) {
void disableQtBearerPoll() { void disableQtBearerPoll() {
// to work around the Qt constant wireless scanning, set the env for polling interval very high // to work around the Qt constant wireless scanning, set the env for polling interval very high
const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT16_MAX).toLocal8Bit();
qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT);
} }

View file

@ -71,7 +71,7 @@ public:
void addSample(T sample) { void addSample(T sample) {
if (numSamples > 0) { if (numSamples > 0) {
average = ((float)sample * WEIGHTING) + ((float)average * ONE_MINUS_WEIGHTING); average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING);
} else { } else {
average = sample; average = sample;
} }

View file

@ -24,7 +24,6 @@
Q_DECLARE_LOGGING_CATEGORY(inputplugins) Q_DECLARE_LOGGING_CATEGORY(inputplugins)
Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
const char* KinectPlugin::NAME = "Kinect"; const char* KinectPlugin::NAME = "Kinect";
const char* KinectPlugin::KINECT_ID_STRING = "Kinect"; const char* KinectPlugin::KINECT_ID_STRING = "Kinect";
@ -493,11 +492,23 @@ void KinectPlugin::ProcessBody(INT64 time, int bodyCount, IBody** bodies) {
//_joints[j].orientation = jointOrientation; //_joints[j].orientation = jointOrientation;
if (joints[j].JointType == JointType_HandRight) { if (joints[j].JointType == JointType_HandRight) {
static const quat kinectToHandRight = glm::angleAxis(-PI / 2.0f, Vectors::UNIT_Y); static const quat kinectToHandRight = glm::angleAxis(-PI / 2.0f, Vectors::UNIT_Y);
_joints[j].orientation = jointOrientation * kinectToHandRight; // add moving average of orientation quaternion
glm::quat jointSample = jointOrientation * kinectToHandRight;
if (glm::dot(jointSample, _RightHandOrientationAverage.getAverage()) < 0) {
jointSample = -jointSample;
}
_RightHandOrientationAverage.addSample(jointSample);
_joints[j].orientation = glm::normalize(_RightHandOrientationAverage.getAverage());
} else if (joints[j].JointType == JointType_HandLeft) { } else if (joints[j].JointType == JointType_HandLeft) {
// To transform from Kinect to our LEFT Hand.... Postive 90 deg around Y // To transform from Kinect to our LEFT Hand.... Postive 90 deg around Y
static const quat kinectToHandLeft = glm::angleAxis(PI / 2.0f, Vectors::UNIT_Y); static const quat kinectToHandLeft = glm::angleAxis(PI / 2.0f, Vectors::UNIT_Y);
_joints[j].orientation = jointOrientation * kinectToHandLeft; // add moving average of orientation quaternion
glm::quat jointSample = jointOrientation * kinectToHandLeft;
if (glm::dot(jointSample, _LeftHandOrientationAverage.getAverage()) < 0) {
jointSample = -jointSample;
}
_LeftHandOrientationAverage.addSample(jointSample);
_joints[j].orientation = glm::normalize(_LeftHandOrientationAverage.getAverage());
} else { } else {
_joints[j].orientation = jointOrientation; _joints[j].orientation = jointOrientation;
} }
@ -643,4 +654,4 @@ void KinectPlugin::InputDevice::clearState() {
int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i); int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i);
_poseStateMap[poseIndex] = controller::Pose(); _poseStateMap[poseIndex] = controller::Pose();
} }
} }

View file

@ -23,6 +23,7 @@
// Kinect Header files // Kinect Header files
#include <Kinect.h> #include <Kinect.h>
#include <SimpleMovingAverage.h>
// Safe release for interfaces // Safe release for interfaces
template<class Interface> inline void SafeRelease(Interface *& pInterfaceToRelease) { template<class Interface> inline void SafeRelease(Interface *& pInterfaceToRelease) {
@ -58,6 +59,11 @@ public:
virtual void saveSettings() const override; virtual void saveSettings() const override;
virtual void loadSettings() override; virtual void loadSettings() override;
private:
// add variables for moving average
ThreadSafeMovingAverage<glm::quat, 2> _LeftHandOrientationAverage;
ThreadSafeMovingAverage<glm::quat, 2> _RightHandOrientationAverage;
protected: protected:
struct KinectJoint { struct KinectJoint {

View file

@ -138,7 +138,13 @@ Agent.isAvatar = true;
Agent.isListeningToAudioStream = true; Agent.isListeningToAudioStream = true;
Avatar.skeletonModelURL = AVATAR_URL; // FIXME - currently setting an avatar while playing a recording doesn't work it will be ignored Avatar.skeletonModelURL = AVATAR_URL; // FIXME - currently setting an avatar while playing a recording doesn't work it will be ignored
Recording.loadRecording(RECORDING_URL); Recording.loadRecording(RECORDING_URL, function(success) {
if (success) {
Script.update.connect(update);
} else {
print("Failed to load recording from " + RECORDING_URL);
}
});
count = 300; // This is necessary to wait for the audio mixer to connect count = 300; // This is necessary to wait for the audio mixer to connect
function update(event) { function update(event) {
@ -174,10 +180,8 @@ function update(event) {
+" FT: " + Avatar.getDataRate("faceTrackerOutbound").toFixed(2) + "\n" +" FT: " + Avatar.getDataRate("faceTrackerOutbound").toFixed(2) + "\n"
+" JD: " + Avatar.getDataRate("jointDataOutbound").toFixed(2)); +" JD: " + Avatar.getDataRate("jointDataOutbound").toFixed(2));
} }
if (!Recording.isPlaying()) { if (!Recording.isPlaying()) {
Script.update.disconnect(update); Script.update.disconnect(update);
} }
} }
Script.update.connect(update);

View file

@ -20,7 +20,13 @@ Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0);
Avatar.scale = 1.0; Avatar.scale = 1.0;
Agent.isAvatar = true; Agent.isAvatar = true;
Recording.loadRecording(recordingFile); Recording.loadRecording(recordingFile, function(success) {
if (success) {
Script.update.connect(update);
} else {
print("Failed to load recording from " + recordingFile);
}
});
count = 300; // This is necessary to wait for the audio mixer to connect count = 300; // This is necessary to wait for the audio mixer to connect
function update(event) { function update(event) {
@ -39,10 +45,8 @@ function update(event) {
Vec3.print("Playing from ", Avatar.position); Vec3.print("Playing from ", Avatar.position);
count--; count--;
} }
if (!Recording.isPlaying()) { if (!Recording.isPlaying()) {
Script.update.disconnect(update); Script.update.disconnect(update);
} }
} }
Script.update.connect(update);

View file

@ -42,10 +42,15 @@ var playRecording = function() {
Recording.setPlayerLoop(false); Recording.setPlayerLoop(false);
Recording.setPlayerTime(STARTING_TIME); Recording.setPlayerTime(STARTING_TIME);
Recording.setPlayerAudioOffset(AUDIO_OFFSET); Recording.setPlayerAudioOffset(AUDIO_OFFSET);
Recording.loadRecording(CLIP_URL); Recording.loadRecording(CLIP_URL, function(success) {
Recording.startPlaying(); if (success) {
isPlaying = true; Recording.startPlaying();
isPlayable = false; // Set this true again after the cooldown period isPlaying = true;
isPlayable = false; // Set this true again after the cooldown period
} else {
print("Failed to load recording from " + CLIP_URL);
}
});
}; };
Script.update.connect(function(deltaTime) { Script.update.connect(function(deltaTime) {

View file

@ -2,7 +2,7 @@
var origin = {x: 512, y: 512, z: 512}; var origin = {x: 512, y: 512, z: 512};
var millisecondsToWaitBeforeStarting = 2 * 1000; // To give the various servers a chance to start. var millisecondsToWaitBeforeStarting = 2 * 1000; // To give the various servers a chance to start.
var millisecondsToWaitBeforeEnding = 30 * 1000; var millisecondsToWaitBeforeEnding = 30 * 1000;
Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy
Avatar.displayName = "AC Avatar"; Avatar.displayName = "AC Avatar";
@ -10,9 +10,15 @@ Agent.isAvatar = true;
Script.setTimeout(function () { Script.setTimeout(function () {
Avatar.position = origin; Avatar.position = origin;
Recording.loadRecording("d:/hifi.rec"); Recording.loadRecording("d:/hifi.rec", function(success) {
Recording.setPlayerLoop(true); if (success) {
Recording.startPlaying(); Recording.setPlayerLoop(true);
Recording.startPlaying();
} else {
print("Failed to load recording");
}
});
}, millisecondsToWaitBeforeStarting); }, millisecondsToWaitBeforeStarting);
@ -21,4 +27,4 @@ Script.setTimeout(function () {
Agent.isAvatar = false; Agent.isAvatar = false;
Recording.stopPlaying(); Recording.stopPlaying();
Script.stop(); Script.stop();
}, millisecondsToWaitBeforeEnding); }, millisecondsToWaitBeforeEnding);

View file

@ -26,7 +26,7 @@ var PLAY = "Play";
function getAction(channel, message, senderID) { function getAction(channel, message, senderID) {
if(subscribed) { if(subscribed) {
print("I'm the agent and I received this: " + message); print("I'm the agent and I received this: " + message);
switch(message) { switch(message) {
case PLAY: case PLAY:
print("Play"); print("Play");
@ -35,7 +35,7 @@ function getAction(channel, message, senderID) {
Recording.startPlaying(); Recording.startPlaying();
} }
break; break;
default: default:
print("Unknown action: " + action); print("Unknown action: " + action);
break; break;
@ -49,16 +49,21 @@ function update(deltaTime) {
totalTime += deltaTime; totalTime += deltaTime;
if (totalTime > WAIT_FOR_AUDIO_MIXER) { if (totalTime > WAIT_FOR_AUDIO_MIXER) {
if (!subscribed) { if (!subscribed) {
Messages.subscribe(PLAYBACK_CHANNEL); Messages.subscribe(PLAYBACK_CHANNEL);
subscribed = true; subscribed = true;
Recording.loadRecording(clip_url); Recording.loadRecording(clip_url, function(success) {
Recording.setPlayFromCurrentLocation(playFromCurrentLocation); if (success) {
Recording.setPlayerUseDisplayName(useDisplayName); Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
Recording.setPlayerUseAttachments(useAttachments); Recording.setPlayerUseDisplayName(useDisplayName);
Recording.setPlayerUseHeadModel(false); Recording.setPlayerUseAttachments(useAttachments);
Recording.setPlayerUseSkeletonModel(useAvatarModel); Recording.setPlayerUseHeadModel(false);
Agent.isAvatar = true; Recording.setPlayerUseSkeletonModel(useAvatarModel);
Agent.isAvatar = true;
} else {
print("Failed to load recording from " + clip_url);
}
});
} }
} }
@ -70,4 +75,4 @@ Messages.messageReceived.connect(function (channel, message, senderID) {
} }
}); });
Script.update.connect(update); Script.update.connect(update);

View file

@ -9,7 +9,7 @@
var command = null; var command = null;
var clip_url = null; var clip_url = null;
var REVIEW_CHANNEL = "reviewChannel"; var REVIEW_CHANNEL = "reviewChannel";
var playFromCurrentLocation = true; var playFromCurrentLocation = true;
@ -28,16 +28,16 @@ var HIDE = "Hide";
function getAction(channel, message, senderID) { function getAction(channel, message, senderID) {
if(subscribed) { if(subscribed) {
print("I'm the agent and I received this: " + message); print("I'm the agent and I received this: " + message);
if (Recording.isPlaying()) { if (Recording.isPlaying()) {
Recording.stopPlaying(); Recording.stopPlaying();
} }
m = JSON.parse(message); m = JSON.parse(message);
command = m.command; command = m.command;
clip_url = m.clip_url; clip_url = m.clip_url;
switch(command) { switch(command) {
case PLAY: case PLAY:
print("Play"); print("Play");
@ -46,21 +46,25 @@ function getAction(channel, message, senderID) {
Recording.startPlaying(); Recording.startPlaying();
} }
break; break;
case SHOW: case SHOW:
print("Show"); print("Show");
Recording.loadRecording(clip_url); Recording.loadRecording(clip_url, function(success) {
Agent.isAvatar = true; if (success) {
Recording.setPlayerTime(0.0); Agent.isAvatar = true;
Recording.startPlaying(); Recording.setPlayerTime(0.0);
Recording.stopPlaying(); Recording.startPlaying();
Recording.stopPlaying();
}
});
break; break;
case HIDE: case HIDE:
print("Hide"); print("Hide");
Agent.isAvatar = false; Agent.isAvatar = false;
break; break;
default: default:
print("Unknown action: " + action); print("Unknown action: " + action);
break; break;
@ -74,7 +78,7 @@ function update(deltaTime) {
totalTime += deltaTime; totalTime += deltaTime;
if (totalTime > WAIT_FOR_AUDIO_MIXER) { if (totalTime > WAIT_FOR_AUDIO_MIXER) {
if (!subscribed) { if (!subscribed) {
Messages.subscribe(REVIEW_CHANNEL); Messages.subscribe(REVIEW_CHANNEL);
subscribed = true; subscribed = true;
Recording.setPlayFromCurrentLocation(playFromCurrentLocation); Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
@ -93,4 +97,4 @@ Messages.messageReceived.connect(function (channel, message, senderID) {
} }
}); });
Script.update.connect(update); Script.update.connect(update);

View file

@ -55,13 +55,13 @@ function setupToolBar() {
} }
Tool.IMAGE_HEIGHT /= 2; Tool.IMAGE_HEIGHT /= 2;
Tool.IMAGE_WIDTH /= 2; Tool.IMAGE_WIDTH /= 2;
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
toolBar.onMove = onToolbarMove; toolBar.onMove = onToolbarMove;
toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF);
recordIcon = toolBar.addTool({ recordIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-record.svg", imageURL: TOOL_ICON_URL + "recording-record.svg",
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
@ -71,7 +71,7 @@ function setupToolBar() {
alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON, alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON,
visible: true visible: true
}, true, !Recording.isRecording()); }, true, !Recording.isRecording());
var playLoopWidthFactor = 1.65; var playLoopWidthFactor = 1.65;
playIcon = toolBar.addTool({ playIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play-pause.svg", imageURL: TOOL_ICON_URL + "play-pause.svg",
@ -80,7 +80,7 @@ function setupToolBar() {
alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true visible: true
}, false); }, false);
playLoopIcon = toolBar.addTool({ playLoopIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "play-and-loop.svg", imageURL: TOOL_ICON_URL + "play-and-loop.svg",
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
@ -89,10 +89,10 @@ function setupToolBar() {
alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true visible: true
}, false); }, false);
timerOffset = toolBar.width + ToolBar.SPACING; timerOffset = toolBar.width + ToolBar.SPACING;
spacing = toolBar.addSpacing(0); spacing = toolBar.addSpacing(0);
saveIcon = toolBar.addTool({ saveIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-save.svg", imageURL: TOOL_ICON_URL + "recording-save.svg",
width: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH,
@ -100,7 +100,7 @@ function setupToolBar() {
alpha: (Recording.isRecording() || Recording.isPlaying() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, alpha: (Recording.isRecording() || Recording.isPlaying() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON,
visible: true visible: true
}, false); }, false);
loadIcon = toolBar.addTool({ loadIcon = toolBar.addTool({
imageURL: TOOL_ICON_URL + "recording-upload.svg", imageURL: TOOL_ICON_URL + "recording-upload.svg",
width: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH,
@ -153,10 +153,10 @@ function onToolbarMove(newX, newY, deltaX, deltaY) {
x: newX + timerOffset - ToolBar.SPACING, x: newX + timerOffset - ToolBar.SPACING,
y: newY y: newY
}); });
slider.x = newX - ToolBar.SPACING; slider.x = newX - ToolBar.SPACING;
slider.y = newY - slider.h - ToolBar.SPACING; slider.y = newY - slider.h - ToolBar.SPACING;
Overlays.editOverlay(slider.background, { Overlays.editOverlay(slider.background, {
x: slider.x, x: slider.x,
y: slider.y y: slider.y
@ -182,13 +182,13 @@ function updateTimer() {
width: timerWidth width: timerWidth
}); });
toolBar.changeSpacing(timerWidth + ToolBar.SPACING, spacing); toolBar.changeSpacing(timerWidth + ToolBar.SPACING, spacing);
if (Recording.isRecording()) { if (Recording.isRecording()) {
slider.pos = 1.0; slider.pos = 1.0;
} else if (Recording.playerLength() > 0) { } else if (Recording.playerLength() > 0) {
slider.pos = Recording.playerElapsed() / Recording.playerLength(); slider.pos = Recording.playerElapsed() / Recording.playerLength();
} }
Overlays.editOverlay(slider.foreground, { Overlays.editOverlay(slider.foreground, {
width: slider.pos * slider.w width: slider.pos * slider.w
}); });
@ -221,7 +221,7 @@ function moveUI() {
function mousePressEvent(event) { function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (recordIcon === toolBar.clicked(clickedOverlay, false) && !Recording.isPlaying()) { if (recordIcon === toolBar.clicked(clickedOverlay, false) && !Recording.isPlaying()) {
if (!Recording.isRecording()) { if (!Recording.isRecording()) {
Recording.startRecording(); Recording.startRecording();
@ -281,8 +281,13 @@ function mousePressEvent(event) {
if (!Recording.isRecording() && !Recording.isPlaying()) { if (!Recording.isRecording() && !Recording.isPlaying()) {
recordingFile = Window.browse("Load recording from file", ".", "Recordings (*.hfr *.rec *.HFR *.REC)"); recordingFile = Window.browse("Load recording from file", ".", "Recordings (*.hfr *.rec *.HFR *.REC)");
if (!(recordingFile === "null" || recordingFile === null || recordingFile === "")) { if (!(recordingFile === "null" || recordingFile === null || recordingFile === "")) {
Recording.loadRecording(recordingFile); Recording.loadRecording(recordingFile, function(success) {
setDefaultPlayerOptions(); if (success) {
setDefaultPlayerOptions();
} else {
print("Failed to load recording from " + recordingFile);
}
});
} }
if (Recording.playerLength() > 0) { if (Recording.playerLength() > 0) {
toolBar.setAlpha(ALPHA_ON, playIcon); toolBar.setAlpha(ALPHA_ON, playIcon);
@ -323,7 +328,7 @@ function update() {
} }
updateTimer(); updateTimer();
if (watchStop && !Recording.isPlaying()) { if (watchStop && !Recording.isPlaying()) {
watchStop = false; watchStop = false;
toolBar.setAlpha(ALPHA_ON, recordIcon); toolBar.setAlpha(ALPHA_ON, recordIcon);

View file

@ -1051,8 +1051,6 @@ function MyController(hand) {
this.homeButtonTouched = false; this.homeButtonTouched = false;
this.editTriggered = false; this.editTriggered = false;
this.controllerJointIndex = getControllerJointIndex(this.hand);
// Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems // Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems
// when more than one avatar does parenting grabs on things. This script tries to work // when more than one avatar does parenting grabs on things. This script tries to work
// around this with two associative arrays: previousParentID and previousParentJointIndex. If // around this with two associative arrays: previousParentID and previousParentJointIndex. If
@ -1736,6 +1734,7 @@ function MyController(hand) {
this.off = function(deltaTime, timestamp) { this.off = function(deltaTime, timestamp) {
this.controllerJointIndex = getControllerJointIndex(this.hand);
this.checkForUnexpectedChildren(); this.checkForUnexpectedChildren();
if (this.editTriggered) { if (this.editTriggered) {

View file

@ -29,13 +29,13 @@ var COLORS_TELEPORT_SEAT = {
red: 255, red: 255,
green: 0, green: 0,
blue: 170 blue: 170
} };
var COLORS_TELEPORT_CAN_TELEPORT = { var COLORS_TELEPORT_CAN_TELEPORT = {
red: 97, red: 97,
green: 247, green: 247,
blue: 255 blue: 255
} };
var COLORS_TELEPORT_CANNOT_TELEPORT = { var COLORS_TELEPORT_CANNOT_TELEPORT = {
red: 0, red: 0,
@ -52,7 +52,7 @@ var COLORS_TELEPORT_CANCEL = {
var TELEPORT_CANCEL_RANGE = 1; var TELEPORT_CANCEL_RANGE = 1;
var COOL_IN_DURATION = 500; var COOL_IN_DURATION = 500;
const handInfo = { var handInfo = {
right: { right: {
controllerInput: Controller.Standard.RightHand controllerInput: Controller.Standard.RightHand
}, },
@ -80,7 +80,7 @@ function Trigger(hand) {
this.down = function() { this.down = function() {
var down = _this.buttonValue === 1 ? 1.0 : 0.0; var down = _this.buttonValue === 1 ? 1.0 : 0.0;
return down return down;
}; };
} }
@ -90,8 +90,9 @@ var ignoredEntities = [];
var TELEPORTER_STATES = { var TELEPORTER_STATES = {
IDLE: 'idle', IDLE: 'idle',
COOL_IN: 'cool_in', COOL_IN: 'cool_in',
TARGETTING: 'targetting',
TARGETTING_INVALID: 'targetting_invalid', TARGETTING_INVALID: 'targetting_invalid',
} };
var TARGET = { var TARGET = {
NONE: 'none', // Not currently targetting anything NONE: 'none', // Not currently targetting anything
@ -99,7 +100,7 @@ var TARGET = {
INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.) INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.)
SURFACE: 'surface', // The current target is a valid surface SURFACE: 'surface', // The current target is a valid surface
SEAT: 'seat', // The current target is a seat SEAT: 'seat', // The current target is a seat
} };
function Teleporter() { function Teleporter() {
var _this = this; var _this = this;
@ -114,8 +115,8 @@ function Teleporter() {
this.updateConnected = null; this.updateConnected = null;
this.activeHand = null; this.activeHand = null;
this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); this.teleporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName); this.teleportMappingInternal = Controller.newMapping(this.teleporterMappingInternalName);
// Setup overlays // Setup overlays
this.cancelOverlay = Overlays.addOverlay("model", { this.cancelOverlay = Overlays.addOverlay("model", {
@ -135,11 +136,11 @@ function Teleporter() {
}); });
this.enableMappings = function() { this.enableMappings = function() {
Controller.enableMapping(this.telporterMappingInternalName); Controller.enableMapping(this.teleporterMappingInternalName);
}; };
this.disableMappings = function() { this.disableMappings = function() {
Controller.disableMapping(teleporter.telporterMappingInternalName); Controller.disableMapping(teleporter.teleporterMappingInternalName);
}; };
this.cleanup = function() { this.cleanup = function() {
@ -179,7 +180,7 @@ function Teleporter() {
if (_this.state === TELEPORTER_STATES.COOL_IN) { if (_this.state === TELEPORTER_STATES.COOL_IN) {
_this.state = TELEPORTER_STATES.TARGETTING; _this.state = TELEPORTER_STATES.TARGETTING;
} }
}, COOL_IN_DURATION) }, COOL_IN_DURATION);
this.activeHand = hand; this.activeHand = hand;
this.enableMappings(); this.enableMappings();
@ -203,13 +204,13 @@ function Teleporter() {
}; };
this.deleteOverlayBeams = function() { this.deleteOverlayBeams = function() {
for (key in this.overlayLines) { for (var key in this.overlayLines) {
if (this.overlayLines[key] !== null) { if (this.overlayLines[key] !== null) {
Overlays.deleteOverlay(this.overlayLines[key]); Overlays.deleteOverlay(this.overlayLines[key]);
this.overlayLines[key] = null; this.overlayLines[key] = null;
} }
} }
} };
this.update = function() { this.update = function() {
if (_this.state === TELEPORTER_STATES.IDLE) { if (_this.state === TELEPORTER_STATES.IDLE) {
@ -272,7 +273,8 @@ function Teleporter() {
this.hideCancelOverlay(); this.hideCancelOverlay();
this.hideSeatOverlay(); this.hideSeatOverlay();
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection,
COLORS_TELEPORT_CAN_TELEPORT);
this.updateDestinationOverlay(this.targetOverlay, intersection); this.updateDestinationOverlay(this.targetOverlay, intersection);
} }
} else if (teleportLocationType === TARGET.SEAT) { } else if (teleportLocationType === TARGET.SEAT) {
@ -284,13 +286,15 @@ function Teleporter() {
} }
if (((_this.activeHand == 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) { if (((_this.activeHand === 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) {
// remember the state before we exit teleport mode and set it back to IDLE
var previousState = this.state;
this.exitTeleportMode(); this.exitTeleportMode();
this.hideCancelOverlay(); this.hideCancelOverlay();
this.hideTargetOverlay(); this.hideTargetOverlay();
this.hideSeatOverlay(); this.hideSeatOverlay();
if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) { if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || previousState === TELEPORTER_STATES.COOL_IN) {
// Do nothing // Do nothing
} else if (teleportLocationType === TARGET.SEAT) { } else if (teleportLocationType === TARGET.SEAT) {
Entities.callEntityMethod(intersection.entityID, 'sit'); Entities.callEntityMethod(intersection.entityID, 'sit');
@ -321,7 +325,7 @@ function Teleporter() {
this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties); this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties);
} else { } else {
var success = Overlays.editOverlay(this.overlayLines[hand], { Overlays.editOverlay(this.overlayLines[hand], {
start: closePoint, start: closePoint,
end: farPoint, end: farPoint,
color: color color: color
@ -361,7 +365,7 @@ function Teleporter() {
}; };
} }
//related to repositioning the avatar after you teleport // related to repositioning the avatar after you teleport
function getAvatarFootOffset() { function getAvatarFootOffset() {
var data = getJointData(); var data = getJointData();
var upperLeg, lowerLeg, foot, toe, toeTop; var upperLeg, lowerLeg, foot, toe, toeTop;
@ -384,14 +388,14 @@ function getAvatarFootOffset() {
var offset = upperLeg + lowerLeg + foot + toe + toeTop; var offset = upperLeg + lowerLeg + foot + toe + toeTop;
offset = offset / 100; offset = offset / 100;
return offset; return offset;
}; }
function getJointData() { function getJointData() {
var allJointData = []; var allJointData = [];
var jointNames = MyAvatar.jointNames; var jointNames = MyAvatar.jointNames;
jointNames.forEach(function(joint, index) { jointNames.forEach(function(joint, index) {
var translation = MyAvatar.getJointTranslation(index); var translation = MyAvatar.getJointTranslation(index);
var rotation = MyAvatar.getJointRotation(index) var rotation = MyAvatar.getJointRotation(index);
allJointData.push({ allJointData.push({
joint: joint, joint: joint,
index: index, index: index,
@ -401,7 +405,7 @@ function getJointData() {
}); });
return allJointData; return allJointData;
}; }
var leftPad = new ThumbPad('left'); var leftPad = new ThumbPad('left');
var rightPad = new ThumbPad('right'); var rightPad = new ThumbPad('right');
@ -420,7 +424,7 @@ function isMoving() {
} else { } else {
return false; return false;
} }
}; }
function parseJSON(json) { function parseJSON(json) {
try { try {
@ -433,7 +437,7 @@ function parseJSON(json) {
// point that is being intersected with is looked at. If this normal is more // point that is being intersected with is looked at. If this normal is more
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
// you can't teleport there. // you can't teleport there.
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
function getTeleportTargetType(intersection) { function getTeleportTargetType(intersection) {
if (!intersection.intersects) { if (!intersection.intersects) {
return TARGET.NONE; return TARGET.NONE;
@ -465,7 +469,7 @@ function getTeleportTargetType(intersection) {
} else { } else {
return TARGET.SURFACE; return TARGET.SURFACE;
} }
}; }
function registerMappings() { function registerMappings() {
mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); mappingName = 'Hifi-Teleporter-Dev-' + Math.random();
@ -487,7 +491,7 @@ function registerMappings() {
if (isMoving() === true) { if (isMoving() === true) {
return; return;
} }
teleporter.enterTeleportMode('left') teleporter.enterTeleportMode('left');
return; return;
}); });
teleportMapping.from(Controller.Standard.RightPrimaryThumb) teleportMapping.from(Controller.Standard.RightPrimaryThumb)
@ -502,10 +506,10 @@ function registerMappings() {
return; return;
} }
teleporter.enterTeleportMode('right') teleporter.enterTeleportMode('right');
return; return;
}); });
}; }
registerMappings(); registerMappings();
@ -521,7 +525,6 @@ Script.scriptEnding.connect(cleanup);
var isDisabled = false; var isDisabled = false;
var handleTeleportMessages = function(channel, message, sender) { var handleTeleportMessages = function(channel, message, sender) {
var data;
if (sender === MyAvatar.sessionUUID) { if (sender === MyAvatar.sessionUUID) {
if (channel === 'Hifi-Teleport-Disabler') { if (channel === 'Hifi-Teleport-Disabler') {
if (message === 'both') { if (message === 'both') {
@ -531,7 +534,7 @@ var handleTeleportMessages = function(channel, message, sender) {
isDisabled = 'left'; isDisabled = 'left';
} }
if (message === 'right') { if (message === 'right') {
isDisabled = 'right' isDisabled = 'right';
} }
if (message === 'none') { if (message === 'none') {
isDisabled = false; isDisabled = false;
@ -545,7 +548,7 @@ var handleTeleportMessages = function(channel, message, sender) {
} }
} }
} }
} };
Messages.subscribe('Hifi-Teleport-Disabler'); Messages.subscribe('Hifi-Teleport-Disabler');
Messages.subscribe('Hifi-Teleport-Ignore-Add'); Messages.subscribe('Hifi-Teleport-Ignore-Add');

View file

@ -23,6 +23,7 @@
HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index",
HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable",
HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable";
HOW_TO_EXIT_MESSAGE = "Press B on your controller to exit FingerPainting mode";
function paintBrush(name) { function paintBrush(name) {
// Paints in 3D. // Paints in 3D.
@ -319,6 +320,15 @@
} }
} }
function howToExitTutorial() {
HMD.requestShowHandControllers();
setControllerPartLayer('button_b', 'highlight');
messageWindow = Window.alert(HOW_TO_EXIT_MESSAGE);
setControllerPartLayer('button_b', 'blank');
HMD.requestHideHandControllers();
Settings.setValue("FingerPaintTutorialComplete", true);
}
function enableProcessing() { function enableProcessing() {
// Connect controller API to handController objects. // Connect controller API to handController objects.
leftHand = handController("left"); leftHand = handController("left");
@ -328,7 +338,12 @@
controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress);
controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress);
controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress);
controllerMapping.from(Controller.Standard.B).to(onButtonClicked);
Controller.enableMapping(CONTROLLER_MAPPING_NAME); Controller.enableMapping(CONTROLLER_MAPPING_NAME);
if (!Settings.getValue("FingerPaintTutorialComplete")) {
howToExitTutorial();
}
// Connect handController outputs to paintBrush objects. // Connect handController outputs to paintBrush objects.
leftBrush = paintBrush("left"); leftBrush = paintBrush("left");
@ -433,6 +448,17 @@
button.clicked.disconnect(onButtonClicked); button.clicked.disconnect(onButtonClicked);
tablet.removeButton(button); tablet.removeButton(button);
} }
/**
* A controller is made up of parts, and each part can have multiple "layers,"
* which are really just different texures. For example, the "trigger" part
* has "normal" and "highlight" layers.
*/
function setControllerPartLayer(part, layer) {
data = {};
data[part] = layer;
Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data));
}
setUp(); setUp();
Script.scriptEnding.connect(tearDown); Script.scriptEnding.connect(tearDown);

View file

@ -21,6 +21,7 @@ function addImage(data) {
img = document.createElement("IMG"), img = document.createElement("IMG"),
div2 = document.createElement("DIV"), div2 = document.createElement("DIV"),
id = "p" + idCounter++; id = "p" + idCounter++;
img.id = id + "img";
function toggle() { data.share = input.checked; } function toggle() { data.share = input.checked; }
div.style.height = "" + Math.floor(100 / imageCount) + "%"; div.style.height = "" + Math.floor(100 / imageCount) + "%";
if (imageCount > 1) { if (imageCount > 1) {
@ -33,7 +34,7 @@ function addImage(data) {
label.setAttribute('for', id); // cannot do label.for = label.setAttribute('for', id); // cannot do label.for =
input.id = id; input.id = id;
input.type = "checkbox"; input.type = "checkbox";
input.checked = (id === "p0"); input.checked = false;
data.share = input.checked; data.share = input.checked;
input.addEventListener('change', toggle); input.addEventListener('change', toggle);
div2.setAttribute("class", "property checkbox"); div2.setAttribute("class", "property checkbox");
@ -46,9 +47,9 @@ function addImage(data) {
document.getElementById("snapshot-images").appendChild(div); document.getElementById("snapshot-images").appendChild(div);
paths.push(data); paths.push(data);
} }
function handleShareButtons(shareMsg) { function handleShareButtons(messageOptions) {
var openFeed = document.getElementById('openFeed'); var openFeed = document.getElementById('openFeed');
openFeed.checked = shareMsg.openFeedAfterShare; openFeed.checked = messageOptions.openFeedAfterShare;
openFeed.onchange = function () { openFeed.onchange = function () {
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "snapshot", type: "snapshot",
@ -56,7 +57,7 @@ function handleShareButtons(shareMsg) {
})); }));
}; };
if (!shareMsg.canShare) { if (!messageOptions.canShare) {
// this means you may or may not be logged in, but can't share // this means you may or may not be logged in, but can't share
// because you are not in a public place. // because you are not in a public place.
document.getElementById("sharing").innerHTML = "<p class='prompt'>Snapshots can be shared when they're taken in shareable places."; document.getElementById("sharing").innerHTML = "<p class='prompt'>Snapshots can be shared when they're taken in shareable places.";
@ -74,13 +75,28 @@ window.onload = function () {
return; return;
} }
// last element of list contains a bool for whether or not we can share stuff // The last element of the message contents list contains a bunch of options,
var shareMsg = message.action.pop(); // including whether or not we can share stuff
handleShareButtons(shareMsg); // The other elements of the list contain image paths.
var messageOptions = message.action.pop();
// rest are image paths which we add handleShareButtons(messageOptions);
imageCount = message.action.length;
message.action.forEach(addImage); if (messageOptions.containsGif) {
if (messageOptions.processingGif) {
imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon
message.action.unshift({ localPath: messageOptions.loadingGifPath });
message.action.forEach(addImage);
document.getElementById('p0').disabled = true;
} else {
var gifPath = message.action[0].localPath;
document.getElementById('p0').disabled = false;
document.getElementById('p0img').src = gifPath;
paths[0].localPath = gifPath;
}
} else {
imageCount = message.action.length;
message.action.forEach(addImage);
}
}); });
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "snapshot", type: "snapshot",

View file

@ -532,7 +532,7 @@ function onNotify(msg) {
createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this
} }
function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { function onSnapshotTaken(pathStillSnapshot, notify) {
if (notify) { if (notify) {
var imageProperties = { var imageProperties = {
path: "file:///" + pathStillSnapshot, path: "file:///" + pathStillSnapshot,
@ -656,8 +656,8 @@ Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding); Script.scriptEnding.connect(scriptEnding);
Menu.menuItemEvent.connect(menuItemEvent); Menu.menuItemEvent.connect(menuItemEvent);
Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.domainConnectionRefused.connect(onDomainConnectionRefused);
Window.snapshotTaken.connect(onSnapshotTaken); Window.stillSnapshotTaken.connect(onSnapshotTaken);
Window.processingGif.connect(processingGif); Window.processingGifStarted.connect(processingGif);
Window.connectionAdded.connect(connectionAdded); Window.connectionAdded.connect(connectionAdded);
Window.connectionError.connect(connectionError); Window.connectionError.connect(connectionError);
Window.notifyEditError = onEditError; Window.notifyEditError = onEditError;

View file

@ -90,12 +90,12 @@ function onMessage(message) {
needsLogin = true; needsLogin = true;
submessage.share = false; submessage.share = false;
shareAfterLogin = true; shareAfterLogin = true;
snapshotToShareAfterLogin = {path: submessage.localPath, href: submessage.href}; snapshotToShareAfterLogin = {path: submessage.localPath, href: submessage.href || href};
} }
if (submessage.share) { if (submessage.share) {
print('sharing', submessage.localPath); print('sharing', submessage.localPath);
outstanding = true; outstanding = true;
Window.shareSnapshot(submessage.localPath, submessage.href); Window.shareSnapshot(submessage.localPath, submessage.href || href);
} else { } else {
print('not sharing', submessage.localPath); print('not sharing', submessage.localPath);
} }
@ -157,7 +157,9 @@ function onClicked() {
resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke. resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke.
reticleVisible = Reticle.visible; reticleVisible = Reticle.visible;
Reticle.visible = false; Reticle.visible = false;
Window.snapshotTaken.connect(resetButtons); Window.stillSnapshotTaken.connect(stillSnapshotTaken);
Window.processingGifStarted.connect(processingGifStarted);
Window.processingGifCompleted.connect(processingGifCompleted);
// hide overlays if they are on // hide overlays if they are on
if (resetOverlays) { if (resetOverlays) {
@ -193,25 +195,14 @@ function isDomainOpen(id) {
response.total_entries; response.total_entries;
} }
function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { function stillSnapshotTaken(pathStillSnapshot, notify) {
// If we're not taking an animated snapshot, we have to show the HUD. // show hud
// If we ARE taking an animated snapshot, we've already re-enabled the HUD by this point. Reticle.visible = reticleVisible;
if (pathAnimatedSnapshot === "") { // show overlays if they were on
// show hud if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", true);
Reticle.visible = reticleVisible;
// show overlays if they were on
if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", true);
}
} else {
// Allow the user to click the snapshot HUD button again
if (!buttonConnected) {
button.clicked.connect(onClicked);
buttonConnected = true;
}
} }
Window.snapshotTaken.disconnect(resetButtons); Window.stillSnapshotTaken.disconnect(stillSnapshotTaken);
// A Snapshot Review dialog might be left open indefinitely after taking the picture, // A Snapshot Review dialog might be left open indefinitely after taking the picture,
// during which time the user may have moved. So stash that info in the dialog so that // during which time the user may have moved. So stash that info in the dialog so that
@ -220,12 +211,11 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) {
var confirmShareContents = [ var confirmShareContents = [
{ localPath: pathStillSnapshot, href: href }, { localPath: pathStillSnapshot, href: href },
{ {
containsGif: false,
processingGif: false,
canShare: !!isDomainOpen(domainId), canShare: !!isDomainOpen(domainId),
openFeedAfterShare: shouldOpenFeedAfterShare() openFeedAfterShare: shouldOpenFeedAfterShare()
}]; }];
if (pathAnimatedSnapshot !== "") {
confirmShareContents.unshift({ localPath: pathAnimatedSnapshot, href: href });
}
confirmShare(confirmShareContents); confirmShare(confirmShareContents);
if (clearOverlayWhenMoving) { if (clearOverlayWhenMoving) {
MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
@ -233,15 +223,52 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) {
HMD.openTablet(); HMD.openTablet();
} }
function processingGif() { function processingGifStarted(pathStillSnapshot) {
// show hud Window.processingGifStarted.disconnect(processingGifStarted);
Reticle.visible = reticleVisible;
button.clicked.disconnect(onClicked); button.clicked.disconnect(onClicked);
buttonConnected = false; buttonConnected = false;
// show hud
Reticle.visible = reticleVisible;
// show overlays if they were on // show overlays if they were on
if (resetOverlays) { if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", true); Menu.setIsOptionChecked("Overlays", true);
} }
var confirmShareContents = [
{ localPath: pathStillSnapshot, href: href },
{
containsGif: true,
processingGif: true,
loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'),
canShare: !!isDomainOpen(domainId),
openFeedAfterShare: shouldOpenFeedAfterShare()
}];
confirmShare(confirmShareContents);
if (clearOverlayWhenMoving) {
MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
}
HMD.openTablet();
}
function processingGifCompleted(pathAnimatedSnapshot) {
Window.processingGifCompleted.disconnect(processingGifCompleted);
button.clicked.connect(onClicked);
buttonConnected = true;
var confirmShareContents = [
{ localPath: pathAnimatedSnapshot, href: href },
{
containsGif: true,
processingGif: false,
canShare: !!isDomainOpen(domainId),
openFeedAfterShare: shouldOpenFeedAfterShare()
}];
readyData = confirmShareContents;
tablet.emitScriptEvent(JSON.stringify({
type: "snapshot",
action: readyData
}));
} }
function onTabletScreenChanged(type, url) { function onTabletScreenChanged(type, url) {
@ -265,7 +292,6 @@ function onConnected() {
button.clicked.connect(onClicked); button.clicked.connect(onClicked);
buttonConnected = true; buttonConnected = true;
Window.snapshotShared.connect(snapshotShared); Window.snapshotShared.connect(snapshotShared);
Window.processingGif.connect(processingGif);
tablet.screenChanged.connect(onTabletScreenChanged); tablet.screenChanged.connect(onTabletScreenChanged);
Account.usernameChanged.connect(onConnected); Account.usernameChanged.connect(onConnected);
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
@ -277,7 +303,6 @@ Script.scriptEnding.connect(function () {
tablet.removeButton(button); tablet.removeButton(button);
} }
Window.snapshotShared.disconnect(snapshotShared); Window.snapshotShared.disconnect(snapshotShared);
Window.processingGif.disconnect(processingGif);
tablet.screenChanged.disconnect(onTabletScreenChanged); tablet.screenChanged.disconnect(onTabletScreenChanged);
}); });

View file

@ -118,7 +118,7 @@ function findEntitiesWithTag(tag) {
} }
/** /**
* A controller in made up of parts, and each part can have multiple "layers," * A controller is made up of parts, and each part can have multiple "layers,"
* which are really just different texures. For example, the "trigger" part * which are really just different texures. For example, the "trigger" part
* has "normal" and "highlight" layers. * has "normal" and "highlight" layers.
*/ */