diff --git a/animation-server/src/main.cpp b/animation-server/src/main.cpp index 8a3d70d83d..126216ea9b 100644 --- a/animation-server/src/main.cpp +++ b/animation-server/src/main.cpp @@ -10,14 +10,16 @@ #include #include #include -#include +#include + +#include #include #include -#include -#include -#include +#include #include #include +#include +#include #ifdef _WIN32 #include "Syssocket.h" @@ -46,11 +48,11 @@ bool wantLocalDomain = false; unsigned long packetsSent = 0; unsigned long bytesSent = 0; -static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { +static void sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail& detail) { unsigned char* bufferOut; int sizeOut; - if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ + if (createVoxelEditMessage(type, 0, 1, &detail, bufferOut, sizeOut)){ ::packetsSent++; ::bytesSent += sizeOut; @@ -159,7 +161,7 @@ static void renderMovingBug() { } // send the "erase message" first... - PACKET_HEADER message = PACKET_HEADER_ERASE_VOXEL; + PACKET_TYPE message = PACKET_TYPE_ERASE_VOXEL; if (createVoxelEditMessage(message, 0, VOXELS_PER_BUG, (VoxelDetail*)&details, bufferOut, sizeOut)){ ::packetsSent++; @@ -229,7 +231,7 @@ static void renderMovingBug() { } // send the "create message" ... - message = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; + message = PACKET_TYPE_SET_VOXEL_DESTRUCTIVE; if (createVoxelEditMessage(message, 0, VOXELS_PER_BUG, (VoxelDetail*)&details, bufferOut, sizeOut)){ ::packetsSent++; @@ -274,7 +276,7 @@ static void sendVoxelBlinkMessage() { detail.green = 0 * ::intensity; detail.blue = 0 * ::intensity; - PACKET_HEADER message = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; + PACKET_TYPE message = PACKET_TYPE_SET_VOXEL_DESTRUCTIVE; sendVoxelEditMessage(message, detail); } @@ -291,7 +293,7 @@ unsigned char onColor[3] = { 0, 255, 255 }; const float STRING_OF_LIGHTS_SIZE = 0.125f / TREE_SCALE; // approximately 1/8th meter static void sendBlinkingStringOfLights() { - PACKET_HEADER message = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; // we're a bully! + PACKET_TYPE message = PACKET_TYPE_SET_VOXEL_DESTRUCTIVE; // we're a bully! float lightScale = STRING_OF_LIGHTS_SIZE; static VoxelDetail details[LIGHTS_PER_SEGMENT]; unsigned char* bufferOut; @@ -421,7 +423,7 @@ const int PACKETS_PER_DANCE_FLOOR = DANCE_FLOOR_VOXELS_PER_PACKET / (DANCE_FLOOR int danceFloorColors[DANCE_FLOOR_WIDTH][DANCE_FLOOR_LENGTH]; void sendDanceFloor() { - PACKET_HEADER message = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; // we're a bully! + PACKET_TYPE message = PACKET_TYPE_SET_VOXEL_DESTRUCTIVE; // we're a bully! float lightScale = DANCE_FLOOR_LIGHT_SIZE; static VoxelDetail details[DANCE_FLOOR_VOXELS_PER_PACKET]; unsigned char* bufferOut; @@ -548,7 +550,7 @@ bool billboardMessage[BILLBOARD_HEIGHT][BILLBOARD_WIDTH] = { }; static void sendBillboard() { - PACKET_HEADER message = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; // we're a bully! + PACKET_TYPE message = PACKET_TYPE_SET_VOXEL_DESTRUCTIVE; // we're a bully! float lightScale = BILLBOARD_LIGHT_SIZE; static VoxelDetail details[VOXELS_PER_PACKET]; unsigned char* bufferOut; @@ -643,14 +645,14 @@ void* animateVoxels(void* args) { sendDanceFloor(); } - long long end = usecTimestampNow(); - long long elapsedSeconds = (end - ::start) / 1000000; + uint64_t end = usecTimestampNow(); + uint64_t elapsedSeconds = (end - ::start) / 1000000; if (::shouldShowPacketsPerSecond) { printf("packetsSent=%ld, bytesSent=%ld pps=%f bps=%f\n",packetsSent,bytesSent, (float)(packetsSent/elapsedSeconds),(float)(bytesSent/elapsedSeconds)); } // dynamically sleep until we need to fire off the next set of voxels - long long usecToSleep = ANIMATE_VOXELS_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSendTime)); + uint64_t usecToSleep = ANIMATE_VOXELS_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSendTime)); if (usecToSleep > 0) { usleep(usecToSleep); @@ -724,7 +726,8 @@ int main(int argc, const char * argv[]) } // Nodes sending messages to us... - if (nodeList->getNodeSocket()->receive(&nodePublicAddress, packetData, &receivedBytes)) { + if (nodeList->getNodeSocket()->receive(&nodePublicAddress, packetData, &receivedBytes) && + packetVersionMatch(packetData)) { NodeList::getInstance()->processNodeData(&nodePublicAddress, packetData, receivedBytes); } } diff --git a/audio-mixer/src/AvatarAudioRingBuffer.cpp b/audio-mixer/src/AvatarAudioRingBuffer.cpp index c3e0e2cbe9..42092cff3a 100644 --- a/audio-mixer/src/AvatarAudioRingBuffer.cpp +++ b/audio-mixer/src/AvatarAudioRingBuffer.cpp @@ -24,6 +24,6 @@ AvatarAudioRingBuffer::~AvatarAudioRingBuffer() { } int AvatarAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) { - _shouldLoopbackForNode = (sourceBuffer[0] == PACKET_HEADER_MICROPHONE_AUDIO_WITH_ECHO); + _shouldLoopbackForNode = (sourceBuffer[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO); return PositionalAudioRingBuffer::parseData(sourceBuffer, numBytes); } \ No newline at end of file diff --git a/audio-mixer/src/InjectedAudioRingBuffer.cpp b/audio-mixer/src/InjectedAudioRingBuffer.cpp index 426bbf361d..7e93c7716c 100644 --- a/audio-mixer/src/InjectedAudioRingBuffer.cpp +++ b/audio-mixer/src/InjectedAudioRingBuffer.cpp @@ -21,7 +21,7 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer() : } int InjectedAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) { - unsigned char* currentBuffer = sourceBuffer + sizeof(PACKET_HEADER_INJECT_AUDIO); + unsigned char* currentBuffer = sourceBuffer + numBytesForPacketHeader(sourceBuffer); // pull stream identifier from the packet memcpy(&_streamIdentifier, currentBuffer, sizeof(_streamIdentifier)); diff --git a/audio-mixer/src/PositionalAudioRingBuffer.cpp b/audio-mixer/src/PositionalAudioRingBuffer.cpp index 87c51e2cf8..60ec9a425e 100644 --- a/audio-mixer/src/PositionalAudioRingBuffer.cpp +++ b/audio-mixer/src/PositionalAudioRingBuffer.cpp @@ -22,7 +22,7 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer() : } int PositionalAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) { - unsigned char* currentBuffer = sourceBuffer + sizeof(PACKET_HEADER); + unsigned char* currentBuffer = sourceBuffer + numBytesForPacketHeader(sourceBuffer); currentBuffer += parsePositionalData(currentBuffer, numBytes - (currentBuffer - sourceBuffer)); currentBuffer += parseAudioSamples(currentBuffer, numBytes - (currentBuffer - sourceBuffer)); diff --git a/audio-mixer/src/main.cpp b/audio-mixer/src/main.cpp index 26f4bb23f8..b900c48479 100644 --- a/audio-mixer/src/main.cpp +++ b/audio-mixer/src/main.cpp @@ -6,60 +6,54 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include +#include +#include #include +#include #include -#include +#include #include #include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "InjectedAudioRingBuffer.h" -#include "AvatarAudioRingBuffer.h" -#include -#include "PacketHeaders.h" +#include #ifdef _WIN32 #include "Syssocket.h" #include "Systime.h" #include #else +#include +#include #include #include -#include -#include #endif //_WIN32 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "AvatarAudioRingBuffer.h" +#include "InjectedAudioRingBuffer.h" + const unsigned short MIXER_LISTEN_PORT = 55443; const short JITTER_BUFFER_MSECS = 12; const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0); -const long long BUFFER_SEND_INTERVAL_USECS = floorf((BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000); +const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000); -const long MAX_SAMPLE_VALUE = std::numeric_limits::max(); -const long MIN_SAMPLE_VALUE = std::numeric_limits::min(); - -void plateauAdditionOfSamples(int16_t &mixSample, int16_t sampleToAdd) { - long sumSample = sampleToAdd + mixSample; - - long normalizedSample = std::min(MAX_SAMPLE_VALUE, sumSample); - normalizedSample = std::max(MIN_SAMPLE_VALUE, sumSample); - - mixSample = normalizedSample; -} +const int MAX_SAMPLE_VALUE = std::numeric_limits::max(); +const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); void attachNewBufferToNode(Node *newNode) { if (!newNode->getLinkedData()) { @@ -103,8 +97,9 @@ int main(int argc, const char* argv[]) { int nextFrame = 0; timeval startTime; - unsigned char clientPacket[BUFFER_LENGTH_BYTES_STEREO + sizeof(PACKET_HEADER_MIXED_AUDIO)]; - clientPacket[0] = PACKET_HEADER_MIXED_AUDIO; + int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MIXED_AUDIO); + unsigned char clientPacket[BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader]; + populateTypeAndVersion(clientPacket, PACKET_TYPE_MIXED_AUDIO); int16_t clientSamples[BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 2] = {}; @@ -304,16 +299,23 @@ int main(int argc, const char* argv[]) { // pull the earlier sample for the delayed channel int earlierSample = delaySamplePointer[s] * attenuationCoefficient * weakChannelAmplitudeRatio; - plateauAdditionOfSamples(delayedChannel[s], earlierSample); + delayedChannel[s] = glm::clamp(delayedChannel[s] + earlierSample, + MIN_SAMPLE_VALUE, + MAX_SAMPLE_VALUE); } int16_t currentSample = stkFrameBuffer[s] * attenuationCoefficient; - plateauAdditionOfSamples(goodChannel[s], currentSample); + goodChannel[s] = glm::clamp(goodChannel[s] + currentSample, + MIN_SAMPLE_VALUE, + MAX_SAMPLE_VALUE); if (s + numSamplesDelay < BUFFER_LENGTH_SAMPLES_PER_CHANNEL) { - plateauAdditionOfSamples(delayedChannel[s + numSamplesDelay], - currentSample * weakChannelAmplitudeRatio); + int sumSample = delayedChannel[s + numSamplesDelay] + + (currentSample * weakChannelAmplitudeRatio); + delayedChannel[s + numSamplesDelay] = glm::clamp(sumSample, + MIN_SAMPLE_VALUE, + MAX_SAMPLE_VALUE); } if (s >= BUFFER_LENGTH_SAMPLES_PER_CHANNEL - PHASE_DELAY_AT_90) { @@ -325,7 +327,7 @@ int main(int argc, const char* argv[]) { } } - memcpy(clientPacket + sizeof(PACKET_HEADER_MIXED_AUDIO), clientSamples, sizeof(clientSamples)); + memcpy(clientPacket + numBytesPacketHeader, clientSamples, sizeof(clientSamples)); nodeList->getNodeSocket()->send(node->getPublicSocket(), clientPacket, sizeof(clientPacket)); } } @@ -345,13 +347,14 @@ int main(int argc, const char* argv[]) { } // pull any new audio data from nodes off of the network stack - while (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes)) { - if (packetData[0] == PACKET_HEADER_MICROPHONE_AUDIO_NO_ECHO || - packetData[0] == PACKET_HEADER_MICROPHONE_AUDIO_WITH_ECHO) { + while (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes) && + packetVersionMatch(packetData)) { + if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO || + packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO) { Node* avatarNode = nodeList->addOrUpdateNode(nodeAddress, - nodeAddress, - NODE_TYPE_AGENT, - nodeList->getLastNodeID()); + nodeAddress, + NODE_TYPE_AGENT, + nodeList->getLastNodeID()); if (avatarNode->getNodeID() == nodeList->getLastNodeID()) { nodeList->increaseNodeID(); @@ -363,7 +366,7 @@ int main(int argc, const char* argv[]) { // kill off this node - temporary solution to mixer crash on mac sleep avatarNode->setAlive(false); } - } else if (packetData[0] == PACKET_HEADER_INJECT_AUDIO) { + } else if (packetData[0] == PACKET_TYPE_INJECT_AUDIO) { Node* matchingInjector = NULL; for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { @@ -382,16 +385,16 @@ int main(int argc, const char* argv[]) { if (!matchingInjector) { matchingInjector = nodeList->addOrUpdateNode(NULL, - NULL, - NODE_TYPE_AUDIO_INJECTOR, - nodeList->getLastNodeID()); + NULL, + NODE_TYPE_AUDIO_INJECTOR, + nodeList->getLastNodeID()); nodeList->increaseNodeID(); } // give the new audio data to the matching injector node nodeList->updateNodeWithData(matchingInjector, packetData, receivedBytes); - } else if (packetData[0] == PACKET_HEADER_PING) { + } else if (packetData[0] == PACKET_TYPE_PING) { // If the packet is a ping, let processNodeData handle it. nodeList->processNodeData(nodeAddress, packetData, receivedBytes); @@ -412,7 +415,7 @@ int main(int argc, const char* argv[]) { numStatCollections++; } - long long usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/avatar-mixer/src/main.cpp b/avatar-mixer/src/main.cpp index 0b6de10029..f2fd031526 100644 --- a/avatar-mixer/src/main.cpp +++ b/avatar-mixer/src/main.cpp @@ -73,7 +73,7 @@ int main(int argc, const char* argv[]) { ssize_t receivedBytes = 0; unsigned char *broadcastPacket = new unsigned char[MAX_PACKET_SIZE]; - *broadcastPacket = PACKET_HEADER_BULK_AVATAR_DATA; + int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_BULK_AVATAR_DATA); unsigned char* currentBufferPosition = NULL; @@ -92,9 +92,10 @@ int main(int argc, const char* argv[]) { NodeList::getInstance()->sendDomainServerCheckIn(); } - if (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes)) { + if (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes) && + packetVersionMatch(packetData)) { switch (packetData[0]) { - case PACKET_HEADER_HEAD_DATA: + case PACKET_TYPE_HEAD_DATA: // grab the node ID from the packet unpackNodeId(packetData + 1, &nodeID); @@ -103,8 +104,8 @@ int main(int argc, const char* argv[]) { // parse positional data from an node nodeList->updateNodeWithData(avatarNode, packetData, receivedBytes); - case PACKET_HEADER_INJECT_AUDIO: - currentBufferPosition = broadcastPacket + 1; + case PACKET_TYPE_INJECT_AUDIO: + currentBufferPosition = broadcastPacket + numHeaderBytes; // send back a packet with other active node data to this node for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { @@ -116,9 +117,9 @@ int main(int argc, const char* argv[]) { nodeList->getNodeSocket()->send(nodeAddress, broadcastPacket, currentBufferPosition - broadcastPacket); break; - case PACKET_HEADER_AVATAR_VOXEL_URL: + case PACKET_TYPE_AVATAR_VOXEL_URL: // grab the node ID from the packet - unpackNodeId(packetData + 1, &nodeID); + unpackNodeId(packetData + numBytesForPacketHeader(packetData), &nodeID); // let everyone else know about the update for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { @@ -127,7 +128,7 @@ int main(int argc, const char* argv[]) { } } break; - case PACKET_HEADER_DOMAIN: + case PACKET_TYPE_DOMAIN: // ignore the DS packet, for now nodes are added only when they communicate directly with us break; default: diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 6fd5fae887..5d41efdc67 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -70,7 +70,7 @@ int main(int argc, const char * argv[]) char nodeType = '\0'; unsigned char broadcastPacket[MAX_PACKET_SIZE]; - broadcastPacket[0] = PACKET_HEADER_DOMAIN; + int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN); unsigned char* currentBufferPos; unsigned char* startPointer; @@ -86,11 +86,15 @@ int main(int argc, const char * argv[]) while (true) { if (nodeList->getNodeSocket()->receive((sockaddr *)&nodePublicAddress, packetData, &receivedBytes) && - (packetData[0] == PACKET_HEADER_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_HEADER_DOMAIN_LIST_REQUEST)) { + (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) && + packetVersionMatch(packetData)) { + // this is an RFD or domain list request packet, and there is a version match std::map newestSoloNodes; - nodeType = packetData[1]; - int numBytesSocket = unpackSocket(packetData + sizeof(PACKET_HEADER) + sizeof(NODE_TYPE), + int numBytesSenderHeader = numBytesForPacketHeader(packetData); + + nodeType = *(packetData + numBytesSenderHeader); + int numBytesSocket = unpackSocket(packetData + numBytesSenderHeader + sizeof(NODE_TYPE), (sockaddr*) &nodeLocalAddress); sockaddr* destinationSocket = (sockaddr*) &nodePublicAddress; @@ -108,18 +112,18 @@ int main(int argc, const char * argv[]) } Node* newNode = nodeList->addOrUpdateNode((sockaddr*) &nodePublicAddress, - (sockaddr*) &nodeLocalAddress, - nodeType, - nodeList->getLastNodeID()); + (sockaddr*) &nodeLocalAddress, + nodeType, + nodeList->getLastNodeID()); if (newNode->getNodeID() == nodeList->getLastNodeID()) { nodeList->increaseNodeID(); } - currentBufferPos = broadcastPacket + sizeof(PACKET_HEADER); + currentBufferPos = broadcastPacket + numHeaderBytes; startPointer = currentBufferPos; - unsigned char* nodeTypesOfInterest = packetData + sizeof(PACKET_HEADER) + sizeof(NODE_TYPE) + unsigned char* nodeTypesOfInterest = packetData + numBytesSenderHeader + sizeof(NODE_TYPE) + numBytesSocket + sizeof(unsigned char); int numInterestTypes = *(nodeTypesOfInterest - 1); @@ -159,10 +163,10 @@ int main(int argc, const char * argv[]) } // update last receive to now - long long timeNow = usecTimestampNow(); + uint64_t timeNow = usecTimestampNow(); newNode->setLastHeardMicrostamp(timeNow); - if (packetData[0] == PACKET_HEADER_DOMAIN_REPORT_FOR_DUTY + if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY && memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { newNode->setWakeMicrostamp(timeNow); } @@ -172,8 +176,8 @@ int main(int argc, const char * argv[]) // send the constructed list back to this node nodeList->getNodeSocket()->send(destinationSocket, - broadcastPacket, - (currentBufferPos - startPointer) + 1); + broadcastPacket, + (currentBufferPos - startPointer) + numHeaderBytes); } if (Logstash::shouldSendStats()) { diff --git a/eve/src/main.cpp b/eve/src/main.cpp index 8a03dc453b..5bbe5c7eac 100644 --- a/eve/src/main.cpp +++ b/eve/src/main.cpp @@ -49,9 +49,10 @@ void *receiveNodeData(void *args) { NodeList* nodeList = NodeList::getInstance(); while (!::stopReceiveNodeDataThread) { - if (nodeList->getNodeSocket()->receive(&senderAddress, incomingPacket, &bytesReceived)) { + if (nodeList->getNodeSocket()->receive(&senderAddress, incomingPacket, &bytesReceived) && + packetVersionMatch(incomingPacket)) { switch (incomingPacket[0]) { - case PACKET_HEADER_BULK_AVATAR_DATA: + case PACKET_TYPE_BULK_AVATAR_DATA: // this is the positional data for other nodes // pass that off to the nodeList processBulkNodeData method nodeList->processBulkNodeData(&senderAddress, incomingPacket, bytesReceived); @@ -85,9 +86,6 @@ int main(int argc, const char* argv[]) { // start the node list thread that will kill off nodes when they stop talking nodeList->startSilentNodeRemovalThread(); - // start the ping thread that hole punches to create an active connection to other nodes - nodeList->startPingUnknownNodesThread(); - pthread_t receiveNodeDataThread; pthread_create(&receiveNodeDataThread, NULL, receiveNodeData, NULL); @@ -125,10 +123,10 @@ int main(int argc, const char* argv[]) { nodeList->linkedDataCreateCallback = createAvatarDataForNode; unsigned char broadcastPacket[MAX_PACKET_SIZE]; - broadcastPacket[0] = PACKET_HEADER_HEAD_DATA; + int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_HEAD_DATA); timeval thisSend; - long long numMicrosecondsSleep = 0; + int numMicrosecondsSleep = 0; int handStateTimer = 0; @@ -153,7 +151,7 @@ int main(int argc, const char* argv[]) { // make sure we actually have an avatar mixer with an active socket if (nodeList->getOwnerID() != UNKNOWN_NODE_ID && avatarMixer && avatarMixer->getActiveSocket() != NULL) { - unsigned char* packetPosition = broadcastPacket + sizeof(PACKET_HEADER); + unsigned char* packetPosition = broadcastPacket + numHeaderBytes; packetPosition += packNodeId(packetPosition, nodeList->getOwnerID()); // use the getBroadcastData method in the AvatarData class to populate the broadcastPacket buffer @@ -210,6 +208,5 @@ int main(int argc, const char* argv[]) { pthread_join(receiveNodeDataThread, NULL); // stop the node list's threads - nodeList->stopPingUnknownNodesThread(); nodeList->stopSilentNodeRemovalThread(); } diff --git a/injector/src/main.cpp b/injector/src/main.cpp index e8cb345471..e2c1effc15 100644 --- a/injector/src/main.cpp +++ b/injector/src/main.cpp @@ -144,7 +144,8 @@ int main(int argc, char* argv[]) { nodeList->linkedDataCreateCallback = createAvatarDataForNode; timeval lastSend = {}; - unsigned char broadcastPacket = PACKET_HEADER_INJECT_AUDIO; + int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_INJECT_AUDIO); + unsigned char* broadcastPacket = new unsigned char[numBytesPacketHeader]; timeval lastDomainServerCheckIn = {}; @@ -168,10 +169,10 @@ int main(int argc, char* argv[]) { NodeList::getInstance()->sendDomainServerCheckIn(); } - while (nodeList->getNodeSocket()->receive(&senderAddress, incomingPacket, &bytesReceived)) { + while (nodeList->getNodeSocket()->receive(&senderAddress, incomingPacket, &bytesReceived) && + packetVersionMatch(incomingPacket)) { switch (incomingPacket[0]) { - case PACKET_HEADER_BULK_AVATAR_DATA: - // this is the positional data for other nodes + case PACKET_TYPE_BULK_AVATAR_DATA: // this is the positional data for other nodes // pass that off to the nodeList processBulkNodeData method nodeList->processBulkNodeData(&senderAddress, incomingPacket, bytesReceived); break; @@ -211,8 +212,8 @@ int main(int argc, char* argv[]) { // use the UDPSocket instance attached to our node list to ask avatar mixer for a list of avatars nodeList->getNodeSocket()->send(avatarMixer->getActiveSocket(), - &broadcastPacket, - sizeof(broadcastPacket)); + broadcastPacket, + numBytesPacketHeader); } } else { if (!injector.isInjectingAudio() && (::shouldLoopAudio || !::hasInjectedAudioOnce)) { diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 829ccbff06..f147ada803 100755 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -99,6 +99,10 @@ find_package(OpenNI) find_package(UVCCameraControl) find_package(ZLIB) +# link the stk library +set(STK_ROOT_DIR ${ROOT_DIR}/externals/stk) +find_package(STK REQUIRED) + # let the source know that we have OpenNI/NITE for Kinect if (OPENNI_FOUND) add_definitions(-DHAVE_OPENNI) @@ -122,6 +126,7 @@ include_directories( ${LEAP_INCLUDE_DIRS} ${MOTIONDRIVER_INCLUDE_DIRS} ${OPENCV_INCLUDE_DIRS} + ${STK_INCLUDE_DIRS} ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${OPENCV_INCLUDE_DIRS}") @@ -131,6 +136,7 @@ target_link_libraries( ${MOTIONDRIVER_LIBRARIES} ${OPENCV_LIBRARIES} ${ZLIB_LIBRARIES} + ${STK_LIBRARIES} fervor ) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6429a08f02..6f70c89f3b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -185,6 +185,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _touchAvgX(0.0f), _touchAvgY(0.0f), _isTouchPressed(false), + _yawFromTouch(0.0f), + _pitchFromTouch(0.0f), _mousePressed(false), _mouseVoxelScale(1.0f / 1024.0f), _justEditedVoxel(false), @@ -253,7 +255,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // start the nodeList threads NodeList::getInstance()->startSilentNodeRemovalThread(); - NodeList::getInstance()->startPingUnknownNodesThread(); _window->setCentralWidget(_glWidget); @@ -907,14 +908,15 @@ void Application::wheelEvent(QWheelEvent* event) { void Application::sendPingPackets() { - char nodeTypesOfInterest[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER}; - long long currentTime = usecTimestampNow(); - unsigned char pingPacket[1 + sizeof(currentTime)]; - pingPacket[0] = PACKET_HEADER_PING; + const char nodesToPing[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER}; + + uint64_t currentTime = usecTimestampNow(); + unsigned char pingPacket[numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_PING) + sizeof(currentTime)]; + int numHeaderBytes = populateTypeAndVersion(pingPacket, PACKET_TYPE_PING); - memcpy(&pingPacket[1], ¤tTime, sizeof(currentTime)); - getInstance()->controlledBroadcastToNodes(pingPacket, 1 + sizeof(currentTime), - nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); + memcpy(pingPacket + numHeaderBytes, ¤tTime, sizeof(currentTime)); + getInstance()->controlledBroadcastToNodes(pingPacket, sizeof(pingPacket), + nodesToPing, sizeof(nodesToPing)); } // Every second, check the frame rates and other stuff @@ -971,6 +973,9 @@ void Application::idle() { gettimeofday(&check, NULL); // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran + sendPostedEvents(NULL, QEvent::TouchBegin); + sendPostedEvents(NULL, QEvent::TouchUpdate); + sendPostedEvents(NULL, QEvent::TouchEnd); double timeSinceLastUpdate = diffclock(&_lastTimeUpdated, &check); if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { @@ -980,9 +985,6 @@ void Application::idle() { // This is necessary because id the idle() call takes longer than the // interval between idle() calls, the event loop never gets to run, // and touch events get delayed. - sendPostedEvents(NULL, QEvent::TouchBegin); - sendPostedEvents(NULL, QEvent::TouchUpdate); - sendPostedEvents(NULL, QEvent::TouchEnd); const float BIGGEST_DELTA_TIME_SECS = 0.25f; update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS)); @@ -1022,7 +1024,11 @@ void Application::sendAvatarVoxelURLMessage(const QUrl& url) { return; // we don't yet know who we are } QByteArray message; - message.append(PACKET_HEADER_AVATAR_VOXEL_URL); + + char packetHeader[MAX_PACKET_HEADER_BYTES]; + int numBytesPacketHeader = populateTypeAndVersion((unsigned char*) packetHeader, PACKET_TYPE_AVATAR_VOXEL_URL); + + message.append(packetHeader, numBytesPacketHeader); message.append((const char*)&ownerID, sizeof(ownerID)); message.append(url.toEncoded()); @@ -1031,8 +1037,9 @@ void Application::sendAvatarVoxelURLMessage(const QUrl& url) { void Application::processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { // skip the header - packetData++; - dataBytes--; + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + packetData += numBytesPacketHeader; + dataBytes -= numBytesPacketHeader; // read the node id uint16_t nodeID = *(uint16_t*)packetData; @@ -1221,6 +1228,10 @@ void Application::doFalseColorizeOccluded() { _voxels.falseColorizeOccluded(); } +void Application::doFalseColorizeOccludedV2() { + _voxels.falseColorizeOccludedV2(); +} + void Application::doTrueVoxelColors() { _voxels.trueColorize(); } @@ -1229,20 +1240,20 @@ void Application::doTreeStats() { _voxels.collectStatsForTreesAndVBOs(); } +void Application::setWantsLowResMoving(bool wantsLowResMoving) { + _myAvatar.setWantLowResMoving(wantsLowResMoving); +} + void Application::setWantsMonochrome(bool wantsMonochrome) { _myAvatar.setWantColor(!wantsMonochrome); } -void Application::setWantsResIn(bool wantsResIn) { - _myAvatar.setWantResIn(wantsResIn); +void Application::disableDeltaSending(bool disableDeltaSending) { + _myAvatar.setWantDelta(!disableDeltaSending); } -void Application::setWantsDelta(bool wantsDelta) { - _myAvatar.setWantDelta(wantsDelta); -} - -void Application::setWantsOcclusionCulling(bool wantsOcclusionCulling) { - _myAvatar.setWantOcclusionCulling(wantsOcclusionCulling); +void Application::disableOcclusionCulling(bool disableOcclusionCulling) { + _myAvatar.setWantOcclusionCulling(!disableOcclusionCulling); } void Application::updateVoxelModeActions() { @@ -1254,11 +1265,11 @@ void Application::updateVoxelModeActions() { } } -void Application::sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { +void Application::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail& detail) { unsigned char* bufferOut; int sizeOut; - if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ + if (createVoxelEditMessage(type, 0, 1, &detail, bufferOut, sizeOut)){ Application::controlledBroadcastToNodes(bufferOut, sizeOut, & NODE_TYPE_VOXEL_SERVER, 1); delete[] bufferOut; } @@ -1345,7 +1356,8 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { // if we have room don't have room in the buffer, then send the previously generated message first if (args->bufferInUse + codeAndColorLength > MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE) { controlledBroadcastToNodes(args->messageBuffer, args->bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); - args->bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // reset + args->bufferInUse = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_SET_VOXEL_DESTRUCTIVE) + + sizeof(unsigned short int); // reset } // copy this node's code color details into our buffer. @@ -1412,10 +1424,12 @@ void Application::importVoxels() { // the server as an set voxel message, this will also rebase the voxels to the new location unsigned char* calculatedOctCode = NULL; SendVoxelsOperationArgs args; - args.messageBuffer[0] = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; - unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE)]; + + int numBytesPacketHeader = populateTypeAndVersion(args.messageBuffer, PACKET_TYPE_SET_VOXEL_DESTRUCTIVE); + + unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[numBytesPacketHeader]; *sequenceAt = 0; - args.bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // set to command + sequence + args.bufferInUse = numBytesPacketHeader + sizeof(unsigned short int); // set to command + sequence // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the // voxel size/position details. @@ -1428,7 +1442,7 @@ void Application::importVoxels() { importVoxels.recurseTreeWithOperation(sendVoxelsOperation, &args); // If we have voxels left in the packet, then send the packet - if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { + if (args.bufferInUse > (numBytesPacketHeader + sizeof(unsigned short int))) { controlledBroadcastToNodes(args.messageBuffer, args.bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); } @@ -1463,10 +1477,12 @@ void Application::pasteVoxels() { // Recurse the clipboard tree, where everything is root relative, and send all the colored voxels to // the server as an set voxel message, this will also rebase the voxels to the new location SendVoxelsOperationArgs args; - args.messageBuffer[0] = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; - unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE)]; + + int numBytesPacketHeader = populateTypeAndVersion(args.messageBuffer, PACKET_TYPE_SET_VOXEL_DESTRUCTIVE); + + unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[numBytesPacketHeader]; *sequenceAt = 0; - args.bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // set to command + sequence + args.bufferInUse = numBytesPacketHeader + sizeof(unsigned short int); // set to command + sequence // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the // voxel size/position details. If we don't have an actual selectedNode then use the mouseVoxel to create a @@ -1480,7 +1496,7 @@ void Application::pasteVoxels() { _clipboardTree.recurseTreeWithOperation(sendVoxelsOperation, &args); // If we have voxels left in the packet, then send the packet - if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { + if (args.bufferInUse > (numBytesPacketHeader + sizeof(unsigned short int))) { controlledBroadcastToNodes(args.messageBuffer, args.bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); } @@ -1624,13 +1640,22 @@ void Application::initMenu() { renderDebugMenu->addAction("FALSE Color Voxels by Distance", this, SLOT(doFalseColorizeByDistance())); renderDebugMenu->addAction("FALSE Color Voxel Out of View", this, SLOT(doFalseColorizeInView())); renderDebugMenu->addAction("FALSE Color Occluded Voxels", this, SLOT(doFalseColorizeOccluded()), Qt::CTRL | Qt::Key_O); + renderDebugMenu->addAction("FALSE Color Occluded V2 Voxels", this, SLOT(doFalseColorizeOccludedV2()), Qt::CTRL | Qt::Key_P); renderDebugMenu->addAction("Show TRUE Colors", this, SLOT(doTrueVoxelColors()), Qt::CTRL | Qt::Key_T); - debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true); - debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true); - debugMenu->addAction("Wants View Delta Sending", this, SLOT(setWantsDelta(bool)))->setCheckable(true); (_shouldLowPassFilter = debugMenu->addAction("Test: LowPass filter"))->setCheckable(true); - debugMenu->addAction("Wants Occlusion Culling", this, SLOT(setWantsOcclusionCulling(bool)))->setCheckable(true); + + debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true); + debugMenu->addAction("Use Lower Resolution While Moving", this, SLOT(setWantsLowResMoving(bool)))->setCheckable(true); + debugMenu->addAction("Disable Delta Sending", this, SLOT(disableDeltaSending(bool)))->setCheckable(true); + debugMenu->addAction("Disable Occlusion Culling", this, SLOT(disableOcclusionCulling(bool)), + Qt::SHIFT | Qt::Key_C)->setCheckable(true); + + (_renderCoverageMap = debugMenu->addAction("Render Coverage Map"))->setCheckable(true); + _renderCoverageMap->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_O); + (_renderCoverageMapV2 = debugMenu->addAction("Render Coverage Map V2"))->setCheckable(true); + _renderCoverageMapV2->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_P); + QMenu* settingsMenu = menuBar->addMenu("Settings"); (_settingsAutosave = settingsMenu->addAction("Autosave"))->setCheckable(true); @@ -1691,8 +1716,6 @@ void Application::init() { _headMouseX = _mouseX = _glWidget->width() / 2; _headMouseY = _mouseY = _glWidget->height() / 2; - _stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0); - _myAvatar.init(); _myAvatar.setPosition(START_LOCATION); _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); @@ -1718,7 +1741,6 @@ void Application::init() { printLog("Loaded settings.\n"); - sendAvatarVoxelURLMessage(_myAvatar.getVoxels()->getVoxelURL()); _palette.init(_glWidget->width(), _glWidget->height()); @@ -1732,6 +1754,22 @@ void Application::init() { const float MAX_AVATAR_EDIT_VELOCITY = 1.0f; const float MAX_VOXEL_EDIT_DISTANCE = 20.0f; +const float HEAD_SPHERE_RADIUS = 0.07; + +bool Application::isLookingAtOtherAvatar(glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection, glm::vec3& eyePosition) { + NodeList* nodeList = NodeList::getInstance(); + for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { + if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { + Avatar* avatar = (Avatar *) node->getLinkedData(); + glm::vec3 headPosition = avatar->getHead().getPosition(); + if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition, HEAD_SPHERE_RADIUS)) { + eyePosition = avatar->getHead().getEyeLevelPosition(); + return true; + } + } + } + return false; +} void Application::update(float deltaTime) { // Use Transmitter Hand to move hand if connected, else use mouse @@ -1759,8 +1797,15 @@ void Application::update(float deltaTime) { _myAvatar.setMouseRay(mouseRayOrigin, mouseRayDirection); // Set where I am looking based on my mouse ray (so that other people can see) - glm::vec3 myLookAtFromMouse(mouseRayOrigin + mouseRayDirection); - _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); + glm::vec3 eyePosition; + if (isLookingAtOtherAvatar(mouseRayOrigin, mouseRayDirection, eyePosition)) { + // If the mouse is over another avatar's head... + glm::vec3 myLookAtFromMouse(eyePosition); + _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); + } else { + glm::vec3 myLookAtFromMouse(mouseRayOrigin + mouseRayDirection); + _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); + } // If we are dragging on a voxel, add thrust according to the amount the mouse is dragging const float VOXEL_GRAB_THRUST = 0.0f; @@ -1861,12 +1906,9 @@ void Application::update(float deltaTime) { if (_isTouchPressed) { float TOUCH_YAW_SCALE = -50.0f; float TOUCH_PITCH_SCALE = -50.0f; - _myAvatar.getHead().addYaw((_touchAvgX - _lastTouchAvgX) - * TOUCH_YAW_SCALE - * deltaTime); - _myAvatar.getHead().addPitch((_touchAvgY - _lastTouchAvgY) - * TOUCH_PITCH_SCALE - * deltaTime); + _yawFromTouch += ((_touchAvgX - _lastTouchAvgX) * TOUCH_YAW_SCALE * deltaTime); + _pitchFromTouch += ((_touchAvgY - _lastTouchAvgY) * TOUCH_PITCH_SCALE * deltaTime); + _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; } @@ -1974,11 +2016,20 @@ void Application::update(float deltaTime) { void Application::updateAvatar(float deltaTime) { + // When head is rotated via touch/mouse look, slowly turn body to follow + const float BODY_FOLLOW_HEAD_RATE = 0.5f; + // update body yaw by body yaw delta + _myAvatar.setOrientation(_myAvatar.getOrientation() + * glm::quat(glm::vec3(0, _yawFromTouch * deltaTime * BODY_FOLLOW_HEAD_RATE, 0) * deltaTime)); + _yawFromTouch -= _yawFromTouch * deltaTime * BODY_FOLLOW_HEAD_RATE; + // Update my avatar's state from gyros and/or webcam _myAvatar.updateFromGyrosAndOrWebcam(_gyroLook->isChecked(), glm::vec3(_headCameraPitchYawScale, _headCameraPitchYawScale, - _headCameraPitchYawScale)); + _headCameraPitchYawScale), + _yawFromTouch, + _pitchFromTouch); if (_serialHeadSensor.isActive()) { @@ -2000,14 +2051,26 @@ void Application::updateAvatar(float deltaTime) { _headMouseY = max(_headMouseY, 0); _headMouseY = min(_headMouseY, _glWidget->height()); + const float MIDPOINT_OF_SCREEN = 0.5; + + // Set lookAtPosition if an avatar is at the center of the screen + glm::vec3 screenCenterRayOrigin, screenCenterRayDirection; + _viewFrustum.computePickRay(MIDPOINT_OF_SCREEN, MIDPOINT_OF_SCREEN, screenCenterRayOrigin, screenCenterRayDirection); + + glm::vec3 eyePosition; + if (isLookingAtOtherAvatar(screenCenterRayOrigin, screenCenterRayDirection, eyePosition)) { + glm::vec3 myLookAtFromMouse(eyePosition); + _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); + } + } if (OculusManager::isConnected()) { float yaw, pitch, roll; OculusManager::getEulerAngles(yaw, pitch, roll); - _myAvatar.getHead().setYaw(yaw); - _myAvatar.getHead().setPitch(pitch); + _myAvatar.getHead().setYaw(yaw + _yawFromTouch); + _myAvatar.getHead().setPitch(pitch + _pitchFromTouch); _myAvatar.getHead().setRoll(roll); } @@ -2036,7 +2099,8 @@ void Application::updateAvatar(float deltaTime) { unsigned char broadcastString[200]; unsigned char* endOfBroadcastStringWrite = broadcastString; - *(endOfBroadcastStringWrite++) = PACKET_HEADER_HEAD_DATA; + endOfBroadcastStringWrite += populateTypeAndVersion(endOfBroadcastStringWrite, PACKET_TYPE_HEAD_DATA); + endOfBroadcastStringWrite += packNodeId(endOfBroadcastStringWrite, nodeList->getOwnerID()); endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); @@ -2066,8 +2130,8 @@ void Application::updateAvatar(float deltaTime) { _paintingVoxel.y >= 0.0 && _paintingVoxel.y <= 1.0 && _paintingVoxel.z >= 0.0 && _paintingVoxel.z <= 1.0) { - PACKET_HEADER message = (_destructiveAddVoxel->isChecked() ? - PACKET_HEADER_SET_VOXEL_DESTRUCTIVE : PACKET_HEADER_SET_VOXEL); + PACKET_TYPE message = (_destructiveAddVoxel->isChecked() ? + PACKET_TYPE_SET_VOXEL_DESTRUCTIVE : PACKET_TYPE_SET_VOXEL); sendVoxelEditMessage(message, _paintingVoxel); } } @@ -2296,6 +2360,9 @@ void Application::displaySide(Camera& whichCamera) { glMateriali(GL_FRONT, GL_SHININESS, 96); if (_renderStarsOn->isChecked()) { + if (!_stars.getFileLoaded()) { + _stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0); + } // should be the first rendering pass - w/o depth buffer / lighting // compute starfield alpha based on distance from atmosphere @@ -2398,6 +2465,7 @@ void Application::displaySide(Camera& whichCamera) { // brad's frustum for debugging if (_frustumOn->isChecked()) renderViewFrustum(_viewFrustum); + } void Application::displayOverlay() { @@ -2446,6 +2514,9 @@ void Application::displayOverlay() { if (_renderStatsOn->isChecked()) { displayStats(); } + // testing rendering coverage map + if (_renderCoverageMapV2->isChecked()) { renderCoverageMapV2(); } + if (_renderCoverageMap->isChecked()) { renderCoverageMap(); } if (_bandwidthDisplayOn->isChecked()) { _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); } if (_logOn->isChecked()) { LogDisplay::instance.render(_glWidget->width(), _glWidget->height()); } @@ -2458,7 +2529,7 @@ void Application::displayOverlay() { // Show on-screen msec timer if (_renderFrameTimerOn->isChecked()) { char frameTimer[10]; - long long mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); + uint64_t mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); drawtext(_glWidget->width() - 100, _glWidget->height() - 20, 0.30, 0, 1.0, 0, frameTimer, 0, 0, 0); drawtext(_glWidget->width() - 102, _glWidget->height() - 22, 0.30, 0, 1.0, 0, frameTimer, 1, 1, 1); @@ -2631,8 +2702,8 @@ void Application::renderThrustAtVoxel(const glm::vec3& thrust) { glVertex3f(voxelTouched.x + thrust.x, voxelTouched.y + thrust.y, voxelTouched.z + thrust.z); glEnd(); } - } + void Application::renderLineToTouchedVoxel() { // Draw a teal line to the voxel I am currently dragging on if (_mousePressed) { @@ -2647,6 +2718,149 @@ void Application::renderLineToTouchedVoxel() { } } + +glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { + float horizontalScale = _glWidget->width() / 2.0f; + float verticalScale = _glWidget->height() / 2.0f; + + // -1,-1 is 0,windowHeight + // 1,1 is windowWidth,0 + + // -1,1 1,1 + // +-----------------------+ + // | | | + // | | | + // | -1,0 | | + // |-----------+-----------| + // | 0,0 | + // | | | + // | | | + // | | | + // +-----------------------+ + // -1,-1 1,-1 + + glm::vec2 screenPoint((projectedPoint.x + 1.0) * horizontalScale, + ((projectedPoint.y + 1.0) * -verticalScale) + _glWidget->height()); + + return screenPoint; +} + +// render the coverage map on screen +void Application::renderCoverageMapV2() { + + //printLog("renderCoverageMap()\n"); + + glDisable(GL_LIGHTING); + glLineWidth(2.0); + glBegin(GL_LINES); + glColor3f(0,1,1); + + renderCoverageMapsV2Recursively(&_voxels.myCoverageMapV2); + + glEnd(); + glEnable(GL_LIGHTING); +} + +void Application::renderCoverageMapsV2Recursively(CoverageMapV2* map) { + // render ourselves... + if (map->isCovered()) { + BoundingBox box = map->getBoundingBox(); + + glm::vec2 firstPoint = getScaledScreenPoint(box.getVertex(0)); + glm::vec2 lastPoint(firstPoint); + + for (int i = 1; i < box.getVertexCount(); i++) { + glm::vec2 thisPoint = getScaledScreenPoint(box.getVertex(i)); + + glVertex2f(lastPoint.x, lastPoint.y); + glVertex2f(thisPoint.x, thisPoint.y); + lastPoint = thisPoint; + } + + glVertex2f(lastPoint.x, lastPoint.y); + glVertex2f(firstPoint.x, firstPoint.y); + } else { + // iterate our children and call render on them. + for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) { + CoverageMapV2* childMap = map->getChild(i); + if (childMap) { + renderCoverageMapsV2Recursively(childMap); + } + } + } +} + +// render the coverage map on screen +void Application::renderCoverageMap() { + + //printLog("renderCoverageMap()\n"); + + glDisable(GL_LIGHTING); + glLineWidth(2.0); + glBegin(GL_LINES); + glColor3f(0,0,1); + + renderCoverageMapsRecursively(&_voxels.myCoverageMap); + + glEnd(); + glEnable(GL_LIGHTING); +} + +void Application::renderCoverageMapsRecursively(CoverageMap* map) { + for (int i = 0; i < map->getPolygonCount(); i++) { + + VoxelProjectedPolygon* polygon = map->getPolygon(i); + + if (polygon->getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) { + glColor3f(.5,0,0); // dark red + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT)) { + glColor3f(.5,.5,0); // dark yellow + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT)) { + glColor3f(.5,.5,.5); // gray + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_BOTTOM)) { + glColor3f(.5,0,.5); // dark magenta + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM)) { + glColor3f(.75,0,0); // red + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP)) { + glColor3f(1,0,1); // magenta + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT | PROJECTION_TOP)) { + glColor3f(0,0,1); // Blue + } else if (polygon->getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT | PROJECTION_TOP)) { + glColor3f(0,1,0); // green + } else if (polygon->getProjectionType() == (PROJECTION_NEAR)) { + glColor3f(1,1,0); // yellow + } else if (polygon->getProjectionType() == (PROJECTION_FAR | PROJECTION_RIGHT | PROJECTION_BOTTOM)) { + glColor3f(0,.5,.5); // dark cyan + } else { + glColor3f(1,0,0); + } + + glm::vec2 firstPoint = getScaledScreenPoint(polygon->getVertex(0)); + glm::vec2 lastPoint(firstPoint); + + for (int i = 1; i < polygon->getVertexCount(); i++) { + glm::vec2 thisPoint = getScaledScreenPoint(polygon->getVertex(i)); + + glVertex2f(lastPoint.x, lastPoint.y); + glVertex2f(thisPoint.x, thisPoint.y); + lastPoint = thisPoint; + } + + glVertex2f(lastPoint.x, lastPoint.y); + glVertex2f(firstPoint.x, firstPoint.y); + } + + // iterate our children and call render on them. + for (int i = 0; i < CoverageMapV2::NUMBER_OF_CHILDREN; i++) { + CoverageMap* childMap = map->getChild(i); + if (childMap) { + renderCoverageMapsRecursively(childMap); + } + } +} + + + ///////////////////////////////////////////////////////////////////////////////////// // renderViewFrustum() // @@ -2803,8 +3017,8 @@ void Application::shiftPaintingColor() { void Application::maybeEditVoxelUnderCursor() { if (_addVoxelMode->isChecked() || _colorVoxelMode->isChecked()) { if (_mouseVoxel.s != 0) { - PACKET_HEADER message = (_destructiveAddVoxel->isChecked() ? - PACKET_HEADER_SET_VOXEL_DESTRUCTIVE : PACKET_HEADER_SET_VOXEL); + PACKET_TYPE message = (_destructiveAddVoxel->isChecked() ? + PACKET_TYPE_SET_VOXEL_DESTRUCTIVE : PACKET_TYPE_SET_VOXEL); sendVoxelEditMessage(message, _mouseVoxel); // create the voxel locally so it appears immediately @@ -2877,7 +3091,7 @@ void Application::maybeEditVoxelUnderCursor() { void Application::deleteVoxelUnderCursor() { if (_mouseVoxel.s != 0) { // sending delete to the server is sufficient, server will send new version so we see updates soon enough - sendVoxelEditMessage(PACKET_HEADER_ERASE_VOXEL, _mouseVoxel); + sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, _mouseVoxel); AudioInjector* voxelInjector = AudioInjectionManager::injectorWithCapacity(5000); voxelInjector->setPosition(glm::vec3(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z)); // voxelInjector->setBearing(0); //straight down the z axis @@ -2987,36 +3201,39 @@ void* Application::networkReceive(void* args) { app->_packetCount++; app->_bytesCount += bytesReceived; - switch (app->_incomingPacket[0]) { - case PACKET_HEADER_TRANSMITTER_DATA_V2: - // V2 = IOS transmitter app - app->_myTransmitter.processIncomingData(app->_incomingPacket, bytesReceived); - - break; - case PACKET_HEADER_MIXED_AUDIO: - app->_audio.addReceivedAudioToBuffer(app->_incomingPacket, bytesReceived); - break; - case PACKET_HEADER_VOXEL_DATA: - case PACKET_HEADER_VOXEL_DATA_MONOCHROME: - case PACKET_HEADER_Z_COMMAND: - case PACKET_HEADER_ERASE_VOXEL: - app->_voxels.parseData(app->_incomingPacket, bytesReceived); - break; - case PACKET_HEADER_ENVIRONMENT_DATA: - app->_environment.parseData(&senderAddress, app->_incomingPacket, bytesReceived); - break; - case PACKET_HEADER_BULK_AVATAR_DATA: - NodeList::getInstance()->processBulkNodeData(&senderAddress, - app->_incomingPacket, - bytesReceived); - getInstance()->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived); - break; - case PACKET_HEADER_AVATAR_VOXEL_URL: - processAvatarVoxelURLMessage(app->_incomingPacket, bytesReceived); - break; - default: - NodeList::getInstance()->processNodeData(&senderAddress, app->_incomingPacket, bytesReceived); - break; + if (packetVersionMatch(app->_incomingPacket)) { + // only process this packet if we have a match on the packet version + switch (app->_incomingPacket[0]) { + case PACKET_TYPE_TRANSMITTER_DATA_V2: + // V2 = IOS transmitter app + app->_myTransmitter.processIncomingData(app->_incomingPacket, bytesReceived); + + break; + case PACKET_TYPE_MIXED_AUDIO: + app->_audio.addReceivedAudioToBuffer(app->_incomingPacket, bytesReceived); + break; + case PACKET_TYPE_VOXEL_DATA: + case PACKET_TYPE_VOXEL_DATA_MONOCHROME: + case PACKET_TYPE_Z_COMMAND: + case PACKET_TYPE_ERASE_VOXEL: + app->_voxels.parseData(app->_incomingPacket, bytesReceived); + break; + case PACKET_TYPE_ENVIRONMENT_DATA: + app->_environment.parseData(&senderAddress, app->_incomingPacket, bytesReceived); + break; + case PACKET_TYPE_BULK_AVATAR_DATA: + NodeList::getInstance()->processBulkNodeData(&senderAddress, + app->_incomingPacket, + bytesReceived); + getInstance()->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived); + break; + case PACKET_TYPE_AVATAR_VOXEL_URL: + processAvatarVoxelURLMessage(app->_incomingPacket, bytesReceived); + break; + default: + NodeList::getInstance()->processNodeData(&senderAddress, app->_incomingPacket, bytesReceived); + break; + } } } else if (!app->_enableNetworkThread) { break; diff --git a/interface/src/Application.h b/interface/src/Application.h index ceacd433e1..c6bbd4eec2 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -132,13 +132,14 @@ private slots: void doFalseRandomizeEveryOtherVoxelColors(); void doFalseColorizeByDistance(); void doFalseColorizeOccluded(); + void doFalseColorizeOccludedV2(); void doFalseColorizeInView(); void doTrueVoxelColors(); void doTreeStats(); void setWantsMonochrome(bool wantsMonochrome); - void setWantsResIn(bool wantsResIn); - void setWantsDelta(bool wantsDelta); - void setWantsOcclusionCulling(bool wantsOcclusionCulling); + void setWantsLowResMoving(bool wantsLowResMoving); + void disableDeltaSending(bool disableDeltaSending); + void disableOcclusionCulling(bool disableOcclusionCulling); void updateVoxelModeActions(); void decreaseVoxelSize(); void increaseVoxelSize(); @@ -154,6 +155,14 @@ private slots: void copyVoxels(); void pasteVoxels(); void runTests(); + + void renderCoverageMap(); + void renderCoverageMapsRecursively(CoverageMap* map); + + void renderCoverageMapV2(); + void renderCoverageMapsV2Recursively(CoverageMapV2* map); + + glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint); void goHome(); private: @@ -163,7 +172,7 @@ private: static void sendVoxelServerAddScene(); static bool sendVoxelsOperation(VoxelNode* node, void* extraData); - static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail); + static void sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail& detail); static void sendAvatarVoxelURLMessage(const QUrl& url); static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes); static void sendPingPackets(); @@ -174,6 +183,7 @@ private: void init(); void update(float deltaTime); + bool isLookingAtOtherAvatar(glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection, glm::vec3& eyePosition); void updateAvatar(float deltaTime); void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum); @@ -247,6 +257,9 @@ private: QAction* _fullScreenMode; // whether we are in full screen mode QAction* _frustumRenderModeAction; QAction* _settingsAutosave; // Whether settings are saved automatically + + QAction* _renderCoverageMapV2; + QAction* _renderCoverageMap; BandwidthMeter _bandwidthMeter; BandwidthDialog* _bandwidthDialog; @@ -320,6 +333,8 @@ private: float _touchDragStartedAvgX; float _touchDragStartedAvgY; bool _isTouchPressed; // true if multitouch has been pressed (clear when finished) + float _yawFromTouch; + float _pitchFromTouch; VoxelDetail _mouseVoxelDragging; glm::vec3 _voxelThrust; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index dc303f8d4b..7af42478d5 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -99,16 +99,18 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o glm::vec3 headPosition = interfaceAvatar->getHeadJointPosition(); glm::quat headOrientation = interfaceAvatar->getHead().getOrientation(); - int leadingBytes = sizeof(PACKET_HEADER_MICROPHONE_AUDIO_NO_ECHO) + sizeof(headPosition) + sizeof(headOrientation); + int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO); + int leadingBytes = numBytesPacketHeader + sizeof(headPosition) + sizeof(headOrientation); // we need the amount of bytes in the buffer + 1 for type // + 12 for 3 floats for position + float for bearing + 1 attenuation byte unsigned char dataPacket[BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes]; - dataPacket[0] = (Application::getInstance()->shouldEchoAudio()) - ? PACKET_HEADER_MICROPHONE_AUDIO_WITH_ECHO - : PACKET_HEADER_MICROPHONE_AUDIO_NO_ECHO; - unsigned char *currentPacketPtr = dataPacket + 1; + PACKET_TYPE packetType = (Application::getInstance()->shouldEchoAudio()) + ? PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO + : PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO; + + unsigned char* currentPacketPtr = dataPacket + populateTypeAndVersion(dataPacket, packetType); // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); @@ -446,10 +448,10 @@ void Audio::addReceivedAudioToBuffer(unsigned char* receivedData, int receivedBy //printf("Got audio packet %d\n", _packetsReceivedThisPlayback); - _ringBuffer.parseData((unsigned char*) receivedData, PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); + _ringBuffer.parseData((unsigned char*) receivedData, receivedBytes); Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO) - .updateValue(PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); + .updateValue(PACKET_LENGTH_BYTES + sizeof(PACKET_TYPE)); _lastReceiveTime = currentReceiveTime; } diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index 426aad57b3..ab57d6d0df 100755 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -16,12 +16,14 @@ #include "Hand.h" #include "Head.h" #include "Log.h" +#include "Physics.h" #include "ui/TextRenderer.h" #include #include #include #include + using namespace std; const bool BALLS_ON = false; @@ -285,7 +287,10 @@ void Avatar::reset() { } // Update avatar head rotation with sensor data -void Avatar::updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngle) { +void Avatar::updateFromGyrosAndOrWebcam(bool gyroLook, + const glm::vec3& amplifyAngle, + float yawFromTouch, + float pitchFromTouch) { SerialInterface* gyros = Application::getInstance()->getSerialHeadSensor(); Webcam* webcam = Application::getInstance()->getWebcam(); glm::vec3 estimatedPosition, estimatedRotation; @@ -296,6 +301,8 @@ void Avatar::updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyA estimatedRotation = webcam->getEstimatedRotation(); } else { + _head.setPitch(pitchFromTouch); + _head.setYaw(yawFromTouch); return; } if (webcam->isActive()) { @@ -316,8 +323,8 @@ void Avatar::updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyA } } } - _head.setPitch(estimatedRotation.x * amplifyAngle.x); - _head.setYaw(estimatedRotation.y * amplifyAngle.y); + _head.setPitch(estimatedRotation.x * amplifyAngle.x + pitchFromTouch); + _head.setYaw(estimatedRotation.y * amplifyAngle.y + yawFromTouch); _head.setRoll(estimatedRotation.z * amplifyAngle.z); _head.setCameraFollowsHead(gyroLook); @@ -356,16 +363,16 @@ void Avatar::updateThrust(float deltaTime, Transmitter * transmitter) { // // Gather thrust information from keyboard and sensors to apply to avatar motion // - glm::quat orientation = getOrientation(); + glm::quat orientation = getHead().getOrientation(); glm::vec3 front = orientation * IDENTITY_FRONT; glm::vec3 right = orientation * IDENTITY_RIGHT; glm::vec3 up = orientation * IDENTITY_UP; const float THRUST_MAG_UP = 800.0f; - const float THRUST_MAG_DOWN = 200.f; - const float THRUST_MAG_FWD = 300.f; - const float THRUST_MAG_BACK = 150.f; - const float THRUST_MAG_LATERAL = 200.f; + const float THRUST_MAG_DOWN = 300.f; + const float THRUST_MAG_FWD = 500.f; + const float THRUST_MAG_BACK = 300.f; + const float THRUST_MAG_LATERAL = 250.f; const float THRUST_JUMP = 120.f; // Add Thrusts from keyboard @@ -420,7 +427,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { glm::quat orientation = getOrientation(); glm::vec3 front = orientation * IDENTITY_FRONT; glm::vec3 right = orientation * IDENTITY_RIGHT; - + // Update movement timers if (isMyAvatar()) { _elapsedTimeSinceCollision += deltaTime; @@ -443,9 +450,6 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { glm::vec3 oldVelocity = getVelocity(); if (isMyAvatar()) { - // update position by velocity - _position += _velocity * deltaTime; - // calculate speed _speed = glm::length(_velocity); } @@ -480,7 +484,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { enableHandMovement &= (it->jointID != AVATAR_JOINT_RIGHT_WRIST); } - // update avatar skeleton + // update avatar skeleton _skeleton.update(deltaTime, getOrientation(), _position); //determine the lengths of the body springs now that we have updated the skeleton at least once @@ -501,51 +505,49 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _ballSpringsInitialized = true; } - // if this is not my avatar, then hand position comes from transmitted data if (!isMyAvatar()) { _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = _handPosition; } - - //detect and respond to collisions with other avatars... - if (isMyAvatar()) { - updateAvatarCollisions(deltaTime); - } - + //update the movement of the hand and process handshaking with other avatars... updateHandMovementAndTouching(deltaTime, enableHandMovement); _avatarTouch.simulate(deltaTime); - // apply gravity and collision with the ground/floor - if (isMyAvatar() && USING_AVATAR_GRAVITY) { - _velocity += _gravity * (GRAVITY_EARTH * deltaTime); - } if (isMyAvatar()) { + + // apply gravity + if (USING_AVATAR_GRAVITY) { + // For gravity, always move the avatar by the amount driven by gravity, so that the collision + // routines will detect it and collide every frame when pulled by gravity to a surface + // + _velocity += _gravity * (GRAVITY_EARTH * deltaTime); + _position += _gravity * (GRAVITY_EARTH * deltaTime) * deltaTime; + } + updateCollisionWithEnvironment(); + updateCollisionWithVoxels(); + updateAvatarCollisions(deltaTime); } // update body balls updateBodyBalls(deltaTime); + // test for avatar collision response with the big sphere if (usingBigSphereCollisionTest) { updateCollisionWithSphere(_TEST_bigSpherePosition, _TEST_bigSphereRadius, deltaTime); } - // collision response with voxels - if (isMyAvatar()) { - updateCollisionWithVoxels(); - } - + if (isMyAvatar()) { // add thrust to velocity _velocity += _thrust * deltaTime; - + // update body yaw by body yaw delta orientation = orientation * glm::quat(glm::radians( glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime)); - // decay body rotation momentum float bodySpinMomentum = 1.0 - BODY_SPIN_FRICTION * deltaTime; if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; } @@ -553,22 +555,14 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _bodyYawDelta *= bodySpinMomentum; _bodyRollDelta *= bodySpinMomentum; - // Decay velocity. If velocity is really low, increase decay to simulate static friction - const float VELOCITY_DECAY_UNDER_THRUST = 0.2; - const float VELOCITY_FAST_DECAY = 0.6; - const float VELOCITY_SLOW_DECAY = 3.0; - const float VELOCITY_FAST_THRESHOLD = 2.0f; - float decayConstant, decay; - if (glm::length(_thrust) > 0.f) { - decayConstant = VELOCITY_DECAY_UNDER_THRUST; - } else if (glm::length(_velocity) > VELOCITY_FAST_THRESHOLD) { - decayConstant = VELOCITY_FAST_DECAY; - } else { - decayConstant = VELOCITY_SLOW_DECAY; - } - decay = glm::clamp(1.0f - decayConstant * deltaTime, 0.0f, 1.0f); - _velocity *= decay; - + const float MAX_STATIC_FRICTION_VELOCITY = 0.5f; + const float STATIC_FRICTION_STRENGTH = 20.f; + applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_VELOCITY, STATIC_FRICTION_STRENGTH); + + const float LINEAR_DAMPING_STRENGTH = 3.0f; + const float SQUARED_DAMPING_STRENGTH = 0.2f; + applyDamping(deltaTime, _velocity, LINEAR_DAMPING_STRENGTH, SQUARED_DAMPING_STRENGTH); + //pitch and roll the body as a function of forward speed and turning delta const float BODY_PITCH_WHILE_WALKING = -20.0; const float BODY_ROLL_WHILE_TURNING = 0.2; @@ -659,6 +653,9 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _mode = AVATAR_MODE_INTERACTING; } + // update position by velocity, and subtract the change added earlier for gravity + _position += _velocity * deltaTime; + // Zero thrust out now that we've added it to velocity in this frame _thrust = glm::vec3(0, 0, 0); diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index 5e46eebb19..777b4dca59 100755 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -87,7 +87,10 @@ public: void reset(); void simulate(float deltaTime, Transmitter* transmitter); void updateThrust(float deltaTime, Transmitter * transmitter); - void updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngles); + void updateFromGyrosAndOrWebcam(bool gyroLook, + const glm::vec3& amplifyAngle, + float yawFromTouch, + float pitchFromTouch); void addBodyYaw(float y) {_bodyYaw += y;}; void render(bool lookingInMirror, bool renderAvatarBalls); diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp index d087281569..35ed121e38 100644 --- a/interface/src/Environment.cpp +++ b/interface/src/Environment.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "Camera.h" @@ -138,8 +139,10 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 int Environment::parseData(sockaddr *senderAddress, unsigned char* sourceBuffer, int numBytes) { // push past the packet header unsigned char* start = sourceBuffer; - sourceBuffer++; - numBytes--; + + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + sourceBuffer += numBytesPacketHeader; + numBytes -= numBytesPacketHeader; // get the lock for the duration of the call QMutexLocker locker(&_mutex); diff --git a/interface/src/Hand.cpp b/interface/src/Hand.cpp index 950a5fe556..29a2c32bf1 100755 --- a/interface/src/Hand.cpp +++ b/interface/src/Hand.cpp @@ -54,7 +54,7 @@ void Hand::calculateGeometry() { _position = head.getPosition() + head.getOrientation() * offset; _orientation = head.getOrientation(); - int numLeapBalls = _fingerTips.size() + _fingerRoots.size(); + int numLeapBalls = _fingerTips.size(); _leapBalls.resize(numLeapBalls); for (int i = 0; i < _fingerTips.size(); ++i) { diff --git a/interface/src/Head.cpp b/interface/src/Head.cpp index 8b116b48ff..44d62e695d 100644 --- a/interface/src/Head.cpp +++ b/interface/src/Head.cpp @@ -54,6 +54,7 @@ Head::Head(Avatar* owningAvatar) : _rotation(0.0f, 0.0f, 0.0f), _leftEyePosition(0.0f, 0.0f, 0.0f), _rightEyePosition(0.0f, 0.0f, 0.0f), + _eyeLevelPosition(0.0f, 0.0f, 0.0f), _leftEyeBrowPosition(0.0f, 0.0f, 0.0f), _rightEyeBrowPosition(0.0f, 0.0f, 0.0f), _leftEarPosition(0.0f, 0.0f, 0.0f), @@ -268,6 +269,8 @@ void Head::calculateGeometry() { + up * _scale * EYE_UP_OFFSET + front * _scale * EYE_FRONT_OFFSET; + _eyeLevelPosition = _position + up * _scale * EYE_UP_OFFSET; + //calculate the eyebrow positions _leftEyeBrowPosition = _leftEyePosition; _rightEyeBrowPosition = _rightEyePosition; diff --git a/interface/src/Head.h b/interface/src/Head.h index 2b1b7aabfb..3cb41f81ec 100644 --- a/interface/src/Head.h +++ b/interface/src/Head.h @@ -53,7 +53,8 @@ public: glm::quat getOrientation() const; glm::quat getCameraOrientation () const; - glm::vec3 getPosition() const { return _position; } + glm::vec3 getPosition() const { return _position; } + const glm::vec3& getEyeLevelPosition() const { return _eyeLevelPosition; } glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getUpDirection () const { return getOrientation() * IDENTITY_UP; } glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } @@ -88,7 +89,8 @@ private: glm::vec3 _position; glm::vec3 _rotation; glm::vec3 _leftEyePosition; - glm::vec3 _rightEyePosition; + glm::vec3 _rightEyePosition; + glm::vec3 _eyeLevelPosition; glm::vec3 _leftEyeBrowPosition; glm::vec3 _rightEyeBrowPosition; glm::vec3 _leftEarPosition; diff --git a/interface/src/Physics.cpp b/interface/src/Physics.cpp new file mode 100644 index 0000000000..31e64ccad3 --- /dev/null +++ b/interface/src/Physics.cpp @@ -0,0 +1,40 @@ +// +// Physics.cpp +// hifi +// +// Created by Philip on July 11, 2013 +// +// Routines to help with doing virtual world physics +// + +#include +#include + +#include "Util.h" +#include "world.h" +#include "Physics.h" + +// +// Applies static friction: maxVelocity is the largest velocity for which there +// there is friction, and strength is the amount of friction force applied to reduce +// velocity. +// +void applyStaticFriction(float deltaTime, glm::vec3& velocity, float maxVelocity, float strength) { + float v = glm::length(velocity); + if (v < maxVelocity) { + velocity *= glm::clamp((1.0f - deltaTime * strength * (1.f - v / maxVelocity)), 0.0f, 1.0f); + } +} + +// +// Applies velocity damping, with a strength value for linear and squared velocity damping +// + +void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, float squaredStrength) { + if (squaredStrength == 0.f) { + velocity *= glm::clamp(1.f - deltaTime * linearStrength, 0.f, 1.f); + } else { + velocity *= glm::clamp(1.f - deltaTime * (linearStrength + glm::length(velocity) * squaredStrength), 0.f, 1.f); + } +} + diff --git a/interface/src/Physics.h b/interface/src/Physics.h new file mode 100644 index 0000000000..699497c187 --- /dev/null +++ b/interface/src/Physics.h @@ -0,0 +1,15 @@ +// +// Balls.h +// hifi +// +// Created by Philip on 4/25/13. +// +// + +#ifndef hifi_Physics_h +#define hifi_Physics_h + +void applyStaticFriction(float deltaTime, glm::vec3& velocity, float maxVelocity, float strength); +void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, float squaredStrength); + +#endif diff --git a/interface/src/SerialInterface.cpp b/interface/src/SerialInterface.cpp index 733ba0386f..aac1f8f1f2 100644 --- a/interface/src/SerialInterface.cpp +++ b/interface/src/SerialInterface.cpp @@ -31,7 +31,7 @@ const short NO_READ_MAXIMUM_MSECS = 3000; const int GRAVITY_SAMPLES = 60; // Use the first few samples to baseline values const int NORTH_SAMPLES = 30; const int ACCELERATION_SENSOR_FUSION_SAMPLES = 20; -const int COMPASS_SENSOR_FUSION_SAMPLES = 200; +const int COMPASS_SENSOR_FUSION_SAMPLES = 100; const int LONG_TERM_RATE_SAMPLES = 1000; const bool USING_INVENSENSE_MPU9150 = 1; @@ -383,7 +383,7 @@ void SerialInterface::resetSerial() { glm::vec3 SerialInterface::recenterCompass(const glm::vec3& compass) { // compensate for "hard iron" distortion by subtracting the midpoint on each axis; see // http://www.sensorsmag.com/sensors/motion-velocity-displacement/compensating-tilt-hard-iron-and-soft-iron-effects-6475 - return compass - (_compassMinima + _compassMaxima) * 0.5f; + return (compass - (_compassMinima + _compassMaxima) * 0.5f) / (_compassMaxima - _compassMinima); } diff --git a/interface/src/SerialInterface.h b/interface/src/SerialInterface.h index 555f2bac58..6398953456 100644 --- a/interface/src/SerialInterface.h +++ b/interface/src/SerialInterface.h @@ -33,8 +33,8 @@ public: _estimatedVelocity(0, 0, 0), _lastAcceleration(0, 0, 0), _lastRotationRates(0, 0, 0), - _compassMinima(-235, -132, -184), // experimentally derived initial values follow - _compassMaxima(83, 155, 120), + _compassMinima(-211, -132, -186), + _compassMaxima(89, 95, 98), _angularVelocityToLinearAccel( 0.003f, -0.001f, -0.006f, -0.005f, -0.001f, -0.006f, diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 7934190e4c..e663ef33bd 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -14,7 +14,7 @@ #undef __interface__Starfield_impl__ Stars::Stars() : - _controller(0l) { + _controller(0l), _fileLoaded(false) { _controller = new starfield::Controller; } @@ -23,7 +23,8 @@ Stars::~Stars() { } bool Stars::readInput(const char* url, const char* cacheFile, unsigned limit) { - return _controller->readInput(url, cacheFile, limit); + _fileLoaded = _controller->readInput(url, cacheFile, limit); + return _fileLoaded; } bool Stars::setResolution(unsigned k) { diff --git a/interface/src/Stars.h b/interface/src/Stars.h index ac2abcde42..83d55d3766 100644 --- a/interface/src/Stars.h +++ b/interface/src/Stars.h @@ -65,6 +65,7 @@ class Stars { float changeLOD(float factor, float overalloc = 0.25, float realloc = 0.15); + bool getFileLoaded() const { return _fileLoaded; }; private: // don't copy/assign Stars(Stars const&); // = delete; @@ -73,6 +74,8 @@ class Stars { // variables starfield::Controller* _controller; + + bool _fileLoaded; }; diff --git a/interface/src/Transmitter.cpp b/interface/src/Transmitter.cpp index 8cbb4a5c29..b16fb79295 100644 --- a/interface/src/Transmitter.cpp +++ b/interface/src/Transmitter.cpp @@ -3,15 +3,20 @@ // hifi // // Created by Philip Rosedale on 5/20/13. -// +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // -#include "Transmitter.h" -#include "InterfaceConfig.h" -#include "Util.h" #include + #include + +#include + +#include "InterfaceConfig.h" #include "Log.h" +#include "Transmitter.h" +#include "Util.h" + const float DELTA_TIME = 1.f / 60.f; const float DECAY_RATE = 0.15f; @@ -45,7 +50,9 @@ void Transmitter::resetLevels() { } void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) { - const int PACKET_HEADER_SIZE = 1; // Packet's first byte is 'T' + // Packet's first byte is 'T' + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + const int ROTATION_MARKER_SIZE = 1; // 'R' = Rotation (clockwise about x,y,z) const int ACCELERATION_MARKER_SIZE = 1; // 'A' = Acceleration (x,y,z) if (!_lastReceivedPacket) { @@ -53,10 +60,10 @@ void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) { } gettimeofday(_lastReceivedPacket, NULL); - if (numBytes == PACKET_HEADER_SIZE + ROTATION_MARKER_SIZE + ACCELERATION_MARKER_SIZE + if (numBytes == numBytesPacketHeader + ROTATION_MARKER_SIZE + ACCELERATION_MARKER_SIZE + sizeof(_lastRotationRate) + sizeof(_lastAcceleration) + sizeof(_touchState.x) + sizeof(_touchState.y) + sizeof(_touchState.state)) { - unsigned char* packetDataPosition = &packetData[PACKET_HEADER_SIZE + ROTATION_MARKER_SIZE]; + unsigned char* packetDataPosition = packetData + numBytesPacketHeader + ROTATION_MARKER_SIZE; memcpy(&_lastRotationRate, packetDataPosition, sizeof(_lastRotationRate)); packetDataPosition += sizeof(_lastRotationRate) + ACCELERATION_MARKER_SIZE; memcpy(&_lastAcceleration, packetDataPosition, sizeof(_lastAcceleration)); diff --git a/interface/src/Transmitter.h b/interface/src/Transmitter.h index 95d80249e8..27426e1d27 100644 --- a/interface/src/Transmitter.h +++ b/interface/src/Transmitter.h @@ -3,7 +3,7 @@ // hifi // // Created by Philip Rosedale on 5/20/13. -// +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // #ifndef __hifi__Transmitter__ diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index f5efd1b13a..65a277b623 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -499,6 +499,20 @@ void runTimingTests() { gettimeofday(&endTime, NULL); elapsedMsecs = diffclock(&startTime, &endTime); printLog("powf(f, 0.5) usecs: %f\n", 1000.0f * elapsedMsecs / (float) numTests); + + // Vector Math + float distance; + glm::vec3 pointA(randVector()), pointB(randVector()); + gettimeofday(&startTime, NULL); + for (int i = 1; i < numTests; i++) { + //glm::vec3 temp = pointA - pointB; + //float distanceSquared = glm::dot(temp, temp); + distance = glm::distance(pointA, pointB); + } + gettimeofday(&endTime, NULL); + elapsedMsecs = diffclock(&startTime, &endTime); + printLog("vector math usecs: %f [%f msecs total for %d tests]\n", + 1000.0f * elapsedMsecs / (float) numTests, elapsedMsecs, numTests); // Vec3 test glm::vec3 vecA(randVector()), vecB(randVector()); @@ -523,3 +537,13 @@ float loadSetting(QSettings* settings, const char* name, float defaultValue) { } return value; } + +bool rayIntersectsSphere(glm::vec3& rayStarting, glm::vec3& rayNormalizedDirection, glm::vec3& sphereCenter, double sphereRadius) { + glm::vec3 vecFromRayToSphereCenter = sphereCenter - rayStarting; + double projection = glm::dot(vecFromRayToSphereCenter, rayNormalizedDirection); + double shortestDistance = sqrt(glm::dot(vecFromRayToSphereCenter, vecFromRayToSphereCenter) - projection * projection); + if (shortestDistance <= sphereRadius) { + return true; + } + return false; +} diff --git a/interface/src/Util.h b/interface/src/Util.h index 2005b76438..37bd0595ec 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -71,4 +71,6 @@ void runTimingTests(); float loadSetting(QSettings* settings, const char* name, float defaultValue); +bool rayIntersectsSphere(glm::vec3& rayStarting, glm::vec3& rayNormalizedDirection, glm::vec3& sphereCenter, double sphereRadius); + #endif diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 97c3e4bcd5..ca7220afa8 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -23,6 +23,7 @@ #include "Log.h" #include "VoxelConstants.h" #include "CoverageMap.h" +#include "CoverageMapV2.h" #include "InterfaceConfig.h" #include "renderer/ProgramObject.h" @@ -112,32 +113,31 @@ float VoxelSystem::getVoxelsBytesReadPerSecondAverage() { int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { unsigned char command = *sourceBuffer; - unsigned char *voxelData = sourceBuffer + 1; + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + unsigned char* voxelData = sourceBuffer + numBytesPacketHeader; pthread_mutex_lock(&_treeLock); switch(command) { - case PACKET_HEADER_VOXEL_DATA: - { + case PACKET_TYPE_VOXEL_DATA: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the bitstream into the tree - _tree->readBitstreamToTree(voxelData, numBytes - 1, WANT_COLOR, WANT_EXISTS_BITS); + _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, WANT_COLOR, WANT_EXISTS_BITS); } - break; - case PACKET_HEADER_VOXEL_DATA_MONOCHROME: - { + break; + case PACKET_TYPE_VOXEL_DATA_MONOCHROME: { PerformanceWarning warn(_renderWarningsOn, "readBitstreamToTree()"); // ask the VoxelTree to read the MONOCHROME bitstream into the tree - _tree->readBitstreamToTree(voxelData, numBytes - 1, NO_COLOR, WANT_EXISTS_BITS); + _tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, NO_COLOR, WANT_EXISTS_BITS); } - break; - case PACKET_HEADER_Z_COMMAND: + break; + case PACKET_TYPE_Z_COMMAND: // the Z command is a special command that allows the sender to send high level semantic // requests, like erase all, or add sphere scene, different receivers may handle these // messages differently char* packetData = (char *)sourceBuffer; - char* command = &packetData[1]; // start of the command + char* command = &packetData[numBytesPacketHeader]; // start of the command int commandLength = strlen(command); // commands are null terminated strings int totalLength = 1+commandLength+1; @@ -168,17 +168,17 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { void VoxelSystem::setupNewVoxelsForDrawing() { PerformanceWarning warn(_renderWarningsOn, "setupNewVoxelsForDrawing()"); // would like to include _voxelsInArrays, _voxelsUpdated - long long start = usecTimestampNow(); - long long sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; + uint64_t start = usecTimestampNow(); + uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging - if (!iAmDebugging && sinceLastTime <= std::max(_setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { + if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { return; // bail early, it hasn't been long enough since the last time we ran } - long long sinceLastViewCulling = (start - _lastViewCulling) / 1000; + uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000; // If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view - if ((sinceLastViewCulling >= std::max(_lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) + if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) && !isViewChanging() && hasViewChanged()) { _lastViewCulling = start; @@ -192,7 +192,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() { // VBO reubuilding. Possibly we should do this only if our actual VBO usage crosses some lower boundary. cleanupRemovedVoxels(); - long long endViewCulling = usecTimestampNow(); + uint64_t endViewCulling = usecTimestampNow(); _lastViewCullingElapsed = (endViewCulling - start) / 1000; } @@ -229,8 +229,8 @@ void VoxelSystem::setupNewVoxelsForDrawing() { pthread_mutex_unlock(&_bufferWriteLock); - long long end = usecTimestampNow(); - long long elapsedmsec = (end - start) / 1000; + uint64_t end = usecTimestampNow(); + int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; _setupNewVoxelsForDrawingLastElapsed = elapsedmsec; } @@ -891,13 +891,11 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) { bool VoxelSystem::isViewChanging() { bool result = false; // assume the best -/** TEMPORARY HACK ****** // If our viewFrustum has changed since our _lastKnowViewFrustum - if (_viewFrustum && !_lastKnowViewFrustum.matches(_viewFrustum)) { + if (!_lastKnowViewFrustum.matches(Application::getInstance()->getViewFrustum())) { result = true; - _lastKnowViewFrustum = *_viewFrustum; // save last known + _lastKnowViewFrustum = *Application::getInstance()->getViewFrustum(); // save last known } -**/ return result; } @@ -1089,13 +1087,13 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { args.expectedMax = _voxelsInWriteArrays; _tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args); - printLog("_voxelsDirty=%s _voxelsInWriteArrays=%ld minDirty=%ld maxDirty=%ld \n", debug::valueOf(_voxelsDirty), - _voxelsInWriteArrays, minDirty, maxDirty); - - printLog("stats: total %ld, leaves %ld, dirty %ld, colored %ld, shouldRender %ld, inVBO %ld\n", + printLog("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n", args.totalNodes, args.leafNodes, args.dirtyNodes, args.coloredNodes, args.shouldRenderNodes); - printLog("inVBO %ld, nodesInVBOOverExpectedMax %ld, duplicateVBOIndex %ld, nodesInVBONotShouldRender %ld\n", + printLog(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld \n", debug::valueOf(_voxelsDirty), + _voxelsInWriteArrays, minDirty, maxDirty); + + printLog(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld \n", args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender); glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN; @@ -1108,7 +1106,7 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { } } - printLog("minInVBO=%ld maxInVBO=%ld _voxelsInWriteArrays=%ld _voxelsInReadArrays=%ld\n", + printLog(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld \n", minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays); } @@ -1162,6 +1160,7 @@ void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* dest struct FalseColorizeOccludedArgs { ViewFrustum* viewFrustum; CoverageMap* map; + CoverageMapV2* mapV2; VoxelTree* tree; long totalVoxels; long coloredVoxels; @@ -1181,9 +1180,11 @@ struct FalseColorizeSubTreeOperationArgs { }; bool VoxelSystem::falseColorizeSubTreeOperation(VoxelNode* node, void* extraData) { - FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData; - node->setFalseColor(args->color[0], args->color[1], args->color[2]); - args->voxelsTouched++; + if (node->getShouldRender()) { + FalseColorizeSubTreeOperationArgs* args = (FalseColorizeSubTreeOperationArgs*) extraData; + node->setFalseColor(args->color[0], args->color[1], args->color[2]); + args->voxelsTouched++; + } return true; } @@ -1263,12 +1264,143 @@ bool VoxelSystem::falseColorizeOccludedOperation(VoxelNode* node, void* extraDat } return true; // keep going! } + void VoxelSystem::falseColorizeOccluded() { PerformanceWarning warn(true, "falseColorizeOccluded()",true); - CoverageMap map; + myCoverageMap.erase(); + FalseColorizeOccludedArgs args; args.viewFrustum = Application::getInstance()->getViewFrustum(); - args.map = ↦ + args.map = &myCoverageMap; + args.totalVoxels = 0; + args.coloredVoxels = 0; + args.occludedVoxels = 0; + args.notOccludedVoxels = 0; + args.outOfView = 0; + args.subtreeVoxelsSkipped = 0; + args.nonLeaves = 0; + args.stagedForDeletion = 0; + args.nonLeavesOutOfView = 0; + args.nonLeavesOccluded = 0; + args.tree = _tree; + + VoxelProjectedPolygon::pointInside_calls = 0; + VoxelProjectedPolygon::occludes_calls = 0; + VoxelProjectedPolygon::intersects_calls = 0; + + glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE); + + _tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args); + + printLog("falseColorizeOccluded()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n", + position.x, position.y, + args.totalVoxels, args.coloredVoxels, args.occludedVoxels, + args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped, + args.stagedForDeletion, + args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded, + VoxelProjectedPolygon::pointInside_calls, + VoxelProjectedPolygon::occludes_calls, + VoxelProjectedPolygon::intersects_calls + ); + + + //myCoverageMap.erase(); + + setupNewVoxelsForDrawing(); +} + +bool VoxelSystem::falseColorizeOccludedV2Operation(VoxelNode* node, void* extraData) { + + FalseColorizeOccludedArgs* args = (FalseColorizeOccludedArgs*) extraData; + args->totalVoxels++; + + // if this node is staged for deletion, then just return + if (node->isStagedForDeletion()) { + args->stagedForDeletion++; + return true; + } + + // If we are a parent, let's see if we're completely occluded. + 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->nonLeavesOutOfView++; + delete voxelPolygon; + return true; + } + + CoverageMapV2StorageResult result = args->mapV2->checkMap(voxelPolygon, false); + if (result == V2_OCCLUDED) { + args->nonLeavesOccluded++; + delete voxelPolygon; + + FalseColorizeSubTreeOperationArgs subArgs; + subArgs.color[0] = 0; + subArgs.color[1] = 255; + subArgs.color[2] = 0; + subArgs.voxelsTouched = 0; + + args->tree->recurseNodeWithOperation(node, falseColorizeSubTreeOperation, &subArgs ); + + args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); + args->totalVoxels += (subArgs.voxelsTouched - 1); + + return false; + } + + delete voxelPolygon; + return true; // keep looking... + } + + 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->outOfView++; + delete voxelPolygon; + return true; + } + + CoverageMapV2StorageResult result = args->mapV2->checkMap(voxelPolygon, true); + if (result == V2_OCCLUDED) { + node->setFalseColor(255, 0, 0); + args->occludedVoxels++; + } else if (result == V2_STORED) { + args->notOccludedVoxels++; + //printLog("***** falseColorizeOccludedOperation() NODE is STORED *****\n"); + } else if (result == V2_DOESNT_FIT) { + //printLog("***** falseColorizeOccludedOperation() NODE DOESNT_FIT???? *****\n"); + } + delete voxelPolygon; // V2 maps don't store polygons, so we're always in charge of freeing + } + return true; // keep going! +} + + +void VoxelSystem::falseColorizeOccludedV2() { + PerformanceWarning warn(true, "falseColorizeOccludedV2()",true); + myCoverageMapV2.erase(); + + CoverageMapV2::wantDebugging = true; + + VoxelProjectedPolygon::pointInside_calls = 0; + VoxelProjectedPolygon::occludes_calls = 0; + VoxelProjectedPolygon::intersects_calls = 0; + + FalseColorizeOccludedArgs args; + args.viewFrustum = Application::getInstance()->getViewFrustum(); + args.mapV2 = &myCoverageMapV2; args.totalVoxels = 0; args.coloredVoxels = 0; args.occludedVoxels = 0; @@ -1283,15 +1415,22 @@ void VoxelSystem::falseColorizeOccluded() { glm::vec3 position = args.viewFrustum->getPosition() * (1.0f/TREE_SCALE); - _tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedOperation, position, (void*)&args); + _tree->recurseTreeWithOperationDistanceSorted(falseColorizeOccludedV2Operation, position, (void*)&args); - printLog("falseColorizeOccluded()\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n", + printLog("falseColorizeOccludedV2()\n position=(%f,%f)\n total=%ld\n colored=%ld\n occluded=%ld\n notOccluded=%ld\n outOfView=%ld\n subtreeVoxelsSkipped=%ld\n stagedForDeletion=%ld\n nonLeaves=%ld\n nonLeavesOutOfView=%ld\n nonLeavesOccluded=%ld\n pointInside_calls=%ld\n occludes_calls=%ld\n intersects_calls=%ld\n", + position.x, position.y, args.totalVoxels, args.coloredVoxels, args.occludedVoxels, args.notOccludedVoxels, args.outOfView, args.subtreeVoxelsSkipped, args.stagedForDeletion, - args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded); + args.nonLeaves, args.nonLeavesOutOfView, args.nonLeavesOccluded, + VoxelProjectedPolygon::pointInside_calls, + VoxelProjectedPolygon::occludes_calls, + VoxelProjectedPolygon::intersects_calls + ); + //myCoverageMapV2.erase(); setupNewVoxelsForDrawing(); } + diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index 26746af3c7..411ae2f81f 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -11,11 +11,15 @@ #include "InterfaceConfig.h" #include + #include #include + +#include #include -#include #include +#include + #include "Camera.h" #include "Util.h" #include "world.h" @@ -57,6 +61,7 @@ public: void falseColorizeDistanceFromView(ViewFrustum* viewFrustum); void falseColorizeRandomEveryOther(); void falseColorizeOccluded(); + void falseColorizeOccludedV2(); void killLocalVoxels(); void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; }; @@ -84,6 +89,9 @@ public: void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot); void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); + + CoverageMapV2 myCoverageMapV2; + CoverageMap myCoverageMap; protected: float _treeScale; @@ -123,6 +131,8 @@ private: static bool collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* extraData); static bool falseColorizeOccludedOperation(VoxelNode* node, void* extraData); static bool falseColorizeSubTreeOperation(VoxelNode* node, void* extraData); + static bool falseColorizeOccludedV2Operation(VoxelNode* node, void* extraData); + int updateNodeInArraysAsFullVBO(VoxelNode* node); int updateNodeInArraysAsPartialVBO(VoxelNode* node); @@ -150,10 +160,10 @@ private: bool _writeRenderFullVBO; bool _readRenderFullVBO; - double _setupNewVoxelsForDrawingLastElapsed; - double _setupNewVoxelsForDrawingLastFinished; - double _lastViewCulling; - double _lastViewCullingElapsed; + int _setupNewVoxelsForDrawingLastElapsed; + uint64_t _setupNewVoxelsForDrawingLastFinished; + uint64_t _lastViewCulling; + int _lastViewCullingElapsed; GLuint _vboVerticesID; GLuint _vboNormalsID; diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 6c7006c5f4..169151861c 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -50,7 +50,7 @@ void Webcam::setEnabled(bool enabled) { QMetaObject::invokeMethod(_grabber, "grabFrame"); } else { - _grabberThread.quit(); + QMetaObject::invokeMethod(_grabber, "shutdown"); _active = false; } } @@ -191,8 +191,8 @@ void Webcam::setFrame(const Mat& frame, int format, const Mat& depth, const Rota const int MAX_FPS = 60; const int MIN_FRAME_DELAY = 1000000 / MAX_FPS; - long long now = usecTimestampNow(); - long long remaining = MIN_FRAME_DELAY; + uint64_t now = usecTimestampNow(); + int remaining = MIN_FRAME_DELAY; if (_startTimestamp == 0) { _startTimestamp = now; } else { @@ -267,8 +267,8 @@ FrameGrabber::FrameGrabber() : _initialized(false), _capture(0), _searchWindow(0 } FrameGrabber::~FrameGrabber() { - if (_capture != 0) { - cvReleaseCapture(&_capture); + if (_initialized) { + shutdown(); } } @@ -366,6 +366,16 @@ void FrameGrabber::reset() { #endif } +void FrameGrabber::shutdown() { + if (_capture != 0) { + cvReleaseCapture(&_capture); + _capture = 0; + } + _initialized = false; + + thread()->quit(); +} + void FrameGrabber::grabFrame() { if (!(_initialized || init())) { return; @@ -475,7 +485,7 @@ bool FrameGrabber::init() { // load our face cascade switchToResourcesParentIfRequired(); - if (!_faceCascade.load("resources/haarcascades/haarcascade_frontalface_alt.xml")) { + if (_faceCascade.empty() && !_faceCascade.load("resources/haarcascades/haarcascade_frontalface_alt.xml")) { printLog("Failed to load Haar cascade for face tracking.\n"); return false; } diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index f0910c7bce..4bc557f4fb 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -73,10 +73,10 @@ private: cv::RotatedRect _initialFaceRect; JointVector _joints; - long long _startTimestamp; + uint64_t _startTimestamp; int _frameCount; - long long _lastFrameTimestamp; + uint64_t _lastFrameTimestamp; glm::vec3 _estimatedPosition; glm::vec3 _estimatedRotation; @@ -94,6 +94,7 @@ public: public slots: void reset(); + void shutdown(); void grabFrame(); private: diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 16c381a036..a2ba56b8ef 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -70,7 +70,7 @@ void AudioInjector::injectAudio(UDPSocket* injectorSocket, sockaddr* destination timeval startTime; // calculate the number of bytes required for additional data - int leadingBytes = sizeof(PACKET_HEADER) + int leadingBytes = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_INJECT_AUDIO) + sizeof(_streamIdentifier) + sizeof(_position) + sizeof(_orientation) @@ -79,8 +79,7 @@ void AudioInjector::injectAudio(UDPSocket* injectorSocket, sockaddr* destination unsigned char dataPacket[(BUFFER_LENGTH_SAMPLES_PER_CHANNEL * sizeof(int16_t)) + leadingBytes]; - dataPacket[0] = PACKET_HEADER_INJECT_AUDIO; - unsigned char *currentPacketPtr = dataPacket + sizeof(PACKET_HEADER_INJECT_AUDIO); + unsigned char* currentPacketPtr = dataPacket + populateTypeAndVersion(dataPacket, PACKET_TYPE_INJECT_AUDIO); // copy the identifier for this injector memcpy(currentPacketPtr, &_streamIdentifier, sizeof(_streamIdentifier)); @@ -115,7 +114,7 @@ void AudioInjector::injectAudio(UDPSocket* injectorSocket, sockaddr* destination injectorSocket->send(destinationSocket, dataPacket, sizeof(dataPacket)); - long long usecToSleep = usecTimestamp(&startTime) + (++nextFrame * INJECT_INTERVAL_USECS) - usecTimestampNow(); + int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * INJECT_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); } diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 29ff920317..875bc815ce 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -21,7 +21,7 @@ const int STREAM_IDENTIFIER_NUM_BYTES = 8; const int MAX_INJECTOR_VOLUME = 0xFF; -const long long INJECT_INTERVAL_USECS = floorf((BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000); +const int INJECT_INTERVAL_USECS = floorf((BUFFER_LENGTH_SAMPLES_PER_CHANNEL / SAMPLE_RATE) * 1000000); class AudioInjector { public: diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 79e94e625d..d5830ee72f 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -34,7 +34,8 @@ void AudioRingBuffer::reset() { } int AudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) { - return parseAudioSamples(sourceBuffer + sizeof(PACKET_HEADER_MIXED_AUDIO), numBytes - sizeof(PACKET_HEADER_MIXED_AUDIO)); + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + return parseAudioSamples(sourceBuffer + numBytesPacketHeader, numBytes - numBytesPacketHeader); } int AudioRingBuffer::parseAudioSamples(unsigned char* sourceBuffer, int numBytes) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1bef9026e7..27a755e03f 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -32,10 +32,10 @@ AvatarData::AvatarData(Node* owningNode) : _cameraNearClip(0.0f), _cameraFarClip(0.0f), _keyState(NO_KEY_DOWN), - _wantResIn(false), _wantColor(true), - _wantDelta(false), - _wantOcclusionCulling(false), + _wantDelta(true), + _wantLowResMoving(false), + _wantOcclusionCulling(true), _headData(NULL), _handData(NULL) { @@ -113,9 +113,9 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) { // bitMask of less than byte wide items unsigned char bitItems = 0; - if (_wantResIn) { setAtBit(bitItems, WANT_RESIN_AT_BIT); } - if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } - if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } + if (_wantLowResMoving) { setAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); } + if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); } + if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); } if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); } // key state @@ -137,6 +137,11 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) { if (numFingerVectors > 255) numFingerVectors = 0; // safety. We shouldn't ever get over 255, so consider that invalid. + ///////////////////////////////// + // Temporarily disable Leap finger sending, as it's causing a crash whenever someone's got a Leap connected + numFingerVectors = 0; + ///////////////////////////////// + *destinationBuffer++ = (unsigned char)numFingerVectors; if (numFingerVectors > 0) { @@ -176,12 +181,13 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { } // increment to push past the packet header - sourceBuffer += sizeof(PACKET_HEADER_HEAD_DATA); + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + sourceBuffer += numBytesPacketHeader; unsigned char* startPosition = sourceBuffer; // push past the node ID - sourceBuffer += + sizeof(uint16_t); + sourceBuffer += sizeof(uint16_t); // Body world position memcpy(&_position, sourceBuffer, sizeof(float) * 3); @@ -240,9 +246,9 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { // voxel sending features... unsigned char bitItems = 0; bitItems = (unsigned char)*sourceBuffer++; - _wantResIn = oneAtBit(bitItems, WANT_RESIN_AT_BIT); - _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); - _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); + _wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT); + _wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT); + _wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT); _wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); // key state, stored as a semi-nibble in the bitItems @@ -254,8 +260,8 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { // leap hand data if (sourceBuffer - startPosition < numBytes) // safety check { - std::vector fingerTips = _handData->getFingerTips(); - std::vector fingerRoots = _handData->getFingerRoots(); + std::vector fingerTips; + std::vector fingerRoots; unsigned int numFingerVectors = *sourceBuffer++; unsigned int numFingerTips = numFingerVectors / 2; unsigned int numFingerRoots = numFingerVectors - numFingerTips; @@ -266,6 +272,11 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((int16_t*) sourceBuffer, &(fingerTips[i].y), 4); sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((int16_t*) sourceBuffer, &(fingerTips[i].z), 4); } + for (size_t i = 0; i < numFingerRoots; ++i) { + sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((int16_t*) sourceBuffer, &(fingerRoots[i].x), 4); + sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((int16_t*) sourceBuffer, &(fingerRoots[i].y), 4); + sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((int16_t*) sourceBuffer, &(fingerRoots[i].z), 4); + } _handData->setFingerTips(fingerTips); _handData->setFingerRoots(fingerRoots); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f65cf7bcb5..439ec9b5f8 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -20,7 +20,7 @@ #include "HeadData.h" #include "HandData.h" -const int WANT_RESIN_AT_BIT = 0; +const int WANT_LOW_RES_MOVING_BIT = 0; const int WANT_COLOR_AT_BIT = 1; const int WANT_DELTA_AT_BIT = 2; const int KEY_STATE_START_BIT = 3; // 4th and 5th bits @@ -91,13 +91,14 @@ public: const std::string& chatMessage () const { return _chatMessage; } // related to Voxel Sending strategies - bool getWantResIn() const { return _wantResIn; } - bool getWantColor() const { return _wantColor; } - bool getWantDelta() const { return _wantDelta; } + bool getWantColor() const { return _wantColor; } + bool getWantDelta() const { return _wantDelta; } + bool getWantLowResMoving() const { return _wantLowResMoving; } bool getWantOcclusionCulling() const { return _wantOcclusionCulling; } - void setWantResIn(bool wantResIn) { _wantResIn = wantResIn; } - void setWantColor(bool wantColor) { _wantColor = wantColor; } - void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } + + void setWantColor(bool wantColor) { _wantColor = wantColor; } + void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; } + void setWantLowResMoving(bool wantLowResMoving) { _wantLowResMoving = wantLowResMoving; } void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } void setHeadData(HeadData* headData) { _headData = headData; } @@ -130,9 +131,9 @@ protected: std::string _chatMessage; // voxel server sending items - bool _wantResIn; bool _wantColor; bool _wantDelta; + bool _wantLowResMoving; bool _wantOcclusionCulling; std::vector _joints; diff --git a/libraries/shared/src/Node.h b/libraries/shared/src/Node.h index 7bafbd5b58..de43558b7a 100644 --- a/libraries/shared/src/Node.h +++ b/libraries/shared/src/Node.h @@ -37,11 +37,11 @@ public: uint16_t getNodeID() const { return _nodeID; } void setNodeID(uint16_t nodeID) { _nodeID = nodeID;} - long long getWakeMicrostamp() const { return _wakeMicrostamp; } - void setWakeMicrostamp(long long wakeMicrostamp) { _wakeMicrostamp = wakeMicrostamp; } + uint64_t getWakeMicrostamp() const { return _wakeMicrostamp; } + void setWakeMicrostamp(uint64_t wakeMicrostamp) { _wakeMicrostamp = wakeMicrostamp; } - long long getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } - void setLastHeardMicrostamp(long long lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } + uint64_t getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } + void setLastHeardMicrostamp(uint64_t lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } sockaddr* getPublicSocket() const { return _publicSocket; } void setPublicSocket(sockaddr* publicSocket) { _publicSocket = publicSocket; } @@ -74,8 +74,8 @@ private: char _type; uint16_t _nodeID; - long long _wakeMicrostamp; - long long _lastHeardMicrostamp; + uint64_t _wakeMicrostamp; + uint64_t _lastHeardMicrostamp; sockaddr* _publicSocket; sockaddr* _localSocket; sockaddr* _activeSocket; diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 832c81cd24..391e22a915 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -72,7 +72,6 @@ NodeList::~NodeList() { // stop the spawned threads, if they were started stopSilentNodeRemovalThread(); - stopPingUnknownNodesThread(); pthread_mutex_destroy(&mutex); } @@ -81,27 +80,29 @@ void NodeList::timePingReply(sockaddr *nodeAddress, unsigned char *packetData) { for(NodeList::iterator node = begin(); node != end(); node++) { if (socketMatch(node->getPublicSocket(), nodeAddress) || socketMatch(node->getLocalSocket(), nodeAddress)) { - int pingTime = usecTimestampNow() - *(long long *)(packetData + 1); + + int pingTime = usecTimestampNow() - *(uint64_t*)(packetData + numBytesForPacketHeader(packetData)); + node->setPingMs(pingTime / 1000); break; } } } -void NodeList::processNodeData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes) { - switch (((char *)packetData)[0]) { - case PACKET_HEADER_DOMAIN: { +void NodeList::processNodeData(sockaddr* senderAddress, unsigned char* packetData, size_t dataBytes) { + switch (packetData[0]) { + case PACKET_TYPE_DOMAIN: { processDomainServerList(packetData, dataBytes); break; } - case PACKET_HEADER_PING: { + case PACKET_TYPE_PING: { char pingPacket[dataBytes]; memcpy(pingPacket, packetData, dataBytes); - pingPacket[0] = PACKET_HEADER_PING_REPLY; + populateTypeAndVersion((unsigned char*) pingPacket, PACKET_TYPE_PING_REPLY); _nodeSocket.send(senderAddress, pingPacket, dataBytes); break; } - case PACKET_HEADER_PING_REPLY: { + case PACKET_TYPE_PING_REPLY: { timePingReply(senderAddress, packetData); break; } @@ -118,18 +119,23 @@ void NodeList::processBulkNodeData(sockaddr *senderAddress, unsigned char *packe bulkSendNode->setLastHeardMicrostamp(usecTimestampNow()); bulkSendNode->recordBytesReceived(numTotalBytes); } - + + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + unsigned char *startPosition = packetData; - unsigned char *currentPosition = startPosition + 1; + unsigned char *currentPosition = startPosition + numBytesPacketHeader; unsigned char packetHolder[numTotalBytes]; - packetHolder[0] = PACKET_HEADER_HEAD_DATA; + // we've already verified packet version for the bulk packet, so all head data in the packet is also up to date + populateTypeAndVersion(packetHolder, PACKET_TYPE_HEAD_DATA); uint16_t nodeID = -1; while ((currentPosition - startPosition) < numTotalBytes) { unpackNodeId(currentPosition, &nodeID); - memcpy(packetHolder + 1, currentPosition, numTotalBytes - (currentPosition - startPosition)); + memcpy(packetHolder + numBytesPacketHeader, + currentPosition, + numTotalBytes - (currentPosition - startPosition)); Node* matchingNode = nodeWithID(nodeID); @@ -139,8 +145,8 @@ void NodeList::processBulkNodeData(sockaddr *senderAddress, unsigned char *packe } currentPosition += updateNodeWithData(matchingNode, - packetHolder, - numTotalBytes - (currentPosition - startPosition)); + packetHolder, + numTotalBytes - (currentPosition - startPosition)); } unlock(); @@ -213,6 +219,7 @@ void NodeList::setNodeTypesOfInterest(const char* nodeTypesOfInterest, int numNo void NodeList::sendDomainServerCheckIn() { static bool printedDomainServerIP = false; + // Lookup the IP address of the domain server if we need to if (atoi(DOMAIN_IP) == 0) { struct hostent* pHostInfo; @@ -229,26 +236,32 @@ void NodeList::sendDomainServerCheckIn() { printedDomainServerIP = true; } - // construct the DS check in packet if we need to static unsigned char* checkInPacket = NULL; - static int checkInPacketSize; - + static int checkInPacketSize = 0; + + // construct the DS check in packet if we need to if (!checkInPacket) { int numBytesNodesOfInterest = _nodeTypesOfInterest ? strlen((char*) _nodeTypesOfInterest) : 0; + const int IP_ADDRESS_BYTES = 4; + // check in packet has header, node type, port, IP, node types of interest, null termination - int numPacketBytes = sizeof(PACKET_HEADER) + sizeof(NODE_TYPE) + sizeof(uint16_t) + (sizeof(char) * 4) + - numBytesNodesOfInterest + sizeof(unsigned char); + int numPacketBytes = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) + sizeof(NODE_TYPE) + sizeof(uint16_t) + + IP_ADDRESS_BYTES + numBytesNodesOfInterest + sizeof(unsigned char); checkInPacket = new unsigned char[numPacketBytes]; unsigned char* packetPosition = checkInPacket; - *(packetPosition++) = (memchr(SOLO_NODE_TYPES, _ownerType, sizeof(SOLO_NODE_TYPES))) - ? PACKET_HEADER_DOMAIN_REPORT_FOR_DUTY - : PACKET_HEADER_DOMAIN_LIST_REQUEST; + PACKET_TYPE nodePacketType = (memchr(SOLO_NODE_TYPES, _ownerType, sizeof(SOLO_NODE_TYPES))) + ? PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY + : PACKET_TYPE_DOMAIN_LIST_REQUEST; + + int numHeaderBytes = populateTypeAndVersion(packetPosition, nodePacketType); + packetPosition += numHeaderBytes; + *(packetPosition++) = _ownerType; - packetPosition += packSocket(checkInPacket + sizeof(PACKET_HEADER) + sizeof(NODE_TYPE), + packetPosition += packSocket(checkInPacket + numHeaderBytes + sizeof(NODE_TYPE), getLocalAddress(), htons(_nodeSocket.getListeningPort())); @@ -269,7 +282,7 @@ void NodeList::sendDomainServerCheckIn() { _nodeSocket.send(DOMAIN_IP, DOMAINSERVER_PORT, checkInPacket, checkInPacketSize); } -int NodeList::processDomainServerList(unsigned char *packetData, size_t dataBytes) { +int NodeList::processDomainServerList(unsigned char* packetData, size_t dataBytes) { int readNodes = 0; char nodeType; @@ -281,16 +294,16 @@ int NodeList::processDomainServerList(unsigned char *packetData, size_t dataByte sockaddr_in nodeLocalSocket; nodeLocalSocket.sin_family = AF_INET; - unsigned char *readPtr = packetData + 1; - unsigned char *startPtr = packetData; + unsigned char* readPtr = packetData + numBytesForPacketHeader(packetData); + unsigned char* startPtr = packetData; while((readPtr - startPtr) < dataBytes - sizeof(uint16_t)) { nodeType = *readPtr++; - readPtr += unpackNodeId(readPtr, (uint16_t *)&nodeId); - readPtr += unpackSocket(readPtr, (sockaddr *)&nodePublicSocket); - readPtr += unpackSocket(readPtr, (sockaddr *)&nodeLocalSocket); + readPtr += unpackNodeId(readPtr, (uint16_t*) &nodeId); + readPtr += unpackSocket(readPtr, (sockaddr*) &nodePublicSocket); + readPtr += unpackSocket(readPtr, (sockaddr*) &nodeLocalSocket); - addOrUpdateNode((sockaddr *)&nodePublicSocket, (sockaddr *)&nodeLocalSocket, nodeType, nodeId); + addOrUpdateNode((sockaddr*) &nodePublicSocket, (sockaddr*) &nodeLocalSocket, nodeType, nodeId); } // read out our ID from the packet @@ -401,49 +414,10 @@ Node* NodeList::soloNodeOfType(char nodeType) { return NULL; } -void *pingUnknownNodes(void *args) { - - NodeList* nodeList = (NodeList*) args; - const int PING_INTERVAL_USECS = 1 * 1000000; - - timeval lastSend; - - while (!pingUnknownNodeThreadStopFlag) { - gettimeofday(&lastSend, NULL); - - for(NodeList::iterator node = nodeList->begin(); - node != nodeList->end(); - node++) { - if (!node->getActiveSocket() && node->getPublicSocket() && node->getLocalSocket()) { - // ping both of the sockets for the node so we can figure out - // which socket we can use - nodeList->getNodeSocket()->send(node->getPublicSocket(), &PACKET_HEADER_PING, 1); - nodeList->getNodeSocket()->send(node->getLocalSocket(), &PACKET_HEADER_PING, 1); - } - } - - long long usecToSleep = PING_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSend)); - - if (usecToSleep > 0) { - usleep(usecToSleep); - } - } - - return NULL; -} - -void NodeList::startPingUnknownNodesThread() { - pthread_create(&pingUnknownNodesThread, NULL, pingUnknownNodes, (void *)this); -} - -void NodeList::stopPingUnknownNodesThread() { - pingUnknownNodeThreadStopFlag = true; - pthread_join(pingUnknownNodesThread, NULL); -} - void *removeSilentNodes(void *args) { NodeList* nodeList = (NodeList*) args; - long long checkTimeUSecs, sleepTime; + uint64_t checkTimeUSecs; + int sleepTime; while (!silentNodeThreadStopFlag) { checkTimeUSecs = usecTimestampNow(); diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index b1f7acdbb3..da9c4a556a 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -89,8 +89,6 @@ public: void startSilentNodeRemovalThread(); void stopSilentNodeRemovalThread(); - void startPingUnknownNodesThread(); - void stopPingUnknownNodesThread(); friend class NodeListIterator; private: @@ -113,7 +111,6 @@ private: uint16_t _lastNodeID; pthread_t removeSilentNodesThread; pthread_t checkInWithDomainServerThread; - pthread_t pingUnknownNodesThread; pthread_mutex_t mutex; void handlePingReply(sockaddr *nodeAddress); diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp new file mode 100644 index 0000000000..9f8314e0b7 --- /dev/null +++ b/libraries/shared/src/PacketHeaders.cpp @@ -0,0 +1,62 @@ +// +// PacketHeaders.cpp +// hifi +// +// Created by Stephen Birarda on 6/28/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include + +#include "PacketHeaders.h" + +PACKET_VERSION versionForPacketType(PACKET_TYPE type) { + switch (type) { + default: + return 0; + break; + } +} + +bool packetVersionMatch(unsigned char* packetHeader) { + // currently this just checks if the version in the packet matches our return from versionForPacketType + // may need to be expanded in the future for types and versions that take > than 1 byte + if (packetHeader[1] == versionForPacketType(packetHeader[0])) { + return true; + } else { + printf("There is a packet version mismatch for packet with header %c\n", packetHeader[0]); + return false; + } +} + +int populateTypeAndVersion(unsigned char* destinationHeader, PACKET_TYPE type) { + destinationHeader[0] = type; + destinationHeader[1] = versionForPacketType(type); + + // return the number of bytes written for pointer pushing + return 2; +} + +int numBytesForPacketType(const unsigned char* packetType) { + if (packetType[0] == 255) { + return 1 + numBytesForPacketType(packetType + 1); + } else { + return 1; + } +} + +int numBytesForPacketVersion(const unsigned char* packetVersion) { + if (packetVersion[0] == 255) { + return 1 + numBytesForPacketVersion(packetVersion + 1); + } else { + return 1; + } +} + +int numBytesForPacketHeader(unsigned char* packetHeader) { + // int numBytesType = numBytesForPacketType(packetHeader); + // return numBytesType + numBytesForPacketVersion(packetHeader + numBytesType); + + // currently this need not be dynamic - there are 2 bytes for each packet header + return 2; +} \ No newline at end of file diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 89f6fbaf54..041303f2b5 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -12,28 +12,38 @@ #ifndef hifi_PacketHeaders_h #define hifi_PacketHeaders_h -typedef char PACKET_HEADER; -const PACKET_HEADER PACKET_HEADER_DOMAIN = 'D'; -const PACKET_HEADER PACKET_HEADER_PING = 'P'; -const PACKET_HEADER PACKET_HEADER_PING_REPLY = 'R'; -const PACKET_HEADER PACKET_HEADER_HEAD_DATA = 'H'; -const PACKET_HEADER PACKET_HEADER_Z_COMMAND = 'Z'; -const PACKET_HEADER PACKET_HEADER_INJECT_AUDIO = 'I'; -const PACKET_HEADER PACKET_HEADER_MIXED_AUDIO = 'A'; -const PACKET_HEADER PACKET_HEADER_MICROPHONE_AUDIO_NO_ECHO = 'M'; -const PACKET_HEADER PACKET_HEADER_MICROPHONE_AUDIO_WITH_ECHO = 'm'; -const PACKET_HEADER PACKET_HEADER_SET_VOXEL = 'S'; -const PACKET_HEADER PACKET_HEADER_SET_VOXEL_DESTRUCTIVE = 'O'; -const PACKET_HEADER PACKET_HEADER_ERASE_VOXEL = 'E'; -const PACKET_HEADER PACKET_HEADER_VOXEL_DATA = 'V'; -const PACKET_HEADER PACKET_HEADER_VOXEL_DATA_MONOCHROME = 'v'; -const PACKET_HEADER PACKET_HEADER_BULK_AVATAR_DATA = 'X'; -const PACKET_HEADER PACKET_HEADER_AVATAR_VOXEL_URL = 'U'; -const PACKET_HEADER PACKET_HEADER_TRANSMITTER_DATA_V2 = 'T'; -const PACKET_HEADER PACKET_HEADER_ENVIRONMENT_DATA = 'e'; -const PACKET_HEADER PACKET_HEADER_DOMAIN_LIST_REQUEST = 'L'; -const PACKET_HEADER PACKET_HEADER_DOMAIN_REPORT_FOR_DUTY = 'C'; +typedef char PACKET_TYPE; +const PACKET_TYPE PACKET_TYPE_DOMAIN = 'D'; +const PACKET_TYPE PACKET_TYPE_PING = 'P'; +const PACKET_TYPE PACKET_TYPE_PING_REPLY = 'R'; +const PACKET_TYPE PACKET_TYPE_HEAD_DATA = 'H'; +const PACKET_TYPE PACKET_TYPE_Z_COMMAND = 'Z'; +const PACKET_TYPE PACKET_TYPE_INJECT_AUDIO = 'I'; +const PACKET_TYPE PACKET_TYPE_MIXED_AUDIO = 'A'; +const PACKET_TYPE PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO = 'M'; +const PACKET_TYPE PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO = 'm'; +const PACKET_TYPE PACKET_TYPE_SET_VOXEL = 'S'; +const PACKET_TYPE PACKET_TYPE_SET_VOXEL_DESTRUCTIVE = 'O'; +const PACKET_TYPE PACKET_TYPE_ERASE_VOXEL = 'E'; +const PACKET_TYPE PACKET_TYPE_VOXEL_DATA = 'V'; +const PACKET_TYPE PACKET_TYPE_VOXEL_DATA_MONOCHROME = 'v'; +const PACKET_TYPE PACKET_TYPE_BULK_AVATAR_DATA = 'X'; +const PACKET_TYPE PACKET_TYPE_AVATAR_VOXEL_URL = 'U'; +const PACKET_TYPE PACKET_TYPE_TRANSMITTER_DATA_V2 = 'T'; +const PACKET_TYPE PACKET_TYPE_ENVIRONMENT_DATA = 'e'; +const PACKET_TYPE PACKET_TYPE_DOMAIN_LIST_REQUEST = 'L'; +const PACKET_TYPE PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY = 'C'; +typedef char PACKET_VERSION; + +PACKET_VERSION versionForPacketType(PACKET_TYPE type); + +bool packetVersionMatch(unsigned char* packetHeader); + +int populateTypeAndVersion(unsigned char* destinationHeader, PACKET_TYPE type); +int numBytesForPacketHeader(unsigned char* packetHeader); + +const int MAX_PACKET_HEADER_BYTES = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION); // These are supported Z-Command #define ERASE_ALL_COMMAND "erase all" diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index c376cfcb3e..d3547b384e 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -105,17 +105,17 @@ int PerfStat::DumpStats(char** array) { // Destructor handles recording all of our stats PerformanceWarning::~PerformanceWarning() { - long long end = usecTimestampNow(); + uint64_t end = usecTimestampNow(); double elapsedmsec = (end - _start) / 1000.0; if ((_alwaysDisplay || _renderWarningsOn) && elapsedmsec > 1) { if (elapsedmsec > 1000) { double elapsedsec = (end - _start) / 1000000.0; - printLog("WARNING! %s took %lf seconds\n", _message, elapsedsec); + printLog("%s%s took %lf seconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedsec); } else { - printLog("WARNING! %s took %lf milliseconds\n", _message, elapsedmsec); + printLog("%s%s took %lf milliseconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedmsec); } } else if (_alwaysDisplay) { - printLog("WARNING! %s took %lf milliseconds\n", _message, elapsedmsec); + printLog("%s took %lf milliseconds\n", _message, elapsedmsec); } }; diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index d39c1f31fb..22eb5d38b5 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -85,7 +85,7 @@ typedef std::map >::iterator class PerformanceWarning { private: - long long _start; + uint64_t _start; const char* _message; bool _renderWarningsOn; bool _alwaysDisplay; diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bdc35ff3f5..3fa7295d90 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -11,22 +11,25 @@ #include #include #include + #ifdef _WIN32 #include "Syssocket.h" #endif -#include "Log.h" -#include "SharedUtil.h" -#include "OctalCode.h" #ifdef __APPLE__ #include #endif -long long usecTimestamp(timeval *time) { +#include "Log.h" +#include "OctalCode.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" + +uint64_t usecTimestamp(timeval *time) { return (time->tv_sec * 1000000 + time->tv_usec); } -long long usecTimestampNow() { +uint64_t usecTimestampNow() { timeval now; gettimeofday(&now, NULL); return (now.tv_sec * 1000000 + now.tv_usec); @@ -209,13 +212,14 @@ bool createVoxelEditMessage(unsigned char command, short int sequence, int messageSize = MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE; // just a guess for now int actualMessageSize = 3; unsigned char* messageBuffer = new unsigned char[messageSize]; - unsigned short int* sequenceAt = (unsigned short int*)&messageBuffer[1]; - messageBuffer[0]=command; - *sequenceAt=sequence; - unsigned char* copyAt = &messageBuffer[3]; + int numBytesPacketHeader = populateTypeAndVersion(messageBuffer, command); + unsigned short int* sequenceAt = (unsigned short int*) &messageBuffer[numBytesPacketHeader]; + + *sequenceAt = sequence; + unsigned char* copyAt = &messageBuffer[numBytesPacketHeader + sizeof(sequence)]; - for (int i=0;i MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE) { - success=false; + success = false; } else { // add it to our message - memcpy(copyAt,voxelData,lengthOfVoxelData); - copyAt+=lengthOfVoxelData; - actualMessageSize+=lengthOfVoxelData; + memcpy(copyAt, voxelData, lengthOfVoxelData); + copyAt += lengthOfVoxelData; + actualMessageSize += lengthOfVoxelData; } // cleanup delete[] voxelData; @@ -238,9 +242,10 @@ bool createVoxelEditMessage(unsigned char command, short int sequence, if (success) { // finally, copy the result to the output bufferOut = new unsigned char[actualMessageSize]; - sizeOut=actualMessageSize; - memcpy(bufferOut,messageBuffer,actualMessageSize); + sizeOut = actualMessageSize; + memcpy(bufferOut, messageBuffer, actualMessageSize); } + delete[] messageBuffer; // clean up our temporary buffer return success; } @@ -440,3 +445,25 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex, return -1; // error case } +int removeFromSortedArrays(void* value, void** valueArray, float* keyArray, int* originalIndexArray, + int currentCount, int maxCount) { + + int i = 0; + if (currentCount > 0) { + while (i < currentCount && value != valueArray[i]) { + i++; + } + + if (value == valueArray[i] && i < currentCount) { + // i is the location of the item we were looking for + // shift array elements to the left + memmove(&valueArray[i], &valueArray[i + 1], sizeof(void*) * ((currentCount-1) - i)); + memmove(&keyArray[i], &keyArray[i + 1], sizeof(float) * ((currentCount-1) - i)); + if (originalIndexArray) { + memmove(&originalIndexArray[i], &originalIndexArray[i + 1], sizeof(int) * ((currentCount-1) - i)); + } + return currentCount-1; + } + } + return -1; // error case +} diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 2c9e1e6317..6973e4cec4 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -36,8 +36,8 @@ static const float DECIMETER = 0.1f; static const float CENTIMETER = 0.01f; static const float MILLIIMETER = 0.001f; -long long usecTimestamp(timeval *time); -long long usecTimestampNow(); +uint64_t usecTimestamp(timeval *time); +uint64_t usecTimestampNow(); float randFloat(); int randIntInRange (int min, int max); @@ -88,6 +88,11 @@ int insertIntoSortedArrays(void* value, float key, int originalIndex, void** valueArray, float* keyArray, int* originalIndexArray, int currentCount, int maxCount); +int removeFromSortedArrays(void* value, void** valueArray, float* keyArray, int* originalIndexArray, + int currentCount, int maxCount); + + + // Helper Class for debugging class debug { public: diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 7c8605db5d..e8a6bc22d0 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -11,7 +11,7 @@ #ifndef __hifi__Stats__ #define __hifi__Stats__ -#include +#include class SimpleMovingAverage { public: @@ -26,7 +26,7 @@ public: float getAverageSampleValuePerSecond(); private: int _numSamples; - long long _lastEventTimestamp; + uint64_t _lastEventTimestamp; float _average; float _eventDeltaAverage; diff --git a/libraries/voxels/src/CoverageMap.cpp b/libraries/voxels/src/CoverageMap.cpp index eae1262a59..d30e0a4290 100644 --- a/libraries/voxels/src/CoverageMap.cpp +++ b/libraries/voxels/src/CoverageMap.cpp @@ -13,9 +13,12 @@ int CoverageMap::_mapCount = 0; int CoverageMap::_checkMapRootCalls = 0; +int CoverageMap::_notAllInView = 0; bool CoverageMap::wantDebugging = false; -const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-2.f,-2.f), glm::vec2(4.f,4.f)); +const int MAX_POLYGONS_PER_REGION = 50; + +const BoundingBox CoverageMap::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.f,-1.f), glm::vec2(2.f,2.f)); // Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. // @@ -64,6 +67,22 @@ CoverageMap::~CoverageMap() { erase(); }; +void CoverageMap::printStats() { + printLog("CoverageMap::printStats()...\n"); + printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE); + printLog("_mapCount=%d\n",_mapCount); + printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls); + printLog("_notAllInView=%d\n",_notAllInView); + printLog("_maxPolygonsUsed=%d\n",CoverageRegion::_maxPolygonsUsed); + printLog("_totalPolygons=%d\n",CoverageRegion::_totalPolygons); + printLog("_occlusionTests=%d\n",CoverageRegion::_occlusionTests); + printLog("_regionSkips=%d\n",CoverageRegion::_regionSkips); + printLog("_tooSmallSkips=%d\n",CoverageRegion::_tooSmallSkips); + printLog("_regionFullSkips=%d\n",CoverageRegion::_regionFullSkips); + printLog("_outOfOrderPolygon=%d\n",CoverageRegion::_outOfOrderPolygon); + printLog("_clippedPolygons=%d\n",CoverageRegion::_clippedPolygons); +} + void CoverageMap::erase() { // tell our regions to erase() _topHalf.erase(); @@ -81,19 +100,19 @@ void CoverageMap::erase() { if (_isRoot && wantDebugging) { printLog("CoverageMap last to be deleted...\n"); - printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE); - printLog("_mapCount=%d\n",_mapCount); - printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls); - printLog("_maxPolygonsUsed=%d\n",CoverageRegion::_maxPolygonsUsed); - printLog("_totalPolygons=%d\n",CoverageRegion::_totalPolygons); - printLog("_occlusionTests=%d\n",CoverageRegion::_occlusionTests); - printLog("_outOfOrderPolygon=%d\n",CoverageRegion::_outOfOrderPolygon); + printStats(); + CoverageRegion::_maxPolygonsUsed = 0; CoverageRegion::_totalPolygons = 0; CoverageRegion::_occlusionTests = 0; + CoverageRegion::_regionSkips = 0; + CoverageRegion::_tooSmallSkips = 0; + CoverageRegion::_regionFullSkips = 0; CoverageRegion::_outOfOrderPolygon = 0; + CoverageRegion::_clippedPolygons = 0; _mapCount = 0; _checkMapRootCalls = 0; + _notAllInView = 0; } } @@ -121,16 +140,60 @@ BoundingBox CoverageMap::getChildBoundingBox(int childIndex) { return result; } +int CoverageMap::getPolygonCount() const { + return (_topHalf.getPolygonCount() + + _bottomHalf.getPolygonCount() + + _leftHalf.getPolygonCount() + + _rightHalf.getPolygonCount() + + _remainder.getPolygonCount()); +} + +VoxelProjectedPolygon* CoverageMap::getPolygon(int index) const { + int base = 0; + if ((index - base) < _topHalf.getPolygonCount()) { + return _topHalf.getPolygon((index - base)); + } + base += _topHalf.getPolygonCount(); + + if ((index - base) < _bottomHalf.getPolygonCount()) { + return _bottomHalf.getPolygon((index - base)); + } + base += _bottomHalf.getPolygonCount(); + + if ((index - base) < _leftHalf.getPolygonCount()) { + return _leftHalf.getPolygon((index - base)); + } + base += _leftHalf.getPolygonCount(); + + if ((index - base) < _rightHalf.getPolygonCount()) { + return _rightHalf.getPolygon((index - base)); + } + base += _rightHalf.getPolygonCount(); + + if ((index - base) < _remainder.getPolygonCount()) { + return _remainder.getPolygon((index - base)); + } + return NULL; +} + + + // possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, bool storeIt) { if (_isRoot) { _checkMapRootCalls++; + + //printLog("CoverageMap::checkMap()... storeIt=%s\n", debug::valueOf(storeIt)); + //polygon->printDebugDetails(); + } // short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is // not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later. if (!polygon->getAllInView()) { + _notAllInView++; + //printLog("CoverageMap2::checkMap()... V2_OCCLUDED\n"); return DOESNT_FIT; } @@ -142,22 +205,25 @@ CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, b bool fitsInAHalf = false; // Check each half of the box independently - if (_topHalf.contains(polygonBox)) { - result = _topHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_topHalf; - fitsInAHalf = true; - } else if (_bottomHalf.contains(polygonBox)) { - result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_bottomHalf; - fitsInAHalf = true; - } else if (_leftHalf.contains(polygonBox)) { - result = _leftHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_leftHalf; - fitsInAHalf = true; - } else if (_rightHalf.contains(polygonBox)) { - result = _rightHalf.checkRegion(polygon, polygonBox, storeIt); - storeIn = &_rightHalf; - fitsInAHalf = true; + const bool useRegions = true; // for now we will continue to use regions + if (useRegions) { + if (_topHalf.contains(polygonBox)) { + result = _topHalf.checkRegion(polygon, polygonBox, storeIt); + storeIn = &_topHalf; + fitsInAHalf = true; + } else if (_bottomHalf.contains(polygonBox)) { + result = _bottomHalf.checkRegion(polygon, polygonBox, storeIt); + storeIn = &_bottomHalf; + fitsInAHalf = true; + } else if (_leftHalf.contains(polygonBox)) { + result = _leftHalf.checkRegion(polygon, polygonBox, storeIt); + storeIn = &_leftHalf; + fitsInAHalf = true; + } else if (_rightHalf.contains(polygonBox)) { + result = _rightHalf.checkRegion(polygon, polygonBox, storeIt); + storeIn = &_rightHalf; + fitsInAHalf = true; + } } // if we got this far, there are one of two possibilities, either a polygon doesn't fit @@ -171,36 +237,77 @@ CoverageMapStorageResult CoverageMap::checkMap(VoxelProjectedPolygon* polygon, b // It's possible that this first set of checks might have resulted in an out of order polygon // in which case we just return.. if (result == STORED || result == OCCLUDED) { + + /* + if (result == STORED) + printLog("CoverageMap2::checkMap()... STORED\n"); + else + printLog("CoverageMap2::checkMap()... OCCLUDED\n"); + */ + return result; } // if we made it here, then it means the polygon being stored is not occluded // at this level of the quad tree, so we can continue to insert it into the map. // First we check to see if it fits in any of our sub maps - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - BoundingBox childMapBoundingBox = getChildBoundingBox(i); - if (childMapBoundingBox.contains(polygon->getBoundingBox())) { - // if no child map exists yet, then create it - if (!_childMaps[i]) { - _childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons); + const bool useChildMaps = true; // for now we will continue to use child maps + if (useChildMaps) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + BoundingBox childMapBoundingBox = getChildBoundingBox(i); + if (childMapBoundingBox.contains(polygon->getBoundingBox())) { + // if no child map exists yet, then create it + if (!_childMaps[i]) { + _childMaps[i] = new CoverageMap(childMapBoundingBox, NOT_ROOT, _managePolygons); + } + result = _childMaps[i]->checkMap(polygon, storeIt); + + /* + switch (result) { + case STORED: + printLog("checkMap() = STORED\n"); + break; + case NOT_STORED: + printLog("checkMap() = NOT_STORED\n"); + break; + case OCCLUDED: + printLog("checkMap() = OCCLUDED\n"); + break; + default: + printLog("checkMap() = ????? \n"); + break; + } + */ + + return result; } - return _childMaps[i]->checkMap(polygon, storeIt); } } // if we got this far, then the polygon is in our bounding box, but doesn't fit in // any of our child bounding boxes, so we should add it here. if (storeIt) { if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) { - //printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area()); - storeIn->storeInArray(polygon); - return STORED; + //printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area()); + if (storeIn->getPolygonCount() < MAX_POLYGONS_PER_REGION) { + storeIn->storeInArray(polygon); + //printLog("CoverageMap2::checkMap()... STORED\n"); + return STORED; + } else { + CoverageRegion::_regionFullSkips++; + //printLog("CoverageMap2::checkMap()... NOT_STORED\n"); + return NOT_STORED; + } } else { + CoverageRegion::_tooSmallSkips++; + //printLog("CoverageMap2::checkMap()... NOT_STORED\n"); return NOT_STORED; } } else { + //printLog("CoverageMap2::checkMap()... NOT_STORED\n"); return NOT_STORED; } } + //printLog("CoverageMap2::checkMap()... DOESNT_FIT\n"); return DOESNT_FIT; } @@ -223,12 +330,13 @@ void CoverageRegion::init() { _polygonArraySize = 0; _polygons = NULL; _polygonDistances = NULL; + _polygonSizes = NULL; } void CoverageRegion::erase() { -/* +/** if (_polygonCount) { printLog("CoverageRegion::erase()...\n"); printLog("_polygonCount=%d\n",_polygonCount); @@ -238,7 +346,7 @@ void CoverageRegion::erase() { // _polygons[i]->getBoundingBox().printDebugDetails(); //} } -*/ +**/ // If we're in charge of managing the polygons, then clean them up first if (_managePolygons) { for (int i = 0; i < _polygonCount; i++) { @@ -258,11 +366,16 @@ void CoverageRegion::erase() { delete[] _polygonDistances; _polygonDistances = NULL; } + if (_polygonSizes) { + delete[] _polygonSizes; + _polygonSizes = NULL; + } } void CoverageRegion::growPolygonArray() { VoxelProjectedPolygon** newPolygons = new VoxelProjectedPolygon*[_polygonArraySize + DEFAULT_GROW_SIZE]; - float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; + float* newDistances = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; + float* newSizes = new float[_polygonArraySize + DEFAULT_GROW_SIZE]; if (_polygons) { @@ -270,9 +383,12 @@ void CoverageRegion::growPolygonArray() { delete[] _polygons; memcpy(newDistances, _polygonDistances, sizeof(float) * _polygonCount); delete[] _polygonDistances; + memcpy(newSizes, _polygonSizes, sizeof(float) * _polygonCount); + delete[] _polygonSizes; } - _polygons = newPolygons; + _polygons = newPolygons; _polygonDistances = newDistances; + _polygonSizes = newSizes; _polygonArraySize = _polygonArraySize + DEFAULT_GROW_SIZE; //printLog("CoverageMap::growPolygonArray() _polygonArraySize=%d...\n",_polygonArraySize); } @@ -297,26 +413,87 @@ const char* CoverageRegion::getRegionName() const { int CoverageRegion::_maxPolygonsUsed = 0; int CoverageRegion::_totalPolygons = 0; int CoverageRegion::_occlusionTests = 0; +int CoverageRegion::_regionSkips = 0; +int CoverageRegion::_tooSmallSkips = 0; +int CoverageRegion::_regionFullSkips = 0; int CoverageRegion::_outOfOrderPolygon = 0; +int CoverageRegion::_clippedPolygons = 0; + + +bool CoverageRegion::mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray) { + for (int i = 0; i < _polygonCount; i++) { + VoxelProjectedPolygon* otherPolygon = _polygons[i]; + if (otherPolygon->canMerge(*seed)) { + otherPolygon->merge(*seed); + + if (seedInArray) { + const int IGNORED = NULL; + // remove this otherOtherPolygon for our polygon array + _polygonCount = removeFromSortedArrays((void*)seed, + (void**)_polygons, _polygonDistances, IGNORED, + _polygonCount, _polygonArraySize); + _totalPolygons--; + } + + //printLog("_polygonCount=%d\n",_polygonCount); + + // clean up + if (_managePolygons) { + delete seed; + } + + // Now run again using our newly merged polygon as the seed + mergeItemsInArray(otherPolygon, true); + + return true; + } + } + return false; +} // just handles storage in the array, doesn't test for occlusion or // determining if this is the correct map to store in! void CoverageRegion::storeInArray(VoxelProjectedPolygon* polygon) { + _currentCoveredBounds.explandToInclude(polygon->getBoundingBox()); + + + // Before we actually store this polygon in the array, check to see if this polygon can be merged to any of the existing + // polygons already in our array. + if (mergeItemsInArray(polygon, false)) { + return; // exit early + } + + // only after we attempt to merge! _totalPolygons++; if (_polygonArraySize < _polygonCount + 1) { growPolygonArray(); } - // This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to - // be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the - // insertion point in this array, and shift the array accordingly + // As an experiment we're going to see if we get an improvement by storing the polygons in coverage area sorted order + // this means the bigger polygons are earlier in the array. We should have a higher probability of being occluded earlier + // in the list. We still check to see if the polygon is "in front" of the target polygon before we test occlusion. Since + // sometimes things come out of order. + const bool SORT_BY_SIZE = false; const int IGNORED = NULL; - _polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED, - (void**)_polygons, _polygonDistances, IGNORED, - _polygonCount, _polygonArraySize); - + if (SORT_BY_SIZE) { + // This old code assumes that polygons will always be added in z-buffer order, but that doesn't seem to + // be a good assumption. So instead, we will need to sort this by distance. Use a binary search to find the + // insertion point in this array, and shift the array accordingly + float area = polygon->getBoundingBox().area(); + float reverseArea = 4.0f - area; + //printLog("store by size area=%f reverse area=%f\n", area, reverseArea); + _polygonCount = insertIntoSortedArrays((void*)polygon, reverseArea, IGNORED, + (void**)_polygons, _polygonSizes, IGNORED, + _polygonCount, _polygonArraySize); + } else { + const int IGNORED = NULL; + _polygonCount = insertIntoSortedArrays((void*)polygon, polygon->getDistance(), IGNORED, + (void**)_polygons, _polygonDistances, IGNORED, + _polygonCount, _polygonArraySize); + } + // Debugging and Optimization Tuning code. if (_polygonCount > _maxPolygonsUsed) { _maxPolygonsUsed = _polygonCount; @@ -335,37 +512,47 @@ CoverageMapStorageResult CoverageRegion::checkRegion(VoxelProjectedPolygon* poly if (_isRoot || _myBoundingBox.contains(polygonBox)) { result = NOT_STORED; // if we got here, then we DO fit... + + // only actually check the polygons if this polygon is in the covered bounds for this region + if (!_currentCoveredBounds.contains(polygonBox)) { + _regionSkips += _polygonCount; + } else { + // check to make sure this polygon isn't occluded by something at this level + for (int i = 0; i < _polygonCount; i++) { + VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i]; - // check to make sure this polygon isn't occluded by something at this level - for (int i = 0; i < _polygonCount; i++) { - VoxelProjectedPolygon* polygonAtThisLevel = _polygons[i]; - // Check to make sure that the polygon in question is "behind" the polygon in the list - // otherwise, we don't need to test it's occlusion (although, it means we've potentially - // added an item previously that may be occluded??? Is that possible? Maybe not, because two - // voxels can't have the exact same outline. So one occludes the other, they can't both occlude - // each other. - - - _occlusionTests++; - if (polygonAtThisLevel->occludes(*polygon)) { - // if the polygonAtThisLevel is actually behind the one we're inserting, then we don't - // want to report our inserted one as occluded, but we do want to add our inserted one. - if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) { - _outOfOrderPolygon++; - if (storeIt) { - if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) { - //printLog("storing polygon of area: %f\n",polygon->getBoundingBox().area()); - storeInArray(polygon); - return STORED; + // Check to make sure that the polygon in question is "behind" the polygon in the list + // otherwise, we don't need to test it's occlusion (although, it means we've potentially + // added an item previously that may be occluded??? Is that possible? Maybe not, because two + // voxels can't have the exact same outline. So one occludes the other, they can't both occlude + // each other. + + _occlusionTests++; + if (polygonAtThisLevel->occludes(*polygon)) { + // if the polygonAtThisLevel is actually behind the one we're inserting, then we don't + // want to report our inserted one as occluded, but we do want to add our inserted one. + if (polygonAtThisLevel->getDistance() >= polygon->getDistance()) { + _outOfOrderPolygon++; + if (storeIt) { + if (polygon->getBoundingBox().area() > CoverageMap::MINIMUM_POLYGON_AREA_TO_STORE) { + if (getPolygonCount() < MAX_POLYGONS_PER_REGION) { + storeInArray(polygon); + return STORED; + } else { + CoverageRegion::_regionFullSkips++; + return NOT_STORED; + } + } else { + _tooSmallSkips++; + return NOT_STORED; + } } else { return NOT_STORED; } - } else { - return NOT_STORED; } + // this polygon is occluded by a closer polygon, so don't store it, and let the caller know + return OCCLUDED; } - // this polygon is occluded by a closer polygon, so don't store it, and let the caller know - return OCCLUDED; } } } diff --git a/libraries/voxels/src/CoverageMap.h b/libraries/voxels/src/CoverageMap.h index 8b01eabeee..46daf646c9 100644 --- a/libraries/voxels/src/CoverageMap.h +++ b/libraries/voxels/src/CoverageMap.h @@ -31,24 +31,38 @@ public: static int _maxPolygonsUsed; static int _totalPolygons; static int _occlusionTests; + static int _regionSkips; + static int _tooSmallSkips; + static int _regionFullSkips; static int _outOfOrderPolygon; + static int _clippedPolygons; const char* getRegionName() const; + + int getPolygonCount() const { return _polygonCount; }; + VoxelProjectedPolygon* getPolygon(int index) const { return _polygons[index]; }; private: void init(); bool _isRoot; // is this map the root, if so, it never returns DOESNT_FIT BoundingBox _myBoundingBox; + BoundingBox _currentCoveredBounds; // area in this region currently covered by some polygon bool _managePolygons; // will the coverage map delete the polygons on destruct RegionName _regionName; int _polygonCount; // how many polygons at this level int _polygonArraySize; // how much room is there to store polygons at this level VoxelProjectedPolygon** _polygons; + + // we will use one or the other of these depending on settings in the code. float* _polygonDistances; + float* _polygonSizes; void growPolygonArray(); static const int DEFAULT_GROW_SIZE = 100; + + bool mergeItemsInArray(VoxelProjectedPolygon* seed, bool seedInArray); + }; class CoverageMap { @@ -68,9 +82,14 @@ public: BoundingBox getChildBoundingBox(int childIndex); void erase(); // erase the coverage map + void printStats(); static bool wantDebugging; + int getPolygonCount() const; + VoxelProjectedPolygon* getPolygon(int index) const; + CoverageMap* getChild(int childIndex) const { return _childMaps[childIndex]; }; + private: void init(); @@ -89,6 +108,7 @@ private: static int _mapCount; static int _checkMapRootCalls; + static int _notAllInView; }; diff --git a/libraries/voxels/src/CoverageMapV2.cpp b/libraries/voxels/src/CoverageMapV2.cpp new file mode 100644 index 0000000000..f5591bb324 --- /dev/null +++ b/libraries/voxels/src/CoverageMapV2.cpp @@ -0,0 +1,246 @@ +// +// CoverageMapV2.cpp - +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "CoverageMapV2.h" +#include +#include +#include "Log.h" + +int CoverageMapV2::_mapCount = 0; +int CoverageMapV2::_checkMapRootCalls = 0; +int CoverageMapV2::_notAllInView = 0; +bool CoverageMapV2::wantDebugging = false; + +const BoundingBox CoverageMapV2::ROOT_BOUNDING_BOX = BoundingBox(glm::vec2(-1.f,-1.f), glm::vec2(2.f,2.f)); + +// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. +// +// (0,0) (windowWidth, 0) +// -1,1 1,1 +// +-----------------------+ +// | | | +// | | | +// | -1,0 | | +// |-----------+-----------| +// | 0,0 | +// | | | +// | | | +// | | | +// +-----------------------+ +// -1,-1 1,-1 +// (0,windowHeight) (windowWidth,windowHeight) +// + +// Choosing a minimum sized polygon. Since we know a typical window is approximately 1500 pixels wide +// then a pixel on our screen will be ~ 2.0/1500 or 0.0013 "units" wide, similarly pixels are typically +// about that tall as well. If we say that polygons should be at least 10x10 pixels to be considered "big enough" +// then we can calculate a reasonable polygon area +const int TYPICAL_SCREEN_WIDTH_IN_PIXELS = 1500; +const int MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS = 10; +const float TYPICAL_SCREEN_PIXEL_WIDTH = (2.0f / TYPICAL_SCREEN_WIDTH_IN_PIXELS); +const float CoverageMapV2::MINIMUM_POLYGON_AREA_TO_STORE = (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS) * + (TYPICAL_SCREEN_PIXEL_WIDTH * MINIMUM_POLYGON_AREA_SIDE_IN_PIXELS); +const float CoverageMapV2::NOT_COVERED = FLT_MAX; +const float CoverageMapV2::MINIMUM_OCCLUSION_CHECK_AREA = MINIMUM_POLYGON_AREA_TO_STORE/10.0f; // one quarter the size of poly + + +CoverageMapV2::CoverageMapV2(BoundingBox boundingBox, bool isRoot, bool isCovered, float coverageDistance) : + _isRoot(isRoot), + _myBoundingBox(boundingBox), + _isCovered(isCovered), + _coveredDistance(coverageDistance) +{ + _mapCount++; + init(); + //printLog("CoverageMapV2 created... _mapCount=%d\n",_mapCount); +}; + +CoverageMapV2::~CoverageMapV2() { + erase(); +}; + +void CoverageMapV2::erase() { + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (_childMaps[i]) { + delete _childMaps[i]; + _childMaps[i] = NULL; + } + } + + if (_isRoot && wantDebugging) { + printLog("CoverageMapV2 last to be deleted...\n"); + printLog("MINIMUM_POLYGON_AREA_TO_STORE=%f\n",MINIMUM_POLYGON_AREA_TO_STORE); + printLog("_mapCount=%d\n",_mapCount); + printLog("_checkMapRootCalls=%d\n",_checkMapRootCalls); + printLog("_notAllInView=%d\n",_notAllInView); + _mapCount = 0; + _checkMapRootCalls = 0; + _notAllInView = 0; + } +} + +void CoverageMapV2::init() { + memset(_childMaps,0,sizeof(_childMaps)); +} + +// 0 = bottom, left +// 1 = bottom, right +// 2 = top, left +// 3 = top, right +BoundingBox CoverageMapV2::getChildBoundingBox(int childIndex) { + const int RIGHT_BIT = 1; + const int TOP_BIT = 2; + // initialize to our corner, and half our size + BoundingBox result(_myBoundingBox.corner,_myBoundingBox.size/2.0f); + // if our "right" bit is set, then add size.x to the corner + if ((childIndex & RIGHT_BIT) == RIGHT_BIT) { + result.corner.x += result.size.x; + } + // if our "top" bit is set, then add size.y to the corner + if ((childIndex & TOP_BIT) == TOP_BIT) { + result.corner.y += result.size.y; + } + return result; +} + +// possible results = STORED/NOT_STORED, OCCLUDED, DOESNT_FIT +CoverageMapV2StorageResult CoverageMapV2::checkMap(const VoxelProjectedPolygon* polygon, bool storeIt) { + assert(_isRoot); // you can only call this on the root map!!! + _checkMapRootCalls++; + + // short circuit: if we're the root node (only case we're here), and we're covered, and this polygon is deeper than our + // covered depth, then this polygon is occluded! + if (_isCovered && _coveredDistance < polygon->getDistance()) { + return V2_OCCLUDED; + } + + // short circuit: we don't handle polygons that aren't all in view, so, if the polygon in question is + // not in view, then we just discard it with a DOESNT_FIT, this saves us time checking values later. + if (!polygon->getAllInView()) { + _notAllInView++; + return V2_DOESNT_FIT; + } + + // Here's where we recursively check the polygon against the coverage map. We need to maintain two pieces of state. + // The first state is: have we seen at least one "fully occluded" map items. If we haven't then we don't track the covered + // state of the polygon. + // The second piece of state is: Are all of our "fully occluded" map items "covered". If even one of these occluded map + // items is not covered, then our polygon is not covered. + bool seenOccludedMapNodes = false; + bool allOccludedMapNodesCovered = false; + + recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered); + + // Ok, no matter how we were called, if all our occluded map nodes are covered, then we know this polygon + // is occluded, otherwise, we will report back to the caller about whether or not we stored the polygon + if (allOccludedMapNodesCovered) { + return V2_OCCLUDED; + } + if (storeIt) { + return V2_STORED; // otherwise report that we STORED it + } + return V2_NOT_STORED; // unless we weren't asked to store it, then we didn't +} + +void CoverageMapV2::recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt, + bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered) { + + // if we are really small, then we act like we don't intersect, this allows us to stop + // recusing as we get to the smalles edge of the polygon + if (_myBoundingBox.area() < MINIMUM_OCCLUSION_CHECK_AREA) { + return; // stop recursion, we're done! + } + + // Determine if this map node intersects the polygon and/or is fully covered by the polygon + // There are a couple special cases: If we're the root, we are assumed to intersect with all + // polygons. Also, any map node that is fully occluded also intersects. + bool nodeIsCoveredByPolygon = polygon->occludes(_myBoundingBox); + bool nodeIsIntersectedByPolygon = nodeIsCoveredByPolygon || _isRoot || polygon->intersects(_myBoundingBox); + + // If we don't intersect, then we can just return, we're done recursing + if (!nodeIsIntersectedByPolygon) { + return; // stop recursion, we're done! + } + + // At this point, we know our node intersects with the polygon. If this node is covered, then we want to treat it + // as if the node was fully covered, because this allows us to short circuit further recursion... + if (_isCovered && _coveredDistance < polygon->getDistance()) { + nodeIsCoveredByPolygon = true; // fake it till you make it + } + + // If this node in the map is fully covered by our polygon, then we don't need to recurse any further, but + // we do need to do some bookkeeping. + if (nodeIsCoveredByPolygon) { + // If this is the very first fully covered node we've seen, then we're initialize our allOccludedMapNodesCovered + // to be our current covered state. This has the following effect: if this node isn't already covered, then by + // definition, we know that at least one node for this polygon isn't covered, and therefore we aren't fully covered. + if (!seenOccludedMapNodes) { + allOccludedMapNodesCovered = (_isCovered && _coveredDistance < polygon->getDistance()); + // We need to mark that we've seen at least one node of our polygon! ;) + seenOccludedMapNodes = true; + } else { + // If this is our second or later node of our polygon, then we need to track our allOccludedMapNodesCovered state + allOccludedMapNodesCovered = allOccludedMapNodesCovered && + (_isCovered && _coveredDistance < polygon->getDistance()); + } + + // if we're in store mode then we want to record that this node is covered. + if (storeIt) { + _isCovered = true; + // store the minimum distance of our previous known distance, or our current polygon's distance. This is because + // we know that we're at least covered at this distance, but if we had previously identified that we're covered + // at a shallower distance, then we want to maintain that distance + _coveredDistance = std::min(polygon->getDistance(), _coveredDistance); + + // Note: this might be a good chance to delete child maps, but we're not going to do that at this point because + // we're trying to maintain the known distances in the lower portion of the tree. + } + + // and since this node of the quad map is covered, we can safely stop recursion. because we know all smaller map + // nodes will also be covered. + return; + } + + // If we got here, then it means we know that this node is not fully covered by the polygon, but it does intersect + // with the polygon. + + // Another case is that we aren't yet marked as covered, and so we should recurse and process smaller quad tree nodes. + // Note: we use this to determine if we can collapse the child quad trees and mark this node as covered + bool allChildrenOccluded = true; + float maxChildCoveredDepth = NOT_COVERED; + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + BoundingBox childMapBoundingBox = getChildBoundingBox(i); + // if no child map exists yet, then create it + if (!_childMaps[i]) { + // children get created with the coverage state of their parent. + _childMaps[i] = new CoverageMapV2(childMapBoundingBox, NOT_ROOT, _isCovered, _coveredDistance); + } + + _childMaps[i]->recurseMap(polygon, storeIt, seenOccludedMapNodes, allOccludedMapNodesCovered); + + // if so far, all of our children are covered, then record our furthest coverage distance + if (allChildrenOccluded && _childMaps[i]->_isCovered) { + maxChildCoveredDepth = std::max(maxChildCoveredDepth, _childMaps[i]->_coveredDistance); + } else { + // otherwise, at least one of our children is not covered, so not all are covered + allChildrenOccluded = false; + } + } + // if all the children are covered, this makes our quad tree "shallower" because it records that + // entire quad is covered, it uses the "furthest" z-order so that if a shalower polygon comes through + // we won't assume its occluded + if (allChildrenOccluded && storeIt) { + _isCovered = true; + _coveredDistance = maxChildCoveredDepth; + } + + // normal exit case... return... +} \ No newline at end of file diff --git a/libraries/voxels/src/CoverageMapV2.h b/libraries/voxels/src/CoverageMapV2.h new file mode 100644 index 0000000000..bce54263c9 --- /dev/null +++ b/libraries/voxels/src/CoverageMapV2.h @@ -0,0 +1,69 @@ +// +// CoverageMapV2.h - 2D CoverageMapV2 Quad tree for storage of VoxelProjectedPolygons +// hifi +// +// Added by Brad Hefta-Gaub on 06/11/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef _COVERAGE_MAP_V2_ +#define _COVERAGE_MAP_V2_ + +#include + +#include "VoxelProjectedPolygon.h" + +typedef enum { + V2_DOESNT_FIT, V2_STORED, V2_NOT_STORED, + V2_INTERSECT, V2_NO_INTERSECT, + V2_OCCLUDED, V2_NOT_OCCLUDED +} CoverageMapV2StorageResult; + +class CoverageMapV2 { + +public: + static const int NUMBER_OF_CHILDREN = 4; + static const bool NOT_ROOT = false; + static const bool IS_ROOT = true; + static const BoundingBox ROOT_BOUNDING_BOX; + static const float MINIMUM_POLYGON_AREA_TO_STORE; + static const float NOT_COVERED; + static const float MINIMUM_OCCLUSION_CHECK_AREA; + static bool wantDebugging; + + CoverageMapV2(BoundingBox boundingBox = ROOT_BOUNDING_BOX, bool isRoot = IS_ROOT, + bool isCovered = false, float coverageDistance = NOT_COVERED); + ~CoverageMapV2(); + + CoverageMapV2StorageResult checkMap(const VoxelProjectedPolygon* polygon, bool storeIt = true); + + BoundingBox getChildBoundingBox(int childIndex); + const BoundingBox& getBoundingBox() const { return _myBoundingBox; }; + CoverageMapV2* getChild(int childIndex) const { return _childMaps[childIndex]; }; + bool isCovered() const { return _isCovered; }; + + void erase(); // erase the coverage map + + void render(); + + +private: + void recurseMap(const VoxelProjectedPolygon* polygon, bool storeIt, + bool& seenOccludedMapNodes, bool& allOccludedMapNodesCovered); + + void init(); + + bool _isRoot; + BoundingBox _myBoundingBox; + CoverageMapV2* _childMaps[NUMBER_OF_CHILDREN]; + + bool _isCovered; + float _coveredDistance; + + static int _mapCount; + static int _checkMapRootCalls; + static int _notAllInView; +}; + + +#endif // _COVERAGE_MAP_V2_ diff --git a/libraries/voxels/src/GeometryUtil.cpp b/libraries/voxels/src/GeometryUtil.cpp index 1fc4e57013..af2a6dfc95 100644 --- a/libraries/voxels/src/GeometryUtil.cpp +++ b/libraries/voxels/src/GeometryUtil.cpp @@ -5,6 +5,9 @@ // Created by Andrzej Kapolka on 5/21/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +#include + +#include #include #include "GeometryUtil.h" @@ -117,6 +120,7 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& } // Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect? +// from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) { int d1 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y); int d2 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y); @@ -140,3 +144,193 @@ int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk) float b = (xj - xi) * (yk - yi); return a < b ? -1 : a > b ? 1 : 0; } + + +// +// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html +// +// Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. +// +// (0,0) (windowWidth, 0) +// -1,1 1,1 +// +-----------------------+ +// | | | +// | | | +// | -1,0 | | +// |-----------+-----------| +// | 0,0 | +// | | | +// | | | +// | | | +// +-----------------------+ +// -1,-1 1,-1 +// (0,windowHeight) (windowWidth,windowHeight) +// + +const float PolygonClip::TOP_OF_CLIPPING_WINDOW = 1.0f; +const float PolygonClip::BOTTOM_OF_CLIPPING_WINDOW = -1.0f; +const float PolygonClip::LEFT_OF_CLIPPING_WINDOW = -1.0f; +const float PolygonClip::RIGHT_OF_CLIPPING_WINDOW = 1.0f; + +const glm::vec2 PolygonClip::TOP_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , TOP_OF_CLIPPING_WINDOW ); +const glm::vec2 PolygonClip::TOP_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, TOP_OF_CLIPPING_WINDOW ); +const glm::vec2 PolygonClip::BOTTOM_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , BOTTOM_OF_CLIPPING_WINDOW ); +const glm::vec2 PolygonClip::BOTTOM_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, BOTTOM_OF_CLIPPING_WINDOW ); + +void PolygonClip::clipToScreen(const glm::vec2* inputVertexArray, int inLength, glm::vec2*& outputVertexArray, int& outLength) { + int tempLengthA = inLength; + int tempLengthB; + int maxLength = inLength * 2; + glm::vec2* tempVertexArrayA = new glm::vec2[maxLength]; + glm::vec2* tempVertexArrayB = new glm::vec2[maxLength]; + + // set up our temporary arrays + memcpy(tempVertexArrayA, inputVertexArray, sizeof(glm::vec2) * inLength); + + // Left edge + LineSegment2 edge; + edge[0] = TOP_LEFT_CLIPPING_WINDOW; + edge[1] = BOTTOM_LEFT_CLIPPING_WINDOW; + // clip the array from tempVertexArrayA and copy end result to tempVertexArrayB + sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge); + // clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA + copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB); + + // Bottom Edge + edge[0] = BOTTOM_LEFT_CLIPPING_WINDOW; + edge[1] = BOTTOM_RIGHT_CLIPPING_WINDOW; + // clip the array from tempVertexArrayA and copy end result to tempVertexArrayB + sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge); + // clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA + copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB); + + // Right Edge + edge[0] = BOTTOM_RIGHT_CLIPPING_WINDOW; + edge[1] = TOP_RIGHT_CLIPPING_WINDOW; + // clip the array from tempVertexArrayA and copy end result to tempVertexArrayB + sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge); + // clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA + copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB); + + // Top Edge + edge[0] = TOP_RIGHT_CLIPPING_WINDOW; + edge[1] = TOP_LEFT_CLIPPING_WINDOW; + // clip the array from tempVertexArrayA and copy end result to tempVertexArrayB + sutherlandHodgmanPolygonClip(tempVertexArrayA, tempVertexArrayB, tempLengthA, tempLengthB, edge); + // clean the array from tempVertexArrayA and copy cleaned result to tempVertexArrayA + copyCleanArray(tempLengthA, tempVertexArrayA, tempLengthB, tempVertexArrayB); + + // copy final output to outputVertexArray + outputVertexArray = tempVertexArrayA; + outLength = tempLengthA; + + // cleanup our unused temporary buffer... + delete[] tempVertexArrayB; + + // Note: we don't delete tempVertexArrayA, because that's the caller's responsibility +} + +void PolygonClip::sutherlandHodgmanPolygonClip(glm::vec2* inVertexArray, glm::vec2* outVertexArray, + int inLength, int& outLength, const LineSegment2& clipBoundary) { + glm::vec2 start, end; // Start, end point of current polygon edge + glm::vec2 intersection; // Intersection point with a clip boundary + + outLength = 0; + start = inVertexArray[inLength - 1]; // Start with the last vertex in inVertexArray + for (int j = 0; j < inLength; j++) { + end = inVertexArray[j]; // Now start and end correspond to the vertices + + // Cases 1 and 4 - the endpoint is inside the boundary + if (pointInsideBoundary(end,clipBoundary)) { + // Case 1 - Both inside + if (pointInsideBoundary(start, clipBoundary)) { + appendPoint(end, outLength, outVertexArray); + } else { // Case 4 - end is inside, but start is outside + segmentIntersectsBoundary(start, end, clipBoundary, intersection); + appendPoint(intersection, outLength, outVertexArray); + appendPoint(end, outLength, outVertexArray); + } + } else { // Cases 2 and 3 - end is outside + if (pointInsideBoundary(start, clipBoundary)) { + // Cases 2 - start is inside, end is outside + segmentIntersectsBoundary(start, end, clipBoundary, intersection); + appendPoint(intersection, outLength, outVertexArray); + } else { + // Case 3 - both are outside, No action + } + } + start = end; // Advance to next pair of vertices + } +} + +bool PolygonClip::pointInsideBoundary(const glm::vec2& testVertex, const LineSegment2& clipBoundary) { + // bottom edge + if (clipBoundary[1].x > clipBoundary[0].x) { + if (testVertex.y >= clipBoundary[0].y) { + return true; + } + } + // top edge + if (clipBoundary[1].x < clipBoundary[0].x) { + if (testVertex.y <= clipBoundary[0].y) { + return true; + } + } + // right edge + if (clipBoundary[1].y > clipBoundary[0].y) { + if (testVertex.x <= clipBoundary[1].x) { + return true; + } + } + // left edge + if (clipBoundary[1].y < clipBoundary[0].y) { + if (testVertex.x >= clipBoundary[1].x) { + return true; + } + } + return false; +} + +void PolygonClip::segmentIntersectsBoundary(const glm::vec2& first, const glm::vec2& second, + const LineSegment2& clipBoundary, glm::vec2& intersection) { + // horizontal + if (clipBoundary[0].y==clipBoundary[1].y) { + intersection.y = clipBoundary[0].y; + intersection.x = first.x + (clipBoundary[0].y - first.y) * (second.x - first.x) / (second.y - first.y); + } else { // Vertical + intersection.x = clipBoundary[0].x; + intersection.y = first.y + (clipBoundary[0].x - first.x) * (second.y - first.y) / (second.x - first.x); + } +} + +void PolygonClip::appendPoint(glm::vec2 newVertex, int& outLength, glm::vec2* outVertexArray) { + outVertexArray[outLength].x = newVertex.x; + outVertexArray[outLength].y = newVertex.y; + outLength++; +} + +// The copyCleanArray() function sets the resulting polygon of the previous step up to be the input polygon for next step of the +// clipping algorithm. As the Sutherland-Hodgman algorithm is a polygon clipping algorithm, it does not handle line +// clipping very well. The modification so that lines may be clipped as well as polygons is included in this function. +// when completed vertexArrayA will be ready for output and/or next step of clipping +void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB) { + // Fix lines: they will come back with a length of 3, from an original of length of 2 + if ((lengthA == 2) && (lengthB == 3)) { + // The first vertex should be copied as is. + vertexArrayA[0] = vertexArrayB[0]; + // If the first two vertices of the "B" array are same, then collapse them down to be the 2nd vertex + if (vertexArrayB[0].x == vertexArrayB[1].x) { + vertexArrayA[1] = vertexArrayB[2]; + } else { + // Otherwise the first vertex should be the same as third vertex + vertexArrayA[1] = vertexArrayB[1]; + } + lengthA=2; + } else { + // for all other polygons, then just copy the vertexArrayB to vertextArrayA for next step + lengthA = lengthB; + for (int i = 0; i < lengthB; i++) { + vertexArrayA[i] = vertexArrayB[i]; + } + } +} diff --git a/libraries/voxels/src/GeometryUtil.h b/libraries/voxels/src/GeometryUtil.h index b16ad4e2c0..bb053d20c6 100644 --- a/libraries/voxels/src/GeometryUtil.h +++ b/libraries/voxels/src/GeometryUtil.h @@ -43,4 +43,39 @@ bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk); int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk); + +typedef glm::vec2 LineSegment2[2]; + +// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html +class PolygonClip { + +public: + static void clipToScreen(const glm::vec2* inputVertexArray, int length, glm::vec2*& outputVertexArray, int& outLength); + + static const float TOP_OF_CLIPPING_WINDOW; + static const float BOTTOM_OF_CLIPPING_WINDOW; + static const float LEFT_OF_CLIPPING_WINDOW; + static const float RIGHT_OF_CLIPPING_WINDOW; + + static const glm::vec2 TOP_LEFT_CLIPPING_WINDOW; + static const glm::vec2 TOP_RIGHT_CLIPPING_WINDOW; + static const glm::vec2 BOTTOM_LEFT_CLIPPING_WINDOW; + static const glm::vec2 BOTTOM_RIGHT_CLIPPING_WINDOW; + +private: + + static void sutherlandHodgmanPolygonClip(glm::vec2* inVertexArray, glm::vec2* outVertexArray, + int inLength, int& outLength, const LineSegment2& clipBoundary); + + static bool pointInsideBoundary(const glm::vec2& testVertex, const LineSegment2& clipBoundary); + + static void segmentIntersectsBoundary(const glm::vec2& first, const glm::vec2& second, + const LineSegment2& clipBoundary, glm::vec2& intersection); + + static void appendPoint(glm::vec2 newVertex, int& outLength, glm::vec2* outVertexArray); + + static void copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB); +}; + + #endif /* defined(__interface__GeometryUtil__) */ diff --git a/libraries/voxels/src/SceneUtils.cpp b/libraries/voxels/src/SceneUtils.cpp index da92734350..bd126ebf31 100644 --- a/libraries/voxels/src/SceneUtils.cpp +++ b/libraries/voxels/src/SceneUtils.cpp @@ -3,12 +3,15 @@ // hifi // // Created by Brad Hefta-Gaub on 5/7/2013. -// +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // -#include "SceneUtils.h" +#include + #include +#include "SceneUtils.h" + void addCornersAndAxisLines(VoxelTree* tree) { // We want our corner voxels to be about 1/2 meter high, and our TREE_SCALE is in meters, so... float voxelSize = 0.5f / TREE_SCALE; diff --git a/libraries/voxels/src/SquarePixelMap.cpp b/libraries/voxels/src/SquarePixelMap.cpp index bdc97624a7..f37bee4757 100644 --- a/libraries/voxels/src/SquarePixelMap.cpp +++ b/libraries/voxels/src/SquarePixelMap.cpp @@ -3,12 +3,15 @@ // hifi // // Created by Tomáš Horáček on 6/25/13. -// +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // -#include "SquarePixelMap.h" -#include +#include +#include #include +#include + +#include "SquarePixelMap.h" #define CHILD_COORD_X_IS_1 0x1 #define CHILD_COORD_Y_IS_1 0x2 diff --git a/libraries/voxels/src/Tags.cpp b/libraries/voxels/src/Tags.cpp index 0cbfa1a37c..8f7dab5390 100644 --- a/libraries/voxels/src/Tags.cpp +++ b/libraries/voxels/src/Tags.cpp @@ -115,8 +115,8 @@ TagCompound::TagCompound(std::stringstream &ss) : _width(0), _length(0), _height(0), - _blocksId(NULL), - _blocksData(NULL) + _blocksData(NULL), + _blocksId(NULL) { int tagId; diff --git a/libraries/voxels/src/ViewFrustum.cpp b/libraries/voxels/src/ViewFrustum.cpp index 4ded457d44..166a2f2126 100644 --- a/libraries/voxels/src/ViewFrustum.cpp +++ b/libraries/voxels/src/ViewFrustum.cpp @@ -12,11 +12,15 @@ #include -#include "ViewFrustum.h" -#include "VoxelConstants.h" #include "SharedUtil.h" #include "Log.h" +#include "CoverageMap.h" +#include "GeometryUtil.h" +#include "ViewFrustum.h" +#include "VoxelConstants.h" + + using namespace std; ViewFrustum::ViewFrustum() : @@ -262,6 +266,7 @@ ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, floa ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const { + ViewFrustum::location regularResult = INSIDE; ViewFrustum::location keyholeResult = OUTSIDE; @@ -274,11 +279,11 @@ ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const { } for(int i=0; i < 6; i++) { - glm::vec3 normal = _planes[i].getNormal(); - glm::vec3 boxVertexP = box.getVertexP(normal); + const glm::vec3& normal = _planes[i].getNormal(); + const glm::vec3& boxVertexP = box.getVertexP(normal); float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP); - glm::vec3 boxVertexN = box.getVertexN(normal); + const glm::vec3& boxVertexN = box.getVertexN(normal); float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN); if (planeToBoxVertexPDistance < 0) { @@ -451,16 +456,22 @@ glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const { const int MAX_POSSIBLE_COMBINATIONS = 43; -const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1] = { +const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERTEX_COUNT+1] = { // Number of vertices in shadow polygon for the visible faces, then a list of the index of each vertice from the AABox + +//0 {0}, // inside {4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR}, // right - {4, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // left + {4, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR }, // left {0}, // n/a - {4, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // bottom - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//bottom, right - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//bottom, left + +//4 + {4, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, // bottom +//5 + {6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR },//bottom, right + {6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, },//bottom, left {0}, // n/a +//8 {4, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top {6, TOP_RIGHT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // top, right {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // top, left @@ -469,32 +480,52 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_SHADOW_VERTEX_COUNT+1] {0}, // n/a {0}, // n/a {0}, // n/a - {4, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front or near - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, right - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, left +//16 + {4, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front or near + + {6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front, right + {6, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, }, // front, left {0}, // n/a - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // front,bottom - {6, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR}, //front,bottom,right - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, //front,bottom,left +//20 + {6, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, // front,bottom + +//21 + {6, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR }, //front,bottom,right +//22 + {6, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR }, //front,bottom,left {0}, // n/a - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR}, // front, top, right - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // front, top, left + + {6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR}, // front, top + + {6, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR }, // front, top, right + + {6, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR }, // front, top, left {0}, // n/a {0}, // n/a {0}, // n/a {0}, // n/a {0}, // n/a - {4, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, // back, right - {6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR}, // back, left +//32 + {4, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR }, // back + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR}, // back, right +//34 + {6, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, TOP_RIGHT_FAR }, // back, left + + {0}, // n/a - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR}, // back, bottom - {6, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR},//back, bottom, right - {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR},//back, bottom, left +//36 + {6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR}, // back, bottom + {6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR},//back, bottom, right + +// 38 + {6, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR },//back, bottom, left {0}, // n/a - {6, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR, TOP_LEFT_FAR, BOTTOM_LEFT_FAR}, // back, top + +// 40 + {6, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR, TOP_RIGHT_FAR}, // back, top + {6, BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, TOP_LEFT_FAR, TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, // back, top, right +//42 {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left }; @@ -508,9 +539,11 @@ VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining + ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes - int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices + //printLog(">>>>>>>>> ViewFrustum::getProjectedPolygon() lookup=%d\n",lookUp); - VoxelProjectedPolygon shadow(vertexCount); + int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices + + VoxelProjectedPolygon projectedPolygon(vertexCount); bool pointInView = true; bool allPointsInView = false; // assume the best, but wait till we know we have a vertex @@ -523,13 +556,40 @@ VoxelProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { glm::vec2 projectedPoint = projectPoint(point, pointInView); allPointsInView = allPointsInView && pointInView; anyPointsInView = anyPointsInView || pointInView; - shadow.setVertex(i, projectedPoint); + projectedPolygon.setVertex(i, projectedPoint); } + + /*** + // Now that we've got the polygon, if it extends beyond the clipping window, then let's clip it + // NOTE: This clipping does not improve our overall performance. It basically causes more polygons to + // end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes() + if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { + + CoverageRegion::_clippedPolygons++; + + glm::vec2* clippedVertices; + int clippedVertexCount; + PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); + + // Now reset the vertices of our projectedPolygon object + projectedPolygon.setVertexCount(clippedVertexCount); + for(int i = 0; i < clippedVertexCount; i++) { + projectedPolygon.setVertex(i, clippedVertices[i]); + } + delete[] clippedVertices; + + lookUp += PROJECTION_CLIPPED; + } + ***/ } // set the distance from our camera position, to the closest vertex float distance = glm::distance(getPosition(), box.getCenter()); - shadow.setDistance(distance); - shadow.setAnyInView(anyPointsInView); - shadow.setAllInView(allPointsInView); - return shadow; + projectedPolygon.setDistance(distance); + projectedPolygon.setAnyInView(anyPointsInView); + projectedPolygon.setAllInView(allPointsInView); + projectedPolygon.setProjectionType(lookUp); // remember the projection type + return projectedPolygon; } diff --git a/libraries/voxels/src/VoxelConstants.h b/libraries/voxels/src/VoxelConstants.h index d0ae6f538a..30a0427292 100644 --- a/libraries/voxels/src/VoxelConstants.h +++ b/libraries/voxels/src/VoxelConstants.h @@ -34,7 +34,7 @@ const int COLOR_VALUES_PER_VOXEL = NUMBER_OF_COLORS * VERTICES_PER_VOXEL; typedef unsigned long int glBufferIndex; const glBufferIndex GLBUFFER_INDEX_UNKNOWN = ULONG_MAX; -const double SIXTY_FPS_IN_MILLISECONDS = 1000.0/60; -const double VIEW_CULLING_RATE_IN_MILLISECONDS = 1000.0; // once a second is fine +const float SIXTY_FPS_IN_MILLISECONDS = 1000.0f / 60.0f; +const float VIEW_CULLING_RATE_IN_MILLISECONDS = 1000.0f; // once a second is fine #endif diff --git a/libraries/voxels/src/VoxelNode.h b/libraries/voxels/src/VoxelNode.h index 4aa64f019e..a92c562a7e 100644 --- a/libraries/voxels/src/VoxelNode.h +++ b/libraries/voxels/src/VoxelNode.h @@ -29,7 +29,7 @@ private: #endif glBufferIndex _glBufferIndex; bool _isDirty; - long long _lastChanged; + uint64_t _lastChanged; bool _shouldRender; bool _isStagedForDeletion; AABox _box; @@ -62,7 +62,7 @@ public: const glm::vec3& getCenter() const { return _box.getCenter(); }; const glm::vec3& getCorner() const { return _box.getCorner(); }; float getScale() const { return _box.getSize().x; /* voxelScale = (1 / powf(2, *node->getOctalCode())); */ }; - int getLevel() const { return *_octalCode + 1; /* one based or zero based? */ }; + int getLevel() const { return *_octalCode + 1; /* one based or zero based? this doesn't correctly handle 2 byte case */ }; float getEnclosingRadius() const; @@ -80,7 +80,7 @@ public: void printDebugDetails(const char* label) const; bool isDirty() const { return _isDirty; }; void clearDirtyBit() { _isDirty = false; }; - bool hasChangedSince(long long time) const { return (_lastChanged > time); }; + bool hasChangedSince(uint64_t time) const { return (_lastChanged > time); }; void markWithChangedTime() { _lastChanged = usecTimestampNow(); }; void handleSubtreeChanged(VoxelTree* myTree); diff --git a/libraries/voxels/src/VoxelProjectedPolygon.cpp b/libraries/voxels/src/VoxelProjectedPolygon.cpp index c609b27df4..95c66b6821 100644 --- a/libraries/voxels/src/VoxelProjectedPolygon.cpp +++ b/libraries/voxels/src/VoxelProjectedPolygon.cpp @@ -5,10 +5,26 @@ // Added by Brad Hefta-Gaub on 06/11/13. // +#include #include "VoxelProjectedPolygon.h" #include "GeometryUtil.h" #include "Log.h" +#include "SharedUtil.h" +glm::vec2 BoundingBox::getVertex(int vertexNumber) const { + switch (vertexNumber) { + case BoundingBox::BOTTOM_LEFT: + return corner; + case BoundingBox::TOP_LEFT: + return glm::vec2(corner.x, corner.y + size.y); + case BoundingBox::BOTTOM_RIGHT: + return glm::vec2(corner.x + size.x, corner.y); + case BoundingBox::TOP_RIGHT: + return corner + size; + } + assert(false); // not allowed + return glm::vec2(0,0); +} BoundingBox BoundingBox::topHalf() const { float halfY = size.y/2.0f; @@ -35,7 +51,7 @@ BoundingBox BoundingBox::rightHalf() const { } bool BoundingBox::contains(const BoundingBox& box) const { - return ( + return ( _set && (box.corner.x >= corner.x) && (box.corner.y >= corner.y) && (box.corner.x + box.size.x <= corner.x + size.x) && @@ -43,13 +59,57 @@ bool BoundingBox::contains(const BoundingBox& box) const { ); }; +bool BoundingBox::contains(const glm::vec2& point) const { + return ( _set && + (point.x > corner.x) && + (point.y > corner.y) && + (point.x < corner.x + size.x) && + (point.y < corner.y + size.y) + ); +}; + +void BoundingBox::explandToInclude(const BoundingBox& box) { + if (!_set) { + corner = box.corner; + size = box.size; + _set = true; + } else { + float minX = std::min(box.corner.x, corner.x); + float minY = std::min(box.corner.y, corner.y); + float maxX = std::max(box.corner.x + box.size.x, corner.x + size.x); + float maxY = std::max(box.corner.y + box.size.y, corner.y + size.y); + corner.x = minX; + corner.y = minY; + size.x = maxX - minX; + size.y = maxY - minY; + } +} + + void BoundingBox::printDebugDetails(const char* label) const { if (label) { printLog(label); } else { printLog("BoundingBox"); } - printLog("\n corner=%f,%f size=%f,%f\n", corner.x, corner.y, size.x, size.y); + printLog("\n _set=%s\n corner=%f,%f size=%f,%f\n bounds=[(%f,%f) to (%f,%f)]\n", + debug::valueOf(_set), corner.x, corner.y, size.x, size.y, corner.x, corner.y, corner.x+size.x, corner.y+size.y); +} + + +long VoxelProjectedPolygon::pointInside_calls = 0; +long VoxelProjectedPolygon::occludes_calls = 0; +long VoxelProjectedPolygon::intersects_calls = 0; + + +VoxelProjectedPolygon::VoxelProjectedPolygon(const BoundingBox& box) : + _vertexCount(4), + _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), + _distance(0) +{ + for (int i = 0; i < _vertexCount; i++) { + setVertex(i, box.getVertex(i)); + } } @@ -72,7 +132,10 @@ void VoxelProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { }; +// can be optimized with new pointInside() bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView) const { + + VoxelProjectedPolygon::occludes_calls++; // if we are completely out of view, then we definitely don't occlude! // if the occludee is completely out of view, then we also don't occlude it @@ -92,20 +155,87 @@ bool VoxelProjectedPolygon::occludes(const VoxelProjectedPolygon& occludee, bool return false; } + // we need to test for identity as well, because in the case of identity, none of the points + // will be "inside" but we don't want to bail early on the first non-inside point + bool potentialIdenity = false; + if ((occludee.getVertexCount() == getVertexCount()) && (getBoundingBox().contains(occludee.getBoundingBox())) ) { + potentialIdenity = true; + } // if we got this far, then check each vertex of the occludee, if all those points // are inside our polygon, then the tested occludee is fully occluded + int pointsInside = 0; for(int i = 0; i < occludee.getVertexCount(); i++) { - if (!pointInside(occludee.getVertex(i))) { - return false; + bool vertexMatched = false; + if (!pointInside(occludee.getVertex(i), &vertexMatched)) { + + // so the point we just tested isn't inside, but it might have matched a vertex + // if it didn't match a vertext, then we bail because we can't be an identity + // or if we're not expecting identity, then we also bail early, no matter what + if (!potentialIdenity || !vertexMatched) { + return false; + } + } else { + pointsInside++; } } - // if we got this far, then indeed the occludee is fully occluded by us - return true; + // we're only here if all points are inside matched and/or we had a potentialIdentity we need to check + if (pointsInside == occludee.getVertexCount()) { + return true; + } + + // If we have the potential for identity, then test to see if we match, if we match, we occlude + if (potentialIdenity) { + return matches(occludee); + } + + return false; // if we got this far, then we're not occluded } -bool VoxelProjectedPolygon::pointInside(const glm::vec2& point) const { - // first check the bounding boxes, the point must be fully within the boounding box of this shadow +bool VoxelProjectedPolygon::occludes(const BoundingBox& boxOccludee) const { + VoxelProjectedPolygon testee(boxOccludee); + return occludes(testee); +} + +bool VoxelProjectedPolygon::matches(const VoxelProjectedPolygon& testee) const { + if (testee.getVertexCount() != getVertexCount()) { + return false; + } + int vertextCount = getVertexCount(); + // find which testee vertex matches our first polygon vertex. + glm::vec2 polygonVertex = getVertex(0); + int originIndex = 0; + for(int i = 0; i < vertextCount; i++) { + glm::vec2 testeeVertex = testee.getVertex(i); + + // if they match, we found our origin. + if (testeeVertex == polygonVertex) { + originIndex = i; + break; + } + } + // Now, starting at the originIndex, walk the vertices of both the testee and ourselves + + for(int i = 0; i < vertextCount; i++) { + glm::vec2 testeeVertex = testee.getVertex((i + originIndex) % vertextCount); + glm::vec2 polygonVertex = getVertex(i); + if (testeeVertex != polygonVertex) { + return false; // we don't match, therefore we're not the same + } + } + return true; // all of our vertices match, therefore we're the same +} + +bool VoxelProjectedPolygon::matches(const BoundingBox& box) const { + VoxelProjectedPolygon testee(box); + return matches(testee); +} + +bool VoxelProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const { + + VoxelProjectedPolygon::pointInside_calls++; + + // first check the bounding boxes, the point must be fully within the boounding box of this polygon if ((point.x > getMaxX()) || (point.y > getMaxY()) || (point.x < getMinX()) || @@ -113,30 +243,22 @@ bool VoxelProjectedPolygon::pointInside(const glm::vec2& point) const { return false; } - float e = (getMaxX() - getMinX()) / 100.0f; // some epsilon - - // We need to have one ray that goes from a known outside position to the point in question. We'll pick a - // start point just outside of our min X - glm::vec2 r1p1(getMinX() - e, point.y); - glm::vec2 r1p2(point); - - glm::vec2 r2p1(getVertex(getVertexCount()-1)); // start with last vertex to first vertex - glm::vec2 r2p2; - - // Test the ray against all sides - int intersections = 0; + // consider each edge of this polygon as a potential separating axis + // check the point against each edge for (int i = 0; i < getVertexCount(); i++) { - r2p2 = getVertex(i); - if (doLineSegmentsIntersect(r1p1, r1p2, r2p1, r2p2)) { - intersections++; + glm::vec2 start = getVertex(i); + glm::vec2 end = getVertex((i + 1) % getVertexCount()); + float a = start.y - end.y; + float b = end.x - start.x; + float c = a * start.x + b * start.y; + if (a * point.x + b * point.y < c) { + return false; } - r2p1 = r2p2; // set up for next side } - - // If odd number of intersections, we're inside - return ((intersections & 1) == 1); + + return true; } - + void VoxelProjectedPolygon::printDebugDetails() const { printf("VoxelProjectedPolygon..."); printf(" minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY()); @@ -147,4 +269,915 @@ void VoxelProjectedPolygon::printDebugDetails() const { } } +bool VoxelProjectedPolygon::intersects(const BoundingBox& box) const { + VoxelProjectedPolygon testee(box); + return intersects(testee); +} +bool VoxelProjectedPolygon::intersects(const VoxelProjectedPolygon& testee) const { + VoxelProjectedPolygon::intersects_calls++; + return intersectsOnAxes(testee) && testee.intersectsOnAxes(*this); +} + +// +// Tests the edges of this polygon as potential separating axes for this polygon and the +// specified other. +// +// @return false if the polygons are disjoint on any of this polygon's axes, true if they +// intersect on all axes. +// +// Note: this only works on convex polygons +// +// +bool VoxelProjectedPolygon::intersectsOnAxes(const VoxelProjectedPolygon& testee) const { + + // consider each edge of this polygon as a potential separating axis + for (int i = 0; i < getVertexCount(); i++) { + glm::vec2 start = getVertex(i); + glm::vec2 end = getVertex((i + 1) % getVertexCount()); + float a = start.y - end.y; + float b = end.x - start.x; + float c = a * start.x + b * start.y; + + // if all vertices fall outside the edge, the polygons are disjoint + // points that are ON the edge, are considered to be "outside" + for (int j = 0; j < testee.getVertexCount(); j++) { + glm::vec2 testeeVertex = testee.getVertex(j); + + // in comparison below: + // >= will cause points on edge to be considered inside + // > will cause points on edge to be considered outside + + float c2 = a * testeeVertex.x + b * testeeVertex.y; + if (c2 >= c) { + goto CONTINUE_OUTER; + } + } + return false; + CONTINUE_OUTER: ; + } + return true; +} + +bool VoxelProjectedPolygon::canMerge(const VoxelProjectedPolygon& that) const { + + // RIGHT/NEAR + // LEFT/NEAR + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR) + ) + ) { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(5)) { + return true; + } + if (getVertex(0) == that.getVertex(1) && getVertex(5) == that.getVertex(4)) { + return true; + } + if (getVertex(2) == that.getVertex(1) && getVertex(3) == that.getVertex(4)) { + return true; + } + if (getVertex(1) == that.getVertex(2) && getVertex(4) == that.getVertex(3)) { + return true; + } + } + + // NEAR/BOTTOM + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(3) == that.getVertex(4)) { + return true; + } + if (getVertex(5) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + return true; + } + if (getVertex(1) == that.getVertex(0) && getVertex(2) == that.getVertex(3)) { + return true; + } + if (getVertex(0) == that.getVertex(1) && getVertex(3) == that.getVertex(2)) { + return true; + } + } + + // NEAR/TOP + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(1) == that.getVertex(2)) { + return true; + } + if (getVertex(5) == that.getVertex(0) && getVertex(2) == that.getVertex(1)) { + return true; + } + if (getVertex(4) == that.getVertex(5) && getVertex(3) == that.getVertex(2)) { + return true; + } + if (getVertex(5) == that.getVertex(4) && getVertex(2) == that.getVertex(3)) { + return true; + } + } + + // RIGHT/NEAR & NEAR/RIGHT/TOP + // LEFT/NEAR & NEAR/LEFT/TOP + if ( + ((getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP)) && + (that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP)) && + (that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + return true; + } + } + // RIGHT/NEAR & NEAR/RIGHT/TOP + // LEFT/NEAR & NEAR/LEFT/TOP + if ( + ((that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP)) && + (getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP)) && + (getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + return true; + } + } + + // RIGHT/NEAR & NEAR/RIGHT/BOTTOM + // NEAR/LEFT & NEAR/LEFT/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + return true; + } + } + // RIGHT/NEAR & NEAR/RIGHT/BOTTOM + // NEAR/LEFT & NEAR/LEFT/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + return true; + } + } + + // NEAR/TOP & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP )) + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(1) == that.getVertex(2)) { + return true; + } + } + + // NEAR/TOP & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP )) + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(2) == that.getVertex(1)) { + return true; + } + } + + // NEAR/BOTTOM & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM )) + ) + { + if (getVertex(2) == that.getVertex(3) && getVertex(3) == that.getVertex(0)) { + return true; + } + } + + // NEAR/BOTTOM & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM )) + ) + { + if (getVertex(3) == that.getVertex(2) && getVertex(0) == that.getVertex(3)) { + return true; + } + } + + // NEAR/RIGHT & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT )) + ) + { + if (getVertex(0) == that.getVertex(1) && getVertex(3) == that.getVertex(4)) { + return true; + } + } + + // NEAR/RIGHT & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT )) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + return true; + } + } + + // NEAR/LEFT & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT )) + ) + { + if (getVertex(1) == that.getVertex(1) && getVertex(2) == that.getVertex(4)) { + return true; + } + } + + // NEAR/LEFT & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT )) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + return true; + } + } + + // NEAR/RIGHT/TOP & NEAR/TOP + if ( + ((getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(0) == that.getVertex(1) && getVertex(4) == that.getVertex(3)) { + return true; + } + } + + // NEAR/RIGHT/TOP & NEAR/TOP + if ( + ((that.getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(3) == that.getVertex(4)) { + return true; + } + } + + + // NEAR/RIGHT/BOTTOM & NEAR/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(1) == that.getVertex(2) && getVertex(5) == that.getVertex(4)) { + return true; + } + } + + // NEAR/RIGHT/BOTTOM & NEAR/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(2) == that.getVertex(1) && getVertex(4) == that.getVertex(5)) { + return true; + } + } + + // NEAR/LEFT/BOTTOM & NEAR/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_LEFT ))) + ) + { + if (getVertex(2) == that.getVertex(0) && getVertex(4) == that.getVertex(4)) { + return true; + } + } + + // NEAR/LEFT/BOTTOM & NEAR/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_LEFT ))) + ) + { + if (getVertex(0) == that.getVertex(2) && getVertex(4) == that.getVertex(4)) { + return true; + } + } + // RIGHT/NEAR/BOTTOM + // RIGHT/NEAR/TOP + // LEFT/NEAR/BOTTOM + // LEFT/NEAR/TOP + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM ) || + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP ) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM ) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP ) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + return true; + } + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + return true; + } + if (getVertex(2) == that.getVertex(1) && getVertex(4) == that.getVertex(5)) { + return true; + } + if (getVertex(1) == that.getVertex(2) && getVertex(5) == that.getVertex(4)) { + return true; + } + if (getVertex(1) == that.getVertex(0) && getVertex(3) == that.getVertex(4)) { + return true; + } + if (getVertex(0) == that.getVertex(1) && getVertex(4) == that.getVertex(3)) { + return true; + } + } + + return false; +} + + +void VoxelProjectedPolygon::merge(const VoxelProjectedPolygon& that) { + + // RIGHT/NEAR + // LEFT/NEAR + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR) + ) + ) { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(5)) { + //setVertex(0, this.getVertex(0)); // no change + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + //setVertex(5, this.getVertex(5)); // no change + return; // done + } + if (getVertex(0) == that.getVertex(1) && getVertex(5) == that.getVertex(4)) { + setVertex(0, that.getVertex(0)); + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, that.getVertex(4)); // no change + setVertex(5, that.getVertex(5)); + return; // done + } + if (getVertex(2) == that.getVertex(1) && getVertex(3) == that.getVertex(4)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, that.getVertex(5)); // no change + return; // done + } + if (getVertex(1) == that.getVertex(2) && getVertex(4) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, that.getVertex(3)); // no change + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + } + + // NEAR/BOTTOM + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(3) == that.getVertex(4)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, that.getVertex(5)); // no change + return; // done + } + if (getVertex(5) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, that.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, that.getVertex(3)); // no change + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + if (getVertex(1) == that.getVertex(0) && getVertex(2) == that.getVertex(3)) { + //setVertex(0, this.getVertex(0)); // no change + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + //setVertex(3, that.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, that.getVertex(5)); // no change + return; // done + } + if (getVertex(0) == that.getVertex(1) && getVertex(3) == that.getVertex(2)) { + setVertex(0, that.getVertex(0)); + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, that.getVertex(2)); // no change + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + } + + // NEAR/TOP + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(1) == that.getVertex(2)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, that.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, that.getVertex(5)); // no change + return; // done + } + if (getVertex(5) == that.getVertex(0) && getVertex(2) == that.getVertex(1)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, that.getVertex(1)); // no change + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + if (getVertex(4) == that.getVertex(5) && getVertex(3) == that.getVertex(2)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, that.getVertex(1)); // no change + //setVertex(2, that.getVertex(2)); // no change + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + //setVertex(5, that.getVertex(5)); // no change + return; // done + } + if (getVertex(5) == that.getVertex(4) && getVertex(2) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, that.getVertex(3)); // no change + setVertex(5, that.getVertex(5)); + return; // done + } + } + + + // RIGHT/NEAR & NEAR/RIGHT/TOP + // LEFT/NEAR & NEAR/LEFT/TOP + if ( + ((getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP)) && + (that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP)) && + (that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + setProjectionType((PROJECTION_RIGHT | PROJECTION_NEAR)); + return; // done + } + } + + // RIGHT/NEAR & NEAR/RIGHT/TOP + // LEFT/NEAR & NEAR/LEFT/TOP + if ( + ((that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP)) && + (getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP)) && + (getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, this.getVertex(5)); // no change + //setProjectionType((PROJECTION_RIGHT | PROJECTION_NEAR)); // no change + return; // done + } + } + + // RIGHT/NEAR & NEAR/RIGHT/BOTTOM + // NEAR/LEFT & NEAR/LEFT/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + //setProjectionType((PROJECTION_RIGHT | PROJECTION_NEAR)); // no change + return; // done + } + } + // RIGHT/NEAR & NEAR/RIGHT/BOTTOM + // NEAR/LEFT & NEAR/LEFT/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (that.getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR))) + || + ((getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM)) && + (that.getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR))) + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, this.getVertex(5)); // no change + setProjectionType((PROJECTION_RIGHT | PROJECTION_NEAR)); + return; // done + } + } + + + // NEAR/TOP & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP )) + ) + { + if (getVertex(0) == that.getVertex(5) && getVertex(1) == that.getVertex(2)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + //setVertexCount(4); // no change + //setProjectionType((PROJECTION_NEAR)); // no change + return; // done + } + } + + // NEAR/TOP & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_TOP )) + ) + { + if (getVertex(5) == that.getVertex(0) && getVertex(2) == that.getVertex(1)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + setVertexCount(4); + setProjectionType((PROJECTION_NEAR)); + return; // done + } + } + + // NEAR/BOTTOM & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM )) + ) + { + if (getVertex(2) == that.getVertex(3) && getVertex(3) == that.getVertex(0)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(4)); + setVertex(3, that.getVertex(5)); + //setVertexCount(4); // no change + //setProjectionType((PROJECTION_NEAR)); // no change + } + } + + // NEAR/BOTTOM & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_BOTTOM )) + ) + { + if (getVertex(3) == that.getVertex(2) && getVertex(0) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, getVertex(4)); + setVertex(3, getVertex(5)); + setVertexCount(4); + setProjectionType((PROJECTION_NEAR)); + return; // done + } + } + + // NEAR/RIGHT & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT )) + ) + { + if (getVertex(0) == that.getVertex(1) && getVertex(3) == that.getVertex(4)) { + setVertex(0, that.getVertex(0)); + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + setVertex(3, that.getVertex(5)); + //setVertexCount(4); // no change + //setProjectionType((PROJECTION_NEAR)); // no change + } + } + + // NEAR/RIGHT & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_RIGHT )) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + //setVertex(0, this.getVertex(0)); // no change + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + setVertex(3, getVertex(5)); + setVertexCount(4); + setProjectionType((PROJECTION_NEAR)); + return; // done + } + } + + // NEAR/LEFT & NEAR + if ( + (getProjectionType() == (PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT )) + ) + { + if (getVertex(1) == that.getVertex(1) && getVertex(2) == that.getVertex(4)) { + //setVertex(0, this.getVertex()); // no change + setVertex(1, that.getVertex(2)); + setVertex(2, that.getVertex(3)); + //setVertex(3, this.getVertex(3)); // no change + //setVertexCount(4); // no change + //setProjectionType((PROJECTION_NEAR)); // no change + return; // done + } + } + + // NEAR/LEFT & NEAR + if ( + (that.getProjectionType() == (PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_NEAR | PROJECTION_LEFT )) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(4) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, getVertex(2)); + setVertex(2, getVertex(3)); + setVertex(3, that.getVertex(3)); + setVertexCount(4); + setProjectionType((PROJECTION_NEAR)); + return; // done + } + } + + // NEAR/RIGHT/TOP & NEAR/TOP + if ( + ((getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(0) == that.getVertex(1) && getVertex(4) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + } + + // NEAR/RIGHT/TOP & NEAR/TOP + if ( + ((that.getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_TOP | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(1) == that.getVertex(0) && getVertex(3) == that.getVertex(4)) { + //setVertex(0, this.getVertex(0)); // no change + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, this.getVertex(5)); // no change + setProjectionType((PROJECTION_TOP | PROJECTION_NEAR)); + return; // done + } + } + + + // NEAR/RIGHT/BOTTOM & NEAR/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(1) == that.getVertex(2) && getVertex(5) == that.getVertex(4)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + setVertex(5, that.getVertex(5)); + return; // done + } + } + + // NEAR/RIGHT/BOTTOM & NEAR/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_RIGHT ))) + ) + { + if (getVertex(2) == that.getVertex(1) && getVertex(4) == that.getVertex(5)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + //setVertex(5, this.getVertex(5)); // no change + setProjectionType((PROJECTION_BOTTOM | PROJECTION_NEAR)); + return; // done + } + } + + // NEAR/LEFT/BOTTOM & NEAR/BOTTOM + if ( + ((getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_LEFT ))) + ) + { + if (getVertex(2) == that.getVertex(0) && getVertex(4) == that.getVertex(4)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(1)); + setVertex(3, that.getVertex(2)); + setVertex(4, that.getVertex(3)); + //setVertex(5, this.getVertex(5)); // no change + return; // done + } + } + + // NEAR/LEFT/BOTTOM & NEAR/BOTTOM + if ( + ((that.getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR )) && + (getProjectionType() == (PROJECTION_BOTTOM | PROJECTION_NEAR | PROJECTION_LEFT ))) + ) + { + if (getVertex(0) == that.getVertex(2) && getVertex(4) == that.getVertex(4)) { + // we need to do this in an unusual order, because otherwise we'd overwrite our own values + setVertex(4, getVertex(3)); + setVertex(3, getVertex(2)); + setVertex(2, getVertex(1)); + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(5, that.getVertex(5)); + setProjectionType((PROJECTION_BOTTOM | PROJECTION_NEAR)); + return; // done + } + } + + + // RIGHT/NEAR/BOTTOM + // RIGHT/NEAR/TOP + // LEFT/NEAR/BOTTOM + // LEFT/NEAR/TOP + if ( + (getProjectionType() == that.getProjectionType()) && + ( + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_BOTTOM ) || + getProjectionType() == (PROJECTION_RIGHT | PROJECTION_NEAR | PROJECTION_TOP ) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_BOTTOM ) || + getProjectionType() == (PROJECTION_LEFT | PROJECTION_NEAR | PROJECTION_TOP ) + ) + ) { + if (getVertex(0) == that.getVertex(5) && getVertex(2) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, this.getVertex(5)); // no change + return; // done + } + if (getVertex(5) == that.getVertex(0) && getVertex(3) == that.getVertex(2)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + if (getVertex(2) == that.getVertex(1) && getVertex(4) == that.getVertex(5)) { + //setVertex(0, this.getVertex(0)); // no change + //setVertex(1, this.getVertex(1)); // no change + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + setVertex(4, that.getVertex(4)); + //setVertex(5, this.getVertex(5)); // no change + return; // done + } + if (getVertex(1) == that.getVertex(2) && getVertex(5) == that.getVertex(4)) { + setVertex(0, that.getVertex(0)); + setVertex(1, that.getVertex(1)); + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + //setVertex(4, this.getVertex(4)); // no change + setVertex(5, that.getVertex(5)); + return; // done + } + // if this.([1],[3]) == that.([0],[4]) then create polygon: this.[0], that.[1], that.[2], that.[3], this.[4], this.[5] + if (getVertex(1) == that.getVertex(0) && getVertex(3) == that.getVertex(4)) { + //setVertex(0, this.getVertex(0)); // no change + setVertex(1, that.getVertex(1)); + setVertex(2, that.getVertex(2)); + setVertex(3, that.getVertex(3)); + //setVertex(4, this.getVertex(4)); // no change + //setVertex(5, this.getVertex(5)); // no change + return; // done + } + // if this.([0],[4]) == that.([1],[3]) then create polygon: that.[0], this.[1], this.[2], this.[3], that.[4], that.[5] + if (getVertex(0) == that.getVertex(1) && getVertex(4) == that.getVertex(3)) { + setVertex(0, that.getVertex(0)); + //setVertex(1, this.getVertex(1)); // no change + //setVertex(2, this.getVertex(2)); // no change + //setVertex(3, this.getVertex(3)); // no change + setVertex(4, that.getVertex(4)); + setVertex(5, that.getVertex(5)); + return; // done + } + } + +} diff --git a/libraries/voxels/src/VoxelProjectedPolygon.h b/libraries/voxels/src/VoxelProjectedPolygon.h index 8123ca6a85..5846b3d34d 100644 --- a/libraries/voxels/src/VoxelProjectedPolygon.h +++ b/libraries/voxels/src/VoxelProjectedPolygon.h @@ -10,29 +10,58 @@ #include -const int MAX_SHADOW_VERTEX_COUNT = 6; - -typedef glm::vec2 ShadowVertices[MAX_SHADOW_VERTEX_COUNT]; +// there's a max of 6 vertices of a project polygon, and a max of twice that when clipped to the screen +const int MAX_PROJECTED_POLYGON_VERTEX_COUNT = 6; +const int MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT = MAX_PROJECTED_POLYGON_VERTEX_COUNT * 2; +typedef glm::vec2 ProjectedVertices[MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT]; class BoundingBox { public: - BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size) {}; + enum { BOTTOM_LEFT, BOTTOM_RIGHT, TOP_RIGHT, TOP_LEFT, VERTEX_COUNT }; + + BoundingBox(glm::vec2 corner, glm::vec2 size) : corner(corner), size(size), _set(true) {}; + BoundingBox() : _set(false) {}; glm::vec2 corner; glm::vec2 size; bool contains(const BoundingBox& box) const; + bool contains(const glm::vec2& point) const; + bool pointInside(const glm::vec2& point) const { return contains(point); }; + + void explandToInclude(const BoundingBox& box); + float area() const { return size.x * size.y; }; + int getVertexCount() const { return VERTEX_COUNT; }; + glm::vec2 getVertex(int vertexNumber) const; + BoundingBox topHalf() const; BoundingBox bottomHalf() const; BoundingBox leftHalf() const; BoundingBox rightHalf() const; + + float getMaxX() const { return corner.x + size.x; } + float getMaxY() const { return corner.y + size.y; } + float getMinX() const { return corner.x; } + float getMinY() const { return corner.y; } void printDebugDetails(const char* label=NULL) const; +private: + bool _set; }; +const int PROJECTION_RIGHT = 1; +const int PROJECTION_LEFT = 2; +const int PROJECTION_BOTTOM = 4; +const int PROJECTION_TOP = 8; +const int PROJECTION_NEAR = 16; +const int PROJECTION_FAR = 32; +const int PROJECTION_CLIPPED = 64; + class VoxelProjectedPolygon { public: + VoxelProjectedPolygon(const BoundingBox& box); + VoxelProjectedPolygon(int vertexCount = 0) : _vertexCount(vertexCount), _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), @@ -40,22 +69,33 @@ public: { }; ~VoxelProjectedPolygon() { }; - const ShadowVertices& getVerices() const { return _vertices; }; + const ProjectedVertices& getVertices() const { return _vertices; }; const glm::vec2& getVertex(int i) const { return _vertices[i]; }; void setVertex(int vertex, const glm::vec2& point); - int getVertexCount() const { return _vertexCount; }; - void setVertexCount(int vertexCount) { _vertexCount = vertexCount; }; - float getDistance() const { return _distance; } - void setDistance(float distance) { _distance = distance; } + int getVertexCount() const { return _vertexCount; }; + void setVertexCount(int vertexCount) { _vertexCount = vertexCount; }; + float getDistance() const { return _distance; } + void setDistance(float distance) { _distance = distance; } + bool getAnyInView() const { return _anyInView; }; + void setAnyInView(bool anyInView) { _anyInView = anyInView; }; + bool getAllInView() const { return _allInView; }; + void setAllInView(bool allInView) { _allInView = allInView; }; + void setProjectionType(unsigned char type) { _projectionType = type; }; + unsigned char getProjectionType() const { return _projectionType; }; - bool getAnyInView() const { return _anyInView; }; - void setAnyInView(bool anyInView) { _anyInView = anyInView; }; - bool getAllInView() const { return _allInView; }; - void setAllInView(bool allInView) { _allInView = allInView; }; + bool pointInside(const glm::vec2& point, bool* matchesVertex = NULL) const; bool occludes(const VoxelProjectedPolygon& occludee, bool checkAllInView = false) const; - bool pointInside(const glm::vec2& point) const; + bool occludes(const BoundingBox& occludee) const; + bool intersects(const VoxelProjectedPolygon& testee) const; + bool intersects(const BoundingBox& box) const; + bool matches(const VoxelProjectedPolygon& testee) const; + bool matches(const BoundingBox& testee) const; + bool intersectsOnAxes(const VoxelProjectedPolygon& testee) const; + + bool canMerge(const VoxelProjectedPolygon& that) const; + void merge(const VoxelProjectedPolygon& that); // replaces vertices of this with new merged version float getMaxX() const { return _maxX; } float getMaxY() const { return _maxY; } @@ -67,10 +107,14 @@ public: }; void printDebugDetails() const; + + static long pointInside_calls; + static long occludes_calls; + static long intersects_calls; private: int _vertexCount; - ShadowVertices _vertices; + ProjectedVertices _vertices; float _maxX; float _maxY; float _minX; @@ -78,6 +122,7 @@ private: float _distance; bool _anyInView; // if any points are in view bool _allInView; // if all points are in view + unsigned char _projectionType; }; diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 207573b860..d2ea383b8e 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -29,12 +29,16 @@ #include - -int boundaryDistanceForRenderLevel(unsigned int renderLevel) { - float voxelSizeScale = 50000.0f; +float boundaryDistanceForRenderLevel(unsigned int renderLevel) { + const float voxelSizeScale = 50000.0f; return voxelSizeScale / powf(2, renderLevel); } +float boundaryDistanceSquaredForRenderLevel(unsigned int renderLevel) { + const float voxelSizeScale = (50000.0f/TREE_SCALE) * (50000.0f/TREE_SCALE); + return voxelSizeScale / powf(2, (2 * renderLevel)); +} + VoxelTree::VoxelTree(bool shouldReaverage) : voxelsCreated(0), voxelsColored(0), @@ -55,6 +59,61 @@ VoxelTree::~VoxelTree() { } } + +void VoxelTree::recurseTreeWithOperationDistanceSortedTimed(PointerStack* stackOfNodes, long allowedTime, + RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData) { + + long long start = usecTimestampNow(); + + // start case, stack empty, so start with root... + if (stackOfNodes->empty()) { + stackOfNodes->push(rootNode); + } + while (!stackOfNodes->empty()) { + VoxelNode* node = (VoxelNode*)stackOfNodes->top(); + stackOfNodes->pop(); + + if (operation(node, extraData)) { + + //sortChildren... CLOSEST to FURTHEST + // determine the distance sorted order of our children + VoxelNode* sortedChildren[NUMBER_OF_CHILDREN]; + float distancesToChildren[NUMBER_OF_CHILDREN]; + int indexOfChildren[NUMBER_OF_CHILDREN]; // not really needed + int currentCount = 0; + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + VoxelNode* childNode = node->getChildAtIndex(i); + if (childNode) { + // chance to optimize, doesn't need to be actual distance!! Could be distance squared + float distanceSquared = childNode->distanceSquareToPoint(point); + currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, + (void**)&sortedChildren, (float*)&distancesToChildren, + (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); + } + } + + //iterate sorted children FURTHEST to CLOSEST + for (int i = currentCount-1; i >= 0; i--) { + VoxelNode* child = sortedChildren[i]; + stackOfNodes->push(child); + } + } + + // at this point, we can check to see if we should bail for timing reasons + // because if we bail at this point, then reenter the while, we will basically + // be back to processing the stack from same place we left off, and all can proceed normally + long long now = usecTimestampNow(); + long elapsedTime = now - start; + + if (elapsedTime > allowedTime) { + return; // caller responsible for calling us again to finish the job! + } + } +} + + // Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. // stops recursion if operation function returns false. void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData) { @@ -62,7 +121,7 @@ void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, vo } // Recurses voxel node with an operation function -void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperation operation, void* extraData) { +void VoxelTree::recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) { if (operation(node, extraData)) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelNode* child = node->getChildAtIndex(i); @@ -77,15 +136,15 @@ void VoxelTree::recurseNodeWithOperation(VoxelNode* node,RecurseVoxelTreeOperati // stops recursion if operation function returns false. void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData) { + recurseNodeWithOperationDistanceSorted(rootNode, operation, point, extraData); } // Recurses voxel node with an operation function -void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, +void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData) { if (operation(node, extraData)) { // determine the distance sorted order of our children - VoxelNode* sortedChildren[NUMBER_OF_CHILDREN]; float distancesToChildren[NUMBER_OF_CHILDREN]; int indexOfChildren[NUMBER_OF_CHILDREN]; // not really needed @@ -537,7 +596,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes) { //unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)])); - int atByte = sizeof(short int) + sizeof(PACKET_HEADER); + int atByte = sizeof(short int) + numBytesForPacketHeader(bitstream); unsigned char* voxelCode = (unsigned char*)&bitstream[atByte]; while (atByte < bufferSizeBytes) { int codeLength = numberOfThreeBitSectionsInCode(voxelCode); @@ -1099,6 +1158,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // Keep track of how deep we've encoded. currentEncodeLevel++; + + params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached); // If we've reached our max Search Level, then stop searching. if (currentEncodeLevel >= params.maxEncodeLevel) { @@ -1108,7 +1169,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // caller can pass NULL as viewFrustum if they want everything if (params.viewFrustum) { float distance = node->distanceToCamera(*params.viewFrustum); - float boundaryDistance = boundaryDistanceForRenderLevel(*node->getOctalCode() + 1); + float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust); // If we're too far away for our render level, then just return if (distance >= boundaryDistance) { @@ -1121,7 +1182,26 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (!node->isInView(*params.viewFrustum)) { return bytesAtThisLevel; } + + // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view + // because we don't send nodes from the previously know in view frustum. + bool wasInView = false; + + if (params.deltaViewFrustum && params.lastViewFrustum) { + ViewFrustum::location location = node->inFrustum(*params.lastViewFrustum); + + // If we're a leaf, then either intersect or inside is considered "formerly in view" + if (node->isLeaf()) { + wasInView = location != ViewFrustum::OUTSIDE; + } else { + wasInView = location == ViewFrustum::INSIDE; + } + } + // If we were in view, then bail out early! + if (wasInView) { + return bytesAtThisLevel; + } // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. // leaf occlusion is handled down below when we check child nodes @@ -1139,16 +1219,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false); delete voxelPolygon; // cleanup if (result == OCCLUDED) { - //node->printDebugDetails("upper section, non-Leaf is occluded!! node="); - //args->nonLeavesOccluded++; - - //args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); - //args->totalVoxels += (subArgs.voxelsTouched - 1); - return bytesAtThisLevel; } } else { - //node->printDebugDetails("upper section, shadow Not in view node="); // If this shadow wasn't "all in view" then we ignored it for occlusion culling, but // we do need to clean up memory and proceed as normal... delete voxelPolygon; @@ -1224,7 +1297,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (childIsInView) { // Before we determine consider this further, let's see if it's in our LOD scope... float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; - float boundaryDistance = params.viewFrustum ? boundaryDistanceForRenderLevel(*childNode->getOctalCode() + 1) : 1; + float boundaryDistance = !params.viewFrustum ? 1 : + boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust); if (distance < boundaryDistance) { inViewCount++; @@ -1270,44 +1344,40 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp } // wants occlusion culling & isLeaf() - bool childWasInView = (childNode && params.deltaViewFrustum && - - (params.lastViewFrustum && ViewFrustum::INSIDE == childNode->inFrustum(*params.lastViewFrustum))); - // There are two types of nodes for which we want to send colors: // 1) Leaves - obviously - // 2) Non-leaves who's children would be visible and beyond our LOD. - // NOTE: This code works, but it's pretty expensive, because we're calculating distances for all the grand - // children, which we'll end up doing again later in the next level of recursion. We need to optimize this - // in the future. + // 2) Non-leaves who's children would be visible but are beyond our LOD. bool isLeafOrLOD = childNode->isLeaf(); if (params.viewFrustum && childNode->isColored() && !childNode->isLeaf()) { - int grandChildrenInView = 0; - int grandChildrenInLOD = 0; - for (int grandChildIndex = 0; grandChildIndex < NUMBER_OF_CHILDREN; grandChildIndex++) { - VoxelNode* grandChild = childNode->getChildAtIndex(grandChildIndex); - - if (grandChild && grandChild->isColored() && grandChild->isInView(*params.viewFrustum)) { - grandChildrenInView++; - - float grandChildDistance = grandChild->distanceToCamera(*params.viewFrustum); - float grandChildBoundaryDistance = boundaryDistanceForRenderLevel(grandChild->getLevel() + 1); - if (grandChildDistance < grandChildBoundaryDistance) { - grandChildrenInLOD++; - } - } - } - // if any of our grandchildren ARE in view, then we don't want to include our color. If none are, then - // we do want to include our color - if (grandChildrenInView > 0 && grandChildrenInLOD==0) { - isLeafOrLOD = true; - } + int childLevel = childNode->getLevel(); + float childBoundary = boundaryDistanceForRenderLevel(childLevel + params.boundaryLevelAdjust); + float grandChildBoundary = boundaryDistanceForRenderLevel(childLevel + 1 + params.boundaryLevelAdjust); + isLeafOrLOD = ((distance <= childBoundary) && !(distance <= grandChildBoundary)); } // track children with actual color, only if the child wasn't previously in view! - if (childNode && isLeafOrLOD && childNode->isColored() && !childWasInView && !childIsOccluded) { - childrenColoredBits += (1 << (7 - originalIndex)); - inViewWithColorCount++; + if (childNode && isLeafOrLOD && childNode->isColored() && !childIsOccluded) { + bool childWasInView = false; + + if (childNode && params.deltaViewFrustum && params.lastViewFrustum) { + ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum); + + // If we're a leaf, then either intersect or inside is considered "formerly in view" + if (childNode->isLeaf()) { + childWasInView = location != ViewFrustum::OUTSIDE; + } else { + childWasInView = location == ViewFrustum::INSIDE; + } + } + + // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items + if (!childWasInView) { + childrenColoredBits += (1 << (7 - originalIndex)); + inViewWithColorCount++; + } else { + // otherwise just track stats of the items we discarded + params.childWasInViewDiscarded++; + } } } } diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index e46ac4c2cb..61f2e13056 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -14,6 +14,7 @@ #include "VoxelNode.h" #include "VoxelNodeBag.h" #include "CoverageMap.h" +#include "PointerStack.h" // Callback function, for recuseTreeWithOperation typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData); @@ -32,10 +33,13 @@ typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; #define WANT_OCCLUSION_CULLING true #define IGNORE_COVERAGE_MAP NULL #define DONT_CHOP 0 +#define NO_BOUNDARY_ADJUST 0 +#define LOW_RES_MOVING_ADJUST 1 class EncodeBitstreamParams { public: int maxEncodeLevel; + int maxLevelReached; const ViewFrustum* viewFrustum; bool includeColor; bool includeExistsBits; @@ -43,28 +47,34 @@ public: bool deltaViewFrustum; const ViewFrustum* lastViewFrustum; bool wantOcclusionCulling; + long childWasInViewDiscarded; + int boundaryLevelAdjust; + CoverageMap* map; EncodeBitstreamParams( int maxEncodeLevel = INT_MAX, - const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, + const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, int chopLevels = 0, bool deltaViewFrustum = false, const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, bool wantOcclusionCulling= NO_OCCLUSION_CULLING, - CoverageMap* map = IGNORE_COVERAGE_MAP) : - - maxEncodeLevel (maxEncodeLevel), - viewFrustum (viewFrustum), - includeColor (includeColor), - includeExistsBits (includeExistsBits), - chopLevels (chopLevels), - deltaViewFrustum (deltaViewFrustum), - lastViewFrustum (lastViewFrustum), - wantOcclusionCulling(wantOcclusionCulling), - map (map) + CoverageMap* map = IGNORE_COVERAGE_MAP, + int boundaryLevelAdjust = NO_BOUNDARY_ADJUST) : + maxEncodeLevel (maxEncodeLevel), + maxLevelReached (0), + viewFrustum (viewFrustum), + includeColor (includeColor), + includeExistsBits (includeExistsBits), + chopLevels (chopLevels), + deltaViewFrustum (deltaViewFrustum), + lastViewFrustum (lastViewFrustum), + wantOcclusionCulling (wantOcclusionCulling), + childWasInViewDiscarded (0), + boundaryLevelAdjust (boundaryLevelAdjust), + map (map) {} }; @@ -148,6 +158,11 @@ public: void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData); + + + void recurseTreeWithOperationDistanceSortedTimed(PointerStack* stackOfNodes, long allowedTime, + RecurseVoxelTreeOperation operation, + const glm::vec3& point, void* extraData); private: void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData); @@ -172,6 +187,7 @@ private: bool _shouldReaverage; }; -int boundaryDistanceForRenderLevel(unsigned int renderLevel); +float boundaryDistanceForRenderLevel(unsigned int renderLevel); +float boundaryDistanceSquaredForRenderLevel(unsigned int renderLevel); #endif /* defined(__hifi__VoxelTree__) */ diff --git a/voxel-server/src/VoxelNodeData.cpp b/voxel-server/src/VoxelNodeData.cpp index f2cce906cf..2fba3e511b 100644 --- a/voxel-server/src/VoxelNodeData.cpp +++ b/voxel-server/src/VoxelNodeData.cpp @@ -17,7 +17,9 @@ VoxelNodeData::VoxelNodeData(Node* owningNode) : _voxelPacketAvailableBytes(MAX_VOXEL_PACKET_SIZE), _maxSearchLevel(1), _maxLevelReachedInLastSearch(1), - _lastTimeBagEmpty(0) + _lastTimeBagEmpty(0), + _viewFrustumChanging(false), + _currentPacketIsColor(true) { _voxelPacket = new unsigned char[MAX_VOXEL_PACKET_SIZE]; _voxelPacketAt = _voxelPacket; @@ -27,9 +29,13 @@ VoxelNodeData::VoxelNodeData(Node* owningNode) : void VoxelNodeData::resetVoxelPacket() { - _voxelPacket[0] = getWantColor() ? PACKET_HEADER_VOXEL_DATA : PACKET_HEADER_VOXEL_DATA_MONOCHROME; - _voxelPacketAt = &_voxelPacket[1]; - _voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE - 1; + // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use + // the clients requested color state. + _currentPacketIsColor = (getWantLowResMoving() && _viewFrustumChanging) ? false : getWantColor(); + PACKET_TYPE voxelPacketType = _currentPacketIsColor ? PACKET_TYPE_VOXEL_DATA : PACKET_TYPE_VOXEL_DATA_MONOCHROME; + int numBytesPacketHeader = populateTypeAndVersion(_voxelPacket, voxelPacketType); + _voxelPacketAt = _voxelPacket + numBytesPacketHeader; + _voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE - numBytesPacketHeader; _voxelPacketWaiting = false; } @@ -63,6 +69,7 @@ bool VoxelNodeData::updateCurrentViewFrustum() { _currentViewFrustum.calculate(); currentViewFrustumChanged = true; } + _viewFrustumChanging = currentViewFrustumChanged; return currentViewFrustumChanged; } diff --git a/voxel-server/src/VoxelNodeData.h b/voxel-server/src/VoxelNodeData.h index ded4093f41..0f96a07c3d 100644 --- a/voxel-server/src/VoxelNodeData.h +++ b/voxel-server/src/VoxelNodeData.h @@ -50,10 +50,10 @@ public: bool getViewSent() const { return _viewSent; }; void setViewSent(bool viewSent) { _viewSent = viewSent; } - long long getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }; - void setLastTimeBagEmpty(long long lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; }; - + uint64_t getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }; + void setLastTimeBagEmpty(uint64_t lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; }; + bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }; private: VoxelNodeData(const VoxelNodeData &); VoxelNodeData& operator= (const VoxelNodeData&); @@ -67,8 +67,9 @@ private: int _maxLevelReachedInLastSearch; ViewFrustum _currentViewFrustum; ViewFrustum _lastKnownViewFrustum; - long long _lastTimeBagEmpty; - + uint64_t _lastTimeBagEmpty; + bool _viewFrustumChanging; + bool _currentPacketIsColor; }; #endif /* defined(__hifi__VoxelNodeData__) */ diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 64a4565b6c..917031b317 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -2,7 +2,7 @@ // main.cpp // Voxel Server // -// Created by Stephen Birara on 03/06/13. +// Created by Stephen Birarda on 03/06/13. // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. // @@ -32,7 +32,7 @@ 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_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds const int VOXEL_LISTEN_PORT = 40106; @@ -110,132 +110,6 @@ void eraseVoxelTreeAndCleanupNodeVisitData() { } } - -// 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 @@ -248,10 +122,48 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, pthread_mutex_lock(&::treeLock); int maxLevelReached = 0; - long long start = usecTimestampNow(); + uint64_t start = usecTimestampNow(); + int truePacketsSent = 0; + int trueBytesSent = 0; // FOR NOW... node tells us if it wants to receive only view frustum deltas - bool wantDelta = nodeData->getWantDelta(); + bool wantDelta = viewFrustumChanged && nodeData->getWantDelta(); + + // If our packet already has content in it, then we must use the color choice of the waiting packet. + // If we're starting a fresh packet, then... + // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use + // the clients requested color state. + bool wantColor = ((nodeData->getWantLowResMoving() && viewFrustumChanged) ? false : nodeData->getWantColor()); + + // If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color + // then let's just send that waiting packet. + if (wantColor != nodeData->getCurrentPacketIsColor()) { + + if (nodeData->isPacketWaiting()) { + if (::debugVoxelSending) { + printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n", + debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor())); + } + nodeList->getNodeSocket()->send(node->getActiveSocket(), + nodeData->getPacket(), nodeData->getPacketLength()); + trueBytesSent += nodeData->getPacketLength(); + truePacketsSent++; + nodeData->resetVoxelPacket(); + } else { + if (::debugVoxelSending) { + printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n", + debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor())); + } + nodeData->resetVoxelPacket(); + } + } + + if (::debugVoxelSending) { + printf("wantColor=%s getCurrentPacketIsColor()=%s, viewFrustumChanged=%s, getWantLowResMoving()=%s\n", + debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()), + debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving())); + } + const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; if (::debugVoxelSending) { @@ -267,18 +179,19 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, if (::debugVoxelSending) { printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n", debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty())); - long long now = usecTimestampNow(); + uint64_t 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())); + printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n", + debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta), + debug::valueOf(wantColor)); } - nodeData->setLastTimeBagEmpty(now); + nodeData->setLastTimeBagEmpty(now); // huh? why is this inside debug? probably not what we want } // if our view has changed, we need to reset these things... @@ -304,9 +217,8 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, } else { nodeData->nodeBag.insert(serverTree.rootNode); } - } - long long end = usecTimestampNow(); + uint64_t end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { @@ -327,14 +239,12 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, 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(); + uint64_t 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(); + uint64_t now = usecTimestampNow(); long elapsedUsec = (now - start); long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent); long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec); @@ -350,22 +260,27 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, if (!nodeData->nodeBag.isEmpty()) { VoxelNode* subTree = nodeData->nodeBag.extract(); - bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; + int boundaryLevelAdjust = viewFrustumChanged && nodeData->getWantLowResMoving() + ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST; - EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), nodeData->getWantColor(), + EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, - wantOcclusionCulling, coverageMap); + wantOcclusionCulling, coverageMap, boundaryLevelAdjust); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeData->nodeBag, params); + + if (::debugVoxelSending && wantDelta) { + printf("encodeTreeBitstream() childWasInViewDiscarded=%ld\n", params.childWasInViewDiscarded); + } if (nodeData->getAvailable() >= bytesWritten) { nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } else { nodeList->getNodeSocket()->send(node->getActiveSocket(), - nodeData->getPacket(), nodeData->getPacketLength()); + nodeData->getPacket(), nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; packetsSentThisInterval++; @@ -375,28 +290,29 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, } else { if (nodeData->isPacketWaiting()) { nodeList->getNodeSocket()->send(node->getActiveSocket(), - nodeData->getPacket(), nodeData->getPacketLength()); + 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++) { + int numBytesPacketHeader = populateTypeAndVersion(tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA); + int envPacketLength = numBytesPacketHeader; + + for (int i = 0; i < sizeof(environmentData) / sizeof(EnvironmentData); i++) { envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength); } + nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength); trueBytesSent += envPacketLength; truePacketsSent++; } - long long end = usecTimestampNow(); + uint64_t end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { @@ -417,6 +333,9 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, if (nodeData->nodeBag.isEmpty()) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); + if (::debugVoxelSending) { + nodeData->map.printStats(); + } nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes } @@ -425,10 +344,10 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, pthread_mutex_unlock(&::treeLock); } -long long lastPersistVoxels = 0; +uint64_t lastPersistVoxels = 0; void persistVoxelsWhenDirty() { - long long now = usecTimestampNow(); - long long sinceLastTime = (now - ::lastPersistVoxels) / 1000; + uint64_t now = usecTimestampNow(); + int sinceLastTime = (now - ::lastPersistVoxels) / 1000; // check the dirty bit and persist here... if (::wantVoxelPersist && ::serverTree.isDirty() && sinceLastTime > VOXEL_PERSIST_INTERVAL) { @@ -463,17 +382,12 @@ void *distributeVoxelsToListeners(void *args) { if (::debugVoxelSending) { printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged)); } - - if (nodeData->getWantResIn()) { - resInVoxelDistributor(nodeList, node, nodeData); - } else { - deepestLevelVoxelDistributor(nodeList, node, nodeData, viewFrustumChanged); - } + 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)); + int usecToSleep = VOXEL_SEND_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSendTime)); if (usecToSleep > 0) { usleep(usecToSleep); @@ -636,19 +550,24 @@ int main(int argc, const char * argv[]) { // 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); + if (nodeList->getNodeSocket()->receive(&nodePublicAddress, packetData, &receivedBytes) && + packetVersionMatch(packetData)) { + + int numBytesPacketHeader = numBytesForPacketHeader(packetData); + + if (packetData[0] == PACKET_TYPE_SET_VOXEL || packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE) { + bool destructive = (packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE); PerformanceWarning warn(::shouldShowAnimationDebug, - destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL", + destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL", ::shouldShowAnimationDebug); - unsigned short int itemNumber = (*((unsigned short int*)&packetData[1])); + + unsigned short int itemNumber = (*((unsigned short int*)(packetData + numBytesPacketHeader))); if (::shouldShowAnimationDebug) { printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n", - destructive ? "PACKET_HEADER_SET_VOXEL_DESTRUCTIVE" : "PACKET_HEADER_SET_VOXEL", + destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL", receivedBytes,itemNumber); } - int atByte = sizeof(PACKET_HEADER) + sizeof(itemNumber); + int atByte = numBytesPacketHeader + sizeof(itemNumber); unsigned char* voxelData = (unsigned char*)&packetData[atByte]; while (atByte < receivedBytes) { unsigned char octets = (unsigned char)*voxelData; @@ -690,21 +609,20 @@ int main(int argc, const char * argv[]) { voxelData += voxelDataSize; atByte += voxelDataSize; } - } - if (packetData[0] == PACKET_HEADER_ERASE_VOXEL) { + } else if (packetData[0] == PACKET_TYPE_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) { + } else if (packetData[0] == PACKET_TYPE_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 + + char* command = (char*) &packetData[numBytesPacketHeader]; // 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 + int totalLength = numBytesPacketHeader + commandLength + 1; // 1 for null termination printf("got Z message len(%ld)= %s\n", receivedBytes, command); bool rebroadcast = true; // by default rebroadcast @@ -730,21 +648,20 @@ int main(int argc, const char * argv[]) { 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) { + } else if (packetData[0] == PACKET_TYPE_HEAD_DATA) { + // If we got a PACKET_TYPE_HEAD_DATA, then we're talking to an NODE_TYPE_AVATAR, and we + // need to make sure we have it in our nodeList. + uint16_t nodeID = 0; - unpackNodeId(packetData + sizeof(PACKET_HEADER_HEAD_DATA), &nodeID); + unpackNodeId(packetData + numBytesPacketHeader, &nodeID); Node* node = nodeList->addOrUpdateNode(&nodePublicAddress, - &nodePublicAddress, - NODE_TYPE_AGENT, - nodeID); + &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) { + } else if (packetData[0] == PACKET_TYPE_PING) { + // If the packet is a ping, let processNodeData handle it. nodeList->processNodeData(&nodePublicAddress, packetData, receivedBytes); } } @@ -755,3 +672,5 @@ int main(int argc, const char * argv[]) { return 0; } + +