Merge branch 'master' of https://github.com/highfidelity/hifi into app-refactor4-merge

This commit is contained in:
howard-stearns 2018-07-16 17:43:53 -07:00
commit cb5ac6f42c
4 changed files with 135 additions and 82 deletions

View file

@ -1108,7 +1108,7 @@ Rectangle {
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
var data = optionalData || nearbyUserModelData, length = data.length;
for (var i = 0; i < length; i++) {
if (data[i].sessionId === sessionId) {
if (data[i].sessionId === sessionId.toString()) {
return i;
}
}
@ -1120,7 +1120,7 @@ Rectangle {
var data = message.params;
var index = -1;
iAmAdmin = Users.canKick;
index = findNearbySessionIndex('', data);
index = findNearbySessionIndex("", data);
if (index !== -1) {
myData = data[index];
data.splice(index, 1);
@ -1197,8 +1197,8 @@ Rectangle {
for (var userId in message.params) {
var audioLevel = message.params[userId][0];
var avgAudioLevel = message.params[userId][1];
// If the userId is 0, we're updating "myData".
if (userId == 0) {
// If the userId is "", we're updating "myData".
if (userId === "") {
myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;

View file

@ -14,6 +14,7 @@
#include <string>
#include <QScriptEngine>
#include <QtCore/QJsonDocument>
#include "AvatarLogging.h"
@ -668,3 +669,49 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
}
}
QVariantMap AvatarManager::getPalData(const QList<QString> specificAvatarIdentifiers) {
QJsonArray palData;
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(*itr);
QString currentSessionUUID = avatar->getSessionUUID().toString();
if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) {
QJsonObject thisAvatarPalData;
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (currentSessionUUID == myAvatar->getSessionUUID().toString()) {
currentSessionUUID = "";
}
thisAvatarPalData.insert("sessionUUID", currentSessionUUID);
thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName());
thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness());
thisAvatarPalData.insert("isReplicated", avatar->getIsReplicated());
glm::vec3 position = avatar->getWorldPosition();
QJsonObject jsonPosition;
jsonPosition.insert("x", position.x);
jsonPosition.insert("y", position.y);
jsonPosition.insert("z", position.z);
thisAvatarPalData.insert("position", jsonPosition);
float palOrbOffset = 0.2f;
int headIndex = avatar->getJointIndex("Head");
if (headIndex > 0) {
glm::vec3 jointTranslation = avatar->getAbsoluteJointTranslationInObjectFrame(headIndex);
palOrbOffset = jointTranslation.y / 2;
}
thisAvatarPalData.insert("palOrbOffset", palOrbOffset);
palData.append(thisAvatarPalData);
}
++itr;
}
QJsonObject doc;
doc.insert("data", palData);
return doc.toVariantMap();
}

View file

@ -157,6 +157,17 @@ public:
*/
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
/**jsdoc
* Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster
* than iterating over each avatar and obtaining data about them in JavaScript, as that method
* locks and unlocks each avatar's data structure potentially hundreds of times per update tick.
* @function AvatarManager.getPalData
* @param {string[]} specificAvatarIdentifiers - A list of specific Avatar Identifiers about
* which you want to get PAL data
* @returns {object}
*/
Q_INVOKABLE QVariantMap getPalData(const QList<QString> specificAvatarIdentifiers = QList<QString>());
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
int getIdentityRequestsSent() const { return _identityRequestsSent; }

View file

@ -20,7 +20,7 @@ var AppUi = Script.require('appUi');
var populateNearbyUserList, color, textures, removeOverlays,
controllerComputePickRay, off,
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
createAudioInterval, CHANNEL, getConnectionData, findableByChanged,
CHANNEL, getConnectionData, findableByChanged,
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
@ -448,21 +448,24 @@ function populateNearbyUserList(selectData, oldAudioData) {
verticalAngleNormal = filter && Quat.getRight(orientation),
horizontalAngleNormal = filter && Quat.getUp(orientation);
avatarsOfInterest = {};
avatars.forEach(function (id) {
var avatar = AvatarList.getAvatar(id);
var name = avatar.sessionDisplayName;
var avatarData = AvatarList.getPalData().data;
avatarData.forEach(function (currentAvatarData) {
var id = currentAvatarData.sessionUUID;
var name = currentAvatarData.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);
print('No avatar identity data for', currentAvatarData.sessionUUID);
return;
}
if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) {
if (id && myPosition && (Vec3.distance(currentAvatarData.position, myPosition) > filter.distance)) {
return;
}
var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition));
var normal = id && filter && Vec3.normalize(Vec3.subtract(currentAvatarData.position, myPosition));
var horizontal = normal && angleBetweenVectorsInPlane(normal, forward, horizontalAngleNormal);
var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal);
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
@ -481,11 +484,11 @@ function populateNearbyUserList(selectData, oldAudioData) {
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
ignore: !!id && Users.getIgnoreStatus(id), // ditto
isPresent: true,
isReplicated: avatar.isReplicated
isReplicated: currentAvatarData.isReplicated
};
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
Users.requestUsernameFromID(id);
if (id) {
if (id !== "") {
addAvatarNode(id); // No overlay for ourselves
avatarsOfInterest[id] = true;
} else {
@ -516,30 +519,63 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
updateUser(data);
}
function updateAudioLevel(avatarData) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
// of updating (the latter for efficiency too).
var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = avatarData.sessionUUID === "" ? myData : ExtendedOverlay.get(avatarData.sessionUUID);
if (data) {
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatarData.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
// decay avgAudioLevel
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// now scale for the gain. Also, asked to boost the low end, so one simple way is
// to take sqrt of the value. Lets try that, see how it feels.
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[avatarData.sessionUUID] || 0.75)));
}
var param = {};
var level = [audioLevel, avgAudioLevel];
var userId = avatarData.sessionUUID;
param[userId] = level;
sendToQml({ method: 'updateAudioLevel', params: param });
}
var pingPong = true;
function updateOverlays() {
var eye = Camera.position;
AvatarList.getAvatarIdentifiers().forEach(function (id) {
if (!id || !avatarsOfInterest[id]) {
var avatarData = AvatarList.getPalData().data;
avatarData.forEach(function (currentAvatarData) {
if (currentAvatarData.sessionUUID === "" || !avatarsOfInterest[currentAvatarData.sessionUUID]) {
return; // don't update ourself, or avatars we're not interested in
}
var avatar = AvatarList.getAvatar(id);
if (!avatar) {
return; // will be deleted below if there had been an overlay.
}
var overlay = ExtendedOverlay.get(id);
updateAudioLevel(currentAvatarData);
var overlay = ExtendedOverlay.get(currentAvatarData.sessionUUID);
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);
overlay = addAvatarNode(id);
print('Adding non-PAL avatar node', currentAvatarData.sessionUUID);
overlay = addAvatarNode(currentAvatarData.sessionUUID);
}
var target = avatar.position;
var target = currentAvatarData.position;
var distance = Vec3.distance(target, eye);
var offset = 0.2;
var offset = currentAvatarData.palOrbOffset;
var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
if (headIndex > 0) {
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
}
// move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
@ -549,7 +585,7 @@ function updateOverlays() {
overlay.ping = pingPong;
overlay.editOverlay({
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
color: color(ExtendedOverlay.isSelected(currentAvatarData.sessionUUID), overlay.hovering, overlay.audioLevel),
position: target,
dimensions: 0.032 * distance
});
@ -678,6 +714,14 @@ function tabletVisibilityChanged() {
if (!ui.tablet.tabletShown && ui.isOpen) {
ui.close();
}
}
var UPDATE_INTERVAL_MS = 100;
var updateInterval;
function createUpdateInterval() {
return Script.setInterval(function () {
updateOverlays();
}, UPDATE_INTERVAL_MS);
}
var previousContextOverlay = ContextOverlay.enabled;
@ -689,10 +733,8 @@ function on() {
ContextOverlay.enabled = false;
Users.requestsDomainListData = true;
audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS);
ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged);
Script.update.connect(updateOverlays);
updateInterval = createUpdateInterval();
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
Users.usernameFromIDReply.connect(usernameFromIDReply);
@ -742,50 +784,6 @@ function scaleAudio(val) {
return audioLevel;
}
function getAudioLevel(id) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
// of updating (the latter for efficiency too).
var avatar = AvatarList.getAvatar(id);
var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = id ? ExtendedOverlay.get(id) : myData;
if (data) {
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
// decay avgAudioLevel
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// now scale for the gain. Also, asked to boost the low end, so one simple way is
// to take sqrt of the value. Lets try that, see how it feels.
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75)));
}
return [audioLevel, avgAudioLevel];
}
function createAudioInterval(interval) {
// we will update the audioLevels periodically
// TODO: tune for efficiency - expecially with large numbers of avatars
return Script.setInterval(function () {
var param = {};
AvatarList.getAvatarIdentifiers().forEach(function (id) {
var level = getAudioLevel(id),
userId = id || 0; // qml didn't like an object with null/empty string for a key, so...
param[userId] = level;
});
sendToQml({method: 'updateAudioLevel', params: param});
}, interval);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
@ -830,20 +828,17 @@ function startup() {
}
startup();
var audioTimer;
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
function off() {
if (ui.isOpen) { // i.e., only when connected
Script.update.disconnect(updateOverlays);
if (updateInterval) {
Script.clearInterval(updateInterval);
}
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
triggerMapping.disable();
triggerPressMapping.disable();
if (audioTimer) {
Script.clearInterval(audioTimer);
}
}
removeOverlays();