Merge branch 'master' of https://github.com/worklist/hifi into 19495

This commit is contained in:
stojce 2014-02-08 01:10:55 +01:00
commit 48b0b25bda
143 changed files with 3817 additions and 1687 deletions

View file

@ -18,9 +18,18 @@ send your resume to hiring@highfidelity.io
Building Interface & other High Fidelity Components
=========
Interface is our OS X and Linux build-able client for accessing our virtual
Interface is our Windows, OS X, and Linux build-able client for accessing our virtual
world.
For detailed notes on building for Windows, please refer to the following wiki page:
https://github.com/highfidelity/hifi/wiki/Building-on-Windows
For detailed notes on building for Ubuntu, please refer to the following wiki page:
https://github.com/highfidelity/hifi/wiki/Building-on-Ubuntu-13.04
Building on Mac OS X and Linux:
--------------------------------
CMake
-----
Hifi uses CMake to generate build files and project files
@ -45,9 +54,9 @@ If Cmake throws you an error related to Qt5 it likely cannot find your Qt5 cmake
You can solve this by setting an environment variable, QT_CMAKE_PREFIX_PATH, to the location of the folder distributed
with Qt5 that contains them.
For example, a Qt5 5.1.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
For example, a Qt5 5.2.0 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.1.1/clang_64/lib/cmake/
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/
The path it needs to be set to will depend on where and how Qt5 was installed.
@ -64,7 +73,7 @@ components located in the build/target_name/Debug directories.
Other dependencies & information
----
In addition to CMake, Qt 5.1 is required to build all components.
In addition to CMake, Qt 5.2 is required to build all components.
What can I build on?
We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux

View file

@ -830,12 +830,16 @@ void AnimationServer::readPendingDatagrams() {
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer());
if (packetVersionMatch(receivedPacket)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
if (packetTypeForPacket(receivedPacket) == PacketTypeJurisdiction) {
int headerBytes = numBytesForPacketHeader(receivedPacket);
// PacketType_JURISDICTION, first byte is the node type...
if (receivedPacket.data()[headerBytes] == NodeType::VoxelServer && ::jurisdictionListener) {
::jurisdictionListener->queueReceivedPacket(nodeSockAddr, receivedPacket);
SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(receivedPacket);
if (matchedNode) {
::jurisdictionListener->queueReceivedPacket(matchedNode, receivedPacket);
}
}
}
NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket);

View file

@ -30,9 +30,6 @@ link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(particle-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxel-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(script-engine ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})

View file

@ -23,33 +23,51 @@
#include "Agent.h"
Agent::Agent(const QByteArray& packet) :
ThreadedAssignment(packet)
ThreadedAssignment(packet),
_voxelEditSender(),
_particleEditSender()
{
_scriptEngine.getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
}
void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
PacketType datagramPacketType = packetTypeForPacket(dataByteArray);
if (datagramPacketType == PacketTypeJurisdiction) {
int headerBytes = numBytesForPacketHeader(dataByteArray);
// PacketType_JURISDICTION, first byte is the node type...
switch (dataByteArray[headerBytes]) {
case NodeType::VoxelServer:
_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
dataByteArray);
break;
case NodeType::ParticleServer:
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
dataByteArray);
break;
void Agent::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
NodeList* nodeList = NodeList::getInstance();
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType datagramPacketType = packetTypeForPacket(receivedPacket);
if (datagramPacketType == PacketTypeJurisdiction) {
int headerBytes = numBytesForPacketHeader(receivedPacket);
SharedNodePointer matchedNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchedNode) {
// PacketType_JURISDICTION, first byte is the node type...
switch (receivedPacket[headerBytes]) {
case NodeType::VoxelServer:
_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(matchedNode,
receivedPacket);
break;
case NodeType::ParticleServer:
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(matchedNode,
receivedPacket);
break;
}
}
} else if (datagramPacketType == PacketTypeParticleAddResponse) {
// this will keep creatorTokenIDs to IDs mapped correctly
Particle::handleAddParticleResponse(receivedPacket);
// also give our local particle tree a chance to remap any internal locally created particles
_particleTree.handleAddParticleResponse(receivedPacket);
} else {
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
}
}
} else if (datagramPacketType == PacketTypeParticleAddResponse) {
// this will keep creatorTokenIDs to IDs mapped correctly
Particle::handleAddParticleResponse(dataByteArray);
// also give our local particle tree a chance to remap any internal locally created particles
_particleTree.handleAddParticleResponse(dataByteArray);
} else {
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
@ -89,10 +107,6 @@ void Agent::run() {
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
QTimer* pingNodesTimer = new QTimer(this);
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
// tell our script engine about our local particle tree
_scriptEngine.getParticlesScriptingInterface()->setParticleTree(&_particleTree);

View file

@ -15,9 +15,12 @@
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <ParticleEditPacketSender.h>
#include <ParticleTree.h>
#include <ScriptEngine.h>
#include <ThreadedAssignment.h>
#include <VoxelEditPacketSender.h>
class Agent : public ThreadedAssignment {
Q_OBJECT
@ -32,13 +35,15 @@ public:
public slots:
void run();
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
void readPendingDatagrams();
signals:
void willSendAudioDataCallback();
void willSendVisualDataCallback();
private:
ScriptEngine _scriptEngine;
ParticleTree _particleTree;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
};
#endif /* defined(__hifi__Agent__) */

View file

@ -90,8 +90,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
timer->start(ASSIGNMENT_REQUEST_INTERVAL_MSECS);
// connect our readPendingDatagrams method to the readyRead() signal of the socket
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams,
Qt::QueuedConnection);
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
}
void AssignmentClient::sendAssignmentRequest() {
@ -111,50 +110,45 @@ void AssignmentClient::readPendingDatagrams() {
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
if (packetVersionMatch(receivedPacket)) {
if (_currentAssignment) {
// have the threaded current assignment handle this datagram
QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection,
Q_ARG(QByteArray, receivedPacket),
Q_ARG(HifiSockAddr, senderSockAddr));
} else if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
// construct the deployed assignment from the packet data
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
if (_currentAssignment) {
qDebug() << "Dropping received assignment since we are currently running one.";
} else {
// construct the deployed assignment from the packet data
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
qDebug() << "Received an assignment -" << *_currentAssignment;
if (_currentAssignment) {
qDebug() << "Received an assignment -" << *_currentAssignment;
// switch our nodelist domain IP and port to whoever sent us the assignment
nodeList->setDomainSockAddr(senderSockAddr);
nodeList->setOwnerUUID(_currentAssignment->getUUID());
qDebug() << "Destination IP for assignment is" << nodeList->getDomainIP().toString();
// start the deployed assignment
QThread* workerThread = new QThread(this);
connect(workerThread, SIGNAL(started()), _currentAssignment, SLOT(run()));
connect(_currentAssignment, SIGNAL(finished()), this, SLOT(assignmentCompleted()));
connect(_currentAssignment, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(_currentAssignment, SIGNAL(finished()), _currentAssignment, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
_currentAssignment->moveToThread(workerThread);
// move the NodeList to the thread used for the _current assignment
nodeList->moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
workerThread->start();
} else {
qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
}
// switch our nodelist domain IP and port to whoever sent us the assignment
nodeList->setDomainSockAddr(senderSockAddr);
nodeList->setSessionUUID(_currentAssignment->getUUID());
qDebug() << "Destination IP for assignment is" << nodeList->getDomainIP().toString();
// start the deployed assignment
QThread* workerThread = new QThread(this);
connect(workerThread, SIGNAL(started()), _currentAssignment, SLOT(run()));
connect(_currentAssignment, SIGNAL(finished()), this, SLOT(assignmentCompleted()));
connect(_currentAssignment, SIGNAL(finished()), workerThread, SLOT(quit()));
connect(_currentAssignment, SIGNAL(finished()), _currentAssignment, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
_currentAssignment->moveToThread(workerThread);
// move the NodeList to the thread used for the _current assignment
nodeList->moveToThread(workerThread);
// let the assignment handle the incoming datagrams for its duration
disconnect(&nodeList->getNodeSocket(), 0, this, 0);
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _currentAssignment,
&ThreadedAssignment::readPendingDatagrams);
// Starts an event loop, and emits workerThread->started()
workerThread->start();
} else {
qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
}
} else {
// have the NodeList attempt to handle it
@ -170,10 +164,14 @@ void AssignmentClient::assignmentCompleted() {
qDebug("Assignment finished or never started - waiting for new assignment.");
_currentAssignment = NULL;
NodeList* nodeList = NodeList::getInstance();
// have us handle incoming NodeList datagrams again
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment, 0);
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
_currentAssignment = NULL;
// reset our NodeList by switching back to unassigned and clearing the list
nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset();

View file

@ -8,15 +8,13 @@
#include <PacketHeaders.h>
#include <ParticleServer.h>
#include <VoxelServer.h>
#include "Agent.h"
#include "AssignmentFactory.h"
#include "audio/AudioMixer.h"
#include "avatars/AvatarMixer.h"
#include "metavoxels/MetavoxelServer.h"
#include "particles/ParticleServer.h"
#include "voxels/VoxelServer.h"
ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) {
QDataStream packetStream(packet);

View file

@ -207,31 +207,25 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
}
void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
// pull any new audio data from nodes off of the network stack
PacketType mixerPacketType = packetTypeForPacket(dataByteArray);
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|| mixerPacketType == PacketTypeInjectAudio) {
QUuid nodeUUID;
deconstructPacketHeader(dataByteArray, nodeUUID);
NodeList* nodeList = NodeList::getInstance();
SharedNodePointer matchingNode = nodeList->nodeWithUUID(nodeUUID);
if (matchingNode) {
nodeList->updateNodeWithData(matchingNode.data(), senderSockAddr, dataByteArray);
if (!matchingNode->getActiveSocket()) {
// we don't have an active socket for this node, but they're talking to us
// this means they've heard from us and can reply, let's assume public is active
matchingNode->activatePublicSocket();
void AudioMixer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
NodeList* nodeList = NodeList::getInstance();
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
// pull any new audio data from nodes off of the network stack
PacketType mixerPacketType = packetTypeForPacket(receivedPacket);
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|| mixerPacketType == PacketTypeInjectAudio) {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
} else {
// let processNodeData handle it.
nodeList->processNodeData(senderSockAddr, receivedPacket);
}
}
} else {
// let processNodeData handle it.
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
@ -279,9 +273,7 @@ void AudioMixer::run() {
prepareMixForListeningNode(node.data());
memcpy(clientPacket + numBytesPacketHeader, _clientSamples, sizeof(_clientSamples));
nodeList->getNodeSocket().writeDatagram((char*) clientPacket, sizeof(clientPacket),
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
nodeList->writeDatagram((char*) clientPacket, sizeof(clientPacket), node);
}
}

View file

@ -25,7 +25,7 @@ public slots:
/// threaded run of assignment
void run();
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
void readPendingDatagrams();
private:
/// adds one buffer to the mix for a listening node
void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,

View file

@ -11,6 +11,7 @@
// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms.
#include <QtCore/QCoreApplication>
#include <QtCore/QElapsedTimer>
#include <QtCore/QTimer>
#include <Logging.h>
@ -19,7 +20,7 @@
#include <SharedUtil.h>
#include <UUID.h>
#include "AvatarData.h"
#include "AvatarMixerClientData.h"
#include "AvatarMixer.h"
@ -36,7 +37,7 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) :
void attachAvatarDataToNode(Node* newNode) {
if (newNode->getLinkedData() == NULL) {
newNode->setLinkedData(new AvatarData());
newNode->setLinkedData(new AvatarMixerClientData());
}
}
@ -52,8 +53,6 @@ void broadcastAvatarData() {
int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
int packetsSent = 0;
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -70,15 +69,11 @@ void broadcastAvatarData() {
QByteArray avatarByteArray;
avatarByteArray.append(otherNode->getUUID().toRfc4122());
AvatarData* nodeData = (AvatarData*) otherNode->getLinkedData();
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
avatarByteArray.append(nodeData->toByteArray());
if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
packetsSent++;
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
nodeList->writeDatagram(mixedAvatarByteArray, node);
// reset the packet
mixedAvatarByteArray.resize(numPacketHeaderBytes);
@ -89,15 +84,45 @@ void broadcastAvatarData() {
}
}
packetsSent++;
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
nodeList->writeDatagram(mixedAvatarByteArray, node);
}
}
}
void broadcastIdentityPacket() {
NodeList* nodeList = NodeList::getInstance();
QByteArray avatarIdentityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
int numPacketHeaderBytes = avatarIdentityPacket.size();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData() && node->getType() == NodeType::Agent) {
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
QByteArray individualData = nodeData->identityByteArray();
individualData.replace(0, NUM_BYTES_RFC4122_UUID, node->getUUID().toRfc4122());
if (avatarIdentityPacket.size() + individualData.size() > MAX_PACKET_SIZE) {
// we've hit MTU, send out the current packet before appending
nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent);
avatarIdentityPacket.resize(numPacketHeaderBytes);
}
// append the individual data to the current the avatarIdentityPacket
avatarIdentityPacket.append(individualData);
// re-set the bool in AvatarMixerClientData so a change between key frames gets sent out
nodeData->setHasSentIdentityBetweenKeyFrames(false);
}
}
// send out the final packet
if (avatarIdentityPacket.size() > numPacketHeaderBytes) {
nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent);
}
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
if (killedNode->getType() == NodeType::Agent
&& killedNode->getLinkedData()) {
@ -111,37 +136,56 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
}
}
void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
void AvatarMixer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
NodeList* nodeList = NodeList::getInstance();
switch (packetTypeForPacket(dataByteArray)) {
case PacketTypeAvatarData: {
QUuid nodeUUID;
deconstructPacketHeader(dataByteArray, nodeUUID);
// add or update the node in our list
SharedNodePointer avatarNode = nodeList->nodeWithUUID(nodeUUID);
if (avatarNode) {
// parse positional data from an node
nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr, dataByteArray);
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
switch (packetTypeForPacket(receivedPacket)) {
case PacketTypeAvatarData: {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
break;
}
case PacketTypeAvatarIdentity: {
// check if we have a matching node in our list
SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket);
if (avatarNode && avatarNode->getLinkedData()) {
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
if (nodeData->hasIdentityChangedAfterParsing(receivedPacket)
&& !nodeData->hasSentIdentityBetweenKeyFrames()) {
// this avatar changed their identity in some way and we haven't sent a packet in this keyframe
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
QByteArray individualByteArray = nodeData->identityByteArray();
individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122());
identityPacket.append(individualByteArray);
nodeData->setHasSentIdentityBetweenKeyFrames(true);
nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent);
}
}
}
case PacketTypeKillAvatar: {
nodeList->processKillNode(receivedPacket);
break;
}
default:
// hand this off to the NodeList
nodeList->processNodeData(senderSockAddr, receivedPacket);
break;
}
break;
}
case PacketTypeKillAvatar: {
nodeList->processKillNode(dataByteArray);
break;
}
default:
// hand this off to the NodeList
nodeList->processNodeData(senderSockAddr, dataByteArray);
break;
}
}
const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000;
void AvatarMixer::run() {
commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
@ -155,6 +199,9 @@ void AvatarMixer::run() {
gettimeofday(&startTime, NULL);
QElapsedTimer identityTimer;
identityTimer.start();
while (!_isFinished) {
QCoreApplication::processEvents();
@ -165,6 +212,14 @@ void AvatarMixer::run() {
broadcastAvatarData();
if (identityTimer.elapsed() >= AVATAR_IDENTITY_KEYFRAME_MSECS) {
// it's time to broadcast the keyframe identity packets
broadcastIdentityPacket();
// restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS
identityTimer.restart();
}
int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow();
if (usecToSleep > 0) {

View file

@ -20,9 +20,10 @@ public slots:
/// runs the avatar mixer
void run();
void nodeAdded(SharedNodePointer nodeAdded);
void nodeKilled(SharedNodePointer killedNode);
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
void readPendingDatagrams();
};
#endif /* defined(__hifi__AvatarMixer__) */

View file

@ -0,0 +1,15 @@
//
// AvatarMixerClientData.cpp
// hifi
//
// Created by Stephen Birarda on 2/4/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include "AvatarMixerClientData.h"
AvatarMixerClientData::AvatarMixerClientData() :
_hasSentIdentityBetweenKeyFrames(false)
{
}

View file

@ -0,0 +1,29 @@
//
// AvatarMixerClientData.h
// hifi
//
// Created by Stephen Birarda on 2/4/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__AvatarMixerClientData__
#define __hifi__AvatarMixerClientData__
#include <QtCore/QUrl>
#include <AvatarData.h>
class AvatarMixerClientData : public AvatarData {
Q_OBJECT
public:
AvatarMixerClientData();
bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; }
void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames)
{ _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; }
private:
bool _hasSentIdentityBetweenKeyFrames;
};
#endif /* defined(__hifi__AvatarMixerClientData__) */

View file

@ -41,15 +41,27 @@ void MetavoxelServer::run() {
_sendTimer.start(SEND_INTERVAL);
}
void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
switch (dataByteArray.at(0)) {
case PacketTypeMetavoxelData:
processData(dataByteArray, senderSockAddr);
break;
default:
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
break;
void MetavoxelServer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
NodeList* nodeList = NodeList::getInstance();
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
switch (packetTypeForPacket(receivedPacket)) {
case PacketTypeMetavoxelData: {
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchingNode) {
processData(receivedPacket, matchingNode);
}
break;
}
default:
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
break;
}
}
}
}
@ -67,10 +79,10 @@ void MetavoxelServer::sendDeltas() {
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
}
void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& sender) {
void MetavoxelServer::processData(const QByteArray& data, const SharedNodePointer& sendingNode) {
// read the session id
int headerPlusIDSize;
QUuid sessionID = readSessionID(data, sender, headerPlusIDSize);
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
if (sessionID.isNull()) {
return;
}
@ -78,18 +90,19 @@ void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& se
// forward to session, creating if necessary
MetavoxelSession*& session = _sessions[sessionID];
if (!session) {
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), sender);
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize),
sendingNode);
}
session->receivedData(data, sender);
session->receivedData(data, sendingNode);
}
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
const QByteArray& datagramHeader, const HifiSockAddr& sender) :
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode) :
QObject(server),
_server(server),
_sessionId(sessionId),
_sequencer(datagramHeader),
_sender(sender) {
_sendingNode(sendingNode) {
const int TIMEOUT_INTERVAL = 30 * 1000;
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
@ -105,15 +118,15 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
SendRecord record = { 0 };
_sendRecords.append(record);
qDebug() << "Opened session [sessionId=" << _sessionId << ", sender=" << _sender << "]";
qDebug() << "Opened session [sessionId=" << _sessionId << ", sendingNode=" << sendingNode << "]";
}
void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
void MetavoxelSession::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
// reset the timeout timer
_timeoutTimer.start();
// save the most recent sender
_sender = sender;
_sendingNode = sendingNode;
// process through sequencer
_sequencer.receivedDatagram(data);
@ -131,12 +144,12 @@ void MetavoxelSession::sendDelta() {
}
void MetavoxelSession::timedOut() {
qDebug() << "Session timed out [sessionId=" << _sessionId << ", sender=" << _sender << "]";
qDebug() << "Session timed out [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
_server->removeSession(_sessionId);
}
void MetavoxelSession::sendData(const QByteArray& data) {
NodeList::getInstance()->getNodeSocket().writeDatagram(data, _sender.getAddress(), _sender.getPort());
NodeList::getInstance()->writeDatagram(data, _sendingNode);
}
void MetavoxelSession::readPacket(Bitstream& in) {
@ -152,7 +165,7 @@ void MetavoxelSession::clearSendRecordsBefore(int index) {
void MetavoxelSession::handleMessage(const QVariant& message) {
int userType = message.userType();
if (userType == CloseSessionMessage::Type) {
qDebug() << "Session closed [sessionId=" << _sessionId << ", sender=" << _sender << "]";
qDebug() << "Session closed [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
_server->removeSession(_sessionId);
} else if (userType == ClientStateMessage::Type) {

View file

@ -39,7 +39,7 @@ public:
virtual void run();
virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
virtual void readPendingDatagrams();
private slots:
@ -47,7 +47,7 @@ private slots:
private:
void processData(const QByteArray& data, const HifiSockAddr& sender);
void processData(const QByteArray& data, const SharedNodePointer& sendingNode);
QTimer _sendTimer;
qint64 _lastSend;
@ -64,9 +64,9 @@ class MetavoxelSession : public QObject {
public:
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
const QByteArray& datagramHeader, const HifiSockAddr& sender);
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode);
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
void sendDelta();
@ -96,7 +96,7 @@ private:
QTimer _timeoutTimer;
DatagramSequencer _sequencer;
HifiSockAddr _sender;
SharedNodePointer _sendingNode;
glm::vec3 _position;

View file

@ -39,7 +39,7 @@ void OctreeInboundPacketProcessor::resetStats() {
}
void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) {
void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
bool debugProcessPacket = _myServer->wantsVerboseDebug();
@ -55,8 +55,6 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA
if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE",debugProcessPacket);
_receivedPacketCount++;
SharedNodePointer senderNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
const unsigned char* packetData = reinterpret_cast<const unsigned char*>(packet.data());
@ -90,7 +88,7 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA
int editDataBytesRead = _myServer->getOctree()->processEditPacketData(packetType,
reinterpret_cast<const unsigned char*>(packet.data()),
packet.size(),
editData, maxSize, senderNode.data());
editData, maxSize, sendingNode);
_myServer->getOctree()->unlock();
quint64 endProcess = usecTimestampNow();
@ -113,9 +111,9 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA
// Make sure our Node and NodeList knows we've heard from this node.
QUuid& nodeUUID = DEFAULT_NODE_ID_REF;
if (senderNode) {
senderNode->setLastHeardMicrostamp(usecTimestampNow());
nodeUUID = senderNode->getUUID();
if (sendingNode) {
sendingNode->setLastHeardMicrostamp(usecTimestampNow());
nodeUUID = sendingNode->getUUID();
if (debugProcessPacket) {
qDebug() << "sender has uuid=" << nodeUUID;
}

View file

@ -63,7 +63,7 @@ public:
NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; }
protected:
virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
private:
void trackInboundPackets(const QUuid& nodeUUID, int sequence, quint64 transitTime,

View file

@ -48,7 +48,7 @@ bool OctreeSendThread::process() {
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
}
packetsSent = packetDistributor(node.data(), nodeData, viewFrustumChanged);
packetsSent = packetDistributor(node, nodeData, viewFrustumChanged);
}
node->getMutex().unlock(); // we're done with this node for now.
@ -86,12 +86,19 @@ quint64 OctreeSendThread::_totalBytes = 0;
quint64 OctreeSendThread::_totalWastedBytes = 0;
quint64 OctreeSendThread::_totalPackets = 0;
int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
bool debug = _myServer->wantsDebugSending();
quint64 now = usecTimestampNow();
bool packetSent = false; // did we send a packet?
int packetsSent = 0;
// double check that the node has an active socket, otherwise, don't send...
const HifiSockAddr* nodeAddress = node->getActiveSocket();
if (!nodeAddress) {
return packetsSent; // without sending...
}
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
// this rate control savings.
@ -135,15 +142,11 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
}
// actually send it
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) statsMessage, statsMessageLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node));
packetSent = true;
} else {
// not enough room in the packet, send two packets
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) statsMessage, statsMessageLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node));
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
// there was nothing else to send.
@ -161,9 +164,8 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
truePacketsSent++;
packetsSent++;
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(),
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(),
SharedNodePointer(node));
packetSent = true;
@ -182,9 +184,8 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
// If there's actually a packet waiting, then send it.
if (nodeData->isPacketWaiting()) {
// just send the voxel packet
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(),
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(),
SharedNodePointer(node));
packetSent = true;
int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength();
@ -211,7 +212,7 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
}
/// Version of voxel distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
bool forceDebugging = false;
int truePacketsSent = 0;

View file

@ -36,8 +36,8 @@ private:
QUuid _nodeUUID;
OctreeServer* _myServer;
int handlePacketSend(Node* node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent);
int packetDistributor(Node* node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
int handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent);
int packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
OctreePacketData _packetData;
};

View file

@ -458,43 +458,43 @@ void OctreeServer::parsePayload() {
}
}
void OctreeServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
void OctreeServer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
NodeList* nodeList = NodeList::getInstance();
PacketType packetType = packetTypeForPacket(dataByteArray);
if (packetType == getMyQueryMessageType()) {
bool debug = false;
if (debug) {
qDebug() << "Got PacketType_VOXEL_QUERY at" << usecTimestampNow();
}
// If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we
// need to make sure we have it in our nodeList.
QUuid nodeUUID;
deconstructPacketHeader(dataByteArray, nodeUUID);
SharedNodePointer node = nodeList->nodeWithUUID(nodeUUID);
if (node) {
nodeList->updateNodeWithData(node.data(), senderSockAddr, dataByteArray);
if (!node->getActiveSocket()) {
// we don't have an active socket for this node, but they're talking to us
// this means they've heard from us and can reply, let's assume public is active
node->activatePublicSocket();
}
OctreeQueryNode* nodeData = (OctreeQueryNode*) node->getLinkedData();
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
nodeData->initializeOctreeSendThread(this, nodeUUID);
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType packetType = packetTypeForPacket(receivedPacket);
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (packetType == getMyQueryMessageType()) {
bool debug = false;
if (debug) {
qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow();
}
// If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we
// need to make sure we have it in our nodeList.
if (matchingNode) {
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData();
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
nodeData->initializeOctreeSendThread(this, matchingNode->getUUID());
}
}
} else if (packetType == PacketTypeJurisdictionRequest) {
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
} else {
// let processNodeData handle it.
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
}
}
} else if (packetType == PacketTypeJurisdictionRequest) {
_jurisdictionSender->queueReceivedPacket(senderSockAddr, dataByteArray);
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
_octreeInboundPacketProcessor->queueReceivedPacket(senderSockAddr, dataByteArray);
} else {
// let processNodeData handle it.
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
@ -655,8 +655,4 @@ void OctreeServer::run() {
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
QTimer* pingNodesTimer = new QTimer(this);
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
}

View file

@ -58,8 +58,8 @@ public:
// subclass may implement these method
virtual void beforeRun() { };
virtual bool hasSpecialPacketToSend(Node* node) { return false; }
virtual int sendSpecialPacket(Node* node) { return 0; }
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; }
virtual int sendSpecialPacket(const SharedNodePointer& node) { return 0; }
static void attachQueryNodeToNode(Node* newNode);
@ -67,7 +67,7 @@ public:
public slots:
/// runs the voxel server assignment
void run();
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
void readPendingDatagrams();
protected:
void parsePayload();

