resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2015-11-18 15:34:56 -08:00
commit cb708859fb
52 changed files with 864 additions and 492 deletions

View file

@ -27,10 +27,14 @@
#include <SoundCache.h> #include <SoundCache.h>
#include <UUID.h> #include <UUID.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <WebSocketServerClass.h> #include <WebSocketServerClass.h>
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h #include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include "avatars/ScriptableAvatar.h" #include "avatars/ScriptableAvatar.h"
#include "RecordingScriptingInterface.h"
#include "Agent.h" #include "Agent.h"
@ -56,8 +60,10 @@ Agent::Agent(NLPacket& packet) :
DependencyManager::set<ResourceCacheSharedItems>(); DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCache>();
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
@ -117,7 +123,6 @@ void Agent::handleAudioPacket(QSharedPointer<NLPacket> packet) {
} }
const QString AGENT_LOGGING_NAME = "agent"; const QString AGENT_LOGGING_NAME = "agent";
const int PING_INTERVAL = 1000;
void Agent::run() { void Agent::run() {
ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent); ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent);
@ -139,10 +144,6 @@ void Agent::run() {
NodeType::MessagesMixer NodeType::MessagesMixer
}); });
_pingTimer = new QTimer(this);
connect(_pingTimer, SIGNAL(timeout()), SLOT(sendPingRequests()));
_pingTimer->start(PING_INTERVAL);
// figure out the URL for the script for this agent assignment // figure out the URL for the script for this agent assignment
QUrl scriptURL; QUrl scriptURL;
if (_payload.isEmpty()) { if (_payload.isEmpty()) {
@ -249,6 +250,8 @@ void Agent::setIsAvatar(bool isAvatar) {
} }
if (!_isAvatar) { if (!_isAvatar) {
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(nullptr);
if (_avatarIdentityTimer) { if (_avatarIdentityTimer) {
_avatarIdentityTimer->stop(); _avatarIdentityTimer->stop();
delete _avatarIdentityTimer; delete _avatarIdentityTimer;
@ -266,6 +269,7 @@ void Agent::setIsAvatar(bool isAvatar) {
void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) { void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) {
_avatarData = avatarData; _avatarData = avatarData;
_scriptEngine->registerGlobalObject(objectName, avatarData); _scriptEngine->registerGlobalObject(objectName, avatarData);
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(avatarData);
} }
void Agent::sendAvatarIdentityPacket() { void Agent::sendAvatarIdentityPacket() {
@ -396,11 +400,6 @@ void Agent::aboutToFinish() {
_scriptEngine->stop(); _scriptEngine->stop();
} }
if (_pingTimer) {
_pingTimer->stop();
delete _pingTimer;
}
// our entity tree is going to go away so tell that to the EntityScriptingInterface // our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL); DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
@ -413,21 +412,3 @@ void Agent::aboutToFinish() {
// cleanup the AudioInjectorManager (and any still running injectors) // cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
} }
void Agent::sendPingRequests() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachMatchingNode([](const SharedNodePointer& node)->bool {
switch (node->getType()) {
case NodeType::AvatarMixer:
case NodeType::AudioMixer:
case NodeType::EntityServer:
case NodeType::AssetServer:
return true;
default:
return false;
}
}, [nodeList](const SharedNodePointer& node) {
nodeList->sendPacket(nodeList->constructPingPacket(), *node);
});
}

View file

@ -58,14 +58,12 @@ private slots:
void handleAudioPacket(QSharedPointer<NLPacket> packet); void handleAudioPacket(QSharedPointer<NLPacket> packet);
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode); void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode); void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void sendPingRequests();
void processAgentAvatarAndAudio(float deltaTime); void processAgentAvatarAndAudio(float deltaTime);
private: private:
std::unique_ptr<ScriptEngine> _scriptEngine; std::unique_ptr<ScriptEngine> _scriptEngine;
EntityEditPacketSender _entityEditSender; EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer; EntityTreeHeadlessViewer _entityViewer;
QTimer* _pingTimer;
MixedAudioStream _receivedAudioStream; MixedAudioStream _receivedAudioStream;
float _lastReceivedAudioLoudness; float _lastReceivedAudioLoudness;

View file

@ -198,7 +198,7 @@ void AssignmentClient::sendStatusPacketToACM() {
} }
void AssignmentClient::sendAssignmentRequest() { void AssignmentClient::sendAssignmentRequest() {
if (!_currentAssignment) { if (!_currentAssignment && !_isAssigned) {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -229,8 +229,9 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<NLPacket> pac
// construct the deployed assignment from the packet data // construct the deployed assignment from the packet data
_currentAssignment = AssignmentFactory::unpackAssignment(*packet); _currentAssignment = AssignmentFactory::unpackAssignment(*packet);
if (_currentAssignment) { if (_currentAssignment && !_isAssigned) {
qDebug() << "Received an assignment -" << *_currentAssignment; qDebug() << "Received an assignment -" << *_currentAssignment;
_isAssigned = true;
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -309,12 +310,11 @@ void AssignmentClient::handleAuthenticationRequest() {
} }
void AssignmentClient::assignmentCompleted() { void AssignmentClient::assignmentCompleted() {
// we expect that to be here the previous assignment has completely cleaned up // we expect that to be here the previous assignment has completely cleaned up
assert(_currentAssignment.isNull()); assert(_currentAssignment.isNull());
// reset our current assignment pointer to NULL now that it has been deleted // reset our current assignment pointer to null now that it has been deleted
_currentAssignment = NULL; _currentAssignment = nullptr;
// reset the logging target to the the CHILD_TARGET_NAME // reset the logging target to the the CHILD_TARGET_NAME
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
@ -330,4 +330,6 @@ void AssignmentClient::assignmentCompleted() {
nodeList->setOwnerType(NodeType::Unassigned); nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset(); nodeList->reset();
nodeList->resetNodeInterestSet(); nodeList->resetNodeInterestSet();
_isAssigned = false;
} }

View file

@ -46,6 +46,7 @@ private:
Assignment _requestAssignment; Assignment _requestAssignment;
QPointer<ThreadedAssignment> _currentAssignment; QPointer<ThreadedAssignment> _currentAssignment;
bool _isAssigned { false };
QString _assignmentServerHostname; QString _assignmentServerHostname;
HifiSockAddr _assignmentServerSocket; HifiSockAddr _assignmentServerSocket;
QTimer _requestTimer; // timer for requesting and assignment QTimer _requestTimer; // timer for requesting and assignment

View file

@ -128,30 +128,7 @@ void MessagesMixer::run() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->addNodeTypeToInterestSet(NodeType::Agent);
// wait until we have the domain-server settings, otherwise we bail // The messages-mixer currently does currently have any domain settings. If it did, they would be
DomainHandler& domainHandler = nodeList->getDomainHandler(); // synchronously grabbed here.
qDebug() << "Waiting for domain settings from domain-server.";
// block until we get the settingsRequestComplete signal
QEventLoop loop;
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
domainHandler.requestDomainSettings();
loop.exec();
if (domainHandler.getSettingsObject().isEmpty()) {
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
return;
}
// parse the settings to pull out the values we need
parseDomainServerSettings(domainHandler.getSettingsObject());
}
void MessagesMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
// TODO - if we want options, parse them here...
const QString MESSAGES_MIXER_SETTINGS_KEY = "messages_mixer";
} }

View file

@ -35,8 +35,6 @@ private slots:
void handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode); void handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
private: private:
void parseDomainServerSettings(const QJsonObject& domainSettings);
QHash<QString,QSet<QUuid>> _channelSubscribers; QHash<QString,QSet<QUuid>> _channelSubscribers;
}; };

View file

@ -953,7 +953,6 @@ bool OctreeServer::readConfiguration() {
if (domainHandler.getSettingsObject().isEmpty()) { if (domainHandler.getSettingsObject().isEmpty()) {
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
return false; return false;
} }
@ -1086,12 +1085,16 @@ void OctreeServer::run() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->setOwnerType(getMyNodeType()); nodeList->setOwnerType(getMyNodeType());
// use common init to setup common timers and logging // use common init to setup common timers and logging
commonInit(getMyLoggingServerTargetName(), getMyNodeType()); commonInit(getMyLoggingServerTargetName(), getMyNodeType());
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
// read the configuration from either the payload or the domain server configuration // read the configuration from either the payload or the domain server configuration
if (!readConfiguration()) { if (!readConfiguration()) {
qDebug() << "OctreeServer bailing on run since readConfiguration has failed.";
setFinished(true);
return; // bailing on run, because readConfiguration failed return; // bailing on run, because readConfiguration failed
} }
@ -1100,10 +1103,6 @@ void OctreeServer::run() {
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
#ifndef WIN32 #ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stdout, NULL, _IOLBF, 0);
#endif #endif

View file

@ -25,9 +25,14 @@ set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/headers CACHE TYPE INTERNA
if (WIN32) if (WIN32)
# FIXME need to account for different architectures # FIXME need to account for different architectures
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32) set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win64/openvr_api.lib CACHE TYPE INTERNAL)
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win64)
else()
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL)
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32)
endif()
elseif(APPLE) elseif(APPLE)

View file

@ -66,8 +66,15 @@ if (APPLE)
elseif (WIN32) elseif (WIN32)
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${SOURCE_DIR}/include CACHE PATH "Location of SDL2 include directory") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${SOURCE_DIR}/include CACHE PATH "Location of SDL2 include directory")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${SOURCE_DIR}/lib/x86/SDL2.lib CACHE FILEPATH "Path to SDL2 library")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${SOURCE_DIR}/lib/x64/SDL2.lib CACHE FILEPATH "Path to SDL2 library")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x64 CACHE PATH "Location of SDL2 DLL")
else()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${SOURCE_DIR}/lib/x86/SDL2.lib CACHE FILEPATH "Path to SDL2 library")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL")
endif()
else () else ()
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include/SDL2 CACHE PATH "Location of SDL2 include directory") set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include/SDL2 CACHE PATH "Location of SDL2 include directory")

View file

@ -34,17 +34,26 @@ if (UNIX)
endif () endif ()
if (WIN32) if (WIN32)
# http://www.slproweb.com/products/Win32OpenSSL.html
set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]"
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
)
file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles)
set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win32" "${_programfiles}/OpenSSL-Win64"
"C:/OpenSSL/" "C:/OpenSSL-Win32/" "C:/OpenSSL-Win64/" if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
) # http://www.slproweb.com/products/Win32OpenSSL.html
set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
)
set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win64" "C:/OpenSSL/" "C:/OpenSSL-Win64/")
else()
# http://www.slproweb.com/products/Win32OpenSSL.html
set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]"
)
set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win32" "C:/OpenSSL/" "C:/OpenSSL-Win32/")
endif()
unset(_programfiles) unset(_programfiles)
set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS}) set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS})
else () else ()
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("openssl") hifi_library_search_hints("openssl")

View file

@ -28,6 +28,8 @@ var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value
var TRIGGER_ON_VALUE = 0.4; var TRIGGER_ON_VALUE = 0.4;
var TRIGGER_OFF_VALUE = 0.15; var TRIGGER_OFF_VALUE = 0.15;
var BUMPER_ON_VALUE = 0.5;
// //
// distant manipulation // distant manipulation
// //
@ -45,7 +47,7 @@ var PICK_MAX_DISTANCE = 500; // max length of pick-ray
// near grabbing // near grabbing
// //
var GRAB_RADIUS = 0.3; // if the ray misses but an object is this close, it will still be selected var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable.
var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected
@ -53,6 +55,13 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
//
// equip
//
var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05;
var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position
// //
// other constants // other constants
// //
@ -68,7 +77,7 @@ var ZERO_VEC = {
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
var MSEC_PER_SEC = 1000.0; var MSEC_PER_SEC = 1000.0;
// these control how long an abandoned pointer line will hang around // these control how long an abandoned pointer line or action will hang around
var LIFETIME = 10; var LIFETIME = 10;
var ACTION_TTL = 15; // seconds var ACTION_TTL = 15; // seconds
var ACTION_TTL_REFRESH = 5; var ACTION_TTL_REFRESH = 5;
@ -106,6 +115,12 @@ var STATE_CONTINUE_NEAR_TRIGGER = 7;
var STATE_FAR_TRIGGER = 8; var STATE_FAR_TRIGGER = 8;
var STATE_CONTINUE_FAR_TRIGGER = 9; var STATE_CONTINUE_FAR_TRIGGER = 9;
var STATE_RELEASE = 10; var STATE_RELEASE = 10;
var STATE_EQUIP_SEARCHING = 11;
var STATE_EQUIP = 12
var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down
var STATE_CONTINUE_EQUIP = 14;
var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
var STATE_EQUIP_SPRING = 16;
function stateToName(state) { function stateToName(state) {
@ -132,6 +147,18 @@ function stateToName(state) {
return "continue_far_trigger"; return "continue_far_trigger";
case STATE_RELEASE: case STATE_RELEASE:
return "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"; return "unknown";
@ -182,6 +209,7 @@ function MyController(hand) {
this.pointer = null; // entity-id of line object this.pointer = null; // entity-id of line object
this.triggerValue = 0; // rolling average of trigger value this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0; this.rawTriggerValue = 0;
this.rawBumperValue = 0;
this.offsetPosition = { x: 0.0, y: 0.0, z: 0.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.offsetRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
@ -200,6 +228,9 @@ function MyController(hand) {
case STATE_SEARCHING: case STATE_SEARCHING:
this.search(); this.search();
break; break;
case STATE_EQUIP_SEARCHING:
this.search();
break;
case STATE_DISTANCE_HOLDING: case STATE_DISTANCE_HOLDING:
this.distanceHolding(); this.distanceHolding();
break; break;
@ -207,9 +238,18 @@ function MyController(hand) {
this.continueDistanceHolding(); this.continueDistanceHolding();
break; break;
case STATE_NEAR_GRABBING: case STATE_NEAR_GRABBING:
case STATE_EQUIP:
this.nearGrabbing(); this.nearGrabbing();
break; break;
case STATE_WAITING_FOR_BUMPER_RELEASE:
this.waitingForBumperRelease();
break;
case STATE_EQUIP_SPRING:
this.pullTowardEquipPosition()
break;
case STATE_CONTINUE_NEAR_GRABBING: case STATE_CONTINUE_NEAR_GRABBING:
case STATE_CONTINUE_EQUIP_BD:
case STATE_CONTINUE_EQUIP:
this.continueNearGrabbing(); this.continueNearGrabbing();
break; break;
case STATE_NEAR_TRIGGER: case STATE_NEAR_TRIGGER:
@ -281,10 +321,15 @@ function MyController(hand) {
this.pointer = null; this.pointer = null;
}; };
this.eitherTrigger = function (value) { this.triggerPress = function (value) {
_this.rawTriggerValue = value; _this.rawTriggerValue = value;
}; };
this.bumperPress = function (value) {
_this.rawBumperValue = value;
};
this.updateSmoothedTrigger = function () { this.updateSmoothedTrigger = function () {
var triggerValue = this.rawTriggerValue; var triggerValue = this.rawTriggerValue;
// smooth out trigger value // smooth out trigger value
@ -305,23 +350,37 @@ function MyController(hand) {
return triggerValue > TRIGGER_ON_VALUE; return triggerValue > TRIGGER_ON_VALUE;
}; };
this.bumperSqueezed = function() {
return _this.rawBumperValue > BUMPER_ON_VALUE;
}
this.bumperReleased = function() {
return _this.rawBumperValue < BUMPER_ON_VALUE;
}
this.off = function() { this.off = function() {
if (this.triggerSmoothedSqueezed()) { if (this.triggerSmoothedSqueezed()) {
this.lastPickTime = 0; this.lastPickTime = 0;
this.setState(STATE_SEARCHING); this.setState(STATE_SEARCHING);
return; return;
} }
if (this.bumperSqueezed()) {
this.lastPickTime = 0;
this.setState(STATE_EQUIP_SEARCHING);
return;
}
} }
this.search = function() { this.search = function() {
this.grabbedEntity = null; this.grabbedEntity = null;
//if this hand is the one that's disabled, we don't want to search for anything at all // if this hand is the one that's disabled, we don't want to search for anything at all
if (this.hand === disabledHand) { if (this.hand === disabledHand) {
return; return;
} }
if (this.triggerSmoothedReleased()) { if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
this.setState(STATE_RELEASE); this.setState(STATE_RELEASE);
return; return;
} }
@ -334,8 +393,6 @@ function MyController(hand) {
length: PICK_MAX_DISTANCE length: PICK_MAX_DISTANCE
}; };
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
// don't pick 60x per second. // don't pick 60x per second.
var pickRays = []; var pickRays = [];
var now = Date.now(); var now = Date.now();
@ -398,7 +455,15 @@ function MyController(hand) {
return; return;
} else if (!intersection.properties.locked) { } else if (!intersection.properties.locked) {
this.grabbedEntity = intersection.entityID; this.grabbedEntity = intersection.entityID;
this.setState(STATE_NEAR_GRABBING); if (this.state == STATE_SEARCHING) {
this.setState(STATE_NEAR_GRABBING);
} else { // equipping
if (typeof grabbableData.spatialKey !== 'undefined') {
this.setState(STATE_EQUIP_SPRING);
} else {
this.setState(STATE_EQUIP);
}
}
return; return;
} }
} else if (! entityIsGrabbedByOther(intersection.entityID)) { } else if (! entityIsGrabbedByOther(intersection.entityID)) {
@ -407,8 +472,14 @@ function MyController(hand) {
&& !intersection.properties.locked) { && !intersection.properties.locked) {
// the hand is far from the intersected object. go into distance-holding mode // the hand is far from the intersected object. go into distance-holding mode
this.grabbedEntity = intersection.entityID; this.grabbedEntity = intersection.entityID;
this.setState(STATE_DISTANCE_HOLDING); if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) {
return; // if a distance pick in equip mode hits something with a spatialKey, equip it
this.setState(STATE_EQUIP_SPRING);
return;
} else if (this.state == STATE_SEARCHING) {
this.setState(STATE_DISTANCE_HOLDING);
return;
}
} else if (grabbableData.wantsTrigger) { } else if (grabbableData.wantsTrigger) {
this.grabbedEntity = intersection.entityID; this.grabbedEntity = intersection.entityID;
this.setState(STATE_FAR_TRIGGER); this.setState(STATE_FAR_TRIGGER);
@ -434,6 +505,7 @@ function MyController(hand) {
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS); var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = PICK_MAX_DISTANCE; var minDistance = PICK_MAX_DISTANCE;
var i, props, distance, grabbableData; var i, props, distance, grabbableData;
this.grabbedEntity = null;
for (i = 0; i < nearbyEntities.length; i++) { for (i = 0; i < nearbyEntities.length; i++) {
var grabbableDataForCandidate = var grabbableDataForCandidate =
getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA);
@ -483,16 +555,17 @@ function MyController(hand) {
grabbableData = grabbableDataForCandidate; grabbableData = grabbableDataForCandidate;
} }
} }
if (this.grabbedEntity === null) { if (this.grabbedEntity !== null) {
return; if (grabbableData.wantsTrigger) {
} this.setState(STATE_NEAR_TRIGGER);
if (grabbableData.wantsTrigger) { return;
this.setState(STATE_NEAR_TRIGGER); } else if (!props.locked && props.collisionsWillMove) {
return; this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP)
} else if (!props.locked && props.collisionsWillMove) { return;
this.setState(STATE_NEAR_GRABBING); }
return;
} }
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
}; };
this.distanceHolding = function() { this.distanceHolding = function() {
@ -551,6 +624,16 @@ function MyController(hand) {
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() &&
typeof grabbableData.spatialKey !== 'undefined') {
var saveGrabbedID = this.grabbedEntity;
this.release();
this.setState(STATE_EQUIP);
this.grabbedEntity = saveGrabbedID;
return;
}
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
@ -634,13 +717,12 @@ function MyController(hand) {
return; return;
} }
if (this.triggerSmoothedReleased()) { if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE); this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
return; return;
} }
this.lineOff(); this.lineOff();
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
@ -656,7 +738,8 @@ function MyController(hand) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (grabbableData.spatialKey) { if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
// if an object is "equipped" and has a spatialKey, use it.
if (grabbableData.spatialKey.relativePosition) { if (grabbableData.spatialKey.relativePosition) {
this.offsetPosition = grabbableData.spatialKey.relativePosition; this.offsetPosition = grabbableData.spatialKey.relativePosition;
} }
@ -686,7 +769,13 @@ function MyController(hand) {
this.actionID = null; this.actionID = null;
} else { } else {
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
this.setState(STATE_CONTINUE_NEAR_GRABBING); if (this.state == STATE_NEAR_GRABBING) {
this.setState(STATE_CONTINUE_NEAR_GRABBING);
} else {
// equipping
this.setState(STATE_CONTINUE_EQUIP_BD);
}
if (this.hand === RIGHT_HAND) { if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else { } else {
@ -696,17 +785,30 @@ function MyController(hand) {
} }
this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;; this.currentHandControllerTipPosition =
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
this.currentObjectTime = Date.now(); this.currentObjectTime = Date.now();
}; };
this.continueNearGrabbing = function() { this.continueNearGrabbing = function() {
if (this.triggerSmoothedReleased()) { if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE); this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
return; return;
} }
if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) {
this.setState(STATE_CONTINUE_EQUIP);
return;
}
if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) {
this.setState(STATE_WAITING_FOR_BUMPER_RELEASE);
return;
}
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) {
this.setState(STATE_CONTINUE_EQUIP_BD);
return;
}
// Keep track of the fingertip velocity to impart when we release the object. // Keep track of the fingertip velocity to impart when we release the object.
// Note that the idea of using a constant 'tip' velocity regardless of the // Note that the idea of using a constant 'tip' velocity regardless of the
@ -740,6 +842,66 @@ function MyController(hand) {
} }
}; };
this.waitingForBumperRelease = function() {
if (this.bumperReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
}
};
this.pullTowardEquipPosition = function() {
this.lineOff();
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
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 };
if (grabbableData.spatialKey.relativePosition) {
relativePosition = grabbableData.spatialKey.relativePosition;
}
if (grabbableData.spatialKey.relativeRotation) {
relativeRotation = grabbableData.spatialKey.relativeRotation;
}
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
var targetRotation = Quat.multiply(handRotation, relativeRotation);
var offset = Vec3.multiplyQbyV(targetRotation, relativePosition);
var targetPosition = Vec3.sum(handPosition, offset);
if (typeof this.equipSpringID === 'undefined' ||
this.equipSpringID === null ||
this.equipSpringID === NULL_ACTION_ID) {
this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, {
targetPosition: targetPosition,
linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL
});
if (this.equipSpringID === NULL_ACTION_ID) {
this.equipSpringID = null;
this.setState(STATE_OFF);
return;
}
} else {
Entities.updateAction(this.grabbedEntity, this.equipSpringID, {
targetPosition: targetPosition,
linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL
});
}
if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) {
Entities.deleteAction(this.grabbedEntity, this.equipSpringID);
this.equipSpringID = null;
this.setState(STATE_EQUIP);
}
};
this.nearTrigger = function() { this.nearTrigger = function() {
if (this.triggerSmoothedReleased()) { if (this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE); this.setState(STATE_RELEASE);
@ -919,6 +1081,7 @@ function MyController(hand) {
} }
Entities.editEntity(entityID, whileHeldProperties); Entities.editEntity(entityID, whileHeldProperties);
} }
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
return data; return data;
}; };
@ -948,8 +1111,12 @@ var leftController = new MyController(LEFT_HAND);
var MAPPING_NAME = "com.highfidelity.handControllerGrab"; var MAPPING_NAME = "com.highfidelity.handControllerGrab";
var mapping = Controller.newMapping(MAPPING_NAME); var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from([Controller.Standard.RB, Controller.Standard.RT]).peek().to(rightController.eitherTrigger); mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress);
mapping.from([Controller.Standard.LB, Controller.Standard.LT]).peek().to(leftController.eitherTrigger); mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress);
mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress);
mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress);
Controller.enableMapping(MAPPING_NAME); Controller.enableMapping(MAPPING_NAME);

View file

