Merge branch 'master' into 21089

Conflicts:
	interface/src/Application.cpp
This commit is contained in:
David Rowe 2016-12-13 14:05:24 +13:00
commit 1f3045ad30
73 changed files with 21843 additions and 626 deletions

View file

@ -351,7 +351,9 @@ void Agent::executeScript() {
Transform audioTransform;
audioTransform.setTranslation(scriptedAvatar->getPosition());
audioTransform.setRotation(scriptedAvatar->getOrientation());
AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber, audioTransform, PacketType::MicrophoneAudioNoEcho);
AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber,
audioTransform, scriptedAvatar->getPosition(), glm::vec3(0),
PacketType::MicrophoneAudioNoEcho);
});
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
@ -424,8 +426,9 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
},
[&](const SharedNodePointer& node) {
qDebug() << "sending KillAvatar message to Audio Mixers";
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
packet->write(getSessionUUID().toRfc4122());
packet->writePrimitive(KillAvatarReason::NoReason);
nodeList->sendPacket(std::move(packet), *node);
});
@ -475,8 +478,9 @@ void Agent::setIsAvatar(bool isAvatar) {
},
[&](const SharedNodePointer& node) {
qDebug() << "sending KillAvatar message to Avatar and Audio Mixers";
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
packet->write(getSessionUUID().toRfc4122());
packet->writePrimitive(KillAvatarReason::NoReason);
nodeList->sendPacket(std::move(packet), *node);
});
}
@ -580,6 +584,8 @@ void Agent::processAgentAvatarAudio() {
audioPacket->writePrimitive(scriptedAvatar->getPosition());
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
audioPacket->writePrimitive(headOrientation);
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(glm::vec3(0));
} else if (nextSoundOutput) {
// write the codec
@ -592,6 +598,8 @@ void Agent::processAgentAvatarAudio() {
audioPacket->writePrimitive(scriptedAvatar->getPosition());
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
audioPacket->writePrimitive(headOrientation);
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(glm::vec3(0));
QByteArray encodedBuffer;
if (_flushEncoder) {

View file

@ -90,6 +90,8 @@ public:
bool shouldMuteClient() { return _shouldMuteClient; }
void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; }
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); }
signals:
void injectorStreamFinished(const QUuid& streamIdentifier);

View file

@ -211,20 +211,46 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
if (otherData
&& !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) {
// check if distance is inside ignore radius
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius());
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) {
// skip, distance is inside ignore radius
return;
// check to see if we're ignoring in radius
bool insideIgnoreRadius = false;
// If the otherNode equals the node, we're doing a comparison on ourselves
if (*otherNode == *node) {
// We'll always be inside the radius in that case.
insideIgnoreRadius = true;
// Check to see if the space bubble is enabled
} else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) && (*otherNode != *node)) {
// Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData());
// Set up the bounding box for the current node
AABox nodeBox(nodeData->getAvatarBoundingBoxCorner(), nodeData->getAvatarBoundingBoxScale());
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(nodeData->getAvatarBoundingBoxScale(), minBubbleSize))) {
nodeBox.setScaleStayCentered(minBubbleSize);
}
// Set up the bounding box for the current other node
AABox otherNodeBox(otherData->getAvatarBoundingBoxCorner(), otherData->getAvatarBoundingBoxScale());
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(otherData->getAvatarBoundingBoxScale(), minBubbleSize))) {
otherNodeBox.setScaleStayCentered(minBubbleSize);
}
// Quadruple the scale of both bounding boxes
nodeBox.embiggen(4.0f);
otherNodeBox.embiggen(4.0f);
// Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) {
insideIgnoreRadius = true;
}
}
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
// Enumerate the audio streams attached to the otherNode
auto streamsCopy = otherData->getAudioStreams();
for (auto& streamPair : streamsCopy) {
auto otherNodeStream = streamPair.second;
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
bool isSelfWithEcho = ((*otherNode == *node) && (otherNodeStream->shouldLoopbackForNode()));
// Add all audio streams that should be added to the mix
if (isSelfWithEcho || (!isSelfWithEcho && !insideIgnoreRadius)) {
addStreamToMix(*nodeData, otherNode->getUUID(), *nodeAudioStream, *otherNodeStream);
}
}

View file

@ -19,6 +19,7 @@
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <AABox.h>
#include <LogHandler.h>
#include <NodeList.h>
#include <udt/PacketHeaders.h>
@ -240,18 +241,39 @@ void AvatarMixer::broadcastAvatarData() {
} else {
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
// check to see if we're ignoring in radius
// Check to see if the space bubble is enabled
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius());
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) {
// Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
// Define the scale of the box for the current node
glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f;
// Define the scale of the box for the current other node
glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f;
// Set up the bounding box for the current node
AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) {
nodeBox.setScaleStayCentered(minBubbleSize);
}
// Set up the bounding box for the current other node
AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
otherNodeBox.setScaleStayCentered(minBubbleSize);
}
// Quadruple the scale of both bounding boxes
nodeBox.embiggen(4.0f);
otherNodeBox.embiggen(4.0f);
// Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) {
nodeData->ignoreOther(node, otherNode);
otherData->ignoreOther(otherNode, node);
return false;
}
}
// not close enough to ignore
// Not close enough to ignore
nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID());
otherData->removeFromRadiusIgnoringSet(node->getUUID());
return true;
}
},
@ -395,8 +417,9 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID);
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
killPacket->write(killedNode->getUUID().toRfc4122());
killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
nodeList->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::Agent);

View file

@ -45,8 +45,13 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node
void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) {
if (!isRadiusIgnoring(other->getUUID())) {
addToRadiusIgnoringSet(other->getUUID());
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID);
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
killPacket->write(other->getUUID().toRfc4122());
if (self->isIgnoreRadiusEnabled()) {
killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble);
} else {
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
}
DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
_hasReceivedFirstPacketsFrom.erase(other->getUUID());
}

View file

@ -81,6 +81,7 @@ public:
void loadJSONStats(QJsonObject& jsonObject) const;
glm::vec3 getPosition() { return _avatar ? _avatar->getPosition() : glm::vec3(0); }
glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); }

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi4.zip
URL_MD5 2abde5340a64d387848f12b9536a7e85
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
URL_MD5 0530753e855ffc00232cc969bf1c84a8
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -43,26 +43,24 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
)
set(QTAUDIO_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audio)
set(QTAUDIO_WIN7_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audioWin7/audio)
set(QTAUDIO_WIN8_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audioWin8/audio)
if (DEPLOY_PACKAGE)
# copy qtaudio_wasapi.dll alongside qtaudio_windows.dll, and let the installer resolve
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
)
elseif (${CMAKE_SYSTEM_VERSION} VERSION_LESS 6.2)
# continue using qtaudio_windows.dll on Windows 7
else ()
# replace qtaudio_windows.dll with qtaudio_wasapi.dll on Windows 8/8.1/10
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windows.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windowsd.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
)
endif ()
# copy qtaudio_wasapi.dll and qtaudio_windows.dll in the correct directories for runtime selection
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN7_PATH}
COMMAND ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN8_PATH}
# copy release DLLs
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windows.dll ${QTAUDIO_WIN7_PATH} )
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_WIN8_PATH} )
# copy debug DLLs
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windowsd.dll ${QTAUDIO_WIN7_PATH} )
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_WIN8_PATH} )
# remove directory
COMMAND ${CMAKE_COMMAND} -E remove_directory ${QTAUDIO_PATH}
)
endif ()
endmacro()

View file

@ -630,17 +630,6 @@ Section "-Core installation"
Delete "$INSTDIR\version"
Delete "$INSTDIR\xinput1_3.dll"
; The installer includes two different Qt audio plugins.
; On Windows 8 and above, only qtaudio_wasapi.dll should be installed.
; On Windows 7 and below, only qtaudio_windows.dll should be installed.
${If} ${AtLeastWin8}
Delete "$INSTDIR\audio\qtaudio_windows.dll"
Delete "$INSTDIR\audio\qtaudio_windows.pdb"
${Else}
Delete "$INSTDIR\audio\qtaudio_wasapi.dll"
Delete "$INSTDIR\audio\qtaudio_wasapi.pdb"
${EndIf}
; Delete old desktop shortcuts before they were renamed during Sandbox rename
Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"

View file

@ -44,8 +44,8 @@
{ "from": "GamePad.RB", "to": "Standard.RB" },
{ "from": "GamePad.RS", "to": "Standard.RS" },
{ "from": "GamePad.Start", "to": "Actions.CycleCamera" },
{ "from": "GamePad.Back", "to": "Standard.Start" },
{ "from": "GamePad.Start", "to": "Standard.Start" },
{ "from": "GamePad.Back", "to": "Actions.CycleCamera" },
{ "from": "GamePad.DU", "to": "Standard.DU" },
{ "from": "GamePad.DD", "to": "Standard.DD" },

View file

@ -40,7 +40,6 @@ ModalWindow {
Loader {
id: bodyLoader
anchors.fill: parent
source: loginDialog.isSteamRunning() ? "LoginDialog/SignInBody.qml" : "LoginDialog/LinkAccountBody.qml"
}
}

View file

@ -18,8 +18,8 @@ import "../styles-uit"
Item {
id: completeProfileBody
clip: true
width: pane.width
height: pane.height
width: root.pane.width
height: root.pane.height
QtObject {
id: d
@ -33,8 +33,8 @@ Item {
termsContainer.contentWidth))
var targetHeight = 5 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height + termsContainer.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
}
}

View file

