Merge pull request #1353 from ZappoMan/particle_details

First real pass at Particles
This commit is contained in:
Philip Rosedale 2013-12-11 11:02:33 -08:00
commit ee2fda1412
49 changed files with 2300 additions and 486 deletions

View file

@ -856,7 +856,9 @@ void AnimationServer::readPendingDatagrams() {
packetVersionMatch(packetData)) {
if (packetData[0] == PACKET_TYPE_JURISDICTION) {
if (::jurisdictionListener) {
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);
}
}

View file

@ -8,7 +8,6 @@
#include "AnimationServer.h"
int main(int argc, char * argv[]) {
AnimationServer animationServer(argc, argv);
return animationServer.exec();

View file

@ -15,6 +15,7 @@
#include <AvatarData.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <UUID.h>
#include <VoxelConstants.h>
@ -25,25 +26,22 @@ Agent::Agent(const unsigned char* dataBuffer, int numBytes) :
{
}
QScriptValue vec3toScriptValue(QScriptEngine *engine, const glm::vec3 &vec3) {
QScriptValue obj = engine->newObject();
obj.setProperty("x", vec3.x);
obj.setProperty("y", vec3.y);
obj.setProperty("z", vec3.z);
return obj;
}
void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) {
vec3.x = object.property("x").toVariant().toFloat();
vec3.y = object.property("y").toVariant().toFloat();
vec3.z = object.property("z").toVariant().toFloat();
}
void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
if (dataByteArray[0] == PACKET_TYPE_JURISDICTION) {
_voxelScriptingInterface.getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
int headerBytes = numBytesForPacketHeader((const unsigned char*) dataByteArray.constData());
// PACKET_TYPE_JURISDICTION, first byte is the node type...
switch (dataByteArray[headerBytes]) {
case NODE_TYPE_VOXEL_SERVER:
_voxelScriptingInterface.getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
(unsigned char*) dataByteArray.data(),
dataByteArray.size());
break;
case NODE_TYPE_PARTICLE_SERVER:
_particleScriptingInterface.getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
(unsigned char*) dataByteArray.data(),
dataByteArray.size());
break;
}
} else {
NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
}
@ -53,7 +51,10 @@ void Agent::run() {
NodeList* nodeList = NodeList::getInstance();
nodeList->setOwnerType(NODE_TYPE_AGENT);
const char AGENT_NODE_TYPES_OF_INTEREST[1] = { NODE_TYPE_VOXEL_SERVER };
// XXXBHG - this seems less than ideal. There might be classes (like jurisdiction listeners, that need access to
// other node types, but for them to get access to those node types, we have to add them here. It seems like
// NodeList should support adding types of interest
const NODE_TYPE AGENT_NODE_TYPES_OF_INTEREST[] = { NODE_TYPE_VOXEL_SERVER, NODE_TYPE_PARTICLE_SERVER };
nodeList->setNodeTypesOfInterest(AGENT_NODE_TYPES_OF_INTEREST, sizeof(AGENT_NODE_TYPES_OF_INTEREST));
@ -76,13 +77,16 @@ void Agent::run() {
QScriptEngine engine;
// register meta-type for glm::vec3 conversions
qScriptRegisterMetaType(&engine, vec3toScriptValue, vec3FromScriptValue);
registerMetaTypes(&engine);
QScriptValue agentValue = engine.newQObject(this);
engine.globalObject().setProperty("Agent", agentValue);
QScriptValue voxelScripterValue = engine.newQObject(&_voxelScriptingInterface);
engine.globalObject().setProperty("Voxels", voxelScripterValue);
QScriptValue particleScripterValue = engine.newQObject(&_particleScriptingInterface);
engine.globalObject().setProperty("Particles", particleScripterValue);
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
engine.globalObject().setProperty("TREE_SCALE", treeScaleValue);
@ -91,6 +95,7 @@ void Agent::run() {
// let the VoxelPacketSender know how frequently we plan to call it
_voxelScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS);
_particleScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS);
qDebug() << "Downloaded script:" << scriptContents << "\n";
QScriptValue result = engine.evaluate(scriptContents);
@ -127,9 +132,11 @@ void Agent::run() {
QCoreApplication::processEvents();
bool willSendVisualDataCallBack = false;
if (_voxelScriptingInterface.getVoxelPacketSender()->voxelServersExist()) {
// allow the scripter's call back to setup visual data
emit willSendVisualDataCallback();
willSendVisualDataCallBack = true;
// release the queue of edit voxel messages.
_voxelScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();
@ -137,6 +144,22 @@ void Agent::run() {
// since we're in non-threaded mode, call process so that the packets are sent
_voxelScriptingInterface.getVoxelPacketSender()->process();
}
if (_particleScriptingInterface.getParticlePacketSender()->serversExist()) {
// allow the scripter's call back to setup visual data
willSendVisualDataCallBack = true;
// release the queue of edit voxel messages.
_particleScriptingInterface.getParticlePacketSender()->releaseQueuedMessages();
// since we're in non-threaded mode, call process so that the packets are sent
_particleScriptingInterface.getParticlePacketSender()->process();
}
if (willSendVisualDataCallBack) {
emit willSendVisualDataCallback();
}
if (engine.hasUncaughtException()) {
int line = engine.uncaughtExceptionLineNumber();

View file

@ -17,7 +17,8 @@
#include <ThreadedAssignment.h>
#include "voxels/VoxelScriptingInterface.h"
#include <VoxelScriptingInterface.h>
#include <ParticleScriptingInterface.h>
class Agent : public ThreadedAssignment {
Q_OBJECT
@ -33,6 +34,7 @@ signals:
void willSendVisualDataCallback();
private:
VoxelScriptingInterface _voxelScriptingInterface;
ParticleScriptingInterface _particleScriptingInterface;
};
#endif /* defined(__hifi__Agent__) */

View file

@ -51,6 +51,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
const char VOXEL_CONFIG_OPTION[] = "--voxelServerConfig";
_voxelServerConfig = getCmdOption(argc, (const char**) argv, VOXEL_CONFIG_OPTION);
const char PARTICLE_CONFIG_OPTION[] = "--particleServerConfig";
_particleServerConfig = getCmdOption(argc, (const char**) argv, PARTICLE_CONFIG_OPTION);
// setup the mongoose web server
struct mg_callbacks callbacks = {};

View file

@ -84,6 +84,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})

View file

@ -131,6 +131,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_voxelProcessor(),
_voxelHideShowThread(&_voxels),
_voxelEditSender(this),
_particleEditSender(this),
_packetCount(0),
_packetsPerSecond(0),
_bytesPerSecond(0),
@ -206,7 +207,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
#endif
// tell the NodeList instance who to tell the domain server we care about
const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER};
const char nodeTypesOfInterest[] = {NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER, NODE_TYPE_VOXEL_SERVER,
NODE_TYPE_PARTICLE_SERVER};
nodeList->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest));
QTimer* silentNodeTimer = new QTimer(this);
@ -232,6 +234,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
// Tell our voxel edit sender about our known jurisdictions
_voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions);
_particleEditSender.setServerJurisdictions(&_particleServerJurisdictions);
}
Application::~Application() {
@ -319,6 +322,7 @@ void Application::initializeGL() {
_voxelProcessor.initialize(_enableProcessVoxelsThread);
_voxelEditSender.initialize(_enableProcessVoxelsThread);
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
_particleEditSender.initialize(_enableProcessVoxelsThread);
if (_enableProcessVoxelsThread) {
qDebug("Voxel parsing thread created.\n");
}
@ -620,6 +624,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier);
bool isMeta = event->modifiers().testFlag(Qt::ControlModifier);
switch (event->key()) {
case Qt::Key_N:
shootParticle();
break;
case Qt::Key_Shift:
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) {
_pasteMode = true;
@ -1229,7 +1236,8 @@ void Application::wheelEvent(QWheelEvent* event) {
void Application::sendPingPackets() {
const char nodesToPing[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER};
const char nodesToPing[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_PARTICLE_SERVER,
NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER};
uint64_t currentTime = usecTimestampNow();
unsigned char pingPacket[numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_PING) + sizeof(currentTime)];
@ -1383,6 +1391,7 @@ void Application::terminate() {
_voxelProcessor.terminate();
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
_particleEditSender.terminate();
}
static Avatar* processAvatarMessageHeader(unsigned char*& packetData, size_t& dataBytes) {
@ -1485,6 +1494,40 @@ void Application::removeVoxel(glm::vec3 position,
_voxels.deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
}
void Application::shootParticle() {
glm::vec3 position = _viewFrustum.getPosition();
glm::vec3 direction = _viewFrustum.getDirection();
const float LINEAR_VELOCITY = 30.0f;
glm::vec3 lookingAt = position + (direction * LINEAR_VELOCITY);
const float radius = 0.5 / TREE_SCALE;
xColor color = { 255, 0, 0};
glm::vec3 velocity = lookingAt - position;
glm::vec3 gravity = DEFAULT_GRAVITY;
float damping = DEFAULT_DAMPING;
QString updateScript("");
makeParticle(position / (float)TREE_SCALE, radius, color,
velocity / (float)TREE_SCALE, gravity, damping, updateScript);
}
void Application::makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, QString updateScript) {
// setup a ParticleDetail struct with the data
ParticleDetail addParticleDetail = { position, radius, {color.red, color.green, color.blue },
velocity, gravity, damping, updateScript };
// queue the packet
_particleEditSender.queueParticleEditMessages(PACKET_TYPE_PARTICLE_ADD, 1, &addParticleDetail);
// release them
_particleEditSender.releaseQueuedMessages();
}
void Application::makeVoxel(glm::vec3 position,
float scale,
unsigned char red,
@ -1802,6 +1845,9 @@ void Application::init() {
_voxels.setVoxelsAsPoints(Menu::getInstance()->isOptionChecked(MenuOption::VoxelsAsPoints));
_voxels.setDisableFastVoxelPipeline(false);
_voxels.init();
_particles.init();
_particles.setViewFrustum(getViewFrustum());
_palette.init(_glWidget->width(), _glWidget->height());
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0);
@ -2288,6 +2334,7 @@ void Application::updateThreads(float deltaTime) {
_voxelProcessor.threadRoutine();
_voxelHideShowThread.threadRoutine();
_voxelEditSender.threadRoutine();
_particleEditSender.threadRoutine();
}
}
@ -2468,6 +2515,8 @@ void Application::update(float deltaTime) {
updateDialogs(deltaTime); // update various stats dialogs if present
updateAudio(deltaTime); // Update audio stats for procedural sounds
updateCursor(deltaTime); // Handle cursor updates
_particles.update(); // update the particles...
}
void Application::updateAvatar(float deltaTime) {
@ -2580,10 +2629,11 @@ void Application::updateAvatar(float deltaTime) {
loadViewFrustum(_myCamera, _viewFrustum);
// Update my voxel servers with my current voxel query...
queryVoxels();
queryOctree(NODE_TYPE_VOXEL_SERVER, PACKET_TYPE_VOXEL_QUERY, _voxelServerJurisdictions);
queryOctree(NODE_TYPE_PARTICLE_SERVER, PACKET_TYPE_PARTICLE_QUERY, _particleServerJurisdictions);
}
void Application::queryVoxels() {
void Application::queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions) {
// if voxels are disabled, then don't send this at all...
if (!Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
@ -2619,8 +2669,9 @@ void Application::queryVoxels() {
int unknownJurisdictionServers = 0;
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER) {
// only send to the NodeTypes that are serverType
if (node->getActiveSocket() != NULL && node->getType() == serverType) {
totalServers++;
// get the server bounds for this server
@ -2628,10 +2679,10 @@ void Application::queryVoxels() {
// if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction...
if (_voxelServerJurisdictions.find(nodeUUID) == _voxelServerJurisdictions.end()) {
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownJurisdictionServers++;
} else {
const JurisdictionMap& map = (_voxelServerJurisdictions)[nodeUUID];
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
@ -2650,7 +2701,7 @@ void Application::queryVoxels() {
}
}
}
if (wantExtraDebugging && unknownJurisdictionServers > 0) {
qDebug("Servers: total %d, in view %d, unknown jurisdiction %d \n",
totalServers, inViewServers, unknownJurisdictionServers);
@ -2677,8 +2728,8 @@ void Application::queryVoxels() {
}
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER) {
// only send to the NodeTypes that are serverType
if (node->getActiveSocket() != NULL && node->getType() == serverType) {
// get the server bounds for this server
@ -2689,13 +2740,13 @@ void Application::queryVoxels() {
// if we haven't heard from this voxel server, go ahead and send it a query, so we
// can get the jurisdiction...
if (_voxelServerJurisdictions.find(nodeUUID) == _voxelServerJurisdictions.end()) {
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownView = true; // assume it's in view
if (wantExtraDebugging) {
qDebug() << "no known jurisdiction for node " << *node << ", assume it's visible.\n";
}
} else {
const JurisdictionMap& map = (_voxelServerJurisdictions)[nodeUUID];
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
@ -2751,7 +2802,7 @@ void Application::queryVoxels() {
unsigned char* endOfVoxelQueryPacket = voxelQueryPacket;
// insert packet type/version and node UUID
endOfVoxelQueryPacket += populateTypeAndVersion(endOfVoxelQueryPacket, PACKET_TYPE_VOXEL_QUERY);
endOfVoxelQueryPacket += populateTypeAndVersion(endOfVoxelQueryPacket, packetType);
QByteArray ownerUUID = nodeList->getOwnerUUID().toRfc4122();
memcpy(endOfVoxelQueryPacket, ownerUUID.constData(), ownerUUID.size());
endOfVoxelQueryPacket += ownerUUID.size();
@ -2997,6 +3048,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
}
}
// render particles...
_particles.render();
// render the ambient occlusion effect if enabled
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
@ -4059,6 +4113,7 @@ void Application::domainChanged(QString domain) {
// reset our node to stats and node to jurisdiction maps... since these must be changing...
_voxelServerJurisdictions.clear();
_voxelServerSceneStats.clear();
_particleServerJurisdictions.clear();
}
void Application::nodeAdded(Node* node) {
@ -4097,6 +4152,37 @@ void Application::nodeKilled(Node* node) {
}
_voxelSceneStatsLock.unlock();
} else if (node->getType() == NODE_TYPE_PARTICLE_SERVER) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
if (_particleServerJurisdictions.find(nodeUUID) != _particleServerJurisdictions.end()) {
unsigned char* rootCode = _particleServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
printf("particle server going away...... v[%f, %f, %f, %f]\n",
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
// Add the jurisditionDetails object to the list of "fade outs"
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE);
fade.voxelDetails = rootDetails;
const float slightly_smaller = 0.99;
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
_voxelFades.push_back(fade);
}
// If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
_particleServerJurisdictions.erase(nodeUUID);
}
// also clean up scene stats for that server
_voxelSceneStatsLock.lockForWrite();
if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) {
_voxelServerSceneStats.erase(nodeUUID);
}
_voxelSceneStatsLock.unlock();
} else if (node->getType() == NODE_TYPE_AGENT) {
Avatar* avatar = static_cast<Avatar*>(node->getLinkedData());
if (avatar == _lookatTargetAvatar) {
@ -4128,10 +4214,10 @@ void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t m
}
}
int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderSockAddr) {
int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderSockAddr) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
Node* server = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
// parse the incoming stats datas stick it in a temporary object for now, while we
// determine which server it belongs to
@ -4139,8 +4225,8 @@ int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLeng
int statsMessageLength = temp.unpackFromMessage(messageData, messageLength);
// quick fix for crash... why would voxelServer be NULL?
if (voxelServer) {
QUuid nodeUUID = voxelServer->getUUID();
if (server) {
QUuid nodeUUID = server->getUUID();
// now that we know the node ID, let's add these stats to the stats for that node...
_voxelSceneStatsLock.lockForWrite();
@ -4155,8 +4241,16 @@ int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLeng
voxelDetailsForCode(temp.getJurisdictionRoot(), rootDetails);
// see if this is the first we've heard of this node...
if (_voxelServerJurisdictions.find(nodeUUID) == _voxelServerJurisdictions.end()) {
printf("stats from new voxel server... v[%f, %f, %f, %f]\n",
NodeToJurisdictionMap* jurisdiction = NULL;
if (server->getType() == NODE_TYPE_VOXEL_SERVER) {
jurisdiction = &_voxelServerJurisdictions;
} else {
jurisdiction = &_particleServerJurisdictions;
}
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
printf("stats from new server... v[%f, %f, %f, %f]\n",
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
// Add the jurisditionDetails object to the list of "fade outs"
@ -4174,7 +4268,7 @@ int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLeng
// details from the VoxelSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
_voxelServerJurisdictions[nodeUUID] = jurisdictionMap;
(*jurisdiction)[nodeUUID] = jurisdictionMap;
}
return statsMessageLength;
}
@ -4210,6 +4304,8 @@ void* Application::networkReceive(void* args) {
QMetaObject::invokeMethod(&app->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
Q_ARG(QByteArray, QByteArray((char*) app->_incomingPacket, bytesReceived)));
break;
case PACKET_TYPE_PARTICLE_DATA:
case PACKET_TYPE_VOXEL_DATA:
case PACKET_TYPE_VOXEL_ERASE:
case PACKET_TYPE_OCTREE_STATS:

View file

@ -22,6 +22,7 @@
#include <NetworkPacket.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <ParticleEditPacketSender.h>
#include <VoxelQuery.h>
#ifndef _WIN32
@ -63,6 +64,7 @@
#include "ui/VoxelStatsDialog.h"
#include "ui/RearMirrorTools.h"
#include "ui/LodToolsDialog.h"
#include "ParticleTreeRenderer.h"
class QAction;
class QActionGroup;
@ -116,6 +118,10 @@ public:
void updateWindowTitle();
void wheelEvent(QWheelEvent* event);
void shootParticle(); // shoots a particle in the direction you're looking
void makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
glm::vec3 gravity, float damping, QString updateScript);
void makeVoxel(glm::vec3 position,
float scale,
@ -284,7 +290,7 @@ private:
void updateAvatar(float deltaTime);
void updateAvatars(float deltaTime, glm::vec3 mouseRayOrigin, glm::vec3 mouseRayDirection);
void queryVoxels();
void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
glm::vec3 getSunDirection();
@ -341,6 +347,8 @@ private:
VoxelImporter _voxelImporter;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
ParticleTreeRenderer _particles;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -450,6 +458,7 @@ private:
VoxelPacketProcessor _voxelProcessor;
VoxelHideShowThread _voxelHideShowThread;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
unsigned char _incomingPacket[MAX_PACKET_SIZE];
int _packetCount;
@ -470,11 +479,12 @@ private:
PieMenu _pieMenu;
int parseVoxelStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
int parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
NodeToVoxelSceneStats _voxelServerSceneStats;
QReadWriteLock _voxelSceneStatsLock;

View file

@ -0,0 +1,62 @@
//
// ParticleTreeRenderer.cpp
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#include "InterfaceConfig.h"
#include "ParticleTreeRenderer.h"
ParticleTreeRenderer::ParticleTreeRenderer() :
OctreeRenderer() {
}
ParticleTreeRenderer::~ParticleTreeRenderer() {
}
void ParticleTreeRenderer::update() {
if (_tree) {
ParticleTree* tree = (ParticleTree*)_tree;
_tree->lockForWrite();
tree->update();
_tree->unlock();
}
}
void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
// actually render it here...
// we need to iterate the actual particles of the element
ParticleTreeElement* particleTreeElement = (ParticleTreeElement*)element;
const std::vector<Particle>& particles = particleTreeElement->getParticles();
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;
args->_renderedItems++;
if (drawAsSphere) {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(sphereRadius, 15, 15);
glPopMatrix();
} else {
glPointSize(sphereRadius);
glBegin(GL_POINTS);
glVertex3f(position.x, position.y, position.z);
glEnd();
}
}
}

View file

@ -0,0 +1,40 @@
//
// ParticleTreeRenderer.h
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#ifndef __hifi__ParticleTreeRenderer__
#define __hifi__ParticleTreeRenderer__
#include <glm/glm.hpp>
#include <stdint.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
#include <ParticleTree.h>
#include <ViewFrustum.h>
// Generic client side Octree renderer class.
class ParticleTreeRenderer : public OctreeRenderer {
public:
ParticleTreeRenderer();
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 void renderElement(OctreeElement* element, RenderArgs* args);
void update();
protected:
};
#endif /* defined(__hifi__ParticleTreeRenderer__) */

View file

@ -39,7 +39,7 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
// then process any remaining bytes as if it was another packet
if (packetData[0] == PACKET_TYPE_OCTREE_STATS) {
int statsMessageLength = app->parseVoxelStats(packetData, messageLength, senderSockAddr);
int statsMessageLength = app->parseOctreeStats(packetData, messageLength, senderSockAddr);
wasStatsPacket = true;
if (messageLength > statsMessageLength) {
packetData += statsMessageLength;
@ -58,7 +58,10 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (voxelServer && *voxelServer->getActiveSocket() == senderSockAddr) {
if (packetData[0] == PACKET_TYPE_ENVIRONMENT_DATA) {
if (packetData[0] == PACKET_TYPE_PARTICLE_DATA) {
//printf("VoxelPacketProcessor::processPacket().... got PACKET_TYPE_PARTICLE_DATA\n");
app->_particles.processDatagram(QByteArray((char*) packetData, messageLength), senderSockAddr);
} else if (packetData[0] == PACKET_TYPE_ENVIRONMENT_DATA) {
app->_environment.parseData(senderSockAddr, packetData, messageLength);
} else {
app->_voxels.setDataSourceUUID(voxelServer->getUUID());

View file

@ -42,7 +42,7 @@ void OctreeInboundPacketProcessor::resetStats() {
void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr,
unsigned char* packetData, ssize_t packetLength) {
bool debugProcessPacket = _myServer->wantsVerboseDebug();
bool debugProcessPacket = true; //_myServer->wantsVerboseDebug();
if (debugProcessPacket) {
printf("OctreeInboundPacketProcessor::processPacket() packetData=%p packetLength=%ld\n", packetData, packetLength);
@ -66,8 +66,9 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA
uint64_t lockWaitTime = 0;
if (_myServer->wantsDebugReceiving()) {
printf("PROCESSING THREAD: got %c - %d command from client receivedBytes=%ld sequence=%d transitTime=%llu usecs\n",
packetType, _receivedPacketCount, packetLength, sequence, transitTime);
printf("PROCESSING THREAD: got '%c' packet - %d command from client "
"receivedBytes=%ld sequence=%d transitTime=%llu usecs\n",
packetType, _receivedPacketCount, packetLength, sequence, transitTime);
}
int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt);
unsigned char* editData = (unsigned char*)&packetData[atByte];

View file

@ -64,8 +64,12 @@ bool OctreePersistThread::process() {
if (isStillRunning()) {
uint64_t MSECS_TO_USECS = 1000;
uint64_t USECS_TO_SLEEP = 100 * MSECS_TO_USECS; // every 100ms
uint64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms
usleep(USECS_TO_SLEEP);
// do our updates then check to save...
_tree->update();
uint64_t now = usecTimestampNow();
uint64_t sinceLastSave = now - _lastCheck;
uint64_t intervalToCheck = _persistInterval * MSECS_TO_USECS;

View file

@ -93,7 +93,7 @@ OctreeServer::~OctreeServer() {
// tell our NodeList we're done with notifications
NodeList::getInstance()->removeHook(this);
delete _jurisdiction;
_jurisdiction = NULL;
@ -539,7 +539,7 @@ void OctreeServer::processDatagram(const QByteArray& dataByteArray, const HifiSo
nodeData->initializeOctreeSendThread(this);
}
}
} else if (_jurisdictionSender && packetType == PACKET_TYPE_JURISDICTION_REQUEST) {
} else if (packetType == PACKET_TYPE_JURISDICTION_REQUEST) {
_jurisdictionSender->queueReceivedPacket(senderSockAddr, (unsigned char*) dataByteArray.data(),
dataByteArray.size());
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
@ -669,18 +669,17 @@ void OctreeServer::run() {
}
HifiSockAddr senderSockAddr;
// set up our jurisdiction broadcaster...
_jurisdictionSender = new JurisdictionSender(_jurisdiction);
if (_jurisdictionSender) {
_jurisdictionSender->initialize(true);
if (_jurisdiction) {
_jurisdiction->setNodeType(getMyNodeType());
}
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
_jurisdictionSender->initialize(true);
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
if (_octreeInboundPacketProcessor) {
_octreeInboundPacketProcessor->initialize(true);
}
_octreeInboundPacketProcessor->initialize(true);
// Convert now to tm struct for local timezone
tm* localtm = localtime(&_started);

View file

@ -16,12 +16,16 @@
#include "JurisdictionListener.h"
JurisdictionListener::JurisdictionListener(PacketSenderNotify* notify) :
JurisdictionListener::JurisdictionListener(NODE_TYPE type, PacketSenderNotify* notify) :
PacketSender(notify, JurisdictionListener::DEFAULT_PACKETS_PER_SECOND)
{
_nodeType = type;
ReceivedPacketProcessor::_dontSleep = true; // we handle sleeping so this class doesn't need to
NodeList* nodeList = NodeList::getInstance();
nodeList->addHook(this);
//qDebug("JurisdictionListener::JurisdictionListener(NODE_TYPE type=%c)\n", type);
}
JurisdictionListener::~JurisdictionListener() {
@ -40,6 +44,8 @@ void JurisdictionListener::nodeKilled(Node* node) {
}
bool JurisdictionListener::queueJurisdictionRequest() {
//qDebug() << "JurisdictionListener::queueJurisdictionRequest()\n";
static unsigned char buffer[MAX_PACKET_SIZE];
unsigned char* bufferOut = &buffer[0];
ssize_t sizeOut = populateTypeAndVersion(bufferOut, PACKET_TYPE_JURISDICTION_REQUEST);
@ -47,7 +53,8 @@ bool JurisdictionListener::queueJurisdictionRequest() {
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (nodeList->getNodeActiveSocketOrPing(&(*node)) && node->getType() == NODE_TYPE_VOXEL_SERVER) {
if (nodeList->getNodeActiveSocketOrPing(&(*node)) &&
node->getType() == getNodeType()) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
PacketSender::queuePacketForSending(*nodeAddress, bufferOut, sizeOut);
nodeCount++;

View file

@ -26,7 +26,7 @@ public:
static const int DEFAULT_PACKETS_PER_SECOND = 1;
static const int NO_SERVER_CHECK_RATE = 60; // if no servers yet detected, keep checking at 60fps
JurisdictionListener(PacketSenderNotify* notify = NULL);
JurisdictionListener(NODE_TYPE type = NODE_TYPE_VOXEL_SERVER, PacketSenderNotify* notify = NULL);
~JurisdictionListener();
virtual bool process();
@ -38,6 +38,9 @@ public:
/// Called by NodeList to inform us that a node has been killed.
void nodeKilled(Node* node);
NODE_TYPE getNodeType() const { return _nodeType; }
void setNodeType(NODE_TYPE type) { _nodeType = type; }
protected:
/// Callback for processing of received packets. Will process any queued PACKET_TYPE_JURISDICTION and update the
/// jurisdiction map member variable
@ -49,6 +52,7 @@ protected:
private:
NodeToJurisdictionMap _jurisdictions;
NODE_TYPE _nodeType;
bool queueJurisdictionRequest();
};

View file

@ -70,6 +70,7 @@ void JurisdictionMap::copyContents(unsigned char* rootCodeIn, const std::vector<
}
void JurisdictionMap::copyContents(const JurisdictionMap& other) {
_nodeType = other._nodeType;
copyContents(other._rootOctalCode, other._endNodes);
}
@ -91,7 +92,8 @@ void JurisdictionMap::clear() {
_endNodes.clear();
}
JurisdictionMap::JurisdictionMap() : _rootOctalCode(NULL) {
JurisdictionMap::JurisdictionMap(NODE_TYPE type) : _rootOctalCode(NULL) {
_nodeType = type;
unsigned char* rootCode = new unsigned char[1];
*rootCode = 0;
@ -260,12 +262,16 @@ bool JurisdictionMap::writeToFile(const char* filename) {
return true;
}
int JurisdictionMap::packEmptyJurisdictionIntoMessage(unsigned char* destinationBuffer, int availableBytes) {
int JurisdictionMap::packEmptyJurisdictionIntoMessage(NODE_TYPE type, unsigned char* destinationBuffer, int availableBytes) {
unsigned char* bufferStart = destinationBuffer;
int headerLength = populateTypeAndVersion(destinationBuffer, PACKET_TYPE_JURISDICTION);
destinationBuffer += headerLength;
// Pack the Node Type in first byte
memcpy(destinationBuffer, &type, sizeof(type));
destinationBuffer += sizeof(type);
// No root or end node details to pack!
int bytes = 0;
memcpy(destinationBuffer, &bytes, sizeof(bytes));
@ -280,6 +286,11 @@ int JurisdictionMap::packIntoMessage(unsigned char* destinationBuffer, int avail
int headerLength = populateTypeAndVersion(destinationBuffer, PACKET_TYPE_JURISDICTION);
destinationBuffer += headerLength;
// Pack the Node Type in first byte
NODE_TYPE type = getNodeType();
memcpy(destinationBuffer, &type, sizeof(type));
destinationBuffer += sizeof(type);
// add the root jurisdiction
if (_rootOctalCode) {
int bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode));

View file

@ -16,6 +16,8 @@
#include <QtCore/QString>
#include <QtCore/QUuid>
#include <NodeTypes.h>
class JurisdictionMap {
public:
enum Area {
@ -25,7 +27,7 @@ public:
};
// standard constructors
JurisdictionMap(); // default constructor
JurisdictionMap(NODE_TYPE type = NODE_TYPE_VOXEL_SERVER); // default constructor
JurisdictionMap(const JurisdictionMap& other); // copy constructor
// standard assignment
@ -58,10 +60,13 @@ public:
int packIntoMessage(unsigned char* destinationBuffer, int availableBytes);
/// Available to pack an empty or unknown jurisdiction into a network packet, used when no JurisdictionMap is available
static int packEmptyJurisdictionIntoMessage(unsigned char* destinationBuffer, int availableBytes);
static int packEmptyJurisdictionIntoMessage(NODE_TYPE type, unsigned char* destinationBuffer, int availableBytes);
void displayDebugDetails() const;
NODE_TYPE getNodeType() const { return _nodeType; }
void setNodeType(NODE_TYPE type) { _nodeType = type; }
private:
void copyContents(const JurisdictionMap& other); // use assignment instead
void clear();
@ -69,6 +74,7 @@ private:
unsigned char* _rootOctalCode;
std::vector<unsigned char*> _endNodes;
NODE_TYPE _nodeType;
};
/// Map between node IDs and their reported JurisdictionMap. Typically used by classes that need to know which nodes are

View file

@ -16,11 +16,12 @@
#include "JurisdictionSender.h"
JurisdictionSender::JurisdictionSender(JurisdictionMap* map, PacketSenderNotify* notify) :
JurisdictionSender::JurisdictionSender(JurisdictionMap* map, NODE_TYPE type, PacketSenderNotify* notify) :
PacketSender(notify, JurisdictionSender::DEFAULT_PACKETS_PER_SECOND),
ReceivedPacketProcessor(),
_jurisdictionMap(map)
{
_nodeType = type;
pthread_mutex_init(&_requestingNodeMutex, 0);
}
@ -54,7 +55,7 @@ bool JurisdictionSender::process() {
if (_jurisdictionMap) {
sizeOut = _jurisdictionMap->packIntoMessage(bufferOut, MAX_PACKET_SIZE);
} else {
sizeOut = JurisdictionMap::packEmptyJurisdictionIntoMessage(bufferOut, MAX_PACKET_SIZE);
sizeOut = JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType(), bufferOut, MAX_PACKET_SIZE);
}
int nodeCount = 0;

View file

@ -24,13 +24,16 @@ class JurisdictionSender : public PacketSender, public ReceivedPacketProcessor {
public:
static const int DEFAULT_PACKETS_PER_SECOND = 1;
JurisdictionSender(JurisdictionMap* map, PacketSenderNotify* notify = NULL);
JurisdictionSender(JurisdictionMap* map, NODE_TYPE type = NODE_TYPE_VOXEL_SERVER, PacketSenderNotify* notify = NULL);
~JurisdictionSender();
void setJurisdiction(JurisdictionMap* map) { _jurisdictionMap = map; }
virtual bool process();
NODE_TYPE getNodeType() const { return _nodeType; }
void setNodeType(NODE_TYPE type) { _nodeType = type; }
protected:
virtual void processPacket(const HifiSockAddr& senderAddress, unsigned char* packetData, ssize_t packetLength);
@ -45,5 +48,6 @@ private:
pthread_mutex_t _requestingNodeMutex;
JurisdictionMap* _jurisdictionMap;
std::queue<QUuid> _nodesRequestingJurisdictions;
NODE_TYPE _nodeType;
};
#endif // __shared__JurisdictionSender__

View file

@ -532,6 +532,12 @@ OctreeElement* Octree::getOctreeElementAt(float x, float y, float z, float s) co
return node;
}
OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, float s) {
return getRoot()->getOrCreateChildElementAt(x, y, z, s);
}
// combines the ray cast arguments into a single object
class RayArgs {
public:

View file

@ -178,12 +178,18 @@ public:
Octree(bool shouldReaverage = false);
~Octree();
/// Your tree class must implement this to create the correct element type
virtual OctreeElement* createNewElement(unsigned char * octalCode = NULL) const = 0;
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool handlesEditPacketType(PACKET_TYPE packetType) const { return false; }
virtual int processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength,
unsigned char* editData, int maxLength) { return 0; }
virtual void update() { }; // nothing to do by default
OctreeElement* getRoot() { return _rootNode; }
void eraseAllOctreeElements();
@ -195,6 +201,7 @@ public:
void deleteOctreeElementAt(float x, float y, float z, float s);
OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const;
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData=NULL);

View file

@ -0,0 +1,299 @@
//
// OctreeEditPacketSender.cpp
// interface
//
// Created by Brad Hefta-Gaub on 8/12/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded packet Sender for the Application
//
#include <assert.h>
#include <PerfStat.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include "OctreeEditPacketSender.h"
EditPacketBuffer::EditPacketBuffer(PACKET_TYPE type, unsigned char* buffer, ssize_t length, QUuid nodeUUID) {
_nodeUUID = nodeUUID;
_currentType = type;
_currentSize = length;
memcpy(_currentBuffer, buffer, length);
};
const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND;
OctreeEditPacketSender::OctreeEditPacketSender(PacketSenderNotify* notify) :
PacketSender(notify),
_shouldSend(true),
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
_releaseQueuedMessagesPending(false),
_serverJurisdictions(NULL),
_sequenceNumber(0),
_maxPacketSize(MAX_PACKET_SIZE) {
}
OctreeEditPacketSender::~OctreeEditPacketSender() {
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
while (!_preServerPackets.empty()) {
EditPacketBuffer* packet = _preServerPackets.front();
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
}
bool OctreeEditPacketSender::serversExist() const {
bool hasServers = false;
bool atLeastOnJurisdictionMissing = false; // assume the best
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType()) {
if (nodeList->getNodeActiveSocketOrPing(&(*node))) {
QUuid nodeUUID = node->getUUID();
// If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server
if (_serverJurisdictions) {
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction
if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) {
atLeastOnJurisdictionMissing = true;
}
}
hasServers = true;
}
}
if (atLeastOnJurisdictionMissing) {
break; // no point in looking further...
}
}
return (hasServers && !atLeastOnJurisdictionMissing);
}
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID. However, we also want to handle the case where the
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType() &&
((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) {
if (nodeList->getNodeActiveSocketOrPing(&(*node))) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
queuePacketForSending(*nodeAddress, buffer, length);
// debugging output...
bool wantDebugging = false;
if (wantDebugging) {
int numBytesPacketHeader = numBytesForPacketHeader(buffer);
unsigned short int sequence = (*((unsigned short int*)(buffer + numBytesPacketHeader)));
uint64_t createdAt = (*((uint64_t*)(buffer + numBytesPacketHeader + sizeof(sequence))));
uint64_t queuedAt = usecTimestampNow();
uint64_t transitTime = queuedAt - createdAt;
printf("OctreeEditPacketSender::queuePacketToNode() queued %c - command to node bytes=%ld sequence=%d transitTimeSoFar=%llu usecs\n",
buffer[0], length, sequence, transitTime);
}
}
}
}
}
void OctreeEditPacketSender::processPreServerExistsPackets() {
assert(serversExist()); // we should only be here if we have jurisdictions
// First send out all the single message packets...
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
// Then "process" all the packable messages...
while (!_preServerPackets.empty()) {
EditPacketBuffer* packet = _preServerPackets.front();
queueOctreeEditMessage(packet->_currentType, &packet->_currentBuffer[0], packet->_currentSize);
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
// if while waiting for the jurisdictions the caller called releaseQueuedMessages()
// then we want to honor that request now.
if (_releaseQueuedMessagesPending) {
releaseQueuedMessages();
_releaseQueuedMessagesPending = false;
}
}
void OctreeEditPacketSender::queuePendingPacketToNodes(PACKET_TYPE type, unsigned char* buffer, ssize_t length) {
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length);
_preServerSingleMessagePackets.push_back(packet);
// if we've saved MORE than our max, then clear out the oldest packet...
int allPendingMessages = _preServerSingleMessagePackets.size() + _preServerPackets.size();
if (allPendingMessages > _maxPendingMessages) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
}
}
void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
assert(serversExist()); // we must have jurisdictions to be here!!
int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short) + sizeof(uint64_t);
unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode
// We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one
// for each server
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) {
QUuid nodeUUID = node->getUUID();
bool isMyJurisdiction = true;
// we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
if (isMyJurisdiction) {
queuePacketToNode(nodeUUID, buffer, length);
}
}
}
}
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
// If we don't have jurisdictions, then we will simply queue up all of these packets and wait till we have
// jurisdictions for processing
if (!serversExist()) {
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, codeColorBuffer, length);
_preServerPackets.push_back(packet);
// if we've saved MORE than out max, then clear out the oldest packet...
int allPendingMessages = _preServerSingleMessagePackets.size() + _preServerPackets.size();
if (allPendingMessages > _maxPendingMessages) {
EditPacketBuffer* packet = _preServerPackets.front();
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
}
return; // bail early
}
// We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one
// for each server
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) {
QUuid nodeUUID = node->getUUID();
bool isMyJurisdiction = true;
if (_serverJurisdictions) {
// we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server
if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
} else {
isMyJurisdiction = false;
}
}
if (isMyJurisdiction) {
EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID];
packetBuffer._nodeUUID = nodeUUID;
// If we're switching type, then we send the last one and start over
if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) ||
(packetBuffer._currentSize + length >= _maxPacketSize)) {
releaseQueuedPacket(packetBuffer);
initializePacket(packetBuffer, type);
}
// If the buffer is empty and not correctly initialized for our type...
if (type != packetBuffer._currentType && packetBuffer._currentSize == 0) {
initializePacket(packetBuffer, type);
}
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length;
}
}
}
}
void OctreeEditPacketSender::releaseQueuedMessages() {
// if we don't yet have jurisdictions then we can't actually release messages yet because we don't
// know where to send them to. Instead, just remember this request and when we eventually get jurisdictions
// call release again at that time.
if (!serversExist()) {
_releaseQueuedMessagesPending = true;
} else {
for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) {
releaseQueuedPacket(i->second);
}
}
}
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PACKET_TYPE_UNKNOWN) {
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
}
packetBuffer._currentSize = 0;
packetBuffer._currentType = PACKET_TYPE_UNKNOWN;
}
void OctreeEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, PACKET_TYPE type) {
packetBuffer._currentSize = populateTypeAndVersion(&packetBuffer._currentBuffer[0], type);
// pack in sequence number
unsigned short int* sequenceAt = (unsigned short int*)&packetBuffer._currentBuffer[packetBuffer._currentSize];
*sequenceAt = _sequenceNumber;
packetBuffer._currentSize += sizeof(unsigned short int); // nudge past sequence
_sequenceNumber++;
// pack in timestamp
uint64_t now = usecTimestampNow();
uint64_t* timeAt = (uint64_t*)&packetBuffer._currentBuffer[packetBuffer._currentSize];
*timeAt = now;
packetBuffer._currentSize += sizeof(uint64_t); // nudge past timestamp
packetBuffer._currentType = type;
}
bool OctreeEditPacketSender::process() {
// if we have server jurisdiction details, and we have pending pre-jurisdiction packets, then process those
// before doing our normal process step. This processPreJurisdictionPackets()
if (serversExist() && (!_preServerPackets.empty() || !_preServerSingleMessagePackets.empty() )) {
processPreServerExistsPackets();
}
// base class does most of the work.
return PacketSender::process();
}

View file

@ -0,0 +1,114 @@
//
// OctreeEditPacketSender.h
// shared
//
// Created by Brad Hefta-Gaub on 8/12/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Octree Edit Packet Sender
//
#ifndef __shared__OctreeEditPacketSender__
#define __shared__OctreeEditPacketSender__
#include <PacketSender.h>
#include <PacketHeaders.h>
#include "JurisdictionMap.h"
/// Used for construction of edit packets
class EditPacketBuffer {
public:
EditPacketBuffer() : _nodeUUID(), _currentType(PACKET_TYPE_UNKNOWN), _currentSize(0) { }
EditPacketBuffer(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid());
QUuid _nodeUUID;
PACKET_TYPE _currentType;
unsigned char _currentBuffer[MAX_PACKET_SIZE];
ssize_t _currentSize;
};
/// Utility for processing, packing, queueing and sending of outbound edit messages.
class OctreeEditPacketSender : public virtual PacketSender {
public:
OctreeEditPacketSender(PacketSenderNotify* notify = NULL);
~OctreeEditPacketSender();
/// Queues a single edit message. Will potentially send a pending multi-command packet. Determines which server
/// node or nodes the packet should be sent to. Can be called even before servers are known, in which case up to
/// MaxPendingMessages will be buffered and processed when servers are known.
void queueOctreeEditMessage(PACKET_TYPE type, unsigned char* buffer, ssize_t length);
/// Releases all queued messages even if those messages haven't filled an MTU packet. This will move the packed message
/// packets onto the send queue. If running in threaded mode, the caller does not need to do any further processing to
/// have these packets get sent. If running in non-threaded mode, the caller must still call process() on a regular
/// interval to ensure that the packets are actually sent. Can be called even before servers are known, in
/// which case up to MaxPendingMessages of the released messages will be buffered and actually released when
/// servers are known.
void releaseQueuedMessages();
/// are we in sending mode. If we're not in sending mode then all packets and messages will be ignored and
/// not queued and not sent
bool getShouldSend() const { return _shouldSend; }
/// set sending mode. By default we are set to shouldSend=TRUE and packets will be sent. If shouldSend=FALSE, then we'll
/// switch to not sending mode, and all packets and messages will be ignored, not queued, and not sent. This might be used
/// in an application like interface when all octree features are disabled.
void setShouldSend(bool shouldSend) { _shouldSend = shouldSend; }
/// call this to inform the OctreeEditPacketSender of the server jurisdictions. This is required for normal operation.
/// The internal contents of the jurisdiction map may change throughout the lifetime of the OctreeEditPacketSender. This map
/// can be set prior to servers being present, so long as the contents of the map accurately reflect the current
/// known jurisdictions.
void setServerJurisdictions(NodeToJurisdictionMap* serverJurisdictions) {
_serverJurisdictions = serverJurisdictions;
}
/// if you're running in non-threaded mode, you must call this method regularly
virtual bool process();
/// Set the desired number of pending messages that the OctreeEditPacketSender should attempt to queue even if
/// servers are not present. This only applies to how the OctreeEditPacketSender will manage messages when no
/// servers are present. By default, this value is the same as the default packets that will be sent in one second.
/// Which means the OctreeEditPacketSender will not buffer all messages given to it if no servers are present.
/// This is the maximum number of queued messages and single messages.
void setMaxPendingMessages(int maxPendingMessages) { _maxPendingMessages = maxPendingMessages; }
// the default number of pending messages we will store if no servers are available
static const int DEFAULT_MAX_PENDING_MESSAGES;
// is there an octree server available to send packets to
bool serversExist() const;
/// Set the desired max packet size in bytes that the OctreeEditPacketSender should create
void setMaxPacketSize(int maxPacketSize) { _maxPacketSize = maxPacketSize; }
/// returns the current desired max packet size in bytes that the OctreeEditPacketSender will create
int getMaxPacketSize() const { return _maxPacketSize; }
// you must override these...
virtual unsigned char getMyNodeType() const = 0;
protected:
bool _shouldSend;
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length);
void queuePendingPacketToNodes(PACKET_TYPE type, unsigned char* buffer, ssize_t length);
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
void initializePacket(EditPacketBuffer& packetBuffer, PACKET_TYPE type);
void releaseQueuedPacket(EditPacketBuffer& packetBuffer); // releases specific queued packet
void processPreServerExistsPackets();
// These are packets which are destined from know servers but haven't been released because they're still too small
std::map<QUuid, EditPacketBuffer> _pendingEditPackets;
// These are packets that are waiting to be processed because we don't yet know if there are servers
int _maxPendingMessages;
bool _releaseQueuedMessagesPending;
std::vector<EditPacketBuffer*> _preServerPackets; // these will get packed into other larger packets
std::vector<EditPacketBuffer*> _preServerSingleMessagePackets; // these will go out as is
NodeToJurisdictionMap* _serverJurisdictions;
unsigned short int _sequenceNumber;
int _maxPacketSize;
};
#endif // __shared__OctreeEditPacketSender__