@ -1617,6 +1617,11 @@ PropertiesTool = function(opts) {
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update();
} }
} else if (data.action == "previewCamera") {
if (selectionManager.hasSelection()) {
Camera.mode = "entity";
Camera.cameraEntity = selectionManager.selections[0];
}
} else if (data.action == "rescaleDimensions") { } else if (data.action == "rescaleDimensions") {
var multiplier = data.percentage / 100; var multiplier = data.percentage / 100;
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {

View file

@ -0,0 +1,21 @@
var rotation = Quat.safeEulerAngles(Camera.getOrientation());
rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0);
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(6, Quat.getFront(rotation)));
var recordAreaEntity = Entities.addEntity({
name: 'recorderEntity',
dimensions: {
x: 10,
y: 10,
z: 10
},
type: 'Box',
position: center,
color: {
red: 255,
green: 255,
blue: 255
},
visible: true,
script: "https://hifi-public.s3.amazonaws.com/sam/record/recordingEntityScript.js",
});

View file

@ -13,9 +13,11 @@
// 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
(function () {
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/utils.js");
(function() {
var insideRecorderArea = false; var insideRecorderArea = false;
var enteredInTime = false; var enteredInTime = false;
var isAvatarRecording = false; var isAvatarRecording = false;
@ -25,51 +27,63 @@
_this = this; _this = this;
return; return;
} }
function update() {
var isRecordingStarted = getEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }).isRecordingStarted;
if (isRecordingStarted && !isAvatarRecording) {
_this.startRecording();
} else if ((!isRecordingStarted && isAvatarRecording) || (isAvatarRecording && !insideRecorderArea)) {
_this.stopRecording();
} else if (!isRecordingStarted && insideRecorderArea && !enteredInTime) {
//if an avatar enters the zone while a recording is started he will be able to participate to the next group recording
enteredInTime = true;
}
};
recordingEntity.prototype = { recordingEntity.prototype = {
update: function(){
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData); preload: function (entityID) {
var isRecordingStarted = userData.recordingKey.isRecordingStarted; print("RECORDING ENTITY PRELOAD");
if(isRecordingStarted && !isAvatarRecording){
_this.startRecording();
}else if((!isRecordingStarted && isAvatarRecording) || (isAvatarRecording && !insideRecorderArea)){
_this.stopRecording();
}else if(!isRecordingStarted && insideRecorderArea && !enteredInTime){
//if an avatar enters the zone while a recording is started he will be able to participate to the next group recording
enteredInTime = true;
}
},
preload: function(entityID) {
this.entityID = entityID; this.entityID = entityID;
Script.update.connect(_this.update);
var entityProperties = Entities.getEntityProperties(_this.entityID);
if (!entityProperties.ignoreForCollisions) {
Entities.editEntity(_this.entityID, { ignoreForCollisions: true });
}
//print(JSON.stringify(entityProperties));
var recordingKey = getEntityCustomData("recordingKey", _this.entityID, undefined);
if (recordingKey === undefined) {
setEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false });
}
Script.update.connect(update);
}, },
enterEntity: function(entityID) { enterEntity: function (entityID) {
print("entering in the recording area"); print("entering in the recording area");
insideRecorderArea = true; insideRecorderArea = true;
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData); var isRecordingStarted = getEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }).isRecordingStarted;
var isRecordingStarted = userData.recordingKey.isRecordingStarted; if (!isRecordingStarted) {
if(!isRecordingStarted){
//i'm in the recording area in time (before the event starts) //i'm in the recording area in time (before the event starts)
enteredInTime = true; enteredInTime = true;
} }
}, },
leaveEntity: function(entityID) { leaveEntity: function (entityID) {
print("leaving the recording area"); print("leaving the recording area");
insideRecorderArea = false; insideRecorderArea = false;
enteredInTime = false; enteredInTime = false;
}, },
startRecording: function(entityID){ startRecording: function (entityID) {
if(enteredInTime && !isAvatarRecording){ if (enteredInTime && !isAvatarRecording) {
print("RECORDING STARTED"); print("RECORDING STARTED");
Recording.startRecording(); Recording.startRecording();
isAvatarRecording = true; isAvatarRecording = true;
} }
}, },
stopRecording: function(entityID){ stopRecording: function (entityID) {
if(isAvatarRecording){ if (isAvatarRecording) {
print("RECORDING ENDED"); print("RECORDING ENDED");
Recording.stopRecording(); Recording.stopRecording();
Recording.loadLastRecording(); Recording.loadLastRecording();
@ -80,12 +94,13 @@
} }
} }
}, },
clean: function(entityID) { unload: function (entityID) {
Script.update.disconnect(_this.update); print("RECORDING ENTITY UNLOAD");
Script.update.disconnect(update);
} }
} }
return new recordingEntity(); return new recordingEntity();
}); });

View file

@ -5,16 +5,15 @@
// Created by Alessandro Signa on 11/12/15. // Created by Alessandro Signa on 11/12/15.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// //
// Run this script to spawn a box (recorder) and drive the start/end of the recording for anyone who is inside the box // Run this script to find the recorder (created by crateRecorder.js) and drive the start/end of the recording for anyone who is inside the box
// //
// 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
var PARAMS_SCRIPT_URL = Script.resolvePath('recordingEntityScript.js');
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
Script.include("../libraries/toolBars.js"); Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.js");
Script.include("../libraries/utils.js"); Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/utils.js");
@ -30,35 +29,25 @@ var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 };
var toolBar = null; var toolBar = null;
var recordIcon; var recordIcon;
var isRecordingEntityFound = false;
var isRecording = false; var isRecording = false;
var recordAreaEntity = Entities.addEntity({ var recordAreaEntity = null;
name: 'recorderEntity', findRecorder();
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({
recordingKey: {
isRecordingStarted: false
}
})
});
function findRecorder() {
foundEntities = Entities.findEntities(MyAvatar.position, 50);
for (var i = 0; i < foundEntities.length; i++) {
var name = Entities.getEntityProperties(foundEntities[i], "name").name;
if (name === "recorderEntity") {
recordAreaEntity = foundEntities[i];
isRecordingEntityFound = true;
print("Found recorder Entity!");
return;
}
}
}
setupToolBar(); setupToolBar();
@ -70,7 +59,7 @@ 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); //put the button in the up-left corner toolBar = new ToolBar(0, 100, ToolBar.HORIZONTAL); //put the button in the up-left corner
toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF);
@ -81,9 +70,8 @@ function setupToolBar() {
width: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT, height: Tool.IMAGE_HEIGHT,
alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON, alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON,
visible: true visible: isRecordingEntityFound,
}, true, isRecording); }, true, isRecording);
} }
function mousePressEvent(event) { function mousePressEvent(event) {
@ -106,8 +94,6 @@ function mousePressEvent(event) {
function cleanup() { function cleanup() {
toolBar.cleanup(); toolBar.cleanup();
Entities.callEntityMethod(recordAreaEntity, 'clean'); //have to call this before deleting to avoid the JSON warnings
Entities.deleteEntity(recordAreaEntity);
} }

View file

@ -0,0 +1,57 @@
//
// securityCamera.js
// examples/example
//
// Created by Thijs Wenker on November 4, 2015
// 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
//
const CAMERA_OFFSET = {x: 0, y: 4, z: -14};
const LOOKAT_START_OFFSET = {x: -10, y: 0, z: 14};
const LOOKAT_END_OFFSET = {x: 10, y: 0, z: 14};
const TINY_VALUE = 0.001;
var lookatTargets = [Vec3.sum(MyAvatar.position, LOOKAT_START_OFFSET), Vec3.sum(MyAvatar.position, LOOKAT_END_OFFSET)];
var currentTarget = 0;
var forward = true;
var oldCameraMode = Camera.mode;
var cameraLookAt = function(cameraPos, lookAtPos) {
var lookAtRaw = Quat.lookAt(cameraPos, lookAtPos, Vec3.UP);
lookAtRaw.w = -lookAtRaw.w;
return lookAtRaw;
};
cameraEntity = Entities.addEntity({
type: "Box",
visible: false,
position: Vec3.sum(MyAvatar.position, CAMERA_OFFSET)
});
Camera.mode = "entity";
Camera.cameraEntity = cameraEntity;
Script.update.connect(function(deltaTime) {
var cameraProperties = Entities.getEntityProperties(cameraEntity, ["position", "rotation"]);
var targetOrientation = cameraLookAt(cameraProperties.position, lookatTargets[currentTarget]);
if (Math.abs(targetOrientation.x - cameraProperties.rotation.x) < TINY_VALUE &&
Math.abs(targetOrientation.y - cameraProperties.rotation.y) < TINY_VALUE &&
Math.abs(targetOrientation.z - cameraProperties.rotation.z) < TINY_VALUE &&
Math.abs(targetOrientation.w - cameraProperties.rotation.w) < TINY_VALUE) {
currentTarget = (currentTarget + 1) % lookatTargets.length;
return;
}
Entities.editEntity(cameraEntity, {rotation: Quat.mix(cameraProperties.rotation, targetOrientation, deltaTime / 3)});
});
Script.scriptEnding.connect(function() {
Entities.deleteEntity(cameraEntity);
Camera.mode = oldCameraMode;
Camera.cameraEntity = null;
});

View file

@ -382,7 +382,7 @@
var elHyperlinkHref = document.getElementById("property-hyperlink-href"); var elHyperlinkHref = document.getElementById("property-hyperlink-href");
var elHyperlinkDescription = document.getElementById("property-hyperlink-description"); var elHyperlinkDescription = document.getElementById("property-hyperlink-description");
var elPreviewCameraButton = document.getElementById("preview-camera-button");
if (window.EventBridge !== undefined) { if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) { EventBridge.scriptEventReceived.connect(function(data) {
@ -931,6 +931,12 @@
action: "centerAtmosphereToZone", action: "centerAtmosphereToZone",
})); }));
}); });
elPreviewCameraButton.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "previewCamera"
}));
});
window.onblur = function() { window.onblur = function() {
// Fake a change event // Fake a change event
@ -1032,7 +1038,7 @@
<div class="section-header"> <div class="section-header">
<label>Spacial Properites</label> <label>Spacial Properties</label>
</div> </div>
<div class="property"> <div class="property">
@ -1044,6 +1050,7 @@
<div> <div>
<input type="button" id="move-selection-to-grid" value="Selection to Grid"> <input type="button" id="move-selection-to-grid" value="Selection to Grid">
<input type="button" id="move-all-to-grid" value="All to Grid"> <input type="button" id="move-all-to-grid" value="All to Grid">
<input type="button" id="preview-camera-button" value="Preview Camera">
</div> </div>
</div> </div>
</div> </div>

View file

@ -204,7 +204,7 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Collisions Will Move:", type: "checkbox", value: properties.collisionsWillMove }); array.push({ label: "Collisions Will Move:", type: "checkbox", value: properties.collisionsWillMove });
index++; index++;
array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL }); array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL });
index++; index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) }); array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++; index++;
@ -260,6 +260,10 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Cutoff (in degrees):", value: properties.cutoff }); array.push({ label: "Cutoff (in degrees):", value: properties.cutoff });
index++; index++;
} }
array.push({ label: "", type: "inlineButton", buttonLabel: "Preview Camera", name: "previewCamera" });
index++;
array.push({ button: "Cancel" }); array.push({ button: "Cancel" });
index++; index++;
@ -268,6 +272,11 @@ EntityPropertyDialogBox = (function () {
}; };
Window.inlineButtonClicked.connect(function (name) { Window.inlineButtonClicked.connect(function (name) {
if (name == "previewCamera") {
Camera.mode = "entity";
Camera.cameraEntity = propertiesForEditedEntity.id;
}
if (name == "resetDimensions") { if (name == "resetDimensions") {
Window.reloadNonBlockingForm([ Window.reloadNonBlockingForm([
{ value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX }, { value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX },

View file

@ -11,6 +11,12 @@ vec3toStr = function(v, digits) {
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }"; return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
} }
quatToStr = function(q, digits) {
if (!digits) { digits = 3; }
return "{ " + q.w.toFixed(digits) + ", " + q.x.toFixed(digits) + ", " +
q.y.toFixed(digits) + ", " + q.z.toFixed(digits)+ " }";
}
vec3equal = function(v0, v1) { vec3equal = function(v0, v1) {
return (v0.x == v1.x) && (v0.y == v1.y) && (v0.z == v1.z); return (v0.x == v1.x) && (v0.y == v1.y) && (v0.z == v1.z);
} }
@ -51,7 +57,7 @@ addLine = function(origin, vector, color) {
// FIXME fetch from a subkey of user data to support non-destructive modifications // FIXME fetch from a subkey of user data to support non-destructive modifications
setEntityUserData = function(id, data) { setEntityUserData = function(id, data) {
var json = JSON.stringify(data) var json = JSON.stringify(data)
Entities.editEntity(id, { userData: json }); Entities.editEntity(id, { userData: json });
} }
// FIXME do non-destructive modification of the existing user data // FIXME do non-destructive modification of the existing user data
@ -60,7 +66,7 @@ getEntityUserData = function(id) {
var properties = Entities.getEntityProperties(id, "userData"); var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) { if (properties.userData) {
try { try {
results = JSON.parse(properties.userData); results = JSON.parse(properties.userData);
} catch(err) { } catch(err) {
logDebug(err); logDebug(err);
logDebug(properties.userData); logDebug(properties.userData);

View file

@ -16,7 +16,7 @@
Script.include("../../libraries/utils.js"); Script.include("../../libraries/utils.js");
var BUBBLE_MODEL = "http://hifi-public.s3.amazonaws.com/models/bubblewand/bubble.fbx"; var BUBBLE_MODEL = "http://hifi-content.s3.amazonaws.com/james/bubblewand/bubble.fbx";
var BUBBLE_INITIAL_DIMENSIONS = { var BUBBLE_INITIAL_DIMENSIONS = {
x: 0.01, x: 0.01,

View file

@ -88,6 +88,7 @@
#include <RenderDeferredTask.h> #include <RenderDeferredTask.h>
#include <ResourceCache.h> #include <ResourceCache.h>
#include <SceneScriptingInterface.h> #include <SceneScriptingInterface.h>
#include <RecordingScriptingInterface.h>
#include <ScriptCache.h> #include <ScriptCache.h>
#include <SoundCache.h> #include <SoundCache.h>
#include <TextureCache.h> #include <TextureCache.h>
@ -128,7 +129,6 @@
#include "scripting/LocationScriptingInterface.h" #include "scripting/LocationScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h" #include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h"
#include "scripting/RecordingScriptingInterface.h"
#include "scripting/WebWindowClass.h" #include "scripting/WebWindowClass.h"
#include "scripting/WindowScriptingInterface.h" #include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h"
@ -745,6 +745,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
applicationUpdater->checkForUpdate(); applicationUpdater->checkForUpdate();
// Assign MyAvatar to th eRecording Singleton
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(getMyAvatar());
// Now that menu is initalized we can sync myAvatar with it's state. // Now that menu is initalized we can sync myAvatar with it's state.
getMyAvatar()->updateMotionBehaviorFromMenu(); getMyAvatar()->updateMotionBehaviorFromMenu();
@ -839,6 +843,7 @@ void Application::cleanupBeforeQuit() {
#ifdef HAVE_IVIEWHMD #ifdef HAVE_IVIEWHMD
DependencyManager::get<EyeTracker>()->setEnabled(false, true); DependencyManager::get<EyeTracker>()->setEnabled(false, true);
#endif #endif
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(nullptr);
AnimDebugDraw::getInstance().shutdown(); AnimDebugDraw::getInstance().shutdown();
@ -1005,10 +1010,6 @@ void Application::initializeGL() {
connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
_entityEditSender.initialize(_enableProcessOctreeThread); _entityEditSender.initialize(_enableProcessOctreeThread);
// call our timer function every second
connect(&pingTimer, &QTimer::timeout, this, &Application::ping);
pingTimer.start(1000);
_idleLoopStdev.reset(); _idleLoopStdev.reset();
// update before the first render // update before the first render
@ -1232,6 +1233,19 @@ void Application::paintGL() {
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
} }
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
} else if (_myCamera.getMode() == CAMERA_MODE_ENTITY) {
EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer();
if (cameraEntity != nullptr) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
_myCamera.setRotation(cameraEntity->getRotation() * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(cameraEntity->getPosition() + (hmdRotation * hmdOffset));
} else {
_myCamera.setRotation(cameraEntity->getRotation());
_myCamera.setPosition(cameraEntity->getPosition());
}
}
} }
// Update camera position // Update camera position
if (!isHMDMode()) { if (!isHMDMode()) {
@ -2136,13 +2150,6 @@ bool Application::acceptSnapshot(const QString& urlString) {
return true; return true;
} }
// Every second, send a ping, if menu item is checked.
void Application::ping() {
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
DependencyManager::get<NodeList>()->sendPingPackets();
}
}
void Application::idle(uint64_t now) { void Application::idle(uint64_t now) {
if (_aboutToQuit) { if (_aboutToQuit) {
return; // bail early, nothing to do here. return; // bail early, nothing to do here.
@ -2682,8 +2689,8 @@ void Application::cycleCamera() {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false); menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true); menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
} else if (menu->isOptionChecked(MenuOption::IndependentMode)) { } else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) {
// do nothing if in independe mode // do nothing if in independent or camera entity modes
return; return;
} }
cameraMenuChanged(); // handle the menu change cameraMenuChanged(); // handle the menu change
@ -2710,6 +2717,10 @@ void Application::cameraMenuChanged() {
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
_myCamera.setMode(CAMERA_MODE_INDEPENDENT); _myCamera.setMode(CAMERA_MODE_INDEPENDENT);
} }
} else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) {
if (_myCamera.getMode() != CAMERA_MODE_ENTITY) {
_myCamera.setMode(CAMERA_MODE_ENTITY);
}
} }
} }
@ -3995,7 +4006,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
RayToOverlayIntersectionResultFromScriptValue); RayToOverlayIntersectionResultFromScriptValue);
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data()); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,

View file

@ -306,7 +306,6 @@ public slots:
private slots: private slots:
void clearDomainOctreeDetails(); void clearDomainOctreeDetails();
void ping();
void idle(uint64_t now); void idle(uint64_t now);
void aboutToQuit(); void aboutToQuit();

View file

@ -28,6 +28,8 @@ CameraMode stringToMode(const QString& mode) {
return CAMERA_MODE_MIRROR; return CAMERA_MODE_MIRROR;
} else if (mode == "independent") { } else if (mode == "independent") {
return CAMERA_MODE_INDEPENDENT; return CAMERA_MODE_INDEPENDENT;
} else if (mode == "entity") {
return CAMERA_MODE_ENTITY;
} }
return CAMERA_MODE_NULL; return CAMERA_MODE_NULL;
} }
@ -41,6 +43,8 @@ QString modeToString(CameraMode mode) {
return "mirror"; return "mirror";
} else if (mode == CAMERA_MODE_INDEPENDENT) { } else if (mode == CAMERA_MODE_INDEPENDENT) {
return "independent"; return "independent";
} else if (mode == CAMERA_MODE_ENTITY) {
return "entity";
} }
return "unknown"; return "unknown";
} }
@ -94,6 +98,17 @@ void Camera::setMode(CameraMode mode) {
emit modeUpdated(modeToString(mode)); emit modeUpdated(modeToString(mode));
} }
QUuid Camera::getCameraEntity() const {
if (_cameraEntity != nullptr) {
return _cameraEntity->getID();
}
return QUuid();
};
void Camera::setCameraEntity(QUuid entityID) {
_cameraEntity = qApp->getEntities()->getTree()->findEntityByID(entityID);
}
void Camera::setProjection(const glm::mat4& projection) { void Camera::setProjection(const glm::mat4& projection) {
_projection = projection; _projection = projection;
} }
@ -118,6 +133,9 @@ void Camera::setModeString(const QString& mode) {
case CAMERA_MODE_INDEPENDENT: case CAMERA_MODE_INDEPENDENT:
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true); Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
break; break;
case CAMERA_MODE_ENTITY:
Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
break;
default: default:
break; break;
} }