@ -19,11 +19,13 @@ import "../styles-uit"
Item {
id: linkAccountBody
clip: true
width: root.pane.width
height: root.pane.height
width: root.pane.width
property bool failAfterSignUp: false
function login() {
mainTextContainer.visible = false
toggleLoading(true)
loginDialog.login(usernameField.text, passwordField.text)
}
@ -51,12 +53,40 @@ Item {
targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height
}
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y);
}
}
function toggleLoading(isLoading) {
linkAccountSpinner.visible = isLoading
form.visible = !isLoading
if (loginDialog.isSteamRunning()) {
additionalInformation.visible = !isLoading
}
leftButton.visible = !isLoading
buttons.visible = !isLoading
}
BusyIndicator {
id: linkAccountSpinner
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
running: true
width: 48
height: 48
}
ShortcutText {
id: mainTextContainer
anchors {
@ -96,7 +126,7 @@ Item {
}
width: 350
label: "User Name or Email"
label: "Username or Email"
}
ShortcutText {
@ -108,6 +138,7 @@ Item {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link)
}
@ -135,6 +166,7 @@ Item {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link)
}
@ -173,6 +205,31 @@ Item {
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Sign Up")
visible: !loginDialog.isSteamRunning()
onClicked: {
bodyLoader.setSource("SignUpBody.qml")
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
Row {
id: buttons
anchors {
@ -209,6 +266,11 @@ Item {
keyboardEnabled = HMD.active;
d.resize();
if (failAfterSignUp) {
mainTextContainer.text = "Account created successfully."
mainTextContainer.visible = true
}
usernameField.forceActiveFocus();
}
@ -228,6 +290,7 @@ Item {
onHandleLoginFailed: {
console.log("Login Failed")
mainTextContainer.visible = true
toggleLoading(false)
}
onHandleLinkCompleted: {
console.log("Link Succeeded")
@ -238,7 +301,7 @@ Item {
}
onHandleLinkFailed: {
console.log("Link Failed")
toggleLoading(false)
}
}

View file

@ -18,8 +18,8 @@ import "../styles-uit"
Item {
id: signInBody
clip: true
width: pane.width
height: pane.height
width: root.pane.width
height: root.pane.height
property bool required: false
@ -43,8 +43,8 @@ Item {
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
}
}

View file

@ -0,0 +1,296 @@
//
// SignUpBody.qml
//
// Created by Stephen Birarda on 7 Dec 2016
// Copyright 2016 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
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: signupBody
clip: true
height: root.pane.height
width: root.pane.width
function signup() {
mainTextContainer.visible = false
toggleLoading(true)
loginDialog.signup(emailField.text, usernameField.text, passwordField.text)
}
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
QtObject {
id: d
readonly property int minWidth: 480
readonly property int maxWidth: 1280
readonly property int minHeight: 120
readonly property int maxHeight: 720
function resize() {
var targetWidth = Math.max(titleWidth, form.contentWidth);
var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height +
4 * hifi.dimensions.contentSpacing.y + form.height +
hifi.dimensions.contentSpacing.y + buttons.height;
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : 0);
}
}
function toggleLoading(isLoading) {
linkAccountSpinner.visible = isLoading
form.visible = !isLoading
leftButton.visible = !isLoading
buttons.visible = !isLoading
}
BusyIndicator {
id: linkAccountSpinner
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
running: true
width: 48
height: 48
}
ShortcutText {
id: mainTextContainer
anchors {
top: parent.top
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
text: qsTr("There was an unknown error while creating your account.")
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
horizontalAlignment: Text.AlignLeft
}
Column {
id: form
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: emailField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Email"
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: usernameField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Username"
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: qsTr("No spaces / special chars.")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.blueAccent
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: passwordField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Password"
echoMode: TextInput.Password
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: qsTr("At least 6 characters")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.blueAccent
}
}
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
Keyboard {
raised: keyboardEnabled && keyboardRaised
numeric: punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Existing User")
onClicked: {
bodyLoader.setSource("LinkAccountBody.qml")
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
Row {
id: buttons
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Sign Up")
color: hifi.buttons.blue
onClicked: signupBody.signup()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.destroy()
}
}
Component.onCompleted: {
root.title = qsTr("Create an Account")
root.iconText = "<"
keyboardEnabled = HMD.active;
d.resize();
emailField.forceActiveFocus();
}
Connections {
target: loginDialog
onHandleSignupCompleted: {
console.log("Sign Up Succeeded");
// now that we have an account, login with that username and password
loginDialog.login(usernameField.text, passwordField.text)
}
onHandleSignupFailed: {
console.log("Sign Up Failed")
toggleLoading(false)
mainTextContainer.text = errorString
mainTextContainer.visible = true
d.resize();
}
onHandleLoginCompleted: {
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack": false })
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
onHandleLoginFailed: {
// we failed to login, show the LoginDialog so the user will try again
bodyLoader.setSource("LinkAccountBody.qml", { "failAfterSignUp": true })
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
Keys.onPressed: {
if (!visible) {
return
}
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
signupBody.signup()
break
}
}
}

View file

@ -47,11 +47,9 @@ Item {
hifi.dimensions.contentSpacing.y + textField.height +
hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y)
height = root.height
}
}

View file

@ -17,8 +17,8 @@ import "../styles-uit"
Item {
id: welcomeBody
clip: true
width: pane.width
height: pane.height
width: root.pane.width
height: root.pane.height
property bool welcomeBack: false
@ -39,8 +39,8 @@ Item {
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
}
}

View file

@ -76,7 +76,7 @@ OriginalDesktop.Desktop {
WebEngine.settings.localContentCanAccessRemoteUrls = true;
[ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts.
"hmdToggle", "mute", "mod", "help",
"hmdToggle", "mute", "mod", "bubble", "help",
"hudToggle",
"com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto"
].forEach(function (name) {

View file

@ -134,6 +134,7 @@
#include "LODManager.h"
#include "ModelPackager.h"
#include "networking/HFWebEngineProfile.h"
#include "scripting/TestScriptingInterface.h"
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AssetMappingsScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h"
@ -172,6 +173,8 @@
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}
@ -422,6 +425,16 @@ bool setupEssentials(int& argc, char** argv) {
Setting::preInit();
#if defined(Q_OS_WIN)
// Select appropriate audio DLL
QString audioDLLPath = QCoreApplication::applicationDirPath();
if (IsWindows8OrGreater()) {
audioDLLPath += "/audioWin8";
} else {
audioDLLPath += "/audioWin7";
}
QCoreApplication::addLibraryPath(audioDLLPath);
#endif
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
@ -545,6 +558,20 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
setProperty(hifi::properties::STEAM, SteamClient::isRunning());
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
{
const QString TEST_SCRIPT = "--testScript";
const QStringList args = arguments();
for (int i = 0; i < args.size() - 1; ++i) {
if (args.at(i) == TEST_SCRIPT) {
QString testScriptPath = args.at(i + 1);
if (QFileInfo(testScriptPath).exists()) {
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
}
}
}
}
_runningMarker.startRunningMarker();
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
@ -1353,95 +1380,101 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return entityServerNode && !isPhysicsEnabled();
});
QVariant testProperty = property(hifi::properties::TEST);
qDebug() << testProperty;
if (testProperty.isValid()) {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
const auto testScript = property(hifi::properties::TEST).toUrl();
scriptEngines->loadScript(testScript, false);
} else {
// Get sandbox content set version, if available
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
auto contentVersionPath = acDirPath + "content-version.txt";
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
auto contentVersion = 0;
QFile contentVersionFile(contentVersionPath);
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString line = contentVersionFile.readAll();
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
contentVersion = line.toInt();
}
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
bool hasTutorialContent = contentVersion >= 1;
// Get sandbox content set version, if available
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
auto contentVersionPath = acDirPath + "content-version.txt";
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
auto contentVersion = 0;
QFile contentVersionFile(contentVersionPath);
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString line = contentVersionFile.readAll();
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
contentVersion = line.toInt();
}
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
Setting::Handle<bool> firstRun { Settings::firstRun, true };
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
bool hasTutorialContent = contentVersion >= 1;
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
Setting::Handle<bool> firstRun { Settings::firstRun, true };
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString;
if (urlIndex != -1) {
addressLookupString = arguments().value(urlIndex + 1);
}
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
const QString TUTORIAL_PATH = "/tutorial_begin";
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString;
if (urlIndex != -1) {
addressLookupString = arguments().value(urlIndex + 1);
}
const QString TUTORIAL_PATH = "/tutorial_begin";
if (shouldGoToTutorial) {
if(sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
if (shouldGoToTutorial) {
if (sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
} else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
if (firstRun.get()) {
showHelp();
}
if (addressLookupString.isEmpty()) {
DependencyManager::get<AddressManager>()->goToEntry();
} else {
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
}
} else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
if (firstRun.get()) {
bool isFirstRun = firstRun.get();
if (isFirstRun) {
showHelp();
}
if (addressLookupString.isEmpty()) {
DependencyManager::get<AddressManager>()->goToEntry();
} else {
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
}
} else {
bool isFirstRun = firstRun.get();
if (isFirstRun) {
showHelp();
}
// If this is a first run we short-circuit the address passed in
if (isFirstRun) {
if (hasHMDAndHandControllers) {
if(sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
// If this is a first run we short-circuit the address passed in
if (isFirstRun) {
if (hasHMDAndHandControllers) {
if (sandboxIsRunning) {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
} else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry();
}
} else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry();
}
} else {
DependencyManager::get<AddressManager>()->goToEntry();
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
} else {
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
}
_connectionMonitor.init();
_connectionMonitor.init();
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
_addAssetToWorldTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS);
connect(&_addAssetToWorldTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize);
// After all of the constructor is completed, then set firstRun to false.
firstRun.set(false);
// After all of the constructor is completed, then set firstRun to false.
firstRun.set(false);
}
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
@ -2567,6 +2600,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
break;
case Qt::Key_N:
if (!isOption && !isShifted && isMeta) {
DependencyManager::get<NodeList>()->toggleIgnoreRadius();
}
break;
case Qt::Key_S:
if (isShifted && isMeta && !isOption) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
@ -4851,7 +4890,7 @@ void Application::resetSensors(bool andReload) {
DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors();
_overlayConductor.centerUI();
getMyAvatar()->reset(andReload);
getMyAvatar()->reset(true, andReload);
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
}
@ -5156,6 +5195,11 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine);
if (property(hifi::properties::TEST).isValid()) {
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
}
scriptEngine->registerGlobalObject("Overlays", &_overlays);
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
// hook our avatar and avatar hash map object into this script engine
@ -6113,6 +6157,7 @@ void Application::updateDisplayMode() {
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
resizeGL();
});
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
first = false;
}

View file

@ -119,7 +119,7 @@ namespace MenuOption {
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools";
const QString Login = "Login";
const QString Login = "Login / Sign Up";
const QString Log = "Log";
const QString LogExtraTimings = "Log Extra Timing Details";
const QString LowVelocityFilter = "Low Velocity Filter";

View file

@ -80,7 +80,7 @@ AvatarManager::AvatarManager(QObject* parent) :
// when we hear that the user has ignored an avatar by session UUID
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
connect(nodeList.data(), &NodeList::ignoredNode, this, &AvatarManager::removeAvatar);
connect(nodeList.data(), "ignoredNode", this, "removeAvatar");
}
AvatarManager::~AvatarManager() {
@ -223,16 +223,16 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
}
// virtual
void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
handleRemovedAvatar(removedAvatar);
handleRemovedAvatar(removedAvatar, removalReason);
}
}
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
AvatarHashMap::handleRemovedAvatar(removedAvatar);
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
@ -247,6 +247,9 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
_motionStatesToRemoveFromPhysics.push_back(motionState);
}
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
}
_avatarFades.push_back(removedAvatar);
}

