mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 01:17:14 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into stack-manager
This commit is contained in:
commit
d17bf28bce
22 changed files with 699 additions and 256 deletions
|
@ -12,30 +12,23 @@
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
|
||||||
#include <LogHandler.h>
|
#include <LogHandler.h>
|
||||||
|
#include <MessagesClient.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
|
|
||||||
#include "MessagesMixer.h"
|
#include "MessagesMixer.h"
|
||||||
|
|
||||||
const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer";
|
const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer";
|
||||||
|
|
||||||
MessagesMixer::MessagesMixer(NLPacket& packet) :
|
MessagesMixer::MessagesMixer(NLPacket& packet) : ThreadedAssignment(packet)
|
||||||
ThreadedAssignment(packet)
|
|
||||||
{
|
{
|
||||||
// make sure we hear about node kills so we can tell the other nodes
|
|
||||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
|
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerMessageListener(PacketType::MessagesData, this, "handleMessages");
|
packetReceiver.registerMessageListener(PacketType::MessagesData, this, "handleMessages");
|
||||||
packetReceiver.registerMessageListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
|
packetReceiver.registerMessageListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
|
||||||
packetReceiver.registerMessageListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
|
packetReceiver.registerMessageListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
|
||||||
}
|
}
|
||||||
|
|
||||||
MessagesMixer::~MessagesMixer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||||
for (auto& channel : _channelSubscribers) {
|
for (auto& channel : _channelSubscribers) {
|
||||||
channel.remove(killedNode->getUUID());
|
channel.remove(killedNode->getUUID());
|
||||||
|
@ -43,92 +36,52 @@ void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesMixer::handleMessages(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
void MessagesMixer::handleMessages(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||||
Q_ASSERT(packetList->getType() == PacketType::MessagesData);
|
QString channel, message;
|
||||||
|
QUuid senderID;
|
||||||
|
MessagesClient::decodeMessagesPacket(packetList, channel, message, senderID);
|
||||||
|
|
||||||
QByteArray packetData = packetList->getMessage();
|
|
||||||
QBuffer packet{ &packetData };
|
|
||||||
packet.open(QIODevice::ReadOnly);
|
|
||||||
|
|
||||||
quint16 channelLength;
|
|
||||||
packet.read(reinterpret_cast<char*>(&channelLength), sizeof(channelLength));
|
|
||||||
auto channelData = packet.read(channelLength);
|
|
||||||
QString channel = QString::fromUtf8(channelData);
|
|
||||||
|
|
||||||
quint16 messageLength;
|
|
||||||
packet.read(reinterpret_cast<char*>(&messageLength), sizeof(messageLength));
|
|
||||||
auto messageData = packet.read(messageLength);
|
|
||||||
QString message = QString::fromUtf8(messageData);
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
nodeList->eachMatchingNode(
|
nodeList->eachMatchingNode(
|
||||||
[&](const SharedNodePointer& node)->bool {
|
[&](const SharedNodePointer& node)->bool {
|
||||||
|
|
||||||
return node->getType() == NodeType::Agent && node->getActiveSocket() &&
|
return node->getType() == NodeType::Agent && node->getActiveSocket() &&
|
||||||
_channelSubscribers[channel].contains(node->getUUID());
|
_channelSubscribers[channel].contains(node->getUUID());
|
||||||
},
|
},
|
||||||
[&](const SharedNodePointer& node) {
|
[&](const SharedNodePointer& node) {
|
||||||
|
auto packetList = MessagesClient::encodeMessagesPacket(channel, message, senderID);
|
||||||
auto packetList = NLPacketList::create(PacketType::MessagesData, QByteArray(), true, true);
|
|
||||||
|
|
||||||
auto channelUtf8 = channel.toUtf8();
|
|
||||||
quint16 channelLength = channelUtf8.length();
|
|
||||||
packetList->writePrimitive(channelLength);
|
|
||||||
packetList->write(channelUtf8);
|
|
||||||
|
|
||||||
auto messageUtf8 = message.toUtf8();
|
|
||||||
quint16 messageLength = messageUtf8.length();
|
|
||||||
packetList->writePrimitive(messageLength);
|
|
||||||
packetList->write(messageUtf8);
|
|
||||||
|
|
||||||
nodeList->sendPacketList(std::move(packetList), *node);
|
nodeList->sendPacketList(std::move(packetList), *node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesMixer::handleMessagesSubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
void MessagesMixer::handleMessagesSubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||||
Q_ASSERT(packetList->getType() == PacketType::MessagesSubscribe);
|
|
||||||
QString channel = QString::fromUtf8(packetList->getMessage());
|
QString channel = QString::fromUtf8(packetList->getMessage());
|
||||||
qDebug() << "Node [" << senderNode->getUUID() << "] subscribed to channel:" << channel;
|
|
||||||
_channelSubscribers[channel] << senderNode->getUUID();
|
_channelSubscribers[channel] << senderNode->getUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||||
Q_ASSERT(packetList->getType() == PacketType::MessagesUnsubscribe);
|
|
||||||
QString channel = QString::fromUtf8(packetList->getMessage());
|
QString channel = QString::fromUtf8(packetList->getMessage());
|
||||||
qDebug() << "Node [" << senderNode->getUUID() << "] unsubscribed from channel:" << channel;
|
|
||||||
|
|
||||||
if (_channelSubscribers.contains(channel)) {
|
if (_channelSubscribers.contains(channel)) {
|
||||||
_channelSubscribers[channel].remove(senderNode->getUUID());
|
_channelSubscribers[channel].remove(senderNode->getUUID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - make these stats relevant
|
|
||||||
void MessagesMixer::sendStatsPacket() {
|
void MessagesMixer::sendStatsPacket() {
|
||||||
QJsonObject statsObject;
|
QJsonObject statsObject, messagesMixerObject;
|
||||||
QJsonObject messagesObject;
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
// add stats for each listerner
|
// add stats for each listerner
|
||||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& node) {
|
||||||
QJsonObject messagesStats;
|
QJsonObject clientStats;
|
||||||
|
clientStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||||
// add the key to ask the domain-server for a username replacement, if it has it
|
clientStats["outbound_kbps"] = node->getOutboundBandwidth();
|
||||||
messagesStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
clientStats["inbound_kbps"] = node->getInboundBandwidth();
|
||||||
messagesStats["outbound_kbps"] = node->getOutboundBandwidth();
|
messagesMixerObject[uuidStringWithoutCurlyBraces(node->getUUID())] = clientStats;
|
||||||
messagesStats["inbound_kbps"] = node->getInboundBandwidth();
|
|
||||||
|
|
||||||
messagesObject[uuidStringWithoutCurlyBraces(node->getUUID())] = messagesStats;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
statsObject["messages"] = messagesObject;
|
statsObject["messages"] = messagesMixerObject;
|
||||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesMixer::run() {
|
void MessagesMixer::run() {
|
||||||
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
|
ThreadedAssignment::commonInit(MESSAGES_MIXER_LOGGING_NAME, NodeType::MessagesMixer);
|
||||||
|
DependencyManager::get<NodeList>()->addNodeTypeToInterestSet(NodeType::Agent);
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
}
|
||||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
|
||||||
|
|
||||||
// The messages-mixer currently does currently have any domain settings. If it did, they would be
|
|
||||||
// synchronously grabbed here.
|
|
||||||
}
|
|
|
@ -22,7 +22,6 @@ class MessagesMixer : public ThreadedAssignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
MessagesMixer(NLPacket& packet);
|
MessagesMixer(NLPacket& packet);
|
||||||
~MessagesMixer();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
|
|
155
examples/acScripts/playbackAgents.js
Normal file
155
examples/acScripts/playbackAgents.js
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
//
|
||||||
|
// playbackAgents.js
|
||||||
|
// acScripts
|
||||||
|
//
|
||||||
|
// Created by Edgar Pironti on 11/17/15.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
// Set the following variables to the values needed
|
||||||
|
var channel = "PlaybackChannel1";
|
||||||
|
var clip_url = null;
|
||||||
|
var playFromCurrentLocation = true;
|
||||||
|
var useDisplayName = true;
|
||||||
|
var useAttachments = true;
|
||||||
|
var useAvatarModel = true;
|
||||||
|
|
||||||
|
// ID of the agent. Two agents can't have the same ID.
|
||||||
|
var id = 0;
|
||||||
|
|
||||||
|
// Set position/orientation/scale here if playFromCurrentLocation is true
|
||||||
|
Avatar.position = { x:0, y: 0, z: 0 };
|
||||||
|
Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0);
|
||||||
|
Avatar.scale = 1.0;
|
||||||
|
|
||||||
|
var totalTime = 0;
|
||||||
|
var subscribed = false;
|
||||||
|
var WAIT_FOR_AUDIO_MIXER = 1;
|
||||||
|
|
||||||
|
// Script. DO NOT MODIFY BEYOND THIS LINE.
|
||||||
|
var DO_NOTHING = 0;
|
||||||
|
var PLAY = 1;
|
||||||
|
var PLAY_LOOP = 2;
|
||||||
|
var STOP = 3;
|
||||||
|
var SHOW = 4;
|
||||||
|
var HIDE = 5;
|
||||||
|
var LOAD = 6;
|
||||||
|
|
||||||
|
Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
|
||||||
|
Recording.setPlayerUseDisplayName(useDisplayName);
|
||||||
|
Recording.setPlayerUseAttachments(useAttachments);
|
||||||
|
Recording.setPlayerUseHeadModel(false);
|
||||||
|
Recording.setPlayerUseSkeletonModel(useAvatarModel);
|
||||||
|
|
||||||
|
function getAction(channel, message, senderID) {
|
||||||
|
|
||||||
|
if(subscribed) {
|
||||||
|
var command = JSON.parse(message);
|
||||||
|
print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key);
|
||||||
|
|
||||||
|
if (command.id_key == id || command.id_key == -1) {
|
||||||
|
if (command.action_key === 6) {
|
||||||
|
clip_url = command.clip_url_key;
|
||||||
|
|
||||||
|
// If the id is -1 (broadcast) and the action is 6, in the url should be the performance file
|
||||||
|
// with all the clips recorded in a session (not just the single clip url).
|
||||||
|
// It has to be computed here in order to retrieve the url for the single agent.
|
||||||
|
// Checking the id we can assign the correct url to the correct agent.
|
||||||
|
|
||||||
|
if (command.id_key == -1) {
|
||||||
|
Assets.downloadData(clip_url, function (data) {
|
||||||
|
var myJSONObject = JSON.parse(data);
|
||||||
|
var hash = myJSONObject.results[id].hashATP;
|
||||||
|
});
|
||||||
|
|
||||||
|
Assets.downloadData(hash, function (data) {
|
||||||
|
clip_url = JSON.parse(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action = command.action_key;
|
||||||
|
print("That command was for me!");
|
||||||
|
print("My clip is: " + clip_url);
|
||||||
|
} else {
|
||||||
|
action = DO_NOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(action) {
|
||||||
|
case PLAY:
|
||||||
|
print("Play");
|
||||||
|
if (!Agent.isAvatar) {
|
||||||
|
Agent.isAvatar = true;
|
||||||
|
}
|
||||||
|
if (!Recording.isPlaying()) {
|
||||||
|
Recording.startPlaying();
|
||||||
|
}
|
||||||
|
Recording.setPlayerLoop(false);
|
||||||
|
break;
|
||||||
|
case PLAY_LOOP:
|
||||||
|
print("Play loop");
|
||||||
|
if (!Agent.isAvatar) {
|
||||||
|
Agent.isAvatar = true;
|
||||||
|
}
|
||||||
|
if (!Recording.isPlaying()) {
|
||||||
|
Recording.startPlaying();
|
||||||
|
}
|
||||||
|
Recording.setPlayerLoop(true);
|
||||||
|
break;
|
||||||
|
case STOP:
|
||||||
|
print("Stop");
|
||||||
|
if (Recording.isPlaying()) {
|
||||||
|
Recording.stopPlaying();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SHOW:
|
||||||
|
print("Show");
|
||||||
|
if (!Agent.isAvatar) {
|
||||||
|
Agent.isAvatar = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HIDE:
|
||||||
|
print("Hide");
|
||||||
|
if (Recording.isPlaying()) {
|
||||||
|
Recording.stopPlaying();
|
||||||
|
}
|
||||||
|
Agent.isAvatar = false;
|
||||||
|
break;
|
||||||
|
case LOAD:
|
||||||
|
print("Load");
|
||||||
|
if(clip_url !== null) {
|
||||||
|
Recording.loadRecording(clip_url);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DO_NOTHING:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print("Unknown action: " + action);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Recording.isPlaying()) {
|
||||||
|
Recording.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function update(deltaTime) {
|
||||||
|
|
||||||
|
totalTime += deltaTime;
|
||||||
|
|
||||||
|
if (totalTime > WAIT_FOR_AUDIO_MIXER && !subscribed) {
|
||||||
|
Messages.subscribe(channel);
|
||||||
|
subscribed = true;
|
||||||
|
print("I'm the agent and I am ready to receive!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.update.connect(update);
|
||||||
|
Messages.messageReceived.connect(getAction);
|
||||||
|
|
260
examples/acScripts/playbackMaster.js
Normal file
260
examples/acScripts/playbackMaster.js
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
//
|
||||||
|
// playbackMaster.js
|
||||||
|
// acScripts
|
||||||
|
//
|
||||||
|
// Created by Edgar Pironti on 11/17/15.
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
|
|
||||||
|
|
||||||
|
var ac_number = 1; // This is the default number of ACs. Their ID need to be unique and between 0 (included) and ac_number (excluded)
|
||||||
|
var names = new Array(); // It is possible to specify the name of the ACs in this array. ACs names ordered by IDs (Default name is "ACx", x = ID + 1))
|
||||||
|
var channel = "PlaybackChannel1";
|
||||||
|
var subscribed = false;
|
||||||
|
var clip_url = null;
|
||||||
|
var input_text = null;
|
||||||
|
|
||||||
|
// Script. DO NOT MODIFY BEYOND THIS LINE.
|
||||||
|
Script.include("../libraries/toolBars.js");
|
||||||
|
|
||||||
|
var DO_NOTHING = 0;
|
||||||
|
var PLAY = 1;
|
||||||
|
var PLAY_LOOP = 2;
|
||||||
|
var STOP = 3;
|
||||||
|
var SHOW = 4;
|
||||||
|
var HIDE = 5;
|
||||||
|
var LOAD = 6;
|
||||||
|
|
||||||
|
var windowDimensions = Controller.getViewportDimensions();
|
||||||
|
var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/";
|
||||||
|
var ALPHA_ON = 1.0;
|
||||||
|
var ALPHA_OFF = 0.7;
|
||||||
|
var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 };
|
||||||
|
var COLOR_MASTER = { red: 0, green: 0, blue: 0 };
|
||||||
|
var TEXT_HEIGHT = 12;
|
||||||
|
var TEXT_MARGIN = 3;
|
||||||
|
|
||||||
|
var toolBars = new Array();
|
||||||
|
var nameOverlays = new Array();
|
||||||
|
var onOffIcon = new Array();
|
||||||
|
var playIcon = new Array();
|
||||||
|
var playLoopIcon = new Array();
|
||||||
|
var stopIcon = new Array();
|
||||||
|
var loadIcon = new Array();
|
||||||
|
|
||||||
|
setupPlayback();
|
||||||
|
|
||||||
|
function setupPlayback() {
|
||||||
|
ac_number = Window.prompt("Insert number of agents: ","1");
|
||||||
|
if (ac_number === "" || ac_number === null)
|
||||||
|
ac_number = 1;
|
||||||
|
Messages.subscribe(channel);
|
||||||
|
subscribed = true;
|
||||||
|
setupToolBars();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupToolBars() {
|
||||||
|
if (toolBars.length > 0) {
|
||||||
|
print("Multiple calls to Recorder.js:setupToolBars()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Tool.IMAGE_HEIGHT /= 2;
|
||||||
|
Tool.IMAGE_WIDTH /= 2;
|
||||||
|
|
||||||
|
for (i = 0; i <= ac_number; i++) {
|
||||||
|
toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL));
|
||||||
|
toolBars[i].setBack((i == ac_number) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF);
|
||||||
|
|
||||||
|
onOffIcon.push(toolBars[i].addTool({
|
||||||
|
imageURL: TOOL_ICON_URL + "ac-on-off.svg",
|
||||||
|
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
x: 0, y: 0,
|
||||||
|
width: Tool.IMAGE_WIDTH,
|
||||||
|
height: Tool.IMAGE_HEIGHT,
|
||||||
|
alpha: ALPHA_ON,
|
||||||
|
visible: true
|
||||||
|
}, true, true));
|
||||||
|
|
||||||
|
playIcon[i] = toolBars[i].addTool({
|
||||||
|
imageURL: TOOL_ICON_URL + "play.svg",
|
||||||
|
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: Tool.IMAGE_WIDTH,
|
||||||
|
height: Tool.IMAGE_HEIGHT,
|
||||||
|
alpha: ALPHA_OFF,
|
||||||
|
visible: true
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
var playLoopWidthFactor = 1.65;
|
||||||
|
playLoopIcon[i] = toolBars[i].addTool({
|
||||||
|
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
|
||||||
|
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||||
|
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
|
||||||
|
height: Tool.IMAGE_HEIGHT,
|
||||||
|
alpha: ALPHA_OFF,
|
||||||
|
visible: true
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
stopIcon[i] = toolBars[i].addTool({
|
||||||
|
imageURL: TOOL_ICON_URL + "recording-stop.svg",
|
||||||
|
width: Tool.IMAGE_WIDTH,
|
||||||
|
height: Tool.IMAGE_HEIGHT,
|
||||||
|
alpha: ALPHA_OFF,
|
||||||
|
visible: true
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
loadIcon[i] = toolBars[i].addTool({
|
||||||
|
imageURL: TOOL_ICON_URL + "recording-upload.svg",
|
||||||
|
width: Tool.IMAGE_WIDTH,
|
||||||
|
height: Tool.IMAGE_HEIGHT,
|
||||||
|
alpha: ALPHA_OFF,
|
||||||
|
visible: true
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
nameOverlays.push(Overlays.addOverlay("text", {
|
||||||
|
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||||
|
font: { size: TEXT_HEIGHT },
|
||||||
|
text: (i == ac_number) ? "Master" : i + ". " +
|
||||||
|
((i < names.length) ? names[i] :
|
||||||
|
"AC" + i),
|
||||||
|
x: 0, y: 0,
|
||||||
|
width: toolBars[i].width + ToolBar.SPACING,
|
||||||
|
height: TEXT_HEIGHT + TEXT_MARGIN,
|
||||||
|
leftMargin: TEXT_MARGIN,
|
||||||
|
topMargin: TEXT_MARGIN,
|
||||||
|
alpha: ALPHA_OFF,
|
||||||
|
backgroundAlpha: ALPHA_OFF,
|
||||||
|
visible: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCommand(id, action) {
|
||||||
|
|
||||||
|
if (action === SHOW) {
|
||||||
|
toolBars[id].selectTool(onOffIcon[id], false);
|
||||||
|
toolBars[id].setAlpha(ALPHA_ON, playIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_ON, loadIcon[id]);
|
||||||
|
} else if (action === HIDE) {
|
||||||
|
toolBars[id].selectTool(onOffIcon[id], true);
|
||||||
|
toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]);
|
||||||
|
toolBars[id].setAlpha(ALPHA_OFF, loadIcon[id]);
|
||||||
|
} else if (toolBars[id].toolSelected(onOffIcon[id])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == (toolBars.length - 1))
|
||||||
|
id = -1; // Master command becomes broadcast.
|
||||||
|
|
||||||
|
var message = {
|
||||||
|
id_key: id,
|
||||||
|
action_key: action,
|
||||||
|
clip_url_key: clip_url
|
||||||
|
};
|
||||||
|
|
||||||
|
if(subscribed){
|
||||||
|
Messages.sendMessage(channel, JSON.stringify(message));
|
||||||
|
print("Message sent!");
|
||||||
|
clip_url = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousePressEvent(event) {
|
||||||
|
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||||
|
|
||||||
|
// Check master control
|
||||||
|
var i = toolBars.length - 1;
|
||||||
|
if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
if (toolBars[i].toolSelected(onOffIcon[i])) {
|
||||||
|
sendCommand(i, SHOW);
|
||||||
|
} else {
|
||||||
|
sendCommand(i, HIDE);
|
||||||
|
}
|
||||||
|
} else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, PLAY);
|
||||||
|
} else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, PLAY_LOOP);
|
||||||
|
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, STOP);
|
||||||
|
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
input_text = Window.prompt("Insert the url of the clip: ","");
|
||||||
|
if (!(input_text === "" || input_text === null)) {
|
||||||
|
clip_url = input_text;
|
||||||
|
sendCommand(i, LOAD);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check individual controls
|
||||||
|
for (i = 0; i < ac_number; i++) {
|
||||||
|
if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
if (toolBars[i].toolSelected(onOffIcon[i], false)) {
|
||||||
|
sendCommand(i, SHOW);
|
||||||
|
} else {
|
||||||
|
sendCommand(i, HIDE);
|
||||||
|
}
|
||||||
|
} else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, PLAY);
|
||||||
|
} else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, PLAY_LOOP);
|
||||||
|
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
sendCommand(i, STOP);
|
||||||
|
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||||
|
input_text = Window.prompt("Insert the url of the clip: ","");
|
||||||
|
if (!(input_text === "" || input_text === null)) {
|
||||||
|
clip_url = input_text;
|
||||||
|
sendCommand(i, LOAD);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUI() {
|
||||||
|
var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN;
|
||||||
|
var relative = { x: 70, y: 75 + (ac_number) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) };
|
||||||
|
|
||||||
|
for (i = 0; i <= ac_number; i++) {
|
||||||
|
toolBars[i].move(relative.x,
|
||||||
|
windowDimensions.y - relative.y +
|
||||||
|
i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize));
|
||||||
|
|
||||||
|
Overlays.editOverlay(nameOverlays[i], {
|
||||||
|
x: toolBars[i].x - ToolBar.SPACING,
|
||||||
|
y: toolBars[i].y - textSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var newDimensions = Controller.getViewportDimensions();
|
||||||
|
if (windowDimensions.x != newDimensions.x ||
|
||||||
|
windowDimensions.y != newDimensions.y) {
|
||||||
|
windowDimensions = newDimensions;
|
||||||
|
moveUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptEnding() {
|
||||||
|
for (i = 0; i <= ac_number; i++) {
|
||||||
|
toolBars[i].cleanup();
|
||||||
|
Overlays.deleteOverlay(nameOverlays[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscribed)
|
||||||
|
Messages.unsubscribe(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
Script.update.connect(update);
|
||||||
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
|
|
||||||
|
moveUI();
|
|
@ -19,4 +19,3 @@ Script.load("controllers/handControllerGrab.js");
|
||||||
Script.load("grab.js");
|
Script.load("grab.js");
|
||||||
Script.load("directory.js");
|
Script.load("directory.js");
|
||||||
Script.load("dialTone.js");
|
Script.load("dialTone.js");
|
||||||
Script.load("libraries/omniTool.js");
|
|
||||||
|
|
|
@ -1187,7 +1187,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
void Avatar::rebuildSkeletonBody() {
|
void Avatar::rebuildSkeletonBody() {
|
||||||
DependencyManager::get<AvatarManager>()->updateAvatarPhysicsShape(getSessionUUID());
|
DependencyManager::get<AvatarManager>()->updateAvatarPhysicsShape(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 Avatar::getLeftPalmPosition() {
|
glm::vec3 Avatar::getLeftPalmPosition() {
|
||||||
|
|
|
@ -110,28 +110,34 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarManager::updateOtherAvatars(float deltaTime) {
|
void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
|
// lock the hash for read to check the size
|
||||||
|
QReadLocker lock(&_hashLock);
|
||||||
|
|
||||||
if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) {
|
if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
|
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
|
||||||
|
|
||||||
PerformanceTimer perfTimer("otherAvatars");
|
PerformanceTimer perfTimer("otherAvatars");
|
||||||
|
|
||||||
// simulate avatars
|
// simulate avatars
|
||||||
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
auto hashCopy = getHashCopy();
|
||||||
while (avatarIterator != _avatarHash.end()) {
|
|
||||||
auto avatar = std::dynamic_pointer_cast<Avatar>(avatarIterator.value());
|
AvatarHash::iterator avatarIterator = hashCopy.begin();
|
||||||
|
while (avatarIterator != hashCopy.end()) {
|
||||||
|
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
|
||||||
|
|
||||||
if (avatar == _myAvatar || !avatar->isInitialized()) {
|
if (avatar == _myAvatar || !avatar->isInitialized()) {
|
||||||
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
||||||
// DO NOT update or fade out uninitialized Avatars
|
// DO NOT update or fade out uninitialized Avatars
|
||||||
++avatarIterator;
|
++avatarIterator;
|
||||||
} else if (avatar->shouldDie()) {
|
} else if (avatar->shouldDie()) {
|
||||||
removeAvatarMotionState(avatar);
|
removeAvatar(avatarIterator.key());
|
||||||
_avatarFades.push_back(avatarIterator.value());
|
++avatarIterator;
|
||||||
QWriteLocker locker(&_hashLock);
|
|
||||||
avatarIterator = _avatarHash.erase(avatarIterator);
|
|
||||||
} else {
|
} else {
|
||||||
avatar->startUpdate();
|
avatar->startUpdate();
|
||||||
avatar->simulate(deltaTime);
|
avatar->simulate(deltaTime);
|
||||||
|
@ -169,19 +175,21 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
||||||
return AvatarSharedPointer(std::make_shared<Avatar>(std::make_shared<AvatarRig>()));
|
return std::make_shared<Avatar>(std::make_shared<AvatarRig>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual
|
|
||||||
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
auto avatar = std::dynamic_pointer_cast<Avatar>(AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer));
|
auto newAvatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
|
||||||
|
auto rawRenderableAvatar = std::static_pointer_cast<Avatar>(newAvatar);
|
||||||
|
|
||||||
render::ScenePointer scene = qApp->getMain3DScene();
|
render::ScenePointer scene = qApp->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
|
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
|
||||||
avatar->addToScene(avatar, scene, pendingChanges);
|
rawRenderableAvatar->addToScene(rawRenderableAvatar, scene, pendingChanges);
|
||||||
}
|
}
|
||||||
scene->enqueuePendingChanges(pendingChanges);
|
scene->enqueuePendingChanges(pendingChanges);
|
||||||
return avatar;
|
|
||||||
|
return newAvatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
// protected
|
// protected
|
||||||
|
@ -200,20 +208,25 @@ void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) {
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
|
void AvatarManager::removeAvatar(const QUuid& sessionUUID) {
|
||||||
AvatarHash::iterator avatarIterator = _avatarHash.find(sessionUUID);
|
QWriteLocker locker(&_hashLock);
|
||||||
if (avatarIterator != _avatarHash.end()) {
|
|
||||||
std::shared_ptr<Avatar> avatar = std::dynamic_pointer_cast<Avatar>(avatarIterator.value());
|
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||||
if (avatar != _myAvatar && avatar->isInitialized()) {
|
if (removedAvatar) {
|
||||||
removeAvatarMotionState(avatar);
|
handleRemovedAvatar(removedAvatar);
|
||||||
_avatarFades.push_back(avatarIterator.value());
|
|
||||||
QWriteLocker locker(&_hashLock);
|
|
||||||
_avatarHash.erase(avatarIterator);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
|
||||||
|
AvatarHashMap::handleRemovedAvatar(removedAvatar);
|
||||||
|
|
||||||
|
removeAvatarMotionState(removedAvatar);
|
||||||
|
_avatarFades.push_back(removedAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarManager::clearOtherAvatars() {
|
void AvatarManager::clearOtherAvatars() {
|
||||||
// clear any avatars that came from an avatar-mixer
|
// clear any avatars that came from an avatar-mixer
|
||||||
|
QWriteLocker locker(&_hashLock);
|
||||||
|
|
||||||
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
||||||
while (avatarIterator != _avatarHash.end()) {
|
while (avatarIterator != _avatarHash.end()) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
|
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
|
||||||
|
@ -221,10 +234,10 @@ void AvatarManager::clearOtherAvatars() {
|
||||||
// don't remove myAvatar or uninitialized avatars from the list
|
// don't remove myAvatar or uninitialized avatars from the list
|
||||||
++avatarIterator;
|
++avatarIterator;
|
||||||
} else {
|
} else {
|
||||||
removeAvatarMotionState(avatar);
|
auto removedAvatar = avatarIterator.value();
|
||||||
_avatarFades.push_back(avatarIterator.value());
|
|
||||||
QWriteLocker locker(&_hashLock);
|
|
||||||
avatarIterator = _avatarHash.erase(avatarIterator);
|
avatarIterator = _avatarHash.erase(avatarIterator);
|
||||||
|
|
||||||
|
handleRemovedAvatar(removedAvatar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_myAvatar->clearLookAtTargetAvatar();
|
_myAvatar->clearLookAtTargetAvatar();
|
||||||
|
@ -252,6 +265,7 @@ QVector<QUuid> AvatarManager::getAvatarIdentifiers() {
|
||||||
QReadLocker locker(&_hashLock);
|
QReadLocker locker(&_hashLock);
|
||||||
return _avatarHash.keys().toVector();
|
return _avatarHash.keys().toVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
|
AvatarData* AvatarManager::getAvatar(QUuid avatarID) {
|
||||||
QReadLocker locker(&_hashLock);
|
QReadLocker locker(&_hashLock);
|
||||||
return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar.
|
return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar.
|
||||||
|
@ -317,23 +331,19 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
|
void AvatarManager::updateAvatarPhysicsShape(Avatar* avatar) {
|
||||||
AvatarHash::iterator avatarItr = _avatarHash.find(id);
|
AvatarMotionState* motionState = avatar->getMotionState();
|
||||||
if (avatarItr != _avatarHash.end()) {
|
if (motionState) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarItr.value());
|
motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
|
||||||
AvatarMotionState* motionState = avatar->getMotionState();
|
} else {
|
||||||
if (motionState) {
|
ShapeInfo shapeInfo;
|
||||||
motionState->addDirtyFlags(Simulation::DIRTY_SHAPE);
|
avatar->computeShapeInfo(shapeInfo);
|
||||||
} else {
|
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||||
ShapeInfo shapeInfo;
|
if (shape) {
|
||||||
avatar->computeShapeInfo(shapeInfo);
|
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
|
||||||
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
avatar->setMotionState(motionState);
|
||||||
if (shape) {
|
_motionStatesToAdd.insert(motionState);
|
||||||
AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape);
|
_avatarMotionStates.insert(motionState);
|
||||||
avatar->setMotionState(motionState);
|
|
||||||
_motionStatesToAdd.insert(motionState);
|
|
||||||
_avatarMotionStates.insert(motionState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,7 +351,7 @@ void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
|
||||||
void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
|
void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
|
||||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
|
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()) {
|
||||||
for (auto avatarData : _avatarHash) {
|
for (auto avatarData : _avatarHash) {
|
||||||
auto avatar = std::dynamic_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
render::ScenePointer scene = qApp->getMain3DScene();
|
render::ScenePointer scene = qApp->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
avatar->addToScene(avatar, scene, pendingChanges);
|
avatar->addToScene(avatar, scene, pendingChanges);
|
||||||
|
@ -349,7 +359,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (auto avatarData : _avatarHash) {
|
for (auto avatarData : _avatarHash) {
|
||||||
auto avatar = std::dynamic_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
render::ScenePointer scene = qApp->getMain3DScene();
|
render::ScenePointer scene = qApp->getMain3DScene();
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
avatar->removeFromScene(avatar, scene, pendingChanges);
|
avatar->removeFromScene(avatar, scene, pendingChanges);
|
||||||
|
@ -363,11 +373,6 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
|
||||||
if (sessionID == _myAvatar->getSessionUUID()) {
|
if (sessionID == _myAvatar->getSessionUUID()) {
|
||||||
return std::static_pointer_cast<Avatar>(_myAvatar);
|
return std::static_pointer_cast<Avatar>(_myAvatar);
|
||||||
}
|
}
|
||||||
QReadLocker locker(&_hashLock);
|
|
||||||
auto iter = _avatarHash.find(sessionID);
|
return findAvatar(sessionID);
|
||||||
if (iter != _avatarHash.end()) {
|
|
||||||
return iter.value();
|
|
||||||
} else {
|
|
||||||
return AvatarSharedPointer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ public:
|
||||||
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
||||||
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
||||||
|
|
||||||
void updateAvatarPhysicsShape(const QUuid& id);
|
void updateAvatarPhysicsShape(Avatar* avatar);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||||
|
@ -79,7 +79,9 @@ private:
|
||||||
virtual AvatarSharedPointer newSharedAvatar();
|
virtual AvatarSharedPointer newSharedAvatar();
|
||||||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
void removeAvatarMotionState(AvatarSharedPointer avatar);
|
void removeAvatarMotionState(AvatarSharedPointer avatar);
|
||||||
|
|
||||||
virtual void removeAvatar(const QUuid& sessionUUID);
|
virtual void removeAvatar(const QUuid& sessionUUID);
|
||||||
|
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar);
|
||||||
|
|
||||||
QVector<AvatarSharedPointer> _avatarFades;
|
QVector<AvatarSharedPointer> _avatarFades;
|
||||||
std::shared_ptr<MyAvatar> _myAvatar;
|
std::shared_ptr<MyAvatar> _myAvatar;
|
||||||
|
|
|
@ -982,10 +982,8 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
||||||
const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f;
|
const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f;
|
||||||
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f;
|
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f;
|
||||||
|
|
||||||
AvatarHash hash;
|
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
||||||
DependencyManager::get<AvatarManager>()->withAvatarHash([&] (const AvatarHash& locked) {
|
|
||||||
hash = locked; // make a shallow copy and operate on that, to minimize lock time
|
|
||||||
});
|
|
||||||
foreach (const AvatarSharedPointer& avatarPointer, hash) {
|
foreach (const AvatarSharedPointer& avatarPointer, hash) {
|
||||||
auto avatar = static_pointer_cast<Avatar>(avatarPointer);
|
auto avatar = static_pointer_cast<Avatar>(avatarPointer);
|
||||||
bool isCurrentTarget = avatar->getIsLookAtTarget();
|
bool isCurrentTarget = avatar->getIsLookAtTarget();
|
||||||
|
|
|
@ -22,13 +22,9 @@ AvatarHashMap::AvatarHashMap() {
|
||||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
connect(DependencyManager::get<NodeList>().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::withAvatarHash(std::function<void(const AvatarHash& hash)> callback) {
|
|
||||||
QReadLocker locker(&_hashLock);
|
|
||||||
callback(_avatarHash);
|
|
||||||
}
|
|
||||||
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
|
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
|
||||||
QReadLocker locker(&_hashLock);
|
auto hashCopy = getHashCopy();
|
||||||
foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) {
|
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {
|
||||||
glm::vec3 avatarPosition = sharedAvatar->getPosition();
|
glm::vec3 avatarPosition = sharedAvatar->getPosition();
|
||||||
float distance = glm::distance(avatarPosition, position);
|
float distance = glm::distance(avatarPosition, position);
|
||||||
if (distance < range) {
|
if (distance < range) {
|
||||||
|
@ -44,16 +40,34 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() {
|
||||||
|
|
||||||
AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap.";
|
qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap.";
|
||||||
|
|
||||||
AvatarSharedPointer avatar = newSharedAvatar();
|
auto avatar = newSharedAvatar();
|
||||||
avatar->setSessionUUID(sessionUUID);
|
avatar->setSessionUUID(sessionUUID);
|
||||||
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
||||||
QWriteLocker locker(&_hashLock);
|
|
||||||
_avatarHash.insert(sessionUUID, avatar);
|
_avatarHash.insert(sessionUUID, avatar);
|
||||||
emit avatarAddedEvent(sessionUUID);
|
emit avatarAddedEvent(sessionUUID);
|
||||||
|
|
||||||
return avatar;
|
return avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
QWriteLocker locker(&_hashLock);
|
||||||
|
|
||||||
|
auto avatar = _avatarHash.value(sessionUUID);
|
||||||
|
|
||||||
|
if (!avatar) {
|
||||||
|
avatar = addAvatar(sessionUUID, mixerWeakPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) {
|
||||||
|
QReadLocker locker(&_hashLock);
|
||||||
|
return _avatarHash.value(sessionUUID);
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarHashMap::processAvatarDataPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
void AvatarHashMap::processAvatarDataPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||||
|
|
||||||
// enumerate over all of the avatars in this packet
|
// enumerate over all of the avatars in this packet
|
||||||
|
@ -66,10 +80,7 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<NLPacket> packet, Sha
|
||||||
QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead());
|
QByteArray byteArray = packet->readWithoutCopy(packet->bytesLeftToRead());
|
||||||
|
|
||||||
if (sessionUUID != _lastOwnerSessionUUID) {
|
if (sessionUUID != _lastOwnerSessionUUID) {
|
||||||
AvatarSharedPointer avatar = _avatarHash.value(sessionUUID);
|
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||||
if (!avatar) {
|
|
||||||
avatar = addAvatar(sessionUUID, sendingNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// have the matching (or new) avatar parse the data from the packet
|
// have the matching (or new) avatar parse the data from the packet
|
||||||
int bytesRead = avatar->parseDataFromBuffer(byteArray);
|
int bytesRead = avatar->parseDataFromBuffer(byteArray);
|
||||||
|
@ -97,10 +108,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<NLPacket> packet,
|
||||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
|
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
|
||||||
|
|
||||||
// mesh URL for a UUID, find avatar in our list
|
// mesh URL for a UUID, find avatar in our list
|
||||||
AvatarSharedPointer avatar = _avatarHash.value(sessionUUID);
|
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||||
if (!avatar) {
|
|
||||||
avatar = addAvatar(sessionUUID, sendingNode);
|
|
||||||
}
|
|
||||||
if (avatar->getFaceModelURL() != faceMeshURL) {
|
if (avatar->getFaceModelURL() != faceMeshURL) {
|
||||||
avatar->setFaceModelURL(faceMeshURL);
|
avatar->setFaceModelURL(faceMeshURL);
|
||||||
}
|
}
|
||||||
|
@ -122,10 +131,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<NLPacket> packet,
|
||||||
void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
void AvatarHashMap::processAvatarBillboardPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
AvatarSharedPointer avatar = _avatarHash.value(sessionUUID);
|
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||||
if (!avatar) {
|
|
||||||
avatar = addAvatar(sessionUUID, sendingNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray billboard = packet->read(packet->bytesLeftToRead());
|
QByteArray billboard = packet->read(packet->bytesLeftToRead());
|
||||||
if (avatar->getBillboard() != billboard) {
|
if (avatar->getBillboard() != billboard) {
|
||||||
|
@ -137,13 +143,22 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<NLPacket> packet, SharedNod
|
||||||
// read the node id
|
// read the node id
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
QUuid sessionUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
removeAvatar(sessionUUID);
|
removeAvatar(sessionUUID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) {
|
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) {
|
||||||
QWriteLocker locker(&_hashLock);
|
QWriteLocker locker(&_hashLock);
|
||||||
_avatarHash.remove(sessionUUID);
|
|
||||||
emit avatarRemovedEvent(sessionUUID);
|
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||||
|
|
||||||
|
if (removedAvatar) {
|
||||||
|
handleRemovedAvatar(removedAvatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) {
|
||||||
|
qDebug() << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||||
|
<< "from AvatarHashMap";
|
||||||
|
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID) {
|
void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AvatarHashMap : public QObject, public Dependency {
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void withAvatarHash(std::function<void(const AvatarHash& hash)>);
|
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||||
int size() { return _avatarHash.size(); }
|
int size() { return _avatarHash.size(); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -55,7 +55,11 @@ protected:
|
||||||
|
|
||||||
virtual AvatarSharedPointer newSharedAvatar();
|
virtual AvatarSharedPointer newSharedAvatar();
|
||||||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock
|
||||||
virtual void removeAvatar(const QUuid& sessionUUID);
|
virtual void removeAvatar(const QUuid& sessionUUID);
|
||||||
|
|
||||||
|
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar);
|
||||||
|
|
||||||
AvatarHash _avatarHash;
|
AvatarHash _avatarHash;
|
||||||
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
|
// "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock.
|
||||||
|
|
|
@ -369,6 +369,11 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) {
|
||||||
void AssetScriptingInterface::uploadData(QString data, QString extension, QScriptValue callback) {
|
void AssetScriptingInterface::uploadData(QString data, QString extension, QScriptValue callback) {
|
||||||
QByteArray dataByteArray = data.toUtf8();
|
QByteArray dataByteArray = data.toUtf8();
|
||||||
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray, extension);
|
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray, extension);
|
||||||
|
if (!upload) {
|
||||||
|
qCWarning(asset_client) << "Error uploading file to asset server";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QObject::connect(upload, &AssetUpload::finished, this, [callback, extension](AssetUpload* upload, const QString& hash) mutable {
|
QObject::connect(upload, &AssetUpload::finished, this, [callback, extension](AssetUpload* upload, const QString& hash) mutable {
|
||||||
if (callback.isFunction()) {
|
if (callback.isFunction()) {
|
||||||
QString url = "atp://" + hash + "." + extension;
|
QString url = "atp://" + hash + "." + extension;
|
||||||
|
@ -376,8 +381,6 @@ void AssetScriptingInterface::uploadData(QString data, QString extension, QScrip
|
||||||
callback.call(QScriptValue(), args);
|
callback.call(QScriptValue(), args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// start the upload now
|
|
||||||
upload->start();
|
upload->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +426,4 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb
|
||||||
});
|
});
|
||||||
|
|
||||||
assetRequest->start();
|
assetRequest->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ void MessagesClient::init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesClient::handleMessagesPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
void MessagesClient::decodeMessagesPacket(QSharedPointer<NLPacketList> packetList, QString& channel, QString& message, QUuid& senderID) {
|
||||||
QByteArray packetData = packetList->getMessage();
|
QByteArray packetData = packetList->getMessage();
|
||||||
QBuffer packet{ &packetData };
|
QBuffer packet{ &packetData };
|
||||||
packet.open(QIODevice::ReadOnly);
|
packet.open(QIODevice::ReadOnly);
|
||||||
|
@ -44,38 +44,60 @@ void MessagesClient::handleMessagesPacket(QSharedPointer<NLPacketList> packetLis
|
||||||
quint16 channelLength;
|
quint16 channelLength;
|
||||||
packet.read(reinterpret_cast<char*>(&channelLength), sizeof(channelLength));
|
packet.read(reinterpret_cast<char*>(&channelLength), sizeof(channelLength));
|
||||||
auto channelData = packet.read(channelLength);
|
auto channelData = packet.read(channelLength);
|
||||||
QString channel = QString::fromUtf8(channelData);
|
channel = QString::fromUtf8(channelData);
|
||||||
|
|
||||||
quint16 messageLength;
|
quint16 messageLength;
|
||||||
packet.read(reinterpret_cast<char*>(&messageLength), sizeof(messageLength));
|
packet.read(reinterpret_cast<char*>(&messageLength), sizeof(messageLength));
|
||||||
auto messageData = packet.read(messageLength);
|
auto messageData = packet.read(messageLength);
|
||||||
QString message = QString::fromUtf8(messageData);
|
message = QString::fromUtf8(messageData);
|
||||||
|
|
||||||
emit messageReceived(channel, message, senderNode->getUUID());
|
QByteArray bytesSenderID = packet.read(NUM_BYTES_RFC4122_UUID);
|
||||||
|
if (bytesSenderID.length() == NUM_BYTES_RFC4122_UUID) {
|
||||||
|
senderID = QUuid::fromRfc4122(bytesSenderID);
|
||||||
|
} else {
|
||||||
|
QUuid emptyUUID;
|
||||||
|
senderID = emptyUUID; // packet was missing UUID use default instead
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesClient::sendMessage(const QString& channel, const QString& message) {
|
std::unique_ptr<NLPacketList> MessagesClient::encodeMessagesPacket(QString channel, QString message, QUuid senderID) {
|
||||||
|
auto packetList = NLPacketList::create(PacketType::MessagesData, QByteArray(), true, true);
|
||||||
|
|
||||||
|
auto channelUtf8 = channel.toUtf8();
|
||||||
|
quint16 channelLength = channelUtf8.length();
|
||||||
|
packetList->writePrimitive(channelLength);
|
||||||
|
packetList->write(channelUtf8);
|
||||||
|
|
||||||
|
auto messageUtf8 = message.toUtf8();
|
||||||
|
quint16 messageLength = messageUtf8.length();
|
||||||
|
packetList->writePrimitive(messageLength);
|
||||||
|
packetList->write(messageUtf8);
|
||||||
|
|
||||||
|
packetList->write(senderID.toRfc4122());
|
||||||
|
|
||||||
|
return packetList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MessagesClient::handleMessagesPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||||
|
QString channel, message;
|
||||||
|
QUuid senderID;
|
||||||
|
decodeMessagesPacket(packetList, channel, message, senderID);
|
||||||
|
emit messageReceived(channel, message, senderID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagesClient::sendMessage(QString channel, QString message) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||||
|
|
||||||
if (messagesMixer) {
|
if (messagesMixer) {
|
||||||
auto packetList = NLPacketList::create(PacketType::MessagesData, QByteArray(), true, true);
|
QUuid senderID = nodeList->getSessionUUID();
|
||||||
|
auto packetList = encodeMessagesPacket(channel, message, senderID);
|
||||||
auto channelUtf8 = channel.toUtf8();
|
|
||||||
quint16 channelLength = channelUtf8.length();
|
|
||||||
packetList->writePrimitive(channelLength);
|
|
||||||
packetList->write(channelUtf8);
|
|
||||||
|
|
||||||
auto messageUtf8 = message.toUtf8();
|
|
||||||
quint16 messageLength = messageUtf8.length();
|
|
||||||
packetList->writePrimitive(messageLength);
|
|
||||||
packetList->write(messageUtf8);
|
|
||||||
|
|
||||||
nodeList->sendPacketList(std::move(packetList), *messagesMixer);
|
nodeList->sendPacketList(std::move(packetList), *messagesMixer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesClient::subscribe(const QString& channel) {
|
void MessagesClient::subscribe(QString channel) {
|
||||||
_subscribedChannels << channel;
|
_subscribedChannels << channel;
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||||
|
@ -87,7 +109,7 @@ void MessagesClient::subscribe(const QString& channel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagesClient::unsubscribe(const QString& channel) {
|
void MessagesClient::unsubscribe(QString channel) {
|
||||||
_subscribedChannels.remove(channel);
|
_subscribedChannels.remove(channel);
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||||
|
|
|
@ -28,12 +28,16 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void init();
|
Q_INVOKABLE void init();
|
||||||
|
|
||||||
Q_INVOKABLE void sendMessage(const QString& channel, const QString& message);
|
Q_INVOKABLE void sendMessage(QString channel, QString message);
|
||||||
Q_INVOKABLE void subscribe(const QString& channel);
|
Q_INVOKABLE void subscribe(QString channel);
|
||||||
Q_INVOKABLE void unsubscribe(const QString& channel);
|
Q_INVOKABLE void unsubscribe(QString channel);
|
||||||
|
|
||||||
|
static void decodeMessagesPacket(QSharedPointer<NLPacketList> packetList, QString& channel, QString& message, QUuid& senderID);
|
||||||
|
static std::unique_ptr<NLPacketList> encodeMessagesPacket(QString channel, QString message, QUuid senderID);
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void messageReceived(const QString& channel, const QString& message, const QUuid& senderUUID);
|
void messageReceived(QString channel, QString message, QUuid senderUUID);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleMessagesPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
void handleMessagesPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||||
|
|
|
@ -1841,6 +1841,7 @@ bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream
|
||||||
|
|
||||||
|
|
||||||
bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStream) {
|
bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStream) {
|
||||||
|
qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon.";
|
||||||
|
|
||||||
bool fileOk = false;
|
bool fileOk = false;
|
||||||
|
|
||||||
|
@ -2062,6 +2063,8 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElementPointer element,
|
||||||
}
|
}
|
||||||
|
|
||||||
void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) {
|
void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) {
|
||||||
|
qWarning() << "SVO file format depricated. Support for reading SVO files is no longer support and will be removed soon.";
|
||||||
|
|
||||||
std::ofstream file(fileName, std::ios::out|std::ios::binary);
|
std::ofstream file(fileName, std::ios::out|std::ios::binary);
|
||||||
|
|
||||||
if(file.is_open()) {
|
if(file.is_open()) {
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
#include "impl/FileClip.h"
|
#include "impl/FileClip.h"
|
||||||
#include "impl/BufferClip.h"
|
#include "impl/BufferClip.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QBuffer>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
Clip::Pointer Clip::fromFile(const QString& filePath) {
|
Clip::Pointer Clip::fromFile(const QString& filePath) {
|
||||||
|
@ -27,6 +32,15 @@ void Clip::toFile(const QString& filePath, const Clip::ConstPointer& clip) {
|
||||||
FileClip::write(filePath, clip->duplicate());
|
FileClip::write(filePath, clip->duplicate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray Clip::toBuffer(const Clip::ConstPointer& clip) {
|
||||||
|
QBuffer buffer;
|
||||||
|
if (buffer.open(QFile::Truncate | QFile::WriteOnly)) {
|
||||||
|
clip->duplicate()->write(buffer);
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
Clip::Pointer Clip::newClip() {
|
Clip::Pointer Clip::newClip() {
|
||||||
return std::make_shared<BufferClip>();
|
return std::make_shared<BufferClip>();
|
||||||
}
|
}
|
||||||
|
@ -37,4 +51,70 @@ void Clip::seek(float offset) {
|
||||||
|
|
||||||
float Clip::position() const {
|
float Clip::position() const {
|
||||||
return Frame::frameTimeToSeconds(positionFrameTime());
|
return Frame::frameTimeToSeconds(positionFrameTime());
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// FIXME move to frame?
|
||||||
|
bool writeFrame(QIODevice& output, const Frame& frame, bool compressed = true) {
|
||||||
|
if (frame.type == Frame::TYPE_INVALID) {
|
||||||
|
qWarning() << "Attempting to write invalid frame";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto written = output.write((char*)&(frame.type), sizeof(FrameType));
|
||||||
|
if (written != sizeof(FrameType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//qDebug() << "Writing frame with time offset " << frame.timeOffset;
|
||||||
|
written = output.write((char*)&(frame.timeOffset), sizeof(Frame::Time));
|
||||||
|
if (written != sizeof(Frame::Time)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray frameData = frame.data;
|
||||||
|
if (compressed) {
|
||||||
|
frameData = qCompress(frameData);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t dataSize = frameData.size();
|
||||||
|
written = output.write((char*)&dataSize, sizeof(FrameSize));
|
||||||
|
if (written != sizeof(uint16_t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataSize != 0) {
|
||||||
|
written = output.write(frameData);
|
||||||
|
if (written != dataSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString Clip::FRAME_TYPE_MAP = QStringLiteral("frameTypes");
|
||||||
|
const QString Clip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed");
|
||||||
|
|
||||||
|
bool Clip::write(QIODevice& output) {
|
||||||
|
auto frameTypes = Frame::getFrameTypes();
|
||||||
|
QJsonObject frameTypeObj;
|
||||||
|
for (const auto& frameTypeName : frameTypes.keys()) {
|
||||||
|
frameTypeObj[frameTypeName] = frameTypes[frameTypeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject rootObject;
|
||||||
|
rootObject.insert(FRAME_TYPE_MAP, frameTypeObj);
|
||||||
|
// Always mark new files as compressed
|
||||||
|
rootObject.insert(FRAME_COMREPSSION_FLAG, true);
|
||||||
|
QByteArray headerFrameData = QJsonDocument(rootObject).toBinaryData();
|
||||||
|
// Never compress the header frame
|
||||||
|
if (!writeFrame(output, Frame({ Frame::TYPE_HEADER, 0, headerFrameData }), false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
seek(0);
|
||||||
|
|
||||||
|
for (auto frame = nextFrame(); frame; frame = nextFrame()) {
|
||||||
|
if (!writeFrame(output, *frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -47,10 +47,16 @@ public:
|
||||||
virtual void skipFrame() = 0;
|
virtual void skipFrame() = 0;
|
||||||
virtual void addFrame(FrameConstPointer) = 0;
|
virtual void addFrame(FrameConstPointer) = 0;
|
||||||
|
|
||||||
|
bool write(QIODevice& output);
|
||||||
|
|
||||||
static Pointer fromFile(const QString& filePath);
|
static Pointer fromFile(const QString& filePath);
|
||||||
static void toFile(const QString& filePath, const ConstPointer& clip);
|
static void toFile(const QString& filePath, const ConstPointer& clip);
|
||||||
|
static QByteArray toBuffer(const ConstPointer& clip);
|
||||||
static Pointer newClip();
|
static Pointer newClip();
|
||||||
|
|
||||||
|
static const QString FRAME_TYPE_MAP;
|
||||||
|
static const QString FRAME_COMREPSSION_FLAG;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class WrapperClip;
|
friend class WrapperClip;
|
||||||
using Mutex = std::recursive_mutex;
|
using Mutex = std::recursive_mutex;
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QJsonDocument>
|
|
||||||
#include <QtCore/QJsonObject>
|
|
||||||
|
|
||||||
#include <Finally.h>
|
#include <Finally.h>
|
||||||
|
|
||||||
|
@ -40,41 +38,7 @@ QString FileClip::getName() const {
|
||||||
return _file.fileName();
|
return _file.fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME move to frame?
|
|
||||||
bool writeFrame(QIODevice& output, const Frame& frame, bool compressed = true) {
|
|
||||||
if (frame.type == Frame::TYPE_INVALID) {
|
|
||||||
qWarning() << "Attempting to write invalid frame";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto written = output.write((char*)&(frame.type), sizeof(FrameType));
|
|
||||||
if (written != sizeof(FrameType)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//qDebug() << "Writing frame with time offset " << frame.timeOffset;
|
|
||||||
written = output.write((char*)&(frame.timeOffset), sizeof(Frame::Time));
|
|
||||||
if (written != sizeof(Frame::Time)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QByteArray frameData = frame.data;
|
|
||||||
if (compressed) {
|
|
||||||
frameData = qCompress(frameData);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t dataSize = frameData.size();
|
|
||||||
written = output.write((char*)&dataSize, sizeof(FrameSize));
|
|
||||||
if (written != sizeof(uint16_t)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataSize != 0) {
|
|
||||||
written = output.write(frameData);
|
|
||||||
if (written != dataSize) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
|
bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
|
||||||
// FIXME need to move this to a different thread
|
// FIXME need to move this to a different thread
|
||||||
|
@ -90,33 +54,7 @@ bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Finally closer([&] { outputFile.close(); });
|
Finally closer([&] { outputFile.close(); });
|
||||||
{
|
return clip->write(outputFile);
|
||||||
auto frameTypes = Frame::getFrameTypes();
|
|
||||||
QJsonObject frameTypeObj;
|
|
||||||
for (const auto& frameTypeName : frameTypes.keys()) {
|
|
||||||
frameTypeObj[frameTypeName] = frameTypes[frameTypeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject rootObject;
|
|
||||||
rootObject.insert(FRAME_TYPE_MAP, frameTypeObj);
|
|
||||||
// Always mark new files as compressed
|
|
||||||
rootObject.insert(FRAME_COMREPSSION_FLAG, true);
|
|
||||||
QByteArray headerFrameData = QJsonDocument(rootObject).toBinaryData();
|
|
||||||
// Never compress the header frame
|
|
||||||
if (!writeFrame(outputFile, Frame({ Frame::TYPE_HEADER, 0, headerFrameData }), false)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
clip->seek(0);
|
|
||||||
for (auto frame = clip->nextFrame(); frame; frame = clip->nextFrame()) {
|
|
||||||
if (!writeFrame(outputFile, *frame)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outputFile.close();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileClip::~FileClip() {
|
FileClip::~FileClip() {
|
||||||
|
|
|
@ -23,16 +23,13 @@
|
||||||
|
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
const QString PointerClip::FRAME_TYPE_MAP = QStringLiteral("frameTypes");
|
|
||||||
const QString PointerClip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed");
|
|
||||||
|
|
||||||
using FrameTranslationMap = QMap<FrameType, FrameType>;
|
using FrameTranslationMap = QMap<FrameType, FrameType>;
|
||||||
|
|
||||||
FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) {
|
FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) {
|
||||||
FrameTranslationMap results;
|
FrameTranslationMap results;
|
||||||
auto headerObj = doc.object();
|
auto headerObj = doc.object();
|
||||||
if (headerObj.contains(PointerClip::FRAME_TYPE_MAP)) {
|
if (headerObj.contains(Clip::FRAME_TYPE_MAP)) {
|
||||||
auto frameTypeObj = headerObj[PointerClip::FRAME_TYPE_MAP].toObject();
|
auto frameTypeObj = headerObj[Clip::FRAME_TYPE_MAP].toObject();
|
||||||
auto currentFrameTypes = Frame::getFrameTypes();
|
auto currentFrameTypes = Frame::getFrameTypes();
|
||||||
for (auto frameTypeName : frameTypeObj.keys()) {
|
for (auto frameTypeName : frameTypeObj.keys()) {
|
||||||
qDebug() << frameTypeName;
|
qDebug() << frameTypeName;
|
||||||
|
|
|
@ -44,9 +44,6 @@ public:
|
||||||
|
|
||||||
// FIXME move to frame?
|
// FIXME move to frame?
|
||||||
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize);
|
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize);
|
||||||
static const QString FRAME_TYPE_MAP;
|
|
||||||
static const QString FRAME_COMREPSSION_FLAG;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void reset();
|
void reset();
|
||||||
virtual FrameConstPointer readFrame(size_t index) const override;
|
virtual FrameConstPointer readFrame(size_t index) const override;
|
||||||
|
|
|
@ -44,19 +44,25 @@ float RecordingScriptingInterface::playerLength() const {
|
||||||
return _player->length();
|
return _player->length();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordingScriptingInterface::loadRecording(const QString& url) {
|
bool RecordingScriptingInterface::loadRecording(const QString& url) {
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
if (QThread::currentThread() != thread()) {
|
auto loader = ClipCache::instance().getClipLoader(url);
|
||||||
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
|
QEventLoop loop;
|
||||||
Q_ARG(QString, url));
|
QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit);
|
||||||
return;
|
QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit);
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
if (!loader->isLoaded()) {
|
||||||
|
qWarning() << "Clip failed to load from " << url;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME make blocking and force off main thread?
|
_player->queueClip(loader->getClip());
|
||||||
_player->queueClip(ClipCache::instance().getClipLoader(url)->getClip());
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RecordingScriptingInterface::startPlaying() {
|
void RecordingScriptingInterface::startPlaying() {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);
|
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
RecordingScriptingInterface();
|
RecordingScriptingInterface();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void loadRecording(const QString& url);
|
bool loadRecording(const QString& url);
|
||||||
|
|
||||||
void startPlaying();
|
void startPlaying();
|
||||||
void pausePlayer();
|
void pausePlayer();
|
||||||
|
|
Loading…
Reference in a new issue