View file

@ -24,6 +24,7 @@ enum CameraMode
CAMERA_MODE_FIRST_PERSON, CAMERA_MODE_FIRST_PERSON,
CAMERA_MODE_MIRROR, CAMERA_MODE_MIRROR,
CAMERA_MODE_INDEPENDENT, CAMERA_MODE_INDEPENDENT,
CAMERA_MODE_ENTITY,
NUM_CAMERA_MODES NUM_CAMERA_MODES
}; };
@ -36,6 +37,7 @@ class Camera : public QObject {
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
Q_PROPERTY(QString mode READ getModeString WRITE setModeString) Q_PROPERTY(QString mode READ getModeString WRITE setModeString)
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)
public: public:
Camera(); Camera();
@ -49,6 +51,8 @@ public:
void loadViewFrustum(ViewFrustum& frustum) const; void loadViewFrustum(ViewFrustum& frustum) const;
ViewFrustum toViewFrustum() const; ViewFrustum toViewFrustum() const;
EntityItemPointer getCameraEntityPointer() const { return _cameraEntity; }
public slots: public slots:
QString getModeString() const; QString getModeString() const;
void setModeString(const QString& mode); void setModeString(const QString& mode);
@ -68,6 +72,9 @@ public slots:
const glm::mat4& getProjection() const { return _projection; } const glm::mat4& getProjection() const { return _projection; }
void setProjection(const glm::mat4& projection); void setProjection(const glm::mat4& projection);
QUuid getCameraEntity() const;
void setCameraEntity(QUuid entityID);
PickRay computePickRay(float x, float y); PickRay computePickRay(float x, float y);
// These only work on independent cameras // These only work on independent cameras
@ -97,6 +104,7 @@ private:
glm::quat _rotation; glm::quat _rotation;
bool _isKeepLookingAt{ false }; bool _isKeepLookingAt{ false };
glm::vec3 _lookingAt; glm::vec3 _lookingAt;
EntityItemPointer _cameraEntity;
}; };
#endif // hifi_Camera_h #endif // hifi_Camera_h

View file

@ -268,6 +268,9 @@ Menu::Menu() {
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
MenuOption::IndependentMode, 0, MenuOption::IndependentMode, 0,
false, qApp, SLOT(cameraMenuChanged()))); false, qApp, SLOT(cameraMenuChanged())));
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
MenuOption::CameraEntityMode, 0,
false, qApp, SLOT(cameraMenuChanged())));
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu,
MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, MenuOption::FullscreenMirror, 0, // QML Qt::Key_H,
false, qApp, SLOT(cameraMenuChanged()))); false, qApp, SLOT(cameraMenuChanged())));
@ -502,7 +505,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);
addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, qApp, SLOT(runTests())); addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, qApp, SLOT(runTests()));
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings);

View file