View file

@ -79,7 +79,7 @@ public slots:
void updateAvatarRenderStatus(bool shouldRenderAvatars);
private slots:
virtual void removeAvatar(const QUuid& sessionUUID) override;
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
private:
explicit AvatarManager(QObject* parent = 0);
@ -91,7 +91,7 @@ private:
virtual AvatarSharedPointer newSharedAvatar() override;
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) override;
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
QVector<AvatarSharedPointer> _avatarFades;
std::shared_ptr<MyAvatar> _myAvatar;

View file

@ -230,6 +230,10 @@ void MyAvatar::simulateAttachments(float deltaTime) {
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
CameraMode mode = qApp->getCamera()->getMode();
_globalPosition = getPosition();
_globalBoundingBoxCorner.x = _characterController.getCapsuleRadius();
_globalBoundingBoxCorner.y = _characterController.getCapsuleHalfHeight();
_globalBoundingBoxCorner.z = _characterController.getCapsuleRadius();
_globalBoundingBoxCorner += _characterController.getCapsuleLocalOffset();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
// fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getPosition();
@ -360,10 +364,18 @@ void MyAvatar::update(float deltaTime) {
updateFromTrackers(deltaTime);
// Get audio loudness data from audio input device
// Also get the AudioClient so we can update the avatar bounding box data
// on the AudioClient side.
auto audio = DependencyManager::get<AudioClient>();
head->setAudioLoudness(audio->getLastInputLoudness());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius());
halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
if (_avatarEntityDataLocallyEdited) {
sendIdentityPacket();
}

View file

@ -0,0 +1,30 @@
//
// Created by Bradley Austin Davis on 2016/12/12
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestScriptingInterface.h"
#include <QtCore/QCoreApplication>
TestScriptingInterface* TestScriptingInterface::getInstance() {
static TestScriptingInterface sharedInstance;
return &sharedInstance;
}
void TestScriptingInterface::quit() {
qApp->quit();
}
void TestScriptingInterface::waitForTextureIdle() {
}
void TestScriptingInterface::waitForDownloadIdle() {
}
void TestScriptingInterface::waitIdle() {
}

View file

@ -0,0 +1,43 @@
//
// Created by Bradley Austin Davis on 2016/12/12
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_TestScriptingInterface_h
#define hifi_TestScriptingInterface_h
#include <QtCore/QObject>
class TestScriptingInterface : public QObject {
Q_OBJECT
public slots:
static TestScriptingInterface* getInstance();
/**jsdoc
* Exits the application
*/
void quit();
/**jsdoc
* Waits for all texture transfers to be complete
*/
void waitForTextureIdle();
/**jsdoc
* Waits for all pending downloads to be complete
*/
void waitForDownloadIdle();
/**jsdoc
* Waits for all pending downloads and texture transfers to be complete
*/
void waitIdle();
};
#endif // hifi_TestScriptingInterface_h

View file

@ -11,9 +11,10 @@
#include "LoginDialog.h"
#include <QDesktopServices>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QtGui/QDesktopServices>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
#include <NetworkingConstants.h>
#include <steamworks-wrapper/SteamClient.h>
@ -47,7 +48,7 @@ void LoginDialog::toggleAction() {
connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout);
} else {
// change the menu item to login
loginAction->setText("Login");
loginAction->setText("Login / Sign Up");
connection = connect(loginAction, &QAction::triggered, [] {
LoginDialog::show();
});
@ -153,3 +154,83 @@ void LoginDialog::createFailed(QNetworkReply& reply) {
emit handleCreateFailed(reply.errorString());
}
void LoginDialog::signup(const QString& email, const QString& username, const QString& password) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "signupCompleted";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "signupFailed";
QJsonObject payload;
QJsonObject userObject;
userObject.insert("email", email);
userObject.insert("username", username);
userObject.insert("password", password);
payload.insert("user", userObject);
static const QString API_SIGNUP_PATH = "api/v1/users";
qDebug() << "Sending a request to create an account for" << username;
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
}
void LoginDialog::signupCompleted(QNetworkReply& reply) {
emit handleSignupCompleted();
}
QString errorStringFromAPIObject(const QJsonValue& apiObject) {
if (apiObject.isArray()) {
return apiObject.toArray()[0].toString();
} else if (apiObject.isString()) {
return apiObject.toString();
} else {
return "is invalid";
}
}
void LoginDialog::signupFailed(QNetworkReply& reply) {
// parse the returned JSON to see what the problem was
auto jsonResponse = QJsonDocument::fromJson(reply.readAll());
static const QString RESPONSE_DATA_KEY = "data";
auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY];
if (dataJsonValue.isObject()) {
auto dataObject = dataJsonValue.toObject();
static const QString EMAIL_DATA_KEY = "email";
static const QString USERNAME_DATA_KEY = "username";
static const QString PASSWORD_DATA_KEY = "password";
QStringList errorStringList;
if (dataObject.contains(EMAIL_DATA_KEY)) {
errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY])));
}
if (dataObject.contains(USERNAME_DATA_KEY)) {
errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY])));
}
if (dataObject.contains(PASSWORD_DATA_KEY)) {
errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY])));
}
emit handleSignupFailed(errorStringList.join('\n'));
} else {
static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later.";
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
}

View file

@ -36,6 +36,9 @@ signals:
void handleCreateCompleted();
void handleCreateFailed(QString error);
void handleSignupCompleted();
void handleSignupFailed(QString errorString);
public slots:
void linkCompleted(QNetworkReply& reply);
@ -43,6 +46,9 @@ public slots:
void createCompleted(QNetworkReply& reply);
void createFailed(QNetworkReply& reply);
void signupCompleted(QNetworkReply& reply);
void signupFailed(QNetworkReply& reply);
protected slots:
Q_INVOKABLE bool isSteamRunning() const;
@ -51,6 +57,8 @@ protected slots:
Q_INVOKABLE void loginThroughSteam();
Q_INVOKABLE void linkSteam();
Q_INVOKABLE void createAccountFromStream(QString username = QString());
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE void openUrl(const QString& url) const;

View file

@ -68,18 +68,6 @@ void setupPreferences() {
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
}
{
auto getter = [=]()->float { return nodeList->getIgnoreRadius(); };
auto setter = [=](float value) {
nodeList->ignoreNodesInRadius(value, nodeList->getIgnoreRadiusEnabled());
};
auto preference = new SpinnerPreference(AVATAR_BASICS, "Personal space bubble radius (default is 1m)", getter, setter);
preference->setMin(0.01f);
preference->setMax(99.9f);
preference->setDecimals(2);
preference->setStep(0.25);
preferences->addPreference(preference);
}
// UI
{

View file

@ -1063,7 +1063,9 @@ void AudioClient::handleAudioInput() {
encodedBuffer = decodedBuffer;
}
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType, _selectedCodecName);
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber,
audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale,
packetType, _selectedCodecName);
_stats.sentPacket();
int bytesInInputRingBuffer = _inputRingBuffer.samplesAvailable() * AudioConstants::SAMPLE_SIZE;
@ -1085,7 +1087,9 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
}
// FIXME check a flag to see if we should echo audio?
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber,
audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale,
PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
}
void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
@ -1098,13 +1102,37 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
if (injector->getLocalBuffer()) {
qint64 samplesToRead = injector->isStereo() ? AudioConstants::NETWORK_FRAME_BYTES_STEREO : AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
static const int HRTF_DATASET_INDEX = 1;
// get one frame from the injector (mono or stereo)
memset(_scratchBuffer, 0, sizeof(_scratchBuffer));
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) {
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
// get one frame from the injector
memset(_scratchBuffer, 0, bytesToRead);
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, bytesToRead)) {
if (injector->isStereo()) {
if (injector->isAmbisonic()) {
// no distance attenuation
float gain = injector->getVolume();
//
// Calculate the soundfield orientation relative to the listener.
// Injector orientation can be used to align a recording to our world coordinates.
//
glm::quat relativeOrientation = injector->getOrientation() * glm::inverse(_orientationGetter());
// convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system
float qw = relativeOrientation.w;
float qx = -relativeOrientation.z;
float qy = -relativeOrientation.x;
float qz = relativeOrientation.y;
// Ambisonic gets spatialized into mixBuffer
injector->getLocalFOA().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else if (injector->isStereo()) {
// stereo gets directly mixed into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
@ -1120,7 +1148,8 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
float azimuth = azimuthForSource(relativePosition);
// mono gets spatialized into mixBuffer
injector->getLocalHRTF().render(_scratchBuffer, mixBuffer, 1, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
injector->getLocalHRTF().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
}
} else {
@ -1225,8 +1254,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
}
}
bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
Lock lock(_injectorsMutex);
if (injector->getLocalBuffer() && _audioInput ) {
// just add it to the vector of active local injectors, if
@ -1595,3 +1623,8 @@ void AudioClient::saveSettings() {
dynamicJitterBufferEnabled.set(_receivedAudioStream.dynamicJitterBufferEnabled());
staticJitterBufferFrames.set(_receivedAudioStream.getStaticJitterBufferFrames());
}
void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale) {
avatarBoundingBoxCorner = corner;
avatarBoundingBoxScale = scale;
}

View file

@ -127,6 +127,8 @@ public:
void setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; }
void checkDevices();
@ -169,7 +171,7 @@ public slots:
int setOutputBufferSize(int numFrames, bool persist = true);
bool outputLocalInjector(bool isStereo, AudioInjector* injector) override;
bool outputLocalInjector(AudioInjector* injector) override;
bool shouldLoopbackInjectors() override { return _shouldEchoToServer; }
bool switchInputToAudioDevice(const QString& inputDeviceName);
@ -297,7 +299,7 @@ private:
// for local hrtf-ing
float _mixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
AudioLimiter _audioLimiter;
// Adds Reverb
@ -324,6 +326,9 @@ private:
AudioPositionGetter _positionGetter;
AudioOrientationGetter _orientationGetter;
glm::vec3 avatarBoundingBoxCorner;
glm::vec3 avatarBoundingBoxScale;
QVector<QString> _inputDevices;
QVector<QString> _outputDevices;

View file

@ -19,8 +19,9 @@
#include "AudioConstants.h"
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
const Transform& transform, PacketType packetType, QString codecName) {
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale,
PacketType packetType, QString codecName) {
static std::mutex _mutex;
using Locker = std::unique_lock<std::mutex>;
auto nodeList = DependencyManager::get<NodeList>();
@ -55,6 +56,10 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes
// pack the orientation
audioPacket->writePrimitive(transform.getRotation());
audioPacket->writePrimitive(avatarBoundingBoxCorner);
audioPacket->writePrimitive(avatarBoundingBoxScale);
if (audioPacket->getType() != PacketType::SilentAudioFrame) {
// audio samples have already been packed (written to networkAudioSamples)
int leadingBytes = audioPacket->getPayloadSize();

View file

@ -28,11 +28,12 @@ class AbstractAudioInterface : public QObject {
public:
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {};
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform,
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale,
PacketType packetType, QString codecName = QString(""));
public slots:
virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0;
virtual bool outputLocalInjector(AudioInjector* injector) = 0;
virtual bool shouldLoopbackInjectors() { return false; }
virtual void setIsStereoInput(bool stereo) = 0;

View file

@ -20,7 +20,7 @@ namespace AudioConstants {
const int SAMPLE_RATE = 24000;
const int MONO = 1;
const int STEREO = 2;
const int AMBISONIC = 4;
typedef int16_t AudioSample;
const int SAMPLE_SIZE = sizeof(AudioSample);
@ -33,6 +33,7 @@ namespace AudioConstants {
const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / SAMPLE_SIZE;
const int NETWORK_FRAME_BYTES_PER_CHANNEL = NETWORK_FRAME_BYTES_STEREO / 2;
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / SAMPLE_SIZE;
const int NETWORK_FRAME_SAMPLES_AMBISONIC = NETWORK_FRAME_SAMPLES_PER_CHANNEL * AMBISONIC;
const float NETWORK_FRAME_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE));
const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f;
const float NETWORK_FRAMES_PER_SEC = 1.0f / NETWORK_FRAME_SECS;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
//
// AudioFOA.h
// libraries/audio/src
//
// Created by Ken Cooke on 10/28/16.
// Copyright 2016 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
//
#ifndef hifi_AudioFOA_h
#define hifi_AudioFOA_h
#include <stdint.h>
static const int FOA_TAPS = 273; // FIR coefs
static const int FOA_NFFT = 512; // FFT length
static const int FOA_OVERLAP = FOA_TAPS - 1;
static const int FOA_TABLES = 25; // number of HRTF subjects
static const int FOA_BLOCK = 240; // block processing size
static const float FOA_GAIN = 1.0f; // FOA global gain adjustment
static_assert((FOA_BLOCK + FOA_OVERLAP) == FOA_NFFT, "FFT convolution requires L+M-1 == NFFT");
class AudioFOA {
public:
AudioFOA() {
// identity matrix
_rotationState[0][0] = 1.0f;
_rotationState[1][1] = 1.0f;
_rotationState[2][2] = 1.0f;
};
//
// input: interleaved First-Order Ambisonic source
// output: interleaved stereo mix buffer (accumulates into existing output)
// index: HRTF subject index
// qw, qx, qy, qz: normalized quaternion for orientation
// gain: gain factor for volume control
// numFrames: must be FOA_BLOCK in this version
//
void render(int16_t* input, float* output, int index, float qw, float qx, float qy, float qz, float gain, int numFrames);
private:
AudioFOA(const AudioFOA&) = delete;
AudioFOA& operator=(const AudioFOA&) = delete;
// For best cache utilization when processing thousands of instances, only
// the minimum persistant state is stored here. No coefs or work buffers.
// input history, for overlap-save
float _fftState[4][FOA_OVERLAP] = {};
// orientation history
float _rotationState[3][3] = {};
};
#endif // AudioFOA_h

File diff suppressed because it is too large Load diff

View file

@ -58,8 +58,10 @@ void AudioInjector::setOptions(const AudioInjectorOptions& options) {
// since options.stereo is computed from the audio stream,
// we need to copy it from existing options just in case.
bool currentlyStereo = _options.stereo;
bool currentlyAmbisonic = _options.ambisonic;
_options = options;
_options.stereo = currentlyStereo;
_options.ambisonic = currentlyAmbisonic;
}
void AudioInjector::finishNetworkInjection() {
@ -134,7 +136,8 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*
int byteOffset = 0;
if (_options.secondOffset > 0.0f) {
byteOffset = (int)floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f));
int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1);
byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels);
byteOffset *= sizeof(AudioConstants::SAMPLE_SIZE);
}
_currentSendOffset = byteOffset;
@ -169,7 +172,7 @@ bool AudioInjector::injectLocally() {
_localBuffer->setCurrentOffset(_currentSendOffset);
// call this function on the AudioClient's thread
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(bool, _options.stereo), Q_ARG(AudioInjector*, this));
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this));
if (!success) {
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";

View file

@ -27,6 +27,7 @@
#include "AudioInjectorLocalBuffer.h"
#include "AudioInjectorOptions.h"
#include "AudioHRTF.h"
#include "AudioFOA.h"
#include "Sound.h"
class AbstractAudioInterface;
@ -59,11 +60,14 @@ public:
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
AudioHRTF& getLocalHRTF() { return _localHRTF; }
AudioFOA& getLocalFOA() { return _localFOA; }
bool isLocalOnly() const { return _options.localOnly; }
float getVolume() const { return _options.volume; }
glm::vec3 getPosition() const { return _options.position; }
glm::quat getOrientation() const { return _options.orientation; }
bool isStereo() const { return _options.stereo; }
bool isAmbisonic() const { return _options.ambisonic; }
bool stateHas(AudioInjectorState state) const ;
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
@ -113,6 +117,7 @@ private:
// when the injector is local, we need this
AudioHRTF _localHRTF;
AudioFOA _localFOA;
friend class AudioInjectorManager;
};

View file

@ -19,6 +19,7 @@ AudioInjectorOptions::AudioInjectorOptions() :
loop(false),
orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
stereo(false),
ambisonic(false),
ignorePenumbra(false),
localOnly(false),
secondOffset(0.0)

View file

@ -25,6 +25,7 @@ public:
bool loop;
glm::quat orientation;
bool stereo;
bool ambisonic;
bool ignorePenumbra;
bool localOnly;
float secondOffset;

View file

@ -77,6 +77,8 @@ int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteA
packetStream.readRawData(reinterpret_cast<char*>(&_position), sizeof(_position));
packetStream.readRawData(reinterpret_cast<char*>(&_orientation), sizeof(_orientation));
packetStream.readRawData(reinterpret_cast<char*>(&_avatarBoundingBoxCorner), sizeof(_avatarBoundingBoxCorner));
packetStream.readRawData(reinterpret_cast<char*>(&_avatarBoundingBoxScale), sizeof(_avatarBoundingBoxScale));
// if this node sent us a NaN for first float in orientation then don't consider this good audio and bail
if (glm::isnan(_orientation.x)) {

View file

@ -46,6 +46,8 @@ public:
PositionalAudioStream::Type getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; }
const glm::quat& getOrientation() const { return _orientation; }
const glm::vec3& getAvatarBoundingBoxCorner() const { return _avatarBoundingBoxCorner; }
const glm::vec3& getAvatarBoundingBoxScale() const { return _avatarBoundingBoxScale; }
protected:
@ -60,6 +62,9 @@ protected:
glm::vec3 _position;
glm::quat _orientation;
glm::vec3 _avatarBoundingBoxCorner;
glm::vec3 _avatarBoundingBoxScale;
bool _shouldLoopbackForNode;
bool _isStereo;
// Ignore penumbra filter

View file

