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

View file

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

View file

@ -211,20 +211,46 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
if (otherData if (otherData
&& !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { && !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) {
// check if distance is inside ignore radius // check to see if we're ignoring in radius
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { bool insideIgnoreRadius = false;
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius()); // If the otherNode equals the node, we're doing a comparison on ourselves
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) { if (*otherNode == *node) {
// skip, distance is inside ignore radius // We'll always be inside the radius in that case.
return; 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(); auto streamsCopy = otherData->getAudioStreams();
for (auto& streamPair : streamsCopy) { for (auto& streamPair : streamsCopy) {
auto otherNodeStream = streamPair.second; 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); addStreamToMix(*nodeData, otherNode->getUUID(), *nodeAudioStream, *otherNodeStream);
} }
} }

View file

@ -19,6 +19,7 @@
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <AABox.h>
#include <LogHandler.h> #include <LogHandler.h>
#include <NodeList.h> #include <NodeList.h>
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
@ -240,18 +241,39 @@ void AvatarMixer::broadcastAvatarData() {
} else { } else {
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()); AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->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()) { if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius()); // Define the minimum bubble size
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) { 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); nodeData->ignoreOther(node, otherNode);
otherData->ignoreOther(otherNode, node);
return false; return false;
} }
} }
// not close enough to ignore // Not close enough to ignore
nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID()); nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID());
otherData->removeFromRadiusIgnoringSet(node->getUUID());
return true; return true;
} }
}, },
@ -395,8 +417,9 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
// this was an avatar we were sending to other people // this was an avatar we were sending to other people
// send a kill packet for it to our other nodes // 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->write(killedNode->getUUID().toRfc4122());
killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
nodeList->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::Agent); 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) { void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) {
if (!isRadiusIgnoring(other->getUUID())) { if (!isRadiusIgnoring(other->getUUID())) {
addToRadiusIgnoringSet(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()); killPacket->write(other->getUUID().toRfc4122());
if (self->isIgnoreRadiusEnabled()) {
killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble);
} else {
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
}
DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self); DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
_hasReceivedFirstPacketsFrom.erase(other->getUUID()); _hasReceivedFirstPacketsFrom.erase(other->getUUID());
} }

