merge fix

This commit is contained in:
Philip Rosedale 2014-02-03 16:09:53 -08:00
commit 5b709aa60c
236 changed files with 6871 additions and 4304 deletions

View file

@ -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
-----

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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__) */

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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__) */

View file

@ -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());

View file

@ -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();

View file

@ -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;

View file

@ -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:

View file

@ -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);
}

View file

@ -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&);

View file

@ -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;

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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());
}
}
}
}
}
}

View file

@ -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();
};

File diff suppressed because it is too large Load diff

View file

@ -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__) */

View file

@ -62,4 +62,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
Agent.willSendVisualDataCallback.connect(maybePlaySound);
Script.willSendVisualDataCallback.connect(maybePlaySound);

View file

@ -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");

View 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);

View file

@ -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);

View file

@ -69,4 +69,4 @@ function checkSticks() {
}
// Connect a call back that happens every frame
Agent.willSendVisualDataCallback.connect(checkSticks);
Script.willSendVisualDataCallback.connect(checkSticks);

View file

@ -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);

View file

@ -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);

View file

@ -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
View 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");

View 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);

View file

@ -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);

95
examples/lookWithMouse.js Normal file
View file

@ -0,0 +1,95 @@
//
// lookWithMouse.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/28/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates use of the Controller class
//
//
var alwaysLook = true; // if you want the mouse look to happen only when you click, change this to false
var isMouseDown = false;
var lastX = 0;
var lastY = 0;
var yawFromMouse = 0;
var pitchFromMouse = 0;
var wantDebugging = false;
function mousePressEvent(event) {
if (wantDebugging) {
print("mousePressEvent event.x,y=" + event.x + ", " + event.y);
}
isMouseDown = true;
lastX = event.x;
lastY = event.y;
}
function mouseReleaseEvent(event) {
if (wantDebugging) {
print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y);
}
isMouseDown = false;
}
function mouseMoveEvent(event) {
if (wantDebugging) {
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
}
if (alwaysLook || isMouseDown) {
var MOUSE_YAW_SCALE = -0.25;
var MOUSE_PITCH_SCALE = -12.5;
var FIXED_MOUSE_TIMESTEP = 0.016;
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
lastX = event.x;
lastY = event.y;
}
}
function update() {
if (wantDebugging) {
print("update()...");
}
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
if (wantDebugging) {
print("changing orientation"
+ " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + ","
+ MyAvatar.orientation.z + "," + MyAvatar.orientation.w
+ " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w);
}
MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
if (wantDebugging) {
print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch);
}
MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
}
// Map the mouse events to our functions
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
// disable the standard application for mouse events
Controller.captureMouseEvents();
function scriptEnding() {
// re-enabled the standard application for mouse events
Controller.releaseMouseEvents();
}
MyAvatar.bodyYaw = 0;
MyAvatar.bodyPitch = 0;
MyAvatar.bodyRoll = 0;
// would be nice to change to update
Script.willSendVisualDataCallback.connect(update);
Script.scriptEnding.connect(scriptEnding);

86
examples/lookWithTouch.js Normal file
View file

@ -0,0 +1,86 @@
//
// lookWithTouch.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/28/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates use of the Controller class
//
//
var lastX = 0;
var lastY = 0;
var yawFromMouse = 0;
var pitchFromMouse = 0;
var wantDebugging = false;
function touchBeginEvent(event) {
if (wantDebugging) {
print("touchBeginEvent event.x,y=" + event.x + ", " + event.y);
}
lastX = event.x;
lastY = event.y;
}
function touchEndEvent(event) {
if (wantDebugging) {
print("touchEndEvent event.x,y=" + event.x + ", " + event.y);
}
}
function touchUpdateEvent(event) {
if (wantDebugging) {
print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y);
}
var MOUSE_YAW_SCALE = -0.25;
var MOUSE_PITCH_SCALE = -12.5;
var FIXED_MOUSE_TIMESTEP = 0.016;
yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
lastX = event.x;
lastY = event.y;
}
function update() {
// rotate body yaw for yaw received from mouse
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
if (wantDebugging) {
print("changing orientation"
+ " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + ","
+ MyAvatar.orientation.z + "," + MyAvatar.orientation.w
+ " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w);
}
MyAvatar.orientation = newOrientation;
yawFromMouse = 0;
// apply pitch from mouse
var newPitch = MyAvatar.headPitch + pitchFromMouse;
if (wantDebugging) {
print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch);
}
MyAvatar.headPitch = newPitch;
pitchFromMouse = 0;
}
// Map the mouse events to our functions
Controller.touchBeginEvent.connect(touchBeginEvent);
Controller.touchUpdateEvent.connect(touchUpdateEvent);
Controller.touchEndEvent.connect(touchEndEvent);
// disable the standard application for mouse events
Controller.captureTouchEvents();
function scriptEnding() {
// re-enabled the standard application for mouse events
Controller.releaseTouchEvents();
}
MyAvatar.bodyYaw = 0;
MyAvatar.bodyPitch = 0;
MyAvatar.bodyRoll = 0;
// would be nice to change to update
Script.willSendVisualDataCallback.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -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);

View file

@ -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);

View file

@ -104,8 +104,9 @@ function moveBird() {
// check to see if we've been running long enough that our bird is dead
var nowTimeInSeconds = new Date().getTime() / 1000;
if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) {
print("This bird has died, how sad.");
Agent.stop();
print("our bird is dying, stop our script");
Script.stop();
return;
}
@ -193,4 +194,4 @@ function moveBird() {
}
// register the call back so it fires before each data send
Agent.willSendVisualDataCallback.connect(moveBird);
Script.willSendVisualDataCallback.connect(moveBird);

View 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);

View file

@ -17,4 +17,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
Agent.willSendVisualDataCallback.connect(maybePlaySound);
Script.willSendVisualDataCallback.connect(maybePlaySound);

View 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);

View file

@ -0,0 +1,246 @@
//
// spaceInvadersExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/30/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates a simple space invaders style of game
//
var iteration = 0;
var invaderStepsPerCycle = 30; // the number of update steps it takes then invaders to move one column to the right
var invaderStepOfCycle = 0; // current iteration in the cycle
var invaderMoveDirection = 1; // 1 for moving to right, -1 for moving to left
var itemLifetimes = 60;
var gameAt = { x: 10, y: 0, z: 10 };
var gameSize = { x: 10, y: 20, z: 1 };
var middleX = gameAt.x + (gameSize.x/2);
var middleY = gameAt.y + (gameSize.y/2);
var shipSize = 0.2;
var missileSize = 0.1;
var myShip;
var myShipProperties;
// create the rows of space invaders
var invaders = new Array();
var numberOfRows = 5;
var invadersPerRow = 8;
var emptyColumns = 2; // number of invader width columns not filled with invaders
var invadersBottomCorner = { x: gameAt.x, y: middleY , z: gameAt.z };
var rowHeight = ((gameAt.y + gameSize.y) - invadersBottomCorner.y) / numberOfRows;
var columnWidth = gameSize.x / (invadersPerRow + emptyColumns);
var missileFired = false;
var myMissile;
function initializeMyShip() {
myShipProperties = {
position: { x: middleX , y: gameAt.y, z: gameAt.z },
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: 0, z: 0 },
damping: 0,
radius: shipSize,
color: { red: 0, green: 255, blue: 0 },
lifetime: itemLifetimes
};
myShip = Particles.addParticle(myShipProperties);
}
// calculate the correct invaderPosition for an column row
function getInvaderPosition(row, column) {
var xMovePart = 0;
var xBasePart = invadersBottomCorner.x + (column * columnWidth);
if (invaderMoveDirection > 0) {
xMovePart = (invaderMoveDirection * columnWidth * (invaderStepOfCycle/invaderStepsPerCycle));
} else {
xMovePart = columnWidth + (invaderMoveDirection * columnWidth * (invaderStepOfCycle/invaderStepsPerCycle));
}
var invaderPosition = {
x: xBasePart + xMovePart,
y: invadersBottomCorner.y + (row * rowHeight),
z: invadersBottomCorner.z };
return invaderPosition;
}
function initializeInvaders() {
for (var row = 0; row < numberOfRows; row++) {
invaders[row] = new Array();
for (var column = 0; column < invadersPerRow; column++) {
invaderPosition = getInvaderPosition(row, column);
invaders[row][column] = Particles.addParticle({
position: invaderPosition,
velocity: { x: 0, y: 0, z: 0 },
gravity: { x: 0, y: 0, z: 0 },
damping: 0,
radius: shipSize,
color: { red: 255, green: 0, blue: 0 },
lifetime: itemLifetimes
});
print("invaders[row][column].creatorTokenID=" + invaders[row][column].creatorTokenID);
}
}
}
function moveInvaders() {
print("moveInvaders()...");
for (var row = 0; row < numberOfRows; row++) {
for (var column = 0; column < invadersPerRow; column++) {
props = Particles.getParticleProperties(invaders[row][column]);
if (props.isKnownID) {
invaderPosition = getInvaderPosition(row, column);
Particles.editParticle(invaders[row][column], { position: invaderPosition });
}
}
}
}
function update() {
print("updating space invaders... iteration="+iteration);
iteration++;
invaderStepOfCycle++;
if (invaderStepOfCycle > invaderStepsPerCycle) {
invaderStepOfCycle = 0;
if (invaderMoveDirection > 0) {
invaderMoveDirection = -1;
} else {
invaderMoveDirection = 1;
}
}
moveInvaders();
}
// register the call back so it fires before each data send
Script.willSendVisualDataCallback.connect(update);
function cleanupGame() {
print("cleaning up game...");
Particles.deleteParticle(myShip);
print("cleanupGame() ... Particles.deleteParticle(myShip)... myShip.id="+myShip.id);
for (var row = 0; row < numberOfRows; row++) {
for (var column = 0; column < invadersPerRow; column++) {
Particles.deleteParticle(invaders[row][column]);
print("cleanupGame() ... Particles.deleteParticle(invaders[row][column])... invaders[row][column].id="
+invaders[row][column].id);
}
}
// clean up our missile
if (missileFired) {
Particles.deleteParticle(myMissile);
}
Script.stop();
}
Script.scriptEnding.connect(cleanupGame);
function endGame() {
print("ending game...");
Script.stop();
}
function moveShipTo(position) {
myShip = Particles.identifyParticle(myShip);
Particles.editParticle(myShip, { position: position });
}
function fireMissile() {
// we only allow one missile at a time...
var canFire = false;
// If we've fired a missile, then check to see if it's still alive
if (missileFired) {
var missileProperties = Particles.getParticleProperties(myMissile);
print("missileProperties.isKnownID=" + missileProperties.isKnownID);
if (!missileProperties.isKnownID) {
print("canFire = true");
canFire = true;
}
} else {
canFire = true;
}
if (canFire) {
print("firing missile");
var missilePosition = { x: myShipProperties.position.x,
y: myShipProperties.position.y + (2* shipSize),
z: myShipProperties.position.z };
myMissile = Particles.addParticle(
{
position: missilePosition,
velocity: { x: 0, y: 5, z: 0},
gravity: { x: 0, y: 0, z: 0 },
damping: 0,
radius: missileSize,
color: { red: 0, green: 0, blue: 255 },
lifetime: 5
});
missileFired = true;
}
}
function keyPressEvent(key) {
//print("keyPressEvent key.text="+key.text);
if (key.text == ",") {
myShipProperties.position.x -= 0.1;
if (myShipProperties.position.x < gameAt.x) {
myShipProperties.position.x = gameAt.x;
}
moveShipTo(myShipProperties.position);
} else if (key.text == ".") {
myShipProperties.position.x += 0.1;
if (myShipProperties.position.x > gameAt.x + gameSize.x) {
myShipProperties.position.x = gameAt.x + gameSize.x;
}
moveShipTo(myShipProperties.position);
} else if (key.text == " ") {
fireMissile();
} else if (key.text == "q") {
endGame();
}
}
// remap the keys...
Controller.keyPressEvent.connect(keyPressEvent);
Controller.captureKeyEvents({text: " "});
function deleteIfInvader(possibleInvaderParticle) {
for (var row = 0; row < numberOfRows; row++) {
for (var column = 0; column < invadersPerRow; column++) {
invaders[row][column] = Particles.identifyParticle(invaders[row][column]);
if (invaders[row][column].isKnownID) {
if (invaders[row][column].id == possibleInvaderParticle.id) {
Particles.deleteParticle(possibleInvaderParticle);
Particles.deleteParticle(myMissile);
}
}
}
}
}
function particleCollisionWithParticle(particleA, particleB) {
print("particleCollisionWithParticle() a.id="+particleA.id + " b.id=" + particleB.id);
if (missileFired) {
myMissile = Particles.identifyParticle(myMissile);
if (myMissile.id == particleA.id) {
deleteIfInvader(particleB);
} else if (myMissile.id == particleB.id) {
deleteIfInvader(particleA);
}
}
}
Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
// initialize the game...
initializeMyShip();
initializeInvaders();

8
examples/timer.js Normal file
View file

@ -0,0 +1,8 @@
var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 10000);
var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000);
// this would stop a scheduled single shot timer
Script.clearTimeout(one_timer);
// this stops the repeating timer
Script.clearInterval(multiple_timer);

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}

Binary file not shown.

View file

@ -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; };

File diff suppressed because it is too large Load diff

View file

@ -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();
@ -283,10 +282,8 @@ private:
void updateSixense(float deltaTime);
void updateSerialDevices(float deltaTime);
void updateThreads(float deltaTime);
void updateMyAvatarSimulation(float deltaTime);
void updateParticles(float deltaTime);
void updateMetavoxels(float deltaTime);
void updateTransmitter(float deltaTime);
void updateCamera(float deltaTime);
void updateDialogs(float deltaTime);
void updateAudio(float deltaTime);
@ -298,8 +295,8 @@ private:
void renderLookatIndicator(glm::vec3 pointOfInterest);
void renderHighlightVoxel(VoxelDetail voxel);
void updateAvatar(float deltaTime);
void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
void updateMyAvatar(float deltaTime);
void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions);
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
glm::vec3 getSunDirection();
@ -356,7 +353,7 @@ private:
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter _voxelImporter;
VoxelImporter* _voxelImporter;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
@ -375,10 +372,8 @@ private:
VoxelQuery _voxelQuery; // NodeData derived class for querying voxels from voxel server
AvatarManager _avatarManager;
MyAvatar _myAvatar; // The rendered avatar of oneself
Profile _profile; // The data-server linked profile for this user
Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
Profile _profile; // The data-server linked profile for this user
Faceshift _faceshift;
@ -398,13 +393,11 @@ private:
Environment _environment;
int _headMouseX, _headMouseY;
int _mouseX;
int _mouseY;
int _mouseDragStartedX;
int _mouseDragStartedY;
uint64_t _lastMouseMove;
quint64 _lastMouseMove;
bool _mouseHidden;
bool _seenMouseMove;
@ -418,8 +411,6 @@ private:
float _touchDragStartedAvgX;
float _touchDragStartedAvgY;
bool _isTouchPressed; // true if multitouch has been pressed (clear when finished)
float _yawFromTouch;
float _pitchFromTouch;
VoxelDetail _mouseVoxelDragging;
bool _mousePressed; // true if mouse has been pressed (clear when finished)
@ -444,9 +435,6 @@ private:
bool _lookingAwayFromOrigin;
glm::vec3 _nudgeGuidePosition;
glm::vec3 _transmitterPickStart;
glm::vec3 _transmitterPickEnd;
ChatEntry _chatEntry; // chat entry field
bool _chatEntryOn; // Whether to show the chat entry
@ -482,9 +470,8 @@ private:
PieMenu _pieMenu;
int parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
int parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderAddress);
void trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
@ -505,6 +492,7 @@ private:
void checkVersion();
void displayUpdateDialog();
bool shouldSkipVersion(QString latestVersion);
void takeSnapshot();
};
#endif /* defined(__interface__Application__) */

View file

@ -22,7 +22,6 @@
#include <AngleUtil.h>
#include <NodeList.h>
#include <NodeTypes.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <StdDev.h>
@ -286,8 +285,8 @@ void Audio::start() {
void Audio::handleAudioInput() {
static char monoAudioDataPacket[MAX_PACKET_SIZE];
static int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO);
static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + NUM_BYTES_RFC4122_UUID;
static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat);
static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes);
@ -366,7 +365,7 @@ void Audio::handleAudioInput() {
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
NodeList* nodeList = NodeList::getInstance();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) {
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
@ -376,16 +375,10 @@ void Audio::handleAudioInput() {
// we need the amount of bytes in the buffer + 1 for type
// + 12 for 3 floats for position + float for bearing + 1 attenuation byte
PACKET_TYPE packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
? PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO : PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO;
PacketType packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
? PacketTypeMicrophoneAudioWithEcho : PacketTypeMicrophoneAudioNoEcho;
char* currentPacketPtr = monoAudioDataPacket + populateTypeAndVersion((unsigned char*) monoAudioDataPacket,
packetType);
// pack Source Data
QByteArray rfcUUID = NodeList::getInstance()->getOwnerUUID().toRfc4122();
memcpy(currentPacketPtr, rfcUUID.constData(), rfcUUID.size());
currentPacketPtr += rfcUUID.size();
char* currentPacketPtr = monoAudioDataPacket + populatePacketHeader(monoAudioDataPacket, packetType);
// memcpy the three float positions
memcpy(currentPacketPtr, &headPosition, sizeof(headPosition));
@ -434,7 +427,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
}
}
_ringBuffer.parseData((unsigned char*) audioByteArray.data(), audioByteArray.size());
_ringBuffer.parseData(audioByteArray);
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
@ -546,7 +539,7 @@ void Audio::toggleMute() {
}
void Audio::render(int screenWidth, int screenHeight) {
if (_audioInput) {
if (_audioInput && _audioOutput) {
glLineWidth(2.0);
glBegin(GL_LINES);
glColor3f(.93f, .93f, .93f);

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -26,52 +26,53 @@ void DatagramProcessor::processDatagrams() {
"DatagramProcessor::processDatagrams()");
HifiSockAddr senderSockAddr;
ssize_t bytesReceived;
static unsigned char incomingPacket[MAX_PACKET_SIZE];
static QByteArray incomingPacket;
Application* application = Application::getInstance();
NodeList* nodeList = NodeList::getInstance();
while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams() &&
(bytesReceived = NodeList::getInstance()->getNodeSocket().readDatagram((char*) incomingPacket,
MAX_PACKET_SIZE,
senderSockAddr.getAddressPointer(),
senderSockAddr.getPortPointer()))) {
while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams()) {
incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
_packetCount++;
_byteCount += bytesReceived;
_byteCount += incomingPacket.size();
if (packetVersionMatch(incomingPacket, senderSockAddr)) {
if (packetVersionMatch(incomingPacket)) {
// only process this packet if we have a match on the packet version
switch (incomingPacket[0]) {
case PACKET_TYPE_TRANSMITTER_DATA_V2:
switch (packetTypeForPacket(incomingPacket)) {
case PacketTypeTransmitterData:
// V2 = IOS transmitter app
application->_myTransmitter.processIncomingData(incomingPacket, bytesReceived);
application->getAvatar()->getTransmitter().processIncomingData(reinterpret_cast<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;
}
}
}
}
}

View file

@ -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) {

View file

@ -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:

View file

@ -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);
}

View file

@ -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; };

View file

@ -10,7 +10,6 @@
#include <QStandardPaths>
#include <QGridLayout>
#include <QSplitter>
#include <QApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
@ -20,38 +19,38 @@ 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>");
"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 {
switchToResourcesParentIfRequired();
// types
// Computer, Desktop, Trashcan, Network, Drive, Folder, File
QString typeString;
switch (type) {
case QFileIconProvider::Computer:
typeString = "computer";
break;
case QFileIconProvider::Desktop:
typeString = "desktop";
break;
case QFileIconProvider::Trashcan:
case QFileIconProvider::Network:
case QFileIconProvider::Drive:
case QFileIconProvider::Folder:
typeString = "folder";
break;
default:
typeString = "file";
break;
@ -63,7 +62,7 @@ QIcon HiFiIconProvider::icon(QFileIconProvider::IconType type) const {
QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
switchToResourcesParentIfRequired();
const QString ext = info.suffix().toLower();
if (info.isDir()) {
if (info.absoluteFilePath() == QDir::homePath()) {
return QIcon("resources/icons/home.svg");
@ -74,12 +73,12 @@ QIcon HiFiIconProvider::icon(const QFileInfo &info) const {
}
return QIcon("resources/icons/folder.svg");
}
QFileInfo iconFile("resources/icons/" + iconsMap[ext]);
if (iconFile.exists() && iconFile.isFile()) {
return QIcon(iconFile.filePath());
}
return QIcon("resources/icons/file.svg");
}
@ -98,8 +97,9 @@ QString HiFiIconProvider::type(const QFileInfo &info) const {
ImportDialog::ImportDialog(QWidget* parent) :
QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, NULL),
_importButton(IMPORT_BUTTON_NAME, this),
_cancelButton(CANCEL_BUTTON_NAME, this) {
_cancelButton(CANCEL_BUTTON_NAME, this),
fileAccepted(false) {
setOption(QFileDialog::DontUseNativeDialog, true);
setFileMode(QFileDialog::ExistingFile);
setViewMode(QFileDialog::Detail);
@ -111,16 +111,18 @@ ImportDialog::ImportDialog(QWidget* parent) :
#endif
QLabel* infoLabel = new QLabel(QString(INFO_LABEL_TEXT).arg(cmdString));
infoLabel->setObjectName("infoLabel");
QGridLayout* gridLayout = (QGridLayout*) layout();
gridLayout->addWidget(infoLabel, 2, 0, 2, 1);
gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1);
gridLayout->addWidget(&_importButton, 2, 2, 2, 1);
setImportTypes();
setLayout();
connect(&_importButton, SIGNAL(pressed()), SLOT(import()));
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
connect(&_cancelButton, SIGNAL(pressed()), SLOT(close()));
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
}
@ -130,12 +132,22 @@ ImportDialog::~ImportDialog() {
}
void ImportDialog::import() {
fileAccepted = true;
emit accepted();
close();
}
void ImportDialog::accept() {
QFileDialog::accept();
// do nothing if import is not enable
if (!_importButton.isEnabled()) {
return;
}
if (!fileAccepted) {
fileAccepted = true;
emit accepted();
} else {
QFileDialog::accept();
}
}
void ImportDialog::reject() {
@ -163,72 +175,68 @@ void ImportDialog::saveCurrentFile(QString filename) {
}
void ImportDialog::setLayout() {
// set ObjectName used in qss for styling
_importButton.setObjectName("importButton");
_cancelButton.setObjectName("cancelButton");
// set fixed size
_importButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
_cancelButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// hide unused embedded widgets in QFileDialog
QWidget* widget = findChild<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);
// remove reference to treeView
widget = NULL;
widget->deleteLater();
switchToResourcesParentIfRequired();
QFile styleSheet("resources/styles/import_dialog.qss");
if (styleSheet.open(QIODevice::ReadOnly)) {
setStyleSheet(styleSheet.readAll());
}
}
void ImportDialog::setImportTypes() {
@ -251,7 +259,6 @@ void ImportDialog::setImportTypes() {
QJsonObject fileFormatObject = fileFormat.toObject();
QString ext(fileFormatObject["extension"].toString());
QString description(fileFormatObject.value("description").toString());
QString icon(fileFormatObject.value("icon").toString());
if (formatsCounter > 0) {
@ -273,7 +280,7 @@ void ImportDialog::setImportTypes() {
// set custom file icons
setIconProvider(new HiFiIconProvider(iconsMap));
setNameFilter(importFormatsFilterList);
#ifdef Q_OS_MAC
QString cmdString = ("Command");
#else

View file

@ -56,6 +56,7 @@ private:
void setLayout();
void setImportTypes();
bool fileAccepted;
};
#endif /* defined(__hifi__ImportDialog__) */

View file

@ -116,10 +116,10 @@ Menu::Menu() :
this,
SLOT(goToLocation()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToUser,
MenuOption::GoTo,
Qt::Key_At,
this,
SLOT(goToUser()));
SLOT(goTo()));
addDisabledActionAndSeparator(fileMenu, "Settings");
@ -910,6 +910,60 @@ void Menu::goToDomain() {
sendFakeEnterEvent();
}
void Menu::goTo() {
QInputDialog gotoDialog(Application::getInstance()->getWindow());
gotoDialog.setWindowTitle("Go to");
gotoDialog.setLabelText("Destination:");
QString destination = Application::getInstance()->getProfile()->getUsername();
gotoDialog.setTextValue(destination);
gotoDialog.setWindowFlags(Qt::Sheet);
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
int dialogReturn = gotoDialog.exec();
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
destination = gotoDialog.textValue();
QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
const int NUMBER_OF_COORDINATE_ITEMS = 3;
const int X_ITEM = 0;
const int Y_ITEM = 1;
const int Z_ITEM = 2;
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble();
double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble();
double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble();
glm::vec3 newAvatarPos(x, y, z);
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
if (newAvatarPos != avatarPos) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
MyAvatar::sendKillAvatar();
qDebug("Going To Location: %f, %f, %f...", x, y, z);
myAvatar->setPosition(newAvatarPos);
}
} else {
// there's a username entered by the user, make a request to the data-server
DataServerClient::getValuesForKeysAndUserString(
QStringList()
<< DataServerKey::Domain
<< DataServerKey::Position
<< DataServerKey::Orientation,
destination, Application::getInstance()->getProfile());
}
}
sendFakeEnterEvent();
}
void Menu::goToLocation() {
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
@ -954,26 +1008,6 @@ void Menu::goToLocation() {
sendFakeEnterEvent();
}
void Menu::goToUser() {
QInputDialog userDialog(Application::getInstance()->getWindow());
userDialog.setWindowTitle("Go to User");
userDialog.setLabelText("Destination user:");
QString username = Application::getInstance()->getProfile()->getUsername();
userDialog.setTextValue(username);
userDialog.setWindowFlags(Qt::Sheet);
userDialog.resize(userDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, userDialog.size().height());
int dialogReturn = userDialog.exec();
if (dialogReturn == QDialog::Accepted && !userDialog.textValue().isEmpty()) {
// there's a username entered by the user, make a request to the data-server
DataServerClient::getValuesForKeysAndUserString(
QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation,
userDialog.textValue(), Application::getInstance()->getProfile());
}
sendFakeEnterEvent();
}
void Menu::pasteToVoxel() {
QInputDialog pasteToOctalCodeDialog(Application::getInstance()->getWindow());
pasteToOctalCodeDialog.setWindowTitle("Paste to Voxel");
@ -1137,3 +1171,14 @@ void Menu::updateFrustumRenderModeAction() {
}
}
QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
int lastIndex;
lastIndex = string.lastIndexOf(search);
if (lastIndex > 0) {
lastIndex = string.lastIndexOf(search);
string.replace(lastIndex, 1, replace);
}
return string;
}

View file

@ -92,7 +92,7 @@ public slots:
void saveSettings(QSettings* settings = NULL);
void importSettings();
void exportSettings();
void goToUser();
void goTo();
void pasteToVoxel();
private slots:
@ -152,6 +152,7 @@ private:
QAction* _useVoxelShader;
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
};
namespace MenuOption {
@ -209,7 +210,7 @@ namespace MenuOption {
const QString GlowMode = "Cycle Glow Mode";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
const QString GoToUser = "Go To User...";
const QString GoTo = "Go To...";
const QString ImportVoxels = "Import Voxels";
const QString ImportVoxelsClipboard = "Import Voxels to Clipboard";
const QString IncreaseAvatarSize = "Increase Avatar Size";

View file

@ -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()) {

View file

@ -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;
};

View file

@ -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();
}
}
}

View file

@ -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__) */

View file

@ -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)

View file

@ -54,8 +54,6 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 safeEulerAngles(const glm::quat& q);
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
glm::vec3 extractTranslation(const glm::mat4& matrix);

View file

@ -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);
}
}

View file

@ -24,15 +24,15 @@ private:
const QString SETTINGS_GROUP_NAME = "VoxelImport";
const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings";
VoxelImporter::VoxelImporter(QWidget* parent)
: QObject(parent),
_voxelTree(true),
_importDialog(parent),
_currentTask(NULL),
_nextTask(NULL) {
connect(&_importDialog, SIGNAL(currentChanged(QString)), SLOT(preImport()));
connect(&_importDialog, SIGNAL(accepted()), SLOT(import()));
VoxelImporter::VoxelImporter(QWidget* parent) :
QObject(parent),
_voxelTree(true),
_importDialog(parent),
_currentTask(NULL),
_nextTask(NULL)
{
connect(&_importDialog, &QFileDialog::currentChanged, this, &VoxelImporter::preImport);
connect(&_importDialog, &QFileDialog::accepted, this, &VoxelImporter::import);
}
void VoxelImporter::saveSettings(QSettings* settings) {
@ -102,7 +102,22 @@ int VoxelImporter::preImport() {
if (!QFileInfo(filename).isFile()) {
return 0;
}
_filename = filename;
if (_nextTask) {
delete _nextTask;
}
_nextTask = new ImportTask(_filename);
connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
if (_currentTask != NULL) {
_voxelTree.cancelImport();
} else {
launchTask();
}
return 1;
}

View file

@ -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;
}

View file

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

View file

@ -16,7 +16,6 @@
#include <PerfStat.h>
#include <SharedUtil.h>
#include <NodeList.h>
#include <NodeTypes.h>
#include "Application.h"
#include "CoverageMap.h"
@ -550,17 +549,17 @@ bool VoxelSystem::readFromSchematicFile(const char* filename) {
return result;
}
int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
int VoxelSystem::parseData(const QByteArray& packet) {
bool showTimingDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData()",showTimingDetails);
unsigned char command = *sourceBuffer;
int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
PacketType command = packetTypeForPacket(packet);
int numBytesPacketHeader = numBytesForPacketHeader(packet);
switch(command) {
case PACKET_TYPE_VOXEL_DATA: {
PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() PACKET_TYPE_VOXEL_DATA part...",showTimingDetails);
unsigned char* dataAt = sourceBuffer + numBytesPacketHeader;
case PacketTypeVoxelData: {
PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() PacketType_VOXEL_DATA part...",showTimingDetails);
const unsigned char* dataAt = reinterpret_cast<const unsigned char*>(packet.data()) + numBytesPacketHeader;
VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt));
dataAt += sizeof(VOXEL_PACKET_FLAGS);
@ -577,7 +576,7 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
int flightTime = arrivedAt - sentAt;
VOXEL_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0;
int dataBytes = numBytes - VOXEL_PACKET_HEADER_SIZE;
int dataBytes = packet.size() - VOXEL_PACKET_HEADER_SIZE;
int subsection = 1;
while (dataBytes > 0) {
@ -605,7 +604,8 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
" color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d"
" subsection:%d sectionLength:%d uncompressed:%d",
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
sequence, flightTime, numBytes, dataBytes, subsection, sectionLength, packetData.getUncompressedSize());
sequence, flightTime, packet.size(), dataBytes, subsection, sectionLength,
packetData.getUncompressedSize());
}
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
unlockTree();
@ -616,7 +616,8 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
}
subsection++;
}
break;
default:
break;
}
if (!_useFastVoxelPipeline || _writeRenderFullVBO) {
setupNewVoxelsForDrawing();
@ -624,9 +625,9 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY);
}
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(numBytes);
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(packet.size());
return numBytes;
return packet.size();
}
void VoxelSystem::setupNewVoxelsForDrawing() {
@ -637,8 +638,8 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
return; // bail early if we're not initialized
}
uint64_t start = usecTimestampNow();
uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
quint64 start = usecTimestampNow();
quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) {
@ -684,7 +685,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
_bufferWriteLock.unlock();
uint64_t end = usecTimestampNow();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
@ -701,8 +702,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode() xxxxx");
uint64_t start = usecTimestampNow();
uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
quint64 start = usecTimestampNow();
quint64 sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (allowBailEarly && !iAmDebugging &&
@ -727,7 +728,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
_bufferWriteLock.unlock();
uint64_t end = usecTimestampNow();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
@ -736,14 +737,14 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
uint64_t start = usecTimestampNow();
quint64 start = usecTimestampNow();
// track how long its been since we were last moving. If we have recently moved then only use delta frustums, if
// it's been a long time since we last moved, then go ahead and do a full frustum cull.
if (isViewChanging()) {
_lastViewIsChanging = start;
}
uint64_t sinceLastMoving = (start - _lastViewIsChanging) / 1000;
quint64 sinceLastMoving = (start - _lastViewIsChanging) / 1000;
bool enoughTime = (sinceLastMoving >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS));
// These has changed events will occur before we stop. So we need to remember this for when we finally have stopped
@ -765,7 +766,7 @@ void VoxelSystem::checkForCulling() {
hideOutOfView(forceFullFrustum);
if (forceFullFrustum) {
uint64_t endViewCulling = usecTimestampNow();
quint64 endViewCulling = usecTimestampNow();
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
}
@ -774,7 +775,7 @@ void VoxelSystem::checkForCulling() {
// VBO reubuilding. Possibly we should do this only if our actual VBO usage crosses some lower boundary.
cleanupRemovedVoxels();
uint64_t sinceLastAudit = (start - _lastAudit) / 1000;
quint64 sinceLastAudit = (start - _lastAudit) / 1000;
if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) {
if (sinceLastAudit >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) {
@ -1590,7 +1591,7 @@ void VoxelSystem::falseColorizeBySource() {
// create a bunch of colors we'll use during colorization
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
if (node->getType() == NodeType::VoxelServer) {
uint16_t nodeID = VoxelTreeElement::getSourceNodeUUIDKey(node->getUUID());
int groupColor = voxelServerCount % NUMBER_OF_COLOR_GROUPS;
args.colors[nodeID] = groupColors[groupColor];
@ -2662,7 +2663,7 @@ void VoxelSystem::falseColorizeOccludedV2() {
}
void VoxelSystem::nodeAdded(SharedNodePointer node) {
if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
if (node->getType() == NodeType::VoxelServer) {
qDebug("VoxelSystem... voxel server %s added...", node->getUUID().toString().toLocal8Bit().constData());
_voxelServerCount++;
}
@ -2683,7 +2684,7 @@ bool VoxelSystem::killSourceVoxelsOperation(OctreeElement* element, void* extraD
}
void VoxelSystem::nodeKilled(SharedNodePointer node) {
if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
if (node->getType() == NodeType::VoxelServer) {
_voxelServerCount--;
QUuid nodeUUID = node->getUUID();
qDebug("VoxelSystem... voxel server %s removed...", nodeUUID.toString().toLocal8Bit().constData());

View file

@ -49,7 +49,7 @@ public:
void setDataSourceUUID(const QUuid& dataSourceUUID) { _dataSourceUUID = dataSourceUUID; }
const QUuid& getDataSourceUUID() const { return _dataSourceUUID; }
int parseData(unsigned char* sourceBuffer, int numBytes);
int parseData(const QByteArray& packet);
virtual void init();
void simulate(float deltaTime) { }
@ -229,10 +229,10 @@ private:
bool _readRenderFullVBO;
int _setupNewVoxelsForDrawingLastElapsed;
uint64_t _setupNewVoxelsForDrawingLastFinished;
uint64_t _lastViewCulling;
uint64_t _lastViewIsChanging;
uint64_t _lastAudit;
quint64 _setupNewVoxelsForDrawingLastFinished;
quint64 _lastViewCulling;
quint64 _lastViewIsChanging;
quint64 _lastAudit;
int _lastViewCullingElapsed;
bool _hasRecentlyChanged;

View file

@ -12,7 +12,6 @@
#include <glm/gtx/vector_angle.hpp>
#include <NodeList.h>
#include <NodeTypes.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
@ -73,7 +72,6 @@ Avatar::Avatar() :
_worldUpDirection(DEFAULT_UP_DIRECTION),
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
_mouseRayDirection(0.0f, 0.0f, 0.0f),
_isCollisionsOn(true),
_moving(false),
_owningAvatarMixer(),
_initialized(false)
@ -108,7 +106,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
return computeRotationFromBodyToWorldUp() * getOrientation();
}
void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
void Avatar::simulate(float deltaTime) {
if (_scale != _targetScale) {
setScale(_targetScale);
}
@ -336,11 +334,11 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi
return false;
}
int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) {
int Avatar::parseData(const QByteArray& packet) {
// change in position implies movement
glm::vec3 oldPosition = _position;
int bytesRead = AvatarData::parseData(sourceBuffer, numBytes);
int bytesRead = AvatarData::parseData(packet);
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
@ -396,30 +394,6 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
glEnd();
}
void Avatar::goHome() {
qDebug("Going Home!");
setPosition(START_LOCATION);
}
void Avatar::increaseSize() {
if ((1.f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
_targetScale *= (1.f + SCALING_RATIO);
qDebug("Changed scale to %f", _targetScale);
}
}
void Avatar::decreaseSize() {
if (MIN_AVATAR_SCALE < (1.f - SCALING_RATIO) * _targetScale) {
_targetScale *= (1.f - SCALING_RATIO);
qDebug("Changed scale to %f", _targetScale);
}
}
void Avatar::resetSize() {
_targetScale = 1.0f;
qDebug("Reseted scale to %f", _targetScale);
}
void Avatar::setScale(float scale) {
_scale = scale;

View file

@ -20,7 +20,6 @@
#include "InterfaceConfig.h"
#include "SkeletonModel.h"
#include "world.h"
#include "devices/Transmitter.h"
static const float SCALING_RATIO = .05f;
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
@ -73,7 +72,7 @@ public:
~Avatar();
void init();
void simulate(float deltaTime, Transmitter* transmitter);
void simulate(float deltaTime);
void render(bool forceRenderHead);
//setters
@ -114,20 +113,10 @@ public:
virtual bool isMyAvatar() { return false; }
int parseData(unsigned char* sourceBuffer, int numBytes);
int parseData(const QByteArray& packet);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
public slots:
void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
void goHome();
void increaseSize();
void decreaseSize();
void resetSize();
friend class MyAvatar;
protected:
Head _head;
Hand _hand;
@ -142,7 +131,6 @@ protected:
glm::vec3 _worldUpDirection;
glm::vec3 _mouseRayOrigin;
glm::vec3 _mouseRayDirection;
bool _isCollisionsOn;
float _stringLength;
bool _moving; ///< set when position is changing
QWeakPointer<Node> _owningAvatarMixer;

View file

@ -16,65 +16,49 @@
#include "AvatarManager.h"
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key
AvatarManager::AvatarManager(QObject* parent) :
_lookAtTargetAvatar(),
_lookAtOtherPosition(),
_lookAtIndicatorScale(1.0f),
_avatarHash(),
_avatarFades()
{
_avatarFades() {
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
_myAvatar = QSharedPointer<MyAvatar>(new MyAvatar());
}
void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateLookatTargetAvatar()");
Application* applicationInstance = Application::getInstance();
if (!applicationInstance->isMousePressed()) {
foreach (const AvatarSharedPointer& avatar, _avatarHash) {
float distance;
if (avatar->findRayIntersection(applicationInstance->getMouseRayOrigin(),
applicationInstance->getMouseRayDirection(), distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
(avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
_lookAtIndicatorScale = avatar->getHead().getScale();
_lookAtOtherPosition = avatar->getHead().getPosition();
_lookAtTargetAvatar = avatar;
// found the look at target avatar, return
return;
}
}
_lookAtTargetAvatar.clear();
}
void AvatarManager::init() {
_myAvatar->init();
_myAvatar->setPosition(START_LOCATION);
_myAvatar->setDisplayingLookatVectors(false);
_avatarHash.insert(MY_AVATAR_KEY, _myAvatar);
}
void AvatarManager::updateAvatars(float deltaTime) {
void AvatarManager::updateOtherAvatars(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateAvatars()");
Application* applicationInstance = Application::getInstance();
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
// simulate avatars
AvatarHash::iterator avatar = _avatarHash.begin();
if (avatar != _avatarHash.end()) {
if (avatar->data()->getOwningAvatarMixer()) {
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
//updateMyAvatar(deltaTime);
++avatarIterator;
continue;
}
if (avatar->getOwningAvatarMixer()) {
// this avatar's mixer is still around, go ahead and simulate it
avatar->data()->simulate(deltaTime, NULL);
Application* applicationInstance = Application::getInstance();
avatar->data()->setMouseRay(applicationInstance->getMouseRayOrigin(),
applicationInstance->getMouseRayDirection());
avatar->simulate(deltaTime);
avatar->setMouseRay(mouseOrigin, mouseDirection);
++avatarIterator;
} else {
// the mixer that owned this avatar is gone, give it to the vector of fades and kill it
avatar = removeAvatarAtHashIterator(avatar);
avatarIterator = erase(avatarIterator);
}
}
@ -88,41 +72,44 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
}
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
if (!selfAvatarOnly) {
// Render avatars of other nodes
foreach (const AvatarSharedPointer& avatar, _avatarHash) {
foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) {
Avatar* avatar = static_cast<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);
++fadingIterator;
}
}
}
@ -132,29 +119,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 +158,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 +174,11 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
if (!matchingAvatar) {
// construct a new Avatar for this node
matchingAvatar = AvatarSharedPointer(new Avatar());
matchingAvatar->setOwningAvatarMixer(mixerWeakPointer);
Avatar* avatar = new Avatar();
avatar->setOwningAvatarMixer(mixerWeakPointer);
// insert the new avatar into our hash
matchingAvatar = AvatarSharedPointer(avatar);
_avatarHash.insert(nodeUUID, matchingAvatar);
// new UUID requires mesh and skeleton request to data-server
@ -197,37 +189,44 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
}
// copy the rest of the packet to the avatarData holder so we can read the next Avatar from there
memcpy(avatarData + numBytesDummyPacketHeader, packetData + bytesRead, datagram.size() - bytesRead);
dummyAvatarByteArray.resize(numDummyHeaderBytesWithoutUUID);
// make this Avatar's UUID the UUID in the packet and tack the remaining data onto the end
dummyAvatarByteArray.append(datagram.mid(bytesRead));
// have the matching (or new) avatar parse the data from the packet
bytesRead += matchingAvatar->parseData(avatarData, datagram.size() - bytesRead);
bytesRead += matchingAvatar->parseData(dummyAvatarByteArray) - numDummyHeaderBytesWithoutUUID;
}
}
void AvatarManager::processKillAvatar(const QByteArray& datagram) {
// read the node id
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(reinterpret_cast<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::clearOtherAvatars() {
// clear any avatars that came from an avatar-mixer
AvatarHash::iterator removeAvatar = _avatarHash.begin();
while (removeAvatar != _avatarHash.end()) {
removeAvatar = removeAvatarAtHashIterator(removeAvatar);
removeAvatar = erase(removeAvatar);
}
}
_myAvatar->clearLookAtTargetAvatar();
}

View file

@ -13,30 +13,27 @@
#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 init();
MyAvatar* getMyAvatar() { return _myAvatar.data(); }
const AvatarHash& getAvatarHash() { return _avatarHash; }
int size() const { return _avatarHash.size(); }
Avatar* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
void updateAvatars(float deltaTime);
void updateOtherAvatars(float deltaTime);
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
void clearHash();
void clearOtherAvatars();
public slots:
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
@ -44,17 +41,16 @@ public slots:
void processKillAvatar(const QByteArray& datagram);
private:
AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);
void renderAvatarFades();
AvatarHash::iterator removeAvatarAtHashIterator(const AvatarHash::iterator& iterator);
// virtual override
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
QWeakPointer<Avatar> _lookAtTargetAvatar;
glm::vec3 _lookAtOtherPosition;
float _lookAtIndicatorScale;
AvatarHash _avatarHash;
QVector<AvatarSharedPointer> _avatarFades;
QSharedPointer<MyAvatar> _myAvatar;
};
#endif /* defined(__hifi__AvatarManager__) */

View file

@ -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) {

View file

@ -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 {

View file

@ -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;

View file

@ -12,15 +12,18 @@
#include <glm/gtx/vector_angle.hpp>
#include <NodeList.h>
#include <NodeTypes.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include "Application.h"
#include "Audio.h"
#include "DataServerClient.h"
#include "Environment.h"
#include "Menu.h"
#include "MyAvatar.h"
#include "Physics.h"
#include "VoxelSystem.h"
#include "devices/Faceshift.h"
#include "devices/OculusManager.h"
#include "ui/TextRenderer.h"
@ -50,19 +53,32 @@ MyAvatar::MyAvatar() :
_elapsedTimeSinceCollision(0.0f),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
_isCollisionsOn(true),
_isThrustOn(false),
_thrustMultiplier(1.0f),
_moveTarget(0,0,0),
_moveTargetStepCounter(0)
_moveTargetStepCounter(0),
_lookAtTargetAvatar()
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
}
MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.clear();
}
void MyAvatar::reset() {
// TODO? resurrect headMouse stuff?
//_headMouseX = _glWidget->width() / 2;
//_headMouseY = _glWidget->height() / 2;
_head.reset();
_hand.reset();
setVelocity(glm::vec3(0,0,0));
setThrust(glm::vec3(0,0,0));
_transmitter.resetLevels();
}
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
@ -70,7 +86,89 @@ void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
_moveTargetStepCounter = 0;
}
void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
void MyAvatar::updateTransmitter(float deltaTime) {
// no transmitter drive implies transmitter pick
if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
_transmitterPickStart = getChestPosition();
glm::vec3 direction = getOrientation() * glm::quat(glm::radians(_transmitter.getEstimatedRotation())) * IDENTITY_FRONT;
// check against voxels, avatars
const float MAX_PICK_DISTANCE = 100.0f;
float minDistance = MAX_PICK_DISTANCE;
VoxelDetail detail;
float distance;
BoxFace face;
VoxelSystem* voxels = Application::getInstance()->getVoxels();
if (voxels->findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) {
minDistance = min(minDistance, distance);
}
_transmitterPickEnd = _transmitterPickStart + direction * minDistance;
} else {
_transmitterPickStart = _transmitterPickEnd = glm::vec3();
}
}
void MyAvatar::update(float deltaTime) {
updateTransmitter(deltaTime);
// TODO: resurrect touch interactions between avatars
//// rotate body yaw for yaw received from multitouch
//setOrientation(getOrientation() * glm::quat(glm::vec3(0, _yawFromTouch, 0)));
//_yawFromTouch = 0.f;
//
//// apply pitch from touch
//_head.setPitch(_head.getPitch() + _pitchFromTouch);
//_pitchFromTouch = 0.0f;
//
//float TOUCH_YAW_SCALE = -0.25f;
//float TOUCH_PITCH_SCALE = -12.5f;
//float FIXED_TOUCH_TIMESTEP = 0.016f;
//_yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * FIXED_TOUCH_TIMESTEP);
//_pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * FIXED_TOUCH_TIMESTEP);
// Update my avatar's state from gyros
updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
// Update head mouse from faceshift if active
Faceshift* faceshift = Application::getInstance()->getFaceshift();
if (faceshift->isActive()) {
glm::vec3 headVelocity = faceshift->getHeadAngularVelocity();
// TODO? resurrect headMouse stuff?
//// sets how quickly head angular rotation moves the head mouse
//const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.f;
//const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.f;
//_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE;
//_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE;
//
//// Constrain head-driven mouse to edges of screen
//_headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width());
//_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
}
if (OculusManager::isConnected()) {
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
_head.setYaw(yaw);
_head.setPitch(pitch);
_head.setRoll(roll);
}
// Get audio loudness data from audio input device
_head.setAudioLoudness(Application::getInstance()->getAudio()->getLastInputLoudness());
if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
} else {
setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
}
simulate(deltaTime);
}
void MyAvatar::simulate(float deltaTime) {
glm::quat orientation = getOrientation();
@ -92,7 +190,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
}
// Collect thrust forces from keyboard and devices
updateThrust(deltaTime, transmitter);
updateThrust(deltaTime);
// copy velocity so we can use it later for acceleration
glm::vec3 oldVelocity = getVelocity();
@ -238,7 +336,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
// Adjust body yaw by yaw from controller
setOrientation(glm::angleAxis(-euler.y, glm::vec3(0, 1, 0)) * getOrientation());
// Adjust head pitch from controller
getHead().setMousePitch(getHead().getMousePitch() - euler.x);
getHead().setPitch(getHead().getPitch() - euler.x);
_position += _velocity * deltaTime;
@ -284,8 +382,6 @@ void MyAvatar::updateFromGyros(bool turnWithHead) {
}
}
} else {
_head.setPitch(_head.getMousePitch());
// restore rotation, lean to neutral positions
const float RESTORE_RATE = 0.05f;
_head.setYaw(glm::mix(_head.getYaw(), 0.0f, RESTORE_RATE));
@ -427,6 +523,74 @@ void MyAvatar::render(bool forceRenderHead) {
}
}
void MyAvatar::renderHeadMouse() const {
// TODO? resurrect headMouse stuff?
/*
// Display small target box at center or head mouse target that can also be used to measure LOD
glColor3f(1.0, 1.0, 1.0);
glDisable(GL_LINE_SMOOTH);
const int PIXEL_BOX = 16;
glBegin(GL_LINES);
glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY);
glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY);
glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2);
glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2);
glEnd();
glEnable(GL_LINE_SMOOTH);
glColor3f(1.f, 0.f, 0.f);
glPointSize(3.0f);
glDisable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
glVertex2f(_headMouseX - 1, _headMouseY + 1);
glEnd();
// If Faceshift is active, show eye pitch and yaw as separate pointer
if (_faceshift.isActive()) {
const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0;
int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE;
int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE;
glColor3f(0.0, 1.0, 1.0);
glDisable(GL_LINE_SMOOTH);
glBegin(GL_LINES);
glVertex2f(eyeTargetX - PIXEL_BOX/2, eyeTargetY);
glVertex2f(eyeTargetX + PIXEL_BOX/2, eyeTargetY);
glVertex2f(eyeTargetX, eyeTargetY - PIXEL_BOX/2);
glVertex2f(eyeTargetX, eyeTargetY + PIXEL_BOX/2);
glEnd();
}
*/
}
void MyAvatar::renderTransmitterPickRay() const {
if (_transmitterPickStart != _transmitterPickEnd) {
Glower glower;
const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
glColor3fv(TRANSMITTER_PICK_COLOR);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z);
glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
glEnd();
glLineWidth(1.0f);
glPushMatrix();
glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
const float PICK_END_RADIUS = 0.025f;
glutSolidSphere(PICK_END_RADIUS, 8, 8);
glPopMatrix();
}
}
void MyAvatar::renderTransmitterLevels(int width, int height) const {
// Show hand transmitter data if detected
if (_transmitter.isConnected()) {
_transmitter.renderLevels(width, height);
}
}
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
@ -434,7 +598,7 @@ void MyAvatar::saveData(QSettings* settings) {
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
settings->setValue("mousePitch", _head.getMousePitch());
settings->setValue("headPitch", _head.getPitch());
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
@ -456,7 +620,7 @@ void MyAvatar::loadData(QSettings* settings) {
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
_head.setMousePitch(loadSetting(settings, "mousePitch", 0.0f));
_head.setPitch(loadSetting(settings, "headPitch", 0.0f));
_position.x = loadSetting(settings, "position_x", 0.0f);
_position.y = loadSetting(settings, "position_y", 0.0f);
@ -473,18 +637,8 @@ void MyAvatar::loadData(QSettings* settings) {
}
void MyAvatar::sendKillAvatar() {
unsigned char packet[MAX_PACKET_SIZE];
unsigned char* packetPosition = packet;
packetPosition += populateTypeAndVersion(packetPosition, PACKET_TYPE_KILL_AVATAR);
NodeList* nodeList = NodeList::getInstance();
QByteArray rfcUUID = nodeList->getOwnerUUID().toRfc4122();
memcpy(packetPosition, rfcUUID.constData(), rfcUUID.size());
packetPosition += rfcUUID.size();
nodeList->broadcastToNodes(packet, packetPosition - packet, QSet<NODE_TYPE>() << NODE_TYPE_AVATAR_MIXER);
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
@ -497,12 +651,42 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
setOrientation(orientation);
// then vertically
float oldMousePitch = _head.getMousePitch();
_head.setMousePitch(oldMousePitch + deltaY * -ANGULAR_SCALE);
rotation = glm::angleAxis(_head.getMousePitch() - oldMousePitch, orientation * IDENTITY_RIGHT);
float oldPitch = _head.getPitch();
_head.setPitch(oldPitch + deltaY * -ANGULAR_SCALE);
rotation = glm::angleAxis(_head.getPitch() - oldPitch, orientation * IDENTITY_RIGHT);
setPosition(position + rotation * (getPosition() - position));
}
void MyAvatar::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
Application* applicationInstance = Application::getInstance();
if (!applicationInstance->isMousePressed()) {
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar == static_cast<Avatar*>(this)) {
continue;
}
float distance;
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) *
(avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot();
_lookAtTargetAvatar = avatarPointer;
return;
}
}
_lookAtTargetAvatar.clear();
}
}
void MyAvatar::clearLookAtTargetAvatar() {
_lookAtTargetAvatar.clear();
}
float MyAvatar::getAbsoluteHeadYaw() const {
return glm::yaw(_head.getOrientation());
}
@ -524,7 +708,7 @@ void MyAvatar::renderBody(bool forceRenderHead) {
_hand.render(true);
}
void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
void MyAvatar::updateThrust(float deltaTime) {
//
// Gather thrust information from keyboard and sensors to apply to avatar motion
//
@ -549,7 +733,7 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_MAG * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_MAG * deltaTime;
_head.setMousePitch(_head.getMousePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
_head.setPitch(_head.getPitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_MAG * deltaTime);
// If thrust keys are being held down, slowly increase thrust to allow reaching great speeds
if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) {
@ -572,9 +756,9 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
}
// Add thrusts from Transmitter
if (transmitter) {
transmitter->checkForLostTransmitter();
glm::vec3 rotation = transmitter->getEstimatedRotation();
if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
_transmitter.checkForLostTransmitter();
glm::vec3 rotation = _transmitter.getEstimatedRotation();
const float TRANSMITTER_MIN_RATE = 1.f;
const float TRANSMITTER_MIN_YAW_RATE = 4.f;
const float TRANSMITTER_LATERAL_FORCE_SCALE = 5.f;
@ -592,9 +776,9 @@ void MyAvatar::updateThrust(float deltaTime, Transmitter * transmitter) {
if (fabs(rotation.y) > TRANSMITTER_MIN_YAW_RATE) {
_bodyYawDelta += rotation.y * TRANSMITTER_YAW_SCALE * deltaTime;
}
if (transmitter->getTouchState()->state == 'D') {
if (_transmitter.getTouchState()->state == 'D') {
_thrust += TRANSMITTER_UP_FORCE_SCALE *
(float)(transmitter->getTouchState()->y - TOUCH_POSITION_RANGE_HALF) / TOUCH_POSITION_RANGE_HALF *
(float)(_transmitter.getTouchState()->y - TOUCH_POSITION_RANGE_HALF) / TOUCH_POSITION_RANGE_HALF *
TRANSMITTER_LIFT_SCALE *
deltaTime *
up;
@ -791,14 +975,15 @@ void MyAvatar::updateChatCircle(float deltaTime) {
// find all circle-enabled members and sort by distance
QVector<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);
}
@ -898,3 +1083,28 @@ void MyAvatar::setOrientation(const glm::quat& orientation) {
_bodyYaw = eulerAngles.y;
_bodyRoll = eulerAngles.z;
}
void MyAvatar::goHome() {
qDebug("Going Home!");
setPosition(START_LOCATION);
}
void MyAvatar::increaseSize() {
if ((1.f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
_targetScale *= (1.f + SCALING_RATIO);
qDebug("Changed scale to %f", _targetScale);
}
}
void MyAvatar::decreaseSize() {
if (MIN_AVATAR_SCALE < (1.f - SCALING_RATIO) * _targetScale) {
_targetScale *= (1.f - SCALING_RATIO);
qDebug("Changed scale to %f", _targetScale);
}
}
void MyAvatar::resetSize() {
_targetScale = 1.0f;
qDebug("Reseted scale to %f", _targetScale);
}

View file

@ -11,6 +11,8 @@
#include <QSettings>
#include <devices/Transmitter.h>
#include "Avatar.h"
enum AvatarHandState
@ -23,14 +25,23 @@ enum AvatarHandState
};
class MyAvatar : public Avatar {
Q_OBJECT
public:
MyAvatar();
~MyAvatar();
void reset();
void simulate(float deltaTime, Transmitter* transmitter);
void update(float deltaTime);
void simulate(float deltaTime);
void updateFromGyros(bool turnWithHead);
void updateTransmitter(float deltaTime);
void render(bool forceRenderHead);
void renderDebugBodyPoints();
void renderHeadMouse() const;
void renderTransmitterPickRay() const;
void renderTransmitterLevels(int width, int height) const;
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
@ -39,7 +50,6 @@ public:
void setLeanScale(float scale) { _leanScale = scale; }
void setGravity(glm::vec3 gravity);
void setOrientation(const glm::quat& orientation);
void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
void setMoveTarget(const glm::vec3 moveTarget);
// getters
@ -51,6 +61,7 @@ public:
float getAbsoluteHeadYaw() const;
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
Transmitter& getTransmitter() { return _transmitter; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
@ -73,6 +84,17 @@ public:
void orbit(const glm::vec3& position, int deltaX, int deltaY);
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
void clearLookAtTargetAvatar();
public slots:
void goHome();
void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
void increaseSize();
void decreaseSize();
void resetSize();
private:
bool _mousePressed;
float _bodyPitchDelta;
@ -86,15 +108,21 @@ private:
float _elapsedTimeSinceCollision;
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
bool _isCollisionsOn;
bool _isThrustOn;
float _thrustMultiplier;
float _collisionRadius;
glm::vec3 _moveTarget;
int _moveTargetStepCounter;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
Transmitter _transmitter; // Gets UDP data from transmitter app used to animate the avatar
glm::vec3 _transmitterPickStart;
glm::vec3 _transmitterPickEnd;
// private methods
void renderBody(bool forceRenderHead);
void updateThrust(float deltaTime, Transmitter * transmitter);
void updateThrust(float deltaTime);
void updateHandMovementAndTouching(float deltaTime);
void updateAvatarCollisions(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime);

View file

@ -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)),

View file

@ -57,7 +57,7 @@ private:
QString _lastDomain;
glm::vec3 _lastPosition;
glm::vec3 _lastOrientation;
uint64_t _lastOrientationSend;
quint64 _lastOrientationSend;
QUrl _faceModelURL;
QUrl _skeletonModelURL;
};

View file

@ -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;
}

View file

@ -90,7 +90,7 @@ private:
bool _tcpEnabled;
int _tcpRetryCount;
bool _tracking;
uint64_t _lastTrackingStateReceived;
quint64 _lastTrackingStateReceived;
glm::quat _headRotation;
glm::vec3 _headAngularVelocity;

View file

@ -25,7 +25,7 @@ public slots:
private:
uint64_t _lastMovement;
quint64 _lastMovement;
};
#endif /* defined(__interface__SixenseManager__) */

View file

@ -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)
@ -113,7 +113,7 @@ void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) {
}
}
void Transmitter::renderLevels(int width, int height) {
void Transmitter::renderLevels(int width, int height) const {
char val[50];
const int LEVEL_CORNER_X = 10;
const int LEVEL_CORNER_Y = 400;
@ -163,7 +163,5 @@ void Transmitter::renderLevels(int width, int height) {
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y - 6);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 30);
glEnd();
}

View file

@ -29,8 +29,8 @@ public:
void render();
void checkForLostTransmitter();
void resetLevels();
void renderLevels(int width, int height);
bool isConnected() { return _isConnected; };
void renderLevels(int width, int height) const;
bool isConnected() const { return _isConnected; };
const glm::vec3 getLastRotationRate() const { return _lastRotationRate; };
const glm::vec3 getLastAcceleration() const { return _lastRotationRate; };
const glm::vec3 getEstimatedRotation() const { return _estimatedRotation; };

View file

@ -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

View file

@ -508,3 +508,17 @@ void NetworkGeometry::maybeReadModelWithMapping() {
_meshes.append(networkMesh);
}
}
bool NetworkMeshPart::isTranslucent() const {
return diffuseTexture && diffuseTexture->isTranslucent();
}
int NetworkMesh::getTranslucentPartCount() const {
int count = 0;
foreach (const NetworkMeshPart& part, parts) {
if (part.isTranslucent()) {
count++;
}
}
return count;
}

View file

@ -93,6 +93,8 @@ public:
QSharedPointer<NetworkTexture> diffuseTexture;
QSharedPointer<NetworkTexture> normalTexture;
bool isTranslucent() const;
};
/// The state associated with a single mesh.
@ -103,6 +105,8 @@ public:
GLuint vertexBufferID;
QVector<NetworkMeshPart> parts;
int getTranslucentPartCount() const;
};
#endif /* defined(__interface__GeometryCache__) */

View file

@ -240,7 +240,6 @@ bool Model::render(float alpha) {
// set up blended buffer ids on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
if (_blendedVertexBufferIDs.isEmpty()) {
foreach (const FBXMesh& mesh, geometry.meshes) {
GLuint id = 0;
@ -264,191 +263,28 @@ bool Model::render(float alpha) {
glDisable(GL_COLOR_MATERIAL);
// render opaque meshes with alpha testing
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
const FBXMesh& mesh = geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
ProgramObject* program = &_program;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
if (!mesh.tangents.isEmpty()) {
program = &_normalMapProgram;
skinProgram = &_skinNormalMapProgram;
skinLocations = &_skinNormalMapLocations;
}
const MeshState& state = _meshStates.at(i);
ProgramObject* activeProgram = program;
int tangentLocation = _normalMapTangentLocation;
if (state.worldSpaceVertices.isEmpty()) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
if (state.clusterMatrices.size() > 1) {
skinProgram->bind();
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData());
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2) +
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
offset + vertexCount * sizeof(glm::vec4), 4);
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
activeProgram = skinProgram;
tangentLocation = skinLocations->tangent;
} else {
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
program->bind();
}
} else {
program->bind();
}
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
if (!mesh.tangents.isEmpty()) {
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
activeProgram->enableAttributeArray(tangentLocation);
}
glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
mesh.tangents.size() * sizeof(glm::vec3)));
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
(mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
} else {
if (!mesh.tangents.isEmpty()) {
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
activeProgram->enableAttributeArray(tangentLocation);
}
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
if (!state.worldSpaceVertices.isEmpty()) {
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
} else {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
float coefficient = _blendshapeCoefficients[j];
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
}
}
glVertexPointer(3, GL_FLOAT, 0, 0);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
if (!mesh.colors.isEmpty()) {
glEnableClientState(GL_COLOR_ARRAY);
} else {
glColor3f(1.0f, 1.0f, 1.0f);
}
if (!mesh.texCoords.isEmpty()) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
qint64 offset = 0;
for (int j = 0; j < networkMesh.parts.size(); j++) {
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
const FBXMeshPart& part = mesh.parts.at(j);
// apply material properties
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
Texture* diffuseMap = networkPart.diffuseTexture.data();
if (mesh.isEye) {
if (diffuseMap != NULL) {
diffuseMap = (_dilatedTextures[i][j] =
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
}
}
glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
Texture* normalMap = networkPart.normalTexture.data();
glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
glActiveTexture(GL_TEXTURE0);
}
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
offset += part.quadIndices.size() * sizeof(int);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
}
if (!mesh.colors.isEmpty()) {
glDisableClientState(GL_COLOR_ARRAY);
}
if (!mesh.texCoords.isEmpty()) {
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
activeProgram->disableAttributeArray(tangentLocation);
}
if (state.worldSpaceVertices.isEmpty()) {
if (state.clusterMatrices.size() > 1) {
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
}
glPopMatrix();
}
activeProgram->release();
}
renderMeshes(alpha, false);
glDisable(GL_ALPHA_TEST);
// render translucent meshes afterwards, with back face culling
glEnable(GL_CULL_FACE);
renderMeshes(alpha, true);
glDisable(GL_CULL_FACE);
// deactivate vertex arrays after drawing
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisable(GL_ALPHA_TEST);
// bind with 0 to switch back to normal operation
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
@ -882,3 +718,191 @@ void Model::deleteGeometry() {
_jointStates.clear();
_meshStates.clear();
}
void Model::renderMeshes(float alpha, bool translucent) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
for (int i = 0; i < networkMeshes.size(); i++) {
// exit early if the translucency doesn't match what we're drawing
const NetworkMesh& networkMesh = networkMeshes.at(i);
if (translucent ? (networkMesh.getTranslucentPartCount() == 0) :
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
continue;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
const FBXMesh& mesh = geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
ProgramObject* program = &_program;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
if (!mesh.tangents.isEmpty()) {
program = &_normalMapProgram;
skinProgram = &_skinNormalMapProgram;
skinLocations = &_skinNormalMapLocations;
}
const MeshState& state = _meshStates.at(i);
ProgramObject* activeProgram = program;
int tangentLocation = _normalMapTangentLocation;
if (state.worldSpaceVertices.isEmpty()) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
if (state.clusterMatrices.size() > 1) {
skinProgram->bind();
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData());
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2) +
(mesh.blendshapes.isEmpty() ? vertexCount * 2 * sizeof(glm::vec3) : 0);
skinProgram->setAttributeBuffer(skinLocations->clusterIndices, GL_FLOAT, offset, 4);
skinProgram->setAttributeBuffer(skinLocations->clusterWeights, GL_FLOAT,
offset + vertexCount * sizeof(glm::vec4), 4);
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
activeProgram = skinProgram;
tangentLocation = skinLocations->tangent;
} else {
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
program->bind();
}
} else {
program->bind();
}
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
if (!mesh.tangents.isEmpty()) {
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
activeProgram->enableAttributeArray(tangentLocation);
}
glColorPointer(3, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
mesh.tangents.size() * sizeof(glm::vec3)));
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3) +
(mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
} else {
if (!mesh.tangents.isEmpty()) {
activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3);
activeProgram->enableAttributeArray(tangentLocation);
}
glColorPointer(3, GL_FLOAT, 0, (void*)(mesh.tangents.size() * sizeof(glm::vec3)));
glTexCoordPointer(2, GL_FLOAT, 0, (void*)((mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3)));
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
if (!state.worldSpaceVertices.isEmpty()) {
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
} else {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
float coefficient = _blendshapeCoefficients[j];
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
}
}
glVertexPointer(3, GL_FLOAT, 0, 0);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
if (!mesh.colors.isEmpty()) {
glEnableClientState(GL_COLOR_ARRAY);
} else {
glColor3f(1.0f, 1.0f, 1.0f);
}
if (!mesh.texCoords.isEmpty()) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
qint64 offset = 0;
for (int j = 0; j < networkMesh.parts.size(); j++) {
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
if (networkPart.isTranslucent() != translucent) {
continue;
}
const FBXMeshPart& part = mesh.parts.at(j);
// apply material properties
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
Texture* diffuseMap = networkPart.diffuseTexture.data();
if (mesh.isEye) {
if (diffuseMap != NULL) {
diffuseMap = (_dilatedTextures[i][j] =
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
}
}
glBindTexture(GL_TEXTURE_2D, diffuseMap == NULL ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
Texture* normalMap = networkPart.normalTexture.data();
glBindTexture(GL_TEXTURE_2D, normalMap == NULL ?
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
glActiveTexture(GL_TEXTURE0);
}
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
offset += part.quadIndices.size() * sizeof(int);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
}
if (!mesh.colors.isEmpty()) {
glDisableClientState(GL_COLOR_ARRAY);
}
if (!mesh.texCoords.isEmpty()) {
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
activeProgram->disableAttributeArray(tangentLocation);
}
if (state.worldSpaceVertices.isEmpty()) {
if (state.clusterMatrices.size() > 1) {
skinProgram->disableAttributeArray(skinLocations->clusterIndices);
skinProgram->disableAttributeArray(skinLocations->clusterWeights);
}
glPopMatrix();
}
activeProgram->release();
}
}

View file

@ -214,6 +214,7 @@ protected:
private:
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);
float _pupilDilation;
std::vector<float> _blendshapeCoefficients;

View file

@ -257,7 +257,8 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
_request(url),
_reply(NULL),
_attempts(0),
_averageColor(1.0f, 1.0f, 1.0f, 1.0f) {
_averageColor(1.0f, 1.0f, 1.0f, 1.0f),
_translucent(false) {
if (!url.isValid()) {
return;
@ -300,19 +301,27 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo
QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32);
// sum up the colors for the average
// sum up the colors for the average and check for translucency
glm::vec4 accumulated;
int translucentPixels = 0;
const int EIGHT_BIT_MAXIMUM = 255;
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
QRgb pixel = image.pixel(x, y);
accumulated.r += qRed(pixel);
accumulated.g += qGreen(pixel);
accumulated.b += qBlue(pixel);
accumulated.a += qAlpha(pixel);
int alpha = qAlpha(pixel);
if (alpha != 0 && alpha != EIGHT_BIT_MAXIMUM) {
translucentPixels++;
}
accumulated.a += alpha;
}
}
const float EIGHT_BIT_MAXIMUM = 255.0f;
_averageColor = accumulated / (image.width() * image.height() * EIGHT_BIT_MAXIMUM);
int imageArea = image.width() * image.height();
_averageColor = accumulated / (imageArea * EIGHT_BIT_MAXIMUM);
_translucent = (translucentPixels >= imageArea / 2);
imageLoaded(image);
glBindTexture(GL_TEXTURE_2D, getID());

View file

@ -121,6 +121,10 @@ public:
/// Returns the average color over the entire texture.
const glm::vec4& getAverageColor() const { return _averageColor; }
/// Checks whether it "looks like" this texture is translucent
/// (majority of pixels neither fully opaque or fully transparent).
bool isTranslucent() const { return _translucent; }
protected:
virtual void imageLoaded(const QImage& image);
@ -137,6 +141,7 @@ private:
QNetworkReply* _reply;
int _attempts;
glm::vec4 _averageColor;
bool _translucent;
};
/// Caches derived, dilated textures.

View file

@ -59,7 +59,7 @@ namespace starfield {
using namespace std;
typedef uint32_t nuint;
typedef uint64_t wuint;
typedef quint64 wuint;
}

View file

@ -58,18 +58,21 @@ MetavoxelEditor::MetavoxelEditor() :
_gridPlane->addItem("X/Z");
_gridPlane->addItem("Y/Z");
_gridPlane->setCurrentIndex(GRID_PLANE_XZ);
connect(_gridPlane, SIGNAL(currentIndexChanged(int)), SLOT(centerGridPosition()));
formLayout->addRow("Grid Spacing:", _gridSpacing = new QDoubleSpinBox());
_gridSpacing->setValue(0.1);
_gridSpacing->setMinimum(-FLT_MAX);
_gridSpacing->setMaximum(FLT_MAX);
_gridSpacing->setSingleStep(0.01);
connect(_gridSpacing, SIGNAL(valueChanged(double)), SLOT(updateGridPosition()));
_gridSpacing->setPrefix("2^");
_gridSpacing->setValue(-3.0);
connect(_gridSpacing, SIGNAL(valueChanged(double)), SLOT(alignGridPosition()));
formLayout->addRow("Grid Position:", _gridPosition = new QDoubleSpinBox());
_gridPosition->setSingleStep(0.1);
_gridPosition->setMinimum(-FLT_MAX);
_gridPosition->setMaximum(FLT_MAX);
alignGridPosition();
centerGridPosition();
_value = new QGroupBox();
_value->setTitle("Value");
topLayout->addWidget(_value);
@ -119,7 +122,7 @@ bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
float top = base + _height;
glm::quat rotation = getGridRotation();
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
float spacing = _gridSpacing->value();
float spacing = getGridSpacing();
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
glm::vec2(spacing, spacing), glm::max(base, top));
@ -181,13 +184,19 @@ void MetavoxelEditor::createNewAttribute() {
updateAttributes(nameText);
}
void MetavoxelEditor::updateGridPosition() {
void MetavoxelEditor::centerGridPosition() {
const float CENTER_OFFSET = 0.625f;
float eyePosition = (glm::inverse(getGridRotation()) * Application::getInstance()->getCamera()->getPosition()).z -
Application::getInstance()->getAvatar()->getScale() * CENTER_OFFSET;
double step = getGridSpacing();
_gridPosition->setValue(step * floor(eyePosition / step));
}
void MetavoxelEditor::alignGridPosition() {
// make sure our grid position matches our grid spacing
double step = _gridSpacing->value();
if (step > 0.0) {
_gridPosition->setSingleStep(step);
_gridPosition->setValue(step * floor(_gridPosition->value() / step));
}
double step = getGridSpacing();
_gridPosition->setSingleStep(step);
_gridPosition->setValue(step * floor(_gridPosition->value() / step));
}
void MetavoxelEditor::render() {
@ -209,7 +218,7 @@ void MetavoxelEditor::render() {
glm::quat inverseRotation = glm::inverse(rotation);
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
float spacing = _gridSpacing->value();
float spacing = getGridSpacing();
float position = _gridPosition->value();
if (_state == RAISING_STATE) {
// find the plane at the mouse position, orthogonal to the plane, facing the eye position
@ -318,6 +327,10 @@ QString MetavoxelEditor::getSelectedAttribute() const {
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
}
double MetavoxelEditor::getGridSpacing() const {
return pow(2.0, _gridSpacing->value());
}
glm::quat MetavoxelEditor::getGridRotation() const {
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
switch (_gridPlane->currentIndex()) {
@ -339,60 +352,14 @@ void MetavoxelEditor::resetState() {
_height = 0.0f;
}
class Applier : public MetavoxelVisitor {
public:
Applier(const glm::vec3& minimum, const glm::vec3& maximum, float granularity, const AttributeValue& value);
virtual bool visit(MetavoxelInfo& info);
protected:
glm::vec3 _minimum;
glm::vec3 _maximum;
float _granularity;
AttributeValue _value;
};
Applier::Applier(const glm::vec3& minimum, const glm::vec3& maximum, float granularity, const AttributeValue& value) :
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << value.getAttribute()),
_minimum(minimum),
_maximum(maximum),
_granularity(granularity),
_value(value) {
}
bool Applier::visit(MetavoxelInfo& info) {
// find the intersection between volume and voxel
glm::vec3 minimum = glm::max(info.minimum, _minimum);
glm::vec3 maximum = glm::min(info.minimum + glm::vec3(info.size, info.size, info.size), _maximum);
glm::vec3 size = maximum - minimum;
if (size.x <= 0.0f || size.y <= 0.0f || size.z <= 0.0f) {
return false; // disjoint
}
float volume = (size.x * size.y * size.z) / (info.size * info.size * info.size);
if (volume >= 1.0f) {
info.outputValues[0] = _value;
return false; // entirely contained
}
if (info.size <= _granularity) {
if (volume > 0.5f) {
info.outputValues[0] = _value;
}
return false; // reached granularity limit; take best guess
}
return true; // subdivide
}
void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(getSelectedAttribute());
if (!attribute) {
return;
}
OwnedAttributeValue value(attribute, attribute->createFromVariant(getValue()));
Applier applier(minimum, maximum, _gridSpacing->value(), value);
Application::getInstance()->getMetavoxels()->getData().guide(applier);
MetavoxelEditMessage edit = { { minimum, maximum }, getGridSpacing(), value };
Application::getInstance()->getMetavoxels()->applyEdit(edit);
}
QVariant MetavoxelEditor::getValue() const {

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