View file

@ -196,6 +196,7 @@ void OctreeElement::calculateAABox() {
void OctreeElement::deleteChildAtIndex(int childIndex) {
OctreeElement* childAt = getChildAtIndex(childIndex);
if (childAt) {
printf("deleteChildAtIndex()... about to call delete childAt=%p\n",childAt);
delete childAt;
setChildAtIndex(childIndex, NULL);
_isDirty = true;
@ -1119,24 +1120,33 @@ OctreeElement* OctreeElement::addChildAtIndex(int childIndex) {
}
// handles staging or deletion of all deep children
void OctreeElement::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) {
bool OctreeElement::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) {
bool deleteApproved = false;
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
qDebug() << "OctreeElement::safeDeepDeleteChildAtIndex() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n";
return;
return deleteApproved;
}
OctreeElement* childToDelete = getChildAtIndex(childIndex);
if (childToDelete) {
// If the child is not a leaf, then call ourselves recursively on all the children
if (!childToDelete->isLeaf()) {
// delete all it's children
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
childToDelete->safeDeepDeleteChildAtIndex(i,recursionCount+1);
if (childToDelete->deleteApproved()) {
// If the child is not a leaf, then call ourselves recursively on all the children
if (!childToDelete->isLeaf()) {
// delete all it's children
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
deleteApproved = childToDelete->safeDeepDeleteChildAtIndex(i,recursionCount+1);
if (!deleteApproved) {
break; // no point in continuing...
}
}
}
if (deleteApproved) {
deleteChildAtIndex(childIndex);
_isDirty = true;
markWithChangedTime();
}
}
deleteChildAtIndex(childIndex);
_isDirty = true;
markWithChangedTime();
}
return deleteApproved;
}
@ -1273,4 +1283,74 @@ void OctreeElement::notifyUpdateHooks() {
for (int i = 0; i < _updateHooks.size(); i++) {
_updateHooks[i]->elementUpdated(this);
}
}
}
OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float z, float s) {
OctreeElement* child = NULL;
// If the requested size is less than or equal to our scale, but greater than half our scale, then
// we are the Element they are looking for.
float ourScale = getScale();
float halfOurScale = ourScale / 2.0f;
assert(s <= ourScale); // This should never happen
if (s > halfOurScale) {
return this;
}
// otherwise, we need to find which of our children we should recurse
glm::vec3 ourCenter = _box.calcCenter();
int childIndex = CHILD_UNKNOWN;
// left half
if (x > ourCenter.x) {
if (y > ourCenter.y) {
// top left
if (z > ourCenter.z) {
// top left far
childIndex = CHILD_TOP_LEFT_FAR;
} else {
// top left near
childIndex = CHILD_TOP_LEFT_NEAR;
}
} else {
// bottom left
if (z > ourCenter.z) {
// bottom left far
childIndex = CHILD_BOTTOM_LEFT_FAR;
} else {
// bottom left near
childIndex = CHILD_BOTTOM_LEFT_NEAR;
}
}
} else {
// right half
if (y > ourCenter.y) {
// top right
if (z > ourCenter.z) {
// top right far
childIndex = CHILD_TOP_RIGHT_FAR;
} else {
// top right near
childIndex = CHILD_TOP_RIGHT_NEAR;
}
} else {
// bottom right
if (z > ourCenter.z) {
// bottom right far
childIndex = CHILD_BOTTOM_RIGHT_FAR;
} else {
// bottom right near
childIndex = CHILD_BOTTOM_RIGHT_NEAR;
}
}
}
// Now, check if we have a child at that location
child = getChildAtIndex(childIndex);
if (!child) {
child = addChildAtIndex(childIndex);
}
// Now that we have the child to recurse down, let it answer the original question...
return child->getOrCreateChildElementAt(x, y, z, s);
}