View file

@ -10,9 +10,10 @@
#ifndef __hifi__ParticleNodeData__
#define __hifi__ParticleNodeData__
#include <OctreeQueryNode.h>
#include <PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
class ParticleNodeData : public OctreeQueryNode {
public:
ParticleNodeData() :

View file

@ -43,7 +43,7 @@ void ParticleServer::beforeRun() {
pruneDeletedParticlesTimer->start(PRUNE_DELETED_PARTICLES_INTERVAL_MSECS);
}
void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
void ParticleServer::particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
unsigned char* copyAt = outputBuffer;
@ -63,14 +63,12 @@ void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
copyAt += sizeof(particleID);
packetLength += sizeof(particleID);
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
}
// ParticleServer will use the "special packets" to send list of recently deleted particles
bool ParticleServer::hasSpecialPacketToSend(Node* node) {
bool ParticleServer::hasSpecialPacketToSend(const SharedNodePointer& node) {
bool shouldSendDeletedParticles = false;
// check to see if any new particles have been added since we last sent to this node...
@ -85,7 +83,7 @@ bool ParticleServer::hasSpecialPacketToSend(Node* node) {
return shouldSendDeletedParticles;
}
int ParticleServer::sendSpecialPacket(Node* node) {
int ParticleServer::sendSpecialPacket(const SharedNodePointer& node) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
size_t packetLength = 0;
@ -104,9 +102,7 @@ int ParticleServer::sendSpecialPacket(Node* node) {
//qDebug() << "sending PacketType_PARTICLE_ERASE packetLength:" << packetLength;
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node));
}
nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt);

View file

@ -10,7 +10,7 @@
#ifndef __particle_server__ParticleServer__
#define __particle_server__ParticleServer__
#include <OctreeServer.h>
#include "../octree/OctreeServer.h"
#include "Particle.h"
#include "ParticleServerConsts.h"
@ -34,10 +34,10 @@ public:
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node);
virtual int sendSpecialPacket(const SharedNodePointer& node);
virtual void particleCreated(const Particle& newParticle, Node* senderNode);
virtual void particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode);
public slots:
void pruneDeletedParticles();

View file

@ -3,15 +3,16 @@
// hifi
//
// Created by Stephen Birarda on 3/21/13.
//
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__VoxelNodeData__
#define __hifi__VoxelNodeData__
#include <OctreeQueryNode.h>
#include <PacketHeaders.h>
#include "../octree/OctreeQueryNode.h"
class VoxelNodeData : public OctreeQueryNode {
public:
VoxelNodeData() : OctreeQueryNode() { };

View file

@ -32,12 +32,12 @@ Octree* VoxelServer::createTree() {
return new VoxelTree(true);
}
bool VoxelServer::hasSpecialPacketToSend(Node* node) {
bool VoxelServer::hasSpecialPacketToSend(const SharedNodePointer& node) {
bool shouldSendEnvironments = _sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, OCTREE_SEND_INTERVAL_USECS);
return shouldSendEnvironments;
}
int VoxelServer::sendSpecialPacket(Node* node) {
int VoxelServer::sendSpecialPacket(const SharedNodePointer& node) {
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_tempOutputBuffer), PacketTypeEnvironmentData);
int envPacketLength = numBytesPacketHeader;
int environmentsToSend = getSendMinimalEnvironment() ? 1 : getEnvironmentDataCount();
@ -46,9 +46,7 @@ int VoxelServer::sendSpecialPacket(Node* node) {
envPacketLength += getEnvironmentData(i)->getBroadcastData(_tempOutputBuffer + envPacketLength);
}
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) _tempOutputBuffer, envPacketLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
NodeList::getInstance()->writeDatagram((char*) _tempOutputBuffer, envPacketLength, SharedNodePointer(node));
return envPacketLength;
}

View file

@ -17,8 +17,7 @@
#include <ThreadedAssignment.h>
#include <EnvironmentData.h>
#include <OctreeServer.h>
#include "../octree/OctreeServer.h"
#include "VoxelServerConsts.h"
@ -44,8 +43,8 @@ public:
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node);
virtual int sendSpecialPacket(const SharedNodePointer& node);
private:

View file

@ -136,7 +136,7 @@ void DataServer::readPendingDatagrams() {
}
if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) &&
packetVersionMatch(receivedPacket)) {
receivedPacket[numBytesArithmeticCodingFromBuffer(receivedPacket.data())] == versionForPacketType(requestType)) {
QDataStream packetStream(receivedPacket);
int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket);

View file

@ -8,11 +8,9 @@ var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIME
var currentCells = [];
var nextCells = [];
var METER_LENGTH = 1 / TREE_SCALE;
var METER_LENGTH = 1;
var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
print("TREE_SCALE = " + TREE_SCALE + "\n");
// randomly populate the cell start values
for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
// create the array to hold this row
@ -108,7 +106,7 @@ function sendNextCells() {
// queue a packet to add a voxel for the new cell
var color = (nextCells[i][j] == 1) ? 255 : 1;
Voxels.queueDestructiveVoxelAdd(x, y, 0, cellScale, color, color, color);
Voxels.setVoxel(x, y, 0, cellScale, color, color, color);
}
}
}
@ -128,4 +126,6 @@ function step() {
sendNextCells();
}
Agent.willSendVisualDataCallback.connect(step);
Script.willSendVisualDataCallback.connect(step);
Voxels.setPacketsPerSecond(200);

View file

@ -23,8 +23,8 @@ $(document).ready(function(){
$.each(json.queued, function (uuid, data) {
queuedTableBody += "<tr>";
queuedTableBody += "<td>" + uuid + "</td>";
queuedTableBody += "<td>" + data.type + "</td>";
queuedTableBody += "<td>" + uuid + "</td>";
queuedTableBody += "<td>" + (data.pool ? data.pool : "") + "</td>";
queuedTableBody += "</tr>";
});

View file

@ -21,6 +21,8 @@
#include <SharedUtil.h>
#include <UUID.h>
#include "DomainServerNodeData.h"
#include "DomainServer.h"
const int RESTART_HOLD_TIME_MSECS = 5 * 1000;
@ -57,8 +59,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
@ -209,6 +212,10 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
}
}
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer
<< NodeType::MetavoxelServer;
void DomainServer::readAvailableDatagrams() {
NodeList* nodeList = NodeList::getInstance();
@ -222,14 +229,14 @@ void DomainServer::readAvailableDatagrams() {
QByteArray receivedPacket;
NodeType_t nodeType;
QUuid nodeUUID;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
if (packetVersionMatch(receivedPacket)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType requestType = packetTypeForPacket(receivedPacket);
if (requestType == PacketTypeDomainListRequest) {
@ -237,7 +244,7 @@ void DomainServer::readAvailableDatagrams() {
QDataStream packetStream(receivedPacket);
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
deconstructPacketHeader(receivedPacket, nodeUUID);
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
packetStream >> nodeType;
packetStream >> nodePublicAddress >> nodeLocalAddress;
@ -255,18 +262,20 @@ void DomainServer::readAvailableDatagrams() {
}
}
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer
<< NodeType::MetavoxelServer;
SharedAssignmentPointer matchingStaticAssignment;
// check if this is a non-statically assigned node, a node that is assigned and checking in for the first time
// or a node that has already checked in and is continuing to report for duty
if (!STATICALLY_ASSIGNED_NODES.contains(nodeType)
|| (matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType))
|| nodeList->getInstance()->nodeWithUUID(nodeUUID))
{
|| nodeList->getInstance()->nodeWithUUID(nodeUUID)) {
if (nodeUUID.isNull()) {
// this is a check in from an unidentified node
// we need to generate a session UUID for this node
nodeUUID = QUuid::createUuid();
}
SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID,
nodeType,
nodePublicAddress,
@ -291,16 +300,37 @@ void DomainServer::readAvailableDatagrams() {
NodeType_t* nodeTypesOfInterest = reinterpret_cast<NodeType_t*>(receivedPacket.data()
+ packetStream.device()->pos());
// always send the node their own UUID back
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
broadcastDataStream << checkInNode->getUUID();
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(checkInNode->getLinkedData());
if (numInterestTypes > 0) {
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
// if the node has sent no types of interest, assume they want nothing but their own ID back
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getUUID() != nodeUUID &&
memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) {
// if the node has any interest types, send back those nodes as well
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
if (otherNode->getUUID() != nodeUUID &&
memchr(nodeTypesOfInterest, otherNode->getType(), numInterestTypes)) {
// don't send avatar nodes to other avatars, that will come from avatar mixer
broadcastDataStream << *node.data();
broadcastDataStream << *otherNode.data();
// pack the secret that these two nodes will use to communicate with each other
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
->getSessionSecretHash().insert(nodeUUID, secretUUID);
}
broadcastDataStream << secretUUID;
}
}
}
@ -344,7 +374,7 @@ void DomainServer::readAvailableDatagrams() {
}
}
QJsonObject jsonForSocket(const HifiSockAddr& socket) {
QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) {
QJsonObject socketJSON;
socketJSON["ip"] = socket.getAddress().toString();
@ -358,7 +388,7 @@ const char JSON_KEY_PUBLIC_SOCKET[] = "public";
const char JSON_KEY_LOCAL_SOCKET[] = "local";
const char JSON_KEY_POOL[] = "pool";
QJsonObject jsonObjectForNode(Node* node) {
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
QJsonObject nodeJson;
// re-format the type name so it matches the target name
@ -372,12 +402,13 @@ QJsonObject jsonObjectForNode(Node* node) {
// add the node socket information
nodeJson[JSON_KEY_PUBLIC_SOCKET] = jsonForSocket(node->getPublicSocket());
nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket());
// if the node has pool information, add it
if (node->getLinkedData() && !((Assignment*) node->getLinkedData())->getPool().isEmpty()) {
nodeJson[JSON_KEY_POOL] = ((Assignment*) node->getLinkedData())->getPool();
SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID());
if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
}
return nodeJson;
}
@ -397,10 +428,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
// enumerate the NodeList to find the assigned nodes
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getLinkedData()) {
if (_staticAssignmentHash.value(node->getUUID())) {
// add the node using the UUID as the key
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
assignedNodesJSON[uuidString] = jsonObjectForNode(node.data());
assignedNodesJSON[uuidString] = jsonObjectForNode(node);
}
}
@ -443,7 +474,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
// add the node using the UUID as the key
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
nodesJSON[uuidString] = jsonObjectForNode(node.data());
nodesJSON[uuidString] = jsonObjectForNode(node);
}
rootJSON["nodes"] = nodesJSON;
@ -546,6 +577,11 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer&
_staticAssignmentHash.remove(oldUUID);
}
void DomainServer::nodeAdded(SharedNodePointer node) {
// we don't use updateNodeWithData, so add the DomainServerNodeData to the node here
node->setLinkedData(new DomainServerNodeData());
}
void DomainServer::nodeKilled(SharedNodePointer node) {
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(node->getUUID());
@ -553,6 +589,15 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
if (matchedAssignment) {
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
}
// cleanup the connection secrets that we set up for this node (on the other nodes)
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID);
if (otherNode) {
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
}
}
}
SharedAssignmentPointer DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {

View file

@ -30,7 +30,9 @@ public:
void exit(int retCode = 0);
public slots:
/// Called by NodeList to inform us that a node has been killed.
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);
/// Called by NodeList to inform us a node has been killed
void nodeKilled(SharedNodePointer node);
private:
@ -46,6 +48,9 @@ private:
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
HTTPManager _HTTPManager;
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;

View file

@ -0,0 +1,26 @@
//
// DomainServerNodeData.h
// hifi
//
// Created by Stephen Birarda on 2/6/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DomainServerNodeData__
#define __hifi__DomainServerNodeData__
#include <QtCore/QHash>
#include <NodeData.h>
class DomainServerNodeData : public NodeData {
public:
DomainServerNodeData() : _sessionSecretHash() {};
int parseData(const QByteArray& packet) { return 0; }
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
private:
QHash<QUuid, QUuid> _sessionSecretHash;
};
#endif /* defined(__hifi__DomainServerNodeData__) */

135
examples/cameraExample.js Normal file
View file

@ -0,0 +1,135 @@
//
// cameraExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/6/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates use of the Camera class
//
//
var damping = 0.9;
var yaw = 0.0;
var pitch = 0.0;
var roll = 0.0;
var thrust = { x: 0, y: 0, z: 0 };
var velocity = { x: 0, y: 0, z: 0 };
var position = { x: MyAvatar.position.x, y: MyAvatar.position.y + 1, z: MyAvatar.position.z };
var joysticksCaptured = false;
var THRUST_CONTROLLER = 0;
var VIEW_CONTROLLER = 1;
function checkCamera() {
if (Camera.getMode() == "independent") {
var deltaTime = 1/60; // approximately our FPS - maybe better to be elapsed time since last call
var THRUST_MAG_UP = 800.0;
var THRUST_MAG_DOWN = 300.0;
var THRUST_MAG_FWD = 500.0;
var THRUST_MAG_BACK = 300.0;
var THRUST_MAG_LATERAL = 250.0;
var THRUST_JUMP = 120.0;
var scale = 1.0;
var thrustMultiplier = 1.0; // maybe increase this as you hold it down?
var YAW_MAG = 500.0;
var PITCH_MAG = 100.0;
var THRUST_MAG_HAND_JETS = THRUST_MAG_FWD;
var JOYSTICK_YAW_MAG = YAW_MAG;
var JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5;
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
var currentOrientation = Camera.getOrientation();
var front = Quat.getFront(currentOrientation);
var right = Quat.getRight(currentOrientation);
var up = Quat.getUp(currentOrientation);
var thrustFront = Vec3.multiply(front, scale * THRUST_MAG_HAND_JETS * thrustJoystickPosition.y * thrustMultiplier * deltaTime);
var thrustRight = Vec3.multiply(right, scale * THRUST_MAG_HAND_JETS * thrustJoystickPosition.x * thrustMultiplier * deltaTime);
thrust = Vec3.sum(thrust, thrustFront);
thrust = Vec3.sum(thrust, thrustRight);
// add thrust to velocity
velocity = Vec3.sum(velocity, Vec3.multiply(thrust, deltaTime));
// add velocity to position
position = Vec3.sum(position, Vec3.multiply(velocity, deltaTime));
Camera.setPosition(position);
// reset thrust
thrust = { x: 0, y: 0, z: 0 };
// damp velocity
velocity = Vec3.multiply(velocity, damping);
// View Controller
var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER);
yaw -= viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime;
pitch += viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime;
var orientation = Quat.fromPitchYawRoll(pitch, yaw, roll);
Camera.setOrientation(orientation);
}
}
Script.willSendVisualDataCallback.connect(checkCamera);
function mouseMoveEvent(event) {
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
var pickRay = Camera.computePickRay(event.x, event.y);
print("called Camera.computePickRay()");
print("computePickRay origin=" + pickRay.origin.x + ", " + pickRay.origin.y + ", " + pickRay.origin.z);
print("computePickRay direction=" + pickRay.direction.x + ", " + pickRay.direction.y + ", " + pickRay.direction.z);
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
function keyPressEvent(event) {
if (joysticksCaptured) {
Controller.releaseJoystick(THRUST_CONTROLLER);
Controller.releaseJoystick(VIEW_CONTROLLER);
joysticksCaptured = false;
}
if (event.text == "1") {
Camera.setMode("first person");
}
if (event.text == "2") {
Camera.setMode("mirror");
}
if (event.text == "3") {
Camera.setMode("third person");
}
if (event.text == "4") {
Camera.setMode("independent");
joysticksCaptured = true;
Controller.captureJoystick(THRUST_CONTROLLER);
Controller.captureJoystick(VIEW_CONTROLLER);
position = { x: MyAvatar.position.x, y: MyAvatar.position.y + 1, z: MyAvatar.position.z };
}
}
// Map keyPress and mouse move events to our callbacks
Controller.keyPressEvent.connect(keyPressEvent);
Controller.captureKeyEvents({ text: "1" });
Controller.captureKeyEvents({ text: "2" });
Controller.captureKeyEvents({ text: "3" });
Controller.captureKeyEvents({ text: "4" });
function scriptEnding() {
// re-enabled the standard application for touch events
Controller.releaseKeyEvents({ text: "1" });
Controller.releaseKeyEvents({ text: "2" });
Controller.releaseKeyEvents({ text: "3" });
Controller.releaseKeyEvents({ text: "4" });
Controller.releaseJoystick(THRUST_CONTROLLER);
Controller.releaseJoystick(VIEW_CONTROLLER);
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -275,6 +275,9 @@ function cleanupGame() {
if (missileFired) {
Particles.deleteParticle(myMissile);
}
Controller.releaseKeyEvents({text: " "});
Script.stop();
}
Script.scriptEnding.connect(cleanupGame);

View file

@ -1,171 +0,0 @@
//
// sphere.js
// interface
//
// Created by Andrzej Kapolka on 12/17/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
function strictIndexOf(array, element) {
for (var i = 0; i < array.length; i++) {
if (array[i] == element) {
return i;
}
}
return -1;
}
var colorIndex;
var normalIndex;
var visitor;
var info;
var MAX_DEPTH = 4;
var sphereCenter = [ 0.5, 0.5, 0.5 ];
var sphereColor = 0xFFFF00FF;
var sphereRadius = 0.25;
var sphereRadiusSquared = sphereRadius * sphereRadius;
function lengthSquared(x, y, z) {
return x*x + y*y + z*z;
}
function setNormal(vector) {
if (normalIndex != -1) {
var length = Math.sqrt(lengthSquared(vector[0], vector[1], vector[2]));
if (length == 0.0) {
info.inputValues[normalIndex] = 0x007F00;
} else {
var scale = 127.0 / length;
info.inputValues[normalIndex] =
(Math.floor(vector[0] * scale) & 0xFF) << 16 |
(Math.floor(vector[1] * scale) & 0xFF) << 8 |
Math.floor(vector[2] * scale) & 0xFF;
}
}
}
function guide(minimum, size, depth) {
info.minimum = minimum;
info.size = size;
// start with a relative fast bounding volume test to find most non-intersecting states
var maximum = [ minimum[0] + size, minimum[1] + size, minimum[2] + size ];
if (minimum[0] >= sphereCenter[0] + sphereRadius ||
minimum[1] >= sphereCenter[1] + sphereRadius ||
minimum[2] >= sphereCenter[2] + sphereRadius ||
maximum[0] <= sphereCenter[0] - sphereRadius ||
maximum[1] <= sphereCenter[1] - sphereRadius ||
maximum[2] <= sphereCenter[2] - sphereRadius) {
info.isLeaf = true;
if (colorIndex != -1) {
info.inputValues[colorIndex] = 0x0;
}
visitor.visit(info);
return;
}
var halfSize = size / 2;
var center = [ minimum[0] + halfSize, minimum[1] + halfSize, minimum[2] + halfSize ];
var vector = [ center[0] - sphereCenter[0], center[1] - sphereCenter[1], center[2] - sphereCenter[2] ];
// count the number of points inside the sphere
var inside = 0;
if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <=
sphereRadiusSquared) {
inside++;
}
if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <=
sphereRadiusSquared) {
inside++;
}
// see if all points are in the sphere
if (inside == 8) {
info.isLeaf = true;
if (colorIndex != -1) {
info.inputValues[colorIndex] = sphereColor;
}
setNormal(vector);
visitor.visit(info);
return;
}
// if we've reached max depth, compute alpha using a volume estimate
if (depth == MAX_DEPTH) {
info.isLeaf = true;
if (inside >= 3) {
if (colorIndex != -1) {
info.inputValues[colorIndex] = sphereColor;
}
setNormal(vector);
} else {
if (colorIndex != -1) {
info.inputValues[colorIndex] = 0x0;
}
}
visitor.visit(info);
return;
}
// recurse
info.isLeaf = false;
if (!visitor.visit(info)) {
return;
}
depth += 1;
guide(minimum, halfSize, depth);
guide([ center[0], minimum[1], minimum[2] ], halfSize, depth);
guide([ minimum[0], center[1], minimum[2] ], halfSize, depth);
guide([ center[0], center[1], minimum[2] ], halfSize, depth);
guide([ minimum[0], minimum[1], center[2] ], halfSize, depth);
guide([ center[0], minimum[1], center[2] ], halfSize, depth);
guide([ minimum[0], center[1], center[2] ], halfSize, depth);
guide([ center[0], center[1], center[2] ], halfSize, depth);
}
(function(visitation) {
var inputs = visitation.visitor.getInputs();
colorIndex = strictIndexOf(inputs, AttributeRegistry.colorAttribute);
normalIndex = strictIndexOf(inputs, AttributeRegistry.normalAttribute);
visitor = visitation.visitor;
info = { inputValues: new Array(inputs.length) };
// have the sphere orbit the center and pulse in size
var time = new Date().getTime();
var ROTATE_PERIOD = 400.0;
sphereCenter[0] = 0.5 + 0.25 * Math.cos(time / ROTATE_PERIOD);
sphereCenter[2] = 0.5 + 0.25 * Math.sin(time / ROTATE_PERIOD);
var PULSE_PERIOD = 300.0;
sphereRadius = 0.25 + 0.0625 * Math.cos(time / PULSE_PERIOD);
sphereRadiusSquared = sphereRadius * sphereRadius;
guide(visitation.info.minimum, visitation.info.size, 0);
})

View file

