mirror of
https://github.com/overte-org/overte.git
synced 2025-08-12 23:40:00 +02:00
Implement MS16562: Massive PAL speedup!
This commit is contained in:
parent
de33ca512f
commit
92756d81ad
4 changed files with 112 additions and 81 deletions
|
@ -1108,7 +1108,7 @@ Rectangle {
|
||||||
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
||||||
var data = optionalData || nearbyUserModelData, length = data.length;
|
var data = optionalData || nearbyUserModelData, length = data.length;
|
||||||
for (var i = 0; i < length; i++) {
|
for (var i = 0; i < length; i++) {
|
||||||
if (data[i].sessionId === sessionId) {
|
if (data[i].sessionId === sessionId.toString()) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1120,7 +1120,7 @@ Rectangle {
|
||||||
var data = message.params;
|
var data = message.params;
|
||||||
var index = -1;
|
var index = -1;
|
||||||
iAmAdmin = Users.canKick;
|
iAmAdmin = Users.canKick;
|
||||||
index = findNearbySessionIndex('', data);
|
index = findNearbySessionIndex(MyAvatar.sessionUUID, data);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
myData = data[index];
|
myData = data[index];
|
||||||
data.splice(index, 1);
|
data.splice(index, 1);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QScriptEngine>
|
#include <QScriptEngine>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
|
@ -668,3 +669,41 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
|
||||||
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
|
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
QJsonDocument doc(palData);
|
||||||
|
return doc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
|
@ -157,6 +157,8 @@ public:
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
|
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
|
||||||
|
|
||||||
|
Q_INVOKABLE QString getPalData(const QList<QString> specificAvatarIdentifiers = QList<QString>());
|
||||||
|
|
||||||
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
|
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
|
||||||
int getIdentityRequestsSent() const { return _identityRequestsSent; }
|
int getIdentityRequestsSent() const { return _identityRequestsSent; }
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
var populateNearbyUserList, color, textures, removeOverlays,
|
var populateNearbyUserList, color, textures, removeOverlays,
|
||||||
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
|
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
|
||||||
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
|
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
|
||||||
createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged,
|
tablet, CHANNEL, getConnectionData, findableByChanged,
|
||||||
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
|
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
|
||||||
|
|
||||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||||
|
@ -447,21 +447,24 @@ function populateNearbyUserList(selectData, oldAudioData) {
|
||||||
verticalAngleNormal = filter && Quat.getRight(orientation),
|
verticalAngleNormal = filter && Quat.getRight(orientation),
|
||||||
horizontalAngleNormal = filter && Quat.getUp(orientation);
|
horizontalAngleNormal = filter && Quat.getUp(orientation);
|
||||||
avatarsOfInterest = {};
|
avatarsOfInterest = {};
|
||||||
avatars.forEach(function (id) {
|
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var avatarData = JSON.parse(AvatarList.getPalData());
|
||||||
var name = avatar.sessionDisplayName;
|
|
||||||
|
avatarData.forEach(function (currentAvatarData) {
|
||||||
|
var id = currentAvatarData.sessionUUID;
|
||||||
|
var name = currentAvatarData.sessionDisplayName;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
// Either we got a data packet but no identity yet, or something is really messed up. In any case,
|
// 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.
|
// 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,
|
// 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.
|
// we could be losing and gaining people randomly.
|
||||||
print('No avatar identity data for', id);
|
print('No avatar identity data for', currentAvatarData.sessionUUID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) {
|
if (id && myPosition && (Vec3.distance(currentAvatarData.position, myPosition) > filter.distance)) {
|
||||||
return;
|
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 horizontal = normal && angleBetweenVectorsInPlane(normal, forward, horizontalAngleNormal);
|
||||||
var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal);
|
var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal);
|
||||||
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
|
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
|
||||||
|
@ -480,11 +483,11 @@ function populateNearbyUserList(selectData, oldAudioData) {
|
||||||
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
|
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
|
||||||
ignore: !!id && Users.getIgnoreStatus(id), // ditto
|
ignore: !!id && Users.getIgnoreStatus(id), // ditto
|
||||||
isPresent: true,
|
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.
|
// 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);
|
||||||
if (id) {
|
if (id !== MyAvatar.sessionUUID) {
|
||||||
addAvatarNode(id); // No overlay for ourselves
|
addAvatarNode(id); // No overlay for ourselves
|
||||||
avatarsOfInterest[id] = true;
|
avatarsOfInterest[id] = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -515,30 +518,60 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
|
||||||
updateUser(data);
|
updateUser(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAudioLevel(overlay, 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;
|
||||||
|
|
||||||
|
// we will do exponential moving average by taking some the last loudness and averaging
|
||||||
|
overlay.accumulatedLevel = AVERAGING_RATIO * (overlay.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(overlay.accumulatedLevel + 1) / LOG2);
|
||||||
|
|
||||||
|
// decay avgAudioLevel
|
||||||
|
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (overlay.avgAudioLevel || 0), audioLevel);
|
||||||
|
|
||||||
|
overlay.avgAudioLevel = avgAudioLevel;
|
||||||
|
overlay.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 || 0;
|
||||||
|
param[userId] = level;
|
||||||
|
sendToQml({ method: 'updateAudioLevel', params: param });
|
||||||
|
}
|
||||||
|
|
||||||
var pingPong = true;
|
var pingPong = true;
|
||||||
function updateOverlays() {
|
function updateOverlays() {
|
||||||
var eye = Camera.position;
|
var eye = Camera.position;
|
||||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
|
||||||
if (!id || !avatarsOfInterest[id]) {
|
var avatarData = JSON.parse(AvatarList.getPalData());
|
||||||
|
|
||||||
|
avatarData.forEach(function (currentAvatarData) {
|
||||||
|
if (currentAvatarData.sessionUUID === MyAvatar.sessionUUID || !avatarsOfInterest[currentAvatarData.sessionUUID]) {
|
||||||
return; // don't update ourself, or avatars we're not interested in
|
return; // don't update ourself, or avatars we're not interested in
|
||||||
}
|
}
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var overlay = ExtendedOverlay.get(currentAvatarData.sessionUUID);
|
||||||
if (!avatar) {
|
|
||||||
return; // will be deleted below if there had been an overlay.
|
|
||||||
}
|
|
||||||
var overlay = ExtendedOverlay.get(id);
|
|
||||||
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
|
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);
|
print('Adding non-PAL avatar node', currentAvatarData.sessionUUID);
|
||||||
overlay = addAvatarNode(id);
|
overlay = addAvatarNode(currentAvatarData.sessionUUID);
|
||||||
}
|
}
|
||||||
var target = avatar.position;
|
|
||||||
|
updateAudioLevel(overlay, currentAvatarData);
|
||||||
|
|
||||||
|
var target = currentAvatarData.position;
|
||||||
var distance = Vec3.distance(target, eye);
|
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 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
|
// move a bit in front, towards the camera
|
||||||
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
|
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
|
||||||
|
@ -548,7 +581,7 @@ function updateOverlays() {
|
||||||
|
|
||||||
overlay.ping = pingPong;
|
overlay.ping = pingPong;
|
||||||
overlay.editOverlay({
|
overlay.editOverlay({
|
||||||
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
|
color: color(ExtendedOverlay.isSelected(currentAvatarData.sessionUUID), overlay.hovering, overlay.audioLevel),
|
||||||
position: target,
|
position: target,
|
||||||
dimensions: 0.032 * distance
|
dimensions: 0.032 * distance
|
||||||
});
|
});
|
||||||
|
@ -704,6 +737,13 @@ function wireEventBridge(on) {
|
||||||
hasEventBridge = false;
|
hasEventBridge = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var UPDATE_INTERVAL_MS = 100;
|
||||||
|
function createUpdateInterval() {
|
||||||
|
return Script.setInterval(function () {
|
||||||
|
updateOverlays();
|
||||||
|
}, UPDATE_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTabletScreenChanged(type, url) {
|
function onTabletScreenChanged(type, url) {
|
||||||
|
@ -719,10 +759,8 @@ function onTabletScreenChanged(type, url) {
|
||||||
ContextOverlay.enabled = false;
|
ContextOverlay.enabled = false;
|
||||||
Users.requestsDomainListData = true;
|
Users.requestsDomainListData = true;
|
||||||
|
|
||||||
audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS);
|
|
||||||
|
|
||||||
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
||||||
Script.update.connect(updateOverlays);
|
updateInterval = createUpdateInterval();
|
||||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||||
|
@ -778,50 +816,6 @@ function scaleAudio(val) {
|
||||||
return audioLevel;
|
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) {
|
function avatarDisconnected(nodeID) {
|
||||||
// remove from the pal list
|
// remove from the pal list
|
||||||
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
||||||
|
@ -874,11 +868,11 @@ startup();
|
||||||
|
|
||||||
|
|
||||||
var isWired = false;
|
var isWired = false;
|
||||||
var audioTimer;
|
|
||||||
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
|
|
||||||
function off() {
|
function off() {
|
||||||
if (isWired) {
|
if (isWired) {
|
||||||
Script.update.disconnect(updateOverlays);
|
if (updateInterval) {
|
||||||
|
Script.clearInterval(updateInterval);
|
||||||
|
}
|
||||||
Controller.mousePressEvent.disconnect(handleMouseEvent);
|
Controller.mousePressEvent.disconnect(handleMouseEvent);
|
||||||
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
|
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
|
||||||
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
||||||
|
@ -889,10 +883,6 @@ function off() {
|
||||||
Users.requestsDomainListData = false;
|
Users.requestsDomainListData = false;
|
||||||
|
|
||||||
isWired = false;
|
isWired = false;
|
||||||
|
|
||||||
if (audioTimer) {
|
|
||||||
Script.clearInterval(audioTimer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOverlays();
|
removeOverlays();
|
||||||
|
|
Loading…
Reference in a new issue