@ -43,9 +43,10 @@ SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _so
QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
}
Sound::Sound(const QUrl& url, bool isStereo) :
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
Resource(url),
_isStereo(isStereo),
_isAmbisonic(isAmbisonic),
_isReady(false)
{
@ -62,8 +63,10 @@ void Sound::downloadFinished(const QByteArray& data) {
QByteArray outputAudioByteArray;
interpretAsWav(rawAudioByteArray, outputAudioByteArray);
downSample(outputAudioByteArray);
int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray);
if (sampleRate != 0) {
downSample(outputAudioByteArray, sampleRate);
}
} else if (fileName.endsWith(RAW_EXTENSION)) {
// check if this was a stereo raw file
// since it's raw the only way for us to know that is if the file was called .stereo.raw
@ -72,8 +75,8 @@ void Sound::downloadFinished(const QByteArray& data) {
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
}
// Process as RAW file
downSample(rawAudioByteArray);
// Process as 48khz RAW file
downSample(rawAudioByteArray, 48000);
} else {
qCDebug(audio) << "Unknown sound file type";
}
@ -84,29 +87,80 @@ void Sound::downloadFinished(const QByteArray& data) {
emit ready();
}
void Sound::downSample(const QByteArray& rawAudioByteArray) {
// assume that this was a RAW file and is now an array of samples that are
// signed, 16-bit, 48Khz
void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
// we want to convert it to the format that the audio-mixer wants
// which is signed, 16-bit, 24Khz
int numChannels = _isStereo ? 2 : 1;
AudioSRC resampler(48000, AudioConstants::SAMPLE_RATE, numChannels);
if (sampleRate == AudioConstants::SAMPLE_RATE) {
// resize to max possible output
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(maxDestinationBytes);
// no resampling needed
_byteArray = rawAudioByteArray;
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
(int16_t*)_byteArray.data(),
numSourceFrames);
} else if (_isAmbisonic) {
// truncate to actual output
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(numDestinationBytes);
// FIXME: add a proper Ambisonic resampler!
int numChannels = 4;
AudioSRC resampler[4] { {sampleRate, AudioConstants::SAMPLE_RATE, 1},
{sampleRate, AudioConstants::SAMPLE_RATE, 1},
{sampleRate, AudioConstants::SAMPLE_RATE, 1},
{sampleRate, AudioConstants::SAMPLE_RATE, 1} };
// resize to max possible output
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
int maxDestinationFrames = resampler[0].getMaxOutput(numSourceFrames);
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(maxDestinationBytes);
int numDestinationFrames = 0;
// iterate over channels
int16_t* srcBuffer = new int16_t[numSourceFrames];
int16_t* dstBuffer = new int16_t[maxDestinationFrames];
for (int ch = 0; ch < 4; ch++) {
int16_t* src = (int16_t*)rawAudioByteArray.data();
int16_t* dst = (int16_t*)_byteArray.data();
// deinterleave samples
for (int i = 0; i < numSourceFrames; i++) {
srcBuffer[i] = src[4*i + ch];
}
// resample one channel
numDestinationFrames = resampler[ch].render(srcBuffer, dstBuffer, numSourceFrames);
// reinterleave samples
for (int i = 0; i < numDestinationFrames; i++) {
dst[4*i + ch] = dstBuffer[i];
}
}
delete[] srcBuffer;
delete[] dstBuffer;
// truncate to actual output
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(numDestinationBytes);
} else {
int numChannels = _isStereo ? 2 : 1;
AudioSRC resampler(sampleRate, AudioConstants::SAMPLE_RATE, numChannels);
// resize to max possible output
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(maxDestinationBytes);
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
(int16_t*)_byteArray.data(),
numSourceFrames);
// truncate to actual output
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
_byteArray.resize(numDestinationBytes);
}
}
//
@ -160,7 +214,8 @@ struct CombinedHeader {
WAVEHeader wave;
};
void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
// returns wavfile sample rate, used for resampling
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
CombinedHeader fileHeader;
@ -174,36 +229,35 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
// descriptor.id == "RIFX" also signifies BigEndian file
// waveStream.setByteOrder(QDataStream::BigEndian);
qCDebug(audio) << "Currently not supporting big-endian audio files.";
return;
return 0;
}
if (strncmp(fileHeader.riff.type, "WAVE", 4) != 0
|| strncmp(fileHeader.wave.descriptor.id, "fmt", 3) != 0) {
qCDebug(audio) << "Not a WAVE Audio file.";
return;
return 0;
}
// added the endianess check as an extra level of security
if (qFromLittleEndian<quint16>(fileHeader.wave.audioFormat) != 1) {
qCDebug(audio) << "Currently not supporting non PCM audio files.";
return;
return 0;
}
if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 2) {
_isStereo = true;
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) > 2) {
qCDebug(audio) << "Currently not support audio files with more than 2 channels.";
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 4) {
_isAmbisonic = true;
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) != 1) {
qCDebug(audio) << "Currently not support audio files with other than 1/2/4 channels.";
return 0;
}
if (qFromLittleEndian<quint16>(fileHeader.wave.bitsPerSample) != 16) {
qCDebug(audio) << "Currently not supporting non 16bit audio files.";
return;
return 0;
}
if (qFromLittleEndian<quint32>(fileHeader.wave.sampleRate) != 48000) {
qCDebug(audio) << "Currently not supporting non 48KHz audio files.";
return;
}
// Skip any extra data in the WAVE chunk
waveStream.skipRawData(fileHeader.wave.descriptor.size - (sizeof(WAVEHeader) - sizeof(chunk)));
@ -218,7 +272,7 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
waveStream.skipRawData(dataHeader.descriptor.size);
} else {
qCDebug(audio) << "Could not read wav audio data header.";
return;
return 0;
}
}
@ -227,12 +281,14 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
outputAudioByteArray.resize(outputAudioByteArraySize);
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
qCDebug(audio) << "Error reading WAV file";
return 0;
}
_duration = (float) (outputAudioByteArraySize / (fileHeader.wave.sampleRate * fileHeader.wave.numChannels * fileHeader.wave.bitsPerSample / 8.0f));
return fileHeader.wave.sampleRate;
} else {
qCDebug(audio) << "Could not read wav audio file header.";
return;
return 0;
}
}

View file

@ -22,9 +22,10 @@ class Sound : public Resource {
Q_OBJECT
public:
Sound(const QUrl& url, bool isStereo = false);
Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false);
bool isStereo() const { return _isStereo; }
bool isAmbisonic() const { return _isAmbisonic; }
bool isReady() const { return _isReady; }
float getDuration() const { return _duration; }
@ -37,11 +38,12 @@ signals:
private:
QByteArray _byteArray;
bool _isStereo;
bool _isAmbisonic;
bool _isReady;
float _duration; // In seconds
void downSample(const QByteArray& rawAudioByteArray);
void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
virtual void downloadFinished(const QByteArray& data) override;
};

File diff suppressed because it is too large Load diff

View file

@ -55,6 +55,7 @@ namespace AvatarDataPacket {
PACKED_BEGIN struct Header {
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
float globalBoundingBoxCorner[3]; // global position of the lowest corner of the avatar's bounding box
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
@ -64,7 +65,7 @@ namespace AvatarDataPacket {
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
uint8_t flags;
} PACKED_END;
const size_t HEADER_SIZE = 69;
const size_t HEADER_SIZE = 81;
// only present if HAS_REFERENTIAL flag is set in header.flags
PACKED_BEGIN struct ParentInfo {
@ -205,6 +206,9 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
header->globalPosition[0] = _globalPosition.x;
header->globalPosition[1] = _globalPosition.y;
header->globalPosition[2] = _globalPosition.z;
header->globalBoundingBoxCorner[0] = getPosition().x - _globalBoundingBoxCorner.x;
header->globalBoundingBoxCorner[1] = getPosition().y - _globalBoundingBoxCorner.y;
header->globalBoundingBoxCorner[2] = getPosition().z - _globalBoundingBoxCorner.z;
glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation()));
packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y);
@ -481,6 +485,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]);
_globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]);
_globalBoundingBoxCorner = glm::vec3(header->globalBoundingBoxCorner[0], header->globalBoundingBoxCorner[1], header->globalBoundingBoxCorner[2]);
if (isNaN(position)) {
if (shouldLogError(now)) {
qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID();

View file

@ -133,6 +133,15 @@ enum KeyState {
DELETE_KEY_DOWN
};
enum KillAvatarReason : uint8_t {
NoReason = 0,
AvatarDisconnected,
AvatarIgnored,
TheirAvatarEnteredYourBubble,
YourAvatarEnteredTheirBubble
};
Q_DECLARE_METATYPE(KillAvatarReason);
class QDataStream;
class AttachmentData;
@ -353,6 +362,7 @@ public:
void fromJson(const QJsonObject& json);
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
glm::vec3 getGlobalBoundingBoxCorner() { return _globalBoundingBoxCorner; }
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
@ -436,6 +446,7 @@ protected:
// where Entities are located. This is currently only used by the mixer to decide how often to send
// updates about one avatar to another.
glm::vec3 _globalPosition;
glm::vec3 _globalBoundingBoxCorner;
mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar

View file

@ -141,20 +141,23 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// read the node id
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
removeAvatar(sessionUUID);
KillAvatarReason reason;
message->readPrimitive(&reason);
removeAvatar(sessionUUID, reason);
}
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) {
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
handleRemovedAvatar(removedAvatar);
handleRemovedAvatar(removedAvatar, removalReason);
}
}
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
<< "from AvatarHashMap";
emit avatarRemovedEvent(removedAvatar->getSessionUUID());

View file

@ -65,9 +65,9 @@ protected:
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock
virtual void removeAvatar(const QUuid& sessionUUID);
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
AvatarHash _avatarHash;
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.

View file

@ -104,11 +104,8 @@ void Node::addIgnoredNode(const QUuid& otherNodeID) {
void Node::parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message) {
bool enabled;
float radius;
message->readPrimitive(&enabled);
message->readPrimitive(&radius);
_ignoreRadiusEnabled = enabled;
_ignoreRadius = radius;
}
QDataStream& operator<<(QDataStream& out, const Node& node) {

View file

@ -80,7 +80,6 @@ public:
friend QDataStream& operator>>(QDataStream& in, Node& node);
bool isIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled; }
float getIgnoreRadius() { return _ignoreRadiusEnabled ? _ignoreRadius.load() : std::numeric_limits<float>::max(); }
private:
// privatize copy and assignment operator to disallow Node copying
@ -100,7 +99,6 @@ private:
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet;
std::atomic_bool _ignoreRadiusEnabled;
std::atomic<float> _ignoreRadius { 0.0f };
};
Q_DECLARE_METATYPE(Node*)

View file

@ -750,10 +750,9 @@ bool NodeList::sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr) {
return _domainHandler.getSockAddr() == sockAddr || LimitedNodeList::sockAddrBelongsToNode(sockAddr);
}
void NodeList::ignoreNodesInRadius(float radiusToIgnore, bool enabled) {
void NodeList::ignoreNodesInRadius(bool enabled) {
bool isEnabledChange = _ignoreRadiusEnabled.get() != enabled;
_ignoreRadiusEnabled.set(enabled);
_ignoreRadius.set(radiusToIgnore);
eachMatchingNode([](const SharedNodePointer& node)->bool {
return (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer);
@ -768,7 +767,6 @@ void NodeList::ignoreNodesInRadius(float radiusToIgnore, bool enabled) {
void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode) {
auto ignorePacket = NLPacket::create(PacketType::RadiusIgnoreRequest, sizeof(bool) + sizeof(float), true);
ignorePacket->writePrimitive(_ignoreRadiusEnabled.get());
ignorePacket->writePrimitive(_ignoreRadius.get());
sendPacket(std::move(ignorePacket), *destinationNode);
}

View file

@ -71,12 +71,11 @@ public:
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
void ignoreNodesInRadius(float radiusToIgnore, bool enabled = true);
float getIgnoreRadius() const { return _ignoreRadius.get(); }
void ignoreNodesInRadius(bool enabled = true);
bool getIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled.get(); }
void toggleIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), !getIgnoreRadiusEnabled()); }
void enableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), true); }
void disableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), false); }
void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); }
void enableIgnoreRadius() { ignoreNodesInRadius(true); }
void disableIgnoreRadius() { ignoreNodesInRadius(false); }
void ignoreNodeBySessionID(const QUuid& nodeID);
bool isIgnoringNode(const QUuid& nodeID) const;
@ -156,7 +155,6 @@ private:
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
Setting::Handle<float> _ignoreRadius { "IgnoreRadius", 1.0f };
#if (PR_BUILD || DEV_BUILD)
bool _shouldSendNewerVersion { false };

View file

@ -53,7 +53,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HandControllerJoints);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HasKillAvatarReason);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::AssetGetInfo:

View file

@ -202,7 +202,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AvatarEntities,
AbsoluteSixByteRotations,
SensorToWorldMat,
HandControllerJoints
HandControllerJoints,
HasKillAvatarReason
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -205,7 +205,8 @@ public:
signals:
void recommendedFramebufferSizeChanged(const QSize & size);
void recommendedFramebufferSizeChanged(const QSize& size);
void resetSensorsRequested();
protected:
void incrementPresentCount();

View file