@ -154,8 +154,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_resetRecentMaxPacketsSoon(true),
_swatch(NULL),
_pasteMode(false),
_logger(new FileLogger(this)),
_persistThread(NULL)
_logger(new FileLogger(this))
{
_myAvatar = _avatarManager.getMyAvatar();
@ -201,10 +200,12 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
audioThread->start();
connect(nodeList, SIGNAL(domainChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);
connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle);
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat);
@ -244,6 +245,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->moveToThread(_nodeThread);
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
// send the identity packet for our avatar each second to our avatar mixer
QTimer* identityPacketTimer = new QTimer();
connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);
identityPacketTimer->start(1000);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
@ -316,12 +322,7 @@ Application::~Application() {
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
_particleEditSender.terminate();
if (_persistThread) {
_persistThread->terminate();
_persistThread->deleteLater();
_persistThread = NULL;
}
storeSizeAndPosition();
saveScripts();
_sharedVoxelSystem.changeTree(new VoxelTree);
@ -440,9 +441,9 @@ void Application::paintGL() {
glEnable(GL_LINE_SMOOTH);
if (OculusManager::isConnected()) {
_myCamera.setUpShift (0.0f);
_myCamera.setDistance (0.0f);
_myCamera.setTightness (0.0f); // Camera is directly connected to head without smoothing
_myCamera.setUpShift(0.0f);
_myCamera.setDistance(0.0f);
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead().getOrientation());
@ -452,7 +453,7 @@ void Application::paintGL() {
_myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setTightness (0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
_myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation());
@ -1262,7 +1263,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
pasteVoxels();
}
if (MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) &&
MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) {
_hoverVoxelOriginalColor[0] = _hoverVoxel.red;
_hoverVoxelOriginalColor[1] = _hoverVoxel.green;
_hoverVoxelOriginalColor[2] = _hoverVoxel.blue;
@ -1857,12 +1859,6 @@ void Application::init() {
}
qDebug("Loaded settings");
if (!_profile.getUsername().isEmpty()) {
// we have a username for this avatar, ask the data-server for the mesh URL for this avatar
DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, _profile.getUserString(), &_profile);
DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, _profile.getUserString(), &_profile);
}
// Set up VoxelSystem after loading preferences so we can get the desired max voxel count
_voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels());
_voxels.setUseVoxelShader(Menu::getInstance()->isOptionChecked(MenuOption::UseVoxelShader));
@ -1907,9 +1903,6 @@ void Application::init() {
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
updateLocalOctreeCache(true);
}
void Application::closeMirrorView() {
@ -1971,8 +1964,13 @@ void Application::updateMouseRay() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMouseRay()");
_viewFrustum.computePickRay(_mouseX / (float)_glWidget->width(), _mouseY / (float)_glWidget->height(),
_mouseRayOrigin, _mouseRayDirection);
// if the mouse pointer isn't visible, act like it's at the center of the screen
float x = 0.5f, y = 0.5f;
if (!_mouseHidden) {
x = _mouseX / (float)_glWidget->width();
y = _mouseY / (float)_glWidget->height();
}
_viewFrustum.computePickRay(x, y, _mouseRayOrigin, _mouseRayDirection);
// adjust for mirroring
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
@ -2009,18 +2007,20 @@ void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
const float FAR_AWAY_STARE = TREE_SCALE;
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
lookAtSpot = _myCamera.getPosition();
} else if (_mouseHidden) {
// if the mouse cursor is hidden, just look straight ahead
glm::vec3 rayOrigin, rayDirection;
_viewFrustum.computePickRay(0.5f, 0.5f, rayOrigin, rayDirection);
lookAtSpot = rayOrigin + rayDirection * FAR_AWAY_STARE;
} else {
// just look in direction of the mouse ray
lookAtSpot = _mouseRayOrigin + _mouseRayDirection * FAR_AWAY_STARE;
// look in direction of the mouse ray, but use distance from intersection, if any
float distance = TREE_SCALE;
if (_myAvatar->getLookAtTargetAvatar()) {
distance = glm::distance(_mouseRayOrigin,
static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead().calculateAverageEyePosition());
} else if (_isHoverVoxel) {
distance = glm::distance(_mouseRayOrigin, getMouseVoxelWorldCoordinates(_hoverVoxel));
}
lookAtSpot = _mouseRayOrigin + _mouseRayDirection * distance;
}
if (_faceshift.isActive()) {
// deflect using Faceshift gaze data
@ -2062,7 +2062,7 @@ void Application::updateHoverVoxels(float deltaTime, float& distance, BoxFace& f
glm::vec4 oldVoxel(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s);
// only do this work if MAKE_SOUND_ON_VOXEL_HOVER or MAKE_SOUND_ON_VOXEL_CLICK is enabled,
// and make sure the tree is not already busy... because otherwise you'll have to wait.
if (!(_voxels.treeIsBusy() || _mousePressed)) {
if (!_mousePressed) {
{
PerformanceWarning warn(showWarnings, "Application::updateHoverVoxels() _voxels.findRayIntersection()");
_isHoverVoxel = _voxels.findRayIntersection(_mouseRayOrigin, _mouseRayDirection, _hoverVoxel, distance, face);
@ -2207,9 +2207,6 @@ void Application::updateThreads(float deltaTime) {
_voxelHideShowThread.threadRoutine();
_voxelEditSender.threadRoutine();
_particleEditSender.threadRoutine();
if (_persistThread) {
_persistThread->threadRoutine();
}
}
}
@ -2231,28 +2228,30 @@ void Application::updateMetavoxels(float deltaTime) {
}
}
void Application::cameraMenuChanged() {
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
_myCamera.setMode(CAMERA_MODE_MIRROR);
_myCamera.setModeShiftRate(100.0f);
}
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftRate(1.0f);
}
} else {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
_myCamera.setModeShiftRate(1.0f);
}
}
}
void Application::updateCamera(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
if (!OculusManager::isConnected() && !TV3DManager::isConnected()) {
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
_myCamera.setMode(CAMERA_MODE_MIRROR);
_myCamera.setModeShiftRate(100.0f);
}
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftRate(1.0f);
}
} else {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
_myCamera.setModeShiftRate(1.0f);
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
float xSign = _myCamera.getMode() == CAMERA_MODE_MIRROR ? 1.0f : -1.0f;
if (_faceshift.isActive()) {
@ -2553,10 +2552,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
int packetLength = endOfVoxelQueryPacket - voxelQueryPacket;
// make sure we still have an active socket
if (node->getActiveSocket()) {
nodeList->getNodeSocket().writeDatagram((char*) voxelQueryPacket, packetLength,
node->getActiveSocket()->getAddress(), node->getActiveSocket()->getPort());
}
nodeList->writeDatagram(reinterpret_cast<const char*>(voxelQueryPacket), packetLength, node);
// Feed number of bytes to corresponding channel of the bandwidth meter
_bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(packetLength);
@ -2884,7 +2880,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
}
}
_avatarManager.renderAvatars(whichCamera.getMode() == CAMERA_MODE_MIRROR, selfAvatarOnly);
bool renderMyHead = (whichCamera.getInterpolatedMode() != CAMERA_MODE_FIRST_PERSON);
_avatarManager.renderAvatars(renderMyHead, selfAvatarOnly);
if (!selfAvatarOnly) {
// Render the world box
@ -3859,7 +3856,7 @@ void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance();
QString title = QString() + _profile.getUsername() + " " + nodeList->getOwnerUUID().toString()
QString title = QString() + _profile.getUsername() + " " + nodeList->getSessionUUID().toString()
+ " @ " + nodeList->getDomainHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
@ -3882,10 +3879,13 @@ void Application::domainChanged(const QString& domainHostname) {
// reset the particle renderer
_particles.clear();
}
// reset our persist thread
qDebug() << "Domain changed to" << domainHostname << ". Swapping persist cache.";
updateLocalOctreeCache();
void Application::nodeAdded(SharedNodePointer node) {
if (node->getType() == NodeType::AvatarMixer) {
// new avatar mixer, send off our identity packet right away
_myAvatar->sendIdentityPacket();
}
}
void Application::nodeKilled(SharedNodePointer node) {
@ -3957,27 +3957,25 @@ void Application::nodeKilled(SharedNodePointer node) {
}
}
void Application::trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket) {
void Application::trackIncomingVoxelPacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (serverNode) {
QUuid nodeUUID = serverNode->getUUID();
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node...
_voxelSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
VoxelSceneStats& stats = _octreeServerSceneStats[nodeUUID];
stats.trackIncomingOctreePacket(packet, wasStatsPacket, serverNode->getClockSkewUsec());
stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec());
}
_voxelSceneStatsLock.unlock();
}
}
int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePointer& sendingNode) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
SharedNodePointer server = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
// parse the incoming stats datas stick it in a temporary object for now, while we
// determine which server it belongs to
@ -3985,8 +3983,8 @@ int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr&
int statsMessageLength = temp.unpackFromMessage(reinterpret_cast<const unsigned char*>(packet.data()), packet.size());
// quick fix for crash... why would voxelServer be NULL?
if (server) {
QUuid nodeUUID = server->getUUID();
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node...
_voxelSceneStatsLock.lockForWrite();
@ -4003,7 +4001,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr&
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
if (server->getType() == NodeType::VoxelServer) {
if (sendingNode->getType() == NodeType::VoxelServer) {
jurisdiction = &_voxelServerJurisdictions;
} else {
jurisdiction = &_particleServerJurisdictions;
@ -4109,6 +4107,10 @@ void Application::loadScript(const QString& fileNameString) {
// hook our avatar object into this script engine
scriptEngine->setAvatarData( static_cast<Avatar*>(_myAvatar), "MyAvatar");
CameraScriptableObject* cameraScriptable = new CameraScriptableObject(&_myCamera, &_viewFrustum);
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
QThread* workerThread = new QThread(this);
// when the worker thread is started, call our engine's run..
@ -4155,49 +4157,6 @@ void Application::initAvatarAndViewFrustum() {
updateMyAvatar(0.f);
}
QString Application::getLocalVoxelCacheFileName() {
QString fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QDir logDir(fileName);
if (!logDir.exists(fileName)) {
logDir.mkdir(fileName);
}
fileName.append(QString("/hifi.voxelscache."));
fileName.append(_profile.getLastDomain());
fileName.append(QString(".svo"));
return fileName;
}
void Application::updateLocalOctreeCache(bool firstTime) {
// only do this if we've already got a persistThread or we're told this is the first time
if (firstTime || _persistThread) {
if (_persistThread) {
_persistThread->terminate();
_persistThread->deleteLater();
_persistThread = NULL;
}
QString localVoxelCacheFileName = getLocalVoxelCacheFileName();
const int LOCAL_CACHE_PERSIST_INTERVAL = 1000 * 10; // every 10 seconds
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableLocalVoxelCache)) {
_persistThread = new OctreePersistThread(_voxels.getTree(),
localVoxelCacheFileName.toLocal8Bit().constData(),LOCAL_CACHE_PERSIST_INTERVAL);
qDebug() << "updateLocalOctreeCache()... localVoxelCacheFileName=" << localVoxelCacheFileName;
}
if (_persistThread) {
_voxels.beginLoadingLocalVoxelCache(); // while local voxels are importing, don't do individual node VBO updates
connect(_persistThread, SIGNAL(loadCompleted()), &_voxels, SLOT(localVoxelCacheLoaded()));
_persistThread->initialize(true);
}
}
}
void Application::checkVersion() {
QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL)));
latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);

View file

@ -126,8 +126,6 @@ public:
void touchEndEvent(QTouchEvent* event);
void touchUpdateEvent(QTouchEvent* event);
void updateWindowTitle();
void wheelEvent(QWheelEvent* event);
void makeVoxel(glm::vec3 position,
@ -170,6 +168,7 @@ public:
GeometryCache* getGeometryCache() { return &_geometryCache; }
TextureCache* getTextureCache() { return &_textureCache; }
GlowEffect* getGlowEffect() { return &_glowEffect; }
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
AvatarManager& getAvatarManager() { return _avatarManager; }
Profile* getProfile() { return &_profile; }
@ -213,6 +212,8 @@ signals:
public slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
void nodeAdded(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
@ -238,6 +239,7 @@ private slots:
void setFullscreen(bool fullscreen);
void setEnable3DTVMode(bool enable3DTVMode);
void cameraMenuChanged();
void renderThrustAtVoxel(const glm::vec3& thrust);
@ -470,8 +472,8 @@ private:
PieMenu _pieMenu;
int parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderAddress);
void trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
int parseOctreeStats(const QByteArray& packet, const SharedNodePointer& sendingNode);
void trackIncomingVoxelPacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
@ -484,11 +486,6 @@ private:
FileLogger* _logger;
OctreePersistThread* _persistThread;
QString getLocalVoxelCacheFileName();
void updateLocalOctreeCache(bool firstTime = false);
void checkVersion();
void displayUpdateDialog();
bool shouldSkipVersion(QString latestVersion);

View file

@ -56,6 +56,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
_numOutputCallbackBytes(0),
_loopbackAudioOutput(NULL),
_loopbackOutputDevice(NULL),
_proceduralAudioOutput(NULL),
_proceduralOutputDevice(NULL),
_inputRingBuffer(0),
_ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL),
_scope(scope),
@ -75,7 +77,7 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
_muted(false)
{
// clear the array of locally injected samples
memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
}
void Audio::init(QGLWidget *parent) {
@ -272,6 +274,9 @@ void Audio::start() {
// setup a loopback audio output device
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
// setup a procedural audio output device
_proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
gettimeofday(&_lastReceiveTime, NULL);
}
@ -332,7 +337,7 @@ void Audio::handleAudioInput() {
memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
// zero out the locally injected audio in preparation for audio procedural sounds
memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
if (!_muted) {
// we aren't muted, downsample the input audio
@ -363,11 +368,27 @@ void Audio::handleAudioInput() {
// add procedural effects to the appropriate input samples
addProceduralSounds(monoAudioSamples,
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
if (!_proceduralOutputDevice) {
_proceduralOutputDevice = _proceduralAudioOutput->start();
}
// send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice
QByteArray proceduralOutput;
proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4 * sizeof(int16_t));
linearResampling(_localProceduralSamples,
reinterpret_cast<int16_t*>(proceduralOutput.data()),
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4,
_desiredInputFormat, _outputFormat);
_proceduralOutputDevice->write(proceduralOutput);
NodeList* nodeList = NodeList::getInstance();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) {
if (audioMixer && audioMixer->getActiveSocket()) {
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
glm::vec3 headPosition = interfaceAvatar->getHead().getPosition();
glm::quat headOrientation = interfaceAvatar->getHead().getOrientation();
@ -388,15 +409,14 @@ void Audio::handleAudioInput() {
memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation));
currentPacketPtr += sizeof(headOrientation);
nodeList->getNodeSocket().writeDatagram(monoAudioDataPacket,
NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes,
audioMixer->getActiveSocket()->getAddress(),
audioMixer->getActiveSocket()->getPort());
nodeList->writeDatagram(monoAudioDataPacket,
NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes,
audioMixer);
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
.updateValue(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes);
}
delete[] inputAudioSamples;
delete[] inputAudioSamples;
}
}
@ -431,12 +451,6 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
static int numRequiredOutputSamples = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / networkOutputToOutputRatio;
QByteArray outputBuffer;
outputBuffer.resize(numRequiredOutputSamples * sizeof(int16_t));
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
// we don't have any audio data left in the output buffer
@ -448,6 +462,14 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
// if there is anything in the ring buffer, decide what to do
if (_ringBuffer.samplesAvailable() > 0) {
int numNetworkOutputSamples = _ringBuffer.samplesAvailable();
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
QByteArray outputBuffer;
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
+ (_jitterBufferSamples * 2))) {
// starved and we don't have enough to start, keep waiting
@ -458,64 +480,28 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
// copy the samples we'll resample from the ring buffer - this also
// pushes the read pointer of the ring buffer forwards
int16_t ringBufferSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO];
_ringBuffer.readSamples(ringBufferSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
// add the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL from each QByteArray
// in our _localInjectionByteArrays QVector to the _localInjectedSamples
// add to the output samples whatever is in the _localAudioOutput byte array
// that lets this user hear sound effects and loopback (if enabled)
for (int b = 0; b < _localInjectionByteArrays.size(); b++) {
QByteArray audioByteArray = _localInjectionByteArrays.at(b);
int16_t* byteArraySamples = (int16_t*) audioByteArray.data();
int samplesToRead = qMin((int)(audioByteArray.size() / sizeof(int16_t)),
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
for (int i = 0; i < samplesToRead; i++) {
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + byteArraySamples[i],
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
}
if (samplesToRead < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
// there isn't anything left to inject from this byte array, remove it from the vector
_localInjectionByteArrays.remove(b);
} else {
// pull out the bytes we just read for outputs
audioByteArray.remove(0, samplesToRead * sizeof(int16_t));
// still data left to read - replace the byte array in the QVector with the smaller one
_localInjectionByteArrays.replace(b, audioByteArray);
}
}
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
ringBufferSamples[i * 2] = glm::clamp(ringBufferSamples[i * 2] + _localInjectedSamples[i],
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
ringBufferSamples[(i * 2) + 1] = glm::clamp(ringBufferSamples[(i * 2) + 1] + _localInjectedSamples[i],
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
}
int16_t* ringBufferSamples= new int16_t[numNetworkOutputSamples];
_ringBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples);
// add the next numNetworkOutputSamples from each QByteArray
// in our _localInjectionByteArrays QVector to the localInjectedSamples
// copy the packet from the RB to the output
linearResampling(ringBufferSamples,
(int16_t*) outputBuffer.data(),
NETWORK_BUFFER_LENGTH_SAMPLES_STEREO,
numRequiredOutputSamples,
numNetworkOutputSamples,
numDeviceOutputSamples,
_desiredOutputFormat, _outputFormat);
if (_outputDevice) {
_outputDevice->write(outputBuffer);
// add output (@speakers) data just written to the scope
QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection,
Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples,
NETWORK_BUFFER_LENGTH_BYTES_STEREO)),
Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples, numNetworkOutputSamples)),
Q_ARG(bool, true), Q_ARG(bool, false));
}
delete[] ringBufferSamples;
}
}
@ -672,7 +658,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
int16_t collisionSample = (int16_t) sample;
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample,
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
_collisionSoundMagnitude *= _collisionSoundDuration;
@ -696,7 +682,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
int16_t collisionSample = (int16_t) sample;
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample,
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
_drumSoundVolume *= (1.f - _drumSoundDecay);
@ -727,8 +713,8 @@ void Audio::startDrumSound(float volume, float frequency, float duration, float
}
void Audio::handleAudioByteArray(const QByteArray& audioByteArray) {
// add this byte array to our QVector
_localInjectionByteArrays.append(audioByteArray);
// TODO: either create a new audio device (up to the limit of the sound card or a hard limit)
// or send to the mixer and use delayed loopback
}
void Audio::renderToolIcon(int screenHeight) {

View file

@ -86,8 +86,7 @@ private:
QAudioFormat _inputFormat;
QIODevice* _inputDevice;
int _numInputCallbackBytes;
int16_t _localInjectedSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL];
QVector<QByteArray> _localInjectionByteArrays;
int16_t _localProceduralSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL];
QAudioOutput* _audioOutput;
QAudioFormat _desiredOutputFormat;
QAudioFormat _outputFormat;
@ -95,6 +94,8 @@ private:
int _numOutputCallbackBytes;
QAudioOutput* _loopbackAudioOutput;
QIODevice* _loopbackOutputDevice;
QAudioOutput* _proceduralAudioOutput;
QIODevice* _proceduralOutputDevice;
AudioRingBuffer _inputRingBuffer;
AudioRingBuffer _ringBuffer;

View file

@ -8,8 +8,11 @@
#include <SharedUtil.h>
#include <VoxelConstants.h>
#include <EventTypes.h>
#include "Application.h"
#include "Camera.h"
#include "Menu.h"
#include "Util.h"
const float CAMERA_MINIMUM_MODE_SHIFT_RATE = 0.5f;
@ -18,6 +21,10 @@ const float CAMERA_FIRST_PERSON_MODE_UP_SHIFT = 0.0f;
const float CAMERA_FIRST_PERSON_MODE_DISTANCE = 0.0f;
const float CAMERA_FIRST_PERSON_MODE_TIGHTNESS = 100.0f;
const float CAMERA_INDEPENDENT_MODE_UP_SHIFT = 0.0f;
const float CAMERA_INDEPENDENT_MODE_DISTANCE = 0.0f;
const float CAMERA_INDEPENDENT_MODE_TIGHTNESS = 100.0f;
const float CAMERA_THIRD_PERSON_MODE_UP_SHIFT = -0.2f;
const float CAMERA_THIRD_PERSON_MODE_DISTANCE = 1.5f;
const float CAMERA_THIRD_PERSON_MODE_TIGHTNESS = 8.0f;
@ -27,32 +34,32 @@ const float CAMERA_MIRROR_MODE_DISTANCE = 0.17f;
const float CAMERA_MIRROR_MODE_TIGHTNESS = 100.0f;
Camera::Camera() {
_needsToInitialize = true;
_frustumNeedsReshape = true;
_modeShift = 1.0f;
_modeShiftRate = 1.0f;
_linearModeShift = 0.0f;
_mode = CAMERA_MODE_THIRD_PERSON;
_tightness = 10.0f; // default
_fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
_aspectRatio = 16.f/9.f;
_nearClip = 0.08f; // default
_farClip = 50.0f * TREE_SCALE; // default
_upShift = 0.0f;
_distance = 0.0f;
_previousUpShift = 0.0f;
_previousDistance = 0.0f;
_previousTightness = 0.0f;
_newUpShift = 0.0f;
_newDistance = 0.0f;
_newTightness = 0.0f;
_targetPosition = glm::vec3(0.0f, 0.0f, 0.0f);
_position = glm::vec3(0.0f, 0.0f, 0.0f);
_idealPosition = glm::vec3(0.0f, 0.0f, 0.0f);
_scale = 1.0f;
Camera::Camera() :
_needsToInitialize(true),
_mode(CAMERA_MODE_THIRD_PERSON),
_prevMode(CAMERA_MODE_THIRD_PERSON),
_frustumNeedsReshape(true),
_position(0.0f, 0.0f, 0.0f),
_idealPosition(0.0f, 0.0f, 0.0f),
_targetPosition(0.0f, 0.0f, 0.0f),
_fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES),
_aspectRatio(16.f/9.f),
_nearClip(0.08f), // default
_farClip(50.0f * TREE_SCALE), // default
_upShift(0.0f),
_distance(0.0f),
_tightness(10.0f), // default
_previousUpShift(0.0f),
_previousDistance(0.0f),
_previousTightness(0.0f),
_newUpShift(0.0f),
_newDistance(0.0f),
_newTightness(0.0f),
_modeShift(1.0f),
_linearModeShift(0.0f),
_modeShiftRate(1.0f),
_scale(1.0f)
{
}
void Camera::update(float deltaTime) {
@ -65,12 +72,9 @@ void Camera::update(float deltaTime) {
// use iterative forces to keep the camera at the desired position and angle
void Camera::updateFollowMode(float deltaTime) {
if (_linearModeShift < 1.0f) {
_linearModeShift += _modeShiftRate * deltaTime;
_modeShift = ONE_HALF - ONE_HALF * cosf(_linearModeShift * PIE );
_upShift = _previousUpShift * (1.0f - _modeShift) + _newUpShift * _modeShift;
_distance = _previousDistance * (1.0f - _modeShift) + _newDistance * _modeShift;
_tightness = _previousTightness * (1.0f - _modeShift) + _newTightness * _modeShift;
@ -97,7 +101,6 @@ void Camera::updateFollowMode(float deltaTime) {
_idealPosition = _targetPosition + _scale * (_rotation * glm::vec3(0.0f, _upShift, _distance));
_position = _idealPosition;
_needsToInitialize = false;
} else {
// pull rotation towards ideal
_rotation = safeMix(_rotation, _targetRotation, t);
@ -123,6 +126,7 @@ void Camera::setModeShiftRate ( float rate ) {
void Camera::setMode(CameraMode m) {
_prevMode = _mode;
_mode = m;
_modeShift = 0.0;
_linearModeShift = 0.0;
@ -135,16 +139,19 @@ void Camera::setMode(CameraMode m) {
_newUpShift = CAMERA_THIRD_PERSON_MODE_UP_SHIFT;
_newDistance = CAMERA_THIRD_PERSON_MODE_DISTANCE;
_newTightness = CAMERA_THIRD_PERSON_MODE_TIGHTNESS;
} else if (_mode == CAMERA_MODE_FIRST_PERSON) {
_newUpShift = CAMERA_FIRST_PERSON_MODE_UP_SHIFT;
_newDistance = CAMERA_FIRST_PERSON_MODE_DISTANCE;
_newTightness = CAMERA_FIRST_PERSON_MODE_TIGHTNESS;
} else if (_mode == CAMERA_MODE_MIRROR) {
_newUpShift = CAMERA_MIRROR_MODE_UP_SHIFT;
_newDistance = CAMERA_MIRROR_MODE_DISTANCE;
_newTightness = CAMERA_MIRROR_MODE_TIGHTNESS;
} else if (_mode == CAMERA_MODE_INDEPENDENT) {
_newUpShift = CAMERA_INDEPENDENT_MODE_UP_SHIFT;
_newDistance = CAMERA_INDEPENDENT_MODE_DISTANCE;
_newTightness = CAMERA_INDEPENDENT_MODE_TIGHTNESS;
}
}
@ -163,22 +170,22 @@ void Camera::setAspectRatio(float a) {
_frustumNeedsReshape = true;
}
void Camera::setNearClip (float n) {
void Camera::setNearClip(float n) {
_nearClip = n;
_frustumNeedsReshape = true;
}
void Camera::setFarClip (float f) {
void Camera::setFarClip(float f) {
_farClip = f;
_frustumNeedsReshape = true;
}
void Camera::setEyeOffsetPosition (const glm::vec3& p) {
void Camera::setEyeOffsetPosition(const glm::vec3& p) {
_eyeOffsetPosition = p;
_frustumNeedsReshape = true;
}
void Camera::setEyeOffsetOrientation (const glm::quat& o) {
void Camera::setEyeOffsetOrientation(const glm::quat& o) {
_eyeOffsetOrientation = o;
_frustumNeedsReshape = true;
}
@ -199,10 +206,82 @@ bool Camera::getFrustumNeedsReshape() const {
return _frustumNeedsReshape;
}
// call this when deciding whether to render the head or not
CameraMode Camera::getInterpolatedMode() const {
const float SHIFT_THRESHOLD_INTO_FIRST_PERSON = 0.7f;
const float SHIFT_THRESHOLD_OUT_OF_FIRST_PERSON = 0.6f;
if ((_mode == CAMERA_MODE_FIRST_PERSON && _linearModeShift < SHIFT_THRESHOLD_INTO_FIRST_PERSON) ||
(_prevMode == CAMERA_MODE_FIRST_PERSON && _linearModeShift < SHIFT_THRESHOLD_OUT_OF_FIRST_PERSON)) {
return _prevMode;
}
return _mode;
}
// call this after reshaping the view frustum
void Camera::setFrustumWasReshaped() {
_frustumNeedsReshape = false;
}
CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) :
_camera(camera), _viewFrustum(viewFrustum)
{
}
PickRay CameraScriptableObject::computePickRay(float x, float y) {
float screenWidth = Application::getInstance()->getGLWidget()->width();
float screenHeight = Application::getInstance()->getGLWidget()->height();
PickRay result;
_viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction);
return result;
}
QString CameraScriptableObject::getMode() const {
QString mode("unknown");
switch(_camera->getMode()) {
case CAMERA_MODE_THIRD_PERSON:
mode = "third person";
break;
case CAMERA_MODE_FIRST_PERSON:
mode = "first person";
break;
case CAMERA_MODE_MIRROR:
mode = "mirror";
break;
case CAMERA_MODE_INDEPENDENT:
mode = "independent";
break;
default:
break;
}
return mode;
}
void CameraScriptableObject::setMode(const QString& mode) {
CameraMode currentMode = _camera->getMode();
CameraMode targetMode = currentMode;
if (mode == "third person") {
targetMode = CAMERA_MODE_THIRD_PERSON;
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
} else if (mode == "first person") {
targetMode = CAMERA_MODE_FIRST_PERSON;
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
} else if (mode == "mirror") {
targetMode = CAMERA_MODE_MIRROR;
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
} else if (mode == "independent") {
targetMode = CAMERA_MODE_INDEPENDENT;
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
}
if (currentMode != targetMode) {
_camera->setMode(targetMode);
_camera->setModeShiftRate(10.0f);
}
}

View file

@ -10,8 +10,10 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <RegisteredMetaTypes.h>
#include <ViewFrustum.h>
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f;
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f;
enum CameraMode
{
@ -19,11 +21,12 @@ enum CameraMode
CAMERA_MODE_THIRD_PERSON,
CAMERA_MODE_FIRST_PERSON,
CAMERA_MODE_MIRROR,
CAMERA_MODE_INDEPENDENT,
NUM_CAMERA_MODES
};
class Camera
{
class Camera {
public:
Camera();
@ -31,70 +34,95 @@ public:
void update( float deltaTime );
void setUpShift ( float u ) { _upShift = u; }
void setDistance ( float d ) { _distance = d; }
void setTargetPosition( const glm::vec3& t ) { _targetPosition = t; }
void setPosition ( const glm::vec3& p ) { _position = p; }
void setTightness ( float t ) { _tightness = t; }
void setTargetRotation( const glm::quat& rotation );
void setUpShift(float u) { _upShift = u; }
void setDistance(float d) { _distance = d; }
void setPosition(const glm::vec3& p) { _position = p; }
void setTargetPosition(const glm::vec3& t) { _targetPosition = t; }
void setTightness(float t) { _tightness = t; }
void setTargetRotation(const glm::quat& rotation);
void setMode ( CameraMode m );
void setModeShiftRate ( float r );
void setFieldOfView ( float f );
void setAspectRatio ( float a );
void setNearClip ( float n );
void setFarClip ( float f );
void setEyeOffsetPosition ( const glm::vec3& p );
void setEyeOffsetOrientation( const glm::quat& o );
void setScale ( const float s );
void setMode(CameraMode m);
void setModeShiftRate(float r);
void setFieldOfView(float f);
void setAspectRatio(float a);
void setNearClip(float n);
void setFarClip(float f);
void setEyeOffsetPosition(const glm::vec3& p);
void setEyeOffsetOrientation(const glm::quat& o);
void setScale(const float s);
const glm::vec3& getTargetPosition () const { return _targetPosition; }
const glm::vec3& getPosition () const { return _position; }
const glm::quat& getTargetRotation () const { return _targetRotation; }
const glm::quat& getRotation () const { return _rotation; }
CameraMode getMode () const { return _mode; }
float getFieldOfView () const { return _fieldOfView; }
float getAspectRatio () const { return _aspectRatio; }
float getNearClip () const { return _scale * _nearClip; }
float getFarClip () const;
const glm::vec3& getEyeOffsetPosition () const { return _eyeOffsetPosition; }
const glm::quat& getEyeOffsetOrientation () const { return _eyeOffsetOrientation; }
float getScale () const { return _scale; }
const glm::vec3& getPosition() const { return _position; }
const glm::quat& getRotation() const { return _rotation; }
CameraMode getMode() const { return _mode; }
const glm::vec3& getTargetPosition() const { return _targetPosition; }
const glm::quat& getTargetRotation() const { return _targetRotation; }
float getFieldOfView() const { return _fieldOfView; }
float getAspectRatio() const { return _aspectRatio; }
float getNearClip() const { return _scale * _nearClip; }
float getFarClip() const;
const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; }
const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; }
float getScale() const { return _scale; }
CameraMode getInterpolatedMode() const;
bool getFrustumNeedsReshape() const; // call to find out if the view frustum needs to be reshaped
void setFrustumWasReshaped(); // call this after reshaping the view frustum.
private:
bool _needsToInitialize;
CameraMode _mode;
bool _frustumNeedsReshape;
glm::vec3 _position;
glm::vec3 _idealPosition;
glm::vec3 _targetPosition;
float _fieldOfView;
float _aspectRatio;
float _nearClip;
float _farClip;
glm::vec3 _eyeOffsetPosition;
glm::quat _eyeOffsetOrientation;
glm::quat _rotation;
glm::quat _targetRotation;
float _upShift;
float _distance;
float _tightness;
float _previousUpShift;
float _previousDistance;
float _previousTightness;
float _newUpShift;
float _newDistance;
float _newTightness;
float _modeShift;
float _linearModeShift;
float _modeShiftRate;
float _scale;
void updateFollowMode( float deltaTime );
bool _needsToInitialize;
CameraMode _mode;
CameraMode _prevMode;
bool _frustumNeedsReshape;
glm::vec3 _position;
glm::vec3 _idealPosition;
glm::vec3 _targetPosition;
float _fieldOfView;
float _aspectRatio;
float _nearClip;
float _farClip;
glm::vec3 _eyeOffsetPosition;
glm::quat _eyeOffsetOrientation;
glm::quat _rotation;
glm::quat _targetRotation;
float _upShift;
float _distance;
float _tightness;
float _previousUpShift;
float _previousDistance;
float _previousTightness;
float _newUpShift;
float _newDistance;
float _newTightness;
float _modeShift;
float _linearModeShift;
float _modeShiftRate;
float _scale;
void updateFollowMode(float deltaTime);
};
class CameraScriptableObject : public QObject {
Q_OBJECT
public:
CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum);
public slots:
QString getMode() const;
void setMode(const QString& mode);
void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);}
glm::vec3 getPosition() const { return _camera->getPosition(); }
void setOrientation(const glm::quat& value) { _camera->setTargetRotation(value); }
glm::quat getOrientation() const { return _camera->getRotation(); }
PickRay computePickRay(float x, float y);
private:
Camera* _camera;
ViewFrustum* _viewFrustum;
};
#endif

View file

@ -219,3 +219,19 @@ void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) {
}
}
bool ControllerScriptingInterface::isJoystickCaptured(int joystickIndex) const {
return _capturedJoysticks.contains(joystickIndex);
}
void ControllerScriptingInterface::captureJoystick(int joystickIndex) {
if (!isJoystickCaptured(joystickIndex)) {
_capturedJoysticks.insert(joystickIndex);
}
}
void ControllerScriptingInterface::releaseJoystick(int joystickIndex) {
if (isJoystickCaptured(joystickIndex)) {
_capturedJoysticks.remove(joystickIndex);
}
}

View file

@ -38,7 +38,7 @@ public:
bool isMouseCaptured() const { return _mouseCaptured; }
bool isTouchCaptured() const { return _touchCaptured; }
bool isWheelCaptured() const { return _wheelCaptured; }
bool isJoystickCaptured(int joystickIndex) const;
public slots:
virtual bool isPrimaryButtonPressed() const;
@ -70,6 +70,9 @@ public slots:
virtual void captureWheelEvents() { _wheelCaptured = true; }
virtual void releaseWheelEvents() { _wheelCaptured = false; }
virtual void captureJoystick(int joystickIndex);
virtual void releaseJoystick(int joystickIndex);
private:
const PalmData* getPrimaryPalm() const;
const PalmData* getPalm(int palmIndex) const;
@ -80,6 +83,7 @@ private:
bool _touchCaptured;
bool _wheelCaptured;
QMultiMap<int,KeyEvent> _capturedKeys;
QSet<int> _capturedJoysticks;
};
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip

View file

@ -40,7 +40,7 @@ void DatagramProcessor::processDatagrams() {
_packetCount++;
_byteCount += incomingPacket.size();
if (packetVersionMatch(incomingPacket)) {
if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
// only process this packet if we have a match on the packet version
switch (packetTypeForPacket(incomingPacket)) {
case PacketTypeTransmitterData:
@ -84,31 +84,31 @@ void DatagramProcessor::processDatagrams() {
printf("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime);
}
// add this packet to our list of voxel packets and process them on the voxel processing
application->_voxelProcessor.queueReceivedPacket(senderSockAddr, incomingPacket);
SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(incomingPacket);
if (matchedNode) {
// add this packet to our list of voxel packets and process them on the voxel processing
application->_voxelProcessor.queueReceivedPacket(matchedNode, incomingPacket);
}
break;
}
case PacketTypeMetavoxelData:
application->_metavoxels.processData(incomingPacket, senderSockAddr);
break;
case PacketTypeBulkAvatarData:
case PacketTypeKillAvatar: {
case PacketTypeKillAvatar:
case PacketTypeAvatarIdentity: {
// update having heard from the avatar-mixer and record the bytes received
SharedNodePointer avatarMixer = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
if (avatarMixer) {
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
avatarMixer->recordBytesReceived(incomingPacket.size());
if (packetTypeForPacket(incomingPacket) == PacketTypeBulkAvatarData) {
QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram",
Q_ARG(const QByteArray&, incomingPacket),
Q_ARG(const QWeakPointer<Node>&, avatarMixer));
} else {
// this is an avatar kill, pass it to the application AvatarManager
QMetaObject::invokeMethod(&application->getAvatarManager(), "processKillAvatar",
Q_ARG(const QByteArray&, incomingPacket));
}
QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram",
Q_ARG(const QByteArray&, incomingPacket),
Q_ARG(const QWeakPointer<Node>&, avatarMixer));
}
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
@ -124,7 +124,7 @@ void DatagramProcessor::processDatagrams() {
DataServerClient::processMessageFromDataServer(incomingPacket);
break;
default:
NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket);
nodeList->processNodeData(senderSockAddr, incomingPacket);
break;
}
}

View file

@ -239,9 +239,12 @@ Menu::Menu() :
false,
appInstance,
SLOT(setFullscreen(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
appInstance,SLOT(cameraMenuChanged()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H,false,
appInstance,SLOT(cameraMenuChanged()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0,
false,
appInstance,
@ -324,7 +327,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableLocalVoxelCache);
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
@ -698,6 +700,9 @@ void Menu::removeAction(QMenu* menu, const QString& actionName) {
menu->removeAction(_actionHash.value(actionName));
}
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
return _actionHash.value(menuOption)->setChecked(isChecked);
}
bool Menu::isOptionChecked(const QString& menuOption) {
return _actionHash.value(menuOption)->isChecked();
@ -768,12 +773,12 @@ void Menu::editPreferences() {
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QString faceURLString = applicationInstance->getProfile()->getFaceModelURL().toString();
QString faceURLString = applicationInstance->getAvatar()->getHead().getFaceModel().getURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Face URL:", faceURLEdit);
QString skeletonURLString = applicationInstance->getProfile()->getSkeletonModelURL().toString();
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Skeleton URL:", skeletonURLEdit);
@ -834,27 +839,25 @@ void Menu::editPreferences() {
int ret = dialog.exec();
if (ret == QDialog::Accepted) {
QUrl faceModelURL(faceURLEdit->text());
bool shouldDispatchIdentityPacket = false;
if (faceModelURL.toString() != faceURLString) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
applicationInstance->getProfile()->setFaceModelURL(faceModelURL);
// send the new face mesh URL to the data-server (if we have a client UUID)
DataServerClient::putValueForKeyAndUserString(DataServerKey::FaceMeshURL,
faceModelURL.toString().toLocal8Bit().constData(),
applicationInstance->getProfile()->getUserString());
applicationInstance->getAvatar()->setFaceModelURL(faceModelURL);
shouldDispatchIdentityPacket = true;
}
QUrl skeletonModelURL(skeletonURLEdit->text());
if (skeletonModelURL.toString() != skeletonURLString) {
// change the skeletonModelURL in the profile, it will also update this user's Body
applicationInstance->getProfile()->setSkeletonModelURL(skeletonModelURL);
// send the new skeleton model URL to the data-server (if we have a client UUID)
DataServerClient::putValueForKeyAndUserString(DataServerKey::SkeletonURL,
skeletonModelURL.toString().toLocal8Bit().constData(),
applicationInstance->getProfile()->getUserString());
applicationInstance->getAvatar()->setSkeletonModelURL(skeletonModelURL);
shouldDispatchIdentityPacket = true;
}
if (shouldDispatchIdentityPacket) {
applicationInstance->getAvatar()->sendIdentityPacket();
}
applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum());

View file

@ -49,6 +49,7 @@ public:
~Menu();
bool isOptionChecked(const QString& menuOption);
void setIsOptionChecked(const QString& menuOption, bool isChecked);
void triggerOption(const QString& menuOption);
QAction* getActionForOption(const QString& menuOption);
bool isVoxelModeActionChecked();
@ -179,7 +180,6 @@ namespace MenuOption {
const QString DestructiveAddVoxel = "Create Voxel is Destructive";
const QString DisableColorVoxels = "Disable Colored Voxels";
const QString DisableDeltaSending = "Disable Delta Sending";
const QString DisableLocalVoxelCache = "Disable Local Voxel Cache";
const QString DisableLowRes = "Disable Lower Resolution While Moving";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayLeapHands = "Display Leap Hands";

View file

@ -12,6 +12,7 @@
#include <SharedUtil.h>
#include <MetavoxelUtil.h>
#include <ScriptCache.h>
#include "Application.h"
#include "MetavoxelSystem.h"
@ -37,6 +38,9 @@ void MetavoxelSystem::init() {
_program.link();
_pointScaleLocation = _program.uniformLocation("pointScale");
// let the script cache know to use our common access manager
ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager());
}
NodeList* nodeList = NodeList::getInstance();
@ -140,9 +144,9 @@ void MetavoxelSystem::removeClient(const QUuid& uuid) {
delete client;
}
void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
void MetavoxelSystem::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
int headerPlusIDSize;
QUuid sessionID = readSessionID(data, sender, headerPlusIDSize);
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
if (sessionID.isNull()) {
return;
}
@ -226,10 +230,7 @@ void MetavoxelClient::receivedData(const QByteArray& data) {
void MetavoxelClient::sendData(const QByteArray& data) {
QMutexLocker locker(&_node->getMutex());
const HifiSockAddr* address = _node->getActiveSocket();
if (address) {
NodeList::getInstance()->getNodeSocket().writeDatagram(data, address->getAddress(), address->getPort());
}
NodeList::getInstance()->writeDatagram(data, _node);
}
void MetavoxelClient::readPacket(Bitstream& in) {

View file

@ -52,7 +52,7 @@ private:
Q_INVOKABLE void addClient(const SharedNodePointer& node);
Q_INVOKABLE void removeClient(const QUuid& uuid);
Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender);
Q_INVOKABLE void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
class Point {
public:

View file

@ -123,7 +123,6 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
}
}
void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr,
Node* sourceNode) {
static_cast<ParticleTree*>(_tree)->processEraseMessage(dataByteArray, senderSockAddr, sourceNode);
void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
static_cast<ParticleTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
}

View file

@ -38,7 +38,7 @@ public:
ParticleTree* getTree() { return (ParticleTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
virtual void init();
virtual void render();

View file

@ -14,7 +14,7 @@
#include "Menu.h"
#include "VoxelPacketProcessor.h"
void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) {
void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelPacketProcessor::processPacket()");
@ -44,11 +44,11 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, con
// then process any remaining bytes as if it was another packet
if (voxelPacketType == PacketTypeOctreeStats) {
int statsMessageLength = app->parseOctreeStats(mutablePacket, senderSockAddr);
int statsMessageLength = app->parseOctreeStats(mutablePacket, sendingNode);
wasStatsPacket = true;
if (messageLength > statsMessageLength) {
mutablePacket = mutablePacket.mid(statsMessageLength);
if (!packetVersionMatch(packet)) {
if (!NodeList::getInstance()->packetVersionAndHashMatch(packet)) {
return; // bail since piggyback data doesn't match our versioning
}
} else {
@ -60,26 +60,25 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, con
voxelPacketType = packetTypeForPacket(mutablePacket);
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
app->trackIncomingVoxelPacket(mutablePacket, senderSockAddr, wasStatsPacket);
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) {
if (sendingNode) {
switch(voxelPacketType) {
case PacketTypeParticleErase: {
app->_particles.processEraseMessage(mutablePacket, senderSockAddr, serverNode.data());
app->_particles.processEraseMessage(mutablePacket, sendingNode);
} break;
case PacketTypeParticleData: {
app->_particles.processDatagram(mutablePacket, senderSockAddr, serverNode.data());
app->_particles.processDatagram(mutablePacket, sendingNode);
} break;
case PacketTypeEnvironmentData: {
app->_environment.parseData(senderSockAddr, mutablePacket);
app->_environment.parseData(*sendingNode->getActiveSocket(), mutablePacket);
} break;
default : {
app->_voxels.setDataSourceUUID(serverNode->getUUID());
app->_voxels.setDataSourceUUID(sendingNode->getUUID());
app->_voxels.parseData(mutablePacket);
app->_voxels.setDataSourceUUID(QUuid());
} break;

View file

@ -17,6 +17,6 @@
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
class VoxelPacketProcessor : public ReceivedPacketProcessor {
protected:
virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
};
#endif // __shared__VoxelPacketProcessor__

View file

@ -99,8 +99,6 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_culledOnce = false;
_inhideOutOfView = false;
_treeIsBusy = false;
}
void VoxelSystem::elementDeleted(OctreeElement* element) {
@ -594,9 +592,10 @@ int VoxelSystem::parseData(const QByteArray& packet) {
}
if (sectionLength) {
PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() section");
// ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID());
lockTree();
_tree->lockForWrite();
VoxelPacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(dataAt, sectionLength);
if (Application::getInstance()->getLogger()->extraDebugging()) {
@ -608,7 +607,7 @@ int VoxelSystem::parseData(const QByteArray& packet) {
packetData.getUncompressedSize());
}
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
unlockTree();
_tree->unlock();
dataBytes -= sectionLength;
dataAt += sectionLength;
@ -1395,9 +1394,11 @@ void VoxelSystem::removeScaleAndReleaseProgram(bool texture) {
int VoxelSystem::_nodeCount = 0;
void VoxelSystem::killLocalVoxels() {
lockTree();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::killLocalVoxels()");
_tree->lockForWrite();
_tree->eraseAllOctreeElements();
unlockTree();
_tree->unlock();
clearFreeBufferIndexes();
_voxelsInReadArrays = 0; // do we need to do this?
setupNewVoxelsForDrawing();
@ -1416,10 +1417,12 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
}
void VoxelSystem::clearAllNodesBufferIndex() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::clearAllNodesBufferIndex()");
_nodeCount = 0;
lockTree();
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
unlockTree();
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug("clearing buffer index of %d nodes", _nodeCount);
}
@ -1481,7 +1484,8 @@ bool VoxelSystem::trueColorizeOperation(OctreeElement* element, void* extraData)
}
void VoxelSystem::trueColorize() {
PerformanceWarning warn(true, "trueColorize()",true);
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"trueColorize()",true);
_nodeCount = 0;
_tree->recurseTreeWithOperation(trueColorizeOperation);
qDebug("setting true color for %d nodes", _nodeCount);
@ -1951,9 +1955,13 @@ void VoxelSystem::hideOutOfView(bool forceFullFrustum) {
return;
}
lockTree();
_tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args);
unlockTree();
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::... recurseTreeWithOperation(hideOutOfViewOperation)");
_tree->lockForRead();
_tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args);
_tree->unlock();
}
_lastCulledViewFrustum = args.thisViewFrustum; // save last stable
_culledOnce = true;
@ -2150,35 +2158,47 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData
bool VoxelSystem::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
VoxelDetail& detail, float& distance, BoxFace& face) {
lockTree();
OctreeElement* element;
if (!_tree->findRayIntersection(origin, direction, element, distance, face)) {
unlockTree();
return false;
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::findRayIntersection()");
bool result = false; // assume no intersection
if (_tree->tryLockForRead()) {
OctreeElement* element;
result = _tree->findRayIntersection(origin, direction, element, distance, face);
if (result) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
detail.x = voxel->getCorner().x;
detail.y = voxel->getCorner().y;
detail.z = voxel->getCorner().z;
detail.s = voxel->getScale();
detail.red = voxel->getColor()[0];
detail.green = voxel->getColor()[1];
detail.blue = voxel->getColor()[2];
}
_tree->unlock();
}
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
detail.x = voxel->getCorner().x;
detail.y = voxel->getCorner().y;
detail.z = voxel->getCorner().z;
detail.s = voxel->getScale();
detail.red = voxel->getColor()[0];
detail.green = voxel->getColor()[1];
detail.blue = voxel->getColor()[2];
unlockTree();
return true;
return result;
}
bool VoxelSystem::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) {
lockTree();
bool result = _tree->findSpherePenetration(center, radius, penetration);
unlockTree();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::findSpherePenetration()");
bool result = false; // assume no penetration
if (_tree->tryLockForRead()) {
result = _tree->findSpherePenetration(center, radius, penetration);
_tree->unlock();
}
return result;
}
bool VoxelSystem::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) {
lockTree();
bool result = _tree->findCapsulePenetration(start, end, radius, penetration);
unlockTree();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::findCapsulePenetration()");
bool result = false; // assume no penetration
if (_tree->tryLockForRead()) {
result = _tree->findCapsulePenetration(start, end, radius, penetration);
_tree->unlock();
}
return result;
}
@ -2354,13 +2374,14 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
void VoxelSystem::deleteVoxelAt(float x, float y, float z, float s) {
lockTree();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::deleteVoxelAt()");
_tree->lockForWrite();
_tree->deleteVoxelAt(x, y, z, s);
unlockTree();
_tree->unlock();
// redraw!
setupNewVoxelsForDrawing(); // do we even need to do this? Or will the next network receive kick in?
};
VoxelTreeElement* VoxelSystem::getVoxelAt(float x, float y, float z, float s) const {
@ -2370,10 +2391,12 @@ VoxelTreeElement* VoxelSystem::getVoxelAt(float x, float y, float z, float s) co
void VoxelSystem::createVoxel(float x, float y, float z, float s,
unsigned char red, unsigned char green, unsigned char blue, bool destructive) {
//qDebug("VoxelSystem::createVoxel(%f,%f,%f,%f)\n",x,y,z,s);
lockTree();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelSystem::createVoxel()");
_tree->lockForWrite();
_tree->createVoxel(x, y, z, s, red, green, blue, destructive);
unlockTree();
_tree->unlock();
setupNewVoxelsForDrawing();
};
@ -2744,37 +2767,3 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() {
return (_initialMemoryUsageGPU - currentFreeMemory);
}
void VoxelSystem::lockTree() {
_treeLock.lock();
_treeIsBusy = true;
}
void VoxelSystem::unlockTree() {
_treeIsBusy = false;
_treeLock.unlock();
}
void VoxelSystem::localVoxelCacheLoaded() {
qDebug() << "localVoxelCacheLoaded()";
// Make sure that the application has properly set up the view frustum for our loaded state
Application::getInstance()->initAvatarAndViewFrustum();
_tree->setDirtyBit(); // make sure the tree thinks it's dirty
_setupNewVoxelsForDrawingLastFinished = 0; // don't allow the setupNewVoxelsForDrawing() shortcuts
_writeRenderFullVBO = true; // this will disable individual node updates, was reset by killLocalVoxels()
setupNewVoxelsForDrawing();
_inhideOutOfView = false; // reenable hideOutOfView behavior
}
void VoxelSystem::beginLoadingLocalVoxelCache() {
qDebug() << "beginLoadingLocalVoxelCache()";
_writeRenderFullVBO = true; // this will disable individual node updates
_inhideOutOfView = true; // this will disable hidOutOfView which we want to do until local cache is loaded
killLocalVoxels();
qDebug() << "DONE beginLoadingLocalVoxelCache()";
}

View file

@ -110,8 +110,6 @@ public:
virtual void elementDeleted(OctreeElement* element);
virtual void elementUpdated(OctreeElement* element);
bool treeIsBusy() const { return _treeIsBusy; }
VoxelTreeElement* getVoxelEnclosing(const glm::vec3& point);
signals:
@ -144,9 +142,6 @@ public slots:
void setUseVoxelShader(bool useVoxelShader);
void setVoxelsAsPoints(bool voxelsAsPoints);
void localVoxelCacheLoaded();
void beginLoadingLocalVoxelCache();
protected:
float _treeScale;
unsigned long _maxVoxels;
@ -304,10 +299,6 @@ private:
bool _useFastVoxelPipeline;
bool _inhideOutOfView;
bool _treeIsBusy; // is the tree mutex locked? if so, it's busy, and if you can avoid it, don't access the tree
void lockTree();
void unlockTree();
};
#endif

View file

@ -242,7 +242,9 @@ void Avatar::renderBody(bool forceRenderHead) {
glm::vec3 pos = getPosition();
//printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z);
_skeletonModel.render(1.0f);
_head.render(1.0f);
if (forceRenderHead) {
_head.render(1.0f);
}
_hand.render(false);
}
@ -334,6 +336,16 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi
return false;
}
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
_head.getFaceModel().setURL(faceModelURL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel.setURL(skeletonModelURL);
}
int Avatar::parseData(const QByteArray& packet) {
// change in position implies movement
glm::vec3 oldPosition = _position;

View file

@ -112,7 +112,10 @@ public:
virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
virtual bool isMyAvatar() { return false; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
int parseData(const QByteArray& packet);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);

View file

@ -75,7 +75,6 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
if (!selfAvatarOnly) {
// Render avatars of other nodes
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (!avatar->isInitialized()) {
@ -84,7 +83,7 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
avatar->render(forceRenderHead);
} else {
avatar->render(false);
avatar->render(true);
}
avatar->setDisplayingLookatVectors(renderLookAtVectors);
}
@ -124,47 +123,29 @@ void AvatarManager::renderAvatarFades() {
}
}
void AvatarManager::processDataServerResponse(const QString& userString, const QStringList& keyList,
const QStringList &valueList) {
QUuid avatarKey = QUuid(userString);
if (avatarKey == MY_AVATAR_KEY) {
// ignore updates to our own mesh
return;
}
for (int i = 0; i < keyList.size(); i++) {
if (valueList[i] != " ") {
if (keyList[i] == DataServerKey::FaceMeshURL || keyList[i] == DataServerKey::SkeletonURL) {
// mesh URL for a UUID, find avatar in our list
AvatarSharedPointer matchingAvatar = _avatarHash.value(avatarKey);
if (matchingAvatar) {
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
if (keyList[i] == DataServerKey::FaceMeshURL) {
qDebug() << "Changing mesh to" << valueList[i] << "for avatar with UUID"
<< uuidStringWithoutCurlyBraces(avatarKey);
QMetaObject::invokeMethod(&(avatar->getHead().getFaceModel()),
"setURL", Q_ARG(QUrl, QUrl(valueList[i])));
} else if (keyList[i] == DataServerKey::SkeletonURL) {
qDebug() << "Changing skeleton to" << valueList[i] << "for avatar with UUID"
<< uuidStringWithoutCurlyBraces(avatarKey.toString());
QMetaObject::invokeMethod(&(avatar->getSkeletonModel()),
"setURL", Q_ARG(QUrl, QUrl(valueList[i])));
}
}
}
}
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
switch (packetTypeForPacket(datagram)) {
case PacketTypeBulkAvatarData:
processAvatarDataPacket(datagram, mixerWeakPointer);
break;
case PacketTypeAvatarIdentity:
processAvatarIdentityPacket(datagram);
break;
case PacketTypeKillAvatar:
processKillAvatar(datagram);
break;
default:
break;
}
}
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer<Node> &mixerWeakPointer) {
int bytesRead = numBytesForPacketHeader(datagram);
QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
int numDummyHeaderBytes = dummyAvatarByteArray.size();
int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID;
// enumerate over all of the avatars in this packet
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
while (bytesRead < datagram.size() && mixerWeakPointer.data()) {
@ -181,10 +162,6 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
matchingAvatar = AvatarSharedPointer(avatar);
_avatarHash.insert(nodeUUID, matchingAvatar);
// new UUID requires mesh and skeleton request to data-server
DataServerClient::getValuesForKeysAndUUID(QStringList() << DataServerKey::FaceMeshURL << DataServerKey::SkeletonURL,
nodeUUID, this);
qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash.";
}
@ -197,6 +174,35 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
// have the matching (or new) avatar parse the data from the packet
bytesRead += matchingAvatar->parseData(dummyAvatarByteArray) - numDummyHeaderBytesWithoutUUID;
}
}
void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
// setup a data stream to parse the packet
QDataStream identityStream(packet);
identityStream.skipRawData(numBytesForPacketHeader(packet));
QUuid nodeUUID;
while (!identityStream.atEnd()) {
QUrl faceMeshURL, skeletonURL;
identityStream >> nodeUUID >> faceMeshURL >> skeletonURL;
// mesh URL for a UUID, find avatar in our list
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
if (matchingAvatar) {
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
if (avatar->getFaceModelURL() != faceMeshURL) {
avatar->setFaceModelURL(faceMeshURL);
}
if (avatar->getSkeletonModelURL() != skeletonURL) {
avatar->setSkeletonModelURL(skeletonURL);
}
}
}
}
void AvatarManager::processKillAvatar(const QByteArray& datagram) {

View file

@ -20,7 +20,7 @@
class MyAvatar;
class AvatarManager : public QObject, public DataServerCallbackObject, public AvatarHashMap {
class AvatarManager : public QObject, public AvatarHashMap {
Q_OBJECT
public:
AvatarManager(QObject* parent = 0);
@ -35,13 +35,14 @@ public:
void clearOtherAvatars();
public slots:
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer);
void processKillAvatar(const QByteArray& datagram);
private:
AvatarManager(const AvatarManager& other);
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
void processAvatarIdentityPacket(const QByteArray& packet);
void processKillAvatar(const QByteArray& datagram);
void simulateAvatarFades(float deltaTime);
void renderAvatarFades();

View file

@ -608,6 +608,9 @@ void MyAvatar::saveData(QSettings* settings) {
settings->setValue("leanScale", _leanScale);
settings->setValue("scale", _targetScale);
settings->setValue("faceModelURL", _faceModelURL);
settings->setValue("skeletonModelURL", _skeletonModelURL);
settings->endGroup();
}
@ -632,6 +635,9 @@ void MyAvatar::loadData(QSettings* settings) {
_targetScale = loadSetting(settings, "scale", 1.0f);
setScale(_scale);
Application::getInstance()->getCamera()->setScale(_scale);
setFaceModelURL(settings->value("faceModelURL").toUrl());
setSkeletonModelURL(settings->value("skeletonModelURL").toUrl());
settings->endGroup();
}
@ -641,6 +647,13 @@ void MyAvatar::sendKillAvatar() {
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::sendIdentityPacket() {
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
identityPacket.append(AvatarData::identityByteArray());
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
// first orbit horizontally
glm::quat orientation = getOrientation();
@ -792,23 +805,27 @@ void MyAvatar::updateThrust(float deltaTime) {
const int VIEW_CONTROLLER = 1;
for (size_t i = 0; i < getHand().getPalms().size(); ++i) {
PalmData& palm = getHand().getPalms()[i];
if (palm.isActive() && (palm.getSixenseID() == THRUST_CONTROLLER)) {
if (palm.getJoystickY() != 0.f) {
FingerData& finger = palm.getFingers()[0];
if (finger.isActive()) {
// If the script hasn't captured this joystick, then let the default behavior work
if (!Application::getInstance()->getControllerScriptingInterface()->isJoystickCaptured(palm.getSixenseID())) {
if (palm.isActive() && (palm.getSixenseID() == THRUST_CONTROLLER)) {
if (palm.getJoystickY() != 0.f) {
FingerData& finger = palm.getFingers()[0];
if (finger.isActive()) {
}
_thrust += front * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickY() * _thrustMultiplier * deltaTime;
}
if (palm.getJoystickX() != 0.f) {
_thrust += right * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickX() * _thrustMultiplier * deltaTime;
}
} else if (palm.isActive() && (palm.getSixenseID() == VIEW_CONTROLLER)) {
if (palm.getJoystickX() != 0.f) {
_bodyYawDelta -= palm.getJoystickX() * JOYSTICK_YAW_MAG * deltaTime;
}
if (palm.getJoystickY() != 0.f) {
getHand().setPitchUpdate(getHand().getPitchUpdate() +
(palm.getJoystickY() * JOYSTICK_PITCH_MAG * deltaTime));
}
_thrust += front * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickY() * _thrustMultiplier * deltaTime;
}
if (palm.getJoystickX() != 0.f) {
_thrust += right * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickX() * _thrustMultiplier * deltaTime;
}
} else if (palm.isActive() && (palm.getSixenseID() == VIEW_CONTROLLER)) {
if (palm.getJoystickX() != 0.f) {
_bodyYawDelta -= palm.getJoystickX() * JOYSTICK_YAW_MAG * deltaTime;
}
if (palm.getJoystickY() != 0.f) {
getHand().setPitchUpdate(getHand().getPitchUpdate() +
(palm.getJoystickY() * JOYSTICK_PITCH_MAG * deltaTime));
}
}

View file

@ -94,6 +94,8 @@ public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void sendIdentityPacket();
private:
bool _mousePressed;

View file

@ -20,17 +20,13 @@ Profile::Profile(const QString &username) :
_uuid(),
_lastDomain(),
_lastPosition(0.0, 0.0, 0.0),
_lastOrientationSend(0),
_faceModelURL(),
_skeletonModelURL()
_lastOrientationSend(0)
{
if (!username.isEmpty()) {
setUsername(username);
// we've been given a new username, ask the data-server for profile
DataServerClient::getValueForKeyAndUserString(DataServerKey::UUID, getUserString(), this);
DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, getUserString(), this);
DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, getUserString(), this);
// send our current domain server to the data-server
updateDomain(NodeList::getInstance()->getDomainHostname());
@ -45,34 +41,6 @@ QString Profile::getUserString() const {
}
}
void Profile::setUUID(const QUuid& uuid) {
_uuid = uuid;
if (!_uuid.isNull()) {
qDebug() << "Changing NodeList owner UUID to" << uuid;
// when the UUID is changed we need set it appropriately on the NodeList instance
NodeList::getInstance()->setOwnerUUID(uuid);
// ask for a window title update so the new UUID is presented
Application::getInstance()->updateWindowTitle();
}
}
void Profile::setFaceModelURL(const QUrl& faceModelURL) {
_faceModelURL = faceModelURL;
QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getHead().getFaceModel(),
"setURL", Q_ARG(QUrl, _faceModelURL));
}
void Profile::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelURL = skeletonModelURL;
QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getSkeletonModel(),
"setURL", Q_ARG(QUrl, _skeletonModelURL));
}
void Profile::updateDomain(const QString& domain) {
if (_lastDomain != domain) {
_lastDomain = domain;
@ -137,8 +105,6 @@ void Profile::saveData(QSettings* settings) {
settings->setValue("username", _username);
settings->setValue("UUID", _uuid);
settings->setValue("faceModelURL", _faceModelURL);
settings->setValue("skeletonModelURL", _skeletonModelURL);
settings->endGroup();
}
@ -148,8 +114,6 @@ void Profile::loadData(QSettings* settings) {
setUsername(settings->value("username").toString());
this->setUUID(settings->value("UUID").toUuid());
_faceModelURL = settings->value("faceModelURL").toUrl();
_skeletonModelURL = settings->value("skeletonModelURL").toUrl();
settings->endGroup();
}
@ -157,17 +121,7 @@ void Profile::loadData(QSettings* settings) {
void Profile::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) {
for (int i = 0; i < keyList.size(); i++) {
if (valueList[i] != " ") {
if (keyList[i] == DataServerKey::FaceMeshURL) {
if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) {
qDebug("Changing user's face model URL to %s", valueList[i].toLocal8Bit().constData());
Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i]));
}
} else if (keyList[i] == DataServerKey::SkeletonURL) {
if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) {
qDebug("Changing user's skeleton URL to %s", valueList[i].toLocal8Bit().constData());
Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i]));
}
} else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position &&
if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position &&
keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " &&
valueList[i + 1] != " " && valueList[i + 2] != " ") {

View file

@ -28,15 +28,9 @@ public:
const QString& getUsername() const { return _username; }
void setUUID(const QUuid& uuid);
void setUUID(const QUuid& uuid) { _uuid = uuid; }
const QUuid& getUUID() { return _uuid; }
void setFaceModelURL(const QUrl& faceModelURL);
const QUrl& getFaceModelURL() const { return _faceModelURL; }
void setSkeletonModelURL(const QUrl& skeletonModelURL);
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
void updateDomain(const QString& domain);
void updatePosition(const glm::vec3 position);
void updateOrientation(const glm::quat& orientation);
@ -58,8 +52,6 @@ private:
glm::vec3 _lastPosition;
glm::vec3 _lastOrientation;
quint64 _lastOrientationSend;
QUrl _faceModelURL;
QUrl _skeletonModelURL;
};
#endif /* defined(__hifi__Profile__) */

View file

@ -14,6 +14,7 @@
#include <QListWidget>
#include <QMetaProperty>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <AttributeRegistry.h>
@ -80,6 +81,9 @@ MetavoxelEditor::MetavoxelEditor() :
QVBoxLayout* valueLayout = new QVBoxLayout();
_value->setLayout(valueLayout);
valueLayout->addWidget(_valueArea = new QScrollArea());
_valueArea->setWidgetResizable(true);
updateAttributes();
connect(Application::getInstance(), SIGNAL(renderingInWorldInterface()), SLOT(render()));
@ -145,14 +149,14 @@ void MetavoxelEditor::updateValueEditor() {
}
_value->setVisible(true);
if (!_value->layout()->isEmpty()) {
delete _value->layout()->takeAt(0);
if (_valueArea->widget()) {
delete _valueArea->widget();
}
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
QWidget* editor = attribute->createEditor();
if (editor) {
_value->layout()->addWidget(editor);
_valueArea->setWidget(editor);
}
}
@ -274,9 +278,13 @@ void MetavoxelEditor::render() {
glutSolidCube(1.0);
glDisable(GL_CULL_FACE);
}
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
glutWireCube(1.0);
glPopMatrix();
} else {
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
}
glLineWidth(1.0f);
@ -292,7 +300,6 @@ void MetavoxelEditor::render() {
_gridProgram.bind();
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
_gridProgram.release();
@ -363,11 +370,8 @@ void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maxi
}
QVariant MetavoxelEditor::getValue() const {
if (_value->layout()->isEmpty()) {
return QVariant();
}
QWidget* editor = _value->layout()->itemAt(0)->widget();
return editor->metaObject()->userProperty().read(editor);
QWidget* editor = _valueArea->widget();
return editor ? editor->metaObject()->userProperty().read(editor) : QVariant();
}
ProgramObject MetavoxelEditor::_gridProgram;

View file

@ -17,6 +17,7 @@ class QComboBox;
class QDoubleSpinBox;
class QGroupBox;
class QListWidget;
class QScrollArea;
/// Allows editing metavoxels.
class MetavoxelEditor : public QDialog {
@ -52,6 +53,7 @@ private:
QDoubleSpinBox* _gridSpacing;
QDoubleSpinBox* _gridPosition;
QGroupBox* _value;
QScrollArea* _valueArea;
enum State { HOVERING_STATE, DRAGGING_STATE, RAISING_STATE };

View file

@ -265,7 +265,7 @@ void VoxelStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t serv
std::stringstream extraDetails("");
std::stringstream linkDetails("");
if (nodeList->getNodeActiveSocketOrPing(node.data())) {
if (node->getActiveSocket()) {
serverDetails << "active ";
} else {
serverDetails << "inactive ";

View file

@ -93,12 +93,8 @@ void AudioInjector::injectAudio() {
// grab our audio mixer from the NodeList, if it exists
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) {
// send off this audio packet
nodeList->getNodeSocket().writeDatagram(injectAudioPacket,
audioMixer->getActiveSocket()->getAddress(),
audioMixer->getActiveSocket()->getPort());
}
// send off this audio packet
nodeList->writeDatagram(injectAudioPacket, audioMixer);
currentSendPosition += bytesToCopy;

View file

@ -70,9 +70,9 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
// read to the end of the buffer
int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput;
memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t));
// read the rest from the beginning of the buffer
memcpy(data + numSamplesToEnd, _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t));
memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t));
} else {
// read the data
memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t));

View file

@ -270,6 +270,48 @@ int AvatarData::parseData(const QByteArray& packet) {
return sourceBuffer - startPosition;
}
bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
QUuid avatarUUID;
QUrl faceModelURL, skeletonModelURL;
packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL;
bool hasIdentityChanged = false;
if (faceModelURL != _faceModelURL) {
setFaceModelURL(faceModelURL);
hasIdentityChanged = true;
}
if (skeletonModelURL != _skeletonModelURL) {
setSkeletonModelURL(skeletonModelURL);
hasIdentityChanged = true;
}
return hasIdentityChanged;
}
QByteArray AvatarData::identityByteArray() {
QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append);
identityStream << QUuid() << _faceModelURL << _skeletonModelURL;
return identityData;
}
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
qDebug() << "Changing face model for avatar to" << faceModelURL.toString();
_faceModelURL = faceModelURL;
}
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
qDebug() << "Changing skeleton model for avatar to" << skeletonModelURL.toString();
_skeletonModelURL = skeletonModelURL;
}
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);

View file

@ -30,6 +30,7 @@ typedef unsigned long long quint64;
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtCore/QUuid>
#include <QtCore/QVariantMap>
@ -51,8 +52,7 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
enum KeyState
{
enum KeyState {
NO_KEY_DOWN = 0,
INSERT_KEY_DOWN,
DELETE_KEY_DOWN
@ -72,7 +72,9 @@ class AvatarData : public NodeData {
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
Q_PROPERTY(QUrl faceModelURL READ getFaceModelURL WRITE setFaceModelURL)
Q_PROPERTY(QUrl skeletonModelURL READ getSkeletonModelURL WRITE setSkeletonModelURL)
public:
AvatarData();
~AvatarData();
@ -142,6 +144,14 @@ public:
return false;
}
bool hasIdentityChangedAfterParsing(const QByteArray& packet);
QByteArray identityByteArray();
const QUrl& getFaceModelURL() const { return _faceModelURL; }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
protected:
glm::vec3 _position;
glm::vec3 _handPosition;
@ -168,6 +178,8 @@ protected:
HeadData* _headData;
HandData* _handData;
QUrl _faceModelURL;
QUrl _skeletonModelURL;
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);

View file

@ -8,6 +8,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm
set(TARGET_NAME metavoxels)
find_package(Qt5Network REQUIRED)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
@ -16,7 +17,7 @@ setup_hifi_library(${TARGET_NAME})
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
qt5_use_modules(${TARGET_NAME} Widgets Script)
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})

View file

@ -6,15 +6,13 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <QColorDialog>
#include <QPushButton>
#include <QScriptEngine>
#include <QVBoxLayout>
#include "AttributeRegistry.h"
#include "MetavoxelData.h"
REGISTER_META_OBJECT(QRgbAttribute)
REGISTER_META_OBJECT(SharedObjectAttribute)
AttributeRegistry* AttributeRegistry::getInstance() {
static AttributeRegistry registry;
@ -22,17 +20,29 @@ AttributeRegistry* AttributeRegistry::getInstance() {
}
AttributeRegistry::AttributeRegistry() :
_guideAttribute(registerAttribute(new PolymorphicAttribute("guide", PolymorphicDataPointer(new DefaultMetavoxelGuide())))),
_guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject,
SharedObjectPointer(new DefaultMetavoxelGuide())))),
_colorAttribute(registerAttribute(new QRgbAttribute("color"))),
_normalAttribute(registerAttribute(new QRgbAttribute("normal", qRgb(0, 127, 0)))) {
}
static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) {
QDebug debug = qDebug();
for (int i = 0; i < context->argumentCount(); i++) {
debug << context->argument(i).toString();
}
return QScriptValue();
}
void AttributeRegistry::configureScriptEngine(QScriptEngine* engine) {
QScriptValue registry = engine->newObject();
registry.setProperty("colorAttribute", engine->newQObject(_colorAttribute.data()));
registry.setProperty("normalAttribute", engine->newQObject(_normalAttribute.data()));
registry.setProperty("getAttribute", engine->newFunction(getAttribute, 1));
engine->globalObject().setProperty("AttributeRegistry", registry);
engine->globalObject().setProperty("qDebug", engine->newFunction(qDebugFunction, 1));
}
AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute) {
@ -84,6 +94,10 @@ OwnedAttributeValue::OwnedAttributeValue(const AttributeValue& other) :
AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) {
}
OwnedAttributeValue::OwnedAttributeValue(const OwnedAttributeValue& other) :
AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) {
}
OwnedAttributeValue::~OwnedAttributeValue() {
if (_attribute) {
_attribute->destroy(_value);
@ -100,6 +114,16 @@ OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other)
return *this;
}
OwnedAttributeValue& OwnedAttributeValue::operator=(const OwnedAttributeValue& other) {
if (_attribute) {
_attribute->destroy(_value);
}
if ((_attribute = other.getAttribute())) {
_value = other.copy();
}
return *this;
}
Attribute::Attribute(const QString& name) {
setObjectName(name);
}
@ -146,41 +170,48 @@ void* QRgbAttribute::createFromVariant(const QVariant& value) const {
}
QWidget* QRgbAttribute::createEditor(QWidget* parent) const {
QRgbEditor* editor = new QRgbEditor(parent);
QColorEditor* editor = new QColorEditor(parent);
editor->setColor(QColor::fromRgba(_defaultValue));
return editor;
}
QRgbEditor::QRgbEditor(QWidget* parent) : QWidget(parent) {
setLayout(new QVBoxLayout());
layout()->addWidget(_button = new QPushButton());
connect(_button, SIGNAL(clicked()), SLOT(selectColor()));
SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject,
const SharedObjectPointer& defaultValue) :
InlineAttribute<SharedObjectPointer>(name, defaultValue),
_metaObject(metaObject) {
}
void QRgbEditor::setColor(const QColor& color) {
QString name = (_color = color).name();
_button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name()));
_button->setText(name);
}
void QRgbEditor::selectColor() {
QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel);
if (color.isValid()) {
setColor(color);
void SharedObjectAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
if (isLeaf) {
in >> *((SharedObjectPointer*)&value);
}
}
PolymorphicData::~PolymorphicData() {
void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
if (isLeaf) {
out << decodeInline<SharedObjectPointer>(value);
}
}
template<> PolymorphicData* QExplicitlySharedDataPointer<PolymorphicData>::clone() {
return d->clone();
bool SharedObjectAttribute::merge(void*& parent, void* children[]) const {
SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
for (int i = 1; i < MERGE_COUNT; i++) {
if (firstChild != decodeInline<SharedObjectPointer>(children[i])) {
*(SharedObjectPointer*)&parent = _defaultValue;
return false;
}
}
*(SharedObjectPointer*)&parent = firstChild;
return true;
}
PolymorphicAttribute::PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue) :
InlineAttribute<PolymorphicDataPointer>(name, defaultValue) {
void* SharedObjectAttribute::createFromVariant(const QVariant& value) const {
return create(encodeInline(value.value<SharedObjectPointer>()));
}
bool PolymorphicAttribute::merge(void*& parent, void* children[]) const {
return false;
QWidget* SharedObjectAttribute::createEditor(QWidget* parent) const {
SharedObjectEditor* editor = new SharedObjectEditor(_metaObject, parent);
editor->setObject(_defaultValue);
return editor;
}

View file

@ -9,18 +9,15 @@
#ifndef __interface__AttributeRegistry__
#define __interface__AttributeRegistry__
#include <QColor>
#include <QExplicitlySharedDataPointer>
#include <QHash>
#include <QObject>
#include <QSharedData>
#include <QSharedPointer>
#include <QString>
#include <QWidget>
#include "Bitstream.h"
#include "SharedObject.h"
class QPushButton;
class QScriptContext;
class QScriptEngine;
class QScriptValue;
@ -127,11 +124,17 @@ public:
/// Creates an owned attribute with a copy of the specified other value.
OwnedAttributeValue(const AttributeValue& other);
/// Creates an owned attribute with a copy of the specified other value.
OwnedAttributeValue(const OwnedAttributeValue& other);
/// Destroys the current value, if any.
~OwnedAttributeValue();
/// Destroys the current value, if any, and copies the specified other value.
OwnedAttributeValue& operator=(const AttributeValue& other);
/// Destroys the current value, if any, and copies the specified other value.
OwnedAttributeValue& operator=(const OwnedAttributeValue& other);
};
/// Represents a registered attribute.
@ -247,104 +250,28 @@ public:
virtual QWidget* createEditor(QWidget* parent = NULL) const;
};
/// Editor for RGBA values.
class QRgbEditor : public QWidget {
/// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject).
class SharedObjectAttribute : public InlineAttribute<SharedObjectPointer> {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER _color WRITE setColor USER true)
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject)
public:
QRgbEditor(QWidget* parent);
Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(), const QMetaObject* metaObject = NULL,
const SharedObjectPointer& defaultValue = SharedObjectPointer());
public slots:
void setColor(const QColor& color);
private slots:
void selectColor();
private:
QPushButton* _button;
QColor _color;
};
/// An attribute class that stores pointers to its values.
template<class T> class PointerAttribute : public Attribute {
public:
PointerAttribute(const QString& name, T defaultValue = T()) : Attribute(name), _defaultValue(defaultValue) { }
virtual void* create(void* copy) const { new T(*static_cast<T*>(copy)); }
virtual void destroy(void* value) const { delete static_cast<T*>(value); }
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
virtual bool equal(void* first, void* second) const { return *static_cast<T*>(first) == *static_cast<T*>(second); }
virtual void* getDefaultValue() const { return const_cast<void*>((void*)&_defaultValue); }
virtual bool merge(void*& parent, void* children[]) const;
virtual void* createFromVariant(const QVariant& value) const;
virtual QWidget* createEditor(QWidget* parent = NULL) const;
private:
T _defaultValue;
};
template<class T> inline void PointerAttribute<T>::read(Bitstream& in, void*& value, bool isLeaf) const {
if (isLeaf) {
in.read(value, sizeof(T) * 8);
}
}
template<class T> inline void PointerAttribute<T>::write(Bitstream& out, void* value, bool isLeaf) const {
if (isLeaf) {
out.write(value, sizeof(T) * 8);
}
}
/// Provides merging using the =, ==, += and /= operators.
template<class T> class SimplePointerAttribute : public PointerAttribute<T> {
public:
SimplePointerAttribute(const QString& name, T defaultValue = T()) : PointerAttribute<T>(name, defaultValue) { }
virtual bool merge(void*& parent, void* children[]) const;
};
template<class T> inline bool SimplePointerAttribute<T>::merge(void*& parent, void* children[]) const {
T& merged = *static_cast<T*>(parent);
merged = *static_cast<T*>(children[0]);
bool allChildrenEqual = true;
for (int i = 1; i < Attribute::MERGE_COUNT; i++) {
merged += *static_cast<T*>(children[i]);
allChildrenEqual &= (*static_cast<T*>(children[0]) == *static_cast<T*>(children[i]));
}
merged /= Attribute::MERGE_COUNT;
return allChildrenEqual;
}
/// Base class for polymorphic attribute data.
class PolymorphicData : public QSharedData {
public:
virtual ~PolymorphicData();
/// Creates a new clone of this object.
virtual PolymorphicData* clone() const = 0;
};
template<> PolymorphicData* QExplicitlySharedDataPointer<PolymorphicData>::clone();
typedef QExplicitlySharedDataPointer<PolymorphicData> PolymorphicDataPointer;
/// Provides polymorphic streaming and averaging.
class PolymorphicAttribute : public InlineAttribute<PolymorphicDataPointer> {
public:
PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue = PolymorphicDataPointer());
virtual bool merge(void*& parent, void* children[]) const;
const QMetaObject* _metaObject;
};
#endif /* defined(__interface__AttributeRegistry__) */

View file

@ -10,16 +10,31 @@
#include <QDataStream>
#include <QMetaProperty>
#include <QMetaType>
#include <QUrl>
#include <QtDebug>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h>
#include "AttributeRegistry.h"
#include "Bitstream.h"
#include "ScriptCache.h"
REGISTER_SIMPLE_TYPE_STREAMER(QByteArray)
REGISTER_SIMPLE_TYPE_STREAMER(QString)
REGISTER_SIMPLE_TYPE_STREAMER(QVariantList)
REGISTER_SIMPLE_TYPE_STREAMER(bool)
REGISTER_SIMPLE_TYPE_STREAMER(int)
REGISTER_SIMPLE_TYPE_STREAMER(float)
REGISTER_SIMPLE_TYPE_STREAMER(QByteArray)
REGISTER_SIMPLE_TYPE_STREAMER(QColor)
REGISTER_SIMPLE_TYPE_STREAMER(QString)
REGISTER_SIMPLE_TYPE_STREAMER(QUrl)
REGISTER_SIMPLE_TYPE_STREAMER(QVariantList)
REGISTER_SIMPLE_TYPE_STREAMER(QVariantHash)
// some types don't quite work with our macro
static int vec3Streamer = Bitstream::registerTypeStreamer(qMetaTypeId<glm::vec3>(), new SimpleTypeStreamer<glm::vec3>());
static int metaObjectStreamer = Bitstream::registerTypeStreamer(qMetaTypeId<const QMetaObject*>(),
new SimpleTypeStreamer<const QMetaObject*>());
IDStreamer::IDStreamer(Bitstream& stream) :
_stream(stream),
@ -52,6 +67,11 @@ IDStreamer& IDStreamer::operator>>(int& value) {
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
getMetaObjects().insert(className, metaObject);
// register it as a subclass of all of its superclasses
for (const QMetaObject* superClass = metaObject->superClass(); superClass != NULL; superClass = superClass->superClass()) {
getMetaObjectSubClasses().insert(superClass, metaObject);
}
return 0;
}
@ -61,16 +81,22 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
return 0;
}
Bitstream::Bitstream(QDataStream& underlying) :
QList<const QMetaObject*> Bitstream::getMetaObjectSubClasses(const QMetaObject* metaObject) {
return getMetaObjectSubClasses().values(metaObject);
}
Bitstream::Bitstream(QDataStream& underlying, QObject* parent) :
QObject(parent),
_underlying(underlying),
_byte(0),
_position(0),
_metaObjectStreamer(*this),
_typeStreamerStreamer(*this),
_attributeStreamer(*this) {
_attributeStreamer(*this),
_scriptStringStreamer(*this),
_sharedObjectStreamer(*this) {
}
const int BITS_IN_BYTE = 8;
const int LAST_BIT_POSITION = BITS_IN_BYTE - 1;
Bitstream& Bitstream::write(const void* data, int bits, int offset) {
@ -124,7 +150,9 @@ void Bitstream::reset() {
Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() {
WriteMappings mappings = { _metaObjectStreamer.getAndResetTransientOffsets(),
_typeStreamerStreamer.getAndResetTransientOffsets(),
_attributeStreamer.getAndResetTransientOffsets() };
_attributeStreamer.getAndResetTransientOffsets(),
_scriptStringStreamer.getAndResetTransientOffsets(),
_sharedObjectStreamer.getAndResetTransientOffsets() };
return mappings;
}
@ -132,12 +160,24 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) {
_metaObjectStreamer.persistTransientOffsets(mappings.metaObjectOffsets);
_typeStreamerStreamer.persistTransientOffsets(mappings.typeStreamerOffsets);
_attributeStreamer.persistTransientOffsets(mappings.attributeOffsets);
_scriptStringStreamer.persistTransientOffsets(mappings.scriptStringOffsets);
_sharedObjectStreamer.persistTransientOffsets(mappings.sharedObjectOffsets);
// find out when shared objects' reference counts drop to one in order to clear their mappings
for (QHash<SharedObjectPointer, int>::const_iterator it = mappings.sharedObjectOffsets.constBegin();
it != mappings.sharedObjectOffsets.constEnd(); it++) {
if (it.key()) {
connect(it.key().data(), SIGNAL(referenceCountDroppedToOne()), SLOT(clearSharedObject()));
}
}
}
Bitstream::ReadMappings Bitstream::getAndResetReadMappings() {
ReadMappings mappings = { _metaObjectStreamer.getAndResetTransientValues(),
_typeStreamerStreamer.getAndResetTransientValues(),
_attributeStreamer.getAndResetTransientValues() };
_attributeStreamer.getAndResetTransientValues(),
_scriptStringStreamer.getAndResetTransientValues(),
_sharedObjectStreamer.getAndResetTransientValues() };
return mappings;
}
@ -145,6 +185,8 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) {
_metaObjectStreamer.persistTransientValues(mappings.metaObjectValues);
_typeStreamerStreamer.persistTransientValues(mappings.typeStreamerValues);
_attributeStreamer.persistTransientValues(mappings.attributeValues);
_scriptStringStreamer.persistTransientValues(mappings.scriptStringValues);
_sharedObjectStreamer.persistTransientValues(mappings.sharedObjectValues);
}
Bitstream& Bitstream::operator<<(bool value) {
@ -205,6 +247,17 @@ Bitstream& Bitstream::operator>>(QByteArray& string) {
return read(string.data(), size * BITS_IN_BYTE);
}
Bitstream& Bitstream::operator<<(const QColor& color) {
return *this << (int)color.rgba();
}
Bitstream& Bitstream::operator>>(QColor& color) {
int rgba;
*this >> rgba;
color.setRgba(rgba);
return *this;
}
Bitstream& Bitstream::operator<<(const QString& string) {
*this << string.size();
return write(string.constData(), string.size() * sizeof(QChar) * BITS_IN_BYTE);
@ -217,6 +270,17 @@ Bitstream& Bitstream::operator>>(QString& string) {
return read(string.data(), size * sizeof(QChar) * BITS_IN_BYTE);
}
Bitstream& Bitstream::operator<<(const QUrl& url) {
return *this << url.toString();
}
Bitstream& Bitstream::operator>>(QUrl& url) {
QString string;
*this >> string;
url = string;
return *this;
}
Bitstream& Bitstream::operator<<(const QVariant& value) {
const TypeStreamer* streamer = getTypeStreamers().value(value.userType());
if (streamer) {
@ -302,11 +366,61 @@ Bitstream& Bitstream::operator>>(QObject*& object) {
}
Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) {
return *this << (metaObject ? QByteArray::fromRawData(
metaObject->className(), strlen(metaObject->className())) : QByteArray());
_metaObjectStreamer << metaObject;
return *this;
}
Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) {
_metaObjectStreamer >> metaObject;
return *this;
}
Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) {
_typeStreamerStreamer << streamer;
return *this;
}
Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
_typeStreamerStreamer >> streamer;
return *this;
}
Bitstream& Bitstream::operator<<(const AttributePointer& attribute) {
_attributeStreamer << attribute;
return *this;
}
Bitstream& Bitstream::operator>>(AttributePointer& attribute) {
_attributeStreamer >> attribute;
return *this;
}
Bitstream& Bitstream::operator<<(const QScriptString& string) {
_scriptStringStreamer << string;
return *this;
}
Bitstream& Bitstream::operator>>(QScriptString& string) {
_scriptStringStreamer >> string;
return *this;
}
Bitstream& Bitstream::operator<<(const SharedObjectPointer& object) {
_sharedObjectStreamer << object;
return *this;
}
Bitstream& Bitstream::operator>>(SharedObjectPointer& object) {
_sharedObjectStreamer >> object;
return *this;
}
Bitstream& Bitstream::operator<(const QMetaObject* metaObject) {
return *this << (metaObject ? QByteArray::fromRawData(metaObject->className(),
strlen(metaObject->className())) : QByteArray());
}
Bitstream& Bitstream::operator>(const QMetaObject*& metaObject) {
QByteArray className;
*this >> className;
if (className.isEmpty()) {
@ -320,12 +434,12 @@ Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) {
return *this;
}
Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) {
Bitstream& Bitstream::operator<(const TypeStreamer* streamer) {
const char* typeName = QMetaType::typeName(streamer->getType());
return *this << QByteArray::fromRawData(typeName, strlen(typeName));
}
Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
Bitstream& Bitstream::operator>(const TypeStreamer*& streamer) {
QByteArray typeName;
*this >> typeName;
streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
@ -335,22 +449,55 @@ Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
return *this;
}
Bitstream& Bitstream::operator<<(const AttributePointer& attribute) {
Bitstream& Bitstream::operator<(const AttributePointer& attribute) {
return *this << (QObject*)attribute.data();
}
Bitstream& Bitstream::operator>>(AttributePointer& attribute) {
Bitstream& Bitstream::operator>(AttributePointer& attribute) {
QObject* object;
*this >> object;
attribute = AttributeRegistry::getInstance()->registerAttribute(static_cast<Attribute*>(object));
return *this;
}
Bitstream& Bitstream::operator<(const QScriptString& string) {
return *this << string.toString();
}
Bitstream& Bitstream::operator>(QScriptString& string) {
QString rawString;
*this >> rawString;
string = ScriptCache::getInstance()->getEngine()->toStringHandle(rawString);
return *this;
}
Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
return *this << object.data();
}
Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
QObject* rawObject;
*this >> rawObject;
object = static_cast<SharedObject*>(rawObject);
return *this;
}
void Bitstream::clearSharedObject() {
SharedObjectPointer object(static_cast<SharedObject*>(sender()));
object->disconnect(this);
emit sharedObjectCleared(_sharedObjectStreamer.takePersistentID(object));
}
QHash<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
static QHash<QByteArray, const QMetaObject*> metaObjects;
return metaObjects;
}
QMultiHash<const QMetaObject*, const QMetaObject*>& Bitstream::getMetaObjectSubClasses() {
static QMultiHash<const QMetaObject*, const QMetaObject*> metaObjectSubClasses;
return metaObjectSubClasses;
}
QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
static QHash<int, const TypeStreamer*> typeStreamers;
return typeStreamers;

View file

@ -11,16 +11,19 @@
#include <QHash>
#include <QMetaType>
#include <QScriptString>
#include <QSharedPointer>
#include <QVariant>
#include <QtDebug>
#include <glm/glm.hpp>
#include "SharedObject.h"
class QByteArray;
class QColor;
class QDataStream;
struct QMetaObject;
class QObject;
class QUrl;
class Attribute;
class AttributeValue;
@ -65,6 +68,10 @@ public:
void persistTransientValues(const QHash<int, T>& transientValues);
int takePersistentID(T value) { return _persistentIDs.take(value); }
void removePersistentValue(int id) { _persistentValues.remove(id); }
RepeatedValueStreamer& operator<<(T value);
RepeatedValueStreamer& operator>>(T& value);
@ -126,7 +133,7 @@ template<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::ope
int& offset = _transientOffsets[value];
if (offset == 0) {
_idStreamer << (_lastPersistentID + (offset = ++_lastTransientOffset));
_stream << value;
_stream < value;
} else {
_idStreamer << (_lastPersistentID + offset);
@ -147,7 +154,7 @@ template<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::ope
int offset = id - _lastPersistentID;
typename QHash<int, T>::iterator it = _transientValues.find(offset);
if (it == _transientValues.end()) {
_stream >> value;
_stream > value;
_transientValues.insert(offset, value);
} else {
@ -158,7 +165,9 @@ template<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::ope
}
/// A stream for bit-aligned data.
class Bitstream {
class Bitstream : public QObject {
Q_OBJECT
public:
class WriteMappings {
@ -166,6 +175,8 @@ public:
QHash<const QMetaObject*, int> metaObjectOffsets;
QHash<const TypeStreamer*, int> typeStreamerOffsets;
QHash<AttributePointer, int> attributeOffsets;
QHash<QScriptString, int> scriptStringOffsets;
QHash<SharedObjectPointer, int> sharedObjectOffsets;
};
class ReadMappings {
@ -173,6 +184,8 @@ public:
QHash<int, const QMetaObject*> metaObjectValues;
QHash<int, const TypeStreamer*> typeStreamerValues;
QHash<int, AttributePointer> attributeValues;
QHash<int, QScriptString> scriptStringValues;
QHash<int, SharedObjectPointer> sharedObjectValues;
};
/// Registers a metaobject under its name so that instances of it can be streamed.
@ -183,8 +196,11 @@ public:
/// \return zero; the function only returns a value so that it can be used in static initialization
static int registerTypeStreamer(int type, TypeStreamer* streamer);
/// Returns the list of registered subclasses for the supplied meta-object.
static QList<const QMetaObject*> getMetaObjectSubClasses(const QMetaObject* metaObject);
/// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both.
Bitstream(QDataStream& underlying);
Bitstream(QDataStream& underlying, QObject* parent = NULL);
/// Writes a set of bits to the underlying stream.
/// \param bits the number of bits to write
@ -202,9 +218,6 @@ public:
/// Resets to the initial state.
void reset();
/// Returns a reference to the attribute streamer.
RepeatedValueStreamer<AttributePointer>& getAttributeStreamer() { return _attributeStreamer; }
/// Returns the set of transient mappings gathered during writing and resets them.
WriteMappings getAndResetWriteMappings();
@ -217,6 +230,9 @@ public:
/// Persists a set of read mappings recorded earlier.
void persistReadMappings(const ReadMappings& mappings);
/// Removes a shared object from the read mappings.
void clearSharedObject(int id) { _sharedObjectStreamer.removePersistentValue(id); }
Bitstream& operator<<(bool value);
Bitstream& operator>>(bool& value);
@ -232,9 +248,15 @@ public:
Bitstream& operator<<(const QByteArray& string);
Bitstream& operator>>(QByteArray& string);
Bitstream& operator<<(const QColor& color);
Bitstream& operator>>(QColor& color);
Bitstream& operator<<(const QString& string);
Bitstream& operator>>(QString& string);
Bitstream& operator<<(const QUrl& url);
Bitstream& operator>>(QUrl& url);
Bitstream& operator<<(const QVariant& value);
Bitstream& operator>>(QVariant& value);
@ -244,6 +266,9 @@ public:
template<class T> Bitstream& operator<<(const QList<T>& list);
template<class T> Bitstream& operator>>(QList<T>& list);
template<class K, class V> Bitstream& operator<<(const QHash<K, V>& hash);
template<class K, class V> Bitstream& operator>>(QHash<K, V>& hash);
Bitstream& operator<<(const QObject* object);
Bitstream& operator>>(QObject*& object);
@ -256,6 +281,35 @@ public:
Bitstream& operator<<(const AttributePointer& attribute);
Bitstream& operator>>(AttributePointer& attribute);
Bitstream& operator<<(const QScriptString& string);
Bitstream& operator>>(QScriptString& string);
Bitstream& operator<<(const SharedObjectPointer& object);
Bitstream& operator>>(SharedObjectPointer& object);
Bitstream& operator<(const QMetaObject* metaObject);
Bitstream& operator>(const QMetaObject*& metaObject);
Bitstream& operator<(const TypeStreamer* streamer);
Bitstream& operator>(const TypeStreamer*& streamer);
Bitstream& operator<(const AttributePointer& attribute);
Bitstream& operator>(AttributePointer& attribute);
Bitstream& operator<(const QScriptString& string);
Bitstream& operator>(QScriptString& string);
Bitstream& operator<(const SharedObjectPointer& object);
Bitstream& operator>(SharedObjectPointer& object);
signals:
void sharedObjectCleared(int id);
private slots:
void clearSharedObject();
private:
QDataStream& _underlying;
@ -265,8 +319,11 @@ private:
RepeatedValueStreamer<const QMetaObject*> _metaObjectStreamer;
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
RepeatedValueStreamer<SharedObjectPointer> _sharedObjectStreamer;
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
static QHash<int, const TypeStreamer*>& getTypeStreamers();
};
@ -291,6 +348,32 @@ template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& list) {
return *this;
}
template<class K, class V> inline Bitstream& Bitstream::operator<<(const QHash<K, V>& hash) {
*this << hash.size();
for (typename QHash<K, V>::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) {
*this << it.key();
*this << it.value();
}
return *this;
}
template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>& hash) {
int size;
*this >> size;
hash.clear();
hash.reserve(size);
for (int i = 0; i < size; i++) {
K key;
V value;
*this >> key;
*this >> value;
hash.insertMulti(key, value);
}
return *this;
}
Q_DECLARE_METATYPE(const QMetaObject*)
/// Macro for registering streamable meta-objects.
#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);

View file

@ -10,9 +10,15 @@
#include <QtDebug>
#include "DatagramSequencer.h"
#include <SharedUtil.h>
const int MAX_DATAGRAM_SIZE = 1500;
#include "DatagramSequencer.h"
#include "MetavoxelMessages.h"
// in sequencer parlance, a "packet" may consist of multiple datagrams. clarify when we refer to actual datagrams
const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
const int DEFAULT_MAX_PACKET_SIZE = 3000;
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) :
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
@ -26,13 +32,16 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) :
_incomingPacketNumber(0),
_incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
_inputStream(_incomingPacketStream),
_receivedHighPriorityMessages(0) {
_receivedHighPriorityMessages(0),
_maxPacketSize(DEFAULT_MAX_PACKET_SIZE) {
_outgoingPacketStream.setByteOrder(QDataStream::LittleEndian);
_incomingDatagramStream.setByteOrder(QDataStream::LittleEndian);
_incomingPacketStream.setByteOrder(QDataStream::LittleEndian);
_outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian);
connect(&_outputStream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize);
}
@ -41,6 +50,22 @@ void DatagramSequencer::sendHighPriorityMessage(const QVariant& data) {
_highPriorityMessages.append(message);
}
ReliableChannel* DatagramSequencer::getReliableOutputChannel(int index) {
ReliableChannel*& channel = _reliableOutputChannels[index];
if (!channel) {
channel = new ReliableChannel(this, index, true);
}
return channel;
}
ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
ReliableChannel*& channel = _reliableInputChannels[index];
if (!channel) {
channel = new ReliableChannel(this, index, false);
}
return channel;
}
Bitstream& DatagramSequencer::startPacket() {
// start with the list of acknowledgements
_outgoingPacketStream << (quint32)_receiveRecords.size();
@ -60,7 +85,18 @@ Bitstream& DatagramSequencer::startPacket() {
void DatagramSequencer::endPacket() {
_outputStream.flush();
sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos()));
// if we have space remaining, send some data from our reliable channels
int remaining = _maxPacketSize - _outgoingPacketStream.device()->pos();
const int MINIMUM_RELIABLE_SIZE = sizeof(quint32) * 5; // count, channel number, segment count, offset, size
QVector<ChannelSpan> spans;
if (remaining > MINIMUM_RELIABLE_SIZE) {
appendReliableData(remaining, spans);
} else {
_outgoingPacketStream << (quint32)0;
}
sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos()), spans);
_outgoingPacketStream.device()->seek(0);
}
@ -138,20 +174,30 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
}
// read and dispatch the high-priority messages
quint32 highPriorityMessageCount;
int highPriorityMessageCount;
_incomingPacketStream >> highPriorityMessageCount;
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
for (int i = 0; i < highPriorityMessageCount; i++) {
QVariant data;
_inputStream >> data;
if (i >= _receivedHighPriorityMessages) {
emit receivedHighPriorityMessage(data);
handleHighPriorityMessage(data);
}
}
_receivedHighPriorityMessages = highPriorityMessageCount;
// alert external parties so that they can read the rest
// alert external parties so that they can read the middle
emit readyToRead(_inputStream);
// read the reliable data, if any
quint32 reliableChannels;
_incomingPacketStream >> reliableChannels;
for (int i = 0; i < reliableChannels; i++) {
quint32 channelIndex;
_incomingPacketStream >> channelIndex;
getReliableOutputChannel(channelIndex)->readData(_incomingPacketStream);
}
_incomingPacketStream.device()->seek(0);
_inputStream.reset();
@ -160,6 +206,12 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
_receiveRecords.append(record);
}
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
// for now, high priority
ClearSharedObjectMessage message = { id };
sendHighPriorityMessage(QVariant::fromValue(message));
}
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
// stop acknowledging the recorded packets
while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) {
@ -178,9 +230,46 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
break;
}
}
// acknowledge the received spans
foreach (const ChannelSpan& span, record.spans) {
getReliableOutputChannel(span.channel)->spanAcknowledged(span);
}
}
void DatagramSequencer::sendPacket(const QByteArray& packet) {
void DatagramSequencer::appendReliableData(int bytes, QVector<ChannelSpan>& spans) {
// gather total number of bytes to write, priority
int totalBytes = 0;
float totalPriority = 0.0f;
int totalChannels = 0;
foreach (ReliableChannel* channel, _reliableOutputChannels) {
int channelBytes = channel->getBytesAvailable();
if (channelBytes > 0) {
totalBytes += channelBytes;
totalPriority += channel->getPriority();
totalChannels++;
}
}
_outgoingPacketStream << (quint32)totalChannels;
if (totalChannels == 0) {
return;
}
totalBytes = qMin(bytes, totalBytes);
foreach (ReliableChannel* channel, _reliableOutputChannels) {
int channelBytes = channel->getBytesAvailable();
if (channelBytes == 0) {
continue;
}
_outgoingPacketStream << (quint32)channel->getIndex();
channelBytes = qMin(channelBytes, (int)(totalBytes * channel->getPriority() / totalPriority));
channel->writeData(_outgoingPacketStream, channelBytes, spans);
totalBytes -= channelBytes;
totalPriority -= channel->getPriority();
}
}
void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans) {
QIODeviceOpener opener(&_outgoingDatagramBuffer, QIODevice::WriteOnly);
// increment the packet number
@ -188,7 +277,7 @@ void DatagramSequencer::sendPacket(const QByteArray& packet) {
// record the send
SendRecord record = { _outgoingPacketNumber, _receiveRecords.isEmpty() ? 0 : _receiveRecords.last().packetNumber,
_outputStream.getAndResetWriteMappings() };
_outputStream.getAndResetWriteMappings(), spans };
_sendRecords.append(record);
// write the sequence number and size, which are the same between all fragments
@ -213,3 +302,228 @@ void DatagramSequencer::sendPacket(const QByteArray& packet) {
} while(offset < packet.size());
}
void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
if (data.userType() == ClearSharedObjectMessage::Type) {
_inputStream.clearSharedObject(data.value<ClearSharedObjectMessage>().id);
} else {
emit receivedHighPriorityMessage(data);
}
}
SpanList::SpanList() : _totalSet(0) {
}
int SpanList::set(int offset, int length) {
// if we intersect the front of the list, consume beginning spans and return advancement
if (offset <= 0) {
int intersection = offset + length;
return (intersection > 0) ? setSpans(_spans.begin(), intersection) : 0;
}
// look for an intersection within the list
int position = 0;
for (QList<Span>::iterator it = _spans.begin(); it != _spans.end(); it++) {
// if we intersect the unset portion, contract it
position += it->unset;
if (offset <= position) {
int remove = position - offset;
it->unset -= remove;
// if we continue into the set portion, expand it and consume following spans
int extra = offset + length - position;
if (extra >= 0) {
int amount = setSpans(it + 1, extra);
it->set += amount;
_totalSet += amount;
// otherwise, insert a new span
} else {
Span span = { it->unset, length + extra };
_spans.insert(it, span);
it->unset = -extra;
_totalSet += span.set;
}
return 0;
}
// if we intersect the set portion, expand it and consume following spans
position += it->set;
if (offset <= position) {
int extra = offset + length - position;
int amount = setSpans(it + 1, extra);
it->set += amount;
_totalSet += amount;
return 0;
}
}
// add to end of list
Span span = { offset - position, length };
_spans.append(span);
_totalSet += length;
return 0;
}
int SpanList::setSpans(QList<Span>::iterator it, int length) {
int remainingLength = length;
int totalRemoved = 0;
for (; it != _spans.end(); it++) {
if (remainingLength < it->unset) {
it->unset -= remainingLength;
totalRemoved += remainingLength;
break;
}
int combined = it->unset + it->set;
remainingLength = qMax(remainingLength - combined, 0);
totalRemoved += combined;
it = _spans.erase(it);
_totalSet -= it->set;
}
return qMax(length, totalRemoved);
}
int ReliableChannel::getBytesAvailable() const {
return _buffer.size() - _acknowledged.getTotalSet();
}
void ReliableChannel::sendMessage(const QVariant& message) {
_bitstream << message;
}
void ReliableChannel::sendClearSharedObjectMessage(int id) {
ClearSharedObjectMessage message = { id };
sendMessage(QVariant::fromValue(message));
}
ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) :
QObject(sequencer),
_index(index),
_dataStream(&_buffer),
_bitstream(_dataStream),
_priority(1.0f),
_offset(0),
_writePosition(0) {
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
_dataStream.setByteOrder(QDataStream::LittleEndian);
connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
}
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
// find out how many spans we want to write
int spanCount = 0;
int remainingBytes = bytes;
bool first = true;
while (remainingBytes > 0) {
int position = 0;
foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
if (remainingBytes <= 0) {
break;
}
spanCount++;
remainingBytes -= getBytesToWrite(first, span.unset);
position += (span.unset + span.set);
}
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
spanCount++;
remainingBytes -= getBytesToWrite(first, leftover);
}
}
// write the count and the spans
out << (quint32)spanCount;
remainingBytes = bytes;
first = true;
while (remainingBytes > 0) {
int position = 0;
foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
if (remainingBytes <= 0) {
break;
}
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans);
position += (span.unset + span.set);
}
if (remainingBytes > 0 && position < _buffer.pos()) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, (int)(_buffer.pos() - position)), spans);
}
}
}
int ReliableChannel::getBytesToWrite(bool& first, int length) const {
if (first) {
first = false;
return length - (_writePosition % length);
}
return length;
}
int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
if (first) {
first = false;
position = _writePosition % length;
length -= position;
_writePosition += length;
}
DatagramSequencer::ChannelSpan span = { _index, _offset + position, length };
spans.append(span);
out << (quint32)span.offset;
out << (quint32)length;
out.writeRawData(_buffer.data().constData() + position, length);
return length;
}
void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& span) {
int advancement = _acknowledged.set(span.offset - _offset, span.length);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - advancement);
_buffer.seek(_buffer.size());
_offset += advancement;
_writePosition = qMax(_writePosition - advancement, 0);
}
}
void ReliableChannel::readData(QDataStream& in) {
quint32 segments;
in >> segments;
for (int i = 0; i < segments; i++) {
quint32 offset, size;
in >> offset >> size;
int position = offset - _offset;
int end = position + size;
if (_assemblyBuffer.size() < end) {
_assemblyBuffer.resize(end);
}
if (end <= 0) {
in.skipRawData(size);
} else if (position < 0) {
in.skipRawData(-position);
in.readRawData(_assemblyBuffer.data(), size + position);
} else {
in.readRawData(_assemblyBuffer.data() + position, size);
}
int advancement = _acknowledged.set(position, size);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer().append(_assemblyBuffer.constData(), advancement);
emit _buffer.readyRead();
_assemblyBuffer = _assemblyBuffer.right(_assemblyBuffer.size() - advancement);
_offset += advancement;
}
}
// when the read head is sufficiently advanced into the buffer, prune it off. this along
// with other buffer usages should be replaced with a circular buffer
const int PRUNE_SIZE = 8192;
if (_buffer.pos() > PRUNE_SIZE) {
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - _buffer.pos());
_buffer.seek(0);
}
}

View file

@ -14,9 +14,12 @@
#include <QByteArray>
#include <QList>
#include <QSet>
#include <QVector>
#include "Bitstream.h"
class ReliableChannel;
/// Performs simple datagram sequencing, packet fragmentation and reassembly.
class DatagramSequencer : public QObject {
Q_OBJECT
@ -46,6 +49,18 @@ public:
/// Returns a reference to the list of high priority messages not yet acknowledged.
const QList<HighPriorityMessage>& getHighPriorityMessages() const { return _highPriorityMessages; }
/// Sets the maximum packet size. This is a soft limit that determines how much
/// reliable data we include with each transmission.
void setMaxPacketSize(int maxPacketSize) { _maxPacketSize = maxPacketSize; }
int getMaxPacketSize() const { return _maxPacketSize; }
/// Returns the output channel at the specified index, creating it if necessary.
ReliableChannel* getReliableOutputChannel(int index = 0);
/// Returns the intput channel at the
ReliableChannel* getReliableInputChannel(int index = 0);
/// Starts a new packet for transmission.
/// \return a reference to the Bitstream to use for writing to the packet
Bitstream& startPacket();
@ -75,14 +90,28 @@ signals:
/// Emitted when our acknowledgement of a received packet has been acknowledged by the remote side.
/// \param index the index of the packet in our list of receive records
void receiveAcknowledged(int index);
private slots:
void sendClearSharedObjectMessage(int id);
private:
friend class ReliableChannel;
class ChannelSpan {
public:
int channel;
int offset;
int length;
};
class SendRecord {
public:
int packetNumber;
int lastReceivedPacketNumber;
Bitstream::WriteMappings mappings;
QVector<ChannelSpan> spans;
};
class ReceiveRecord {
@ -97,9 +126,14 @@ private:
/// Notes that the described send was acknowledged by the other party.
void sendRecordAcknowledged(const SendRecord& record);
/// Appends some reliable data to the outgoing packet.
void appendReliableData(int bytes, QVector<ChannelSpan>& spans);
/// Sends a packet to the other party, fragmenting it into multiple datagrams (and emitting
/// readyToWrite) as necessary.
void sendPacket(const QByteArray& packet);
void sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans);
void handleHighPriorityMessage(const QVariant& data);
QList<SendRecord> _sendRecords;
QList<ReceiveRecord> _receiveRecords;
@ -126,6 +160,92 @@ private:
QList<HighPriorityMessage> _highPriorityMessages;
int _receivedHighPriorityMessages;
int _maxPacketSize;
QHash<int, ReliableChannel*> _reliableOutputChannels;
QHash<int, ReliableChannel*> _reliableInputChannels;
};
/// A list of contiguous spans, alternating between set and unset. Conceptually, the list is preceeded by a set
/// span of infinite length and followed by an unset span of infinite length. Within those bounds, it alternates
/// between unset and set.
class SpanList {
public:
class Span {
public:
int unset;
int set;
};
SpanList();
const QList<Span>& getSpans() const { return _spans; }
/// Returns the total length set.
int getTotalSet() const { return _totalSet; }
/// Sets a region of the list.
/// \return the advancement of the set length at the beginning of the list
int set(int offset, int length);
private:
/// Sets the spans starting at the specified iterator, consuming at least the given length.
/// \return the actual amount set, which may be greater if we ran into an existing set span
int setSpans(QList<Span>::iterator it, int length);
QList<Span> _spans;
int _totalSet;
};
/// Represents a single reliable channel multiplexed onto the datagram sequence.
class ReliableChannel : public QObject {
Q_OBJECT
public:
int getIndex() const { return _index; }
QDataStream& getDataStream() { return _dataStream; }
Bitstream& getBitstream() { return _bitstream; }
void setPriority(float priority) { _priority = priority; }
float getPriority() const { return _priority; }
int getBytesAvailable() const;
void sendMessage(const QVariant& message);
private slots:
void sendClearSharedObjectMessage(int id);
private:
friend class DatagramSequencer;
ReliableChannel(DatagramSequencer* sequencer, int index, bool output);
void writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans);
int getBytesToWrite(bool& first, int length) const;
int writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
void spanAcknowledged(const DatagramSequencer::ChannelSpan& span);
void readData(QDataStream& in);
int _index;
QBuffer _buffer;
QByteArray _assemblyBuffer;
QDataStream _dataStream;
Bitstream _bitstream;
float _priority;
int _offset;
int _writePosition;
SpanList _acknowledged;
};
#endif /* defined(__interface__DatagramSequencer__) */

View file

@ -6,11 +6,18 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <QDateTime>
#include <QScriptEngine>
#include <QtDebug>
#include "MetavoxelData.h"
#include "MetavoxelUtil.h"
#include "ScriptCache.h"
REGISTER_META_OBJECT(MetavoxelGuide)
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
REGISTER_META_OBJECT(ScriptedMetavoxelGuide)
REGISTER_META_OBJECT(ThrobbingMetavoxelGuide)
MetavoxelData::MetavoxelData() : _size(1.0f) {
}
@ -61,7 +68,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
firstVisitation.outputNodes[i] = node;
}
static_cast<MetavoxelGuide*>(firstVisitation.info.inputValues.last().getInlineValue<
PolymorphicDataPointer>().data())->guide(firstVisitation);
SharedObjectPointer>().data())->guide(firstVisitation);
for (int i = 0; i < outputs.size(); i++) {
AttributeValue& value = firstVisitation.info.outputValues[i];
if (!value.getAttribute()) {
@ -129,7 +136,7 @@ void MetavoxelData::read(Bitstream& in) {
in >> rootCount;
for (int i = 0; i < rootCount; i++) {
AttributePointer attribute;
in.getAttributeStreamer() >> attribute;
in >> attribute;
MetavoxelNode*& root = _roots[attribute];
root = new MetavoxelNode(attribute);
root->read(attribute, in);
@ -140,7 +147,7 @@ void MetavoxelData::write(Bitstream& out) const {
out << _size;
out << _roots.size();
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
out.getAttributeStreamer() << it.key();
out << it.key();
it.value()->write(it.key(), out);
}
}
@ -169,7 +176,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) {
in >> changedCount;
for (int i = 0; i < changedCount; i++) {
AttributePointer attribute;
in.getAttributeStreamer() >> attribute;
in >> attribute;
MetavoxelNode*& root = _roots[attribute];
if (root) {
MetavoxelNode* oldRoot = root;
@ -187,7 +194,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) {
in >> removedCount;
for (int i = 0; i < removedCount; i++) {
AttributePointer attribute;
in.getAttributeStreamer() >> attribute;
in >> attribute;
_roots.take(attribute)->decrementReferenceCount(attribute);
}
}
@ -227,7 +234,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) c
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
MetavoxelNode* referenceRoot = expandedReference->_roots.value(it.key());
if (it.value() != referenceRoot) {
out.getAttributeStreamer() << it.key();
out << it.key();
if (referenceRoot) {
it.value()->writeDelta(it.key(), *referenceRoot, out);
} else {
@ -248,7 +255,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) c
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = expandedReference->_roots.constBegin();
it != expandedReference->_roots.constEnd(); it++) {
if (!_roots.contains(it.key())) {
out.getAttributeStreamer() << it.key();
out << it.key();
}
}
@ -425,8 +432,7 @@ MetavoxelVisitor::MetavoxelVisitor(const QVector<AttributePointer>& inputs, cons
MetavoxelVisitor::~MetavoxelVisitor() {
}
PolymorphicData* DefaultMetavoxelGuide::clone() const {
return new DefaultMetavoxelGuide();
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
}
void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
@ -470,7 +476,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
(i & Y_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f,
(i & Z_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f);
static_cast<MetavoxelGuide*>(nextVisitation.info.inputValues.last().getInlineValue<
PolymorphicDataPointer>().data())->guide(nextVisitation);
SharedObjectPointer>().data())->guide(nextVisitation);
for (int j = 0; j < nextVisitation.outputNodes.size(); j++) {
AttributeValue& value = nextVisitation.info.outputValues[j];
if (!value.getAttribute()) {
@ -514,6 +520,25 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
}
}
ThrobbingMetavoxelGuide::ThrobbingMetavoxelGuide() : _rate(10.0) {
}
void ThrobbingMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
AttributePointer colorAttribute = AttributeRegistry::getInstance()->getColorAttribute();
for (int i = 0; i < visitation.info.inputValues.size(); i++) {
AttributeValue& attributeValue = visitation.info.inputValues[i];
if (attributeValue.getAttribute() == colorAttribute) {
QRgb base = attributeValue.getInlineValue<QRgb>();
double seconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
double amplitude = sin(_rate * seconds) * 0.5 + 0.5;
attributeValue.setInlineValue<QRgb>(qRgba(qRed(base) * amplitude, qGreen(base) * amplitude,
qBlue(base) * amplitude, qAlpha(base)));
}
}
DefaultMetavoxelGuide::guide(visitation);
}
static QScriptValue getAttributes(QScriptEngine* engine, ScriptedMetavoxelGuide* guide,
const QVector<AttributePointer>& attributes) {
@ -569,35 +594,47 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin
return result;
}
ScriptedMetavoxelGuide::ScriptedMetavoxelGuide(const QScriptValue& guideFunction) :
_guideFunction(guideFunction),
_minimumHandle(guideFunction.engine()->toStringHandle("minimum")),
_sizeHandle(guideFunction.engine()->toStringHandle("size")),
_inputValuesHandle(guideFunction.engine()->toStringHandle("inputValues")),
_outputValuesHandle(guideFunction.engine()->toStringHandle("outputValues")),
_isLeafHandle(guideFunction.engine()->toStringHandle("isLeaf")),
_getInputsFunction(guideFunction.engine()->newFunction(getInputs, 0)),
_getOutputsFunction(guideFunction.engine()->newFunction(getOutputs, 0)),
_visitFunction(guideFunction.engine()->newFunction(visit, 1)),
_info(guideFunction.engine()->newObject()),
_minimum(guideFunction.engine()->newArray(3)) {
_arguments.append(guideFunction.engine()->newObject());
QScriptValue visitor = guideFunction.engine()->newObject();
visitor.setProperty("getInputs", _getInputsFunction);
visitor.setProperty("getOutputs", _getOutputsFunction);
visitor.setProperty("visit", _visitFunction);
_arguments[0].setProperty("visitor", visitor);
_arguments[0].setProperty("info", _info);
_info.setProperty(_minimumHandle, _minimum);
}
PolymorphicData* ScriptedMetavoxelGuide::clone() const {
return new ScriptedMetavoxelGuide(_guideFunction);
ScriptedMetavoxelGuide::ScriptedMetavoxelGuide() {
}
void ScriptedMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
QScriptValue data = _guideFunction.engine()->newVariant(QVariant::fromValue<void*>(this));
QScriptValue guideFunction;
if (_guideFunction) {
guideFunction = _guideFunction->getValue();
} else if (_url.isValid()) {
_guideFunction = ScriptCache::getInstance()->getValue(_url);
guideFunction = _guideFunction->getValue();
}
if (!guideFunction.isValid()) {
// before we load, just use the default behavior
DefaultMetavoxelGuide::guide(visitation);
return;
}
QScriptEngine* engine = guideFunction.engine();
if (!_minimumHandle.isValid()) {
_minimumHandle = engine->toStringHandle("minimum");
_sizeHandle = engine->toStringHandle("size");
_inputValuesHandle = engine->toStringHandle("inputValues");
_outputValuesHandle = engine->toStringHandle("outputValues");
_isLeafHandle = engine->toStringHandle("isLeaf");
_getInputsFunction = engine->newFunction(getInputs, 0);
_getOutputsFunction = engine->newFunction(getOutputs, 0);
_visitFunction = engine->newFunction(visit, 1);
_info = engine->newObject();
_minimum = engine->newArray(3);
_arguments.clear();
_arguments.append(engine->newObject());
QScriptValue visitor = engine->newObject();
visitor.setProperty("getInputs", _getInputsFunction);
visitor.setProperty("getOutputs", _getOutputsFunction);
visitor.setProperty("visit", _visitFunction);
_arguments[0].setProperty("visitor", visitor);
_arguments[0].setProperty("info", _info);
_info.setProperty(_minimumHandle, _minimum);
}
QScriptValue data = engine->newVariant(QVariant::fromValue<void*>(this));
_getInputsFunction.setData(data);
_visitFunction.setData(data);
_minimum.setProperty(0, visitation.info.minimum.x);
@ -606,12 +643,18 @@ void ScriptedMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
_info.setProperty(_sizeHandle, visitation.info.size);
_info.setProperty(_isLeafHandle, visitation.info.isLeaf);
_visitation = &visitation;
_guideFunction.call(QScriptValue(), _arguments);
if (_guideFunction.engine()->hasUncaughtException()) {
qDebug() << "Script error: " << _guideFunction.engine()->uncaughtException().toString();
guideFunction.call(QScriptValue(), _arguments);
if (engine->hasUncaughtException()) {
qDebug() << "Script error: " << engine->uncaughtException().toString();
}
}
void ScriptedMetavoxelGuide::setURL(const ParameterizedURL& url) {
_url = url;
_guideFunction.reset();
_minimumHandle = QScriptString();
}
bool MetavoxelVisitation::allInputNodesLeaves() const {
foreach (MetavoxelNode* node, inputNodes) {
if (node != NULL && !node->isLeaf()) {

View file

@ -12,6 +12,7 @@
#include <QBitArray>
#include <QHash>
#include <QSharedData>
#include <QSharedPointer>
#include <QScriptString>
#include <QScriptValue>
#include <QVector>
@ -19,13 +20,14 @@
#include <glm/glm.hpp>
#include "AttributeRegistry.h"
#include "MetavoxelUtil.h"
class QScriptContext;
class Box;
class MetavoxelNode;
class MetavoxelVisitation;
class MetavoxelVisitor;
class NetworkValue;
/// The base metavoxel representation shared between server and client.
class MetavoxelData {
@ -150,7 +152,9 @@ protected:
typedef QSharedPointer<MetavoxelVisitor> MetavoxelVisitorPointer;
/// Interface for objects that guide metavoxel visitors.
class MetavoxelGuide : public PolymorphicData {
class MetavoxelGuide : public SharedObject {
Q_OBJECT
public:
/// Guides the specified visitor to the contained voxels.
@ -159,30 +163,56 @@ public:
/// Guides visitors through the explicit content of the system.
class DefaultMetavoxelGuide : public MetavoxelGuide {
Q_OBJECT
public:
virtual PolymorphicData* clone() const;
Q_INVOKABLE DefaultMetavoxelGuide();
virtual void guide(MetavoxelVisitation& visitation);
};
/// Represents a guide implemented in Javascript.
class ScriptedMetavoxelGuide : public MetavoxelGuide {
/// A temporary test guide that just makes the existing voxels throb with delight.
class ThrobbingMetavoxelGuide : public DefaultMetavoxelGuide {
Q_OBJECT
Q_PROPERTY(float rate MEMBER _rate)
public:
ScriptedMetavoxelGuide(const QScriptValue& guideFunction);
virtual PolymorphicData* clone() const;
Q_INVOKABLE ThrobbingMetavoxelGuide();
virtual void guide(MetavoxelVisitation& visitation);
private:
float _rate;
};
/// Represents a guide implemented in Javascript.
class ScriptedMetavoxelGuide : public DefaultMetavoxelGuide {
Q_OBJECT
Q_PROPERTY(ParameterizedURL url MEMBER _url WRITE setURL)
public:
Q_INVOKABLE ScriptedMetavoxelGuide();
virtual void guide(MetavoxelVisitation& visitation);
public slots:
void setURL(const ParameterizedURL& url);
private:
static QScriptValue getInputs(QScriptContext* context, QScriptEngine* engine);
static QScriptValue getOutputs(QScriptContext* context, QScriptEngine* engine);
static QScriptValue visit(QScriptContext* context, QScriptEngine* engine);
QScriptValue _guideFunction;
ParameterizedURL _url;
QSharedPointer<NetworkValue> _guideFunction;
QScriptString _minimumHandle;
QScriptString _sizeHandle;
QScriptString _inputValuesHandle;

View file

@ -21,6 +21,17 @@ class CloseSessionMessage {
DECLARE_STREAMABLE_METATYPE(CloseSessionMessage)
/// Clears the mapping for a shared object.
class ClearSharedObjectMessage {
STREAMABLE
public:
STREAM int id;
};
DECLARE_STREAMABLE_METATYPE(ClearSharedObjectMessage)
/// A message containing the state of a client.
class ClientStateMessage {
STREAMABLE

View file

@ -7,14 +7,106 @@
//
#include <QByteArray>
#include <QColorDialog>
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QItemEditorFactory>
#include <QLineEdit>
#include <QMetaType>
#include <QPushButton>
#include <QScriptEngine>
#include <QStandardItemEditorCreator>
#include <QVBoxLayout>
#include <QtDebug>
#include <HifiSockAddr.h>
#include <PacketHeaders.h>
#include "MetavoxelUtil.h"
#include "ScriptCache.h"
QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize) {
static int scriptHashType = qRegisterMetaType<ScriptHash>();
static int parameterizedURLType = qRegisterMetaType<ParameterizedURL>();
REGISTER_SIMPLE_TYPE_STREAMER(ScriptHash)
REGISTER_SIMPLE_TYPE_STREAMER(ParameterizedURL)
class DelegatingItemEditorFactory : public QItemEditorFactory {
public:
DelegatingItemEditorFactory();
virtual QWidget* createEditor(int userType, QWidget* parent) const;
virtual QByteArray valuePropertyName(int userType) const;
private:
const QItemEditorFactory* _parentFactory;
};
class DoubleEditor : public QDoubleSpinBox {
public:
DoubleEditor(QWidget* parent = NULL);
};
DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) {
setMinimum(-FLT_MAX);
}
DelegatingItemEditorFactory::DelegatingItemEditorFactory() :
_parentFactory(QItemEditorFactory::defaultFactory()) {
QItemEditorFactory::setDefaultFactory(this);
}
QWidget* DelegatingItemEditorFactory::createEditor(int userType, QWidget* parent) const {
QWidget* editor = QItemEditorFactory::createEditor(userType, parent);
return (editor == NULL) ? _parentFactory->createEditor(userType, parent) : editor;
}
QByteArray DelegatingItemEditorFactory::valuePropertyName(int userType) const {
QByteArray propertyName = QItemEditorFactory::valuePropertyName(userType);
return propertyName.isNull() ? _parentFactory->valuePropertyName(userType) : propertyName;
}
static QItemEditorFactory* getItemEditorFactory() {
static QItemEditorFactory* factory = new DelegatingItemEditorFactory();
return factory;
}
static QItemEditorCreatorBase* createDoubleEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<DoubleEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<double>(), creator);
getItemEditorFactory()->registerEditor(qMetaTypeId<float>(), creator);
return creator;
}
static QItemEditorCreatorBase* createQColorEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<QColorEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<QColor>(), creator);
return creator;
}
static QItemEditorCreatorBase* createVec3EditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<Vec3Editor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::vec3>(), creator);
return creator;
}
static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<ParameterizedURLEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<ParameterizedURL>(), creator);
return creator;
}
static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator();
static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator();
static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator();
static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator();
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize) {
// get the header size
int headerSize = numBytesForPacketHeader(data);
@ -22,7 +114,7 @@ QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& hea
const int UUID_BYTES = 16;
headerPlusIDSize = headerSize + UUID_BYTES;
if (data.size() < headerPlusIDSize) {
qWarning() << "Metavoxel data too short [size=" << data.size() << ", sender=" << sender << "]\n";
qWarning() << "Metavoxel data too short [size=" << data.size() << ", sendingNode=" << sendingNode << "]\n";
return QUuid();
}
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
@ -33,3 +125,185 @@ bool Box::contains(const Box& other) const {
other.minimum.y >= minimum.y && other.maximum.y <= maximum.y &&
other.minimum.z >= minimum.z && other.maximum.z <= maximum.z;
}
QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(QMargins());
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
layout->addWidget(_button = new QPushButton());
connect(_button, SIGNAL(clicked()), SLOT(selectColor()));
}
void QColorEditor::setColor(const QColor& color) {
QString name = (_color = color).name();
_button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name()));
_button->setText(name);
}
void QColorEditor::selectColor() {
QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel);
if (color.isValid()) {
setColor(color);
emit colorChanged(color);
}
}
Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) {
QHBoxLayout* layout = new QHBoxLayout();
layout->setContentsMargins(QMargins());
setLayout(layout);
layout->addWidget(_x = createComponentBox());
layout->addWidget(_y = createComponentBox());
layout->addWidget(_z = createComponentBox());
}
void Vec3Editor::setVector(const glm::vec3& vector) {
_vector = vector;
_x->setValue(vector.x);
_y->setValue(vector.y);
_z->setValue(vector.z);
}
void Vec3Editor::updateVector() {
emit vectorChanged(_vector = glm::vec3(_x->value(), _y->value(), _z->value()));
}
QDoubleSpinBox* Vec3Editor::createComponentBox() {
QDoubleSpinBox* box = new QDoubleSpinBox();
box->setMinimum(-FLT_MAX);
box->setMaximumWidth(100);
connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector()));
return box;
}
ParameterizedURL::ParameterizedURL(const QUrl& url, const ScriptHash& parameters) :
_url(url),
_parameters(parameters) {
}
bool ParameterizedURL::operator==(const ParameterizedURL& other) const {
return _url == other._url && _parameters == other._parameters;
}
bool ParameterizedURL::operator!=(const ParameterizedURL& other) const {
return _url != other._url || _parameters != other._parameters;
}
uint qHash(const ParameterizedURL& url, uint seed) {
// just hash on the URL, for now
return qHash(url.getURL(), seed);
}
Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url) {
out << url.getURL();
out << url.getParameters();
return out;
}
Bitstream& operator>>(Bitstream& in, ParameterizedURL& url) {
QUrl qurl;
in >> qurl;
ScriptHash parameters;
in >> parameters;
url = ParameterizedURL(qurl, parameters);
return in;
}
ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(QMargins());
setLayout(layout);
QWidget* lineContainer = new QWidget();
layout->addWidget(lineContainer);
QHBoxLayout* lineLayout = new QHBoxLayout();
lineContainer->setLayout(lineLayout);
lineLayout->setContentsMargins(QMargins());
lineLayout->addWidget(_line = new QLineEdit(), 1);
connect(_line, SIGNAL(textChanged(const QString&)), SLOT(updateURL()));
QPushButton* refresh = new QPushButton("...");
connect(refresh, SIGNAL(clicked(bool)), SLOT(updateParameters()));
lineLayout->addWidget(refresh);
}
void ParameterizedURLEditor::setURL(const ParameterizedURL& url) {
_url = url;
_line->setText(url.getURL().toString());
updateParameters();
}
void ParameterizedURLEditor::updateURL() {
ScriptHash parameters;
if (layout()->count() > 1) {
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
for (int i = 0; i < form->rowCount(); i++) {
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
QByteArray valuePropertyName = widget->property("valuePropertyName").toByteArray();
const QMetaObject* widgetMetaObject = widget->metaObject();
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
parameters.insert(ScriptCache::getInstance()->getEngine()->toStringHandle(
widget->property("parameterName").toString()), widgetProperty.read(widget));
}
}
emit urlChanged(_url = ParameterizedURL(_line->text(), parameters));
if (_program) {
_program->disconnect(this);
}
}
void ParameterizedURLEditor::updateParameters() {
if (_program) {
_program->disconnect(this);
}
_program = ScriptCache::getInstance()->getProgram(_url.getURL());
if (_program->isLoaded()) {
continueUpdatingParameters();
} else {
connect(_program.data(), SIGNAL(loaded()), SLOT(continueUpdatingParameters()));
}
}
void ParameterizedURLEditor::continueUpdatingParameters() {
QVBoxLayout* layout = static_cast<QVBoxLayout*>(this->layout());
if (layout->count() > 1) {
QFormLayout* form = static_cast<QFormLayout*>(layout->takeAt(1));
for (int i = form->count() - 1; i >= 0; i--) {
QLayoutItem* item = form->takeAt(i);
if (item->widget()) {
delete item->widget();
}
delete item;
}
delete form;
}
QSharedPointer<NetworkValue> value = ScriptCache::getInstance()->getValue(_url.getURL());
const QList<ParameterInfo>& parameters = static_cast<RootNetworkValue*>(value.data())->getParameterInfo();
if (parameters.isEmpty()) {
return;
}
QFormLayout* form = new QFormLayout();
layout->addLayout(form);
foreach (const ParameterInfo& parameter, parameters) {
QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(parameter.type, NULL);
if (widget) {
form->addRow(parameter.name.toString() + ":", widget);
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(parameter.type);
widget->setProperty("parameterName", parameter.name.toString());
widget->setProperty("valuePropertyName", valuePropertyName);
const QMetaObject* widgetMetaObject = widget->metaObject();
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, _url.getParameters().value(parameter.name));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(updateURL()));
}
}
}
}