View file

@ -52,16 +52,56 @@ protected:
public:
virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction.
virtual ~OctreeElement();
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual OctreeElement* addChildAtIndex(int childIndex);
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren() { }
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren() { return false; }
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const { return true; }
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
{ return 0; }
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
/// the element should be rendered, then your rendering engine is rendering. But some rendering engines my have cases
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return true; }
// Base class methods you don't need to implement
const unsigned char* getOctalCode() const { return (_octcodePointer) ? _octalCode.pointer : &_octalCode.buffer[0]; }
OctreeElement* getChildAtIndex(int childIndex) const;
void deleteChildAtIndex(int childIndex);
OctreeElement* removeChildAtIndex(int childIndex);
virtual OctreeElement* addChildAtIndex(int childIndex);
void safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0); // handles deletion of all descendents
virtual void calculateAverageFromChildren() { };
virtual bool collapseChildren() { return false; };
/// handles deletion of all descendants, returns false if delete not approved
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
const AABox& getAABox() const { return _box; }
const glm::vec3& getCorner() const { return _box.getCorner(); }
@ -70,12 +110,6 @@ public:
float getEnclosingRadius() const;
virtual bool hasContent() const { return isLeaf(); }
virtual void splitChildren() { }
virtual bool requiresSplit() const { return false; }
virtual bool appendElementData(OctreePacketData* packetData) const { return true; }
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
{ return 0; }
bool isInView(const ViewFrustum& viewFrustum) const;
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
@ -104,9 +138,6 @@ public:
void setShouldRender(bool shouldRender);
bool getShouldRender() const { return _shouldRender; }
/// we assume that if you should be rendered, then your subclass is rendering, but this allows subclasses to
/// implement alternate rendering strategies
virtual bool isRendered() const { return getShouldRender(); }
void setSourceUUID(const QUuid& sourceID);
QUuid getSourceUUID() const;
@ -153,7 +184,22 @@ public:
#endif // def HAS_AUDIT_CHILDREN
#endif // def BLENDED_UNION_CHILDREN
enum ChildIndex {
CHILD_BOTTOM_RIGHT_NEAR = 0,
CHILD_BOTTOM_RIGHT_FAR = 1,
CHILD_TOP_RIGHT_NEAR = 2,
CHILD_TOP_RIGHT_FAR = 3,
CHILD_BOTTOM_LEFT_NEAR = 4,
CHILD_BOTTOM_LEFT_FAR = 5,
CHILD_TOP_LEFT_NEAR = 6,
CHILD_TOP_LEFT_FAR = 7,
CHILD_UNKNOWN = -1
};
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
protected:
void deleteAllChildren();
void setChildAtIndex(int childIndex, OctreeElement* child);

View file

@ -13,6 +13,9 @@ bool OctreePacketData::_debug = false;
uint64_t OctreePacketData::_totalBytesOfOctalCodes = 0;
uint64_t OctreePacketData::_totalBytesOfBitMasks = 0;
uint64_t OctreePacketData::_totalBytesOfColor = 0;
uint64_t OctreePacketData::_totalBytesOfValues = 0;
uint64_t OctreePacketData::_totalBytesOfPositions = 0;
uint64_t OctreePacketData::_totalBytesOfRawData = 0;
@ -210,14 +213,22 @@ bool OctreePacketData::appendBitMask(unsigned char bitmask) {
}
bool OctreePacketData::appendColor(const nodeColor& color) {
return appendColor(color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX]);
}
bool OctreePacketData::appendColor(const rgbColor& color) {
return appendColor(color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX]);
}
bool OctreePacketData::appendColor(colorPart red, colorPart green, colorPart blue) {
// eventually we can make this use a dictionary...
bool success = false;
const int BYTES_PER_COLOR = 3;
if (_bytesAvailable > BYTES_PER_COLOR) {
// handles checking compression...
if (append(color[RED_INDEX])) {
if (append(color[GREEN_INDEX])) {
if (append(color[BLUE_INDEX])) {
if (append(red)) {
if (append(green)) {
if (append(blue)) {
success = true;
}
}
@ -230,6 +241,90 @@ bool OctreePacketData::appendColor(const nodeColor& color) {
return success;
}
bool OctreePacketData::appendValue(uint8_t value) {
bool success = append(value); // used unsigned char version
if (success) {
_bytesOfValues++;
_totalBytesOfValues++;
}
return success;
}
bool OctreePacketData::appendValue(uint16_t value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfValues += length;
_totalBytesOfValues += length;
}
return success;
}
bool OctreePacketData::appendValue(uint32_t value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfValues += length;
_totalBytesOfValues += length;
}
return success;
}
bool OctreePacketData::appendValue(uint64_t value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfValues += length;
_totalBytesOfValues += length;
}
return success;
}
bool OctreePacketData::appendValue(float value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfValues += length;
_totalBytesOfValues += length;
}
return success;
}
bool OctreePacketData::appendValue(const glm::vec3& value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfValues += length;
_totalBytesOfValues += length;
}
return success;
}
bool OctreePacketData::appendPosition(const glm::vec3& value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
_bytesOfPositions += length;
_totalBytesOfPositions += length;
}
return success;
}
bool OctreePacketData::appendRawData(const unsigned char* data, int length) {
bool success = append(data, length);
if (success) {
_bytesOfRawData += length;
_totalBytesOfRawData += length;
}
return success;
}
uint64_t OctreePacketData::_compressContentTime = 0;
uint64_t OctreePacketData::_compressContentCalls = 0;

View file

@ -106,6 +106,36 @@ public:
/// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendColor(const nodeColor& color);
/// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendColor(const rgbColor& color);
/// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendColor(colorPart red, colorPart green, colorPart blue);
/// appends a unsigned 8 bit int to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(uint8_t value);
/// appends a unsigned 16 bit int to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(uint16_t value);
/// appends a unsigned 32 bit int to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(uint32_t value);
/// appends a unsigned 64 bit int to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(uint64_t value);
/// appends a float value to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(float value);
/// appends a non-position vector to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendValue(const glm::vec3& value);
/// appends a position to the end of the stream, may fail if new data stream is too long to fit in packet
bool appendPosition(const glm::vec3& value);
/// appends raw bytes, might fail if byte would cause packet to be too large
bool appendRawData(const unsigned char* data, int length);
/// returns a byte offset from beginning of the uncompressed stream based on offset from end.
/// Positive offsetFromEnd returns that many bytes before the end of uncompressed stream
int getUncompressedByteOffset(int offsetFromEnd = 0) const { return _bytesInUse - offsetFromEnd; }
@ -167,6 +197,10 @@ private:
int _bytesOfOctalCodes;
int _bytesOfBitMasks;
int _bytesOfColor;
int _bytesOfValues;
int _bytesOfPositions;
int _bytesOfRawData;
int _bytesOfOctalCodesCurrentSubTree;
static bool _debug;
@ -177,6 +211,9 @@ private:
static uint64_t _totalBytesOfOctalCodes;
static uint64_t _totalBytesOfBitMasks;
static uint64_t _totalBytesOfColor;
static uint64_t _totalBytesOfValues;
static uint64_t _totalBytesOfPositions;
static uint64_t _totalBytesOfRawData;
};
#endif /* defined(__hifi__OctreePacketData__) */

View file

@ -0,0 +1,135 @@
//
// OctreeRenderer.cpp
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#include <glm/glm.hpp>
#include <stdint.h>
#include <SharedUtil.h>
#include <PerfStat.h>
#include "OctreeRenderer.h"
OctreeRenderer::OctreeRenderer() {
_tree = NULL;
_viewFrustum = NULL;
}
void OctreeRenderer::init() {
_tree = createTree();
}
OctreeRenderer::~OctreeRenderer() {
}
void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
bool extraDebugging = false; // Menu::getInstance()->isOptionChecked(MenuOption::ExtraDebugging)
PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram()",showTimingDetails);
const unsigned char* packetData = (const unsigned char*)dataByteArray.constData();
int packetLength = dataByteArray.size();
unsigned char command = *packetData;
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
PACKET_TYPE expectedType = getExpectedPacketType();
if(command == expectedType) {
PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PACKET_TYPE",showTimingDetails);
const unsigned char* dataAt = packetData + numBytesPacketHeader;
OCTREE_PACKET_FLAGS flags = (*(OCTREE_PACKET_FLAGS*)(dataAt));
dataAt += sizeof(OCTREE_PACKET_FLAGS);
OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt);
dataAt += sizeof(OCTREE_PACKET_SEQUENCE);
OCTREE_PACKET_SENT_TIME sentAt = (*(OCTREE_PACKET_SENT_TIME*)dataAt);
dataAt += sizeof(OCTREE_PACKET_SENT_TIME);
bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT);
bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT);
OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
int flightTime = arrivedAt - sentAt;
OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0;
int dataBytes = packetLength - OCTREE_PACKET_HEADER_SIZE;
if (extraDebugging) {
qDebug("OctreeRenderer::processDatagram() ... Got Packet Section"
" color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d"
"\n",
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
sequence, flightTime, packetLength, dataBytes);
}
int subsection = 1;
while (dataBytes > 0) {
if (packetIsCompressed) {
if (dataBytes > sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) {
sectionLength = (*(OCTREE_PACKET_INTERNAL_SECTION_SIZE*)dataAt);
dataAt += sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
dataBytes -= sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
} else {
sectionLength = 0;
dataBytes = 0; // stop looping something is wrong
}
} else {
sectionLength = dataBytes;
}
if (sectionLength) {
// ask the VoxelTree to read the bitstream into the tree
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL,
getDataSourceUUID());
_tree->lockForWrite();
OctreePacketData packetData(packetIsCompressed);
packetData.loadFinalizedContent(dataAt, sectionLength);
if (extraDebugging) {
qDebug("OctreeRenderer::processDatagram() ... Got Packet Section"
" color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d"
" subsection:%d sectionLength:%d uncompressed:%d\n",
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
sequence, flightTime, packetLength, dataBytes, subsection, sectionLength, packetData.getUncompressedSize());
}
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
_tree->unlock();
dataBytes -= sectionLength;
dataAt += sectionLength;
}
}
subsection++;
}
}
bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
RenderArgs* args = static_cast<RenderArgs*>(extraData);
//if (true || element->isInView(*args->_viewFrustum)) {
if (element->isInView(*args->_viewFrustum)) {
if (element->hasContent()) {
args->_renderer->renderElement(element, args);
}
return true;
}
// if not in view stop recursing
return false;
}
void OctreeRenderer::render() {
RenderArgs args = { 0, this, _viewFrustum };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);
_tree->unlock();
}
}

View file

@ -0,0 +1,67 @@
//
// OctreeRenderer.h
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#ifndef __hifi__OctreeRenderer__
#define __hifi__OctreeRenderer__
#include <glm/glm.hpp>
#include <stdint.h>
#include <NodeTypes.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include "Octree.h"
#include "OctreePacketData.h"
#include "ViewFrustum.h"
class OctreeRenderer;
class RenderArgs {
public:
int _renderedItems;
OctreeRenderer* _renderer;
ViewFrustum* _viewFrustum;
};
// Generic client side Octree renderer class.
class OctreeRenderer {
public:
OctreeRenderer();
virtual ~OctreeRenderer();
virtual Octree* createTree() = 0;
virtual NODE_TYPE getMyNodeType() const = 0;
virtual PACKET_TYPE getMyQueryMessageType() const = 0;
virtual PACKET_TYPE getExpectedPacketType() const = 0;
virtual void renderElement(OctreeElement* element, RenderArgs* args) = 0;
/// process incoming data
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
/// initialize and GPU/rendering related resources
void init();
/// render the content of the octree
virtual void render();
void setDataSourceUUID(const QUuid& dataSourceUUID) { _dataSourceUUID = dataSourceUUID; }
const QUuid& getDataSourceUUID() const { return _dataSourceUUID; }
ViewFrustum* getViewFrustum() const { return _viewFrustum; }
void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; }
static bool renderOperation(OctreeElement* element, void* extraData);
protected:
Octree* _tree;
QUuid _dataSourceUUID;
ViewFrustum* _viewFrustum;
};
#endif /* defined(__hifi__OctreeRenderer__) */

View file

@ -0,0 +1,338 @@
//
// Particle.cpp
// hifi
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h> // usecTimestampNow()
#include "Particle.h"
uint32_t Particle::_nextID = 0;
Particle::Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
float damping, glm::vec3 gravity, QString updateScript) {
init(position, radius, color, velocity, damping, gravity, updateScript);
}
Particle::Particle() {
rgbColor noColor = { 0, 0, 0 };
init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0), DEFAULT_DAMPING, DEFAULT_GRAVITY, DEFAULT_SCRIPT);
}
Particle::~Particle() {
}
void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
float damping, glm::vec3 gravity, QString updateScript) {
_id = _nextID;
_nextID++;
_lastUpdated = usecTimestampNow();
_position = position;
_radius = radius;
memcpy(_color, color, sizeof(_color));
_velocity = velocity;
_damping = damping;
_gravity = gravity;
_updateScript = updateScript;
}
bool Particle::appendParticleData(OctreePacketData* packetData) const {
bool success = packetData->appendValue(getID());
//printf("Particle::appendParticleData()... getID()=%d\n", getID());
if (success) {
success = packetData->appendValue(getLastUpdated());
}
if (success) {
success = packetData->appendValue(getRadius());
}
if (success) {
success = packetData->appendPosition(getPosition());
}
if (success) {
success = packetData->appendColor(getColor());
}
if (success) {
success = packetData->appendValue(getVelocity());
}
if (success) {
success = packetData->appendValue(getGravity());
}
if (success) {
success = packetData->appendValue(getDamping());
}
if (success) {
uint16_t scriptLength = _updateScript.size() + 1; // include NULL
success = packetData->appendValue(scriptLength);
if (success) {
success = packetData->appendRawData((const unsigned char*)qPrintable(_updateScript), scriptLength);
}
}
return success;
}
int Particle::expectedBytes() {
int expectedBytes = sizeof(uint32_t) + sizeof(uint64_t) + sizeof(float) +
sizeof(glm::vec3) + sizeof(rgbColor) + sizeof(glm::vec3) +
sizeof(glm::vec3) + sizeof(float);
return expectedBytes;
}
int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
int bytesRead = 0;
if (bytesLeftToRead >= expectedBytes()) {
const unsigned char* dataAt = data;
// id
memcpy(&_id, dataAt, sizeof(_id));
dataAt += sizeof(_id);
bytesRead += sizeof(_id);
// lastupdated
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
dataAt += sizeof(_lastUpdated);
bytesRead += sizeof(_lastUpdated);
// radius
memcpy(&_radius, dataAt, sizeof(_radius));
dataAt += sizeof(_radius);
bytesRead += sizeof(_radius);
// position
memcpy(&_position, dataAt, sizeof(_position));
dataAt += sizeof(_position);
bytesRead += sizeof(_position);
// color
memcpy(_color, dataAt, sizeof(_color));
dataAt += sizeof(_color);
bytesRead += sizeof(_color);
// velocity
memcpy(&_velocity, dataAt, sizeof(_velocity));
dataAt += sizeof(_velocity);
bytesRead += sizeof(_velocity);
// gravity
memcpy(&_gravity, dataAt, sizeof(_gravity));
dataAt += sizeof(_gravity);
bytesRead += sizeof(_gravity);
// damping
memcpy(&_damping, dataAt, sizeof(_damping));
dataAt += sizeof(_damping);
bytesRead += sizeof(_damping);
// script
uint16_t scriptLength;
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
dataAt += sizeof(scriptLength);
bytesRead += sizeof(scriptLength);
QString tempString((const char*)dataAt);
_updateScript = tempString;
dataAt += scriptLength;
bytesRead += scriptLength;
}
return bytesRead;
}
Particle Particle::fromEditPacket(unsigned char* data, int length, int& processedBytes) {
Particle newParticle; // id and lastUpdated will get set here...
unsigned char* dataAt = data;
processedBytes = 0;
// the first part of the data is our octcode...
int octets = numberOfThreeBitSectionsInCode(data);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
// we don't actually do anything with this octcode...
dataAt += lengthOfOctcode;
processedBytes += lengthOfOctcode;
// radius
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius));
dataAt += sizeof(newParticle._radius);
processedBytes += sizeof(newParticle._radius);
// position
memcpy(&newParticle._position, dataAt, sizeof(newParticle._position));
dataAt += sizeof(newParticle._position);
processedBytes += sizeof(newParticle._position);
// color
memcpy(newParticle._color, dataAt, sizeof(newParticle._color));
dataAt += sizeof(newParticle._color);
processedBytes += sizeof(newParticle._color);
// velocity
memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity));
dataAt += sizeof(newParticle._velocity);
processedBytes += sizeof(newParticle._velocity);
// gravity
memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity));
dataAt += sizeof(newParticle._gravity);
processedBytes += sizeof(newParticle._gravity);
// damping
memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping));
dataAt += sizeof(newParticle._damping);
processedBytes += sizeof(newParticle._damping);
// script
uint16_t scriptLength;
memcpy(&scriptLength, dataAt, sizeof(scriptLength));
dataAt += sizeof(scriptLength);
processedBytes += sizeof(scriptLength);
QString tempString((const char*)dataAt);
newParticle._updateScript = tempString;
dataAt += scriptLength;
processedBytes += scriptLength;
return newParticle;
}
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
bool success = true; // assume the best
unsigned char* copyAt = bufferOut;
sizeOut = 0;
for (int i = 0; i < count && success; i++) {
// get the octal code for the particle
unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y, details[i].position.z, details[i].radius);
int octets = numberOfThreeBitSectionsInCode(octcode);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
int lenfthOfEditData = lengthOfOctcode + expectedBytes();
// make sure we have room to copy this particle
if (sizeOut + lenfthOfEditData > sizeIn) {
success = false;
} else {
// add it to our message
memcpy(copyAt, octcode, lengthOfOctcode);
copyAt += lengthOfOctcode;
sizeOut += lengthOfOctcode;
// Now add our edit content details...
// radius
memcpy(copyAt, &details[i].radius, sizeof(details[i].radius));
copyAt += sizeof(details[i].radius);
sizeOut += sizeof(details[i].radius);
// position
memcpy(copyAt, &details[i].position, sizeof(details[i].position));
copyAt += sizeof(details[i].position);
sizeOut += sizeof(details[i].position);
// color
memcpy(copyAt, details[i].color, sizeof(details[i].color));
copyAt += sizeof(details[i].color);
sizeOut += sizeof(details[i].color);
// velocity
memcpy(copyAt, &details[i].velocity, sizeof(details[i].velocity));
copyAt += sizeof(details[i].velocity);
sizeOut += sizeof(details[i].velocity);
// gravity
memcpy(copyAt, &details[i].gravity, sizeof(details[i].gravity));
copyAt += sizeof(details[i].gravity);
sizeOut += sizeof(details[i].gravity);
// damping
memcpy(copyAt, &details[i].damping, sizeof(details[i].damping));
copyAt += sizeof(details[i].damping);
sizeOut += sizeof(details[i].damping);
// script
uint16_t scriptLength = details[i].updateScript.size() + 1;
memcpy(copyAt, &scriptLength, sizeof(scriptLength));
copyAt += sizeof(scriptLength);
sizeOut += sizeof(scriptLength);
memcpy(copyAt, qPrintable(details[i].updateScript), scriptLength);
copyAt += scriptLength;
sizeOut += scriptLength;
}
// cleanup
delete[] octcode;
}
return success;
}
void Particle::update() {
uint64_t now = usecTimestampNow();
uint64_t elapsed = now - _lastUpdated;
uint64_t USECS_PER_SECOND = 1000 * 1000;
float timeElapsed = (float)((float)elapsed/(float)USECS_PER_SECOND);
// calculate our default shouldDie state... then allow script to change it if it wants...
float velocityScalar = glm::length(getVelocity());
const float STILL_MOVING = 0.05 / TREE_SCALE;
setShouldDie(velocityScalar < STILL_MOVING);
runScript(); // allow the javascript to alter our state
_position += _velocity * timeElapsed;
// handle bounces off the ground...
if (_position.y <= 0) {
_velocity = _velocity * glm::vec3(1,-1,1);
_position.y = 0;
}
// handle gravity....
_velocity += _gravity * timeElapsed;
// handle damping
glm::vec3 dampingResistance = _velocity * _damping;
_velocity -= dampingResistance * timeElapsed;
_lastUpdated = now;
}
void Particle::runScript() {
if (!_updateScript.isEmpty()) {
QScriptEngine engine;
// register meta-type for glm::vec3 and rgbColor conversions
registerMetaTypes(&engine);
ParticleScriptObject particleScriptable(this);
QScriptValue particleValue = engine.newQObject(&particleScriptable);
engine.globalObject().setProperty("Particle", particleValue);
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
engine.globalObject().setProperty("TREE_SCALE", TREE_SCALE);
//qDebug() << "Downloaded script:" << _updateScript << "\n";
QScriptValue result = engine.evaluate(_updateScript);
//qDebug() << "Evaluated script.\n";
if (engine.hasUncaughtException()) {
int line = engine.uncaughtExceptionLineNumber();
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
}
}
}

