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 <UUID.h>
#include <recording/Deck.h>
#include <recording/Recorder.h>
#include <WebSocketServerClass.h>
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include "avatars/ScriptableAvatar.h"
#include "RecordingScriptingInterface.h"
#include "Agent.h"
@ -56,8 +60,10 @@ Agent::Agent(NLPacket& packet) :
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>();
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
@ -117,7 +123,6 @@ void Agent::handleAudioPacket(QSharedPointer<NLPacket> packet) {
}
const QString AGENT_LOGGING_NAME = "agent";
const int PING_INTERVAL = 1000;
void Agent::run() {
ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent);
@ -139,10 +144,6 @@ void Agent::run() {
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
QUrl scriptURL;
if (_payload.isEmpty()) {
@ -249,6 +250,8 @@ void Agent::setIsAvatar(bool isAvatar) {
}
if (!_isAvatar) {
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(nullptr);
if (_avatarIdentityTimer) {
_avatarIdentityTimer->stop();
delete _avatarIdentityTimer;
@ -266,6 +269,7 @@ void Agent::setIsAvatar(bool isAvatar) {
void Agent::setAvatarData(AvatarData* avatarData, const QString& objectName) {
_avatarData = avatarData;
_scriptEngine->registerGlobalObject(objectName, avatarData);
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(avatarData);
}
void Agent::sendAvatarIdentityPacket() {
@ -396,11 +400,6 @@ void Agent::aboutToFinish() {
_scriptEngine->stop();
}
if (_pingTimer) {
_pingTimer->stop();
delete _pingTimer;
}
// our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
@ -413,21 +412,3 @@ void Agent::aboutToFinish() {
// cleanup the AudioInjectorManager (and any still running injectors)
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 handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void sendPingRequests();
void processAgentAvatarAndAudio(float deltaTime);
private:
std::unique_ptr<ScriptEngine> _scriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
QTimer* _pingTimer;
MixedAudioStream _receivedAudioStream;
float _lastReceivedAudioLoudness;

View file

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

View file

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

View file

@ -128,30 +128,7 @@ void MessagesMixer::run() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = nodeList->getDomainHandler();
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";
// The messages-mixer currently does currently have any domain settings. If it did, they would be
// synchronously grabbed here.
}

View file

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

View file

@ -953,7 +953,6 @@ bool OctreeServer::readConfiguration() {
if (domainHandler.getSettingsObject().isEmpty()) {
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
return false;
}
@ -1086,12 +1085,16 @@ void OctreeServer::run() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->setOwnerType(getMyNodeType());
// use common init to setup common timers and logging
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
if (!readConfiguration()) {
qDebug() << "OctreeServer bailing on run since readConfiguration has failed.";
setFinished(true);
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(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
setvbuf(stdout, NULL, _IOLBF, 0);
#endif

View file

@ -25,9 +25,14 @@ set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/headers CACHE TYPE INTERNA
if (WIN32)
# FIXME need to account for different architectures
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL)
add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32)
# FIXME need to account for different architectures
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
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)

View file

@ -66,8 +66,15 @@ if (APPLE)
elseif (WIN32)
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}_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 ()
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
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 ()
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)
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)
set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS})
else ()
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
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_OFF_VALUE = 0.15;
var BUMPER_ON_VALUE = 0.5;
//
// distant manipulation
//
@ -45,7 +47,7 @@ var PICK_MAX_DISTANCE = 500; // max length of pick-ray
// 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_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
@ -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 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
//
@ -68,7 +77,7 @@ var ZERO_VEC = {
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
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 ACTION_TTL = 15; // seconds
var ACTION_TTL_REFRESH = 5;
@ -106,6 +115,12 @@ var STATE_CONTINUE_NEAR_TRIGGER = 7;
var STATE_FAR_TRIGGER = 8;
var STATE_CONTINUE_FAR_TRIGGER = 9;
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) {
@ -132,6 +147,18 @@ function stateToName(state) {
return "continue_far_trigger";
case STATE_RELEASE:
return "release";
case STATE_EQUIP_SEARCHING:
return "equip_searching";
case STATE_EQUIP:
return "equip";
case STATE_CONTINUE_EQUIP_BD:
return "continue_equip_bd";
case STATE_CONTINUE_EQUIP:
return "continue_equip";
case STATE_WAITING_FOR_BUMPER_RELEASE:
return "waiting_for_bumper_release";
case STATE_EQUIP_SPRING:
return "state_equip_spring";
}
return "unknown";
@ -182,6 +209,7 @@ function MyController(hand) {
this.pointer = null; // entity-id of line object
this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0;
this.rawBumperValue = 0;
this.offsetPosition = { x: 0.0, y: 0.0, z: 0.0 };
this.offsetRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
@ -200,6 +228,9 @@ function MyController(hand) {
case STATE_SEARCHING:
this.search();
break;
case STATE_EQUIP_SEARCHING:
this.search();
break;
case STATE_DISTANCE_HOLDING:
this.distanceHolding();
break;
@ -207,9 +238,18 @@ function MyController(hand) {
this.continueDistanceHolding();
break;
case STATE_NEAR_GRABBING:
case STATE_EQUIP:
this.nearGrabbing();
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_EQUIP_BD:
case STATE_CONTINUE_EQUIP:
this.continueNearGrabbing();
break;
case STATE_NEAR_TRIGGER:
@ -281,10 +321,15 @@ function MyController(hand) {
this.pointer = null;
};
this.eitherTrigger = function (value) {
this.triggerPress = function (value) {
_this.rawTriggerValue = value;
};
this.bumperPress = function (value) {
_this.rawBumperValue = value;
};
this.updateSmoothedTrigger = function () {
var triggerValue = this.rawTriggerValue;
// smooth out trigger value
@ -305,23 +350,37 @@ function MyController(hand) {
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() {
if (this.triggerSmoothedSqueezed()) {
this.lastPickTime = 0;
this.setState(STATE_SEARCHING);
return;
}
if (this.bumperSqueezed()) {
this.lastPickTime = 0;
this.setState(STATE_EQUIP_SEARCHING);
return;
}
}
this.search = function() {
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) {
return;
}
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
this.setState(STATE_RELEASE);
return;
}
@ -334,8 +393,6 @@ function MyController(hand) {
length: PICK_MAX_DISTANCE
};
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
// don't pick 60x per second.
var pickRays = [];
var now = Date.now();
@ -398,7 +455,15 @@ function MyController(hand) {
return;
} else if (!intersection.properties.locked) {
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;
}
} else if (! entityIsGrabbedByOther(intersection.entityID)) {
@ -407,8 +472,14 @@ function MyController(hand) {
&& !intersection.properties.locked) {
// the hand is far from the intersected object. go into distance-holding mode
this.grabbedEntity = intersection.entityID;
this.setState(STATE_DISTANCE_HOLDING);
return;
if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) {
// 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) {
this.grabbedEntity = intersection.entityID;
this.setState(STATE_FAR_TRIGGER);
@ -434,6 +505,7 @@ function MyController(hand) {
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = PICK_MAX_DISTANCE;
var i, props, distance, grabbableData;
this.grabbedEntity = null;
for (i = 0; i < nearbyEntities.length; i++) {
var grabbableDataForCandidate =
getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA);
@ -483,16 +555,17 @@ function MyController(hand) {
grabbableData = grabbableDataForCandidate;
}
}
if (this.grabbedEntity === null) {
return;
}
if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_TRIGGER);
return;
} else if (!props.locked && props.collisionsWillMove) {
this.setState(STATE_NEAR_GRABBING);
return;
if (this.grabbedEntity !== null) {
if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_TRIGGER);
return;
} else if (!props.locked && props.collisionsWillMove) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP)
return;
}
}
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
};
this.distanceHolding = function() {
@ -551,6 +624,16 @@ function MyController(hand) {
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
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);
@ -634,13 +717,12 @@ function MyController(hand) {
return;
}
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
return;
}
this.lineOff();
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);
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) {
this.offsetPosition = grabbableData.spatialKey.relativePosition;
}
@ -686,7 +769,13 @@ function MyController(hand) {
this.actionID = null;
} else {
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) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} 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.continueNearGrabbing = function() {
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
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.
// 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() {
if (this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
@ -919,6 +1081,7 @@ function MyController(hand) {
}
Entities.editEntity(entityID, whileHeldProperties);
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
return data;
};
@ -948,8 +1111,12 @@ var leftController = new MyController(LEFT_HAND);
var MAPPING_NAME = "com.highfidelity.handControllerGrab";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from([Controller.Standard.RB, Controller.Standard.RT]).peek().to(rightController.eitherTrigger);
mapping.from([Controller.Standard.LB, Controller.Standard.LT]).peek().to(leftController.eitherTrigger);
mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress);
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);