@ -155,6 +155,7 @@ namespace MenuOption {
const QString Bookmarks = "Bookmarks"; const QString Bookmarks = "Bookmarks";
const QString CachesSize = "RAM Caches Size"; const QString CachesSize = "RAM Caches Size";
const QString CalibrateCamera = "Calibrate Camera"; const QString CalibrateCamera = "Calibrate Camera";
const QString CameraEntityMode = "Entity Mode";
const QString CenterPlayerInView = "Center Player In View"; const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat..."; const QString Chat = "Chat...";
const QString Collisions = "Collisions"; const QString Collisions = "Collisions";
@ -280,7 +281,6 @@ namespace MenuOption {
const QString Stats = "Stats"; const QString Stats = "Stats";
const QString StopAllScripts = "Stop All Scripts"; const QString StopAllScripts = "Stop All Scripts";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString TestPing = "Test Ping";
const QString ThirdPerson = "Third Person"; const QString ThirdPerson = "Third Person";
const QString ThreePointCalibration = "3 Point Calibration"; const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp

View file

@ -10,7 +10,6 @@
// //
#include "QVariantGLM.h" #include "QVariantGLM.h"
#include "avatar/MyAvatar.h"
#include "avatar/AvatarManager.h" #include "avatar/AvatarManager.h"
#include "AvatarActionHold.h" #include "AvatarActionHold.h"
@ -22,8 +21,7 @@ AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntit
_relativePosition(glm::vec3(0.0f)), _relativePosition(glm::vec3(0.0f)),
_relativeRotation(glm::quat()), _relativeRotation(glm::quat()),
_hand("right"), _hand("right"),
_holderID(QUuid()) _holderID(QUuid()) {
{
_type = ACTION_TYPE_HOLD; _type = ACTION_TYPE_HOLD;
#if WANT_DEBUG #if WANT_DEBUG
qDebug() << "AvatarActionHold::AvatarActionHold"; qDebug() << "AvatarActionHold::AvatarActionHold";
@ -36,13 +34,10 @@ AvatarActionHold::~AvatarActionHold() {
#endif #endif
} }
void AvatarActionHold::updateActionWorker(float deltaTimeStep) { std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
bool gotLock = false;
glm::quat rotation;
glm::vec3 position;
std::shared_ptr<Avatar> holdingAvatar = nullptr; std::shared_ptr<Avatar> holdingAvatar = nullptr;
gotLock = withTryReadLock([&]{ withTryReadLock([&]{
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>(); QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID); AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID);
holdingAvatar = std::static_pointer_cast<Avatar>(holdingAvatarData); holdingAvatar = std::static_pointer_cast<Avatar>(holdingAvatarData);
@ -65,22 +60,53 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
} }
}); });
if (holdingAvatar) { return holdingAvatar;
}
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
glm::quat rotation;
glm::vec3 position;
bool valid = false;
int holdCount = 0;
auto ownerEntity = _ownerEntity.lock();
if (!ownerEntity) {
return;
}
QList<EntityActionPointer> holdActions = ownerEntity->getActionsOfType(ACTION_TYPE_HOLD);
foreach (EntityActionPointer action, holdActions) {
std::shared_ptr<AvatarActionHold> holdAction = std::static_pointer_cast<AvatarActionHold>(action);
glm::quat rotationForAction;
glm::vec3 positionForAction;
std::shared_ptr<Avatar> holdingAvatar = holdAction->getTarget(rotationForAction, positionForAction);
if (holdingAvatar) {
holdCount ++;
if (holdAction.get() == this) {
// only use the rotation for this action
valid = true;
rotation = rotationForAction;
}
position += positionForAction;
}
}
if (valid && holdCount > 0) {
position /= holdCount;
bool gotLock = withTryWriteLock([&]{
_positionalTarget = position;
_rotationalTarget = rotation;
_positionalTargetSet = true;
_rotationalTargetSet = true;
_active = true;
});
if (gotLock) { if (gotLock) {
gotLock = withTryWriteLock([&]{ if (_kinematic) {
_positionalTarget = position; doKinematicUpdate(deltaTimeStep);
_rotationalTarget = rotation; } else {
_positionalTargetSet = true; activateBody();
_rotationalTargetSet = true; ObjectActionSpring::updateActionWorker(deltaTimeStep);
_active = true;
});
if (gotLock) {
if (_kinematic) {
doKinematicUpdate(deltaTimeStep);
} else {
activateBody();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
} }
} }
} }
@ -109,7 +135,8 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
if (_previousSet) { if (_previousSet) {
// smooth velocity over 2 frames // smooth velocity over 2 frames
glm::vec3 positionalDelta = _positionalTarget - _previousPositionalTarget; glm::vec3 positionalDelta = _positionalTarget - _previousPositionalTarget;
glm::vec3 positionalVelocity = (positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep); glm::vec3 positionalVelocity =
(positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep);
rigidBody->setLinearVelocity(glmToBullet(positionalVelocity)); rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
_previousPositionalDelta = positionalDelta; _previousPositionalDelta = positionalDelta;
_previousDeltaTimeStep = deltaTimeStep; _previousDeltaTimeStep = deltaTimeStep;

View file

@ -17,6 +17,9 @@
#include <EntityItem.h> #include <EntityItem.h>
#include <ObjectActionSpring.h> #include <ObjectActionSpring.h>
#include "avatar/MyAvatar.h"
class AvatarActionHold : public ObjectActionSpring { class AvatarActionHold : public ObjectActionSpring {
public: public:
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity); AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
@ -32,6 +35,8 @@ public:
virtual bool shouldSuppressLocationEdits() { return _active && !_ownerEntity.expired(); } virtual bool shouldSuppressLocationEdits() { return _active && !_ownerEntity.expired(); }
std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position);
private: private:
static const uint16_t holdVersion; static const uint16_t holdVersion;

View file

@ -625,19 +625,15 @@ void SkeletonModel::computeBoundingShape() {
totalExtents.addPoint(glm::vec3(0.0f)); totalExtents.addPoint(glm::vec3(0.0f));
int numStates = _rig->getJointStateCount(); int numStates = _rig->getJointStateCount();
for (int i = 0; i < numStates; i++) { for (int i = 0; i < numStates; i++) {
const JointState& state = _rig->getJointState(i);
const glm::mat4& jointTransform = state.getTransform();
float scale = extractUniformScale(jointTransform);
// Each joint contributes a capsule defined by FBXJoint.shapeInfo. // Each joint contributes a capsule defined by FBXJoint.shapeInfo.
// For totalExtents we use the capsule endpoints expanded by the radius. // For totalExtents we use the capsule endpoints expanded by the radius.
const JointState& state = _rig->getJointState(i);
const glm::mat4& jointTransform = state.getTransform();
const FBXJointShapeInfo& shapeInfo = geometry.joints.at(i).shapeInfo; const FBXJointShapeInfo& shapeInfo = geometry.joints.at(i).shapeInfo;
for (int j = 0; j < shapeInfo.points.size(); ++j) { if (shapeInfo.points.size() > 0) {
glm::vec3 transformedPoint = extractTranslation(jointTransform * glm::translate(shapeInfo.points[j])); for (int j = 0; j < shapeInfo.points.size(); ++j) {
vec3 radius(scale * shapeInfo.radius); totalExtents.addPoint(extractTranslation(jointTransform * glm::translate(shapeInfo.points[j])));
totalExtents.addPoint(transformedPoint + radius); }
totalExtents.addPoint(transformedPoint - radius);
} }
// HACK so that default legless robot doesn't knuckle-drag // HACK so that default legless robot doesn't knuckle-drag
if (shapeInfo.points.size() == 0 && (state.getName() == "LeftFoot" || state.getName() == "RightFoot")) { if (shapeInfo.points.size() == 0 && (state.getName() == "LeftFoot" || state.getName() == "RightFoot")) {
@ -668,7 +664,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
// draw a blue sphere at the capsule top point // draw a blue sphere at the capsule top point
glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f);
deferredLighting->renderSolidSphereInstance(batch, deferredLighting->renderSolidSphereInstance(batch,
Transform().setTranslation(topPoint).postScale(_boundingCapsuleRadius), Transform().setTranslation(topPoint).postScale(_boundingCapsuleRadius),
glm::vec4(0.6f, 0.6f, 0.8f, alpha)); glm::vec4(0.6f, 0.6f, 0.8f, alpha));
@ -676,7 +672,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f); glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f);
glm::vec3 axis = topPoint - bottomPoint; glm::vec3 axis = topPoint - bottomPoint;
deferredLighting->renderSolidSphereInstance(batch, deferredLighting->renderSolidSphereInstance(batch,
Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius), Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius),
glm::vec4(0.8f, 0.8f, 0.6f, alpha)); glm::vec4(0.8f, 0.8f, 0.6f, alpha));

View file

@ -82,7 +82,7 @@ public:
bool getNeckPosition(glm::vec3& neckPosition) const; bool getNeckPosition(glm::vec3& neckPosition) const;
bool getLocalNeckPosition(glm::vec3& neckPosition) const; bool getLocalNeckPosition(glm::vec3& neckPosition) const;
/// Returns the rotation of the neck joint's parent from default orientation /// Returns the rotation of the neck joint's parent from default orientation
/// \return whether or not the neck was found /// \return whether or not the neck was found
bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const; bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const;

View file

@ -127,36 +127,30 @@ void Stats::updateStats(bool force) {
STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f); STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f);
// Second column: ping // Second column: ping
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer); STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1); STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1);
STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1); STAT_UPDATE(assetPing, assetServerNode ? assetServerNode->getPingMs() : -1);
STAT_UPDATE(assetPing, assetServerNode ? assetServerNode->getPingMs() : -1);
//// Now handle entity servers, since there could be more than one, we average their ping times
int totalPingOctree = 0;
int octreeServerCount = 0;
int pingOctreeMax = 0;
nodeList->eachNode([&](const SharedNodePointer& node) {
// TODO: this should also support entities
if (node->getType() == NodeType::EntityServer) {
totalPingOctree += node->getPingMs();
octreeServerCount++;
if (pingOctreeMax < node->getPingMs()) {
pingOctreeMax = node->getPingMs();
}
}
});
// update the entities ping with the average for all connected entity servers
STAT_UPDATE(entitiesPing, octreeServerCount ? totalPingOctree / octreeServerCount : -1);
} else {
// -2 causes the QML to hide the ping column
STAT_UPDATE(audioPing, -2);
}
//// Now handle entity servers, since there could be more than one, we average their ping times
int totalPingOctree = 0;
int octreeServerCount = 0;
int pingOctreeMax = 0;
nodeList->eachNode([&](const SharedNodePointer& node) {
// TODO: this should also support entities
if (node->getType() == NodeType::EntityServer) {
totalPingOctree += node->getPingMs();
octreeServerCount++;
if (pingOctreeMax < node->getPingMs()) {
pingOctreeMax = node->getPingMs();
}
}
});
// update the entities ping with the average for all connected entity servers
STAT_UPDATE(entitiesPing, octreeServerCount ? totalPingOctree / octreeServerCount : -1);
// Third column, avatar stats // Third column, avatar stats
MyAvatar* myAvatar = avatarManager->getMyAvatar(); MyAvatar* myAvatar = avatarManager->getMyAvatar();

View file

