Merge branch 'master' of https://github.com/highfidelity/hifi into tuneAvatarInfo

This commit is contained in:
Brad Hefta-Gaub 2017-01-09 10:07:05 -08:00
commit ef7b9bfde0
37 changed files with 602 additions and 118 deletions

View file

@ -68,6 +68,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
} }
@ -221,6 +222,20 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet,
} }
} }
void AudioMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getOrCreateLinkedData(senderNode);
if (senderNode->getLinkedData()) {
AudioMixerClientData* nodeData = dynamic_cast<AudioMixerClientData*>(senderNode->getLinkedData());
if (nodeData != nullptr) {
bool isRequesting;
message->readPrimitive(&isRequesting);
nodeData->setRequestsDomainListData(isRequesting);
}
}
}
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) { void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRequestMessage(packet); sendingNode->parseIgnoreRequestMessage(packet);
} }

View file

@ -61,6 +61,7 @@ private slots:
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void handleNodeKilled(SharedNodePointer killedNode); void handleNodeKilled(SharedNodePointer killedNode);
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);

View file

@ -92,6 +92,8 @@ public:
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); } glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); } glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); } glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); }
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
signals: signals:
void injectorStreamFinished(const QUuid& streamIdentifier); void injectorStreamFinished(const QUuid& streamIdentifier);
@ -122,6 +124,7 @@ private:
bool _shouldFlushEncoder { false }; bool _shouldFlushEncoder { false };
bool _shouldMuteClient { false }; bool _shouldMuteClient { false };
bool _requestsDomainListData { false };
}; };
#endif // hifi_AudioMixerClientData_h #endif // hifi_AudioMixerClientData_h

View file

@ -209,8 +209,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
// and that it isn't being ignored by our listening node // and that it isn't being ignored by our listening node
// and that it isn't ignoring our listening node // and that it isn't ignoring our listening node
AudioMixerClientData* otherData = static_cast<AudioMixerClientData*>(otherNode->getLinkedData()); AudioMixerClientData* otherData = static_cast<AudioMixerClientData*>(otherNode->getLinkedData());
// When this is true, the AudioMixer will send Audio data to a client about avatars that have ignored them
bool getsAnyIgnored = nodeData->getRequestsDomainListData() && node->getCanKick();
if (otherData if (otherData
&& !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { && (!node->isIgnoringNodeWithID(otherNode->getUUID()) || (otherData->getRequestsDomainListData() && otherNode->getCanKick()))
&& (!otherNode->isIgnoringNodeWithID(node->getUUID()) || getsAnyIgnored)) {
// check to see if we're ignoring in radius // check to see if we're ignoring in radius
bool insideIgnoreRadius = false; bool insideIgnoreRadius = false;
@ -219,7 +224,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
// We'll always be inside the radius in that case. // We'll always be inside the radius in that case.
insideIgnoreRadius = true; insideIgnoreRadius = true;
// Check to see if the space bubble is enabled // Check to see if the space bubble is enabled
} else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) && (*otherNode != *node)) { } else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled())) {
// Define the minimum bubble size // Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData()); AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData());

View file

@ -299,7 +299,7 @@ void AvatarMixer::broadcastAvatarData() {
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()); AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()); AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
// Check to see if the space bubble is enabled // Check to see if the space bubble is enabled
if ((node->isIgnoreRadiusEnabled() && !getsIgnoredByMe) || (otherNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
// Define the minimum bubble size // Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
// Define the scale of the box for the current node // Define the scale of the box for the current node
@ -326,11 +326,11 @@ void AvatarMixer::broadcastAvatarData() {
// Perform the collision check between the two bounding boxes // Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) { if (nodeBox.touches(otherNodeBox)) {
nodeData->ignoreOther(node, otherNode); nodeData->ignoreOther(node, otherNode);
return false; return getsAnyIgnored;
} }
} }
// Not close enough to ignore // Not close enough to ignore
nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID()); nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID());
return true; return true;
} }
}, },

View file

@ -58,6 +58,15 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
} }
} }
void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) {
if (isRadiusIgnoring(other)) {
_radiusIgnoredOthers.erase(other);
auto exitingSpaceBubblePacket = NLPacket::create(PacketType::ExitingSpaceBubble, NUM_BYTES_RFC4122_UUID);
exitingSpaceBubblePacket->write(other.toRfc4122());
DependencyManager::get<NodeList>()->sendUnreliablePacket(*exitingSpaceBubblePacket, *self);
}
}
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
_currentViewFrustum.fromByteArray(message); _currentViewFrustum.fromByteArray(message);
} }

View file

@ -89,7 +89,7 @@ public:
glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); } void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other);
void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(SharedNodePointer self, SharedNodePointer other);
void readViewFrustumPacket(const QByteArray& message); void readViewFrustumPacket(const QByteArray& message);

View file

@ -83,6 +83,18 @@ Item {
text: "Missed Frame Count: " + root.appdropped; text: "Missed Frame Count: " + root.appdropped;
visible: root.appdropped > 0; visible: root.appdropped > 0;
} }
StatText {
text: "Long Render Count: " + root.longrenders;
visible: root.longrenders > 0;
}
StatText {
text: "Long Submit Count: " + root.longsubmits;
visible: root.longsubmits > 0;
}
StatText {
text: "Long Frame Count: " + root.longframes;
visible: root.longframes > 0;
}
StatText { StatText {
text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount
} }

View file

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

View file

@ -0,0 +1,52 @@
//
// LetterboxMessage.qml
// qml/hifi
//
// Created by Zach Fox and Howard Stearns on 1/5/2017
// Copyright 2017 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 QtQuick 2.5
import QtQuick.Controls 1.4
import "../styles-uit"
Item {
property alias text: popupText.text
property real radius: hifi.dimensions.borderRadius
visible: false
id: letterbox
anchors.fill: parent
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
radius: radius
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
anchors.centerIn: parent
radius: radius
color: "white"
FiraSansSemiBold {
id: popupText
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
letterbox.visible = false
}
}
}