View file

@ -9,18 +9,29 @@
#ifndef __interface__MetavoxelUtil__
#define __interface__MetavoxelUtil__
#include <QColor>
#include <QSharedPointer>
#include <QUrl>
#include <QUuid>
#include <QWidget>
#include <NodeList.h>
#include <RegisteredMetaTypes.h>
#include "Bitstream.h"
class QByteArray;
class QDoubleSpinBox;
class QLineEdit;
class QPushButton;
class HifiSockAddr;
class NetworkProgram;
/// Reads and returns the session ID from a datagram.
/// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize);
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize);
/// A streamable axis-aligned bounding box.
class Box {
@ -36,4 +47,127 @@ public:
DECLARE_STREAMABLE_METATYPE(Box)
/// Editor for color values.
class QColorEditor : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged USER true)
public:
QColorEditor(QWidget* parent);
signals:
void colorChanged(const QColor& color);
public slots:
void setColor(const QColor& color);
private slots:
void selectColor();
private:
QPushButton* _button;
QColor _color;
};
/// Editor for vector values.
class Vec3Editor : public QWidget {
Q_OBJECT
Q_PROPERTY(glm::vec3 vector MEMBER _vector WRITE setVector NOTIFY vectorChanged USER true)
public:
Vec3Editor(QWidget* parent);
signals:
void vectorChanged(const glm::vec3& vector);
public slots:
void setVector(const glm::vec3& vector);
private slots:
void updateVector();
private:
QDoubleSpinBox* createComponentBox();
QDoubleSpinBox* _x;
QDoubleSpinBox* _y;
QDoubleSpinBox* _z;
glm::vec3 _vector;
};
typedef QHash<QScriptString, QVariant> ScriptHash;
Q_DECLARE_METATYPE(ScriptHash)
/// Combines a URL with a set of typed parameters.
class ParameterizedURL {
public:
ParameterizedURL(const QUrl& url = QUrl(), const ScriptHash& parameters = ScriptHash());
bool isValid() const { return _url.isValid(); }
void setURL(const QUrl& url) { _url = url; }
const QUrl& getURL() const { return _url; }
void setParameters(const ScriptHash& parameters) { _parameters = parameters; }
const ScriptHash& getParameters() const { return _parameters; }
bool operator==(const ParameterizedURL& other) const;
bool operator!=(const ParameterizedURL& other) const;
private:
QUrl _url;
ScriptHash _parameters;
};
uint qHash(const ParameterizedURL& url, uint seed = 0);
Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url);
Bitstream& operator>>(Bitstream& in, ParameterizedURL& url);
Q_DECLARE_METATYPE(ParameterizedURL)
/// Allows editing parameterized URLs.
class ParameterizedURLEditor : public QWidget {
Q_OBJECT
Q_PROPERTY(ParameterizedURL url MEMBER _url WRITE setURL NOTIFY urlChanged USER true)
public:
ParameterizedURLEditor(QWidget* parent = NULL);
signals:
void urlChanged(const ParameterizedURL& url);
public slots:
void setURL(const ParameterizedURL& url);
private slots:
void updateURL();
void updateParameters();
void continueUpdatingParameters();
private:
ParameterizedURL _url;
QSharedPointer<NetworkProgram> _program;
QLineEdit* _line;
};
#endif /* defined(__interface__MetavoxelUtil__) */

View file

@ -0,0 +1,177 @@
//
// ScriptCache.cpp
// metavoxels
//
// Created by Andrzej Kapolka on 2/4/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <cmath>
#include <QNetworkReply>
#include <QScriptEngine>
#include <QTextStream>
#include <QTimer>
#include <QtDebug>
#include "AttributeRegistry.h"
#include "ScriptCache.h"
ScriptCache* ScriptCache::getInstance() {
static ScriptCache cache;
return &cache;
}
ScriptCache::ScriptCache() :
_networkAccessManager(NULL),
_engine(NULL) {
setEngine(new QScriptEngine(this));
}
void ScriptCache::setEngine(QScriptEngine* engine) {
if (_engine && _engine->parent() == this) {
delete _engine;
}
AttributeRegistry::getInstance()->configureScriptEngine(_engine = engine);
_parametersString = engine->toStringHandle("parameters");
_lengthString = engine->toStringHandle("length");
_nameString = engine->toStringHandle("name");
_typeString = engine->toStringHandle("type");
_generatorString = engine->toStringHandle("generator");
}
QSharedPointer<NetworkProgram> ScriptCache::getProgram(const QUrl& url) {
QSharedPointer<NetworkProgram> program = _networkPrograms.value(url);
if (program.isNull()) {
program = QSharedPointer<NetworkProgram>(new NetworkProgram(this, url));
_networkPrograms.insert(url, program);
}
return program;
}
QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url) {
QSharedPointer<NetworkValue> value = _networkValues.value(url);
if (value.isNull()) {
value = QSharedPointer<NetworkValue>(url.getParameters().isEmpty() ?
(NetworkValue*)new RootNetworkValue(getProgram(url.getURL())) :
(NetworkValue*)new DerivedNetworkValue(getValue(url.getURL()), url.getParameters()));
_networkValues.insert(url, value);
}
return value;
}
NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) :
_cache(cache),
_request(url),
_reply(NULL),
_attempts(0) {
if (!url.isValid()) {
return;
}
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
makeRequest();
}
NetworkProgram::~NetworkProgram() {
if (_reply != NULL) {
delete _reply;
}
}
void NetworkProgram::makeRequest() {
QNetworkAccessManager* manager = _cache->getNetworkAccessManager();
if (manager == NULL) {
return;
}
_reply = manager->get(_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
}
void NetworkProgram::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (bytesReceived < bytesTotal && !_reply->isFinished()) {
return;
}
_program = QScriptProgram(QTextStream(_reply).readAll(), _reply->url().toString());
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
emit loaded();
}
void NetworkProgram::handleReplyError() {
QDebug debug = qDebug() << _reply->errorString();
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
debug << " -- retrying...";
}
}
NetworkValue::~NetworkValue() {
}
RootNetworkValue::RootNetworkValue(const QSharedPointer<NetworkProgram>& program) :
_program(program) {
}
QScriptValue& RootNetworkValue::getValue() {
if (!_value.isValid() && _program->isLoaded()) {
_value = _program->getCache()->getEngine()->evaluate(_program->getProgram());
}
return _value;
}
const QList<ParameterInfo>& RootNetworkValue::getParameterInfo() {
if (isLoaded() && _parameterInfo.isEmpty()) {
ScriptCache* cache = _program->getCache();
QScriptEngine* engine = cache->getEngine();
QScriptValue parameters = _value.property(cache->getParametersString());
if (parameters.isArray()) {
int length = parameters.property(cache->getLengthString()).toInt32();
for (int i = 0; i < length; i++) {
QScriptValue parameter = parameters.property(i);
ParameterInfo info = { engine->toStringHandle(parameter.property(cache->getNameString()).toString()),
QMetaType::type(parameter.property(cache->getTypeString()).toString().toUtf8().constData()) };
_parameterInfo.append(info);
}
}
}
return _parameterInfo;
}
DerivedNetworkValue::DerivedNetworkValue(const QSharedPointer<NetworkValue>& baseValue, const ScriptHash& parameters) :
_baseValue(baseValue),
_parameters(parameters) {
}
QScriptValue& DerivedNetworkValue::getValue() {
if (!_value.isValid() && _baseValue->isLoaded()) {
RootNetworkValue* root = static_cast<RootNetworkValue*>(_baseValue.data());
ScriptCache* cache = root->getProgram()->getCache();
QScriptValue generator = _baseValue->getValue().property(cache->getGeneratorString());
if (generator.isFunction()) {
QScriptValueList arguments;
foreach (const ParameterInfo& parameter, root->getParameterInfo()) {
arguments.append(cache->getEngine()->newVariant(_parameters.value(parameter.name)));
}
_value = generator.call(QScriptValue(), arguments);
} else {
_value = _baseValue->getValue();
}
}
return _value;
}

View file

@ -0,0 +1,159 @@
//
// ScriptCache.h
// metavoxels
//
// Created by Andrzej Kapolka on 2/4/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__ScriptCache__
#define __interface__ScriptCache__
#include <QHash>
#include <QList>
#include <QNetworkRequest>
#include <QObject>
#include <QScriptProgram>
#include <QScriptValue>
#include <QSharedPointer>
#include <QWeakPointer>
#include "MetavoxelUtil.h"
class QNetworkAccessManager;
class QNetworkReply;
class QScriptEngine;
class NetworkProgram;
class NetworkValue;
/// Maintains a cache of loaded scripts.
class ScriptCache : public QObject {
Q_OBJECT
public:
static ScriptCache* getInstance();
ScriptCache();
void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; }
QNetworkAccessManager* getNetworkAccessManager() const { return _networkAccessManager; }
void setEngine(QScriptEngine* engine);
QScriptEngine* getEngine() const { return _engine; }
/// Loads a script program from the specified URL.
QSharedPointer<NetworkProgram> getProgram(const QUrl& url);
/// Loads a script value from the specified URL.
QSharedPointer<NetworkValue> getValue(const ParameterizedURL& url);
const QScriptString& getParametersString() const { return _parametersString; }
const QScriptString& getLengthString() const { return _lengthString; }
const QScriptString& getNameString() const { return _nameString; }
const QScriptString& getTypeString() const { return _typeString; }
const QScriptString& getGeneratorString() const { return _generatorString; }
private:
QNetworkAccessManager* _networkAccessManager;
QScriptEngine* _engine;
QHash<QUrl, QWeakPointer<NetworkProgram> > _networkPrograms;
QHash<ParameterizedURL, QWeakPointer<NetworkValue> > _networkValues;
QScriptString _parametersString;
QScriptString _lengthString;
QScriptString _nameString;
QScriptString _typeString;
QScriptString _generatorString;
};
/// A program loaded from the network.
class NetworkProgram : public QObject {
Q_OBJECT
public:
NetworkProgram(ScriptCache* cache, const QUrl& url);
~NetworkProgram();
ScriptCache* getCache() const { return _cache; }
bool isLoaded() const { return !_program.isNull(); }
const QScriptProgram& getProgram() const { return _program; }
signals:
void loaded();
private slots:
void makeRequest();
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleReplyError();
private:
ScriptCache* _cache;
QNetworkRequest _request;
QNetworkReply* _reply;
int _attempts;
QScriptProgram _program;
};
/// Abstract base class of values loaded from the network.
class NetworkValue {
public:
virtual ~NetworkValue();
bool isLoaded() { return getValue().isValid(); }
virtual QScriptValue& getValue() = 0;
protected:
QScriptValue _value;
};
/// Contains information about a script parameter.
class ParameterInfo {
public:
QScriptString name;
int type;
};
/// The direct result of running a program.
class RootNetworkValue : public NetworkValue {
public:
RootNetworkValue(const QSharedPointer<NetworkProgram>& program);
const QSharedPointer<NetworkProgram>& getProgram() const { return _program; }
virtual QScriptValue& getValue();
const QList<ParameterInfo>& getParameterInfo();
private:
QSharedPointer<NetworkProgram> _program;
QList<ParameterInfo> _parameterInfo;
};
/// The result of running a program's generator using a set of arguments.
class DerivedNetworkValue : public NetworkValue {
public:
DerivedNetworkValue(const QSharedPointer<NetworkValue>& baseValue, const ScriptHash& parameters);
virtual QScriptValue& getValue();
private:
QSharedPointer<NetworkValue> _baseValue;
ScriptHash _parameters;
};
#endif /* defined(__interface__ScriptCache__) */

View file

@ -0,0 +1,228 @@
//
// SharedObject.cpp
// metavoxels
//
// Created by Andrzej Kapolka on 2/5/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <QComboBox>
#include <QFormLayout>
#include <QItemEditorFactory>
#include <QMetaProperty>
#include <QVBoxLayout>
#include "Bitstream.h"
#include "SharedObject.h"
SharedObject::SharedObject() : _referenceCount(0) {
}
void SharedObject::incrementReferenceCount() {
_referenceCount++;
}
void SharedObject::decrementReferenceCount() {
if (--_referenceCount == 0) {
delete this;
} else if (_referenceCount == 1) {
emit referenceCountDroppedToOne();
}
}
SharedObject* SharedObject::clone() const {
// default behavior is to make a copy using the no-arg constructor and copy the stored properties
const QMetaObject* metaObject = this->metaObject();
SharedObject* newObject = static_cast<SharedObject*>(metaObject->newInstance());
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (property.isStored()) {
property.write(newObject, property.read(this));
}
}
foreach (const QByteArray& propertyName, dynamicPropertyNames()) {
newObject->setProperty(propertyName, property(propertyName));
}
return newObject;
}
bool SharedObject::equals(const SharedObject* other) const {
// default behavior is to compare the properties
const QMetaObject* metaObject = this->metaObject();
if (metaObject != other->metaObject()) {
return false;
}
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (property.isStored() && property.read(this) != property.read(other)) {
return false;
}
}
QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) {
return false;
}
foreach (const QByteArray& propertyName, dynamicPropertyNames) {
if (property(propertyName) != other->property(propertyName)) {
return false;
}
}
return true;
}
SharedObjectPointer::SharedObjectPointer(SharedObject* data) : _data(data) {
if (_data) {
_data->incrementReferenceCount();
}
}
SharedObjectPointer::SharedObjectPointer(const SharedObjectPointer& other) : _data(other._data) {
if (_data) {
_data->incrementReferenceCount();
}
}
SharedObjectPointer::~SharedObjectPointer() {
if (_data) {
_data->decrementReferenceCount();
}
}
void SharedObjectPointer::detach() {
if (_data && _data->getReferenceCount() > 1) {
_data->decrementReferenceCount();
(_data = _data->clone())->incrementReferenceCount();
}
}
void SharedObjectPointer::reset() {
if (_data) {
_data->decrementReferenceCount();
}
_data = NULL;
}
SharedObjectPointer& SharedObjectPointer::operator=(SharedObject* data) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = data)) {
_data->incrementReferenceCount();
}
return *this;
}
SharedObjectPointer& SharedObjectPointer::operator=(const SharedObjectPointer& other) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = other._data)) {
_data->incrementReferenceCount();
}
return *this;
}
uint qHash(const SharedObjectPointer& pointer, uint seed) {
return qHash(pointer.data(), seed);
}
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent) : QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form);
form->addRow("Type:", _type = new QComboBox());
_type->addItem("(none)");
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) {
_type->addItem(metaObject->className(), QVariant::fromValue(metaObject));
}
connect(_type, SIGNAL(currentIndexChanged(int)), SLOT(updateType()));
}
void SharedObjectEditor::setObject(const SharedObjectPointer& object) {
_object = object;
const QMetaObject* metaObject = object ? object->metaObject() : NULL;
int index = _type->findData(QVariant::fromValue(metaObject));
if (index != -1) {
// ensure that we call updateType to obtain the values
if (_type->currentIndex() == index) {
updateType();
} else {
_type->setCurrentIndex(index);
}
}
}
const QMetaObject* getOwningAncestor(const QMetaObject* metaObject, int propertyIndex) {
while (propertyIndex < metaObject->propertyOffset()) {
metaObject = metaObject->superClass();
}
return metaObject;
}
void SharedObjectEditor::updateType() {
// delete the existing rows
if (layout()->count() > 1) {
QFormLayout* form = static_cast<QFormLayout*>(layout()->takeAt(1));
while (!form->isEmpty()) {
QLayoutItem* item = form->takeAt(0);
if (item->widget()) {
delete item->widget();
}
delete item;
}
delete form;
}
const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value<const QMetaObject*>();
if (metaObject == NULL) {
_object.reset();
return;
}
QObject* oldObject = static_cast<SharedObject*>(_object.data());
const QMetaObject* oldMetaObject = oldObject ? oldObject->metaObject() : NULL;
QObject* newObject = metaObject->newInstance();
QFormLayout* form = new QFormLayout();
static_cast<QVBoxLayout*>(layout())->addLayout(form);
for (int i = QObject::staticMetaObject.propertyCount(); i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (oldMetaObject && i < oldMetaObject->propertyCount() &&
getOwningAncestor(metaObject, i) == getOwningAncestor(oldMetaObject, i)) {
// copy the state of the shared ancestry
property.write(newObject, property.read(oldObject));
}
QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(property.userType(), NULL);
if (widget) {
widget->setProperty("propertyIndex", i);
form->addRow(QByteArray(property.name()) + ':', widget);
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
const QMetaObject* widgetMetaObject = widget->metaObject();
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, property.read(newObject));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(propertyChanged()));
}
}
}
_object = static_cast<SharedObject*>(newObject);
}
void SharedObjectEditor::propertyChanged() {
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
for (int i = 0; i < form->rowCount(); i++) {
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
if (widget != sender()) {
continue;
}
_object.detach();
QObject* object = _object.data();
QMetaProperty property = object->metaObject()->property(widget->property("propertyIndex").toInt());
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
property.write(object, widget->property(valuePropertyName));
}
}

View file

@ -0,0 +1,115 @@
//
// SharedObject.h
// metavoxels
//
// Created by Andrzej Kapolka on 2/5/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__SharedObject__
#define __interface__SharedObject__
#include <QMetaType>
#include <QObject>
#include <QWidget>
class QComboBox;
/// A QObject that may be shared over the network.
class SharedObject : public QObject {
Q_OBJECT
public:
SharedObject();
int getReferenceCount() const { return _referenceCount; }
void incrementReferenceCount();
void decrementReferenceCount();
/// Creates a new clone of this object.
virtual SharedObject* clone() const;
/// Tests this object for equality with another.
virtual bool equals(const SharedObject* other) const;
signals:
/// Emitted when the reference count drops to one.
void referenceCountDroppedToOne();
private:
int _referenceCount;
};
/// A pointer to a shared object.
class SharedObjectPointer {
public:
SharedObjectPointer(SharedObject* data = NULL);
SharedObjectPointer(const SharedObjectPointer& other);
~SharedObjectPointer();
SharedObject* data() { return _data; }
const SharedObject* data() const { return _data; }
const SharedObject* constData() const { return _data; }
void detach();
void swap(SharedObjectPointer& other) { qSwap(_data, other._data); }
void reset();
operator SharedObject*() { return _data; }
operator const SharedObject*() const { return _data; }
bool operator!() const { return !_data; }
bool operator!=(const SharedObjectPointer& other) const { return _data != other._data; }
SharedObject& operator*() { return *_data; }
const SharedObject& operator*() const { return *_data; }
SharedObject* operator->() { return _data; }
const SharedObject* operator->() const { return _data; }
SharedObjectPointer& operator=(SharedObject* data);
SharedObjectPointer& operator=(const SharedObjectPointer& other);
bool operator==(const SharedObjectPointer& other) const { return _data == other._data; }
private:
SharedObject* _data;
};
Q_DECLARE_METATYPE(SharedObjectPointer)
uint qHash(const SharedObjectPointer& pointer, uint seed = 0);
/// Allows editing shared object instances.
class SharedObjectEditor : public QWidget {
Q_OBJECT
Q_PROPERTY(SharedObjectPointer object MEMBER _object WRITE setObject USER true)
public:
SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent);
public slots:
void setObject(const SharedObjectPointer& object);
private slots:
void updateType();
void propertyChanged();
private:
QComboBox* _type;
SharedObjectPointer _object;
};
#endif /* defined(__interface__SharedObject__) */

View file

@ -1,36 +0,0 @@
cmake_minimum_required(VERSION 2.8)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME octree-server)
find_package(Qt5Network REQUIRED)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS})
qt5_use_modules(${TARGET_NAME} Network Widgets)
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link ZLIB
find_package(ZLIB)
include_directories(${ZLIB_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES})
# link in the shared library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
# link in the hifi octree library
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
# link the embedded webserver
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})

View file

@ -45,9 +45,8 @@ bool JurisdictionListener::queueJurisdictionRequest() {
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (nodeList->getNodeActiveSocketOrPing(node.data()) && node->getType() == getNodeType()) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
_packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast<char*>(bufferOut), sizeOut));
if (node->getType() == getNodeType() && node->getActiveSocket()) {
_packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast<char*>(bufferOut), sizeOut));
nodeCount++;
}
}
@ -62,16 +61,13 @@ bool JurisdictionListener::queueJurisdictionRequest() {
return isStillRunning();
}
void JurisdictionListener::processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet) {
void JurisdictionListener::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
//qDebug() << "JurisdictionListener::processPacket()";
if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest) {
SharedNodePointer node = NodeList::getInstance()->nodeWithAddress(senderAddress);
if (node) {
QUuid nodeUUID = node->getUUID();
JurisdictionMap map;
map.unpackFromMessage(reinterpret_cast<const unsigned char*>(packet.data()), packet.size());
_jurisdictions[nodeUUID] = map;
}
if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest && sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
JurisdictionMap map;
map.unpackFromMessage(reinterpret_cast<const unsigned char*>(packet.data()), packet.size());
_jurisdictions[nodeUUID] = map;
}
}

View file

@ -49,7 +49,7 @@ protected:
/// \param packetData pointer to received data
/// \param ssize_t packetLength size of received data
/// \thread "this" individual processing thread
virtual void processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet);
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
private:
NodeToJurisdictionMap _jurisdictions;

View file

@ -28,13 +28,11 @@ JurisdictionSender::~JurisdictionSender() {
}
void JurisdictionSender::processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet) {
void JurisdictionSender::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest) {
QUuid nodeUUID;
deconstructPacketHeader(packet, nodeUUID);
if (!nodeUUID.isNull()) {
if (sendingNode) {
lockRequestingNodes();
_nodesRequestingJurisdictions.push(nodeUUID);
_nodesRequestingJurisdictions.push(sendingNode->getUUID());
unlockRequestingNodes();
}
}
@ -65,8 +63,7 @@ bool JurisdictionSender::process() {
SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(nodeUUID);
if (node && node->getActiveSocket() != NULL) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
_packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast<char *>(bufferOut), sizeOut));
_packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast<char *>(bufferOut), sizeOut));
nodeCount++;
}
}

View file

@ -37,7 +37,7 @@ public:
void setNodeType(NodeType_t type) { _nodeType = type; }
protected:
virtual void processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet);
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
/// Locks all the resources of the thread.
void lockRequestingNodes() { _requestingNodeMutex.lock(); }

View file

@ -1347,7 +1347,7 @@ bool Octree::readFromSVOFile(const char* fileName) {
fileOk = true; // assume the file is ok
}
if (fileOk) {
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, NULL, wantImportProgress);
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress);
readBitstreamToTree(dataAt, dataLength, args);
}
delete[] entireFile;
@ -1485,7 +1485,7 @@ void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinat
// ask destination tree to read the bitstream
bool wantImportProgress = true;
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, NULL, wantImportProgress);
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, SharedNodePointer(), wantImportProgress);
readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
}
}

View file

@ -156,7 +156,7 @@ public:
bool includeExistsBits;
OctreeElement* destinationNode;
QUuid sourceUUID;
Node* sourceNode;
SharedNodePointer sourceNode;
bool wantImportProgress;
ReadBitstreamToTreeParams(
@ -164,7 +164,7 @@ public:
bool includeExistsBits = WANT_EXISTS_BITS,
OctreeElement* destinationNode = NULL,
QUuid sourceUUID = QUuid(),
Node* sourceNode = NULL,
SharedNodePointer sourceNode = SharedNodePointer(),
bool wantImportProgress = false) :
includeColor(includeColor),
includeExistsBits(includeExistsBits),
@ -190,7 +190,7 @@ public:
virtual PacketType expectedDataPacketType() const { return PacketTypeUnknown; }
virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, Node* senderNode) { return 0; }
const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; }
virtual void update() { }; // nothing to do by default

View file

@ -60,19 +60,17 @@ bool OctreeEditPacketSender::serversExist() const {
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType()) {
if (nodeList->getNodeActiveSocketOrPing(node.data())) {
QUuid nodeUUID = node->getUUID();
// If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server
if (_serverJurisdictions) {
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction
if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) {
atLeastOnJurisdictionMissing = true;
}
if (node->getType() == getMyNodeType() && node->getActiveSocket()) {
QUuid nodeUUID = node->getUUID();
// If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server
if (_serverJurisdictions) {
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction
if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) {
atLeastOnJurisdictionMissing = true;
}
hasServers = true;
}
hasServers = true;
}
if (atLeastOnJurisdictionMissing) {
break; // no point in looking further...
@ -91,9 +89,8 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
// only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType() &&
((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) {
if (nodeList->getNodeActiveSocketOrPing(node.data())) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast<char*>(buffer), length));
if (node->getActiveSocket()) {
queuePacketForSending(node, QByteArray(reinterpret_cast<char*>(buffer), length));
// debugging output...
bool wantDebugging = false;

View file

@ -224,4 +224,4 @@ private:
static quint64 _totalBytesOfRawData;
};
#endif /* defined(__hifi__OctreePacketData__) */
#endif /* defined(__hifi__OctreePacketData__) */

Some files were not shown because too many files have changed in this diff Show more