mirror of
https://github.com/overte-org/overte.git
synced 2025-06-15 22:19:35 +02:00
Merge pull request #9720 from howard-stearns/filter-pal-by-view-distance
Filter pal by view distance
This commit is contained in:
commit
a9b8045697
6 changed files with 91 additions and 50 deletions
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
import Qt.labs.settings 1.0
|
||||||
import "../styles-uit"
|
import "../styles-uit"
|
||||||
import "../controls-uit" as HifiControls
|
import "../controls-uit" as HifiControls
|
||||||
|
|
||||||
|
@ -29,7 +30,9 @@ Rectangle {
|
||||||
property int myCardHeight: 90
|
property int myCardHeight: 90
|
||||||
property int rowHeight: 70
|
property int rowHeight: 70
|
||||||
property int actionButtonWidth: 55
|
property int actionButtonWidth: 55
|
||||||
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
|
property int actionButtonAllowance: actionButtonWidth * 2
|
||||||
|
property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
|
||||||
|
property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance)
|
||||||
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
|
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
|
||||||
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
||||||
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
|
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
|
||||||
|
@ -52,6 +55,16 @@ Rectangle {
|
||||||
letterboxMessage.visible = true
|
letterboxMessage.visible = true
|
||||||
letterboxMessage.popupRadius = 0
|
letterboxMessage.popupRadius = 0
|
||||||
}
|
}
|
||||||
|
Settings {
|
||||||
|
id: settings
|
||||||
|
category: "pal"
|
||||||
|
property bool filtered: false
|
||||||
|
property int nearDistance: 30
|
||||||
|
}
|
||||||
|
function refreshWithFilter() {
|
||||||
|
// We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving.
|
||||||
|
pal.sendToScript({method: 'refresh', params: {filter: filter.checked && {distance: settings.nearDistance}}});
|
||||||
|
}
|
||||||
|
|
||||||
// This is the container for the PAL
|
// This is the container for the PAL
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -88,11 +101,32 @@ Rectangle {
|
||||||
audioLevel: myData.audioLevel
|
audioLevel: myData.audioLevel
|
||||||
isMyCard: true
|
isMyCard: true
|
||||||
// Size
|
// Size
|
||||||
width: nameCardWidth
|
width: minNameCardWidth
|
||||||
height: parent.height
|
height: parent.height
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
}
|
}
|
||||||
|
Row {
|
||||||
|
HifiControls.CheckBox {
|
||||||
|
id: filter
|
||||||
|
checked: settings.filtered
|
||||||
|
text: "in view"
|
||||||
|
boxSize: reload.height * 0.70
|
||||||
|
onCheckedChanged: refreshWithFilter()
|
||||||
|
}
|
||||||
|
HifiControls.GlyphButton {
|
||||||
|
id: reload
|
||||||
|
glyph: hifi.glyphs.reload
|
||||||
|
width: reload.height
|
||||||
|
onClicked: refreshWithFilter()
|
||||||
|
}
|
||||||
|
spacing: 50
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Rectangles used to cover up rounded edges on bottom of MyInfo Rectangle
|
// Rectangles used to cover up rounded edges on bottom of MyInfo Rectangle
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -306,45 +340,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Refresh button
|
|
||||||
Rectangle {
|
|
||||||
// Size
|
|
||||||
width: hifi.dimensions.tableHeaderHeight-1
|
|
||||||
height: hifi.dimensions.tableHeaderHeight-1
|
|
||||||
// Anchors
|
|
||||||
anchors.left: table.left
|
|
||||||
anchors.leftMargin: 4
|
|
||||||
anchors.top: table.top
|
|
||||||
// Style
|
|
||||||
color: hifi.colors.tableBackgroundLight
|
|
||||||
// Actual refresh icon
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: reloadButton
|
|
||||||
text: hifi.glyphs.reloadSmall
|
|
||||||
// Size
|
|
||||||
size: parent.width*1.5
|
|
||||||
// Anchors
|
|
||||||
anchors.fill: parent
|
|
||||||
// Style
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
color: hifi.colors.darkGray
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
id: reloadButtonArea
|
|
||||||
// Anchors
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
// Everyone likes a responsive refresh button!
|
|
||||||
// So use onPressed instead of onClicked
|
|
||||||
onPressed: {
|
|
||||||
reloadButton.color = hifi.colors.lightGrayText
|
|
||||||
pal.sendToScript({method: 'refresh'})
|
|
||||||
}
|
|
||||||
onReleased: reloadButton.color = (containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.darkGray)
|
|
||||||
onEntered: reloadButton.color = hifi.colors.baseGrayHighlight
|
|
||||||
onExited: reloadButton.color = (pressed ? hifi.colors.lightGrayText: hifi.colors.darkGray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Separator between user and admin functions
|
// Separator between user and admin functions
|
||||||
Rectangle {
|
Rectangle {
|
||||||
// Size
|
// Size
|
||||||
|
@ -501,7 +497,7 @@ Rectangle {
|
||||||
if (alreadyRefreshed === true) {
|
if (alreadyRefreshed === true) {
|
||||||
letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
|
letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
|
||||||
} else {
|
} else {
|
||||||
pal.sendToScript({method: 'refresh', params: message.params});
|
pal.sendToScript({method: 'refresh', params: {selected: message.params}});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we've already refreshed the PAL and found the avatar in the model
|
// If we've already refreshed the PAL and found the avatar in the model
|
||||||
|
|
|
@ -192,6 +192,8 @@ QVariantMap Camera::getViewFrustum() {
|
||||||
result["orientation"].setValue(frustum.getOrientation());
|
result["orientation"].setValue(frustum.getOrientation());
|
||||||
result["projection"].setValue(frustum.getProjection());
|
result["projection"].setValue(frustum.getProjection());
|
||||||
result["centerRadius"].setValue(frustum.getCenterRadius());
|
result["centerRadius"].setValue(frustum.getCenterRadius());
|
||||||
|
result["fieldOfView"].setValue(frustum.getFieldOfView());
|
||||||
|
result["aspectRatio"].setValue(frustum.getAspectRatio());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ AvatarManager::AvatarManager(QObject* parent) :
|
||||||
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
||||||
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) {
|
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
removeAvatar(nodeID);
|
removeAvatar(nodeID, KillAvatarReason::AvatarIgnored);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
|
||||||
|
|
||||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||||
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||||
<< "from AvatarHashMap";
|
<< "from AvatarHashMap" << removalReason;
|
||||||
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,6 @@ signals:
|
||||||
private:
|
private:
|
||||||
bool getRequestsDomainListData();
|
bool getRequestsDomainListData();
|
||||||
void setRequestsDomainListData(bool requests);
|
void setRequestsDomainListData(bool requests);
|
||||||
bool _requestsDomainListData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,15 @@ var conserveResources = true;
|
||||||
|
|
||||||
Script.include("/~/system/libraries/controllers.js");
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
|
function projectVectorOntoPlane(normalizedVector, planeNormal) {
|
||||||
|
return Vec3.cross(planeNormal, Vec3.cross(normalizedVector, planeNormal));
|
||||||
|
}
|
||||||
|
function angleBetweenVectorsInPlane(from, to, normal) {
|
||||||
|
var projectedFrom = projectVectorOntoPlane(from, normal);
|
||||||
|
var projectedTo = projectVectorOntoPlane(to, normal);
|
||||||
|
return Vec3.orientedAngle(projectedFrom, projectedTo, normal);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Overlays.
|
// Overlays.
|
||||||
//
|
//
|
||||||
|
@ -229,7 +238,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
||||||
break;
|
break;
|
||||||
case 'refresh':
|
case 'refresh':
|
||||||
removeOverlays();
|
removeOverlays();
|
||||||
populateUserList(message.params);
|
// If filter is specified from .qml instead of through settings, update the settings.
|
||||||
|
if (message.params.filter !== undefined) {
|
||||||
|
Settings.setValue('pal/filtered', !!message.params.filter);
|
||||||
|
}
|
||||||
|
populateUserList(message.params.selected);
|
||||||
UserActivityLogger.palAction("refresh", "");
|
UserActivityLogger.palAction("refresh", "");
|
||||||
break;
|
break;
|
||||||
case 'updateGain':
|
case 'updateGain':
|
||||||
|
@ -271,13 +284,42 @@ function addAvatarNode(id) {
|
||||||
color: color(selected, false, 0.0),
|
color: color(selected, false, 0.0),
|
||||||
ignoreRayIntersection: false}, selected, !conserveResources);
|
ignoreRayIntersection: false}, selected, !conserveResources);
|
||||||
}
|
}
|
||||||
|
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
|
||||||
|
var avatarsOfInterest = {};
|
||||||
function populateUserList(selectData) {
|
function populateUserList(selectData) {
|
||||||
|
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
|
||||||
var data = [], avatars = AvatarList.getAvatarIdentifiers();
|
var data = [], avatars = AvatarList.getAvatarIdentifiers();
|
||||||
conserveResources = avatars.length > 20;
|
avatarsOfInterest = {};
|
||||||
|
var myPosition = filter && Camera.position,
|
||||||
|
frustum = filter && Camera.frustum,
|
||||||
|
verticalHalfAngle = filter && (frustum.fieldOfView / 2),
|
||||||
|
horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio),
|
||||||
|
orientation = filter && Camera.orientation,
|
||||||
|
front = filter && Quat.getFront(orientation),
|
||||||
|
verticalAngleNormal = filter && Quat.getRight(orientation),
|
||||||
|
horizontalAngleNormal = filter && Quat.getUp(orientation);
|
||||||
avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging
|
avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var avatar = AvatarList.getAvatar(id);
|
||||||
|
var name = avatar.sessionDisplayName;
|
||||||
|
if (!name) {
|
||||||
|
// Either we got a data packet but no identity yet, or something is really messed up. In any case,
|
||||||
|
// we won't be able to do anything with this user, so don't include them.
|
||||||
|
// In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded,
|
||||||
|
// we could be losing and gaining people randomly.
|
||||||
|
print('No avatar identity data for', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition));
|
||||||
|
var horizontal = normal && angleBetweenVectorsInPlane(normal, front, horizontalAngleNormal);
|
||||||
|
var vertical = normal && angleBetweenVectorsInPlane(normal, front, verticalAngleNormal);
|
||||||
|
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var avatarPalDatum = {
|
var avatarPalDatum = {
|
||||||
displayName: avatar.sessionDisplayName,
|
displayName: name,
|
||||||
userName: '',
|
userName: '',
|
||||||
sessionId: id || '',
|
sessionId: id || '',
|
||||||
audioLevel: 0.0,
|
audioLevel: 0.0,
|
||||||
|
@ -289,10 +331,12 @@ function populateUserList(selectData) {
|
||||||
addAvatarNode(id); // No overlay for ourselves
|
addAvatarNode(id); // No overlay for ourselves
|
||||||
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
|
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
|
||||||
Users.requestUsernameFromID(id);
|
Users.requestUsernameFromID(id);
|
||||||
|
avatarsOfInterest[id] = true;
|
||||||
}
|
}
|
||||||
data.push(avatarPalDatum);
|
data.push(avatarPalDatum);
|
||||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||||
});
|
});
|
||||||
|
conserveResources = Object.keys(avatarsOfInterest).length > 20;
|
||||||
sendToQml({ method: 'users', params: data });
|
sendToQml({ method: 'users', params: data });
|
||||||
if (selectData) {
|
if (selectData) {
|
||||||
selectData[2] = true;
|
selectData[2] = true;
|
||||||
|
@ -317,8 +361,8 @@ var pingPong = true;
|
||||||
function updateOverlays() {
|
function updateOverlays() {
|
||||||
var eye = Camera.position;
|
var eye = Camera.position;
|
||||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
||||||
if (!id) {
|
if (!id || !avatarsOfInterest[id]) {
|
||||||
return; // don't update ourself
|
return; // don't update ourself, or avatars we're not interested in
|
||||||
}
|
}
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var avatar = AvatarList.getAvatar(id);
|
||||||
if (!avatar) {
|
if (!avatar) {
|
||||||
|
|
Loading…
Reference in a new issue