diff --git a/assignment-client/src/models/ModelServer.cpp b/assignment-client/src/models/ModelServer.cpp index 07359f001a..ae6ffaf969 100644 --- a/assignment-client/src/models/ModelServer.cpp +++ b/assignment-client/src/models/ModelServer.cpp @@ -86,7 +86,7 @@ bool ModelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendDeletedModels; } -int ModelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { +int ModelServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { unsigned char outputBuffer[MAX_PACKET_SIZE]; size_t packetLength = 0; @@ -99,6 +99,7 @@ int ModelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodeP bool hasMoreToSend = true; // TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 models? + packetsSent = 0; while (hasMoreToSend) { hasMoreToSend = tree->encodeModelsDeletedSince(queryNode->getSequenceNumber(), deletedModelsSentAt, outputBuffer, MAX_PACKET_SIZE, packetLength); @@ -107,6 +108,7 @@ int ModelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodeP NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); queryNode->packetSent(outputBuffer, packetLength); + packetsSent++; } nodeData->setLastDeletedModelsSentAt(deletePacketSentAt); diff --git a/assignment-client/src/models/ModelServer.h b/assignment-client/src/models/ModelServer.h index 7e7f239f2a..38acc7f1e1 100644 --- a/assignment-client/src/models/ModelServer.h +++ b/assignment-client/src/models/ModelServer.h @@ -37,7 +37,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent); virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode); diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 30e3011ae8..cb149b1d96 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -85,7 +85,6 @@ bool OctreeSendThread::process() { if (nodeData && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); packetDistributor(nodeData, viewFrustumChanged); - resendNackedPackets(nodeData); } } } @@ -281,26 +280,6 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes return packetsSent; } -int OctreeSendThread::resendNackedPackets(OctreeQueryNode* nodeData) { - - const int MAX_PACKETS_RESEND = 10; - int packetsSent = 0; - - const QByteArray* packet; - while (nodeData->hasNextNackedPacket() && packetsSent < MAX_PACKETS_RESEND) { - packet = nodeData->getNextNackedPacket(); - if (packet) { - NodeList::getInstance()->writeDatagram(*packet, _node); - packetsSent++; - - _totalBytes += packet->size(); - _totalPackets++; - _totalWastedBytes += MAX_PACKET_SIZE - packet->size(); - } - } - return packetsSent; -} - /// Version of voxel distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged) { @@ -311,6 +290,10 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus return 0; } + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + int truePacketsSent = 0; int trueBytesSent = 0; int packetsSentThisInterval = 0; @@ -408,9 +391,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus //quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; //quint64 startCompressCalls = OctreePacketData::getCompressContentCalls(); - int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND)); - int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); - int extraPackingAttempts = 0; bool completedScene = false; while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { @@ -581,12 +561,26 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets if (_myServer->hasSpecialPacketToSend(_node) && !nodeData->isShuttingDown()) { - trueBytesSent += _myServer->sendSpecialPacket(nodeData, _node); + int specialPacketsSent; + trueBytesSent += _myServer->sendSpecialPacket(_node, nodeData, specialPacketsSent); nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - truePacketsSent++; - packetsSentThisInterval++; + truePacketsSent += specialPacketsSent; + packetsSentThisInterval += specialPacketsSent; } + // Re-send packets that were nacked by the client + while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) { + const QByteArray* packet = nodeData->getNextNackedPacket(); + if (packet) { + NodeList::getInstance()->writeDatagram(*packet, _node); + truePacketsSent++; + packetsSentThisInterval++; + + _totalBytes += packet->size(); + _totalPackets++; + _totalWastedBytes += MAX_PACKET_SIZE - packet->size(); + } + } quint64 end = usecTimestampNow(); int elapsedmsec = (end - start)/USECS_PER_MSEC; diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index e7599ebcd2..d8eed27802 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -55,9 +55,6 @@ private: int _nodeMissingCount; bool _isShuttingDown; - - int resendNackedPackets(OctreeQueryNode* nodeData); - }; #endif // hifi_OctreeSendThread_h diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index f0db93feb3..5595d139be 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -72,7 +72,7 @@ public: // subclass may implement these method virtual void beforeRun() { }; virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; } - virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { return 0; } + virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; } static void attachQueryNodeToNode(Node* newNode); diff --git a/assignment-client/src/particles/ParticleServer.cpp b/assignment-client/src/particles/ParticleServer.cpp index e7a0f75dfd..674d22145f 100644 --- a/assignment-client/src/particles/ParticleServer.cpp +++ b/assignment-client/src/particles/ParticleServer.cpp @@ -86,7 +86,7 @@ bool ParticleServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendDeletedParticles; } -int ParticleServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { +int ParticleServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { unsigned char outputBuffer[MAX_PACKET_SIZE]; size_t packetLength = 0; @@ -99,6 +99,7 @@ int ParticleServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNo bool hasMoreToSend = true; // TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 particles? + packetsSent = 0; while (hasMoreToSend) { hasMoreToSend = tree->encodeParticlesDeletedSince(queryNode->getSequenceNumber(), deletedParticlesSentAt, outputBuffer, MAX_PACKET_SIZE, packetLength); @@ -107,6 +108,7 @@ int ParticleServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNo NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); queryNode->packetSent(outputBuffer, packetLength); + packetsSent++; } nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt); diff --git a/assignment-client/src/particles/ParticleServer.h b/assignment-client/src/particles/ParticleServer.h index 3066c5fa98..d444368a9d 100644 --- a/assignment-client/src/particles/ParticleServer.h +++ b/assignment-client/src/particles/ParticleServer.h @@ -37,7 +37,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent); virtual void particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode); diff --git a/assignment-client/src/voxels/VoxelServer.cpp b/assignment-client/src/voxels/VoxelServer.cpp index 34b01f529a..b021ddd9f6 100644 --- a/assignment-client/src/voxels/VoxelServer.cpp +++ b/assignment-client/src/voxels/VoxelServer.cpp @@ -40,7 +40,7 @@ bool VoxelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendEnvironments; } -int VoxelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { +int VoxelServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { unsigned char* copyAt = _tempOutputBuffer; @@ -76,6 +76,7 @@ int VoxelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodeP NodeList::getInstance()->writeDatagram((char*) _tempOutputBuffer, envPacketLength, SharedNodePointer(node)); queryNode->packetSent(_tempOutputBuffer, envPacketLength); + packetsSent = 1; return envPacketLength; } diff --git a/assignment-client/src/voxels/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h index 4e04c48cfd..b13f83b65f 100644 --- a/assignment-client/src/voxels/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -46,7 +46,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent); private: bool _sendEnvironments; diff --git a/examples/growTrees.js b/examples/growTrees.js new file mode 100644 index 0000000000..142c72300a --- /dev/null +++ b/examples/growTrees.js @@ -0,0 +1,832 @@ +// +// growPlants.js +// examples +// +// Created by Benjamin Arnold on May 29, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// This sample script allows the user to grow different types of plants on the voxels +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting +var previewLineWidth = 2.0; + +var voxelSize = 1; +var windowDimensions = Controller.getViewportDimensions(); +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; + +var MAX_VOXEL_SCALE_POWER = 5; +var MIN_VOXEL_SCALE_POWER = -8; +var MAX_VOXEL_SCALE = Math.pow(2.0, MAX_VOXEL_SCALE_POWER); +var MIN_VOXEL_SCALE = Math.pow(2.0, MIN_VOXEL_SCALE_POWER); + + +var linePreviewTop = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + +var linePreviewBottom = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + +var linePreviewLeft = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + +var linePreviewRight = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + + +var UIColor = { red: 0, green: 160, blue: 0}; +var activeUIColor = { red: 0, green: 255, blue: 0}; + +var toolHeight = 50; +var toolWidth = 50; + +var editToolsOn = true; + +var voxelToolSelected = false; + +var scaleSelectorWidth = 144; +var scaleSelectorHeight = 37; + +var scaleSelectorX = windowDimensions.x / 5.0; +var scaleSelectorY = windowDimensions.y - scaleSelectorHeight; + +var voxelTool = Overlays.addOverlay("image", { + x: scaleSelectorX + scaleSelectorWidth + 1, y: windowDimensions.y - toolHeight, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-tool.svg", + visible: editToolsOn, + color: UIColor, + alpha: 0.9 + }); + +var copyScale = true; +function ScaleSelector() { + this.x = scaleSelectorX; + this.y = scaleSelectorY; + this.width = scaleSelectorWidth; + this.height = scaleSelectorHeight; + + this.displayPower = false; + this.scale = 1.0; + this.power = 0; + + this.FIRST_PART = this.width * 40.0 / 100.0; + this.SECOND_PART = this.width * 37.0 / 100.0; + + this.buttonsOverlay = Overlays.addOverlay("image", { + x: this.x, y: this.y, + width: this.width, height: this.height, + //subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-size-selector.svg", + alpha: 0.9, + visible: editToolsOn, + color: activeUIColor + }); + this.textOverlay = Overlays.addOverlay("text", { + x: this.x + this.FIRST_PART, y: this.y, + width: this.SECOND_PART, height: this.height, + topMargin: 13, + text: this.scale.toString(), + alpha: 0.0, + visible: editToolsOn, + color: activeUIColor + }); + this.powerOverlay = Overlays.addOverlay("text", { + x: this.x + this.FIRST_PART, y: this.y, + width: this.SECOND_PART, height: this.height, + leftMargin: 28, + text: this.power.toString(), + alpha: 0.0, + visible: false, + color: activeUIColor + }); + this.setScale = function(scale) { + if (scale > MAX_VOXEL_SCALE) { + scale = MAX_VOXEL_SCALE; + } + if (scale < MIN_VOXEL_SCALE) { + scale = MIN_VOXEL_SCALE; + } + + this.scale = scale; + this.power = Math.floor(Math.log(scale) / Math.log(2)); + this.update(); + } + + this.show = function(doShow) { + Overlays.editOverlay(this.buttonsOverlay, {visible: doShow}); + Overlays.editOverlay(this.textOverlay, {visible: doShow}); + Overlays.editOverlay(this.powerOverlay, {visible: doShow && this.displayPower}); + } + + this.move = function() { + this.x = swatchesX + swatchesWidth; + this.y = swatchesY; + + Overlays.editOverlay(this.buttonsOverlay, { + x: this.x, y: this.y, + }); + Overlays.editOverlay(this.textOverlay, { + x: this.x + this.FIRST_PART, y: this.y, + }); + Overlays.editOverlay(this.powerOverlay, { + x: this.x + this.FIRST_PART, y: this.y, + }); + } + + + this.switchDisplay = function() { + this.displayPower = !this.displayPower; + + if (this.displayPower) { + Overlays.editOverlay(this.textOverlay, { + leftMargin: 18, + text: "2" + }); + Overlays.editOverlay(this.powerOverlay, { + text: this.power.toString(), + visible: editToolsOn + }); + } else { + Overlays.editOverlay(this.textOverlay, { + leftMargin: 13, + text: this.scale.toString() + }); + Overlays.editOverlay(this.powerOverlay, { + visible: false + }); + } + } + + this.update = function() { + if (this.displayPower) { + Overlays.editOverlay(this.powerOverlay, {text: this.power.toString()}); + } else { + Overlays.editOverlay(this.textOverlay, {text: this.scale.toString()}); + } + } + + this.incrementScale = function() { + copyScale = false; + if (this.power < MAX_VOXEL_SCALE_POWER) { + ++this.power; + this.scale *= 2.0; + this.update(); + } + } + + this.decrementScale = function() { + copyScale = false; + if (MIN_VOXEL_SCALE_POWER < this.power) { + --this.power; + this.scale /= 2.0; + this.update(); + } + } + + this.clicked = function(x, y) { + if (this.x < x && x < this.x + this.width && + this.y < y && y < this.y + this.height) { + + if (x < this.x + this.FIRST_PART) { + this.decrementScale(); + } else if (x < this.x + this.FIRST_PART + this.SECOND_PART) { + this.switchDisplay(); + } else { + this.incrementScale(); + } + return true; + } + return false; + } + + this.cleanup = function() { + Overlays.deleteOverlay(this.buttonsOverlay); + Overlays.deleteOverlay(this.textOverlay); + Overlays.deleteOverlay(this.powerOverlay); + } + +} +var scaleSelector = new ScaleSelector(); + + +function calculateVoxelFromIntersection(intersection, operation) { + + var resultVoxel; + + var x; + var y; + var z; + + // if our "target voxel size" is larger than the voxel we intersected with, then we need to find the closest + // ancestor voxel of our target size that contains our intersected voxel. + if (voxelSize > intersection.voxel.s) { + x = Math.floor(intersection.voxel.x / voxelSize) * voxelSize; + y = Math.floor(intersection.voxel.y / voxelSize) * voxelSize; + z = Math.floor(intersection.voxel.z / voxelSize) * voxelSize; + } else { + // otherwise, calculate the enclosed voxel of size voxelSize that the intersection point falls inside of. + // if you have a voxelSize that's smaller than the voxel you're intersecting, this calculation will result + // in the subvoxel that the intersection point falls in, if the target voxelSize matches the intersecting + // voxel this still works and results in returning the intersecting voxel which is what we want + var adjustToCenter = Vec3.multiply(Voxels.getFaceVector(intersection.face), (voxelSize * -0.5)); + + var centerOfIntersectingVoxel = Vec3.sum(intersection.intersection, adjustToCenter); + x = Math.floor(centerOfIntersectingVoxel.x / voxelSize) * voxelSize; + y = Math.floor(centerOfIntersectingVoxel.y / voxelSize) * voxelSize; + z = Math.floor(centerOfIntersectingVoxel.z / voxelSize) * voxelSize; + } + resultVoxel = { x: x, y: y, z: z, s: voxelSize }; + var highlightAt = { x: x, y: y, z: z, s: voxelSize }; + + + + // we only do the "add to the face we're pointing at" adjustment, if the operation is an add + // operation, and the target voxel size is equal to or smaller than the intersecting voxel. + var wantAddAdjust = (operation == "add" && (voxelSize <= intersection.voxel.s)); + + // now we also want to calculate the "edge square" for the face for this voxel + if (intersection.face == "MIN_X_FACE") { + + highlightAt.x = x - zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.x -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + + } else if (intersection.face == "MAX_X_FACE") { + + highlightAt.x = x + voxelSize + zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.x += resultVoxel.s; + } + + resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + + } else if (intersection.face == "MIN_Y_FACE") { + + highlightAt.y = y - zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.y -= voxelSize; + } + + resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; + + } else if (intersection.face == "MAX_Y_FACE") { + + highlightAt.y = y + voxelSize + zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.y += voxelSize; + } + + resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust }; + resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust}; + resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust}; + resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust}; + + } else if (intersection.face == "MIN_Z_FACE") { + + highlightAt.z = z - zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.z -= voxelSize; + } + + resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z }; + resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z}; + resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z }; + resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z}; + + } else if (intersection.face == "MAX_Z_FACE") { + + highlightAt.z = z + voxelSize + zFightingSizeAdjust; + if (wantAddAdjust) { + resultVoxel.z += voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z}; + resultVoxel.topLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z }; + resultVoxel.topRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z}; + + } + return resultVoxel; +} + +var trackLastMouseX = 0; +var trackLastMouseY = 0; + +function showPreviewLines() { + + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); + + var intersection = Voxels.findRayIntersection(pickRay); + + if (intersection.intersects) { + var resultVoxel = calculateVoxelFromIntersection(intersection, ""); + Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true }); + Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true }); + Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true }); + Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true }); + } else { + Overlays.editOverlay(linePreviewTop, { visible: false }); + Overlays.editOverlay(linePreviewBottom, { visible: false }); + Overlays.editOverlay(linePreviewLeft, { visible: false }); + Overlays.editOverlay(linePreviewRight, { visible: false }); + } +} + +function mouseMoveEvent(event) { + trackLastMouseX = event.x; + trackLastMouseY = event.y; + if (!voxelToolSelected) { + return; + } + showPreviewLines(); +} + + +// Array of possible trees, right now there is only one +var treeTypes = []; + +treeTypes.push({ + name: "Tall Green", + // Voxel Colors + wood: { r: 133, g: 81, b: 53 }, + leaves: { r: 22, g: 83, b: 31 }, + + // How tall the tree is + height: { min: 20, max: 60 }, + middleHeight: 0.3, + + // Chance of making a branch + branchChance: { min: 0.01, max: 0.1 }, + branchLength: { min: 30, max: 60 }, + branchThickness: { min: 2, max: 7}, + + // The width of the core, affects width and shape + coreWidth: { min: 1, max: 4 }, + + //TODO: Make this quadratic splines instead of linear + bottomThickness: { min: 2, max: 8 }, + middleThickness: { min: 1, max: 4 }, + topThickness: { min: 3, max: 6 }, + + //Modifies leaves at top + leafCapSizeOffset: 0 +}); + +// Applies noise to color +var colorNoiseRange = 0.2; + + +// Useful constants +var LEFT = 0; +var BACK = 1; +var RIGHT = 2; +var FRONT = 3; +var UP = 4; + +// Interpolates between min and max of treevar based on b +function interpolate(treeVar, b) { + return (treeVar.min + (treeVar.max - treeVar.min) * b); +} + +function makeBranch(x, y, z, step, length, dir, thickness, wood, leaves) { + var moveDir; + + var currentThickness; + //thickness attenuates to thickness - 3 + var finalThickness = thickness - 3; + if (finalThickness < 1) { + finalThickness = 1; + } + //Iterative branch generation + while (true) { + + //If we are at the end, place a ball of leaves + if (step == 0) { + makeSphere(x, y, z, 2 + finalThickness, leaves); + return; + } + //thickness attenuation + currentThickness = Math.round((finalThickness + (thickness - finalThickness) * (step/length))) - 1; + + + // If the branch is thick, grow a vertical slice + if (currentThickness > 0) { + for (var i = -currentThickness; i <= currentThickness; i++) { + var len = currentThickness - Math.abs(i); + switch (dir) { + case 0: //left + case 2: //right + growInDirection(x, y + i * voxelSize, z, len, len, BACK, wood, false, true); + growInDirection(x, y + i * voxelSize, z, len, len, FRONT, wood, false, false) + break; + case 1: //back + case 3: //front + growInDirection(x, y + i * voxelSize, z, len, len, LEFT, wood, false, true); + growInDirection(x, y + i * voxelSize, z, len, len, RIGHT, wood, false, false) + break; + } + } + } else { + //Otherwise place a single voxel + var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0; + Voxels.setVoxel(x, y, z, voxelSize, wood.r * colorNoise, wood.g * colorNoise, wood.b * colorNoise); + } + + // determines random change in direction for branch + var r = Math.floor(Math.random() * 9); + + + if (r >= 6){ + moveDir = dir; //in same direction + } else if (r >= 4) { + moveDir = UP; //up + } + else if (dir == LEFT){ + if (r >= 2){ + moveDir = FRONT; + } + else{ + moveDir = BACK; + } + } + else if (dir == BACK){ + if (r >= 2){ + moveDir = LEFT; + } + else{ + moveDir = RIGHT; + } + } + else if (dir == RIGHT){ + if (r >= 2){ + moveDir = BACK; + } + else{ + moveDir = FRONT; + } + } + else if (dir == FRONT){ + if (r >= 2){ + moveDir = RIGHT; + } + else{ + moveDir = LEFT; + } + } + + //Move the branch by moveDir + switch (moveDir) { + case 0: //left + x = x - voxelSize; + break; + case 1: //back + z = z - voxelSize; + break; + case 2: //right + x = x + voxelSize; + break; + case 3: //front + z = z + voxelSize; + break; + case 4: //up + y = y + voxelSize; + break; + } + + step--; + } +} + +// Places a sphere of voxels +function makeSphere(x, y, z, radius, color) { + if (radius <= 0) { + return; + } + var width = radius * 2 + 1; + var distance; + + for (var i = -radius; i <= radius; i++){ + for (var j = -radius; j <= radius; j++){ + for (var k = -radius; k <= radius; k++){ + distance = Math.sqrt(i * i + j * j + k * k); + if (distance <= radius){ + var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0; + Voxels.setVoxel(x + i * voxelSize, y + j * voxelSize, z + k * voxelSize, voxelSize, color.r * colorNoise, color.g * colorNoise, color.b * colorNoise); + } + } + } + } +} + +function growInDirection(x, y, z, step, length, dir, color, isSideBranching, addVoxel) { + + + if (addVoxel == true) { + var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0; + Voxels.setVoxel(x, y, z, voxelSize, color.r * colorNoise, color.g * colorNoise, color.b * colorNoise); + } + + // If this is a main vein, it will branch outward perpendicular to its motion + if (isSideBranching == true){ + var step2; + if (step >= length - 1){ + step2 = length; + } + else{ + step2 = step + 1; + } + growInDirection(x, y, z, step, length, BACK, color, false, false); + growInDirection(x, y, z, step, length, FRONT, color, false, false); + } + + if (step < 1) return; + + // Recursively move in the direction + if (dir == LEFT) { //left + growInDirection(x - voxelSize, y, z, step - 1, length, dir, color, isSideBranching, true); + } + else if (dir == BACK) { //back + growInDirection(x, y, z - voxelSize, step - 1, length, dir, color, isSideBranching, true); + } + else if (dir == RIGHT) { //right + growInDirection(x + voxelSize, y, z, step - 1, length, dir, color, isSideBranching, true); + } + else if (dir == FRONT) {//front + growInDirection(x, y, z + voxelSize, step - 1, length, dir, color, isSideBranching, true); + } + +} + +// Grows the thickness of the tree +function growHorizontalSlice(x, y, z, thickness, color, side) { + // The side variable determines which directions we should grow in + // it is an optimization that prevents us from visiting voxels multiple + // times for trees with a coreWidth > 1 + + // side: + // 8 == all directions + // 0 1 2 + // 3 -1 4 + // 5 6 7 + + Voxels.setVoxel(x, y, z, voxelSize, color.r, color.g, color.b); + + //We are done if there is no thickness + if (thickness == 0) { + return; + } + + + switch (side) { + case 0: + growInDirection(x, y, z, thickness, thickness, LEFT, color, true, false); + break; + case 1: + growInDirection(x, y, z, thickness, thickness, BACK, color, false, false); + break; + case 2: + growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false); + break; + case 3: + growInDirection(x, y, z, thickness, thickness, LEFT, color, false, false); + break; + case 4: + growInDirection(x, y, z, thickness, thickness, BACK, color, false, false); + break; + case 5: + growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false); + break; + case 6: + growInDirection(x, y, z, thickness, thickness, FRONT, color, false, false); + break; + case 7: + growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false); + break; + case 8: + if (thickness > 1){ + growInDirection(x, y, z, thickness, thickness, LEFT, color, true, false); + growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false) + } else if (thickness == 1){ + Voxels.setVoxel(x - voxelSize, y, z, voxelSize, color.r, color.g, color.b); + Voxels.setVoxel(x + voxelSize, y, z, voxelSize, color.r, color.g, color.b); + Voxels.setVoxel(x, y, z - voxelSize, voxelSize, color.r, color.g, color.b); + Voxels.setVoxel(x, y, z + voxelSize, voxelSize, color.r, color.g, color.b); + } + break; + } +} + +function computeSide(x, z, coreWidth) { + // side: + // 8 == all directions + // 0 1 2 + // 3 -1 4 + // 5 6 7 + + // if the core is only a single block, we can grow out in all directions + if (coreWidth == 1){ + return 8; + } + + // Back face + if (z == 0) { + if (x == 0) { + return 0; + } else if (x == coreWidth - 1) { + return 2; + } else { + return 1; + } + } + + // Front face + if (z == (coreWidth - 1)) { + if (x == 0) { + return 5; + } else if (x == (coreWidth - 1)) { + return 7; + } else { + return 6; + } + } + + // Left face + if (x == 0) { + return 3; + } + + // Right face + if (x == (coreWidth - 1)) { + return 4; + } + + //Interior + return -1; +} + + +function growTree(x, y, z, tree) { + + // The size of the tree, from 0-1 + var treeSize = Math.random(); + + // Get tree properties by interpolating with the treeSize + var height = interpolate(tree.height, treeSize); + var baseHeight = Math.ceil(tree.middleHeight * height); + var bottomThickness = interpolate(tree.bottomThickness, treeSize); + var middleThickness = interpolate(tree.middleThickness, treeSize); + var topThickness = interpolate(tree.topThickness, treeSize); + var coreWidth = Math.ceil(interpolate(tree.coreWidth, treeSize)); + + var thickness; + var side; + + //Loop upwards through each slice of the tree + for (var i = 0; i < height; i++){ + + //Branch properties are based on current height as well as the overall tree size + var branchChance = interpolate(tree.branchChance, i / height); + var branchLength = Math.ceil(interpolate(tree.branchLength, (i / height) * treeSize)); + var branchThickness = Math.round(interpolate(tree.branchThickness, (i / height) * treeSize)); + + // Get the "thickness" of the tree by doing linear interpolation between the middle thickness + // and the top and bottom thickness. + if (i <= baseHeight && baseHeight != 0){ + thickness = (i / (baseHeight) * (middleThickness - bottomThickness) + bottomThickness); + } else { + var denom = ((height - baseHeight)) * (topThickness - middleThickness) + middleThickness; + if (denom != 0) { + thickness = (i - baseHeight) / denom; + } else { + thickness = 0; + } + } + // The core of the tree is a vertical rectangular prism through the middle of the tree + + //Loop through the "core", which helps shape the trunk + var startX = x - Math.floor(coreWidth / 2) * voxelSize; + var startZ = z - Math.floor(coreWidth / 2) * voxelSize; + for (var j = 0; j < coreWidth; j++) { + for (var k = 0; k < coreWidth; k++) { + //determine which side of the tree we are on + side = computeSide(j, k, coreWidth); + //grow a horizontal slice of the tree + growHorizontalSlice(startX + j * voxelSize, y + i * voxelSize, startZ + k * voxelSize, Math.floor(thickness), tree.wood, side); + + // Branches + if (side != -1) { + var r = Math.random(); + if (r <= branchChance){ + var dir = Math.floor((Math.random() * 4)); + makeBranch(startX + j * voxelSize, y + i * voxelSize, startZ + k * voxelSize, branchLength, branchLength, dir, branchThickness, tree.wood, tree.leaves); + + } + } + } + } + } + + makeSphere(x, y + height * voxelSize, z, topThickness + coreWidth + tree.leafCapSizeOffset, tree.leaves); +} + +function mousePressEvent(event) { + var mouseX = event.x; + var mouseY = event.y; + + var clickedOnSomething = false; + // Check if we clicked an overlay + var clickedOverlay = Overlays.getOverlayAtPoint({x: mouseX, y: mouseY}); + + if (clickedOverlay == voxelTool) { + voxelToolSelected = !voxelToolSelected; + + if (voxelToolSelected == true) { + Overlays.editOverlay(voxelTool, { + color: activeUIColor + }); + } else { + Overlays.editOverlay(voxelTool, { + color: UIColor + }); + } + + clickedOnSomething = true; + } else if (scaleSelector.clicked(event.x, event.y)) { + clickedOnSomething = true; + voxelSize = scaleSelector.scale; + } + + // Return if we clicked on the UI or the voxel tool is disabled + if (clickedOnSomething || !voxelToolSelected) { + return; + } + + // Compute the picking ray for the click + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Voxels.findRayIntersection(pickRay); + var resultVoxel = calculateVoxelFromIntersection(intersection, "add"); + + // Currently not in use, could randomly select a tree + var treeIndex = Math.floor(Math.random() * treeTypes.length); + + // Grow the first tree type + growTree(resultVoxel.x, resultVoxel.y, resultVoxel.z, treeTypes[0]); + +} + + +function scriptEnding() { + Overlays.deleteOverlay(linePreviewTop); + Overlays.deleteOverlay(linePreviewBottom); + Overlays.deleteOverlay(linePreviewLeft); + Overlays.deleteOverlay(linePreviewRight); + scaleSelector.cleanup(); + Overlays.deleteOverlay(voxelTool); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); + +Script.scriptEnding.connect(scriptEnding); + +Voxels.setPacketsPerSecond(10000); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1956ce9c6c..e01e1df1c4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -158,7 +158,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _mousePressed(false), _audio(STARTUP_JITTER_SAMPLES), _enableProcessVoxelsThread(true), - _voxelProcessor(), + _octreeProcessor(), _voxelHideShowThread(&_voxels), _packetsPerSecond(0), _bytesPerSecond(0), @@ -246,6 +246,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); + connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_octreeProcessor, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&))); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); @@ -420,7 +421,7 @@ Application::~Application() { _audio.thread()->quit(); _audio.thread()->wait(); - _voxelProcessor.terminate(); + _octreeProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); _particleEditSender.terminate(); @@ -519,7 +520,7 @@ void Application::initializeGL() { qDebug( "init() complete."); // create thread for parsing of voxel data independent of the main network and rendering threads - _voxelProcessor.initialize(_enableProcessVoxelsThread); + _octreeProcessor.initialize(_enableProcessVoxelsThread); _voxelEditSender.initialize(_enableProcessVoxelsThread); _voxelHideShowThread.initialize(_enableProcessVoxelsThread); _particleEditSender.initialize(_enableProcessVoxelsThread); @@ -1268,7 +1269,7 @@ void Application::dropEvent(QDropEvent *event) { void Application::sendPingPackets() { QByteArray pingPacket = NodeList::getInstance()->constructPingPacket(); - controlledBroadcastToNodes(pingPacket, NodeSet() +controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::MetavoxelServer); @@ -1886,7 +1887,7 @@ void Application::updateThreads(float deltaTime) { // parse voxel packets if (!_enableProcessVoxelsThread) { - _voxelProcessor.threadRoutine(); + _octreeProcessor.threadRoutine(); _voxelHideShowThread.threadRoutine(); _voxelEditSender.threadRoutine(); _particleEditSender.threadRoutine(); @@ -2097,7 +2098,7 @@ void Application::updateMyAvatar(float deltaTime) { } } - // sent a nack packet containing missing sequence numbers of received packets + // sent nack packets containing missing sequence numbers of received packets from nodes { quint64 now = usecTimestampNow(); quint64 sinceLastNack = now - _lastNackTime; @@ -2127,6 +2128,12 @@ void Application::sendNack() { || node->getType() == NodeType::ModelServer) ) { + // if there are octree packets from this node that are waiting to be processed, + // don't send a NACK since the missing packets may be among those waiting packets. + if (_octreeProcessor.hasPacketsToProcessFrom(node)) { + continue; + } + QUuid nodeUUID = node->getUUID(); _octreeSceneStatsLock.lockForRead(); @@ -3489,8 +3496,10 @@ void Application::saveScripts() { } ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) { - if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptName) && !_scriptEnginesHash[scriptName]->isFinished()){ - return _scriptEnginesHash[scriptName]; + QUrl scriptUrl(scriptName); + const QString& scriptURLString = scriptUrl.toString(); + if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptURLString) && !_scriptEnginesHash[scriptURLString]->isFinished()){ + return _scriptEnginesHash[scriptURLString]; } ScriptEngine* scriptEngine; @@ -3498,9 +3507,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); } else { // start the script on a new thread... - QUrl scriptUrl(scriptName); scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); - _scriptEnginesHash.insert(scriptName, scriptEngine); + _scriptEnginesHash.insert(scriptURLString, scriptEngine); if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; diff --git a/interface/src/Application.h b/interface/src/Application.h index a6c4df3d31..418b44dd91 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -88,7 +88,7 @@ #include "voxels/VoxelFade.h" #include "voxels/VoxelHideShowThread.h" #include "voxels/VoxelImporter.h" -#include "voxels/VoxelPacketProcessor.h" +#include "voxels/OctreePacketProcessor.h" #include "voxels/VoxelSystem.h" @@ -129,7 +129,7 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f; class Application : public QApplication { Q_OBJECT - friend class VoxelPacketProcessor; + friend class OctreePacketProcessor; friend class VoxelEditPacketSender; friend class DatagramProcessor; @@ -192,7 +192,7 @@ public: ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } VoxelSystem* getVoxels() { return &_voxels; } VoxelTree* getVoxelTree() { return _voxels.getTree(); } - const VoxelPacketProcessor& getVoxelPacketProcessor() const { return _voxelProcessor; } + const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } ParticleTreeRenderer* getParticles() { return &_particles; } MetavoxelSystem* getMetavoxels() { return &_metavoxels; } ModelTreeRenderer* getModels() { return &_models; } @@ -533,7 +533,7 @@ private: Audio _audio; bool _enableProcessVoxelsThread; - VoxelPacketProcessor _voxelProcessor; + OctreePacketProcessor _octreeProcessor; VoxelHideShowThread _voxelHideShowThread; VoxelEditPacketSender _voxelEditSender; ParticleEditPacketSender _particleEditSender; diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 56078c1a8d..cdd7e7ef0f 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -71,7 +71,7 @@ void DatagramProcessor::processDatagrams() { case PacketTypeOctreeStats: case PacketTypeEnvironmentData: { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "Application::networkReceive()... _voxelProcessor.queueReceivedPacket()"); + "Application::networkReceive()... _octreeProcessor.queueReceivedPacket()"); bool wantExtraDebugging = application->getLogger()->extraDebugging(); if (wantExtraDebugging && packetTypeForPacket(incomingPacket) == PacketTypeVoxelData) { @@ -92,7 +92,7 @@ void DatagramProcessor::processDatagrams() { if (matchedNode) { // add this packet to our list of voxel packets and process them on the voxel processing - application->_voxelProcessor.queueReceivedPacket(matchedNode, incomingPacket); + application->_octreeProcessor.queueReceivedPacket(matchedNode, incomingPacket); } break; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index f5de7459a8..a48e0a2c1a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -45,7 +45,7 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { QGLWidget* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); Audio* audio = application->getAudio(); - const VoxelPacketProcessor& voxelPacketProcessor = application->getVoxelPacketProcessor(); + const OctreePacketProcessor& octreePacketProcessor = application->getOctreePacketProcessor(); BandwidthMeter* bandwidthMeter = application->getBandwidthMeter(); NodeBounds& nodeBoundsDisplay = application->getNodeBoundsDisplay(); @@ -200,7 +200,7 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { // let's set horizontal offset to give stats some margin to mirror int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; - int voxelPacketsToProcess = voxelPacketProcessor.packetsToProcessCount(); + int voxelPacketsToProcess = octreePacketProcessor.packetsToProcessCount(); // Onscreen text about position, servers, etc Stats::getInstance()->display(WHITE_TEXT, horizontalOffset, application->getFps(), application->getPacketsPerSecond(), application->getBytesPerSecond(), voxelPacketsToProcess); // Bandwidth meter diff --git a/interface/src/voxels/VoxelPacketProcessor.cpp b/interface/src/voxels/OctreePacketProcessor.cpp similarity index 92% rename from interface/src/voxels/VoxelPacketProcessor.cpp rename to interface/src/voxels/OctreePacketProcessor.cpp index 095c378c04..66190a5689 100644 --- a/interface/src/voxels/VoxelPacketProcessor.cpp +++ b/interface/src/voxels/OctreePacketProcessor.cpp @@ -1,5 +1,5 @@ // -// VoxelPacketProcessor.cpp +// OctreePacketProcessor.cpp // interface/src/voxels // // Created by Brad Hefta-Gaub on 8/12/13. @@ -13,18 +13,18 @@ #include "Application.h" #include "Menu.h" -#include "VoxelPacketProcessor.h" +#include "OctreePacketProcessor.h" -void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { +void OctreePacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "VoxelPacketProcessor::processPacket()"); + "OctreePacketProcessor::processPacket()"); QByteArray mutablePacket = packet; const int WAY_BEHIND = 300; if (packetsToProcessCount() > WAY_BEHIND && Application::getInstance()->getLogger()->extraDebugging()) { - qDebug("VoxelPacketProcessor::processPacket() packets to process=%d", packetsToProcessCount()); + qDebug("OctreePacketProcessor::processPacket() packets to process=%d", packetsToProcessCount()); } ssize_t messageLength = mutablePacket.size(); diff --git a/interface/src/voxels/VoxelPacketProcessor.h b/interface/src/voxels/OctreePacketProcessor.h similarity index 76% rename from interface/src/voxels/VoxelPacketProcessor.h rename to interface/src/voxels/OctreePacketProcessor.h index 36456c5cc2..bdf25806f3 100644 --- a/interface/src/voxels/VoxelPacketProcessor.h +++ b/interface/src/voxels/OctreePacketProcessor.h @@ -1,5 +1,5 @@ // -// VoxelPacketProcessor.h +// OctreePacketProcessor.h // interface/src/voxels // // Created by Brad Hefta-Gaub on 8/12/13. @@ -9,16 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_VoxelPacketProcessor_h -#define hifi_VoxelPacketProcessor_h +#ifndef hifi_OctreePacketProcessor_h +#define hifi_OctreePacketProcessor_h #include /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() -class VoxelPacketProcessor : public ReceivedPacketProcessor { +class OctreePacketProcessor : public ReceivedPacketProcessor { Q_OBJECT protected: virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); }; -#endif // hifi_VoxelPacketProcessor_h +#endif // hifi_OctreePacketProcessor_h diff --git a/libraries/networking/src/NetworkPacket.cpp b/libraries/networking/src/NetworkPacket.cpp index bf18aa9b37..a8110847e1 100644 --- a/libraries/networking/src/NetworkPacket.cpp +++ b/libraries/networking/src/NetworkPacket.cpp @@ -17,9 +17,9 @@ #include "NetworkPacket.h" -void NetworkPacket::copyContents(const SharedNodePointer& destinationNode, const QByteArray& packet) { +void NetworkPacket::copyContents(const SharedNodePointer& node, const QByteArray& packet) { if (packet.size() && packet.size() <= MAX_PACKET_SIZE) { - _destinationNode = destinationNode; + _node = node; _byteArray = packet; } else { qDebug(">>> NetworkPacket::copyContents() unexpected length = %d", packet.size()); @@ -27,28 +27,28 @@ void NetworkPacket::copyContents(const SharedNodePointer& destinationNode, const } NetworkPacket::NetworkPacket(const NetworkPacket& packet) { - copyContents(packet.getDestinationNode(), packet.getByteArray()); + copyContents(packet.getNode(), packet.getByteArray()); } -NetworkPacket::NetworkPacket(const SharedNodePointer& destinationNode, const QByteArray& packet) { - copyContents(destinationNode, packet); +NetworkPacket::NetworkPacket(const SharedNodePointer& node, const QByteArray& packet) { + copyContents(node, packet); }; // copy assignment NetworkPacket& NetworkPacket::operator=(NetworkPacket const& other) { - copyContents(other.getDestinationNode(), other.getByteArray()); + copyContents(other.getNode(), other.getByteArray()); return *this; } #ifdef HAS_MOVE_SEMANTICS // move, same as copy, but other packet won't be used further NetworkPacket::NetworkPacket(NetworkPacket && packet) { - copyContents(packet.getDestinationNode(), packet.getByteArray()); + copyContents(packet.getNode(), packet.getByteArray()); } // move assignment NetworkPacket& NetworkPacket::operator=(NetworkPacket&& other) { - copyContents(other.getDestinationNode(), other.getByteArray()); + copyContents(other.getNode(), other.getByteArray()); return *this; } #endif diff --git a/libraries/networking/src/NetworkPacket.h b/libraries/networking/src/NetworkPacket.h index 94ddf8d56e..0be35f9fff 100644 --- a/libraries/networking/src/NetworkPacket.h +++ b/libraries/networking/src/NetworkPacket.h @@ -26,6 +26,7 @@ /// Storage of not-yet processed inbound, or not yet sent outbound generic UDP network packet class NetworkPacket { public: + NetworkPacket() { } NetworkPacket(const NetworkPacket& packet); // copy constructor NetworkPacket& operator= (const NetworkPacket& other); // copy assignment @@ -34,15 +35,15 @@ public: NetworkPacket& operator= (NetworkPacket&& other); // move assignment #endif - NetworkPacket(const SharedNodePointer& destinationNode, const QByteArray& byteArray); + NetworkPacket(const SharedNodePointer& node, const QByteArray& byteArray); - const SharedNodePointer& getDestinationNode() const { return _destinationNode; } + const SharedNodePointer& getNode() const { return _node; } const QByteArray& getByteArray() const { return _byteArray; } private: - void copyContents(const SharedNodePointer& destinationNode, const QByteArray& byteArray); + void copyContents(const SharedNodePointer& node, const QByteArray& byteArray); - SharedNodePointer _destinationNode; + SharedNodePointer _node; QByteArray _byteArray; }; diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 5f7502a738..3edfc47c04 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -271,7 +271,7 @@ bool PacketSender::nonThreadedProcess() { unlock(); // send the packet through the NodeList... - NodeList::getInstance()->writeDatagram(temporary.getByteArray(), temporary.getDestinationNode()); + NodeList::getInstance()->writeDatagram(temporary.getByteArray(), temporary.getNode()); packetsSentThisCall++; _packetsOverCheckInterval++; _totalPacketsSent++; diff --git a/libraries/networking/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp index d54e165285..d556e8a059 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -17,13 +17,14 @@ void ReceivedPacketProcessor::terminating() { _hasPackets.wakeAll(); } -void ReceivedPacketProcessor::queueReceivedPacket(const SharedNodePointer& destinationNode, const QByteArray& packet) { +void ReceivedPacketProcessor::queueReceivedPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { // Make sure our Node and NodeList knows we've heard from this node. - destinationNode->setLastHeardMicrostamp(usecTimestampNow()); + sendingNode->setLastHeardMicrostamp(usecTimestampNow()); - NetworkPacket networkPacket(destinationNode, packet); + NetworkPacket networkPacket(sendingNode, packet); lock(); _packets.push_back(networkPacket); + _nodePacketCounts[sendingNode->getUUID()]++; unlock(); // Make sure to wake our actual processing thread because we now have packets for it to process. @@ -42,8 +43,13 @@ bool ReceivedPacketProcessor::process() { NetworkPacket& packet = _packets.front(); // get the oldest packet NetworkPacket temporary = packet; // make a copy of the packet in case the vector is resized on us _packets.erase(_packets.begin()); // remove the oldest packet + _nodePacketCounts[temporary.getNode()->getUUID()]--; unlock(); // let others add to the packets - processPacket(temporary.getDestinationNode(), temporary.getByteArray()); // process our temporary copy + processPacket(temporary.getNode(), temporary.getByteArray()); // process our temporary copy } return isStillRunning(); // keep running till they terminate us } + +void ReceivedPacketProcessor::killNode(const SharedNodePointer& node) { + _nodePacketCounts.remove(node->getUUID()); +} diff --git a/libraries/networking/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h index f8306b4896..80fe75aaa7 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -28,20 +28,26 @@ public: /// \param packetData pointer to received data /// \param ssize_t packetLength size of received data /// \thread network receive thread - void queueReceivedPacket(const SharedNodePointer& destinationNode, const QByteArray& packet); + void queueReceivedPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); /// Are there received packets waiting to be processed bool hasPacketsToProcess() const { return _packets.size() > 0; } + /// Are there received packets waiting to be processed from a certain node + bool hasPacketsToProcessFrom(const SharedNodePointer& sendingNode) const { + return _nodePacketCounts[sendingNode->getUUID()] > 0; + } + /// How many received packets waiting are to be processed int packetsToProcessCount() const { return _packets.size(); } +public slots: + void killNode(const SharedNodePointer& node); + protected: /// Callback for processing of recieved packets. Implement this to process the incoming packets. - /// \param sockaddr& senderAddress the address of the sender - /// \param packetData pointer to received data - /// \param ssize_t packetLength size of received data - /// \thread "this" individual processing thread + /// \param SharedNodePointer& sendingNode the node that sent this packet + /// \param QByteArray& the packet to be processed virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) = 0; /// Implements generic processing behavior for this thread. @@ -51,7 +57,9 @@ protected: private: - std::vector _packets; + QVector _packets; + QHash _nodePacketCounts; + QWaitCondition _hasPackets; QMutex _waitingOnPacketsMutex; };