mirror of
https://github.com/overte-org/overte.git
synced 2025-08-17 09:15:37 +02:00
Merge pull request #1353 from ZappoMan/particle_details
First real pass at Particles
This commit is contained in:
commit
ee2fda1412
49 changed files with 2300 additions and 486 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include "AnimationServer.h"
|
||||
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
AnimationServer animationServer(argc, argv);
|
||||
return animationServer.exec();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
62
interface/src/ParticleTreeRenderer.cpp
Normal file
62
interface/src/ParticleTreeRenderer.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
40
interface/src/ParticleTreeRenderer.h
Normal file
40
interface/src/ParticleTreeRenderer.h
Normal 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__) */
|
|
@ -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());
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
299
libraries/octree/src/OctreeEditPacketSender.cpp
Normal file
299
libraries/octree/src/OctreeEditPacketSender.cpp
Normal 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();
|
||||
}
|
114
libraries/octree/src/OctreeEditPacketSender.h
Normal file
114
libraries/octree/src/OctreeEditPacketSender.h
Normal 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__
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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__) */
|
135
libraries/octree/src/OctreeRenderer.cpp
Normal file
135
libraries/octree/src/OctreeRenderer.cpp
Normal 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();
|
||||
}
|
||||
}
|
67
libraries/octree/src/OctreeRenderer.h
Normal file
67
libraries/octree/src/OctreeRenderer.h
Normal 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__) */
|
338
libraries/particles/src/Particle.cpp
Normal file
338
libraries/particles/src/Particle.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
130
libraries/particles/src/Particle.h
Normal file
130
libraries/particles/src/Particle.h
Normal 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__) */
|
55
libraries/particles/src/ParticleEditPacketSender.cpp
Normal file
55
libraries/particles/src/ParticleEditPacketSender.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
libraries/particles/src/ParticleEditPacketSender.h
Normal file
34
libraries/particles/src/ParticleEditPacketSender.h
Normal 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__
|
30
libraries/particles/src/ParticleScriptingInterface.cpp
Normal file
30
libraries/particles/src/ParticleScriptingInterface.cpp
Normal 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);
|
||||
}
|
89
libraries/particles/src/ParticleScriptingInterface.h
Normal file
89
libraries/particles/src/ParticleScriptingInterface.h
Normal 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__) */
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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__) */
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
44
libraries/shared/src/RegisteredMetaTypes.cpp
Normal file
44
libraries/shared/src/RegisteredMetaTypes.cpp
Normal 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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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 {
|
Loading…
Reference in a new issue