View file

@ -0,0 +1,130 @@
//
// Particle.h
// hifi
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
//
#ifndef __hifi__Particle__
#define __hifi__Particle__
#include <glm/glm.hpp>
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <SharedUtil.h>
#include <OctreePacketData.h>
class ParticleDetail {
public:
glm::vec3 position;
float radius;
rgbColor color;
glm::vec3 velocity;
glm::vec3 gravity;
float damping;
QString updateScript;
};
const float DEFAULT_DAMPING = 0.99f;
const glm::vec3 DEFAULT_GRAVITY(0, (-9.8f / TREE_SCALE), 0);
const QString DEFAULT_SCRIPT("");
class Particle {
public:
Particle();
Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
float damping = DEFAULT_DAMPING, glm::vec3 gravity = DEFAULT_GRAVITY, QString updateScript = DEFAULT_SCRIPT);
/// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD edit data buffer
static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes);
virtual ~Particle();
virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
float damping, glm::vec3 gravity, QString updateScript);
const glm::vec3& getPosition() const { return _position; }
const rgbColor& getColor() const { return _color; }
xColor getColor() { return { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; }
float getRadius() const { return _radius; }
const glm::vec3& getVelocity() const { return _velocity; }
const glm::vec3& getGravity() const { return _gravity; }
float getDamping() const { return _damping; }
uint64_t getLastUpdated() const { return _lastUpdated; }
uint32_t getID() const { return _id; }
bool getShouldDie() const { return _shouldDie; }
QString getUpdateScript() const { return _updateScript; }
void setPosition(const glm::vec3& value) { _position = value; }
void setVelocity(const glm::vec3& value) { _velocity = value; }
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
void setColor(const xColor& value) {
_color[RED_INDEX] = value.red;
_color[GREEN_INDEX] = value.green;
_color[BLUE_INDEX] = value.blue;
}
void setRadius(float value) { _radius = value; }
void setGravity(const glm::vec3& value) { _gravity = value; }
void setDamping(float value) { _damping = value; }
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
void setUpdateScript(QString updateScript) { _updateScript = updateScript; }
bool appendParticleData(OctreePacketData* packetData) const;
int readParticleDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
static int expectedBytes();
static bool encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
unsigned char* bufferOut, int sizeIn, int& sizeOut);
void update();
protected:
void runScript();
static QScriptValue vec3toScriptValue(QScriptEngine *engine, const glm::vec3 &vec3);
static void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3);
static QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color);
static void xColorFromScriptValue(const QScriptValue &object, xColor& color);
glm::vec3 _position;
rgbColor _color;
float _radius;
glm::vec3 _velocity;
uint64_t _lastUpdated;
uint32_t _id;
static uint32_t _nextID;
bool _shouldDie;
glm::vec3 _gravity;
float _damping;
QString _updateScript;
};
class ParticleScriptObject : public QObject {
Q_OBJECT
public:
ParticleScriptObject(Particle* particle) { _particle = particle; }
public slots:
glm::vec3 getPosition() const { return _particle->getPosition(); }
glm::vec3 getVelocity() const { return _particle->getVelocity(); }
xColor getColor() const { return _particle->getColor(); }
glm::vec3 getGravity() const { return _particle->getGravity(); }
float getDamping() const { return _particle->getDamping(); }
float getRadius() const { return _particle->getRadius(); }
void setPosition(glm::vec3 value) { return _particle->setPosition(value); }
void setVelocity(glm::vec3 value) { return _particle->setVelocity(value); }
void setGravity(glm::vec3 value) { return _particle->setGravity(value); }
void setDamping(float value) { return _particle->setDamping(value); }
void setColor(xColor value) { return _particle->setColor(value); }
void setRadius(float value) { return _particle->setRadius(value); }
private:
Particle* _particle;
};
#endif /* defined(__hifi__Particle__) */

View file

@ -0,0 +1,55 @@
//
// ParticleEditPacketSender.cpp
// interface
//
// Created by Brad Hefta-Gaub on 8/12/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded voxel packet Sender for the Application
//
#include <assert.h>
#include <PerfStat.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include "ParticleEditPacketSender.h"
#include "Particle.h"
void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail) {
// allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) {
return; // bail early
}
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
// This encodes the voxel edit message into a buffer...
if (Particle::encodeParticleEditMessageDetails(type, 1, &detail, &bufferOut[0], _maxPacketSize, sizeOut)){
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!serversExist()) {
queuePendingPacketToNodes(type, bufferOut, sizeOut);
} else {
queuePacketToNodes(bufferOut, sizeOut);
}
}
}
void ParticleEditPacketSender::queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details) {
if (!_shouldSend) {
return; // bail early
}
for (int i = 0; i < numberOfDetails; i++) {
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
if (Particle::encodeParticleEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}
}

View file

@ -0,0 +1,34 @@
//
// ParticleEditPacketSender.h
// shared
//
// Created by Brad Hefta-Gaub on 8/12/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Voxel Packet Sender
//
#ifndef __shared__ParticleEditPacketSender__
#define __shared__ParticleEditPacketSender__
#include <OctreeEditPacketSender.h>
#include "Particle.h"
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
class ParticleEditPacketSender : public virtual OctreeEditPacketSender {
public:
ParticleEditPacketSender(PacketSenderNotify* notify = NULL) : OctreeEditPacketSender(notify) { }
~ParticleEditPacketSender() { }
/// Send particle add message immediately
void sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail);
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
void queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details);
// My server type is the voxel server
virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }
};
#endif // __shared__ParticleEditPacketSender__

View file

@ -0,0 +1,30 @@
//
// ParticleScriptingInterface.cpp
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include "ParticleScriptingInterface.h"
ParticleScriptingInterface::ParticleScriptingInterface() :
_jurisdictionListener(NODE_TYPE_PARTICLE_SERVER)
{
_jurisdictionListener.initialize(true);
_particlePacketSender.setServerJurisdictions(_jurisdictionListener.getJurisdictions());
}
void ParticleScriptingInterface::queueParticleAdd(PACKET_TYPE addPacketType, ParticleDetail& addParticleDetails) {
_particlePacketSender.queueParticleEditMessages(addPacketType, 1, &addParticleDetails);
}
void ParticleScriptingInterface::queueParticleAdd(glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, QString updateScript) {
// setup a ParticleDetail struct with the data
ParticleDetail addParticleDetail = { position, radius, {color.red, color.green, color.blue }, velocity, gravity, damping, updateScript };
// queue the packet
queueParticleAdd(PACKET_TYPE_PARTICLE_ADD, addParticleDetail);
}

View file

@ -0,0 +1,89 @@
//
// ParticleScriptingInterface.h
// hifi
//
// Created by Brad Hefta-Gaub on 12/6/13
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__ParticleScriptingInterface__
#define __hifi__ParticleScriptingInterface__
#include <QtCore/QObject>
#include <JurisdictionListener.h>
#include "ParticleEditPacketSender.h"
/// handles scripting of Particle commands from JS passed to assigned clients
class ParticleScriptingInterface : public QObject {
Q_OBJECT
public:
ParticleScriptingInterface();
ParticleEditPacketSender* getParticlePacketSender() { return &_particlePacketSender; }
JurisdictionListener* getJurisdictionListener() { return &_jurisdictionListener; }
public slots:
/// queues the creation of a Particle which will be sent by calling process on the PacketSender
void queueParticleAdd(glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, QString updateScript);
/// Set the desired max packet size in bytes that should be created
void setMaxPacketSize(int maxPacketSize) { return _particlePacketSender.setMaxPacketSize(maxPacketSize); }
/// returns the current desired max packet size in bytes that will be created
int getMaxPacketSize() const { return _particlePacketSender.getMaxPacketSize(); }
/// set the max packets per second send rate
void setPacketsPerSecond(int packetsPerSecond) { return _particlePacketSender.setPacketsPerSecond(packetsPerSecond); }
/// get the max packets per second send rate
int getPacketsPerSecond() const { return _particlePacketSender.getPacketsPerSecond(); }
/// does a particle server exist to send to
bool serversExist() const { return _particlePacketSender.serversExist(); }
/// are there packets waiting in the send queue to be sent
bool hasPacketsToSend() const { return _particlePacketSender.hasPacketsToSend(); }
/// how many packets are there in the send queue waiting to be sent
int packetsToSendCount() const { return _particlePacketSender.packetsToSendCount(); }
/// returns the packets per second send rate of this object over its lifetime
float getLifetimePPS() const { return _particlePacketSender.getLifetimePPS(); }
/// returns the bytes per second send rate of this object over its lifetime
float getLifetimeBPS() const { return _particlePacketSender.getLifetimeBPS(); }
/// returns the packets per second queued rate of this object over its lifetime
float getLifetimePPSQueued() const { return _particlePacketSender.getLifetimePPSQueued(); }
/// returns the bytes per second queued rate of this object over its lifetime
float getLifetimeBPSQueued() const { return _particlePacketSender.getLifetimeBPSQueued(); }
/// returns lifetime of this object from first packet sent to now in usecs
long long unsigned int getLifetimeInUsecs() const { return _particlePacketSender.getLifetimeInUsecs(); }
/// returns lifetime of this object from first packet sent to now in usecs
float getLifetimeInSeconds() const { return _particlePacketSender.getLifetimeInSeconds(); }
/// returns the total packets sent by this object over its lifetime
long long unsigned int getLifetimePacketsSent() const { return _particlePacketSender.getLifetimePacketsSent(); }
/// returns the total bytes sent by this object over its lifetime
long long unsigned int getLifetimeBytesSent() const { return _particlePacketSender.getLifetimeBytesSent(); }
/// returns the total packets queued by this object over its lifetime
long long unsigned int getLifetimePacketsQueued() const { return _particlePacketSender.getLifetimePacketsQueued(); }
/// returns the total bytes queued by this object over its lifetime
long long unsigned int getLifetimeBytesQueued() const { return _particlePacketSender.getLifetimeBytesQueued(); }
private:
/// attached ParticleEditPacketSender that handles queuing and sending of packets to VS
ParticleEditPacketSender _particlePacketSender;
JurisdictionListener _jurisdictionListener;
void queueParticleAdd(PACKET_TYPE addPacketType, ParticleDetail& addParticleDetails);
};
#endif /* defined(__hifi__ParticleScriptingInterface__) */

