mirror of
https://github.com/overte-org/overte.git
synced 2025-04-11 16:03:24 +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.Controls 1.4
|
||||
import Qt.labs.settings 1.0
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
|
@ -29,7 +30,9 @@ Rectangle {
|
|||
property int myCardHeight: 90
|
||||
property int rowHeight: 70
|
||||
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 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.
|
||||
|
@ -52,6 +55,16 @@ Rectangle {
|
|||
letterboxMessage.visible = true
|
||||
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
|
||||
Rectangle {
|
||||
|
@ -88,11 +101,32 @@ Rectangle {
|
|||
audioLevel: myData.audioLevel
|
||||
isMyCard: true
|
||||
// Size
|
||||
width: nameCardWidth
|
||||
width: minNameCardWidth
|
||||
height: parent.height
|
||||
// Anchors
|
||||
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
|
||||
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
|
||||
Rectangle {
|
||||
// Size
|
||||
|
@ -501,7 +497,7 @@ Rectangle {
|
|||
if (alreadyRefreshed === true) {
|
||||
letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
|
||||
} else {
|
||||
pal.sendToScript({method: 'refresh', params: message.params});
|
||||
pal.sendToScript({method: 'refresh', params: {selected: message.params}});
|
||||
}
|
||||
} else {
|
||||
// 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["projection"].setValue(frustum.getProjection());
|
||||
result["centerRadius"].setValue(frustum.getCenterRadius());
|
||||
result["fieldOfView"].setValue(frustum.getFieldOfView());
|
||||
result["aspectRatio"].setValue(frustum.getAspectRatio());
|
||||
|
||||
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
|
||||
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool 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) {
|
||||
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||
<< "from AvatarHashMap";
|
||||
<< "from AvatarHashMap" << removalReason;
|
||||
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,6 @@ signals:
|
|||
private:
|
||||
bool getRequestsDomainListData();
|
||||
void setRequestsDomainListData(bool requests);
|
||||
bool _requestsDomainListData;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -37,6 +37,15 @@ var conserveResources = true;
|
|||
|
||||
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.
|
||||
//
|
||||
|
@ -229,7 +238,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
break;
|
||||
case 'refresh':
|
||||
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", "");
|
||||
break;
|
||||
case 'updateGain':
|
||||
|
@ -271,13 +284,42 @@ function addAvatarNode(id) {
|
|||
color: color(selected, false, 0.0),
|
||||
ignoreRayIntersection: false}, selected, !conserveResources);
|
||||
}
|
||||
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
|
||||
var avatarsOfInterest = {};
|
||||
function populateUserList(selectData) {
|
||||
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
|
||||
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
|
||||
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 = {
|
||||
displayName: avatar.sessionDisplayName,
|
||||
displayName: name,
|
||||
userName: '',
|
||||
sessionId: id || '',
|
||||
audioLevel: 0.0,
|
||||
|
@ -289,10 +331,12 @@ function populateUserList(selectData) {
|
|||
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.
|
||||
Users.requestUsernameFromID(id);
|
||||
avatarsOfInterest[id] = true;
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||
});
|
||||
conserveResources = Object.keys(avatarsOfInterest).length > 20;
|
||||
sendToQml({ method: 'users', params: data });
|
||||
if (selectData) {
|
||||
selectData[2] = true;
|
||||
|
@ -317,8 +361,8 @@ var pingPong = true;
|
|||
function updateOverlays() {
|
||||
var eye = Camera.position;
|
||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
||||
if (!id) {
|
||||
return; // don't update ourself
|
||||
if (!id || !avatarsOfInterest[id]) {
|
||||
return; // don't update ourself, or avatars we're not interested in
|
||||
}
|
||||
var avatar = AvatarList.getAvatar(id);
|
||||
if (!avatar) {
|
||||
|
|
Loading…
Reference in a new issue