View file

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

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject) include(ExternalProject)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi4.zip URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
URL_MD5 2abde5340a64d387848f12b9536a7e85 URL_MD5 0530753e855ffc00232cc969bf1c84a8
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_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_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 and qtaudio_windows.dll in the correct directories for runtime selection
# copy qtaudio_wasapi.dll alongside qtaudio_windows.dll, and let the installer resolve add_custom_command(
add_custom_command( TARGET ${TARGET_NAME}
TARGET ${TARGET_NAME} POST_BUILD
POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN7_PATH}
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 ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN8_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} ) # copy release DLLs
) COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windows.dll ${QTAUDIO_WIN7_PATH} )
elseif (${CMAKE_SYSTEM_VERSION} VERSION_LESS 6.2) COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_WIN8_PATH} )
# continue using qtaudio_windows.dll on Windows 7 # copy debug DLLs
else () COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windowsd.dll ${QTAUDIO_WIN7_PATH} )
# replace qtaudio_windows.dll with qtaudio_wasapi.dll on Windows 8/8.1/10 COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_WIN8_PATH} )
add_custom_command( # remove directory
TARGET ${TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E remove_directory ${QTAUDIO_PATH}
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 ()
endif () endif ()
endmacro() endmacro()

View file

@ -630,17 +630,6 @@ Section "-Core installation"
Delete "$INSTDIR\version" Delete "$INSTDIR\version"
Delete "$INSTDIR\xinput1_3.dll" 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 old desktop shortcuts before they were renamed during Sandbox rename
Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk" Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_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.RB", "to": "Standard.RB" },
{ "from": "GamePad.RS", "to": "Standard.RS" }, { "from": "GamePad.RS", "to": "Standard.RS" },
{ "from": "GamePad.Start", "to": "Actions.CycleCamera" }, { "from": "GamePad.Start", "to": "Standard.Start" },
{ "from": "GamePad.Back", "to": "Standard.Start" }, { "from": "GamePad.Back", "to": "Actions.CycleCamera" },
{ "from": "GamePad.DU", "to": "Standard.DU" }, { "from": "GamePad.DU", "to": "Standard.DU" },
{ "from": "GamePad.DD", "to": "Standard.DD" }, { "from": "GamePad.DD", "to": "Standard.DD" },

View file

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

View file

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

View file

@ -19,11 +19,13 @@ import "../styles-uit"
Item { Item {
id: linkAccountBody id: linkAccountBody
clip: true clip: true
width: root.pane.width
height: root.pane.height height: root.pane.height
width: root.pane.width
property bool failAfterSignUp: false
function login() { function login() {
mainTextContainer.visible = false mainTextContainer.visible = false
toggleLoading(true)
loginDialog.login(usernameField.text, passwordField.text) loginDialog.login(usernameField.text, passwordField.text)
} }
@ -51,12 +53,40 @@ Item {
targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height
} }
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) 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); + (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 { ShortcutText {
id: mainTextContainer id: mainTextContainer
anchors { anchors {
@ -96,7 +126,7 @@ Item {
} }
width: 350 width: 350
label: "User Name or Email" label: "Username or Email"
} }
ShortcutText { ShortcutText {
@ -108,6 +138,7 @@ Item {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link) onLinkActivated: loginDialog.openUrl(link)
} }
@ -135,6 +166,7 @@ Item {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link) 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 { Row {
id: buttons id: buttons
anchors { anchors {
@ -209,6 +266,11 @@ Item {
keyboardEnabled = HMD.active; keyboardEnabled = HMD.active;
d.resize(); d.resize();
if (failAfterSignUp) {
mainTextContainer.text = "Account created successfully."
mainTextContainer.visible = true
}
usernameField.forceActiveFocus(); usernameField.forceActiveFocus();
} }
@ -228,6 +290,7 @@ Item {
onHandleLoginFailed: { onHandleLoginFailed: {
console.log("Login Failed") console.log("Login Failed")
mainTextContainer.visible = true mainTextContainer.visible = true
toggleLoading(false)
} }
onHandleLinkCompleted: { onHandleLinkCompleted: {
console.log("Link Succeeded") console.log("Link Succeeded")
@ -238,7 +301,7 @@ Item {
} }
onHandleLinkFailed: { onHandleLinkFailed: {
console.log("Link Failed") console.log("Link Failed")
toggleLoading(false)
} }
} }

View file

@ -18,8 +18,8 @@ import "../styles-uit"
Item { Item {
id: signInBody id: signInBody
clip: true clip: true
width: pane.width width: root.pane.width
height: pane.height height: root.pane.height
property bool required: false property bool required: false
@ -43,8 +43,8 @@ Item {
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth) var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)) parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) 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 + textField.height +
hifi.dimensions.contentSpacing.y + buttons.height hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)) parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) 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) + (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 { Item {
id: welcomeBody id: welcomeBody
clip: true clip: true
width: pane.width width: root.pane.width
height: pane.height height: root.pane.height
property bool welcomeBack: false property bool welcomeBack: false
@ -39,8 +39,8 @@ Item {
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth) var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)) parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) 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; WebEngine.settings.localContentCanAccessRemoteUrls = true;
[ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts. [ // 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", "hudToggle",
"com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto" "com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto"
].forEach(function (name) { ].forEach(function (name) {

View file

@ -134,6 +134,7 @@
#include "LODManager.h" #include "LODManager.h"
#include "ModelPackager.h" #include "ModelPackager.h"
#include "networking/HFWebEngineProfile.h" #include "networking/HFWebEngineProfile.h"
#include "scripting/TestScriptingInterface.h"
#include "scripting/AccountScriptingInterface.h" #include "scripting/AccountScriptingInterface.h"
#include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h" #include "scripting/AudioDeviceScriptingInterface.h"
@ -172,6 +173,8 @@
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken. // FIXME seems to be broken.
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
#include <VersionHelpers.h>
extern "C" { extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
} }
@ -422,6 +425,16 @@ bool setupEssentials(int& argc, char** argv) {
Setting::preInit(); 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"; static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), 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::STEAM, SteamClient::isRunning());
setProperty(hifi::properties::CRASHED, _previousSessionCrashed); 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(); _runningMarker.startRunningMarker();
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care 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(); 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 Setting::Handle<bool> firstRun { Settings::firstRun, true };
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/"; bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
auto contentVersionPath = acDirPath + "content-version.txt"; Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
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; bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
Setting::Handle<bool> firstRun { Settings::firstRun, true }; qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable(); qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false }; 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(); const QString TUTORIAL_PATH = "/tutorial_begin";
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
// when --url in command line, teleport to location if (shouldGoToTutorial) {
const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; if (sandboxIsRunning) {
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
QString addressLookupString; DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
if (urlIndex != -1) { } else {
addressLookupString = arguments().value(urlIndex + 1); qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
} if (firstRun.get()) {
showHelp();
const QString TUTORIAL_PATH = "/tutorial_begin"; }
if (addressLookupString.isEmpty()) {
if (shouldGoToTutorial) { DependencyManager::get<AddressManager>()->goToEntry();
if(sandboxIsRunning) { } else {
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH); }
}
} else { } else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
if (firstRun.get()) { bool isFirstRun = firstRun.get();
if (isFirstRun) {
showHelp(); showHelp();
} }
if (addressLookupString.isEmpty()) {
DependencyManager::get<AddressManager>()->goToEntry();
} else {
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
}
} else {
bool isFirstRun = firstRun.get(); // If this is a first run we short-circuit the address passed in
if (isFirstRun) {
if (isFirstRun) { if (hasHMDAndHandControllers) {
showHelp(); 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 } else {
if (isFirstRun) { qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
if (hasHMDAndHandControllers) { DependencyManager::get<AddressManager>()->goToEntry();
if(sandboxIsRunning) { }
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
} else { } else {
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry(); DependencyManager::get<AddressManager>()->goToEntry();
} }
} else { } 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. // 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; static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
_addAssetToWorldTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS); _addAssetToWorldTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS);
connect(&_addAssetToWorldTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize); connect(&_addAssetToWorldTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize);
// After all of the constructor is completed, then set firstRun to false. // After all of the constructor is completed, then set firstRun to false.
firstRun.set(false); firstRun.set(false);
}
} }
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { 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); Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
break; break;
case Qt::Key_N:
if (!isOption && !isShifted && isMeta) {
DependencyManager::get<NodeList>()->toggleIgnoreRadius();
}
break;
case Qt::Key_S: case Qt::Key_S:
if (isShifted && isMeta && !isOption) { if (isShifted && isMeta && !isOption) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
@ -4851,7 +4890,7 @@ void Application::resetSensors(bool andReload) {
DependencyManager::get<EyeTracker>()->reset(); DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors(); getActiveDisplayPlugin()->resetSensors();
_overlayConductor.centerUI(); _overlayConductor.centerUI();
getMyAvatar()->reset(andReload); getMyAvatar()->reset(true, andReload);
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection); QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
} }
@ -5156,6 +5195,11 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// AvatarManager has some custom types // AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine); AvatarManager::registerMetaTypes(scriptEngine);
if (property(hifi::properties::TEST).isValid()) {
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
}
scriptEngine->registerGlobalObject("Overlays", &_overlays);
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this)); scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
// hook our avatar and avatar hash map object into this script engine // 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) { QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
resizeGL(); resizeGL();
}); });
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
first = false; first = false;
} }

View file

@ -119,7 +119,7 @@ namespace MenuOption {
const QString LoadScript = "Open and Run Script File..."; const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL..."; const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools"; const QString LodTools = "LOD Tools";
const QString Login = "Login"; const QString Login = "Login / Sign Up";
const QString Log = "Log"; const QString Log = "Log";
const QString LogExtraTimings = "Log Extra Timing Details"; const QString LogExtraTimings = "Log Extra Timing Details";
const QString LowVelocityFilter = "Low Velocity Filter"; 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 // 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 // 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() { AvatarManager::~AvatarManager() {
@ -223,16 +223,16 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
} }
// virtual // virtual
void AvatarManager::removeAvatar(const QUuid& sessionUUID) { void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock); QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID); auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) { if (removedAvatar) {
handleRemovedAvatar(removedAvatar); handleRemovedAvatar(removedAvatar, removalReason);
} }
} }
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
AvatarHashMap::handleRemovedAvatar(removedAvatar); AvatarHashMap::handleRemovedAvatar(removedAvatar);
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar // 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); _motionStatesToRemoveFromPhysics.push_back(motionState);
} }
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
}
_avatarFades.push_back(removedAvatar); _avatarFades.push_back(removedAvatar);
} }

View file

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

View file

@ -230,6 +230,10 @@ void MyAvatar::simulateAttachments(float deltaTime) {
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
CameraMode mode = qApp->getCamera()->getMode(); CameraMode mode = qApp->getCamera()->getMode();
_globalPosition = getPosition(); _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) { if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
// fake the avatar position that is sent up to the AvatarMixer // fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getPosition(); glm::vec3 oldPosition = getPosition();
@ -360,10 +364,18 @@ void MyAvatar::update(float deltaTime) {
updateFromTrackers(deltaTime); updateFromTrackers(deltaTime);
// Get audio loudness data from audio input device // 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>(); auto audio = DependencyManager::get<AudioClient>();
head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioLoudness(audio->getLastInputLoudness());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); 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) { if (_avatarEntityDataLocallyEdited) {
sendIdentityPacket(); 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 "LoginDialog.h"
#include <QDesktopServices> #include <QtGui/QDesktopServices>
#include <QJsonDocument> #include <QtCore/QJsonArray>
#include <QNetworkReply> #include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
#include <steamworks-wrapper/SteamClient.h> #include <steamworks-wrapper/SteamClient.h>
@ -47,7 +48,7 @@ void LoginDialog::toggleAction() {
connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout); connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout);
} else { } else {
// change the menu item to login // change the menu item to login
loginAction->setText("Login"); loginAction->setText("Login / Sign Up");
connection = connect(loginAction, &QAction::triggered, [] { connection = connect(loginAction, &QAction::triggered, [] {
LoginDialog::show(); LoginDialog::show();
}); });
@ -153,3 +154,83 @@ void LoginDialog::createFailed(QNetworkReply& reply) {
emit handleCreateFailed(reply.errorString()); 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 handleCreateCompleted();
void handleCreateFailed(QString error); void handleCreateFailed(QString error);
void handleSignupCompleted();
void handleSignupFailed(QString errorString);
public slots: public slots:
void linkCompleted(QNetworkReply& reply); void linkCompleted(QNetworkReply& reply);
@ -43,6 +46,9 @@ public slots:
void createCompleted(QNetworkReply& reply); void createCompleted(QNetworkReply& reply);
void createFailed(QNetworkReply& reply); void createFailed(QNetworkReply& reply);
void signupCompleted(QNetworkReply& reply);
void signupFailed(QNetworkReply& reply);
protected slots: protected slots:
Q_INVOKABLE bool isSteamRunning() const; Q_INVOKABLE bool isSteamRunning() const;
@ -51,6 +57,8 @@ protected slots:
Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void loginThroughSteam();
Q_INVOKABLE void linkSteam(); Q_INVOKABLE void linkSteam();
Q_INVOKABLE void createAccountFromStream(QString username = QString()); 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; Q_INVOKABLE void openUrl(const QString& url) const;

View file

@ -68,18 +68,6 @@ void setupPreferences() {
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); 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 // UI
{ {

View file

@ -1063,7 +1063,9 @@ void AudioClient::handleAudioInput() {
encodedBuffer = decodedBuffer; encodedBuffer = decodedBuffer;
} }
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType, _selectedCodecName); emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber,
audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale,
packetType, _selectedCodecName);
_stats.sentPacket(); _stats.sentPacket();
int bytesInInputRingBuffer = _inputRingBuffer.samplesAvailable() * AudioConstants::SAMPLE_SIZE; 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? // 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) { void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
@ -1098,13 +1102,37 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
for (AudioInjector* injector : getActiveLocalAudioInjectors()) { for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
if (injector->getLocalBuffer()) { 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) int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
memset(_scratchBuffer, 0, sizeof(_scratchBuffer)); qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) {
// 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 // stereo gets directly mixed into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) { for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
@ -1120,7 +1148,8 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
float azimuth = azimuthForSource(relativePosition); float azimuth = azimuthForSource(relativePosition);
// mono gets spatialized into mixBuffer // 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 { } else {
@ -1225,8 +1254,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
} }
} }
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
Lock lock(_injectorsMutex); Lock lock(_injectorsMutex);
if (injector->getLocalBuffer() && _audioInput ) { if (injector->getLocalBuffer() && _audioInput ) {
// just add it to the vector of active local injectors, if // just add it to the vector of active local injectors, if
@ -1595,3 +1623,8 @@ void AudioClient::saveSettings() {
dynamicJitterBufferEnabled.set(_receivedAudioStream.dynamicJitterBufferEnabled()); dynamicJitterBufferEnabled.set(_receivedAudioStream.dynamicJitterBufferEnabled());
staticJitterBufferFrames.set(_receivedAudioStream.getStaticJitterBufferFrames()); 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 setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; } QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; }
void checkDevices(); void checkDevices();
@ -169,7 +171,7 @@ public slots:
int setOutputBufferSize(int numFrames, bool persist = true); 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 shouldLoopbackInjectors() override { return _shouldEchoToServer; }
bool switchInputToAudioDevice(const QString& inputDeviceName); bool switchInputToAudioDevice(const QString& inputDeviceName);
@ -297,7 +299,7 @@ private:
// for local hrtf-ing // for local hrtf-ing
float _mixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; 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; AudioLimiter _audioLimiter;
// Adds Reverb // Adds Reverb
@ -324,6 +326,9 @@ private:
AudioPositionGetter _positionGetter; AudioPositionGetter _positionGetter;
AudioOrientationGetter _orientationGetter; AudioOrientationGetter _orientationGetter;
glm::vec3 avatarBoundingBoxCorner;
glm::vec3 avatarBoundingBoxScale;
QVector<QString> _inputDevices; QVector<QString> _inputDevices;
QVector<QString> _outputDevices; QVector<QString> _outputDevices;

View file

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

View file

@ -28,11 +28,12 @@ class AbstractAudioInterface : public QObject {
public: public:
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {}; 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("")); PacketType packetType, QString codecName = QString(""));
public slots: public slots:
virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0; virtual bool outputLocalInjector(AudioInjector* injector) = 0;
virtual bool shouldLoopbackInjectors() { return false; } virtual bool shouldLoopbackInjectors() { return false; }
virtual void setIsStereoInput(bool stereo) = 0; virtual void setIsStereoInput(bool stereo) = 0;

View file

@ -20,7 +20,7 @@ namespace AudioConstants {
const int SAMPLE_RATE = 24000; const int SAMPLE_RATE = 24000;
const int MONO = 1; const int MONO = 1;
const int STEREO = 2; const int STEREO = 2;
const int AMBISONIC = 4;
typedef int16_t AudioSample; typedef int16_t AudioSample;
const int SAMPLE_SIZE = sizeof(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_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / SAMPLE_SIZE;
const int NETWORK_FRAME_BYTES_PER_CHANNEL = NETWORK_FRAME_BYTES_STEREO / 2; 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_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_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE));
const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f; const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f;
const float NETWORK_FRAMES_PER_SEC = 1.0f / NETWORK_FRAME_SECS; 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, // since options.stereo is computed from the audio stream,
// we need to copy it from existing options just in case. // we need to copy it from existing options just in case.
bool currentlyStereo = _options.stereo; bool currentlyStereo = _options.stereo;
bool currentlyAmbisonic = _options.ambisonic;
_options = options; _options = options;
_options.stereo = currentlyStereo; _options.stereo = currentlyStereo;
_options.ambisonic = currentlyAmbisonic;
} }
void AudioInjector::finishNetworkInjection() { void AudioInjector::finishNetworkInjection() {
@ -134,7 +136,8 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*
int byteOffset = 0; int byteOffset = 0;
if (_options.secondOffset > 0.0f) { 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); byteOffset *= sizeof(AudioConstants::SAMPLE_SIZE);
} }
_currentSendOffset = byteOffset; _currentSendOffset = byteOffset;
@ -169,7 +172,7 @@ bool AudioInjector::injectLocally() {
_localBuffer->setCurrentOffset(_currentSendOffset); _localBuffer->setCurrentOffset(_currentSendOffset);
// call this function on the AudioClient's thread // 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) { if (!success) {
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";

View file

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

View file

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

View file

@ -25,6 +25,7 @@ public:
bool loop; bool loop;
glm::quat orientation; glm::quat orientation;
bool stereo; bool stereo;
bool ambisonic;
bool ignorePenumbra; bool ignorePenumbra;
bool localOnly; bool localOnly;
float secondOffset; 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*>(&_position), sizeof(_position));
packetStream.readRawData(reinterpret_cast<char*>(&_orientation), sizeof(_orientation)); 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 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)) { if (glm::isnan(_orientation.x)) {

View file

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

View file

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

View file

@ -22,9 +22,10 @@ class Sound : public Resource {
Q_OBJECT Q_OBJECT
public: public:
Sound(const QUrl& url, bool isStereo = false); Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false);
bool isStereo() const { return _isStereo; } bool isStereo() const { return _isStereo; }
bool isAmbisonic() const { return _isAmbisonic; }
bool isReady() const { return _isReady; } bool isReady() const { return _isReady; }
float getDuration() const { return _duration; } float getDuration() const { return _duration; }
@ -37,11 +38,12 @@ signals:
private: private:
QByteArray _byteArray; QByteArray _byteArray;
bool _isStereo; bool _isStereo;
bool _isAmbisonic;
bool _isReady; bool _isReady;
float _duration; // In seconds float _duration; // In seconds
void downSample(const QByteArray& rawAudioByteArray); void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
virtual void downloadFinished(const QByteArray& data) override; 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 { PACKED_BEGIN struct Header {
float position[3]; // skeletal model's position float position[3]; // skeletal model's position
float globalPosition[3]; // avatar'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 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. uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on. 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 float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
uint8_t flags; uint8_t flags;
} PACKED_END; } 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 // only present if HAS_REFERENTIAL flag is set in header.flags
PACKED_BEGIN struct ParentInfo { PACKED_BEGIN struct ParentInfo {
@ -205,6 +206,9 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
header->globalPosition[0] = _globalPosition.x; header->globalPosition[0] = _globalPosition.x;
header->globalPosition[1] = _globalPosition.y; header->globalPosition[1] = _globalPosition.y;
header->globalPosition[2] = _globalPosition.z; 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())); glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation()));
packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); 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]); 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]); _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 (isNaN(position)) {
if (shouldLogError(now)) { if (shouldLogError(now)) {
qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID(); qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID();

View file

@ -133,6 +133,15 @@ enum KeyState {
DELETE_KEY_DOWN DELETE_KEY_DOWN
}; };
enum KillAvatarReason : uint8_t {
NoReason = 0,
AvatarDisconnected,
AvatarIgnored,
TheirAvatarEnteredYourBubble,
YourAvatarEnteredTheirBubble
};
Q_DECLARE_METATYPE(KillAvatarReason);
class QDataStream; class QDataStream;
class AttachmentData; class AttachmentData;
@ -353,6 +362,7 @@ public:
void fromJson(const QJsonObject& json); void fromJson(const QJsonObject& json);
glm::vec3 getClientGlobalPosition() { return _globalPosition; } glm::vec3 getClientGlobalPosition() { return _globalPosition; }
glm::vec3 getGlobalBoundingBoxCorner() { return _globalBoundingBoxCorner; }
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); 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 // where Entities are located. This is currently only used by the mixer to decide how often to send
// updates about one avatar to another. // updates about one avatar to another.
glm::vec3 _globalPosition; glm::vec3 _globalPosition;
glm::vec3 _globalBoundingBoxCorner;
mutable ReadWriteLockable _avatarEntitiesLock; mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar 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) { void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// read the node id // read the node id
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); 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); QWriteLocker locker(&_hashLock);
auto removedAvatar = _avatarHash.take(sessionUUID); auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) { 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()) qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
<< "from AvatarHashMap"; << "from AvatarHashMap";
emit avatarRemovedEvent(removedAvatar->getSessionUUID()); emit avatarRemovedEvent(removedAvatar->getSessionUUID());

View file

@ -65,9 +65,9 @@ protected:
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
AvatarSharedPointer newOrExistingAvatar(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 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; AvatarHash _avatarHash;
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock. // "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) { void Node::parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message) {
bool enabled; bool enabled;
float radius;
message->readPrimitive(&enabled); message->readPrimitive(&enabled);
message->readPrimitive(&radius);
_ignoreRadiusEnabled = enabled; _ignoreRadiusEnabled = enabled;
_ignoreRadius = radius;
} }
QDataStream& operator<<(QDataStream& out, const Node& node) { QDataStream& operator<<(QDataStream& out, const Node& node) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -205,7 +205,8 @@ public:
signals: signals:
void recommendedFramebufferSizeChanged(const QSize & size); void recommendedFramebufferSizeChanged(const QSize& size);
void resetSensorsRequested();
protected: protected:
void incrementPresentCount(); 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 // stereo option isn't set from script, this comes from sound metadata or filename
AudioInjectorOptions optionsCopy = injectorOptions; AudioInjectorOptions optionsCopy = injectorOptions;
optionsCopy.stereo = sound->isStereo(); 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); auto injector = AudioInjector::playSound(sound->getByteArray(), optionsCopy);
if (!injector) { if (!injector) {
return NULL; return NULL;

View file

@ -52,14 +52,6 @@ void UsersScriptingInterface::disableIgnoreRadius() {
DependencyManager::get<NodeList>()->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() { bool UsersScriptingInterface::getIgnoreRadiusEnabled() {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled(); return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
} }

View file

@ -76,21 +76,6 @@ public slots:
*/ */
void disableIgnoreRadius(); 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 /**jsdoc
* Returns `true` if the ignore in radius feature is enabled * Returns `true` if the ignore in radius feature is enabled
* @function Users.getIgnoreRadiusEnabled * @function Users.getIgnoreRadiusEnabled
@ -101,6 +86,12 @@ public slots:
signals: signals:
void canKickChanged(bool canKick); void canKickChanged(bool canKick);
void ignoreRadiusEnabledChanged(bool isEnabled); 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; _scale *= scale;
} }
void AABox::setScaleStayCentered(const glm::vec3& scale) {
_corner += -0.5f * scale;
_scale = scale;
}
void AABox::scale(float scale) { void AABox::scale(float scale) {
_corner *= scale; _corner *= scale;
_scale *= scale; _scale *= scale;

View file

@ -96,6 +96,9 @@ public:
void embiggen(float scale); void embiggen(float scale);
void embiggen(const glm::vec3& 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 // Transform the extents with transform
void transform(const Transform& transform); void transform(const Transform& transform);

View file

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

View file

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

View file

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

View file

@ -20,15 +20,15 @@
#include <controllers/Pose.h> #include <controllers/Pose.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
Q_DECLARE_LOGGING_CATEGORY(oculus) Q_DECLARE_LOGGING_CATEGORY(oculus)
Q_LOGGING_CATEGORY(oculus, "hifi.plugins.oculus") Q_LOGGING_CATEGORY(oculus, "hifi.plugins.oculus")
static std::atomic<uint32_t> refCount { 0 }; static std::atomic<uint32_t> refCount { 0 };
static ovrSession session { nullptr }; static ovrSession session { nullptr };
static bool _quitRequested { false };
static bool _reorientRequested { false };
inline ovrErrorInfo getError() { inline ovrErrorInfo getError() {
ovrErrorInfo error; ovrErrorInfo error;
ovr_GetLastErrorInfo(&error); ovr_GetLastErrorInfo(&error);
@ -116,6 +116,26 @@ void releaseOculusSession() {
#endif #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( controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand, ovrHandType hand,

View file

@ -20,6 +20,10 @@ bool oculusAvailable();
ovrSession acquireOculusSession(); ovrSession acquireOculusSession();
void releaseOculusSession(); void releaseOculusSession();
void handleOVREvents();
bool quitRequested();
bool reorientRequested();
// Convenience method for looping over each eye with a lambda // Convenience method for looping over each eye with a lambda
template <typename Function> template <typename Function>
inline void ovr_for_each_eye(Function 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); Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth // create new model at centre of the photobooth
var newProps = { var newProps = {
name: "Photo Booth Model",
type: "Model", type: "Model",
modelURL: newModelURL, modelURL: newModelURL,
position: this.centrePos position: this.centrePos

View file

@ -8,69 +8,28 @@
"green": 245, "green": 245,
"red": 245 "red": 245
}, },
"created": "2016-11-29T23:20:47Z", "created": "2016-12-10T00:09:18Z",
"dimensions": { "dimensions": {
"x": 0.05000000074505806, "x": 0.05000000074505806,
"y": 0.05000000074505806, "y": 0.05000000074505806,
"z": 0.0099999997764825821 "z": 0.0099999997764825821
}, },
"id": "{4a7b6258-ccc5-472e-ba41-dfd224115bee}", "id": "{3925ba89-b94a-4555-97d4-18c217655b73}",
"ignoreForCollisions": 1, "ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}", "lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"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}",
"name": "Photo Booth Camera:Main Camera", "name": "Photo Booth Camera:Main Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{d5d16926-6f05-4411-931a-3ff8c897d728}", "parentID": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
"position": { "position": {
"x": -0.021826114505529404, "x": -0.021826114505529404,
"y": -0.25215747952461243, "y": -0.25215747952461243,
"z": 0.17469465732574463 "z": 0.17469465732574463
}, },
"queryAACube": { "queryAACube": {
"scale": 0.071414284408092499, "scale": 0.2142428457736969,
"x": 16.758693695068359, "x": 18.361545562744141,
"y": -193.97714233398438, "y": -200.90092468261719,
"z": 24.816326141357422 "z": -10.209855079650879
}, },
"rotation": { "rotation": {
"w": -1.52587890625e-05, "w": -1.52587890625e-05,
@ -90,18 +49,18 @@
"green": 245, "green": 245,
"red": 245 "red": 245
}, },
"created": "2016-11-29T23:20:47Z", "created": "2016-12-10T00:09:18Z",
"dimensions": { "dimensions": {
"x": 0.05000000074505806, "x": 0.05000000074505806,
"y": 0.05000000074505806, "y": 0.05000000074505806,
"z": 0.0099999997764825821 "z": 0.0099999997764825821
}, },
"id": "{77817ac3-0862-46b6-8648-fdb8b855e4cb}", "id": "{d2bbcea8-7cdc-4f16-a017-a4567fd82a61}",
"ignoreForCollisions": 1, "ignoreForCollisions": 1,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}", "lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Photo Booth Camera:Left Camera", "name": "Photo Booth Camera:Left Camera",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{2fc83747-6652-4fd1-bf21-c3d44ad610ea}", "parentID": "{349e0687-52d4-4e0d-a03c-88f5bf427ce8}",
"position": { "position": {
"x": -0.021829158067703247, "x": -0.021829158067703247,
"y": -0.25214886665344238, "y": -0.25214886665344238,
@ -109,9 +68,9 @@
}, },
"queryAACube": { "queryAACube": {
"scale": 0.071414284408092499, "scale": 0.071414284408092499,
"x": 18.187423706054688, "x": 19.931711196899414,
"y": -193.3980712890625, "y": -200.49835205078125,
"z": 25.408010482788086 "z": -10.340259552001953
}, },
"rotation": { "rotation": {
"w": -1.52587890625e-05, "w": -1.52587890625e-05,
@ -126,7 +85,91 @@
{ {
"clientOnly": 0, "clientOnly": 0,
"collisionless": 1, "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": { "dimensions": {
"x": 0.43360000848770142, "x": 0.43360000848770142,
"y": 0.65679997205734253, "y": 0.65679997205734253,
@ -137,28 +180,29 @@
"y": -9, "y": -9,
"z": 0 "z": 0
}, },
"id": "{d5d16926-6f05-4411-931a-3ff8c897d728}", "id": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
"ignoreForCollisions": 1, "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", "modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones", "name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": { "position": {
"x": 2.5208168029785156, "x": 0.23746109008789062,
"y": 2.11322021484375, "y": -1.0055081844329834,
"z": 1.1888446807861328 "z": 2.7560458183288574
}, },
"queryAACube": { "queryAACube": {
"scale": 0.89282792806625366, "scale": 2.6784837245941162,
"x": 2.0744028091430664, "x": 17.151266098022461,
"y": 1.6668062210083008, "y": -201.88088989257812,
"z": 0.74243068695068359 "z": -11.616677284240723
}, },
"rotation": { "rotation": {
"w": 1, "w": 4.6566128730773926e-10,
"x": -1.52587890625e-05, "x": 4.6566128730773926e-10,
"y": -1.52587890625e-05, "y": 1,
"z": -1.52587890625e-05 "z": 3.0517578125e-05
}, },
"scriptTimestamp": 1479859505510, "scriptTimestamp": 1479859505510,
"shapeType": "simple-hull", "shapeType": "simple-hull",
@ -168,7 +212,7 @@
{ {
"clientOnly": 0, "clientOnly": 0,
"collisionless": 1, "collisionless": 1,
"created": "2016-11-29T23:20:47Z", "created": "2016-12-10T00:09:18Z",
"dimensions": { "dimensions": {
"x": 0.43360000848770142, "x": 0.43360000848770142,
"y": 0.65679997205734253, "y": 0.65679997205734253,
@ -179,28 +223,29 @@
"y": -9, "y": -9,
"z": 0 "z": 0
}, },
"id": "{838ac5ff-5e06-4768-9389-9796577c5bc5}", "id": "{f81a5b69-6d64-4a79-8233-a4cc182bf9f8}",
"ignoreForCollisions": 1, "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", "modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
"name": "35 MM SLR by Lazybones", "name": "35 MM SLR by Lazybones",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": { "position": {
"x": 0.86136627197265625, "x": 1.5688023567199707,
"y": 3.2271270751953125, "y": 0.14506533741950989,
"z": 1.8821067810058594 "z": 1.930147647857666
}, },
"queryAACube": { "queryAACube": {
"scale": 0.89280432462692261, "scale": 2.678412914276123,
"x": 0.41496410965919495, "x": 15.81997013092041,
"y": 2.7807250022888184, "y": -200.73027038574219,
"z": 1.4357045888900757 "z": -10.790749549865723
}, },
"rotation": { "rotation": {
"w": 0.91699087619781494, "w": -0.3342345654964447,
"x": 0.11256575584411621, "x": -0.089916400611400604,
"y": 0.37981235980987549, "y": 0.90600436925888062,
"z": -0.046890974044799805 "z": -0.24358996748924255
}, },
"scriptTimestamp": 1479859456707, "scriptTimestamp": 1479859456707,
"shapeType": "simple-hull", "shapeType": "simple-hull",
@ -209,236 +254,141 @@
}, },
{ {
"clientOnly": 0, "clientOnly": 0,
"collisionless": 1, "created": "2016-12-10T01:03:54Z",
"created": "2016-11-29T23:20:47Z",
"dimensions": { "dimensions": {
"x": 0.43360000848770142, "x": 1.0173934698104858,
"y": 0.65679997205734253, "y": 1.1924806833267212,
"z": 0.42155000567436218 "z": 1.0164999961853027
}, },
"gravity": { "id": "{5d8e6ca9-eec6-4cd9-9931-343c8e122f5d}",
"x": 0, "lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"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}",
"modelURL": "http://hifi-content.s3.amazonaws.com/alan/dev/Test-Object-7-metal.fbx", "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}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": { "position": {
"x": 2.5627613067626953, "x": 0.19543394446372986,
"y": 1.8016510009765625, "y": -1.2587889432907104,
"z": 3.6444053649902344 "z": 0.30047845840454102
}, },
"queryAACube": { "queryAACube": {
"scale": 1.8682348728179932, "scale": 5.6047611236572266,
"x": 1.6286438703536987, "x": 15.730070114135742,
"y": 0.86753356456756592, "y": -201.99229431152344,
"z": 2.7102880477905273 "z": -10.624255180358887
},
"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
}, },
"rotation": { "rotation": {
"w": -1.52587890625e-05, "w": -1.52587890625e-05,
"x": -4.57763671875e-05, "x": 1.52587890625e-05,
"y": 1, "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 "z": -1.52587890625e-05
}, },
"shapeType": "static-mesh", "shapeType": "static-mesh",
@ -446,36 +396,38 @@
}, },
{ {
"clientOnly": 0, "clientOnly": 0,
"created": "2016-11-29T23:20:47Z", "created": "2016-12-10T00:50:48Z",
"cutoff": 90,
"dimensions": { "dimensions": {
"x": 1.3296165466308594, "x": 6.2771282196044922,
"y": 2.3939504623413086, "y": 6.2771282196044922,
"z": 2.4247901439666748 "z": 6.2771282196044922
}, },
"id": "{3773db2f-5bd8-4d23-a3cc-53ffcc7c30e9}", "falloffRadius": 0.5,
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}", "id": "{cf16fbc7-0a5b-4850-a03a-40e1499c3c5b}",
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx", "intensity": 3,
"name": "Photo Booth Stand - Left", "lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
"name": "Stage Light 2",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}", "owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
"position": { "position": {
"x": 4.8861484527587891, "x": -1.7681519985198975,
"y": 1.8541412353515625, "y": -0.79602539539337158,
"z": 0.99666976928710938 "z": -0.071040049195289612
}, },
"queryAACube": { "queryAACube": {
"scale": 3.6576614379882812, "scale": 32.616912841796875,
"x": 3.0573177337646484, "x": 4.1875829696655273,
"y": 0.025310516357421875, "y": -212.75062561035156,
"z": -0.83216094970703125 "z": -23.758739471435547
}, },
"rotation": { "rotation": {
"w": 0.38268101215362549, "w": 4.6566128730773926e-10,
"x": -4.57763671875e-05, "x": 4.6566128730773926e-10,
"y": 0.92385745048522949, "y": 1,
"z": -1.52587890625e-05 "z": 3.0517578125e-05
}, },
"shapeType": "static-mesh", "type": "Light"
"type": "Model"
} }
], ],
"Version": 65 "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 */ /* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
(function() { // BEGIN LOCAL_SCOPE (function () { // BEGIN LOCAL_SCOPE
// grab the toolbar // grab the toolbar
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); 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"); const BUBBLE_VISIBLE_DURATION_MS = 3000;
var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500;
function buttonImageURL() { var ASSETS_PATH = Script.resolvePath("assets");
return TOOLS_PATH + 'bubble.svg'; var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
}
function onBubbleToggled() { function buttonImageURL() {
var bubbleActive = Users.getIgnoreRadiusEnabled(); return TOOLS_PATH + 'bubble.svg';
button.writeProperty('buttonState', bubbleActive ? 0 : 1); }
button.writeProperty('defaultState', bubbleActive ? 0 : 1);
button.writeProperty('hoverState', bubbleActive ? 2 : 3);
}
// setup the mod button and add it to the toolbar // Hides the bubble model overlay and resets the button flash state
var button = toolbar.addButton({ function hideOverlays() {
objectName: 'bubble', Overlays.editOverlay(bubbleOverlay, {
imageURL: buttonImageURL(), visible: false
visible: true, });
alpha: 0.9 bubbleButtonFlashState = false;
}); }
onBubbleToggled();
button.clicked.connect(Users.toggleIgnoreRadius); // Make the bubble overlay visible, set its position, and play the sound
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); 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 Overlays.editOverlay(bubbleOverlay, {
Script.scriptEnding.connect(function() { position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28, z: MyAvatar.position.z },
toolbar.removeButton('bubble'); rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
button.clicked.disconnect(Users.toggleIgnoreRadius); scale: { x: 2, y: MyAvatar.scale * 0.5 + 0.5, z: 2 },
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); 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 }()); // END LOCAL_SCOPE