@ -45,6 +45,9 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound
// stereo option isn't set from script, this comes from sound metadata or filename
AudioInjectorOptions optionsCopy = injectorOptions;
optionsCopy.stereo = sound->isStereo();
optionsCopy.ambisonic = sound->isAmbisonic();
optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic
auto injector = AudioInjector::playSound(sound->getByteArray(), optionsCopy);
if (!injector) {
return NULL;

View file

@ -52,14 +52,6 @@ void UsersScriptingInterface::disableIgnoreRadius() {
DependencyManager::get<NodeList>()->disableIgnoreRadius();
}
void UsersScriptingInterface::setIgnoreRadius(float radius, bool enabled) {
DependencyManager::get<NodeList>()->ignoreNodesInRadius(radius, enabled);
}
float UsersScriptingInterface::getIgnoreRadius() {
return DependencyManager::get<NodeList>()->getIgnoreRadius();
}
bool UsersScriptingInterface::getIgnoreRadiusEnabled() {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
}

View file

@ -76,21 +76,6 @@ public slots:
*/
void disableIgnoreRadius();
/**jsdoc
* sets the parameters for the ignore radius feature.
* @function Users.setIgnoreRadius
* @param {number} radius The radius for the auto ignore in radius feature
* @param {bool} [enabled=true] Whether the ignore in radius feature should be enabled
*/
void setIgnoreRadius(float radius, bool enabled = true);
/**jsdoc
* Returns the effective radius of the ingore radius feature if it is enabled.
* @function Users.getIgnoreRadius
* @return {number} radius of the ignore feature
*/
float getIgnoreRadius();
/**jsdoc
* Returns `true` if the ignore in radius feature is enabled
* @function Users.getIgnoreRadiusEnabled
@ -101,6 +86,12 @@ public slots:
signals:
void canKickChanged(bool canKick);
void ignoreRadiusEnabledChanged(bool isEnabled);
/**jsdoc
* Notifies scripts that another user has entered the ignore radius
* @function Users.enteredIgnoreRadius
*/
void enteredIgnoreRadius();
};

View file