View file

@ -11,21 +11,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/* TODO:
prototype:
- only show kick/mute when canKick
- margins everywhere
- column head centering
- column head font
- proper button .svg on toolbar
mvp:
- Show all participants, including ignored, and populate initial ignore/mute status.
- If name is elided, hover should scroll name left so the full name can be read.
*/
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import "../styles-uit" import "../styles-uit"
@ -104,6 +89,7 @@ Item {
border.width: 2 border.width: 2
// "ADMIN" text // "ADMIN" text
RalewaySemiBold { RalewaySemiBold {
id: adminTabText
text: "ADMIN" text: "ADMIN"
// Text size // Text size
size: hifi.fontSizes.tableHeading + 2 size: hifi.fontSizes.tableHeading + 2
@ -325,7 +311,12 @@ Item {
visible: iAmAdmin visible: iAmAdmin
color: hifi.colors.lightGrayText color: hifi.colors.lightGrayText
} }
// This Rectangle refers to the [?] popup button function letterbox(message) {
letterboxMessage.text = message;
letterboxMessage.visible = true
}
// This Rectangle refers to the [?] popup button next to "NAMES"
Rectangle { Rectangle {
color: hifi.colors.tableBackgroundLight color: hifi.colors.tableBackgroundLight
width: 20 width: 20
@ -348,50 +339,46 @@ Item {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
hoverEnabled: true hoverEnabled: true
onClicked: namesPopup.visible = true onClicked: letterbox("Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present.")
onEntered: helpText.color = hifi.colors.baseGrayHighlight onEntered: helpText.color = hifi.colors.baseGrayHighlight
onExited: helpText.color = hifi.colors.darkGray onExited: helpText.color = hifi.colors.darkGray
} }
} }
// Explanitory popup upon clicking "[?]" // This Rectangle refers to the [?] popup button next to "ADMIN"
Item { Rectangle {
visible: false visible: iAmAdmin
id: namesPopup color: adminTab.color
anchors.fill: pal width: 20
Rectangle { height: 28
anchors.right: adminTab.right
anchors.rightMargin: 31
anchors.top: adminTab.top
anchors.topMargin: 2
RalewayRegular {
id: adminHelpText
text: "[?]"
size: hifi.fontSizes.tableHeading + 2
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent anchors.fill: parent
color: "black"
opacity: 0.5
radius: hifi.dimensions.borderRadius
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
anchors.centerIn: parent
radius: hifi.dimensions.borderRadius
color: "white"
FiraSansSemiBold {
id: popupText
text: "Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present."
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: { hoverEnabled: true
namesPopup.visible = false onClicked: letterbox('Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
} "Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page.)")
onEntered: adminHelpText.color = "#94132e"
onExited: adminHelpText.color = hifi.colors.redHighlight
} }
} }
LetterboxMessage {
id: letterboxMessage
}
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
var data = optionalData || userModelData, length = data.length; var data = optionalData || userModelData, length = data.length;
@ -427,14 +414,20 @@ Item {
sortModel(); sortModel();
break; break;
case 'select': case 'select':
var sessionId = message.params[0]; var sessionIds = message.params[0];
var selected = message.params[1]; var selected = message.params[1];
var userIndex = findSessionIndex(sessionId); var userIndex = findSessionIndex(sessionIds[0]);
if (selected) { if (sessionIds.length > 1) {
table.selection.clear(); // for now, no multi-select letterbox('Only one user can be selected at a time.');
table.selection.select(userIndex); } else if (userIndex < 0) {
letterbox('The last editor is not among this list of users.');
} else { } else {
table.selection.deselect(userIndex); if (selected) {
table.selection.clear(); // for now, no multi-select
table.selection.select(userIndex);
} else {
table.selection.deselect(userIndex);
}
} }
break; break;
// Received an "updateUsername()" request from the JS // Received an "updateUsername()" request from the JS

View file

@ -95,7 +95,7 @@ Item {
readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background
readonly property color tableBackgroundLight: tableRowLightEven readonly property color tableBackgroundLight: tableRowLightEven
readonly property color tableBackgroundDark: tableRowDarkEven readonly property color tableBackgroundDark: tableRowDarkEven
readonly property color tableScrollHandleLight: tableRowLightOdd readonly property color tableScrollHandleLight: "#8F8F8F"
readonly property color tableScrollHandleDark: "#707070" readonly property color tableScrollHandleDark: "#707070"
readonly property color tableScrollBackgroundLight: tableRowLightEven readonly property color tableScrollBackgroundLight: tableRowLightEven
readonly property color tableScrollBackgroundDark: "#323232" readonly property color tableScrollBackgroundDark: "#323232"

View file

@ -6145,7 +6145,7 @@ void Application::loadScriptURLDialog() const {
void Application::toggleLogDialog() { void Application::toggleLogDialog() {
if (! _logDialog) { if (! _logDialog) {
_logDialog = new LogDialog(_glWidget, getLogger()); _logDialog = new LogDialog(nullptr, getLogger());
} }
if (_logDialog->isVisible()) { if (_logDialog->isVisible()) {

View file

@ -1339,7 +1339,10 @@ void Avatar::addToScene(AvatarSharedPointer myHandle) {
render::ScenePointer scene = qApp->getMain3DScene(); render::ScenePointer scene = qApp->getMain3DScene();
if (scene) { if (scene) {
render::PendingChanges pendingChanges; render::PendingChanges pendingChanges;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars() && !DependencyManager::get<NodeList>()->isIgnoringNode(getSessionUUID())) { auto nodelist = DependencyManager::get<NodeList>();
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()
&& !nodelist->isIgnoringNode(getSessionUUID())
&& !nodelist->isRadiusIgnoringNode(getSessionUUID())) {
addToScene(myHandle, scene, pendingChanges); addToScene(myHandle, scene, pendingChanges);
} }
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);

View file

@ -79,6 +79,7 @@ AvatarManager::AvatarManager(QObject* parent) :
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::ExitingSpaceBubble, this, "processExitingSpaceBubble");
// when we hear that the user has ignored an avatar by session UUID // when we hear that the user has ignored an avatar by session UUID
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
@ -257,6 +258,9 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius(); emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
} }
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
}
_avatarFades.push_back(removedAvatar); _avatarFades.push_back(removedAvatar);
} }

View file

@ -44,7 +44,7 @@ const QString HIGHLIGHT_COLOR = "#3366CC";
int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor"); int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor");
int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock"); int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock");
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Dialog) { LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Window) {
_logger = logger; _logger = logger;
setWindowTitle("Log"); setWindowTitle("Log");

View file

@ -127,12 +127,18 @@ void Stats::updateStats(bool force) {
auto displayPlugin = qApp->getActiveDisplayPlugin(); auto displayPlugin = qApp->getActiveDisplayPlugin();
auto stats = displayPlugin->getHardwareStats(); auto stats = displayPlugin->getHardwareStats();
STAT_UPDATE(appdropped, stats["app_dropped_frame_count"].toInt()); STAT_UPDATE(appdropped, stats["app_dropped_frame_count"].toInt());
STAT_UPDATE(longrenders, stats["long_render_count"].toInt());
STAT_UPDATE(longsubmits, stats["long_submit_count"].toInt());
STAT_UPDATE(longframes, stats["long_frame_count"].toInt());
STAT_UPDATE(renderrate, displayPlugin->renderRate()); STAT_UPDATE(renderrate, displayPlugin->renderRate());
STAT_UPDATE(presentrate, displayPlugin->presentRate()); STAT_UPDATE(presentrate, displayPlugin->presentRate());
STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate()); STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate());
STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate()); STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate());
STAT_UPDATE(stutterrate, displayPlugin->stutterRate()); STAT_UPDATE(stutterrate, displayPlugin->stutterRate());
} else { } else {
STAT_UPDATE(appdropped, -1);
STAT_UPDATE(longrenders, -1);
STAT_UPDATE(longsubmits, -1);
STAT_UPDATE(presentrate, -1); STAT_UPDATE(presentrate, -1);
STAT_UPDATE(presentnewrate, -1); STAT_UPDATE(presentnewrate, -1);
STAT_UPDATE(presentdroprate, -1); STAT_UPDATE(presentdroprate, -1);

View file

@ -40,6 +40,9 @@ class Stats : public QQuickItem {
STATS_PROPERTY(float, stutterrate, 0) STATS_PROPERTY(float, stutterrate, 0)
STATS_PROPERTY(int, appdropped, 0) STATS_PROPERTY(int, appdropped, 0)
STATS_PROPERTY(int, longsubmits, 0)
STATS_PROPERTY(int, longrenders, 0)
STATS_PROPERTY(int, longframes, 0)
STATS_PROPERTY(float, presentnewrate, 0) STATS_PROPERTY(float, presentnewrate, 0)
STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(float, presentdroprate, 0)
@ -137,6 +140,9 @@ public slots:
void forceUpdateStats() { updateStats(true); } void forceUpdateStats() { updateStats(true); }
signals: signals:
void longsubmitsChanged();
void longrendersChanged();
void longframesChanged();
void appdroppedChanged(); void appdroppedChanged();
void framerateChanged(); void framerateChanged();
void expandedChanged(); void expandedChanged();

View file

@ -161,6 +161,13 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S
removeAvatar(sessionUUID, reason); removeAvatar(sessionUUID, reason);
} }
void AvatarHashMap::processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// read the node id
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
auto nodeList = DependencyManager::get<NodeList>();
nodeList->radiusIgnoreNodeBySessionID(sessionUUID, false);
}
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock); QWriteLocker locker(&_hashLock);

View file

@ -57,6 +57,7 @@ private slots:
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
protected: protected:
AvatarHashMap(); AvatarHashMap();

View file

@ -165,6 +165,7 @@ public:
if (newPlugin) { if (newPlugin) {
bool hasVsync = true; bool hasVsync = true;
QThread::setPriority(newPlugin->getPresentPriority());
bool wantVsync = newPlugin->wantVsync(); bool wantVsync = newPlugin->wantVsync();
_context->makeCurrent(); _context->makeCurrent();
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)

View file

@ -14,6 +14,7 @@
#include <memory> #include <memory>
#include <queue> #include <queue>
#include <QtCore/QThread>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtGui/QImage> #include <QtGui/QImage>
@ -80,6 +81,7 @@ protected:
void updateCompositeFramebuffer(); void updateCompositeFramebuffer();
virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; }
virtual void compositeLayers(); virtual void compositeLayers();
virtual void compositeScene(); virtual void compositeScene();
virtual void compositeOverlay(); virtual void compositeOverlay();

View file

@ -238,6 +238,10 @@ void NodeList::reset() {
_numNoReplyDomainCheckIns = 0; _numNoReplyDomainCheckIns = 0;
// lock and clear our set of radius ignored IDs
_radiusIgnoredSetLock.lockForWrite();
_radiusIgnoredNodeIDs.clear();
_radiusIgnoredSetLock.unlock();
// lock and clear our set of ignored IDs // lock and clear our set of ignored IDs
_ignoredSetLock.lockForWrite(); _ignoredSetLock.lockForWrite();
_ignoredNodeIDs.clear(); _ignoredNodeIDs.clear();
@ -781,6 +785,22 @@ void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationN
sendPacket(std::move(ignorePacket), *destinationNode); sendPacket(std::move(ignorePacket), *destinationNode);
} }
void NodeList::radiusIgnoreNodeBySessionID(const QUuid& nodeID, bool radiusIgnoreEnabled) {
if (radiusIgnoreEnabled) {
QReadLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // read lock for insert
// add this nodeID to our set of ignored IDs
_radiusIgnoredNodeIDs.insert(nodeID);
} else {
QWriteLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // write lock for unsafe_erase
_radiusIgnoredNodeIDs.unsafe_erase(nodeID);
}
}
bool NodeList::isRadiusIgnoringNode(const QUuid& nodeID) const {
QReadLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // read lock for reading
return _radiusIgnoredNodeIDs.find(nodeID) != _radiusIgnoredNodeIDs.cend();
}
void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) { void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
// enumerate the nodes to send a reliable ignore packet to each that can leverage it // enumerate the nodes to send a reliable ignore packet to each that can leverage it
if (!nodeID.isNull() && _sessionUUID != nodeID) { if (!nodeID.isNull() && _sessionUUID != nodeID) {
@ -1020,12 +1040,12 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
} }
void NodeList::setRequestsDomainListData(bool isRequesting) { void NodeList::setRequestsDomainListData(bool isRequesting) {
// Tell the avatar mixer whether I want to receive any additional data to which I might be entitled // Tell the avatar mixer and audio mixer whether I want to receive any additional data to which I might be entitled
if (_requestsDomainListData == isRequesting) { if (_requestsDomainListData == isRequesting) {
return; return;
} }
eachMatchingNode([](const SharedNodePointer& node)->bool { eachMatchingNode([](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer; return (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer);
}, [this, isRequesting](const SharedNodePointer& destinationNode) { }, [this, isRequesting](const SharedNodePointer& destinationNode) {
auto packet = NLPacket::create(PacketType::RequestsDomainListData, sizeof(bool), true); // reliable auto packet = NLPacket::create(PacketType::RequestsDomainListData, sizeof(bool), true); // reliable
packet->writePrimitive(isRequesting); packet->writePrimitive(isRequesting);

View file

@ -76,6 +76,8 @@ public:
void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); } void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); }
void enableIgnoreRadius() { ignoreNodesInRadius(true); } void enableIgnoreRadius() { ignoreNodesInRadius(true); }
void disableIgnoreRadius() { ignoreNodesInRadius(false); } void disableIgnoreRadius() { ignoreNodesInRadius(false); }
void radiusIgnoreNodeBySessionID(const QUuid& nodeID, bool radiusIgnoreEnabled);
bool isRadiusIgnoringNode(const QUuid& other) const;
void ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled); void ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled);
bool isIgnoringNode(const QUuid& nodeID) const; bool isIgnoringNode(const QUuid& nodeID) const;
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled); void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
@ -159,6 +161,8 @@ private:
QTimer _keepAlivePingTimer; QTimer _keepAlivePingTimer;
bool _requestsDomainListData; bool _requestsDomainListData;
mutable QReadWriteLock _radiusIgnoredSetLock;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _radiusIgnoredNodeIDs;
mutable QReadWriteLock _ignoredSetLock; mutable QReadWriteLock _ignoredSetLock;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs; tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
mutable QReadWriteLock _personalMutedSetLock; mutable QReadWriteLock _personalMutedSetLock;

