// // main.cpp // Voxel Server // // Created by Stephen Birara on 03/06/13. // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. // #include #include #include #include #include #include #include #include #include #include "VoxelNodeData.h" #include #include #include #include #ifdef _WIN32 #include "Syssocket.h" #include "Systime.h" #else #include #include #include #endif const char* LOCAL_VOXELS_PERSIST_FILE = "resources/voxels.svo"; const char* VOXELS_PERSIST_FILE = "/etc/highfidelity/voxel-server/resources/voxels.svo"; const long long VOXEL_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds const int VOXEL_LISTEN_PORT = 40106; const int VOXEL_SIZE_BYTES = 3 + (3 * sizeof(float)); const int VOXELS_PER_PACKET = (MAX_PACKET_SIZE - 1) / VOXEL_SIZE_BYTES; const int MIN_BRIGHTNESS = 64; const float DEATH_STAR_RADIUS = 4.0; const float MAX_CUBE = 0.05f; const int VOXEL_SEND_INTERVAL_USECS = 100 * 1000; int PACKETS_PER_CLIENT_PER_INTERVAL = 30; const int SENDING_TIME_TO_SPARE = 20 * 1000; // usec of sending interval to spare for calculating voxels const int MAX_VOXEL_TREE_DEPTH_LEVELS = 4; const int ENVIRONMENT_SEND_INTERVAL_USECS = 1000000; VoxelTree serverTree(true); // this IS a reaveraging tree bool wantVoxelPersist = true; bool wantLocalDomain = false; bool wantColorRandomizer = false; bool debugVoxelSending = false; bool shouldShowAnimationDebug = false; bool wantSearchForColoredNodes = false; EnvironmentData environmentData[3]; void scanTreeWithOcclusion(VoxelTree* tree, ViewFrustum* viewFrustum, CoverageMap* coverageMap, bool wantsOcclusionCulling); void randomlyFillVoxelTree(int levelsToGo, VoxelNode *currentRootNode) { // randomly generate children for this node // the first level of the tree (where levelsToGo = MAX_VOXEL_TREE_DEPTH_LEVELS) has all 8 if (levelsToGo > 0) { bool createdChildren = false; createdChildren = false; for (int i = 0; i < 8; i++) { if (true) { // create a new VoxelNode to put here currentRootNode->addChildAtIndex(i); randomlyFillVoxelTree(levelsToGo - 1, currentRootNode->getChildAtIndex(i)); createdChildren = true; } } if (!createdChildren) { // we didn't create any children for this node, making it a leaf // give it a random color currentRootNode->setRandomColor(MIN_BRIGHTNESS); } else { // set the color value for this node currentRootNode->setColorFromAverageOfChildren(); } } else { // this is a leaf node, just give it a color currentRootNode->setRandomColor(MIN_BRIGHTNESS); } } void eraseVoxelTreeAndCleanupNodeVisitData() { // As our tree to erase all it's voxels ::serverTree.eraseAllVoxels(); // enumerate the nodes clean up their marker nodes for (NodeList::iterator node = NodeList::getInstance()->begin(); node != NodeList::getInstance()->end(); node++) { VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData(); if (nodeData) { // clean up the node visit data nodeData->nodeBag.deleteAll(); } } } // Version of voxel distributor that sends each LOD level at a time void resInVoxelDistributor(NodeList* nodeList, NodeList::iterator& node, VoxelNodeData* nodeData) { ViewFrustum viewFrustum = nodeData->getCurrentViewFrustum(); bool searchReset = false; int searchLoops = 0; int searchLevelWas = nodeData->getMaxSearchLevel(); long long start = usecTimestampNow(); while (!searchReset && nodeData->nodeBag.isEmpty()) { searchLoops++; searchLevelWas = nodeData->getMaxSearchLevel(); int maxLevelReached = serverTree.searchForColoredNodes(nodeData->getMaxSearchLevel(), serverTree.rootNode, viewFrustum, nodeData->nodeBag); nodeData->setMaxLevelReached(maxLevelReached); // If nothing got added, then we bump our levels. if (nodeData->nodeBag.isEmpty()) { if (nodeData->getMaxLevelReached() < nodeData->getMaxSearchLevel()) { nodeData->resetMaxSearchLevel(); searchReset = true; } else { nodeData->incrementMaxSearchLevel(); } } } long long end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { int elapsedsec = (end - start)/1000000; printf("WARNING! searchForColoredNodes() took %d seconds to identify %d nodes at level %d in %d loops\n", elapsedsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops); } else { printf("WARNING! searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d in %d loops\n", elapsedmsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops); } } else if (::debugVoxelSending) { printf("searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d in %d loops\n", elapsedmsec, nodeData->nodeBag.count(), searchLevelWas, searchLoops); } // If we have something in our nodeBag, then turn them into packets and send them out... if (!nodeData->nodeBag.isEmpty()) { static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static int bytesWritten = 0; int packetsSentThisInterval = 0; int truePacketsSent = 0; int trueBytesSent = 0; long long start = usecTimestampNow(); bool shouldSendEnvironments = shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS); while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) { if (!nodeData->nodeBag.isEmpty()) { VoxelNode* subTree = nodeData->nodeBag.extract(); EncodeBitstreamParams params(nodeData->getMaxSearchLevel(), &viewFrustum, nodeData->getWantColor(), WANT_EXISTS_BITS); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeData->nodeBag, params); if (nodeData->getAvailable() >= bytesWritten) { nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } else { nodeList->getNodeSocket()->send(node->getActiveSocket(), nodeData->getPacket(), nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; packetsSentThisInterval++; nodeData->resetVoxelPacket(); nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } } else { if (nodeData->isPacketWaiting()) { nodeList->getNodeSocket()->send(node->getActiveSocket(), nodeData->getPacket(), nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; nodeData->resetVoxelPacket(); } packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left } } // send the environment packets if (shouldSendEnvironments) { int envPacketLength = 1; *tempOutputBuffer = PACKET_HEADER_ENVIRONMENT_DATA; for (int i = 0; i < sizeof(environmentData) / sizeof(environmentData[0]); i++) { envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength); } nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength); trueBytesSent += envPacketLength; truePacketsSent++; } long long end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { int elapsedsec = (end - start)/1000000; printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", elapsedsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count()); } else { printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count()); } } else if (::debugVoxelSending) { printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets at level %d, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, searchLevelWas, nodeData->nodeBag.count()); } // if during this last pass, we emptied our bag, then we want to move to the next level. if (nodeData->nodeBag.isEmpty()) { if (nodeData->getMaxLevelReached() < nodeData->getMaxSearchLevel()) { nodeData->resetMaxSearchLevel(); } else { nodeData->incrementMaxSearchLevel(); } } } } pthread_mutex_t treeLock; // Version of voxel distributor that sends the deepest LOD level at once void deepestLevelVoxelDistributor(NodeList* nodeList, NodeList::iterator& node, VoxelNodeData* nodeData, bool viewFrustumChanged) { pthread_mutex_lock(&::treeLock); int maxLevelReached = 0; long long start = usecTimestampNow(); // FOR NOW... node tells us if it wants to receive only view frustum deltas bool wantDelta = nodeData->getWantDelta(); const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; if (::debugVoxelSending) { printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n", debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()), debug::valueOf(nodeData->getViewSent()) ); } // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) { if (::debugVoxelSending) { printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n", debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty())); long long now = usecTimestampNow(); if (nodeData->getLastTimeBagEmpty() > 0) { float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f; if (viewFrustumChanged) { printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend); } else { printf("elapsed time to send scene = %f seconds", elapsedSceneSend); } printf(" [occlusionCulling: %s]\n", debug::valueOf(nodeData->getWantOcclusionCulling())); } nodeData->setLastTimeBagEmpty(now); } // if our view has changed, we need to reset these things... if (viewFrustumChanged) { nodeData->nodeBag.deleteAll(); nodeData->map.erase(); } // For now, we're going to disable the "search for colored nodes" because that strategy doesn't work when we support // deletion of nodes. Instead if we just start at the root we get the correct behavior we want. We are keeping this // code for now because we want to be able to go back to it and find a solution to support both. The search method // helps improve overall bitrate performance. if (::wantSearchForColoredNodes) { // If the bag was empty, then send everything in view, not just the delta maxLevelReached = serverTree.searchForColoredNodes(INT_MAX, serverTree.rootNode, nodeData->getCurrentViewFrustum(), nodeData->nodeBag, wantDelta, lastViewFrustum); // if nothing was found in view, send the root node. if (nodeData->nodeBag.isEmpty()){ nodeData->nodeBag.insert(serverTree.rootNode); } nodeData->setViewSent(false); } else { nodeData->nodeBag.insert(serverTree.rootNode); } } long long end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { int elapsedsec = (end - start)/1000000; printf("WARNING! searchForColoredNodes() took %d seconds to identify %d nodes at level %d\n", elapsedsec, nodeData->nodeBag.count(), maxLevelReached); } else { printf("WARNING! searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d\n", elapsedmsec, nodeData->nodeBag.count(), maxLevelReached); } } else if (::debugVoxelSending) { printf("searchForColoredNodes() took %d milliseconds to identify %d nodes at level %d\n", elapsedmsec, nodeData->nodeBag.count(), maxLevelReached); } // If we have something in our nodeBag, then turn them into packets and send them out... if (!nodeData->nodeBag.isEmpty()) { static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static int bytesWritten = 0; int packetsSentThisInterval = 0; int truePacketsSent = 0; int trueBytesSent = 0; long long start = usecTimestampNow(); bool shouldSendEnvironments = shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS); while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) { // Check to see if we're taking too long, and if so bail early... long long now = usecTimestampNow(); long elapsedUsec = (now - start); long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent); long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec); if (elapsedUsecPerPacket + SENDING_TIME_TO_SPARE > usecRemaining) { if (::debugVoxelSending) { printf("packetLoop() usecRemaining=%ld bailing early took %ld usecs to generate %d bytes in %d packets (%ld usec avg), %d nodes still to send\n", usecRemaining, elapsedUsec, trueBytesSent, truePacketsSent, elapsedUsecPerPacket, nodeData->nodeBag.count()); } break; } if (!nodeData->nodeBag.isEmpty()) { VoxelNode* subTree = nodeData->nodeBag.extract(); bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), nodeData->getWantColor(), WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, wantOcclusionCulling, coverageMap); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeData->nodeBag, params); if (nodeData->getAvailable() >= bytesWritten) { nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } else { nodeList->getNodeSocket()->send(node->getActiveSocket(),nodeData->getPacket(), nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; packetsSentThisInterval++; nodeData->resetVoxelPacket(); nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } } else { if (nodeData->isPacketWaiting()) { nodeList->getNodeSocket()->send(node->getActiveSocket(),nodeData->getPacket(), nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; nodeData->resetVoxelPacket(); } packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left } } // send the environment packet if (shouldSendEnvironments) { int envPacketLength = 1; *tempOutputBuffer = PACKET_HEADER_ENVIRONMENT_DATA; for (int i = 0; i < sizeof(environmentData) / sizeof(environmentData[0]); i++) { envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength); } nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength); trueBytesSent += envPacketLength; truePacketsSent++; } long long end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { int elapsedsec = (end - start)/1000000; printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets %d nodes still to send\n", elapsedsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } else { printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } } else if (::debugVoxelSending) { printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } // if after sending packets we've emptied our bag, then we want to remember that we've sent all // the voxels from the current view frustum if (nodeData->nodeBag.isEmpty()) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes } } // end if bag wasn't empty, and so we sent stuff... pthread_mutex_unlock(&::treeLock); } long long lastPersistVoxels = 0; void persistVoxelsWhenDirty() { long long now = usecTimestampNow(); long long sinceLastTime = (now - ::lastPersistVoxels) / 1000; // check the dirty bit and persist here... if (::wantVoxelPersist && ::serverTree.isDirty() && sinceLastTime > VOXEL_PERSIST_INTERVAL) { { PerformanceWarning warn(::shouldShowAnimationDebug, "persistVoxelsWhenDirty() - writeToSVOFile()", ::shouldShowAnimationDebug); printf("saving voxels to file...\n"); serverTree.writeToSVOFile(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); serverTree.clearDirtyBit(); // tree is clean after saving printf("DONE saving voxels to file...\n"); } ::lastPersistVoxels = usecTimestampNow(); } } void *distributeVoxelsToListeners(void *args) { NodeList* nodeList = NodeList::getInstance(); timeval lastSendTime; while (true) { gettimeofday(&lastSendTime, NULL); // enumerate the nodes to send 3 packets to each for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData(); // Sometimes the node data has not yet been linked, in which case we can't really do anything if (nodeData) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); if (::debugVoxelSending) { printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged)); } if (nodeData->getWantResIn()) { //resInVoxelDistributor(nodeList, node, nodeData); // hack, just test scanning the tree here! scanTreeWithOcclusion(&serverTree, &nodeData->getCurrentViewFrustum(), &nodeData->map, nodeData->getWantOcclusionCulling()); printf("AFTER scanTreeWithOcclusion() map->printStats()\n"); CoverageMap::wantDebugging = true; nodeData->map.printStats(); } else { deepestLevelVoxelDistributor(nodeList, node, nodeData, viewFrustumChanged); } } } // dynamically sleep until we need to fire off the next set of voxels long long usecToSleep = VOXEL_SEND_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSendTime)); if (usecToSleep > 0) { usleep(usecToSleep); } else { std::cout << "Last send took too much time, not sleeping!\n"; } } pthread_exit(0); } void attachVoxelNodeDataToNode(Node* newNode) { if (newNode->getLinkedData() == NULL) { newNode->setLinkedData(new VoxelNodeData(newNode)); } } int main(int argc, const char * argv[]) { pthread_mutex_init(&::treeLock, NULL); NodeList* nodeList = NodeList::createInstance(NODE_TYPE_VOXEL_SERVER, VOXEL_LISTEN_PORT); setvbuf(stdout, NULL, _IOLBF, 0); // Handle Local Domain testing with the --local command line const char* local = "--local"; ::wantLocalDomain = cmdOptionExists(argc, argv,local); if (::wantLocalDomain) { printf("Local Domain MODE!\n"); int ip = getLocalAddress(); sprintf(DOMAIN_IP,"%d.%d.%d.%d", (ip & 0xFF), ((ip >> 8) & 0xFF),((ip >> 16) & 0xFF), ((ip >> 24) & 0xFF)); } nodeList->linkedDataCreateCallback = &attachVoxelNodeDataToNode; nodeList->startSilentNodeRemovalThread(); srand((unsigned)time(0)); const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; ::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING); printf("debugVoxelSending=%s\n", debug::valueOf(::debugVoxelSending)); const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug"; ::shouldShowAnimationDebug = cmdOptionExists(argc, argv, WANT_ANIMATION_DEBUG); printf("shouldShowAnimationDebug=%s\n", debug::valueOf(::shouldShowAnimationDebug)); const char* WANT_COLOR_RANDOMIZER = "--wantColorRandomizer"; ::wantColorRandomizer = cmdOptionExists(argc, argv, WANT_COLOR_RANDOMIZER); printf("wantColorRandomizer=%s\n", debug::valueOf(::wantColorRandomizer)); const char* WANT_SEARCH_FOR_NODES = "--wantSearchForColoredNodes"; ::wantSearchForColoredNodes = cmdOptionExists(argc, argv, WANT_SEARCH_FOR_NODES); printf("wantSearchForColoredNodes=%s\n", debug::valueOf(::wantSearchForColoredNodes)); // By default we will voxel persist, if you want to disable this, then pass in this parameter const char* NO_VOXEL_PERSIST = "--NoVoxelPersist"; if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) { ::wantVoxelPersist = false; } printf("wantVoxelPersist=%s\n", debug::valueOf(::wantVoxelPersist)); // if we want Voxel Persistance, load the local file now... bool persistantFileRead = false; if (::wantVoxelPersist) { printf("loading voxels from file...\n"); persistantFileRead = ::serverTree.readFromSVOFile(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); if (persistantFileRead) { PerformanceWarning warn(::shouldShowAnimationDebug, "persistVoxelsWhenDirty() - reaverageVoxelColors()", ::shouldShowAnimationDebug); // after done inserting all these voxels, then reaverage colors serverTree.reaverageVoxelColors(serverTree.rootNode); printf("Voxels reAveraged\n"); } ::serverTree.clearDirtyBit(); // the tree is clean since we just loaded it printf("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); unsigned long nodeCount = ::serverTree.getVoxelCount(); printf("Nodes after loading scene %ld nodes\n", nodeCount); } // Check to see if the user passed in a command line option for loading an old style local // Voxel File. If so, load it now. This is not the same as a voxel persist file const char* INPUT_FILE = "-i"; const char* voxelsFilename = getCmdOption(argc, argv, INPUT_FILE); if (voxelsFilename) { serverTree.readFromSVOFile(voxelsFilename); } // Check to see if the user passed in a command line option for setting packet send rate const char* PACKETS_PER_SECOND = "--packetsPerSecond"; const char* packetsPerSecond = getCmdOption(argc, argv, PACKETS_PER_SECOND); if (packetsPerSecond) { PACKETS_PER_CLIENT_PER_INTERVAL = atoi(packetsPerSecond)/10; if (PACKETS_PER_CLIENT_PER_INTERVAL < 1) { PACKETS_PER_CLIENT_PER_INTERVAL = 1; } printf("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, PACKETS_PER_CLIENT_PER_INTERVAL); } const char* ADD_RANDOM_VOXELS = "--AddRandomVoxels"; if (cmdOptionExists(argc, argv, ADD_RANDOM_VOXELS)) { // create an octal code buffer and load it with 0 so that the recursive tree fill can give // octal codes to the tree nodes that it is creating randomlyFillVoxelTree(MAX_VOXEL_TREE_DEPTH_LEVELS, serverTree.rootNode); } const char* ADD_SCENE = "--AddScene"; bool addScene = cmdOptionExists(argc, argv, ADD_SCENE); const char* NO_ADD_SCENE = "--NoAddScene"; bool noAddScene = cmdOptionExists(argc, argv, NO_ADD_SCENE); if (addScene && noAddScene) { printf("WARNING! --AddScene and --NoAddScene are mutually exclusive. We will honor --NoAddScene\n"); } // We will add a scene if... // 1) we attempted to load a persistant file and it wasn't there // 2) you asked us to add a scene // HOWEVER -- we will NEVER add a scene if you explicitly tell us not to! // // TEMPORARILY DISABLED!!! bool actuallyAddScene = false; // !noAddScene && (addScene || (::wantVoxelPersist && !persistantFileRead)); if (actuallyAddScene) { addSphereScene(&serverTree); } // for now, initialize the environments with fixed values environmentData[1].setID(1); environmentData[1].setGravity(1.0f); environmentData[1].setAtmosphereCenter(glm::vec3(0.5, 0.5, (0.25 - 0.06125)) * (float)TREE_SCALE); environmentData[1].setAtmosphereInnerRadius(0.030625f * TREE_SCALE); environmentData[1].setAtmosphereOuterRadius(0.030625f * TREE_SCALE * 1.05f); environmentData[2].setID(2); environmentData[2].setGravity(1.0f); environmentData[2].setAtmosphereCenter(glm::vec3(0.5f, 0.5f, 0.5f) * (float)TREE_SCALE); environmentData[2].setAtmosphereInnerRadius(0.1875f * TREE_SCALE); environmentData[2].setAtmosphereOuterRadius(0.1875f * TREE_SCALE * 1.05f); environmentData[2].setScatteringWavelengths(glm::vec3(0.475f, 0.570f, 0.650f)); // swaps red and blue pthread_t sendVoxelThread; pthread_create(&sendVoxelThread, NULL, distributeVoxelsToListeners, NULL); sockaddr nodePublicAddress; unsigned char *packetData = new unsigned char[MAX_PACKET_SIZE]; ssize_t receivedBytes; timeval lastDomainServerCheckIn = {}; // loop to send to nodes requesting data while (true) { // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); NodeList::getInstance()->sendDomainServerCheckIn(); } // check to see if we need to persist our voxel state persistVoxelsWhenDirty(); if (nodeList->getNodeSocket()->receive(&nodePublicAddress, packetData, &receivedBytes)) { if (packetData[0] == PACKET_HEADER_SET_VOXEL || packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) { bool destructive = (packetData[0] == PACKET_HEADER_SET_VOXEL_DESTRUCTIVE); PerformanceWarning warn(::shouldShowAnimationDebug, destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL", ::shouldShowAnimationDebug); unsigned short int itemNumber = (*((unsigned short int*)&packetData[1])); if (::shouldShowAnimationDebug) { printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n", destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL", receivedBytes,itemNumber); } int atByte = sizeof(PACKET_HEADER) + sizeof(itemNumber); unsigned char* voxelData = (unsigned char*)&packetData[atByte]; while (atByte < receivedBytes) { unsigned char octets = (unsigned char)*voxelData; const int COLOR_SIZE_IN_BYTES = 3; int voxelDataSize = bytesRequiredForCodeLength(octets) + COLOR_SIZE_IN_BYTES; int voxelCodeSize = bytesRequiredForCodeLength(octets); // color randomization on insert int colorRandomizer = ::wantColorRandomizer ? randIntInRange (-50, 50) : 0; int red = voxelData[voxelCodeSize + 0]; int green = voxelData[voxelCodeSize + 1]; int blue = voxelData[voxelCodeSize + 2]; if (::shouldShowAnimationDebug) { printf("insert voxels - wantColorRandomizer=%s old r=%d,g=%d,b=%d \n", (::wantColorRandomizer?"yes":"no"),red,green,blue); } red = std::max(0, std::min(255, red + colorRandomizer)); green = std::max(0, std::min(255, green + colorRandomizer)); blue = std::max(0, std::min(255, blue + colorRandomizer)); if (::shouldShowAnimationDebug) { printf("insert voxels - wantColorRandomizer=%s NEW r=%d,g=%d,b=%d \n", (::wantColorRandomizer?"yes":"no"),red,green,blue); } voxelData[voxelCodeSize + 0] = red; voxelData[voxelCodeSize + 1] = green; voxelData[voxelCodeSize + 2] = blue; if (::shouldShowAnimationDebug) { float* vertices = firstVertexForCode(voxelData); printf("inserting voxel at: %f,%f,%f\n", vertices[0], vertices[1], vertices[2]); delete []vertices; } serverTree.readCodeColorBufferToTree(voxelData, destructive); // skip to next voxelData += voxelDataSize; atByte += voxelDataSize; } } if (packetData[0] == PACKET_HEADER_ERASE_VOXEL) { // Send these bits off to the VoxelTree class to process them pthread_mutex_lock(&::treeLock); serverTree.processRemoveVoxelBitstream((unsigned char*)packetData, receivedBytes); pthread_mutex_unlock(&::treeLock); } if (packetData[0] == PACKET_HEADER_Z_COMMAND) { // the Z command is a special command that allows the sender to send the voxel server high level semantic // requests, like erase all, or add sphere scene char* command = (char*) &packetData[1]; // start of the command int commandLength = strlen(command); // commands are null terminated strings int totalLength = sizeof(PACKET_HEADER_Z_COMMAND) + commandLength + 1; // 1 for null termination printf("got Z message len(%ld)= %s\n", receivedBytes, command); bool rebroadcast = true; // by default rebroadcast while (totalLength <= receivedBytes) { if (strcmp(command, ERASE_ALL_COMMAND) == 0) { printf("got Z message == erase all\n"); eraseVoxelTreeAndCleanupNodeVisitData(); rebroadcast = false; } if (strcmp(command, ADD_SCENE_COMMAND) == 0) { printf("got Z message == add scene\n"); addSphereScene(&serverTree); rebroadcast = false; } if (strcmp(command, TEST_COMMAND) == 0) { printf("got Z message == a message, nothing to do, just report\n"); } totalLength += commandLength + 1; // 1 for null termination } if (rebroadcast) { // Now send this to the connected nodes so they can also process these messages printf("rebroadcasting Z message to connected nodes... nodeList.broadcastToNodes()\n"); nodeList->broadcastToNodes(packetData, receivedBytes, &NODE_TYPE_AGENT, 1); } } // If we got a PACKET_HEADER_HEAD_DATA, then we're talking to an NODE_TYPE_AVATAR, and we // need to make sure we have it in our nodeList. if (packetData[0] == PACKET_HEADER_HEAD_DATA) { uint16_t nodeID = 0; unpackNodeId(packetData + sizeof(PACKET_HEADER_HEAD_DATA), &nodeID); Node* node = nodeList->addOrUpdateNode(&nodePublicAddress, &nodePublicAddress, NODE_TYPE_AGENT, nodeID); nodeList->updateNodeWithData(node, packetData, receivedBytes); } // If the packet is a ping, let processNodeData handle it. if (packetData[0] == PACKET_HEADER_PING) { nodeList->processNodeData(&nodePublicAddress, packetData, receivedBytes); } } } pthread_join(sendVoxelThread, NULL); pthread_mutex_destroy(&::treeLock); return 0; } const long MAX_SCAN = 100 * 1000; struct ScanTreeArgs { ViewFrustum* viewFrustum; glm::vec3 cameraPosition; CoverageMap* map; // CoverageMapV2* mapV2; VoxelTree* tree; long totalVoxels; long coloredVoxels; long occludedVoxels; long notOccludedVoxels; long outOfView; // not in the view frustum long leavesOutOfView; // not in the view frustum long tooFar; // out of LOD long leavesTooFar; // out of LOD long notAllInView; // in the view frustum, but the projection can't be calculated, assume it needs to be sent long subtreeVoxelsSkipped; long nonLeaves; long nonLeavesNotAllInView; long nonLeavesOutOfView; // not in the view frustum long nonLeavesOccluded; long nonLeavesTooFar; long stagedForDeletion; long doesntFit; bool wantsOcclusionCulling; ViewFrustum::location subTreeLocation; long subTreeInside; int level; }; struct CountSubTreeOperationArgs { long voxelsTouched; }; bool scanTreeWithOcclusionOperation(VoxelNode* node, int level, void* extraData) { ScanTreeArgs* args = (ScanTreeArgs*) extraData; // fastest, scan entire tree... //args->totalVoxels++; //return true; args->totalVoxels++; // SOME FACTS WE KNOW... // If a node is INSIDE the view frustum, then ALL it's child nodes are also INSIDE, and so we // shouldn't have to check that any more... use InFrustum() and store something in args to know // that that part of the stack is in frustum. (do we also need to store a pointer to the parent node // that is INSIDE so that when we unwind back to that point we start checking again?... probably) if (args->subTreeLocation == ViewFrustum::INSIDE) { args->subTreeInside++; // continue without checking further } else { // if this node is not in the view frustum, then just return, since we know none of it's children will be // in the view. ViewFrustum::location location = node->inFrustum(*args->viewFrustum); if (location == ViewFrustum::OUTSIDE) { args->outOfView++; if (node->isLeaf()) { args->leavesOutOfView++; } else { args->nonLeavesOutOfView++; } return false; } // remember this for next recursion pass // NOTE: This doesn't actually work because we need a stack of these, and our code doesn't execute // to pop the stack. Similar problem with level. So we need a solution to allow operation functions to // store information on a stack. //args->subTreeLocation = location; } // If this node is too far for LOD... float distanceSquared = node->distanceSquareToPoint(args->cameraPosition); float boundaryDistanceSquared = boundaryDistanceSquaredForRenderLevel(node->getLevel()); //level+1); if (distanceSquared >= boundaryDistanceSquared) { args->tooFar++; if (node->isLeaf()) { args->leavesTooFar++; } else { args->nonLeavesTooFar++; } return false; } // Handle occlusion culling tests. if (args->wantsOcclusionCulling) { if (!node->isLeaf()) { args->nonLeaves++; AABox voxelBox = node->getAABox(); voxelBox.scale(TREE_SCALE); VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { args->nonLeavesNotAllInView++; delete voxelPolygon; // we can't determine occlusion, so assume it's not return true; } CoverageMapStorageResult result = args->map->checkMap(voxelPolygon, false); if (result == OCCLUDED) { args->nonLeavesOccluded++; delete voxelPolygon; return false; } delete voxelPolygon; return true; // keep looking... } // we don't want to check shouldRender in the server... that's not used if (node->isLeaf() && node->isColored() /*&& node->getShouldRender()*/) { args->coloredVoxels++; AABox voxelBox = node->getAABox(); voxelBox.scale(TREE_SCALE); VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(args->viewFrustum->getProjectedPolygon(voxelBox)); // If we're not all in view, then ignore it, and just return. But keep searching... if (!voxelPolygon->getAllInView()) { args->notAllInView++; delete voxelPolygon; // But, this is one we do want to send... return true; } CoverageMapStorageResult result = args->map->checkMap(voxelPolygon, true); if (result == OCCLUDED) { args->occludedVoxels++; // This is one we don't want to send... } else if (result == STORED) { args->notOccludedVoxels++; // This is one we do want to send... } else if (result == DOESNT_FIT) { args->doesntFit++; // Not sure what to do with this one... probably send it? } } } return true; // keep going! } PointerStack stackOfNodes; long long scanStart = 0; ScanTreeArgs args; void scanTreeWithOcclusion(VoxelTree* tree, ViewFrustum* viewFrustum, CoverageMap* coverageMap, bool wantsOcclusionCulling) { PerformanceWarning warn(true, "scanTreeWithOcclusion()",true); //CoverageMap::wantDebugging = false; //coverageMap->erase(); // if we're starting from scratch, remember our time if (stackOfNodes.isEmpty()) { printf("scanTreeWithOcclusion() -----> STARTING \n"); ::scanStart = usecTimestampNow(); args.viewFrustum = viewFrustum; args.cameraPosition = viewFrustum->getPosition()/(float)TREE_SCALE; args.map = coverageMap; args.totalVoxels = 0; args.coloredVoxels = 0; args.occludedVoxels = 0; args.notOccludedVoxels = 0; args.notAllInView = 0; args.outOfView = 0; args.leavesOutOfView = 0; args.nonLeavesOutOfView = 0; args.subtreeVoxelsSkipped = 0; args.nonLeaves = 0; args.stagedForDeletion = 0; args.nonLeavesNotAllInView = 0; args.nonLeavesOccluded = 0; args.doesntFit = 0; args.tooFar = 0; args.leavesTooFar = 0; args.nonLeavesTooFar = 0; args.tree = tree; args.subTreeLocation = ViewFrustum::OUTSIDE; // assume the worst args.subTreeInside = 0; args.level = 0; args.wantsOcclusionCulling = wantsOcclusionCulling; VoxelProjectedPolygon::pointInside_calls = 0; VoxelProjectedPolygon::occludes_calls = 0; VoxelProjectedPolygon::intersects_calls = 0; } else { printf("scanTreeWithOcclusion() -----> still working\n"); } glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE); long allowedTime = 80 * 1000; tree->recurseTreeWithOperationDistanceSortedTimed(&stackOfNodes, allowedTime, scanTreeWithOcclusionOperation, position, (void*)&args); if (stackOfNodes.isEmpty()) { printf("scanTreeWithOcclusion() -----> finished??? \n"); long long endScan = usecTimestampNow(); int totalScanmsecs = (endScan - scanStart)/1000; float elapsedSceneSend = totalScanmsecs/1000.0f; printf("scanTreeWithOcclusion() -----> totalScanmsecs=%d \n", totalScanmsecs); printf("scanTreeWithOcclusion() --> elapsed time to send scene = %f seconds, args.totalVoxels=%ld ", elapsedSceneSend,args.totalVoxels); printf(" [occlusionCulling: %s]\n", debug::valueOf(args.wantsOcclusionCulling)); coverageMap->erase(); } else { printf("scanTreeWithOcclusion() -----> still working\n"); } //tree->recurseTreeWithOperationDistanceSorted(scanTreeWithOcclusionOperation, position, (void*)&args); printf("scanTreeWithOcclusion()\n"); printf(" position=(%f,%f)\n", position.x, position.y); printf(" total=%ld\n", args.totalVoxels); printf(" colored=%ld\n", args.coloredVoxels); printf("\n"); printf(" occluded=%ld\n", args.occludedVoxels); printf(" notOccluded=%ld\n", args.notOccludedVoxels); printf(" nonLeavesOccluded=%ld\n", args.nonLeavesOccluded); printf("\n"); printf(" outOfView=%ld\n", args.outOfView); printf(" leavesOutOfView=%ld\n", args.leavesOutOfView); printf(" nonLeavesOutOfView=%ld\n", args.nonLeavesOutOfView); printf("\n"); printf(" tooFar=%ld\n", args.tooFar); printf(" leavesTooFar=%ld\n", args.leavesTooFar); printf(" nonLeavesTooFar=%ld\n", args.nonLeavesTooFar); printf("\n"); printf(" nonLeaves=%ld\n", args.nonLeaves); printf(" notAllInView=%ld\n", args.notAllInView); printf(" nonLeavesNotAllInView=%ld\n", args.nonLeavesNotAllInView); printf(" subtreeVoxelsSkipped=%ld\n", args.subtreeVoxelsSkipped); printf(" stagedForDeletion=%ld\n", args.stagedForDeletion); printf(" pointInside_calls=%ld\n", VoxelProjectedPolygon::pointInside_calls); printf(" occludes_calls=%ld\n", VoxelProjectedPolygon::occludes_calls); printf(" intersects_calls=%ld\n", VoxelProjectedPolygon::intersects_calls); printf("\n"); printf(" subTreeInside=%ld\n", args.subTreeInside); }