diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 8d9a5e6951..dd25aa4c4b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -384,18 +384,20 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { if (includeThisAvatar) { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); - _stats.numOthersIncluded++; - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); - // remember the last time we sent details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + // remember the last time we sent details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + } } avatarPacketList->endSegment(); diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index c1fea7c09b..cf5ea98b81 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -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 diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index f930424569..cf3261ee88 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -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; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b1a09b46ce..d806c042b9 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -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); } }); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 10378ff858..70b1fa4b71 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -259,6 +259,8 @@ bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3 } void Base3DOverlay::locationChanged(bool tellPhysics) { + SpatiallyNestable::locationChanged(tellPhysics); + auto itemID = getRenderItemID(); if (render::Item::isValidID(itemID)) { render::ScenePointer scene = qApp->getMain3DScene(); @@ -266,8 +268,6 @@ void Base3DOverlay::locationChanged(bool tellPhysics) { pendingChanges.updateItem(itemID); scene->enqueuePendingChanges(pendingChanges); } - // Overlays can't currently have children. - // SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also } void Base3DOverlay::parentDeleted() { diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 277a86e93f..aa06741638 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -53,7 +53,7 @@ namespace render { return overlay->getBounds(); } template <> int payloadGetLayer(const Overlay::Pointer& overlay) { - // MAgic number while we are defining the layering mechanism: + // Magic number while we are defining the layering mechanism: const int LAYER_NO_AA = 3; const int LAYER_2D = 2; const int LAYER_3D_FRONT = 1; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index cdbf5f2a85..48e5d673c9 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -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()); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index cd7f1235bb..a5bd0135e4 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1419,8 +1419,7 @@ QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare return; } parent->forEachChild([&](SpatiallyNestablePointer child) { - if (child->getParentJointIndex() == jointIndex && - child->getNestableType() != NestableType::Overlay) { + if (child->getParentJointIndex() == jointIndex) { result.push_back(child->getID()); } }); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index adef3ce2b5..2cd96a84c7 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -44,6 +44,8 @@ public: // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; + bool isGeometryLoaded() const { return (bool)_fbxGeometry; } + const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr getShapeMaterial(int shapeID) const; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 2042a16801..7c373274e4 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -114,7 +114,7 @@ public: void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); - bool isLoaded() const { return (bool)_renderGeometry; } + bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 76b98c6217..608fa937c8 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -150,7 +150,6 @@ signals: private: bool getRequestsDomainListData(); void setRequestsDomainListData(bool requests); - bool _requestsDomainListData; }; diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 86f080ae8d..701bc35ce3 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3231,9 +3231,13 @@ function MyController(hand) { } _this.previouslyUnhooked[childID] = now; + // we don't know if it's an entity or an overlay Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); + Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); + } else { Entities.editEntity(childID, { parentID: NULL_UUID }); + Overlays.editOverlay(childID, { parentID: NULL_UUID }); } } }); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index c4b41bcab5..1768d475a7 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -34,7 +34,7 @@ var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; -// var TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; +var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; // returns object with two fields: // * position - position in front of the user @@ -112,11 +112,18 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width); } + var modelURL; + if (Settings.getValue("tabletVisibleToOthers")) { + modelURL = TABLET_MODEL_PATH; + } else { + modelURL = LOCAL_TABLET_MODEL_PATH; + } + var tabletProperties = { name: "WebTablet Tablet", type: "Model", - modelURL: TABLET_MODEL_PATH, - url: TABLET_MODEL_PATH, // for overlay + modelURL: modelURL, + url: modelURL, // for overlay grabbable: true, // for overlay userData: JSON.stringify({ "grabbableKey": {"grabbable": true} diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 36ecc1f084..9df4b2df92 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -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) {