mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-08 21:03:06 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into app-refactor4-merge
This commit is contained in:
commit
cb5ac6f42c
4 changed files with 135 additions and 82 deletions
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue