diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cc0ddd59f2..015c3916ea 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -113,6 +113,10 @@ void Agent::run() { // setup an Avatar for the script to use AvatarData scriptedAvatar; + // call model URL setters with empty URLs so our avatar, if user, will have the default models + scriptedAvatar.setFaceModelURL(QUrl()); + scriptedAvatar.setSkeletonModelURL(QUrl()); + // give this AvatarData object to the script engine _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index bc6b4f65fe..1502093d5b 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -29,7 +29,7 @@ class Agent : public ThreadedAssignment { public: Agent(const QByteArray& packet); - void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); } + void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); } bool isAvatar() const { return _scriptEngine.isAvatar(); } public slots: diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c7989bc9e9..63bdccbab7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -93,7 +93,7 @@ void broadcastIdentityPacket() { NodeList* nodeList = NodeList::getInstance(); - QByteArray avatarIdentityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); + QByteArray avatarIdentityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); int numPacketHeaderBytes = avatarIdentityPacket.size(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { @@ -128,7 +128,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { && killedNode->getLinkedData()) { // this was an avatar we were sending to other people // send a kill packet for it to our other nodes - QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar); + QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar); killPacket += killedNode->getUUID().toRfc4122(); NodeList::getInstance()->broadcastToNodes(killPacket, @@ -159,7 +159,7 @@ void AvatarMixer::readPendingDatagrams() { if (nodeData->hasIdentityChangedAfterParsing(receivedPacket) && !nodeData->hasSentIdentityBetweenKeyFrames()) { // this avatar changed their identity in some way and we haven't sent a packet in this keyframe - QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); + QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); QByteArray individualByteArray = nodeData->identityByteArray(); individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122()); diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp index 97228b67a3..76386d7b5a 100644 --- a/data-server/src/DataServer.cpp +++ b/data-server/src/DataServer.cpp @@ -121,7 +121,7 @@ void DataServer::readPendingDatagrams() { if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) { // if redis stored the value successfully reply back with a confirm // which is a reply packet with the sequence number - QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid); + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerConfirm, _uuid); replyPacket.append(sequenceNumber); @@ -132,7 +132,7 @@ void DataServer::readPendingDatagrams() { } else { // setup a send packet with the returned data // leverage the packetData sent by overwriting and appending - QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid); + QByteArray sendPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerSend, _uuid); QDataStream sendPacketStream(&sendPacket, QIODevice::Append); sendPacketStream << sequenceNumber; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 896b709e2a..e9816abe4c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -221,10 +221,10 @@ void DomainServer::readAvailableDatagrams() { HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress; - static QByteArray broadcastPacket = byteArrayWithPopluatedHeader(PacketTypeDomainList); + static QByteArray broadcastPacket = byteArrayWithPopulatedHeader(PacketTypeDomainList); static int numBroadcastPacketHeaderBytes = broadcastPacket.size(); - static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment); + static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment); static int numAssignmentPacketHeaderBytes = assignmentPacket.size(); QByteArray receivedPacket; diff --git a/examples/bot.js b/examples/bot.js new file mode 100644 index 0000000000..6d0abd740e --- /dev/null +++ b/examples/bot.js @@ -0,0 +1,34 @@ +// +// bot.js +// hifi +// +// Created by Brad Hefta-Gaub on 2/20/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates an NPC avatar. +// +// + +function getRandomFloat(min, max) { + return Math.random() * (max - min) + min; +} + +function getRandomInt (min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// choose a random x and y in the range of 0 to 50 +positionX = getRandomFloat(0, 50); +positionZ = getRandomFloat(0, 50); + +// change the avatar's position to the random one +Avatar.position = {x: positionX, y: 0, z: positionZ}; + +// pick an integer between 1 and 20 for the face model for this bot +botNumber = getRandomInt(1, 20); + +// set the face model fst using the bot number +// there is no need to change the body model - we're using the default +Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/bot" + botNumber + ".fst"; + +Agent.isAvatar = true; \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a0a9033187..d516a70820 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,7 +41,7 @@ if (WIN32) # which are not accessible on windows without glew or some other dynamic linking mechanism set(GLEW_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/glew) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${GLEW_ROOT_PATH}) - include_directories(SYSTEM ${GLEW_ROOT_PATH}/include) + include_directories(SYSTEM ${GLEW_ROOT_PATH}/include ${GLUT_ROOT_PATH}/include) #set(GL_HEADERS "#define GLEW_STATIC\n#define FREEGLUT_STATIC\n#define FREEGLUT_LIB_PRAGMAS 0\n#include \n#include \n#include \n#include ") set(GL_HEADERS "#define GLEW_STATIC\n#include \n#include \n#include ") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9ef3fdc76b..a71cedec70 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -253,7 +253,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); - identityPacketTimer->start(1000); + identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); @@ -2347,7 +2347,7 @@ void Application::updateMyAvatar(float deltaTime) { _myAvatar->update(deltaTime); // send head/hand data to the avatar mixer and voxel server - QByteArray packet = byteArrayWithPopluatedHeader(PacketTypeAvatarData); + QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData); packet.append(_myAvatar->toByteArray()); controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 327f905194..100022c2a6 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -437,6 +437,20 @@ Menu::Menu() : appInstance->getVoxels(), SLOT(trueColorize())); + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::CullSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + false, + appInstance->getVoxels(), + SLOT(cullSharedFaces())); + + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::ShowCulledSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_X, + false, + appInstance->getVoxels(), + SLOT(showCulledSharedFaces())); + addDisabledActionAndSeparator(renderDebugMenu, "Coverage Maps"); addActionToQMenuAndActionHash(renderDebugMenu, MenuOption::FalseColorOccluded, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index bd9cdc523e..262b618526 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -193,6 +193,7 @@ namespace MenuOption { const QString CopyVoxels = "Copy"; const QString CoverageMap = "Render Coverage Map"; const QString CoverageMapV2 = "Render Coverage Map V2"; + const QString CullSharedFaces = "Cull Shared Voxel Faces"; const QString CutVoxels = "Cut"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; @@ -275,6 +276,7 @@ namespace MenuOption { const QString Shadows = "Shadows"; const QString SettingsExport = "Export Settings"; const QString ShowAllLocalVoxels = "Show All Local Voxels"; + const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces"; const QString ShowTrueColors = "Show TRUE Colors"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString Stars = "Stars"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 34c3be5308..3ed62cdb14 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -180,7 +180,7 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) { } static QByteArray createDatagramHeader(const QUuid& sessionID) { - QByteArray header = byteArrayWithPopluatedHeader(PacketTypeMetavoxelData); + QByteArray header = byteArrayWithPopulatedHeader(PacketTypeMetavoxelData); header += sessionID.toRfc4122(); return header; } diff --git a/interface/src/PrimitiveRenderer.cpp b/interface/src/PrimitiveRenderer.cpp new file mode 100644 index 0000000000..7f3607e363 --- /dev/null +++ b/interface/src/PrimitiveRenderer.cpp @@ -0,0 +1,740 @@ +/// +/// @file PrimitiveRenderer.cpp +/// A geometric primitive renderer. +/// +/// @author: Norman Crafts +/// @copyright 2014, High Fidelity, Inc. All rights reserved. +/// + +#include + +#include "InterfaceConfig.h" +#include "OctreeElement.h" +#include "PrimitiveRenderer.h" + +Primitive::Primitive() { +} + +Primitive::~Primitive() { +} + +// Simple dispatch between API and SPI + +const VertexElementList& Primitive::vertexElements() const { + return vVertexElements(); +} + +VertexElementIndexList& Primitive::vertexElementIndices() { + return vVertexElementIndices(); +} + +TriElementList& Primitive::triElements() { + return vTriElements(); +} + +void Primitive::releaseVertexElements() { + vReleaseVertexElements(); +} + +unsigned long Primitive::getMemoryUsage() { + return vGetMemoryUsage(); +} + + +Cube::Cube( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faceExclusions + ) : + _cpuMemoryUsage(0) { + init(x, y, z, s, r, g, b, faceExclusions); +} + +Cube::~Cube() { + terminate(); +} + +void Cube::init( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faceExclusions + ) { + + initializeVertices(x, y, z, s, r, g, b, faceExclusions); + initializeTris(faceExclusions); +} + +void Cube::terminate() { + + terminateTris(); + terminateVertices(); +} + +void Cube::initializeVertices( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faceExclusions + ) { + + for (int i = 0; i < _sNumVerticesPerCube; i++) { + // Check whether the vertex is necessary for the faces indicated by faceExclusions bit mask. + // uncomment this line to load all faces: if (~0x00 & _sFaceIndexToHalfSpaceMask[i >> 2]) { + // uncomment this line to include shared faces: if (faceExclusions & _sFaceIndexToHalfSpaceMask[i >> 2]) { + // uncomment this line to exclude shared faces: + if (~faceExclusions & _sFaceIndexToHalfSpaceMask[i >> 2]) { + + VertexElement* v = new VertexElement(); + if (v) { + // Construct vertex position + v->position.x = x + s * _sVertexIndexToConstructionVector[i][0]; + v->position.y = y + s * _sVertexIndexToConstructionVector[i][1]; + v->position.z = z + s * _sVertexIndexToConstructionVector[i][2]; + + // Construct vertex normal + v->normal.x = _sVertexIndexToNormalVector[i >> 2][0]; + v->normal.y = _sVertexIndexToNormalVector[i >> 2][1]; + v->normal.z = _sVertexIndexToNormalVector[i >> 2][2]; + + // Construct vertex color +//#define FALSE_COLOR +#ifndef FALSE_COLOR + v->color.r = r; + v->color.g = g; + v->color.b = b; + v->color.a = 255; +#else + static unsigned char falseColor[6][3] = { + 192, 0, 0, // Bot + 0, 192, 0, // Top + 0, 0, 192, // Right + 192, 0, 192, // Left + 192, 192, 0, // Near + 192, 192, 192 // Far + }; + v->color.r = falseColor[i >> 2][0]; + v->color.g = falseColor[i >> 2][1]; + v->color.b = falseColor[i >> 2][2]; + v->color.a = 255; +#endif + + // Add vertex element to list + _vertices.push_back(v); + _cpuMemoryUsage += sizeof(VertexElement); + _cpuMemoryUsage += sizeof(VertexElement*); + } + } + } +} + +void Cube::terminateVertices() { + + for (VertexElementList::iterator it = _vertices.begin(); it != _vertices.end(); ++it) { + delete *it; + } + _cpuMemoryUsage -= _vertices.size() * (sizeof(VertexElement) + sizeof(VertexElement*)); + _vertices.clear(); +} + +void Cube::initializeTris( + unsigned char faceExclusions + ) { + + int index = 0; + for (int i = 0; i < _sNumFacesPerCube; i++) { + // Check whether the vertex is necessary for the faces indicated by faceExclusions bit mask. + // uncomment this line to load all faces: if (~0x00 & _sFaceIndexToHalfSpaceMask[i]) { + // uncomment this line to include shared faces: if (faceExclusions & _sFaceIndexToHalfSpaceMask[i]) { + // uncomment this line to exclude shared faces: + if (~faceExclusions & _sFaceIndexToHalfSpaceMask[i]) { + + int start = index; + // Create the triangulated face, two tris, six indices referencing four vertices, both + // with cw winding order, such that: + + // A-B + // |\| + // D-C + + // Store triangle ABC + + TriElement* tri = new TriElement(); + if (tri) { + tri->indices[0] = index++; + tri->indices[1] = index++; + tri->indices[2] = index; + + // Add tri element to list + _tris.push_back(tri); + _cpuMemoryUsage += sizeof(TriElement); + _cpuMemoryUsage += sizeof(TriElement*); + } + + // Now store triangle ACD + tri = new TriElement(); + if (tri) { + tri->indices[0] = start; + tri->indices[1] = index++; + tri->indices[2] = index++; + + // Add tri element to list + _tris.push_back(tri); + _cpuMemoryUsage += sizeof(TriElement); + _cpuMemoryUsage += sizeof(TriElement*); + } + } + } +} + +void Cube::terminateTris() { + + for (TriElementList::iterator it = _tris.begin(); it != _tris.end(); ++it) { + delete *it; + } + _cpuMemoryUsage -= _tris.size() * (sizeof(TriElement) + sizeof(TriElement*)); + _tris.clear(); +} + +const VertexElementList& Cube::vVertexElements() const { + return _vertices; +} + +VertexElementIndexList& Cube::vVertexElementIndices() { + return _vertexIndices; +} + +TriElementList& Cube::vTriElements() { + return _tris; +} + +void Cube::vReleaseVertexElements() { + terminateVertices(); +} + +unsigned long Cube::vGetMemoryUsage() { + return _cpuMemoryUsage; +} + +unsigned char Cube::_sFaceIndexToHalfSpaceMask[6] = { + OctreeElement::HalfSpace::Bottom, + OctreeElement::HalfSpace::Top, + OctreeElement::HalfSpace::Right, + OctreeElement::HalfSpace::Left, + OctreeElement::HalfSpace::Near, + OctreeElement::HalfSpace::Far, +}; + +// Construction vectors ordered such that the vertices of each face are +// clockwise in a right-handed coordinate system with B-L-N at 0,0,0. +float Cube::_sVertexIndexToConstructionVector[24][3] = { + // Bottom + { 0,0,0 }, + { 1,0,0 }, + { 1,0,1 }, + { 0,0,1 }, + // Top + { 0,1,0 }, + { 0,1,1 }, + { 1,1,1 }, + { 1,1,0 }, + // Right + { 1,0,0 }, + { 1,1,0 }, + { 1,1,1 }, + { 1,0,1 }, + // Left + { 0,0,0 }, + { 0,0,1 }, + { 0,1,1 }, + { 0,1,0 }, + // Near + { 0,0,0 }, + { 0,1,0 }, + { 1,1,0 }, + { 1,0,0 }, + // Far + { 0,0,1 }, + { 1,0,1 }, + { 1,1,1 }, + { 0,1,1 }, +}; + +// Normals for a right-handed coordinate system +float Cube::_sVertexIndexToNormalVector[6][3] = { + { 0,-1, 0 }, // Bottom + { 0, 1, 0 }, // Top + { 1, 0, 0 }, // Right + { -1, 0, 0 }, // Left + { 0, 0,-1 }, // Near + { 0, 0, 1 }, // Far +}; + +Renderer::Renderer() { +} + +Renderer::~Renderer() { +} + +// Simple dispatch between API and SPI +int Renderer::add( + Primitive* primitive + ) { + return vAdd(primitive); +} + +void Renderer::remove( + int id + ) { + vRemove(id); +} + +void Renderer::release() { + vRelease(); +} + +void Renderer::render() { + vRender(); +} + +unsigned long Renderer::getMemoryUsage() { + return vGetMemoryUsage(); +} + +unsigned long Renderer::getMemoryUsageGPU() { + return vGetMemoryUsageGPU(); +} + +PrimitiveRenderer::PrimitiveRenderer( + int maxCount + ) : + _maxCount(maxCount), + _triBufferId(0), + _vertexBufferId(0), + + _vertexElementCount(0), + _maxVertexElementCount(0), + + _triElementCount(0), + _maxTriElementCount(0), + + _primitives(), + _primitiveCount(0), + + _gpuMemoryUsage(0), + _cpuMemoryUsage(0) + +{ + init(); +} + +PrimitiveRenderer::~PrimitiveRenderer() { + + terminate(); +} + +void PrimitiveRenderer::init() { + + initializeGL(); + initializeBookkeeping(); +} + +void PrimitiveRenderer::initializeGL() { + + glGenBuffers(1, &_triBufferId); + glGenBuffers(1, &_vertexBufferId); + + // Set up the element array buffer containing the index ids + _maxTriElementCount = _maxCount * 2; + int size = _maxTriElementCount * _sIndicesPerTri * sizeof(GLint); + _gpuMemoryUsage += size; + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Set up the array buffer in the form of array of structures + // I chose AOS because it maximizes the amount of data tranferred + // by a single glBufferSubData call. + _maxVertexElementCount = _maxCount * 8; + size = _maxVertexElementCount * sizeof(VertexElement); + _gpuMemoryUsage += size; + + glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId); + glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Initialize the first tri element in the buffer to all zeros, the + // degenerate case + deconstructTriElement(0); + + // Initialize the first vertex element in the buffer to all zeros, the + // degenerate case + deconstructVertexElement(0); +} + +void PrimitiveRenderer::initializeBookkeeping() { + + // Start primitive count at one, because zero is reserved for the degenerate triangle + _primitives.resize(_maxCount + 1); + + // Set the counters + _primitiveCount = 1; + _vertexElementCount = 1; + _triElementCount = 1; + + // Guesstimate the memory consumption + _cpuMemoryUsage = sizeof(PrimitiveRenderer); + _cpuMemoryUsage += _availablePrimitiveIndex.capacity() * sizeof(int); + _cpuMemoryUsage += _availableVertexElementIndex.capacity() * sizeof(int); + _cpuMemoryUsage += _availableTriElementIndex.capacity() * sizeof(int); + _cpuMemoryUsage += _deconstructTriElementIndex.capacity() * sizeof(int); + _cpuMemoryUsage += _constructPrimitiveIndex.capacity() * sizeof(int); +} + +void PrimitiveRenderer::terminate() { + + terminateBookkeeping(); + terminateGL(); +} + +void PrimitiveRenderer::terminateGL() { + + if (_vertexBufferId) { + glDeleteBuffers(1, &_vertexBufferId); + _vertexBufferId = 0; + } + + if (_triBufferId) { + glDeleteBuffers(1, &_triBufferId); + _triBufferId = 0; + } +} + +void PrimitiveRenderer::terminateBookkeeping() { + + // Delete all of the primitives + for (int i = _primitiveCount + 1; --i > 0; ) { + Primitive* primitive = _primitives[i]; + if (primitive) { + _cpuMemoryUsage -= primitive->getMemoryUsage(); + _primitives[i] = 0; + delete primitive; + } + } + + // Drain the queues + _availablePrimitiveIndex.clear(); + _availableVertexElementIndex.clear(); + _availableTriElementIndex.clear(); + _deconstructTriElementIndex.clear(); + _constructPrimitiveIndex.clear(); + + _cpuMemoryUsage = sizeof(PrimitiveRenderer) + _primitives.size() * sizeof(Primitive *); +} + +void PrimitiveRenderer::constructElements( + Primitive* primitive + ) { + + // Load vertex elements + VertexElementIndexList& vertexElementIndexList = primitive->vertexElementIndices(); + const VertexElementList& vertices = primitive->vertexElements(); + { + for (VertexElementList::const_iterator it = vertices.begin(); it != vertices.end(); ++it ) { + int index = getAvailableVertexElementIndex(); + if (index != 0) { + // Store the vertex element index in the primitive's + // vertex element index list + vertexElementIndexList.push_back(index); + + VertexElement* vertex = *it; + transferVertexElement(index, vertex); + } else { + break; + } + } + } + + // Load tri elements + if (vertexElementIndexList.size() == vertices.size()) { + TriElementList& tris = primitive->triElements(); + + for (TriElementList::iterator it = tris.begin(); it != tris.end(); ++it) { + TriElement* tri = *it; + int index = getAvailableTriElementIndex(); + if (index != 0) { + int k; + k = tri->indices[0]; + tri->indices[0] = vertexElementIndexList[k]; + + k = tri->indices[1]; + tri->indices[1] = vertexElementIndexList[k]; + + k = tri->indices[2]; + tri->indices[2] = vertexElementIndexList[k]; + + tri->id = index; + transferTriElement(index, tri->indices); + } else { + break; + } + } + } else { + // TODO: failure mode + } +} + +void PrimitiveRenderer::deconstructElements( + Primitive* primitive + ) { + + // Schedule the tri elements of the face for deconstruction + { + TriElementList& tris = primitive->triElements(); + + for (TriElementList::const_iterator it = tris.begin(); it != tris.end(); ++it) { + const TriElement* tri = *it; + + if (tri->id) { + // Put the tri element index into decon queue + _deconstructTriElementIndex.push(tri->id); + } + } + } + + // Return the vertex element index to the available queue, it is not necessary + // to zero the data + { + VertexElementIndexList& vertexIndexList = primitive->vertexElementIndices(); + + for (VertexElementIndexList::const_iterator it = vertexIndexList.begin(); it != vertexIndexList.end(); ++it) { + int index = *it; + + if (index) { + // Put the vertex element index into the available queue + _availableVertexElementIndex.push(index); + } + } + } + + delete primitive; +} + +int PrimitiveRenderer::getAvailablePrimitiveIndex() { + + int index; + + // Check the available primitive index queue first for an available index. + if (!_availablePrimitiveIndex.isEmpty()) { + index = _availablePrimitiveIndex.pop(); + } else if (_primitiveCount < _maxCount) { + // There are no primitive indices available from the queue, + // make one up + index = _primitiveCount++; + } else { + index = 0; + } + return index; +} + +int PrimitiveRenderer::getAvailableVertexElementIndex() { + + int index; + + // Check the available vertex element queue first for an available index. + if (!_availableVertexElementIndex.isEmpty()) { + index = _availableVertexElementIndex.pop(); + } else if (_vertexElementCount < _maxVertexElementCount) { + // There are no vertex elements available from the queue, + // grab one from the end of the list + index = _vertexElementCount++; + } else { + index = 0; + } + return index; +} + +int PrimitiveRenderer::getAvailableTriElementIndex() { + + int index; + + // Check the tri elements scheduled for deconstruction queue first to + // intercept and reuse an index without it having to be destroyed + if (!_deconstructTriElementIndex.isEmpty()) { + index = _deconstructTriElementIndex.pop(); + } else if (!_availableTriElementIndex.isEmpty()) { + // Nothing available in the deconstruction queue, now + // check the available tri element queue for an available index. + index = _availableTriElementIndex.pop(); + } else if (_triElementCount < _maxTriElementCount) { + // There are no reusable tri elements available from the queue, + // grab one from the end of the list + index = _triElementCount++; + } else { + index = 0; + } + return index; +} + +void PrimitiveRenderer::deconstructTriElement( + int idx + ) { + + // Set the tri element to the degenerate case. + static int degenerate[3] = { 0, 0, 0 }; + transferTriElement(idx, degenerate); + +} + +void PrimitiveRenderer::deconstructVertexElement( + int idx + ) { + + // Set the vertex element to the degenerate case. + VertexElement degenerate; + memset(°enerate, 0, sizeof(degenerate)); + + transferVertexElement(idx, °enerate); + +} + +void PrimitiveRenderer::transferVertexElement( + int idx, + VertexElement* vertex + ) { + + glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId); + glBufferSubData(GL_ARRAY_BUFFER, idx * sizeof(VertexElement), sizeof(VertexElement), vertex); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void PrimitiveRenderer::transferTriElement( + int idx, + int tri[3] + ) { + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, idx * _sBytesPerTriElement, _sBytesPerTriElement, tri); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +int PrimitiveRenderer::vAdd( + Primitive* primitive + ) { + + QMutexLocker lock(&_guard); + int id = getAvailablePrimitiveIndex(); + if (id != 0) { + // Take ownership of primitive, including responsibility + // for destruction + _primitives[id] = primitive; + _constructPrimitiveIndex.push(id); + _cpuMemoryUsage += primitive->getMemoryUsage(); + } + return id; +} + +void PrimitiveRenderer::vRemove( + int id + ) { + + if (id != 0) { + QMutexLocker lock(&_guard); + + // Locate and remove the primitive by id in the vector map + Primitive* primitive = _primitives[id]; + if (primitive) { + _primitives[id] = 0; + _cpuMemoryUsage -= primitive->getMemoryUsage(); + deconstructElements(primitive); + + // Queue the index onto the available primitive stack. + _availablePrimitiveIndex.push(id); + } + } +} + +void PrimitiveRenderer::vRelease() { + + QMutexLocker lock(&_guard); + + terminateBookkeeping(); + initializeBookkeeping(); +} + +void PrimitiveRenderer::vRender() { + + int id; + QMutexLocker lock(&_guard); + + // Iterate over the set of triangle element array buffer ids scheduled for + // destruction. Set the triangle element to the degenerate case. Queue the id + // onto the available tri element stack. + while (!_deconstructTriElementIndex.isEmpty()) { + id = _deconstructTriElementIndex.pop(); + deconstructTriElement(id); + _availableTriElementIndex.push(id); + } + + // Iterate over the set of primitive ids scheduled for construction. Transfer + // primitive data to the GPU. + while (!_constructPrimitiveIndex.isEmpty()) { + id = _constructPrimitiveIndex.pop(); + Primitive* primitive = _primitives[id]; + if (primitive) { + constructElements(primitive); + + // No need to keep an extra copy of the vertices + _cpuMemoryUsage -= primitive->getMemoryUsage(); + primitive->releaseVertexElements(); + _cpuMemoryUsage += primitive->getMemoryUsage(); + } + } + + // The application uses clockwise winding for the definition of front face, this renderer + // aalso uses clockwise (that is the gl default) to construct the triangulation + // so... + //glFrontFace(GL_CW); + glEnable(GL_CULL_FACE); + + glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(VertexElement), 0); + + glEnableClientState(GL_NORMAL_ARRAY); + glNormalPointer(GL_FLOAT, sizeof(VertexElement), (const GLvoid*)12); + + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(VertexElement), (const GLvoid*)24); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId); + glDrawElements(GL_TRIANGLES, 3 * _triElementCount, GL_UNSIGNED_INT, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glDisable(GL_CULL_FACE); +} + +unsigned long PrimitiveRenderer::vGetMemoryUsage() { + return _cpuMemoryUsage; +} + +unsigned long PrimitiveRenderer::vGetMemoryUsageGPU() { + return _gpuMemoryUsage; +} \ No newline at end of file diff --git a/interface/src/PrimitiveRenderer.h b/interface/src/PrimitiveRenderer.h new file mode 100644 index 0000000000..51d96e0840 --- /dev/null +++ b/interface/src/PrimitiveRenderer.h @@ -0,0 +1,488 @@ +/// +/// @file PrimitiveRenderer.h +/// A geometric primitive renderer. +/// +/// @author: Norman Crafts +/// @copyright 2014, High Fidelity, Inc. All rights reserved. +/// + +#ifndef __interface__PrimitiveRenderer__ +#define __interface__PrimitiveRenderer__ + +#include +#include +#include + +/// Vertex element structure. +/// Using the array of structures approach to specifying +/// vertex data to GL cuts down on the calls to glBufferSubData +/// +typedef + struct __VertexElement { + struct __position { + float x; + float y; + float z; + } position; + struct __normal { + float x; + float y; + float z; + } normal; + struct __color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } color; + } VertexElement; + +/// Triangle element index structure. +/// Specify the vertex indices of the triangle and its element index. +/// +typedef + struct __TriElement { + int indices[3]; + int id; + + } TriElement; + +/// Vertex element list container. +/// +typedef QVector VertexElementList; + +/// Vertex element index list container. +/// +typedef QVector VertexElementIndexList; + +/// Triangle element list container +/// +typedef QVector TriElementList; + +/// +/// @class Primitive +/// Primitive Interface class. +/// Abstract class for accessing vertex and tri elements of geometric primitives +/// +/// +class Primitive { +public: + virtual ~Primitive(); + + // API methods go here + + /// Vertex element accessor. + /// @return A list of vertex elements of the primitive + /// + const VertexElementList& vertexElements() const; + + /// Vertex element index accessor. + /// @return A list of vertex element indices of the primitive + /// + VertexElementIndexList& vertexElementIndices(); + + /// Tri element accessor. + /// @return A list of tri elements of the primitive + /// + TriElementList& triElements(); + + /// Release vertex elements. + /// + void releaseVertexElements(); + + /// Get memory usage. + /// + unsigned long getMemoryUsage(); + +protected: + /// Default constructor prohibited to API user, restricted to service implementer. + /// + Primitive(); + +private: + /// Copy constructor prohibited. + /// + Primitive( + const Primitive& prim + ); + + // SPI methods are defined here + + /// Vertex element accessor. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual const VertexElementList& vVertexElements() const = 0; + + /// Vertex element index accessor. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual VertexElementIndexList& vVertexElementIndices() = 0; + + /// Tri element accessor. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual TriElementList& vTriElements() = 0; + + /// Release vertex elements. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual void vReleaseVertexElements() = 0; + + /// Get memory usage. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual unsigned long vGetMemoryUsage() = 0; + +}; + + +/// +/// @class Cube +/// Class for accessing the vertex and triangle elements of a cube +/// +class Cube: public Primitive { +public: + /// Configuration dependency injection constructor. + /// + Cube( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faces + ); + + ~Cube(); + +private: + /// Copy constructor prohibited. + /// + Cube ( + const Cube& cube + ); + + void init( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faceExclusions + ); + + void terminate(); + + void initializeVertices( + float x, + float y, + float z, + float s, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char faceExclusions + ); + + void terminateVertices(); + + void initializeTris( + unsigned char faceExclusions + ); + + void terminateTris(); + + // SPI virtual override methods go here + + const VertexElementList& vVertexElements() const; + VertexElementIndexList& vVertexElementIndices(); + TriElementList& vTriElements(); + void vReleaseVertexElements(); + unsigned long vGetMemoryUsage(); + +private: + VertexElementList _vertices; ///< Vertex element list + VertexElementIndexList _vertexIndices; ///< Vertex element index list + TriElementList _tris; ///< Tri element list + + unsigned long _cpuMemoryUsage; ///< Memory allocation of object + + static const int _sNumFacesPerCube = 6; + static const int _sNumVerticesPerCube = 24; + static unsigned char _sFaceIndexToHalfSpaceMask[6]; + static float _sVertexIndexToConstructionVector[24][3]; + static float _sVertexIndexToNormalVector[6][3]; + +}; + + +/// +/// @class Renderer +/// GL renderer interface class. +/// Abstract class for rendering geometric primitives in GL +/// +class Renderer { +public: + virtual ~Renderer(); + + // API methods go here + + /// Add primitive to renderer database. + /// + int add( + Primitive* primitive ///< Pointer to primitive + ); + + /// Remove primitive from renderer database. + /// + void remove( + int id ///< Primitive id + ); + + /// Clear all primitives from renderer database + /// + void release(); + + /// Render primitive database. + /// The render method assumes appropriate GL context and state has + /// already been provided for + /// + void render(); + + /// Get memory usage. + /// + unsigned long getMemoryUsage(); + + /// Get GPU memory usage. + /// + unsigned long getMemoryUsageGPU(); + +protected: + /// Default constructor prohibited to API user, restricted to service implementer. + /// + Renderer(); + +private: + /// Copy constructor prohibited. + /// + Renderer( + const Renderer& primitive + ); + + // SPI methods are defined here + + /// Add primitive to renderer database. + /// Service implementer to provide private override for this method + /// in derived class + /// @return primitive id + /// + virtual int vAdd( + Primitive* primitive ///< Pointer to primitive + ) = 0; + + /// Remove primitive from renderer database. + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual void vRemove( + int id ///< Primitive id + ) = 0; + + /// Clear all primitives from renderer database + /// Service implementer to provide private override for this method + /// in derived class + /// + virtual void vRelease() = 0; + + /// Render primitive database. + /// Service implementer to provide private virtual override for this method + /// in derived class + /// + virtual void vRender() = 0; + + /// Get memory usage. + /// + virtual unsigned long vGetMemoryUsage() = 0; + + /// Get GPU memory usage. + /// + virtual unsigned long vGetMemoryUsageGPU() = 0; + +}; + +/// +/// @class PrimitiveRenderer +/// Renderer implementation class for the rendering of geometric primitives +/// using GL element array and GL array buffers +/// +class PrimitiveRenderer : public Renderer { +public: + /// Configuration dependency injection constructor. + /// + PrimitiveRenderer( + int maxCount + ); + + ~PrimitiveRenderer(); + +private: + /// Default constructor prohibited. + /// + PrimitiveRenderer(); + + /// Copy constructor prohibited. + /// + PrimitiveRenderer( + const PrimitiveRenderer& renderer + ); + + void init(); + void terminate(); + + /// Allocate and initialize GL buffers. + /// + void initializeGL(); + + /// Terminate and deallocate GL buffers. + /// + void terminateGL(); + + void initializeBookkeeping(); + void terminateBookkeeping(); + + /// Construct the elements of the faces of the primitive. + /// + void constructElements( + Primitive* primitive + ); + + /// Deconstruct the elements of the faces of the primitive. + /// + void deconstructElements( + Primitive* primitive + ); + + /// Deconstruct the triangle element from the GL buffer. + /// + void deconstructTriElement( + int idx + ); + + /// Deconstruct the vertex element from the GL buffer. + /// + void deconstructVertexElement( + int idx + ); + + /// Transfer the vertex element to the GL buffer. + /// + void transferVertexElement( + int idx, + VertexElement *vertex + ); + + /// Transfer the triangle element to the GL buffer. + /// + void transferTriElement( + int idx, + int tri[3] + ); + + /// Get available primitive index. + /// Get an available primitive index from either the recycling + /// queue or incrementing the counter + /// + int getAvailablePrimitiveIndex(); + + /// Get available vertex element index. + /// Get an available vertex element index from either the recycling + /// queue or incrementing the counter + /// + int getAvailableVertexElementIndex(); + + /// Get available triangle element index. + /// Get an available triangle element index from either the elements + /// scheduled for deconstruction queue, the recycling + /// queue or incrementing the counter + /// + int getAvailableTriElementIndex(); + + // SPI virtual override methods go here + + /// Add primitive to renderer database. + /// + int vAdd( + Primitive* primitive + ); + + /// Remove primitive from renderer database. + /// + void vRemove( + int id + ); + + /// Clear all primitives from renderer database + /// + void vRelease(); + + /// Render triangle database. + /// + void vRender(); + + /// Get memory usage. + /// + unsigned long vGetMemoryUsage(); + + /// Get gpu memory usage. + /// + unsigned long vGetMemoryUsageGPU(); + +private: + + int _maxCount; + + // GL related parameters + + GLuint _triBufferId; ///< GL element array buffer id + GLuint _vertexBufferId; ///< GL vertex array buffer id + + // Book keeping parameters + + int _vertexElementCount; ///< Count of vertices + int _maxVertexElementCount; ///< Max count of vertices + + int _triElementCount; ///< Count of triangles + int _maxTriElementCount; ///< Max count of triangles + + QVector _primitives; ///< Vector of primitive + int _primitiveCount; ///< Count of primitives + + QStack _availablePrimitiveIndex; ///< Queue of primitive indices available + QStack _availableVertexElementIndex; ///< Queue of vertex element indices available + QStack _availableTriElementIndex; ///< Queue of triangle element indices available + QStack _deconstructTriElementIndex; ///< Queue of triangle element indices requiring deletion from GL + QStack _constructPrimitiveIndex; ///< Queue of primitives requiring addition to GL + + QMutex _guard; + + // Statistics parameters, not necessary for proper operation + + unsigned long _gpuMemoryUsage; + unsigned long _cpuMemoryUsage; + + + static const int _sIndicesPerTri = 3; + static const int _sBytesPerTriElement = sizeof(GLint) * _sIndicesPerTri; +}; + + +#endif diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 693a010182..99f6171b7a 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -61,8 +61,12 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _maxVoxels(maxVoxels), _initialized(false), _writeArraysLock(QReadWriteLock::Recursive), - _readArraysLock(QReadWriteLock::Recursive) - { + _readArraysLock(QReadWriteLock::Recursive), + _inOcclusions(false), + _showCulledSharedFaces(false), + _usePrimitiveRenderer(false), + _renderer(0) +{ _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; @@ -110,7 +114,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) void VoxelSystem::elementDeleted(OctreeElement* element) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; if (voxel->getVoxelSystem() == this) { - if (_voxelsInWriteArrays != 0) { + if ((_voxelsInWriteArrays != 0) || _usePrimitiveRenderer) { forceRemoveNodeFromArrays(voxel); } else { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { @@ -290,6 +294,7 @@ void VoxelSystem::setUseVoxelShader(bool useVoxelShader) { cleanupVoxelMemory(); } _useVoxelShader = useVoxelShader; + _usePrimitiveRenderer = false; if (wasInitialized) { initVoxelMemory(); } @@ -375,8 +380,11 @@ void VoxelSystem::cleanupVoxelMemory() { _writeVerticesArray = NULL; _readColorsArray = NULL; _writeColorsArray = NULL; - } + + delete _renderer; + _renderer = 0; + delete[] _writeVoxelDirtyArray; delete[] _readVoxelDirtyArray; _writeVoxelDirtyArray = _readVoxelDirtyArray = NULL; @@ -506,7 +514,6 @@ void VoxelSystem::initVoxelMemory() { _readColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels]; _memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels); - // create our simple fragment shader if we're the first system to init if (!_perlinModulateProgram.isLinked()) { switchToResourcesParentIfRequired(); @@ -526,6 +533,7 @@ void VoxelSystem::initVoxelMemory() { _shadowMapProgram.release(); } } + _renderer = new PrimitiveRenderer(_maxVoxels); _initialized = true; @@ -669,7 +677,12 @@ void VoxelSystem::setupNewVoxelsForDrawing() { }; PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); _callsToTreesToArrays++; + if (_writeRenderFullVBO) { + if (_usePrimitiveRenderer) { + _renderer->release(); + clearAllNodesBufferIndex(); + } clearFreeBufferIndexes(); } _voxelsUpdated = newTreeToArrays(_tree->getRoot()); @@ -686,18 +699,24 @@ void VoxelSystem::setupNewVoxelsForDrawing() { _voxelsUpdated = 0; } - // lock on the buffer write lock so we can't modify the data when the GPU is reading it - _readArraysLock.lockForWrite(); + if (_usePrimitiveRenderer) { + if (_voxelsUpdated) { + _voxelsDirty=true; + } + } else { + // lock on the buffer write lock so we can't modify the data when the GPU is reading it + _readArraysLock.lockForWrite(); + + if (_voxelsUpdated) { + _voxelsDirty=true; + } + + // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated + copyWrittenDataToReadArrays(didWriteFullVBO); + _readArraysLock.unlock(); - if (_voxelsUpdated) { - _voxelsDirty=true; } - // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated - copyWrittenDataToReadArrays(didWriteFullVBO); - - _readArraysLock.unlock(); - quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; @@ -724,23 +743,26 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { return; // bail early, it hasn't been long enough since the last time we ran } - // lock on the buffer write lock so we can't modify the data when the GPU is reading it - { - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" ); - _readArraysLock.lockForWrite(); + if (_usePrimitiveRenderer) { + _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty + _voxelsUpdated = 0; + } else { + // lock on the buffer write lock so we can't modify the data when the GPU is reading it + { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" ); + _readArraysLock.lockForWrite(); + } + + _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty + + // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated + copyWrittenDataToReadArrays(_writeRenderFullVBO); + + // after... + _voxelsUpdated = 0; + _readArraysLock.unlock(); } - - _voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty - - // copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated - copyWrittenDataToReadArrays(_writeRenderFullVBO); - - // after... - _voxelsUpdated = 0; - - _readArraysLock.unlock(); - quint64 end = usecTimestampNow(); int elapsedmsec = (end - start) / 1000; _setupNewVoxelsForDrawingLastFinished = end; @@ -892,7 +914,8 @@ void VoxelSystem::cleanupRemovedVoxels() { // we also might have VBO slots that have been abandoned, if too many of our VBO slots // are abandonded we want to rerender our full VBOs const float TOO_MANY_ABANDONED_RATIO = 0.5f; - if (!_writeRenderFullVBO && (_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) { + if (!_usePrimitiveRenderer && !_writeRenderFullVBO && + (_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { qDebug() << "cleanupRemovedVoxels().. _abandonedVBOSlots [" << _abandonedVBOSlots << "] > TOO_MANY_ABANDONED_RATIO"; @@ -1052,13 +1075,22 @@ int VoxelSystem::forceRemoveNodeFromArrays(VoxelTreeElement* node) { return 0; } - // if the node is not in the VBOs then we have nothing to do! - if (node->isKnownBufferIndex()) { - // If this node has not yet been written to the array, then add it to the end of the array. - glBufferIndex nodeIndex = node->getBufferIndex(); - node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); - freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible! - return 1; // updated! + if (_usePrimitiveRenderer) { + if (node->isKnownBufferIndex()) { + int primitiveIndex = node->getBufferIndex(); + _renderer->remove(primitiveIndex); + node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + return 1; + } + } else { + // if the node is not in the VBOs then we have nothing to do! + if (node->isKnownBufferIndex()) { + // If this node has not yet been written to the array, then add it to the end of the array. + glBufferIndex nodeIndex = node->getBufferIndex(); + node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible! + return 1; // updated! + } } return 0; // not-updated } @@ -1088,17 +1120,44 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo if (node->getShouldRender()) { glm::vec3 startVertex = node->getCorner(); float voxelScale = node->getScale(); + nodeColor const & color = node->getColor(); - glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN; - if (reuseIndex && node->isKnownBufferIndex()) { - nodeIndex = node->getBufferIndex(); + if (_usePrimitiveRenderer) { + if (node->isKnownBufferIndex()) { + int primitiveIndex = node->getBufferIndex(); + _renderer->remove(primitiveIndex); + node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + } else { + node->setVoxelSystem(this); + } + unsigned char occlusions; + if (_showCulledSharedFaces) { + occlusions = ~node->getInteriorOcclusions(); + } else { + occlusions = node->getInteriorOcclusions(); + } + if (occlusions != OctreeElement::HalfSpace::All) { + Cube* cube = new Cube( + startVertex.x, startVertex.y, startVertex.z, voxelScale, + color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX], + occlusions); + if (cube) { + int primitiveIndex = _renderer->add(cube); + node->setBufferIndex(primitiveIndex); + } + } } else { - nodeIndex = getNextBufferIndex(); - node->setBufferIndex(nodeIndex); - node->setVoxelSystem(this); + glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN; + if (reuseIndex && node->isKnownBufferIndex()) { + nodeIndex = node->getBufferIndex(); + } else { + nodeIndex = getNextBufferIndex(); + node->setBufferIndex(nodeIndex); + node->setVoxelSystem(this); + } + // populate the array with points for the 8 vertices and RGB color for each added vertex + updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor()); } - // populate the array with points for the 8 vertices and RGB color for each added vertex - updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor()); return 1; // updated! } else { // If we shouldn't render, and we're in reuseIndex mode, then free our index, this only operates @@ -1246,22 +1305,24 @@ void VoxelSystem::updateVBOs() { }; // would like to include _callsToTreesToArrays PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); - if (_voxelsDirty) { + if (! _usePrimitiveRenderer) { + if (_voxelsDirty) { - // attempt to lock the read arrays, to for copying from them to the actual GPU VBOs. - // if we fail to get the lock, that's ok, our VBOs will update on the next frame... - const int WAIT_FOR_LOCK_IN_MS = 5; - if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { - if (_readRenderFullVBO) { - updateFullVBOs(); + // attempt to lock the read arrays, to for copying from them to the actual GPU VBOs. + // if we fail to get the lock, that's ok, our VBOs will update on the next frame... + const int WAIT_FOR_LOCK_IN_MS = 5; + if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { + if (_readRenderFullVBO) { + updateFullVBOs(); + } else { + updatePartialVBOs(); + } + _voxelsDirty = false; + _readRenderFullVBO = false; + _readArraysLock.unlock(); } else { - updatePartialVBOs(); + qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()"; } - _voxelsDirty = false; - _readRenderFullVBO = false; - _readArraysLock.unlock(); - } else { - qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()"; } } _callsToTreesToArrays = 0; // clear it @@ -1391,7 +1452,8 @@ void VoxelSystem::render() { glDisableVertexAttribArray(attributeLocation); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); } - } else { + } else + if (!_usePrimitiveRenderer) { PerformanceWarning warn(showWarnings, "render().. TRIANGLES..."); { @@ -1465,6 +1527,12 @@ void VoxelSystem::render() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } } + else { + applyScaleAndBindProgram(texture); + _renderer->render(); + removeScaleAndReleaseProgram(texture); + + } } void VoxelSystem::applyScaleAndBindProgram(bool texture) { @@ -1517,6 +1585,12 @@ void VoxelSystem::killLocalVoxels() { _tree->eraseAllOctreeElements(); _tree->unlock(); clearFreeBufferIndexes(); + if (_usePrimitiveRenderer) { + if (_renderer) { + _renderer->release(); + } + clearAllNodesBufferIndex(); + } _voxelsInReadArrays = 0; // do we need to do this? setupNewVoxelsForDrawing(); } @@ -1544,6 +1618,209 @@ void VoxelSystem::clearAllNodesBufferIndex() { } } +bool VoxelSystem::inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData) { + _nodeCount++; + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + + // Nothing to do at the leaf level + if (voxel->isLeaf()) { + return false; + } + + // Bit mask of occluded shared faces indexed by child + unsigned char occludedSharedFace[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + // Traverse all pair combinations of children + for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) { + + VoxelTreeElement* childA = voxel->getChildAtIndex(i); + if (childA) { + + // Get the child A's occluding faces, for a leaf that will be + // all six voxel faces, and for a non leaf, that will be + // all faces which are completely covered by four child octants. + unsigned char exteriorOcclusionsA = childA->getExteriorOcclusions(); + + for (int j = i; --j >= 0; ) { + + VoxelTreeElement* childB = voxel->getChildAtIndex(j); + if (childB) { + + // Get child B's occluding faces + unsigned char exteriorOcclusionsB = childB->getExteriorOcclusions(); + + // Determine the shared halfspace partition between siblings A and B, + // i.e., near/far, left/right, or top/bottom + unsigned char partitionA = _sOctantIndexToSharedBitMask[i][j] & + exteriorOcclusionsA; + unsigned char partitionB = _sOctantIndexToSharedBitMask[i][j] & + exteriorOcclusionsB; + + // Determine which face of each sibling is occluded. + + // The _sOctantIndexToBitMask is a partition occupancy mask. For + // example, if the near-left-top (NLT) and near-left-bottom (NLB) child voxels + // exist, the shared partition is top-bottom (TB), and thus the occluded + // shared face of the NLT voxel is its bottom face. + occludedSharedFace[i] |= (partitionB & _sOctantIndexToBitMask[i]); + occludedSharedFace[j] |= (partitionA & _sOctantIndexToBitMask[j]); + } + } + // Exchange bit pairs, left to right, vice versa, etc. + occludedSharedFace[i] = _sSwizzledOcclusionBits[occludedSharedFace[i]]; + // Combine this voxel's interior excluded shared face only to those children which are coincident + // with the excluded face. + occludedSharedFace[i] |= (voxel->getInteriorOcclusions() & _sOctantIndexToBitMask[i]); + + // Inform the child + childA->setInteriorOcclusions(occludedSharedFace[i]); + if (occludedSharedFace[i] != OctreeElement::HalfSpace::None) { + //const glm::vec3& v = voxel->getCorner(); + //float s = voxel->getScale(); + + //qDebug("Child %d of voxel at %f %f %f size: %f has %02x occlusions", i, v.x, v.y, v.z, s, occludedSharedFace[i]); + } + } + } + return true; +} + +bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData) { + _nodeCount++; + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + + // Nothing to do at the leaf level + if (voxel->isLeaf()) { + // By definition the the exterior faces of a leaf voxel are + // always occluders. + voxel->setExteriorOcclusions(OctreeElement::HalfSpace::All); + // And the sibling occluders + voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None); + return false; + } else { + voxel->setExteriorOcclusions(OctreeElement::HalfSpace::None); + voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None); + } + + // Count of exterior occluding faces of this voxel element indexed + // by half space partition + unsigned int exteriorOcclusionsCt[6] = { 0, 0, 0, 0, 0, 0 }; + + // Traverse all children + for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) { + + VoxelTreeElement* child = voxel->getChildAtIndex(i); + if (child) { + + // Get the child's occluding faces, for a leaf, that will be + // all six voxel faces, and for a non leaf, that will be + // all faces which are completely covered by four child octants. + unsigned char exteriorOcclusionsOfChild = child->getExteriorOcclusions(); + exteriorOcclusionsOfChild &= _sOctantIndexToBitMask[i]; + + for (int j = 6; --j >= 0; ) { + + // Determine if the halfspace partition indexed by 1 << j is + // present in the exterior occlusions of the child. + unsigned char partition = exteriorOcclusionsOfChild & (1 << j); + + if (partition) { + exteriorOcclusionsCt[j]++; + } + } + } + } + { + // Derive the exterior occlusions of the voxel elements from the exclusions + // of its children + unsigned char exteriorOcclusions = OctreeElement::HalfSpace::None; + for (int i = 6; --i >= 0; ) { + if (exteriorOcclusionsCt[i] == _sNumOctantsPerHemiVoxel) { + + // Exactly four octants qualify for full exterior occlusion + exteriorOcclusions |= (1 << i); + } + } + + // Inform the voxel element + voxel->setExteriorOcclusions(exteriorOcclusions); + + if (exteriorOcclusions == OctreeElement::HalfSpace::All) { + //const glm::vec3& v = voxel->getCorner(); + //float s = voxel->getScale(); + + //qDebug("Completely occupied voxel at %f %f %f size: %f", v.x, v.y, v.z, s); + + // TODO: All of the exterior faces of this voxel element are + // occluders, which means that this element is completely + // occupied. Hence, the subtree from this node could be + // pruned and replaced by a leaf voxel, if the visible + // properties of the children are the same + } else if (exteriorOcclusions != OctreeElement::HalfSpace::None) { + //const glm::vec3& v = voxel->getCorner(); + //float s = voxel->getScale(); + + //qDebug("Partially occupied voxel at %f %f %f size: %f with %02x", v.x, v.y, v.z, s, exteriorOcclusions); + } + } + return true; +} + + +void VoxelSystem::cullSharedFaces() { + + if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { + _useVoxelShader = false; + _usePrimitiveRenderer = true; + inspectForOcclusions(); + } else { + _usePrimitiveRenderer = false; + clearAllNodesBufferIndex(); + } + _writeRenderFullVBO = true; + _tree->setDirtyBit(); + setupNewVoxelsForDrawing(); +} + +void VoxelSystem::showCulledSharedFaces() { + + _tree->lockForRead(); + if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) { + _showCulledSharedFaces = true; + } else { + _showCulledSharedFaces = false; + } + _tree->unlock(); + if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { + _writeRenderFullVBO = true; + _tree->setDirtyBit(); + setupNewVoxelsForDrawing(); + } +} + +void VoxelSystem::inspectForOcclusions() { + + if (_inOcclusions) { + return; + } + _inOcclusions = true; + _nodeCount = 0; + + bool showDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); + PerformanceWarning warn(showDebugDetails, "inspectForOcclusions()"); + + _tree->lockForRead(); + _tree->recurseTreeWithPostOperation(inspectForExteriorOcclusionsOperation); + _nodeCount = 0; + _tree->recurseTreeWithOperation(inspectForInteriorOcclusionsOperation); + _tree->unlock(); + + if (showDebugDetails) { + qDebug("inspecting all occlusions of %d nodes", _nodeCount); + } + _inOcclusions = false; +} + bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData) { _nodeCount++; element->setDirtyBit(); @@ -2172,6 +2449,13 @@ bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraDat // These are both needed to force redraw... voxel->setDirtyBit(); voxel->markWithChangedTime(); + // and this? +// no, not needed, because markWithChangedTime notifies hooks, which calls elementUpdated, which calls updateNodeInArrays +// { +// VoxelSystem* thisVoxelSystem = args->thisVoxelSystem; +// thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->updateNodeInArrays(voxel, true, true); +// thisVoxelSystem->setupNewVoxelsForDrawingSingleNode(); +// } args->nodesShown++; } @@ -2363,7 +2647,9 @@ public: nodesInVBONotShouldRender(0), nodesInVBOOverExpectedMax(0), duplicateVBOIndex(0), - leafNodes(0) + leafNodes(0), + culledLeafNodes(0), + nodesInPrimitiveRenderer(0) { hasIndexFound = new bool[maxVoxels]; memset(hasIndexFound, false, maxVoxels * sizeof(bool)); @@ -2382,6 +2668,8 @@ public: unsigned long nodesInVBOOverExpectedMax; unsigned long duplicateVBOIndex; unsigned long leafNodes; + unsigned long culledLeafNodes; ///< Number of completely culled nodes because of face sharing + unsigned long nodesInPrimitiveRenderer; unsigned long expectedMax; @@ -2396,6 +2684,9 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v if (voxel->isLeaf()) { args->leafNodes++; + if (voxel->getInteriorOcclusions() == OctreeElement::HalfSpace::All) { + args->culledLeafNodes++; + } } if (voxel->isColored()) { @@ -2410,13 +2701,27 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v args->dirtyNodes++; } - if (voxel->isKnownBufferIndex()) { - args->nodesInVBO++; - unsigned long nodeIndex = voxel->getBufferIndex(); + unsigned long nodeIndex = 0; + if (voxel->getBufferIndex()) { + args->nodesInPrimitiveRenderer++; + nodeIndex = voxel->getBufferIndex(); const bool extraDebugging = false; // enable for extra debugging if (extraDebugging) { - qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s", + qDebug("node In Renderer... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s, culledFaces=0x%02x \n", + voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(), + nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender()), + voxel->getInteriorOcclusions()); + } + } + + if (voxel->isKnownBufferIndex()) { + args->nodesInVBO++; + nodeIndex = voxel->getBufferIndex(); + + const bool extraDebugging = false; // enable for extra debugging + if (extraDebugging) { + qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s \n", voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(), nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender())); } @@ -2447,10 +2752,12 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { glBufferIndex minDirty = GLBUFFER_INDEX_UNKNOWN; glBufferIndex maxDirty = 0; - for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) { - if (_writeVoxelDirtyArray[i]) { - minDirty = std::min(minDirty,i); - maxDirty = std::max(maxDirty,i); + if (!_usePrimitiveRenderer) { + for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) { + if (_writeVoxelDirtyArray[i]) { + minDirty = std::min(minDirty,i); + maxDirty = std::max(maxDirty,i); + } } } @@ -2461,31 +2768,40 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { _tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args); - qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld", + qDebug("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); - qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty), - _voxelsInWriteArrays, minDirty, maxDirty); + if (!_usePrimitiveRenderer) { + qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty), + _voxelsInWriteArrays, minDirty, maxDirty); - qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld", - args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender); + qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld", + args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender); - glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN; - glBufferIndex maxInVBO = 0; + qDebug(" memory usage %ld \n gpu memory usage %ld \n", _memoryUsageRAM, _memoryUsageVBO); - for (glBufferIndex i = 0; i < _maxVoxels; i++) { - if (args.hasIndexFound[i]) { - minInVBO = std::min(minInVBO,i); - maxInVBO = std::max(maxInVBO,i); + glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN; + glBufferIndex maxInVBO = 0; + + for (glBufferIndex i = 0; i < _maxVoxels; i++) { + if (args.hasIndexFound[i]) { + minInVBO = std::min(minInVBO,i); + maxInVBO = std::max(maxInVBO,i); + } } + + qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld", + minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays); + + qDebug(" _freeIndexes.size()=%ld", + _freeIndexes.size()); + } else { + qDebug(" PrimitiveRenderer nodes %ld \n completely culled nodes %ld \n", + args.nodesInPrimitiveRenderer, args.culledLeafNodes); + + qDebug(" memory usage %ld \n gpu memory usage %ld \n", + _renderer->getMemoryUsage(), _renderer->getMemoryUsageGPU()); } - - qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld", - minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays); - - qDebug(" _freeIndexes.size()=%ld", - _freeIndexes.size()); - qDebug("DONE WITH Local Voxel Tree Statistics >>>>>>>>>>>>"); } @@ -2884,3 +3200,170 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() { return (_initialMemoryUsageGPU - currentFreeMemory); } +// Swizzle value of bit pairs of the value of index +unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = { + 0x0000, // 00000000 + 0x0002, // 00000001 + 0x0001, // 00000010 + 0x0003, // 00000011 + 0x0008, // 00000100 + 0x000a, // 00000101 + 0x0009, // 00000110 + 0x000b, // 00000111 + 0x0004, // 00001000 + 0x0006, // 00001001 + 0x0005, // 00001010 + 0x0007, // 00001011 + 0x000c, // 00001100 + 0x000e, // 00001101 + 0x000d, // 00001110 + 0x000f, // 00001111 + 0x0020, // 00010000 + 0x0022, // 00010001 + 0x0021, // 00010010 + 0x0023, // 00010011 + 0x0028, // 00010100 + 0x002a, // 00010101 + 0x0029, // 00010110 + 0x002b, // 00010111 + 0x0024, // 00011000 + 0x0026, // 00011001 + 0x0025, // 00011010 + 0x0027, // 00011011 + 0x002c, // 00011100 + 0x002e, // 00011101 + 0x002d, // 00011110 + 0x002f, // 00011111 + 0x0010, // 00100000 + 0x0012, // 00100001 + 0x0011, // 00100010 + 0x0013, // 00100011 + 0x0018, // 00100100 + 0x001a, // 00100101 + 0x0019, // 00100110 + 0x001b, // 00100111 + 0x0014, // 00101000 + 0x0016, // 00101001 + 0x0015, // 00101010 + 0x0017, // 00101011 + 0x001c, // 00101100 + 0x001e, // 00101101 + 0x001d, // 00101110 + 0x001f, // 00101111 + 0x0030, // 00110000 + 0x0032, // 00110001 + 0x0031, // 00110010 + 0x0033, // 00110011 + 0x0038, // 00110100 + 0x003a, // 00110101 + 0x0039, // 00110110 + 0x003b, // 00110111 + 0x0034, // 00111000 + 0x0036, // 00111001 + 0x0035, // 00111010 + 0x0037, // 00111011 + 0x003c, // 00111100 + 0x003e, // 00111101 + 0x003d, // 00111110 + 0x003f, // 00111111 +}; + +// Octant bitmask array indexed by octant. The mask value indicates the octant's halfspace partitioning. The index +// value corresponds to the voxel's octal code derived in "pointToVoxel" in SharedUtil.cpp, which, BTW, does *not* +// correspond to the "ChildIndex" enum value in OctreeElement.h +unsigned char VoxelSystem::_sOctantIndexToBitMask[8] = { + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near, + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far, + OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near, + OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far, + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near, + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far, + OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near, + OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far, +}; + +// Two dimensional array map indexed by octant row and column. The mask value +// indicates the two faces shared by the octants +unsigned char VoxelSystem::_sOctantIndexToSharedBitMask[8][8] = { + { // Index 0: Bottom-Left-Near + 0, // Bottom-Left-Near + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Far + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Near + 0, // Top-Left-Far + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Near + 0, // Bottom-Right-Far + 0, // Top-Right-Near + 0, // Top-Right-Far + }, + { // Index 1: Bottom-Left-Far + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Near + 0, // Bottom-Left-Far + 0, // Top-Left-Near + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Far + 0, // Bottom-Right-Near + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Far + 0, // Top-Right-Near + 0, // Top-Right-Far + }, + { // Index 2: Top-Left-Near + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Near + 0, // Bottom-Left-Far + 0, // Top-Left-Near + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Far + 0, // Bottom-Right-Near + 0, // Bottom-Right-Far + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Near + 0, // Top-Right-Far + }, + { // Index 3: Top-Left-Far + 0, // Bottom-Left-Near + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Far + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Near + 0, // Top-Left-Far + 0, // Bottom-Right-Near + 0, // Bottom-Right-Far + 0, // Top-Right-Near + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Far + }, + { // Index 4: Bottom-Right-Near + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Near + 0, // Bottom-Left-Far + 0, // Top-Left-Near + 0, // Top-Left-Far + 0, // Bottom-Right-Near + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Far + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Near + 0, // Top-Right-Far + }, + { // Index 5: Bottom-Right-Far + 0, // Bottom-Left-Near + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Far + 0, // Top-Left-Near + 0, // Top-Left-Far + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Near + 0, // Bottom-Right-Far + 0, // Top-Right-Near + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Far + }, + { // Index 6: Top-Right-Near + 0, // Bottom-Left-Near + 0, // Bottom-Left-Far + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Near + 0, // Top-Left-Far + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Near + 0, // Bottom-Right-Far + 0, // Top-Right-Near + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Far + }, + { // Index 7: Top-Right-Far + 0, // Bottom-Left-Near + 0, // Bottom-Left-Far + 0, // Top-Left-Near + OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Far + 0, // Bottom-Right-Near + OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Far + OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Near + 0, // Top-Right-Far + }, +}; + diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index 121a7f86c4..0e2ae29475 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -24,11 +24,13 @@ #include "Util.h" #include "world.h" #include "renderer/VoxelShader.h" +#include "PrimitiveRenderer.h" class ProgramObject; const int NUM_CHILDREN = 8; + struct VoxelShaderVBOData { float x, y, z; // position @@ -82,6 +84,7 @@ public: virtual void removeOutOfView(); virtual void hideOutOfView(bool forceFullFrustum = false); + void inspectForOcclusions(); bool hasViewChanged(); bool isViewChanging(); @@ -134,6 +137,8 @@ public slots: void falseColorizeBySource(); void forceRedrawEntireTree(); void clearAllNodesBufferIndex(); + void cullSharedFaces(); + void showCulledSharedFaces(); void cancelImport(); @@ -189,6 +194,8 @@ private: static bool killSourceVoxelsOperation(OctreeElement* element, void* extraData); static bool forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData); static bool clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData); + static bool inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData); + static bool inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData); static bool hideOutOfViewOperation(OctreeElement* element, void* extraData); static bool hideAllSubTreeOperation(OctreeElement* element, void* extraData); static bool showAllSubTreeOperation(OctreeElement* element, void* extraData); @@ -305,6 +312,18 @@ private: float _lastKnownVoxelSizeScale; int _lastKnownBoundaryLevelAdjust; + + bool _inOcclusions; + bool _showCulledSharedFaces; ///< Flag visibility of culled faces + bool _usePrimitiveRenderer; ///< Flag primitive renderer for use + PrimitiveRenderer* _renderer; ///< Voxel renderer + + static const int _sNumOctantsPerHemiVoxel = 4; + static int _sCorrectedChildIndex[8]; + static unsigned short _sSwizzledOcclusionBits[64]; ///< Swizzle value of bit pairs of the value of index + static unsigned char _sOctantIndexToBitMask[8]; ///< Map octant index to partition mask + static unsigned char _sOctantIndexToSharedBitMask[8][8]; ///< Map octant indices to shared partition mask + }; #endif diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4cc568cf18..b873b10017 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -144,7 +144,7 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { int bytesRead = numBytesForPacketHeader(datagram); - QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData); + QByteArray dummyAvatarByteArray = byteArrayWithPopulatedHeader(PacketTypeAvatarData); int numDummyHeaderBytes = dummyAvatarByteArray.size(); int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7358a34fda..f388f89701 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -651,17 +651,10 @@ void MyAvatar::loadData(QSettings* settings) { } void MyAvatar::sendKillAvatar() { - QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar); + QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar); NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer); } -void MyAvatar::sendIdentityPacket() { - QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); - identityPacket.append(AvatarData::identityByteArray()); - - NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); -} - void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) { // first orbit horizontally glm::quat orientation = getOrientation(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 29f8f6d3bf..cbefd6ba0a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -92,7 +92,6 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); - void sendIdentityPacket(); // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index ff243459b7..2d4074f125 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -47,7 +47,7 @@ void AudioInjector::injectAudio() { NodeList* nodeList = NodeList::getInstance(); // setup the packet for injected audio - QByteArray injectAudioPacket = byteArrayWithPopluatedHeader(PacketTypeInjectAudio); + QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio); QDataStream packetStream(&injectAudioPacket, QIODevice::Append); packetStream << QUuid::createUuid(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index da176f3fd9..3c50f2622c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -323,7 +323,6 @@ void AvatarData::setDisplayName(const QString& displayName) { qDebug() << "Changing display name for avatar to" << displayName; } - void AvatarData::setClampedTargetScale(float targetScale) { targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); @@ -338,3 +337,10 @@ void AvatarData::setOrientation(const glm::quat& orientation) { _bodyYaw = eulerAngles.y; _bodyRoll = eulerAngles.z; } + +void AvatarData::sendIdentityPacket() { + QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); + identityPacket.append(identityByteArray()); + + NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 1345a69f8e..07c774d5e6 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,6 +53,8 @@ static const float MIN_AVATAR_SCALE = .005f; const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation +const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; + const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst"); const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst"); @@ -81,8 +83,8 @@ class AvatarData : public NodeData { Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness) Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness) - Q_PROPERTY(QUrl faceModelURL READ getFaceModelURL WRITE setFaceModelURL) - Q_PROPERTY(QUrl skeletonModelURL READ getSkeletonModelURL WRITE setSkeletonModelURL) + Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript) + Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) public: AvatarData(); ~AvatarData(); @@ -150,14 +152,24 @@ public: QByteArray identityByteArray(); const QUrl& getFaceModelURL() const { return _faceModelURL; } + QString getFaceModelURLString() const { return _faceModelURL.toString(); } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); + QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); } + void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); } + + QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } + void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } + virtual float getBoundingRadius() const { return 1.f; } +public slots: + void sendIdentityPacket(); + protected: glm::vec3 _position; glm::vec3 _handPosition; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5a1e31e0d0..051d4f5ca2 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -58,6 +58,11 @@ void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* ex recurseNodeWithOperation(_rootNode, operation, extraData); } +// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each node in post-fix order. +void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData) { + recurseNodeWithPostOperation(_rootNode, operation, extraData); +} + // Recurses voxel node with an operation function void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, int recursionCount) { @@ -76,6 +81,23 @@ void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperatio } } +// Recurses voxel node with an operation function +void Octree::recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, + int recursionCount) { + if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + return; + } + + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* child = node->getChildAtIndex(i); + if (child) { + recurseNodeWithPostOperation(child, operation, extraData, recursionCount+1); + } + } + operation(node, extraData); +} + // Recurses voxel tree calling the RecurseOctreeOperation function for each node. // stops recursion if operation function returns false. void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 5287e3ce37..e0c7f414f8 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -208,10 +208,12 @@ public: OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const; OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s); - void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData=NULL); + void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL); + + void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL); void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, - const glm::vec3& point, void* extraData=NULL); + const glm::vec3& point, void* extraData = NULL); int encodeTreeBitstream(OctreeElement* node, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params) ; @@ -255,6 +257,11 @@ public: void recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, int recursionCount = 0); + /// Traverse child nodes of node applying operation in post-fix order + /// + void recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation, + void* extraData, int recursionCount = 0); + void recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, const glm::vec3& point, void* extraData, int recursionCount = 0); diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 38a30218d1..1785307696 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -199,6 +199,20 @@ public: CHILD_UNKNOWN = -1 }; + struct HalfSpace { + enum { + None = 0x00, + Bottom = 0x01, + Top = 0x02, + Right = 0x04, + Left = 0x08, + Near = 0x10, + Far = 0x20, + All = 0x3f, + }; + }; + + OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s); protected: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 5c089b9a74..076f941222 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -44,6 +44,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) : _isAvatar(false), + _avatarIdentityTimer(NULL), _avatarData(NULL) { _scriptContents = scriptContents; @@ -74,6 +75,21 @@ ScriptEngine::~ScriptEngine() { //printf("ScriptEngine::~ScriptEngine()...\n"); } +void ScriptEngine::setIsAvatar(bool isAvatar) { + _isAvatar = isAvatar; + + if (_isAvatar && !_avatarIdentityTimer) { + // set up the avatar identity timer + _avatarIdentityTimer = new QTimer(this); + + // connect our slot + connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); + + // start the timer + _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); + } +} + void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; @@ -84,6 +100,7 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa registerGlobalObject(objectName, _avatarData); } + void ScriptEngine::setupMenuItems() { if (_menu && _wantMenuItems) { _menu->addActionToQMenuAndActionHash(_menu->getActiveScriptsMenu(), _scriptMenuName, 0, this, SLOT(stop())); @@ -173,6 +190,12 @@ void ScriptEngine::evaluate() { } } +void ScriptEngine::sendAvatarIdentityPacket() { + if (_isAvatar && _avatarData) { + _avatarData->sendIdentityPacket(); + } +} + void ScriptEngine::run() { if (!_isInitialized) { init(); @@ -229,16 +252,7 @@ void ScriptEngine::run() { } if (_isAvatar && _avatarData) { - static QByteArray avatarPacket; - int numAvatarHeaderBytes = 0; - - if (avatarPacket.size() == 0) { - // pack the avatar header bytes the first time - // unlike the _avatar.getBroadcastData these won't change - numAvatarHeaderBytes = populatePacketHeader(avatarPacket, PacketTypeAvatarData); - } - - avatarPacket.resize(numAvatarHeaderBytes); + QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData); avatarPacket.append(_avatarData->toByteArray()); nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer); @@ -253,6 +267,9 @@ void ScriptEngine::run() { } emit scriptEnding(); + // kill the avatar identity timer + delete _avatarIdentityTimer; + if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) { // release the queue of edit voxel messages. _voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 3de16348d4..cf5ad1a68b 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -11,9 +11,9 @@ #include -#include #include #include +#include #include #include @@ -52,7 +52,7 @@ public: void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name - void setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; } + Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } void setAvatarData(AvatarData* avatarData, const QString& objectName); @@ -84,9 +84,12 @@ protected: bool _isInitialized; QScriptEngine _engine; bool _isAvatar; + QTimer* _avatarIdentityTimer; QHash _timerFunctionMap; private: + void sendAvatarIdentityPacket(); + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); diff --git a/libraries/shared/src/DataServerClient.cpp b/libraries/shared/src/DataServerClient.cpp index fd003aa3bb..8903aa0524 100644 --- a/libraries/shared/src/DataServerClient.cpp +++ b/libraries/shared/src/DataServerClient.cpp @@ -31,7 +31,7 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() { void DataServerClient::putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString) { // setup the header for this packet and push packetStream to desired spot - QByteArray putPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerPut); + QByteArray putPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerPut); QDataStream packetStream(&putPacket, QIODevice::Append); // pack our data for the put packet @@ -66,7 +66,7 @@ void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QU void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, DataServerCallbackObject* callbackObject) { if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { - QByteArray getPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerGet); + QByteArray getPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerGet); QDataStream packetStream(&getPacket, QIODevice::Append); // pack our data for the getPacket diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index bbb7750a2f..67e15b1f7c 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -531,7 +531,7 @@ void NodeList::sendDomainServerCheckIn() { // construct the DS check in packet if we can // check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination - QByteArray domainServerPacket = byteArrayWithPopluatedHeader(PacketTypeDomainListRequest); + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest); QDataStream packetStream(&domainServerPacket, QIODevice::Append); // pack our data to send to the domain-server @@ -619,7 +619,7 @@ void NodeList::sendAssignment(Assignment& assignment) { ? PacketTypeCreateAssignment : PacketTypeRequestAssignment; - QByteArray packet = byteArrayWithPopluatedHeader(assignmentPacketType); + QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); QDataStream packetStream(&packet, QIODevice::Append); packetStream << assignment; @@ -634,7 +634,7 @@ void NodeList::sendAssignment(Assignment& assignment) { } QByteArray NodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopluatedHeader(PacketTypePing); + QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); QDataStream packetStream(&pingPacket, QIODevice::Append); @@ -654,7 +654,7 @@ QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { quint64 timeFromOriginalPing; pingPacketStream >> timeFromOriginalPing; - QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypePingReply); + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); QDataStream packetStream(&replyPacket, QIODevice::Append); packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index b1f6ef1730..69178d7bd5 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -65,7 +65,7 @@ PacketVersion versionForPacketType(PacketType type) { } } -QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID) { +QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID) { QByteArray freshByteArray(MAX_PACKET_HEADER_BYTES, 0); freshByteArray.resize(populatePacketHeader(freshByteArray, type, connectionUUID)); return freshByteArray; diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index bb5821dd67..f6d745cb44 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType type); const QUuid nullUUID = QUuid(); -QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID); +QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID); diff --git a/libraries/voxels/src/VoxelTreeElement.cpp b/libraries/voxels/src/VoxelTreeElement.cpp index 6fd3997165..3f8af58afd 100644 --- a/libraries/voxels/src/VoxelTreeElement.cpp +++ b/libraries/voxels/src/VoxelTreeElement.cpp @@ -13,7 +13,11 @@ #include "VoxelTreeElement.h" #include "VoxelTree.h" -VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : OctreeElement() { +VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : + OctreeElement(), + _exteriorOcclusions(OctreeElement::HalfSpace::All), + _interiorOcclusions(OctreeElement::HalfSpace::None) +{ init(octalCode); }; diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h index 028d2456eb..83544bea32 100644 --- a/libraries/voxels/src/VoxelTreeElement.h +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -68,7 +68,12 @@ public: void setDensity(float density) { _density = density; } float getDensity() const { return _density; } - + + void setInteriorOcclusions(unsigned char interiorExclusions); + void setExteriorOcclusions(unsigned char exteriorOcclusions); + unsigned char getExteriorOcclusions() const; + unsigned char getInteriorOcclusions() const; + // type safe versions of OctreeElement methods VoxelTreeElement* getChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::getChildAtIndex(childIndex); } VoxelTreeElement* addChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::addChildAtIndex(childIndex); } @@ -89,6 +94,33 @@ protected: nodeColor _trueColor; /// Client and server, true color of this voxel, 4 bytes nodeColor _currentColor; /// Client only, false color of this voxel, 4 bytes + +private: + unsigned char _exteriorOcclusions; ///< Exterior shared partition boundaries that are completely occupied + unsigned char _interiorOcclusions; ///< Interior shared partition boundaries with siblings }; -#endif /* defined(__hifi__VoxelTreeElement__) */ \ No newline at end of file +inline void VoxelTreeElement::setExteriorOcclusions(unsigned char exteriorOcclusions) { + if (_exteriorOcclusions != exteriorOcclusions) { + _exteriorOcclusions = exteriorOcclusions; + setDirtyBit(); + } +} + +inline void VoxelTreeElement::setInteriorOcclusions(unsigned char interiorOcclusions) { + if (_interiorOcclusions != interiorOcclusions) { + _interiorOcclusions = interiorOcclusions; + setDirtyBit(); + } +} + +inline unsigned char VoxelTreeElement::getInteriorOcclusions() const { + return _interiorOcclusions; +} + +inline unsigned char VoxelTreeElement::getExteriorOcclusions() const { + return _exteriorOcclusions; +} + +#endif /* defined(__hifi__VoxelTreeElement__) */ +