@ -743,19 +743,9 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
} }
void AudioClient::handleAudioInput() { void AudioClient::handleAudioInput() {
if (!_audioPacket) {
// we don't have an audioPacket yet - set that up now
_audioPacket = NLPacket::create(PacketType::MicrophoneAudioNoEcho);
}
const float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(); const float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio();
const int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio); const int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio);
const auto inputAudioSamples = std::unique_ptr<int16_t[]>(new int16_t[inputSamplesRequired]); const auto inputAudioSamples = std::unique_ptr<int16_t[]>(new int16_t[inputSamplesRequired]);
static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
int16_t* const networkAudioSamples = (int16_t*)(_audioPacket->getPayload() + leadingBytes);
QByteArray inputByteArray = _inputDevice->readAll(); QByteArray inputByteArray = _inputDevice->readAll();
// Add audio source injection if enabled // Add audio source injection if enabled
@ -784,30 +774,30 @@ void AudioClient::handleAudioInput() {
float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC)); float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
_stats.updateInputMsecsRead(audioInputMsecsRead); _stats.updateInputMsecsRead(audioInputMsecsRead);
while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) { const int numNetworkBytes = _isStereoInput
? AudioConstants::NETWORK_FRAME_BYTES_STEREO
: AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
const int numNetworkSamples = _isStereoInput
? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
const int numNetworkBytes = _isStereoInput static int16_t networkAudioSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
? AudioConstants::NETWORK_FRAME_BYTES_STEREO
: AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) {
const int numNetworkSamples = _isStereoInput
? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
if (!_muted) { if (!_muted) {
// zero out the monoAudioSamples array and the locally injected audio
memset(networkAudioSamples, 0, numNetworkBytes);
// Increment the time since the last clip // Increment the time since the last clip
if (_timeSinceLastClip >= 0.0f) { if (_timeSinceLastClip >= 0.0f) {
_timeSinceLastClip += (float) numNetworkSamples / (float) AudioConstants::SAMPLE_RATE; _timeSinceLastClip += (float)numNetworkSamples / (float)AudioConstants::SAMPLE_RATE;
} }
_inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired);
possibleResampling(_inputToNetworkResampler, possibleResampling(_inputToNetworkResampler,
inputAudioSamples.get(), networkAudioSamples, inputAudioSamples.get(), networkAudioSamples,
inputSamplesRequired, numNetworkSamples, inputSamplesRequired, numNetworkSamples,
_inputFormat, _desiredInputFormat); _inputFormat, _desiredInputFormat);
// Remove DC offset // Remove DC offset
if (!_isStereoInput && !_audioSourceInjectEnabled) { if (!_isStereoInput && !_audioSourceInjectEnabled) {
@ -829,7 +819,7 @@ void AudioClient::handleAudioInput() {
for (int i = 0; i < numNetworkSamples; i++) { for (int i = 0; i < numNetworkSamples; i++) {
int thisSample = std::abs(networkAudioSamples[i]); int thisSample = std::abs(networkAudioSamples[i]);
loudness += (float) thisSample; loudness += (float)thisSample;
if (thisSample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)) { if (thisSample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)) {
_timeSinceLastClip = 0.0f; _timeSinceLastClip = 0.0f;
@ -839,7 +829,7 @@ void AudioClient::handleAudioInput() {
_lastInputLoudness = fabs(loudness / numNetworkSamples); _lastInputLoudness = fabs(loudness / numNetworkSamples);
} }
emit inputReceived({reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes}); emit inputReceived({ reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes });
} else { } else {
// our input loudness is 0, since we're muted // our input loudness is 0, since we're muted
@ -849,14 +839,38 @@ void AudioClient::handleAudioInput() {
_inputRingBuffer.shiftReadPosition(inputSamplesRequired); _inputRingBuffer.shiftReadPosition(inputSamplesRequired);
} }
auto nodeList = DependencyManager::get<NodeList>(); emitAudioPacket(networkAudioSamples);
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); }
}
if (audioMixer && audioMixer->getActiveSocket()) { void AudioClient::emitAudioPacket(const int16_t* audioData, PacketType packetType) {
glm::vec3 headPosition = _positionGetter(); static std::mutex _mutex;
glm::quat headOrientation = _orientationGetter(); using Locker = std::unique_lock<std::mutex>;
quint8 isStereo = _isStereoInput ? 1 : 0;
// FIXME recorded audio isn't guaranteed to have the same stereo state
// as the current system
const int numNetworkBytes = _isStereoInput
? AudioConstants::NETWORK_FRAME_BYTES_STEREO
: AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
const int numNetworkSamples = _isStereoInput
? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && audioMixer->getActiveSocket()) {
Locker lock(_mutex);
if (!_audioPacket) {
// we don't have an audioPacket yet - set that up now
_audioPacket = NLPacket::create(PacketType::MicrophoneAudioWithEcho);
}
glm::vec3 headPosition = _positionGetter();
glm::quat headOrientation = _orientationGetter();
quint8 isStereo = _isStereoInput ? 1 : 0;
if (packetType == PacketType::Unknown) {
if (_lastInputLoudness == 0) { if (_lastInputLoudness == 0) {
_audioPacket->setType(PacketType::SilentAudioFrame); _audioPacket->setType(PacketType::SilentAudioFrame);
} else { } else {
@ -866,70 +880,52 @@ void AudioClient::handleAudioInput() {
_audioPacket->setType(PacketType::MicrophoneAudioNoEcho); _audioPacket->setType(PacketType::MicrophoneAudioNoEcho);
} }
} }
} else {
// reset the audio packet so we can start writing _audioPacket->setType(packetType);
_audioPacket->reset();
// write sequence number
_audioPacket->writePrimitive(_outgoingAvatarAudioSequenceNumber);
if (_audioPacket->getType() == PacketType::SilentAudioFrame) {
// pack num silent samples
quint16 numSilentSamples = numNetworkSamples;
_audioPacket->writePrimitive(numSilentSamples);
} else {
// set the mono/stereo byte
_audioPacket->writePrimitive(isStereo);
}
// pack the three float positions
_audioPacket->writePrimitive(headPosition);
// pack the orientation
_audioPacket->writePrimitive(headOrientation);
if (_audioPacket->getType() != PacketType::SilentAudioFrame) {
// audio samples have already been packed (written to networkAudioSamples)
_audioPacket->setPayloadSize(_audioPacket->getPayloadSize() + numNetworkBytes);
}
_stats.sentPacket();
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
nodeList->sendUnreliablePacket(*_audioPacket, *audioMixer);
_outgoingAvatarAudioSequenceNumber++;
} }
// reset the audio packet so we can start writing
_audioPacket->reset();
// write sequence number
_audioPacket->writePrimitive(_outgoingAvatarAudioSequenceNumber);
if (_audioPacket->getType() == PacketType::SilentAudioFrame) {
// pack num silent samples
quint16 numSilentSamples = numNetworkSamples;
_audioPacket->writePrimitive(numSilentSamples);
} else {
// set the mono/stereo byte
_audioPacket->writePrimitive(isStereo);
}
// pack the three float positions
_audioPacket->writePrimitive(headPosition);
// pack the orientation
_audioPacket->writePrimitive(headOrientation);
if (_audioPacket->getType() != PacketType::SilentAudioFrame) {
// audio samples have already been packed (written to networkAudioSamples)
_audioPacket->setPayloadSize(_audioPacket->getPayloadSize() + numNetworkBytes);
}
static const int leadingBytes = sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
int16_t* const networkAudioSamples = (int16_t*)(_audioPacket->getPayload() + leadingBytes);
memcpy(networkAudioSamples, audioData, numNetworkBytes);
_stats.sentPacket();
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
nodeList->sendUnreliablePacket(*_audioPacket, *audioMixer);
_outgoingAvatarAudioSequenceNumber++;
} }
} }
void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
if (!_audioPacket) { emitAudioPacket((int16_t*)audio.data(), PacketType::MicrophoneAudioWithEcho);
// we don't have an audioPacket yet - set that up now
_audioPacket = NLPacket::create(PacketType::MicrophoneAudioWithEcho);
}
// FIXME either discard stereo in the recording or record a stereo flag
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && audioMixer->getActiveSocket()) {
glm::vec3 headPosition = _positionGetter();
glm::quat headOrientation = _orientationGetter();
quint8 isStereo = _isStereoInput ? 1 : 0;
_audioPacket->reset();
_audioPacket->setType(PacketType::MicrophoneAudioWithEcho);
_audioPacket->writePrimitive(_outgoingAvatarAudioSequenceNumber);
_audioPacket->writePrimitive(isStereo);
_audioPacket->writePrimitive(headPosition);
_audioPacket->writePrimitive(headOrientation);
_audioPacket->write(audio);
_stats.sentPacket();
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
nodeList->sendUnreliablePacket(*_audioPacket, *audioMixer);
_outgoingAvatarAudioSequenceNumber++;
}
} }
void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) {

View file

@ -212,6 +212,7 @@ protected:
} }
private: private:
void emitAudioPacket(const int16_t* audioData, PacketType packetType = PacketType::Unknown);
void outputFormatChanged(); void outputFormatChanged();
QByteArray firstInputFrame; QByteArray firstInputFrame;

View file

@ -1,3 +1,3 @@
set(TARGET_NAME avatars) set(TARGET_NAME avatars)
setup_hifi_library(Network Script) setup_hifi_library(Network Script)
link_hifi_libraries(audio shared networking recording) link_hifi_libraries(shared networking)

View file

@ -33,7 +33,6 @@
#include <StreamUtils.h> #include <StreamUtils.h>
#include <UUID.h> #include <UUID.h>
#include <shared/JSONHelpers.h> #include <shared/JSONHelpers.h>
#include <recording/Frame.h>
#include "AvatarLogging.h" #include "AvatarLogging.h"
@ -178,7 +177,7 @@ float AvatarData::getTargetScale() const {
void AvatarData::setTargetScale(float targetScale, bool overideReferential) { void AvatarData::setTargetScale(float targetScale, bool overideReferential) {
if (!_referential || overideReferential) { if (!_referential || overideReferential) {
_targetScale = targetScale; _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, targetScale));
} }
} }
@ -532,7 +531,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
} }
return maxAvailableSize; return maxAvailableSize;
} }
_targetScale = scale; _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale));
} // 20 bytes } // 20 bytes
{ // Lookat Position { // Lookat Position
@ -1443,14 +1442,10 @@ QByteArray AvatarData::toFrame(const AvatarData& avatar) {
auto recordingBasis = avatar.getRecordingBasis(); auto recordingBasis = avatar.getRecordingBasis();
if (recordingBasis) { if (recordingBasis) {
root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis);
// Find the relative transform // Find the relative transform
auto relativeTransform = recordingBasis->relativeTransform(avatar.getTransform()); auto relativeTransform = recordingBasis->relativeTransform(avatar.getTransform());
root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform);
// if the resulting relative basis is identity, we shouldn't record anything
if (!relativeTransform.isIdentity()) {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform);
root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis);
}
} else { } else {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatar.getTransform()); root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatar.getTransform());
} }
@ -1484,6 +1479,9 @@ QByteArray AvatarData::toFrame(const AvatarData& avatar) {
void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) {
QJsonDocument doc = QJsonDocument::fromBinaryData(frameData); QJsonDocument doc = QJsonDocument::fromBinaryData(frameData);
#ifdef WANT_JSON_DEBUG
qDebug() << doc.toJson(QJsonDocument::JsonFormat::Indented);
#endif
QJsonObject root = doc.object(); QJsonObject root = doc.object();
if (root.contains(JSON_AVATAR_HEAD_MODEL)) { if (root.contains(JSON_AVATAR_HEAD_MODEL)) {

View file

@ -50,7 +50,6 @@ typedef unsigned long long quint64;
#include <Node.h> #include <Node.h>
#include <RegisteredMetaTypes.h> #include <RegisteredMetaTypes.h>
#include <SimpleMovingAverage.h> #include <SimpleMovingAverage.h>
#include <recording/Forward.h>
#include "AABox.h" #include "AABox.h"
#include "HandData.h" #include "HandData.h"

View file

@ -12,11 +12,14 @@
#ifndef hifi_EntityActionInterface_h #ifndef hifi_EntityActionInterface_h
#define hifi_EntityActionInterface_h #define hifi_EntityActionInterface_h
#include <memory>
#include <QUuid> #include <QUuid>
#include <glm/glm.hpp>
#include "EntityItem.h" class EntityItem;
class EntitySimulation; class EntitySimulation;
using EntityItemPointer = std::shared_ptr<EntityItem>;
using EntityItemWeakPointer = std::weak_ptr<EntityItem>;
enum EntityActionType { enum EntityActionType {
// keep these synchronized with actionTypeFromString and actionTypeToString // keep these synchronized with actionTypeFromString and actionTypeToString
@ -34,6 +37,8 @@ public:
const QUuid& getID() const { return _id; } const QUuid& getID() const { return _id; }
EntityActionType getType() const { return _type; } EntityActionType getType() const { return _type; }
bool isActive() { return _active; }
virtual void removeFromSimulation(EntitySimulation* simulation) const = 0; virtual void removeFromSimulation(EntitySimulation* simulation) const = 0;
virtual EntityItemWeakPointer getOwnerEntity() const = 0; virtual EntityItemWeakPointer getOwnerEntity() const = 0;
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0; virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0;
@ -81,6 +86,7 @@ protected:
QUuid _id; QUuid _id;
EntityActionType _type; EntityActionType _type;
bool _active { false };
}; };

View file

@ -1844,3 +1844,18 @@ bool EntityItem::shouldSuppressLocationEdits() const {
return false; return false;
} }
QList<EntityActionPointer> EntityItem::getActionsOfType(EntityActionType typeToGet) {
QList<EntityActionPointer> result;
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
while (i != _objectActions.end()) {
EntityActionPointer action = i.value();
if (action->getType() == typeToGet && action->isActive()) {
result += action;
}
i++;
}
return result;
}

View file

@ -30,6 +30,7 @@
#include "EntityTypes.h" #include "EntityTypes.h"
#include "SimulationOwner.h" #include "SimulationOwner.h"
#include "SimulationFlags.h" #include "SimulationFlags.h"
#include "EntityActionInterface.h"
class EntitySimulation; class EntitySimulation;
class EntityTreeElement; class EntityTreeElement;
@ -419,7 +420,9 @@ public:
void setSourceUUID(const QUuid& sourceUUID) { _sourceUUID = sourceUUID; } void setSourceUUID(const QUuid& sourceUUID) { _sourceUUID = sourceUUID; }
const QUuid& getSourceUUID() const { return _sourceUUID; } const QUuid& getSourceUUID() const { return _sourceUUID; }
bool matchesSourceUUID(const QUuid& sourceUUID) const { return _sourceUUID == sourceUUID; } bool matchesSourceUUID(const QUuid& sourceUUID) const { return _sourceUUID == sourceUUID; }
QList<EntityActionPointer> getActionsOfType(EntityActionType typeToGet);
protected: protected:

View file

@ -704,6 +704,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
changedProperties[index] = QString("locked:") + changeHint; changedProperties[index] = QString("locked:") + changeHint;
} }
} }
if (properties.userDataChanged()) {
int index = changedProperties.indexOf("userData");
if (index >= 0) {
QString changeHint = properties.getUserData();
changedProperties[index] = QString("userData:") + changeHint;
}
}
} }
int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength,

