Merging upstream
11
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:
|
||||
<pre>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</pre>
|
||||
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
|
||||
-----
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include <EnvironmentData.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <OctalCode.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <JurisdictionListener.h>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>() << 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();
|
||||
|
|
|
@ -15,13 +15,19 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <ParticleTree.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
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__) */
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include <Logging.h>
|
||||
#include <NodeList.h>
|
||||
#include <Node.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StdDev.h>
|
||||
|
@ -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<char*>(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());
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
const std::vector<PositionalAudioRingBuffer*> 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:
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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&);
|
||||
|
|
|
@ -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>() << 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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ClientStateMessage>();
|
||||
_position = state.position;
|
||||
|
||||
} else if (userType == MetavoxelDeltaMessage::Type) {
|
||||
} else if (userType == MetavoxelEditMessage::Type) {
|
||||
_server->applyEdit(message.value<MetavoxelEditMessage>());
|
||||
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
handleMessage(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
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<QUuid, MetavoxelSession*> _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;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <HifiSockAddr.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(packetData), numSendPacketBytes,
|
||||
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
|
||||
|
||||
_socket.writeDatagram(sendPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define __hifi__DataServer__
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtNetwork/QUdpSocket>
|
||||
|
||||
#include <hiredis.h>
|
||||
|
@ -22,6 +23,7 @@ public:
|
|||
private:
|
||||
QUdpSocket _socket;
|
||||
redisContext* _redis;
|
||||
QUuid _uuid;
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
};
|
||||
|
|
|
@ -9,17 +9,16 @@
|
|||
#ifndef __hifi__DomainServer__
|
||||
#define __hifi__DomainServer__
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000;
|
||||
typedef QSharedPointer<Assignment> 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<Assignment::Type>& excludedTypes);
|
||||
void readConfigFile(const QString& path, QSet<Assignment::Type>& 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<Assignment::Type>& 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<Assignment*> _assignmentQueue;
|
||||
|
||||
QFile _staticAssignmentFile;
|
||||
uchar* _staticAssignmentFileData;
|
||||
|
||||
Assignment* _staticAssignments;
|
||||
|
||||
const char* _voxelServerConfig;
|
||||
const char* _particleServerConfig;
|
||||
const char* _metavoxelServerConfig;
|
||||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _assignmentQueue;
|
||||
|
||||
bool _hasCompletedRestartHold;
|
||||
private slots:
|
||||
void readAvailableDatagrams();
|
||||
void addStaticAssignmentsBackToQueueAfterRestart();
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__DomainServer__) */
|
||||
|
|
|
@ -62,4 +62,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
|
@ -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");
|
||||
|
|
202
examples/controllerExample.js
Normal file
|
@ -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 <space> 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);
|
|
@ -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);
|
||||
|
|
|
@ -69,4 +69,4 @@ function checkSticks() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(checkSticks);
|
||||
Script.willSendVisualDataCallback.connect(checkSticks);
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
Script.willSendVisualDataCallback.connect(makeFountain);
|
133
examples/gameoflife.js
Normal file
|
@ -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");
|
26
examples/globalCollisionsExample.js
Normal file
|
@ -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);
|
|
@ -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);
|
||||
|
|
74
examples/lookWithMouse.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// 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 isMouseDown = false;
|
||||
var lastX = 0;
|
||||
var lastY = 0;
|
||||
var yawFromMouse = 0;
|
||||
var pitchFromMouse = 0;
|
||||
|
||||
function mousePressEvent(event) {
|
||||
print("mousePressEvent event.x,y=" + event.x + ", " + event.y);
|
||||
isMouseDown = true;
|
||||
lastX = event.x;
|
||||
lastY = event.y;
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y);
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
|
||||
|
||||
if (isMouseDown) {
|
||||
print("isMouseDown... attempting to change pitch...");
|
||||
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
|
||||
MyAvatar.orientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
|
||||
yawFromMouse = 0;
|
||||
|
||||
// apply pitch from mouse
|
||||
MyAvatar.headPitch = MyAvatar.headPitch + pitchFromMouse;
|
||||
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);
|
|
@ -41,4 +41,4 @@ function moveVoxel() {
|
|||
|
||||
Voxels.setPacketsPerSecond(300);
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(moveVoxel);
|
||||
Script.willSendVisualDataCallback.connect(moveVoxel);
|
|
@ -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);
|
||||
|
|
|
@ -95,7 +95,7 @@ function moveBird() {
|
|||
var nowTimeInSeconds = new Date().getTime() / 1000;
|
||||
if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) {
|
||||
print("our bird is dying, stop our script");
|
||||
Agent.stop();
|
||||
Script.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -171,4 +171,4 @@ function moveBird() {
|
|||
}
|
||||
}
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(moveBird);
|
||||
Script.willSendVisualDataCallback.connect(moveBird);
|
||||
|
|
51
examples/particleModelExample.js
Normal file
|
@ -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);
|
||||
|
|
@ -17,4 +17,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
50
examples/rideAlongWithAParticleExample.js
Normal file
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -130,4 +130,4 @@ function moveBird() {
|
|||
|
||||
Voxels.setPacketsPerSecond(10000);
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(moveBird);
|
||||
Script.willSendVisualDataCallback.connect(moveBird);
|
19
interface/resources/config/config.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"importFormats" : [
|
||||
{
|
||||
"extension": "png",
|
||||
"description": "Square PNG",
|
||||
"icon": "raster.svg"
|
||||
},
|
||||
{
|
||||
"extension": "svo",
|
||||
"description": "Sparse Voxel Octree Files",
|
||||
"icon": "voxel.svg"
|
||||
},
|
||||
{
|
||||
"extension": "schematic",
|
||||
"description": "Schematic Files",
|
||||
"icon": "voxel.svg"
|
||||
}
|
||||
]
|
||||
}
|
12
interface/resources/icons/computer.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#666666" d="M18.223,11.845c0,0.835-0.683,1.519-1.519,1.519h-5.163c0,0.807,0.607,1.49,0.607,1.822
|
||||
s-0.275,0.607-0.607,0.607H6.682c-0.332,0-0.607-0.275-0.607-0.607c0-0.351,0.607-0.997,0.607-1.822H1.519
|
||||
C0.683,13.363,0,12.68,0,11.845V1.519C0,0.684,0.683,0,1.519,0h15.186c0.835,0,1.519,0.684,1.519,1.519V11.845z M17.008,1.519
|
||||
c0-0.161-0.143-0.304-0.304-0.304H1.519c-0.161,0-0.304,0.143-0.304,0.304v7.896c0,0.161,0.143,0.304,0.304,0.304h15.186
|
||||
c0.161,0,0.304-0.143,0.304-0.304V1.519z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 884 B |
17
interface/resources/icons/desktop.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<rect fill="#666666" width="17" height="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect y="2" fill="#666666" width="17" height="11"/>
|
||||
</g>
|
||||
<rect x="1" y="4" fill="#989898" width="1" height="1"/>
|
||||
<rect x="1" y="6" fill="#989898" width="1" height="1"/>
|
||||
<rect x="4" y="4" fill="#989898" width="1" height="1"/>
|
||||
<rect x="4" y="6" fill="#989898" width="1" height="1"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 732 B |
15
interface/resources/icons/documents.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#666666" d="M17.615,4.556v11.541c0,0.503-0.408,0.911-0.911,0.911H7.593c-0.503,0-0.911-0.408-0.911-0.911v-2.733
|
||||
H1.519c-0.503,0-0.911-0.408-0.911-0.911V6.074c0-0.503,0.295-1.206,0.646-1.557l3.872-3.872C5.477,0.294,6.179,0,6.682,0h3.948
|
||||
c0.503,0,0.911,0.408,0.911,0.911v3.113c0.37-0.219,0.845-0.38,1.215-0.38h3.948C17.207,3.645,17.615,4.053,17.615,4.556z
|
||||
M10.326,5.163V1.215H6.682v3.948c0,0.503-0.408,0.911-0.911,0.911H1.822v6.074h4.859v-2.43c0-0.503,0.295-1.206,0.646-1.557
|
||||
L10.326,5.163z M2.629,4.859h2.838V2.021L2.629,4.859z M16.4,4.859h-3.645v3.948c0,0.503-0.408,0.911-0.911,0.911H7.896v6.074
|
||||
H16.4V4.859z M8.703,8.504h2.838V5.666L8.703,8.504z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
14
interface/resources/icons/file.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#666666" d="M12.148,13.667c0,0.503-0.408,0.911-0.911,0.911H0.911C0.408,14.578,0,14.17,0,13.667V0.911
|
||||
C0,0.408,0.408,0,0.911,0h6.074c0.503,0,1.206,0.294,1.557,0.646l2.961,2.961c0.351,0.351,0.646,1.054,0.646,1.557V13.667z
|
||||
M10.934,13.363V6.074H6.985c-0.503,0-0.911-0.408-0.911-0.911V1.215H1.215v12.148H10.934z M9.719,8.2
|
||||
c0,0.171-0.133,0.304-0.304,0.304H2.733C2.562,8.504,2.43,8.371,2.43,8.2V7.593c0-0.171,0.133-0.304,0.304-0.304h6.682
|
||||
c0.171,0,0.304,0.133,0.304,0.304V8.2z M9.719,10.63c0,0.171-0.133,0.304-0.304,0.304H2.733c-0.171,0-0.304-0.133-0.304-0.304
|
||||
v-0.607c0-0.171,0.133-0.304,0.304-0.304h6.682c0.171,0,0.304,0.133,0.304,0.304V10.63z M10.857,4.859
|
||||
c-0.057-0.161-0.142-0.323-0.208-0.389L7.678,1.5C7.612,1.433,7.45,1.348,7.289,1.291v3.568H10.857z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
11
interface/resources/icons/folder.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#666666" d="M14.854,10.569c0,1.098-0.901,2-2,2H2c-1.098,0-2-0.901-2-2V2c0-1.098,0.901-2,2-2h2.856
|
||||
c1.098,0,2,0.901,2,2v0.286h5.999c1.098,0,2,0.901,2,2V10.569z M13.711,4.285c0-0.473-0.384-0.857-0.857-0.857H6.57
|
||||
c-0.473,0-0.857-0.384-0.857-0.857V2c0-0.473-0.384-0.857-0.857-0.857H2C1.526,1.143,1.143,1.526,1.143,2v8.569
|
||||
c0,0.473,0.384,0.857,0.857,0.857h10.854c0.473,0,0.857-0.384,0.857-0.857V4.285z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 800 B |
16
interface/resources/icons/home.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#666666" d="M16.191,7.893c-0.054,0.063-0.139,0.106-0.223,0.116c-0.011,0-0.021,0-0.032,0
|
||||
c-0.085,0-0.159-0.021-0.223-0.074L8.375,1.816L1.037,7.935C0.963,7.988,0.878,8.02,0.782,8.009
|
||||
c-0.085-0.01-0.17-0.053-0.223-0.116l-0.657-0.785c-0.117-0.138-0.096-0.36,0.042-0.477l7.625-6.352
|
||||
c0.445-0.372,1.166-0.372,1.612,0l2.587,2.163V0.374c0-0.191,0.148-0.339,0.34-0.339h2.036c0.19,0,0.339,0.148,0.339,0.339v4.327
|
||||
l2.322,1.93c0.139,0.117,0.159,0.339,0.043,0.477L16.191,7.893z M14.483,12.93c0,0.371-0.308,0.679-0.679,0.679H9.732V9.536H7.018
|
||||
v4.072H2.945c-0.371,0-0.679-0.308-0.679-0.679V7.84c0-0.021,0.011-0.042,0.011-0.063l6.098-5.027l6.098,5.027
|
||||
c0.011,0.021,0.011,0.042,0.011,0.063V12.93z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
12
interface/resources/icons/raster.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#666666" d="M15.049,10.75c0,0.688-0.562,1.25-1.25,1.25h-12.5c-0.688,0-1.25-0.562-1.25-1.25v-9.5
|
||||
C0.049,0.562,0.611,0,1.299,0h12.5c0.688,0,1.25,0.562,1.25,1.25V10.75z M1.299,1c-0.133,0-0.25,0.117-0.25,0.25v9.5
|
||||
c0,0.133,0.117,0.25,0.25,0.25h12.5c0.133,0,0.25-0.117,0.25-0.25v-9.5c0-0.133-0.117-0.25-0.25-0.25H1.299z M3.549,5
|
||||
c-0.828,0-1.5-0.672-1.5-1.5S2.72,2,3.549,2s1.5,0.672,1.5,1.5S4.377,5,3.549,5z M13.049,10h-11V8.5l2.5-2.5l1.25,1.25l4-4
|
||||
l3.25,3.25V10z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 862 B |
18
interface/resources/icons/voxel.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#666666" d="M13.402,4.612c0-0.098-0.052-0.188-0.137-0.237c-0.085-0.049-0.189-0.049-0.274,0L7.704,7.428
|
||||
C7.423,7.59,7.25,7.89,7.25,8.215v6.105c0,0.098,0.052,0.188,0.137,0.237c0.085,0.049,0.189,0.049,0.274,0l5.085-2.936
|
||||
c0.407-0.235,0.657-0.668,0.657-1.138V4.612z"/>
|
||||
<path fill="#666666" d="M0.41,4.375c-0.085-0.049-0.189-0.049-0.274,0C0.052,4.424,0,4.514,0,4.612c0,0,0,0,0,0v5.872
|
||||
c0,0.469,0.25,0.903,0.657,1.138l5.085,2.936c0,0,0,0,0,0c0.085,0.049,0.189,0.049,0.274,0c0.085-0.049,0.136-0.139,0.136-0.237
|
||||
V8.215c0-0.325-0.173-0.625-0.455-0.787L0.41,4.375z"/>
|
||||
</g>
|
||||
<path fill="#666666" d="M12.443,3.586c0.085-0.049,0.137-0.139,0.137-0.237c0-0.098-0.052-0.188-0.137-0.237c0,0,0,0,0,0
|
||||
L7.358,0.176c-0.407-0.235-0.907-0.235-1.313,0L0.959,3.112c0,0,0,0,0,0C0.874,3.161,0.822,3.251,0.822,3.349
|
||||
c0,0.098,0.052,0.188,0.137,0.237l5.287,3.053c0.281,0.162,0.628,0.162,0.909,0L12.443,3.586z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
BIN
interface/resources/sounds/snap.wav
Normal file
88
interface/resources/styles/import_dialog.qss
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* import_dialog.qss
|
||||
* hifi
|
||||
*
|
||||
* Created by Stojce on 1/5/2014.
|
||||
* Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
* {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
QLabel {
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
QLabel#infoLabel {
|
||||
padding: 7px 0 0 7px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
QPushButton {
|
||||
border-width: 0;
|
||||
border-radius: 9px;
|
||||
font-size: 18px;
|
||||
padding: 17px 0px 15px;
|
||||
width: 107px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
QPushButton#importButton {
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
QPushButton#importButton:enabled {
|
||||
background: #333333;
|
||||
}
|
||||
|
||||
QPushButton#importButton:!enabled {
|
||||
background: rgba(50, 50, 50, 0.5);
|
||||
}
|
||||
|
||||
QPushButton#cancelButton {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
width: 74px;
|
||||
margin-right: 11px;
|
||||
}
|
||||
|
||||
QSidebar, QTreeView {
|
||||
border: 1px solid #C5C5C5;
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
selection-background-color: #BDE4E3;
|
||||
selection-color: #333333;
|
||||
|
||||
}
|
||||
|
||||
QTreeView {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
QSplitter::handle, QDialog {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
QTreeView QHeaderView {
|
||||
background: white;
|
||||
}
|
||||
|
||||
QTreeView QHeaderView:section {
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
border-bottom: 1px solid #C5C5C5;
|
||||
border-right: 1px solid #C5C5C5;
|
||||
background: white;
|
||||
color: #666666;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
QSidebar::item,
|
||||
QTreeView::item {
|
||||
padding: 5px 0 4px;
|
||||
}
|
|
@ -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; };
|
||||
|
||||
|
|
|
@ -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<NODE_TYPE>& 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();
|
||||
|
@ -299,14 +298,18 @@ private:
|
|||
void renderHighlightVoxel(VoxelDetail voxel);
|
||||
|
||||
void updateAvatar(float deltaTime);
|
||||
void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
|
||||
void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions);
|
||||
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
|
||||
|
||||
glm::vec3 getSunDirection();
|
||||
|
||||
void updateShadowMap();
|
||||
void displayOverlay();
|
||||
void displayStatsBackground(unsigned int rgba, int x, int y, int width, int height);
|
||||
void displayStats();
|
||||
void checkStatsClick();
|
||||
void toggleStatsExpanded();
|
||||
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
|
||||
void renderViewFrustum(ViewFrustum& viewFrustum);
|
||||
|
||||
void checkBandwidthMeterClick();
|
||||
|
@ -327,6 +330,7 @@ private:
|
|||
QMainWindow* _window;
|
||||
QGLWidget* _glWidget;
|
||||
|
||||
bool _statsExpanded;
|
||||
BandwidthMeter _bandwidthMeter;
|
||||
|
||||
QThread* _nodeThread;
|
||||
|
@ -351,7 +355,7 @@ private:
|
|||
|
||||
VoxelSystem _voxels;
|
||||
VoxelTree _clipboard; // if I copy/paste
|
||||
VoxelImporter _voxelImporter;
|
||||
VoxelImporter* _voxelImporter;
|
||||
VoxelSystem _sharedVoxelSystem;
|
||||
ViewFrustum _sharedVoxelSystemViewFrustum;
|
||||
|
||||
|
@ -370,10 +374,10 @@ 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
|
||||
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
|
||||
|
||||
Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
|
||||
Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
|
||||
|
||||
Faceshift _faceshift;
|
||||
|
||||
|
@ -399,7 +403,7 @@ private:
|
|||
int _mouseY;
|
||||
int _mouseDragStartedX;
|
||||
int _mouseDragStartedY;
|
||||
uint64_t _lastMouseMove;
|
||||
quint64 _lastMouseMove;
|
||||
bool _mouseHidden;
|
||||
bool _seenMouseMove;
|
||||
|
||||
|
@ -477,9 +481,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;
|
||||
|
@ -500,6 +503,7 @@ private:
|
|||
void checkVersion();
|
||||
void displayUpdateDialog();
|
||||
bool shouldSkipVersion(QString latestVersion);
|
||||
void takeSnapshot();
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__Application__) */
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
#include <AngleUtil.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StdDev.h>
|
||||
|
@ -40,8 +39,9 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_
|
|||
|
||||
// Mute icon configration
|
||||
static const int ICON_SIZE = 24;
|
||||
static const int ICON_LEFT = 20;
|
||||
static const int BOTTOM_PADDING = 110;
|
||||
static const int ICON_LEFT = 0;
|
||||
static const int ICON_TOP = 115;
|
||||
static const int ICON_TOP_MIRROR = 220;
|
||||
|
||||
Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent) :
|
||||
AbstractAudioInterface(parent),
|
||||
|
@ -285,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);
|
||||
|
||||
|
@ -365,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();
|
||||
|
@ -375,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));
|
||||
|
@ -433,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());
|
||||
|
@ -545,16 +539,16 @@ void Audio::toggleMute() {
|
|||
}
|
||||
|
||||
void Audio::render(int screenWidth, int screenHeight) {
|
||||
if (_audioInput) {
|
||||
if (_audioInput && _audioOutput) {
|
||||
glLineWidth(2.0);
|
||||
glBegin(GL_LINES);
|
||||
glColor3f(1,1,1);
|
||||
glColor3f(.93f, .93f, .93f);
|
||||
|
||||
int startX = 20.0;
|
||||
int currentX = startX;
|
||||
int topY = screenHeight - 40;
|
||||
int bottomY = screenHeight - 20;
|
||||
float frameWidth = 20.0;
|
||||
int topY = screenHeight - 45;
|
||||
int bottomY = screenHeight - 25;
|
||||
float frameWidth = 23.0;
|
||||
float halfY = topY + ((bottomY - topY) / 2.0);
|
||||
|
||||
// draw the lines for the base of the ring buffer
|
||||
|
@ -583,9 +577,9 @@ void Audio::render(int screenWidth, int screenHeight) {
|
|||
float msLeftForAudioOutput = (secondsLeftForAudioOutput + secondsLeftForRingBuffer) * 1000;
|
||||
|
||||
if (_numFramesDisplayStarve == 0) {
|
||||
glColor3f(0, 1, 0);
|
||||
glColor3f(0, .8f, .4f);
|
||||
} else {
|
||||
glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), 0, 0);
|
||||
glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), .2f, .4f);
|
||||
_numFramesDisplayStarve--;
|
||||
}
|
||||
|
||||
|
@ -596,44 +590,44 @@ void Audio::render(int screenWidth, int screenHeight) {
|
|||
}
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(startX, topY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, topY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, bottomY - 2);
|
||||
glVertex2f(startX, bottomY - 2);
|
||||
glVertex2f(startX + 1, topY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, topY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, bottomY - 2);
|
||||
glVertex2f(startX + 1, bottomY - 2);
|
||||
glEnd();
|
||||
|
||||
// Show a yellow bar with the averaged msecs latency you are hearing (from time of packet receipt)
|
||||
glColor3f(1,1,0);
|
||||
glColor3f(1, .8f, 0);
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, topY - 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, topY - 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, bottomY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, bottomY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, topY - 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, topY - 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, bottomY + 2);
|
||||
glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, bottomY + 2);
|
||||
glEnd();
|
||||
|
||||
char out[40];
|
||||
sprintf(out, "%3.0f\n", _averagedLatency);
|
||||
drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 0, out, 1,1,0);
|
||||
drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 2, out, 1, .8f, 0);
|
||||
|
||||
// Show a red bar with the 'start' point of one frame plus the jitter buffer
|
||||
|
||||
glColor3f(1, 0, 0);
|
||||
glColor3f(1, .2f, .4f);
|
||||
int jitterBufferPels = (1.f + (float)getJitterBufferSamples()
|
||||
/ (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) * frameWidth;
|
||||
sprintf(out, "%.0f\n", getJitterBufferSamples() / SAMPLE_RATE * 1000.f);
|
||||
drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 0, out, 1, 0, 0);
|
||||
drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
|
||||
sprintf(out, "j %.1f\n", _measuredJitter);
|
||||
if (Menu::getInstance()->getAudioJitterBufferSamples() == 0) {
|
||||
drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0);
|
||||
drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
|
||||
} else {
|
||||
drawtext(startX, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0);
|
||||
drawtext(startX, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
|
||||
}
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(startX + jitterBufferPels - 2, topY - 2);
|
||||
glVertex2f(startX + jitterBufferPels + 2, topY - 2);
|
||||
glVertex2f(startX + jitterBufferPels + 2, bottomY + 2);
|
||||
glVertex2f(startX + jitterBufferPels - 2, bottomY + 2);
|
||||
glVertex2f(startX + jitterBufferPels - 1, topY - 2);
|
||||
glVertex2f(startX + jitterBufferPels + 3, topY - 2);
|
||||
glVertex2f(startX + jitterBufferPels + 3, bottomY + 2);
|
||||
glVertex2f(startX + jitterBufferPels - 1, bottomY + 2);
|
||||
glEnd();
|
||||
|
||||
}
|
||||
|
@ -739,11 +733,13 @@ void Audio::handleAudioByteArray(const QByteArray& audioByteArray) {
|
|||
|
||||
void Audio::renderToolIcon(int screenHeight) {
|
||||
|
||||
_iconBounds = QRect(ICON_LEFT, screenHeight - BOTTOM_PADDING, ICON_SIZE, ICON_SIZE);
|
||||
int iconTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? ICON_TOP_MIRROR : ICON_TOP;
|
||||
|
||||
_iconBounds = QRect(ICON_LEFT, iconTop, ICON_SIZE, ICON_SIZE);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _micTextureId);
|
||||
glColor3f(1, 1, 1);
|
||||
glColor3f(.93f, .93f, .93f);
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
glTexCoord2f(1, 1);
|
||||
|
|
|
@ -14,21 +14,21 @@
|
|||
namespace { // .cpp-local
|
||||
|
||||
int const AREA_WIDTH = -280; // Width of the area used. Aligned to the right when negative.
|
||||
int const AREA_HEIGHT = 40; // Height of the area used. Aligned to the bottom when negative.
|
||||
int const BORDER_DISTANCE_HORIZ = -20; // Distance to edge of screen (use negative value when width is negative).
|
||||
int const BORDER_DISTANCE_VERT = 40; // Distance to edge of screen (use negative value when height is negative).
|
||||
int const AREA_HEIGHT = -40; // Height of the area used. Aligned to the bottom when negative.
|
||||
int const BORDER_DISTANCE_HORIZ = -10; // Distance to edge of screen (use negative value when width is negative).
|
||||
int const BORDER_DISTANCE_VERT = -15; // Distance to edge of screen (use negative value when height is negative).
|
||||
|
||||
int SPACING_VERT_BARS = 2; // Vertical distance between input and output bar
|
||||
int SPACING_RIGHT_CAPTION_IN_OUT = 4; // IN/OUT <--> |######## : |
|
||||
int SPACING_LEFT_CAPTION_UNIT = 4; // |######## : | <--> UNIT
|
||||
int PADDING_HORIZ_VALUE = 2; // |<-->X.XX<:-># |
|
||||
|
||||
unsigned const COLOR_TEXT = 0xe0e0e0e0; // ^ ^ ^ ^ ^ ^
|
||||
unsigned const COLOR_TEXT = 0xedededff; // ^ ^ ^ ^ ^ ^
|
||||
unsigned const COLOR_FRAME = 0xe0e0e0b0; // | | |
|
||||
unsigned const COLOR_INDICATOR = 0xc0c0c0b0; // |
|
||||
|
||||
char const* CAPTION_IN = "IN";
|
||||
char const* CAPTION_OUT = "OUT";
|
||||
char const* CAPTION_IN = "In";
|
||||
char const* CAPTION_OUT = "Out";
|
||||
char const* CAPTION_UNIT = "Mbps";
|
||||
|
||||
double const UNIT_SCALE = 8000.0 / (1024.0 * 1024.0); // Bytes/ms -> Mbps
|
||||
|
@ -38,13 +38,13 @@ namespace { // .cpp-local
|
|||
}
|
||||
|
||||
BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = {
|
||||
{ "Audio" , "Kbps", 8000.0 / 1024.0, 0x40ff40d0 },
|
||||
{ "Audio" , "Kbps", 8000.0 / 1024.0, 0x33cc99ff },
|
||||
{ "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 },
|
||||
{ "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 }
|
||||
};
|
||||
|
||||
BandwidthMeter::BandwidthMeter() :
|
||||
_textRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT),
|
||||
_textRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false),
|
||||
_scaleMaxIndex(INITIAL_SCALE_MAXIMUM_INDEX) {
|
||||
|
||||
_channels = static_cast<ChannelInfo*>( malloc(sizeof(_CHANNELS)) );
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<int,KeyEvent> _capturedKeys;
|
||||
};
|
||||
|
||||
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip
|
||||
|
|
|
@ -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->_myTransmitter.processIncomingData(reinterpret_cast<unsigned char*>(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<unsigned char*>(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<char*>(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<Node>&, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<const unsigned char*>(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) {
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -14,17 +14,20 @@
|
|||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -6,212 +6,148 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
#include "ImportDialog.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QGridLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QSplitter>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
const QString WINDOW_NAME = QObject::tr("Import Voxels");
|
||||
const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
|
||||
const QString IMPORT_TO_CLIPBOARD_CHECKBOX_STRING = QObject::tr("Import into clipboard");
|
||||
const QString PREVIEW_CHECKBOX_STRING = QObject::tr("Load preview");
|
||||
const QString IMPORT_FILE_TYPES = QObject::tr("Sparse Voxel Octree Files, "
|
||||
"Square PNG, "
|
||||
"Schematic Files "
|
||||
"(*.svo *.png *.schematic)");
|
||||
const QString WINDOW_NAME = QObject::tr("Import Voxels");
|
||||
const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
|
||||
const QString IMPORT_INFO = QObject::tr("<b>Import</b> %1 as voxels");
|
||||
const QString CANCEL_BUTTON_NAME = QObject::tr("Cancel");
|
||||
const QString INFO_LABEL_TEXT = QObject::tr("<div style='line-height:20px;'>"
|
||||
"This will load the selected file into Hifi and allow you<br/>"
|
||||
"to place it with %1-V; you must be in select or<br/>"
|
||||
"add mode (S or V keys will toggle mode) to place.</div>");
|
||||
|
||||
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 {
|
||||
|
||||
const glm::vec3 UP_VECT = glm::vec3(0, 1, 0);
|
||||
const float ANGULAR_RATE = 0.02f;
|
||||
const float VERTICAL_ANGLE = (float)M_PI_4 / 2.0f;
|
||||
const float RETURN_RATE = 0.02f;
|
||||
const float NEAR_CLIP = 0.5f;
|
||||
const float FAR_CLIP = 10.0f;
|
||||
const float FIELD_OF_VIEW = 60.0f;
|
||||
switchToResourcesParentIfRequired();
|
||||
|
||||
class GLWidget : public QGLWidget {
|
||||
public:
|
||||
GLWidget(QWidget* parent = NULL);
|
||||
void setDraw(bool draw) {_draw = draw;}
|
||||
void setTargetCenter(glm::vec3 targetCenter) { _targetCenter = targetCenter; }
|
||||
// types
|
||||
// Computer, Desktop, Trashcan, Network, Drive, Folder, File
|
||||
QString typeString;
|
||||
|
||||
protected:
|
||||
virtual void initializeGL();
|
||||
virtual void resizeGL(int width, int height);
|
||||
virtual void paintGL();
|
||||
switch (type) {
|
||||
case QFileIconProvider::Computer:
|
||||
typeString = "computer";
|
||||
break;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
case QFileIconProvider::Desktop:
|
||||
typeString = "desktop";
|
||||
break;
|
||||
|
||||
private:
|
||||
VoxelSystem* _voxelSystem;
|
||||
case QFileIconProvider::Trashcan:
|
||||
case QFileIconProvider::Network:
|
||||
case QFileIconProvider::Drive:
|
||||
case QFileIconProvider::Folder:
|
||||
typeString = "folder";
|
||||
break;
|
||||
|
||||
bool _draw;
|
||||
|
||||
double _a; // horizontal angle of the camera to the center of the object
|
||||
double _h; // vertical angle of the camera to the center of the object
|
||||
glm::vec3 _targetCenter;
|
||||
|
||||
bool _pressed;
|
||||
int _mouseX;
|
||||
int _mouseY;
|
||||
};
|
||||
|
||||
GLWidget::GLWidget(QWidget *parent)
|
||||
: QGLWidget(parent, Application::getInstance()->getGLWidget()),
|
||||
_draw(false),
|
||||
_a(0.0f),
|
||||
_h(VERTICAL_ANGLE),
|
||||
_targetCenter(0.5f, 0.5f, 0.5f),
|
||||
_pressed(false),
|
||||
_mouseX(0),
|
||||
_mouseY(0) {
|
||||
_voxelSystem = Application::getInstance()->getSharedVoxelSystem();
|
||||
}
|
||||
|
||||
void GLWidget::initializeGL() {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glShadeModel (GL_SMOOTH);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void GLWidget::resizeGL(int width, int height) {
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(FIELD_OF_VIEW,
|
||||
(float) width / height,
|
||||
NEAR_CLIP,
|
||||
FAR_CLIP);
|
||||
}
|
||||
|
||||
void GLWidget::paintGL() {
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
if (!_pressed) {
|
||||
_a += ANGULAR_RATE;
|
||||
_h = (1.0f - RETURN_RATE) * _h + RETURN_RATE * VERTICAL_ANGLE;
|
||||
default:
|
||||
typeString = "file";
|
||||
break;
|
||||
}
|
||||
|
||||
gluLookAt(_targetCenter.x + (glm::length(_targetCenter) + NEAR_CLIP) * cos(_a),
|
||||
_targetCenter.y + (glm::length(_targetCenter) + NEAR_CLIP) * sin(_h),
|
||||
_targetCenter.z + (glm::length(_targetCenter) + NEAR_CLIP) * sin(_a),
|
||||
_targetCenter.x, _targetCenter.y, _targetCenter.z,
|
||||
UP_VECT.x, UP_VECT.y, UP_VECT.z);
|
||||
return QIcon("resources/icons/" + typeString + ".svg");
|
||||
}
|
||||
|
||||
QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
|
||||
switchToResourcesParentIfRequired();
|
||||
const QString ext = info.suffix().toLower();
|
||||
|
||||
if (_draw) {
|
||||
glBegin(GL_LINES);
|
||||
glColor3d(1, 1 ,1);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(1, 0, 0);
|
||||
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, 1, 0);
|
||||
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, 0, 1);
|
||||
|
||||
|
||||
glColor3d(0.4f, 0.4f ,0.4f);
|
||||
glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z);
|
||||
glVertex3d(0 , 2 * _targetCenter.y, 2 * _targetCenter.z);
|
||||
|
||||
glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z);
|
||||
glVertex3d(2 * _targetCenter.x, 0 , 2 * _targetCenter.z);
|
||||
|
||||
glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z);
|
||||
glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 0 );
|
||||
glEnd();
|
||||
|
||||
glScalef(1.0f / TREE_SCALE, 1.0f / TREE_SCALE, 1.0f / TREE_SCALE);
|
||||
_voxelSystem->render(false);
|
||||
if (info.isDir()) {
|
||||
if (info.absoluteFilePath() == QDir::homePath()) {
|
||||
return QIcon("resources/icons/home.svg");
|
||||
} else if (info.absoluteFilePath() == DESKTOP_LOCATION) {
|
||||
return QIcon("resources/icons/desktop.svg");
|
||||
} else if (info.absoluteFilePath() == QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) {
|
||||
return QIcon("resources/icons/documents.svg");
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
void GLWidget::mousePressEvent(QMouseEvent* event) {
|
||||
_pressed = true;
|
||||
_mouseX = event->globalX();
|
||||
_mouseY = event->globalY();
|
||||
QString HiFiIconProvider::type(const QFileInfo &info) const {
|
||||
if (info.isFile()) {
|
||||
if (info.suffix().size() > SHORT_FILE_EXTENSION) {
|
||||
// Capitalize extension
|
||||
return info.suffix().left(SECOND_INDEX_LETTER).toUpper() + info.suffix().mid(SECOND_INDEX_LETTER);
|
||||
}
|
||||
return info.suffix().toUpper();
|
||||
}
|
||||
|
||||
return QFileIconProvider::type(info);
|
||||
}
|
||||
|
||||
void GLWidget::mouseMoveEvent(QMouseEvent* event) {
|
||||
_a += (M_PI * (event->globalX() - _mouseX)) / height();
|
||||
_h += (M_PI * (event->globalY() - _mouseY)) / height();
|
||||
_h = glm::clamp(_h, -M_PI_4, M_PI_4);
|
||||
ImportDialog::ImportDialog(QWidget* parent) :
|
||||
QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, NULL),
|
||||
_importButton(IMPORT_BUTTON_NAME, this),
|
||||
_cancelButton(CANCEL_BUTTON_NAME, this),
|
||||
fileAccepted(false) {
|
||||
|
||||
_mouseX = event->globalX();
|
||||
_mouseY = event->globalY();
|
||||
}
|
||||
|
||||
void GLWidget::mouseReleaseEvent(QMouseEvent* event) {
|
||||
_pressed = false;
|
||||
}
|
||||
|
||||
ImportDialog::ImportDialog(QWidget *parent)
|
||||
: QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, IMPORT_FILE_TYPES),
|
||||
_importButton (IMPORT_BUTTON_NAME, this),
|
||||
_clipboardImportBox(IMPORT_TO_CLIPBOARD_CHECKBOX_STRING, this),
|
||||
_previewBox (PREVIEW_CHECKBOX_STRING, this),
|
||||
_previewBar (this),
|
||||
_glPreview (new GLWidget(this)) {
|
||||
setOption(QFileDialog::DontUseNativeDialog, true);
|
||||
setFileMode(QFileDialog::ExistingFile);
|
||||
setViewMode(QFileDialog::Detail);
|
||||
|
||||
QGridLayout* gridLayout = (QGridLayout*) layout();
|
||||
gridLayout->addWidget(&_importButton , 2, 2);
|
||||
gridLayout->addWidget(&_clipboardImportBox, 2, 3);
|
||||
gridLayout->addWidget(&_previewBox , 3, 3);
|
||||
gridLayout->addWidget(&_previewBar , 0, 3);
|
||||
gridLayout->addWidget(_glPreview , 1, 3);
|
||||
gridLayout->setColumnStretch(3, 1);
|
||||
#ifdef Q_OS_MAC
|
||||
QString cmdString = ("Command");
|
||||
#else
|
||||
QString cmdString = ("Control");
|
||||
#endif
|
||||
QLabel* infoLabel = new QLabel(QString(INFO_LABEL_TEXT).arg(cmdString));
|
||||
infoLabel->setObjectName("infoLabel");
|
||||
|
||||
_previewBar.setVisible(false);
|
||||
_previewBar.setRange(0, 100);
|
||||
_previewBar.setValue(0);
|
||||
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(&_previewBox, SIGNAL(toggled(bool)), SIGNAL(previewToggled(bool)));
|
||||
connect(&_previewBox, SIGNAL(toggled(bool)), SLOT(preview(bool)));
|
||||
|
||||
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
|
||||
connect(&_glTimer, SIGNAL(timeout()), SLOT(timer()));
|
||||
|
||||
connect(&_cancelButton, SIGNAL(pressed()), SLOT(close()));
|
||||
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
|
||||
}
|
||||
|
||||
ImportDialog::~ImportDialog() {
|
||||
delete _glPreview;
|
||||
}
|
||||
|
||||
void ImportDialog::init() {
|
||||
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
|
||||
connect(voxelSystem, SIGNAL(importSize(float,float,float)), SLOT(setGLCamera(float,float,float)));
|
||||
connect(voxelSystem, SIGNAL(importProgress(int)), &_previewBar, SLOT(setValue(int)));
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ImportDialog::import() {
|
||||
_importButton.setDisabled(true);
|
||||
_clipboardImportBox.setDisabled(true);
|
||||
_previewBox.setDisabled(true);
|
||||
|
||||
_previewBar.setValue(0);
|
||||
_previewBar.setVisible(true);
|
||||
|
||||
fileAccepted = true;
|
||||
emit accepted();
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -219,44 +155,137 @@ void ImportDialog::reject() {
|
|||
}
|
||||
|
||||
int ImportDialog::exec() {
|
||||
// deselect selected file
|
||||
selectFile(" ");
|
||||
return QFileDialog::exec();
|
||||
}
|
||||
|
||||
void ImportDialog::setGLCamera(float x, float y, float z) {
|
||||
_glPreview->setTargetCenter(glm::vec3(x, y, z) / 2.0f);
|
||||
}
|
||||
|
||||
void ImportDialog::reset() {
|
||||
_previewBox.setChecked(false);
|
||||
_previewBar.setVisible(false);
|
||||
_previewBar.setValue(0);
|
||||
_importButton.setEnabled(true);
|
||||
_clipboardImportBox.setEnabled(true);
|
||||
_previewBox.setEnabled(true);
|
||||
|
||||
_glTimer.stop();
|
||||
_glPreview->setDraw(false);
|
||||
_glPreview->updateGL();
|
||||
}
|
||||
|
||||
void ImportDialog::preview(bool wantPreview) {
|
||||
_previewBar.setValue(0);
|
||||
_previewBar.setVisible(wantPreview);
|
||||
_glPreview->setDraw(wantPreview);
|
||||
|
||||
if (wantPreview) {
|
||||
_glTimer.start();
|
||||
} else {
|
||||
_glTimer.stop();
|
||||
_glPreview->updateGL();
|
||||
}
|
||||
_importButton.setEnabled(false);
|
||||
}
|
||||
|
||||
void ImportDialog::saveCurrentFile(QString filename) {
|
||||
_currentFile = filename;
|
||||
if (!filename.isEmpty() && QFileInfo(filename).isFile()) {
|
||||
_currentFile = filename;
|
||||
_importButton.setEnabled(true);
|
||||
} else {
|
||||
_currentFile.clear();
|
||||
_importButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportDialog::timer() {
|
||||
_glPreview->updateGL();
|
||||
_glTimer.start(16);
|
||||
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<QWidget*>("lookInCombo");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("backButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("forwardButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("toParentButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("newFolderButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("listModeButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("detailModeButton");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("fileNameEdit");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("fileTypeCombo");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("fileTypeLabel");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("fileNameLabel");
|
||||
widget->hide();
|
||||
|
||||
widget = findChild<QWidget*>("buttonBox");
|
||||
widget->hide();
|
||||
|
||||
QSplitter* splitter = findChild<QSplitter*>("splitter");
|
||||
splitter->setHandleWidth(0);
|
||||
|
||||
// remove blue outline on Mac
|
||||
widget = findChild<QWidget*>("sidebar");
|
||||
widget->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
|
||||
widget = findChild<QWidget*>("treeView");
|
||||
widget->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
|
||||
switchToResourcesParentIfRequired();
|
||||
QFile styleSheet("resources/styles/import_dialog.qss");
|
||||
if (styleSheet.open(QIODevice::ReadOnly)) {
|
||||
setStyleSheet(styleSheet.readAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ImportDialog::setImportTypes() {
|
||||
|
||||
switchToResourcesParentIfRequired();
|
||||
QFile config("resources/config/config.json");
|
||||
config.open(QFile::ReadOnly | QFile::Text);
|
||||
QJsonDocument document = QJsonDocument::fromJson(config.readAll());
|
||||
if (!document.isNull() && !document.isEmpty()) {
|
||||
|
||||
QString importFormatsInfo;
|
||||
QString importFormatsFilterList;
|
||||
QHash<QString, QString> iconsMap;
|
||||
|
||||
QJsonObject configObject = document.object();
|
||||
if (!configObject.isEmpty()) {
|
||||
QJsonArray fileFormats = configObject["importFormats"].toArray();
|
||||
int formatsCounter = 0;
|
||||
foreach (const QJsonValue& fileFormat, fileFormats) {
|
||||
QJsonObject fileFormatObject = fileFormat.toObject();
|
||||
|
||||
QString ext(fileFormatObject["extension"].toString());
|
||||
QString icon(fileFormatObject.value("icon").toString());
|
||||
|
||||
if (formatsCounter > 0) {
|
||||
importFormatsInfo.append(",");
|
||||
}
|
||||
|
||||
// set ' or' on last import type text
|
||||
if (formatsCounter == fileFormats.count() - 1) {
|
||||
importFormatsInfo.append(" or");
|
||||
}
|
||||
|
||||
importFormatsFilterList.append(QString("*.%1 ").arg(ext));
|
||||
importFormatsInfo.append(" .").append(ext);
|
||||
iconsMap[ext] = icon;
|
||||
formatsCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// set custom file icons
|
||||
setIconProvider(new HiFiIconProvider(iconsMap));
|
||||
setNameFilter(importFormatsFilterList);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
QString cmdString = ("Command");
|
||||
#else
|
||||
QString cmdString = ("Control");
|
||||
#endif
|
||||
setLabelText(QFileDialog::LookIn, QString(IMPORT_INFO).arg(importFormatsInfo));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,54 +9,54 @@
|
|||
#ifndef __hifi__ImportDialog__
|
||||
#define __hifi__ImportDialog__
|
||||
|
||||
#include <VoxelSystem.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QProgressBar>
|
||||
#include <QGLWidget>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
#include <QFileIconProvider>
|
||||
#include <QHash>
|
||||
|
||||
class GLWidget;
|
||||
#include <SharedUtil.h>
|
||||
|
||||
class HiFiIconProvider : public QFileIconProvider {
|
||||
public:
|
||||
HiFiIconProvider(const QHash<QString, QString> map) { iconsMap = map; };
|
||||
virtual QIcon icon(IconType type) const;
|
||||
virtual QIcon icon(const QFileInfo &info) const;
|
||||
virtual QString type(const QFileInfo &info) const;
|
||||
QHash<QString, QString> iconsMap;
|
||||
};
|
||||
|
||||
class ImportDialog : public QFileDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ImportDialog(QWidget* parent = NULL);
|
||||
~ImportDialog();
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
|
||||
bool getWantPreview() const { return _previewBox.isChecked(); }
|
||||
QString getCurrentFile() const { return _currentFile; }
|
||||
bool getImportIntoClipboard() const { return _clipboardImportBox.isChecked(); }
|
||||
|
||||
signals:
|
||||
void previewToggled(bool);
|
||||
void accepted();
|
||||
|
||||
public slots:
|
||||
int exec();
|
||||
void setGLCamera(float x, float y, float z);
|
||||
void import();
|
||||
void accept();
|
||||
void reject();
|
||||
|
||||
private slots:
|
||||
void preview(bool preview);
|
||||
void saveCurrentFile(QString);
|
||||
void timer();
|
||||
|
||||
private:
|
||||
QString _currentFile;
|
||||
QPushButton _importButton;
|
||||
QCheckBox _clipboardImportBox;
|
||||
QCheckBox _previewBox;
|
||||
QProgressBar _previewBar;
|
||||
GLWidget* _glPreview;
|
||||
QTimer _glTimer;
|
||||
QString _currentFile;
|
||||
QPushButton _importButton;
|
||||
QPushButton _cancelButton;
|
||||
|
||||
void setLayout();
|
||||
void setImportTypes();
|
||||
bool fileAccepted;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ImportDialog__) */
|
||||
|
|
|
@ -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");
|
||||
|
@ -917,6 +917,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();
|
||||
|
@ -961,26 +1015,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");
|
||||
|
@ -1144,3 +1178,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -210,7 +211,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";
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -24,6 +24,12 @@ MetavoxelSystem::MetavoxelSystem() :
|
|||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
|
||||
MetavoxelSystem::~MetavoxelSystem() {
|
||||
for (QHash<QUuid, MetavoxelClient*>::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<unsigned char*>(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<MetavoxelEditMessage>().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()) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
#include <MetavoxelMessages.h>
|
||||
|
||||
#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<Point> _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<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
//
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#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<ParticleTree*>(_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <OctreeRenderer.h>
|
||||
#include <ParticleTree.h>
|
||||
#include <ViewFrustum.h>
|
||||
#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<QString, Model*> _particleModels;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ParticleTreeRenderer__) */
|
|
@ -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)
|
||||
|
@ -373,9 +345,18 @@ const glm::vec3 randVector() {
|
|||
}
|
||||
|
||||
static TextRenderer* textRenderer(int mono) {
|
||||
static TextRenderer* monoRenderer = new TextRenderer(MONO_FONT_FAMILY);
|
||||
static TextRenderer* proportionalRenderer = new TextRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT);
|
||||
return mono ? monoRenderer : proportionalRenderer;
|
||||
static TextRenderer* monoRenderer = new TextRenderer(MONO_FONT_FAMILY);
|
||||
static TextRenderer* proportionalRenderer = new TextRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT);
|
||||
static TextRenderer* inconsolataRenderer = new TextRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false);
|
||||
switch (mono) {
|
||||
case 1:
|
||||
return monoRenderer;
|
||||
case 2:
|
||||
return inconsolataRenderer;
|
||||
case 0:
|
||||
default:
|
||||
return proportionalRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
int widthText(float scale, int mono, char const* string) {
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
// the standard mono font family
|
||||
#define MONO_FONT_FAMILY "Courier"
|
||||
|
||||
// the Inconsolata font family
|
||||
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
|
||||
|
||||
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
|
||||
|
||||
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);
|
||||
|
@ -51,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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,20 +21,30 @@ private:
|
|||
QString _filename;
|
||||
};
|
||||
|
||||
VoxelImporter::VoxelImporter(QWidget* parent)
|
||||
: QObject(parent),
|
||||
_voxelTree(true),
|
||||
_importDialog(parent),
|
||||
_currentTask(NULL),
|
||||
_nextTask(NULL) {
|
||||
const QString SETTINGS_GROUP_NAME = "VoxelImport";
|
||||
const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
|
||||
|
||||
connect(&_importDialog, SIGNAL(previewToggled(bool)), SLOT(preImport()));
|
||||
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::init() {
|
||||
_importDialog.init();
|
||||
void VoxelImporter::saveSettings(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
settings->setValue(IMPORT_DIALOG_SETTINGS_KEY, _importDialog.saveState());
|
||||
settings->endGroup();
|
||||
}
|
||||
|
||||
void VoxelImporter::init(QSettings* settings) {
|
||||
settings->beginGroup(SETTINGS_GROUP_NAME);
|
||||
_importDialog.restoreState(settings->value(IMPORT_DIALOG_SETTINGS_KEY).toByteArray());
|
||||
settings->endGroup();
|
||||
}
|
||||
|
||||
VoxelImporter::~VoxelImporter() {
|
||||
|
@ -74,15 +84,13 @@ int VoxelImporter::exec() {
|
|||
reset();
|
||||
} else {
|
||||
_importDialog.reset();
|
||||
|
||||
if (_importDialog.getImportIntoClipboard()) {
|
||||
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
|
||||
|
||||
voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(),
|
||||
Application::getInstance()->getClipboard(),
|
||||
true);
|
||||
voxelSystem->changeTree(Application::getInstance()->getClipboard());
|
||||
}
|
||||
|
||||
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
|
||||
|
||||
voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(),
|
||||
Application::getInstance()->getClipboard(),
|
||||
true);
|
||||
voxelSystem->changeTree(Application::getInstance()->getClipboard());
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -94,24 +102,22 @@ int VoxelImporter::preImport() {
|
|||
if (!QFileInfo(filename).isFile()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_importDialog.getWantPreview()) {
|
||||
_filename = filename;
|
||||
|
||||
if (_nextTask) {
|
||||
delete _nextTask;
|
||||
}
|
||||
|
||||
_nextTask = new ImportTask(_filename);
|
||||
connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
|
||||
|
||||
if (_currentTask != NULL) {
|
||||
_voxelTree.cancelImport();
|
||||
} else {
|
||||
launchTask();
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ public:
|
|||
VoxelImporter(QWidget* parent = NULL);
|
||||
~VoxelImporter();
|
||||
|
||||
void init();
|
||||
void init(QSettings* settings);
|
||||
void reset();
|
||||
void saveSettings(QSettings* settings);
|
||||
|
||||
VoxelTree* getVoxelTree() { return &_voxelTree; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "CoverageMap.h"
|
||||
|
@ -581,17 +580,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<const unsigned char*>(packet.data()) + numBytesPacketHeader;
|
||||
|
||||
VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt));
|
||||
dataAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
|
@ -608,7 +607,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) {
|
||||
|
@ -636,7 +635,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();
|
||||
|
@ -647,7 +647,8 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
|
|||
}
|
||||
subsection++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!_useFastVoxelPipeline || _writeRenderFullVBO) {
|
||||
setupNewVoxelsForDrawing();
|
||||
|
@ -655,9 +656,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() {
|
||||
|
@ -668,8 +669,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)) {
|
||||
|
@ -729,7 +730,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
|||
_bufferWriteLock.unlock();
|
||||
}
|
||||
|
||||
uint64_t end = usecTimestampNow();
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / 1000;
|
||||
_setupNewVoxelsForDrawingLastFinished = end;
|
||||
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
|
||||
|
@ -746,8 +747,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 &&
|
||||
|
@ -777,7 +778,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
|||
|
||||
_bufferWriteLock.unlock();
|
||||
}
|
||||
uint64_t end = usecTimestampNow();
|
||||
quint64 end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start) / 1000;
|
||||
_setupNewVoxelsForDrawingLastFinished = end;
|
||||
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
|
||||
|
@ -786,14 +787,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
|
||||
|
@ -815,7 +816,7 @@ void VoxelSystem::checkForCulling() {
|
|||
hideOutOfView(forceFullFrustum);
|
||||
|
||||
if (forceFullFrustum) {
|
||||
uint64_t endViewCulling = usecTimestampNow();
|
||||
quint64 endViewCulling = usecTimestampNow();
|
||||
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
|
||||
}
|
||||
|
||||
|
@ -824,7 +825,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)) {
|
||||
|
@ -1895,7 +1896,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];
|
||||
|
@ -2995,7 +2996,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++;
|
||||
}
|
||||
|
@ -3016,7 +3017,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());
|
||||
|
|
|
@ -51,7 +51,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) { }
|
||||
|
@ -235,10 +235,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;
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
|
@ -336,11 +335,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;
|
||||
|
|
|
@ -114,7 +114,7 @@ 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);
|
||||
|
||||
|
|
|
@ -16,15 +16,31 @@
|
|||
|
||||
#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<QWeakPointer<Node> >("NodeWeakPointer");
|
||||
_myAvatar = QSharedPointer<MyAvatar>(new MyAvatar());
|
||||
}
|
||||
|
||||
void AvatarManager::clear() {
|
||||
_lookAtTargetAvatar.clear();
|
||||
_avatarFades.clear();
|
||||
_avatarHash.clear();
|
||||
_myAvatar.clear();
|
||||
}
|
||||
|
||||
void AvatarManager::init() {
|
||||
_myAvatar->init();
|
||||
_myAvatar->setPosition(START_LOCATION);
|
||||
_myAvatar->setDisplayingLookatVectors(false);
|
||||
_avatarHash.insert(MY_AVATAR_KEY, _myAvatar);
|
||||
}
|
||||
|
||||
void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
|
||||
|
@ -34,22 +50,26 @@ void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
|
|||
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;
|
||||
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
|
||||
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
|
||||
|
||||
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
if (avatar != static_cast<Avatar*>(_myAvatar.data())) {
|
||||
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();
|
||||
|
||||
_lookAtIndicatorScale = avatar->getHead().getScale();
|
||||
_lookAtOtherPosition = avatar->getHead().getPosition();
|
||||
|
||||
_lookAtTargetAvatar = avatarPointer;
|
||||
|
||||
// found the look at target avatar, return
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,20 +81,28 @@ void AvatarManager::updateAvatars(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<Avatar*>(avatarIterator.value().data());
|
||||
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
|
||||
// for now skip updates to _myAvatar because it is done explicitly in Application
|
||||
// TODO: update _myAvatar in this context
|
||||
++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, NULL);
|
||||
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 +116,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<Avatar*>(avatarPointer.data());
|
||||
if (!avatar->isInitialized()) {
|
||||
avatar->init();
|
||||
}
|
||||
avatar->render(false);
|
||||
avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors));
|
||||
if (avatar == static_cast<Avatar*>(_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<AvatarSharedPointer>::iterator fadingAvatar = _avatarFades.begin();
|
||||
QVector<AvatarSharedPointer>::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<Avatar*>(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, NULL);
|
||||
++fadingIterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,29 +163,36 @@ void AvatarManager::renderAvatarFades() {
|
|||
Glower glower;
|
||||
|
||||
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
|
||||
fadingAvatar->render(false);
|
||||
Avatar* avatar = static_cast<Avatar*>(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<Avatar*>(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 +202,12 @@ void AvatarManager::processDataServerResponse(const QString& userString, const Q
|
|||
}
|
||||
|
||||
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& 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 +218,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 +233,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<const unsigned char*>
|
||||
(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::clearMixedAvatars() {
|
||||
// clear any avatars that came from an avatar-mixer
|
||||
AvatarHash::iterator removeAvatar = _avatarHash.begin();
|
||||
|
||||
while (removeAvatar != _avatarHash.end()) {
|
||||
removeAvatar = removeAvatarAtHashIterator(removeAvatar);
|
||||
removeAvatar = erase(removeAvatar);
|
||||
}
|
||||
}
|
||||
_lookAtTargetAvatar.clear();
|
||||
}
|
||||
|
|
|
@ -13,30 +13,33 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <DataServerClient.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
|
||||
typedef QSharedPointer<Avatar> AvatarSharedPointer;
|
||||
typedef QHash<QUuid, AvatarSharedPointer> 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 clear();
|
||||
|
||||
void init();
|
||||
|
||||
MyAvatar* getMyAvatar() { return _myAvatar.data(); }
|
||||
|
||||
const AvatarHash& getAvatarHash() { return _avatarHash; }
|
||||
int size() const { return _avatarHash.size(); }
|
||||
|
||||
Avatar* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
|
||||
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
|
||||
|
||||
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
|
||||
|
||||
void updateAvatars(float deltaTime);
|
||||
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
|
||||
|
||||
void clearHash();
|
||||
|
||||
void clearMixedAvatars();
|
||||
|
||||
public slots:
|
||||
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
|
||||
|
||||
|
@ -44,17 +47,20 @@ 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<Avatar> _lookAtTargetAvatar;
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
glm::vec3 _lookAtOtherPosition;
|
||||
float _lookAtIndicatorScale;
|
||||
|
||||
AvatarHash _avatarHash;
|
||||
QVector<AvatarSharedPointer> _avatarFades;
|
||||
QSharedPointer<MyAvatar> _myAvatar;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__AvatarManager__) */
|
||||
|
|
|
@ -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<Avatar*>(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) {
|
||||
|
|
|
@ -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<Avatar*>(_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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
|
@ -238,7 +237,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 +283,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));
|
||||
|
@ -434,7 +431,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 +453,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 +470,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>() << 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,9 +484,10 @@ 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));
|
||||
}
|
||||
|
||||
|
@ -549,7 +537,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]) {
|
||||
|
@ -791,14 +779,15 @@ void MyAvatar::updateChatCircle(float deltaTime) {
|
|||
// find all circle-enabled members and sort by distance
|
||||
QVector<SortedAvatar> 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<Avatar*>(avatarPointer.data());
|
||||
if ( ! avatar->isChatCirclingEnabled() ||
|
||||
avatar == static_cast<Avatar*>(this)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
SortedAvatar sortedAvatar;
|
||||
sortedAvatar.avatar = avatar;
|
||||
sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition());
|
||||
sortedAvatars.append(sortedAvatar);
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -57,7 +57,7 @@ private:
|
|||
QString _lastDomain;
|
||||
glm::vec3 _lastPosition;
|
||||
glm::vec3 _lastOrientation;
|
||||
uint64_t _lastOrientationSend;
|
||||
quint64 _lastOrientationSend;
|
||||
QUrl _faceModelURL;
|
||||
QUrl _skeletonModelURL;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ private:
|
|||
bool _tcpEnabled;
|
||||
int _tcpRetryCount;
|
||||
bool _tracking;
|
||||
uint64_t _lastTrackingStateReceived;
|
||||
quint64 _lastTrackingStateReceived;
|
||||
|
||||
glm::quat _headRotation;
|
||||
glm::vec3 _headAngularVelocity;
|
||||
|
|
|
@ -25,7 +25,7 @@ public slots:
|
|||
|
||||
private:
|
||||
|
||||
uint64_t _lastMovement;
|
||||
quint64 _lastMovement;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__SixenseManager__) */
|
||||
|
|
|
@ -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<const char*>(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)
|
||||
|
|
|
@ -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<double>& doubleVector) {
|
|||
}
|
||||
|
||||
QVector<int> getIntVector(const QVariantList& properties, int index) {
|
||||
if (index >= properties.size()) {
|
||||
return QVector<int>();
|
||||
}
|
||||
QVector<int> vector = properties.at(index).value<QVector<int> >();
|
||||
if (!vector.isEmpty()) {
|
||||
return vector;
|
||||
|
@ -388,6 +400,9 @@ QVector<int> getIntVector(const QVariantList& properties, int index) {
|
|||
}
|
||||
|
||||
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
|
||||
if (index >= properties.size()) {
|
||||
return QVector<double>();
|
||||
}
|
||||
QVector<double> vector = properties.at(index).value<QVector<double> >();
|
||||
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<int> getIndices(const QVector<QString> ids, QVector<QString> modelIDs) {
|
|||
return indices;
|
||||
}
|
||||
|
||||
typedef QPair<int, float> WeightedIndex;
|
||||
|
||||
void addBlendshapes(const ExtractedBlendshape& extracted, const QList<WeightedIndex>& 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<int, int>& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first];
|
||||
for (int i = 0; i < extracted.blendshape.indices.size(); i++) {
|
||||
int oldIndex = extracted.blendshape.indices.at(i);
|
||||
for (QMultiHash<int, int>::const_iterator it = extractedMesh.newIndices.constFind(oldIndex);
|
||||
it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) {
|
||||
QHash<int, int>::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<QString, QString>& parentMap,
|
||||
const QHash<QString, FBXModel>& 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<QString, ExtractedMesh> meshes;
|
||||
QVector<ExtractedBlendshape> blendshapes;
|
||||
|
@ -784,7 +858,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
QVector<QString> jointRightFingertipIDs(jointRightFingertipNames.size());
|
||||
|
||||
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
typedef QPair<int, float> WeightedIndex;
|
||||
QMultiHash<QByteArray, WeightedIndex> 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<ExtractedBlendshape> 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<int, int>& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first];
|
||||
for (int i = 0; i < extracted.blendshape.indices.size(); i++) {
|
||||
int oldIndex = extracted.blendshape.indices.at(i);
|
||||
for (QMultiHash<int, int>::const_iterator it = extractedMesh.newIndices.constFind(oldIndex);
|
||||
it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) {
|
||||
QHash<int, int>::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<QString> modelIDs;
|
||||
QSet<QString> remainingModels;
|
||||
for (QHash<QString, FBXModel>::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
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <QNetworkReply>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Application.h"
|
||||
#include "GeometryCache.h"
|
||||
|
@ -507,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;
|
||||
}
|
||||
|
|