@ -508,6 +508,11 @@ void AABox::embiggen(const glm::vec3& scale) {
_scale *= scale;
}
void AABox::setScaleStayCentered(const glm::vec3& scale) {
_corner += -0.5f * scale;
_scale = scale;
}
void AABox::scale(float scale) {
_corner *= scale;
_scale *= scale;

View file

@ -96,6 +96,9 @@ public:
void embiggen(float scale);
void embiggen(const glm::vec3& scale);
// Set a new scale for the Box, but keep it centered at its current location
void setScaleStayCentered(const glm::vec3& scale);
// Transform the extents with transform
void transform(const Transform& transform);

View file

@ -13,6 +13,7 @@ namespace hifi { namespace properties {
const char* CRASHED = "com.highfidelity.crashed";
const char* STEAM = "com.highfidelity.launchedFromSteam";
const char* LOGGER = "com.highfidelity.logger";
const char* TEST = "com.highfidelity.test";
namespace gl {
const char* BACKEND = "com.highfidelity.gl.backend";

View file

@ -15,6 +15,7 @@ namespace hifi { namespace properties {
extern const char* CRASHED;
extern const char* STEAM;
extern const char* LOGGER;
extern const char* TEST;
namespace gl {
extern const char* BACKEND;

View file

@ -21,6 +21,15 @@ void OculusBaseDisplayPlugin::resetSensors() {
}
bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
handleOVREvents();
if (quitRequested()) {
QMetaObject::invokeMethod(qApp, "quit");
return false;
}
if (reorientRequested()) {
emit resetSensorsRequested();
}
_currentRenderFrameInfo = FrameInfo();
_currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex);

View file

@ -20,15 +20,15 @@
#include <controllers/Pose.h>
#include <NumericalConstants.h>
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
Q_DECLARE_LOGGING_CATEGORY(oculus)
Q_LOGGING_CATEGORY(oculus, "hifi.plugins.oculus")
static std::atomic<uint32_t> refCount { 0 };
static ovrSession session { nullptr };
static bool _quitRequested { false };
static bool _reorientRequested { false };
inline ovrErrorInfo getError() {
ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
@ -116,6 +116,26 @@ void releaseOculusSession() {
#endif
}
void handleOVREvents() {
if (!session) {
return;
}
ovrSessionStatus status;
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
return;
}
_quitRequested = status.ShouldQuit;
_reorientRequested = status.ShouldRecenter;
}
bool quitRequested() {
return _quitRequested;
}
bool reorientRequested() {
return _reorientRequested;
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,

View file

@ -20,6 +20,10 @@ bool oculusAvailable();
ovrSession acquireOculusSession();
void releaseOculusSession();
void handleOVREvents();
bool quitRequested();
bool reorientRequested();
// Convenience method for looping over each eye with a lambda
template <typename Function>
inline void ovr_for_each_eye(Function function) {

View file

@ -0,0 +1,15 @@
//
// Created by Bradley Austin Davis on 2016/12/12
// Copyright 2013-2016 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
//
print("Fooooo");
Script.setTimeout(function() {
Test.quit();
}, 10 * 1000);

View file

@ -49,6 +49,7 @@
Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth
var newProps = {
name: "Photo Booth Model",
type: "Model",
modelURL: newModelURL,
position: this.centrePos

View file

@ -8,69 +8,28 @@
"green": 245,
"red": 245
},
"created": "2016-11-29T23:20:47Z",
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.05000000074505806,
"y": 0.05000000074505806,
"z": 0.0099999997764825821
},
"id": "{4a7b6258-ccc5-472e-ba41-dfd224115bee}",
"id": "{3925ba89-b94a-4555-97d4-18c217655b73}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"name": "Photo Booth Camera:Right Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{838ac5ff-5e06-4768-9389-9796577c5bc5}",
"position": {
"x": -0.022934332489967346,
"y": -0.25898283720016479,
"z": 0.17889007925987244
},
"queryAACube": {
"scale": 0.071414284408092499,
"x": 15.183169364929199,
"y": -192.90565490722656,
"z": 25.429607391357422
},
"rotation": {
"w": -7.62939453125e-05,
"x": -1.52587890625e-05,
"y": 1,
"z": -4.57763671875e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collisionless": 1,
"color": {
"blue": 149,
"green": 245,
"red": 245
},
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 0.05000000074505806,
"y": 0.05000000074505806,
"z": 0.0099999997764825821
},
"id": "{81ae005c-4738-4359-8860-98d00c8dd3a4}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Photo Booth Camera:Main Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{d5d16926-6f05-4411-931a-3ff8c897d728}",
"parentID": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
"position": {
"x": -0.021826114505529404,
"y": -0.25215747952461243,
"z": 0.17469465732574463
},
"queryAACube": {
"scale": 0.071414284408092499,
"x": 16.758693695068359,
"y": -193.97714233398438,
"z": 24.816326141357422
"scale": 0.2142428457736969,
"x": 18.361545562744141,
"y": -200.90092468261719,
"z": -10.209855079650879
},
"rotation": {
"w": -1.52587890625e-05,
@ -90,18 +49,18 @@
"green": 245,
"red": 245
},
"created": "2016-11-29T23:20:47Z",
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.05000000074505806,
"y": 0.05000000074505806,
"z": 0.0099999997764825821
},
"id": "{77817ac3-0862-46b6-8648-fdb8b855e4cb}",
"id": "{d2bbcea8-7cdc-4f16-a017-a4567fd82a61}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Photo Booth Camera:Left Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{2fc83747-6652-4fd1-bf21-c3d44ad610ea}",
"parentID": "{349e0687-52d4-4e0d-a03c-88f5bf427ce8}",
"position": {
"x": -0.021829158067703247,
"y": -0.25214886665344238,
@ -109,9 +68,9 @@
},
"queryAACube": {
"scale": 0.071414284408092499,
"x": 18.187423706054688,
"y": -193.3980712890625,
"z": 25.408010482788086
"x": 19.931711196899414,
"y": -200.49835205078125,
"z": -10.340259552001953
},
"rotation": {
"w": -1.52587890625e-05,
@ -126,7 +85,91 @@
{
"clientOnly": 0,
"collisionless": 1,
"created": "2016-11-29T23:20:47Z",
"color": {
"blue": 149,
"green": 245,
"red": 245
},
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.05000000074505806,
"y": 0.05000000074505806,
"z": 0.0099999997764825821
},
"id": "{24f1dbc2-6feb-4d01-81f6-9c88d41a7fcc}",
"ignoreForCollisions": 1,
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Photo Booth Camera:Right Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{f81a5b69-6d64-4a79-8233-a4cc182bf9f8}",
"position": {
"x": -0.022934332489967346,
"y": -0.25898283720016479,
"z": 0.17889007925987244
},
"queryAACube": {
"scale": 0.2142428457736969,
"x": 17.050743103027344,
"y": -199.81195068359375,
"z": -9.5248327255249023
},
"rotation": {
"w": -7.62939453125e-05,
"x": -1.52587890625e-05,
"y": 1,
"z": -4.57763671875e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collisionless": 1,
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.43360000848770142,
"y": 0.65679997205734253,
"z": 0.42155000567436218
},
"gravity": {
"x": 0,
"y": -9,
"z": 0
},
"id": "{349e0687-52d4-4e0d-a03c-88f5bf427ce8}",
"ignoreForCollisions": 1,
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": -1.3280529975891113,
"y": -0.65367329120635986,
"z": 2.894162654876709
},
"queryAACube": {
"scale": 2.678412914276123,
"x": 18.716829299926758,
"y": -201.52906799316406,
"z": -11.754700660705566
},
"rotation": {
"w": 0.25635090470314026,
"x": 0.016817826777696609,
"y": 0.96435952186584473,
"z": -0.063177049160003662
},
"scriptTimestamp": 1479859451129,
"shapeType": "simple-hull",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"LeftHand\":[{\"x\":-0.23937,\"y\":0.334177,\"z\":0.150116},{\"x\":-0.31183,\"y\":0.535888,\"z\":-0.37311,\"w\":-0.69021}],\"RightHand\":[{\"x\":0.11031082272529602,\"y\":0.19449540972709656,\"z\":0.0405043363571167},{\"x\":0.2807741165161133,\"y\":0.6332069635391235,\"z\":0.2997693121433258,\"w\":-0.6557632088661194}]}}}"
},
{
"clientOnly": 0,
"collisionless": 1,
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.43360000848770142,
"y": 0.65679997205734253,
@ -137,28 +180,29 @@
"y": -9,
"z": 0
},
"id": "{d5d16926-6f05-4411-931a-3ff8c897d728}",
"id": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 2.5208168029785156,
"y": 2.11322021484375,
"z": 1.1888446807861328
"x": 0.23746109008789062,
"y": -1.0055081844329834,
"z": 2.7560458183288574
},
"queryAACube": {
"scale": 0.89282792806625366,
"x": 2.0744028091430664,
"y": 1.6668062210083008,
"z": 0.74243068695068359
"scale": 2.6784837245941162,
"x": 17.151266098022461,
"y": -201.88088989257812,
"z": -11.616677284240723
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
"w": 4.6566128730773926e-10,
"x": 4.6566128730773926e-10,
"y": 1,
"z": 3.0517578125e-05
},
"scriptTimestamp": 1479859505510,
"shapeType": "simple-hull",
@ -168,7 +212,7 @@
{
"clientOnly": 0,
"collisionless": 1,
"created": "2016-11-29T23:20:47Z",
"created": "2016-12-10T00:09:18Z",
"dimensions": {
"x": 0.43360000848770142,
"y": 0.65679997205734253,
@ -179,28 +223,29 @@
"y": -9,
"z": 0
},
"id": "{838ac5ff-5e06-4768-9389-9796577c5bc5}",
"id": "{f81a5b69-6d64-4a79-8233-a4cc182bf9f8}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 0.86136627197265625,
"y": 3.2271270751953125,
"z": 1.8821067810058594
"x": 1.5688023567199707,
"y": 0.14506533741950989,
"z": 1.930147647857666
},
"queryAACube": {
"scale": 0.89280432462692261,
"x": 0.41496410965919495,
"y": 2.7807250022888184,
"z": 1.4357045888900757
"scale": 2.678412914276123,
"x": 15.81997013092041,
"y": -200.73027038574219,
"z": -10.790749549865723
},
"rotation": {
"w": 0.91699087619781494,
"x": 0.11256575584411621,
"y": 0.37981235980987549,
"z": -0.046890974044799805
"w": -0.3342345654964447,
"x": -0.089916400611400604,
"y": 0.90600436925888062,
"z": -0.24358996748924255
},
"scriptTimestamp": 1479859456707,
"shapeType": "simple-hull",
@ -209,236 +254,141 @@
},
{
"clientOnly": 0,
"collisionless": 1,
"created": "2016-11-29T23:20:47Z",
"created": "2016-12-10T01:03:54Z",
"dimensions": {
"x": 0.43360000848770142,
"y": 0.65679997205734253,
"z": 0.42155000567436218
"x": 1.0173934698104858,
"y": 1.1924806833267212,
"z": 1.0164999961853027
},
"gravity": {
"x": 0,
"y": -9,
"z": 0
},
"id": "{2fc83747-6652-4fd1-bf21-c3d44ad610ea}",
"ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 4.0442371368408203,
"y": 2.7116241455078125,
"z": 1.869842529296875
},
"queryAACube": {
"scale": 0.89280432462692261,
"x": 3.5978350639343262,
"y": 2.2652220726013184,
"z": 1.4234403371810913
},
"rotation": {
"w": 0.92196536064147949,
"x": 0.056198954582214355,
"y": -0.38243687152862549,
"z": 0.023208975791931152
},
"scriptTimestamp": 1479859451129,
"shapeType": "simple-hull",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"LeftHand\":[{\"x\":-0.23937,\"y\":0.334177,\"z\":0.150116},{\"x\":-0.31183,\"y\":0.535888,\"z\":-0.37311,\"w\":-0.69021}],\"RightHand\":[{\"x\":0.11031082272529602,\"y\":0.19449540972709656,\"z\":0.0405043363571167},{\"x\":0.2807741165161133,\"y\":0.6332069635391235,\"z\":0.2997693121433258,\"w\":-0.6557632088661194}]}}}"
},
{
"clientOnly": 0,
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 1.0173832178115845,
"y": 1.1924686431884766,
"z": 1.0164898633956909
},
"id": "{541efd7c-7e5f-40d5-b6ed-8e195afe9197}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"id": "{5d8e6ca9-eec6-4cd9-9931-343c8e122f5d}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"modelURL": "http://hifi-content.s3.amazonaws.com/alan/dev/Test-Object-7-metal.fbx",
"name": "Photo Booth Model:Default",
"name": "Photo Booth Model",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 2.5627613067626953,
"y": 1.8016510009765625,
"z": 3.6444053649902344
"x": 0.19543394446372986,
"y": -1.2587889432907104,
"z": 0.30047845840454102
},
"queryAACube": {
"scale": 1.8682348728179932,
"x": 1.6286438703536987,
"y": 0.86753356456756592,
"z": 2.7102880477905273
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2016-11-29T23:44:49Z",
"dimensions": {
"x": 1.1263399124145508,
"y": 0.55930328369140625,
"z": 1.0736434459686279
},
"id": "{5a286dd0-d6d8-4ed9-a579-1c0587b63fbc}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "atp:/jimi/tutorialroom/table3.fbx",
"name": "Photo Booth Stool",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 2.5654888153076172,
"y": 0.9199371337890625,
"z": 3.7379035949707031
},
"queryAACube": {
"scale": 1.6535331010818481,
"x": 1.7387223243713379,
"y": 0.093170583248138428,
"z": 2.9111371040344238
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 1.3296165466308594,
"y": 3.0967316627502441,
"z": 2.4247901439666748
},
"id": "{a0cd3304-e7e3-4522-8fbb-c4cf8d234eca}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
"name": "Photo Booth Stand Right",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 0,
"y": 2.207672119140625,
"z": 0.97442245483398438
},
"queryAACube": {
"scale": 4.1517748832702637,
"x": -2.0758874416351318,
"y": 0.13178467750549316,
"z": -1.1014649868011475
},
"rotation": {
"w": -0.37505149841308594,
"x": -1.52587890625e-05,
"y": 0.92700088024139404,
"z": 1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 0.6701958179473877,
"y": 3.0894412994384766,
"z": 6.0362682342529297
},
"id": "{f3c937d3-4493-41a1-8928-0cae4c4ce19f}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "http://mpassets.highfidelity.com/af67a13f-7610-49b4-9723-b284fb8ff37a-v1/Dungeon-Wall-6X3.fbx",
"name": "Photo Booth Backdrop",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 2.4534530639648438,
"y": 2.0988006591796875,
"z": 5.0006065368652344
},
"queryAACube": {
"scale": 6.8139815330505371,
"x": -0.9535377025604248,
"y": -1.3081901073455811,
"z": 1.5936157703399658
},
"rotation": {
"w": -0.70128941535949707,
"x": 1.52587890625e-05,
"y": 0.71288621425628662,
"z": -1.52587890625e-05
},
"shapeType": "box",
"type": "Model"
},
{
"clientOnly": 0,
"color": {
"blue": 201,
"green": 252,
"red": 255
},
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 8.0457677841186523,
"y": 1.3176910877227783,
"z": 8.4534158706665039
},
"id": "{7e4f4bed-a47b-449f-acb2-cb0410847e84}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 2.4956779479980469,
"y": 0,
"z": 2.2972354888916016
},
"queryAACube": {
"scale": 11.744400024414062,
"x": -3.3765220642089844,
"y": -5.8722000122070312,
"z": -3.5749645233154297
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box"
},
{
"clientOnly": 0,
"created": "2016-11-29T23:20:47Z",
"dimensions": {
"x": 1.3296165466308594,
"y": 1.5271610021591187,
"z": 2.4247901439666748
},
"id": "{891fb90c-ca13-46fb-b21e-0da6063ad07b}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
"name": "Photo Booth Stand Middle",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 2.4825477600097656,
"y": 1.4576416015625,
"z": 0
},
"queryAACube": {
"scale": 3.1590676307678223,
"x": 0.90301394462585449,
"y": -0.12189221382141113,
"z": -1.5795338153839111
"scale": 5.6047611236572266,
"x": 15.730070114135742,
"y": -201.99229431152344,
"z": -10.624255180358887
},
"rotation": {
"w": -1.52587890625e-05,
"x": -4.57763671875e-05,
"x": 1.52587890625e-05,
"y": 1,
"z": 1.52587890625e-05
},
"type": "Model"
},
{
"clientOnly": 0,
"created": "2016-12-10T00:50:48Z",
"cutoff": 90,
"dimensions": {
"x": 6.2771282196044922,
"y": 6.2771282196044922,
"z": 6.2771282196044922
},
"falloffRadius": 0.5,
"id": "{0fe028c8-4a14-482f-acf0-b26beb0224b2}",
"intensity": 3,
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Stage Light 1",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 1.8010828495025635,
"y": -0.796134352684021,
"z": -0.071148976683616638
},
"queryAACube": {
"scale": 32.616912841796875,
"x": 0.61834812164306641,
"y": -212.75062561035156,
"z": -23.758739471435547
},
"rotation": {
"w": 4.6566128730773926e-10,
"x": 4.6566128730773926e-10,
"y": 1,
"z": 3.0517578125e-05
},
"type": "Light"
},
{
"backgroundMode": "skybox",
"clientOnly": 0,
"created": "2016-12-10T00:18:02Z",
"dimensions": {
"x": 48.120223999023438,
"y": 16.455753326416016,
"z": 26.184453964233398
},
"id": "{98031a0d-1667-4bd0-8ac2-cf0a869b46f9}",
"keyLight": {
"ambientIntensity": 0.69999998807907104,
"direction": {
"x": 0,
"y": -0.61566150188446045,
"z": 0.78801077604293823
},
"intensity": 0.30000001192092896
},
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Stage Zone",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 0.069642722606658936,
"y": 3.9073746204376221,
"z": 4.0827426910400391
},
"queryAACube": {
"scale": 171.60348510742188,
"x": -67.143234252929688,
"y": -277.95611572265625,
"z": -97.405723571777344
},
"rotation": {
"w": 4.6566128730773926e-10,
"x": 4.6566128730773926e-10,
"y": 1,
"z": 3.0517578125e-05
},
"shapeType": "box",
"skybox": {
"url": "http://hifi-content.s3.amazonaws.com/alan/dev/Skybox-Sun-high.png"
},
"type": "Zone"
},
{
"clientOnly": 0,
"created": "2016-12-10T00:11:29Z",
"dimensions": {
"x": 46.438556671142578,
"y": 5.1657028198242188,
"z": 9.020604133605957
},
"id": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"modelURL": "http://hifi-content.s3.amazonaws.com/alan/dev/PhotoStage-v1.fbx",
"name": "Photo Booth Stage",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 47.587764739990234,
"x": -23.793882369995117,
"y": -23.793882369995117,
"z": -23.793882369995117
},
"rotation": {
"w": -1.52587890625e-05,
"x": -1.52587890625e-05,
"y": -1,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
@ -446,36 +396,38 @@
},
{
"clientOnly": 0,
"created": "2016-11-29T23:20:47Z",
"created": "2016-12-10T00:50:48Z",
"cutoff": 90,
"dimensions": {
"x": 1.3296165466308594,
"y": 2.3939504623413086,
"z": 2.4247901439666748
"x": 6.2771282196044922,
"y": 6.2771282196044922,
"z": 6.2771282196044922
},
"id": "{3773db2f-5bd8-4d23-a3cc-53ffcc7c30e9}",
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
"name": "Photo Booth Stand - Left",
"falloffRadius": 0.5,
"id": "{cf16fbc7-0a5b-4850-a03a-40e1499c3c5b}",
"intensity": 3,
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Stage Light 2",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": {
"x": 4.8861484527587891,
"y": 1.8541412353515625,
"z": 0.99666976928710938
"x": -1.7681519985198975,
"y": -0.79602539539337158,
"z": -0.071040049195289612
},
"queryAACube": {
"scale": 3.6576614379882812,
"x": 3.0573177337646484,
"y": 0.025310516357421875,
"z": -0.83216094970703125
"scale": 32.616912841796875,
"x": 4.1875829696655273,
"y": -212.75062561035156,
"z": -23.758739471435547
},
"rotation": {
"w": 0.38268101215362549,
"x": -4.57763671875e-05,
"y": 0.92385745048522949,
"z": -1.52587890625e-05
"w": 4.6566128730773926e-10,
"x": 4.6566128730773926e-10,
"y": 1,
"z": 3.0517578125e-05
},
"shapeType": "static-mesh",
"type": "Model"
"type": "Light"
}
],
"Version": 65

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,93 @@
// avatarFinderBeacon.js
//
// Created by Thijs Wenker on 12/7/16
// Copyright 2016 High Fidelity, Inc.
//
// Shows 2km long red beams for each avatar outside of the 20 meter radius of your avatar, tries to ignore AC Agents.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var MIN_DISPLAY_DISTANCE = 20.0; // meters
var BEAM_COLOR = {red: 255, green: 0, blue: 0};
var SHOW_THROUGH_WALLS = false;
var BEACON_LENGTH = 2000.0; // meters
var TRY_TO_IGNORE_AC_AGENTS = true;
var HALF_BEACON_LENGTH = BEACON_LENGTH / 2.0;
var beacons = {};
// List of .fst files used by AC scripts, that should be ignored in the script in case TRY_TO_IGNORE_AC_AGENTS is enabled
var POSSIBLE_AC_AVATARS = [
'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst',
'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/camera_man/pod/_latest/camera_man_pod.fst'
];
AvatarFinderBeacon = function(avatar) {
var visible = false;
var avatarSessionUUID = avatar.sessionUUID;
this.overlay = Overlays.addOverlay('line3d', {
color: BEAM_COLOR,
dashed: false,
start: Vec3.sum(avatar.position, {x: 0, y: -HALF_BEACON_LENGTH, z: 0}),
end: Vec3.sum(avatar.position, {x: 0, y: HALF_BEACON_LENGTH, z: 0}),
rotation: {x: 0, y: 0, z: 0, w: 1},
visible: visible,
drawInFront: SHOW_THROUGH_WALLS,
ignoreRayIntersection: true,
parentID: avatarSessionUUID,
parentJointIndex: -2
});
this.cleanup = function() {
Overlays.deleteOverlay(this.overlay);
};
this.shouldShow = function() {
return Vec3.distance(MyAvatar.position, avatar.position) >= MIN_DISPLAY_DISTANCE;
};
this.update = function() {
avatar = AvatarList.getAvatar(avatarSessionUUID);
Overlays.editOverlay(this.overlay, {
visible: this.shouldShow()
});
};
};
function updateBeacon(avatarSessionUUID) {
if (!(avatarSessionUUID in beacons)) {
var avatar = AvatarList.getAvatar(avatarSessionUUID);
if (TRY_TO_IGNORE_AC_AGENTS
&& (POSSIBLE_AC_AVATARS.indexOf(avatar.skeletonModelURL) !== -1 || Vec3.length(avatar.position) === 0.0)) {
return;
}
beacons[avatarSessionUUID] = new AvatarFinderBeacon(avatar);
return;
}
beacons[avatarSessionUUID].update();
}
Window.domainChanged.connect(function () {
beacons = {};
});
Script.update.connect(function() {
AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) {
updateBeacon(avatarSessionUUID);
});
});
AvatarList.avatarRemovedEvent.connect(function(avatarSessionUUID) {
if (avatarSessionUUID in beacons) {
beacons[avatarSessionUUID].cleanup();
delete beacons[avatarSessionUUID];
}
});
Script.scriptEnding.connect(function() {
for (var sessionUUID in beacons) {
if (!beacons.hasOwnProperty(sessionUUID)) {
return;
}
beacons[sessionUUID].cleanup();
}
});

View file

@ -13,42 +13,181 @@
/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
(function() { // BEGIN LOCAL_SCOPE
(function () { // BEGIN LOCAL_SCOPE
// grab the toolbar
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
// grab the toolbar
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
// Used for animating and disappearing the bubble
var bubbleOverlayTimestamp;
// Used for flashing the HUD button upon activation
var bubbleButtonFlashState = false;
// Used for flashing the HUD button upon activation
var bubbleButtonTimestamp;
// The bubble model itself
var bubbleOverlay = Overlays.addOverlay("model", {
url: Script.resolvePath("assets/models/bubble-v12.fbx"), // If you'd like to change the model, modify this line (and the dimensions below)
dimensions: { x: 1.0, y: 0.75, z: 1.0 },
position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28, z: MyAvatar.position.z },
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
scale: { x: 2, y: MyAvatar.scale * 0.5 + 0.5, z: 2 },
visible: false,
ignoreRayIntersection: true
});
// The bubble activation sound
var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav"));
// Is the update() function connected?
var updateConnected = false;
var ASSETS_PATH = Script.resolvePath("assets");
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
const BUBBLE_VISIBLE_DURATION_MS = 3000;
const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500;
function buttonImageURL() {
return TOOLS_PATH + 'bubble.svg';
}
var ASSETS_PATH = Script.resolvePath("assets");
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
function onBubbleToggled() {
var bubbleActive = Users.getIgnoreRadiusEnabled();
button.writeProperty('buttonState', bubbleActive ? 0 : 1);
button.writeProperty('defaultState', bubbleActive ? 0 : 1);
button.writeProperty('hoverState', bubbleActive ? 2 : 3);
}
function buttonImageURL() {
return TOOLS_PATH + 'bubble.svg';
}
// setup the mod button and add it to the toolbar
var button = toolbar.addButton({
objectName: 'bubble',
imageURL: buttonImageURL(),
visible: true,
alpha: 0.9
});
onBubbleToggled();
// Hides the bubble model overlay and resets the button flash state
function hideOverlays() {
Overlays.editOverlay(bubbleOverlay, {
visible: false
});
bubbleButtonFlashState = false;
}
button.clicked.connect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
// Make the bubble overlay visible, set its position, and play the sound
function createOverlays() {
Audio.playSound(bubbleActivateSound, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 0.4
});
hideOverlays();
if (updateConnected === true) {
updateConnected = false;
Script.update.disconnect(update);
}
// cleanup the toolbar button and overlays when script is stopped
Script.scriptEnding.connect(function() {
toolbar.removeButton('bubble');
button.clicked.disconnect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
});
Overlays.editOverlay(bubbleOverlay, {
position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28, z: MyAvatar.position.z },
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
scale: { x: 2, y: MyAvatar.scale * 0.5 + 0.5, z: 2 },
visible: true
});
bubbleOverlayTimestamp = Date.now();
bubbleButtonTimestamp = bubbleOverlayTimestamp;
Script.update.connect(update);
updateConnected = true;
}
// Called from the C++ scripting interface to show the bubble overlay
function enteredIgnoreRadius() {
createOverlays();
}
// Used to set the state of the bubble HUD button
function writeButtonProperties(parameter) {
button.writeProperty('buttonState', parameter ? 0 : 1);
button.writeProperty('defaultState', parameter ? 0 : 1);
button.writeProperty('hoverState', parameter ? 2 : 3);
}
// The bubble script's update function
update = function () {
var timestamp = Date.now();
var delay = (timestamp - bubbleOverlayTimestamp);
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
if (overlayAlpha > 0) {
// Flash button
if ((timestamp - bubbleButtonTimestamp) >= BUBBLE_VISIBLE_DURATION_MS) {
writeButtonProperties(bubbleButtonFlashState);
bubbleButtonTimestamp = timestamp;
bubbleButtonFlashState = !bubbleButtonFlashState;
}
if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) {
Overlays.editOverlay(bubbleOverlay, {
// Quickly raise the bubble from the ground up
position: {
x: MyAvatar.position.x,
y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28,
z: MyAvatar.position.z
},
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
scale: {
x: 2,
y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5),
z: 2
}
});
} else {
// Keep the bubble in place for a couple seconds
Overlays.editOverlay(bubbleOverlay, {
position: {
x: MyAvatar.position.x,
y: MyAvatar.position.y + MyAvatar.scale * 0.28,
z: MyAvatar.position.z
},
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
scale: {
x: 2,
y: MyAvatar.scale * 0.5 + 0.5,
z: 2
}
});
}
} else {
hideOverlays();
if (updateConnected === true) {
Script.update.disconnect(update);
updateConnected = false;
}
var bubbleActive = Users.getIgnoreRadiusEnabled();
writeButtonProperties(bubbleActive);
}
};
// When the space bubble is toggled...
function onBubbleToggled() {
var bubbleActive = Users.getIgnoreRadiusEnabled();
writeButtonProperties(bubbleActive);
if (bubbleActive) {
createOverlays();
} else {
hideOverlays();
if (updateConnected === true) {
Script.update.disconnect(update);
updateConnected = false;
}
}
}
// Setup the bubble button and add it to the toolbar
var button = toolbar.addButton({
objectName: 'bubble',
imageURL: buttonImageURL(),
visible: true,
alpha: 0.9
});
onBubbleToggled();
button.clicked.connect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
Users.enteredIgnoreRadius.connect(enteredIgnoreRadius);
// Cleanup the toolbar button and overlays when script is stopped
Script.scriptEnding.connect(function () {
toolbar.removeButton('bubble');
button.clicked.disconnect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius);
Overlays.deleteOverlay(bubbleOverlay);
bubbleButtonFlashState = false;
if (updateConnected === true) {
Script.update.disconnect(update);
}
});
}()); // END LOCAL_SCOPE