View file

@ -105,7 +105,8 @@ public:
UsernameFromIDReply, UsernameFromIDReply,
ViewFrustum, ViewFrustum,
RequestsDomainListData, RequestsDomainListData,
LAST_PACKET_TYPE = RequestsDomainListData ExitingSpaceBubble,
LAST_PACKET_TYPE = ExitingSpaceBubble
}; };
}; };

View file

@ -37,7 +37,7 @@ public slots:
* @param {nodeID} nodeID The node or session ID of the user you want to ignore. * @param {nodeID} nodeID The node or session ID of the user you want to ignore.
* @param {bool} enable True for ignored; false for un-ignored. * @param {bool} enable True for ignored; false for un-ignored.
*/ */
void ignore(const QUuid& nodeID, bool ignoreEnabled); void ignore(const QUuid& nodeID, bool ignoreEnabled = true);
/**jsdoc /**jsdoc
* Gets a bool containing whether you have ignored the given Avatar UUID. * Gets a bool containing whether you have ignored the given Avatar UUID.
@ -52,7 +52,7 @@ public slots:
* @param {nodeID} nodeID The node or session ID of the user you want to mute. * @param {nodeID} nodeID The node or session ID of the user you want to mute.
* @param {bool} enable True for enabled; false for disabled. * @param {bool} enable True for enabled; false for disabled.
*/ */
void personalMute(const QUuid& nodeID, bool muteEnabled); void personalMute(const QUuid& nodeID, bool muteEnabled = true);
/**jsdoc /**jsdoc
* Requests a bool containing whether you have personally muted the given Avatar UUID. * Requests a bool containing whether you have personally muted the given Avatar UUID.

View file

@ -31,6 +31,10 @@ OculusDisplayPlugin::OculusDisplayPlugin() {
bool OculusDisplayPlugin::internalActivate() { bool OculusDisplayPlugin::internalActivate() {
bool result = Parent::internalActivate(); bool result = Parent::internalActivate();
_longSubmits = 0;
_longRenders = 0;
_longFrames = 0;
currentDebugMode = ovrPerfHud_Off; currentDebugMode = ovrPerfHud_Off;
if (result && _session) { if (result && _session) {
ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode);
@ -112,35 +116,43 @@ void OculusDisplayPlugin::uncustomizeContext() {
Parent::uncustomizeContext(); Parent::uncustomizeContext();
} }
static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC);
static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC);
void OculusDisplayPlugin::hmdPresent() { void OculusDisplayPlugin::hmdPresent() {
static uint64_t lastSubmitEnd = 0;
if (!_customized) { if (!_customized) {
return; return;
} }
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
int curIndex; {
ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex); PROFILE_RANGE_EX(render, "Oculus Blit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
GLuint curTexId; int curIndex;
ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId); ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex);
GLuint curTexId;
ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId);
// Manually bind the texture to the FBO // Manually bind the texture to the FBO
// FIXME we should have a way of wrapping raw GL ids in GPU objects without // FIXME we should have a way of wrapping raw GL ids in GPU objects without
// taking ownership of the object // taking ownership of the object
auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer); auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer);
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0);
render([&](gpu::Batch& batch) { render([&](gpu::Batch& batch) {
batch.enableStereo(false); batch.enableStereo(false);
batch.setFramebuffer(_outputFramebuffer); batch.setFramebuffer(_outputFramebuffer);
batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize()));
batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize()));
batch.resetViewTransform(); batch.resetViewTransform();
batch.setProjectionTransform(mat4()); batch.setProjectionTransform(mat4());
batch.setPipeline(_presentPipeline); batch.setPipeline(_presentPipeline);
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
batch.draw(gpu::TRIANGLE_STRIP, 4); batch.draw(gpu::TRIANGLE_STRIP, 4);
}); });
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0);
}
{ {
auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain); auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain);
@ -148,8 +160,33 @@ void OculusDisplayPlugin::hmdPresent() {
_sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime; _sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime;
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
auto submitStart = usecTimestampNow();
uint64_t nonSubmitInterval = 0;
if (lastSubmitEnd != 0) {
nonSubmitInterval = submitStart - lastSubmitEnd;
if (nonSubmitInterval > FRAME_BUDGET) {
++_longRenders;
}
}
ovrLayerHeader* layers = &_sceneLayer.Header; ovrLayerHeader* layers = &_sceneLayer.Header;
result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1); {
PROFILE_RANGE_EX(render, "Oculus Submit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1);
}
lastSubmitEnd = usecTimestampNow();
if (nonSubmitInterval != 0) {
auto submitInterval = lastSubmitEnd - submitStart;
if (nonSubmitInterval < FRAME_BUDGET && submitInterval > FRAME_BUDGET) {
++_longSubmits;
}
if ((nonSubmitInterval + submitInterval) > FRAME_OVER_BUDGET) {
++_longFrames;
}
}
if (!OVR_SUCCESS(result)) { if (!OVR_SUCCESS(result)) {
logWarning("Failed to present"); logWarning("Failed to present");
} }
@ -168,6 +205,7 @@ void OculusDisplayPlugin::hmdPresent() {
_appDroppedFrames.store(appDroppedFrames); _appDroppedFrames.store(appDroppedFrames);
_compositorDroppedFrames.store(compositorDroppedFrames); _compositorDroppedFrames.store(compositorDroppedFrames);
} }
_presentRate.increment(); _presentRate.increment();
} }
@ -176,6 +214,9 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const {
QJsonObject hardwareStats; QJsonObject hardwareStats;
hardwareStats["app_dropped_frame_count"] = _appDroppedFrames.load(); hardwareStats["app_dropped_frame_count"] = _appDroppedFrames.load();
hardwareStats["compositor_dropped_frame_count"] = _compositorDroppedFrames.load(); hardwareStats["compositor_dropped_frame_count"] = _compositorDroppedFrames.load();
hardwareStats["long_render_count"] = _longRenders.load();
hardwareStats["long_submit_count"] = _longSubmits.load();
hardwareStats["long_frame_count"] = _longFrames.load();
return hardwareStats; return hardwareStats;
} }

View file

@ -24,6 +24,8 @@ public:
virtual QJsonObject getHardwareStats() const; virtual QJsonObject getHardwareStats() const;
protected: protected:
QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; }
bool internalActivate() override; bool internalActivate() override;
void hmdPresent() override; void hmdPresent() override;
bool isHmdMounted() const override; bool isHmdMounted() const override;
@ -39,5 +41,8 @@ private:
std::atomic_int _compositorDroppedFrames; std::atomic_int _compositorDroppedFrames;
std::atomic_int _appDroppedFrames; std::atomic_int _appDroppedFrames;
std::atomic_int _longSubmits;
std::atomic_int _longRenders;
std::atomic_int _longFrames;
}; };

View file

@ -18,9 +18,9 @@ var DEFAULT_SCRIPTS = [
"system/mute.js", "system/mute.js",
"system/goto.js", "system/goto.js",
"system/hmd.js", "system/hmd.js",
"system/marketplaces/marketplace.js", "system/marketplaces/marketplaces.js",
"system/edit.js", "system/edit.js",
"system/mod.js", "system/pal.js", //"system/mod.js", // older UX, if you prefer
"system/selectAudioDevice.js", "system/selectAudioDevice.js",
"system/notifications.js", "system/notifications.js",
"system/controllers/controllerDisplayManager.js", "system/controllers/controllerDisplayManager.js",

Binary file not shown.

View file

@ -20,6 +20,9 @@
// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand // When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand
// controller beam intersects the HUD. // controller beam intersects the HUD.
var systemLaserOn = false;
Script.include("../libraries/controllers.js"); Script.include("../libraries/controllers.js");
// UTILITIES ------------- // UTILITIES -------------
@ -121,6 +124,12 @@ function ignoreMouseActivity() {
if (!Reticle.allowMouseCapture) { if (!Reticle.allowMouseCapture) {
return true; return true;
} }
// if the lasers are on, then reticle/mouse should be hidden and we can ignore it for seeking or depth updating
if (systemLaserOn) {
return true;
}
var pos = Reticle.position; var pos = Reticle.position;
if (!pos || (pos.x == -1 && pos.y == -1)) { if (!pos || (pos.x == -1 && pos.y == -1)) {
return true; return true;
@ -261,6 +270,12 @@ var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20; var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20;
function isShakingMouse() { // True if the person is waving the mouse around trying to find it. function isShakingMouse() { // True if the person is waving the mouse around trying to find it.
var now = Date.now(), mouse = Reticle.position, isShaking = false; var now = Date.now(), mouse = Reticle.position, isShaking = false;
// if the lasers are on, then we ignore mouse shaking
if (systemLaserOn) {
return false;
}
if (lastIntegration && (lastIntegration !== now)) { if (lastIntegration && (lastIntegration !== now)) {
var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration); var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration);
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity); averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity);
@ -275,9 +290,14 @@ function isShakingMouse() { // True if the person is waving the mouse around try
var NON_LINEAR_DIVISOR = 2; var NON_LINEAR_DIVISOR = 2;
var MINIMUM_SEEK_DISTANCE = 0.1; var MINIMUM_SEEK_DISTANCE = 0.1;
function updateSeeking(doNotStartSeeking) { function updateSeeking(doNotStartSeeking) {
// if the lasers are on, then we never do seeking
if (systemLaserOn) {
isSeeking = false;
return;
}
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) { if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
if (!isSeeking) { if (!isSeeking) {
print('Start seeking mouse.');
isSeeking = true; isSeeking = true;
} }
} // e.g., if we're about to turn it on with first movement. } // e.g., if we're about to turn it on with first movement.
@ -287,7 +307,6 @@ function updateSeeking(doNotStartSeeking) {
averageMouseVelocity = lastIntegration = 0; averageMouseVelocity = lastIntegration = 0;
var lookAt2D = HMD.getHUDLookAtPosition2D(); var lookAt2D = HMD.getHUDLookAtPosition2D();
if (!lookAt2D) { // If this happens, something has gone terribly wrong. if (!lookAt2D) { // If this happens, something has gone terribly wrong.
print('Cannot seek without lookAt position');
isSeeking = false; isSeeking = false;
return; // E.g., if parallel to location in HUD return; // E.g., if parallel to location in HUD
} }
@ -303,7 +322,6 @@ function updateSeeking(doNotStartSeeking) {
} }
var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit. var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit.
if (okX && okY) { if (okX && okY) {
print('Finished seeking mouse');
isSeeking = false; isSeeking = false;
} else { } else {
Reticle.setPosition(copy); // Not setReticlePosition Reticle.setPosition(copy); // Not setReticlePosition
@ -322,7 +340,7 @@ function updateMouseActivity(isClick) {
return; return;
} // Bug: mouse clicks should keep going. Just not hand controller clicks } // Bug: mouse clicks should keep going. Just not hand controller clicks
handControllerLockOut.update(now); handControllerLockOut.update(now);
Reticle.visible = true; Reticle.visible = !systemLaserOn;
} }
function expireMouseCursor(now) { function expireMouseCursor(now) {
if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) { if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) {
@ -474,7 +492,6 @@ var LASER_ALPHA = 0.5;
var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA};
var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA};
var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1};
var systemLaserOn = false;
function clearSystemLaser() { function clearSystemLaser() {
if (!systemLaserOn) { if (!systemLaserOn) {
return; return;

View file

@ -25,6 +25,7 @@
<input type="button" id="visible" class="glyph" value="&#xe007;" /> <input type="button" id="visible" class="glyph" value="&#xe007;" />
</div> </div>
<input type="button" id="teleport" value="Jump To Selection" /> <input type="button" id="teleport" value="Jump To Selection" />
<input type="button" id="pal" class="glyph" value="&#xe00c;" />
<input type="button" class="red" id="delete" value="Delete" /> <input type="button" class="red" id="delete" value="Delete" />
</div> </div>
<div id="entity-list"> <div id="entity-list">
@ -94,4 +95,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -39,6 +39,7 @@ function loaded() {
elInView = document.getElementById("in-view") elInView = document.getElementById("in-view")
elRadius = document.getElementById("radius"); elRadius = document.getElementById("radius");
elTeleport = document.getElementById("teleport"); elTeleport = document.getElementById("teleport");
elPal = document.getElementById("pal");
elEntityTable = document.getElementById("entity-table"); elEntityTable = document.getElementById("entity-table");
elInfoToggle = document.getElementById("info-toggle"); elInfoToggle = document.getElementById("info-toggle");
elInfoToggleGlyph = elInfoToggle.firstChild; elInfoToggleGlyph = elInfoToggle.firstChild;
@ -274,6 +275,9 @@ function loaded() {
elTeleport.onclick = function () { elTeleport.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
} }
elPal.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
}
elDelete.onclick = function() { elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
refreshEntities(); refreshEntities();

View file

@ -98,7 +98,6 @@ EntityListTool = function(opts) {
webView.emitScriptEvent(JSON.stringify(data)); webView.emitScriptEvent(JSON.stringify(data));
} }
webView.webEventReceived.connect(function(data) { webView.webEventReceived.connect(function(data) {
data = JSON.parse(data); data = JSON.parse(data);
if (data.type == "selectionUpdate") { if (data.type == "selectionUpdate") {
@ -120,6 +119,23 @@ EntityListTool = function(opts) {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
MyAvatar.position = selectionManager.worldPosition; MyAvatar.position = selectionManager.worldPosition;
} }
} else if (data.type == "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
if (lastEditedBy) {
sessionIds[lastEditedBy] = true;
}
});
var dedupped = Object.keys(sessionIds);
if (!selectionManager.selections.length) {
Window.alert('No objects selected.');
} else if (!dedupped.length) {
Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.');
} else {
// No need to subscribe if we're just sending.
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true]}), 'local');
}
} else if (data.type == "delete") { } else if (data.type == "delete") {
deleteSelectedEntities(); deleteSelectedEntities();
} else if (data.type == "toggleLocked") { } else if (data.type == "toggleLocked") {
@ -140,4 +156,4 @@ EntityListTool = function(opts) {
}); });
return that; return that;
}; };

View file

@ -11,7 +11,19 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
// FIXME when we make this a defaultScript: (function() { // BEGIN LOCAL_SCOPE // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
// something, will revisit as this is sorta horrible.
const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
};
const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
};
const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
const SELECTED_COLOR = {red: 0xf3, green: 0x91, blue: 0x29};
(function() { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllers.js");
@ -19,8 +31,19 @@ Script.include("/~/system/libraries/controllers.js");
// Overlays. // Overlays.
// //
var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
function ExtendedOverlay(key, type, properties, selected) { // A wrapper around overlays to store the key it is associated with.
function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with.
overlays[key] = this; overlays[key] = this;
if (hasModel) {
var modelKey = key + "-m";
this.model = new ExtendedOverlay(modelKey, "model", {
url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"),
textures: textures(selected),
ignoreRayIntersection: true
}, false, false);
} else {
this.model = undefined;
}
this.key = key; this.key = key;
this.selected = selected || false; // not undefined this.selected = selected || false; // not undefined
this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
@ -34,14 +57,24 @@ ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and da
ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
Overlays.editOverlay(this.activeOverlay, properties); Overlays.editOverlay(this.activeOverlay, properties);
}; };
const UNSELECTED_COLOR = {red: 20, green: 250, blue: 20};
const SELECTED_COLOR = {red: 250, green: 20, blue: 20}; function color(selected) {
function color(selected) { return selected ? SELECTED_COLOR : UNSELECTED_COLOR; } return selected ? SELECTED_COLOR : UNSELECTED_COLOR;
}
function textures(selected) {
return selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES;
}
ExtendedOverlay.prototype.select = function (selected) { ExtendedOverlay.prototype.select = function (selected) {
if (this.selected === selected) { if (this.selected === selected) {
return; return;
} }
this.editOverlay({color: color(selected)}); this.editOverlay({color: color(selected)});
if (this.model) {
this.model.editOverlay({textures: textures(selected)});
}
this.selected = selected; this.selected = selected;
}; };
// Class methods: // Class methods:
@ -91,7 +124,7 @@ function HighlightedEntity(id, entityProperties) {
}, },
lineWidth: 1.0, lineWidth: 1.0,
ignoreRayIntersection: true, ignoreRayIntersection: true,
drawInFront: true drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
}); });
HighlightedEntity.overlays.push(this); HighlightedEntity.overlays.push(this);
} }
@ -167,12 +200,12 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
// //
function addAvatarNode(id) { function addAvatarNode(id) {
var selected = ExtendedOverlay.isSelected(id); var selected = ExtendedOverlay.isSelected(id);
return new ExtendedOverlay(id, "sphere", { // 3d so we don't go cross-eyed looking at it, but on top of everything return new ExtendedOverlay(id, "sphere", {
solid: true, drawInFront: true,
alpha: 0.8, solid: true,
color: color(selected), alpha: 0.8,
drawInFront: true color: color(selected),
}, selected); ignoreRayIntersection: false}, selected, true);
} }
function populateUserList() { function populateUserList() {
var data = []; var data = [];
@ -227,6 +260,7 @@ function updateOverlays() {
if (!id) { if (!id) {
return; // don't update ourself return; // don't update ourself
} }
var overlay = ExtendedOverlay.get(id); var overlay = ExtendedOverlay.get(id);
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
print('Adding non-PAL avatar node', id); print('Adding non-PAL avatar node', id);
@ -235,11 +269,36 @@ function updateOverlays() {
var avatar = AvatarList.getAvatar(id); var avatar = AvatarList.getAvatar(id);
var target = avatar.position; var target = avatar.position;
var distance = Vec3.distance(target, eye); var distance = Vec3.distance(target, eye);
var offset = 0.2;
// base offset on 1/2 distance from hips to head if we can
var headIndex = avatar.getJointIndex("Head");
if (headIndex > 0) {
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
}
// get diff between target and eye (a vector pointing to the eye from avatar position)
var diff = Vec3.subtract(target, eye);
// move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
// now bump it up a bit
target.y = target.y + offset;
overlay.ping = pingPong; overlay.ping = pingPong;
overlay.editOverlay({ overlay.editOverlay({
position: target, position: target,
dimensions: 0.05 * distance // constant apparent size dimensions: 0.032 * distance
}); });
if (overlay.model) {
overlay.model.ping = pingPong;
overlay.model.editOverlay({
position: target,
scale: 0.2 * distance, // constant apparent size
rotation: Camera.orientation
});
}
}); });
pingPong = !pingPong; pingPong = !pingPong;
ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
@ -262,7 +321,7 @@ function removeOverlays() {
function handleClick(pickRay) { function handleClick(pickRay) {
ExtendedOverlay.applyPickRay(pickRay, function (overlay) { ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
// Don't select directly. Tell qml, who will give us back a list of ids. // Don't select directly. Tell qml, who will give us back a list of ids.
var message = {method: 'select', params: [overlay.key, !overlay.selected]}; var message = {method: 'select', params: [[overlay.key], !overlay.selected]};
pal.sendToQml(message); pal.sendToQml(message);
return true; return true;
}); });
@ -333,6 +392,31 @@ function onClicked() {
pal.setVisible(!pal.visible); pal.setVisible(!pal.visible);
} }
//
// Message from other scripts, such as edit.js
//
var CHANNEL = 'com.highfidelity.pal';
function receiveMessage(channel, messageString, senderID) {
if ((channel !== CHANNEL) ||
(senderID !== MyAvatar.sessionUUID)) {
return;
}
var message = JSON.parse(messageString);
switch (message.method) {
case 'select':
if (!pal.visible) {
onClicked();
}
pal.sendToQml(message); // Accepts objects, not just strings.
break;
default:
print('Unrecognized PAL message', messageString);
}
}
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage);
var AVERAGING_RATIO = 0.05; var AVERAGING_RATIO = 0.05;
var LOUDNESS_FLOOR = 11.0; var LOUDNESS_FLOOR = 11.0;
var LOUDNESS_SCALE = 2.8 / 5.0; var LOUDNESS_SCALE = 2.8 / 5.0;
@ -412,8 +496,10 @@ Script.scriptEnding.connect(function () {
Users.usernameFromIDReply.disconnect(usernameFromIDReply); Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL); Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL); Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
off(); off();
}); });
// FIXME: }()); // END LOCAL_SCOPE }()); // END LOCAL_SCOPE

View file

@ -0,0 +1,169 @@
// chair.js
//
// Restrictions right now:
// Chair objects need to be set as not colliding with avatars, so that they pull avatar
// avatar into collision with them. Also they need to be at or above standing height
// (like a stool).
//
// 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
//
(function(){
var CHECK_INTERVAL_MSECS = 250; // When sitting, check for need to stand up
var SETTINGS_INTERVAL_MSECS = 1000; // Periodically check user data for updates
var DEFAULT_SIT_DISTANCE = 1.0; // How far away from the chair can you sit?
var HYSTERESIS = 1.1;
var sitTarget = { x: 0, y: 0, z: 0 }; // Offset where your butt should go relative
// to the object's center.
var SITTING = 0;
var STANDING = 1;
var state = STANDING;
var sitDistance = DEFAULT_SIT_DISTANCE;
var entity;
var props;
var checkTimer = false;
var settingsTimer = false;
var sitting = false;
var _this;
var WANT_DEBUG = false;
function debugPrint(string) {
if (WANT_DEBUG) {
print(string);
}
}
function howFarAway(position) {
return Vec3.distance(MyAvatar.position, position);
}
function isSeatOpen(position, distance) {
closest = true;
AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) {
var avatar = AvatarList.getAvatar(avatarSessionUUID);
if (avatarSessionUUID && Vec3.distance(avatar.position, position) < distance) {
debugPrint("Seat Occupied!");
closest = false;
}
});
return closest;
}
function enterSitPose() {
var rot;
var UPPER_LEG_ANGLE = 240;
var LOWER_LEG_ANGLE = -80;
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("RightUpLeg"));
MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(UPPER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("RightUpLeg"));
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("RightLeg"));
MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees(LOWER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("RightLeg"));
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("LeftUpLeg"));
MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(UPPER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("LeftUpLeg"));
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("LeftLeg"));
MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees(LOWER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("LeftLeg"));
}
function leaveSitPose() {
MyAvatar.clearJointData("RightUpLeg");
MyAvatar.clearJointData("LeftUpLeg");
MyAvatar.clearJointData("RightLeg");
MyAvatar.clearJointData("LeftLeg");
}
function sitDown(position, rotation) {
var eulers = Quat.safeEulerAngles(MyAvatar.orientation);
eulers.y = Quat.safeEulerAngles(rotation).y;
MyAvatar.position = Vec3.sum(position, Vec3.multiplyQbyV(props.rotation, sitTarget));
MyAvatar.orientation = Quat.fromPitchYawRollDegrees(eulers.x, eulers.y, eulers.z);
enterSitPose();
state = SITTING;
}
this.preload = function(entityID) {
// Load the sound and range from the entity userData fields, and note the position of the entity.
debugPrint("chair preload");
entity = entityID;
_this = this;
settingsTimer = Script.setInterval(this.checkSettings, SETTINGS_INTERVAL_MSECS);
};
this.maybeStand = function() {
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
// First, check if the entity is far enough away to not need to do anything with it
var howFar = howFarAway(props.position);
if ((state === SITTING) && (howFar > sitDistance * HYSTERESIS)) {
leaveSitPose();
Script.clearInterval(checkTimer);
checkTimer = null;
state = STANDING;
debugPrint("Standing");
}
}
this.clickDownOnEntity = function(entityID, mouseEvent) {
// If entity is clicked, sit
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
if ((state === STANDING) && isSeatOpen(props.position, sitDistance)) {
sitDown(props.position, props.rotation);
checkTimer = Script.setInterval(this.maybeStand, CHECK_INTERVAL_MSECS);
debugPrint("Sitting from mouse click");
}
}
this.startFarTrigger = function() {
// If entity is far clicked, sit
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
if ((state === STANDING) && isSeatOpen(props.position, sitDistance)) {
sitDown(props.position, props.rotation);
checkTimer = Script.setInterval(this.maybeStand, CHECK_INTERVAL_MSECS);
debugPrint("Sitting from far trigger");
}
}
this.checkSettings = function() {
var dataProps = Entities.getEntityProperties(entity, [ "userData" ]);
if (dataProps.userData) {
var data = JSON.parse(dataProps.userData);
if (data.sitDistance) {
if (!(sitDistance === data.sitDistance)) {
debugPrint("Read new sit distance: " + data.sitDistance);
}
sitDistance = data.sitDistance;
}
if (data.sitTarget) {
if (data.sitTarget.y && (data.sitTarget.y != sitTarget.y)) {
debugPrint("Read new sitTarget.y: " + data.sitTarget.y);
sitTarget.y = data.sitTarget.y;
}
if (data.sitTarget.x && (data.sitTarget.x != sitTarget.x)) {
debugPrint("Read new sitTarget.x: " + data.sitTarget.x);
sitTarget.x = data.sitTarget.x;
}
if (data.sitTarget.z && (data.sitTarget.z != sitTarget.z)) {
debugPrint("Read new sitTarget.z: " + data.sitTarget.z);
sitTarget.z = data.sitTarget.z;
}
}
}
}
this.unload = function(entityID) {
debugPrint("chair unload");
if (checkTimer) {
Script.clearInterval(checkTimer);
}
if (settingsTimer) {
Script.clearInterval(settingsTimer);
}
};
})