View file

@ -20,8 +20,8 @@
#include <OctreeRenderer.h> // for RenderArgs #include <OctreeRenderer.h> // for RenderArgs
class EntityItem; class EntityItem;
typedef std::shared_ptr<EntityItem> EntityItemPointer; using EntityItemPointer = std::shared_ptr<EntityItem>;
typedef std::weak_ptr<EntityItem> EntityItemWeakPointer; using EntityItemWeakPointer = std::weak_ptr<EntityItem>;
inline uint qHash(const EntityItemPointer& a, uint seed) { inline uint qHash(const EntityItemPointer& a, uint seed) {
return qHash(a.get(), seed); return qHash(a.get(), seed);

View file

@ -1460,7 +1460,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
it != extracted.newIndices.end() && it.key() == oldIndex; it++) { it != extracted.newIndices.end() && it.key() == oldIndex; it++) {
// remember vertices with at least 1/4 weight // remember vertices with at least 1/4 weight
const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; const float EXPANSION_WEIGHT_THRESHOLD = 0.99f;
if (weight > EXPANSION_WEIGHT_THRESHOLD) { if (weight > EXPANSION_WEIGHT_THRESHOLD) {
// transform to joint-frame and save for later // transform to joint-frame and save for later
const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(it.value())); const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(it.value()));
@ -1535,63 +1535,49 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
meshIDsToMeshIndices.insert(it.key(), meshIndex); meshIDsToMeshIndices.insert(it.key(), meshIndex);
} }
// now that all joints have been scanned, compute a radius for each bone const float INV_SQRT_3 = 0.57735026918f;
ShapeVertices cardinalDirections = {
Vectors::UNIT_X,
Vectors::UNIT_Y,
Vectors::UNIT_Z,
glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3),
glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3),
glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3),
glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3)
};
// now that all joints have been scanned compute a k-Dop bounding volume of mesh
glm::vec3 defaultCapsuleAxis(0.0f, 1.0f, 0.0f); glm::vec3 defaultCapsuleAxis(0.0f, 1.0f, 0.0f);
for (int i = 0; i < geometry.joints.size(); ++i) { for (int i = 0; i < geometry.joints.size(); ++i) {
FBXJoint& joint = geometry.joints[i]; FBXJoint& joint = geometry.joints[i];
// NOTE: points are in joint-frame // NOTE: points are in joint-frame
// compute average point
ShapeVertices& points = shapeVertices[i]; ShapeVertices& points = shapeVertices[i];
glm::vec3 avgPoint = glm::vec3(0.0f);
for (uint32_t j = 0; j < points.size(); ++j) {
avgPoint += points[j];
}
avgPoint /= (float)points.size();
// compute axis from begin to avgPoint
glm::vec3 begin(0.0f);
glm::vec3 end = avgPoint;
glm::vec3 axis = end - begin;
float axisLength = glm::length(axis);
if (axisLength > EPSILON) {
axis /= axisLength;
} else {
axis = glm::vec3(0.0f);
}
// measure average cylindrical radius
float avgRadius = 0.0f;
if (points.size() > 0) { if (points.size() > 0) {
float minProjection = FLT_MAX; // compute average point
float maxProjection = -FLT_MIN; glm::vec3 avgPoint = glm::vec3(0.0f);
for (uint32_t j = 0; j < points.size(); ++j) { for (uint32_t j = 0; j < points.size(); ++j) {
glm::vec3 offset = points[j] - avgPoint; avgPoint += points[j];
float projection = glm::dot(offset, axis);
maxProjection = glm::max(maxProjection, projection);
minProjection = glm::min(minProjection, projection);
avgRadius += glm::length(offset - projection * axis);
} }
avgRadius /= (float)points.size(); avgPoint /= (float)points.size();
// compute endpoints of capsule in joint-frame // compute a k-Dop bounding volume
glm::vec3 capsuleBegin = avgPoint; for (uint32_t j = 0; j < cardinalDirections.size(); ++j) {
glm::vec3 capsuleEnd = avgPoint; float maxDot = -FLT_MAX;
if (maxProjection - minProjection < 2.0f * avgRadius) { float minDot = FLT_MIN;
// the mesh-as-cylinder approximation is too short to collide as a capsule for (uint32_t k = 0; k < points.size(); ++k) {
// so we'll collapse it to a sphere (although that isn't a very good approximation) float kDot = glm::dot(cardinalDirections[j], points[k] - avgPoint);
capsuleBegin = avgPoint + 0.5f * (maxProjection + minProjection) * axis; if (kDot > maxDot) {
capsuleEnd = capsuleBegin; maxDot = kDot;
} else { }
capsuleBegin = avgPoint + (minProjection + avgRadius) * axis; if (kDot < minDot) {
capsuleEnd = avgPoint + (maxProjection - avgRadius) * axis; minDot = kDot;
}
}
joint.shapeInfo.points.push_back(avgPoint + maxDot * cardinalDirections[j]);
joint.shapeInfo.points.push_back(avgPoint + minDot * cardinalDirections[j]);
} }
// save points for later
joint.shapeInfo.points.push_back(capsuleBegin);
joint.shapeInfo.points.push_back(capsuleEnd);
} }
joint.shapeInfo.radius = avgRadius;
} }
geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString());

View file

@ -56,7 +56,6 @@ public:
struct FBXJointShapeInfo { struct FBXJointShapeInfo {
// same units and frame as FBXJoint.translation // same units and frame as FBXJoint.translation
QVector<glm::vec3> points; QVector<glm::vec3> points;
float radius;
}; };
/// A single joint (transformation node) extracted from an FBX document. /// A single joint (transformation node) extracted from an FBX document.

View file

@ -700,22 +700,6 @@ void LimitedNodeList::sendSTUNRequest() {
_nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); _nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
} }
void LimitedNodeList::sendPingPackets() {
eachMatchingNode([](const SharedNodePointer& node)->bool {
switch (node->getType()) {
case NodeType::AvatarMixer:
case NodeType::AudioMixer:
case NodeType::EntityServer:
case NodeType::AssetServer:
return true;
default:
return false;
}
}, [&](const SharedNodePointer& node) {
sendPacket(constructPingPacket(), *node);
});
}
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) { void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
// check the cookie to make sure this is actually a STUN response // check the cookie to make sure this is actually a STUN response
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS

View file

@ -228,7 +228,6 @@ public slots:
void startSTUNPublicSocketUpdate(); void startSTUNPublicSocketUpdate();
virtual void sendSTUNRequest(); virtual void sendSTUNRequest();
void sendPingPackets();
bool killNodeWithUUID(const QUuid& nodeUUID); bool killNodeWithUUID(const QUuid& nodeUUID);

View file

@ -32,6 +32,7 @@
#include "udt/PacketHeaders.h" #include "udt/PacketHeaders.h"
#include "SharedUtil.h" #include "SharedUtil.h"
const int KEEPALIVE_PING_INTERVAL_MS = 1000;
NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) :
LimitedNodeList(socketListenPort, dtlsListenPort), LimitedNodeList(socketListenPort, dtlsListenPort),
@ -87,6 +88,12 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
// anytime we get a new node we will want to attempt to punch to it // anytime we get a new node we will want to attempt to punch to it
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS);
connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);
connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start()));
connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop);
// we definitely want STUN to update our public socket, so call the LNL to kick that off // we definitely want STUN to update our public socket, so call the LNL to kick that off
startSTUNPublicSocketUpdate(); startSTUNPublicSocketUpdate();
@ -632,3 +639,11 @@ void NodeList::activateSocketFromNodeCommunication(QSharedPointer<NLPacket> pack
flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetAudioMixerSocket); flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetAudioMixerSocket);
} }
} }
void NodeList::sendKeepAlivePings() {
eachMatchingNode([this](const SharedNodePointer& node)->bool {
return _nodeTypesOfInterest.contains(node->getType());
}, [&](const SharedNodePointer& node) {
sendPacket(constructPingPacket(), *node);
});
}

View file

@ -85,6 +85,7 @@ public slots:
void processPingReplyPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode); void processPingReplyPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
void processICEPingPacket(QSharedPointer<NLPacket> packet); void processICEPingPacket(QSharedPointer<NLPacket> packet);
signals: signals:
void limitOfSilentDomainCheckInsReached(); void limitOfSilentDomainCheckInsReached();
private slots: private slots:
@ -95,6 +96,8 @@ private slots:
void handleNodePingTimeout(); void handleNodePingTimeout();
void pingPunchForDomainServer(); void pingPunchForDomainServer();
void sendKeepAlivePings();
private: private:
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
@ -118,6 +121,7 @@ private:
int _numNoReplyDomainCheckIns; int _numNoReplyDomainCheckIns;
HifiSockAddr _assignmentServerSocket; HifiSockAddr _assignmentServerSocket;
bool _isShuttingDown { false }; bool _isShuttingDown { false };
QTimer _keepAlivePingTimer;
}; };
#endif // hifi_NodeList_h #endif // hifi_NodeList_h

View file

@ -67,7 +67,6 @@ protected:
EntityItemWeakPointer _ownerEntity; EntityItemWeakPointer _ownerEntity;
QString _tag; QString _tag;
quint64 _expires { 0 }; // in seconds since epoch quint64 _expires { 0 }; // in seconds since epoch
bool _active { false };
private: private:
int getEntityServerClockSkew() const; int getEntityServerClockSkew() const;

View file