View file

@ -9,7 +9,9 @@
#include "ParticleTree.h"
ParticleTree::ParticleTree(bool shouldReaverage) : Octree(shouldReaverage) {
_rootNode = createNewElement();
ParticleTreeElement* rootNode = createNewElement();
rootNode->setTree(this);
_rootNode = rootNode;
}
ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) const {
@ -17,3 +19,96 @@ ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) c
return newElement;
}
bool ParticleTree::handlesEditPacketType(PACKET_TYPE packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD:
case PACKET_TYPE_PARTICLE_ERASE:
return true;
}
return false;
}
class FindAndUpdateParticleArgs {
public:
const Particle& searchParticle;
bool found;
};
bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateParticleArgs* args = static_cast<FindAndUpdateParticleArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
if (particleTreeElement->containsParticle(args->searchParticle)) {
particleTreeElement->updateParticle(args->searchParticle);
args->found = true;
return false; // stop searching
}
return true;
}
void ParticleTree::storeParticle(const Particle& particle) {
// First, look for the existing particle in the tree..
FindAndUpdateParticleArgs args = { particle, false };
recurseTreeWithOperation(findAndUpdateOperation, &args);
// if we didn't find it in the tree, then store it...
if (!args.found) {
glm::vec3 position = particle.getPosition();
float size = particle.getRadius();
ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
element->storeParticle(particle);
}
// what else do we need to do here to get reaveraging to work
_isDirty = true;
}
int ParticleTree::processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength,
unsigned char* editData, int maxLength) {
int processedBytes = 0;
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD: {
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes);
storeParticle(newParticle);
// It seems like we need some way to send the ID back to the creator??
} break;
case PACKET_TYPE_PARTICLE_ERASE: {
processedBytes = 0;
} break;
}
return processedBytes;
}
bool ParticleTree::updateOperation(OctreeElement* element, void* extraData) {
ParticleTreeUpdateArgs* args = static_cast<ParticleTreeUpdateArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->update(*args);
return true;
}
void ParticleTree::update() {
_isDirty = true;
ParticleTreeUpdateArgs args = { };
recurseTreeWithOperation(updateOperation, &args);
// now add back any of the particles that moved elements....
int movingParticles = args._movingParticles.size();
for (int i = 0; i < movingParticles; i++) {
bool shouldDie = args._movingParticles[i].getShouldDie();
// if the particle is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) {
storeParticle(args._movingParticles[i]);
}
}
}

View file

@ -12,13 +12,41 @@
#include <Octree.h>
#include "ParticleTreeElement.h"
class ParticleTreeUpdateArgs {
public:
std::vector<Particle> _movingParticles;
};
class ParticleTree : public Octree {
Q_OBJECT
public:
ParticleTree(bool shouldReaverage = false);
/// Implements our type specific root element factory
virtual ParticleTreeElement* createNewElement(unsigned char * octalCode = NULL) const;
/// Type safe version of getRoot()
ParticleTreeElement* getRoot() { return (ParticleTreeElement*)_rootNode; }
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing
virtual bool handlesEditPacketType(PACKET_TYPE packetType) const;
virtual int processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength,
unsigned char* editData, int maxLength);
virtual void update();
void storeParticle(const Particle& particle);
private:
static bool updateOperation(OctreeElement* element, void* extraData);
static bool findAndUpdateOperation(OctreeElement* element, void* extraData);
};
#endif /* defined(__hifi__ParticleTree__) */

View file

@ -25,6 +25,7 @@ ParticleTreeElement::~ParticleTreeElement() {
// we know must match ours.
OctreeElement* ParticleTreeElement::createNewElement(unsigned char* octalCode) const {
ParticleTreeElement* newChild = new ParticleTreeElement(octalCode);
newChild->setTree(_myTree);
return newChild;
}
@ -33,17 +34,106 @@ void ParticleTreeElement::init(unsigned char* octalCode) {
_voxelMemoryUsage += sizeof(ParticleTreeElement);
}
ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) {
ParticleTreeElement* newElement = (ParticleTreeElement*)OctreeElement::addChildAtIndex(index);
newElement->setTree(_myTree);
return newElement;
}
bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const {
// will write to the encoded stream all of the contents of this element
return true;
bool success = true; // assume the best...
// write our particles out...
uint16_t numberOfParticles = _particles.size();
success = packetData->appendValue(numberOfParticles);
if (success) {
for (uint16_t i = 0; i < numberOfParticles; i++) {
const Particle& particle = _particles[i];
success = particle.appendParticleData(packetData);
if (!success) {
break;
}
}
}
return success;
}
void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) {
markWithChangedTime();
// update our contained particles
uint16_t numberOfParticles = _particles.size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
_particles[i].update();
// If the particle wants to die, or if it's left our bounding box, then move it
// into the arguments moving particles. These will be added back or deleted completely
if (_particles[i].getShouldDie() || !_box.contains(_particles[i].getPosition())) {
args._movingParticles.push_back(_particles[i]);
// erase this particle
_particles.erase(_particles.begin()+i);
// reduce our index since we just removed this item
i--;
numberOfParticles--;
}
}
}
bool ParticleTreeElement::containsParticle(const Particle& particle) const {
uint16_t numberOfParticles = _particles.size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
if (_particles[i].getID() == particle.getID()) {
return true;
}
}
return false;
}
bool ParticleTreeElement::updateParticle(const Particle& particle) {
uint16_t numberOfParticles = _particles.size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
if (_particles[i].getID() == particle.getID()) {
_particles[i] = particle;
return true;
}
}
return false;
}
int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {
// will read from the encoded stream all of the contents of this element
return 0;
const unsigned char* dataAt = data;
int bytesRead = 0;
uint16_t numberOfParticles = 0;
int expectedBytesPerParticle = Particle::expectedBytes();
if (bytesLeftToRead >= sizeof(numberOfParticles)) {
// read our particles in....
numberOfParticles = *(uint16_t*)dataAt;
dataAt += sizeof(numberOfParticles);
bytesLeftToRead -= sizeof(numberOfParticles);
bytesRead += sizeof(numberOfParticles);
if (bytesLeftToRead >= (numberOfParticles * expectedBytesPerParticle)) {
for (uint16_t i = 0; i < numberOfParticles; i++) {
Particle tempParticle;
int bytesForThisParticle = tempParticle.readParticleDataFromBuffer(dataAt, bytesLeftToRead, args);
_myTree->storeParticle(tempParticle);
dataAt += bytesForThisParticle;
bytesLeftToRead -= bytesForThisParticle;
bytesRead += bytesForThisParticle;
}
}
}
return bytesRead;
}
// will average a "common reduced LOD view" from the the child elements...
@ -60,3 +150,9 @@ bool ParticleTreeElement::collapseChildren() {
return false;
}
void ParticleTreeElement::storeParticle(const Particle& particle) {
_particles.push_back(particle);
markWithChangedTime();
}

View file

@ -10,10 +10,16 @@
#ifndef __hifi__ParticleTreeElement__
#define __hifi__ParticleTreeElement__
#include <vector>
#include <OctreeElement.h>
#include "Particle.h"
#include "ParticleTree.h"
class ParticleTree;
class ParticleTreeElement;
class ParticleTreeUpdateArgs;
class ParticleTreeElement : public OctreeElement {
friend class ParticleTree; // to allow createElement to new us...
@ -26,20 +32,60 @@ public:
virtual ~ParticleTreeElement();
virtual void init(unsigned char * octalCode);
virtual bool hasContent() const { return isLeaf(); }
virtual void splitChildren() {}
virtual bool requiresSplit() const { return false; }
virtual bool appendElementData(OctreePacketData* packetData) const;
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
virtual void calculateAverageFromChildren();
virtual bool collapseChildren();
virtual bool isRendered() const { return getShouldRender(); }
// type safe versions of OctreeElement methods
ParticleTreeElement* getChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::getChildAtIndex(index); }
ParticleTreeElement* addChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::addChildAtIndex(index); }
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual ParticleTreeElement* addChildAtIndex(int index);
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren();
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren();
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
/// the element should be rendered, then your rendering engine is rendering. But some rendering engines my have cases
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return (_particles.size() == 0); }
const std::vector<Particle>& getParticles() const { return _particles; }
void update(ParticleTreeUpdateArgs& args);
void setTree(ParticleTree* tree) { _myTree = tree; }
bool containsParticle(const Particle& particle) const;
bool updateParticle(const Particle& particle);
protected:
void storeParticle(const Particle& particle);
ParticleTree* _myTree;
std::vector<Particle> _particles;
};
#endif /* defined(__hifi__ParticleTreeElement__) */

View file

@ -50,6 +50,7 @@ Node::~Node() {
// Names of Node Types
const char* NODE_TYPE_NAME_DOMAIN = "Domain";
const char* NODE_TYPE_NAME_VOXEL_SERVER = "Voxel Server";
const char* NODE_TYPE_NAME_PARTICLE_SERVER = "Particle Server";
const char* NODE_TYPE_NAME_AGENT = "Agent";
const char* NODE_TYPE_NAME_AUDIO_MIXER = "Audio Mixer";
const char* NODE_TYPE_NAME_AVATAR_MIXER = "Avatar Mixer";
@ -64,6 +65,8 @@ const char* Node::getTypeName() const {
return NODE_TYPE_NAME_DOMAIN;
case NODE_TYPE_VOXEL_SERVER:
return NODE_TYPE_NAME_VOXEL_SERVER;
case NODE_TYPE_PARTICLE_SERVER:
return NODE_TYPE_NAME_PARTICLE_SERVER;
case NODE_TYPE_AGENT:
return NODE_TYPE_NAME_AGENT;
case NODE_TYPE_AUDIO_MIXER:

View file

@ -46,6 +46,9 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) {
case PACKET_TYPE_VOXEL_DATA:
return 1;
case PACKET_TYPE_JURISDICTION:
return 1;
default:
return 0;

View file

@ -0,0 +1,44 @@
//
// RegisteredMetaTypes.cpp
// hifi
//
// Created by Stephen Birarda on 10/3/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
// Used to register meta-types with Qt so that they can be used as properties for objects exposed to our
// Agent scripting.
#include "RegisteredMetaTypes.h"
void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, vec3toScriptValue, vec3FromScriptValue);
qScriptRegisterMetaType(engine, xColorToScriptValue, xColorFromScriptValue);
}
QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3) {
QScriptValue obj = engine->newObject();
obj.setProperty("x", vec3.x);
obj.setProperty("y", vec3.y);
obj.setProperty("z", vec3.z);
return obj;
}
void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) {
vec3.x = object.property("x").toVariant().toFloat();
vec3.y = object.property("y").toVariant().toFloat();
vec3.z = object.property("z").toVariant().toFloat();
}
QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
QScriptValue obj = engine->newObject();
obj.setProperty("red", color.red);
obj.setProperty("green", color.green);
obj.setProperty("blue", color.blue);
return obj;
}
void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
color.red = object.property("red").toVariant().toInt();
color.green = object.property("green").toVariant().toInt();
color.blue = object.property("blue").toVariant().toInt();
}

View file

@ -11,6 +11,18 @@
#ifndef hifi_RegisteredMetaTypes_h
#define hifi_RegisteredMetaTypes_h
#include <glm/glm.hpp>
#include <QtScript/QScriptEngine>
#include "SharedUtil.h"
Q_DECLARE_METATYPE(glm::vec3)
Q_DECLARE_METATYPE(xColor)
void registerMetaTypes(QScriptEngine* engine);
QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3);
void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3);
QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color);
void xColorFromScriptValue(const QScriptValue &object, xColor& color);
#endif

View file

@ -31,6 +31,13 @@ typedef unsigned char colorPart;
typedef unsigned char nodeColor[BYTES_PER_COLOR + BYTES_PER_FLAGS];
typedef unsigned char rgbColor[BYTES_PER_COLOR];
struct xColor {
unsigned char red;
unsigned char green;
unsigned char blue;
};
static const float ZERO = 0.0f;
static const float ONE = 1.0f;
static const float ONE_HALF = 0.5f;

View file

