mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 12:35:19 +02:00
resolve conflicts on merge with upstream master
This commit is contained in:
commit
cb708859fb
52 changed files with 864 additions and 492 deletions
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ private:
|
|||
|
||||
Assignment _requestAssignment;
|
||||
QPointer<ThreadedAssignment> _currentAssignment;
|
||||
bool _isAssigned { false };
|
||||
QString _assignmentServerHostname;
|
||||
HifiSockAddr _assignmentServerSocket;
|
||||
QTimer _requestTimer; // timer for requesting and assignment
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -35,8 +35,6 @@ private slots:
|
|||
void handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
||||
QHash<QString,QSet<QUuid>> _channelSubscribers;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
11
cmake/externals/openvr/CMakeLists.txt
vendored
11
cmake/externals/openvr/CMakeLists.txt
vendored
|
@ -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)
|
||||
|
||||
|
|
11
cmake/externals/sdl2/CMakeLists.txt
vendored
11
cmake/externals/sdl2/CMakeLists.txt
vendored
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
21
examples/entityScripts/createRecorder.js
Normal file
21
examples/entityScripts/createRecorder.js
Normal 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",
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
57
examples/example/securityCamera.js
Normal file
57
examples/example/securityCamera.js
Normal 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;
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -306,7 +306,6 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void clearDomainOctreeDetails();
|
||||
void ping();
|
||||
void idle(uint64_t now);
|
||||
void aboutToQuit();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -212,6 +212,7 @@ protected:
|
|||
}
|
||||
|
||||
private:
|
||||
void emitAudioPacket(const int16_t* audioData, PacketType packetType = PacketType::Unknown);
|
||||
void outputFormatChanged();
|
||||
|
||||
QByteArray firstInputFrame;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -228,7 +228,6 @@ public slots:
|
|||
|
||||
void startSTUNPublicSocketUpdate();
|
||||
virtual void sendSTUNRequest();
|
||||
void sendPingPackets();
|
||||
|
||||
bool killNodeWithUUID(const QUuid& nodeUUID);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -67,7 +67,6 @@ protected:
|
|||
EntityItemWeakPointer _ownerEntity;
|
||||
QString _tag;
|
||||
quint64 _expires { 0 }; // in seconds since epoch
|
||||
bool _active { false };
|
||||
|
||||
private:
|
||||
int getEntityServerClockSkew() const;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue