mirror of
https://github.com/overte-org/overte.git
synced 2025-04-24 05:53:29 +02:00
Merge branch 'master' into 21089
Conflicts: interface/src/Application.cpp
This commit is contained in:
commit
1f3045ad30
73 changed files with 21843 additions and 626 deletions
|
@ -351,7 +351,9 @@ void Agent::executeScript() {
|
|||
Transform audioTransform;
|
||||
audioTransform.setTranslation(scriptedAvatar->getPosition());
|
||||
audioTransform.setRotation(scriptedAvatar->getOrientation());
|
||||
AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber, audioTransform, PacketType::MicrophoneAudioNoEcho);
|
||||
AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber,
|
||||
audioTransform, scriptedAvatar->getPosition(), glm::vec3(0),
|
||||
PacketType::MicrophoneAudioNoEcho);
|
||||
});
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
@ -424,8 +426,9 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
|
|||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
qDebug() << "sending KillAvatar message to Audio Mixers";
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
packet->write(getSessionUUID().toRfc4122());
|
||||
packet->writePrimitive(KillAvatarReason::NoReason);
|
||||
nodeList->sendPacket(std::move(packet), *node);
|
||||
});
|
||||
|
||||
|
@ -475,8 +478,9 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
qDebug() << "sending KillAvatar message to Avatar and Audio Mixers";
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
packet->write(getSessionUUID().toRfc4122());
|
||||
packet->writePrimitive(KillAvatarReason::NoReason);
|
||||
nodeList->sendPacket(std::move(packet), *node);
|
||||
});
|
||||
}
|
||||
|
@ -580,6 +584,8 @@ void Agent::processAgentAvatarAudio() {
|
|||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
audioPacket->writePrimitive(glm::vec3(0));
|
||||
} else if (nextSoundOutput) {
|
||||
|
||||
// write the codec
|
||||
|
@ -592,6 +598,8 @@ void Agent::processAgentAvatarAudio() {
|
|||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
audioPacket->writePrimitive(glm::vec3(0));
|
||||
|
||||
QByteArray encodedBuffer;
|
||||
if (_flushEncoder) {
|
||||
|
|
|
@ -90,6 +90,8 @@ public:
|
|||
bool shouldMuteClient() { return _shouldMuteClient; }
|
||||
void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; }
|
||||
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
|
||||
glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); }
|
||||
glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); }
|
||||
|
||||
signals:
|
||||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
|
|
|
@ -211,20 +211,46 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
|
|||
if (otherData
|
||||
&& !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) {
|
||||
|
||||
// check if distance is inside ignore radius
|
||||
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
|
||||
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius());
|
||||
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) {
|
||||
// skip, distance is inside ignore radius
|
||||
return;
|
||||
// check to see if we're ignoring in radius
|
||||
bool insideIgnoreRadius = false;
|
||||
// If the otherNode equals the node, we're doing a comparison on ourselves
|
||||
if (*otherNode == *node) {
|
||||
// We'll always be inside the radius in that case.
|
||||
insideIgnoreRadius = true;
|
||||
// Check to see if the space bubble is enabled
|
||||
} else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) && (*otherNode != *node)) {
|
||||
// Define the minimum bubble size
|
||||
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
// Set up the bounding box for the current node
|
||||
AABox nodeBox(nodeData->getAvatarBoundingBoxCorner(), nodeData->getAvatarBoundingBoxScale());
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(nodeData->getAvatarBoundingBoxScale(), minBubbleSize))) {
|
||||
nodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Set up the bounding box for the current other node
|
||||
AABox otherNodeBox(otherData->getAvatarBoundingBoxCorner(), otherData->getAvatarBoundingBoxScale());
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(otherData->getAvatarBoundingBoxScale(), minBubbleSize))) {
|
||||
otherNodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Quadruple the scale of both bounding boxes
|
||||
nodeBox.embiggen(4.0f);
|
||||
otherNodeBox.embiggen(4.0f);
|
||||
|
||||
// Perform the collision check between the two bounding boxes
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
insideIgnoreRadius = true;
|
||||
}
|
||||
}
|
||||
|
||||
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
|
||||
// Enumerate the audio streams attached to the otherNode
|
||||
auto streamsCopy = otherData->getAudioStreams();
|
||||
for (auto& streamPair : streamsCopy) {
|
||||
auto otherNodeStream = streamPair.second;
|
||||
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
|
||||
bool isSelfWithEcho = ((*otherNode == *node) && (otherNodeStream->shouldLoopbackForNode()));
|
||||
// Add all audio streams that should be added to the mix
|
||||
if (isSelfWithEcho || (!isSelfWithEcho && !insideIgnoreRadius)) {
|
||||
addStreamToMix(*nodeData, otherNode->getUUID(), *nodeAudioStream, *otherNodeStream);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <LogHandler.h>
|
||||
#include <NodeList.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
@ -240,18 +241,39 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
} else {
|
||||
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
// check to see if we're ignoring in radius
|
||||
// Check to see if the space bubble is enabled
|
||||
if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
|
||||
float ignoreRadius = glm::min(node->getIgnoreRadius(), otherNode->getIgnoreRadius());
|
||||
if (glm::distance(nodeData->getPosition(), otherData->getPosition()) < ignoreRadius) {
|
||||
// Define the minimum bubble size
|
||||
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
// Define the scale of the box for the current node
|
||||
glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f;
|
||||
// Define the scale of the box for the current other node
|
||||
glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f;
|
||||
|
||||
// Set up the bounding box for the current node
|
||||
AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale);
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) {
|
||||
nodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Set up the bounding box for the current other node
|
||||
AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
|
||||
otherNodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Quadruple the scale of both bounding boxes
|
||||
nodeBox.embiggen(4.0f);
|
||||
otherNodeBox.embiggen(4.0f);
|
||||
|
||||
// Perform the collision check between the two bounding boxes
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
nodeData->ignoreOther(node, otherNode);
|
||||
otherData->ignoreOther(otherNode, node);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// not close enough to ignore
|
||||
// Not close enough to ignore
|
||||
nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID());
|
||||
otherData->removeFromRadiusIgnoringSet(node->getUUID());
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
@ -395,8 +417,9 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
|
||||
// this was an avatar we were sending to other people
|
||||
// send a kill packet for it to our other nodes
|
||||
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID);
|
||||
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
|
||||
killPacket->write(killedNode->getUUID().toRfc4122());
|
||||
killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
|
||||
|
||||
nodeList->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::Agent);
|
||||
|
||||
|
|
|
@ -45,8 +45,13 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node
|
|||
void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) {
|
||||
if (!isRadiusIgnoring(other->getUUID())) {
|
||||
addToRadiusIgnoringSet(other->getUUID());
|
||||
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID);
|
||||
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
|
||||
killPacket->write(other->getUUID().toRfc4122());
|
||||
if (self->isIgnoreRadiusEnabled()) {
|
||||
killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble);
|
||||
} else {
|
||||
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
|
||||
}
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
|
||||
_hasReceivedFirstPacketsFrom.erase(other->getUUID());
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ public:
|
|||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
|
||||
glm::vec3 getPosition() { return _avatar ? _avatar->getPosition() : glm::vec3(0); }
|
||||
glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
|
||||
bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
|
||||
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
|
||||
void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); }
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi4.zip
|
||||
URL_MD5 2abde5340a64d387848f12b9536a7e85
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
|
||||
URL_MD5 0530753e855ffc00232cc969bf1c84a8
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -43,26 +43,24 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
|
|||
)
|
||||
|
||||
set(QTAUDIO_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audio)
|
||||
set(QTAUDIO_WIN7_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audioWin7/audio)
|
||||
set(QTAUDIO_WIN8_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audioWin8/audio)
|
||||
|
||||
if (DEPLOY_PACKAGE)
|
||||
# copy qtaudio_wasapi.dll alongside qtaudio_windows.dll, and let the installer resolve
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
|
||||
)
|
||||
elseif (${CMAKE_SYSTEM_VERSION} VERSION_LESS 6.2)
|
||||
# continue using qtaudio_windows.dll on Windows 7
|
||||
else ()
|
||||
# replace qtaudio_windows.dll with qtaudio_wasapi.dll on Windows 8/8.1/10
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windows.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windowsd.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
|
||||
)
|
||||
endif ()
|
||||
# copy qtaudio_wasapi.dll and qtaudio_windows.dll in the correct directories for runtime selection
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN7_PATH}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${QTAUDIO_WIN8_PATH}
|
||||
# copy release DLLs
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windows.dll ${QTAUDIO_WIN7_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_WIN8_PATH} )
|
||||
# copy debug DLLs
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${QTAUDIO_PATH}/qtaudio_windowsd.dll ${QTAUDIO_WIN7_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_WIN8_PATH} )
|
||||
# remove directory
|
||||
COMMAND ${CMAKE_COMMAND} -E remove_directory ${QTAUDIO_PATH}
|
||||
)
|
||||
|
||||
endif ()
|
||||
endmacro()
|
||||
|
|
|
@ -630,17 +630,6 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\version"
|
||||
Delete "$INSTDIR\xinput1_3.dll"
|
||||
|
||||
; The installer includes two different Qt audio plugins.
|
||||
; On Windows 8 and above, only qtaudio_wasapi.dll should be installed.
|
||||
; On Windows 7 and below, only qtaudio_windows.dll should be installed.
|
||||
${If} ${AtLeastWin8}
|
||||
Delete "$INSTDIR\audio\qtaudio_windows.dll"
|
||||
Delete "$INSTDIR\audio\qtaudio_windows.pdb"
|
||||
${Else}
|
||||
Delete "$INSTDIR\audio\qtaudio_wasapi.dll"
|
||||
Delete "$INSTDIR\audio\qtaudio_wasapi.pdb"
|
||||
${EndIf}
|
||||
|
||||
; Delete old desktop shortcuts before they were renamed during Sandbox rename
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
{ "from": "GamePad.RB", "to": "Standard.RB" },
|
||||
{ "from": "GamePad.RS", "to": "Standard.RS" },
|
||||
|
||||
{ "from": "GamePad.Start", "to": "Actions.CycleCamera" },
|
||||
{ "from": "GamePad.Back", "to": "Standard.Start" },
|
||||
{ "from": "GamePad.Start", "to": "Standard.Start" },
|
||||
{ "from": "GamePad.Back", "to": "Actions.CycleCamera" },
|
||||
|
||||
{ "from": "GamePad.DU", "to": "Standard.DU" },
|
||||
{ "from": "GamePad.DD", "to": "Standard.DD" },
|
||||
|
|
|
@ -40,7 +40,6 @@ ModalWindow {
|
|||
|
||||
Loader {
|
||||
id: bodyLoader
|
||||
anchors.fill: parent
|
||||
source: loginDialog.isSteamRunning() ? "LoginDialog/SignInBody.qml" : "LoginDialog/LinkAccountBody.qml"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ import "../styles-uit"
|
|||
Item {
|
||||
id: completeProfileBody
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
width: root.pane.width
|
||||
height: root.pane.height
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
@ -33,8 +33,8 @@ Item {
|
|||
termsContainer.contentWidth))
|
||||
var targetHeight = 5 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height + termsContainer.height
|
||||
|
||||
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,13 @@ import "../styles-uit"
|
|||
Item {
|
||||
id: linkAccountBody
|
||||
clip: true
|
||||
width: root.pane.width
|
||||
height: root.pane.height
|
||||
width: root.pane.width
|
||||
property bool failAfterSignUp: false
|
||||
|
||||
function login() {
|
||||
mainTextContainer.visible = false
|
||||
toggleLoading(true)
|
||||
loginDialog.login(usernameField.text, passwordField.text)
|
||||
}
|
||||
|
||||
|
@ -51,12 +53,40 @@ Item {
|
|||
targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height
|
||||
}
|
||||
|
||||
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
|
||||
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
|
||||
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLoading(isLoading) {
|
||||
linkAccountSpinner.visible = isLoading
|
||||
form.visible = !isLoading
|
||||
|
||||
if (loginDialog.isSteamRunning()) {
|
||||
additionalInformation.visible = !isLoading
|
||||
}
|
||||
|
||||
leftButton.visible = !isLoading
|
||||
buttons.visible = !isLoading
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: linkAccountSpinner
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
topMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
|
||||
visible: false
|
||||
running: true
|
||||
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
ShortcutText {
|
||||
id: mainTextContainer
|
||||
anchors {
|
||||
|
@ -96,7 +126,7 @@ Item {
|
|||
}
|
||||
width: 350
|
||||
|
||||
label: "User Name or Email"
|
||||
label: "Username or Email"
|
||||
}
|
||||
|
||||
ShortcutText {
|
||||
|
@ -108,6 +138,7 @@ Item {
|
|||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
linkColor: hifi.colors.blueAccent
|
||||
|
||||
onLinkActivated: loginDialog.openUrl(link)
|
||||
}
|
||||
|
@ -135,6 +166,7 @@ Item {
|
|||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
linkColor: hifi.colors.blueAccent
|
||||
|
||||
onLinkActivated: loginDialog.openUrl(link)
|
||||
}
|
||||
|
@ -173,6 +205,31 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: leftButton
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
onHeightChanged: d.resize(); onWidthChanged: d.resize();
|
||||
|
||||
Button {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: qsTr("Sign Up")
|
||||
visible: !loginDialog.isSteamRunning()
|
||||
|
||||
onClicked: {
|
||||
bodyLoader.setSource("SignUpBody.qml")
|
||||
bodyLoader.item.width = root.pane.width
|
||||
bodyLoader.item.height = root.pane.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons
|
||||
anchors {
|
||||
|
@ -209,6 +266,11 @@ Item {
|
|||
keyboardEnabled = HMD.active;
|
||||
d.resize();
|
||||
|
||||
if (failAfterSignUp) {
|
||||
mainTextContainer.text = "Account created successfully."
|
||||
mainTextContainer.visible = true
|
||||
}
|
||||
|
||||
usernameField.forceActiveFocus();
|
||||
}
|
||||
|
||||
|
@ -228,6 +290,7 @@ Item {
|
|||
onHandleLoginFailed: {
|
||||
console.log("Login Failed")
|
||||
mainTextContainer.visible = true
|
||||
toggleLoading(false)
|
||||
}
|
||||
onHandleLinkCompleted: {
|
||||
console.log("Link Succeeded")
|
||||
|
@ -238,7 +301,7 @@ Item {
|
|||
}
|
||||
onHandleLinkFailed: {
|
||||
console.log("Link Failed")
|
||||
|
||||
toggleLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ import "../styles-uit"
|
|||
Item {
|
||||
id: signInBody
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
width: root.pane.width
|
||||
height: root.pane.height
|
||||
|
||||
property bool required: false
|
||||
|
||||
|
@ -43,8 +43,8 @@ Item {
|
|||
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
|
||||
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
|
||||
|
||||
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
296
interface/resources/qml/LoginDialog/SignUpBody.qml
Normal file
296
interface/resources/qml/LoginDialog/SignUpBody.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,11 +47,9 @@ Item {
|
|||
hifi.dimensions.contentSpacing.y + textField.height +
|
||||
hifi.dimensions.contentSpacing.y + buttons.height
|
||||
|
||||
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
+ (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y)
|
||||
|
||||
height = root.height
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ import "../styles-uit"
|
|||
Item {
|
||||
id: welcomeBody
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
width: root.pane.width
|
||||
height: root.pane.height
|
||||
|
||||
property bool welcomeBack: false
|
||||
|
||||
|
@ -39,8 +39,8 @@ Item {
|
|||
var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
|
||||
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y + buttons.height
|
||||
|
||||
root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
|
||||
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ OriginalDesktop.Desktop {
|
|||
WebEngine.settings.localContentCanAccessRemoteUrls = true;
|
||||
|
||||
[ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts.
|
||||
"hmdToggle", "mute", "mod", "help",
|
||||
"hmdToggle", "mute", "mod", "bubble", "help",
|
||||
"hudToggle",
|
||||
"com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto"
|
||||
].forEach(function (name) {
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
#include "LODManager.h"
|
||||
#include "ModelPackager.h"
|
||||
#include "networking/HFWebEngineProfile.h"
|
||||
#include "scripting/TestScriptingInterface.h"
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/AssetMappingsScriptingInterface.h"
|
||||
#include "scripting/AudioDeviceScriptingInterface.h"
|
||||
|
@ -172,6 +173,8 @@
|
|||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
extern "C" {
|
||||
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
||||
}
|
||||
|
@ -422,6 +425,16 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
|
||||
Setting::preInit();
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
// Select appropriate audio DLL
|
||||
QString audioDLLPath = QCoreApplication::applicationDirPath();
|
||||
if (IsWindows8OrGreater()) {
|
||||
audioDLLPath += "/audioWin8";
|
||||
} else {
|
||||
audioDLLPath += "/audioWin7";
|
||||
}
|
||||
QCoreApplication::addLibraryPath(audioDLLPath);
|
||||
#endif
|
||||
|
||||
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
|
||||
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
|
||||
|
@ -545,6 +558,20 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
setProperty(hifi::properties::STEAM, SteamClient::isRunning());
|
||||
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
|
||||
|
||||
{
|
||||
const QString TEST_SCRIPT = "--testScript";
|
||||
const QStringList args = arguments();
|
||||
for (int i = 0; i < args.size() - 1; ++i) {
|
||||
if (args.at(i) == TEST_SCRIPT) {
|
||||
QString testScriptPath = args.at(i + 1);
|
||||
if (QFileInfo(testScriptPath).exists()) {
|
||||
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_runningMarker.startRunningMarker();
|
||||
|
||||
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
||||
|
@ -1353,95 +1380,101 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
return entityServerNode && !isPhysicsEnabled();
|
||||
});
|
||||
|
||||
QVariant testProperty = property(hifi::properties::TEST);
|
||||
qDebug() << testProperty;
|
||||
if (testProperty.isValid()) {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||
scriptEngines->loadScript(testScript, false);
|
||||
} else {
|
||||
// Get sandbox content set version, if available
|
||||
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||
auto contentVersion = 0;
|
||||
QFile contentVersionFile(contentVersionPath);
|
||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString line = contentVersionFile.readAll();
|
||||
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
|
||||
contentVersion = line.toInt();
|
||||
}
|
||||
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
|
||||
|
||||
bool hasTutorialContent = contentVersion >= 1;
|
||||
|
||||
// Get sandbox content set version, if available
|
||||
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||
auto contentVersion = 0;
|
||||
QFile contentVersionFile(contentVersionPath);
|
||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString line = contentVersionFile.readAll();
|
||||
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
|
||||
contentVersion = line.toInt();
|
||||
}
|
||||
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
|
||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
||||
|
||||
bool hasTutorialContent = contentVersion >= 1;
|
||||
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
|
||||
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
|
||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
||||
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
|
||||
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
|
||||
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
|
||||
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
|
||||
|
||||
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
|
||||
// when --url in command line, teleport to location
|
||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
||||
QString addressLookupString;
|
||||
if (urlIndex != -1) {
|
||||
addressLookupString = arguments().value(urlIndex + 1);
|
||||
}
|
||||
|
||||
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
|
||||
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
|
||||
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
|
||||
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
|
||||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||
|
||||
// when --url in command line, teleport to location
|
||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
||||
QString addressLookupString;
|
||||
if (urlIndex != -1) {
|
||||
addressLookupString = arguments().value(urlIndex + 1);
|
||||
}
|
||||
|
||||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||
|
||||
if (shouldGoToTutorial) {
|
||||
if(sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
if (shouldGoToTutorial) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
showHelp();
|
||||
}
|
||||
if (addressLookupString.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
|
||||
bool isFirstRun = firstRun.get();
|
||||
|
||||
if (isFirstRun) {
|
||||
showHelp();
|
||||
}
|
||||
if (addressLookupString.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
bool isFirstRun = firstRun.get();
|
||||
|
||||
if (isFirstRun) {
|
||||
showHelp();
|
||||
}
|
||||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (hasHMDAndHandControllers) {
|
||||
if(sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (hasHMDAndHandControllers) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
|
||||
_connectionMonitor.init();
|
||||
_connectionMonitor.init();
|
||||
|
||||
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
||||
static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
|
||||
_addAssetToWorldTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS);
|
||||
connect(&_addAssetToWorldTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize);
|
||||
|
||||
// After all of the constructor is completed, then set firstRun to false.
|
||||
firstRun.set(false);
|
||||
// After all of the constructor is completed, then set firstRun to false.
|
||||
firstRun.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||
|
@ -2567,6 +2600,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
|
||||
break;
|
||||
|
||||
case Qt::Key_N:
|
||||
if (!isOption && !isShifted && isMeta) {
|
||||
DependencyManager::get<NodeList>()->toggleIgnoreRadius();
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_S:
|
||||
if (isShifted && isMeta && !isOption) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
|
||||
|
@ -4851,7 +4890,7 @@ void Application::resetSensors(bool andReload) {
|
|||
DependencyManager::get<EyeTracker>()->reset();
|
||||
getActiveDisplayPlugin()->resetSensors();
|
||||
_overlayConductor.centerUI();
|
||||
getMyAvatar()->reset(andReload);
|
||||
getMyAvatar()->reset(true, andReload);
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
|
@ -5156,6 +5195,11 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
// AvatarManager has some custom types
|
||||
AvatarManager::registerMetaTypes(scriptEngine);
|
||||
|
||||
if (property(hifi::properties::TEST).isValid()) {
|
||||
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
|
||||
}
|
||||
|
||||
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
|
||||
|
||||
// hook our avatar and avatar hash map object into this script engine
|
||||
|
@ -6113,6 +6157,7 @@ void Application::updateDisplayMode() {
|
|||
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
|
||||
resizeGL();
|
||||
});
|
||||
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
|
||||
first = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace MenuOption {
|
|||
const QString LoadScript = "Open and Run Script File...";
|
||||
const QString LoadScriptURL = "Open and Run Script from URL...";
|
||||
const QString LodTools = "LOD Tools";
|
||||
const QString Login = "Login";
|
||||
const QString Login = "Login / Sign Up";
|
||||
const QString Log = "Log";
|
||||
const QString LogExtraTimings = "Log Extra Timing Details";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
|
|
|
@ -80,7 +80,7 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
|
||||
// when we hear that the user has ignored an avatar by session UUID
|
||||
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
||||
connect(nodeList.data(), &NodeList::ignoredNode, this, &AvatarManager::removeAvatar);
|
||||
connect(nodeList.data(), "ignoredNode", this, "removeAvatar");
|
||||
}
|
||||
|
||||
AvatarManager::~AvatarManager() {
|
||||
|
@ -223,16 +223,16 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
|
|||
}
|
||||
|
||||
// virtual
|
||||
void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
|
||||
void AvatarManager::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
|
||||
QWriteLocker locker(&_hashLock);
|
||||
|
||||
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||
if (removedAvatar) {
|
||||
handleRemovedAvatar(removedAvatar);
|
||||
handleRemovedAvatar(removedAvatar, removalReason);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
|
||||
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||
AvatarHashMap::handleRemovedAvatar(removedAvatar);
|
||||
|
||||
// removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar
|
||||
|
@ -247,6 +247,9 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
_motionStatesToRemoveFromPhysics.push_back(motionState);
|
||||
}
|
||||
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
|
||||
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
|
||||
}
|
||||
_avatarFades.push_back(removedAvatar);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ public slots:
|
|||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||
|
||||
private slots:
|
||||
virtual void removeAvatar(const QUuid& sessionUUID) override;
|
||||
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
|
||||
|
||||
private:
|
||||
explicit AvatarManager(QObject* parent = 0);
|
||||
|
@ -91,7 +91,7 @@ private:
|
|||
virtual AvatarSharedPointer newSharedAvatar() override;
|
||||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
|
||||
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) override;
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
|
||||
|
||||
QVector<AvatarSharedPointer> _avatarFades;
|
||||
std::shared_ptr<MyAvatar> _myAvatar;
|
||||
|
|
|
@ -230,6 +230,10 @@ void MyAvatar::simulateAttachments(float deltaTime) {
|
|||
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
|
||||
CameraMode mode = qApp->getCamera()->getMode();
|
||||
_globalPosition = getPosition();
|
||||
_globalBoundingBoxCorner.x = _characterController.getCapsuleRadius();
|
||||
_globalBoundingBoxCorner.y = _characterController.getCapsuleHalfHeight();
|
||||
_globalBoundingBoxCorner.z = _characterController.getCapsuleRadius();
|
||||
_globalBoundingBoxCorner += _characterController.getCapsuleLocalOffset();
|
||||
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
|
||||
// fake the avatar position that is sent up to the AvatarMixer
|
||||
glm::vec3 oldPosition = getPosition();
|
||||
|
@ -360,10 +364,18 @@ void MyAvatar::update(float deltaTime) {
|
|||
updateFromTrackers(deltaTime);
|
||||
|
||||
// Get audio loudness data from audio input device
|
||||
// Also get the AudioClient so we can update the avatar bounding box data
|
||||
// on the AudioClient side.
|
||||
auto audio = DependencyManager::get<AudioClient>();
|
||||
head->setAudioLoudness(audio->getLastInputLoudness());
|
||||
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
|
||||
|
||||
glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius());
|
||||
halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
|
||||
QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
|
||||
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
||||
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
|
||||
|
||||
if (_avatarEntityDataLocallyEdited) {
|
||||
sendIdentityPacket();
|
||||
}
|
||||
|
|
30
interface/src/scripting/TestScriptingInterface.cpp
Normal file
30
interface/src/scripting/TestScriptingInterface.cpp
Normal 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() {
|
||||
}
|
43
interface/src/scripting/TestScriptingInterface.h
Normal file
43
interface/src/scripting/TestScriptingInterface.h
Normal 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
|
|
@ -11,9 +11,10 @@
|
|||
|
||||
#include "LoginDialog.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <NetworkingConstants.h>
|
||||
#include <steamworks-wrapper/SteamClient.h>
|
||||
|
@ -47,7 +48,7 @@ void LoginDialog::toggleAction() {
|
|||
connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout);
|
||||
} else {
|
||||
// change the menu item to login
|
||||
loginAction->setText("Login");
|
||||
loginAction->setText("Login / Sign Up");
|
||||
connection = connect(loginAction, &QAction::triggered, [] {
|
||||
LoginDialog::show();
|
||||
});
|
||||
|
@ -153,3 +154,83 @@ void LoginDialog::createFailed(QNetworkReply& reply) {
|
|||
emit handleCreateFailed(reply.errorString());
|
||||
}
|
||||
|
||||
void LoginDialog::signup(const QString& email, const QString& username, const QString& password) {
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "signupCompleted";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "signupFailed";
|
||||
|
||||
QJsonObject payload;
|
||||
|
||||
QJsonObject userObject;
|
||||
userObject.insert("email", email);
|
||||
userObject.insert("username", username);
|
||||
userObject.insert("password", password);
|
||||
|
||||
payload.insert("user", userObject);
|
||||
|
||||
static const QString API_SIGNUP_PATH = "api/v1/users";
|
||||
|
||||
qDebug() << "Sending a request to create an account for" << username;
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None,
|
||||
QNetworkAccessManager::PostOperation, callbackParams,
|
||||
QJsonDocument(payload).toJson());
|
||||
}
|
||||
|
||||
void LoginDialog::signupCompleted(QNetworkReply& reply) {
|
||||
emit handleSignupCompleted();
|
||||
}
|
||||
|
||||
QString errorStringFromAPIObject(const QJsonValue& apiObject) {
|
||||
if (apiObject.isArray()) {
|
||||
return apiObject.toArray()[0].toString();
|
||||
} else if (apiObject.isString()) {
|
||||
return apiObject.toString();
|
||||
} else {
|
||||
return "is invalid";
|
||||
}
|
||||
}
|
||||
|
||||
void LoginDialog::signupFailed(QNetworkReply& reply) {
|
||||
|
||||
// parse the returned JSON to see what the problem was
|
||||
auto jsonResponse = QJsonDocument::fromJson(reply.readAll());
|
||||
|
||||
static const QString RESPONSE_DATA_KEY = "data";
|
||||
|
||||
auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY];
|
||||
|
||||
if (dataJsonValue.isObject()) {
|
||||
auto dataObject = dataJsonValue.toObject();
|
||||
|
||||
static const QString EMAIL_DATA_KEY = "email";
|
||||
static const QString USERNAME_DATA_KEY = "username";
|
||||
static const QString PASSWORD_DATA_KEY = "password";
|
||||
|
||||
QStringList errorStringList;
|
||||
|
||||
if (dataObject.contains(EMAIL_DATA_KEY)) {
|
||||
errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY])));
|
||||
}
|
||||
|
||||
if (dataObject.contains(USERNAME_DATA_KEY)) {
|
||||
errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY])));
|
||||
}
|
||||
|
||||
if (dataObject.contains(PASSWORD_DATA_KEY)) {
|
||||
errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY])));
|
||||
}
|
||||
|
||||
emit handleSignupFailed(errorStringList.join('\n'));
|
||||
} else {
|
||||
static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later.";
|
||||
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,9 @@ signals:
|
|||
|
||||
void handleCreateCompleted();
|
||||
void handleCreateFailed(QString error);
|
||||
|
||||
void handleSignupCompleted();
|
||||
void handleSignupFailed(QString errorString);
|
||||
|
||||
public slots:
|
||||
void linkCompleted(QNetworkReply& reply);
|
||||
|
@ -43,6 +46,9 @@ public slots:
|
|||
|
||||
void createCompleted(QNetworkReply& reply);
|
||||
void createFailed(QNetworkReply& reply);
|
||||
|
||||
void signupCompleted(QNetworkReply& reply);
|
||||
void signupFailed(QNetworkReply& reply);
|
||||
|
||||
protected slots:
|
||||
Q_INVOKABLE bool isSteamRunning() const;
|
||||
|
@ -51,6 +57,8 @@ protected slots:
|
|||
Q_INVOKABLE void loginThroughSteam();
|
||||
Q_INVOKABLE void linkSteam();
|
||||
Q_INVOKABLE void createAccountFromStream(QString username = QString());
|
||||
|
||||
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
|
||||
|
||||
Q_INVOKABLE void openUrl(const QString& url) const;
|
||||
|
||||
|
|
|
@ -68,18 +68,6 @@ void setupPreferences() {
|
|||
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
|
||||
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return nodeList->getIgnoreRadius(); };
|
||||
auto setter = [=](float value) {
|
||||
nodeList->ignoreNodesInRadius(value, nodeList->getIgnoreRadiusEnabled());
|
||||
};
|
||||
auto preference = new SpinnerPreference(AVATAR_BASICS, "Personal space bubble radius (default is 1m)", getter, setter);
|
||||
preference->setMin(0.01f);
|
||||
preference->setMax(99.9f);
|
||||
preference->setDecimals(2);
|
||||
preference->setStep(0.25);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
// UI
|
||||
{
|
||||
|
|
|
@ -1063,7 +1063,9 @@ void AudioClient::handleAudioInput() {
|
|||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
|
||||
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType, _selectedCodecName);
|
||||
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber,
|
||||
audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale,
|
||||
packetType, _selectedCodecName);
|
||||
_stats.sentPacket();
|
||||
|
||||
int bytesInInputRingBuffer = _inputRingBuffer.samplesAvailable() * AudioConstants::SAMPLE_SIZE;
|
||||
|
@ -1085,7 +1087,9 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
|
|||
}
|
||||
|
||||
// FIXME check a flag to see if we should echo audio?
|
||||
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
|
||||
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber,
|
||||
audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale,
|
||||
PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
|
||||
}
|
||||
|
||||
void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||
|
@ -1098,13 +1102,37 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
|
||||
if (injector->getLocalBuffer()) {
|
||||
|
||||
qint64 samplesToRead = injector->isStereo() ? AudioConstants::NETWORK_FRAME_BYTES_STEREO : AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
static const int HRTF_DATASET_INDEX = 1;
|
||||
|
||||
// get one frame from the injector (mono or stereo)
|
||||
memset(_scratchBuffer, 0, sizeof(_scratchBuffer));
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) {
|
||||
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
|
||||
// get one frame from the injector
|
||||
memset(_scratchBuffer, 0, bytesToRead);
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, bytesToRead)) {
|
||||
|
||||
if (injector->isStereo()) {
|
||||
if (injector->isAmbisonic()) {
|
||||
|
||||
// no distance attenuation
|
||||
float gain = injector->getVolume();
|
||||
|
||||
//
|
||||
// Calculate the soundfield orientation relative to the listener.
|
||||
// Injector orientation can be used to align a recording to our world coordinates.
|
||||
//
|
||||
glm::quat relativeOrientation = injector->getOrientation() * glm::inverse(_orientationGetter());
|
||||
|
||||
// convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system
|
||||
float qw = relativeOrientation.w;
|
||||
float qx = -relativeOrientation.z;
|
||||
float qy = -relativeOrientation.x;
|
||||
float qz = relativeOrientation.y;
|
||||
|
||||
// Ambisonic gets spatialized into mixBuffer
|
||||
injector->getLocalFOA().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
} else if (injector->isStereo()) {
|
||||
|
||||
// stereo gets directly mixed into mixBuffer
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||
|
@ -1120,7 +1148,8 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
float azimuth = azimuthForSource(relativePosition);
|
||||
|
||||
// mono gets spatialized into mixBuffer
|
||||
injector->getLocalHRTF().render(_scratchBuffer, mixBuffer, 1, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
injector->getLocalHRTF().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -1225,8 +1254,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
|
||||
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
|
||||
Lock lock(_injectorsMutex);
|
||||
if (injector->getLocalBuffer() && _audioInput ) {
|
||||
// just add it to the vector of active local injectors, if
|
||||
|
@ -1595,3 +1623,8 @@ void AudioClient::saveSettings() {
|
|||
dynamicJitterBufferEnabled.set(_receivedAudioStream.dynamicJitterBufferEnabled());
|
||||
staticJitterBufferFrames.set(_receivedAudioStream.getStaticJitterBufferFrames());
|
||||
}
|
||||
|
||||
void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale) {
|
||||
avatarBoundingBoxCorner = corner;
|
||||
avatarBoundingBoxScale = scale;
|
||||
}
|
||||
|
|
|
@ -127,6 +127,8 @@ public:
|
|||
void setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; }
|
||||
void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
|
||||
|
||||
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
|
||||
|
||||
QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; }
|
||||
|
||||
void checkDevices();
|
||||
|
@ -169,7 +171,7 @@ public slots:
|
|||
|
||||
int setOutputBufferSize(int numFrames, bool persist = true);
|
||||
|
||||
bool outputLocalInjector(bool isStereo, AudioInjector* injector) override;
|
||||
bool outputLocalInjector(AudioInjector* injector) override;
|
||||
bool shouldLoopbackInjectors() override { return _shouldEchoToServer; }
|
||||
|
||||
bool switchInputToAudioDevice(const QString& inputDeviceName);
|
||||
|
@ -297,7 +299,7 @@ private:
|
|||
|
||||
// for local hrtf-ing
|
||||
float _mixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
|
||||
AudioLimiter _audioLimiter;
|
||||
|
||||
// Adds Reverb
|
||||
|
@ -324,6 +326,9 @@ private:
|
|||
AudioPositionGetter _positionGetter;
|
||||
AudioOrientationGetter _orientationGetter;
|
||||
|
||||
glm::vec3 avatarBoundingBoxCorner;
|
||||
glm::vec3 avatarBoundingBoxScale;
|
||||
|
||||
QVector<QString> _inputDevices;
|
||||
QVector<QString> _outputDevices;
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
#include "AudioConstants.h"
|
||||
|
||||
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
|
||||
const Transform& transform, PacketType packetType, QString codecName) {
|
||||
void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
|
||||
const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale,
|
||||
PacketType packetType, QString codecName) {
|
||||
static std::mutex _mutex;
|
||||
using Locker = std::unique_lock<std::mutex>;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -55,6 +56,10 @@ void AbstractAudioInterface::emitAudioPacket(const void* audioData, size_t bytes
|
|||
// pack the orientation
|
||||
audioPacket->writePrimitive(transform.getRotation());
|
||||
|
||||
audioPacket->writePrimitive(avatarBoundingBoxCorner);
|
||||
audioPacket->writePrimitive(avatarBoundingBoxScale);
|
||||
|
||||
|
||||
if (audioPacket->getType() != PacketType::SilentAudioFrame) {
|
||||
// audio samples have already been packed (written to networkAudioSamples)
|
||||
int leadingBytes = audioPacket->getPayloadSize();
|
||||
|
|
|
@ -28,11 +28,12 @@ class AbstractAudioInterface : public QObject {
|
|||
public:
|
||||
AbstractAudioInterface(QObject* parent = 0) : QObject(parent) {};
|
||||
|
||||
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber, const Transform& transform,
|
||||
static void emitAudioPacket(const void* audioData, size_t bytes, quint16& sequenceNumber,
|
||||
const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale,
|
||||
PacketType packetType, QString codecName = QString(""));
|
||||
|
||||
public slots:
|
||||
virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0;
|
||||
virtual bool outputLocalInjector(AudioInjector* injector) = 0;
|
||||
virtual bool shouldLoopbackInjectors() { return false; }
|
||||
|
||||
virtual void setIsStereoInput(bool stereo) = 0;
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace AudioConstants {
|
|||
const int SAMPLE_RATE = 24000;
|
||||
const int MONO = 1;
|
||||
const int STEREO = 2;
|
||||
|
||||
const int AMBISONIC = 4;
|
||||
|
||||
typedef int16_t AudioSample;
|
||||
const int SAMPLE_SIZE = sizeof(AudioSample);
|
||||
|
@ -33,6 +33,7 @@ namespace AudioConstants {
|
|||
const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / SAMPLE_SIZE;
|
||||
const int NETWORK_FRAME_BYTES_PER_CHANNEL = NETWORK_FRAME_BYTES_STEREO / 2;
|
||||
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / SAMPLE_SIZE;
|
||||
const int NETWORK_FRAME_SAMPLES_AMBISONIC = NETWORK_FRAME_SAMPLES_PER_CHANNEL * AMBISONIC;
|
||||
const float NETWORK_FRAME_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE));
|
||||
const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f;
|
||||
const float NETWORK_FRAMES_PER_SEC = 1.0f / NETWORK_FRAME_SECS;
|
||||
|
|
1095
libraries/audio/src/AudioFOA.cpp
Normal file
1095
libraries/audio/src/AudioFOA.cpp
Normal file
File diff suppressed because it is too large
Load diff
62
libraries/audio/src/AudioFOA.h
Normal file
62
libraries/audio/src/AudioFOA.h
Normal 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
|
17735
libraries/audio/src/AudioFOAData.h
Normal file
17735
libraries/audio/src/AudioFOAData.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -58,8 +58,10 @@ void AudioInjector::setOptions(const AudioInjectorOptions& options) {
|
|||
// since options.stereo is computed from the audio stream,
|
||||
// we need to copy it from existing options just in case.
|
||||
bool currentlyStereo = _options.stereo;
|
||||
bool currentlyAmbisonic = _options.ambisonic;
|
||||
_options = options;
|
||||
_options.stereo = currentlyStereo;
|
||||
_options.ambisonic = currentlyAmbisonic;
|
||||
}
|
||||
|
||||
void AudioInjector::finishNetworkInjection() {
|
||||
|
@ -134,7 +136,8 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*
|
|||
|
||||
int byteOffset = 0;
|
||||
if (_options.secondOffset > 0.0f) {
|
||||
byteOffset = (int)floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f));
|
||||
int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1);
|
||||
byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels);
|
||||
byteOffset *= sizeof(AudioConstants::SAMPLE_SIZE);
|
||||
}
|
||||
_currentSendOffset = byteOffset;
|
||||
|
@ -169,7 +172,7 @@ bool AudioInjector::injectLocally() {
|
|||
_localBuffer->setCurrentOffset(_currentSendOffset);
|
||||
|
||||
// call this function on the AudioClient's thread
|
||||
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(bool, _options.stereo), Q_ARG(AudioInjector*, this));
|
||||
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this));
|
||||
|
||||
if (!success) {
|
||||
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "AudioInjectorLocalBuffer.h"
|
||||
#include "AudioInjectorOptions.h"
|
||||
#include "AudioHRTF.h"
|
||||
#include "AudioFOA.h"
|
||||
#include "Sound.h"
|
||||
|
||||
class AbstractAudioInterface;
|
||||
|
@ -59,11 +60,14 @@ public:
|
|||
|
||||
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
|
||||
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
||||
AudioFOA& getLocalFOA() { return _localFOA; }
|
||||
|
||||
bool isLocalOnly() const { return _options.localOnly; }
|
||||
float getVolume() const { return _options.volume; }
|
||||
glm::vec3 getPosition() const { return _options.position; }
|
||||
glm::quat getOrientation() const { return _options.orientation; }
|
||||
bool isStereo() const { return _options.stereo; }
|
||||
bool isAmbisonic() const { return _options.ambisonic; }
|
||||
|
||||
bool stateHas(AudioInjectorState state) const ;
|
||||
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
||||
|
@ -113,6 +117,7 @@ private:
|
|||
|
||||
// when the injector is local, we need this
|
||||
AudioHRTF _localHRTF;
|
||||
AudioFOA _localFOA;
|
||||
friend class AudioInjectorManager;
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ AudioInjectorOptions::AudioInjectorOptions() :
|
|||
loop(false),
|
||||
orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
|
||||
stereo(false),
|
||||
ambisonic(false),
|
||||
ignorePenumbra(false),
|
||||
localOnly(false),
|
||||
secondOffset(0.0)
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
bool loop;
|
||||
glm::quat orientation;
|
||||
bool stereo;
|
||||
bool ambisonic;
|
||||
bool ignorePenumbra;
|
||||
bool localOnly;
|
||||
float secondOffset;
|
||||
|
|
|
@ -77,6 +77,8 @@ int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteA
|
|||
|
||||
packetStream.readRawData(reinterpret_cast<char*>(&_position), sizeof(_position));
|
||||
packetStream.readRawData(reinterpret_cast<char*>(&_orientation), sizeof(_orientation));
|
||||
packetStream.readRawData(reinterpret_cast<char*>(&_avatarBoundingBoxCorner), sizeof(_avatarBoundingBoxCorner));
|
||||
packetStream.readRawData(reinterpret_cast<char*>(&_avatarBoundingBoxScale), sizeof(_avatarBoundingBoxScale));
|
||||
|
||||
// if this node sent us a NaN for first float in orientation then don't consider this good audio and bail
|
||||
if (glm::isnan(_orientation.x)) {
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
PositionalAudioStream::Type getType() const { return _type; }
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
const glm::quat& getOrientation() const { return _orientation; }
|
||||
const glm::vec3& getAvatarBoundingBoxCorner() const { return _avatarBoundingBoxCorner; }
|
||||
const glm::vec3& getAvatarBoundingBoxScale() const { return _avatarBoundingBoxScale; }
|
||||
|
||||
|
||||
protected:
|
||||
|
@ -60,6 +62,9 @@ protected:
|
|||
glm::vec3 _position;
|
||||
glm::quat _orientation;
|
||||
|
||||
glm::vec3 _avatarBoundingBoxCorner;
|
||||
glm::vec3 _avatarBoundingBoxScale;
|
||||
|
||||
bool _shouldLoopbackForNode;
|
||||
bool _isStereo;
|
||||
// Ignore penumbra filter
|
||||
|
|
|
@ -43,9 +43,10 @@ SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _so
|
|||
QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
|
||||
}
|
||||
|
||||
Sound::Sound(const QUrl& url, bool isStereo) :
|
||||
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
|
||||
Resource(url),
|
||||
_isStereo(isStereo),
|
||||
_isAmbisonic(isAmbisonic),
|
||||
_isReady(false)
|
||||
{
|
||||
|
||||
|
@ -62,8 +63,10 @@ void Sound::downloadFinished(const QByteArray& data) {
|
|||
|
||||
QByteArray outputAudioByteArray;
|
||||
|
||||
interpretAsWav(rawAudioByteArray, outputAudioByteArray);
|
||||
downSample(outputAudioByteArray);
|
||||
int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray);
|
||||
if (sampleRate != 0) {
|
||||
downSample(outputAudioByteArray, sampleRate);
|
||||
}
|
||||
} else if (fileName.endsWith(RAW_EXTENSION)) {
|
||||
// check if this was a stereo raw file
|
||||
// since it's raw the only way for us to know that is if the file was called .stereo.raw
|
||||
|
@ -72,8 +75,8 @@ void Sound::downloadFinished(const QByteArray& data) {
|
|||
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
|
||||
}
|
||||
|
||||
// Process as RAW file
|
||||
downSample(rawAudioByteArray);
|
||||
// Process as 48khz RAW file
|
||||
downSample(rawAudioByteArray, 48000);
|
||||
} else {
|
||||
qCDebug(audio) << "Unknown sound file type";
|
||||
}
|
||||
|
@ -84,29 +87,80 @@ void Sound::downloadFinished(const QByteArray& data) {
|
|||
emit ready();
|
||||
}
|
||||
|
||||
void Sound::downSample(const QByteArray& rawAudioByteArray) {
|
||||
// assume that this was a RAW file and is now an array of samples that are
|
||||
// signed, 16-bit, 48Khz
|
||||
void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
||||
|
||||
// we want to convert it to the format that the audio-mixer wants
|
||||
// which is signed, 16-bit, 24Khz
|
||||
|
||||
int numChannels = _isStereo ? 2 : 1;
|
||||
AudioSRC resampler(48000, AudioConstants::SAMPLE_RATE, numChannels);
|
||||
if (sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(maxDestinationBytes);
|
||||
// no resampling needed
|
||||
_byteArray = rawAudioByteArray;
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)_byteArray.data(),
|
||||
numSourceFrames);
|
||||
} else if (_isAmbisonic) {
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(numDestinationBytes);
|
||||
// FIXME: add a proper Ambisonic resampler!
|
||||
int numChannels = 4;
|
||||
AudioSRC resampler[4] { {sampleRate, AudioConstants::SAMPLE_RATE, 1},
|
||||
{sampleRate, AudioConstants::SAMPLE_RATE, 1},
|
||||
{sampleRate, AudioConstants::SAMPLE_RATE, 1},
|
||||
{sampleRate, AudioConstants::SAMPLE_RATE, 1} };
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler[0].getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(maxDestinationBytes);
|
||||
|
||||
int numDestinationFrames = 0;
|
||||
|
||||
// iterate over channels
|
||||
int16_t* srcBuffer = new int16_t[numSourceFrames];
|
||||
int16_t* dstBuffer = new int16_t[maxDestinationFrames];
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
|
||||
int16_t* src = (int16_t*)rawAudioByteArray.data();
|
||||
int16_t* dst = (int16_t*)_byteArray.data();
|
||||
|
||||
// deinterleave samples
|
||||
for (int i = 0; i < numSourceFrames; i++) {
|
||||
srcBuffer[i] = src[4*i + ch];
|
||||
}
|
||||
|
||||
// resample one channel
|
||||
numDestinationFrames = resampler[ch].render(srcBuffer, dstBuffer, numSourceFrames);
|
||||
|
||||
// reinterleave samples
|
||||
for (int i = 0; i < numDestinationFrames; i++) {
|
||||
dst[4*i + ch] = dstBuffer[i];
|
||||
}
|
||||
}
|
||||
delete[] srcBuffer;
|
||||
delete[] dstBuffer;
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(numDestinationBytes);
|
||||
|
||||
} else {
|
||||
|
||||
int numChannels = _isStereo ? 2 : 1;
|
||||
AudioSRC resampler(sampleRate, AudioConstants::SAMPLE_RATE, numChannels);
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(maxDestinationBytes);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)_byteArray.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(numDestinationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -160,7 +214,8 @@ struct CombinedHeader {
|
|||
WAVEHeader wave;
|
||||
};
|
||||
|
||||
void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
// returns wavfile sample rate, used for resampling
|
||||
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
|
||||
CombinedHeader fileHeader;
|
||||
|
||||
|
@ -174,36 +229,35 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
|
|||
// descriptor.id == "RIFX" also signifies BigEndian file
|
||||
// waveStream.setByteOrder(QDataStream::BigEndian);
|
||||
qCDebug(audio) << "Currently not supporting big-endian audio files.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncmp(fileHeader.riff.type, "WAVE", 4) != 0
|
||||
|| strncmp(fileHeader.wave.descriptor.id, "fmt", 3) != 0) {
|
||||
qCDebug(audio) << "Not a WAVE Audio file.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// added the endianess check as an extra level of security
|
||||
|
||||
if (qFromLittleEndian<quint16>(fileHeader.wave.audioFormat) != 1) {
|
||||
qCDebug(audio) << "Currently not supporting non PCM audio files.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 2) {
|
||||
_isStereo = true;
|
||||
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) > 2) {
|
||||
qCDebug(audio) << "Currently not support audio files with more than 2 channels.";
|
||||
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 4) {
|
||||
_isAmbisonic = true;
|
||||
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) != 1) {
|
||||
qCDebug(audio) << "Currently not support audio files with other than 1/2/4 channels.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (qFromLittleEndian<quint16>(fileHeader.wave.bitsPerSample) != 16) {
|
||||
qCDebug(audio) << "Currently not supporting non 16bit audio files.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
if (qFromLittleEndian<quint32>(fileHeader.wave.sampleRate) != 48000) {
|
||||
qCDebug(audio) << "Currently not supporting non 48KHz audio files.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Skip any extra data in the WAVE chunk
|
||||
waveStream.skipRawData(fileHeader.wave.descriptor.size - (sizeof(WAVEHeader) - sizeof(chunk)));
|
||||
|
||||
|
@ -218,7 +272,7 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
|
|||
waveStream.skipRawData(dataHeader.descriptor.size);
|
||||
} else {
|
||||
qCDebug(audio) << "Could not read wav audio data header.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,12 +281,14 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou
|
|||
outputAudioByteArray.resize(outputAudioByteArraySize);
|
||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
||||
qCDebug(audio) << "Error reading WAV file";
|
||||
return 0;
|
||||
}
|
||||
|
||||
_duration = (float) (outputAudioByteArraySize / (fileHeader.wave.sampleRate * fileHeader.wave.numChannels * fileHeader.wave.bitsPerSample / 8.0f));
|
||||
return fileHeader.wave.sampleRate;
|
||||
|
||||
} else {
|
||||
qCDebug(audio) << "Could not read wav audio file header.";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,10 @@ class Sound : public Resource {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Sound(const QUrl& url, bool isStereo = false);
|
||||
Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false);
|
||||
|
||||
bool isStereo() const { return _isStereo; }
|
||||
bool isAmbisonic() const { return _isAmbisonic; }
|
||||
bool isReady() const { return _isReady; }
|
||||
float getDuration() const { return _duration; }
|
||||
|
||||
|
@ -37,11 +38,12 @@ signals:
|
|||
private:
|
||||
QByteArray _byteArray;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
bool _isReady;
|
||||
float _duration; // In seconds
|
||||
|
||||
void downSample(const QByteArray& rawAudioByteArray);
|
||||
void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
|
||||
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
};
|
||||
|
|
1346
libraries/audio/src/avx2/AudioFOA_avx2.cpp
Normal file
1346
libraries/audio/src/avx2/AudioFOA_avx2.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -55,6 +55,7 @@ namespace AvatarDataPacket {
|
|||
PACKED_BEGIN struct Header {
|
||||
float position[3]; // skeletal model's position
|
||||
float globalPosition[3]; // avatar's position
|
||||
float globalBoundingBoxCorner[3]; // global position of the lowest corner of the avatar's bounding box
|
||||
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
|
||||
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
|
||||
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
||||
|
@ -64,7 +65,7 @@ namespace AvatarDataPacket {
|
|||
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
|
||||
uint8_t flags;
|
||||
} PACKED_END;
|
||||
const size_t HEADER_SIZE = 69;
|
||||
const size_t HEADER_SIZE = 81;
|
||||
|
||||
// only present if HAS_REFERENTIAL flag is set in header.flags
|
||||
PACKED_BEGIN struct ParentInfo {
|
||||
|
@ -205,6 +206,9 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
header->globalPosition[0] = _globalPosition.x;
|
||||
header->globalPosition[1] = _globalPosition.y;
|
||||
header->globalPosition[2] = _globalPosition.z;
|
||||
header->globalBoundingBoxCorner[0] = getPosition().x - _globalBoundingBoxCorner.x;
|
||||
header->globalBoundingBoxCorner[1] = getPosition().y - _globalBoundingBoxCorner.y;
|
||||
header->globalBoundingBoxCorner[2] = getPosition().z - _globalBoundingBoxCorner.z;
|
||||
|
||||
glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation()));
|
||||
packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y);
|
||||
|
@ -481,6 +485,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
|
||||
glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]);
|
||||
_globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]);
|
||||
_globalBoundingBoxCorner = glm::vec3(header->globalBoundingBoxCorner[0], header->globalBoundingBoxCorner[1], header->globalBoundingBoxCorner[2]);
|
||||
if (isNaN(position)) {
|
||||
if (shouldLogError(now)) {
|
||||
qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID();
|
||||
|
|
|
@ -133,6 +133,15 @@ enum KeyState {
|
|||
DELETE_KEY_DOWN
|
||||
};
|
||||
|
||||
enum KillAvatarReason : uint8_t {
|
||||
NoReason = 0,
|
||||
AvatarDisconnected,
|
||||
AvatarIgnored,
|
||||
TheirAvatarEnteredYourBubble,
|
||||
YourAvatarEnteredTheirBubble
|
||||
};
|
||||
Q_DECLARE_METATYPE(KillAvatarReason);
|
||||
|
||||
class QDataStream;
|
||||
|
||||
class AttachmentData;
|
||||
|
@ -353,6 +362,7 @@ public:
|
|||
void fromJson(const QJsonObject& json);
|
||||
|
||||
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
|
||||
glm::vec3 getGlobalBoundingBoxCorner() { return _globalBoundingBoxCorner; }
|
||||
|
||||
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
|
||||
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
||||
|
@ -436,6 +446,7 @@ protected:
|
|||
// where Entities are located. This is currently only used by the mixer to decide how often to send
|
||||
// updates about one avatar to another.
|
||||
glm::vec3 _globalPosition;
|
||||
glm::vec3 _globalBoundingBoxCorner;
|
||||
|
||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||
|
|
|
@ -141,20 +141,23 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
// read the node id
|
||||
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
removeAvatar(sessionUUID);
|
||||
|
||||
KillAvatarReason reason;
|
||||
message->readPrimitive(&reason);
|
||||
removeAvatar(sessionUUID, reason);
|
||||
}
|
||||
|
||||
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) {
|
||||
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
|
||||
QWriteLocker locker(&_hashLock);
|
||||
|
||||
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||
|
||||
if (removedAvatar) {
|
||||
handleRemovedAvatar(removedAvatar);
|
||||
handleRemovedAvatar(removedAvatar, removalReason);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
|
||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||
qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||
<< "from AvatarHashMap";
|
||||
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||
|
|
|
@ -65,9 +65,9 @@ protected:
|
|||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||
AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock
|
||||
virtual void removeAvatar(const QUuid& sessionUUID);
|
||||
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
|
||||
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar);
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
|
||||
|
||||
AvatarHash _avatarHash;
|
||||
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
|
||||
|
|
|
@ -104,11 +104,8 @@ void Node::addIgnoredNode(const QUuid& otherNodeID) {
|
|||
|
||||
void Node::parseIgnoreRadiusRequestMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
bool enabled;
|
||||
float radius;
|
||||
message->readPrimitive(&enabled);
|
||||
message->readPrimitive(&radius);
|
||||
_ignoreRadiusEnabled = enabled;
|
||||
_ignoreRadius = radius;
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const Node& node) {
|
||||
|
|
|
@ -80,7 +80,6 @@ public:
|
|||
friend QDataStream& operator>>(QDataStream& in, Node& node);
|
||||
|
||||
bool isIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled; }
|
||||
float getIgnoreRadius() { return _ignoreRadiusEnabled ? _ignoreRadius.load() : std::numeric_limits<float>::max(); }
|
||||
|
||||
private:
|
||||
// privatize copy and assignment operator to disallow Node copying
|
||||
|
@ -100,7 +99,6 @@ private:
|
|||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet;
|
||||
|
||||
std::atomic_bool _ignoreRadiusEnabled;
|
||||
std::atomic<float> _ignoreRadius { 0.0f };
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Node*)
|
||||
|
|
|
@ -750,10 +750,9 @@ bool NodeList::sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr) {
|
|||
return _domainHandler.getSockAddr() == sockAddr || LimitedNodeList::sockAddrBelongsToNode(sockAddr);
|
||||
}
|
||||
|
||||
void NodeList::ignoreNodesInRadius(float radiusToIgnore, bool enabled) {
|
||||
void NodeList::ignoreNodesInRadius(bool enabled) {
|
||||
bool isEnabledChange = _ignoreRadiusEnabled.get() != enabled;
|
||||
_ignoreRadiusEnabled.set(enabled);
|
||||
_ignoreRadius.set(radiusToIgnore);
|
||||
|
||||
eachMatchingNode([](const SharedNodePointer& node)->bool {
|
||||
return (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer);
|
||||
|
@ -768,7 +767,6 @@ void NodeList::ignoreNodesInRadius(float radiusToIgnore, bool enabled) {
|
|||
void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode) {
|
||||
auto ignorePacket = NLPacket::create(PacketType::RadiusIgnoreRequest, sizeof(bool) + sizeof(float), true);
|
||||
ignorePacket->writePrimitive(_ignoreRadiusEnabled.get());
|
||||
ignorePacket->writePrimitive(_ignoreRadius.get());
|
||||
sendPacket(std::move(ignorePacket), *destinationNode);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,12 +71,11 @@ public:
|
|||
|
||||
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
||||
|
||||
void ignoreNodesInRadius(float radiusToIgnore, bool enabled = true);
|
||||
float getIgnoreRadius() const { return _ignoreRadius.get(); }
|
||||
void ignoreNodesInRadius(bool enabled = true);
|
||||
bool getIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled.get(); }
|
||||
void toggleIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), !getIgnoreRadiusEnabled()); }
|
||||
void enableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), true); }
|
||||
void disableIgnoreRadius() { ignoreNodesInRadius(getIgnoreRadius(), false); }
|
||||
void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); }
|
||||
void enableIgnoreRadius() { ignoreNodesInRadius(true); }
|
||||
void disableIgnoreRadius() { ignoreNodesInRadius(false); }
|
||||
void ignoreNodeBySessionID(const QUuid& nodeID);
|
||||
bool isIgnoringNode(const QUuid& nodeID) const;
|
||||
|
||||
|
@ -156,7 +155,6 @@ private:
|
|||
|
||||
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
||||
Setting::Handle<float> _ignoreRadius { "IgnoreRadius", 1.0f };
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
bool _shouldSendNewerVersion { false };
|
||||
|
|
|
@ -53,7 +53,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HandControllerJoints);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HasKillAvatarReason);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
|
|
@ -202,7 +202,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
AvatarEntities,
|
||||
AbsoluteSixByteRotations,
|
||||
SensorToWorldMat,
|
||||
HandControllerJoints
|
||||
HandControllerJoints,
|
||||
HasKillAvatarReason
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -205,7 +205,8 @@ public:
|
|||
|
||||
|
||||
signals:
|
||||
void recommendedFramebufferSizeChanged(const QSize & size);
|
||||
void recommendedFramebufferSizeChanged(const QSize& size);
|
||||
void resetSensorsRequested();
|
||||
|
||||
protected:
|
||||
void incrementPresentCount();
|
||||
|
|
|
@ -45,6 +45,9 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound
|
|||
// stereo option isn't set from script, this comes from sound metadata or filename
|
||||
AudioInjectorOptions optionsCopy = injectorOptions;
|
||||
optionsCopy.stereo = sound->isStereo();
|
||||
optionsCopy.ambisonic = sound->isAmbisonic();
|
||||
optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic
|
||||
|
||||
auto injector = AudioInjector::playSound(sound->getByteArray(), optionsCopy);
|
||||
if (!injector) {
|
||||
return NULL;
|
||||
|
|
|
@ -52,14 +52,6 @@ void UsersScriptingInterface::disableIgnoreRadius() {
|
|||
DependencyManager::get<NodeList>()->disableIgnoreRadius();
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::setIgnoreRadius(float radius, bool enabled) {
|
||||
DependencyManager::get<NodeList>()->ignoreNodesInRadius(radius, enabled);
|
||||
}
|
||||
|
||||
float UsersScriptingInterface::getIgnoreRadius() {
|
||||
return DependencyManager::get<NodeList>()->getIgnoreRadius();
|
||||
}
|
||||
|
||||
bool UsersScriptingInterface::getIgnoreRadiusEnabled() {
|
||||
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
|
||||
}
|
||||
|
|
|
@ -76,21 +76,6 @@ public slots:
|
|||
*/
|
||||
void disableIgnoreRadius();
|
||||
|
||||
/**jsdoc
|
||||
* sets the parameters for the ignore radius feature.
|
||||
* @function Users.setIgnoreRadius
|
||||
* @param {number} radius The radius for the auto ignore in radius feature
|
||||
* @param {bool} [enabled=true] Whether the ignore in radius feature should be enabled
|
||||
*/
|
||||
void setIgnoreRadius(float radius, bool enabled = true);
|
||||
|
||||
/**jsdoc
|
||||
* Returns the effective radius of the ingore radius feature if it is enabled.
|
||||
* @function Users.getIgnoreRadius
|
||||
* @return {number} radius of the ignore feature
|
||||
*/
|
||||
float getIgnoreRadius();
|
||||
|
||||
/**jsdoc
|
||||
* Returns `true` if the ignore in radius feature is enabled
|
||||
* @function Users.getIgnoreRadiusEnabled
|
||||
|
@ -101,6 +86,12 @@ public slots:
|
|||
signals:
|
||||
void canKickChanged(bool canKick);
|
||||
void ignoreRadiusEnabledChanged(bool isEnabled);
|
||||
|
||||
/**jsdoc
|
||||
* Notifies scripts that another user has entered the ignore radius
|
||||
* @function Users.enteredIgnoreRadius
|
||||
*/
|
||||
void enteredIgnoreRadius();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -508,6 +508,11 @@ void AABox::embiggen(const glm::vec3& scale) {
|
|||
_scale *= scale;
|
||||
}
|
||||
|
||||
void AABox::setScaleStayCentered(const glm::vec3& scale) {
|
||||
_corner += -0.5f * scale;
|
||||
_scale = scale;
|
||||
}
|
||||
|
||||
void AABox::scale(float scale) {
|
||||
_corner *= scale;
|
||||
_scale *= scale;
|
||||
|
|
|
@ -96,6 +96,9 @@ public:
|
|||
void embiggen(float scale);
|
||||
void embiggen(const glm::vec3& scale);
|
||||
|
||||
// Set a new scale for the Box, but keep it centered at its current location
|
||||
void setScaleStayCentered(const glm::vec3& scale);
|
||||
|
||||
// Transform the extents with transform
|
||||
void transform(const Transform& transform);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace hifi { namespace properties {
|
|||
const char* CRASHED = "com.highfidelity.crashed";
|
||||
const char* STEAM = "com.highfidelity.launchedFromSteam";
|
||||
const char* LOGGER = "com.highfidelity.logger";
|
||||
const char* TEST = "com.highfidelity.test";
|
||||
|
||||
namespace gl {
|
||||
const char* BACKEND = "com.highfidelity.gl.backend";
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace hifi { namespace properties {
|
|||
extern const char* CRASHED;
|
||||
extern const char* STEAM;
|
||||
extern const char* LOGGER;
|
||||
extern const char* TEST;
|
||||
|
||||
namespace gl {
|
||||
extern const char* BACKEND;
|
||||
|
|
|
@ -21,6 +21,15 @@ void OculusBaseDisplayPlugin::resetSensors() {
|
|||
}
|
||||
|
||||
bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
||||
handleOVREvents();
|
||||
if (quitRequested()) {
|
||||
QMetaObject::invokeMethod(qApp, "quit");
|
||||
return false;
|
||||
}
|
||||
if (reorientRequested()) {
|
||||
emit resetSensorsRequested();
|
||||
}
|
||||
|
||||
_currentRenderFrameInfo = FrameInfo();
|
||||
_currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
|
||||
_currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex);
|
||||
|
|
|
@ -20,15 +20,15 @@
|
|||
#include <controllers/Pose.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(oculus)
|
||||
Q_LOGGING_CATEGORY(oculus, "hifi.plugins.oculus")
|
||||
|
||||
static std::atomic<uint32_t> refCount { 0 };
|
||||
static ovrSession session { nullptr };
|
||||
|
||||
static bool _quitRequested { false };
|
||||
static bool _reorientRequested { false };
|
||||
|
||||
inline ovrErrorInfo getError() {
|
||||
ovrErrorInfo error;
|
||||
ovr_GetLastErrorInfo(&error);
|
||||
|
@ -116,6 +116,26 @@ void releaseOculusSession() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void handleOVREvents() {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
ovrSessionStatus status;
|
||||
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
|
||||
return;
|
||||
}
|
||||
|
||||
_quitRequested = status.ShouldQuit;
|
||||
_reorientRequested = status.ShouldRecenter;
|
||||
}
|
||||
|
||||
bool quitRequested() {
|
||||
return _quitRequested;
|
||||
}
|
||||
bool reorientRequested() {
|
||||
return _reorientRequested;
|
||||
}
|
||||
|
||||
controller::Pose ovrControllerPoseToHandPose(
|
||||
ovrHandType hand,
|
||||
|
|
|
@ -20,6 +20,10 @@ bool oculusAvailable();
|
|||
ovrSession acquireOculusSession();
|
||||
void releaseOculusSession();
|
||||
|
||||
void handleOVREvents();
|
||||
bool quitRequested();
|
||||
bool reorientRequested();
|
||||
|
||||
// Convenience method for looping over each eye with a lambda
|
||||
template <typename Function>
|
||||
inline void ovr_for_each_eye(Function function) {
|
||||
|
|
15
scripts/developer/tests/testTestMode.js
Normal file
15
scripts/developer/tests/testTestMode.js
Normal 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);
|
||||
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
Entities.deleteEntity(this.modelEntityID);
|
||||
// create new model at centre of the photobooth
|
||||
var newProps = {
|
||||
name: "Photo Booth Model",
|
||||
type: "Model",
|
||||
modelURL: newModelURL,
|
||||
position: this.centrePos
|
||||
|
|
|
@ -8,69 +8,28 @@
|
|||
"green": 245,
|
||||
"red": 245
|
||||
},
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.05000000074505806,
|
||||
"y": 0.05000000074505806,
|
||||
"z": 0.0099999997764825821
|
||||
},
|
||||
"id": "{4a7b6258-ccc5-472e-ba41-dfd224115bee}",
|
||||
"id": "{3925ba89-b94a-4555-97d4-18c217655b73}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"name": "Photo Booth Camera:Right Camera",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{838ac5ff-5e06-4768-9389-9796577c5bc5}",
|
||||
"position": {
|
||||
"x": -0.022934332489967346,
|
||||
"y": -0.25898283720016479,
|
||||
"z": 0.17889007925987244
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.071414284408092499,
|
||||
"x": 15.183169364929199,
|
||||
"y": -192.90565490722656,
|
||||
"z": 25.429607391357422
|
||||
},
|
||||
"rotation": {
|
||||
"w": -7.62939453125e-05,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": 1,
|
||||
"z": -4.57763671875e-05
|
||||
},
|
||||
"shape": "Cube",
|
||||
"type": "Box",
|
||||
"visible": 0
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"color": {
|
||||
"blue": 149,
|
||||
"green": 245,
|
||||
"red": 245
|
||||
},
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 0.05000000074505806,
|
||||
"y": 0.05000000074505806,
|
||||
"z": 0.0099999997764825821
|
||||
},
|
||||
"id": "{81ae005c-4738-4359-8860-98d00c8dd3a4}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Photo Booth Camera:Main Camera",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{d5d16926-6f05-4411-931a-3ff8c897d728}",
|
||||
"parentID": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
|
||||
"position": {
|
||||
"x": -0.021826114505529404,
|
||||
"y": -0.25215747952461243,
|
||||
"z": 0.17469465732574463
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.071414284408092499,
|
||||
"x": 16.758693695068359,
|
||||
"y": -193.97714233398438,
|
||||
"z": 24.816326141357422
|
||||
"scale": 0.2142428457736969,
|
||||
"x": 18.361545562744141,
|
||||
"y": -200.90092468261719,
|
||||
"z": -10.209855079650879
|
||||
},
|
||||
"rotation": {
|
||||
"w": -1.52587890625e-05,
|
||||
|
@ -90,18 +49,18 @@
|
|||
"green": 245,
|
||||
"red": 245
|
||||
},
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.05000000074505806,
|
||||
"y": 0.05000000074505806,
|
||||
"z": 0.0099999997764825821
|
||||
},
|
||||
"id": "{77817ac3-0862-46b6-8648-fdb8b855e4cb}",
|
||||
"id": "{d2bbcea8-7cdc-4f16-a017-a4567fd82a61}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Photo Booth Camera:Left Camera",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{2fc83747-6652-4fd1-bf21-c3d44ad610ea}",
|
||||
"parentID": "{349e0687-52d4-4e0d-a03c-88f5bf427ce8}",
|
||||
"position": {
|
||||
"x": -0.021829158067703247,
|
||||
"y": -0.25214886665344238,
|
||||
|
@ -109,9 +68,9 @@
|
|||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.071414284408092499,
|
||||
"x": 18.187423706054688,
|
||||
"y": -193.3980712890625,
|
||||
"z": 25.408010482788086
|
||||
"x": 19.931711196899414,
|
||||
"y": -200.49835205078125,
|
||||
"z": -10.340259552001953
|
||||
},
|
||||
"rotation": {
|
||||
"w": -1.52587890625e-05,
|
||||
|
@ -126,7 +85,91 @@
|
|||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"color": {
|
||||
"blue": 149,
|
||||
"green": 245,
|
||||
"red": 245
|
||||
},
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.05000000074505806,
|
||||
"y": 0.05000000074505806,
|
||||
"z": 0.0099999997764825821
|
||||
},
|
||||
"id": "{24f1dbc2-6feb-4d01-81f6-9c88d41a7fcc}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Photo Booth Camera:Right Camera",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{f81a5b69-6d64-4a79-8233-a4cc182bf9f8}",
|
||||
"position": {
|
||||
"x": -0.022934332489967346,
|
||||
"y": -0.25898283720016479,
|
||||
"z": 0.17889007925987244
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.2142428457736969,
|
||||
"x": 17.050743103027344,
|
||||
"y": -199.81195068359375,
|
||||
"z": -9.5248327255249023
|
||||
},
|
||||
"rotation": {
|
||||
"w": -7.62939453125e-05,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": 1,
|
||||
"z": -4.57763671875e-05
|
||||
},
|
||||
"shape": "Cube",
|
||||
"type": "Box",
|
||||
"visible": 0
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.43360000848770142,
|
||||
"y": 0.65679997205734253,
|
||||
"z": 0.42155000567436218
|
||||
},
|
||||
"gravity": {
|
||||
"x": 0,
|
||||
"y": -9,
|
||||
"z": 0
|
||||
},
|
||||
"id": "{349e0687-52d4-4e0d-a03c-88f5bf427ce8}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
|
||||
"name": "35 MM SLR by Lazybones",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": -1.3280529975891113,
|
||||
"y": -0.65367329120635986,
|
||||
"z": 2.894162654876709
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 2.678412914276123,
|
||||
"x": 18.716829299926758,
|
||||
"y": -201.52906799316406,
|
||||
"z": -11.754700660705566
|
||||
},
|
||||
"rotation": {
|
||||
"w": 0.25635090470314026,
|
||||
"x": 0.016817826777696609,
|
||||
"y": 0.96435952186584473,
|
||||
"z": -0.063177049160003662
|
||||
},
|
||||
"scriptTimestamp": 1479859451129,
|
||||
"shapeType": "simple-hull",
|
||||
"type": "Model",
|
||||
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"LeftHand\":[{\"x\":-0.23937,\"y\":0.334177,\"z\":0.150116},{\"x\":-0.31183,\"y\":0.535888,\"z\":-0.37311,\"w\":-0.69021}],\"RightHand\":[{\"x\":0.11031082272529602,\"y\":0.19449540972709656,\"z\":0.0405043363571167},{\"x\":0.2807741165161133,\"y\":0.6332069635391235,\"z\":0.2997693121433258,\"w\":-0.6557632088661194}]}}}"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.43360000848770142,
|
||||
"y": 0.65679997205734253,
|
||||
|
@ -137,28 +180,29 @@
|
|||
"y": -9,
|
||||
"z": 0
|
||||
},
|
||||
"id": "{d5d16926-6f05-4411-931a-3ff8c897d728}",
|
||||
"id": "{16f2cec8-2ba0-4567-ac3c-89b2779b1351}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
|
||||
"name": "35 MM SLR by Lazybones",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 2.5208168029785156,
|
||||
"y": 2.11322021484375,
|
||||
"z": 1.1888446807861328
|
||||
"x": 0.23746109008789062,
|
||||
"y": -1.0055081844329834,
|
||||
"z": 2.7560458183288574
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.89282792806625366,
|
||||
"x": 2.0744028091430664,
|
||||
"y": 1.6668062210083008,
|
||||
"z": 0.74243068695068359
|
||||
"scale": 2.6784837245941162,
|
||||
"x": 17.151266098022461,
|
||||
"y": -201.88088989257812,
|
||||
"z": -11.616677284240723
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": -1.52587890625e-05,
|
||||
"z": -1.52587890625e-05
|
||||
"w": 4.6566128730773926e-10,
|
||||
"x": 4.6566128730773926e-10,
|
||||
"y": 1,
|
||||
"z": 3.0517578125e-05
|
||||
},
|
||||
"scriptTimestamp": 1479859505510,
|
||||
"shapeType": "simple-hull",
|
||||
|
@ -168,7 +212,7 @@
|
|||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"created": "2016-12-10T00:09:18Z",
|
||||
"dimensions": {
|
||||
"x": 0.43360000848770142,
|
||||
"y": 0.65679997205734253,
|
||||
|
@ -179,28 +223,29 @@
|
|||
"y": -9,
|
||||
"z": 0
|
||||
},
|
||||
"id": "{838ac5ff-5e06-4768-9389-9796577c5bc5}",
|
||||
"id": "{f81a5b69-6d64-4a79-8233-a4cc182bf9f8}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
|
||||
"name": "35 MM SLR by Lazybones",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 0.86136627197265625,
|
||||
"y": 3.2271270751953125,
|
||||
"z": 1.8821067810058594
|
||||
"x": 1.5688023567199707,
|
||||
"y": 0.14506533741950989,
|
||||
"z": 1.930147647857666
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.89280432462692261,
|
||||
"x": 0.41496410965919495,
|
||||
"y": 2.7807250022888184,
|
||||
"z": 1.4357045888900757
|
||||
"scale": 2.678412914276123,
|
||||
"x": 15.81997013092041,
|
||||
"y": -200.73027038574219,
|
||||
"z": -10.790749549865723
|
||||
},
|
||||
"rotation": {
|
||||
"w": 0.91699087619781494,
|
||||
"x": 0.11256575584411621,
|
||||
"y": 0.37981235980987549,
|
||||
"z": -0.046890974044799805
|
||||
"w": -0.3342345654964447,
|
||||
"x": -0.089916400611400604,
|
||||
"y": 0.90600436925888062,
|
||||
"z": -0.24358996748924255
|
||||
},
|
||||
"scriptTimestamp": 1479859456707,
|
||||
"shapeType": "simple-hull",
|
||||
|
@ -209,236 +254,141 @@
|
|||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"collisionless": 1,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"created": "2016-12-10T01:03:54Z",
|
||||
"dimensions": {
|
||||
"x": 0.43360000848770142,
|
||||
"y": 0.65679997205734253,
|
||||
"z": 0.42155000567436218
|
||||
"x": 1.0173934698104858,
|
||||
"y": 1.1924806833267212,
|
||||
"z": 1.0164999961853027
|
||||
},
|
||||
"gravity": {
|
||||
"x": 0,
|
||||
"y": -9,
|
||||
"z": 0
|
||||
},
|
||||
"id": "{2fc83747-6652-4fd1-bf21-c3d44ad610ea}",
|
||||
"ignoreForCollisions": 1,
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/lazybonesToybox/cameras/35mm%20camera.fbx?232222",
|
||||
"name": "35 MM SLR by Lazybones",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 4.0442371368408203,
|
||||
"y": 2.7116241455078125,
|
||||
"z": 1.869842529296875
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 0.89280432462692261,
|
||||
"x": 3.5978350639343262,
|
||||
"y": 2.2652220726013184,
|
||||
"z": 1.4234403371810913
|
||||
},
|
||||
"rotation": {
|
||||
"w": 0.92196536064147949,
|
||||
"x": 0.056198954582214355,
|
||||
"y": -0.38243687152862549,
|
||||
"z": 0.023208975791931152
|
||||
},
|
||||
"scriptTimestamp": 1479859451129,
|
||||
"shapeType": "simple-hull",
|
||||
"type": "Model",
|
||||
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"LeftHand\":[{\"x\":-0.23937,\"y\":0.334177,\"z\":0.150116},{\"x\":-0.31183,\"y\":0.535888,\"z\":-0.37311,\"w\":-0.69021}],\"RightHand\":[{\"x\":0.11031082272529602,\"y\":0.19449540972709656,\"z\":0.0405043363571167},{\"x\":0.2807741165161133,\"y\":0.6332069635391235,\"z\":0.2997693121433258,\"w\":-0.6557632088661194}]}}}"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 1.0173832178115845,
|
||||
"y": 1.1924686431884766,
|
||||
"z": 1.0164898633956909
|
||||
},
|
||||
"id": "{541efd7c-7e5f-40d5-b6ed-8e195afe9197}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"id": "{5d8e6ca9-eec6-4cd9-9931-343c8e122f5d}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/alan/dev/Test-Object-7-metal.fbx",
|
||||
"name": "Photo Booth Model:Default",
|
||||
"name": "Photo Booth Model",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 2.5627613067626953,
|
||||
"y": 1.8016510009765625,
|
||||
"z": 3.6444053649902344
|
||||
"x": 0.19543394446372986,
|
||||
"y": -1.2587889432907104,
|
||||
"z": 0.30047845840454102
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 1.8682348728179932,
|
||||
"x": 1.6286438703536987,
|
||||
"y": 0.86753356456756592,
|
||||
"z": 2.7102880477905273
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": -1.52587890625e-05,
|
||||
"z": -1.52587890625e-05
|
||||
},
|
||||
"shapeType": "static-mesh",
|
||||
"type": "Model"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:44:49Z",
|
||||
"dimensions": {
|
||||
"x": 1.1263399124145508,
|
||||
"y": 0.55930328369140625,
|
||||
"z": 1.0736434459686279
|
||||
},
|
||||
"id": "{5a286dd0-d6d8-4ed9-a579-1c0587b63fbc}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "atp:/jimi/tutorialroom/table3.fbx",
|
||||
"name": "Photo Booth Stool",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 2.5654888153076172,
|
||||
"y": 0.9199371337890625,
|
||||
"z": 3.7379035949707031
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 1.6535331010818481,
|
||||
"x": 1.7387223243713379,
|
||||
"y": 0.093170583248138428,
|
||||
"z": 2.9111371040344238
|
||||
},
|
||||
"shapeType": "static-mesh",
|
||||
"type": "Model"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 1.3296165466308594,
|
||||
"y": 3.0967316627502441,
|
||||
"z": 2.4247901439666748
|
||||
},
|
||||
"id": "{a0cd3304-e7e3-4522-8fbb-c4cf8d234eca}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
|
||||
"name": "Photo Booth Stand Right",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 2.207672119140625,
|
||||
"z": 0.97442245483398438
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 4.1517748832702637,
|
||||
"x": -2.0758874416351318,
|
||||
"y": 0.13178467750549316,
|
||||
"z": -1.1014649868011475
|
||||
},
|
||||
"rotation": {
|
||||
"w": -0.37505149841308594,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": 0.92700088024139404,
|
||||
"z": 1.52587890625e-05
|
||||
},
|
||||
"shapeType": "static-mesh",
|
||||
"type": "Model"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 0.6701958179473877,
|
||||
"y": 3.0894412994384766,
|
||||
"z": 6.0362682342529297
|
||||
},
|
||||
"id": "{f3c937d3-4493-41a1-8928-0cae4c4ce19f}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "http://mpassets.highfidelity.com/af67a13f-7610-49b4-9723-b284fb8ff37a-v1/Dungeon-Wall-6X3.fbx",
|
||||
"name": "Photo Booth Backdrop",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 2.4534530639648438,
|
||||
"y": 2.0988006591796875,
|
||||
"z": 5.0006065368652344
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 6.8139815330505371,
|
||||
"x": -0.9535377025604248,
|
||||
"y": -1.3081901073455811,
|
||||
"z": 1.5936157703399658
|
||||
},
|
||||
"rotation": {
|
||||
"w": -0.70128941535949707,
|
||||
"x": 1.52587890625e-05,
|
||||
"y": 0.71288621425628662,
|
||||
"z": -1.52587890625e-05
|
||||
},
|
||||
"shapeType": "box",
|
||||
"type": "Model"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"color": {
|
||||
"blue": 201,
|
||||
"green": 252,
|
||||
"red": 255
|
||||
},
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 8.0457677841186523,
|
||||
"y": 1.3176910877227783,
|
||||
"z": 8.4534158706665039
|
||||
},
|
||||
"id": "{7e4f4bed-a47b-449f-acb2-cb0410847e84}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 2.4956779479980469,
|
||||
"y": 0,
|
||||
"z": 2.2972354888916016
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 11.744400024414062,
|
||||
"x": -3.3765220642089844,
|
||||
"y": -5.8722000122070312,
|
||||
"z": -3.5749645233154297
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": -1.52587890625e-05,
|
||||
"z": -1.52587890625e-05
|
||||
},
|
||||
"shape": "Cube",
|
||||
"type": "Box"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"dimensions": {
|
||||
"x": 1.3296165466308594,
|
||||
"y": 1.5271610021591187,
|
||||
"z": 2.4247901439666748
|
||||
},
|
||||
"id": "{891fb90c-ca13-46fb-b21e-0da6063ad07b}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
|
||||
"name": "Photo Booth Stand Middle",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 2.4825477600097656,
|
||||
"y": 1.4576416015625,
|
||||
"z": 0
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 3.1590676307678223,
|
||||
"x": 0.90301394462585449,
|
||||
"y": -0.12189221382141113,
|
||||
"z": -1.5795338153839111
|
||||
"scale": 5.6047611236572266,
|
||||
"x": 15.730070114135742,
|
||||
"y": -201.99229431152344,
|
||||
"z": -10.624255180358887
|
||||
},
|
||||
"rotation": {
|
||||
"w": -1.52587890625e-05,
|
||||
"x": -4.57763671875e-05,
|
||||
"x": 1.52587890625e-05,
|
||||
"y": 1,
|
||||
"z": 1.52587890625e-05
|
||||
},
|
||||
"type": "Model"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-12-10T00:50:48Z",
|
||||
"cutoff": 90,
|
||||
"dimensions": {
|
||||
"x": 6.2771282196044922,
|
||||
"y": 6.2771282196044922,
|
||||
"z": 6.2771282196044922
|
||||
},
|
||||
"falloffRadius": 0.5,
|
||||
"id": "{0fe028c8-4a14-482f-acf0-b26beb0224b2}",
|
||||
"intensity": 3,
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Stage Light 1",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 1.8010828495025635,
|
||||
"y": -0.796134352684021,
|
||||
"z": -0.071148976683616638
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 32.616912841796875,
|
||||
"x": 0.61834812164306641,
|
||||
"y": -212.75062561035156,
|
||||
"z": -23.758739471435547
|
||||
},
|
||||
"rotation": {
|
||||
"w": 4.6566128730773926e-10,
|
||||
"x": 4.6566128730773926e-10,
|
||||
"y": 1,
|
||||
"z": 3.0517578125e-05
|
||||
},
|
||||
"type": "Light"
|
||||
},
|
||||
{
|
||||
"backgroundMode": "skybox",
|
||||
"clientOnly": 0,
|
||||
"created": "2016-12-10T00:18:02Z",
|
||||
"dimensions": {
|
||||
"x": 48.120223999023438,
|
||||
"y": 16.455753326416016,
|
||||
"z": 26.184453964233398
|
||||
},
|
||||
"id": "{98031a0d-1667-4bd0-8ac2-cf0a869b46f9}",
|
||||
"keyLight": {
|
||||
"ambientIntensity": 0.69999998807907104,
|
||||
"direction": {
|
||||
"x": 0,
|
||||
"y": -0.61566150188446045,
|
||||
"z": 0.78801077604293823
|
||||
},
|
||||
"intensity": 0.30000001192092896
|
||||
},
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Stage Zone",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 0.069642722606658936,
|
||||
"y": 3.9073746204376221,
|
||||
"z": 4.0827426910400391
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 171.60348510742188,
|
||||
"x": -67.143234252929688,
|
||||
"y": -277.95611572265625,
|
||||
"z": -97.405723571777344
|
||||
},
|
||||
"rotation": {
|
||||
"w": 4.6566128730773926e-10,
|
||||
"x": 4.6566128730773926e-10,
|
||||
"y": 1,
|
||||
"z": 3.0517578125e-05
|
||||
},
|
||||
"shapeType": "box",
|
||||
"skybox": {
|
||||
"url": "http://hifi-content.s3.amazonaws.com/alan/dev/Skybox-Sun-high.png"
|
||||
},
|
||||
"type": "Zone"
|
||||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-12-10T00:11:29Z",
|
||||
"dimensions": {
|
||||
"x": 46.438556671142578,
|
||||
"y": 5.1657028198242188,
|
||||
"z": 9.020604133605957
|
||||
},
|
||||
"id": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/alan/dev/PhotoStage-v1.fbx",
|
||||
"name": "Photo Booth Stage",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"queryAACube": {
|
||||
"scale": 47.587764739990234,
|
||||
"x": -23.793882369995117,
|
||||
"y": -23.793882369995117,
|
||||
"z": -23.793882369995117
|
||||
},
|
||||
"rotation": {
|
||||
"w": -1.52587890625e-05,
|
||||
"x": -1.52587890625e-05,
|
||||
"y": -1,
|
||||
"z": -1.52587890625e-05
|
||||
},
|
||||
"shapeType": "static-mesh",
|
||||
|
@ -446,36 +396,38 @@
|
|||
},
|
||||
{
|
||||
"clientOnly": 0,
|
||||
"created": "2016-11-29T23:20:47Z",
|
||||
"created": "2016-12-10T00:50:48Z",
|
||||
"cutoff": 90,
|
||||
"dimensions": {
|
||||
"x": 1.3296165466308594,
|
||||
"y": 2.3939504623413086,
|
||||
"z": 2.4247901439666748
|
||||
"x": 6.2771282196044922,
|
||||
"y": 6.2771282196044922,
|
||||
"z": 6.2771282196044922
|
||||
},
|
||||
"id": "{3773db2f-5bd8-4d23-a3cc-53ffcc7c30e9}",
|
||||
"lastEditedBy": "{d74cd0af-624e-4d3d-a930-f6cb7e47667d}",
|
||||
"modelURL": "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/basketball/hoop.fbx",
|
||||
"name": "Photo Booth Stand - Left",
|
||||
"falloffRadius": 0.5,
|
||||
"id": "{cf16fbc7-0a5b-4850-a03a-40e1499c3c5b}",
|
||||
"intensity": 3,
|
||||
"lastEditedBy": "{42574037-43c0-4446-8d75-56a232fa2da5}",
|
||||
"name": "Stage Light 2",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"parentID": "{8da928c7-a1d0-4a2e-b256-047498ddcfe7}",
|
||||
"position": {
|
||||
"x": 4.8861484527587891,
|
||||
"y": 1.8541412353515625,
|
||||
"z": 0.99666976928710938
|
||||
"x": -1.7681519985198975,
|
||||
"y": -0.79602539539337158,
|
||||
"z": -0.071040049195289612
|
||||
},
|
||||
"queryAACube": {
|
||||
"scale": 3.6576614379882812,
|
||||
"x": 3.0573177337646484,
|
||||
"y": 0.025310516357421875,
|
||||
"z": -0.83216094970703125
|
||||
"scale": 32.616912841796875,
|
||||
"x": 4.1875829696655273,
|
||||
"y": -212.75062561035156,
|
||||
"z": -23.758739471435547
|
||||
},
|
||||
"rotation": {
|
||||
"w": 0.38268101215362549,
|
||||
"x": -4.57763671875e-05,
|
||||
"y": 0.92385745048522949,
|
||||
"z": -1.52587890625e-05
|
||||
"w": 4.6566128730773926e-10,
|
||||
"x": 4.6566128730773926e-10,
|
||||
"y": 1,
|
||||
"z": 3.0517578125e-05
|
||||
},
|
||||
"shapeType": "static-mesh",
|
||||
"type": "Model"
|
||||
"type": "Light"
|
||||
}
|
||||
],
|
||||
"Version": 65
|
||||
|
|
BIN
scripts/system/assets/models/bubble-v12.fbx
Normal file
BIN
scripts/system/assets/models/bubble-v12.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/sounds/bubble.wav
Normal file
BIN
scripts/system/assets/sounds/bubble.wav
Normal file
Binary file not shown.
93
scripts/system/avatarFinderBeacon.js
Normal file
93
scripts/system/avatarFinderBeacon.js
Normal 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();
|
||||
}
|
||||
});
|
|
@ -13,42 +13,181 @@
|
|||
/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
|
||||
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
// grab the toolbar
|
||||
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
// grab the toolbar
|
||||
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
// Used for animating and disappearing the bubble
|
||||
var bubbleOverlayTimestamp;
|
||||
// Used for flashing the HUD button upon activation
|
||||
var bubbleButtonFlashState = false;
|
||||
// Used for flashing the HUD button upon activation
|
||||
var bubbleButtonTimestamp;
|
||||
// The bubble model itself
|
||||
var bubbleOverlay = Overlays.addOverlay("model", {
|
||||
url: Script.resolvePath("assets/models/bubble-v12.fbx"), // If you'd like to change the model, modify this line (and the dimensions below)
|
||||
dimensions: { x: 1.0, y: 0.75, z: 1.0 },
|
||||
position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28, z: MyAvatar.position.z },
|
||||
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
|
||||
scale: { x: 2, y: MyAvatar.scale * 0.5 + 0.5, z: 2 },
|
||||
visible: false,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
// The bubble activation sound
|
||||
var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav"));
|
||||
// Is the update() function connected?
|
||||
var updateConnected = false;
|
||||
|
||||
var ASSETS_PATH = Script.resolvePath("assets");
|
||||
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
|
||||
const BUBBLE_VISIBLE_DURATION_MS = 3000;
|
||||
const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
|
||||
const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500;
|
||||
|
||||
function buttonImageURL() {
|
||||
return TOOLS_PATH + 'bubble.svg';
|
||||
}
|
||||
var ASSETS_PATH = Script.resolvePath("assets");
|
||||
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
|
||||
|
||||
function onBubbleToggled() {
|
||||
var bubbleActive = Users.getIgnoreRadiusEnabled();
|
||||
button.writeProperty('buttonState', bubbleActive ? 0 : 1);
|
||||
button.writeProperty('defaultState', bubbleActive ? 0 : 1);
|
||||
button.writeProperty('hoverState', bubbleActive ? 2 : 3);
|
||||
}
|
||||
function buttonImageURL() {
|
||||
return TOOLS_PATH + 'bubble.svg';
|
||||
}
|
||||
|
||||
// setup the mod button and add it to the toolbar
|
||||
var button = toolbar.addButton({
|
||||
objectName: 'bubble',
|
||||
imageURL: buttonImageURL(),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
onBubbleToggled();
|
||||
// Hides the bubble model overlay and resets the button flash state
|
||||
function hideOverlays() {
|
||||
Overlays.editOverlay(bubbleOverlay, {
|
||||
visible: false
|
||||
});
|
||||
bubbleButtonFlashState = false;
|
||||
}
|
||||
|
||||
button.clicked.connect(Users.toggleIgnoreRadius);
|
||||
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
|
||||
// Make the bubble overlay visible, set its position, and play the sound
|
||||
function createOverlays() {
|
||||
Audio.playSound(bubbleActivateSound, {
|
||||
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
|
||||
localOnly: true,
|
||||
volume: 0.4
|
||||
});
|
||||
hideOverlays();
|
||||
if (updateConnected === true) {
|
||||
updateConnected = false;
|
||||
Script.update.disconnect(update);
|
||||
}
|
||||
|
||||
// cleanup the toolbar button and overlays when script is stopped
|
||||
Script.scriptEnding.connect(function() {
|
||||
toolbar.removeButton('bubble');
|
||||
button.clicked.disconnect(Users.toggleIgnoreRadius);
|
||||
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
|
||||
});
|
||||
Overlays.editOverlay(bubbleOverlay, {
|
||||
position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28, z: MyAvatar.position.z },
|
||||
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
|
||||
scale: { x: 2, y: MyAvatar.scale * 0.5 + 0.5, z: 2 },
|
||||
visible: true
|
||||
});
|
||||
bubbleOverlayTimestamp = Date.now();
|
||||
bubbleButtonTimestamp = bubbleOverlayTimestamp;
|
||||
Script.update.connect(update);
|
||||
updateConnected = true;
|
||||
}
|
||||
|
||||
// Called from the C++ scripting interface to show the bubble overlay
|
||||
function enteredIgnoreRadius() {
|
||||
createOverlays();
|
||||
}
|
||||
|
||||
// Used to set the state of the bubble HUD button
|
||||
function writeButtonProperties(parameter) {
|
||||
button.writeProperty('buttonState', parameter ? 0 : 1);
|
||||
button.writeProperty('defaultState', parameter ? 0 : 1);
|
||||
button.writeProperty('hoverState', parameter ? 2 : 3);
|
||||
}
|
||||
|
||||
// The bubble script's update function
|
||||
update = function () {
|
||||
var timestamp = Date.now();
|
||||
var delay = (timestamp - bubbleOverlayTimestamp);
|
||||
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
|
||||
if (overlayAlpha > 0) {
|
||||
// Flash button
|
||||
if ((timestamp - bubbleButtonTimestamp) >= BUBBLE_VISIBLE_DURATION_MS) {
|
||||
writeButtonProperties(bubbleButtonFlashState);
|
||||
bubbleButtonTimestamp = timestamp;
|
||||
bubbleButtonFlashState = !bubbleButtonFlashState;
|
||||
}
|
||||
|
||||
if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) {
|
||||
Overlays.editOverlay(bubbleOverlay, {
|
||||
// Quickly raise the bubble from the ground up
|
||||
position: {
|
||||
x: MyAvatar.position.x,
|
||||
y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * 0.28,
|
||||
z: MyAvatar.position.z
|
||||
},
|
||||
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
|
||||
scale: {
|
||||
x: 2,
|
||||
y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5),
|
||||
z: 2
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Keep the bubble in place for a couple seconds
|
||||
Overlays.editOverlay(bubbleOverlay, {
|
||||
position: {
|
||||
x: MyAvatar.position.x,
|
||||
y: MyAvatar.position.y + MyAvatar.scale * 0.28,
|
||||
z: MyAvatar.position.z
|
||||
},
|
||||
rotation: Quat.fromPitchYawRollDegrees(MyAvatar.bodyPitch, 0, MyAvatar.bodyRoll),
|
||||
scale: {
|
||||
x: 2,
|
||||
y: MyAvatar.scale * 0.5 + 0.5,
|
||||
z: 2
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
hideOverlays();
|
||||
if (updateConnected === true) {
|
||||
Script.update.disconnect(update);
|
||||
updateConnected = false;
|
||||
}
|
||||
var bubbleActive = Users.getIgnoreRadiusEnabled();
|
||||
writeButtonProperties(bubbleActive);
|
||||
}
|
||||
};
|
||||
|
||||
// When the space bubble is toggled...
|
||||
function onBubbleToggled() {
|
||||
var bubbleActive = Users.getIgnoreRadiusEnabled();
|
||||
writeButtonProperties(bubbleActive);
|
||||
if (bubbleActive) {
|
||||
createOverlays();
|
||||
} else {
|
||||
hideOverlays();
|
||||
if (updateConnected === true) {
|
||||
Script.update.disconnect(update);
|
||||
updateConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the bubble button and add it to the toolbar
|
||||
var button = toolbar.addButton({
|
||||
objectName: 'bubble',
|
||||
imageURL: buttonImageURL(),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
onBubbleToggled();
|
||||
|
||||
button.clicked.connect(Users.toggleIgnoreRadius);
|
||||
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
|
||||
Users.enteredIgnoreRadius.connect(enteredIgnoreRadius);
|
||||
|
||||
// Cleanup the toolbar button and overlays when script is stopped
|
||||
Script.scriptEnding.connect(function () {
|
||||
toolbar.removeButton('bubble');
|
||||
button.clicked.disconnect(Users.toggleIgnoreRadius);
|
||||
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
|
||||
Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius);
|
||||
Overlays.deleteOverlay(bubbleOverlay);
|
||||
bubbleButtonFlashState = false;
|
||||
if (updateConnected === true) {
|
||||
Script.update.disconnect(update);
|
||||
}
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
Loading…
Reference in a new issue