@ -9,48 +9,12 @@
//
#include <assert.h>
#include <PerfStat.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include "VoxelEditPacketSender.h"
EditPacketBuffer::EditPacketBuffer(PACKET_TYPE type, unsigned char* buffer, ssize_t length, QUuid nodeUUID) {
_nodeUUID = nodeUUID;
_currentType = type;
_currentSize = length;
memcpy(_currentBuffer, buffer, length);
};
const int VoxelEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND;
VoxelEditPacketSender::VoxelEditPacketSender(PacketSenderNotify* notify) :
PacketSender(notify),
_shouldSend(true),
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
_releaseQueuedMessagesPending(false),
_voxelServerJurisdictions(NULL),
_sequenceNumber(0),
_maxPacketSize(MAX_PACKET_SIZE) {
}
VoxelEditPacketSender::~VoxelEditPacketSender() {
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
while (!_preServerPackets.empty()) {
EditPacketBuffer* packet = _preServerPackets.front();
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
}
void VoxelEditPacketSender::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail& detail) {
// allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) {
@ -65,19 +29,7 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail&
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!voxelServersExist()) {
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, bufferOut, sizeOut);
_preServerSingleMessagePackets.push_back(packet);
// if we've saved MORE than out max, then clear out the oldest packet...
int allPendingMessages = _preServerSingleMessagePackets.size() + _preServerPackets.size();
if (allPendingMessages > _maxPendingMessages) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
}
return; // bail early
queuePendingPacketToNodes(type, bufferOut, sizeOut);
} else {
queuePacketToNodes(bufferOut, sizeOut);
}
@ -87,74 +39,6 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail&
}
}
bool VoxelEditPacketSender::voxelServersExist() const {
bool hasVoxelServers = false;
bool atLeastOnJurisdictionMissing = false; // assume the best
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
if (nodeList->getNodeActiveSocketOrPing(&(*node))) {
QUuid nodeUUID = node->getUUID();
// If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server
if (_voxelServerJurisdictions) {
// lookup our nodeUUID in the jurisdiction map, if it's missing then we're
// missing at least one jurisdiction
if ((*_voxelServerJurisdictions).find(nodeUUID) == (*_voxelServerJurisdictions).end()) {
atLeastOnJurisdictionMissing = true;
}
}
hasVoxelServers = true;
}
}
if (atLeastOnJurisdictionMissing) {
break; // no point in looking further...
}
}
return (hasVoxelServers && !atLeastOnJurisdictionMissing);
}
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID. However, we also want to handle the case where the
void VoxelEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getType() == NODE_TYPE_VOXEL_SERVER &&
((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) {
if (nodeList->getNodeActiveSocketOrPing(&(*node))) {
const HifiSockAddr* nodeAddress = node->getActiveSocket();
queuePacketForSending(*nodeAddress, buffer, length);
// debugging output...
bool wantDebugging = false;
if (wantDebugging) {
int numBytesPacketHeader = numBytesForPacketHeader(buffer);
unsigned short int sequence = (*((unsigned short int*)(buffer + numBytesPacketHeader)));
uint64_t createdAt = (*((uint64_t*)(buffer + numBytesPacketHeader + sizeof(sequence))));
uint64_t queuedAt = usecTimestampNow();
uint64_t transitTime = queuedAt - createdAt;
const char* messageName;
switch (buffer[0]) {
case PACKET_TYPE_VOXEL_SET:
messageName = "PACKET_TYPE_VOXEL_SET";
break;
case PACKET_TYPE_VOXEL_SET_DESTRUCTIVE:
messageName = "PACKET_TYPE_VOXEL_SET_DESTRUCTIVE";
break;
case PACKET_TYPE_VOXEL_ERASE:
messageName = "PACKET_TYPE_VOXEL_ERASE";
break;
}
printf("VoxelEditPacketSender::queuePacketToNode() queued %s - command to node bytes=%ld sequence=%d transitTimeSoFar=%llu usecs\n",
messageName, length, sequence, transitTime);
}
}
}
}
}
void VoxelEditPacketSender::queueVoxelEditMessages(PACKET_TYPE type, int numberOfDetails, VoxelDetail* details) {
if (!_shouldSend) {
return; // bail early
@ -166,184 +50,8 @@ void VoxelEditPacketSender::queueVoxelEditMessages(PACKET_TYPE type, int numberO
int sizeOut = 0;
if (encodeVoxelEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
queueVoxelEditMessage(type, bufferOut, sizeOut);
queueOctreeEditMessage(type, bufferOut, sizeOut);
}
}
}
void VoxelEditPacketSender::processPreServerExistsPackets() {
assert(voxelServersExist()); // we should only be here if we have jurisdictions
// First send out all the single message packets...
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
// Then "process" all the packable messages...
while (!_preServerPackets.empty()) {
EditPacketBuffer* packet = _preServerPackets.front();
queueVoxelEditMessage(packet->_currentType, &packet->_currentBuffer[0], packet->_currentSize);
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
// if while waiting for the jurisdictions the caller called releaseQueuedMessages()
// then we want to honor that request now.
if (_releaseQueuedMessagesPending) {
releaseQueuedMessages();
_releaseQueuedMessagesPending = false;
}
}
void VoxelEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
assert(voxelServersExist()); // we must have jurisdictions to be here!!
int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short) + sizeof(uint64_t);
unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode
// We want to filter out edit messages for voxel servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different voxel server... So we need to actually manage multiple queued packets... one
// for each voxel server
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER) {
QUuid nodeUUID = node->getUUID();
bool isMyJurisdiction = true;
// we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server
const JurisdictionMap& map = (*_voxelServerJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
if (isMyJurisdiction) {
queuePacketToNode(nodeUUID, buffer, length);
}
}
}
}
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void VoxelEditPacketSender::queueVoxelEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
// If we don't have voxel jurisdictions, then we will simply queue up all of these packets and wait till we have
// jurisdictions for processing
if (!voxelServersExist()) {
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, codeColorBuffer, length);
_preServerPackets.push_back(packet);
// if we've saved MORE than out max, then clear out the oldest packet...
int allPendingMessages = _preServerSingleMessagePackets.size() + _preServerPackets.size();
if (allPendingMessages > _maxPendingMessages) {
EditPacketBuffer* packet = _preServerPackets.front();
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
}
return; // bail early
}
// We want to filter out edit messages for voxel servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different voxel server... So we need to actually manage multiple queued packets... one
// for each voxel server
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER) {
QUuid nodeUUID = node->getUUID();
bool isMyJurisdiction = true;
if (_voxelServerJurisdictions) {
// we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server
if ((*_voxelServerJurisdictions).find(nodeUUID) != (*_voxelServerJurisdictions).end()) {
const JurisdictionMap& map = (*_voxelServerJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
} else {
isMyJurisdiction = false;
}
}
if (isMyJurisdiction) {
EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID];
packetBuffer._nodeUUID = nodeUUID;
// If we're switching type, then we send the last one and start over
if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) ||
(packetBuffer._currentSize + length >= _maxPacketSize)) {
releaseQueuedPacket(packetBuffer);
initializePacket(packetBuffer, type);
}
// If the buffer is empty and not correctly initialized for our type...
if (type != packetBuffer._currentType && packetBuffer._currentSize == 0) {
initializePacket(packetBuffer, type);
}
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length;
}
}
}
}
void VoxelEditPacketSender::releaseQueuedMessages() {
// if we don't yet have jurisdictions then we can't actually release messages yet because we don't
// know where to send them to. Instead, just remember this request and when we eventually get jurisdictions
// call release again at that time.
if (!voxelServersExist()) {
_releaseQueuedMessagesPending = true;
} else {
for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) {
releaseQueuedPacket(i->second);
}
}
}
void VoxelEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PACKET_TYPE_UNKNOWN) {
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
}
packetBuffer._currentSize = 0;
packetBuffer._currentType = PACKET_TYPE_UNKNOWN;
}
void VoxelEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, PACKET_TYPE type) {
packetBuffer._currentSize = populateTypeAndVersion(&packetBuffer._currentBuffer[0], type);
// pack in sequence number
unsigned short int* sequenceAt = (unsigned short int*)&packetBuffer._currentBuffer[packetBuffer._currentSize];
*sequenceAt = _sequenceNumber;
packetBuffer._currentSize += sizeof(unsigned short int); // nudge past sequence
_sequenceNumber++;
// pack in timestamp
uint64_t now = usecTimestampNow();
uint64_t* timeAt = (uint64_t*)&packetBuffer._currentBuffer[packetBuffer._currentSize];
*timeAt = now;
packetBuffer._currentSize += sizeof(uint64_t); // nudge past timestamp
packetBuffer._currentType = type;
}
bool VoxelEditPacketSender::process() {
// if we have server jurisdiction details, and we have pending pre-jurisdiction packets, then process those
// before doing our normal process step. This processPreJurisdictionPackets()
if (voxelServersExist() && (!_preServerPackets.empty() || !_preServerSingleMessagePackets.empty() )) {
processPreServerExistsPackets();
}
// base class does most of the work.
return PacketSender::process();
}

View file

@ -11,27 +11,13 @@
#ifndef __shared__VoxelEditPacketSender__
#define __shared__VoxelEditPacketSender__
#include <PacketSender.h>
#include <PacketHeaders.h>
#include <SharedUtil.h> // for VoxelDetail
#include "JurisdictionMap.h"
/// Used for construction of edit voxel packets
class EditPacketBuffer {
public:
EditPacketBuffer() : _nodeUUID(), _currentType(PACKET_TYPE_UNKNOWN), _currentSize(0) { }
EditPacketBuffer(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid());
QUuid _nodeUUID;
PACKET_TYPE _currentType;
unsigned char _currentBuffer[MAX_PACKET_SIZE];
ssize_t _currentSize;
};
#include <OctreeEditPacketSender.h>
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
class VoxelEditPacketSender : public virtual PacketSender {
class VoxelEditPacketSender : public virtual OctreeEditPacketSender {
public:
VoxelEditPacketSender(PacketSenderNotify* notify = NULL);
~VoxelEditPacketSender();
VoxelEditPacketSender(PacketSenderNotify* notify = NULL) : OctreeEditPacketSender(notify) { }
~VoxelEditPacketSender() { }
/// Send voxel edit message immediately
void sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail& detail);
@ -39,81 +25,27 @@ public:
/// Queues a single voxel edit message. Will potentially send a pending multi-command packet. Determines which voxel-server
/// node or nodes the packet should be sent to. Can be called even before voxel servers are known, in which case up to
/// MaxPendingMessages will be buffered and processed when voxel servers are known.
void queueVoxelEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length);
void queueVoxelEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
queueOctreeEditMessage(type, codeColorBuffer, length);
}
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
void queueVoxelEditMessages(PACKET_TYPE type, int numberOfDetails, VoxelDetail* details);
/// Releases all queued messages even if those messages haven't filled an MTU packet. This will move the packed message
/// packets onto the send queue. If running in threaded mode, the caller does not need to do any further processing to
/// have these packets get sent. If running in non-threaded mode, the caller must still call process() on a regular
/// interval to ensure that the packets are actually sent. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages of the released messages will be buffered and actually released when
/// voxel servers are known.
void releaseQueuedMessages();
/// are we in sending mode. If we're not in sending mode then all packets and messages will be ignored and
/// not queued and not sent
bool getShouldSend() const { return _shouldSend; }
/// set sending mode. By default we are set to shouldSend=TRUE and packets will be sent. If shouldSend=FALSE, then we'll
/// switch to not sending mode, and all packets and messages will be ignored, not queued, and not sent. This might be used
/// in an application like interface when all voxel features are disabled.
void setShouldSend(bool shouldSend) { _shouldSend = shouldSend; }
/// call this to inform the VoxelEditPacketSender of the voxel server jurisdictions. This is required for normal operation.
/// The internal contents of the jurisdiction map may change throughout the lifetime of the VoxelEditPacketSender. This map
/// can be set prior to voxel servers being present, so long as the contents of the map accurately reflect the current
/// known jurisdictions.
void setVoxelServerJurisdictions(NodeToJurisdictionMap* voxelServerJurisdictions) {
_voxelServerJurisdictions = voxelServerJurisdictions;
setServerJurisdictions(voxelServerJurisdictions);
}
/// if you're running in non-threaded mode, you must call this method regularly
virtual bool process();
/// Set the desired number of pending messages that the VoxelEditPacketSender should attempt to queue even if voxel
/// servers are not present. This only applies to how the VoxelEditPacketSender will manage messages when no voxel
/// servers are present. By default, this value is the same as the default packets that will be sent in one second.
/// Which means the VoxelEditPacketSender will not buffer all messages given to it if no voxel servers are present.
/// This is the maximum number of queued messages and single messages.
void setMaxPendingMessages(int maxPendingMessages) { _maxPendingMessages = maxPendingMessages; }
// the default number of pending messages we will store if no voxel servers are available
static const int DEFAULT_MAX_PENDING_MESSAGES;
// is there a voxel server available to send packets to
bool voxelServersExist() const;
bool voxelServersExist() const { return serversExist(); }
/// Set the desired max packet size in bytes that the VoxelEditPacketSender should create
void setMaxPacketSize(int maxPacketSize) { _maxPacketSize = maxPacketSize; }
/// returns the current desired max packet size in bytes that the VoxelEditPacketSender will create
int getMaxPacketSize() const { return _maxPacketSize; }
private:
bool _shouldSend;
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length);
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
void initializePacket(EditPacketBuffer& packetBuffer, PACKET_TYPE type);
void releaseQueuedPacket(EditPacketBuffer& packetBuffer); // releases specific queued packet
void processPreServerExistsPackets();
// These are packets which are destined from know servers but haven't been released because they're still too small
std::map<QUuid, EditPacketBuffer> _pendingEditPackets;
// These are packets that are waiting to be processed because we don't yet know if there are voxel servers
int _maxPendingMessages;
bool _releaseQueuedMessagesPending;
std::vector<EditPacketBuffer*> _preServerPackets; // these will get packed into other larger packets
std::vector<EditPacketBuffer*> _preServerSingleMessagePackets; // these will go out as is
NodeToJurisdictionMap* _voxelServerJurisdictions;
unsigned short int _sequenceNumber;
int _maxPacketSize;
// My server type is the voxel server
virtual unsigned char getMyNodeType() const { return NODE_TYPE_VOXEL_SERVER; }
};
#endif // __shared__VoxelEditPacketSender__

View file

@ -12,7 +12,7 @@
#include <QtCore/QObject>
#include <JurisdictionListener.h>
#include <VoxelEditPacketSender.h>
#include "VoxelEditPacketSender.h"
/// handles scripting of voxel commands from JS passed to assigned clients
class VoxelScriptingInterface : public QObject {