@ -8,6 +8,8 @@
#include "Deck.h" #include "Deck.h"
#include <QtCore/QThread>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
@ -101,9 +103,13 @@ float Deck::position() const {
} }
static const Frame::Time MIN_FRAME_WAIT_INTERVAL = Frame::secondsToFrameTime(0.001f); static const Frame::Time MIN_FRAME_WAIT_INTERVAL = Frame::secondsToFrameTime(0.001f);
static const Frame::Time MAX_FRAME_PROCESSING_TIME = Frame::secondsToFrameTime(0.002f); static const Frame::Time MAX_FRAME_PROCESSING_TIME = Frame::secondsToFrameTime(0.004f);
void Deck::processFrames() { void Deck::processFrames() {
if (qApp->thread() != QThread::currentThread()) {
qWarning() << "Processing frames must only happen on the main thread.";
return;
}
Locker lock(_mutex); Locker lock(_mutex);
if (_pause) { if (_pause) {
return; return;
@ -115,10 +121,17 @@ void Deck::processFrames() {
// FIXME add code to start dropping frames if we fall behind. // FIXME add code to start dropping frames if we fall behind.
// Alternatively, add code to cache frames here and then process only the last frame of a given type // Alternatively, add code to cache frames here and then process only the last frame of a given type
// ... the latter will work for Avatar, but not well for audio I suspect. // ... the latter will work for Avatar, but not well for audio I suspect.
bool overLimit = false;
for (nextClip = getNextClip(); nextClip; nextClip = getNextClip()) { for (nextClip = getNextClip(); nextClip; nextClip = getNextClip()) {
auto currentPosition = Frame::frameTimeFromEpoch(_startEpoch); auto currentPosition = Frame::frameTimeFromEpoch(_startEpoch);
if ((currentPosition - startingPosition) >= MAX_FRAME_PROCESSING_TIME) { if ((currentPosition - startingPosition) >= MAX_FRAME_PROCESSING_TIME) {
qCWarning(recordingLog) << "Exceeded maximum frame processing time, breaking early"; qCWarning(recordingLog) << "Exceeded maximum frame processing time, breaking early";
#ifdef WANT_RECORDING_DEBUG
qCDebug(recordingLog) << "Starting: " << currentPosition;
qCDebug(recordingLog) << "Current: " << startingPosition;
qCDebug(recordingLog) << "Trigger: " << triggerPosition;
#endif
overLimit = true;
break; break;
} }
@ -150,9 +163,19 @@ void Deck::processFrames() {
// If we have more clip frames available, set the timer for the next one // If we have more clip frames available, set the timer for the next one
_position = Frame::frameTimeFromEpoch(_startEpoch); _position = Frame::frameTimeFromEpoch(_startEpoch);
auto nextFrameTime = nextClip->positionFrameTime(); int nextInterval = 1;
auto interval = Frame::frameTimeToMilliseconds(nextFrameTime - _position); if (!overLimit) {
_timer.singleShot(interval, [this] { auto nextFrameTime = nextClip->positionFrameTime();
nextInterval = (int)Frame::frameTimeToMilliseconds(nextFrameTime - _position);
#ifdef WANT_RECORDING_DEBUG
qCDebug(recordingLog) << "Now " << _position;
qCDebug(recordingLog) << "Next frame time " << nextInterval;
#endif
}
#ifdef WANT_RECORDING_DEBUG
qCDebug(recordingLog) << "Setting timer for next processing " << nextInterval;
#endif
_timer.singleShot(nextInterval, [this] {
processFrames(); processFrames();
}); });
} }

View file

@ -8,17 +8,19 @@
#include "RecordingScriptingInterface.h" #include "RecordingScriptingInterface.h"
#include <QThread>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <recording/Clip.h> #include <recording/Clip.h>
#include <recording/Frame.h> #include <recording/Frame.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <AudioClient.h> // FiXME
//#include <AudioClient.h>
#include <AudioConstants.h> #include <AudioConstants.h>
#include <Transform.h>
#include "avatar/AvatarManager.h" #include "ScriptEngineLogging.h"
#include "Application.h"
#include "InterfaceLogging.h"
typedef int16_t AudioSample; typedef int16_t AudioSample;
@ -43,23 +45,28 @@ RecordingScriptingInterface::RecordingScriptingInterface() {
_player = DependencyManager::get<Deck>(); _player = DependencyManager::get<Deck>();
_recorder = DependencyManager::get<Recorder>(); _recorder = DependencyManager::get<Recorder>();
auto audioClient = DependencyManager::get<AudioClient>(); // FIXME : Disabling Sound
connect(audioClient.data(), &AudioClient::inputReceived, this, &RecordingScriptingInterface::processAudioInput); // auto audioClient = DependencyManager::get<AudioClient>();
// connect(audioClient.data(), &AudioClient::inputReceived, this, &RecordingScriptingInterface::processAudioInput);
} }
bool RecordingScriptingInterface::isPlaying() { void RecordingScriptingInterface::setControlledAvatar(AvatarData* avatar) {
_controlledAvatar = avatar;
}
bool RecordingScriptingInterface::isPlaying() const {
return _player->isPlaying(); return _player->isPlaying();
} }
bool RecordingScriptingInterface::isPaused() { bool RecordingScriptingInterface::isPaused() const {
return _player->isPaused(); return _player->isPaused();
} }
float RecordingScriptingInterface::playerElapsed() { float RecordingScriptingInterface::playerElapsed() const {
return _player->position(); return _player->position();
} }
float RecordingScriptingInterface::playerLength() { float RecordingScriptingInterface::playerLength() const {
return _player->length(); return _player->length();
} }
@ -84,10 +91,10 @@ void RecordingScriptingInterface::startPlaying() {
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);
return; return;
} }
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// Playback from the current position // Playback from the current position
if (_playFromCurrentLocation) { if (_playFromCurrentLocation && _controlledAvatar) {
_dummyAvatar.setRecordingBasis(std::make_shared<Transform>(myAvatar->getTransform())); _dummyAvatar.setRecordingBasis(std::make_shared<Transform>(_controlledAvatar->getTransform()));
} else { } else {
_dummyAvatar.clearRecordingBasis(); _dummyAvatar.clearRecordingBasis();
} }
@ -103,6 +110,10 @@ void RecordingScriptingInterface::setPlayerAudioOffset(float audioOffset) {
} }
void RecordingScriptingInterface::setPlayerTime(float time) { void RecordingScriptingInterface::setPlayerTime(float time) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPlayerTime", Qt::BlockingQueuedConnection, Q_ARG(float, time));
return;
}
_player->seek(time); _player->seek(time);
} }
@ -130,29 +141,33 @@ void RecordingScriptingInterface::setPlayerUseSkeletonModel(bool useSkeletonMode
_useSkeletonModel = useSkeletonModel; _useSkeletonModel = useSkeletonModel;
} }
void RecordingScriptingInterface::play() {
_player->play();
}
void RecordingScriptingInterface::pausePlayer() { void RecordingScriptingInterface::pausePlayer() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "pausePlayer", Qt::BlockingQueuedConnection);
return;
}
_player->pause(); _player->pause();
} }
void RecordingScriptingInterface::stopPlaying() { void RecordingScriptingInterface::stopPlaying() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection);
return;
}
_player->stop(); _player->stop();
} }
bool RecordingScriptingInterface::isRecording() { bool RecordingScriptingInterface::isRecording() const {
return _recorder->isRecording(); return _recorder->isRecording();
} }
float RecordingScriptingInterface::recorderElapsed() { float RecordingScriptingInterface::recorderElapsed() const {
return _recorder->position(); return _recorder->position();
} }
void RecordingScriptingInterface::startRecording() { void RecordingScriptingInterface::startRecording() {
if (_recorder->isRecording()) { if (_recorder->isRecording()) {
qCWarning(interfaceapp) << "Recorder is already running"; qCWarning(scriptengine) << "Recorder is already running";
return; return;
} }
@ -163,25 +178,21 @@ void RecordingScriptingInterface::startRecording() {
_recordingEpoch = Frame::epochForFrameTime(0); _recordingEpoch = Frame::epochForFrameTime(0);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); if (_controlledAvatar) {
myAvatar->setRecordingBasis(); _controlledAvatar->setRecordingBasis();
}
_recorder->start(); _recorder->start();
} }
void RecordingScriptingInterface::stopRecording() { void RecordingScriptingInterface::stopRecording() {
_recorder->stop(); _recorder->stop();
_lastClip = _recorder->getClip(); _lastClip = _recorder->getClip();
// post-process the audio into discreet chunks based on times of received samples
_lastClip->seek(0);
Frame::ConstPointer frame;
while (frame = _lastClip->nextFrame()) {
qDebug() << "Frame time " << frame->timeOffset << " size " << frame->data.size();
}
_lastClip->seek(0); _lastClip->seek(0);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); if (_controlledAvatar) {
myAvatar->clearRecordingBasis(); _controlledAvatar->clearRecordingBasis();
}
} }
void RecordingScriptingInterface::saveRecording(const QString& filename) { void RecordingScriptingInterface::saveRecording(const QString& filename) {
@ -206,7 +217,7 @@ void RecordingScriptingInterface::loadLastRecording() {
} }
if (!_lastClip) { if (!_lastClip) {
qCDebug(interfaceapp) << "There is no recording to load"; qCDebug(scriptengine) << "There is no recording to load";
return; return;
} }
@ -217,27 +228,32 @@ void RecordingScriptingInterface::loadLastRecording() {
void RecordingScriptingInterface::processAvatarFrame(const Frame::ConstPointer& frame) { void RecordingScriptingInterface::processAvatarFrame(const Frame::ConstPointer& frame) {
Q_ASSERT(QThread::currentThread() == thread()); Q_ASSERT(QThread::currentThread() == thread());
if (!_controlledAvatar) {
return;
}
AvatarData::fromFrame(frame->data, _dummyAvatar); AvatarData::fromFrame(frame->data, _dummyAvatar);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (_useHeadModel && _dummyAvatar.getFaceModelURL().isValid() && if (_useHeadModel && _dummyAvatar.getFaceModelURL().isValid() &&
(_dummyAvatar.getFaceModelURL() != myAvatar->getFaceModelURL())) { (_dummyAvatar.getFaceModelURL() != _controlledAvatar->getFaceModelURL())) {
// FIXME // FIXME
//myAvatar->setFaceModelURL(_dummyAvatar.getFaceModelURL()); //myAvatar->setFaceModelURL(_dummyAvatar.getFaceModelURL());
} }
if (_useSkeletonModel && _dummyAvatar.getSkeletonModelURL().isValid() && if (_useSkeletonModel && _dummyAvatar.getSkeletonModelURL().isValid() &&
(_dummyAvatar.getSkeletonModelURL() != myAvatar->getSkeletonModelURL())) { (_dummyAvatar.getSkeletonModelURL() != _controlledAvatar->getSkeletonModelURL())) {
// FIXME // FIXME
//myAvatar->useFullAvatarURL() //myAvatar->useFullAvatarURL()
} }
if (_useDisplayName && _dummyAvatar.getDisplayName() != myAvatar->getDisplayName()) { if (_useDisplayName && _dummyAvatar.getDisplayName() != _controlledAvatar->getDisplayName()) {
myAvatar->setDisplayName(_dummyAvatar.getDisplayName()); _controlledAvatar->setDisplayName(_dummyAvatar.getDisplayName());
} }
myAvatar->setPosition(_dummyAvatar.getPosition()); _controlledAvatar->setPosition(_dummyAvatar.getPosition());
myAvatar->setOrientation(_dummyAvatar.getOrientation()); _controlledAvatar->setOrientation(_dummyAvatar.getOrientation());
// FIXME attachments // FIXME attachments
// FIXME joints // FIXME joints
@ -253,6 +269,6 @@ void RecordingScriptingInterface::processAudioInput(const QByteArray& audio) {
} }
void RecordingScriptingInterface::processAudioFrame(const recording::FrameConstPointer& frame) { void RecordingScriptingInterface::processAudioFrame(const recording::FrameConstPointer& frame) {
auto audioClient = DependencyManager::get<AudioClient>(); // auto audioClient = DependencyManager::get<AudioClient>();
audioClient->handleRecordedAudioInput(frame->data); // audioClient->handleRecordedAudioInput(frame->data);
} }

View file

@ -24,13 +24,20 @@ class RecordingScriptingInterface : public QObject, public Dependency {
public: public:
RecordingScriptingInterface(); RecordingScriptingInterface();
void setControlledAvatar(AvatarData* avatar);
public slots: public slots:
bool isPlaying();
bool isPaused();
float playerElapsed();
float playerLength();
void loadRecording(const QString& filename); void loadRecording(const QString& filename);
void startPlaying(); void startPlaying();
void pausePlayer();
void stopPlaying();
bool isPlaying() const;
bool isPaused() const;
float playerElapsed() const;
float playerLength() const;
void setPlayerVolume(float volume); void setPlayerVolume(float volume);
void setPlayerAudioOffset(float audioOffset); void setPlayerAudioOffset(float audioOffset);
void setPlayerTime(float time); void setPlayerTime(float time);
@ -40,13 +47,13 @@ public slots:
void setPlayerUseAttachments(bool useAttachments); void setPlayerUseAttachments(bool useAttachments);
void setPlayerUseHeadModel(bool useHeadModel); void setPlayerUseHeadModel(bool useHeadModel);
void setPlayerUseSkeletonModel(bool useSkeletonModel); void setPlayerUseSkeletonModel(bool useSkeletonModel);
void play();
void pausePlayer();
void stopPlaying();
bool isRecording();
float recorderElapsed();
void startRecording(); void startRecording();
void stopRecording(); void stopRecording();
bool isRecording() const;
float recorderElapsed() const;
void saveRecording(const QString& filename); void saveRecording(const QString& filename);
void loadLastRecording(); void loadLastRecording();
@ -74,6 +81,7 @@ private:
Flag _useSkeletonModel { false }; Flag _useSkeletonModel { false };
recording::ClipPointer _lastClip; recording::ClipPointer _lastClip;
AvatarData _dummyAvatar; AvatarData _dummyAvatar;
AvatarData* _controlledAvatar;
}; };
#endif // hifi_RecordingScriptingInterface_h #endif // hifi_RecordingScriptingInterface_h

View file

@ -47,6 +47,7 @@
#include "WebSocketClass.h" #include "WebSocketClass.h"
#include "SceneScriptingInterface.h" #include "SceneScriptingInterface.h"
#include "RecordingScriptingInterface.h"
#include "MIDIEvent.h" #include "MIDIEvent.h"
@ -377,6 +378,9 @@ void ScriptEngine::init() {
auto scriptingInterface = DependencyManager::get<controller::ScriptingInterface>(); auto scriptingInterface = DependencyManager::get<controller::ScriptingInterface>();
registerGlobalObject("Controller", scriptingInterface.data()); registerGlobalObject("Controller", scriptingInterface.data());
UserInputMapper::registerControllerTypes(this); UserInputMapper::registerControllerTypes(this);
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
registerGlobalObject("Recording", recordingInterface.data());
} }
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {