mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
merge fix
This commit is contained in:
commit
5b709aa60c
236 changed files with 6871 additions and 4304 deletions
11
README.md
11
README.md
|
@ -71,8 +71,15 @@ We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux
|
|||
distributions. A Windows build is planned for the future, but not currently in
|
||||
development.
|
||||
|
||||
On a fresh Ubuntu 13.10 install, these are all the packages you need to grab and build the hifi project:
|
||||
<pre>sudo apt-get install build-essential cmake git libcurl4-openssl-dev libqt5scripttools5 libqt5svg5-dev libqt5webkit5-dev libqt5location5 qtlocation5-dev qtdeclarative5-dev qtscript5-dev qtsensors5-dev qtmultimedia5-dev qtquick1-5-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev</pre>
|
||||
On a fresh Ubuntu 13.10 install, get these requirements from Ubuntu repositories:
|
||||
|
||||
sudo apt-get install build-essential cmake git libcurl4-openssl-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
|
||||
|
||||
Then [download lastest Qt packages](http://qt-project.org/downloads), untar/install to your prefered path
|
||||
and set your `QT_CMAKE_PREFIX_PATH` environment variable as described above in the CMake section. It's
|
||||
recommended to set the variable automatically on each shell instance to save this task in the future:
|
||||
|
||||
echo 'export QT_CMAKE_PREFIX_PATH=~/Qt5.2.0/5.2.0/gcc_64/lib/cmake' >> ~/.bashrc
|
||||
|
||||
Running Interface
|
||||
-----
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include <EnvironmentData.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <OctalCode.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <JurisdictionListener.h>
|
||||
|
@ -152,7 +151,7 @@ static void renderMovingBug() {
|
|||
}
|
||||
|
||||
// send the "erase message" first...
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_ERASE;
|
||||
PacketType message = PacketTypeVoxelErase;
|
||||
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
|
||||
|
||||
// Move the bug...
|
||||
|
@ -212,7 +211,7 @@ static void renderMovingBug() {
|
|||
}
|
||||
|
||||
// send the "create message" ...
|
||||
message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
|
||||
message = PacketTypeVoxelSetDestructive;
|
||||
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
|
||||
}
|
||||
|
||||
|
@ -247,7 +246,7 @@ static void sendVoxelBlinkMessage() {
|
|||
detail.green = 0 * ::intensity;
|
||||
detail.blue = 0 * ::intensity;
|
||||
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
|
||||
PacketType message = PacketTypeVoxelSetDestructive;
|
||||
|
||||
::voxelEditPacketSender->sendVoxelEditMessage(message, detail);
|
||||
}
|
||||
|
@ -264,7 +263,7 @@ unsigned char onColor[3] = { 0, 255, 255 };
|
|||
const float STRING_OF_LIGHTS_SIZE = 0.125f / TREE_SCALE; // approximately 1/8th meter
|
||||
|
||||
static void sendBlinkingStringOfLights() {
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
|
||||
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
|
||||
float lightScale = STRING_OF_LIGHTS_SIZE;
|
||||
static VoxelDetail details[LIGHTS_PER_SEGMENT];
|
||||
|
||||
|
@ -370,7 +369,7 @@ const int PACKETS_PER_DANCE_FLOOR = DANCE_FLOOR_VOXELS_PER_PACKET / (DANCE_FLOOR
|
|||
int danceFloorColors[DANCE_FLOOR_WIDTH][DANCE_FLOOR_LENGTH];
|
||||
|
||||
void sendDanceFloor() {
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
|
||||
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
|
||||
float lightScale = DANCE_FLOOR_LIGHT_SIZE;
|
||||
static VoxelDetail details[DANCE_FLOOR_VOXELS_PER_PACKET];
|
||||
|
||||
|
@ -486,7 +485,7 @@ bool billboardMessage[BILLBOARD_HEIGHT][BILLBOARD_WIDTH] = {
|
|||
};
|
||||
|
||||
static void sendBillboard() {
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
|
||||
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
|
||||
float lightScale = BILLBOARD_LIGHT_SIZE;
|
||||
static VoxelDetail details[VOXELS_PER_PACKET];
|
||||
|
||||
|
@ -557,7 +556,7 @@ void doBuildStreet() {
|
|||
return;
|
||||
}
|
||||
|
||||
PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
|
||||
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
|
||||
static VoxelDetail details[BRICKS_PER_PACKET];
|
||||
|
||||
for (int z = 0; z < ROAD_LENGTH; z++) {
|
||||
|
@ -592,8 +591,8 @@ double start = 0;
|
|||
|
||||
void* animateVoxels(void* args) {
|
||||
|
||||
uint64_t lastAnimateTime = 0;
|
||||
uint64_t lastProcessTime = 0;
|
||||
quint64 lastAnimateTime = 0;
|
||||
quint64 lastProcessTime = 0;
|
||||
int processesPerAnimate = 0;
|
||||
|
||||
bool firstTime = true;
|
||||
|
@ -623,8 +622,8 @@ void* animateVoxels(void* args) {
|
|||
|
||||
// The while loop will be running at PROCESSING_FPS, but we only want to call these animation functions at
|
||||
// ANIMATE_FPS. So we check out last animate time and only call these if we've elapsed that time.
|
||||
uint64_t now = usecTimestampNow();
|
||||
uint64_t animationElapsed = now - lastAnimateTime;
|
||||
quint64 now = usecTimestampNow();
|
||||
quint64 animationElapsed = now - lastAnimateTime;
|
||||
int withinAnimationTarget = ANIMATE_VOXELS_INTERVAL_USECS - animationElapsed;
|
||||
const int CLOSE_ENOUGH_TO_ANIMATE = 2000; // approximately 2 ms
|
||||
|
||||
|
@ -677,7 +676,7 @@ void* animateVoxels(void* args) {
|
|||
processesPerAnimate++;
|
||||
}
|
||||
// dynamically sleep until we need to fire off the next set of voxels
|
||||
uint64_t usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
|
||||
quint64 usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
|
||||
if (usecToSleep > PROCESSING_INTERVAL_USECS) {
|
||||
usecToSleep = PROCESSING_INTERVAL_USECS;
|
||||
}
|
||||
|
@ -695,7 +694,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
|
|||
{
|
||||
::start = usecTimestampNow();
|
||||
|
||||
NodeList* nodeList = NodeList::createInstance(NODE_TYPE_ANIMATION_SERVER, ANIMATION_LISTEN_PORT);
|
||||
NodeList* nodeList = NodeList::createInstance(NodeType::AnimationServer, ANIMATION_LISTEN_PORT);
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
|
||||
// Handle Local Domain testing with the --local command line
|
||||
|
@ -807,7 +806,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
|
|||
|
||||
pthread_create(&::animateVoxelThread, NULL, animateVoxels, NULL);
|
||||
|
||||
NodeList::getInstance()->addNodeTypeToInterestSet(NODE_TYPE_VOXEL_SERVER);
|
||||
NodeList::getInstance()->addNodeTypeToInterestSet(NodeType::VoxelServer);
|
||||
|
||||
QTimer* domainServerTimer = new QTimer(this);
|
||||
connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn()));
|
||||
|
@ -823,25 +822,24 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
|
|||
void AnimationServer::readPendingDatagrams() {
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
static int receivedBytes = 0;
|
||||
static unsigned char packetData[MAX_PACKET_SIZE];
|
||||
static QByteArray receivedPacket;
|
||||
static HifiSockAddr nodeSockAddr;
|
||||
|
||||
// Nodes sending messages to us...
|
||||
while (nodeList->getNodeSocket().hasPendingDatagrams()
|
||||
&& (receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
|
||||
nodeSockAddr.getAddressPointer(),
|
||||
nodeSockAddr.getPortPointer())) &&
|
||||
packetVersionMatch(packetData, nodeSockAddr)) {
|
||||
|
||||
if (packetData[0] == PACKET_TYPE_JURISDICTION) {
|
||||
int headerBytes = numBytesForPacketHeader(packetData);
|
||||
// PACKET_TYPE_JURISDICTION, first byte is the node type...
|
||||
if (packetData[headerBytes] == NODE_TYPE_VOXEL_SERVER && ::jurisdictionListener) {
|
||||
::jurisdictionListener->queueReceivedPacket(nodeSockAddr, packetData, receivedBytes);
|
||||
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
|
||||
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
|
||||
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
|
||||
nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer());
|
||||
if (packetVersionMatch(receivedPacket)) {
|
||||
if (packetTypeForPacket(receivedPacket) == PacketTypeJurisdiction) {
|
||||
int headerBytes = numBytesForPacketHeader(receivedPacket);
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
if (receivedPacket.data()[headerBytes] == NodeType::VoxelServer && ::jurisdictionListener) {
|
||||
::jurisdictionListener->queueReceivedPacket(nodeSockAddr, receivedPacket);
|
||||
}
|
||||
}
|
||||
NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket);
|
||||
}
|
||||
NodeList::getInstance()->processNodeData(nodeSockAddr, packetData, receivedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,37 +22,42 @@
|
|||
|
||||
#include "Agent.h"
|
||||
|
||||
Agent::Agent(const unsigned char* dataBuffer, int numBytes) :
|
||||
ThreadedAssignment(dataBuffer, numBytes)
|
||||
Agent::Agent(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet)
|
||||
{
|
||||
}
|
||||
|
||||
void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
|
||||
if (dataByteArray[0] == PACKET_TYPE_JURISDICTION) {
|
||||
int headerBytes = numBytesForPacketHeader((const unsigned char*) dataByteArray.constData());
|
||||
// PACKET_TYPE_JURISDICTION, first byte is the node type...
|
||||
PacketType datagramPacketType = packetTypeForPacket(dataByteArray);
|
||||
if (datagramPacketType == PacketTypeJurisdiction) {
|
||||
int headerBytes = numBytesForPacketHeader(dataByteArray);
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
switch (dataByteArray[headerBytes]) {
|
||||
case NODE_TYPE_VOXEL_SERVER:
|
||||
case NodeType::VoxelServer:
|
||||
_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
|
||||
(unsigned char*) dataByteArray.data(),
|
||||
dataByteArray.size());
|
||||
dataByteArray);
|
||||
break;
|
||||
case NODE_TYPE_PARTICLE_SERVER:
|
||||
case NodeType::ParticleServer:
|
||||
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
|
||||
(unsigned char*) dataByteArray.data(),
|
||||
dataByteArray.size());
|
||||
dataByteArray);
|
||||
break;
|
||||
}
|
||||
} else if (datagramPacketType == PacketTypeParticleAddResponse) {
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
Particle::handleAddParticleResponse(dataByteArray);
|
||||
|
||||
// also give our local particle tree a chance to remap any internal locally created particles
|
||||
_particleTree.handleAddParticleResponse(dataByteArray);
|
||||
} else {
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::run() {
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->setOwnerType(NODE_TYPE_AGENT);
|
||||
nodeList->setOwnerType(NodeType::Agent);
|
||||
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(QSet<NODE_TYPE>() << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER);
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer);
|
||||
|
||||
// figure out the URL for the script for this agent assignment
|
||||
QString scriptURLString("http://%1:8080/assignment/%2");
|
||||
|
@ -88,11 +93,17 @@ void Agent::run() {
|
|||
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
|
||||
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
|
||||
|
||||
// tell our script engine about our local particle tree
|
||||
_scriptEngine.getParticlesScriptingInterface()->setParticleTree(&_particleTree);
|
||||
|
||||
// setup an Avatar for the script to use
|
||||
AvatarData scriptedAvatar;
|
||||
|
||||
// give this AvatarData object to the script engine
|
||||
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
||||
|
||||
// register ourselves to the script engine
|
||||
_scriptEngine.registerGlobalObject("Agent", this);
|
||||
|
||||
_scriptEngine.setScriptContents(scriptContents);
|
||||
_scriptEngine.run();
|
||||
|
|
|
@ -15,13 +15,19 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <ParticleTree.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
class Agent : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
|
||||
public:
|
||||
Agent(const unsigned char* dataBuffer, int numBytes);
|
||||
Agent(const QByteArray& packet);
|
||||
|
||||
void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); }
|
||||
bool isAvatar() const { return _scriptEngine.isAvatar(); }
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
|
@ -32,6 +38,7 @@ signals:
|
|||
void willSendVisualDataCallback();
|
||||
private:
|
||||
ScriptEngine _scriptEngine;
|
||||
ParticleTree _particleTree;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Agent__) */
|
||||
|
|
|
@ -52,7 +52,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
|||
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool);
|
||||
|
||||
// create a NodeList as an unassigned client
|
||||
NodeList* nodeList = NodeList::createInstance(NODE_TYPE_UNASSIGNED);
|
||||
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
|
||||
|
||||
const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a";
|
||||
const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p";
|
||||
|
@ -90,7 +90,8 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
|||
timer->start(ASSIGNMENT_REQUEST_INTERVAL_MSECS);
|
||||
|
||||
// connect our readPendingDatagrams method to the readyRead() signal of the socket
|
||||
connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void AssignmentClient::sendAssignmentRequest() {
|
||||
|
@ -102,34 +103,33 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
void AssignmentClient::readPendingDatagrams() {
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
static unsigned char packetData[1500];
|
||||
static qint64 receivedBytes = 0;
|
||||
static HifiSockAddr senderSockAddr;
|
||||
QByteArray receivedPacket;
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
|
||||
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
|
||||
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
|
||||
if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
|
||||
senderSockAddr.getAddressPointer(),
|
||||
senderSockAddr.getPortPointer()))
|
||||
&& packetVersionMatch(packetData, senderSockAddr)) {
|
||||
|
||||
if (packetVersionMatch(receivedPacket)) {
|
||||
if (_currentAssignment) {
|
||||
// have the threaded current assignment handle this datagram
|
||||
QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection,
|
||||
Q_ARG(QByteArray, QByteArray((char*) packetData, receivedBytes)),
|
||||
Q_ARG(QByteArray, receivedPacket),
|
||||
Q_ARG(HifiSockAddr, senderSockAddr));
|
||||
} else if (packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT || packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
|
||||
} else if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
|
||||
|
||||
if (_currentAssignment) {
|
||||
qDebug() << "Dropping received assignment since we are currently running one.";
|
||||
} else {
|
||||
// construct the deployed assignment from the packet data
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(packetData, receivedBytes);
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
|
||||
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
||||
// switch our nodelist domain IP and port to whoever sent us the assignment
|
||||
if (packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
|
||||
if (_currentAssignment) {
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
||||
// switch our nodelist domain IP and port to whoever sent us the assignment
|
||||
|
||||
nodeList->setDomainSockAddr(senderSockAddr);
|
||||
nodeList->setOwnerUUID(_currentAssignment->getUUID());
|
||||
|
||||
|
@ -153,12 +153,12 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
// Starts an event loop, and emits workerThread->started()
|
||||
workerThread->start();
|
||||
} else {
|
||||
qDebug("Received a bad destination socket for assignment.");
|
||||
qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// have the NodeList attempt to handle it
|
||||
nodeList->processNodeData(senderSockAddr, packetData, receivedBytes);
|
||||
nodeList->processNodeData(senderSockAddr, receivedPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +175,6 @@ void AssignmentClient::assignmentCompleted() {
|
|||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
// reset our NodeList by switching back to unassigned and clearing the list
|
||||
nodeList->setOwnerType(NODE_TYPE_UNASSIGNED);
|
||||
nodeList->setOwnerType(NodeType::Unassigned);
|
||||
nodeList->reset();
|
||||
}
|
||||
|
|
|
@ -18,25 +18,28 @@
|
|||
#include "avatars/AvatarMixer.h"
|
||||
#include "metavoxels/MetavoxelServer.h"
|
||||
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dataBuffer, int numBytes) {
|
||||
int headerBytes = numBytesForPacketHeader(dataBuffer);
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) {
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
quint8 packedType;
|
||||
packetStream >> packedType;
|
||||
|
||||
Assignment::Type assignmentType = Assignment::AllTypes;
|
||||
memcpy(&assignmentType, dataBuffer + headerBytes, sizeof(Assignment::Type));
|
||||
Assignment::Type unpackedType = (Assignment::Type) packedType;
|
||||
|
||||
switch (assignmentType) {
|
||||
switch (unpackedType) {
|
||||
case Assignment::AudioMixerType:
|
||||
return new AudioMixer(dataBuffer, numBytes);
|
||||
return new AudioMixer(packet);
|
||||
case Assignment::AvatarMixerType:
|
||||
return new AvatarMixer(dataBuffer, numBytes);
|
||||
return new AvatarMixer(packet);
|
||||
case Assignment::AgentType:
|
||||
return new Agent(dataBuffer, numBytes);
|
||||
return new Agent(packet);
|
||||
case Assignment::VoxelServerType:
|
||||
return new VoxelServer(dataBuffer, numBytes);
|
||||
return new VoxelServer(packet);
|
||||
case Assignment::ParticleServerType:
|
||||
return new ParticleServer(dataBuffer, numBytes);
|
||||
return new ParticleServer(packet);
|
||||
case Assignment::MetavoxelServerType:
|
||||
return new MetavoxelServer(dataBuffer, numBytes);
|
||||
return new MetavoxelServer(packet);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
class AssignmentFactory {
|
||||
public:
|
||||
static ThreadedAssignment* unpackAssignment(const unsigned char* dataBuffer, int numBytes);
|
||||
static ThreadedAssignment* unpackAssignment(const QByteArray& packet);
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__AssignmentFactory__) */
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include <Logging.h>
|
||||
#include <NodeList.h>
|
||||
#include <Node.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StdDev.h>
|
||||
|
@ -61,8 +60,8 @@ void attachNewBufferToNode(Node *newNode) {
|
|||
}
|
||||
}
|
||||
|
||||
AudioMixer::AudioMixer(const unsigned char* dataBuffer, int numBytes) :
|
||||
ThreadedAssignment(dataBuffer, numBytes)
|
||||
AudioMixer::AudioMixer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -210,18 +209,19 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
|
||||
void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
|
||||
// pull any new audio data from nodes off of the network stack
|
||||
if (dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO
|
||||
|| dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
|
||||
|| dataByteArray[0] == PACKET_TYPE_INJECT_AUDIO) {
|
||||
QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
|
||||
NUM_BYTES_RFC4122_UUID));
|
||||
PacketType mixerPacketType = packetTypeForPacket(dataByteArray);
|
||||
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|
||||
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|
||||
|| mixerPacketType == PacketTypeInjectAudio) {
|
||||
QUuid nodeUUID;
|
||||
deconstructPacketHeader(dataByteArray, nodeUUID);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
SharedNodePointer matchingNode = nodeList->nodeWithUUID(nodeUUID);
|
||||
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithData(matchingNode.data(), senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
|
||||
nodeList->updateNodeWithData(matchingNode.data(), senderSockAddr, dataByteArray);
|
||||
|
||||
if (!matchingNode->getActiveSocket()) {
|
||||
// we don't have an active socket for this node, but they're talking to us
|
||||
|
@ -231,17 +231,17 @@ void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSock
|
|||
}
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::run() {
|
||||
|
||||
commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NODE_TYPE_AUDIO_MIXER);
|
||||
commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
|
||||
|
||||
|
@ -250,14 +250,14 @@ void AudioMixer::run() {
|
|||
|
||||
gettimeofday(&startTime, NULL);
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MIXED_AUDIO);
|
||||
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio);
|
||||
// note: Visual Studio 2010 doesn't support variable sized local arrays
|
||||
#ifdef _WIN32
|
||||
unsigned char clientPacket[MAX_PACKET_SIZE];
|
||||
#else
|
||||
unsigned char clientPacket[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader];
|
||||
#endif
|
||||
populateTypeAndVersion(clientPacket, PACKET_TYPE_MIXED_AUDIO);
|
||||
populatePacketHeader(reinterpret_cast<char*>(clientPacket), PacketTypeMixedAudio);
|
||||
|
||||
while (!_isFinished) {
|
||||
|
||||
|
@ -274,7 +274,7 @@ void AudioMixer::run() {
|
|||
}
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getType() == NODE_TYPE_AGENT && node->getActiveSocket() && node->getLinkedData()
|
||||
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
|
||||
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
|
||||
prepareMixForListeningNode(node.data());
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class AvatarAudioRingBuffer;
|
|||
class AudioMixer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioMixer(const unsigned char* dataBuffer, int numBytes);
|
||||
AudioMixer(const QByteArray& packet);
|
||||
public slots:
|
||||
/// threaded run of assignment
|
||||
void run();
|
||||
|
|
|
@ -31,9 +31,10 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
|
||||
if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
|
||||
|| packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO) {
|
||||
int AudioMixerClientData::parseData(const QByteArray& packet) {
|
||||
PacketType packetType = packetTypeForPacket(packet);
|
||||
if (packetType == PacketTypeMicrophoneAudioWithEcho
|
||||
|| packetType == PacketTypeMicrophoneAudioNoEcho) {
|
||||
|
||||
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
|
||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||
|
@ -45,14 +46,12 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
|
|||
}
|
||||
|
||||
// ask the AvatarAudioRingBuffer instance to parse the data
|
||||
avatarRingBuffer->parseData(packetData, numBytes);
|
||||
avatarRingBuffer->parseData(packet);
|
||||
} else {
|
||||
// this is injected audio
|
||||
|
||||
// grab the stream identifier for this injected audio
|
||||
QByteArray rfcUUID = QByteArray((char*) packetData + numBytesForPacketHeader(packetData) + NUM_BYTES_RFC4122_UUID,
|
||||
NUM_BYTES_RFC4122_UUID);
|
||||
QUuid streamIdentifier = QUuid::fromRfc4122(rfcUUID);
|
||||
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
|
||||
|
||||
|
@ -69,7 +68,7 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
|
|||
_ringBuffers.push_back(matchingInjectedRingBuffer);
|
||||
}
|
||||
|
||||
matchingInjectedRingBuffer->parseData(packetData, numBytes);
|
||||
matchingInjectedRingBuffer->parseData(packet);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
const std::vector<PositionalAudioRingBuffer*> getRingBuffers() const { return _ringBuffers; }
|
||||
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
|
||||
|
||||
int parseData(unsigned char* packetData, int numBytes);
|
||||
int parseData(const QByteArray& packet);
|
||||
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples);
|
||||
void pushBuffersAfterFrameSend();
|
||||
private:
|
||||
|
|
|
@ -15,7 +15,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer() :
|
|||
|
||||
}
|
||||
|
||||
int AvatarAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) {
|
||||
_shouldLoopbackForNode = (sourceBuffer[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO);
|
||||
return PositionalAudioRingBuffer::parseData(sourceBuffer, numBytes);
|
||||
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
|
||||
return PositionalAudioRingBuffer::parseData(packet);
|
||||
}
|
|
@ -17,7 +17,7 @@ class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
|
|||
public:
|
||||
AvatarAudioRingBuffer();
|
||||
|
||||
int parseData(unsigned char* sourceBuffer, int numBytes);
|
||||
int parseData(const QByteArray& packet);
|
||||
private:
|
||||
// disallow copying of AvatarAudioRingBuffer objects
|
||||
AvatarAudioRingBuffer(const AvatarAudioRingBuffer&);
|
||||
|
|
|
@ -27,24 +27,13 @@ const char AVATAR_MIXER_LOGGING_NAME[] = "avatar-mixer";
|
|||
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000;
|
||||
|
||||
AvatarMixer::AvatarMixer(const unsigned char* dataBuffer, int numBytes) :
|
||||
ThreadedAssignment(dataBuffer, numBytes)
|
||||
AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet)
|
||||
{
|
||||
// make sure we hear about node kills so we can tell the other nodes
|
||||
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
|
||||
}
|
||||
|
||||
unsigned char* addNodeToBroadcastPacket(unsigned char *currentPosition, Node *nodeToAdd) {
|
||||
QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122();
|
||||
memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size());
|
||||
currentPosition += rfcUUID.size();
|
||||
|
||||
AvatarData *nodeData = (AvatarData *)nodeToAdd->getLinkedData();
|
||||
currentPosition += nodeData->getBroadcastData(currentPosition);
|
||||
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
void attachAvatarDataToNode(Node* newNode) {
|
||||
if (newNode->getLinkedData() == NULL) {
|
||||
newNode->setLinkedData(new AvatarData());
|
||||
|
@ -59,58 +48,50 @@ void attachAvatarDataToNode(Node* newNode) {
|
|||
// determine which avatars are included in the packet stream
|
||||
// 4) we should optimize the avatar data format to be more compact (100 bytes is pretty wasteful).
|
||||
void broadcastAvatarData() {
|
||||
static unsigned char broadcastPacket[MAX_PACKET_SIZE];
|
||||
static unsigned char avatarDataBuffer[MAX_PACKET_SIZE];
|
||||
int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_BULK_AVATAR_DATA);
|
||||
unsigned char* currentBufferPosition = broadcastPacket + numHeaderBytes;
|
||||
int packetLength = currentBufferPosition - broadcastPacket;
|
||||
static QByteArray mixedAvatarByteArray;
|
||||
|
||||
int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
|
||||
|
||||
int packetsSent = 0;
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT && node->getActiveSocket()) {
|
||||
if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()) {
|
||||
|
||||
// reset packet pointers for this node
|
||||
currentBufferPosition = broadcastPacket + numHeaderBytes;
|
||||
packetLength = currentBufferPosition - broadcastPacket;
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
|
||||
// this is an AGENT we have received head data from
|
||||
// send back a packet with other active node data to this node
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()) {
|
||||
|
||||
unsigned char* avatarDataEndpoint = addNodeToBroadcastPacket((unsigned char*)&avatarDataBuffer[0],
|
||||
otherNode.data());
|
||||
int avatarDataLength = avatarDataEndpoint - (unsigned char*)&avatarDataBuffer;
|
||||
QByteArray avatarByteArray;
|
||||
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
||||
|
||||
if (avatarDataLength + packetLength <= MAX_PACKET_SIZE) {
|
||||
memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
|
||||
packetLength += avatarDataLength;
|
||||
currentBufferPosition += avatarDataLength;
|
||||
} else {
|
||||
AvatarData* nodeData = (AvatarData*) otherNode->getLinkedData();
|
||||
avatarByteArray.append(nodeData->toByteArray());
|
||||
|
||||
if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) {
|
||||
packetsSent++;
|
||||
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
|
||||
nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket,
|
||||
currentBufferPosition - broadcastPacket,
|
||||
nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
|
||||
node->getActiveSocket()->getAddress(),
|
||||
node->getActiveSocket()->getPort());
|
||||
|
||||
// reset the packet
|
||||
currentBufferPosition = broadcastPacket + numHeaderBytes;
|
||||
packetLength = currentBufferPosition - broadcastPacket;
|
||||
|
||||
// copy the avatar that didn't fit into the next packet
|
||||
memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
|
||||
packetLength += avatarDataLength;
|
||||
currentBufferPosition += avatarDataLength;
|
||||
mixedAvatarByteArray.resize(numPacketHeaderBytes);
|
||||
}
|
||||
|
||||
// copy the avatar into the mixedAvatarByteArray packet
|
||||
mixedAvatarByteArray.append(avatarByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
packetsSent++;
|
||||
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
|
||||
nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, currentBufferPosition - broadcastPacket,
|
||||
nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
|
||||
node->getActiveSocket()->getAddress(),
|
||||
node->getActiveSocket()->getPort());
|
||||
}
|
||||
|
@ -118,18 +99,15 @@ void broadcastAvatarData() {
|
|||
}
|
||||
|
||||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (killedNode->getType() == NODE_TYPE_AGENT
|
||||
if (killedNode->getType() == NodeType::Agent
|
||||
&& killedNode->getLinkedData()) {
|
||||
// this was an avatar we were sending to other people
|
||||
// send a kill packet for it to our other nodes
|
||||
unsigned char packetData[MAX_PACKET_SIZE];
|
||||
int numHeaderBytes = populateTypeAndVersion(packetData, PACKET_TYPE_KILL_AVATAR);
|
||||
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
|
||||
killPacket += killedNode->getUUID().toRfc4122();
|
||||
|
||||
QByteArray rfcUUID = killedNode->getUUID().toRfc4122();
|
||||
memcpy(packetData + numHeaderBytes, rfcUUID.constData(), rfcUUID.size());
|
||||
|
||||
NodeList::getInstance()->broadcastToNodes(packetData, numHeaderBytes + NUM_BYTES_RFC4122_UUID,
|
||||
QSet<NODE_TYPE>() << NODE_TYPE_AGENT);
|
||||
NodeList::getInstance()->broadcastToNodes(killPacket,
|
||||
NodeSet() << NodeType::Agent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,39 +115,38 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc
|
|||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
switch (dataByteArray[0]) {
|
||||
case PACKET_TYPE_HEAD_DATA: {
|
||||
QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
|
||||
NUM_BYTES_RFC4122_UUID));
|
||||
switch (packetTypeForPacket(dataByteArray)) {
|
||||
case PacketTypeAvatarData: {
|
||||
QUuid nodeUUID;
|
||||
deconstructPacketHeader(dataByteArray, nodeUUID);
|
||||
|
||||
// add or update the node in our list
|
||||
SharedNodePointer avatarNode = nodeList->nodeWithUUID(nodeUUID);
|
||||
|
||||
if (avatarNode) {
|
||||
// parse positional data from an node
|
||||
nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr,
|
||||
(unsigned char*) dataByteArray.data(), dataByteArray.size());
|
||||
nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr, dataByteArray);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_KILL_AVATAR: {
|
||||
case PacketTypeKillAvatar: {
|
||||
nodeList->processKillNode(dataByteArray);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// hand this off to the NodeList
|
||||
nodeList->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
|
||||
nodeList->processNodeData(senderSockAddr, dataByteArray);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AvatarMixer::run() {
|
||||
commonInit(AVATAR_MIXER_LOGGING_NAME, NODE_TYPE_AVATAR_MIXER);
|
||||
commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
|
||||
class AvatarMixer : public ThreadedAssignment {
|
||||
public:
|
||||
AvatarMixer(const unsigned char* dataBuffer, int numBytes);
|
||||
AvatarMixer(const QByteArray& packet);
|
||||
|
||||
public slots:
|
||||
/// runs the avatar mixer
|
||||
|
|
|
@ -17,22 +17,25 @@
|
|||
|
||||
const int SEND_INTERVAL = 50;
|
||||
|
||||
MetavoxelServer::MetavoxelServer(const unsigned char* dataBuffer, int numBytes) :
|
||||
ThreadedAssignment(dataBuffer, numBytes),
|
||||
_data(new MetavoxelData()) {
|
||||
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
|
||||
}
|
||||
|
||||
void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
edit.apply(_data);
|
||||
}
|
||||
|
||||
void MetavoxelServer::removeSession(const QUuid& sessionId) {
|
||||
delete _sessions.take(sessionId);
|
||||
_sessions.take(sessionId)->deleteLater();
|
||||
}
|
||||
|
||||
const char METAVOXEL_SERVER_LOGGING_NAME[] = "metavoxel-server";
|
||||
|
||||
void MetavoxelServer::run() {
|
||||
commonInit(METAVOXEL_SERVER_LOGGING_NAME, NODE_TYPE_METAVOXEL_SERVER);
|
||||
commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer);
|
||||
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
|
@ -40,12 +43,12 @@ void MetavoxelServer::run() {
|
|||
|
||||
void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
|
||||
switch (dataByteArray.at(0)) {
|
||||
case PACKET_TYPE_METAVOXEL_DATA:
|
||||
case PacketTypeMetavoxelData:
|
||||
processData(dataByteArray, senderSockAddr);
|
||||
break;
|
||||
|
||||
default:
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*)dataByteArray.data(), dataByteArray.size());
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -75,16 +78,18 @@ void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& se
|
|||
// forward to session, creating if necessary
|
||||
MetavoxelSession*& session = _sessions[sessionID];
|
||||
if (!session) {
|
||||
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize));
|
||||
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), sender);
|
||||
}
|
||||
session->receivedData(data, sender);
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader) :
|
||||
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
|
||||
const QByteArray& datagramHeader, const HifiSockAddr& sender) :
|
||||
QObject(server),
|
||||
_server(server),
|
||||
_sessionId(sessionId),
|
||||
_sequencer(datagramHeader) {
|
||||
_sequencer(datagramHeader),
|
||||
_sender(sender) {
|
||||
|
||||
const int TIMEOUT_INTERVAL = 30 * 1000;
|
||||
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
|
||||
|
@ -94,10 +99,13 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
|
|||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
|
||||
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
|
||||
|
||||
// insert the baseline send record
|
||||
SendRecord record = { 0, MetavoxelDataPointer(new MetavoxelData()) };
|
||||
SendRecord record = { 0 };
|
||||
_sendRecords.append(record);
|
||||
|
||||
qDebug() << "Opened session [sessionId=" << _sessionId << ", sender=" << _sender << "]";
|
||||
}
|
||||
|
||||
void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
|
@ -114,7 +122,7 @@ void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr&
|
|||
void MetavoxelSession::sendDelta() {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
writeDelta(_server->getData(), _sendRecords.first().data, out);
|
||||
_server->getData().writeDelta(_sendRecords.first().data, out);
|
||||
_sequencer.endPacket();
|
||||
|
||||
// record the send
|
||||
|
@ -134,25 +142,29 @@ void MetavoxelSession::sendData(const QByteArray& data) {
|
|||
void MetavoxelSession::readPacket(Bitstream& in) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
handleMessage(message);
|
||||
}
|
||||
|
||||
void MetavoxelSession::clearSendRecordsBefore(int index) {
|
||||
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
void MetavoxelSession::handleMessage(const QVariant& message) {
|
||||
int userType = message.userType();
|
||||
if (userType == ClientStateMessage::Type) {
|
||||
if (userType == CloseSessionMessage::Type) {
|
||||
qDebug() << "Session closed [sessionId=" << _sessionId << ", sender=" << _sender << "]";
|
||||
_server->removeSession(_sessionId);
|
||||
|
||||
} else if (userType == ClientStateMessage::Type) {
|
||||
ClientStateMessage state = message.value<ClientStateMessage>();
|
||||
_position = state.position;
|
||||
|
||||
} else if (userType == MetavoxelDeltaMessage::Type) {
|
||||
} else if (userType == MetavoxelEditMessage::Type) {
|
||||
_server->applyEdit(message.value<MetavoxelEditMessage>());
|
||||
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
handleMessage(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelSession;
|
||||
|
||||
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
|
||||
|
@ -28,9 +29,11 @@ class MetavoxelServer : public ThreadedAssignment {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelServer(const unsigned char* dataBuffer, int numBytes);
|
||||
MetavoxelServer(const QByteArray& packet);
|
||||
|
||||
const MetavoxelDataPointer& getData() const { return _data; }
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
void removeSession(const QUuid& sessionId);
|
||||
|
||||
|
@ -51,7 +54,7 @@ private:
|
|||
|
||||
QHash<QUuid, MetavoxelSession*> _sessions;
|
||||
|
||||
MetavoxelDataPointer _data;
|
||||
MetavoxelData _data;
|
||||
};
|
||||
|
||||
/// Contains the state of a single client session.
|
||||
|
@ -60,7 +63,8 @@ class MetavoxelSession : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader);
|
||||
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
|
||||
const QByteArray& datagramHeader, const HifiSockAddr& sender);
|
||||
|
||||
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
|
@ -76,14 +80,14 @@ private slots:
|
|||
|
||||
void clearSendRecordsBefore(int index);
|
||||
|
||||
private:
|
||||
void handleMessage(const QVariant& message);
|
||||
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
private:
|
||||
|
||||
class SendRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
MetavoxelDataPointer data;
|
||||
MetavoxelData data;
|
||||
};
|
||||
|
||||
MetavoxelServer* _server;
|
||||
|
|
|
@ -9,10 +9,12 @@ macro(AUTO_MTC TARGET ROOT_DIR)
|
|||
${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
|
||||
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Script REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp)
|
||||
|
||||
qt5_use_modules(${TARGET}_automtc Core)
|
||||
qt5_use_modules(${TARGET}_automtc Core Script Widgets)
|
||||
|
||||
target_link_libraries(${TARGET} ${TARGET}_automtc)
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <HifiSockAddr.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "DataServer.h"
|
||||
|
@ -21,7 +24,8 @@ const unsigned short REDIS_PORT = 6379;
|
|||
DataServer::DataServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_socket(),
|
||||
_redis(NULL)
|
||||
_redis(NULL),
|
||||
_uuid(QUuid::createUuid())
|
||||
{
|
||||
_socket.bind(QHostAddress::Any, DATA_SERVER_LISTEN_PORT);
|
||||
|
||||
|
@ -51,66 +55,60 @@ DataServer::~DataServer() {
|
|||
const int MAX_PACKET_SIZE = 1500;
|
||||
|
||||
void DataServer::readPendingDatagrams() {
|
||||
qint64 receivedBytes = 0;
|
||||
static unsigned char packetData[MAX_PACKET_SIZE];
|
||||
|
||||
QByteArray receivedPacket;
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
while (_socket.hasPendingDatagrams() &&
|
||||
(receivedBytes = _socket.readDatagram(reinterpret_cast<char*>(packetData), MAX_PACKET_SIZE,
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()))) {
|
||||
if ((packetData[0] == PACKET_TYPE_DATA_SERVER_PUT || packetData[0] == PACKET_TYPE_DATA_SERVER_GET) &&
|
||||
packetVersionMatch(packetData, senderSockAddr)) {
|
||||
|
||||
int readBytes = numBytesForPacketHeader(packetData);
|
||||
while (_socket.hasPendingDatagrams()) {
|
||||
receivedPacket.resize(_socket.pendingDatagramSize());
|
||||
_socket.readDatagram(receivedPacket.data(), _socket.pendingDatagramSize(),
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
|
||||
PacketType requestType = packetTypeForPacket(receivedPacket);
|
||||
|
||||
if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) &&
|
||||
packetVersionMatch(receivedPacket)) {
|
||||
|
||||
QDataStream packetStream(receivedPacket);
|
||||
int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket);
|
||||
packetStream.skipRawData(numReceivedHeaderBytes);
|
||||
|
||||
// pull the sequence number used for this packet
|
||||
quint8 sequenceNumber = 0;
|
||||
memcpy(&sequenceNumber, packetData + readBytes, sizeof(sequenceNumber));
|
||||
readBytes += sizeof(sequenceNumber);
|
||||
|
||||
|
||||
packetStream >> sequenceNumber;
|
||||
|
||||
// pull the UUID that we will need as part of the key
|
||||
QString uuidString(reinterpret_cast<char*>(packetData + readBytes));
|
||||
QUuid parsedUUID(uuidString);
|
||||
QString userString;
|
||||
packetStream >> userString;
|
||||
QUuid parsedUUID(userString);
|
||||
|
||||
if (parsedUUID.isNull()) {
|
||||
// we failed to parse a UUID, this means the user has sent us a username
|
||||
|
||||
QString username(reinterpret_cast<char*>(packetData + readBytes));
|
||||
readBytes += username.size() + sizeof('\0');
|
||||
|
||||
// ask redis for the UUID for this user
|
||||
redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username));
|
||||
redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(userString));
|
||||
|
||||
if (reply->type == REDIS_REPLY_STRING) {
|
||||
parsedUUID = QUuid(QString(reply->str));
|
||||
}
|
||||
|
||||
if (!parsedUUID.isNull()) {
|
||||
qDebug() << "Found UUID" << parsedUUID << "for username" << username;
|
||||
qDebug() << "Found UUID" << parsedUUID << "for username" << userString;
|
||||
} else {
|
||||
qDebug() << "Failed UUID lookup for username" << username;
|
||||
qDebug() << "Failed UUID lookup for username" << userString;
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
reply = NULL;
|
||||
} else {
|
||||
readBytes += uuidString.size() + sizeof('\0');
|
||||
}
|
||||
|
||||
if (!parsedUUID.isNull()) {
|
||||
// pull the number of keys the user has sent
|
||||
unsigned char numKeys = packetData[readBytes++];
|
||||
|
||||
if (packetData[0] == PACKET_TYPE_DATA_SERVER_PUT) {
|
||||
if (requestType == PacketTypeDataServerPut) {
|
||||
|
||||
// pull the key that specifies the data the user is putting/getting
|
||||
QString dataKey(reinterpret_cast<char*>(packetData + readBytes));
|
||||
readBytes += dataKey.size() + sizeof('\0');
|
||||
// pull the key and value that specifies the data the user is putting/getting
|
||||
QString dataKey, dataValue;
|
||||
|
||||
// grab the string value the user wants us to put, null terminate it
|
||||
QString dataValue(reinterpret_cast<char*>(packetData + readBytes));
|
||||
readBytes += dataValue.size() + sizeof('\0');
|
||||
packetStream >> dataKey >> dataValue;
|
||||
|
||||
qDebug("Sending command to redis: SET uuid:%s:%s %s",
|
||||
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
|
||||
|
@ -122,78 +120,72 @@ void DataServer::readPendingDatagrams() {
|
|||
|
||||
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
|
||||
// if redis stored the value successfully reply back with a confirm
|
||||
// which is the sent packet with the header replaced
|
||||
packetData[0] = PACKET_TYPE_DATA_SERVER_CONFIRM;
|
||||
_socket.writeDatagram(reinterpret_cast<char*>(packetData), receivedBytes,
|
||||
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
// which is a reply packet with the sequence number
|
||||
QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid);
|
||||
|
||||
replyPacket.append(sequenceNumber);
|
||||
|
||||
_socket.writeDatagram(replyPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} else {
|
||||
// setup a send packet with the returned data
|
||||
// leverage the packetData sent by overwriting and appending
|
||||
int numSendPacketBytes = receivedBytes;
|
||||
QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid);
|
||||
QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
|
||||
|
||||
packetData[0] = PACKET_TYPE_DATA_SERVER_SEND;
|
||||
sendPacketStream << sequenceNumber;
|
||||
|
||||
if (strcmp((char*) packetData + readBytes, "uuid") != 0) {
|
||||
// pull the key list that specifies the data the user is putting/getting
|
||||
QString keyListString;
|
||||
packetStream >> keyListString;
|
||||
|
||||
if (keyListString != "uuid") {
|
||||
|
||||
// copy the parsed UUID
|
||||
sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
|
||||
|
||||
const char MULTI_KEY_VALUE_SEPARATOR = '|';
|
||||
|
||||
// the user has sent one or more keys - make the associated requests
|
||||
for (int j = 0; j < numKeys; j++) {
|
||||
|
||||
// pull the key that specifies the data the user is putting/getting, null terminate it
|
||||
int numDataKeyBytes = 0;
|
||||
|
||||
// look for the key separator or the null terminator
|
||||
while (packetData[readBytes + numDataKeyBytes] != MULTI_KEY_VALUE_SEPARATOR
|
||||
&& packetData[readBytes + numDataKeyBytes] != '\0') {
|
||||
numDataKeyBytes++;
|
||||
}
|
||||
|
||||
QString dataKey(QByteArray(reinterpret_cast<char*>(packetData + readBytes), numDataKeyBytes));
|
||||
readBytes += dataKey.size() + sizeof('\0');
|
||||
|
||||
// append the keyListString back to the sendPacket
|
||||
sendPacketStream << keyListString;
|
||||
|
||||
QStringList keyList = keyListString.split(MULTI_KEY_VALUE_SEPARATOR);
|
||||
QStringList valueList;
|
||||
|
||||
foreach (const QString& dataKey, keyList) {
|
||||
qDebug("Sending command to redis: GET uuid:%s:%s",
|
||||
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
|
||||
qPrintable(dataKey));
|
||||
redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s",
|
||||
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
|
||||
qPrintable(dataKey));
|
||||
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
|
||||
qPrintable(dataKey));
|
||||
|
||||
if (reply->len) {
|
||||
// copy the value that redis returned
|
||||
memcpy(packetData + numSendPacketBytes, reply->str, reply->len);
|
||||
numSendPacketBytes += reply->len;
|
||||
|
||||
valueList << QString(reply->str);
|
||||
} else {
|
||||
// didn't find a value - insert a space
|
||||
packetData[numSendPacketBytes++] = ' ';
|
||||
valueList << QChar(' ');
|
||||
}
|
||||
|
||||
// add the multi-value separator
|
||||
packetData[numSendPacketBytes++] = MULTI_KEY_VALUE_SEPARATOR;
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
// null terminate the packet we're sending back (erases the trailing separator)
|
||||
packetData[(numSendPacketBytes - 1)] = '\0';
|
||||
// append the value QStringList using the right separator
|
||||
sendPacketStream << valueList.join(MULTI_KEY_VALUE_SEPARATOR);
|
||||
} else {
|
||||
// user is asking for a UUID matching username, copy the UUID we found
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(parsedUUID);
|
||||
memcpy(packetData + numSendPacketBytes, qPrintable(uuidString), uuidString.size() + sizeof('\0'));
|
||||
numSendPacketBytes += uuidString.size() + sizeof('\0');
|
||||
// user was asking for their UUID
|
||||
sendPacketStream << userString;
|
||||
sendPacketStream << QString("uuid");
|
||||
sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
|
||||
}
|
||||
|
||||
// reply back with the send packet
|
||||
_socket.writeDatagram(reinterpret_cast<char*>(packetData), numSendPacketBytes,
|
||||
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
|
||||
|
||||
_socket.writeDatagram(sendPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define __hifi__DataServer__
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtNetwork/QUdpSocket>
|
||||
|
||||
#include <hiredis.h>
|
||||
|
@ -22,6 +23,7 @@ public:
|
|||
private:
|
||||
QUdpSocket _socket;
|
||||
redisContext* _redis;
|
||||
QUuid _uuid;
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,17 +9,16 @@
|
|||
#ifndef __hifi__DomainServer__
|
||||
#define __hifi__DomainServer__
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QQueue>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000;
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
|
||||
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
Q_OBJECT
|
||||
|
@ -35,39 +34,27 @@ public slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
private:
|
||||
bool readConfigFile(const char* path);
|
||||
QString readServerAssignmentConfig(QJsonObject jsonObj, const char* nodeName);
|
||||
|
||||
void prepopulateStaticAssignmentFile();
|
||||
Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType);
|
||||
Assignment* deployableAssignmentForRequest(Assignment& requestAssignment);
|
||||
void removeAssignmentFromQueue(Assignment* removableAssignment);
|
||||
bool checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket,
|
||||
const HifiSockAddr& nodeLocalSocket,
|
||||
const QUuid& checkInUUI);
|
||||
void addReleasedAssignmentBackToQueue(Assignment* releasedAssignment);
|
||||
void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet<Assignment::Type>& excludedTypes);
|
||||
void readConfigFile(const QString& path, QSet<Assignment::Type>& excludedTypes);
|
||||
QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName);
|
||||
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
||||
void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString);
|
||||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||
|
||||
unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd);
|
||||
SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
|
||||
HTTPManager _HTTPManager;
|
||||
|
||||
QMutex _assignmentQueueMutex;
|
||||
std::deque<Assignment*> _assignmentQueue;
|
||||
|
||||
QFile _staticAssignmentFile;
|
||||
uchar* _staticAssignmentFileData;
|
||||
|
||||
Assignment* _staticAssignments;
|
||||
|
||||
const char* _voxelServerConfig;
|
||||
const char* _particleServerConfig;
|
||||
const char* _metavoxelServerConfig;
|
||||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _assignmentQueue;
|
||||
|
||||
bool _hasCompletedRestartHold;
|
||||
private slots:
|
||||
void readAvailableDatagrams();
|
||||
void addStaticAssignmentsBackToQueueAfterRestart();
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__DomainServer__) */
|
||||
|
|
|
@ -62,4 +62,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
|
@ -132,7 +132,7 @@ function draw() {
|
|||
print(scriptB);
|
||||
numberParticlesAdded++;
|
||||
} else {
|
||||
Agent.stop();
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
print("Particles Stats: " + Particles.getLifetimeInSeconds() + " seconds," +
|
||||
|
@ -150,5 +150,5 @@ function draw() {
|
|||
// register the call back so it fires before each data send
|
||||
print("here...\n");
|
||||
Particles.setPacketsPerSecond(40000);
|
||||
Agent.willSendVisualDataCallback.connect(draw);
|
||||
Script.willSendVisualDataCallback.connect(draw);
|
||||
print("and here...\n");
|
||||
|
|
202
examples/controllerExample.js
Normal file
202
examples/controllerExample.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
//
|
||||
// controllerExample.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/28/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that demonstrates use of the Controller class
|
||||
//
|
||||
//
|
||||
|
||||
// initialize our triggers
|
||||
var triggerPulled = new Array();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
for (t = 0; t < numberOfTriggers; t++) {
|
||||
triggerPulled[t] = false;
|
||||
}
|
||||
|
||||
function checkController() {
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
// this is expected for hydras
|
||||
if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
|
||||
for (var t = 0; t < numberOfTriggers; t++) {
|
||||
var triggerValue = Controller.getTriggerValue(t);
|
||||
|
||||
if (triggerPulled[t]) {
|
||||
// must release to at least 0.1
|
||||
if (triggerValue < 0.1) {
|
||||
triggerPulled[t] = false; // unpulled
|
||||
}
|
||||
} else {
|
||||
// must pull to at least 0.9
|
||||
if (triggerValue > 0.9) {
|
||||
triggerPulled[t] = true; // pulled
|
||||
triggerToggled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerToggled) {
|
||||
print("a trigger was toggled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
print("keyPressEvent event.key=" + event.key);
|
||||
print("keyPressEvent event.text=" + event.text);
|
||||
|
||||
print("keyPressEvent event.isShifted=" + event.isShifted);
|
||||
print("keyPressEvent event.isControl=" + event.isControl);
|
||||
print("keyPressEvent event.isMeta=" + event.isMeta);
|
||||
print("keyPressEvent event.isAlt=" + event.isAlt);
|
||||
print("keyPressEvent event.isKeypad=" + event.isKeypad);
|
||||
|
||||
|
||||
if (event.key == "A".charCodeAt(0)) {
|
||||
print("the A key was pressed");
|
||||
}
|
||||
if (event.key == " ".charCodeAt(0)) {
|
||||
print("the <space> key was pressed");
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
|
||||
}
|
||||
|
||||
function touchBeginEvent(event) {
|
||||
print("touchBeginEvent event.x,y=" + event.x + ", " + event.y);
|
||||
}
|
||||
|
||||
function touchUpdateEvent(event) {
|
||||
print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y);
|
||||
}
|
||||
|
||||
function touchEndEvent(event) {
|
||||
print("touchEndEvent event.x,y=" + event.x + ", " + event.y);
|
||||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
|
||||
// Map keyPress and mouse move events to our callbacks
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
var KeyEvent_A = {
|
||||
key: "A".charCodeAt(0),
|
||||
text: "A",
|
||||
isShifted: false,
|
||||
isMeta: false
|
||||
};
|
||||
|
||||
var KeyEvent_a = {
|
||||
text: "a",
|
||||
isShifted: false,
|
||||
isMeta: false
|
||||
};
|
||||
|
||||
var KeyEvent_a2 = {
|
||||
key: "a".charCodeAt(0),
|
||||
isShifted: false,
|
||||
isMeta: false
|
||||
};
|
||||
|
||||
var KeyEvent_a3 = {
|
||||
text: "a"
|
||||
};
|
||||
|
||||
var KeyEvent_A2 = {
|
||||
text: "A"
|
||||
};
|
||||
|
||||
|
||||
var KeyEvent_9 = {
|
||||
text: "9"
|
||||
};
|
||||
|
||||
var KeyEvent_Num = {
|
||||
text: "#"
|
||||
};
|
||||
|
||||
var KeyEvent_At = {
|
||||
text: "@"
|
||||
};
|
||||
|
||||
var KeyEvent_MetaAt = {
|
||||
text: "@",
|
||||
isMeta: true
|
||||
};
|
||||
|
||||
var KeyEvent_Up = {
|
||||
text: "up"
|
||||
};
|
||||
var KeyEvent_Down = {
|
||||
text: "down"
|
||||
};
|
||||
var KeyEvent_Left = {
|
||||
text: "left"
|
||||
};
|
||||
var KeyEvent_Right = {
|
||||
text: "right"
|
||||
};
|
||||
|
||||
// prevent the A key from going through to the application
|
||||
print("KeyEvent_A");
|
||||
Controller.captureKeyEvents(KeyEvent_A);
|
||||
|
||||
print("KeyEvent_A2");
|
||||
Controller.captureKeyEvents(KeyEvent_A2);
|
||||
|
||||
print("KeyEvent_a");
|
||||
Controller.captureKeyEvents(KeyEvent_a);
|
||||
|
||||
print("KeyEvent_a2");
|
||||
Controller.captureKeyEvents(KeyEvent_a2);
|
||||
|
||||
print("KeyEvent_a3");
|
||||
Controller.captureKeyEvents(KeyEvent_a3);
|
||||
|
||||
print("KeyEvent_9");
|
||||
Controller.captureKeyEvents(KeyEvent_9);
|
||||
|
||||
print("KeyEvent_Num");
|
||||
Controller.captureKeyEvents(KeyEvent_Num);
|
||||
|
||||
print("KeyEvent_At");
|
||||
Controller.captureKeyEvents(KeyEvent_At);
|
||||
|
||||
print("KeyEvent_MetaAt");
|
||||
Controller.captureKeyEvents(KeyEvent_MetaAt);
|
||||
|
||||
print("KeyEvent_Up");
|
||||
Controller.captureKeyEvents(KeyEvent_Up);
|
||||
print("KeyEvent_Down");
|
||||
Controller.captureKeyEvents(KeyEvent_Down);
|
||||
print("KeyEvent_Left");
|
||||
Controller.captureKeyEvents(KeyEvent_Left);
|
||||
print("KeyEvent_Right");
|
||||
Controller.captureKeyEvents(KeyEvent_Right);
|
||||
|
||||
|
||||
|
||||
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
||||
// Map touch events to our callbacks
|
||||
Controller.touchBeginEvent.connect(touchBeginEvent);
|
||||
Controller.touchUpdateEvent.connect(touchUpdateEvent);
|
||||
Controller.touchEndEvent.connect(touchEndEvent);
|
||||
|
||||
// disable the standard application for touch events
|
||||
Controller.captureTouchEvents();
|
||||
|
||||
function scriptEnding() {
|
||||
// re-enabled the standard application for touch events
|
||||
Controller.releaseTouchEvents();
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(scriptEnding);
|
|
@ -20,7 +20,7 @@ function scriptEnding() {
|
|||
}
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(displayCount);
|
||||
Script.willSendVisualDataCallback.connect(displayCount);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Agent.scriptEnding.connect(scriptEnding);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -69,4 +69,4 @@ function checkSticks() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(checkSticks);
|
||||
Script.willSendVisualDataCallback.connect(checkSticks);
|
|
@ -9,6 +9,9 @@
|
|||
//
|
||||
|
||||
var count = 0;
|
||||
var moveUntil = 2000;
|
||||
var stopAfter = moveUntil + 100;
|
||||
var expectedLifetime = (moveUntil/60) + 1; // 1 second after done moving...
|
||||
|
||||
var originalProperties = {
|
||||
position: { x: 10,
|
||||
|
@ -28,7 +31,9 @@ var originalProperties = {
|
|||
|
||||
color: { red: 0,
|
||||
green: 255,
|
||||
blue: 0 }
|
||||
blue: 0 },
|
||||
|
||||
lifetime: expectedLifetime
|
||||
|
||||
};
|
||||
|
||||
|
@ -38,19 +43,18 @@ var positionDelta = { x: 0.05, y: 0, z: 0 };
|
|||
var particleID = Particles.addParticle(originalProperties);
|
||||
|
||||
function moveParticle() {
|
||||
if (count >= 100) {
|
||||
//Agent.stop();
|
||||
if (count >= moveUntil) {
|
||||
|
||||
// delete it...
|
||||
if (count == 100) {
|
||||
if (count == moveUntil) {
|
||||
print("calling Particles.deleteParticle()");
|
||||
Particles.deleteParticle(particleID);
|
||||
}
|
||||
|
||||
// stop it...
|
||||
if (count >= 200) {
|
||||
print("calling Agent.stop()");
|
||||
Agent.stop();
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
count++;
|
||||
|
@ -77,19 +81,9 @@ function moveParticle() {
|
|||
print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
|
||||
|
||||
Particles.editParticle(particleID, newProperties);
|
||||
|
||||
// also check to see if we can "find" particles...
|
||||
var searchAt = { x: 9, y: 0, z: 0};
|
||||
var searchRadius = 2;
|
||||
var foundParticle = Particles.findClosestParticle(searchAt, searchRadius);
|
||||
if (foundParticle.isKnownID) {
|
||||
print("found particle:" + foundParticle.id);
|
||||
} else {
|
||||
print("could not find particle in or around x=9 to x=11:");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(moveParticle);
|
||||
Script.willSendVisualDataCallback.connect(moveParticle);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// editParticleExample.js
|
||||
// findParticleExample.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/24/14.
|
||||
|
@ -65,8 +65,8 @@ function findParticles() {
|
|||
// run for a while, then clean up
|
||||
// stop it...
|
||||
if (iteration >= 100) {
|
||||
print("calling Agent.stop()");
|
||||
Agent.stop();
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
print("--------------------------");
|
||||
|
@ -122,7 +122,7 @@ function findParticles() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(findParticles);
|
||||
Script.willSendVisualDataCallback.connect(findParticles);
|
||||
|
||||
// register our scriptEnding callback
|
||||
Agent.scriptEnding.connect(scriptEnding);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -60,8 +60,8 @@ function makeFountain() {
|
|||
totalParticles++;
|
||||
}
|
||||
if (totalParticles > 100) {
|
||||
Agent.stop();
|
||||
Script.stop();
|
||||
}
|
||||
}
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(makeFountain);
|
||||
Script.willSendVisualDataCallback.connect(makeFountain);
|
133
examples/gameoflife.js
Normal file
133
examples/gameoflife.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Add your JavaScript for assignment below this line
|
||||
|
||||
// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life)
|
||||
|
||||
var NUMBER_OF_CELLS_EACH_DIMENSION = 64;
|
||||
var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION;
|
||||
|
||||
var currentCells = [];
|
||||
var nextCells = [];
|
||||
|
||||
var METER_LENGTH = 1;
|
||||
var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
|
||||
|
||||
// randomly populate the cell start values
|
||||
for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
|
||||
// create the array to hold this row
|
||||
currentCells[i] = [];
|
||||
|
||||
// create the array to hold this row in the nextCells array
|
||||
nextCells[i] = [];
|
||||
|
||||
for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
|
||||
currentCells[i][j] = Math.floor(Math.random() * 2);
|
||||
|
||||
// put the same value in the nextCells array for first board draw
|
||||
nextCells[i][j] = currentCells[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
function isNeighbourAlive(i, j) {
|
||||
if (i < 0 || i >= NUMBER_OF_CELLS_EACH_DIMENSION
|
||||
|| i < 0 || j >= NUMBER_OF_CELLS_EACH_DIMENSION) {
|
||||
return 0;
|
||||
} else {
|
||||
return currentCells[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
function updateCells() {
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
|
||||
for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
|
||||
for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
|
||||
// figure out the number of live neighbours for the i-j cell
|
||||
var liveNeighbours =
|
||||
isNeighbourAlive(i + 1, j - 1) + isNeighbourAlive(i + 1, j) + isNeighbourAlive(i + 1, j + 1) +
|
||||
isNeighbourAlive(i, j - 1) + isNeighbourAlive(i, j + 1) +
|
||||
isNeighbourAlive(i - 1, j - 1) + isNeighbourAlive(i - 1, j) + isNeighbourAlive(i - 1, j + 1);
|
||||
|
||||
if (currentCells[i][j]) {
|
||||
// live cell
|
||||
|
||||
if (liveNeighbours < 2) {
|
||||
// rule #1 - under-population - this cell will die
|
||||
// mark it zero to mark the change
|
||||
nextCells[i][j] = 0;
|
||||
} else if (liveNeighbours < 4) {
|
||||
// rule #2 - this cell lives
|
||||
// mark it -1 to mark no change
|
||||
nextCells[i][j] = -1;
|
||||
} else {
|
||||
// rule #3 - overcrowding - this cell dies
|
||||
// mark it zero to mark the change
|
||||
nextCells[i][j] = 0;
|
||||
}
|
||||
} else {
|
||||
// dead cell
|
||||
if (liveNeighbours == 3) {
|
||||
// rule #4 - reproduction - this cell revives
|
||||
// mark it one to mark the change
|
||||
nextCells[i][j] = 1;
|
||||
} else {
|
||||
// this cell stays dead
|
||||
// mark it -1 for no change
|
||||
nextCells[i][j] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.001) {
|
||||
// Random mutation to keep things interesting in there.
|
||||
nextCells[i][j] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
|
||||
for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
|
||||
if (nextCells[i][j] != -1) {
|
||||
// there has been a change to this cell, change the value in the currentCells array
|
||||
currentCells[i][j] = nextCells[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendNextCells() {
|
||||
for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
|
||||
for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
|
||||
if (nextCells[i][j] != -1) {
|
||||
// there has been a change to the state of this cell, send it
|
||||
|
||||
// find the x and y position for this voxel, z = 0
|
||||
var x = j * cellScale;
|
||||
var y = i * cellScale;
|
||||
|
||||
// queue a packet to add a voxel for the new cell
|
||||
var color = (nextCells[i][j] == 1) ? 255 : 1;
|
||||
Voxels.setVoxel(x, y, 0, cellScale, color, color, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sentFirstBoard = false;
|
||||
|
||||
function step() {
|
||||
print("step()...");
|
||||
if (sentFirstBoard) {
|
||||
// we've already sent the first full board, perform a step in time
|
||||
updateCells();
|
||||
} else {
|
||||
// this will be our first board send
|
||||
sentFirstBoard = true;
|
||||
}
|
||||
|
||||
sendNextCells();
|
||||
}
|
||||
|
||||
print("here");
|
||||
Script.willSendVisualDataCallback.connect(step);
|
||||
Voxels.setPacketsPerSecond(200);
|
||||
print("now here");
|
26
examples/globalCollisionsExample.js
Normal file
26
examples/globalCollisionsExample.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// globalCollisionsExample.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/29/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that demonstrates use of the Controller class
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
function particleCollisionWithVoxel(particle, voxel) {
|
||||
print("particleCollisionWithVoxel()..");
|
||||
print(" particle.getID()=" + particle.id);
|
||||
print(" voxel color...=" + voxel.red + ", " + voxel.green + ", " + voxel.blue);
|
||||
}
|
||||
|
||||
function particleCollisionWithParticle(particleA, particleB) {
|
||||
print("particleCollisionWithParticle()..");
|
||||
print(" particleA.getID()=" + particleA.id);
|
||||
print(" particleB.getID()=" + particleB.id);
|
||||
}
|
||||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
|
|
@ -72,13 +72,13 @@ function checkController() {
|
|||
" function collisionWithVoxel(voxel) { " +
|
||||
" print('collisionWithVoxel(voxel)... '); " +
|
||||
" print('myID=' + Particle.getID() + '\\n'); " +
|
||||
" var voxelColor = voxel.getColor();" +
|
||||
" print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
|
||||
" var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
|
||||
" var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
|
||||
" var voxelScale = voxel.s;" +
|
||||
" print('voxelColor=' + voxel.red + ', ' + voxel.green + ', ' + voxel.blue + '\\n'); " +
|
||||
" var myColor = Particle.getColor();" +
|
||||
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
|
||||
" Particle.setColor(voxelColor); " +
|
||||
" var voxelAt = voxel.getPosition();" +
|
||||
" var voxelScale = voxel.getScale();" +
|
||||
" Voxels.eraseVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); " +
|
||||
" print('Voxels.eraseVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
|
||||
" } " +
|
||||
|
@ -99,4 +99,4 @@ function checkController() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(checkController);
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
|
|
95
examples/lookWithMouse.js
Normal file
95
examples/lookWithMouse.js
Normal 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
86
examples/lookWithTouch.js
Normal 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);
|
|
@ -41,4 +41,4 @@ function moveVoxel() {
|
|||
|
||||
Voxels.setPacketsPerSecond(300);
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(moveVoxel);
|
||||
Script.willSendVisualDataCallback.connect(moveVoxel);
|
|
@ -65,13 +65,13 @@ function checkController() {
|
|||
" function collisionWithVoxel(voxel) { " +
|
||||
" print('collisionWithVoxel(voxel)... '); " +
|
||||
" print('myID=' + Particle.getID() + '\\n'); " +
|
||||
" var voxelColor = voxel.getColor();" +
|
||||
" var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
|
||||
" var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
|
||||
" var voxelScale = voxel.s;" +
|
||||
" print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
|
||||
" var myColor = Particle.getColor();" +
|
||||
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
|
||||
" Particle.setColor(voxelColor); " +
|
||||
" var voxelAt = voxel.getPosition();" +
|
||||
" var voxelScale = voxel.getScale();" +
|
||||
" Voxels.setVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale, 255, 255, 0); " +
|
||||
" print('Voxels.setVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
|
||||
" } " +
|
||||
|
@ -93,4 +93,4 @@ function checkController() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(checkController);
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
|
|
|
@ -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);
|
||||
|
|
51
examples/particleModelExample.js
Normal file
51
examples/particleModelExample.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// particleModelExample.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/28/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that demonstrates creating and editing a particle
|
||||
//
|
||||
|
||||
var count = 0;
|
||||
var stopAfter = 100;
|
||||
|
||||
var modelProperties = {
|
||||
position: { x: 1, y: 1, z: 1 },
|
||||
velocity: { x: 0.5, y: 0, z: 0.5 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
damping: 0,
|
||||
radius : 0.25,
|
||||
modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
lifetime: 20
|
||||
};
|
||||
|
||||
var ballProperties = {
|
||||
position: { x: 1, y: 0.5, z: 1 },
|
||||
velocity: { x: 0.5, y: 0, z: 0.5 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
damping: 0,
|
||||
radius : 0.25,
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
lifetime: 20
|
||||
};
|
||||
|
||||
var modelParticleID = Particles.addParticle(modelProperties);
|
||||
var ballParticleID = Particles.addParticle(ballProperties);
|
||||
|
||||
function endAfterAWhile() {
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
print("count =" + count);
|
||||
count++;
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(endAfterAWhile);
|
||||
|
|
@ -17,4 +17,4 @@ function maybePlaySound() {
|
|||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
||||
Script.willSendVisualDataCallback.connect(maybePlaySound);
|
50
examples/rideAlongWithAParticleExample.js
Normal file
50
examples/rideAlongWithAParticleExample.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// rideAlongWithAParticleExample.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/24/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that demonstrates "finding" particles
|
||||
//
|
||||
|
||||
var iteration = 0;
|
||||
var lengthOfRide = 2000; // in iterations
|
||||
|
||||
var particleA = Particles.addParticle(
|
||||
{
|
||||
position: { x: 10, y: 0, z: 10 },
|
||||
velocity: { x: 5, y: 0, z: 5 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
radius : 0.1,
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
damping: 0,
|
||||
lifetime: (lengthOfRide * 60) + 1
|
||||
});
|
||||
|
||||
function rideWithParticle() {
|
||||
|
||||
if (iteration <= lengthOfRide) {
|
||||
|
||||
// Check to see if we've been notified of the actual identity of the particles we created
|
||||
if (!particleA.isKnownID) {
|
||||
particleA = Particles.identifyParticle(particleA);
|
||||
}
|
||||
|
||||
var propertiesA = Particles.getParticleProperties(particleA);
|
||||
var newPosition = propertiesA.position;
|
||||
MyAvatar.position = { x: propertiesA.position.x,
|
||||
y: propertiesA.position.y + 2,
|
||||
z: propertiesA.position.z };
|
||||
} else {
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
iteration++;
|
||||
print("iteration="+iteration);
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.willSendVisualDataCallback.connect(rideWithParticle);
|
||||
|
246
examples/spaceInvadersExample.js
Normal file
246
examples/spaceInvadersExample.js
Normal 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
8
examples/timer.js
Normal 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);
|
|
@ -226,4 +226,4 @@ function checkController() {
|
|||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Agent.willSendVisualDataCallback.connect(checkController);
|
||||
Script.willSendVisualDataCallback.connect(checkController);
|
||||
|
|
|
@ -130,4 +130,4 @@ function moveBird() {
|
|||
|
||||
Voxels.setPacketsPerSecond(10000);
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(moveBird);
|
||||
Script.willSendVisualDataCallback.connect(moveBird);
|
|
@ -25,5 +25,5 @@ void main(void) {
|
|||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
|
||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||
}
|
||||
|
|
|
@ -37,5 +37,5 @@ void main(void) {
|
|||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
|
||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||
}
|
||||
|
|
BIN
interface/resources/sounds/snap.wav
Normal file
BIN
interface/resources/sounds/snap.wav
Normal file
Binary file not shown.
|
@ -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
|
@ -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__) */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -10,6 +10,14 @@
|
|||
#include "Application.h"
|
||||
#include "ControllerScriptingInterface.h"
|
||||
|
||||
ControllerScriptingInterface::ControllerScriptingInterface() :
|
||||
_mouseCaptured(false),
|
||||
_touchCaptured(false),
|
||||
_wheelCaptured(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
const PalmData* ControllerScriptingInterface::getPrimaryPalm() const {
|
||||
int leftPalmIndex, rightPalmIndex;
|
||||
|
||||
|
@ -179,5 +187,35 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex
|
|||
return glm::vec3(0); // bad index
|
||||
}
|
||||
|
||||
bool ControllerScriptingInterface::isKeyCaptured(QKeyEvent* event) const {
|
||||
return isKeyCaptured(KeyEvent(*event));
|
||||
}
|
||||
|
||||
bool ControllerScriptingInterface::isKeyCaptured(const KeyEvent& event) const {
|
||||
// if we've captured some combination of this key it will be in the map
|
||||
if (_capturedKeys.contains(event.key, event)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ControllerScriptingInterface::captureKeyEvents(const KeyEvent& event) {
|
||||
// if it's valid
|
||||
if (event.isValid) {
|
||||
// and not already captured
|
||||
if (!isKeyCaptured(event)) {
|
||||
// then add this KeyEvent record to the captured combos for this key
|
||||
_capturedKeys.insert(event.key, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) {
|
||||
if (event.isValid) {
|
||||
// and not already captured
|
||||
if (isKeyCaptured(event)) {
|
||||
_capturedKeys.remove(event.key, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
//
|
||||
// ControllerScriptingInterface.h
|
||||
// hifi
|
||||
|
@ -18,6 +19,27 @@ class PalmData;
|
|||
class ControllerScriptingInterface : public AbstractControllerScriptingInterface {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerScriptingInterface();
|
||||
void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); }
|
||||
void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); }
|
||||
|
||||
void emitMouseMoveEvent(QMouseEvent* event) { emit mouseMoveEvent(MouseEvent(*event)); }
|
||||
void emitMousePressEvent(QMouseEvent* event) { emit mousePressEvent(MouseEvent(*event)); }
|
||||
void emitMouseReleaseEvent(QMouseEvent* event) { emit mouseReleaseEvent(MouseEvent(*event)); }
|
||||
|
||||
void emitTouchBeginEvent(QTouchEvent* event) { emit touchBeginEvent(*event); }
|
||||
void emitTouchEndEvent(QTouchEvent* event) { emit touchEndEvent(*event); }
|
||||
void emitTouchUpdateEvent(QTouchEvent* event) { emit touchUpdateEvent(*event); }
|
||||
void emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); }
|
||||
|
||||
bool isKeyCaptured(QKeyEvent* event) const;
|
||||
bool isKeyCaptured(const KeyEvent& event) const;
|
||||
bool isMouseCaptured() const { return _mouseCaptured; }
|
||||
bool isTouchCaptured() const { return _touchCaptured; }
|
||||
bool isWheelCaptured() const { return _wheelCaptured; }
|
||||
|
||||
|
||||
public slots:
|
||||
virtual bool isPrimaryButtonPressed() const;
|
||||
virtual glm::vec2 getPrimaryJoystickPosition() const;
|
||||
|
@ -36,11 +58,28 @@ public slots:
|
|||
virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const;
|
||||
virtual glm::vec3 getSpatialControlNormal(int controlIndex) const;
|
||||
|
||||
virtual void captureKeyEvents(const KeyEvent& event);
|
||||
virtual void releaseKeyEvents(const KeyEvent& event);
|
||||
|
||||
virtual void captureMouseEvents() { _mouseCaptured = true; }
|
||||
virtual void releaseMouseEvents() { _mouseCaptured = false; }
|
||||
|
||||
virtual void captureTouchEvents() { _touchCaptured = true; }
|
||||
virtual void releaseTouchEvents() { _touchCaptured = false; }
|
||||
|
||||
virtual void captureWheelEvents() { _wheelCaptured = true; }
|
||||
virtual void releaseWheelEvents() { _wheelCaptured = false; }
|
||||
|
||||
private:
|
||||
const PalmData* getPrimaryPalm() const;
|
||||
const PalmData* getPalm(int palmIndex) const;
|
||||
int getNumberOfActivePalms() const;
|
||||
const PalmData* getActivePalm(int palmIndex) const;
|
||||
|
||||
bool _mouseCaptured;
|
||||
bool _touchCaptured;
|
||||
bool _wheelCaptured;
|
||||
QMultiMap<int,KeyEvent> _capturedKeys;
|
||||
};
|
||||
|
||||
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip
|
||||
|
|
|
@ -26,52 +26,53 @@ void DatagramProcessor::processDatagrams() {
|
|||
"DatagramProcessor::processDatagrams()");
|
||||
|
||||
HifiSockAddr senderSockAddr;
|
||||
ssize_t bytesReceived;
|
||||
|
||||
static unsigned char incomingPacket[MAX_PACKET_SIZE];
|
||||
static QByteArray incomingPacket;
|
||||
|
||||
Application* application = Application::getInstance();
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams() &&
|
||||
(bytesReceived = NodeList::getInstance()->getNodeSocket().readDatagram((char*) incomingPacket,
|
||||
MAX_PACKET_SIZE,
|
||||
senderSockAddr.getAddressPointer(),
|
||||
senderSockAddr.getPortPointer()))) {
|
||||
while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams()) {
|
||||
incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
|
||||
nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
|
||||
_packetCount++;
|
||||
_byteCount += bytesReceived;
|
||||
_byteCount += incomingPacket.size();
|
||||
|
||||
if (packetVersionMatch(incomingPacket, senderSockAddr)) {
|
||||
if (packetVersionMatch(incomingPacket)) {
|
||||
// only process this packet if we have a match on the packet version
|
||||
switch (incomingPacket[0]) {
|
||||
case PACKET_TYPE_TRANSMITTER_DATA_V2:
|
||||
switch (packetTypeForPacket(incomingPacket)) {
|
||||
case PacketTypeTransmitterData:
|
||||
// V2 = IOS transmitter app
|
||||
application->_myTransmitter.processIncomingData(incomingPacket, bytesReceived);
|
||||
application->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,32 +148,28 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3
|
|||
return found;
|
||||
}
|
||||
|
||||
int Environment::parseData(const HifiSockAddr& senderAddress, unsigned char* sourceBuffer, int numBytes) {
|
||||
int Environment::parseData(const HifiSockAddr& senderAddress, const QByteArray& packet) {
|
||||
// push past the packet header
|
||||
unsigned char* start = sourceBuffer;
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
|
||||
sourceBuffer += numBytesPacketHeader;
|
||||
numBytes -= numBytesPacketHeader;
|
||||
int bytesRead = numBytesForPacketHeader(packet);
|
||||
|
||||
// get the lock for the duration of the call
|
||||
QMutexLocker locker(&_mutex);
|
||||
|
||||
EnvironmentData newData;
|
||||
while (numBytes > 0) {
|
||||
int dataLength = newData.parseData(sourceBuffer, numBytes);
|
||||
while (bytesRead < packet.size()) {
|
||||
int dataLength = newData.parseData(reinterpret_cast<const unsigned char*>(packet.data()) + bytesRead,
|
||||
packet.size() - bytesRead);
|
||||
|
||||
// update the mapping by address/ID
|
||||
_data[senderAddress][newData.getID()] = newData;
|
||||
|
||||
sourceBuffer += dataLength;
|
||||
numBytes -= dataLength;
|
||||
bytesRead += dataLength;
|
||||
}
|
||||
|
||||
// remove the default mapping, if any
|
||||
_data.remove(HifiSockAddr());
|
||||
|
||||
return sourceBuffer - start;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
ProgramObject* Environment::createSkyProgram(const char* from, int* locations) {
|
||||
|
|
|
@ -34,7 +34,7 @@ public:
|
|||
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration);
|
||||
|
||||
int parseData(const HifiSockAddr& senderSockAddr, unsigned char* sourceBuffer, int numBytes);
|
||||
int parseData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -14,17 +14,20 @@
|
|||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
|
||||
FileLogger::FileLogger() : _logData(NULL) {
|
||||
setExtraDebugging(false);
|
||||
_fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
QDir logDir(_fileName);
|
||||
if (!logDir.exists(_fileName)) {
|
||||
logDir.mkdir(_fileName);
|
||||
}
|
||||
const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
|
||||
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
const QString LOGS_DIRECTORY = "Logs";
|
||||
|
||||
FileLogger::FileLogger(QObject* parent) :
|
||||
AbstractLoggerInterface(parent),
|
||||
_logData(NULL)
|
||||
{
|
||||
setExtraDebugging(false);
|
||||
|
||||
_fileName = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = QHostAddress(getHostOrderLocalAddress());
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
_fileName.append(QString("/hifi-log_%1_%2.txt").arg(clientAddress.toString(), now.toString("yyyy-MM-dd_hh.mm.ss")));
|
||||
_fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
}
|
||||
|
||||
void FileLogger::addMessage(QString message) {
|
||||
|
@ -40,5 +43,5 @@ void FileLogger::addMessage(QString message) {
|
|||
}
|
||||
|
||||
void FileLogger::locateLog() {
|
||||
FileUtils::LocateFile(_fileName);
|
||||
FileUtils::locateFile(_fileName);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ class FileLogger : public AbstractLoggerInterface {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FileLogger();
|
||||
FileLogger(QObject* parent = NULL);
|
||||
|
||||
virtual void addMessage(QString);
|
||||
virtual QStringList getLogData() { return _logData; };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -56,6 +56,7 @@ private:
|
|||
|
||||
void setLayout();
|
||||
void setImportTypes();
|
||||
bool fileAccepted;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ImportDialog__) */
|
||||
|
|
|
@ -116,10 +116,10 @@ Menu::Menu() :
|
|||
this,
|
||||
SLOT(goToLocation()));
|
||||
addActionToQMenuAndActionHash(fileMenu,
|
||||
MenuOption::GoToUser,
|
||||
MenuOption::GoTo,
|
||||
Qt::Key_At,
|
||||
this,
|
||||
SLOT(goToUser()));
|
||||
SLOT(goTo()));
|
||||
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Settings");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -24,6 +24,12 @@ MetavoxelSystem::MetavoxelSystem() :
|
|||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
|
||||
MetavoxelSystem::~MetavoxelSystem() {
|
||||
for (QHash<QUuid, MetavoxelClient*>::const_iterator it = _clients.begin(); it != _clients.end(); it++) {
|
||||
delete it.value();
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::init() {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
|
@ -42,19 +48,23 @@ void MetavoxelSystem::init() {
|
|||
_buffer.create();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->applyEdit(edit);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender));
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
// simulate the clients
|
||||
_points.clear();
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->simulate(deltaTime);
|
||||
client->simulate(deltaTime, _pointVisitor);
|
||||
}
|
||||
|
||||
_points.clear();
|
||||
_data.guide(_pointVisitor);
|
||||
|
||||
_buffer.bind();
|
||||
int bytes = _points.size() * sizeof(Point);
|
||||
if (_buffer.size() < bytes) {
|
||||
|
@ -107,21 +117,20 @@ void MetavoxelSystem::render() {
|
|||
}
|
||||
|
||||
void MetavoxelSystem::nodeAdded(SharedNodePointer node) {
|
||||
if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
|
||||
QMetaObject::invokeMethod(this, "addClient", Q_ARG(const QUuid&, node->getUUID()),
|
||||
Q_ARG(const HifiSockAddr&, node->getLocalSocket()));
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMetaObject::invokeMethod(this, "addClient", Q_ARG(const SharedNodePointer&, node));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::nodeKilled(SharedNodePointer node) {
|
||||
if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID()));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::addClient(const QUuid& uuid, const HifiSockAddr& address) {
|
||||
MetavoxelClient* client = new MetavoxelClient(address);
|
||||
_clients.insert(uuid, client);
|
||||
void MetavoxelSystem::addClient(const SharedNodePointer& node) {
|
||||
MetavoxelClient* client = new MetavoxelClient(node);
|
||||
_clients.insert(node->getUUID(), client);
|
||||
_clientsBySessionID.insert(client->getSessionID(), client);
|
||||
}
|
||||
|
||||
|
@ -139,7 +148,7 @@ void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& s
|
|||
}
|
||||
MetavoxelClient* client = _clientsBySessionID.value(sessionID);
|
||||
if (client) {
|
||||
client->receivedData(data, sender);
|
||||
client->receivedData(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,17 +176,15 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
|
|||
}
|
||||
|
||||
static QByteArray createDatagramHeader(const QUuid& sessionID) {
|
||||
QByteArray header(MAX_PACKET_HEADER_BYTES, 0);
|
||||
populateTypeAndVersion(reinterpret_cast<unsigned char*>(header.data()), PACKET_TYPE_METAVOXEL_DATA);
|
||||
QByteArray header = byteArrayWithPopluatedHeader(PacketTypeMetavoxelData);
|
||||
header += sessionID.toRfc4122();
|
||||
return header;
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) :
|
||||
_address(address),
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
|
||||
_node(node),
|
||||
_sessionID(QUuid::createUuid()),
|
||||
_sequencer(createDatagramHeader(_sessionID)),
|
||||
_data(new MetavoxelData()) {
|
||||
_sequencer(createDatagramHeader(_sessionID)) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
|
@ -188,23 +195,41 @@ MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) :
|
|||
_receiveRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelClient::simulate(float deltaTime) {
|
||||
MetavoxelClient::~MetavoxelClient() {
|
||||
// close the session
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
out << QVariant::fromValue(CloseSessionMessage());
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
|
||||
void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
// apply immediately to local tree
|
||||
edit.apply(_data);
|
||||
|
||||
// start sending it out
|
||||
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
|
||||
}
|
||||
|
||||
void MetavoxelClient::simulate(float deltaTime, MetavoxelVisitor& visitor) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() };
|
||||
out << QVariant::fromValue(state);
|
||||
_sequencer.endPacket();
|
||||
|
||||
_data.guide(visitor);
|
||||
}
|
||||
|
||||
void MetavoxelClient::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
// save the most recent sender
|
||||
_address = sender;
|
||||
|
||||
void MetavoxelClient::receivedData(const QByteArray& data) {
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(data);
|
||||
}
|
||||
|
||||
void MetavoxelClient::sendData(const QByteArray& data) {
|
||||
NodeList::getInstance()->getNodeSocket().writeDatagram(data, _address.getAddress(), _address.getPort());
|
||||
QMutexLocker locker(&_node->getMutex());
|
||||
const HifiSockAddr* address = _node->getActiveSocket();
|
||||
if (address) {
|
||||
NodeList::getInstance()->getNodeSocket().writeDatagram(data, address->getAddress(), address->getPort());
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClient::readPacket(Bitstream& in) {
|
||||
|
@ -215,6 +240,13 @@ void MetavoxelClient::readPacket(Bitstream& in) {
|
|||
// record the receipt
|
||||
ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data };
|
||||
_receiveRecords.append(record);
|
||||
|
||||
// reapply local edits
|
||||
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
|
||||
if (message.data.userType() == MetavoxelEditMessage::Type) {
|
||||
message.data.value<MetavoxelEditMessage>().apply(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClient::clearReceiveRecordsBefore(int index) {
|
||||
|
@ -224,7 +256,7 @@ void MetavoxelClient::clearReceiveRecordsBefore(int index) {
|
|||
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
int userType = message.userType();
|
||||
if (userType == MetavoxelDeltaMessage::Type) {
|
||||
readDelta(_data, _receiveRecords.first().data, in);
|
||||
_data.readDelta(_receiveRecords.first().data, in);
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
#include <MetavoxelMessages.h>
|
||||
|
||||
#include "renderer/ProgramObject.h"
|
||||
|
||||
|
@ -31,10 +32,11 @@ class MetavoxelSystem : public QObject {
|
|||
public:
|
||||
|
||||
MetavoxelSystem();
|
||||
~MetavoxelSystem();
|
||||
|
||||
void init();
|
||||
|
||||
MetavoxelData& getData() { return _data; }
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
void processData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
|
@ -48,7 +50,7 @@ public slots:
|
|||
|
||||
private:
|
||||
|
||||
Q_INVOKABLE void addClient(const QUuid& uuid, const HifiSockAddr& address);
|
||||
Q_INVOKABLE void addClient(const SharedNodePointer& node);
|
||||
Q_INVOKABLE void removeClient(const QUuid& uuid);
|
||||
Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
|
@ -71,7 +73,6 @@ private:
|
|||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
MetavoxelData _data;
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
|
@ -86,13 +87,16 @@ class MetavoxelClient : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelClient(const HifiSockAddr& address);
|
||||
MetavoxelClient(const SharedNodePointer& node);
|
||||
virtual ~MetavoxelClient();
|
||||
|
||||
const QUuid& getSessionID() const { return _sessionID; }
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
void simulate(float deltaTime, MetavoxelVisitor& visitor);
|
||||
|
||||
void receivedData(const QByteArray& data);
|
||||
|
||||
private slots:
|
||||
|
||||
|
@ -109,15 +113,15 @@ private:
|
|||
class ReceiveRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
MetavoxelDataPointer data;
|
||||
MetavoxelData data;
|
||||
};
|
||||
|
||||
HifiSockAddr _address;
|
||||
SharedNodePointer _node;
|
||||
QUuid _sessionID;
|
||||
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
MetavoxelDataPointer _data;
|
||||
MetavoxelData _data;
|
||||
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
//
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include "ParticleTreeRenderer.h"
|
||||
|
@ -16,17 +18,47 @@ ParticleTreeRenderer::ParticleTreeRenderer() :
|
|||
}
|
||||
|
||||
ParticleTreeRenderer::~ParticleTreeRenderer() {
|
||||
// delete the models in _particleModels
|
||||
foreach(Model* model, _particleModels) {
|
||||
delete model;
|
||||
}
|
||||
_particleModels.clear();
|
||||
}
|
||||
|
||||
void ParticleTreeRenderer::init() {
|
||||
OctreeRenderer::init();
|
||||
}
|
||||
|
||||
|
||||
void ParticleTreeRenderer::update() {
|
||||
if (_tree) {
|
||||
ParticleTree* tree = (ParticleTree*)_tree;
|
||||
_tree->lockForWrite();
|
||||
tree->update();
|
||||
_tree->unlock();
|
||||
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
|
||||
if (tree->tryLockForWrite()) {
|
||||
tree->update();
|
||||
tree->unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleTreeRenderer::render() {
|
||||
OctreeRenderer::render();
|
||||
}
|
||||
|
||||
Model* ParticleTreeRenderer::getModel(const QString& url) {
|
||||
Model* model = NULL;
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_particleModels.find(url) == _particleModels.end()) {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
_particleModels[url] = model;
|
||||
} else {
|
||||
model = _particleModels[url];
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
|
||||
// actually render it here...
|
||||
// we need to iterate the actual particles of the element
|
||||
|
@ -36,27 +68,48 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
|
|||
|
||||
uint16_t numberOfParticles = particles.size();
|
||||
|
||||
bool drawAsSphere = true;
|
||||
|
||||
for (uint16_t i = 0; i < numberOfParticles; i++) {
|
||||
const Particle& particle = particles[i];
|
||||
// render particle aspoints
|
||||
glm::vec3 position = particle.getPosition() * (float)TREE_SCALE;
|
||||
glColor3ub(particle.getColor()[RED_INDEX],particle.getColor()[GREEN_INDEX],particle.getColor()[BLUE_INDEX]);
|
||||
float sphereRadius = particle.getRadius() * (float)TREE_SCALE;
|
||||
float radius = particle.getRadius() * (float)TREE_SCALE;
|
||||
|
||||
bool drawAsModel = particle.hasModel();
|
||||
|
||||
args->_renderedItems++;
|
||||
|
||||
if (drawAsSphere) {
|
||||
if (drawAsModel) {
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutSolidSphere(sphereRadius, 15, 15);
|
||||
const float alpha = 1.0f;
|
||||
|
||||
Model* model = getModel(particle.getModelURL());
|
||||
|
||||
glm::vec3 translationAdjustment = particle.getModelTranslation();
|
||||
|
||||
// set the position
|
||||
glm::vec3 translation(position.x, position.y, position.z);
|
||||
model->setTranslation(translation + translationAdjustment);
|
||||
|
||||
// set the rotation
|
||||
glm::quat rotation = particle.getModelRotation();
|
||||
model->setRotation(rotation);
|
||||
|
||||
// scale
|
||||
// TODO: need to figure out correct scale adjust, this was arbitrarily set to make a couple models work
|
||||
const float MODEL_SCALE = 0.00575f;
|
||||
glm::vec3 scale(1.0f,1.0f,1.0f);
|
||||
model->setScale(scale * MODEL_SCALE * radius * particle.getModelScale());
|
||||
|
||||
model->simulate(0.0f);
|
||||
model->render(alpha); // TODO: should we allow particles to have alpha on their models?
|
||||
|
||||
glPopMatrix();
|
||||
} else {
|
||||
glPointSize(sphereRadius);
|
||||
glBegin(GL_POINTS);
|
||||
glVertex3f(position.x, position.y, position.z);
|
||||
glEnd();
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutSolidSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <OctreeRenderer.h>
|
||||
#include <ParticleTree.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include "renderer/Model.h"
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class ParticleTreeRenderer : public OctreeRenderer {
|
||||
|
@ -28,9 +29,9 @@ public:
|
|||
virtual ~ParticleTreeRenderer();
|
||||
|
||||
virtual Octree* createTree() { return new ParticleTree(true); }
|
||||
virtual NODE_TYPE getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }
|
||||
virtual PACKET_TYPE getMyQueryMessageType() const { return PACKET_TYPE_PARTICLE_QUERY; }
|
||||
virtual PACKET_TYPE getExpectedPacketType() const { return PACKET_TYPE_PARTICLE_DATA; }
|
||||
virtual NodeType_t getMyNodeType() const { return NodeType::ParticleServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeParticleData; }
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args);
|
||||
|
||||
void update();
|
||||
|
@ -39,7 +40,13 @@ public:
|
|||
|
||||
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
|
||||
|
||||
virtual void init();
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
Model* getModel(const QString& url);
|
||||
|
||||
QMap<QString, Model*> _particleModels;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ParticleTreeRenderer__) */
|
|
@ -105,34 +105,6 @@ glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
|
|||
return glm::angleAxis(angle, axis);
|
||||
}
|
||||
|
||||
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
|
||||
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,
|
||||
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
|
||||
glm::vec3 safeEulerAngles(const glm::quat& q) {
|
||||
float sy = 2.0f * (q.y * q.w - q.x * q.z);
|
||||
if (sy < 1.0f - EPSILON) {
|
||||
if (sy > -1.0f + EPSILON) {
|
||||
return glm::degrees(glm::vec3(
|
||||
atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)),
|
||||
asinf(sy),
|
||||
atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))));
|
||||
|
||||
} else {
|
||||
// not a unique solution; x + z = atan2(-m21, m11)
|
||||
return glm::degrees(glm::vec3(
|
||||
0.0f,
|
||||
PIf * -0.5f,
|
||||
atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))));
|
||||
}
|
||||
} else {
|
||||
// not a unique solution; x - z = atan2(-m21, m11)
|
||||
return glm::degrees(glm::vec3(
|
||||
0.0f,
|
||||
PIf * 0.5f,
|
||||
-atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))));
|
||||
}
|
||||
}
|
||||
|
||||
// Safe version of glm::mix; based on the code in Nick Bobick's article,
|
||||
// http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde,
|
||||
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,18 @@
|
|||
#include "Menu.h"
|
||||
#include "VoxelPacketProcessor.h"
|
||||
|
||||
void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, unsigned char* packetData, ssize_t packetLength) {
|
||||
void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"VoxelPacketProcessor::processPacket()");
|
||||
|
||||
QByteArray mutablePacket = packet;
|
||||
|
||||
const int WAY_BEHIND = 300;
|
||||
|
||||
if (packetsToProcessCount() > WAY_BEHIND && Application::getInstance()->getLogger()->extraDebugging()) {
|
||||
qDebug("VoxelPacketProcessor::processPacket() packets to process=%d", packetsToProcessCount());
|
||||
}
|
||||
ssize_t messageLength = packetLength;
|
||||
ssize_t messageLength = mutablePacket.size();
|
||||
|
||||
Application* app = Application::getInstance();
|
||||
bool wasStatsPacket = false;
|
||||
|
@ -34,18 +36,19 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
|
|||
app->_voxels.killLocalVoxels();
|
||||
app->_wantToKillLocalVoxels = false;
|
||||
}
|
||||
|
||||
PacketType voxelPacketType = packetTypeForPacket(mutablePacket);
|
||||
|
||||
// note: PACKET_TYPE_OCTREE_STATS can have PACKET_TYPE_VOXEL_DATA
|
||||
// immediately following them inside the same packet. So, we process the PACKET_TYPE_OCTREE_STATS first
|
||||
// note: PacketType_OCTREE_STATS can have PacketType_VOXEL_DATA
|
||||
// immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first
|
||||
// then process any remaining bytes as if it was another packet
|
||||
if (packetData[0] == PACKET_TYPE_OCTREE_STATS) {
|
||||
if (voxelPacketType == PacketTypeOctreeStats) {
|
||||
|
||||
int statsMessageLength = app->parseOctreeStats(packetData, messageLength, senderSockAddr);
|
||||
int statsMessageLength = app->parseOctreeStats(mutablePacket, senderSockAddr);
|
||||
wasStatsPacket = true;
|
||||
if (messageLength > statsMessageLength) {
|
||||
packetData += statsMessageLength;
|
||||
messageLength -= statsMessageLength;
|
||||
if (!packetVersionMatch(packetData, senderSockAddr)) {
|
||||
mutablePacket = mutablePacket.mid(statsMessageLength);
|
||||
if (!packetVersionMatch(packet)) {
|
||||
return; // bail since piggyback data doesn't match our versioning
|
||||
}
|
||||
} else {
|
||||
|
@ -53,31 +56,31 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
|
|||
return; // bail since no piggyback data
|
||||
}
|
||||
} // fall through to piggyback message
|
||||
|
||||
|
||||
voxelPacketType = packetTypeForPacket(mutablePacket);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||
app->trackIncomingVoxelPacket(packetData, messageLength, senderSockAddr, wasStatsPacket);
|
||||
app->trackIncomingVoxelPacket(mutablePacket, senderSockAddr, wasStatsPacket);
|
||||
|
||||
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
|
||||
if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) {
|
||||
|
||||
switch(packetData[0]) {
|
||||
case PACKET_TYPE_PARTICLE_ERASE: {
|
||||
app->_particles.processEraseMessage(QByteArray((char*) packetData, messageLength),
|
||||
senderSockAddr, serverNode.data());
|
||||
switch(voxelPacketType) {
|
||||
case PacketTypeParticleErase: {
|
||||
app->_particles.processEraseMessage(mutablePacket, senderSockAddr, serverNode.data());
|
||||
} break;
|
||||
|
||||
case PACKET_TYPE_PARTICLE_DATA: {
|
||||
app->_particles.processDatagram(QByteArray((char*) packetData, messageLength),
|
||||
senderSockAddr, serverNode.data());
|
||||
case PacketTypeParticleData: {
|
||||
app->_particles.processDatagram(mutablePacket, senderSockAddr, serverNode.data());
|
||||
} break;
|
||||
|
||||
case PACKET_TYPE_ENVIRONMENT_DATA: {
|
||||
app->_environment.parseData(senderSockAddr, packetData, messageLength);
|
||||
case PacketTypeEnvironmentData: {
|
||||
app->_environment.parseData(senderSockAddr, mutablePacket);
|
||||
} break;
|
||||
|
||||
default : {
|
||||
app->_voxels.setDataSourceUUID(serverNode->getUUID());
|
||||
app->_voxels.parseData(packetData, messageLength);
|
||||
app->_voxels.parseData(mutablePacket);
|
||||
app->_voxels.setDataSourceUUID(QUuid());
|
||||
} break;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
||||
class VoxelPacketProcessor : public ReceivedPacketProcessor {
|
||||
protected:
|
||||
virtual void processPacket(const HifiSockAddr& senderSockAddr, unsigned char* packetData, ssize_t packetLength);
|
||||
virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
|
||||
};
|
||||
#endif // __shared__VoxelPacketProcessor__
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "CoverageMap.h"
|
||||
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -183,7 +183,12 @@ void Hand::updateCollisions() {
|
|||
glm::vec3 totalPenetration;
|
||||
|
||||
// check other avatars
|
||||
foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
||||
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
if (avatar == _owningAvatar) {
|
||||
// don't collid with our own hands
|
||||
continue;
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
||||
// Check for palm collisions
|
||||
glm::vec3 myPalmPosition = palm.getPosition();
|
||||
|
@ -205,9 +210,9 @@ void Hand::updateCollisions() {
|
|||
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
|
||||
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
|
||||
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
|
||||
PALM_COLLIDE_FREQUENCY,
|
||||
PALM_COLLIDE_DURATION_MAX,
|
||||
PALM_COLLIDE_DECAY_PER_SAMPLE);
|
||||
PALM_COLLIDE_FREQUENCY,
|
||||
PALM_COLLIDE_DURATION_MAX,
|
||||
PALM_COLLIDE_DECAY_PER_SAMPLE);
|
||||
// If the other person's palm is in motion, move mine downward to show I was hit
|
||||
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
|
||||
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
|
||||
|
|
|
@ -37,8 +37,6 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_leftEyeBlinkVelocity(0.0f),
|
||||
_rightEyeBlinkVelocity(0.0f),
|
||||
_timeWithoutTalking(0.0f),
|
||||
_cameraPitch(_pitch),
|
||||
_mousePitch(0.f),
|
||||
_cameraYaw(_yaw),
|
||||
_isCameraMoving(false),
|
||||
_faceModel(this)
|
||||
|
@ -52,7 +50,6 @@ void Head::init() {
|
|||
|
||||
void Head::reset() {
|
||||
_yaw = _pitch = _roll = 0.0f;
|
||||
_mousePitch = 0.0f;
|
||||
_leanForward = _leanSideways = 0.0f;
|
||||
_faceModel.reset();
|
||||
}
|
||||
|
@ -186,13 +183,6 @@ void Head::setScale (float scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
void Head::setMousePitch(float mousePitch) {
|
||||
const float MAX_PITCH = 90.0f;
|
||||
_mousePitch = glm::clamp(mousePitch, -MAX_PITCH, MAX_PITCH);
|
||||
}
|
||||
|
||||
|
||||
|
||||
glm::quat Head::getOrientation() const {
|
||||
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
|
||||
}
|
||||
|
@ -200,7 +190,7 @@ glm::quat Head::getOrientation() const {
|
|||
glm::quat Head::getCameraOrientation () const {
|
||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
||||
return owningAvatar->getWorldAlignedOrientation()
|
||||
* glm::quat(glm::radians(glm::vec3(_cameraPitch + _mousePitch, _cameraYaw, 0.0f)));
|
||||
* glm::quat(glm::radians(glm::vec3(_pitch, _cameraYaw, 0.0f)));
|
||||
}
|
||||
|
||||
glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
|
||||
|
|
|
@ -46,9 +46,6 @@ public:
|
|||
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
|
||||
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
|
||||
|
||||
float getMousePitch() const { return _mousePitch; }
|
||||
void setMousePitch(float mousePitch);
|
||||
|
||||
glm::quat getOrientation() const;
|
||||
glm::quat getCameraOrientation () const;
|
||||
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
|
||||
|
@ -99,8 +96,6 @@ private:
|
|||
float _leftEyeBlinkVelocity;
|
||||
float _rightEyeBlinkVelocity;
|
||||
float _timeWithoutTalking;
|
||||
float _cameraPitch; // Used to position the camera differently from the head
|
||||
float _mousePitch;
|
||||
float _cameraYaw;
|
||||
bool _isCameraMoving;
|
||||
FaceModel _faceModel;
|
||||
|
|
|
@ -12,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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -53,7 +53,10 @@ void Profile::setUUID(const QUuid& uuid) {
|
|||
|
||||
// when the UUID is changed we need set it appropriately on the NodeList instance
|
||||
NodeList::getInstance()->setOwnerUUID(uuid);
|
||||
}
|
||||
|
||||
// ask for a window title update so the new UUID is presented
|
||||
Application::getInstance()->updateWindowTitle();
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
|
@ -87,7 +90,7 @@ void Profile::updatePosition(const glm::vec3 position) {
|
|||
if (_lastPosition != position) {
|
||||
|
||||
static timeval lastPositionSend = {};
|
||||
const uint64_t DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
|
||||
const quint64 DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
|
||||
const float DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS = 1;
|
||||
|
||||
if (usecTimestampNow() - usecTimestamp(&lastPositionSend) >= DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS &&
|
||||
|
@ -115,10 +118,10 @@ void Profile::updateOrientation(const glm::quat& orientation) {
|
|||
if (_lastOrientation == eulerAngles) {
|
||||
return;
|
||||
}
|
||||
const uint64_t DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
|
||||
const quint64 DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
|
||||
const float DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES = 5.0f;
|
||||
|
||||
uint64_t now = usecTimestampNow();
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now - _lastOrientationSend >= DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS &&
|
||||
glm::distance(_lastOrientation, eulerAngles) >= DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES) {
|
||||
DataServerClient::putValueForKeyAndUserString(DataServerKey::Orientation, QString(createByteArray(eulerAngles)),
|
||||
|
|
|
@ -57,7 +57,7 @@ private:
|
|||
QString _lastDomain;
|
||||
glm::vec3 _lastPosition;
|
||||
glm::vec3 _lastOrientation;
|
||||
uint64_t _lastOrientationSend;
|
||||
quint64 _lastOrientationSend;
|
||||
QUrl _faceModelURL;
|
||||
QUrl _skeletonModelURL;
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@ Faceshift::Faceshift() :
|
|||
}
|
||||
|
||||
bool Faceshift::isActive() const {
|
||||
const uint64_t ACTIVE_TIMEOUT_USECS = 1000000;
|
||||
const quint64 ACTIVE_TIMEOUT_USECS = 1000000;
|
||||
return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ private:
|
|||
bool _tcpEnabled;
|
||||
int _tcpRetryCount;
|
||||
bool _tracking;
|
||||
uint64_t _lastTrackingStateReceived;
|
||||
quint64 _lastTrackingStateReceived;
|
||||
|
||||
glm::quat _headRotation;
|
||||
glm::vec3 _headAngularVelocity;
|
||||
|
|
|
@ -25,7 +25,7 @@ public slots:
|
|||
|
||||
private:
|
||||
|
||||
uint64_t _lastMovement;
|
||||
quint64 _lastMovement;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__SixenseManager__) */
|
||||
|
|
|
@ -60,7 +60,7 @@ void Transmitter::resetLevels() {
|
|||
|
||||
void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) {
|
||||
// Packet's first byte is 'T'
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(packetData));
|
||||
|
||||
const int ROTATION_MARKER_SIZE = 1; // 'R' = Rotation (clockwise about x,y,z)
|
||||
const int ACCELERATION_MARKER_SIZE = 1; // 'A' = Acceleration (x,y,z)
|
||||
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,6 +214,7 @@ protected:
|
|||
private:
|
||||
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, bool translucent);
|
||||
|
||||
float _pupilDilation;
|
||||
std::vector<float> _blendshapeCoefficients;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace starfield {
|
|||
using namespace std;
|
||||
|
||||
typedef uint32_t nuint;
|
||||
typedef uint64_t wuint;
|
||||
typedef quint64 wuint;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue