diff --git a/README.md b/README.md
index bc32998113..a42bbd1626 100644
--- a/README.md
+++ b/README.md
@@ -71,8 +71,15 @@ We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux
distributions. A Windows build is planned for the future, but not currently in
development.
-On a fresh Ubuntu 13.10 install, these are all the packages you need to grab and build the hifi project:
-
sudo apt-get install build-essential cmake git libcurl4-openssl-dev libqt5scripttools5 libqt5svg5-dev libqt5webkit5-dev libqt5location5 qtlocation5-dev qtdeclarative5-dev qtscript5-dev qtsensors5-dev qtmultimedia5-dev qtquick1-5-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
+On a fresh Ubuntu 13.10 install, get these requirements from Ubuntu repositories:
+
+ sudo apt-get install build-essential cmake git libcurl4-openssl-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
+
+Then [download lastest Qt packages](http://qt-project.org/downloads), untar/install to your prefered path
+and set your `QT_CMAKE_PREFIX_PATH` environment variable as described above in the CMake section. It's
+recommended to set the variable automatically on each shell instance to save this task in the future:
+
+ echo 'export QT_CMAKE_PREFIX_PATH=~/Qt5.2.0/5.2.0/gcc_64/lib/cmake' >> ~/.bashrc
Running Interface
-----
diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp
index 6d493b62e5..24ea7ede9c 100644
--- a/animation-server/src/AnimationServer.cpp
+++ b/animation-server/src/AnimationServer.cpp
@@ -16,7 +16,6 @@
#include
#include
-#include
#include
#include
#include
@@ -152,7 +151,7 @@ static void renderMovingBug() {
}
// send the "erase message" first...
- PACKET_TYPE message = PACKET_TYPE_VOXEL_ERASE;
+ PacketType message = PacketTypeVoxelErase;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
// Move the bug...
@@ -212,7 +211,7 @@ static void renderMovingBug() {
}
// send the "create message" ...
- message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
+ message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
}
@@ -247,7 +246,7 @@ static void sendVoxelBlinkMessage() {
detail.green = 0 * ::intensity;
detail.blue = 0 * ::intensity;
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
+ PacketType message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->sendVoxelEditMessage(message, detail);
}
@@ -264,7 +263,7 @@ unsigned char onColor[3] = { 0, 255, 255 };
const float STRING_OF_LIGHTS_SIZE = 0.125f / TREE_SCALE; // approximately 1/8th meter
static void sendBlinkingStringOfLights() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = STRING_OF_LIGHTS_SIZE;
static VoxelDetail details[LIGHTS_PER_SEGMENT];
@@ -370,7 +369,7 @@ const int PACKETS_PER_DANCE_FLOOR = DANCE_FLOOR_VOXELS_PER_PACKET / (DANCE_FLOOR
int danceFloorColors[DANCE_FLOOR_WIDTH][DANCE_FLOOR_LENGTH];
void sendDanceFloor() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = DANCE_FLOOR_LIGHT_SIZE;
static VoxelDetail details[DANCE_FLOOR_VOXELS_PER_PACKET];
@@ -486,7 +485,7 @@ bool billboardMessage[BILLBOARD_HEIGHT][BILLBOARD_WIDTH] = {
};
static void sendBillboard() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = BILLBOARD_LIGHT_SIZE;
static VoxelDetail details[VOXELS_PER_PACKET];
@@ -557,7 +556,7 @@ void doBuildStreet() {
return;
}
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
static VoxelDetail details[BRICKS_PER_PACKET];
for (int z = 0; z < ROAD_LENGTH; z++) {
@@ -592,8 +591,8 @@ double start = 0;
void* animateVoxels(void* args) {
- uint64_t lastAnimateTime = 0;
- uint64_t lastProcessTime = 0;
+ quint64 lastAnimateTime = 0;
+ quint64 lastProcessTime = 0;
int processesPerAnimate = 0;
bool firstTime = true;
@@ -623,8 +622,8 @@ void* animateVoxels(void* args) {
// The while loop will be running at PROCESSING_FPS, but we only want to call these animation functions at
// ANIMATE_FPS. So we check out last animate time and only call these if we've elapsed that time.
- uint64_t now = usecTimestampNow();
- uint64_t animationElapsed = now - lastAnimateTime;
+ quint64 now = usecTimestampNow();
+ quint64 animationElapsed = now - lastAnimateTime;
int withinAnimationTarget = ANIMATE_VOXELS_INTERVAL_USECS - animationElapsed;
const int CLOSE_ENOUGH_TO_ANIMATE = 2000; // approximately 2 ms
@@ -677,7 +676,7 @@ void* animateVoxels(void* args) {
processesPerAnimate++;
}
// dynamically sleep until we need to fire off the next set of voxels
- uint64_t usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
+ quint64 usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
if (usecToSleep > PROCESSING_INTERVAL_USECS) {
usecToSleep = PROCESSING_INTERVAL_USECS;
}
@@ -695,7 +694,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
{
::start = usecTimestampNow();
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_ANIMATION_SERVER, ANIMATION_LISTEN_PORT);
+ NodeList* nodeList = NodeList::createInstance(NodeType::AnimationServer, ANIMATION_LISTEN_PORT);
setvbuf(stdout, NULL, _IOLBF, 0);
// Handle Local Domain testing with the --local command line
@@ -807,7 +806,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
pthread_create(&::animateVoxelThread, NULL, animateVoxels, NULL);
- NodeList::getInstance()->addNodeTypeToInterestSet(NODE_TYPE_VOXEL_SERVER);
+ NodeList::getInstance()->addNodeTypeToInterestSet(NodeType::VoxelServer);
QTimer* domainServerTimer = new QTimer(this);
connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn()));
@@ -823,25 +822,24 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
void AnimationServer::readPendingDatagrams() {
NodeList* nodeList = NodeList::getInstance();
- static int receivedBytes = 0;
- static unsigned char packetData[MAX_PACKET_SIZE];
+ static QByteArray receivedPacket;
static HifiSockAddr nodeSockAddr;
// Nodes sending messages to us...
- while (nodeList->getNodeSocket().hasPendingDatagrams()
- && (receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- nodeSockAddr.getAddressPointer(),
- nodeSockAddr.getPortPointer())) &&
- packetVersionMatch(packetData, nodeSockAddr)) {
-
- if (packetData[0] == PACKET_TYPE_JURISDICTION) {
- int headerBytes = numBytesForPacketHeader(packetData);
- // PACKET_TYPE_JURISDICTION, first byte is the node type...
- if (packetData[headerBytes] == NODE_TYPE_VOXEL_SERVER && ::jurisdictionListener) {
- ::jurisdictionListener->queueReceivedPacket(nodeSockAddr, packetData, receivedBytes);
+ while (nodeList->getNodeSocket().hasPendingDatagrams()) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer());
+ if (packetVersionMatch(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);
+ }
}
+ NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket);
}
- NodeList::getInstance()->processNodeData(nodeSockAddr, packetData, receivedBytes);
}
}
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 4f81c42046..2694bf83e2 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -22,37 +22,42 @@
#include "Agent.h"
-Agent::Agent(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+Agent::Agent(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
}
void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
- if (dataByteArray[0] == PACKET_TYPE_JURISDICTION) {
- int headerBytes = numBytesForPacketHeader((const unsigned char*) dataByteArray.constData());
- // PACKET_TYPE_JURISDICTION, first byte is the node type...
+ PacketType datagramPacketType = packetTypeForPacket(dataByteArray);
+ if (datagramPacketType == PacketTypeJurisdiction) {
+ int headerBytes = numBytesForPacketHeader(dataByteArray);
+ // PacketType_JURISDICTION, first byte is the node type...
switch (dataByteArray[headerBytes]) {
- case NODE_TYPE_VOXEL_SERVER:
+ case NodeType::VoxelServer:
_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
- (unsigned char*) dataByteArray.data(),
- dataByteArray.size());
+ dataByteArray);
break;
- case NODE_TYPE_PARTICLE_SERVER:
+ case NodeType::ParticleServer:
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
- (unsigned char*) dataByteArray.data(),
- dataByteArray.size());
+ dataByteArray);
break;
}
+ } 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, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
void Agent::run() {
NodeList* nodeList = NodeList::getInstance();
- nodeList->setOwnerType(NODE_TYPE_AGENT);
+ nodeList->setOwnerType(NodeType::Agent);
- nodeList->addSetOfNodeTypesToNodeInterestSet(QSet() << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER);
+ nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer);
// figure out the URL for the script for this agent assignment
QString scriptURLString("http://%1:8080/assignment/%2");
@@ -88,11 +93,17 @@ void Agent::run() {
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);
+
// setup an Avatar for the script to use
AvatarData scriptedAvatar;
// give this AvatarData object to the script engine
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
+
+ // register ourselves to the script engine
+ _scriptEngine.registerGlobalObject("Agent", this);
_scriptEngine.setScriptContents(scriptContents);
_scriptEngine.run();
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 980885fcf3..8b2038a8b0 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -15,13 +15,19 @@
#include
#include
+#include
#include
#include
class Agent : public ThreadedAssignment {
Q_OBJECT
+
+ Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
public:
- Agent(const unsigned char* dataBuffer, int numBytes);
+ Agent(const QByteArray& packet);
+
+ void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); }
+ bool isAvatar() const { return _scriptEngine.isAvatar(); }
public slots:
void run();
@@ -32,6 +38,7 @@ signals:
void willSendVisualDataCallback();
private:
ScriptEngine _scriptEngine;
+ ParticleTree _particleTree;
};
#endif /* defined(__hifi__Agent__) */
diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp
index fc8d2deffd..b2a5555e36 100644
--- a/assignment-client/src/AssignmentClient.cpp
+++ b/assignment-client/src/AssignmentClient.cpp
@@ -52,7 +52,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool);
// create a NodeList as an unassigned client
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_UNASSIGNED);
+ NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a";
const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p";
@@ -90,7 +90,8 @@ 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(), SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
+ connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams,
+ Qt::QueuedConnection);
}
void AssignmentClient::sendAssignmentRequest() {
@@ -102,34 +103,33 @@ void AssignmentClient::sendAssignmentRequest() {
void AssignmentClient::readPendingDatagrams() {
NodeList* nodeList = NodeList::getInstance();
- static unsigned char packetData[1500];
- static qint64 receivedBytes = 0;
- static HifiSockAddr senderSockAddr;
+ QByteArray receivedPacket;
+ HifiSockAddr senderSockAddr;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
- if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))
- && packetVersionMatch(packetData, senderSockAddr)) {
-
+ if (packetVersionMatch(receivedPacket)) {
if (_currentAssignment) {
// have the threaded current assignment handle this datagram
QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection,
- Q_ARG(QByteArray, QByteArray((char*) packetData, receivedBytes)),
+ Q_ARG(QByteArray, receivedPacket),
Q_ARG(HifiSockAddr, senderSockAddr));
- } else if (packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT || packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
+ } else if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
if (_currentAssignment) {
qDebug() << "Dropping received assignment since we are currently running one.";
} else {
// construct the deployed assignment from the packet data
- _currentAssignment = AssignmentFactory::unpackAssignment(packetData, receivedBytes);
+ _currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
- qDebug() << "Received an assignment -" << *_currentAssignment;
-
- // switch our nodelist domain IP and port to whoever sent us the assignment
- if (packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
+ 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());
@@ -153,12 +153,12 @@ void AssignmentClient::readPendingDatagrams() {
// Starts an event loop, and emits workerThread->started()
workerThread->start();
} else {
- qDebug("Received a bad destination socket for assignment.");
+ qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
}
}
} else {
// have the NodeList attempt to handle it
- nodeList->processNodeData(senderSockAddr, packetData, receivedBytes);
+ nodeList->processNodeData(senderSockAddr, receivedPacket);
}
}
}
@@ -175,6 +175,6 @@ void AssignmentClient::assignmentCompleted() {
NodeList* nodeList = NodeList::getInstance();
// reset our NodeList by switching back to unassigned and clearing the list
- nodeList->setOwnerType(NODE_TYPE_UNASSIGNED);
+ nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset();
}
diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp
index b32a909bfe..5bf0417f22 100644
--- a/assignment-client/src/AssignmentFactory.cpp
+++ b/assignment-client/src/AssignmentFactory.cpp
@@ -18,25 +18,28 @@
#include "avatars/AvatarMixer.h"
#include "metavoxels/MetavoxelServer.h"
-ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dataBuffer, int numBytes) {
- int headerBytes = numBytesForPacketHeader(dataBuffer);
+ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) {
+ QDataStream packetStream(packet);
+ packetStream.skipRawData(numBytesForPacketHeader(packet));
+
+ quint8 packedType;
+ packetStream >> packedType;
- Assignment::Type assignmentType = Assignment::AllTypes;
- memcpy(&assignmentType, dataBuffer + headerBytes, sizeof(Assignment::Type));
+ Assignment::Type unpackedType = (Assignment::Type) packedType;
- switch (assignmentType) {
+ switch (unpackedType) {
case Assignment::AudioMixerType:
- return new AudioMixer(dataBuffer, numBytes);
+ return new AudioMixer(packet);
case Assignment::AvatarMixerType:
- return new AvatarMixer(dataBuffer, numBytes);
+ return new AvatarMixer(packet);
case Assignment::AgentType:
- return new Agent(dataBuffer, numBytes);
+ return new Agent(packet);
case Assignment::VoxelServerType:
- return new VoxelServer(dataBuffer, numBytes);
+ return new VoxelServer(packet);
case Assignment::ParticleServerType:
- return new ParticleServer(dataBuffer, numBytes);
+ return new ParticleServer(packet);
case Assignment::MetavoxelServerType:
- return new MetavoxelServer(dataBuffer, numBytes);
+ return new MetavoxelServer(packet);
default:
return NULL;
}
diff --git a/assignment-client/src/AssignmentFactory.h b/assignment-client/src/AssignmentFactory.h
index 4605d961ec..9eff29a468 100644
--- a/assignment-client/src/AssignmentFactory.h
+++ b/assignment-client/src/AssignmentFactory.h
@@ -13,7 +13,7 @@
class AssignmentFactory {
public:
- static ThreadedAssignment* unpackAssignment(const unsigned char* dataBuffer, int numBytes);
+ static ThreadedAssignment* unpackAssignment(const QByteArray& packet);
};
#endif /* defined(__hifi__AssignmentFactory__) */
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 705a877a00..90e2810f27 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -37,7 +37,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -61,8 +60,8 @@ void attachNewBufferToNode(Node *newNode) {
}
}
-AudioMixer::AudioMixer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+AudioMixer::AudioMixer(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
}
@@ -210,18 +209,19 @@ 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
- if (dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO
- || dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
- || dataByteArray[0] == PACKET_TYPE_INJECT_AUDIO) {
- QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
- NUM_BYTES_RFC4122_UUID));
+ 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, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ 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
@@ -231,17 +231,17 @@ void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSock
}
} else {
// let processNodeData handle it.
- NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
void AudioMixer::run() {
- commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NODE_TYPE_AUDIO_MIXER);
+ commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
NodeList* nodeList = NodeList::getInstance();
- nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
+ nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
@@ -250,14 +250,14 @@ void AudioMixer::run() {
gettimeofday(&startTime, NULL);
- int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MIXED_AUDIO);
+ int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio);
// note: Visual Studio 2010 doesn't support variable sized local arrays
#ifdef _WIN32
unsigned char clientPacket[MAX_PACKET_SIZE];
#else
unsigned char clientPacket[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader];
#endif
- populateTypeAndVersion(clientPacket, PACKET_TYPE_MIXED_AUDIO);
+ populatePacketHeader(reinterpret_cast(clientPacket), PacketTypeMixedAudio);
while (!_isFinished) {
@@ -274,7 +274,7 @@ void AudioMixer::run() {
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getType() == NODE_TYPE_AGENT && node->getActiveSocket() && node->getLinkedData()
+ if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
prepareMixForListeningNode(node.data());
diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h
index 7326e1a161..2d42a6b629 100644
--- a/assignment-client/src/audio/AudioMixer.h
+++ b/assignment-client/src/audio/AudioMixer.h
@@ -20,7 +20,7 @@ class AvatarAudioRingBuffer;
class AudioMixer : public ThreadedAssignment {
Q_OBJECT
public:
- AudioMixer(const unsigned char* dataBuffer, int numBytes);
+ AudioMixer(const QByteArray& packet);
public slots:
/// threaded run of assignment
void run();
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 0838de4f45..a41889e77c 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -31,9 +31,10 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
return NULL;
}
-int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
- if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
- || packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO) {
+int AudioMixerClientData::parseData(const QByteArray& packet) {
+ PacketType packetType = packetTypeForPacket(packet);
+ if (packetType == PacketTypeMicrophoneAudioWithEcho
+ || packetType == PacketTypeMicrophoneAudioNoEcho) {
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
@@ -45,14 +46,12 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
}
// ask the AvatarAudioRingBuffer instance to parse the data
- avatarRingBuffer->parseData(packetData, numBytes);
+ avatarRingBuffer->parseData(packet);
} else {
// this is injected audio
// grab the stream identifier for this injected audio
- QByteArray rfcUUID = QByteArray((char*) packetData + numBytesForPacketHeader(packetData) + NUM_BYTES_RFC4122_UUID,
- NUM_BYTES_RFC4122_UUID);
- QUuid streamIdentifier = QUuid::fromRfc4122(rfcUUID);
+ QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
@@ -69,7 +68,7 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
_ringBuffers.push_back(matchingInjectedRingBuffer);
}
- matchingInjectedRingBuffer->parseData(packetData, numBytes);
+ matchingInjectedRingBuffer->parseData(packet);
}
return 0;
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 05cfa51e5f..8031dfec3e 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -23,7 +23,7 @@ public:
const std::vector getRingBuffers() const { return _ringBuffers; }
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
- int parseData(unsigned char* packetData, int numBytes);
+ int parseData(const QByteArray& packet);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples);
void pushBuffersAfterFrameSend();
private:
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
index 64d71d9836..4b1907efb8 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
@@ -15,7 +15,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer() :
}
-int AvatarAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) {
- _shouldLoopbackForNode = (sourceBuffer[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO);
- return PositionalAudioRingBuffer::parseData(sourceBuffer, numBytes);
+int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
+ _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
+ return PositionalAudioRingBuffer::parseData(packet);
}
\ No newline at end of file
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.h b/assignment-client/src/audio/AvatarAudioRingBuffer.h
index 15542383fb..0d4f28467b 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.h
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.h
@@ -17,7 +17,7 @@ class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
public:
AvatarAudioRingBuffer();
- int parseData(unsigned char* sourceBuffer, int numBytes);
+ int parseData(const QByteArray& packet);
private:
// disallow copying of AvatarAudioRingBuffer objects
AvatarAudioRingBuffer(const AvatarAudioRingBuffer&);
diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 64e93fd751..358e507fc4 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -27,24 +27,13 @@ const char AVATAR_MIXER_LOGGING_NAME[] = "avatar-mixer";
const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000;
-AvatarMixer::AvatarMixer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+AvatarMixer::AvatarMixer(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
// make sure we hear about node kills so we can tell the other nodes
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
}
-unsigned char* addNodeToBroadcastPacket(unsigned char *currentPosition, Node *nodeToAdd) {
- QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122();
- memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size());
- currentPosition += rfcUUID.size();
-
- AvatarData *nodeData = (AvatarData *)nodeToAdd->getLinkedData();
- currentPosition += nodeData->getBroadcastData(currentPosition);
-
- return currentPosition;
-}
-
void attachAvatarDataToNode(Node* newNode) {
if (newNode->getLinkedData() == NULL) {
newNode->setLinkedData(new AvatarData());
@@ -59,58 +48,50 @@ void attachAvatarDataToNode(Node* newNode) {
// determine which avatars are included in the packet stream
// 4) we should optimize the avatar data format to be more compact (100 bytes is pretty wasteful).
void broadcastAvatarData() {
- static unsigned char broadcastPacket[MAX_PACKET_SIZE];
- static unsigned char avatarDataBuffer[MAX_PACKET_SIZE];
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_BULK_AVATAR_DATA);
- unsigned char* currentBufferPosition = broadcastPacket + numHeaderBytes;
- int packetLength = currentBufferPosition - broadcastPacket;
+ static QByteArray mixedAvatarByteArray;
+
+ int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
+
int packetsSent = 0;
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT && node->getActiveSocket()) {
+ if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()) {
// reset packet pointers for this node
- currentBufferPosition = broadcastPacket + numHeaderBytes;
- packetLength = currentBufferPosition - broadcastPacket;
+ mixedAvatarByteArray.resize(numPacketHeaderBytes);
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()) {
- unsigned char* avatarDataEndpoint = addNodeToBroadcastPacket((unsigned char*)&avatarDataBuffer[0],
- otherNode.data());
- int avatarDataLength = avatarDataEndpoint - (unsigned char*)&avatarDataBuffer;
+ QByteArray avatarByteArray;
+ avatarByteArray.append(otherNode->getUUID().toRfc4122());
- if (avatarDataLength + packetLength <= MAX_PACKET_SIZE) {
- memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
- packetLength += avatarDataLength;
- currentBufferPosition += avatarDataLength;
- } else {
+ AvatarData* nodeData = (AvatarData*) 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((char*) broadcastPacket,
- currentBufferPosition - broadcastPacket,
+ nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
// reset the packet
- currentBufferPosition = broadcastPacket + numHeaderBytes;
- packetLength = currentBufferPosition - broadcastPacket;
-
- // copy the avatar that didn't fit into the next packet
- memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
- packetLength += avatarDataLength;
- currentBufferPosition += avatarDataLength;
+ mixedAvatarByteArray.resize(numPacketHeaderBytes);
}
+
+ // copy the avatar into the mixedAvatarByteArray packet
+ mixedAvatarByteArray.append(avatarByteArray);
}
}
packetsSent++;
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, currentBufferPosition - broadcastPacket,
+ nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
@@ -118,18 +99,15 @@ void broadcastAvatarData() {
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
- if (killedNode->getType() == NODE_TYPE_AGENT
+ if (killedNode->getType() == NodeType::Agent
&& killedNode->getLinkedData()) {
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
- unsigned char packetData[MAX_PACKET_SIZE];
- int numHeaderBytes = populateTypeAndVersion(packetData, PACKET_TYPE_KILL_AVATAR);
+ QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
+ killPacket += killedNode->getUUID().toRfc4122();
- QByteArray rfcUUID = killedNode->getUUID().toRfc4122();
- memcpy(packetData + numHeaderBytes, rfcUUID.constData(), rfcUUID.size());
-
- NodeList::getInstance()->broadcastToNodes(packetData, numHeaderBytes + NUM_BYTES_RFC4122_UUID,
- QSet() << NODE_TYPE_AGENT);
+ NodeList::getInstance()->broadcastToNodes(killPacket,
+ NodeSet() << NodeType::Agent);
}
}
@@ -137,39 +115,38 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc
NodeList* nodeList = NodeList::getInstance();
- switch (dataByteArray[0]) {
- case PACKET_TYPE_HEAD_DATA: {
- QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
- NUM_BYTES_RFC4122_UUID));
+ 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,
- (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr, dataByteArray);
}
break;
}
- case PACKET_TYPE_KILL_AVATAR: {
+ case PacketTypeKillAvatar: {
nodeList->processKillNode(dataByteArray);
break;
}
default:
// hand this off to the NodeList
- nodeList->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ nodeList->processNodeData(senderSockAddr, dataByteArray);
break;
}
}
void AvatarMixer::run() {
- commonInit(AVATAR_MIXER_LOGGING_NAME, NODE_TYPE_AVATAR_MIXER);
+ commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
NodeList* nodeList = NodeList::getInstance();
- nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
+ nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h
index 2da0ed98eb..92ab1a5c53 100644
--- a/assignment-client/src/avatars/AvatarMixer.h
+++ b/assignment-client/src/avatars/AvatarMixer.h
@@ -14,7 +14,7 @@
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
class AvatarMixer : public ThreadedAssignment {
public:
- AvatarMixer(const unsigned char* dataBuffer, int numBytes);
+ AvatarMixer(const QByteArray& packet);
public slots:
/// runs the avatar mixer
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp
index af982abf9b..3cd81d8ac7 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.cpp
+++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp
@@ -17,22 +17,25 @@
const int SEND_INTERVAL = 50;
-MetavoxelServer::MetavoxelServer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes),
- _data(new MetavoxelData()) {
+MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
+ ThreadedAssignment(packet) {
_sendTimer.setSingleShot(true);
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
}
+void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
+ edit.apply(_data);
+}
+
void MetavoxelServer::removeSession(const QUuid& sessionId) {
- delete _sessions.take(sessionId);
+ _sessions.take(sessionId)->deleteLater();
}
const char METAVOXEL_SERVER_LOGGING_NAME[] = "metavoxel-server";
void MetavoxelServer::run() {
- commonInit(METAVOXEL_SERVER_LOGGING_NAME, NODE_TYPE_METAVOXEL_SERVER);
+ commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer);
_lastSend = QDateTime::currentMSecsSinceEpoch();
_sendTimer.start(SEND_INTERVAL);
@@ -40,12 +43,12 @@ void MetavoxelServer::run() {
void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
switch (dataByteArray.at(0)) {
- case PACKET_TYPE_METAVOXEL_DATA:
+ case PacketTypeMetavoxelData:
processData(dataByteArray, senderSockAddr);
break;
default:
- NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*)dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
break;
}
}
@@ -75,16 +78,18 @@ 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));
+ session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), sender);
}
session->receivedData(data, sender);
}
-MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader) :
+MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
+ const QByteArray& datagramHeader, const HifiSockAddr& sender) :
QObject(server),
_server(server),
_sessionId(sessionId),
- _sequencer(datagramHeader) {
+ _sequencer(datagramHeader),
+ _sender(sender) {
const int TIMEOUT_INTERVAL = 30 * 1000;
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
@@ -94,10 +99,13 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
+ connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
// insert the baseline send record
- SendRecord record = { 0, MetavoxelDataPointer(new MetavoxelData()) };
+ SendRecord record = { 0 };
_sendRecords.append(record);
+
+ qDebug() << "Opened session [sessionId=" << _sessionId << ", sender=" << _sender << "]";
}
void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
@@ -114,7 +122,7 @@ void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr&
void MetavoxelSession::sendDelta() {
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(MetavoxelDeltaMessage());
- writeDelta(_server->getData(), _sendRecords.first().data, out);
+ _server->getData().writeDelta(_sendRecords.first().data, out);
_sequencer.endPacket();
// record the send
@@ -134,25 +142,29 @@ void MetavoxelSession::sendData(const QByteArray& data) {
void MetavoxelSession::readPacket(Bitstream& in) {
QVariant message;
in >> message;
- handleMessage(message, in);
+ handleMessage(message);
}
void MetavoxelSession::clearSendRecordsBefore(int index) {
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
}
-void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
+void MetavoxelSession::handleMessage(const QVariant& message) {
int userType = message.userType();
- if (userType == ClientStateMessage::Type) {
+ if (userType == CloseSessionMessage::Type) {
+ qDebug() << "Session closed [sessionId=" << _sessionId << ", sender=" << _sender << "]";
+ _server->removeSession(_sessionId);
+
+ } else if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value();
_position = state.position;
- } else if (userType == MetavoxelDeltaMessage::Type) {
+ } else if (userType == MetavoxelEditMessage::Type) {
+ _server->applyEdit(message.value());
-
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
- handleMessage(element, in);
+ handleMessage(element);
}
}
}
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h
index 407c520116..f106bd3494 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.h
+++ b/assignment-client/src/metavoxels/MetavoxelServer.h
@@ -20,6 +20,7 @@
#include
#include
+class MetavoxelEditMessage;
class MetavoxelSession;
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
@@ -28,9 +29,11 @@ class MetavoxelServer : public ThreadedAssignment {
public:
- MetavoxelServer(const unsigned char* dataBuffer, int numBytes);
+ MetavoxelServer(const QByteArray& packet);
- const MetavoxelDataPointer& getData() const { return _data; }
+ void applyEdit(const MetavoxelEditMessage& edit);
+
+ const MetavoxelData& getData() const { return _data; }
void removeSession(const QUuid& sessionId);
@@ -51,7 +54,7 @@ private:
QHash _sessions;
- MetavoxelDataPointer _data;
+ MetavoxelData _data;
};
/// Contains the state of a single client session.
@@ -60,7 +63,8 @@ class MetavoxelSession : public QObject {
public:
- MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader);
+ MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
+ const QByteArray& datagramHeader, const HifiSockAddr& sender);
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
@@ -76,14 +80,14 @@ private slots:
void clearSendRecordsBefore(int index);
-private:
+ void handleMessage(const QVariant& message);
- void handleMessage(const QVariant& message, Bitstream& in);
+private:
class SendRecord {
public:
int packetNumber;
- MetavoxelDataPointer data;
+ MetavoxelData data;
};
MetavoxelServer* _server;
diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake
index fca8170e64..f0c9ebc6a0 100644
--- a/cmake/macros/AutoMTC.cmake
+++ b/cmake/macros/AutoMTC.cmake
@@ -9,10 +9,12 @@ macro(AUTO_MTC TARGET ROOT_DIR)
${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
find_package(Qt5Core REQUIRED)
+ find_package(Qt5Script REQUIRED)
+ find_package(Qt5Widgets REQUIRED)
add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp)
- qt5_use_modules(${TARGET}_automtc Core)
+ qt5_use_modules(${TARGET}_automtc Core Script Widgets)
target_link_libraries(${TARGET} ${TARGET}_automtc)
diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp
index c59749ed08..43fc52fb06 100644
--- a/data-server/src/DataServer.cpp
+++ b/data-server/src/DataServer.cpp
@@ -6,9 +6,12 @@
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
+#include
+#include
#include
#include
+#include
#include
#include "DataServer.h"
@@ -21,7 +24,8 @@ const unsigned short REDIS_PORT = 6379;
DataServer::DataServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_socket(),
- _redis(NULL)
+ _redis(NULL),
+ _uuid(QUuid::createUuid())
{
_socket.bind(QHostAddress::Any, DATA_SERVER_LISTEN_PORT);
@@ -51,66 +55,60 @@ DataServer::~DataServer() {
const int MAX_PACKET_SIZE = 1500;
void DataServer::readPendingDatagrams() {
- qint64 receivedBytes = 0;
- static unsigned char packetData[MAX_PACKET_SIZE];
-
+ QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
- while (_socket.hasPendingDatagrams() &&
- (receivedBytes = _socket.readDatagram(reinterpret_cast(packetData), MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()))) {
- if ((packetData[0] == PACKET_TYPE_DATA_SERVER_PUT || packetData[0] == PACKET_TYPE_DATA_SERVER_GET) &&
- packetVersionMatch(packetData, senderSockAddr)) {
-
- int readBytes = numBytesForPacketHeader(packetData);
+ while (_socket.hasPendingDatagrams()) {
+ receivedPacket.resize(_socket.pendingDatagramSize());
+ _socket.readDatagram(receivedPacket.data(), _socket.pendingDatagramSize(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
+
+ PacketType requestType = packetTypeForPacket(receivedPacket);
+
+ if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) &&
+ packetVersionMatch(receivedPacket)) {
+
+ QDataStream packetStream(receivedPacket);
+ int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket);
+ packetStream.skipRawData(numReceivedHeaderBytes);
// pull the sequence number used for this packet
quint8 sequenceNumber = 0;
- memcpy(&sequenceNumber, packetData + readBytes, sizeof(sequenceNumber));
- readBytes += sizeof(sequenceNumber);
-
+
+ packetStream >> sequenceNumber;
+
// pull the UUID that we will need as part of the key
- QString uuidString(reinterpret_cast(packetData + readBytes));
- QUuid parsedUUID(uuidString);
+ QString userString;
+ packetStream >> userString;
+ QUuid parsedUUID(userString);
if (parsedUUID.isNull()) {
// we failed to parse a UUID, this means the user has sent us a username
- QString username(reinterpret_cast(packetData + readBytes));
- readBytes += username.size() + sizeof('\0');
-
// ask redis for the UUID for this user
- redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username));
+ redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(userString));
if (reply->type == REDIS_REPLY_STRING) {
parsedUUID = QUuid(QString(reply->str));
}
if (!parsedUUID.isNull()) {
- qDebug() << "Found UUID" << parsedUUID << "for username" << username;
+ qDebug() << "Found UUID" << parsedUUID << "for username" << userString;
} else {
- qDebug() << "Failed UUID lookup for username" << username;
+ qDebug() << "Failed UUID lookup for username" << userString;
}
freeReplyObject(reply);
reply = NULL;
- } else {
- readBytes += uuidString.size() + sizeof('\0');
}
if (!parsedUUID.isNull()) {
- // pull the number of keys the user has sent
- unsigned char numKeys = packetData[readBytes++];
-
- if (packetData[0] == PACKET_TYPE_DATA_SERVER_PUT) {
+ if (requestType == PacketTypeDataServerPut) {
- // pull the key that specifies the data the user is putting/getting
- QString dataKey(reinterpret_cast(packetData + readBytes));
- readBytes += dataKey.size() + sizeof('\0');
+ // pull the key and value that specifies the data the user is putting/getting
+ QString dataKey, dataValue;
- // grab the string value the user wants us to put, null terminate it
- QString dataValue(reinterpret_cast(packetData + readBytes));
- readBytes += dataValue.size() + sizeof('\0');
+ packetStream >> dataKey >> dataValue;
qDebug("Sending command to redis: SET uuid:%s:%s %s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
@@ -122,78 +120,72 @@ void DataServer::readPendingDatagrams() {
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
// if redis stored the value successfully reply back with a confirm
- // which is the sent packet with the header replaced
- packetData[0] = PACKET_TYPE_DATA_SERVER_CONFIRM;
- _socket.writeDatagram(reinterpret_cast(packetData), receivedBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
+ // which is a reply packet with the sequence number
+ QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid);
+
+ replyPacket.append(sequenceNumber);
+
+ _socket.writeDatagram(replyPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
freeReplyObject(reply);
} else {
// setup a send packet with the returned data
// leverage the packetData sent by overwriting and appending
- int numSendPacketBytes = receivedBytes;
+ QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid);
+ QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
- packetData[0] = PACKET_TYPE_DATA_SERVER_SEND;
+ sendPacketStream << sequenceNumber;
- if (strcmp((char*) packetData + readBytes, "uuid") != 0) {
+ // pull the key list that specifies the data the user is putting/getting
+ QString keyListString;
+ packetStream >> keyListString;
+
+ if (keyListString != "uuid") {
+
+ // copy the parsed UUID
+ sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
const char MULTI_KEY_VALUE_SEPARATOR = '|';
- // the user has sent one or more keys - make the associated requests
- for (int j = 0; j < numKeys; j++) {
-
- // pull the key that specifies the data the user is putting/getting, null terminate it
- int numDataKeyBytes = 0;
-
- // look for the key separator or the null terminator
- while (packetData[readBytes + numDataKeyBytes] != MULTI_KEY_VALUE_SEPARATOR
- && packetData[readBytes + numDataKeyBytes] != '\0') {
- numDataKeyBytes++;
- }
-
- QString dataKey(QByteArray(reinterpret_cast(packetData + readBytes), numDataKeyBytes));
- readBytes += dataKey.size() + sizeof('\0');
-
+ // append the keyListString back to the sendPacket
+ sendPacketStream << keyListString;
+
+ QStringList keyList = keyListString.split(MULTI_KEY_VALUE_SEPARATOR);
+ QStringList valueList;
+
+ foreach (const QString& dataKey, keyList) {
qDebug("Sending command to redis: GET uuid:%s:%s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey));
redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s",
- qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
- qPrintable(dataKey));
+ qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
+ qPrintable(dataKey));
if (reply->len) {
// copy the value that redis returned
- memcpy(packetData + numSendPacketBytes, reply->str, reply->len);
- numSendPacketBytes += reply->len;
-
+ valueList << QString(reply->str);
} else {
// didn't find a value - insert a space
- packetData[numSendPacketBytes++] = ' ';
+ valueList << QChar(' ');
}
- // add the multi-value separator
- packetData[numSendPacketBytes++] = MULTI_KEY_VALUE_SEPARATOR;
-
freeReplyObject(reply);
}
- // null terminate the packet we're sending back (erases the trailing separator)
- packetData[(numSendPacketBytes - 1)] = '\0';
+ // append the value QStringList using the right separator
+ sendPacketStream << valueList.join(MULTI_KEY_VALUE_SEPARATOR);
} else {
- // user is asking for a UUID matching username, copy the UUID we found
- QString uuidString = uuidStringWithoutCurlyBraces(parsedUUID);
- memcpy(packetData + numSendPacketBytes, qPrintable(uuidString), uuidString.size() + sizeof('\0'));
- numSendPacketBytes += uuidString.size() + sizeof('\0');
+ // user was asking for their UUID
+ sendPacketStream << userString;
+ sendPacketStream << QString("uuid");
+ sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
}
// reply back with the send packet
- _socket.writeDatagram(reinterpret_cast(packetData), numSendPacketBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
-
-
+ _socket.writeDatagram(sendPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/data-server/src/DataServer.h b/data-server/src/DataServer.h
index 097f6a6533..392387f4d8 100644
--- a/data-server/src/DataServer.h
+++ b/data-server/src/DataServer.h
@@ -10,6 +10,7 @@
#define __hifi__DataServer__
#include
+#include
#include
#include
@@ -22,6 +23,7 @@ public:
private:
QUdpSocket _socket;
redisContext* _redis;
+ QUuid _uuid;
private slots:
void readPendingDatagrams();
};
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 0ee7cca467..37626e8a43 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -8,9 +8,11 @@
#include
+#include
#include
#include
#include
+#include
#include
#include
@@ -27,65 +29,37 @@ const char* VOXEL_SERVER_CONFIG = "voxelServerConfig";
const char* PARTICLE_SERVER_CONFIG = "particleServerConfig";
const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig";
-void signalhandler(int sig){
- if (sig == SIGINT) {
- qApp->quit();
- }
-}
-
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
- _assignmentQueueMutex(),
+ _staticAssignmentHash(),
_assignmentQueue(),
- _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())),
- _staticAssignmentFileData(NULL),
- _voxelServerConfig(NULL),
- _metavoxelServerConfig(NULL),
_hasCompletedRestartHold(false)
{
- signal(SIGINT, signalhandler);
-
const char CUSTOM_PORT_OPTION[] = "-p";
const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION);
unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT;
-
- const char CONFIG_FILE_OPTION[] = "-c";
- const char* configFilePath = getCmdOption(argc, (const char**) argv, CONFIG_FILE_OPTION);
-
- if (!readConfigFile(configFilePath)) {
- QByteArray voxelConfigOption = QString("--%1").arg(VOXEL_SERVER_CONFIG).toLocal8Bit();
- _voxelServerConfig = getCmdOption(argc, (const char**) argv, voxelConfigOption.constData());
-
- QByteArray particleConfigOption = QString("--%1").arg(PARTICLE_SERVER_CONFIG).toLocal8Bit();
- _particleServerConfig = getCmdOption(argc, (const char**) argv, particleConfigOption.constData());
-
- QByteArray metavoxelConfigOption = QString("--%1").arg(METAVOXEL_SERVER_CONFIG).toLocal8Bit();
- _metavoxelServerConfig = getCmdOption(argc, (const char**) argv, metavoxelConfigOption.constData());
+
+ QStringList argumentList = arguments();
+ int argumentIndex = 0;
+
+ QSet parsedTypes(QSet() << Assignment::AgentType);
+ parseCommandLineTypeConfigs(argumentList, parsedTypes);
+
+ const QString CONFIG_FILE_OPTION = "--configFile";
+ if ((argumentIndex = argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) {
+ QString configFilePath = argumentList.value(argumentIndex + 1);
+ readConfigFile(configFilePath, parsedTypes);
}
+
+ populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, domainServerPort);
+ NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer)));
- if (!_staticAssignmentFile.exists() || _voxelServerConfig) {
-
- if (_voxelServerConfig) {
- // we have a new VS config, clear the existing file to start fresh
- _staticAssignmentFile.remove();
- }
-
- prepopulateStaticAssignmentFile();
- }
-
- _staticAssignmentFile.open(QIODevice::ReadWrite);
-
- _staticAssignmentFileData = _staticAssignmentFile.map(0, _staticAssignmentFile.size());
-
- _staticAssignments = (Assignment*) _staticAssignmentFileData;
-
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
@@ -94,47 +68,184 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// fire a single shot timer to add static assignments back into the queue after a restart
QTimer::singleShot(RESTART_HOLD_TIME_MSECS, this, SLOT(addStaticAssignmentsBackToQueueAfterRestart()));
+}
- connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
+void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) {
+ // check for configs from the command line, these take precedence
+ const QString CONFIG_TYPE_OPTION = "--configType";
+ int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION);
+
+ // enumerate all CL config overrides and parse them to files
+ while (clConfigIndex != -1) {
+ int clConfigType = argumentList.value(clConfigIndex + 1).toInt();
+ if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) {
+ Assignment::Type assignmentType = (Assignment::Type) clConfigType;
+ createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType,
+ argumentList.value(clConfigIndex + 2));
+ excludedTypes.insert(assignmentType);
+ }
+
+ clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1);
+ }
+}
+
+// Attempts to read configuration from specified path
+// returns true on success, false otherwise
+void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) {
+ if (path.isEmpty()) {
+ // config file not specified
+ return;
+ }
+
+ if (!QFile::exists(path)) {
+ qWarning("Specified configuration file does not exist!");
+ return;
+ }
+
+ QFile configFile(path);
+ if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qWarning("Can't open specified configuration file!");
+ return;
+ } else {
+ qDebug() << "Reading configuration from" << path;
+ }
+
+ QTextStream configStream(&configFile);
+ QByteArray configStringByteArray = configStream.readAll().toUtf8();
+ QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object();
+ configFile.close();
+
+ QSet appendedExcludedTypes = excludedTypes;
+
+ foreach (const QString& rootStringValue, configDocObject.keys()) {
+ int possibleConfigType = rootStringValue.toInt();
+
+ if (possibleConfigType < Assignment::AllTypes
+ && !excludedTypes.contains((Assignment::Type) possibleConfigType)) {
+ // this is an appropriate config type and isn't already in our excluded types
+ // we are good to parse it
+ Assignment::Type assignmentType = (Assignment::Type) possibleConfigType;
+ QString configString = readServerAssignmentConfig(configDocObject, rootStringValue);
+ createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString);
+
+ excludedTypes.insert(assignmentType);
+ }
+ }
+}
+
+// find assignment configurations on the specified node name and json object
+// returns a string in the form of its equivalent cmd line params
+QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) {
+ QJsonArray nodeArray = jsonObject[nodeName].toArray();
+
+ QStringList serverConfig;
+ foreach (const QJsonValue& childValue, nodeArray) {
+ QString cmdParams;
+ QJsonObject childObject = childValue.toObject();
+ QStringList keys = childObject.keys();
+ for (int i = 0; i < keys.size(); i++) {
+ QString key = keys[i];
+ QString value = childObject[key].toString();
+ // both cmd line params and json keys are the same
+ cmdParams += QString("--%1 %2 ").arg(key, value);
+ }
+ serverConfig << cmdParams;
+ }
+
+ // according to split() calls from DomainServer::prepopulateStaticAssignmentFile
+ // we shold simply join them with semicolons
+ return serverConfig.join(';');
+}
+
+void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
+ qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash.";
+ _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
+}
+
+void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) {
+ // we have a string for config for this type
+ qDebug() << "Parsing command line config for assignment type" << type;
+
+ QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts);
+
+ const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*(\\w+)";
+ QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX);
+
+ // read each config to a payload for this type of assignment
+ for (int i = 0; i < multiConfigList.size(); i++) {
+ QString config = multiConfigList.at(i);
+
+ // check the config string for a pool
+ QString assignmentPool;
+
+ int poolIndex = poolRegex.indexIn(config);
+
+ if (poolIndex != -1) {
+ assignmentPool = poolRegex.cap(1);
+
+ // remove the pool from the config string, the assigned node doesn't need it
+ config.remove(poolIndex, poolRegex.matchedLength());
+ }
+
+ qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData());
+
+ Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool);
+
+ configAssignment->setPayload(config.toUtf8());
+
+ addStaticAssignmentToAssignmentHash(configAssignment);
+ }
+}
+
+void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) {
+ // enumerate over all assignment types and see if we've already excluded it
+ for (int defaultedType = Assignment::AudioMixerType; defaultedType != Assignment::AllTypes; defaultedType++) {
+ if (!excludedTypes.contains((Assignment::Type) defaultedType)) {
+ // type has not been set from a command line or config file config, use the default
+ // by clearing whatever exists and writing a single default assignment with no payload
+ Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
+ addStaticAssignmentToAssignmentHash(newAssignment);
+ }
+ }
}
void DomainServer::readAvailableDatagrams() {
NodeList* nodeList = NodeList::getInstance();
HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress;
-
- static unsigned char packetData[MAX_PACKET_SIZE];
-
- static unsigned char broadcastPacket[MAX_PACKET_SIZE];
-
- static unsigned char* currentBufferPos;
- static unsigned char* startPointer;
-
- int receivedBytes = 0;
+
+ static QByteArray broadcastPacket = byteArrayWithPopluatedHeader(PacketTypeDomainList);
+ static int numBroadcastPacketHeaderBytes = broadcastPacket.size();
+
+ static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment);
+ static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
+
+ QByteArray receivedPacket;
+ NodeType_t nodeType;
+ QUuid nodeUUID;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
- if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))
- && packetVersionMatch((unsigned char*) packetData, senderSockAddr)) {
- if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
+
+ if (packetVersionMatch(receivedPacket)) {
+ PacketType requestType = packetTypeForPacket(receivedPacket);
+ if (requestType == PacketTypeDomainListRequest) {
+
// this is an RFD or domain list request packet, and there is a version match
-
- int numBytesSenderHeader = numBytesForPacketHeader((unsigned char*) packetData);
-
- NODE_TYPE nodeType = *(packetData + numBytesSenderHeader);
-
- int packetIndex = numBytesSenderHeader + sizeof(NODE_TYPE);
- QUuid nodeUUID = QUuid::fromRfc4122(QByteArray(((char*) packetData + packetIndex), NUM_BYTES_RFC4122_UUID));
- packetIndex += NUM_BYTES_RFC4122_UUID;
-
- int numBytesPrivateSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodePublicAddress);
- packetIndex += numBytesPrivateSocket;
-
+ QDataStream packetStream(receivedPacket);
+ packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
+
+ deconstructPacketHeader(receivedPacket, nodeUUID);
+
+ packetStream >> nodeType;
+ packetStream >> nodePublicAddress >> nodeLocalAddress;
+
if (nodePublicAddress.getAddress().isNull()) {
// this node wants to use us its STUN server
// so set the node public address to whatever we perceive the public address to be
-
+
// if the sender is on our box then leave its public address to 0 so that
// other users attempt to reach it on the same address they have for the domain-server
if (senderSockAddr.getAddress().isLoopback()) {
@@ -143,105 +254,90 @@ void DomainServer::readAvailableDatagrams() {
nodePublicAddress.setAddress(senderSockAddr.getAddress());
}
}
-
- int numBytesPublicSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodeLocalAddress);
- packetIndex += numBytesPublicSocket;
-
- const char STATICALLY_ASSIGNED_NODES[] = {
- NODE_TYPE_AUDIO_MIXER,
- NODE_TYPE_AVATAR_MIXER,
- NODE_TYPE_VOXEL_SERVER,
- NODE_TYPE_METAVOXEL_SERVER
- };
-
- Assignment* matchingStaticAssignment = NULL;
-
- if (memchr(STATICALLY_ASSIGNED_NODES, nodeType, sizeof(STATICALLY_ASSIGNED_NODES)) == NULL
- || ((matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType))
- || checkInWithUUIDMatchesExistingNode(nodePublicAddress,
- nodeLocalAddress,
- nodeUUID)))
+
+ 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))
{
SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID,
nodeType,
nodePublicAddress,
nodeLocalAddress);
-
+
+ // resize our broadcast packet in preparation to set it up again
+ broadcastPacket.resize(numBroadcastPacketHeaderBytes);
+
if (matchingStaticAssignment) {
// this was a newly added node with a matching static assignment
-
+
+ // remove the matching assignment from the assignment queue so we don't take the next check in
+ // (if it exists)
if (_hasCompletedRestartHold) {
- // remove the matching assignment from the assignment queue so we don't take the next check in
- removeAssignmentFromQueue(matchingStaticAssignment);
+ removeMatchingAssignmentFromQueue(matchingStaticAssignment);
}
-
- // set the linked data for this node to a copy of the matching assignment
- // so we can re-queue it should the node die
- Assignment* nodeCopyOfMatchingAssignment = new Assignment(*matchingStaticAssignment);
-
- checkInNode->setLinkedData(nodeCopyOfMatchingAssignment);
}
-
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN);
-
- currentBufferPos = broadcastPacket + numHeaderBytes;
- startPointer = currentBufferPos;
-
- unsigned char* nodeTypesOfInterest = packetData + packetIndex + sizeof(unsigned char);
- int numInterestTypes = *(nodeTypesOfInterest - 1);
-
+
+ quint8 numInterestTypes = 0;
+ packetStream >> numInterestTypes;
+
+ NodeType_t* nodeTypesOfInterest = reinterpret_cast(receivedPacket.data()
+ + packetStream.device()->pos());
+
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)) {
-
+
// don't send avatar nodes to other avatars, that will come from avatar mixer
- if (nodeType != NODE_TYPE_AGENT || node->getType() != NODE_TYPE_AGENT) {
- currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, node.data());
- }
-
+ broadcastDataStream << *node.data();
}
}
}
-
+
// update last receive to now
- uint64_t timeNow = usecTimestampNow();
+ quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
-
+
// send the constructed list back to this node
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket,
- (currentBufferPos - startPointer) + numHeaderBytes,
+ nodeList->getNodeSocket().writeDatagram(broadcastPacket,
senderSockAddr.getAddress(), senderSockAddr.getPort());
}
- } else if (packetData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) {
-
- if (_assignmentQueue.size() > 0) {
- // construct the requested assignment from the packet data
- Assignment requestAssignment(packetData, receivedBytes);
-
- qDebug("Received a request for assignment type %i from %s.",
- requestAssignment.getType(), qPrintable(senderSockAddr.getAddress().toString()));
-
- Assignment* assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
-
- if (assignmentToDeploy) {
-
- // give this assignment out, either the type matches or the requestor said they will take any
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_CREATE_ASSIGNMENT);
- int numAssignmentBytes = assignmentToDeploy->packToBuffer(broadcastPacket + numHeaderBytes);
-
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, numHeaderBytes + numAssignmentBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
-
- if (assignmentToDeploy->getNumberOfInstances() == 0) {
- // there are no more instances of this script to send out, delete it
- delete assignmentToDeploy;
- }
- }
-
+ } else if (requestType == PacketTypeRequestAssignment) {
+
+ // construct the requested assignment from the packet data
+ Assignment requestAssignment(receivedPacket);
+
+ qDebug() << "Received a request for assignment type" << requestAssignment.getType()
+ << "from" << senderSockAddr;
+
+ SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
+
+ if (assignmentToDeploy) {
+ qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr;
+
+ // give this assignment out, either the type matches or the requestor said they will take any
+ assignmentPacket.resize(numAssignmentPacketHeaderBytes);
+
+ QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
+
+ assignmentStream << *assignmentToDeploy.data();
+
+ nodeList->getNodeSocket().writeDatagram(assignmentPacket,
+ senderSockAddr.getAddress(), senderSockAddr.getPort());
} else {
- qDebug() << "Received an invalid assignment request from" << senderSockAddr.getAddress();
+ qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
+ << "from" << senderSockAddr;
}
}
}
@@ -266,7 +362,7 @@ QJsonObject jsonObjectForNode(Node* node) {
QJsonObject nodeJson;
// re-format the type name so it matches the target name
- QString nodeTypeName(node->getTypeName());
+ QString nodeTypeName = NodeType::getNodeTypeName(node->getType());
nodeTypeName = nodeTypeName.toLower();
nodeTypeName.replace(' ', '-');
@@ -278,77 +374,13 @@ QJsonObject jsonObjectForNode(Node* node) {
nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket());
// if the node has pool information, add it
- if (node->getLinkedData() && ((Assignment*) node->getLinkedData())->hasPool()) {
- nodeJson[JSON_KEY_POOL] = QString(((Assignment*) node->getLinkedData())->getPool());
+ if (node->getLinkedData() && !((Assignment*) node->getLinkedData())->getPool().isEmpty()) {
+ nodeJson[JSON_KEY_POOL] = ((Assignment*) node->getLinkedData())->getPool();
}
return nodeJson;
}
-// Attempts to read configuration from specified path
-// returns true on success, false otherwise
-bool DomainServer::readConfigFile(const char* path) {
- if (!path) {
- // config file not specified
- return false;
- }
-
- if (!QFile::exists(path)) {
- qWarning("Specified configuration file does not exist!\n");
- return false;
- }
-
- QFile configFile(path);
- if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
- qWarning("Can't open specified configuration file!\n");
- return false;
- } else {
- qDebug("Reading configuration from %s\n", path);
- }
- QTextStream configStream(&configFile);
- QByteArray configStringByteArray = configStream.readAll().toUtf8();
- QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object();
- configFile.close();
-
- QString voxelServerConfig = readServerAssignmentConfig(configDocObject, VOXEL_SERVER_CONFIG);
- _voxelServerConfig = new char[strlen(voxelServerConfig.toLocal8Bit().constData()) +1];
- _voxelServerConfig = strcpy((char *) _voxelServerConfig, voxelServerConfig.toLocal8Bit().constData() + '\0');
-
- QString particleServerConfig = readServerAssignmentConfig(configDocObject, PARTICLE_SERVER_CONFIG);
- _particleServerConfig = new char[strlen(particleServerConfig.toLocal8Bit().constData()) +1];
- _particleServerConfig = strcpy((char *) _particleServerConfig, particleServerConfig.toLocal8Bit().constData() + '\0');
-
- QString metavoxelServerConfig = readServerAssignmentConfig(configDocObject, METAVOXEL_SERVER_CONFIG);
- _metavoxelServerConfig = new char[strlen(metavoxelServerConfig.toLocal8Bit().constData()) +1];
- _metavoxelServerConfig = strcpy((char *) _metavoxelServerConfig, metavoxelServerConfig.toLocal8Bit().constData() + '\0');
-
- return true;
-}
-
-// find assignment configurations on the specified node name and json object
-// returns a string in the form of its equivalent cmd line params
-QString DomainServer::readServerAssignmentConfig(QJsonObject jsonObject, const char* nodeName) {
- QJsonArray nodeArray = jsonObject[nodeName].toArray();
-
- QStringList serverConfig;
- foreach (const QJsonValue & childValue, nodeArray) {
- QString cmdParams;
- QJsonObject childObject = childValue.toObject();
- QStringList keys = childObject.keys();
- for (int i = 0; i < keys.size(); i++) {
- QString key = keys[i];
- QString value = childObject[key].toString();
- // both cmd line params and json keys are the same
- cmdParams += QString("--%1 %2 ").arg(key, value);
- }
- serverConfig << cmdParams;
- }
-
- // according to split() calls from DomainServer::prepopulateStaticAssignmentFile
- // we shold simply join them with semicolons
- return serverConfig.join(';');
-}
-
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) {
const QString JSON_MIME_TYPE = "application/json";
@@ -377,24 +409,19 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json
- std::deque::iterator assignment = _assignmentQueue.begin();
-
- while (assignment != _assignmentQueue.end()) {
+ foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) {
QJsonObject queuedAssignmentJSON;
- QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID());
- queuedAssignmentJSON[JSON_KEY_TYPE] = QString((*assignment)->getTypeName());
+ QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID());
+ queuedAssignmentJSON[JSON_KEY_TYPE] = QString(assignment->getTypeName());
// if the assignment has a pool, add it
- if ((*assignment)->hasPool()) {
- queuedAssignmentJSON[JSON_KEY_POOL] = QString((*assignment)->getPool());
+ if (!assignment->getPool().isEmpty()) {
+ queuedAssignmentJSON[JSON_KEY_POOL] = assignment->getPool();
}
// add this queued assignment to the JSON
queuedAssignmentsJSON[uuidString] = queuedAssignmentJSON;
-
- // push forward the iterator to check the next assignment
- assignment++;
}
assignmentJSON["queued"] = queuedAssignmentsJSON;
@@ -432,11 +459,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
// this is a script upload - ask the HTTPConnection to parse the form data
QList formData = connection->parseFormData();
- // create an assignment for this saved script, for now make it local only
- Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
- Assignment::AgentType,
- NULL,
- Assignment::LocalLocation);
+ // create an assignment for this saved script
+ Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType);
// check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header
const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES";
@@ -470,10 +494,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
connection->respond(HTTPConnection::StatusCode200);
// add the script assigment to the assignment queue
- // lock the assignment queue mutex since we're operating on a different thread than DS main
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(scriptAssignment);
- _assignmentQueueMutex.unlock();
+ _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
}
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
if (path.startsWith(URI_NODE)) {
@@ -508,326 +529,137 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
return false;
}
-void DomainServer::addReleasedAssignmentBackToQueue(Assignment* releasedAssignment) {
- qDebug() << "Adding assignment" << *releasedAssignment << " back to queue.";
-
- // find this assignment in the static file
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID() == releasedAssignment->getUUID()) {
- // reset the UUID on the static assignment
- _staticAssignments[i].resetUUID();
-
- // put this assignment back in the queue so it goes out
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(&_staticAssignments[i]);
- _assignmentQueueMutex.unlock();
-
- } else if (_staticAssignments[i].getUUID().isNull()) {
- // we are at the blank part of the static assignments - break out
- break;
- }
- }
+void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
+ QUuid oldUUID = assignment->getUUID();
+ assignment->resetUUID();
+
+ qDebug() << "Reset UUID for assignment -" << *assignment.data() << "- and added to queue. Old UUID was"
+ << uuidStringWithoutCurlyBraces(oldUUID);
+
+ // add the static assignment back under the right UUID, and to the queue
+ _staticAssignmentHash.insert(assignment->getUUID(), assignment);
+
+ _assignmentQueue.enqueue(assignment);
+
+ // remove the old assignment from the _staticAssignmentHash
+ // this must be done last so copies are created before the assignment passed by reference is killed
+ _staticAssignmentHash.remove(oldUUID);
}
void DomainServer::nodeKilled(SharedNodePointer node) {
- // if this node has linked data it was from an assignment
- if (node->getLinkedData()) {
- Assignment* nodeAssignment = (Assignment*) node->getLinkedData();
-
- addReleasedAssignmentBackToQueue(nodeAssignment);
+ // 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());
+
+ if (matchedAssignment) {
+ refreshStaticAssignmentAndAddToQueue(matchedAssignment);
}
}
-unsigned char* DomainServer::addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd) {
- *currentPosition++ = nodeToAdd->getType();
-
-
- QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122();
- memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size());
- currentPosition += rfcUUID.size();
-
- currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getPublicSocket());
- currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getLocalSocket());
-
- // return the new unsigned char * for broadcast packet
- return currentPosition;
-}
-
-void DomainServer::prepopulateStaticAssignmentFile() {
- int numFreshStaticAssignments = 0;
-
- // write a fresh static assignment array to file
-
- Assignment freshStaticAssignments[MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS];
-
- // pre-populate the first static assignment list with assignments for root AuM, AvM, VS
- freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AudioMixerType);
- freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AvatarMixerType);
-
- // Handle Domain/Voxel Server configuration command line arguments
- if (_voxelServerConfig) {
- qDebug("Reading Voxel Server Configuration.");
- qDebug() << "config: " << _voxelServerConfig;
-
- QString multiConfig((const char*) _voxelServerConfig);
- QStringList multiConfigList = multiConfig.split(";");
-
- // read each config to a payload for a VS assignment
- for (int i = 0; i < multiConfigList.size(); i++) {
- QString config = multiConfigList.at(i);
-
- qDebug("config[%d]=%s", i, config.toLocal8Bit().constData());
-
- // Now, parse the config to check for a pool
- const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool";
- QString assignmentPool;
-
- int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION);
-
- if (poolIndex >= 0) {
- int spaceBeforePoolIndex = config.indexOf(' ', poolIndex);
- int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex);
-
- assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex);
- qDebug() << "The pool for this voxel-assignment is" << assignmentPool;
- }
-
- Assignment voxelServerAssignment(Assignment::CreateCommand,
- Assignment::VoxelServerType,
- (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData()));
-
- int payloadLength = config.length() + sizeof(char);
- voxelServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength);
-
- freshStaticAssignments[numFreshStaticAssignments++] = voxelServerAssignment;
- }
- } else {
- Assignment rootVoxelServerAssignment(Assignment::CreateCommand, Assignment::VoxelServerType);
- freshStaticAssignments[numFreshStaticAssignments++] = rootVoxelServerAssignment;
- }
-
- // Handle Domain/Particle Server configuration command line arguments
- if (_particleServerConfig) {
- qDebug("Reading Particle Server Configuration.");
- qDebug() << "config: " << _particleServerConfig;
-
- QString multiConfig((const char*) _particleServerConfig);
- QStringList multiConfigList = multiConfig.split(";");
-
- // read each config to a payload for a VS assignment
- for (int i = 0; i < multiConfigList.size(); i++) {
- QString config = multiConfigList.at(i);
-
- qDebug("config[%d]=%s", i, config.toLocal8Bit().constData());
-
- // Now, parse the config to check for a pool
- const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool";
- QString assignmentPool;
-
- int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION);
-
- if (poolIndex >= 0) {
- int spaceBeforePoolIndex = config.indexOf(' ', poolIndex);
- int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex);
-
- assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex);
- qDebug() << "The pool for this particle-assignment is" << assignmentPool;
- }
-
- Assignment particleServerAssignment(Assignment::CreateCommand,
- Assignment::ParticleServerType,
- (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData()));
-
- int payloadLength = config.length() + sizeof(char);
- particleServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength);
-
- freshStaticAssignments[numFreshStaticAssignments++] = particleServerAssignment;
- }
- } else {
- Assignment rootParticleServerAssignment(Assignment::CreateCommand, Assignment::ParticleServerType);
- freshStaticAssignments[numFreshStaticAssignments++] = rootParticleServerAssignment;
- }
-
- // handle metavoxel configuration command line argument
- Assignment& metavoxelAssignment = (freshStaticAssignments[numFreshStaticAssignments++] =
- Assignment(Assignment::CreateCommand, Assignment::MetavoxelServerType));
- if (_metavoxelServerConfig) {
- metavoxelAssignment.setPayload((const unsigned char*)_metavoxelServerConfig, strlen(_metavoxelServerConfig));
- }
-
- qDebug() << "Adding" << numFreshStaticAssignments << "static assignments to fresh file.";
-
- _staticAssignmentFile.open(QIODevice::WriteOnly);
- _staticAssignmentFile.write((char*) &freshStaticAssignments, sizeof(freshStaticAssignments));
- _staticAssignmentFile.resize(MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS * sizeof(Assignment));
- _staticAssignmentFile.close();
-}
-
-Assignment* DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType) {
- // pull the UUID passed with the check in
-
+SharedAssignmentPointer DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
if (_hasCompletedRestartHold) {
- _assignmentQueueMutex.lock();
-
- // iterate the assignment queue to check for a match
- std::deque::iterator assignment = _assignmentQueue.begin();
- while (assignment != _assignmentQueue.end()) {
- if ((*assignment)->getUUID() == checkInUUID) {
- // return the matched assignment
- _assignmentQueueMutex.unlock();
- return *assignment;
+ // look for a match in the assignment hash
+
+ QQueue::iterator i = _assignmentQueue.begin();
+
+ while (i != _assignmentQueue.end()) {
+ if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
+ return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
} else {
- // no match, push deque iterator forwards
- assignment++;
+ ++i;
}
}
-
- _assignmentQueueMutex.unlock();
} else {
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID() == checkInUUID) {
- // return matched assignment
- return &_staticAssignments[i];
- } else if (_staticAssignments[i].getUUID().isNull()) {
- // end of static assignments, no match - return NULL
- return NULL;
- }
+ SharedAssignmentPointer matchingStaticAssignment = _staticAssignmentHash.value(checkInUUID);
+ if (matchingStaticAssignment && matchingStaticAssignment->getType() == nodeType) {
+ return matchingStaticAssignment;
}
}
- return NULL;
+ return SharedAssignmentPointer();
}
-Assignment* DomainServer::deployableAssignmentForRequest(Assignment& requestAssignment) {
- _assignmentQueueMutex.lock();
-
+SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
// this is an unassigned client talking to us directly for an assignment
// go through our queue and see if there are any assignments to give out
- std::deque::iterator assignment = _assignmentQueue.begin();
+ QQueue::iterator sharedAssignment = _assignmentQueue.begin();
- while (assignment != _assignmentQueue.end()) {
+ while (sharedAssignment != _assignmentQueue.end()) {
+ Assignment* assignment = sharedAssignment->data();
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
- bool assignmentTypesMatch = (*assignment)->getType() == requestAssignment.getType();
- bool nietherHasPool = !(*assignment)->hasPool() && !requestAssignment.hasPool();
- bool assignmentPoolsMatch = memcmp((*assignment)->getPool(),
- requestAssignment.getPool(),
- MAX_ASSIGNMENT_POOL_BYTES) == 0;
-
+ bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
+ bool nietherHasPool = assignment->getPool().isEmpty() && requestAssignment.getPool().isEmpty();
+ bool assignmentPoolsMatch = assignment->getPool() == requestAssignment.getPool();
+
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
- Assignment* deployableAssignment = *assignment;
-
- if ((*assignment)->getType() == Assignment::AgentType) {
+ if (assignment->getType() == Assignment::AgentType) {
// if there is more than one instance to send out, simply decrease the number of instances
- if ((*assignment)->getNumberOfInstances() == 1) {
- _assignmentQueue.erase(assignment);
+ if (assignment->getNumberOfInstances() == 1) {
+ return _assignmentQueue.takeAt(sharedAssignment - _assignmentQueue.begin());
+ } else {
+ assignment->decrementNumberOfInstances();
+ return *sharedAssignment;
}
- deployableAssignment->decrementNumberOfInstances();
-
} else {
// remove the assignment from the queue
- _assignmentQueue.erase(assignment);
+ SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment
+ - _assignmentQueue.begin());
// until we get a check-in from that GUID
// put assignment back in queue but stick it at the back so the others have a chance to go out
- _assignmentQueue.push_back(deployableAssignment);
+ _assignmentQueue.enqueue(deployableAssignment);
+
+ // stop looping, we've handed out an assignment
+ return deployableAssignment;
}
-
- // stop looping, we've handed out an assignment
- _assignmentQueueMutex.unlock();
- return deployableAssignment;
} else {
// push forward the iterator to check the next assignment
- assignment++;
+ ++sharedAssignment;
}
}
-
- _assignmentQueueMutex.unlock();
- return NULL;
+
+ return SharedAssignmentPointer();
}
-void DomainServer::removeAssignmentFromQueue(Assignment* removableAssignment) {
-
- _assignmentQueueMutex.lock();
-
- std::deque::iterator assignment = _assignmentQueue.begin();
-
- while (assignment != _assignmentQueue.end()) {
- if ((*assignment)->getUUID() == removableAssignment->getUUID()) {
- _assignmentQueue.erase(assignment);
+void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
+ QQueue::iterator potentialMatchingAssignment = _assignmentQueue.begin();
+ while (potentialMatchingAssignment != _assignmentQueue.end()) {
+ if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
+ _assignmentQueue.erase(potentialMatchingAssignment);
+
+ // we matched and removed an assignment, bail out
break;
} else {
- // push forward the iterator to check the next assignment
- assignment++;
+ ++potentialMatchingAssignment;
}
}
-
- _assignmentQueueMutex.unlock();
-}
-
-bool DomainServer::checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket,
- const HifiSockAddr& nodeLocalSocket,
- const QUuid& checkInUUID) {
- NodeList* nodeList = NodeList::getInstance();
-
- foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData()
- && nodePublicSocket == node->getPublicSocket()
- && nodeLocalSocket == node->getLocalSocket()
- && node->getUUID() == checkInUUID) {
- // this is a matching existing node if the public socket, local socket, and UUID match
- return true;
- }
- }
-
- return false;
}
void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() {
_hasCompletedRestartHold = true;
// if the domain-server has just restarted,
- // check if there are static assignments in the file that we need to
- // throw into the assignment queue
-
- // pull anything in the static assignment file that isn't spoken for and add to the assignment queue
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID().isNull()) {
- // reached the end of static assignments, bail
- break;
- }
-
+ // check if there are static assignments that we need to throw into the assignment queue
+ QHash staticHashCopy = _staticAssignmentHash;
+ QHash::iterator staticAssignment = staticHashCopy.begin();
+ while (staticAssignment != staticHashCopy.end()) {
+ // add any of the un-matched static assignments to the queue
bool foundMatchingAssignment = false;
-
- NodeList* nodeList = NodeList::getInstance();
-
+
// enumerate the nodes and check if there is one with an attached assignment with matching UUID
- foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData()) {
- Assignment* linkedAssignment = (Assignment*) node->getLinkedData();
- if (linkedAssignment->getUUID() == _staticAssignments[i].getUUID()) {
- foundMatchingAssignment = true;
- break;
- }
+ foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
+ if (node->getUUID() == staticAssignment->data()->getUUID()) {
+ foundMatchingAssignment = true;
}
}
-
+
if (!foundMatchingAssignment) {
// this assignment has not been fulfilled - reset the UUID and add it to the assignment queue
- _staticAssignments[i].resetUUID();
-
- qDebug() << "Adding static assignment to queue -" << _staticAssignments[i];
-
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(&_staticAssignments[i]);
- _assignmentQueueMutex.unlock();
+ refreshStaticAssignmentAndAddToQueue(*staticAssignment);
}
+
+ ++staticAssignment;
}
}
-
-void DomainServer::cleanup() {
- _staticAssignmentFile.unmap(_staticAssignmentFileData);
- _staticAssignmentFile.close();
-}
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index fed5aaaa43..60251b3bb4 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -9,17 +9,16 @@
#ifndef __hifi__DomainServer__
#define __hifi__DomainServer__
-#include
-
#include
-#include
-#include
+#include
+#include
+#include
#include
#include
#include
-const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000;
+typedef QSharedPointer SharedAssignmentPointer;
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
Q_OBJECT
@@ -35,39 +34,27 @@ public slots:
void nodeKilled(SharedNodePointer node);
private:
- bool readConfigFile(const char* path);
- QString readServerAssignmentConfig(QJsonObject jsonObj, const char* nodeName);
-
- void prepopulateStaticAssignmentFile();
- Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType);
- Assignment* deployableAssignmentForRequest(Assignment& requestAssignment);
- void removeAssignmentFromQueue(Assignment* removableAssignment);
- bool checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket,
- const HifiSockAddr& nodeLocalSocket,
- const QUuid& checkInUUI);
- void addReleasedAssignmentBackToQueue(Assignment* releasedAssignment);
+ void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes);
+ void readConfigFile(const QString& path, QSet& excludedTypes);
+ QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName);
+ void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
+ void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString);
+ void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes);
- unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd);
+ SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
+ SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
+ void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
+ void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
HTTPManager _HTTPManager;
- QMutex _assignmentQueueMutex;
- std::deque _assignmentQueue;
-
- QFile _staticAssignmentFile;
- uchar* _staticAssignmentFileData;
-
- Assignment* _staticAssignments;
-
- const char* _voxelServerConfig;
- const char* _particleServerConfig;
- const char* _metavoxelServerConfig;
+ QHash _staticAssignmentHash;
+ QQueue _assignmentQueue;
bool _hasCompletedRestartHold;
private slots:
void readAvailableDatagrams();
void addStaticAssignmentsBackToQueueAfterRestart();
- void cleanup();
};
#endif /* defined(__hifi__DomainServer__) */
diff --git a/examples/clap.js b/examples/clap.js
index fdd2b29aa2..81ccda64b7 100644
--- a/examples/clap.js
+++ b/examples/clap.js
@@ -62,4 +62,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
diff --git a/examples/collidingParticles.js b/examples/collidingParticles.js
index cf1fce5660..81ccfe108b 100644
--- a/examples/collidingParticles.js
+++ b/examples/collidingParticles.js
@@ -132,7 +132,7 @@ function draw() {
print(scriptB);
numberParticlesAdded++;
} else {
- Agent.stop();
+ Script.stop();
}
print("Particles Stats: " + Particles.getLifetimeInSeconds() + " seconds," +
@@ -150,5 +150,5 @@ function draw() {
// register the call back so it fires before each data send
print("here...\n");
Particles.setPacketsPerSecond(40000);
-Agent.willSendVisualDataCallback.connect(draw);
+Script.willSendVisualDataCallback.connect(draw);
print("and here...\n");
diff --git a/examples/controllerExample.js b/examples/controllerExample.js
new file mode 100644
index 0000000000..43eb516cee
--- /dev/null
+++ b/examples/controllerExample.js
@@ -0,0 +1,202 @@
+//
+// controllerExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+// initialize our triggers
+var triggerPulled = new Array();
+var numberOfTriggers = Controller.getNumberOfTriggers();
+for (t = 0; t < numberOfTriggers; t++) {
+ triggerPulled[t] = false;
+}
+
+function checkController() {
+ var numberOfTriggers = Controller.getNumberOfTriggers();
+ var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
+ var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
+
+ // this is expected for hydras
+ if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
+ for (var t = 0; t < numberOfTriggers; t++) {
+ var triggerValue = Controller.getTriggerValue(t);
+
+ if (triggerPulled[t]) {
+ // must release to at least 0.1
+ if (triggerValue < 0.1) {
+ triggerPulled[t] = false; // unpulled
+ }
+ } else {
+ // must pull to at least 0.9
+ if (triggerValue > 0.9) {
+ triggerPulled[t] = true; // pulled
+ triggerToggled = true;
+ }
+ }
+
+ if (triggerToggled) {
+ print("a trigger was toggled");
+ }
+ }
+ }
+}
+
+function keyPressEvent(event) {
+ print("keyPressEvent event.key=" + event.key);
+ print("keyPressEvent event.text=" + event.text);
+
+ print("keyPressEvent event.isShifted=" + event.isShifted);
+ print("keyPressEvent event.isControl=" + event.isControl);
+ print("keyPressEvent event.isMeta=" + event.isMeta);
+ print("keyPressEvent event.isAlt=" + event.isAlt);
+ print("keyPressEvent event.isKeypad=" + event.isKeypad);
+
+
+ if (event.key == "A".charCodeAt(0)) {
+ print("the A key was pressed");
+ }
+ if (event.key == " ".charCodeAt(0)) {
+ print("the key was pressed");
+ }
+}
+
+function mouseMoveEvent(event) {
+ print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchBeginEvent(event) {
+ print("touchBeginEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchUpdateEvent(event) {
+ print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchEndEvent(event) {
+ print("touchEndEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(checkController);
+
+// Map keyPress and mouse move events to our callbacks
+Controller.keyPressEvent.connect(keyPressEvent);
+var KeyEvent_A = {
+ key: "A".charCodeAt(0),
+ text: "A",
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a = {
+ text: "a",
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a2 = {
+ key: "a".charCodeAt(0),
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a3 = {
+ text: "a"
+};
+
+var KeyEvent_A2 = {
+ text: "A"
+};
+
+
+var KeyEvent_9 = {
+ text: "9"
+};
+
+var KeyEvent_Num = {
+ text: "#"
+};
+
+var KeyEvent_At = {
+ text: "@"
+};
+
+var KeyEvent_MetaAt = {
+ text: "@",
+ isMeta: true
+};
+
+var KeyEvent_Up = {
+ text: "up"
+};
+var KeyEvent_Down = {
+ text: "down"
+};
+var KeyEvent_Left = {
+ text: "left"
+};
+var KeyEvent_Right = {
+ text: "right"
+};
+
+// prevent the A key from going through to the application
+print("KeyEvent_A");
+Controller.captureKeyEvents(KeyEvent_A);
+
+print("KeyEvent_A2");
+Controller.captureKeyEvents(KeyEvent_A2);
+
+print("KeyEvent_a");
+Controller.captureKeyEvents(KeyEvent_a);
+
+print("KeyEvent_a2");
+Controller.captureKeyEvents(KeyEvent_a2);
+
+print("KeyEvent_a3");
+Controller.captureKeyEvents(KeyEvent_a3);
+
+print("KeyEvent_9");
+Controller.captureKeyEvents(KeyEvent_9);
+
+print("KeyEvent_Num");
+Controller.captureKeyEvents(KeyEvent_Num);
+
+print("KeyEvent_At");
+Controller.captureKeyEvents(KeyEvent_At);
+
+print("KeyEvent_MetaAt");
+Controller.captureKeyEvents(KeyEvent_MetaAt);
+
+print("KeyEvent_Up");
+Controller.captureKeyEvents(KeyEvent_Up);
+print("KeyEvent_Down");
+Controller.captureKeyEvents(KeyEvent_Down);
+print("KeyEvent_Left");
+Controller.captureKeyEvents(KeyEvent_Left);
+print("KeyEvent_Right");
+Controller.captureKeyEvents(KeyEvent_Right);
+
+
+
+
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+
+// Map touch events to our callbacks
+Controller.touchBeginEvent.connect(touchBeginEvent);
+Controller.touchUpdateEvent.connect(touchUpdateEvent);
+Controller.touchEndEvent.connect(touchEndEvent);
+
+// disable the standard application for touch events
+Controller.captureTouchEvents();
+
+function scriptEnding() {
+ // re-enabled the standard application for touch events
+ Controller.releaseTouchEvents();
+}
+
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/count.js b/examples/count.js
index 917bec342d..29799a8271 100644
--- a/examples/count.js
+++ b/examples/count.js
@@ -20,7 +20,7 @@ function scriptEnding() {
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(displayCount);
+Script.willSendVisualDataCallback.connect(displayCount);
// register our scriptEnding callback
-Agent.scriptEnding.connect(scriptEnding);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/drumStick.js b/examples/drumStick.js
index 955fddbdee..78de8351db 100644
--- a/examples/drumStick.js
+++ b/examples/drumStick.js
@@ -69,4 +69,4 @@ function checkSticks() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(checkSticks);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(checkSticks);
\ No newline at end of file
diff --git a/examples/editParticleExample.js b/examples/editParticleExample.js
index 4eb5dfe907..152bb18fca 100644
--- a/examples/editParticleExample.js
+++ b/examples/editParticleExample.js
@@ -9,6 +9,9 @@
//
var count = 0;
+var moveUntil = 2000;
+var stopAfter = moveUntil + 100;
+var expectedLifetime = (moveUntil/60) + 1; // 1 second after done moving...
var originalProperties = {
position: { x: 10,
@@ -28,7 +31,9 @@ var originalProperties = {
color: { red: 0,
green: 255,
- blue: 0 }
+ blue: 0 },
+
+ lifetime: expectedLifetime
};
@@ -38,19 +43,18 @@ var positionDelta = { x: 0.05, y: 0, z: 0 };
var particleID = Particles.addParticle(originalProperties);
function moveParticle() {
- if (count >= 100) {
- //Agent.stop();
+ if (count >= moveUntil) {
// delete it...
- if (count == 100) {
+ if (count == moveUntil) {
print("calling Particles.deleteParticle()");
Particles.deleteParticle(particleID);
}
// stop it...
- if (count >= 200) {
- print("calling Agent.stop()");
- Agent.stop();
+ if (count >= stopAfter) {
+ print("calling Script.stop()");
+ Script.stop();
}
count++;
@@ -77,19 +81,9 @@ function moveParticle() {
print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
Particles.editParticle(particleID, newProperties);
-
- // also check to see if we can "find" particles...
- var searchAt = { x: 9, y: 0, z: 0};
- var searchRadius = 2;
- var foundParticle = Particles.findClosestParticle(searchAt, searchRadius);
- if (foundParticle.isKnownID) {
- print("found particle:" + foundParticle.id);
- } else {
- print("could not find particle in or around x=9 to x=11:");
- }
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(moveParticle);
+Script.willSendVisualDataCallback.connect(moveParticle);
diff --git a/examples/findParticleExample.js b/examples/findParticleExample.js
index f582ee6469..5eb257d502 100644
--- a/examples/findParticleExample.js
+++ b/examples/findParticleExample.js
@@ -1,5 +1,5 @@
//
-// editParticleExample.js
+// findParticleExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/24/14.
@@ -65,8 +65,8 @@ function findParticles() {
// run for a while, then clean up
// stop it...
if (iteration >= 100) {
- print("calling Agent.stop()");
- Agent.stop();
+ print("calling Script.stop()");
+ Script.stop();
}
print("--------------------------");
@@ -122,7 +122,7 @@ function findParticles() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(findParticles);
+Script.willSendVisualDataCallback.connect(findParticles);
// register our scriptEnding callback
-Agent.scriptEnding.connect(scriptEnding);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/fountain.js b/examples/fountain.js
index a095f91ed3..3c943d72a0 100644
--- a/examples/fountain.js
+++ b/examples/fountain.js
@@ -60,8 +60,8 @@ function makeFountain() {
totalParticles++;
}
if (totalParticles > 100) {
- Agent.stop();
+ Script.stop();
}
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(makeFountain);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(makeFountain);
\ No newline at end of file
diff --git a/examples/gameoflife.js b/examples/gameoflife.js
new file mode 100644
index 0000000000..6779941dc7
--- /dev/null
+++ b/examples/gameoflife.js
@@ -0,0 +1,133 @@
+// Add your JavaScript for assignment below this line
+
+// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life)
+
+var NUMBER_OF_CELLS_EACH_DIMENSION = 64;
+var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION;
+
+var currentCells = [];
+var nextCells = [];
+
+var METER_LENGTH = 1;
+var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
+
+// randomly populate the cell start values
+for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ // create the array to hold this row
+ currentCells[i] = [];
+
+ // create the array to hold this row in the nextCells array
+ nextCells[i] = [];
+
+ for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ currentCells[i][j] = Math.floor(Math.random() * 2);
+
+ // put the same value in the nextCells array for first board draw
+ nextCells[i][j] = currentCells[i][j];
+ }
+}
+
+function isNeighbourAlive(i, j) {
+ if (i < 0 || i >= NUMBER_OF_CELLS_EACH_DIMENSION
+ || i < 0 || j >= NUMBER_OF_CELLS_EACH_DIMENSION) {
+ return 0;
+ } else {
+ return currentCells[i][j];
+ }
+}
+
+function updateCells() {
+ var i = 0;
+ var j = 0;
+
+ for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ // figure out the number of live neighbours for the i-j cell
+ var liveNeighbours =
+ isNeighbourAlive(i + 1, j - 1) + isNeighbourAlive(i + 1, j) + isNeighbourAlive(i + 1, j + 1) +
+ isNeighbourAlive(i, j - 1) + isNeighbourAlive(i, j + 1) +
+ isNeighbourAlive(i - 1, j - 1) + isNeighbourAlive(i - 1, j) + isNeighbourAlive(i - 1, j + 1);
+
+ if (currentCells[i][j]) {
+ // live cell
+
+ if (liveNeighbours < 2) {
+ // rule #1 - under-population - this cell will die
+ // mark it zero to mark the change
+ nextCells[i][j] = 0;
+ } else if (liveNeighbours < 4) {
+ // rule #2 - this cell lives
+ // mark it -1 to mark no change
+ nextCells[i][j] = -1;
+ } else {
+ // rule #3 - overcrowding - this cell dies
+ // mark it zero to mark the change
+ nextCells[i][j] = 0;
+ }
+ } else {
+ // dead cell
+ if (liveNeighbours == 3) {
+ // rule #4 - reproduction - this cell revives
+ // mark it one to mark the change
+ nextCells[i][j] = 1;
+ } else {
+ // this cell stays dead
+ // mark it -1 for no change
+ nextCells[i][j] = -1;
+ }
+ }
+
+ if (Math.random() < 0.001) {
+ // Random mutation to keep things interesting in there.
+ nextCells[i][j] = 1;
+ }
+ }
+ }
+
+ for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ if (nextCells[i][j] != -1) {
+ // there has been a change to this cell, change the value in the currentCells array
+ currentCells[i][j] = nextCells[i][j];
+ }
+ }
+ }
+}
+
+function sendNextCells() {
+ for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ if (nextCells[i][j] != -1) {
+ // there has been a change to the state of this cell, send it
+
+ // find the x and y position for this voxel, z = 0
+ var x = j * cellScale;
+ var y = i * cellScale;
+
+ // queue a packet to add a voxel for the new cell
+ var color = (nextCells[i][j] == 1) ? 255 : 1;
+ Voxels.setVoxel(x, y, 0, cellScale, color, color, color);
+ }
+ }
+ }
+}
+
+var sentFirstBoard = false;
+
+function step() {
+print("step()...");
+ if (sentFirstBoard) {
+ // we've already sent the first full board, perform a step in time
+ updateCells();
+ } else {
+ // this will be our first board send
+ sentFirstBoard = true;
+ }
+
+ sendNextCells();
+}
+
+print("here");
+Script.willSendVisualDataCallback.connect(step);
+Voxels.setPacketsPerSecond(200);
+print("now here");
diff --git a/examples/globalCollisionsExample.js b/examples/globalCollisionsExample.js
new file mode 100644
index 0000000000..4db4c808e5
--- /dev/null
+++ b/examples/globalCollisionsExample.js
@@ -0,0 +1,26 @@
+//
+// globalCollisionsExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/29/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+
+function particleCollisionWithVoxel(particle, voxel) {
+ print("particleCollisionWithVoxel()..");
+ print(" particle.getID()=" + particle.id);
+ print(" voxel color...=" + voxel.red + ", " + voxel.green + ", " + voxel.blue);
+}
+
+function particleCollisionWithParticle(particleA, particleB) {
+ print("particleCollisionWithParticle()..");
+ print(" particleA.getID()=" + particleA.id);
+ print(" particleB.getID()=" + particleB.id);
+}
+
+Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
+Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
diff --git a/examples/gun.js b/examples/gun.js
index 30d2b41449..e7cd2973e2 100644
--- a/examples/gun.js
+++ b/examples/gun.js
@@ -72,13 +72,13 @@ function checkController() {
" function collisionWithVoxel(voxel) { " +
" print('collisionWithVoxel(voxel)... '); " +
" print('myID=' + Particle.getID() + '\\n'); " +
- " var voxelColor = voxel.getColor();" +
- " print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
+ " var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
+ " var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
+ " var voxelScale = voxel.s;" +
+ " print('voxelColor=' + voxel.red + ', ' + voxel.green + ', ' + voxel.blue + '\\n'); " +
" var myColor = Particle.getColor();" +
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
" Particle.setColor(voxelColor); " +
- " var voxelAt = voxel.getPosition();" +
- " var voxelScale = voxel.getScale();" +
" Voxels.eraseVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); " +
" print('Voxels.eraseVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
@@ -99,4 +99,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js
new file mode 100644
index 0000000000..460663c054
--- /dev/null
+++ b/examples/lookWithMouse.js
@@ -0,0 +1,95 @@
+//
+// lookWithMouse.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+var alwaysLook = true; // if you want the mouse look to happen only when you click, change this to false
+var isMouseDown = false;
+var lastX = 0;
+var lastY = 0;
+var yawFromMouse = 0;
+var pitchFromMouse = 0;
+var wantDebugging = false;
+
+function mousePressEvent(event) {
+ if (wantDebugging) {
+ print("mousePressEvent event.x,y=" + event.x + ", " + event.y);
+ }
+ isMouseDown = true;
+ lastX = event.x;
+ lastY = event.y;
+}
+
+function mouseReleaseEvent(event) {
+ if (wantDebugging) {
+ print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y);
+ }
+ isMouseDown = false;
+}
+
+function mouseMoveEvent(event) {
+ if (wantDebugging) {
+ print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
+ }
+
+ if (alwaysLook || isMouseDown) {
+ var MOUSE_YAW_SCALE = -0.25;
+ var MOUSE_PITCH_SCALE = -12.5;
+ var FIXED_MOUSE_TIMESTEP = 0.016;
+ yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
+ pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
+ lastX = event.x;
+ lastY = event.y;
+ }
+}
+
+function update() {
+ if (wantDebugging) {
+ print("update()...");
+ }
+ // rotate body yaw for yaw received from mouse
+ var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
+ if (wantDebugging) {
+ print("changing orientation"
+ + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + ","
+ + MyAvatar.orientation.z + "," + MyAvatar.orientation.w
+ + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w);
+ }
+ MyAvatar.orientation = newOrientation;
+ yawFromMouse = 0;
+
+ // apply pitch from mouse
+ var newPitch = MyAvatar.headPitch + pitchFromMouse;
+ if (wantDebugging) {
+ print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch);
+ }
+ MyAvatar.headPitch = newPitch;
+ pitchFromMouse = 0;
+}
+
+// Map the mouse events to our functions
+Controller.mousePressEvent.connect(mousePressEvent);
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
+
+// disable the standard application for mouse events
+Controller.captureMouseEvents();
+
+function scriptEnding() {
+ // re-enabled the standard application for mouse events
+ Controller.releaseMouseEvents();
+}
+
+MyAvatar.bodyYaw = 0;
+MyAvatar.bodyPitch = 0;
+MyAvatar.bodyRoll = 0;
+
+// would be nice to change to update
+Script.willSendVisualDataCallback.connect(update);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/lookWithTouch.js b/examples/lookWithTouch.js
new file mode 100644
index 0000000000..bb54a055c4
--- /dev/null
+++ b/examples/lookWithTouch.js
@@ -0,0 +1,86 @@
+//
+// lookWithTouch.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+var lastX = 0;
+var lastY = 0;
+var yawFromMouse = 0;
+var pitchFromMouse = 0;
+var wantDebugging = false;
+
+function touchBeginEvent(event) {
+ if (wantDebugging) {
+ print("touchBeginEvent event.x,y=" + event.x + ", " + event.y);
+ }
+ lastX = event.x;
+ lastY = event.y;
+}
+
+function touchEndEvent(event) {
+ if (wantDebugging) {
+ print("touchEndEvent event.x,y=" + event.x + ", " + event.y);
+ }
+}
+
+function touchUpdateEvent(event) {
+ if (wantDebugging) {
+ print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y);
+ }
+
+ var MOUSE_YAW_SCALE = -0.25;
+ var MOUSE_PITCH_SCALE = -12.5;
+ var FIXED_MOUSE_TIMESTEP = 0.016;
+ yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
+ pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
+ lastX = event.x;
+ lastY = event.y;
+}
+
+function update() {
+ // rotate body yaw for yaw received from mouse
+ var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
+ if (wantDebugging) {
+ print("changing orientation"
+ + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + ","
+ + MyAvatar.orientation.z + "," + MyAvatar.orientation.w
+ + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w);
+ }
+ MyAvatar.orientation = newOrientation;
+ yawFromMouse = 0;
+
+ // apply pitch from mouse
+ var newPitch = MyAvatar.headPitch + pitchFromMouse;
+ if (wantDebugging) {
+ print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch);
+ }
+ MyAvatar.headPitch = newPitch;
+ pitchFromMouse = 0;
+}
+
+// Map the mouse events to our functions
+Controller.touchBeginEvent.connect(touchBeginEvent);
+Controller.touchUpdateEvent.connect(touchUpdateEvent);
+Controller.touchEndEvent.connect(touchEndEvent);
+
+// disable the standard application for mouse events
+Controller.captureTouchEvents();
+
+function scriptEnding() {
+ // re-enabled the standard application for mouse events
+ Controller.releaseTouchEvents();
+}
+
+MyAvatar.bodyYaw = 0;
+MyAvatar.bodyPitch = 0;
+MyAvatar.bodyRoll = 0;
+
+// would be nice to change to update
+Script.willSendVisualDataCallback.connect(update);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/movingVoxel.js b/examples/movingVoxel.js
index 0aadf7b30c..14a7e671c6 100644
--- a/examples/movingVoxel.js
+++ b/examples/movingVoxel.js
@@ -41,4 +41,4 @@ function moveVoxel() {
Voxels.setPacketsPerSecond(300);
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(moveVoxel);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(moveVoxel);
\ No newline at end of file
diff --git a/examples/paintGun.js b/examples/paintGun.js
index 0cbe3ff366..56e916a183 100644
--- a/examples/paintGun.js
+++ b/examples/paintGun.js
@@ -65,13 +65,13 @@ function checkController() {
" function collisionWithVoxel(voxel) { " +
" print('collisionWithVoxel(voxel)... '); " +
" print('myID=' + Particle.getID() + '\\n'); " +
- " var voxelColor = voxel.getColor();" +
+ " var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
+ " var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
+ " var voxelScale = voxel.s;" +
" print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
" var myColor = Particle.getColor();" +
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
" Particle.setColor(voxelColor); " +
- " var voxelAt = voxel.getPosition();" +
- " var voxelScale = voxel.getScale();" +
" Voxels.setVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale, 255, 255, 0); " +
" print('Voxels.setVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
@@ -93,4 +93,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/particleBird.js b/examples/particleBird.js
index 036b86bf0b..440a91166c 100644
--- a/examples/particleBird.js
+++ b/examples/particleBird.js
@@ -104,8 +104,9 @@ function moveBird() {
// check to see if we've been running long enough that our bird is dead
var nowTimeInSeconds = new Date().getTime() / 1000;
if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) {
- print("This bird has died, how sad.");
- Agent.stop();
+
+ print("our bird is dying, stop our script");
+ Script.stop();
return;
}
@@ -193,4 +194,4 @@ function moveBird() {
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(moveBird);
+Script.willSendVisualDataCallback.connect(moveBird);
diff --git a/examples/particleModelExample.js b/examples/particleModelExample.js
new file mode 100644
index 0000000000..e95cc0c2bf
--- /dev/null
+++ b/examples/particleModelExample.js
@@ -0,0 +1,51 @@
+//
+// particleModelExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates creating and editing a particle
+//
+
+var count = 0;
+var stopAfter = 100;
+
+var modelProperties = {
+ position: { x: 1, y: 1, z: 1 },
+ velocity: { x: 0.5, y: 0, z: 0.5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius : 0.25,
+ modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
+ lifetime: 20
+};
+
+var ballProperties = {
+ position: { x: 1, y: 0.5, z: 1 },
+ velocity: { x: 0.5, y: 0, z: 0.5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius : 0.25,
+ color: { red: 255, green: 0, blue: 0 },
+ lifetime: 20
+};
+
+var modelParticleID = Particles.addParticle(modelProperties);
+var ballParticleID = Particles.addParticle(ballProperties);
+
+function endAfterAWhile() {
+ // stop it...
+ if (count >= stopAfter) {
+ print("calling Script.stop()");
+ Script.stop();
+ }
+
+ print("count =" + count);
+ count++;
+}
+
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(endAfterAWhile);
+
diff --git a/examples/playSound.js b/examples/playSound.js
index 6631d5526a..657f052aa5 100644
--- a/examples/playSound.js
+++ b/examples/playSound.js
@@ -17,4 +17,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
diff --git a/examples/rideAlongWithAParticleExample.js b/examples/rideAlongWithAParticleExample.js
new file mode 100644
index 0000000000..b2b6627063
--- /dev/null
+++ b/examples/rideAlongWithAParticleExample.js
@@ -0,0 +1,50 @@
+//
+// rideAlongWithAParticleExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/24/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates "finding" particles
+//
+
+var iteration = 0;
+var lengthOfRide = 2000; // in iterations
+
+var particleA = Particles.addParticle(
+ {
+ position: { x: 10, y: 0, z: 10 },
+ velocity: { x: 5, y: 0, z: 5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ radius : 0.1,
+ color: { red: 0, green: 255, blue: 0 },
+ damping: 0,
+ lifetime: (lengthOfRide * 60) + 1
+ });
+
+function rideWithParticle() {
+
+ if (iteration <= lengthOfRide) {
+
+ // Check to see if we've been notified of the actual identity of the particles we created
+ if (!particleA.isKnownID) {
+ particleA = Particles.identifyParticle(particleA);
+ }
+
+ var propertiesA = Particles.getParticleProperties(particleA);
+ var newPosition = propertiesA.position;
+ MyAvatar.position = { x: propertiesA.position.x,
+ y: propertiesA.position.y + 2,
+ z: propertiesA.position.z };
+ } else {
+ Script.stop();
+ }
+
+ iteration++;
+ print("iteration="+iteration);
+}
+
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(rideWithParticle);
+
diff --git a/examples/spaceInvadersExample.js b/examples/spaceInvadersExample.js
new file mode 100644
index 0000000000..3a13a1c52f
--- /dev/null
+++ b/examples/spaceInvadersExample.js
@@ -0,0 +1,246 @@
+//
+// spaceInvadersExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/30/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates a simple space invaders style of game
+//
+
+var iteration = 0;
+
+var invaderStepsPerCycle = 30; // the number of update steps it takes then invaders to move one column to the right
+var invaderStepOfCycle = 0; // current iteration in the cycle
+var invaderMoveDirection = 1; // 1 for moving to right, -1 for moving to left
+
+var itemLifetimes = 60;
+var gameAt = { x: 10, y: 0, z: 10 };
+var gameSize = { x: 10, y: 20, z: 1 };
+var middleX = gameAt.x + (gameSize.x/2);
+var middleY = gameAt.y + (gameSize.y/2);
+
+var shipSize = 0.2;
+var missileSize = 0.1;
+var myShip;
+var myShipProperties;
+
+// create the rows of space invaders
+var invaders = new Array();
+var numberOfRows = 5;
+var invadersPerRow = 8;
+var emptyColumns = 2; // number of invader width columns not filled with invaders
+var invadersBottomCorner = { x: gameAt.x, y: middleY , z: gameAt.z };
+var rowHeight = ((gameAt.y + gameSize.y) - invadersBottomCorner.y) / numberOfRows;
+var columnWidth = gameSize.x / (invadersPerRow + emptyColumns);
+
+var missileFired = false;
+var myMissile;
+
+function initializeMyShip() {
+ myShipProperties = {
+ position: { x: middleX , y: gameAt.y, z: gameAt.z },
+ velocity: { x: 0, y: 0, z: 0 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius: shipSize,
+ color: { red: 0, green: 255, blue: 0 },
+ lifetime: itemLifetimes
+ };
+ myShip = Particles.addParticle(myShipProperties);
+}
+
+// calculate the correct invaderPosition for an column row
+function getInvaderPosition(row, column) {
+ var xMovePart = 0;
+ var xBasePart = invadersBottomCorner.x + (column * columnWidth);
+ if (invaderMoveDirection > 0) {
+ xMovePart = (invaderMoveDirection * columnWidth * (invaderStepOfCycle/invaderStepsPerCycle));
+ } else {
+ xMovePart = columnWidth + (invaderMoveDirection * columnWidth * (invaderStepOfCycle/invaderStepsPerCycle));
+ }
+ var invaderPosition = {
+ x: xBasePart + xMovePart,
+ y: invadersBottomCorner.y + (row * rowHeight),
+ z: invadersBottomCorner.z };
+
+ return invaderPosition;
+}
+
+function initializeInvaders() {
+ for (var row = 0; row < numberOfRows; row++) {
+ invaders[row] = new Array();
+ for (var column = 0; column < invadersPerRow; column++) {
+ invaderPosition = getInvaderPosition(row, column);
+ invaders[row][column] = Particles.addParticle({
+ position: invaderPosition,
+ velocity: { x: 0, y: 0, z: 0 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius: shipSize,
+ color: { red: 255, green: 0, blue: 0 },
+ lifetime: itemLifetimes
+ });
+
+ print("invaders[row][column].creatorTokenID=" + invaders[row][column].creatorTokenID);
+ }
+ }
+}
+
+function moveInvaders() {
+ print("moveInvaders()...");
+ for (var row = 0; row < numberOfRows; row++) {
+ for (var column = 0; column < invadersPerRow; column++) {
+ props = Particles.getParticleProperties(invaders[row][column]);
+ if (props.isKnownID) {
+ invaderPosition = getInvaderPosition(row, column);
+ Particles.editParticle(invaders[row][column], { position: invaderPosition });
+ }
+ }
+ }
+}
+
+function update() {
+ print("updating space invaders... iteration="+iteration);
+ iteration++;
+ invaderStepOfCycle++;
+ if (invaderStepOfCycle > invaderStepsPerCycle) {
+ invaderStepOfCycle = 0;
+ if (invaderMoveDirection > 0) {
+ invaderMoveDirection = -1;
+ } else {
+ invaderMoveDirection = 1;
+ }
+ }
+ moveInvaders();
+}
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(update);
+
+function cleanupGame() {
+ print("cleaning up game...");
+ Particles.deleteParticle(myShip);
+ print("cleanupGame() ... Particles.deleteParticle(myShip)... myShip.id="+myShip.id);
+ for (var row = 0; row < numberOfRows; row++) {
+ for (var column = 0; column < invadersPerRow; column++) {
+ Particles.deleteParticle(invaders[row][column]);
+ print("cleanupGame() ... Particles.deleteParticle(invaders[row][column])... invaders[row][column].id="
+ +invaders[row][column].id);
+ }
+ }
+
+ // clean up our missile
+ if (missileFired) {
+ Particles.deleteParticle(myMissile);
+ }
+ Script.stop();
+}
+Script.scriptEnding.connect(cleanupGame);
+
+function endGame() {
+ print("ending game...");
+ Script.stop();
+}
+
+function moveShipTo(position) {
+ myShip = Particles.identifyParticle(myShip);
+ Particles.editParticle(myShip, { position: position });
+}
+
+function fireMissile() {
+ // we only allow one missile at a time...
+ var canFire = false;
+
+ // If we've fired a missile, then check to see if it's still alive
+ if (missileFired) {
+ var missileProperties = Particles.getParticleProperties(myMissile);
+ print("missileProperties.isKnownID=" + missileProperties.isKnownID);
+
+ if (!missileProperties.isKnownID) {
+ print("canFire = true");
+ canFire = true;
+ }
+ } else {
+ canFire = true;
+ }
+
+ if (canFire) {
+ print("firing missile");
+ var missilePosition = { x: myShipProperties.position.x,
+ y: myShipProperties.position.y + (2* shipSize),
+ z: myShipProperties.position.z };
+
+ myMissile = Particles.addParticle(
+ {
+ position: missilePosition,
+ velocity: { x: 0, y: 5, z: 0},
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius: missileSize,
+ color: { red: 0, green: 0, blue: 255 },
+ lifetime: 5
+ });
+
+ missileFired = true;
+ }
+}
+
+function keyPressEvent(key) {
+ //print("keyPressEvent key.text="+key.text);
+ if (key.text == ",") {
+ myShipProperties.position.x -= 0.1;
+ if (myShipProperties.position.x < gameAt.x) {
+ myShipProperties.position.x = gameAt.x;
+ }
+ moveShipTo(myShipProperties.position);
+ } else if (key.text == ".") {
+ myShipProperties.position.x += 0.1;
+ if (myShipProperties.position.x > gameAt.x + gameSize.x) {
+ myShipProperties.position.x = gameAt.x + gameSize.x;
+ }
+ moveShipTo(myShipProperties.position);
+ } else if (key.text == " ") {
+ fireMissile();
+ } else if (key.text == "q") {
+ endGame();
+ }
+}
+
+// remap the keys...
+Controller.keyPressEvent.connect(keyPressEvent);
+Controller.captureKeyEvents({text: " "});
+
+function deleteIfInvader(possibleInvaderParticle) {
+ for (var row = 0; row < numberOfRows; row++) {
+ for (var column = 0; column < invadersPerRow; column++) {
+ invaders[row][column] = Particles.identifyParticle(invaders[row][column]);
+ if (invaders[row][column].isKnownID) {
+ if (invaders[row][column].id == possibleInvaderParticle.id) {
+ Particles.deleteParticle(possibleInvaderParticle);
+ Particles.deleteParticle(myMissile);
+ }
+ }
+ }
+ }
+}
+
+function particleCollisionWithParticle(particleA, particleB) {
+ print("particleCollisionWithParticle() a.id="+particleA.id + " b.id=" + particleB.id);
+ if (missileFired) {
+ myMissile = Particles.identifyParticle(myMissile);
+ if (myMissile.id == particleA.id) {
+ deleteIfInvader(particleB);
+ } else if (myMissile.id == particleB.id) {
+ deleteIfInvader(particleA);
+ }
+ }
+}
+Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
+
+
+// initialize the game...
+initializeMyShip();
+initializeInvaders();
+
+
diff --git a/examples/timer.js b/examples/timer.js
new file mode 100644
index 0000000000..c7ad0290ab
--- /dev/null
+++ b/examples/timer.js
@@ -0,0 +1,8 @@
+var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 10000);
+var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000);
+
+// this would stop a scheduled single shot timer
+Script.clearTimeout(one_timer);
+
+// this stops the repeating timer
+Script.clearInterval(multiple_timer);
\ No newline at end of file
diff --git a/examples/toyball.js b/examples/toyball.js
index 6c40fc2932..c5672877f7 100644
--- a/examples/toyball.js
+++ b/examples/toyball.js
@@ -226,4 +226,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/voxelBird.js b/examples/voxelBird.js
index 54c0129045..254f93c21e 100644
--- a/examples/voxelBird.js
+++ b/examples/voxelBird.js
@@ -130,4 +130,4 @@ function moveBird() {
Voxels.setPacketsPerSecond(10000);
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(moveBird);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(moveBird);
\ No newline at end of file
diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag
index 877cdca885..e035f9bfe9 100644
--- a/interface/resources/shaders/model.frag
+++ b/interface/resources/shaders/model.frag
@@ -25,5 +25,5 @@ void main(void) {
// modulate texture by base color and add specular contribution
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
- pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
+ vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
}
diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag
index 9740a4d4b1..9a0e964500 100644
--- a/interface/resources/shaders/model_normal_map.frag
+++ b/interface/resources/shaders/model_normal_map.frag
@@ -37,5 +37,5 @@ void main(void) {
// modulate texture by base color and add specular contribution
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
- pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
+ vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
}
diff --git a/interface/resources/sounds/snap.wav b/interface/resources/sounds/snap.wav
new file mode 100644
index 0000000000..bb562e1db9
Binary files /dev/null and b/interface/resources/sounds/snap.wav differ
diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h
index d8a48fc2fd..cedab1fad2 100644
--- a/interface/src/AbstractLoggerInterface.h
+++ b/interface/src/AbstractLoggerInterface.h
@@ -17,6 +17,7 @@ class AbstractLoggerInterface : public QObject {
Q_OBJECT
public:
+ AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {};
inline bool extraDebugging() { return _extraDebugging; };
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; };
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 285a395c07..ca87c812d3 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -47,9 +47,9 @@
#include
#include
#include
+#include
#include
-#include
#include
#include
#include
@@ -70,6 +70,7 @@
#include "renderer/ProgramObject.h"
#include "ui/TextRenderer.h"
#include "InfoView.h"
+#include "ui/Snapshot.h"
using namespace std;
@@ -110,15 +111,16 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
QApplication(argc, argv),
_window(new QMainWindow(desktop())),
_glWidget(new GLCanvas()),
+ _statsExpanded(false),
_nodeThread(new QThread(this)),
_datagramProcessor(),
_frameCount(0),
_fps(120.0f),
_justStarted(true),
- _voxelImporter(_window),
+ _voxelImporter(NULL),
_wantToKillLocalVoxels(false),
_audioScope(256, 200, true),
- _avatarManager(),
+ _myAvatar(),
_profile(QString()),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
_mouseX(0),
@@ -129,8 +131,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_touchAvgX(0.0f),
_touchAvgY(0.0f),
_isTouchPressed(false),
- _yawFromTouch(0.0f),
- _pitchFromTouch(0.0f),
_mousePressed(false),
_isHoverVoxel(false),
_isHoverVoxelSounding(false),
@@ -152,10 +152,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_resetRecentMaxPacketsSoon(true),
_swatch(NULL),
_pasteMode(false),
- _logger(new FileLogger()),
- _persistThread(NULL),
- _statsExpanded(false)
+ _logger(new FileLogger(this)),
+ _persistThread(NULL)
{
+ _myAvatar = _avatarManager.getMyAvatar();
+
_applicationStartupTime = startup_time;
switchToResourcesParentIfRequired();
@@ -181,7 +182,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_nodeThread->setPriority(QThread::TimeCriticalPriority);
// put the NodeList and datagram processing on the node thread
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_AGENT, listenPort);
+ NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort);
nodeList->moveToThread(_nodeThread);
_datagramProcessor.moveToThread(_nodeThread);
@@ -228,9 +229,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
#endif
// tell the NodeList instance who to tell the domain server we care about
- nodeList->addSetOfNodeTypesToNodeInterestSet(QSet() << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER
- << NODE_TYPE_VOXEL_SERVER << NODE_TYPE_PARTICLE_SERVER
- << NODE_TYPE_METAVOXEL_SERVER);
+ nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
+ << NodeType::VoxelServer << NodeType::ParticleServer
+ << NodeType::MetavoxelServer);
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
@@ -252,7 +253,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_window->setCentralWidget(_glWidget);
restoreSizeAndPosition();
- loadScripts();
QFontDatabase fontDatabase;
fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf");
@@ -282,6 +282,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
checkVersion();
+
+ // do this as late as possible so that all required subsystems are inialized
+ loadScripts();
}
Application::~Application() {
@@ -316,7 +319,7 @@ Application::~Application() {
_persistThread->deleteLater();
_persistThread = NULL;
}
-
+
storeSizeAndPosition();
saveScripts();
_sharedVoxelSystem.changeTree(new VoxelTree);
@@ -324,8 +327,8 @@ Application::~Application() {
VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown
Menu::getInstance()->deleteLater();
- delete _logger;
- delete _settings;
+ _myAvatar = NULL;
+
delete _glWidget;
}
@@ -438,25 +441,25 @@ void Application::paintGL() {
_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());
+ _myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
+ _myCamera.setTargetRotation(_myAvatar->getHead().getOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setTightness(0.0f); // In first person, camera follows head exactly without delay
- _myCamera.setTargetPosition(_myAvatar.getHead().calculateAverageEyePosition());
- _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());
+ _myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
+ _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.setTargetPosition(_myAvatar.getUprightHeadPosition());
- _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());
+ _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
+ _myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_myCamera.setTightness(0.0f);
- float headHeight = _myAvatar.getHead().calculateAverageEyePosition().y - _myAvatar.getPosition().y;
- _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar.getScale());
- _myCamera.setTargetPosition(_myAvatar.getPosition() + glm::vec3(0, headHeight, 0));
- _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
+ float headHeight = _myAvatar->getHead().calculateAverageEyePosition().y - _myAvatar->getPosition().y;
+ _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale());
+ _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0));
+ _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
}
// Update camera position
@@ -513,22 +516,22 @@ void Application::paintGL() {
bool eyeRelativeCamera = false;
if (_rearMirrorTools->getZoomLevel() == BODY) {
- _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar.getScale());
- _mirrorCamera.setTargetPosition(_myAvatar.getChestPosition());
+ _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
+ _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
} else { // HEAD zoom level
- _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar.getScale());
- if (_myAvatar.getSkeletonModel().isActive() && _myAvatar.getHead().getFaceModel().isActive()) {
+ _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
+ if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead().getFaceModel().isActive()) {
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
// face/body so that the average eye position lies at the origin
eyeRelativeCamera = true;
_mirrorCamera.setTargetPosition(glm::vec3());
} else {
- _mirrorCamera.setTargetPosition(_myAvatar.getHead().calculateAverageEyePosition());
+ _mirrorCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
}
}
- _mirrorCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
+ _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
@@ -545,27 +548,27 @@ void Application::paintGL() {
glPushMatrix();
if (eyeRelativeCamera) {
// save absolute translations
- glm::vec3 absoluteSkeletonTranslation = _myAvatar.getSkeletonModel().getTranslation();
- glm::vec3 absoluteFaceTranslation = _myAvatar.getHead().getFaceModel().getTranslation();
+ glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
+ glm::vec3 absoluteFaceTranslation = _myAvatar->getHead().getFaceModel().getTranslation();
// get the eye positions relative to the neck and use them to set the face translation
glm::vec3 leftEyePosition, rightEyePosition;
- _myAvatar.getHead().getFaceModel().setTranslation(glm::vec3());
- _myAvatar.getHead().getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
- _myAvatar.getHead().getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
+ _myAvatar->getHead().getFaceModel().setTranslation(glm::vec3());
+ _myAvatar->getHead().getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
+ _myAvatar->getHead().getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
// get the neck position relative to the body and use it to set the skeleton translation
glm::vec3 neckPosition;
- _myAvatar.getSkeletonModel().setTranslation(glm::vec3());
- _myAvatar.getSkeletonModel().getNeckPosition(neckPosition);
- _myAvatar.getSkeletonModel().setTranslation(_myAvatar.getHead().getFaceModel().getTranslation() -
+ _myAvatar->getSkeletonModel().setTranslation(glm::vec3());
+ _myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
+ _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead().getFaceModel().getTranslation() -
neckPosition);
displaySide(_mirrorCamera, true);
// restore absolute translations
- _myAvatar.getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
- _myAvatar.getHead().getFaceModel().setTranslation(absoluteFaceTranslation);
+ _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
+ _myAvatar->getHead().getFaceModel().setTranslation(absoluteFaceTranslation);
} else {
displaySide(_mirrorCamera, true);
}
@@ -647,45 +650,51 @@ void Application::resetProfile(const QString& username) {
updateWindowTitle();
}
-void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,
- const QSet& destinationNodeTypes) {
- foreach(NODE_TYPE type, destinationNodeTypes) {
+void Application::controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
+ foreach(NodeType_t type, destinationNodeTypes) {
// Intercept data to voxel server when voxels are disabled
- if (type == NODE_TYPE_VOXEL_SERVER && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
+ if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
continue;
}
// Perform the broadcast for one type
- int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(broadcastData, dataBytes,
- QSet() << type);
+ int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type);
// Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise)
BandwidthMeter::ChannelIndex channel;
switch (type) {
- case NODE_TYPE_AGENT:
- case NODE_TYPE_AVATAR_MIXER:
+ case NodeType::Agent:
+ case NodeType::AvatarMixer:
channel = BandwidthMeter::AVATARS;
break;
- case NODE_TYPE_VOXEL_SERVER:
+ case NodeType::VoxelServer:
channel = BandwidthMeter::VOXELS;
break;
default:
continue;
}
- _bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * dataBytes);
+ _bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * packet.size());
}
}
void Application::keyPressEvent(QKeyEvent* event) {
+
+ _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isKeyCaptured(event)) {
+ return;
+ }
+
if (activeWindow() == _window) {
if (_chatEntryOn) {
if (_chatEntry.keyPressEvent(event)) {
- _myAvatar.setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ?
+ _myAvatar->setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ?
DELETE_KEY_DOWN : INSERT_KEY_DOWN);
- _myAvatar.setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR));
+ _myAvatar->setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR));
} else {
- _myAvatar.setChatMessage(_chatEntry.getContents());
+ _myAvatar->setChatMessage(_chatEntry.getContents());
_chatEntry.clear();
_chatEntryOn = false;
setMenuShortcutsEnabled(true);
@@ -729,10 +738,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (_nudgeStarted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
- if (!_myAvatar.getDriveKeys(UP)) {
- _myAvatar.jump();
+ if (!_myAvatar->getDriveKeys(UP)) {
+ _myAvatar->jump();
}
- _myAvatar.setDriveKeys(UP, 1);
+ _myAvatar->setDriveKeys(UP, 1);
}
break;
@@ -744,7 +753,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (_nudgeStarted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(DOWN, 1);
+ _myAvatar->setDriveKeys(DOWN, 1);
}
break;
@@ -764,7 +773,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(FWD, 1);
+ _myAvatar->setDriveKeys(FWD, 1);
}
break;
@@ -773,6 +782,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
_voxels.collectStatsForTreesAndVBOs();
} else if (isShifted && isMeta) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
+ } else if (!isShifted && isMeta) {
+ takeSnapshot();
} else if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
@@ -788,7 +799,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(BACK, 1);
+ _myAvatar->setDriveKeys(BACK, 1);
}
break;
@@ -822,7 +833,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(ROT_LEFT, 1);
+ _myAvatar->setDriveKeys(ROT_LEFT, 1);
}
break;
@@ -842,7 +853,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(ROT_RIGHT, 1);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 1);
}
break;
@@ -852,8 +863,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
nudgeVoxels();
} else {
_chatEntryOn = true;
- _myAvatar.setKeyState(NO_KEY_DOWN);
- _myAvatar.setChatMessage(string());
+ _myAvatar->setKeyState(NO_KEY_DOWN);
+ _myAvatar->setChatMessage(string());
setMenuShortcutsEnabled(false);
}
break;
@@ -876,7 +887,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(isShifted ? UP : FWD, 1);
+ _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1);
}
break;
@@ -898,7 +909,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(isShifted ? DOWN : BACK, 1);
+ _myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1);
}
break;
@@ -918,7 +929,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
+ _myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
}
break;
@@ -938,7 +949,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
+ _myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
}
break;
@@ -1056,13 +1067,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
break;
case Qt::Key_Plus:
- _myAvatar.increaseSize();
+ _myAvatar->increaseSize();
break;
case Qt::Key_Minus:
- _myAvatar.decreaseSize();
+ _myAvatar->decreaseSize();
break;
case Qt::Key_Equal:
- _myAvatar.resetSize();
+ _myAvatar->resetSize();
break;
case Qt::Key_1:
@@ -1076,7 +1087,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
_swatch.handleEvent(event->key(), Menu::getInstance()->isOptionChecked(MenuOption::VoxelGetColorMode));
break;
case Qt::Key_At:
- Menu::getInstance()->goToUser();
+ Menu::getInstance()->goTo();
break;
default:
event->ignore();
@@ -1086,9 +1097,18 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
void Application::keyReleaseEvent(QKeyEvent* event) {
+
+ _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isKeyCaptured(event)) {
+ return;
+ }
+
+
if (activeWindow() == _window) {
if (_chatEntryOn) {
- _myAvatar.setKeyState(NO_KEY_DOWN);
+ _myAvatar->setKeyState(NO_KEY_DOWN);
return;
}
@@ -1097,47 +1117,47 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
_pasteMode = false;
break;
case Qt::Key_E:
- _myAvatar.setDriveKeys(UP, 0);
+ _myAvatar->setDriveKeys(UP, 0);
break;
case Qt::Key_C:
- _myAvatar.setDriveKeys(DOWN, 0);
+ _myAvatar->setDriveKeys(DOWN, 0);
break;
case Qt::Key_W:
- _myAvatar.setDriveKeys(FWD, 0);
+ _myAvatar->setDriveKeys(FWD, 0);
break;
case Qt::Key_S:
- _myAvatar.setDriveKeys(BACK, 0);
+ _myAvatar->setDriveKeys(BACK, 0);
break;
case Qt::Key_A:
- _myAvatar.setDriveKeys(ROT_LEFT, 0);
+ _myAvatar->setDriveKeys(ROT_LEFT, 0);
break;
case Qt::Key_D:
- _myAvatar.setDriveKeys(ROT_RIGHT, 0);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 0);
break;
case Qt::Key_Up:
- _myAvatar.setDriveKeys(FWD, 0);
- _myAvatar.setDriveKeys(UP, 0);
+ _myAvatar->setDriveKeys(FWD, 0);
+ _myAvatar->setDriveKeys(UP, 0);
break;
case Qt::Key_Down:
- _myAvatar.setDriveKeys(BACK, 0);
- _myAvatar.setDriveKeys(DOWN, 0);
+ _myAvatar->setDriveKeys(BACK, 0);
+ _myAvatar->setDriveKeys(DOWN, 0);
break;
case Qt::Key_Left:
- _myAvatar.setDriveKeys(LEFT, 0);
- _myAvatar.setDriveKeys(ROT_LEFT, 0);
+ _myAvatar->setDriveKeys(LEFT, 0);
+ _myAvatar->setDriveKeys(ROT_LEFT, 0);
break;
case Qt::Key_Right:
- _myAvatar.setDriveKeys(RIGHT, 0);
- _myAvatar.setDriveKeys(ROT_RIGHT, 0);
+ _myAvatar->setDriveKeys(RIGHT, 0);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 0);
break;
default:
@@ -1148,6 +1168,14 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
}
void Application::mouseMoveEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMouseMoveEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
+
_lastMouseMove = usecTimestampNow();
if (_mouseHidden) {
getGLWidget()->setCursor(Qt::ArrowCursor);
@@ -1163,12 +1191,12 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
if (activeWindow() == _window) {
// orbit behavior
if (_mousePressed && !Menu::getInstance()->isVoxelModeActionChecked()) {
- if (_avatarManager.getLookAtTargetAvatar()) {
- _myAvatar.orbit(_avatarManager.getLookAtTargetAvatar()->getPosition(), deltaX, deltaY);
+ if (_myAvatar->getLookAtTargetAvatar()) {
+ _myAvatar->orbit(_myAvatar->getLookAtTargetAvatar()->getPosition(), deltaX, deltaY);
return;
}
if (_isHoverVoxel) {
- _myAvatar.orbit(getMouseVoxelWorldCoordinates(_hoverVoxel), deltaX, deltaY);
+ _myAvatar->orbit(getMouseVoxelWorldCoordinates(_hoverVoxel), deltaX, deltaY);
return;
}
}
@@ -1194,6 +1222,14 @@ const float HOVER_VOXEL_FREQUENCY = 7040.f;
const float HOVER_VOXEL_DECAY = 0.999f;
void Application::mousePressEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
+
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
@@ -1215,7 +1251,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
return;
}
- if (!_palette.isActive() && (!_isHoverVoxel || _avatarManager.getLookAtTargetAvatar())) {
+ if (!_palette.isActive() && (!_isHoverVoxel || _myAvatar->getLookAtTargetAvatar())) {
// disable for now
// _pieMenu.mousePressEvent(_mouseX, _mouseY);
}
@@ -1243,14 +1279,14 @@ void Application::mousePressEvent(QMouseEvent* event) {
const float PERCENTAGE_TO_MOVE_TOWARD = 0.90f;
glm::vec3 newTarget = getMouseVoxelWorldCoordinates(_hoverVoxel);
- glm::vec3 myPosition = _myAvatar.getPosition();
+ glm::vec3 myPosition = _myAvatar->getPosition();
// If there is not an action tool set (add, delete, color), move to this voxel
if (Menu::getInstance()->isOptionChecked(MenuOption::ClickToFly) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) ||
Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) ||
Menu::getInstance()->isOptionChecked(MenuOption::VoxelColorMode))) {
- _myAvatar.setMoveTarget(myPosition + (newTarget - myPosition) * PERCENTAGE_TO_MOVE_TOWARD);
+ _myAvatar->setMoveTarget(myPosition + (newTarget - myPosition) * PERCENTAGE_TO_MOVE_TOWARD);
}
}
@@ -1261,6 +1297,13 @@ void Application::mousePressEvent(QMouseEvent* event) {
}
void Application::mouseReleaseEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
@@ -1277,6 +1320,13 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
}
void Application::touchUpdateEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchUpdateEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
bool validTouch = false;
if (activeWindow() == _window) {
const QList& tPoints = event->touchPoints();
@@ -1301,19 +1351,46 @@ void Application::touchUpdateEvent(QTouchEvent* event) {
}
void Application::touchBeginEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchBeginEvent(event); // send events to any registered scripts
+
touchUpdateEvent(event);
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
+ // put any application specific touch behavior below here..
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
+
}
void Application::touchEndEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchEndEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
+ // put any application specific touch behavior below here..
_touchDragStartedAvgX = _touchAvgX;
_touchDragStartedAvgY = _touchAvgY;
_isTouchPressed = false;
+
}
const bool USE_MOUSEWHEEL = false;
void Application::wheelEvent(QWheelEvent* event) {
+
+ _controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isWheelCaptured()) {
+ return;
+ }
+
// Wheel Events disabled for now because they are also activated by touch look pitch up/down.
if (USE_MOUSEWHEEL && (activeWindow() == _window)) {
if (!Menu::getInstance()->isVoxelModeActionChecked()) {
@@ -1329,13 +1406,11 @@ void Application::wheelEvent(QWheelEvent* event) {
}
void Application::sendPingPackets() {
- unsigned char pingPacket[MAX_PACKET_SIZE];
- int length = NodeList::getInstance()->fillPingPacket(pingPacket);
-
- getInstance()->controlledBroadcastToNodes(pingPacket, length, QSet()
- << NODE_TYPE_VOXEL_SERVER << NODE_TYPE_PARTICLE_SERVER
- << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER
- << NODE_TYPE_METAVOXEL_SERVER);
+ QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
+ controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
+ << NodeType::ParticleServer
+ << NodeType::AudioMixer << NodeType::AvatarMixer
+ << NodeType::MetavoxelServer);
}
// Every second, check the frame rates and other stuff
@@ -1363,8 +1438,8 @@ void Application::timer() {
DataServerClient::resendUnmatchedPackets();
// give the MyAvatar object position, orientation to the Profile so it can propagate to the data-server
- _profile.updatePosition(_myAvatar.getPosition());
- _profile.updateOrientation(_myAvatar.getOrientation());
+ _profile.updatePosition(_myAvatar->getPosition());
+ _profile.updateOrientation(_myAvatar->getOrientation());
}
static glm::vec3 getFaceVector(BoxFace face) {
@@ -1473,7 +1548,7 @@ void Application::removeVoxel(glm::vec3 position,
voxel.y = position.y / TREE_SCALE;
voxel.z = position.z / TREE_SCALE;
voxel.s = scale / TREE_SCALE;
- _voxelEditSender.sendVoxelEditMessage(PACKET_TYPE_VOXEL_ERASE, voxel);
+ _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, voxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
@@ -1494,7 +1569,7 @@ void Application::makeVoxel(glm::vec3 position,
voxel.red = red;
voxel.green = green;
voxel.blue = blue;
- PACKET_TYPE message = isDestructive ? PACKET_TYPE_VOXEL_SET_DESTRUCTIVE : PACKET_TYPE_VOXEL_SET;
+ PacketType message = isDestructive ? PacketTypeVoxelSetDestructive : PacketTypeVoxelSet;
_voxelEditSender.sendVoxelEditMessage(message, voxel);
// create the voxel locally so it appears immediately
@@ -1564,7 +1639,7 @@ bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX];
codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX];
codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX];
- getInstance()->_voxelEditSender.queueVoxelEditMessage(PACKET_TYPE_VOXEL_SET_DESTRUCTIVE,
+ getInstance()->_voxelEditSender.queueVoxelEditMessage(PacketTypeVoxelSetDestructive,
codeColorBuffer, codeAndColorLength);
delete[] codeColorBuffer;
@@ -1592,7 +1667,12 @@ void Application::exportVoxels() {
}
void Application::importVoxels() {
- if (_voxelImporter.exec()) {
+ if (!_voxelImporter) {
+ _voxelImporter = new VoxelImporter(_window);
+ _voxelImporter->init(_settings);
+ }
+
+ if (_voxelImporter->exec()) {
qDebug("[DEBUG] Import succeeded.");
} else {
qDebug("[DEBUG] Import failed.");
@@ -1659,7 +1739,7 @@ void Application::pasteVoxels() {
}
void Application::findAxisAlignment() {
- glm::vec3 direction = _myAvatar.getMouseRayDirection();
+ glm::vec3 direction = _myAvatar->getMouseRayDirection();
if (fabs(direction.z) > fabs(direction.x)) {
_lookingAlongX = false;
if (direction.z < 0) {
@@ -1732,8 +1812,6 @@ void Application::init() {
_sharedVoxelSystem.changeTree(&_clipboard);
delete tmpTree;
- _voxelImporter.init(_settings);
-
_environment.init();
_glowEffect.init();
@@ -1741,15 +1819,14 @@ void Application::init() {
_voxelShader.init();
_pointShader.init();
- _headMouseX = _mouseX = _glWidget->width() / 2;
- _headMouseY = _mouseY = _glWidget->height() / 2;
- QCursor::setPos(_headMouseX, _headMouseY);
+ _mouseX = _glWidget->width() / 2;
+ _mouseY = _glWidget->height() / 2;
+ QCursor::setPos(_mouseX, _mouseY);
- _myAvatar.init();
- _myAvatar.setPosition(START_LOCATION);
+ // TODO: move _myAvatar out of Application. Move relevant code to MyAvataar or AvatarManager
+ _avatarManager.init();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftRate(1.0f);
- _myAvatar.setDisplayingLookatVectors(false);
_mirrorCamera.setMode(CAMERA_MODE_MIRROR);
_mirrorCamera.setAspectRatio((float)MIRROR_VIEW_WIDTH / (float)MIRROR_VIEW_HEIGHT);
@@ -1796,7 +1873,18 @@ void Application::init() {
_metavoxels.init();
- _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_myAvatar);
+ _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
+
+ // connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface
+ connect(&_particleCollisionSystem,
+ SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)),
+ ScriptEngine::getParticlesScriptingInterface(),
+ SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)));
+
+ connect(&_particleCollisionSystem,
+ SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&)),
+ ScriptEngine::getParticlesScriptingInterface(),
+ SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&)));
_palette.init(_glWidget->width(), _glWidget->height());
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0);
@@ -1854,9 +1942,9 @@ const float HEAD_SPHERE_RADIUS = 0.07f;
bool Application::isLookingAtMyAvatar(Avatar* avatar) {
glm::vec3 theirLookat = avatar->getHead().getLookAtPosition();
- glm::vec3 myHeadPosition = _myAvatar.getHead().getPosition();
+ glm::vec3 myHeadPosition = _myAvatar->getHead().getPosition();
- if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar.getScale())) {
+ if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar->getScale())) {
return true;
}
return false;
@@ -1894,10 +1982,10 @@ void Application::updateMouseRay() {
}
// tell my avatar if the mouse is being pressed...
- _myAvatar.setMousePressed(_mousePressed);
+ _myAvatar->setMousePressed(_mousePressed);
// tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position
- _myAvatar.setMouseRay(_mouseRayOrigin, _mouseRayDirection);
+ _myAvatar->setMouseRay(_mouseRayOrigin, _mouseRayDirection);
}
void Application::updateFaceshift() {
@@ -1910,7 +1998,7 @@ void Application::updateFaceshift() {
// Copy angular velocity if measured by faceshift, to the head
if (_faceshift.isActive()) {
- _myAvatar.getHead().setAngularVelocity(_faceshift.getHeadAngularVelocity());
+ _myAvatar->getHead().setAngularVelocity(_faceshift.getHeadAngularVelocity());
}
}
@@ -1934,14 +2022,14 @@ void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot) {
}
if (_faceshift.isActive()) {
// deflect using Faceshift gaze data
- glm::vec3 origin = _myAvatar.getHead().calculateAverageEyePosition();
+ glm::vec3 origin = _myAvatar->getHead().calculateAverageEyePosition();
float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f;
float deflection = Menu::getInstance()->getFaceshiftEyeDeflection();
lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3(
_faceshift.getEstimatedEyePitch() * pitchSign * deflection, _faceshift.getEstimatedEyeYaw() * deflection, 0.0f))) *
glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin);
}
- _myAvatar.getHead().setLookAtPosition(lookAtSpot);
+ _myAvatar->getHead().setLookAtPosition(lookAtSpot);
}
void Application::updateHoverVoxels(float deltaTime, float& distance, BoxFace& face) {
@@ -1999,9 +2087,9 @@ void Application::updateMouseVoxels(float deltaTime, float& distance, BoxFace& f
_mouseVoxel.s = 0.0f;
bool wasInitialized = _mouseVoxelScaleInitialized;
if (Menu::getInstance()->isVoxelModeActionChecked() &&
- (fabs(_myAvatar.getVelocity().x) +
- fabs(_myAvatar.getVelocity().y) +
- fabs(_myAvatar.getVelocity().z)) / 3 < MAX_AVATAR_EDIT_VELOCITY) {
+ (fabs(_myAvatar->getVelocity().x) +
+ fabs(_myAvatar->getVelocity().y) +
+ fabs(_myAvatar->getVelocity().z)) / 3 < MAX_AVATAR_EDIT_VELOCITY) {
if (_voxels.findRayIntersection(_mouseRayOrigin, _mouseRayDirection, _mouseVoxel, distance, face)) {
if (distance < MAX_VOXEL_EDIT_DISTANCE) {
@@ -2085,11 +2173,6 @@ void Application::updateHandAndTouch(float deltaTime) {
// Update from Touch
if (_isTouchPressed) {
- float TOUCH_YAW_SCALE = -0.25f;
- float TOUCH_PITCH_SCALE = -12.5f;
- float FIXED_TOUCH_TIMESTEP = 0.016f;
- _yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP);
- _pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP);
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
}
@@ -2128,24 +2211,6 @@ void Application::updateThreads(float deltaTime) {
}
}
-void Application::updateMyAvatarSimulation(float deltaTime) {
- bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
- PerformanceWarning warn(showWarnings, "Application::updateMyAvatarSimulation()");
-
- if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
- _myAvatar.setGravity(_environment.getGravity(_myAvatar.getPosition()));
- }
- else {
- _myAvatar.setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
- }
-
- if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
- _myAvatar.simulate(deltaTime, &_myTransmitter);
- } else {
- _myAvatar.simulate(deltaTime, NULL);
- }
-}
-
void Application::updateParticles(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateParticles()");
@@ -2164,32 +2229,6 @@ void Application::updateMetavoxels(float deltaTime) {
}
}
-void Application::updateTransmitter(float deltaTime) {
- bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
- PerformanceWarning warn(showWarnings, "Application::updateTransmitter()");
-
- // no transmitter drive implies transmitter pick
- if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
- _transmitterPickStart = _myAvatar.getChestPosition();
- glm::vec3 direction = _myAvatar.getOrientation() *
- glm::quat(glm::radians(_myTransmitter.getEstimatedRotation())) * IDENTITY_FRONT;
-
- // check against voxels, avatars
- const float MAX_PICK_DISTANCE = 100.0f;
- float minDistance = MAX_PICK_DISTANCE;
- VoxelDetail detail;
- float distance;
- BoxFace face;
- if (_voxels.findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) {
- minDistance = min(minDistance, distance);
- }
- _transmitterPickEnd = _transmitterPickStart + direction * minDistance;
-
- } else {
- _transmitterPickStart = _transmitterPickEnd = glm::vec3();
- }
-}
-
void Application::updateCamera(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
@@ -2245,8 +2284,8 @@ void Application::updateAudio(float deltaTime) {
PerformanceWarning warn(showWarnings, "Application::updateAudio()");
// Update audio stats for procedural sounds
- _audio.setLastAcceleration(_myAvatar.getThrust());
- _audio.setLastVelocity(_myAvatar.getVelocity());
+ _audio.setLastAcceleration(_myAvatar->getThrust());
+ _audio.setLastVelocity(_myAvatar->getVelocity());
}
void Application::updateCursor(float deltaTime) {
@@ -2256,7 +2295,7 @@ void Application::updateCursor(float deltaTime) {
// watch mouse position, if it hasn't moved, hide the cursor
bool underMouse = _glWidget->underMouse();
if (!_mouseHidden) {
- uint64_t now = usecTimestampNow();
+ quint64 now = usecTimestampNow();
int elapsed = now - _lastMouseMove;
const int HIDE_CURSOR_TIMEOUT = 1 * 1000 * 1000; // 1 second
if (elapsed > HIDE_CURSOR_TIMEOUT && (underMouse || !_seenMouseMove)) {
@@ -2284,7 +2323,7 @@ void Application::update(float deltaTime) {
glm::vec3 lookAtSpot;
updateFaceshift();
- _avatarManager.updateLookAtTargetAvatar(lookAtSpot);
+ _myAvatar->updateLookAtTargetAvatar(lookAtSpot);
updateMyAvatarLookAtPosition(lookAtSpot);
// Find the voxel we are hovering over, and respond if clicked
@@ -2297,81 +2336,31 @@ void Application::update(float deltaTime) {
updateLeap(deltaTime); // Leap finger-sensing device
updateSixense(deltaTime); // Razer Hydra controllers
updateSerialDevices(deltaTime); // Read serial port interface devices
- updateAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
+ updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
- _avatarManager.updateAvatars(deltaTime); //loop through all the other avatars and simulate them...
- updateMyAvatarSimulation(deltaTime); // Simulate myself
+ _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
updateParticles(deltaTime); // Simulate particle cloud movements
updateMetavoxels(deltaTime); // update metavoxels
- updateTransmitter(deltaTime); // transmitter drive or pick
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
updateDialogs(deltaTime); // update various stats dialogs if present
updateAudio(deltaTime); // Update audio stats for procedural sounds
updateCursor(deltaTime); // Handle cursor updates
_particles.update(); // update the particles...
- _particleCollisionSystem.update(); // handle collisions for the particles...
+ _particleCollisionSystem.update(); // collide the particles...
}
-void Application::updateAvatar(float deltaTime) {
+void Application::updateMyAvatar(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
- PerformanceWarning warn(showWarnings, "Application::updateAvatar()");
+ PerformanceWarning warn(showWarnings, "Application::updateMyAvatar()");
- // rotate body yaw for yaw received from multitouch
- _myAvatar.setOrientation(_myAvatar.getOrientation()
- * glm::quat(glm::vec3(0, _yawFromTouch, 0)));
- _yawFromTouch = 0.f;
-
- // apply pitch from touch
- _myAvatar.getHead().setMousePitch(_myAvatar.getHead().getMousePitch() +
- _myAvatar.getHand().getPitchUpdate() +
- _pitchFromTouch);
- _myAvatar.getHand().setPitchUpdate(0.f);
- _pitchFromTouch = 0.0f;
-
- // Update my avatar's state from gyros
- _myAvatar.updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
-
- // Update head mouse from faceshift if active
- if (_faceshift.isActive()) {
- glm::vec3 headVelocity = _faceshift.getHeadAngularVelocity();
-
- // sets how quickly head angular rotation moves the head mouse
- const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.f;
- const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.f;
- _headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE;
- _headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE;
- }
-
- // Constrain head-driven mouse to edges of screen
- _headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width());
- _headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
-
- if (OculusManager::isConnected()) {
- float yaw, pitch, roll;
- OculusManager::getEulerAngles(yaw, pitch, roll);
-
- _myAvatar.getHead().setYaw(yaw);
- _myAvatar.getHead().setPitch(pitch);
- _myAvatar.getHead().setRoll(roll);
- }
-
- // Get audio loudness data from audio input device
- _myAvatar.getHead().setAudioLoudness(_audio.getLastInputLoudness());
+ _myAvatar->update(deltaTime);
// send head/hand data to the avatar mixer and voxel server
- unsigned char broadcastString[MAX_PACKET_SIZE];
- unsigned char* endOfBroadcastStringWrite = broadcastString;
+ QByteArray packet = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
+ packet.append(_myAvatar->toByteArray());
- endOfBroadcastStringWrite += populateTypeAndVersion(endOfBroadcastStringWrite, PACKET_TYPE_HEAD_DATA);
-
- // pack the NodeList owner UUID
- endOfBroadcastStringWrite += NodeList::getInstance()->packOwnerUUID(endOfBroadcastStringWrite);
-
- endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite);
-
- controlledBroadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString,
- QSet() << NODE_TYPE_AVATAR_MIXER);
+ controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);
// Update _viewFrustum with latest camera and view frustum data...
// NOTE: we get this from the view frustum, to make it simpler, since the
@@ -2382,11 +2371,11 @@ void Application::updateAvatar(float deltaTime) {
loadViewFrustum(_myCamera, _viewFrustum);
// Update my voxel servers with my current voxel query...
- queryOctree(NODE_TYPE_VOXEL_SERVER, PACKET_TYPE_VOXEL_QUERY, _voxelServerJurisdictions);
- queryOctree(NODE_TYPE_PARTICLE_SERVER, PACKET_TYPE_PARTICLE_QUERY, _particleServerJurisdictions);
+ queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
+ queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
}
-void Application::queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions) {
+void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
// if voxels are disabled, then don't send this at all...
if (!Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
@@ -2554,10 +2543,7 @@ void Application::queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, Node
unsigned char* endOfVoxelQueryPacket = voxelQueryPacket;
// insert packet type/version and node UUID
- endOfVoxelQueryPacket += populateTypeAndVersion(endOfVoxelQueryPacket, packetType);
- QByteArray ownerUUID = nodeList->getOwnerUUID().toRfc4122();
- memcpy(endOfVoxelQueryPacket, ownerUUID.constData(), ownerUUID.size());
- endOfVoxelQueryPacket += ownerUUID.size();
+ endOfVoxelQueryPacket += populatePacketHeader(reinterpret_cast(endOfVoxelQueryPacket), packetType);
// encode the query data...
endOfVoxelQueryPacket += _voxelQuery.getBroadcastData(endOfVoxelQueryPacket);
@@ -2926,29 +2912,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
}
// render transmitter pick ray, if non-empty
- if (_transmitterPickStart != _transmitterPickEnd) {
- PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
- "Application::displaySide() ... transmitter pick ray...");
+ _myAvatar->renderTransmitterPickRay();
- Glower glower;
- const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
- glColor3fv(TRANSMITTER_PICK_COLOR);
- glLineWidth(3.0f);
- glBegin(GL_LINES);
- glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z);
- glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
- glEnd();
- glLineWidth(1.0f);
-
- glPushMatrix();
- glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
-
- const float PICK_END_RADIUS = 0.025f;
- glutSolidSphere(PICK_END_RADIUS, 8, 8);
-
- glPopMatrix();
- }
-
// give external parties a change to hook in
emit renderingInWorldInterface();
}
@@ -2972,71 +2937,38 @@ void Application::displayOverlay() {
// Render 2D overlay: I/O level bar graphs and text
glMatrixMode(GL_PROJECTION);
glPushMatrix();
- glLoadIdentity();
- gluOrtho2D(0, _glWidget->width(), _glWidget->height(), 0);
- glDisable(GL_DEPTH_TEST);
- glDisable(GL_LIGHTING);
- // Display a single screen-size quad to create an alpha blended 'collision' flash
- if (_audio.getCollisionFlashesScreen()) {
- float collisionSoundMagnitude = _audio.getCollisionSoundMagnitude();
- const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f;
- if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) {
- renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude());
- }
+ glLoadIdentity();
+ gluOrtho2D(0, _glWidget->width(), _glWidget->height(), 0);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_LIGHTING);
+
+ // Display a single screen-size quad to create an alpha blended 'collision' flash
+ if (_audio.getCollisionFlashesScreen()) {
+ float collisionSoundMagnitude = _audio.getCollisionSoundMagnitude();
+ const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f;
+ if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) {
+ renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude());
}
+ }
- if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
- displayStatsBackground(0x33333399, 0, _glWidget->height() - 68, 296, 68);
- _audio.render(_glWidget->width(), _glWidget->height());
- if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
- int oscilloscopeTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? 130 : 25;
- _audioScope.render(25, oscilloscopeTop);
- }
+ if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
+ displayStatsBackground(0x33333399, 0, _glWidget->height() - 68, 296, 68);
+ _audio.render(_glWidget->width(), _glWidget->height());
+ if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
+ int oscilloscopeTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? 130 : 25;
+ _audioScope.render(25, oscilloscopeTop);
}
+ }
- //noiseTest(_glWidget->width(), _glWidget->height());
+ //noiseTest(_glWidget->width(), _glWidget->height());
if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) {
- // Display small target box at center or head mouse target that can also be used to measure LOD
- glColor3f(1.0, 1.0, 1.0);
- glDisable(GL_LINE_SMOOTH);
- const int PIXEL_BOX = 16;
- glBegin(GL_LINES);
- glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY);
- glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY);
- glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2);
- glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2);
- glEnd();
- glEnable(GL_LINE_SMOOTH);
- glColor3f(1.f, 0.f, 0.f);
- glPointSize(3.0f);
- glDisable(GL_POINT_SMOOTH);
- glBegin(GL_POINTS);
- glVertex2f(_headMouseX - 1, _headMouseY + 1);
- glEnd();
- // If Faceshift is active, show eye pitch and yaw as separate pointer
- if (_faceshift.isActive()) {
- const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0;
- int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE;
- int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE;
-
- glColor3f(0.0, 1.0, 1.0);
- glDisable(GL_LINE_SMOOTH);
- glBegin(GL_LINES);
- glVertex2f(eyeTargetX - PIXEL_BOX/2, eyeTargetY);
- glVertex2f(eyeTargetX + PIXEL_BOX/2, eyeTargetY);
- glVertex2f(eyeTargetX, eyeTargetY - PIXEL_BOX/2);
- glVertex2f(eyeTargetX, eyeTargetY + PIXEL_BOX/2);
- glEnd();
-
- }
+ _myAvatar->renderHeadMouse();
}
- // Show hand transmitter data if detected
- if (_myTransmitter.isConnected()) {
- _myTransmitter.renderLevels(_glWidget->width(), _glWidget->height());
- }
+ _myAvatar->renderTransmitterLevels(_glWidget->width(), _glWidget->height());
+
// Display stats and log text onscreen
glLineWidth(1.0f);
glPointSize(1.0f);
@@ -3068,7 +3000,7 @@ void Application::displayOverlay() {
// Show on-screen msec timer
if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) {
char frameTimer[10];
- uint64_t mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
+ quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000));
int timerBottom =
(Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
@@ -3159,10 +3091,9 @@ void Application::displayStats() {
glPointSize(1.0f);
- int totalAvatars = 0, totalServers = 0;
- foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
- node->getType() == NODE_TYPE_AGENT ? totalAvatars++ : totalServers++;
- }
+ // we need to take one avatar out so we don't include ourselves
+ int totalAvatars = _avatarManager.size() - 1;
+ int totalServers = NodeList::getInstance()->size();
if (mirrorEnabled) {
horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
@@ -3205,8 +3136,8 @@ void Application::displayStats() {
int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0;
NodeList* nodeList = NodeList::getInstance();
- SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
- SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
+ SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
+ SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0;
pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0;
@@ -3216,7 +3147,7 @@ void Application::displayStats() {
int voxelServerCount = 0;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
totalPingVoxel += node->getPingMs();
voxelServerCount++;
if (pingVoxelMax < node->getPingMs()) {
@@ -3259,7 +3190,7 @@ void Application::displayStats() {
horizontalOffset += 171;
}
- glm::vec3 avatarPos = _myAvatar.getPosition();
+ glm::vec3 avatarPos = _myAvatar->getPosition();
lines = _statsExpanded ? 4 : 3;
displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - (mirrorEnabled ? 301 : 411) - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
@@ -3274,9 +3205,9 @@ void Application::displayStats() {
sprintf(avatarPosition, "Position: %.3f, %.3f, %.3f", avatarPos.x, avatarPos.y, avatarPos.z);
}
char avatarVelocity[30];
- sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar.getVelocity()));
+ sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar->getVelocity()));
char avatarBodyYaw[30];
- sprintf(avatarBodyYaw, "Yaw: %.2f", _myAvatar.getBodyYaw());
+ sprintf(avatarBodyYaw, "Yaw: %.2f", _myAvatar->getBodyYaw());
char avatarMixerStats[200];
verticalOffset += STATS_PELS_PER_LINE;
@@ -3287,7 +3218,7 @@ void Application::displayStats() {
drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarBodyYaw, .93f, .93f, .93f);
if (_statsExpanded) {
- SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
+ SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AvatarMixer);
if (avatarMixer) {
sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
roundf(avatarMixer->getAverageKilobitsPerSecond()),
@@ -3861,7 +3792,7 @@ bool Application::maybeEditVoxelUnderCursor() {
void Application::deleteVoxelUnderCursor() {
if (_mouseVoxel.s != 0) {
// sending delete to the server is sufficient, server will send new version so we see updates soon enough
- _voxelEditSender.sendVoxelEditMessage(PACKET_TYPE_VOXEL_ERASE, _mouseVoxel);
+ _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, _mouseVoxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
@@ -3887,8 +3818,8 @@ void Application::eyedropperVoxelUnderCursor() {
}
void Application::resetSensors() {
- _headMouseX = _mouseX = _glWidget->width() / 2;
- _headMouseY = _mouseY = _glWidget->height() / 2;
+ _mouseX = _glWidget->width() / 2;
+ _mouseY = _glWidget->height() / 2;
_faceshift.reset();
@@ -3896,11 +3827,8 @@ void Application::resetSensors() {
OculusManager::reset();
}
- QCursor::setPos(_headMouseX, _headMouseY);
- _myAvatar.reset();
- _myTransmitter.resetLevels();
- _myAvatar.setVelocity(glm::vec3(0,0,0));
- _myAvatar.setThrust(glm::vec3(0,0,0));
+ QCursor::setPos(_mouseX, _mouseY);
+ _myAvatar->reset();
QMetaObject::invokeMethod(&_audio, "reset", Qt::QueuedConnection);
}
@@ -3925,18 +3853,12 @@ void Application::setMenuShortcutsEnabled(bool enabled) {
}
void Application::updateWindowTitle(){
- QString title = "";
-
- QString buildVersion = " (build " + applicationVersion() + ")";
-
- QString username = _profile.getUsername();
- if(!username.isEmpty()){
- title += username;
- title += " @ ";
- }
- title += NodeList::getInstance()->getDomainHostname();
- title += buildVersion;
+ QString buildVersion = " (build " + applicationVersion() + ")";
+ NodeList* nodeList = NodeList::getInstance();
+
+ QString title = QString() + _profile.getUsername() + " " + nodeList->getOwnerUUID().toString()
+ + " @ " + nodeList->getDomainHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title);
@@ -3965,7 +3887,7 @@ void Application::domainChanged(const QString& domainHostname) {
}
void Application::nodeKilled(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
if (_voxelServerJurisdictions.find(nodeUUID) != _voxelServerJurisdictions.end()) {
@@ -3996,7 +3918,7 @@ void Application::nodeKilled(SharedNodePointer node) {
}
_voxelSceneStatsLock.unlock();
- } else if (node->getType() == NODE_TYPE_PARTICLE_SERVER) {
+ } else if (node->getType() == NodeType::ParticleServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
if (_particleServerJurisdictions.find(nodeUUID) != _particleServerJurisdictions.end()) {
@@ -4027,14 +3949,13 @@ void Application::nodeKilled(SharedNodePointer node) {
}
_voxelSceneStatsLock.unlock();
- } else if (node->getType() == NODE_TYPE_AVATAR_MIXER) {
+ } else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars
- _avatarManager.clearHash();
+ _avatarManager.clearOtherAvatars();
}
}
-void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
- const HifiSockAddr& senderSockAddr, bool wasStatsPacket) {
+void Application::trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
@@ -4045,13 +3966,13 @@ void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t m
_voxelSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
VoxelSceneStats& stats = _octreeServerSceneStats[nodeUUID];
- stats.trackIncomingOctreePacket(messageData, messageLength, wasStatsPacket, serverNode->getClockSkewUsec());
+ stats.trackIncomingOctreePacket(packet, wasStatsPacket, serverNode->getClockSkewUsec());
}
_voxelSceneStatsLock.unlock();
}
}
-int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderSockAddr) {
+int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
SharedNodePointer server = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
@@ -4059,7 +3980,7 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// parse the incoming stats datas stick it in a temporary object for now, while we
// determine which server it belongs to
VoxelSceneStats temp;
- int statsMessageLength = temp.unpackFromMessage(messageData, messageLength);
+ int statsMessageLength = temp.unpackFromMessage(reinterpret_cast(packet.data()), packet.size());
// quick fix for crash... why would voxelServer be NULL?
if (server) {
@@ -4068,7 +3989,8 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// 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()) {
- _octreeServerSceneStats[nodeUUID].unpackFromMessage(messageData, messageLength);
+ _octreeServerSceneStats[nodeUUID].unpackFromMessage(reinterpret_cast(packet.data()),
+ packet.size());
} else {
_octreeServerSceneStats[nodeUUID] = temp;
}
@@ -4079,7 +4001,7 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
- if (server->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (server->getType() == NodeType::VoxelServer) {
jurisdiction = &_voxelServerJurisdictions;
} else {
jurisdiction = &_particleServerJurisdictions;
@@ -4114,37 +4036,37 @@ void Application::packetSent(quint64 length) {
_bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length);
}
-void Application::loadScripts(){
- // loads all saved scripts
- QSettings* settings = new QSettings(this);
- int size = settings->beginReadArray("Settings");
- for(int i=0; isetArrayIndex(i);
- QString string = settings->value("script").toString();
- loadScript(string);
- }
- settings->endArray();
-
+void Application::loadScripts() {
+ // loads all saved scripts
+ QSettings* settings = new QSettings(this);
+ int size = settings->beginReadArray("Settings");
+
+ for (int i = 0; i < size; ++i){
+ settings->setArrayIndex(i);
+ QString string = settings->value("script").toString();
+ loadScript(string);
+ }
+
+ settings->endArray();
}
-void Application::saveScripts(){
- // saves all current running scripts
- QSettings* settings = new QSettings(this);
- settings->beginWriteArray("Settings");
- for(int i=0; i<_activeScripts.size(); ++i){
- settings->setArrayIndex(i);
- settings->setValue("script", _activeScripts.at(i));
- }
- settings->endArray();
-
+void Application::saveScripts() {
+ // saves all current running scripts
+ QSettings* settings = new QSettings(this);
+ settings->beginWriteArray("Settings");
+ for (int i = 0; i < _activeScripts.size(); ++i){
+ settings->setArrayIndex(i);
+ settings->setValue("script", _activeScripts.at(i));
+ }
+
+ settings->endArray();
}
-void Application::removeScriptName(const QString& fileNameString)
-{
+void Application::removeScriptName(const QString& fileNameString) {
_activeScripts.removeOne(fileNameString);
}
-void Application::loadScript(const QString& fileNameString){
+void Application::loadScript(const QString& fileNameString) {
_activeScripts.append(fileNameString);
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
const char* fileName = fileNameAscii.data();
@@ -4172,7 +4094,8 @@ void Application::loadScript(const QString& fileNameString){
// start the script on a new thread...
bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself
- ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface);
+ ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(),
+ &_controllerScriptingInterface);
scriptEngine->setupMenuItems();
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
@@ -4182,12 +4105,12 @@ void Application::loadScript(const QString& fileNameString){
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
// hook our avatar object into this script engine
- scriptEngine->setAvatarData(&_myAvatar, "MyAvatar");
+ scriptEngine->setAvatarData( static_cast(_myAvatar), "MyAvatar");
QThread* workerThread = new QThread(this);
// when the worker thread is started, call our engine's run..
- connect(workerThread, SIGNAL(started()), scriptEngine, SLOT(run()));
+ connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run);
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater()));
@@ -4227,7 +4150,7 @@ void Application::toggleLogDialog() {
}
void Application::initAvatarAndViewFrustum() {
- updateAvatar(0.f);
+ updateMyAvatar(0.f);
}
QString Application::getLocalVoxelCacheFileName() {
@@ -4341,3 +4264,14 @@ void Application::skipVersion(QString latestVersion) {
skipFile.seek(0);
skipFile.write(latestVersion.toStdString().c_str());
}
+
+void Application::takeSnapshot() {
+ switchToResourcesParentIfRequired();
+ QMediaPlayer* player = new QMediaPlayer();
+ QFileInfo inf = QFileInfo("resources/sounds/snap.wav");
+ player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
+ player->play();
+
+ Snapshot::saveSnapshot(_glWidget, _profile.getUsername(), _myAvatar->getPosition());
+}
+
diff --git a/interface/src/Application.h b/interface/src/Application.h
index c3404b4035..bf9981a160 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -142,7 +142,7 @@ public:
glm::vec3 getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVoxel);
QGLWidget* getGLWidget() { return _glWidget; }
- MyAvatar* getAvatar() { return &_myAvatar; }
+ MyAvatar* getAvatar() { return _myAvatar; }
Audio* getAudio() { return &_audio; }
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
@@ -175,8 +175,7 @@ public:
Profile* getProfile() { return &_profile; }
void resetProfile(const QString& username);
- void controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,
- const QSet& destinationNodeTypes);
+ void controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes);
void setupWorldLight();
@@ -265,7 +264,7 @@ private:
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
static bool sendVoxelsOperation(OctreeElement* node, void* extraData);
- static void sendPingPackets();
+ void sendPingPackets();
void initDisplay();
void init();
@@ -283,10 +282,8 @@ private:
void updateSixense(float deltaTime);
void updateSerialDevices(float deltaTime);
void updateThreads(float deltaTime);
- void updateMyAvatarSimulation(float deltaTime);
void updateParticles(float deltaTime);
void updateMetavoxels(float deltaTime);
- void updateTransmitter(float deltaTime);
void updateCamera(float deltaTime);
void updateDialogs(float deltaTime);
void updateAudio(float deltaTime);
@@ -298,8 +295,8 @@ private:
void renderLookatIndicator(glm::vec3 pointOfInterest);
void renderHighlightVoxel(VoxelDetail voxel);
- void updateAvatar(float deltaTime);
- void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
+ void updateMyAvatar(float deltaTime);
+ void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions);
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
glm::vec3 getSunDirection();
@@ -356,7 +353,7 @@ private:
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
- VoxelImporter _voxelImporter;
+ VoxelImporter* _voxelImporter;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
@@ -375,10 +372,8 @@ private:
VoxelQuery _voxelQuery; // NodeData derived class for querying voxels from voxel server
AvatarManager _avatarManager;
- MyAvatar _myAvatar; // The rendered avatar of oneself
- Profile _profile; // The data-server linked profile for this user
-
- Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
+ MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
+ Profile _profile; // The data-server linked profile for this user
Faceshift _faceshift;
@@ -398,13 +393,11 @@ private:
Environment _environment;
- int _headMouseX, _headMouseY;
-
int _mouseX;
int _mouseY;
int _mouseDragStartedX;
int _mouseDragStartedY;
- uint64_t _lastMouseMove;
+ quint64 _lastMouseMove;
bool _mouseHidden;
bool _seenMouseMove;
@@ -418,8 +411,6 @@ private:
float _touchDragStartedAvgX;
float _touchDragStartedAvgY;
bool _isTouchPressed; // true if multitouch has been pressed (clear when finished)
- float _yawFromTouch;
- float _pitchFromTouch;
VoxelDetail _mouseVoxelDragging;
bool _mousePressed; // true if mouse has been pressed (clear when finished)
@@ -444,9 +435,6 @@ private:
bool _lookingAwayFromOrigin;
glm::vec3 _nudgeGuidePosition;
- glm::vec3 _transmitterPickStart;
- glm::vec3 _transmitterPickEnd;
-
ChatEntry _chatEntry; // chat entry field
bool _chatEntryOn; // Whether to show the chat entry
@@ -482,9 +470,8 @@ private:
PieMenu _pieMenu;
- int parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
- void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
- const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
+ int parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderAddress);
+ void trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
@@ -505,6 +492,7 @@ private:
void checkVersion();
void displayUpdateDialog();
bool shouldSkipVersion(QString latestVersion);
+ void takeSnapshot();
};
#endif /* defined(__interface__Application__) */
diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp
index ea6bc80712..9fede84a93 100644
--- a/interface/src/Audio.cpp
+++ b/interface/src/Audio.cpp
@@ -22,7 +22,6 @@
#include
#include
-#include
#include
#include
#include
@@ -286,8 +285,8 @@ void Audio::start() {
void Audio::handleAudioInput() {
static char monoAudioDataPacket[MAX_PACKET_SIZE];
- static int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO);
- static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + NUM_BYTES_RFC4122_UUID;
+ static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
+ static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat);
static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes);
@@ -366,7 +365,7 @@ void Audio::handleAudioInput() {
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
NodeList* nodeList = NodeList::getInstance();
- SharedNodePointer audioMixer = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
+ SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) {
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
@@ -376,16 +375,10 @@ void Audio::handleAudioInput() {
// we need the amount of bytes in the buffer + 1 for type
// + 12 for 3 floats for position + float for bearing + 1 attenuation byte
- PACKET_TYPE packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
- ? PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO : PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO;
+ PacketType packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
+ ? PacketTypeMicrophoneAudioWithEcho : PacketTypeMicrophoneAudioNoEcho;
- char* currentPacketPtr = monoAudioDataPacket + populateTypeAndVersion((unsigned char*) monoAudioDataPacket,
- packetType);
-
- // pack Source Data
- QByteArray rfcUUID = NodeList::getInstance()->getOwnerUUID().toRfc4122();
- memcpy(currentPacketPtr, rfcUUID.constData(), rfcUUID.size());
- currentPacketPtr += rfcUUID.size();
+ char* currentPacketPtr = monoAudioDataPacket + populatePacketHeader(monoAudioDataPacket, packetType);
// memcpy the three float positions
memcpy(currentPacketPtr, &headPosition, sizeof(headPosition));
@@ -434,7 +427,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
}
}
- _ringBuffer.parseData((unsigned char*) audioByteArray.data(), audioByteArray.size());
+ _ringBuffer.parseData(audioByteArray);
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
@@ -546,7 +539,7 @@ void Audio::toggleMute() {
}
void Audio::render(int screenWidth, int screenHeight) {
- if (_audioInput) {
+ if (_audioInput && _audioOutput) {
glLineWidth(2.0);
glBegin(GL_LINES);
glColor3f(.93f, .93f, .93f);
diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp
index fd27eb2428..4d7540c06c 100644
--- a/interface/src/ControllerScriptingInterface.cpp
+++ b/interface/src/ControllerScriptingInterface.cpp
@@ -10,6 +10,14 @@
#include "Application.h"
#include "ControllerScriptingInterface.h"
+ControllerScriptingInterface::ControllerScriptingInterface() :
+ _mouseCaptured(false),
+ _touchCaptured(false),
+ _wheelCaptured(false)
+{
+}
+
+
const PalmData* ControllerScriptingInterface::getPrimaryPalm() const {
int leftPalmIndex, rightPalmIndex;
@@ -179,5 +187,35 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex
return glm::vec3(0); // bad index
}
+bool ControllerScriptingInterface::isKeyCaptured(QKeyEvent* event) const {
+ return isKeyCaptured(KeyEvent(*event));
+}
+bool ControllerScriptingInterface::isKeyCaptured(const KeyEvent& event) const {
+ // if we've captured some combination of this key it will be in the map
+ if (_capturedKeys.contains(event.key, event)) {
+ return true;
+ }
+ return false;
+}
+
+void ControllerScriptingInterface::captureKeyEvents(const KeyEvent& event) {
+ // if it's valid
+ if (event.isValid) {
+ // and not already captured
+ if (!isKeyCaptured(event)) {
+ // then add this KeyEvent record to the captured combos for this key
+ _capturedKeys.insert(event.key, event);
+ }
+ }
+}
+
+void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) {
+ if (event.isValid) {
+ // and not already captured
+ if (isKeyCaptured(event)) {
+ _capturedKeys.remove(event.key, event);
+ }
+ }
+}
diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h
index 69daefa3fb..e84039bcb0 100644
--- a/interface/src/ControllerScriptingInterface.h
+++ b/interface/src/ControllerScriptingInterface.h
@@ -1,3 +1,4 @@
+
//
// ControllerScriptingInterface.h
// hifi
@@ -18,6 +19,27 @@ class PalmData;
class ControllerScriptingInterface : public AbstractControllerScriptingInterface {
Q_OBJECT
+public:
+ ControllerScriptingInterface();
+ void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); }
+ void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); }
+
+ void emitMouseMoveEvent(QMouseEvent* event) { emit mouseMoveEvent(MouseEvent(*event)); }
+ void emitMousePressEvent(QMouseEvent* event) { emit mousePressEvent(MouseEvent(*event)); }
+ void emitMouseReleaseEvent(QMouseEvent* event) { emit mouseReleaseEvent(MouseEvent(*event)); }
+
+ void emitTouchBeginEvent(QTouchEvent* event) { emit touchBeginEvent(*event); }
+ void emitTouchEndEvent(QTouchEvent* event) { emit touchEndEvent(*event); }
+ void emitTouchUpdateEvent(QTouchEvent* event) { emit touchUpdateEvent(*event); }
+ void emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); }
+
+ bool isKeyCaptured(QKeyEvent* event) const;
+ bool isKeyCaptured(const KeyEvent& event) const;
+ bool isMouseCaptured() const { return _mouseCaptured; }
+ bool isTouchCaptured() const { return _touchCaptured; }
+ bool isWheelCaptured() const { return _wheelCaptured; }
+
+
public slots:
virtual bool isPrimaryButtonPressed() const;
virtual glm::vec2 getPrimaryJoystickPosition() const;
@@ -36,11 +58,28 @@ public slots:
virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const;
virtual glm::vec3 getSpatialControlNormal(int controlIndex) const;
+ virtual void captureKeyEvents(const KeyEvent& event);
+ virtual void releaseKeyEvents(const KeyEvent& event);
+
+ virtual void captureMouseEvents() { _mouseCaptured = true; }
+ virtual void releaseMouseEvents() { _mouseCaptured = false; }
+
+ virtual void captureTouchEvents() { _touchCaptured = true; }
+ virtual void releaseTouchEvents() { _touchCaptured = false; }
+
+ virtual void captureWheelEvents() { _wheelCaptured = true; }
+ virtual void releaseWheelEvents() { _wheelCaptured = false; }
+
private:
const PalmData* getPrimaryPalm() const;
const PalmData* getPalm(int palmIndex) const;
int getNumberOfActivePalms() const;
const PalmData* getActivePalm(int palmIndex) const;
+
+ bool _mouseCaptured;
+ bool _touchCaptured;
+ bool _wheelCaptured;
+ QMultiMap _capturedKeys;
};
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip
diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp
index b78bd0c309..2938be80ea 100644
--- a/interface/src/DatagramProcessor.cpp
+++ b/interface/src/DatagramProcessor.cpp
@@ -26,52 +26,53 @@ void DatagramProcessor::processDatagrams() {
"DatagramProcessor::processDatagrams()");
HifiSockAddr senderSockAddr;
- ssize_t bytesReceived;
- static unsigned char incomingPacket[MAX_PACKET_SIZE];
+ static QByteArray incomingPacket;
Application* application = Application::getInstance();
+ NodeList* nodeList = NodeList::getInstance();
- while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams() &&
- (bytesReceived = NodeList::getInstance()->getNodeSocket().readDatagram((char*) incomingPacket,
- MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))) {
+ while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams()) {
+ incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
_packetCount++;
- _byteCount += bytesReceived;
+ _byteCount += incomingPacket.size();
- if (packetVersionMatch(incomingPacket, senderSockAddr)) {
+ if (packetVersionMatch(incomingPacket)) {
// only process this packet if we have a match on the packet version
- switch (incomingPacket[0]) {
- case PACKET_TYPE_TRANSMITTER_DATA_V2:
+ switch (packetTypeForPacket(incomingPacket)) {
+ case PacketTypeTransmitterData:
// V2 = IOS transmitter app
- application->_myTransmitter.processIncomingData(incomingPacket, bytesReceived);
+ application->getAvatar()->getTransmitter().processIncomingData(reinterpret_cast(incomingPacket.data()),
+ incomingPacket.size());
break;
- case PACKET_TYPE_MIXED_AUDIO:
+ case PacketTypeMixedAudio:
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
- Q_ARG(QByteArray, QByteArray((char*) incomingPacket, bytesReceived)));
+ Q_ARG(QByteArray, incomingPacket));
break;
- case PACKET_TYPE_PARTICLE_ADD_RESPONSE:
+ case PacketTypeParticleAddResponse:
// this will keep creatorTokenIDs to IDs mapped correctly
- Particle::handleAddParticleResponse(incomingPacket, bytesReceived);
+ Particle::handleAddParticleResponse(incomingPacket);
+ application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
break;
- case PACKET_TYPE_PARTICLE_DATA:
- case PACKET_TYPE_PARTICLE_ERASE:
- case PACKET_TYPE_VOXEL_DATA:
- case PACKET_TYPE_VOXEL_ERASE:
- case PACKET_TYPE_OCTREE_STATS:
- case PACKET_TYPE_ENVIRONMENT_DATA: {
+ case PacketTypeParticleData:
+ case PacketTypeParticleErase:
+ case PacketTypeVoxelData:
+ case PacketTypeVoxelErase:
+ case PacketTypeOctreeStats:
+ case PacketTypeEnvironmentData: {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::networkReceive()... _voxelProcessor.queueReceivedPacket()");
bool wantExtraDebugging = application->getLogger()->extraDebugging();
- if (wantExtraDebugging && incomingPacket[0] == PACKET_TYPE_VOXEL_DATA) {
+ if (wantExtraDebugging && packetTypeForPacket(incomingPacket) == PacketTypeVoxelData) {
int numBytesPacketHeader = numBytesForPacketHeader(incomingPacket);
- unsigned char* dataAt = incomingPacket + numBytesPacketHeader;
+ unsigned char* dataAt = reinterpret_cast(incomingPacket.data()) + numBytesPacketHeader;
dataAt += sizeof(VOXEL_PACKET_FLAGS);
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
@@ -80,52 +81,49 @@ void DatagramProcessor::processDatagrams() {
VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
int flightTime = arrivedAt - sentAt;
- printf("got PACKET_TYPE_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime);
+ 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, bytesReceived);
+ application->_voxelProcessor.queueReceivedPacket(senderSockAddr, incomingPacket);
break;
}
- case PACKET_TYPE_METAVOXEL_DATA:
- application->_metavoxels.processData(QByteArray((const char*) incomingPacket, bytesReceived),
- senderSockAddr);
+ case PacketTypeMetavoxelData:
+ application->_metavoxels.processData(incomingPacket, senderSockAddr);
break;
- case PACKET_TYPE_BULK_AVATAR_DATA:
- case PACKET_TYPE_KILL_AVATAR: {
+ case PacketTypeBulkAvatarData:
+ case PacketTypeKillAvatar: {
// update having heard from the avatar-mixer and record the bytes received
SharedNodePointer avatarMixer = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (avatarMixer) {
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
- avatarMixer->recordBytesReceived(bytesReceived);
+ avatarMixer->recordBytesReceived(incomingPacket.size());
- QByteArray datagram(reinterpret_cast(incomingPacket), bytesReceived);
-
- if (incomingPacket[0] == PACKET_TYPE_BULK_AVATAR_DATA) {
+ if (packetTypeForPacket(incomingPacket) == PacketTypeBulkAvatarData) {
QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram",
- Q_ARG(const QByteArray&, datagram),
+ Q_ARG(const QByteArray&, incomingPacket),
Q_ARG(const QWeakPointer&, avatarMixer));
} else {
// this is an avatar kill, pass it to the application AvatarManager
QMetaObject::invokeMethod(&application->getAvatarManager(), "processKillAvatar",
- Q_ARG(const QByteArray&, datagram));
+ Q_ARG(const QByteArray&, incomingPacket));
}
}
- application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived);
+ application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break;
- }
- case PACKET_TYPE_DATA_SERVER_GET:
- case PACKET_TYPE_DATA_SERVER_PUT:
- case PACKET_TYPE_DATA_SERVER_SEND:
- case PACKET_TYPE_DATA_SERVER_CONFIRM:
- DataServerClient::processMessageFromDataServer(incomingPacket, bytesReceived);
+ }
+ case PacketTypeDataServerGet:
+ case PacketTypeDataServerPut:
+ case PacketTypeDataServerSend:
+ case PacketTypeDataServerConfirm:
+ DataServerClient::processMessageFromDataServer(incomingPacket);
break;
default:
- NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket, bytesReceived);
+ NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket);
break;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp
index 46c156f860..59f16fc5eb 100644
--- a/interface/src/Environment.cpp
+++ b/interface/src/Environment.cpp
@@ -148,32 +148,28 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3
return found;
}
-int Environment::parseData(const HifiSockAddr& senderAddress, unsigned char* sourceBuffer, int numBytes) {
+int Environment::parseData(const HifiSockAddr& senderAddress, const QByteArray& packet) {
// push past the packet header
- unsigned char* start = sourceBuffer;
-
- int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
- sourceBuffer += numBytesPacketHeader;
- numBytes -= numBytesPacketHeader;
+ int bytesRead = numBytesForPacketHeader(packet);
// get the lock for the duration of the call
QMutexLocker locker(&_mutex);
EnvironmentData newData;
- while (numBytes > 0) {
- int dataLength = newData.parseData(sourceBuffer, numBytes);
+ while (bytesRead < packet.size()) {
+ int dataLength = newData.parseData(reinterpret_cast(packet.data()) + bytesRead,
+ packet.size() - bytesRead);
// update the mapping by address/ID
_data[senderAddress][newData.getID()] = newData;
- sourceBuffer += dataLength;
- numBytes -= dataLength;
+ bytesRead += dataLength;
}
// remove the default mapping, if any
_data.remove(HifiSockAddr());
- return sourceBuffer - start;
+ return bytesRead;
}
ProgramObject* Environment::createSkyProgram(const char* from, int* locations) {
diff --git a/interface/src/Environment.h b/interface/src/Environment.h
index fc572c5e03..273ee54cee 100644
--- a/interface/src/Environment.h
+++ b/interface/src/Environment.h
@@ -34,7 +34,7 @@ public:
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration);
- int parseData(const HifiSockAddr& senderSockAddr, unsigned char* sourceBuffer, int numBytes);
+ int parseData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
private:
diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp
index 4c96dfcfa5..81c626a46e 100644
--- a/interface/src/FileLogger.cpp
+++ b/interface/src/FileLogger.cpp
@@ -14,17 +14,20 @@
#include
#include
-FileLogger::FileLogger() : _logData(NULL) {
- setExtraDebugging(false);
- _fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
- QDir logDir(_fileName);
- if (!logDir.exists(_fileName)) {
- logDir.mkdir(_fileName);
- }
+const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
+const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
+const QString LOGS_DIRECTORY = "Logs";
+FileLogger::FileLogger(QObject* parent) :
+ AbstractLoggerInterface(parent),
+ _logData(NULL)
+{
+ setExtraDebugging(false);
+
+ _fileName = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = QHostAddress(getHostOrderLocalAddress());
QDateTime now = QDateTime::currentDateTime();
- _fileName.append(QString("/hifi-log_%1_%2.txt").arg(clientAddress.toString(), now.toString("yyyy-MM-dd_hh.mm.ss")));
+ _fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
}
void FileLogger::addMessage(QString message) {
@@ -40,5 +43,5 @@ void FileLogger::addMessage(QString message) {
}
void FileLogger::locateLog() {
- FileUtils::LocateFile(_fileName);
+ FileUtils::locateFile(_fileName);
}
diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h
index 6a17032ae2..35cafa4db7 100644
--- a/interface/src/FileLogger.h
+++ b/interface/src/FileLogger.h
@@ -16,7 +16,7 @@ class FileLogger : public AbstractLoggerInterface {
Q_OBJECT
public:
- FileLogger();
+ FileLogger(QObject* parent = NULL);
virtual void addMessage(QString);
virtual QStringList getLogData() { return _logData; };
diff --git a/interface/src/ImportDialog.cpp b/interface/src/ImportDialog.cpp
index c3f499b038..ac7853629c 100644
--- a/interface/src/ImportDialog.cpp
+++ b/interface/src/ImportDialog.cpp
@@ -10,7 +10,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -20,38 +19,38 @@ const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
const QString IMPORT_INFO = QObject::tr("Import %1 as voxels");
const QString CANCEL_BUTTON_NAME = QObject::tr("Cancel");
const QString INFO_LABEL_TEXT = QObject::tr(""
- "This will load the selected file into Hifi and allow you
"
- "to place it with %1-V; you must be in select or
"
- "add mode (S or V keys will toggle mode) to place.
");
+ "This will load the selected file into Hifi and allow you
"
+ "to place it with %1-V; you must be in select or
"
+ "add mode (S or V keys will toggle mode) to place.");
const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
const int SHORT_FILE_EXTENSION = 4;
const int SECOND_INDEX_LETTER = 1;
QIcon HiFiIconProvider::icon(QFileIconProvider::IconType type) const {
-
+
switchToResourcesParentIfRequired();
-
+
// types
// Computer, Desktop, Trashcan, Network, Drive, Folder, File
QString typeString;
-
+
switch (type) {
case QFileIconProvider::Computer:
typeString = "computer";
break;
-
+
case QFileIconProvider::Desktop:
typeString = "desktop";
break;
-
+
case QFileIconProvider::Trashcan:
case QFileIconProvider::Network:
case QFileIconProvider::Drive:
case QFileIconProvider::Folder:
typeString = "folder";
break;
-
+
default:
typeString = "file";
break;
@@ -63,7 +62,7 @@ QIcon HiFiIconProvider::icon(QFileIconProvider::IconType type) const {
QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
switchToResourcesParentIfRequired();
const QString ext = info.suffix().toLower();
-
+
if (info.isDir()) {
if (info.absoluteFilePath() == QDir::homePath()) {
return QIcon("resources/icons/home.svg");
@@ -74,12 +73,12 @@ QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
}
return QIcon("resources/icons/folder.svg");
}
-
+
QFileInfo iconFile("resources/icons/" + iconsMap[ext]);
if (iconFile.exists() && iconFile.isFile()) {
return QIcon(iconFile.filePath());
}
-
+
return QIcon("resources/icons/file.svg");
}
@@ -98,8 +97,9 @@ QString HiFiIconProvider::type(const QFileInfo &info) const {
ImportDialog::ImportDialog(QWidget* parent) :
QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, NULL),
_importButton(IMPORT_BUTTON_NAME, this),
- _cancelButton(CANCEL_BUTTON_NAME, this) {
-
+ _cancelButton(CANCEL_BUTTON_NAME, this),
+ fileAccepted(false) {
+
setOption(QFileDialog::DontUseNativeDialog, true);
setFileMode(QFileDialog::ExistingFile);
setViewMode(QFileDialog::Detail);
@@ -111,16 +111,18 @@ ImportDialog::ImportDialog(QWidget* parent) :
#endif
QLabel* infoLabel = new QLabel(QString(INFO_LABEL_TEXT).arg(cmdString));
infoLabel->setObjectName("infoLabel");
-
+
QGridLayout* gridLayout = (QGridLayout*) layout();
gridLayout->addWidget(infoLabel, 2, 0, 2, 1);
gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1);
gridLayout->addWidget(&_importButton, 2, 2, 2, 1);
-
+
setImportTypes();
setLayout();
connect(&_importButton, SIGNAL(pressed()), SLOT(import()));
+ connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
+
connect(&_cancelButton, SIGNAL(pressed()), SLOT(close()));
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
}
@@ -130,12 +132,22 @@ ImportDialog::~ImportDialog() {
}
void ImportDialog::import() {
+ fileAccepted = true;
emit accepted();
- close();
}
void ImportDialog::accept() {
- QFileDialog::accept();
+ // do nothing if import is not enable
+ if (!_importButton.isEnabled()) {
+ return;
+ }
+
+ if (!fileAccepted) {
+ fileAccepted = true;
+ emit accepted();
+ } else {
+ QFileDialog::accept();
+ }
}
void ImportDialog::reject() {
@@ -163,72 +175,68 @@ void ImportDialog::saveCurrentFile(QString filename) {
}
void ImportDialog::setLayout() {
-
+
// set ObjectName used in qss for styling
_importButton.setObjectName("importButton");
_cancelButton.setObjectName("cancelButton");
-
+
// set fixed size
_importButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
_cancelButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-
+
// hide unused embedded widgets in QFileDialog
QWidget* widget = findChild("lookInCombo");
widget->hide();
-
+
widget = findChild("backButton");
widget->hide();
-
+
widget = findChild("forwardButton");
widget->hide();
-
+
widget = findChild("toParentButton");
widget->hide();
-
+
widget = findChild("newFolderButton");
widget->hide();
-
+
widget = findChild("listModeButton");
widget->hide();
-
+
widget = findChild("detailModeButton");
widget->hide();
-
+
widget = findChild("fileNameEdit");
widget->hide();
-
+
widget = findChild("fileTypeCombo");
widget->hide();
-
+
widget = findChild("fileTypeLabel");
widget->hide();
-
+
widget = findChild("fileNameLabel");
widget->hide();
-
+
widget = findChild("buttonBox");
widget->hide();
-
+
QSplitter* splitter = findChild("splitter");
splitter->setHandleWidth(0);
-
+
// remove blue outline on Mac
widget = findChild("sidebar");
widget->setAttribute(Qt::WA_MacShowFocusRect, false);
-
+
widget = findChild("treeView");
widget->setAttribute(Qt::WA_MacShowFocusRect, false);
-
- // remove reference to treeView
- widget = NULL;
- widget->deleteLater();
-
+
switchToResourcesParentIfRequired();
QFile styleSheet("resources/styles/import_dialog.qss");
if (styleSheet.open(QIODevice::ReadOnly)) {
setStyleSheet(styleSheet.readAll());
}
-
+
}
void ImportDialog::setImportTypes() {
@@ -251,7 +259,6 @@ void ImportDialog::setImportTypes() {
QJsonObject fileFormatObject = fileFormat.toObject();
QString ext(fileFormatObject["extension"].toString());
- QString description(fileFormatObject.value("description").toString());
QString icon(fileFormatObject.value("icon").toString());
if (formatsCounter > 0) {
@@ -273,7 +280,7 @@ void ImportDialog::setImportTypes() {
// set custom file icons
setIconProvider(new HiFiIconProvider(iconsMap));
setNameFilter(importFormatsFilterList);
-
+
#ifdef Q_OS_MAC
QString cmdString = ("Command");
#else
diff --git a/interface/src/ImportDialog.h b/interface/src/ImportDialog.h
index f66321c209..5cfc49e51e 100644
--- a/interface/src/ImportDialog.h
+++ b/interface/src/ImportDialog.h
@@ -56,6 +56,7 @@ private:
void setLayout();
void setImportTypes();
+ bool fileAccepted;
};
#endif /* defined(__hifi__ImportDialog__) */
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 8fd268938e..67eaa8782c 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -116,10 +116,10 @@ Menu::Menu() :
this,
SLOT(goToLocation()));
addActionToQMenuAndActionHash(fileMenu,
- MenuOption::GoToUser,
+ MenuOption::GoTo,
Qt::Key_At,
this,
- SLOT(goToUser()));
+ SLOT(goTo()));
addDisabledActionAndSeparator(fileMenu, "Settings");
@@ -910,6 +910,60 @@ void Menu::goToDomain() {
sendFakeEnterEvent();
}
+void Menu::goTo() {
+
+ QInputDialog gotoDialog(Application::getInstance()->getWindow());
+ gotoDialog.setWindowTitle("Go to");
+ gotoDialog.setLabelText("Destination:");
+ QString destination = Application::getInstance()->getProfile()->getUsername();
+ gotoDialog.setTextValue(destination);
+ gotoDialog.setWindowFlags(Qt::Sheet);
+ gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
+
+ int dialogReturn = gotoDialog.exec();
+ if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
+
+ destination = gotoDialog.textValue();
+
+ QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
+
+ const int NUMBER_OF_COORDINATE_ITEMS = 3;
+ const int X_ITEM = 0;
+ const int Y_ITEM = 1;
+ const int Z_ITEM = 2;
+ if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
+
+ double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
+ double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
+ double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
+
+ glm::vec3 newAvatarPos(x, y, z);
+
+ MyAvatar* myAvatar = Application::getInstance()->getAvatar();
+ glm::vec3 avatarPos = myAvatar->getPosition();
+ if (newAvatarPos != avatarPos) {
+ // send a node kill request, indicating to other clients that they should play the "disappeared" effect
+ MyAvatar::sendKillAvatar();
+
+ qDebug("Going To Location: %f, %f, %f...", x, y, z);
+ myAvatar->setPosition(newAvatarPos);
+ }
+
+ } else {
+ // there's a username entered by the user, make a request to the data-server
+ DataServerClient::getValuesForKeysAndUserString(
+ QStringList()
+ << DataServerKey::Domain
+ << DataServerKey::Position
+ << DataServerKey::Orientation,
+ destination, Application::getInstance()->getProfile());
+
+ }
+ }
+
+ sendFakeEnterEvent();
+}
+
void Menu::goToLocation() {
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
@@ -954,26 +1008,6 @@ void Menu::goToLocation() {
sendFakeEnterEvent();
}
-void Menu::goToUser() {
- QInputDialog userDialog(Application::getInstance()->getWindow());
- userDialog.setWindowTitle("Go to User");
- userDialog.setLabelText("Destination user:");
- QString username = Application::getInstance()->getProfile()->getUsername();
- userDialog.setTextValue(username);
- userDialog.setWindowFlags(Qt::Sheet);
- userDialog.resize(userDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, userDialog.size().height());
-
- int dialogReturn = userDialog.exec();
- if (dialogReturn == QDialog::Accepted && !userDialog.textValue().isEmpty()) {
- // there's a username entered by the user, make a request to the data-server
- DataServerClient::getValuesForKeysAndUserString(
- QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation,
- userDialog.textValue(), Application::getInstance()->getProfile());
- }
-
- sendFakeEnterEvent();
-}
-
void Menu::pasteToVoxel() {
QInputDialog pasteToOctalCodeDialog(Application::getInstance()->getWindow());
pasteToOctalCodeDialog.setWindowTitle("Paste to Voxel");
@@ -1137,3 +1171,14 @@ void Menu::updateFrustumRenderModeAction() {
}
}
+QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
+ int lastIndex;
+ lastIndex = string.lastIndexOf(search);
+ if (lastIndex > 0) {
+ lastIndex = string.lastIndexOf(search);
+ string.replace(lastIndex, 1, replace);
+ }
+
+ return string;
+}
+
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 03149ce07c..5e49ca6fd1 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -92,7 +92,7 @@ public slots:
void saveSettings(QSettings* settings = NULL);
void importSettings();
void exportSettings();
- void goToUser();
+ void goTo();
void pasteToVoxel();
private slots:
@@ -152,6 +152,7 @@ private:
QAction* _useVoxelShader;
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
+ QString replaceLastOccurrence(QChar search, QChar replace, QString string);
};
namespace MenuOption {
@@ -209,7 +210,7 @@ namespace MenuOption {
const QString GlowMode = "Cycle Glow Mode";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
- const QString GoToUser = "Go To User...";
+ const QString GoTo = "Go To...";
const QString ImportVoxels = "Import Voxels";
const QString ImportVoxelsClipboard = "Import Voxels to Clipboard";
const QString IncreaseAvatarSize = "Increase Avatar Size";
diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp
index 258db6da00..311c8f2556 100644
--- a/interface/src/MetavoxelSystem.cpp
+++ b/interface/src/MetavoxelSystem.cpp
@@ -6,11 +6,11 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
+#include
#include
#include
-#include
#include
#include "Application.h"
@@ -24,6 +24,12 @@ MetavoxelSystem::MetavoxelSystem() :
_buffer(QOpenGLBuffer::VertexBuffer) {
}
+MetavoxelSystem::~MetavoxelSystem() {
+ for (QHash::const_iterator it = _clients.begin(); it != _clients.end(); it++) {
+ delete it.value();
+ }
+}
+
void MetavoxelSystem::init() {
if (!_program.isLinked()) {
switchToResourcesParentIfRequired();
@@ -42,19 +48,23 @@ void MetavoxelSystem::init() {
_buffer.create();
}
+void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit) {
+ foreach (MetavoxelClient* client, _clients) {
+ client->applyEdit(edit);
+ }
+}
+
void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) {
QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender));
}
void MetavoxelSystem::simulate(float deltaTime) {
// simulate the clients
+ _points.clear();
foreach (MetavoxelClient* client, _clients) {
- client->simulate(deltaTime);
+ client->simulate(deltaTime, _pointVisitor);
}
- _points.clear();
- _data.guide(_pointVisitor);
-
_buffer.bind();
int bytes = _points.size() * sizeof(Point);
if (_buffer.size() < bytes) {
@@ -107,21 +117,20 @@ void MetavoxelSystem::render() {
}
void MetavoxelSystem::nodeAdded(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
- QMetaObject::invokeMethod(this, "addClient", Q_ARG(const QUuid&, node->getUUID()),
- Q_ARG(const HifiSockAddr&, node->getLocalSocket()));
+ if (node->getType() == NodeType::MetavoxelServer) {
+ QMetaObject::invokeMethod(this, "addClient", Q_ARG(const SharedNodePointer&, node));
}
}
void MetavoxelSystem::nodeKilled(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
+ if (node->getType() == NodeType::MetavoxelServer) {
QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID()));
}
}
-void MetavoxelSystem::addClient(const QUuid& uuid, const HifiSockAddr& address) {
- MetavoxelClient* client = new MetavoxelClient(address);
- _clients.insert(uuid, client);
+void MetavoxelSystem::addClient(const SharedNodePointer& node) {
+ MetavoxelClient* client = new MetavoxelClient(node);
+ _clients.insert(node->getUUID(), client);
_clientsBySessionID.insert(client->getSessionID(), client);
}
@@ -139,7 +148,7 @@ void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& s
}
MetavoxelClient* client = _clientsBySessionID.value(sessionID);
if (client) {
- client->receivedData(data, sender);
+ client->receivedData(data);
}
}
@@ -167,17 +176,15 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
}
static QByteArray createDatagramHeader(const QUuid& sessionID) {
- QByteArray header(MAX_PACKET_HEADER_BYTES, 0);
- populateTypeAndVersion(reinterpret_cast(header.data()), PACKET_TYPE_METAVOXEL_DATA);
+ QByteArray header = byteArrayWithPopluatedHeader(PacketTypeMetavoxelData);
header += sessionID.toRfc4122();
return header;
}
-MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) :
- _address(address),
+MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
+ _node(node),
_sessionID(QUuid::createUuid()),
- _sequencer(createDatagramHeader(_sessionID)),
- _data(new MetavoxelData()) {
+ _sequencer(createDatagramHeader(_sessionID)) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
@@ -188,23 +195,41 @@ MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) :
_receiveRecords.append(record);
}
-void MetavoxelClient::simulate(float deltaTime) {
+MetavoxelClient::~MetavoxelClient() {
+ // close the session
+ Bitstream& out = _sequencer.startPacket();
+ out << QVariant::fromValue(CloseSessionMessage());
+ _sequencer.endPacket();
+}
+
+void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit) {
+ // apply immediately to local tree
+ edit.apply(_data);
+
+ // start sending it out
+ _sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
+}
+
+void MetavoxelClient::simulate(float deltaTime, MetavoxelVisitor& visitor) {
Bitstream& out = _sequencer.startPacket();
ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() };
out << QVariant::fromValue(state);
_sequencer.endPacket();
+
+ _data.guide(visitor);
}
-void MetavoxelClient::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
- // save the most recent sender
- _address = sender;
-
+void MetavoxelClient::receivedData(const QByteArray& data) {
// process through sequencer
_sequencer.receivedDatagram(data);
}
void MetavoxelClient::sendData(const QByteArray& data) {
- NodeList::getInstance()->getNodeSocket().writeDatagram(data, _address.getAddress(), _address.getPort());
+ QMutexLocker locker(&_node->getMutex());
+ const HifiSockAddr* address = _node->getActiveSocket();
+ if (address) {
+ NodeList::getInstance()->getNodeSocket().writeDatagram(data, address->getAddress(), address->getPort());
+ }
}
void MetavoxelClient::readPacket(Bitstream& in) {
@@ -215,6 +240,13 @@ void MetavoxelClient::readPacket(Bitstream& in) {
// record the receipt
ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data };
_receiveRecords.append(record);
+
+ // reapply local edits
+ foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
+ if (message.data.userType() == MetavoxelEditMessage::Type) {
+ message.data.value().apply(_data);
+ }
+ }
}
void MetavoxelClient::clearReceiveRecordsBefore(int index) {
@@ -224,7 +256,7 @@ void MetavoxelClient::clearReceiveRecordsBefore(int index) {
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == MetavoxelDeltaMessage::Type) {
- readDelta(_data, _receiveRecords.first().data, in);
+ _data.readDelta(_receiveRecords.first().data, in);
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h
index 708b4d0839..0956a9d0d3 100644
--- a/interface/src/MetavoxelSystem.h
+++ b/interface/src/MetavoxelSystem.h
@@ -19,6 +19,7 @@
#include
#include
+#include
#include "renderer/ProgramObject.h"
@@ -31,10 +32,11 @@ class MetavoxelSystem : public QObject {
public:
MetavoxelSystem();
+ ~MetavoxelSystem();
void init();
- MetavoxelData& getData() { return _data; }
+ void applyEdit(const MetavoxelEditMessage& edit);
void processData(const QByteArray& data, const HifiSockAddr& sender);
@@ -48,7 +50,7 @@ public slots:
private:
- Q_INVOKABLE void addClient(const QUuid& uuid, const HifiSockAddr& address);
+ Q_INVOKABLE void addClient(const SharedNodePointer& node);
Q_INVOKABLE void removeClient(const QUuid& uuid);
Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender);
@@ -71,7 +73,6 @@ private:
static ProgramObject _program;
static int _pointScaleLocation;
- MetavoxelData _data;
QVector _points;
PointVisitor _pointVisitor;
QOpenGLBuffer _buffer;
@@ -86,13 +87,16 @@ class MetavoxelClient : public QObject {
public:
- MetavoxelClient(const HifiSockAddr& address);
+ MetavoxelClient(const SharedNodePointer& node);
+ virtual ~MetavoxelClient();
const QUuid& getSessionID() const { return _sessionID; }
- void simulate(float deltaTime);
+ void applyEdit(const MetavoxelEditMessage& edit);
- void receivedData(const QByteArray& data, const HifiSockAddr& sender);
+ void simulate(float deltaTime, MetavoxelVisitor& visitor);
+
+ void receivedData(const QByteArray& data);
private slots:
@@ -109,15 +113,15 @@ private:
class ReceiveRecord {
public:
int packetNumber;
- MetavoxelDataPointer data;
+ MetavoxelData data;
};
- HifiSockAddr _address;
+ SharedNodePointer _node;
QUuid _sessionID;
DatagramSequencer _sequencer;
- MetavoxelDataPointer _data;
+ MetavoxelData _data;
QList _receiveRecords;
};
diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp
index aa4978891f..d96ac2beaa 100644
--- a/interface/src/ParticleTreeRenderer.cpp
+++ b/interface/src/ParticleTreeRenderer.cpp
@@ -7,6 +7,8 @@
//
//
+#include
+
#include "InterfaceConfig.h"
#include "ParticleTreeRenderer.h"
@@ -16,17 +18,47 @@ ParticleTreeRenderer::ParticleTreeRenderer() :
}
ParticleTreeRenderer::~ParticleTreeRenderer() {
+ // delete the models in _particleModels
+ foreach(Model* model, _particleModels) {
+ delete model;
+ }
+ _particleModels.clear();
}
+void ParticleTreeRenderer::init() {
+ OctreeRenderer::init();
+}
+
+
void ParticleTreeRenderer::update() {
if (_tree) {
- ParticleTree* tree = (ParticleTree*)_tree;
- _tree->lockForWrite();
- tree->update();
- _tree->unlock();
+ ParticleTree* tree = static_cast(_tree);
+ if (tree->tryLockForWrite()) {
+ tree->update();
+ tree->unlock();
+ }
}
}
+void ParticleTreeRenderer::render() {
+ OctreeRenderer::render();
+}
+
+Model* ParticleTreeRenderer::getModel(const QString& url) {
+ Model* model = NULL;
+
+ // if we don't already have this model then create it and initialize it
+ if (_particleModels.find(url) == _particleModels.end()) {
+ model = new Model();
+ model->init();
+ model->setURL(QUrl(url));
+ _particleModels[url] = model;
+ } else {
+ model = _particleModels[url];
+ }
+ return model;
+}
+
void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
// actually render it here...
// we need to iterate the actual particles of the element
@@ -36,27 +68,48 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
uint16_t numberOfParticles = particles.size();
- bool drawAsSphere = true;
-
for (uint16_t i = 0; i < numberOfParticles; i++) {
const Particle& particle = particles[i];
// render particle aspoints
glm::vec3 position = particle.getPosition() * (float)TREE_SCALE;
glColor3ub(particle.getColor()[RED_INDEX],particle.getColor()[GREEN_INDEX],particle.getColor()[BLUE_INDEX]);
- float sphereRadius = particle.getRadius() * (float)TREE_SCALE;
+ float radius = particle.getRadius() * (float)TREE_SCALE;
+
+ bool drawAsModel = particle.hasModel();
args->_renderedItems++;
- if (drawAsSphere) {
+ if (drawAsModel) {
glPushMatrix();
- glTranslatef(position.x, position.y, position.z);
- glutSolidSphere(sphereRadius, 15, 15);
+ const float alpha = 1.0f;
+
+ Model* model = getModel(particle.getModelURL());
+
+ glm::vec3 translationAdjustment = particle.getModelTranslation();
+
+ // set the position
+ glm::vec3 translation(position.x, position.y, position.z);
+ model->setTranslation(translation + translationAdjustment);
+
+ // set the rotation
+ glm::quat rotation = particle.getModelRotation();
+ model->setRotation(rotation);
+
+ // scale
+ // TODO: need to figure out correct scale adjust, this was arbitrarily set to make a couple models work
+ const float MODEL_SCALE = 0.00575f;
+ glm::vec3 scale(1.0f,1.0f,1.0f);
+ model->setScale(scale * MODEL_SCALE * radius * particle.getModelScale());
+
+ model->simulate(0.0f);
+ model->render(alpha); // TODO: should we allow particles to have alpha on their models?
+
glPopMatrix();
} else {
- glPointSize(sphereRadius);
- glBegin(GL_POINTS);
- glVertex3f(position.x, position.y, position.z);
- glEnd();
+ glPushMatrix();
+ glTranslatef(position.x, position.y, position.z);
+ glutSolidSphere(radius, 15, 15);
+ glPopMatrix();
}
}
}
diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/ParticleTreeRenderer.h
index 1c76cf623d..48b15f9c67 100644
--- a/interface/src/ParticleTreeRenderer.h
+++ b/interface/src/ParticleTreeRenderer.h
@@ -20,6 +20,7 @@
#include
#include
#include
+#include "renderer/Model.h"
// Generic client side Octree renderer class.
class ParticleTreeRenderer : public OctreeRenderer {
@@ -28,9 +29,9 @@ public:
virtual ~ParticleTreeRenderer();
virtual Octree* createTree() { return new ParticleTree(true); }
- virtual NODE_TYPE getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }
- virtual PACKET_TYPE getMyQueryMessageType() const { return PACKET_TYPE_PARTICLE_QUERY; }
- virtual PACKET_TYPE getExpectedPacketType() const { return PACKET_TYPE_PARTICLE_DATA; }
+ virtual NodeType_t getMyNodeType() const { return NodeType::ParticleServer; }
+ virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; }
+ virtual PacketType getExpectedPacketType() const { return PacketTypeParticleData; }
virtual void renderElement(OctreeElement* element, RenderArgs* args);
void update();
@@ -39,7 +40,13 @@ public:
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
+ virtual void init();
+ virtual void render();
+
protected:
+ Model* getModel(const QString& url);
+
+ QMap _particleModels;
};
#endif /* defined(__hifi__ParticleTreeRenderer__) */
\ No newline at end of file
diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp
index a2e518b2d7..ef7e049d75 100644
--- a/interface/src/Util.cpp
+++ b/interface/src/Util.cpp
@@ -105,34 +105,6 @@ glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
return glm::angleAxis(angle, axis);
}
-// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
-// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
-// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
-glm::vec3 safeEulerAngles(const glm::quat& q) {
- float sy = 2.0f * (q.y * q.w - q.x * q.z);
- if (sy < 1.0f - EPSILON) {
- if (sy > -1.0f + EPSILON) {
- return glm::degrees(glm::vec3(
- atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)),
- asinf(sy),
- atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))));
-
- } else {
- // not a unique solution; x + z = atan2(-m21, m11)
- return glm::degrees(glm::vec3(
- 0.0f,
- PIf * -0.5f,
- atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))));
- }
- } else {
- // not a unique solution; x - z = atan2(-m21, m11)
- return glm::degrees(glm::vec3(
- 0.0f,
- PIf * 0.5f,
- -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))));
- }
-}
-
// Safe version of glm::mix; based on the code in Nick Bobick's article,
// http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde,
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
diff --git a/interface/src/Util.h b/interface/src/Util.h
index 2a812120f0..09d1fa0484 100644
--- a/interface/src/Util.h
+++ b/interface/src/Util.h
@@ -54,8 +54,6 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
-glm::vec3 safeEulerAngles(const glm::quat& q);
-
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
glm::vec3 extractTranslation(const glm::mat4& matrix);
diff --git a/interface/src/VoxelHideShowThread.cpp b/interface/src/VoxelHideShowThread.cpp
index 2a42db7f70..d7a25b4b6d 100644
--- a/interface/src/VoxelHideShowThread.cpp
+++ b/interface/src/VoxelHideShowThread.cpp
@@ -21,15 +21,15 @@ VoxelHideShowThread::VoxelHideShowThread(VoxelSystem* theSystem) :
}
bool VoxelHideShowThread::process() {
- const uint64_t MSECS_TO_USECS = 1000;
- const uint64_t SECS_TO_USECS = 1000 * MSECS_TO_USECS;
- const uint64_t FRAME_RATE = 60;
- const uint64_t USECS_PER_FRAME = SECS_TO_USECS / FRAME_RATE; // every 60fps
+ const quint64 MSECS_TO_USECS = 1000;
+ const quint64 SECS_TO_USECS = 1000 * MSECS_TO_USECS;
+ const quint64 FRAME_RATE = 60;
+ const quint64 USECS_PER_FRAME = SECS_TO_USECS / FRAME_RATE; // every 60fps
- uint64_t start = usecTimestampNow();
+ quint64 start = usecTimestampNow();
_theSystem->checkForCulling();
- uint64_t end = usecTimestampNow();
- uint64_t elapsed = end - start;
+ quint64 end = usecTimestampNow();
+ quint64 elapsed = end - start;
bool showExtraDebugging = Application::getInstance()->getLogger()->extraDebugging();
if (showExtraDebugging && elapsed > USECS_PER_FRAME) {
@@ -38,7 +38,7 @@ bool VoxelHideShowThread::process() {
if (isStillRunning()) {
if (elapsed < USECS_PER_FRAME) {
- uint64_t sleepFor = USECS_PER_FRAME - elapsed;
+ quint64 sleepFor = USECS_PER_FRAME - elapsed;
usleep(sleepFor);
}
}
diff --git a/interface/src/VoxelImporter.cpp b/interface/src/VoxelImporter.cpp
index 49b28c850f..653d04cee4 100644
--- a/interface/src/VoxelImporter.cpp
+++ b/interface/src/VoxelImporter.cpp
@@ -24,15 +24,15 @@ private:
const QString SETTINGS_GROUP_NAME = "VoxelImport";
const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
-VoxelImporter::VoxelImporter(QWidget* parent)
- : QObject(parent),
- _voxelTree(true),
- _importDialog(parent),
- _currentTask(NULL),
- _nextTask(NULL) {
-
- connect(&_importDialog, SIGNAL(currentChanged(QString)), SLOT(preImport()));
- connect(&_importDialog, SIGNAL(accepted()), SLOT(import()));
+VoxelImporter::VoxelImporter(QWidget* parent) :
+ QObject(parent),
+ _voxelTree(true),
+ _importDialog(parent),
+ _currentTask(NULL),
+ _nextTask(NULL)
+{
+ connect(&_importDialog, &QFileDialog::currentChanged, this, &VoxelImporter::preImport);
+ connect(&_importDialog, &QFileDialog::accepted, this, &VoxelImporter::import);
}
void VoxelImporter::saveSettings(QSettings* settings) {
@@ -102,7 +102,22 @@ int VoxelImporter::preImport() {
if (!QFileInfo(filename).isFile()) {
return 0;
}
-
+
+ _filename = filename;
+
+ if (_nextTask) {
+ delete _nextTask;
+ }
+
+ _nextTask = new ImportTask(_filename);
+ connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
+
+ if (_currentTask != NULL) {
+ _voxelTree.cancelImport();
+ } else {
+ launchTask();
+ }
+
return 1;
}
diff --git a/interface/src/VoxelPacketProcessor.cpp b/interface/src/VoxelPacketProcessor.cpp
index 6f51dc041d..bdb934430c 100644
--- a/interface/src/VoxelPacketProcessor.cpp
+++ b/interface/src/VoxelPacketProcessor.cpp
@@ -14,16 +14,18 @@
#include "Menu.h"
#include "VoxelPacketProcessor.h"
-void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, unsigned char* packetData, ssize_t packetLength) {
+void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelPacketProcessor::processPacket()");
+
+ QByteArray mutablePacket = packet;
const int WAY_BEHIND = 300;
if (packetsToProcessCount() > WAY_BEHIND && Application::getInstance()->getLogger()->extraDebugging()) {
qDebug("VoxelPacketProcessor::processPacket() packets to process=%d", packetsToProcessCount());
}
- ssize_t messageLength = packetLength;
+ ssize_t messageLength = mutablePacket.size();
Application* app = Application::getInstance();
bool wasStatsPacket = false;
@@ -34,18 +36,19 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
app->_voxels.killLocalVoxels();
app->_wantToKillLocalVoxels = false;
}
+
+ PacketType voxelPacketType = packetTypeForPacket(mutablePacket);
- // note: PACKET_TYPE_OCTREE_STATS can have PACKET_TYPE_VOXEL_DATA
- // immediately following them inside the same packet. So, we process the PACKET_TYPE_OCTREE_STATS first
+ // note: PacketType_OCTREE_STATS can have PacketType_VOXEL_DATA
+ // immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first
// then process any remaining bytes as if it was another packet
- if (packetData[0] == PACKET_TYPE_OCTREE_STATS) {
+ if (voxelPacketType == PacketTypeOctreeStats) {
- int statsMessageLength = app->parseOctreeStats(packetData, messageLength, senderSockAddr);
+ int statsMessageLength = app->parseOctreeStats(mutablePacket, senderSockAddr);
wasStatsPacket = true;
if (messageLength > statsMessageLength) {
- packetData += statsMessageLength;
- messageLength -= statsMessageLength;
- if (!packetVersionMatch(packetData, senderSockAddr)) {
+ mutablePacket = mutablePacket.mid(statsMessageLength);
+ if (!packetVersionMatch(packet)) {
return; // bail since piggyback data doesn't match our versioning
}
} else {
@@ -53,31 +56,31 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
return; // bail since no piggyback data
}
} // fall through to piggyback message
-
+
+ voxelPacketType = packetTypeForPacket(mutablePacket);
+
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
- app->trackIncomingVoxelPacket(packetData, messageLength, senderSockAddr, wasStatsPacket);
+ app->trackIncomingVoxelPacket(mutablePacket, senderSockAddr, wasStatsPacket);
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) {
- switch(packetData[0]) {
- case PACKET_TYPE_PARTICLE_ERASE: {
- app->_particles.processEraseMessage(QByteArray((char*) packetData, messageLength),
- senderSockAddr, serverNode.data());
+ switch(voxelPacketType) {
+ case PacketTypeParticleErase: {
+ app->_particles.processEraseMessage(mutablePacket, senderSockAddr, serverNode.data());
} break;
- case PACKET_TYPE_PARTICLE_DATA: {
- app->_particles.processDatagram(QByteArray((char*) packetData, messageLength),
- senderSockAddr, serverNode.data());
+ case PacketTypeParticleData: {
+ app->_particles.processDatagram(mutablePacket, senderSockAddr, serverNode.data());
} break;
- case PACKET_TYPE_ENVIRONMENT_DATA: {
- app->_environment.parseData(senderSockAddr, packetData, messageLength);
+ case PacketTypeEnvironmentData: {
+ app->_environment.parseData(senderSockAddr, mutablePacket);
} break;
default : {
app->_voxels.setDataSourceUUID(serverNode->getUUID());
- app->_voxels.parseData(packetData, messageLength);
+ app->_voxels.parseData(mutablePacket);
app->_voxels.setDataSourceUUID(QUuid());
} break;
}
diff --git a/interface/src/VoxelPacketProcessor.h b/interface/src/VoxelPacketProcessor.h
index e8e77e6895..e0c3e9f542 100644
--- a/interface/src/VoxelPacketProcessor.h
+++ b/interface/src/VoxelPacketProcessor.h
@@ -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, unsigned char* packetData, ssize_t packetLength);
+ virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
};
#endif // __shared__VoxelPacketProcessor__
diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp
index c5071570d5..3297750fb9 100644
--- a/interface/src/VoxelSystem.cpp
+++ b/interface/src/VoxelSystem.cpp
@@ -16,7 +16,6 @@
#include
#include
#include
-#include
#include "Application.h"
#include "CoverageMap.h"
@@ -550,17 +549,17 @@ bool VoxelSystem::readFromSchematicFile(const char* filename) {
return result;
}
-int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
+int VoxelSystem::parseData(const QByteArray& packet) {
bool showTimingDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData()",showTimingDetails);
- unsigned char command = *sourceBuffer;
- int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
+ PacketType command = packetTypeForPacket(packet);
+ int numBytesPacketHeader = numBytesForPacketHeader(packet);
switch(command) {
- case PACKET_TYPE_VOXEL_DATA: {
- PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() PACKET_TYPE_VOXEL_DATA part...",showTimingDetails);
-
- unsigned char* dataAt = sourceBuffer + numBytesPacketHeader;
+ case PacketTypeVoxelData: {
+ PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() PacketType_VOXEL_DATA part...",showTimingDetails);
+
+ const unsigned char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader;
VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt));
dataAt += sizeof(VOXEL_PACKET_FLAGS);
@@ -577,7 +576,7 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
int flightTime = arrivedAt - sentAt;
VOXEL_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0;
- int dataBytes = numBytes - VOXEL_PACKET_HEADER_SIZE;
+ int dataBytes = packet.size() - VOXEL_PACKET_HEADER_SIZE;
int subsection = 1;
while (dataBytes > 0) {
@@ -605,7 +604,8 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
" color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d"
" subsection:%d sectionLength:%d uncompressed:%d",
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
- sequence, flightTime, numBytes, dataBytes, subsection, sectionLength, packetData.getUncompressedSize());
+ sequence, flightTime, packet.size(), dataBytes, subsection, sectionLength,
+ packetData.getUncompressedSize());
}
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
unlockTree();
@@ -616,7 +616,8 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
}
subsection++;
}
- break;
+ default:
+ break;
}
if (!_useFastVoxelPipeline || _writeRenderFullVBO) {
setupNewVoxelsForDrawing();
@@ -624,9 +625,9 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY);
}
- Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(numBytes);
+ Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(packet.size());
- return numBytes;
+ return packet.size();
}
void VoxelSystem::setupNewVoxelsForDrawing() {
@@ -637,8 +638,8 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
return; // bail early if we're not initialized
}
- uint64_t start = usecTimestampNow();
- uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
+ quint64 start = usecTimestampNow();
+ quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) {
@@ -684,7 +685,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
_bufferWriteLock.unlock();
- uint64_t end = usecTimestampNow();
+ quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
@@ -701,8 +702,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode() xxxxx");
- uint64_t start = usecTimestampNow();
- uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
+ quint64 start = usecTimestampNow();
+ quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (allowBailEarly && !iAmDebugging &&
@@ -727,7 +728,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_bufferWriteLock.unlock();
- uint64_t end = usecTimestampNow();
+ quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
@@ -736,14 +737,14 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
- uint64_t start = usecTimestampNow();
+ quint64 start = usecTimestampNow();
// track how long its been since we were last moving. If we have recently moved then only use delta frustums, if
// it's been a long time since we last moved, then go ahead and do a full frustum cull.
if (isViewChanging()) {
_lastViewIsChanging = start;
}
- uint64_t sinceLastMoving = (start - _lastViewIsChanging) / 1000;
+ quint64 sinceLastMoving = (start - _lastViewIsChanging) / 1000;
bool enoughTime = (sinceLastMoving >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS));
// These has changed events will occur before we stop. So we need to remember this for when we finally have stopped
@@ -765,7 +766,7 @@ void VoxelSystem::checkForCulling() {
hideOutOfView(forceFullFrustum);
if (forceFullFrustum) {
- uint64_t endViewCulling = usecTimestampNow();
+ quint64 endViewCulling = usecTimestampNow();
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
}
@@ -774,7 +775,7 @@ void VoxelSystem::checkForCulling() {
// VBO reubuilding. Possibly we should do this only if our actual VBO usage crosses some lower boundary.
cleanupRemovedVoxels();
- uint64_t sinceLastAudit = (start - _lastAudit) / 1000;
+ quint64 sinceLastAudit = (start - _lastAudit) / 1000;
if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) {
if (sinceLastAudit >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) {
@@ -1590,7 +1591,7 @@ void VoxelSystem::falseColorizeBySource() {
// create a bunch of colors we'll use during colorization
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
uint16_t nodeID = VoxelTreeElement::getSourceNodeUUIDKey(node->getUUID());
int groupColor = voxelServerCount % NUMBER_OF_COLOR_GROUPS;
args.colors[nodeID] = groupColors[groupColor];
@@ -2662,7 +2663,7 @@ void VoxelSystem::falseColorizeOccludedV2() {
}
void VoxelSystem::nodeAdded(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
qDebug("VoxelSystem... voxel server %s added...", node->getUUID().toString().toLocal8Bit().constData());
_voxelServerCount++;
}
@@ -2683,7 +2684,7 @@ bool VoxelSystem::killSourceVoxelsOperation(OctreeElement* element, void* extraD
}
void VoxelSystem::nodeKilled(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
_voxelServerCount--;
QUuid nodeUUID = node->getUUID();
qDebug("VoxelSystem... voxel server %s removed...", nodeUUID.toString().toLocal8Bit().constData());
diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h
index 728106a922..ca90424b70 100644
--- a/interface/src/VoxelSystem.h
+++ b/interface/src/VoxelSystem.h
@@ -49,7 +49,7 @@ public:
void setDataSourceUUID(const QUuid& dataSourceUUID) { _dataSourceUUID = dataSourceUUID; }
const QUuid& getDataSourceUUID() const { return _dataSourceUUID; }
- int parseData(unsigned char* sourceBuffer, int numBytes);
+ int parseData(const QByteArray& packet);
virtual void init();
void simulate(float deltaTime) { }
@@ -229,10 +229,10 @@ private:
bool _readRenderFullVBO;
int _setupNewVoxelsForDrawingLastElapsed;
- uint64_t _setupNewVoxelsForDrawingLastFinished;
- uint64_t _lastViewCulling;
- uint64_t _lastViewIsChanging;
- uint64_t _lastAudit;
+ quint64 _setupNewVoxelsForDrawingLastFinished;
+ quint64 _lastViewCulling;
+ quint64 _lastViewIsChanging;
+ quint64 _lastAudit;
int _lastViewCullingElapsed;
bool _hasRecentlyChanged;
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 5e41619997..4b4c189729 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -12,7 +12,6 @@
#include
#include
-#include
#include
#include
@@ -73,7 +72,6 @@ Avatar::Avatar() :
_worldUpDirection(DEFAULT_UP_DIRECTION),
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
_mouseRayDirection(0.0f, 0.0f, 0.0f),
- _isCollisionsOn(true),
_moving(false),
_owningAvatarMixer(),
_initialized(false)
@@ -108,7 +106,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
return computeRotationFromBodyToWorldUp() * getOrientation();
}
-void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
+void Avatar::simulate(float deltaTime) {
if (_scale != _targetScale) {
setScale(_targetScale);
}
@@ -336,11 +334,11 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi
return false;
}
-int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) {
+int Avatar::parseData(const QByteArray& packet) {
// change in position implies movement
glm::vec3 oldPosition = _position;
- int bytesRead = AvatarData::parseData(sourceBuffer, numBytes);
+ int bytesRead = AvatarData::parseData(packet);
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
@@ -396,30 +394,6 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
glEnd();
}
-void Avatar::goHome() {
- qDebug("Going Home!");
- setPosition(START_LOCATION);
-}
-
-void Avatar::increaseSize() {
- if ((1.f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
- _targetScale *= (1.f + SCALING_RATIO);
- qDebug("Changed scale to %f", _targetScale);
- }
-}
-
-void Avatar::decreaseSize() {
- if (MIN_AVATAR_SCALE < (1.f - SCALING_RATIO) * _targetScale) {
- _targetScale *= (1.f - SCALING_RATIO);
- qDebug("Changed scale to %f", _targetScale);
- }
-}
-
-void Avatar::resetSize() {
- _targetScale = 1.0f;
- qDebug("Reseted scale to %f", _targetScale);
-}
-
void Avatar::setScale(float scale) {
_scale = scale;
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 10a3760f6e..61b36706d2 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -20,7 +20,6 @@
#include "InterfaceConfig.h"
#include "SkeletonModel.h"
#include "world.h"
-#include "devices/Transmitter.h"
static const float SCALING_RATIO = .05f;
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
@@ -73,7 +72,7 @@ public:
~Avatar();
void init();
- void simulate(float deltaTime, Transmitter* transmitter);
+ void simulate(float deltaTime);
void render(bool forceRenderHead);
//setters
@@ -114,20 +113,10 @@ public:
virtual bool isMyAvatar() { return false; }
- int parseData(unsigned char* sourceBuffer, int numBytes);
+ int parseData(const QByteArray& packet);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
-public slots:
- void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
- void goHome();
- void increaseSize();
- void decreaseSize();
- void resetSize();
-
- friend class MyAvatar;
-
-
protected:
Head _head;
Hand _hand;
@@ -142,7 +131,6 @@ protected:
glm::vec3 _worldUpDirection;
glm::vec3 _mouseRayOrigin;
glm::vec3 _mouseRayDirection;
- bool _isCollisionsOn;
float _stringLength;
bool _moving; ///< set when position is changing
QWeakPointer _owningAvatarMixer;
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index dd1686a0d2..d98d30cf6a 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -16,65 +16,49 @@
#include "AvatarManager.h"
+// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
+const QUuid MY_AVATAR_KEY; // NULL key
+
AvatarManager::AvatarManager(QObject* parent) :
- _lookAtTargetAvatar(),
- _lookAtOtherPosition(),
- _lookAtIndicatorScale(1.0f),
- _avatarHash(),
- _avatarFades()
-{
+ _avatarFades() {
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
qRegisterMetaType >("NodeWeakPointer");
+ _myAvatar = QSharedPointer(new MyAvatar());
}
-void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
- bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
- PerformanceWarning warn(showWarnings, "Application::updateLookatTargetAvatar()");
-
- Application* applicationInstance = Application::getInstance();
-
- if (!applicationInstance->isMousePressed()) {
- foreach (const AvatarSharedPointer& avatar, _avatarHash) {
- float distance;
-
- if (avatar->findRayIntersection(applicationInstance->getMouseRayOrigin(),
- applicationInstance->getMouseRayDirection(), distance)) {
- // rescale to compensate for head embiggening
- eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
- (avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
-
- _lookAtIndicatorScale = avatar->getHead().getScale();
- _lookAtOtherPosition = avatar->getHead().getPosition();
-
- _lookAtTargetAvatar = avatar;
-
- // found the look at target avatar, return
- return;
- }
- }
-
- _lookAtTargetAvatar.clear();
- }
+void AvatarManager::init() {
+ _myAvatar->init();
+ _myAvatar->setPosition(START_LOCATION);
+ _myAvatar->setDisplayingLookatVectors(false);
+ _avatarHash.insert(MY_AVATAR_KEY, _myAvatar);
}
-void AvatarManager::updateAvatars(float deltaTime) {
+void AvatarManager::updateOtherAvatars(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
-
+
+ Application* applicationInstance = Application::getInstance();
+ glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
+ glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
+
// simulate avatars
- AvatarHash::iterator avatar = _avatarHash.begin();
- if (avatar != _avatarHash.end()) {
- if (avatar->data()->getOwningAvatarMixer()) {
+ AvatarHash::iterator avatarIterator = _avatarHash.begin();
+ while (avatarIterator != _avatarHash.end()) {
+ Avatar* avatar = static_cast(avatarIterator.value().data());
+ if (avatar == static_cast(_myAvatar.data())) {
+ // DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
+ //updateMyAvatar(deltaTime);
+ ++avatarIterator;
+ continue;
+ }
+ if (avatar->getOwningAvatarMixer()) {
// this avatar's mixer is still around, go ahead and simulate it
- avatar->data()->simulate(deltaTime, NULL);
-
- Application* applicationInstance = Application::getInstance();
-
- avatar->data()->setMouseRay(applicationInstance->getMouseRayOrigin(),
- applicationInstance->getMouseRayDirection());
+ avatar->simulate(deltaTime);
+ avatar->setMouseRay(mouseOrigin, mouseDirection);
+ ++avatarIterator;
} else {
// the mixer that owned this avatar is gone, give it to the vector of fades and kill it
- avatar = removeAvatarAtHashIterator(avatar);
+ avatarIterator = erase(avatarIterator);
}
}
@@ -88,41 +72,44 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
}
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
+ bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
if (!selfAvatarOnly) {
-
// Render avatars of other nodes
- foreach (const AvatarSharedPointer& avatar, _avatarHash) {
+ foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
+ Avatar* avatar = static_cast(avatarPointer.data());
if (!avatar->isInitialized()) {
avatar->init();
}
- avatar->render(false);
- avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors));
+ if (avatar == static_cast(_myAvatar.data())) {
+ avatar->render(forceRenderHead);
+ } else {
+ avatar->render(false);
+ }
+ avatar->setDisplayingLookatVectors(renderLookAtVectors);
}
-
renderAvatarFades();
+ } else {
+ // just render myAvatar
+ _myAvatar->render(forceRenderHead);
+ _myAvatar->setDisplayingLookatVectors(renderLookAtVectors);
}
-
- // Render my own Avatar
- Avatar* myAvatar = Application::getInstance()->getAvatar();
- myAvatar->render(forceRenderHead);
- myAvatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors));
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
- QVector::iterator fadingAvatar = _avatarFades.begin();
+ QVector::iterator fadingIterator = _avatarFades.begin();
- while (fadingAvatar != _avatarFades.end()) {
- const float SHRINK_RATE = 0.9f;
-
- fadingAvatar->data()->setTargetScale(fadingAvatar->data()->getScale() * SHRINK_RATE);
-
- const float MIN_FADE_SCALE = 0.001f;
-
- if (fadingAvatar->data()->getTargetScale() < MIN_FADE_SCALE) {
- fadingAvatar = _avatarFades.erase(fadingAvatar);
+ const float SHRINK_RATE = 0.9f;
+ const float MIN_FADE_SCALE = 0.001f;
+
+ while (fadingIterator != _avatarFades.end()) {
+ Avatar* avatar = static_cast(fadingIterator->data());
+ avatar->setTargetScale(avatar->getScale() * SHRINK_RATE);
+ if (avatar->getTargetScale() < MIN_FADE_SCALE) {
+ fadingIterator = _avatarFades.erase(fadingIterator);
} else {
- fadingAvatar->data()->simulate(deltaTime, NULL);
+ avatar->simulate(deltaTime);
+ ++fadingIterator;
}
}
}
@@ -132,29 +119,36 @@ void AvatarManager::renderAvatarFades() {
Glower glower;
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
- fadingAvatar->render(false);
+ Avatar* avatar = static_cast(fadingAvatar.data());
+ avatar->render(false);
}
}
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(QUuid(userString));
+ AvatarSharedPointer matchingAvatar = _avatarHash.value(avatarKey);
if (matchingAvatar) {
+ Avatar* avatar = static_cast(matchingAvatar.data());
if (keyList[i] == DataServerKey::FaceMeshURL) {
qDebug() << "Changing mesh to" << valueList[i] << "for avatar with UUID"
- << uuidStringWithoutCurlyBraces(QUuid(userString));
+ << uuidStringWithoutCurlyBraces(avatarKey);
- QMetaObject::invokeMethod(&matchingAvatar->getHead().getFaceModel(),
+ 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(QString(userString));
+ << uuidStringWithoutCurlyBraces(avatarKey.toString());
- QMetaObject::invokeMethod(&matchingAvatar->getSkeletonModel(),
+ QMetaObject::invokeMethod(&(avatar->getSkeletonModel()),
"setURL", Q_ARG(QUrl, QUrl(valueList[i])));
}
}
@@ -164,15 +158,12 @@ void AvatarManager::processDataServerResponse(const QString& userString, const Q
}
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) {
- unsigned char packetData[MAX_PACKET_SIZE];
- memcpy(packetData, datagram.data(), datagram.size());
- int numBytesPacketHeader = numBytesForPacketHeader(packetData);
+ int bytesRead = numBytesForPacketHeader(datagram);
- int bytesRead = numBytesPacketHeader;
-
- unsigned char avatarData[MAX_PACKET_SIZE];
- int numBytesDummyPacketHeader = populateTypeAndVersion(avatarData, PACKET_TYPE_HEAD_DATA);
+ 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)
@@ -183,10 +174,11 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
if (!matchingAvatar) {
// construct a new Avatar for this node
- matchingAvatar = AvatarSharedPointer(new Avatar());
- matchingAvatar->setOwningAvatarMixer(mixerWeakPointer);
+ Avatar* avatar = new Avatar();
+ avatar->setOwningAvatarMixer(mixerWeakPointer);
// insert the new avatar into our hash
+ matchingAvatar = AvatarSharedPointer(avatar);
_avatarHash.insert(nodeUUID, matchingAvatar);
// new UUID requires mesh and skeleton request to data-server
@@ -197,37 +189,44 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
}
// copy the rest of the packet to the avatarData holder so we can read the next Avatar from there
- memcpy(avatarData + numBytesDummyPacketHeader, packetData + bytesRead, datagram.size() - bytesRead);
+ dummyAvatarByteArray.resize(numDummyHeaderBytesWithoutUUID);
+
+ // make this Avatar's UUID the UUID in the packet and tack the remaining data onto the end
+ dummyAvatarByteArray.append(datagram.mid(bytesRead));
// have the matching (or new) avatar parse the data from the packet
- bytesRead += matchingAvatar->parseData(avatarData, datagram.size() - bytesRead);
+ bytesRead += matchingAvatar->parseData(dummyAvatarByteArray) - numDummyHeaderBytesWithoutUUID;
}
}
void AvatarManager::processKillAvatar(const QByteArray& datagram) {
// read the node id
- QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(reinterpret_cast
- (datagram.data())),
- NUM_BYTES_RFC4122_UUID));
+ QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID));
// remove the avatar with that UUID from our hash, if it exists
AvatarHash::iterator matchedAvatar = _avatarHash.find(nodeUUID);
if (matchedAvatar != _avatarHash.end()) {
- removeAvatarAtHashIterator(matchedAvatar);
+ erase(matchedAvatar);
}
}
-AvatarHash::iterator AvatarManager::removeAvatarAtHashIterator(const AvatarHash::iterator& iterator) {
- qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
- _avatarFades.push_back(iterator.value());
- return _avatarHash.erase(iterator);
+AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
+ if (iterator.key() != MY_AVATAR_KEY) {
+ qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
+ _avatarFades.push_back(iterator.value());
+ return AvatarHashMap::erase(iterator);
+ } else {
+ // never remove _myAvatar from the list
+ AvatarHash::iterator returnIterator = iterator;
+ return ++returnIterator;
+ }
}
-void AvatarManager::clearHash() {
- // clear the AvatarManager hash - typically happens on the removal of the avatar-mixer
+void AvatarManager::clearOtherAvatars() {
+ // clear any avatars that came from an avatar-mixer
AvatarHash::iterator removeAvatar = _avatarHash.begin();
-
while (removeAvatar != _avatarHash.end()) {
- removeAvatar = removeAvatarAtHashIterator(removeAvatar);
+ removeAvatar = erase(removeAvatar);
}
-}
\ No newline at end of file
+ _myAvatar->clearLookAtTargetAvatar();
+}
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 7d0f11646d..6605f5c234 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -13,30 +13,27 @@
#include
#include
+#include
#include
#include "Avatar.h"
-typedef QSharedPointer AvatarSharedPointer;
-typedef QHash AvatarHash;
+class MyAvatar;
-class AvatarManager : public QObject, public DataServerCallbackObject {
+class AvatarManager : public QObject, public DataServerCallbackObject, public AvatarHashMap {
Q_OBJECT
public:
AvatarManager(QObject* parent = 0);
+
+ void init();
+
+ MyAvatar* getMyAvatar() { return _myAvatar.data(); }
- const AvatarHash& getAvatarHash() { return _avatarHash; }
- int size() const { return _avatarHash.size(); }
-
- Avatar* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
-
- void updateLookAtTargetAvatar(glm::vec3& eyePosition);
-
- void updateAvatars(float deltaTime);
+ void updateOtherAvatars(float deltaTime);
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
- void clearHash();
-
+ void clearOtherAvatars();
+
public slots:
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
@@ -44,17 +41,16 @@ public slots:
void processKillAvatar(const QByteArray& datagram);
private:
+ AvatarManager(const AvatarManager& other);
+
void simulateAvatarFades(float deltaTime);
void renderAvatarFades();
- AvatarHash::iterator removeAvatarAtHashIterator(const AvatarHash::iterator& iterator);
+ // virtual override
+ AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
- QWeakPointer _lookAtTargetAvatar;
- glm::vec3 _lookAtOtherPosition;
- float _lookAtIndicatorScale;
-
- AvatarHash _avatarHash;
QVector _avatarFades;
+ QSharedPointer _myAvatar;
};
#endif /* defined(__hifi__AvatarManager__) */
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index 18d6031f98..b441000cc1 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -183,7 +183,12 @@ void Hand::updateCollisions() {
glm::vec3 totalPenetration;
// check other avatars
- foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) {
+ foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
+ Avatar* avatar = static_cast(avatarPointer.data());
+ if (avatar == _owningAvatar) {
+ // don't collid with our own hands
+ continue;
+ }
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
@@ -205,9 +210,9 @@ void Hand::updateCollisions() {
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
- PALM_COLLIDE_FREQUENCY,
- PALM_COLLIDE_DURATION_MAX,
- PALM_COLLIDE_DECAY_PER_SAMPLE);
+ PALM_COLLIDE_FREQUENCY,
+ PALM_COLLIDE_DURATION_MAX,
+ PALM_COLLIDE_DECAY_PER_SAMPLE);
// If the other person's palm is in motion, move mine downward to show I was hit
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp
index 78e93996c8..2269d1d4a6 100644
--- a/interface/src/avatar/Head.cpp
+++ b/interface/src/avatar/Head.cpp
@@ -37,8 +37,6 @@ Head::Head(Avatar* owningAvatar) :
_leftEyeBlinkVelocity(0.0f),
_rightEyeBlinkVelocity(0.0f),
_timeWithoutTalking(0.0f),
- _cameraPitch(_pitch),
- _mousePitch(0.f),
_cameraYaw(_yaw),
_isCameraMoving(false),
_faceModel(this)
@@ -52,7 +50,6 @@ void Head::init() {
void Head::reset() {
_yaw = _pitch = _roll = 0.0f;
- _mousePitch = 0.0f;
_leanForward = _leanSideways = 0.0f;
_faceModel.reset();
}
@@ -186,13 +183,6 @@ void Head::setScale (float scale) {
_scale = scale;
}
-void Head::setMousePitch(float mousePitch) {
- const float MAX_PITCH = 90.0f;
- _mousePitch = glm::clamp(mousePitch, -MAX_PITCH, MAX_PITCH);
-}
-
-
-
glm::quat Head::getOrientation() const {
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
}
@@ -200,7 +190,7 @@ glm::quat Head::getOrientation() const {
glm::quat Head::getCameraOrientation () const {
Avatar* owningAvatar = static_cast(_owningAvatar);
return owningAvatar->getWorldAlignedOrientation()
- * glm::quat(glm::radians(glm::vec3(_cameraPitch + _mousePitch, _cameraYaw, 0.0f)));
+ * glm::quat(glm::radians(glm::vec3(_pitch, _cameraYaw, 0.0f)));
}
glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h
index 5d9f9d9bbd..94b8bd3dc7 100644
--- a/interface/src/avatar/Head.h
+++ b/interface/src/avatar/Head.h
@@ -46,9 +46,6 @@ public:
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
- float getMousePitch() const { return _mousePitch; }
- void setMousePitch(float mousePitch);
-
glm::quat getOrientation() const;
glm::quat getCameraOrientation () const;
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
@@ -99,8 +96,6 @@ private:
float _leftEyeBlinkVelocity;
float _rightEyeBlinkVelocity;
float _timeWithoutTalking;
- float _cameraPitch; // Used to position the camera differently from the head
- float _mousePitch;
float _cameraYaw;
bool _isCameraMoving;
FaceModel _faceModel;
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index ca7a3b863b..0bdc5e9748 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -12,15 +12,18 @@
#include
#include
-#include
#include
#include
#include "Application.h"
+#include "Audio.h"
#include "DataServerClient.h"
+#include "Environment.h"
#include "Menu.h"
#include "MyAvatar.h"
#include "Physics.h"
+#include "VoxelSystem.h"
+#include "devices/Faceshift.h"
#include "devices/OculusManager.h"
#include "ui/TextRenderer.h"
@@ -50,19 +53,32 @@ MyAvatar::MyAvatar() :
_elapsedTimeSinceCollision(0.0f),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
+ _isCollisionsOn(true),
_isThrustOn(false),
_thrustMultiplier(1.0f),
_moveTarget(0,0,0),
- _moveTargetStepCounter(0)
+ _moveTargetStepCounter(0),
+ _lookAtTargetAvatar()
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
}
+MyAvatar::~MyAvatar() {
+ _lookAtTargetAvatar.clear();
+}
+
void MyAvatar::reset() {
+ // TODO? resurrect headMouse stuff?
+ //_headMouseX = _glWidget->width() / 2;
+ //_headMouseY = _glWidget->height() / 2;
_head.reset();
_hand.reset();
+
+ setVelocity(glm::vec3(0,0,0));
+ setThrust(glm::vec3(0,0,0));
+ _transmitter.resetLevels();
}
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
@@ -70,7 +86,89 @@ void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
_moveTargetStepCounter = 0;
}
-void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
+void MyAvatar::updateTransmitter(float deltaTime) {
+ // no transmitter drive implies transmitter pick
+ if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
+ _transmitterPickStart = getChestPosition();
+ glm::vec3 direction = getOrientation() * glm::quat(glm::radians(_transmitter.getEstimatedRotation())) * IDENTITY_FRONT;
+
+ // check against voxels, avatars
+ const float MAX_PICK_DISTANCE = 100.0f;
+ float minDistance = MAX_PICK_DISTANCE;
+ VoxelDetail detail;
+ float distance;
+ BoxFace face;
+ VoxelSystem* voxels = Application::getInstance()->getVoxels();
+ if (voxels->findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) {
+ minDistance = min(minDistance, distance);
+ }
+ _transmitterPickEnd = _transmitterPickStart + direction * minDistance;
+
+ } else {
+ _transmitterPickStart = _transmitterPickEnd = glm::vec3();
+ }
+}
+
+void MyAvatar::update(float deltaTime) {
+ updateTransmitter(deltaTime);
+
+ // TODO: resurrect touch interactions between avatars
+ //// rotate body yaw for yaw received from multitouch
+ //setOrientation(getOrientation() * glm::quat(glm::vec3(0, _yawFromTouch, 0)));
+ //_yawFromTouch = 0.f;
+ //
+ //// apply pitch from touch
+ //_head.setPitch(_head.getPitch() + _pitchFromTouch);
+ //_pitchFromTouch = 0.0f;
+ //
+ //float TOUCH_YAW_SCALE = -0.25f;
+ //float TOUCH_PITCH_SCALE = -12.5f;
+ //float FIXED_TOUCH_TIMESTEP = 0.016f;
+ //_yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP);
+ //_pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP);
+
+ // Update my avatar's state from gyros
+ updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
+
+ // Update head mouse from faceshift if active
+ Faceshift* faceshift = Application::getInstance()->getFaceshift();
+ if (faceshift->isActive()) {
+ glm::vec3 headVelocity = faceshift->getHeadAngularVelocity();
+
+ // TODO? resurrect headMouse stuff?
+ //// sets how quickly head angular rotation moves the head mouse
+ //const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.f;
+ //const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.f;
+ //_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE;
+ //_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE;
+ //
+ //// Constrain head-driven mouse to edges of screen
+ //_headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width());
+ //_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
+ }
+
+ if (OculusManager::isConnected()) {
+ float yaw, pitch, roll;
+ OculusManager::getEulerAngles(yaw, pitch, roll);
+
+ _head.setYaw(yaw);
+ _head.setPitch(pitch);
+ _head.setRoll(roll);
+ }
+
+ // Get audio loudness data from audio input device
+ _head.setAudioLoudness(Application::getInstance()->getAudio()->getLastInputLoudness());
+
+ if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
+ setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
+ } else {
+ setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
+ }
+
+ simulate(deltaTime);
+}
+
+void MyAvatar::simulate(float deltaTime) {
glm::quat orientation = getOrientation();
@@ -92,7 +190,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
}
// Collect thrust forces from keyboard and devices
- updateThrust(deltaTime, transmitter);
+ updateThrust(deltaTime);
// copy velocity so we can use it later for acceleration
glm::vec3 oldVelocity = getVelocity();
@@ -238,7 +336,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
// Adjust body yaw by yaw from controller
setOrientation(glm::angleAxis(-euler.y, glm::vec3(0, 1, 0)) * getOrientation());
// Adjust head pitch from controller
- getHead().setMousePitch(getHead().getMousePitch() - euler.x);
+ getHead().setPitch(getHead().getPitch() - euler.x);
_position += _velocity * deltaTime;
@@ -284,8 +382,6 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
}
}
} else {
- _head.setPitch(_head.getMousePitch());
-
// restore rotation, lean to neutral positions
const float RESTORE_RATE = 0.05f;
_head.setYaw(glm::mix(_head.getYaw(), 0.0f, RESTORE_RATE));
@@ -427,6 +523,74 @@ void MyAvatar::render(bool forceRenderHead) {
}
}
+void MyAvatar::renderHeadMouse() const {
+ // TODO? resurrect headMouse stuff?
+ /*
+ // Display small target box at center or head mouse target that can also be used to measure LOD
+ glColor3f(1.0, 1.0, 1.0);
+ glDisable(GL_LINE_SMOOTH);
+ const int PIXEL_BOX = 16;
+ glBegin(GL_LINES);
+ glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY);
+ glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY);
+ glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2);
+ glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2);
+ glEnd();
+ glEnable(GL_LINE_SMOOTH);
+ glColor3f(1.f, 0.f, 0.f);
+ glPointSize(3.0f);
+ glDisable(GL_POINT_SMOOTH);
+ glBegin(GL_POINTS);
+ glVertex2f(_headMouseX - 1, _headMouseY + 1);
+ glEnd();
+ // If Faceshift is active, show eye pitch and yaw as separate pointer
+ if (_faceshift.isActive()) {
+ const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0;
+ int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE;
+ int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE;
+
+ glColor3f(0.0, 1.0, 1.0);
+ glDisable(GL_LINE_SMOOTH);
+ glBegin(GL_LINES);
+ glVertex2f(eyeTargetX - PIXEL_BOX/2, eyeTargetY);
+ glVertex2f(eyeTargetX + PIXEL_BOX/2, eyeTargetY);
+ glVertex2f(eyeTargetX, eyeTargetY - PIXEL_BOX/2);
+ glVertex2f(eyeTargetX, eyeTargetY + PIXEL_BOX/2);
+ glEnd();
+
+ }
+ */
+}
+
+void MyAvatar::renderTransmitterPickRay() const {
+ if (_transmitterPickStart != _transmitterPickEnd) {
+ Glower glower;
+ const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
+ glColor3fv(TRANSMITTER_PICK_COLOR);
+ glLineWidth(3.0f);
+ glBegin(GL_LINES);
+ glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z);
+ glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
+ glEnd();
+ glLineWidth(1.0f);
+
+ glPushMatrix();
+ glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
+
+ const float PICK_END_RADIUS = 0.025f;
+ glutSolidSphere(PICK_END_RADIUS, 8, 8);
+
+ glPopMatrix();
+ }
+}
+
+void MyAvatar::renderTransmitterLevels(int width, int height) const {
+ // Show hand transmitter data if detected
+ if (_transmitter.isConnected()) {
+ _transmitter.renderLevels(width, height);
+ }
+}
+
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
@@ -434,7 +598,7 @@ void MyAvatar::saveData(QSettings* settings) {
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
- settings->setValue("mousePitch", _head.getMousePitch());
+ settings->setValue("headPitch", _head.getPitch());
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
@@ -456,7 +620,7 @@ void MyAvatar::loadData(QSettings* settings) {
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
- _head.setMousePitch(loadSetting(settings, "mousePitch", 0.0f));
+ _head.setPitch(loadSetting(settings, "headPitch", 0.0f));
_position.x = loadSetting(settings, "position_x", 0.0f);
_position.y = loadSetting(settings, "position_y", 0.0f);
@@ -473,18 +637,8 @@ void MyAvatar::loadData(QSettings* settings) {
}
void MyAvatar::sendKillAvatar() {
- unsigned char packet[MAX_PACKET_SIZE];
- unsigned char* packetPosition = packet;
-
- packetPosition += populateTypeAndVersion(packetPosition, PACKET_TYPE_KILL_AVATAR);
-
- NodeList* nodeList = NodeList::getInstance();
-
- QByteArray rfcUUID = nodeList->getOwnerUUID().toRfc4122();
- memcpy(packetPosition, rfcUUID.constData(), rfcUUID.size());
- packetPosition += rfcUUID.size();
-
- nodeList->broadcastToNodes(packet, packetPosition - packet, QSet() << NODE_TYPE_AVATAR_MIXER);
+ QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
+ NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
@@ -497,12 +651,42 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
setOrientation(orientation);
// then vertically
- float oldMousePitch = _head.getMousePitch();
- _head.setMousePitch(oldMousePitch + deltaY * -ANGULAR_SCALE);
- rotation = glm::angleAxis(_head.getMousePitch() - oldMousePitch, orientation * IDENTITY_RIGHT);
+ float oldPitch = _head.getPitch();
+ _head.setPitch(oldPitch + deltaY * -ANGULAR_SCALE);
+ rotation = glm::angleAxis(_head.getPitch() - oldPitch, orientation * IDENTITY_RIGHT);
+
setPosition(position + rotation * (getPosition() - position));
}
+void MyAvatar::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
+ Application* applicationInstance = Application::getInstance();
+
+ if (!applicationInstance->isMousePressed()) {
+ glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
+ glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
+
+ foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
+ Avatar* avatar = static_cast(avatarPointer.data());
+ if (avatar == static_cast(this)) {
+ continue;
+ }
+ float distance;
+ if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
+ // rescale to compensate for head embiggening
+ eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
+ (avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
+ _lookAtTargetAvatar = avatarPointer;
+ return;
+ }
+ }
+ _lookAtTargetAvatar.clear();
+ }
+}
+
+void MyAvatar::clearLookAtTargetAvatar() {
+ _lookAtTargetAvatar.clear();
+}
+
float MyAvatar::getAbsoluteHeadYaw() const {
return glm::yaw(_head.getOrientation());
}
@@ -524,7 +708,7 @@ void MyAvatar::renderBody(bool forceRenderHead) {
_hand.render(true);
}
-void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
+void MyAvatar::updateThrust(float deltaTime) {
//
// Gather thrust information from keyboard and sensors to apply to avatar motion
//
@@ -549,7 +733,7 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_MAG * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_MAG * deltaTime;
- _head.setMousePitch(_head.getMousePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
+ _head.setPitch(_head.getPitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
@@ -572,9 +756,9 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
}
// Add thrusts from Transmitter
- if (transmitter) {
- transmitter->checkForLostTransmitter();
- glm::vec3 rotation = transmitter->getEstimatedRotation();
+ if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
+ _transmitter.checkForLostTransmitter();
+ glm::vec3 rotation = _transmitter.getEstimatedRotation();
const float TRANSMITTER_MIN_RATE = 1.f;
const float TRANSMITTER_MIN_YAW_RATE = 4.f;
const float TRANSMITTER_LATERAL_FORCE_SCALE = 5.f;
@@ -592,9 +776,9 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
if (fabs(rotation.y) > TRANSMITTER_MIN_YAW_RATE) {
_bodyYawDelta += rotation.y * TRANSMITTER_YAW_SCALE * deltaTime;
}
- if (transmitter->getTouchState()->state == 'D') {
+ if (_transmitter.getTouchState()->state == 'D') {
_thrust += TRANSMITTER_UP_FORCE_SCALE *
- (float)(transmitter->getTouchState()->y - TOUCH_POSITION_RANGE_HALF) / TOUCH_POSITION_RANGE_HALF *
+ (float)(_transmitter.getTouchState()->y - TOUCH_POSITION_RANGE_HALF) / TOUCH_POSITION_RANGE_HALF *
TRANSMITTER_LIFT_SCALE *
deltaTime *
up;
@@ -791,14 +975,15 @@ void MyAvatar::updateChatCircle(float deltaTime) {
// find all circle-enabled members and sort by distance
QVector sortedAvatars;
- foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) {
- SortedAvatar sortedAvatar;
- sortedAvatar.avatar = avatar.data();
-
- if (!sortedAvatar.avatar->isChatCirclingEnabled()) {
+ foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
+ Avatar* avatar = static_cast(avatarPointer.data());
+ if ( ! avatar->isChatCirclingEnabled() ||
+ avatar == static_cast(this)) {
continue;
}
-
+
+ SortedAvatar sortedAvatar;
+ sortedAvatar.avatar = avatar;
sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition());
sortedAvatars.append(sortedAvatar);
}
@@ -898,3 +1083,28 @@ void MyAvatar::setOrientation(const glm::quat& orientation) {
_bodyYaw = eulerAngles.y;
_bodyRoll = eulerAngles.z;
}
+
+void MyAvatar::goHome() {
+ qDebug("Going Home!");
+ setPosition(START_LOCATION);
+}
+
+void MyAvatar::increaseSize() {
+ if ((1.f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
+ _targetScale *= (1.f + SCALING_RATIO);
+ qDebug("Changed scale to %f", _targetScale);
+ }
+}
+
+void MyAvatar::decreaseSize() {
+ if (MIN_AVATAR_SCALE < (1.f - SCALING_RATIO) * _targetScale) {
+ _targetScale *= (1.f - SCALING_RATIO);
+ qDebug("Changed scale to %f", _targetScale);
+ }
+}
+
+void MyAvatar::resetSize() {
+ _targetScale = 1.0f;
+ qDebug("Reseted scale to %f", _targetScale);
+}
+
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 92afe530eb..c3ef1e4bfb 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -11,6 +11,8 @@
#include
+#include
+
#include "Avatar.h"
enum AvatarHandState
@@ -23,14 +25,23 @@ enum AvatarHandState
};
class MyAvatar : public Avatar {
+ Q_OBJECT
+
public:
MyAvatar();
+ ~MyAvatar();
void reset();
- void simulate(float deltaTime, Transmitter* transmitter);
+ void update(float deltaTime);
+ void simulate(float deltaTime);
void updateFromGyros(bool turnWithHead);
+ void updateTransmitter(float deltaTime);
+
void render(bool forceRenderHead);
void renderDebugBodyPoints();
+ void renderHeadMouse() const;
+ void renderTransmitterPickRay() const;
+ void renderTransmitterLevels(int width, int height) const;
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
@@ -39,7 +50,6 @@ public:
void setLeanScale(float scale) { _leanScale = scale; }
void setGravity(glm::vec3 gravity);
void setOrientation(const glm::quat& orientation);
- void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
void setMoveTarget(const glm::vec3 moveTarget);
// getters
@@ -51,6 +61,7 @@ public:
float getAbsoluteHeadYaw() const;
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
+ Transmitter& getTransmitter() { return _transmitter; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
@@ -73,6 +84,17 @@ public:
void orbit(const glm::vec3& position, int deltaX, int deltaY);
+ AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
+ void updateLookAtTargetAvatar(glm::vec3& eyePosition);
+ void clearLookAtTargetAvatar();
+
+public slots:
+ void goHome();
+ void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
+ void increaseSize();
+ void decreaseSize();
+ void resetSize();
+
private:
bool _mousePressed;
float _bodyPitchDelta;
@@ -86,15 +108,21 @@ private:
float _elapsedTimeSinceCollision;
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
+ bool _isCollisionsOn;
bool _isThrustOn;
float _thrustMultiplier;
float _collisionRadius;
glm::vec3 _moveTarget;
int _moveTargetStepCounter;
+ QWeakPointer _lookAtTargetAvatar;
+
+ Transmitter _transmitter; // Gets UDP data from transmitter app used to animate the avatar
+ glm::vec3 _transmitterPickStart;
+ glm::vec3 _transmitterPickEnd;
// private methods
void renderBody(bool forceRenderHead);
- void updateThrust(float deltaTime, Transmitter * transmitter);
+ void updateThrust(float deltaTime);
void updateHandMovementAndTouching(float deltaTime);
void updateAvatarCollisions(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime);
diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp
index 83105f3f41..902a0ea12a 100644
--- a/interface/src/avatar/Profile.cpp
+++ b/interface/src/avatar/Profile.cpp
@@ -53,7 +53,10 @@ void Profile::setUUID(const QUuid& 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) {
@@ -87,7 +90,7 @@ void Profile::updatePosition(const glm::vec3 position) {
if (_lastPosition != position) {
static timeval lastPositionSend = {};
- const uint64_t DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
+ const quint64 DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
const float DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS = 1;
if (usecTimestampNow() - usecTimestamp(&lastPositionSend) >= DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS &&
@@ -115,10 +118,10 @@ void Profile::updateOrientation(const glm::quat& orientation) {
if (_lastOrientation == eulerAngles) {
return;
}
- const uint64_t DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
+ const quint64 DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
const float DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES = 5.0f;
- uint64_t now = usecTimestampNow();
+ quint64 now = usecTimestampNow();
if (now - _lastOrientationSend >= DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS &&
glm::distance(_lastOrientation, eulerAngles) >= DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES) {
DataServerClient::putValueForKeyAndUserString(DataServerKey::Orientation, QString(createByteArray(eulerAngles)),
diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h
index 225022b99d..5cb2295f5b 100644
--- a/interface/src/avatar/Profile.h
+++ b/interface/src/avatar/Profile.h
@@ -57,7 +57,7 @@ private:
QString _lastDomain;
glm::vec3 _lastPosition;
glm::vec3 _lastOrientation;
- uint64_t _lastOrientationSend;
+ quint64 _lastOrientationSend;
QUrl _faceModelURL;
QUrl _skeletonModelURL;
};
diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp
index 437a3268d2..60839c362d 100644
--- a/interface/src/devices/Faceshift.cpp
+++ b/interface/src/devices/Faceshift.cpp
@@ -56,7 +56,7 @@ Faceshift::Faceshift() :
}
bool Faceshift::isActive() const {
- const uint64_t ACTIVE_TIMEOUT_USECS = 1000000;
+ const quint64 ACTIVE_TIMEOUT_USECS = 1000000;
return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS;
}
diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h
index 62eb494f42..d3981fc182 100644
--- a/interface/src/devices/Faceshift.h
+++ b/interface/src/devices/Faceshift.h
@@ -90,7 +90,7 @@ private:
bool _tcpEnabled;
int _tcpRetryCount;
bool _tracking;
- uint64_t _lastTrackingStateReceived;
+ quint64 _lastTrackingStateReceived;
glm::quat _headRotation;
glm::vec3 _headAngularVelocity;
diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h
index 863db7852f..1d8263ee30 100644
--- a/interface/src/devices/SixenseManager.h
+++ b/interface/src/devices/SixenseManager.h
@@ -25,7 +25,7 @@ public slots:
private:
- uint64_t _lastMovement;
+ quint64 _lastMovement;
};
#endif /* defined(__interface__SixenseManager__) */
diff --git a/interface/src/devices/Transmitter.cpp b/interface/src/devices/Transmitter.cpp
index 8ae6bdc405..5fca045cfd 100644
--- a/interface/src/devices/Transmitter.cpp
+++ b/interface/src/devices/Transmitter.cpp
@@ -60,7 +60,7 @@ void Transmitter::resetLevels() {
void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) {
// Packet's first byte is 'T'
- int numBytesPacketHeader = numBytesForPacketHeader(packetData);
+ int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(packetData));
const int ROTATION_MARKER_SIZE = 1; // 'R' = Rotation (clockwise about x,y,z)
const int ACCELERATION_MARKER_SIZE = 1; // 'A' = Acceleration (x,y,z)
@@ -113,7 +113,7 @@ void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) {
}
}
-void Transmitter::renderLevels(int width, int height) {
+void Transmitter::renderLevels(int width, int height) const {
char val[50];
const int LEVEL_CORNER_X = 10;
const int LEVEL_CORNER_Y = 400;
@@ -163,7 +163,5 @@ void Transmitter::renderLevels(int width, int height) {
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y - 6);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 30);
glEnd();
-
-
}
diff --git a/interface/src/devices/Transmitter.h b/interface/src/devices/Transmitter.h
index b35d7200bd..1fa392a280 100644
--- a/interface/src/devices/Transmitter.h
+++ b/interface/src/devices/Transmitter.h
@@ -29,8 +29,8 @@ public:
void render();
void checkForLostTransmitter();
void resetLevels();
- void renderLevels(int width, int height);
- bool isConnected() { return _isConnected; };
+ void renderLevels(int width, int height) const;
+ bool isConnected() const { return _isConnected; };
const glm::vec3 getLastRotationRate() const { return _lastRotationRate; };
const glm::vec3 getLastAcceleration() const { return _lastRotationRate; };
const glm::vec3 getEstimatedRotation() const { return _estimatedRotation; };
diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp
index 8a7f839b70..e2c3bfafdd 100644
--- a/interface/src/renderer/FBXReader.cpp
+++ b/interface/src/renderer/FBXReader.cpp
@@ -170,6 +170,7 @@ public:
const QByteArray& getDatum() const { return _datum; }
void pushBackToken(int token) { _pushedBackToken = token; }
+ void ungetChar(char ch) { _device->ungetChar(ch); }
private:
@@ -221,7 +222,7 @@ int Tokenizer::nextToken() {
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') {
- _device->ungetChar(ch); // read until we encounter a special character, then replace it
+ ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
@@ -257,9 +258,17 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
expectingDatum = true;
} else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) {
- node.properties.append(tokenizer.getDatum());
- expectingDatum = false;
-
+ QByteArray datum = tokenizer.getDatum();
+ if ((token = tokenizer.nextToken()) == ':') {
+ tokenizer.ungetChar(':');
+ tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN);
+ return node;
+
+ } else {
+ tokenizer.pushBackToken(token);
+ node.properties.append(datum);
+ expectingDatum = false;
+ }
} else {
tokenizer.pushBackToken(token);
return node;
@@ -377,6 +386,9 @@ glm::mat4 createMat4(const QVector& doubleVector) {
}
QVector getIntVector(const QVariantList& properties, int index) {
+ if (index >= properties.size()) {
+ return QVector();
+ }
QVector vector = properties.at(index).value >();
if (!vector.isEmpty()) {
return vector;
@@ -388,6 +400,9 @@ QVector getIntVector(const QVariantList& properties, int index) {
}
QVector getDoubleVector(const QVariantList& properties, int index) {
+ if (index >= properties.size()) {
+ return QVector();
+ }
QVector vector = properties.at(index).value >();
if (!vector.isEmpty()) {
return vector;
@@ -723,6 +738,22 @@ ExtractedMesh extractMesh(const FBXNode& object) {
return data.extracted;
}
+FBXBlendshape extractBlendshape(const FBXNode& object) {
+ FBXBlendshape blendshape;
+ foreach (const FBXNode& data, object.children) {
+ if (data.name == "Indexes") {
+ blendshape.indices = getIntVector(data.properties, 0);
+
+ } else if (data.name == "Vertices") {
+ blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0));
+
+ } else if (data.name == "Normals") {
+ blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0));
+ }
+ }
+ return blendshape;
+}
+
void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) {
glm::vec3 normal = glm::normalize(mesh.normals.at(firstIndex));
glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex));
@@ -745,6 +776,49 @@ QVector getIndices(const QVector ids, QVector modelIDs) {
return indices;
}
+typedef QPair WeightedIndex;
+
+void addBlendshapes(const ExtractedBlendshape& extracted, const QList& indices, ExtractedMesh& extractedMesh) {
+ foreach (const WeightedIndex& index, indices) {
+ extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1));
+ extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size());
+ FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first];
+ QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first];
+ for (int i = 0; i < extracted.blendshape.indices.size(); i++) {
+ int oldIndex = extracted.blendshape.indices.at(i);
+ for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex);
+ it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) {
+ QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value());
+ if (blendshapeIndex == blendshapeIndexMap.end()) {
+ blendshapeIndexMap.insert(it.value(), blendshape.indices.size());
+ blendshape.indices.append(it.value());
+ blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second);
+ blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second);
+ } else {
+ blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second;
+ blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second;
+ }
+ }
+ }
+ }
+}
+
+QString getTopModelID(const QMultiHash& parentMap,
+ const QHash& models, const QString& modelID) {
+ QString topID = modelID;
+ forever {
+ foreach (const QString& parentID, parentMap.values(topID)) {
+ if (models.contains(parentID)) {
+ topID = parentID;
+ goto outerContinue;
+ }
+ }
+ return topID;
+
+ outerContinue: ;
+ }
+}
+
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash meshes;
QVector blendshapes;
@@ -784,7 +858,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QVector jointRightFingertipIDs(jointRightFingertipNames.size());
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
- typedef QPair WeightedIndex;
QMultiHash blendshapeIndices;
for (int i = 0;; i++) {
QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
@@ -812,22 +885,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
meshes.insert(getID(object.properties), extractMesh(object));
} else { // object.properties.at(2) == "Shape"
- ExtractedBlendshape extracted = { getID(object.properties) };
-
- foreach (const FBXNode& data, object.children) {
- if (data.name == "Indexes") {
- extracted.blendshape.indices = getIntVector(data.properties, 0);
-
- } else if (data.name == "Vertices") {
- extracted.blendshape.vertices = createVec3Vector(
- getDoubleVector(data.properties, 0));
-
- } else if (data.name == "Normals") {
- extracted.blendshape.normals = createVec3Vector(
- getDoubleVector(data.properties, 0));
- }
- }
-
+ ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) };
blendshapes.append(extracted);
}
} else if (object.name == "Model") {
@@ -885,6 +943,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false;
glm::vec3 rotationMin, rotationMax;
FBXModel model = { name, -1 };
+ ExtractedMesh* mesh = NULL;
+ QVector blendshapes;
foreach (const FBXNode& subobject, object.children) {
bool properties = false;
QByteArray propertyName;
@@ -954,9 +1014,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
} else if (subobject.name == "Vertices") {
// it's a mesh as well as a model
- meshes.insert(getID(object.properties), extractMesh(object));
+ mesh = &meshes[getID(object.properties)];
+ *mesh = extractMesh(object);
+
+ } else if (subobject.name == "Shape") {
+ ExtractedBlendshape blendshape = { subobject.properties.at(0).toString(),
+ extractBlendshape(subobject) };
+ blendshapes.append(blendshape);
}
}
+
+ // add the blendshapes included in the model, if any
+ if (mesh) {
+ foreach (const ExtractedBlendshape& extracted, blendshapes) {
+ addBlendshapes(extracted, blendshapeIndices.values(extracted.id.toLatin1()), *mesh);
+ }
+ }
+
// see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html
model.translation = translation;
model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot);
@@ -1069,29 +1143,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QString blendshapeChannelID = parentMap.value(extracted.id);
QString blendshapeID = parentMap.value(blendshapeChannelID);
QString meshID = parentMap.value(blendshapeID);
- ExtractedMesh& extractedMesh = meshes[meshID];
- foreach (const WeightedIndex& index, blendshapeChannelIndices.values(blendshapeChannelID)) {
- extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1));
- extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size());
- FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first];
- QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first];
- for (int i = 0; i < extracted.blendshape.indices.size(); i++) {
- int oldIndex = extracted.blendshape.indices.at(i);
- for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex);
- it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) {
- QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value());
- if (blendshapeIndex == blendshapeIndexMap.end()) {
- blendshapeIndexMap.insert(it.value(), blendshape.indices.size());
- blendshape.indices.append(it.value());
- blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second);
- blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second);
- } else {
- blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second;
- blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second;
- }
- }
- }
- }
+ addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]);
}
// get offset transform from mapping
@@ -1106,23 +1158,30 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QVector modelIDs;
QSet remainingModels;
for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) {
+ // models with clusters must be parented to the cluster top
+ foreach (const QString& deformerID, childMap.values(model.key())) {
+ foreach (const QString& clusterID, childMap.values(deformerID)) {
+ if (!clusters.contains(clusterID)) {
+ continue;
+ }
+ QString topID = getTopModelID(parentMap, models, childMap.value(clusterID));
+ childMap.remove(parentMap.take(model.key()), model.key());
+ parentMap.insert(model.key(), topID);
+ goto outerBreak;
+ }
+ }
+ outerBreak:
+
+ // make sure the parent is in the child map
+ QString parent = parentMap.value(model.key());
+ if (!childMap.contains(parent, model.key())) {
+ childMap.insert(parent, model.key());
+ }
remainingModels.insert(model.key());
}
while (!remainingModels.isEmpty()) {
- QString top = *remainingModels.constBegin();
- forever {
- foreach (const QString& name, parentMap.values(top)) {
- if (models.contains(name)) {
- top = name;
- goto outerContinue;
- }
- }
- top = parentMap.value(top);
- break;
-
- outerContinue: ;
- }
- appendModelIDs(top, childMap, models, remainingModels, modelIDs);
+ QString topID = getTopModelID(parentMap, models, *remainingModels.constBegin());
+ appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs);
}
// convert the models to joints
diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp
index 73248b413f..63a0c51f0b 100644
--- a/interface/src/renderer/GeometryCache.cpp
+++ b/interface/src/renderer/GeometryCache.cpp
@@ -508,3 +508,17 @@ void NetworkGeometry::maybeReadModelWithMapping() {
_meshes.append(networkMesh);
}
}
+
+bool NetworkMeshPart::isTranslucent() const {
+ return diffuseTexture && diffuseTexture->isTranslucent();
+}
+
+int NetworkMesh::getTranslucentPartCount() const {
+ int count = 0;
+ foreach (const NetworkMeshPart& part, parts) {
+ if (part.isTranslucent()) {
+ count++;
+ }
+ }
+ return count;
+}
diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h
index 312d8bcd91..e65aed31d4 100644
--- a/interface/src/renderer/GeometryCache.h
+++ b/interface/src/renderer/GeometryCache.h
@@ -93,6 +93,8 @@ public:
QSharedPointer diffuseTexture;
QSharedPointer normalTexture;
+
+ bool isTranslucent() const;
};
/// The state associated with a single mesh.
@@ -103,6 +105,8 @@ public:
GLuint vertexBufferID;
QVector parts;
+
+ int getTranslucentPartCount() const;
};
#endif /* defined(__interface__GeometryCache__) */
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index c6c820a0f9..6d61b2df68 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -240,7 +240,6 @@ bool Model::render(float alpha) {
// set up blended buffer ids on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry();
- const QVector& networkMeshes = _geometry->getMeshes();
if (_blendedVertexBufferIDs.isEmpty()) {
foreach (const FBXMesh& mesh, geometry.meshes) {
GLuint id = 0;
@@ -264,191 +263,28 @@ bool Model::render(float alpha) {
glDisable(GL_COLOR_MATERIAL);
+ // render opaque meshes with alpha testing
+
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
- for (int i = 0; i < networkMeshes.size(); i++) {
- const NetworkMesh& networkMesh = networkMeshes.at(i);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
-
- const FBXMesh& mesh = geometry.meshes.at(i);
- int vertexCount = mesh.vertices.size();
-
- glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
-
- ProgramObject* program = &_program;
- ProgramObject* skinProgram = &_skinProgram;
- SkinLocations* skinLocations = &_skinLocations;
- if (!mesh.tangents.isEmpty()) {
- program = &_normalMapProgram;
- skinProgram = &_skinNormalMapProgram;
- skinLocations = &_skinNormalMapLocations;
- }
-
- const MeshState& state = _meshStates.at(i);
- ProgramObject* activeProgram = program;
- int tangentLocation = _normalMapTangentLocation;
- if (state.worldSpaceVertices.isEmpty()) {
- glPushMatrix();
- Application::getInstance()->loadTranslatedViewMatrix(_translation);
-
- if (state.clusterMatrices.size() > 1) {
- skinProgram->bind();
- glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
- (const float*)state.clusterMatrices.constData());
- int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
- mesh.texCoords.size() * sizeof(glm::vec2) +
- (mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
- skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
- skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
- offset + vertexCount * sizeof(glm::vec4), 4);
- skinProgram->enableAttributeArray(skinLocations->clusterIndices);
- skinProgram->enableAttributeArray(skinLocations->clusterWeights);
- activeProgram = skinProgram;
- tangentLocation = skinLocations->tangent;
-
- } else {
- glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
- program->bind();
- }
- } else {
- program->bind();
- }
-
- if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
- if (!mesh.tangents.isEmpty()) {
- activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
- activeProgram->enableAttributeArray(tangentLocation);
- }
- glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
- mesh.tangents.size() * sizeof(glm::vec3)));
- glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
- (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
-
- } else {
- if (!mesh.tangents.isEmpty()) {
- activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
- activeProgram->enableAttributeArray(tangentLocation);
- }
- glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
- glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
- glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
-
- if (!state.worldSpaceVertices.isEmpty()) {
- glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
- glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
- vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
-
- } else {
- _blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
- _blendedNormals.resize(_blendedVertices.size());
- memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
- memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
-
- // blend in each coefficient
- for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
- float coefficient = _blendshapeCoefficients[j];
- if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
- continue;
- }
- const float NORMAL_COEFFICIENT_SCALE = 0.01f;
- float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
- const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
- const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
- for (const int* index = mesh.blendshapes[j].indices.constData(),
- *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
- _blendedVertices[*index] += *vertex * coefficient;
- _blendedNormals[*index] += *normal * normalCoefficient;
- }
- }
-
- glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
- glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
- vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
- }
- }
- glVertexPointer(3, GL_FLOAT, 0, 0);
- glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
-
- if (!mesh.colors.isEmpty()) {
- glEnableClientState(GL_COLOR_ARRAY);
- } else {
- glColor3f(1.0f, 1.0f, 1.0f);
- }
- if (!mesh.texCoords.isEmpty()) {
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- }
-
- qint64 offset = 0;
- for (int j = 0; j < networkMesh.parts.size(); j++) {
- const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
- const FBXMeshPart& part = mesh.parts.at(j);
-
- // apply material properties
- glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
- glm::vec4 specular = glm::vec4(part.specularColor, alpha);
- glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
- glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
- glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
- glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
-
- Texture* diffuseMap = networkPart.diffuseTexture.data();
- if (mesh.isEye) {
- if (diffuseMap != NULL) {
- diffuseMap = (_dilatedTextures[i][j] =
- static_cast(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
- }
- }
- glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
- Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
-
- if (!mesh.tangents.isEmpty()) {
- glActiveTexture(GL_TEXTURE1);
- Texture* normalMap = networkPart.normalTexture.data();
- glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
- Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
- glActiveTexture(GL_TEXTURE0);
- }
-
- glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
- offset += part.quadIndices.size() * sizeof(int);
- glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
- GL_UNSIGNED_INT, (void*)offset);
- offset += part.triangleIndices.size() * sizeof(int);
- }
-
- if (!mesh.colors.isEmpty()) {
- glDisableClientState(GL_COLOR_ARRAY);
- }
- if (!mesh.texCoords.isEmpty()) {
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- }
-
- if (!mesh.tangents.isEmpty()) {
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, 0);
- glActiveTexture(GL_TEXTURE0);
-
- activeProgram->disableAttributeArray(tangentLocation);
- }
-
- if (state.worldSpaceVertices.isEmpty()) {
- if (state.clusterMatrices.size() > 1) {
- skinProgram->disableAttributeArray(skinLocations->clusterIndices);
- skinProgram->disableAttributeArray(skinLocations->clusterWeights);
- }
- glPopMatrix();
- }
- activeProgram->release();
- }
+ renderMeshes(alpha, false);
+
+ glDisable(GL_ALPHA_TEST);
+
+ // render translucent meshes afterwards, with back face culling
+
+ glEnable(GL_CULL_FACE);
+
+ renderMeshes(alpha, true);
+
+ glDisable(GL_CULL_FACE);
// deactivate vertex arrays after drawing
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- glDisable(GL_ALPHA_TEST);
-
// bind with 0 to switch back to normal operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
@@ -882,3 +718,191 @@ void Model::deleteGeometry() {
_jointStates.clear();
_meshStates.clear();
}
+
+void Model::renderMeshes(float alpha, bool translucent) {
+ const FBXGeometry& geometry = _geometry->getFBXGeometry();
+ const QVector& networkMeshes = _geometry->getMeshes();
+
+ for (int i = 0; i < networkMeshes.size(); i++) {
+ // exit early if the translucency doesn't match what we're drawing
+ const NetworkMesh& networkMesh = networkMeshes.at(i);
+ if (translucent ? (networkMesh.getTranslucentPartCount() == 0) :
+ (networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
+ continue;
+ }
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
+
+ const FBXMesh& mesh = geometry.meshes.at(i);
+ int vertexCount = mesh.vertices.size();
+
+ glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
+
+ ProgramObject* program = &_program;
+ ProgramObject* skinProgram = &_skinProgram;
+ SkinLocations* skinLocations = &_skinLocations;
+ if (!mesh.tangents.isEmpty()) {
+ program = &_normalMapProgram;
+ skinProgram = &_skinNormalMapProgram;
+ skinLocations = &_skinNormalMapLocations;
+ }
+
+ const MeshState& state = _meshStates.at(i);
+ ProgramObject* activeProgram = program;
+ int tangentLocation = _normalMapTangentLocation;
+ if (state.worldSpaceVertices.isEmpty()) {
+ glPushMatrix();
+ Application::getInstance()->loadTranslatedViewMatrix(_translation);
+
+ if (state.clusterMatrices.size() > 1) {
+ skinProgram->bind();
+ glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
+ (const float*)state.clusterMatrices.constData());
+ int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
+ mesh.texCoords.size() * sizeof(glm::vec2) +
+ (mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
+ skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
+ skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
+ offset + vertexCount * sizeof(glm::vec4), 4);
+ skinProgram->enableAttributeArray(skinLocations->clusterIndices);
+ skinProgram->enableAttributeArray(skinLocations->clusterWeights);
+ activeProgram = skinProgram;
+ tangentLocation = skinLocations->tangent;
+
+ } else {
+ glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
+ program->bind();
+ }
+ } else {
+ program->bind();
+ }
+
+ if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
+ if (!mesh.tangents.isEmpty()) {
+ activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
+ activeProgram->enableAttributeArray(tangentLocation);
+ }
+ glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
+ mesh.tangents.size() * sizeof(glm::vec3)));
+ glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
+ (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
+
+ } else {
+ if (!mesh.tangents.isEmpty()) {
+ activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
+ activeProgram->enableAttributeArray(tangentLocation);
+ }
+ glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
+ glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
+ glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
+
+ if (!state.worldSpaceVertices.isEmpty()) {
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
+ glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
+ vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
+
+ } else {
+ _blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
+ _blendedNormals.resize(_blendedVertices.size());
+ memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
+ memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
+
+ // blend in each coefficient
+ for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
+ float coefficient = _blendshapeCoefficients[j];
+ if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
+ continue;
+ }
+ const float NORMAL_COEFFICIENT_SCALE = 0.01f;
+ float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
+ const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
+ const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
+ for (const int* index = mesh.blendshapes[j].indices.constData(),
+ *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
+ _blendedVertices[*index] += *vertex * coefficient;
+ _blendedNormals[*index] += *normal * normalCoefficient;
+ }
+ }
+
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
+ glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
+ vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
+ }
+ }
+ glVertexPointer(3, GL_FLOAT, 0, 0);
+ glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
+
+ if (!mesh.colors.isEmpty()) {
+ glEnableClientState(GL_COLOR_ARRAY);
+ } else {
+ glColor3f(1.0f, 1.0f, 1.0f);
+ }
+ if (!mesh.texCoords.isEmpty()) {
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ }
+
+ qint64 offset = 0;
+ for (int j = 0; j < networkMesh.parts.size(); j++) {
+ const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
+ if (networkPart.isTranslucent() != translucent) {
+ continue;
+ }
+ const FBXMeshPart& part = mesh.parts.at(j);
+
+ // apply material properties
+ glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
+ glm::vec4 specular = glm::vec4(part.specularColor, alpha);
+ glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
+ glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
+ glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
+ glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
+
+ Texture* diffuseMap = networkPart.diffuseTexture.data();
+ if (mesh.isEye) {
+ if (diffuseMap != NULL) {
+ diffuseMap = (_dilatedTextures[i][j] =
+ static_cast(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
+ }
+ }
+ glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
+ Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
+
+ if (!mesh.tangents.isEmpty()) {
+ glActiveTexture(GL_TEXTURE1);
+ Texture* normalMap = networkPart.normalTexture.data();
+ glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
+ Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
+ glActiveTexture(GL_TEXTURE0);
+ }
+
+ glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
+ offset += part.quadIndices.size() * sizeof(int);
+ glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
+ GL_UNSIGNED_INT, (void*)offset);
+ offset += part.triangleIndices.size() * sizeof(int);
+ }
+
+ if (!mesh.colors.isEmpty()) {
+ glDisableClientState(GL_COLOR_ARRAY);
+ }
+ if (!mesh.texCoords.isEmpty()) {
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ }
+
+ if (!mesh.tangents.isEmpty()) {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glActiveTexture(GL_TEXTURE0);
+
+ activeProgram->disableAttributeArray(tangentLocation);
+ }
+
+ if (state.worldSpaceVertices.isEmpty()) {
+ if (state.clusterMatrices.size() > 1) {
+ skinProgram->disableAttributeArray(skinLocations->clusterIndices);
+ skinProgram->disableAttributeArray(skinLocations->clusterWeights);
+ }
+ glPopMatrix();
+ }
+ activeProgram->release();
+ }
+}
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 3e8cd2be9c..389020d1b1 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -214,6 +214,7 @@ protected:
private:
void deleteGeometry();
+ void renderMeshes(float alpha, bool translucent);
float _pupilDilation;
std::vector