View file

@ -1617,6 +1617,11 @@ PropertiesTool = function(opts) {
pushCommandForSelections();
selectionManager._update();
}
} else if (data.action == "previewCamera") {
if (selectionManager.hasSelection()) {
Camera.mode = "entity";
Camera.cameraEntity = selectionManager.selections[0];
}
} else if (data.action == "rescaleDimensions") {
var multiplier = data.percentage / 100;
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
(function () {
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/utils.js");
(function() {
var insideRecorderArea = false;
var enteredInTime = false;
var isAvatarRecording = false;
@ -25,51 +27,63 @@
_this = this;
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 = {
update: function(){
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData);
var isRecordingStarted = userData.recordingKey.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;
}
},
preload: function(entityID) {
preload: function (entityID) {
print("RECORDING ENTITY PRELOAD");
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");
insideRecorderArea = true;
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData);
var isRecordingStarted = userData.recordingKey.isRecordingStarted;
if(!isRecordingStarted){
var isRecordingStarted = getEntityCustomData("recordingKey", _this.entityID, { isRecordingStarted: false }).isRecordingStarted;
if (!isRecordingStarted) {
//i'm in the recording area in time (before the event starts)
enteredInTime = true;
}
},
leaveEntity: function(entityID) {
leaveEntity: function (entityID) {
print("leaving the recording area");
insideRecorderArea = false;
enteredInTime = false;
},
startRecording: function(entityID){
if(enteredInTime && !isAvatarRecording){
startRecording: function (entityID) {
if (enteredInTime && !isAvatarRecording) {
print("RECORDING STARTED");
Recording.startRecording();
isAvatarRecording = true;
}
},
stopRecording: function(entityID){
if(isAvatarRecording){
stopRecording: function (entityID) {
if (isAvatarRecording) {
print("RECORDING ENDED");
Recording.stopRecording();
Recording.loadLastRecording();
@ -80,12 +94,13 @@
}
}
},
clean: function(entityID) {
Script.update.disconnect(_this.update);
unload: function (entityID) {
print("RECORDING ENTITY UNLOAD");
Script.update.disconnect(update);
}
}
return new recordingEntity();
});
});

View file

@ -5,16 +5,15 @@
// Created by Alessandro Signa on 11/12/15.
// 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.
// 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/";
Script.include("../libraries/toolBars.js");
Script.include("../libraries/utils.js");
Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.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 recordIcon;
var isRecordingEntityFound = false;
var isRecording = false;
var recordAreaEntity = Entities.addEntity({
name: 'recorderEntity',
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
}
})
});
var recordAreaEntity = null;
findRecorder();
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();
@ -70,7 +59,7 @@ function setupToolBar() {
Tool.IMAGE_HEIGHT /= 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);
@ -81,9 +70,8 @@ function setupToolBar() {
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT,
alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON,
visible: true
visible: isRecordingEntityFound,
}, true, isRecording);
}
function mousePressEvent(event) {
@ -106,8 +94,6 @@ function mousePressEvent(event) {
function 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 elHyperlinkDescription = document.getElementById("property-hyperlink-description");
var elPreviewCameraButton = document.getElementById("preview-camera-button");
if (window.EventBridge !== undefined) {
EventBridge.scriptEventReceived.connect(function(data) {
@ -931,6 +931,12 @@
action: "centerAtmosphereToZone",
}));
});
elPreviewCameraButton.addEventListener("click", function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "action",
action: "previewCamera"
}));
});
window.onblur = function() {
// Fake a change event
@ -1032,7 +1038,7 @@
<div class="section-header">
<label>Spacial Properites</label>
<label>Spacial Properties</label>
</div>
<div class="property">
@ -1044,6 +1050,7 @@
<div>
<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="preview-camera-button" value="Preview Camera">
</div>
</div>
</div>

View file

@ -204,7 +204,7 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Collisions Will Move:", type: "checkbox", value: properties.collisionsWillMove });
index++;
array.push({ label: "Collision Sound URL:", value: properties.collisionSoundURL });
index++;
index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
@ -260,6 +260,10 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Cutoff (in degrees):", value: properties.cutoff });
index++;
}
array.push({ label: "", type: "inlineButton", buttonLabel: "Preview Camera", name: "previewCamera" });
index++;
array.push({ button: "Cancel" });
index++;
@ -268,6 +272,11 @@ EntityPropertyDialogBox = (function () {
};
Window.inlineButtonClicked.connect(function (name) {
if (name == "previewCamera") {
Camera.mode = "entity";
Camera.cameraEntity = propertiesForEditedEntity.id;
}
if (name == "resetDimensions") {
Window.reloadNonBlockingForm([
{ 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)+ " }";
}
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) {
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
setEntityUserData = function(id, 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
@ -60,7 +66,7 @@ getEntityUserData = function(id) {
var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) {
try {
results = JSON.parse(properties.userData);
results = JSON.parse(properties.userData);
} catch(err) {
logDebug(err);
logDebug(properties.userData);

View file

@ -16,7 +16,7 @@
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 = {
x: 0.01,

View file

@ -88,6 +88,7 @@
#include <RenderDeferredTask.h>
#include <ResourceCache.h>
#include <SceneScriptingInterface.h>
#include <RecordingScriptingInterface.h>
#include <ScriptCache.h>
#include <SoundCache.h>
#include <TextureCache.h>
@ -128,7 +129,6 @@
#include "scripting/LocationScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/RecordingScriptingInterface.h"
#include "scripting/WebWindowClass.h"
#include "scripting/WindowScriptingInterface.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);
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.
getMyAvatar()->updateMotionBehaviorFromMenu();
@ -839,6 +843,7 @@ void Application::cleanupBeforeQuit() {
#ifdef HAVE_IVIEWHMD
DependencyManager::get<EyeTracker>()->setEnabled(false, true);
#endif
DependencyManager::get<RecordingScriptingInterface>()->setControlledAvatar(nullptr);
AnimDebugDraw::getInstance().shutdown();
@ -1005,10 +1010,6 @@ void Application::initializeGL() {
connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
_entityEditSender.initialize(_enableProcessOctreeThread);
// call our timer function every second
connect(&pingTimer, &QTimer::timeout, this, &Application::ping);
pingTimer.start(1000);
_idleLoopStdev.reset();
// update before the first render
@ -1232,6 +1233,19 @@ void Application::paintGL() {
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
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
if (!isHMDMode()) {
@ -2136,13 +2150,6 @@ bool Application::acceptSnapshot(const QString& urlString) {
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) {
if (_aboutToQuit) {
return; // bail early, nothing to do here.
@ -2682,8 +2689,8 @@ void Application::cycleCamera() {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
} else if (menu->isOptionChecked(MenuOption::IndependentMode)) {
// do nothing if in independe mode
} else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) {
// do nothing if in independent or camera entity modes
return;
}
cameraMenuChanged(); // handle the menu change
@ -2710,6 +2717,10 @@ void Application::cameraMenuChanged() {
if (_myCamera.getMode() != 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);
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,

View file

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

View file

@ -28,6 +28,8 @@ CameraMode stringToMode(const QString& mode) {
return CAMERA_MODE_MIRROR;
} else if (mode == "independent") {
return CAMERA_MODE_INDEPENDENT;
} else if (mode == "entity") {
return CAMERA_MODE_ENTITY;
}
return CAMERA_MODE_NULL;
}
@ -41,6 +43,8 @@ QString modeToString(CameraMode mode) {
return "mirror";
} else if (mode == CAMERA_MODE_INDEPENDENT) {
return "independent";
} else if (mode == CAMERA_MODE_ENTITY) {
return "entity";
}
return "unknown";
}
@ -94,6 +98,17 @@ void Camera::setMode(CameraMode 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) {
_projection = projection;
}
@ -118,6 +133,9 @@ void Camera::setModeString(const QString& mode) {
case CAMERA_MODE_INDEPENDENT:
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
break;
case CAMERA_MODE_ENTITY:
Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
break;
default:
break;
}

View file

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

View file

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

View file

@ -155,6 +155,7 @@ namespace MenuOption {
const QString Bookmarks = "Bookmarks";
const QString CachesSize = "RAM Caches Size";
const QString CalibrateCamera = "Calibrate Camera";
const QString CameraEntityMode = "Entity Mode";
const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat...";
const QString Collisions = "Collisions";
@ -280,7 +281,6 @@ namespace MenuOption {
const QString Stats = "Stats";
const QString StopAllScripts = "Stop All Scripts";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString TestPing = "Test Ping";
const QString ThirdPerson = "Third Person";
const QString ThreePointCalibration = "3 Point Calibration";
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 "avatar/MyAvatar.h"
#include "avatar/AvatarManager.h"
#include "AvatarActionHold.h"
@ -22,8 +21,7 @@ AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntit
_relativePosition(glm::vec3(0.0f)),
_relativeRotation(glm::quat()),
_hand("right"),
_holderID(QUuid())
{
_holderID(QUuid()) {
_type = ACTION_TYPE_HOLD;
#if WANT_DEBUG
qDebug() << "AvatarActionHold::AvatarActionHold";
@ -36,13 +34,10 @@ AvatarActionHold::~AvatarActionHold() {
#endif
}
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
bool gotLock = false;
glm::quat rotation;
glm::vec3 position;
std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
std::shared_ptr<Avatar> holdingAvatar = nullptr;
gotLock = withTryReadLock([&]{
withTryReadLock([&]{
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID);
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) {
gotLock = withTryWriteLock([&]{
_positionalTarget = position;
_rotationalTarget = rotation;
_positionalTargetSet = true;
_rotationalTargetSet = true;
_active = true;
});
if (gotLock) {
if (_kinematic) {
doKinematicUpdate(deltaTimeStep);
} else {
activateBody();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
if (_kinematic) {
doKinematicUpdate(deltaTimeStep);
} else {
activateBody();
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
}
}
@ -109,7 +135,8 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
if (_previousSet) {
// smooth velocity over 2 frames
glm::vec3 positionalDelta = _positionalTarget - _previousPositionalTarget;
glm::vec3 positionalVelocity = (positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep);
glm::vec3 positionalVelocity =
(positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep);
rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
_previousPositionalDelta = positionalDelta;
_previousDeltaTimeStep = deltaTimeStep;

View file

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

View file

@ -625,19 +625,15 @@ void SkeletonModel::computeBoundingShape() {
totalExtents.addPoint(glm::vec3(0.0f));
int numStates = _rig->getJointStateCount();
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.
// 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;
for (int j = 0; j < shapeInfo.points.size(); ++j) {
glm::vec3 transformedPoint = extractTranslation(jointTransform * glm::translate(shapeInfo.points[j]));
vec3 radius(scale * shapeInfo.radius);
totalExtents.addPoint(transformedPoint + radius);
totalExtents.addPoint(transformedPoint - radius);
if (shapeInfo.points.size() > 0) {
for (int j = 0; j < shapeInfo.points.size(); ++j) {
totalExtents.addPoint(extractTranslation(jointTransform * glm::translate(shapeInfo.points[j])));
}
}
// HACK so that default legless robot doesn't knuckle-drag
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
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),
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 axis = topPoint - bottomPoint;
deferredLighting->renderSolidSphereInstance(batch,
deferredLighting->renderSolidSphereInstance(batch,
Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius),
glm::vec4(0.8f, 0.8f, 0.6f, alpha));

View file

@ -82,7 +82,7 @@ public:
bool getNeckPosition(glm::vec3& neckPosition) const;
bool getLocalNeckPosition(glm::vec3& neckPosition) const;
/// Returns the rotation of the neck joint's parent from default orientation
/// \return whether or not the neck was found
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);
// Second column: ping
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->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);
}
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->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);
// Third column, avatar stats
MyAvatar* myAvatar = avatarManager->getMyAvatar();

View file

@ -743,19 +743,9 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
}
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 int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio);
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();
// Add audio source injection if enabled
@ -784,30 +774,30 @@ void AudioClient::handleAudioInput() {
float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
_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
? 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;
static int16_t networkAudioSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) {
if (!_muted) {
// zero out the monoAudioSamples array and the locally injected audio
memset(networkAudioSamples, 0, numNetworkBytes);
// Increment the time since the last clip
if (_timeSinceLastClip >= 0.0f) {
_timeSinceLastClip += (float) numNetworkSamples / (float) AudioConstants::SAMPLE_RATE;
_timeSinceLastClip += (float)numNetworkSamples / (float)AudioConstants::SAMPLE_RATE;
}
_inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired);
possibleResampling(_inputToNetworkResampler,
inputAudioSamples.get(), networkAudioSamples,
inputSamplesRequired, numNetworkSamples,
_inputFormat, _desiredInputFormat);
inputAudioSamples.get(), networkAudioSamples,
inputSamplesRequired, numNetworkSamples,
_inputFormat, _desiredInputFormat);
// Remove DC offset
if (!_isStereoInput && !_audioSourceInjectEnabled) {
@ -829,7 +819,7 @@ void AudioClient::handleAudioInput() {
for (int i = 0; i < numNetworkSamples; i++) {
int thisSample = std::abs(networkAudioSamples[i]);
loudness += (float) thisSample;
loudness += (float)thisSample;
if (thisSample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)) {
_timeSinceLastClip = 0.0f;
@ -839,7 +829,7 @@ void AudioClient::handleAudioInput() {
_lastInputLoudness = fabs(loudness / numNetworkSamples);
}
emit inputReceived({reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes});
emit inputReceived({ reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes });
} else {
// our input loudness is 0, since we're muted
@ -849,14 +839,38 @@ void AudioClient::handleAudioInput() {
_inputRingBuffer.shiftReadPosition(inputSamplesRequired);
}
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
emitAudioPacket(networkAudioSamples);
}
}
if (audioMixer && audioMixer->getActiveSocket()) {
glm::vec3 headPosition = _positionGetter();
glm::quat headOrientation = _orientationGetter();
quint8 isStereo = _isStereoInput ? 1 : 0;
void AudioClient::emitAudioPacket(const int16_t* audioData, PacketType packetType) {
static std::mutex _mutex;
using Locker = std::unique_lock<std::mutex>;
// 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) {
_audioPacket->setType(PacketType::SilentAudioFrame);
} else {
@ -866,70 +880,52 @@ void AudioClient::handleAudioInput() {
_audioPacket->setType(PacketType::MicrophoneAudioNoEcho);
}
}
// 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);
}
_stats.sentPacket();
nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket);
nodeList->sendUnreliablePacket(*_audioPacket, *audioMixer);
_outgoingAvatarAudioSequenceNumber++;
} else {
_audioPacket->setType(packetType);
}
// 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) {
if (!_audioPacket) {
// 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++;
}
emitAudioPacket((int16_t*)audio.data(), PacketType::MicrophoneAudioWithEcho);
}
void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) {

View file

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

View file

@ -1,3 +1,3 @@
set(TARGET_NAME avatars)
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 <UUID.h>
#include <shared/JSONHelpers.h>
#include <recording/Frame.h>
#include "AvatarLogging.h"
@ -178,7 +177,7 @@ float AvatarData::getTargetScale() const {
void AvatarData::setTargetScale(float targetScale, bool 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;
}
_targetScale = scale;
_targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale));
} // 20 bytes
{ // Lookat Position
@ -1443,14 +1442,10 @@ QByteArray AvatarData::toFrame(const AvatarData& avatar) {
auto recordingBasis = avatar.getRecordingBasis();
if (recordingBasis) {
root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis);
// Find the relative transform
auto relativeTransform = recordingBasis->relativeTransform(avatar.getTransform());
// 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);
}
root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform);
} else {
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) {
QJsonDocument doc = QJsonDocument::fromBinaryData(frameData);
#ifdef WANT_JSON_DEBUG
qDebug() << doc.toJson(QJsonDocument::JsonFormat::Indented);
#endif
QJsonObject root = doc.object();
if (root.contains(JSON_AVATAR_HEAD_MODEL)) {

View file

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

View file

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

View file

@ -1844,3 +1844,18 @@ bool EntityItem::shouldSuppressLocationEdits() const {
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 "SimulationOwner.h"
#include "SimulationFlags.h"
#include "EntityActionInterface.h"
class EntitySimulation;
class EntityTreeElement;
@ -419,7 +420,9 @@ public:
void setSourceUUID(const QUuid& sourceUUID) { _sourceUUID = 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:

View file

@ -704,6 +704,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
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,

View file

@ -20,8 +20,8 @@
#include <OctreeRenderer.h> // for RenderArgs
class EntityItem;
typedef std::shared_ptr<EntityItem> EntityItemPointer;
typedef std::weak_ptr<EntityItem> EntityItemWeakPointer;
using EntityItemPointer = std::shared_ptr<EntityItem>;
using EntityItemWeakPointer = std::weak_ptr<EntityItem>;
inline uint qHash(const EntityItemPointer& a, uint 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++) {
// 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) {
// transform to joint-frame and save for later
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);
}
// 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);
for (int i = 0; i < geometry.joints.size(); ++i) {
FBXJoint& joint = geometry.joints[i];
// NOTE: points are in joint-frame
// compute average point
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) {
float minProjection = FLT_MAX;
float maxProjection = -FLT_MIN;
// compute average point
glm::vec3 avgPoint = glm::vec3(0.0f);
for (uint32_t j = 0; j < points.size(); ++j) {
glm::vec3 offset = points[j] - avgPoint;
float projection = glm::dot(offset, axis);
maxProjection = glm::max(maxProjection, projection);
minProjection = glm::min(minProjection, projection);
avgRadius += glm::length(offset - projection * axis);
avgPoint += points[j];
}
avgRadius /= (float)points.size();
avgPoint /= (float)points.size();
// compute endpoints of capsule in joint-frame
glm::vec3 capsuleBegin = avgPoint;
glm::vec3 capsuleEnd = avgPoint;
if (maxProjection - minProjection < 2.0f * avgRadius) {
// the mesh-as-cylinder approximation is too short to collide as a capsule
// so we'll collapse it to a sphere (although that isn't a very good approximation)
capsuleBegin = avgPoint + 0.5f * (maxProjection + minProjection) * axis;
capsuleEnd = capsuleBegin;
} else {
capsuleBegin = avgPoint + (minProjection + avgRadius) * axis;
capsuleEnd = avgPoint + (maxProjection - avgRadius) * axis;
// compute a k-Dop bounding volume
for (uint32_t j = 0; j < cardinalDirections.size(); ++j) {
float maxDot = -FLT_MAX;
float minDot = FLT_MIN;
for (uint32_t k = 0; k < points.size(); ++k) {
float kDot = glm::dot(cardinalDirections[j], points[k] - avgPoint);
if (kDot > maxDot) {
maxDot = kDot;
}
if (kDot < minDot) {
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());

View file

@ -56,7 +56,6 @@ public:
struct FBXJointShapeInfo {
// same units and frame as FBXJoint.translation
QVector<glm::vec3> points;
float radius;
};
/// 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);
}
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) {
// 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

View file

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

View file

@ -32,6 +32,7 @@
#include "udt/PacketHeaders.h"
#include "SharedUtil.h"
const int KEEPALIVE_PING_INTERVAL_MS = 1000;
NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short 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
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
startSTUNPublicSocketUpdate();
@ -632,3 +639,11 @@ void NodeList::activateSocketFromNodeCommunication(QSharedPointer<NLPacket> pack
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 processICEPingPacket(QSharedPointer<NLPacket> packet);
signals:
void limitOfSilentDomainCheckInsReached();
private slots:
@ -95,6 +96,8 @@ private slots:
void handleNodePingTimeout();
void pingPunchForDomainServer();
void sendKeepAlivePings();
private:
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
@ -118,6 +121,7 @@ private:
int _numNoReplyDomainCheckIns;
HifiSockAddr _assignmentServerSocket;
bool _isShuttingDown { false };
QTimer _keepAlivePingTimer;
};
#endif // hifi_NodeList_h

View file

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

View file

@ -8,6 +8,8 @@
#include "Deck.h"
#include <QtCore/QThread>
#include <NumericalConstants.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 MAX_FRAME_PROCESSING_TIME = Frame::secondsToFrameTime(0.002f);
static const Frame::Time MAX_FRAME_PROCESSING_TIME = Frame::secondsToFrameTime(0.004f);
void Deck::processFrames() {
if (qApp->thread() != QThread::currentThread()) {
qWarning() << "Processing frames must only happen on the main thread.";
return;
}
Locker lock(_mutex);
if (_pause) {
return;
@ -115,10 +121,17 @@ void Deck::processFrames() {
// 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
// ... the latter will work for Avatar, but not well for audio I suspect.
bool overLimit = false;
for (nextClip = getNextClip(); nextClip; nextClip = getNextClip()) {
auto currentPosition = Frame::frameTimeFromEpoch(_startEpoch);
if ((currentPosition - startingPosition) >= MAX_FRAME_PROCESSING_TIME) {
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;
}
@ -150,9 +163,19 @@ void Deck::processFrames() {
// If we have more clip frames available, set the timer for the next one
_position = Frame::frameTimeFromEpoch(_startEpoch);
auto nextFrameTime = nextClip->positionFrameTime();
auto interval = Frame::frameTimeToMilliseconds(nextFrameTime - _position);
_timer.singleShot(interval, [this] {
int nextInterval = 1;
if (!overLimit) {
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();
});
}

View file

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

View file

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

View file

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