diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 914d5ae63b..5f4c3827f2 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #include "AudioRingBuffer.h" #include "AudioMixerClientData.h" +#include "AudioMixerDatagramProcessor.h" #include "AvatarAudioStream.h" #include "InjectedAudioStream.h" @@ -297,38 +299,34 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { } } -void AudioMixer::readPendingDatagrams() { - QByteArray receivedPacket; - HifiSockAddr senderSockAddr; +void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { NodeList* nodeList = NodeList::getInstance(); - while (readAvailableDatagram(receivedPacket, senderSockAddr)) { - if (nodeList->packetVersionAndHashMatch(receivedPacket)) { - // pull any new audio data from nodes off of the network stack - PacketType mixerPacketType = packetTypeForPacket(receivedPacket); - if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho - || mixerPacketType == PacketTypeMicrophoneAudioWithEcho - || mixerPacketType == PacketTypeInjectAudio - || mixerPacketType == PacketTypeSilentAudioFrame - || mixerPacketType == PacketTypeAudioStreamStats) { - - nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); - } else if (mixerPacketType == PacketTypeMuteEnvironment) { - QByteArray packet = receivedPacket; - populatePacketHeader(packet, PacketTypeMuteEnvironment); - - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) { - nodeList->writeDatagram(packet, packet.size(), node); - } + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + // pull any new audio data from nodes off of the network stack + PacketType mixerPacketType = packetTypeForPacket(receivedPacket); + if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho + || mixerPacketType == PacketTypeMicrophoneAudioWithEcho + || mixerPacketType == PacketTypeInjectAudio + || mixerPacketType == PacketTypeSilentAudioFrame + || mixerPacketType == PacketTypeAudioStreamStats) { + + nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); + } else if (mixerPacketType == PacketTypeMuteEnvironment) { + QByteArray packet = receivedPacket; + populatePacketHeader(packet, PacketTypeMuteEnvironment); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) { + nodeList->writeDatagram(packet, packet.size(), node); } - - } else { - // let processNodeData handle it. - nodeList->processNodeData(senderSockAddr, receivedPacket); } + + } else { + // let processNodeData handle it. + nodeList->processNodeData(senderSockAddr, receivedPacket); } - } + } } void AudioMixer::sendStatsPacket() { @@ -392,7 +390,35 @@ void AudioMixer::run() { ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); NodeList* nodeList = NodeList::getInstance(); - + + // we do not want this event loop to be the handler for UDP datagrams, so disconnect + disconnect(&nodeList->getNodeSocket(), 0, this, 0); + + // setup a QThread with us as parent that will house the AudioMixerDatagramProcessor + _datagramProcessingThread = new QThread(this); + + // create an AudioMixerDatagramProcessor and move it to that thread + AudioMixerDatagramProcessor* datagramProcessor = new AudioMixerDatagramProcessor(nodeList->getNodeSocket(), thread()); + datagramProcessor->moveToThread(_datagramProcessingThread); + + // remove the NodeList as the parent of the node socket + nodeList->getNodeSocket().setParent(NULL); + nodeList->getNodeSocket().moveToThread(_datagramProcessingThread); + + // let the datagram processor handle readyRead from node socket + connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, + datagramProcessor, &AudioMixerDatagramProcessor::readPendingDatagrams); + + // connect to the datagram processing thread signal that tells us we have to handle a packet + connect(datagramProcessor, &AudioMixerDatagramProcessor::packetRequiresProcessing, this, &AudioMixer::readPendingDatagram); + + // delete the datagram processor and the associated thread when the QThread quits + connect(_datagramProcessingThread, &QThread::finished, datagramProcessor, &QObject::deleteLater); + connect(datagramProcessor, &QObject::destroyed, _datagramProcessingThread, &QThread::deleteLater); + + // start the datagram processing thread + _datagramProcessingThread->start(); + nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->linkedDataCreateCallback = attachNewNodeDataToNode; diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index d11539e22e..2a4b93149c 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -33,7 +33,8 @@ public slots: /// threaded run of assignment void run(); - void readPendingDatagrams(); + void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle + void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void sendStatsPacket(); diff --git a/assignment-client/src/audio/AudioMixerDatagramProcessor.cpp b/assignment-client/src/audio/AudioMixerDatagramProcessor.cpp new file mode 100644 index 0000000000..73a4e01844 --- /dev/null +++ b/assignment-client/src/audio/AudioMixerDatagramProcessor.cpp @@ -0,0 +1,47 @@ +// +// AudioMixerDatagramProcessor.cpp +// assignment-client/src +// +// Created by Stephen Birarda on 2014-08-14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include + +#include "AudioMixerDatagramProcessor.h" + +AudioMixerDatagramProcessor::AudioMixerDatagramProcessor(QUdpSocket& nodeSocket, QThread* previousNodeSocketThread) : + _nodeSocket(nodeSocket), + _previousNodeSocketThread(previousNodeSocketThread) +{ + +} + +AudioMixerDatagramProcessor::~AudioMixerDatagramProcessor() { + // return the node socket to its previous thread + _nodeSocket.moveToThread(_previousNodeSocketThread); +} + +void AudioMixerDatagramProcessor::readPendingDatagrams() { + + HifiSockAddr senderSockAddr; + static QByteArray incomingPacket; + + // read everything that is available + while (_nodeSocket.hasPendingDatagrams()) { + incomingPacket.resize(_nodeSocket.pendingDatagramSize()); + + // just get this packet off the stack + _nodeSocket.readDatagram(incomingPacket.data(), incomingPacket.size(), + senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + + // emit the signal to tell AudioMixer it needs to process a packet + emit packetRequiresProcessing(incomingPacket, senderSockAddr); + } +} diff --git a/assignment-client/src/audio/AudioMixerDatagramProcessor.h b/assignment-client/src/audio/AudioMixerDatagramProcessor.h new file mode 100644 index 0000000000..94233a1373 --- /dev/null +++ b/assignment-client/src/audio/AudioMixerDatagramProcessor.h @@ -0,0 +1,32 @@ +// +// AudioMixerDatagramProcessor.h +// assignment-client/src +// +// Created by Stephen Birarda on 2014-08-14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AudioMixerDatagramProcessor_h +#define hifi_AudioMixerDatagramProcessor_h + +#include +#include + +class AudioMixerDatagramProcessor : public QObject { + Q_OBJECT +public: + AudioMixerDatagramProcessor(QUdpSocket& nodeSocket, QThread* previousNodeSocketThread); + ~AudioMixerDatagramProcessor(); +public slots: + void readPendingDatagrams(); +signals: + void packetRequiresProcessing(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); +private: + QUdpSocket& _nodeSocket; + QThread* _previousNodeSocketThread; +}; + +#endif // hifi_AudioMixerDatagramProcessor_h \ No newline at end of file diff --git a/interface/resources/shaders/metavoxel_heightfield.vert b/interface/resources/shaders/metavoxel_heightfield.vert index cc4f68e9e0..70cf3f9419 100644 --- a/interface/resources/shaders/metavoxel_heightfield.vert +++ b/interface/resources/shaders/metavoxel_heightfield.vert @@ -17,6 +17,12 @@ uniform sampler2D heightMap; // the distance between height points in texture space uniform float heightScale; +// the scale between height and color textures +uniform float colorScale; + +// the interpolated position +varying vec4 position; + // the interpolated normal varying vec4 normal; @@ -29,13 +35,17 @@ void main(void) { texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r; normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0)); - // pass along the texture coordinates - gl_TexCoord[0] = gl_MultiTexCoord0; - // add the height to the position float height = texture2D(heightMap, heightCoord).r; - gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + gl_Position = gl_ProjectionMatrix * position; // the zero height should be invisible gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale; + + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); } diff --git a/interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag new file mode 100644 index 0000000000..059a4e0296 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_cascaded_shadow_map.frag @@ -0,0 +1,48 @@ +#version 120 + +// +// metavoxel_heightfield.frag +// fragment shader +// +// Created by Andrzej Kapolka on 7/28/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_cursor.vert b/interface/resources/shaders/metavoxel_heightfield_cursor.vert index 20502fbdce..93ed36449e 100644 --- a/interface/resources/shaders/metavoxel_heightfield_cursor.vert +++ b/interface/resources/shaders/metavoxel_heightfield_cursor.vert @@ -14,9 +14,6 @@ // the height texture uniform sampler2D heightMap; -// the distance between height points in texture space -uniform float heightScale; - void main(void) { // compute the view space coordinates float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; diff --git a/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag new file mode 100644 index 0000000000..bf319462d3 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_shadow_map.frag @@ -0,0 +1,37 @@ +#version 120 + +// +// metavoxel_heightfield.frag +// fragment shader +// +// Created by Andrzej Kapolka on 7/28/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st); +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9d49dc6281..853ae47def 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -242,6 +242,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); + connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); // hookup VoxelEditSender to PaymentManager so we can pay for octree edits @@ -814,9 +816,8 @@ bool Application::event(QEvent* event) { QFileOpenEvent* fileEvent = static_cast(event); bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME); if (isHifiSchemeURL) { - Menu::getInstance()->goTo(fileEvent->url().toString()); + Menu::getInstance()->goToURL(fileEvent->url().toLocalFile()); } - return false; } return QApplication::event(event); @@ -3329,9 +3330,10 @@ void Application::updateWindowTitle(){ QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); + QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) - + nodeList->getDomainHandler().getHostname() + buildVersion; + + nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion; AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.getAccountInfo().hasBalance()) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e6850aac6d..c21b533695 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1153,8 +1153,8 @@ void Menu::goTo() { } bool Menu::goToURL(QString location) { - if (location.startsWith(CUSTOM_URL_SCHEME + "//")) { - QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + if (location.startsWith(CUSTOM_URL_SCHEME + "/")) { + QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length()).split('/', QString::SkipEmptyParts); if (urlParts.count() > 1) { // if url has 2 or more parts, the first one is domain name diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 597542778a..433c8af23e 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -37,6 +37,9 @@ void MetavoxelSystem::init() { _pointBufferAttribute = AttributeRegistry::getInstance()->registerAttribute(new BufferDataAttribute("pointBuffer")); _heightfieldBufferAttribute = AttributeRegistry::getInstance()->registerAttribute( new BufferDataAttribute("heightfieldBuffer")); + + _heightfieldBufferAttribute->setLODThresholdMultiplier( + AttributeRegistry::getInstance()->getHeightfieldAttribute()->getLODThresholdMultiplier()); } MetavoxelLOD MetavoxelSystem::getLOD() { @@ -118,10 +121,271 @@ void MetavoxelSystem::render() { guideToAugmented(renderVisitor); } +class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor { +public: + + float intersectionDistance; + + RayHeightfieldIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info, float distance); +}; + +RayHeightfieldIntersectionVisitor::RayHeightfieldIntersectionVisitor(const glm::vec3& origin, + const glm::vec3& direction, const MetavoxelLOD& lod) : + RayIntersectionVisitor(origin, direction, QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector(), lod), + intersectionDistance(FLT_MAX) { +} + +static const float EIGHT_BIT_MAXIMUM_RECIPROCAL = 1.0f / 255.0f; + +int RayHeightfieldIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { + if (!info.isLeaf) { + return _order; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + const QByteArray& contents = buffer->getHeight(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int unextendedSize = size - HeightfieldBuffer::HEIGHT_EXTENSION; + int highest = HeightfieldBuffer::HEIGHT_BORDER + unextendedSize; + float heightScale = unextendedSize * EIGHT_BIT_MAXIMUM_RECIPROCAL; + + // find the initial location in heightfield coordinates + glm::vec3 entry = (_origin + distance * _direction - info.minimum) * (float)unextendedSize / info.size; + entry.x += HeightfieldBuffer::HEIGHT_BORDER; + entry.z += HeightfieldBuffer::HEIGHT_BORDER; + glm::vec3 floors = glm::floor(entry); + glm::vec3 ceils = glm::ceil(entry); + if (floors.x == ceils.x) { + if (_direction.x > 0.0f) { + ceils.x += 1.0f; + } else { + floors.x -= 1.0f; + } + } + if (floors.z == ceils.z) { + if (_direction.z > 0.0f) { + ceils.z += 1.0f; + } else { + floors.z -= 1.0f; + } + } + + bool withinBounds = true; + float accumulatedDistance = 0.0f; + while (withinBounds) { + // find the heights at the corners of the current cell + int floorX = qMin(qMax((int)floors.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int floorZ = qMin(qMax((int)floors.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilX = qMin(qMax((int)ceils.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilZ = qMin(qMax((int)ceils.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + float upperLeft = src[floorZ * size + floorX] * heightScale; + float upperRight = src[floorZ * size + ceilX] * heightScale; + float lowerLeft = src[ceilZ * size + floorX] * heightScale; + float lowerRight = src[ceilZ * size + ceilX] * heightScale; + + // find the distance to the next x coordinate + float xDistance = FLT_MAX; + if (_direction.x > 0.0f) { + xDistance = (ceils.x - entry.x) / _direction.x; + } else if (_direction.x < 0.0f) { + xDistance = (floors.x - entry.x) / _direction.x; + } + + // and the distance to the next z coordinate + float zDistance = FLT_MAX; + if (_direction.z > 0.0f) { + zDistance = (ceils.z - entry.z) / _direction.z; + } else if (_direction.z < 0.0f) { + zDistance = (floors.z - entry.z) / _direction.z; + } + + // the exit distance is the lower of those two + float exitDistance = qMin(xDistance, zDistance); + glm::vec3 exit, nextFloors = floors, nextCeils = ceils; + if (exitDistance == FLT_MAX) { + if (_direction.y > 0.0f) { + return SHORT_CIRCUIT; // line points upwards; no collisions possible + } + withinBounds = false; // line points downwards; check this cell only + + } else { + // find the exit point and the next cell, and determine whether it's still within the bounds + exit = entry + exitDistance * _direction; + withinBounds = (exit.y >= HeightfieldBuffer::HEIGHT_BORDER && exit.y <= highest); + if (exitDistance == xDistance) { + if (_direction.x > 0.0f) { + nextFloors.x += 1.0f; + withinBounds &= (nextCeils.x += 1.0f) <= highest; + } else { + withinBounds &= (nextFloors.x -= 1.0f) >= HeightfieldBuffer::HEIGHT_BORDER; + nextCeils.x -= 1.0f; + } + } + if (exitDistance == zDistance) { + if (_direction.z > 0.0f) { + nextFloors.z += 1.0f; + withinBounds &= (nextCeils.z += 1.0f) <= highest; + } else { + withinBounds &= (nextFloors.z -= 1.0f) >= HeightfieldBuffer::HEIGHT_BORDER; + nextCeils.z -= 1.0f; + } + } + // check the vertical range of the ray against the ranges of the cell heights + if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) || + qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) { + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + continue; + } + } + // having passed the bounds check, we must check against the planes + glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z); + + // first check the triangle including the Z+ segment + glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft); + float lowerProduct = glm::dot(lowerNormal, _direction); + if (lowerProduct < 0.0f) { + float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; + glm::vec3 intersection = relativeEntry + planeDistance * _direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.z >= intersection.x) { + intersectionDistance = qMin(intersectionDistance, distance + + (accumulatedDistance + planeDistance) * (info.size / unextendedSize)); + return SHORT_CIRCUIT; + } + } + + // then the one with the X+ segment + glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); + float upperProduct = glm::dot(upperNormal, _direction); + if (upperProduct < 0.0f) { + float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; + glm::vec3 intersection = relativeEntry + planeDistance * _direction; + if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && + intersection.x >= intersection.z) { + intersectionDistance = qMin(intersectionDistance, distance + + (accumulatedDistance + planeDistance) * (info.size / unextendedSize)); + return SHORT_CIRCUIT; + } + } + + // no joy; continue on our way + entry = exit; + floors = nextFloors; + ceils = nextCeils; + accumulatedDistance += exitDistance; + } + + return STOP_RECURSION; +} + +bool MetavoxelSystem::findFirstRayHeightfieldIntersection(const glm::vec3& origin, + const glm::vec3& direction, float& distance) { + RayHeightfieldIntersectionVisitor visitor(origin, direction, getLOD()); + guideToAugmented(visitor); + if (visitor.intersectionDistance == FLT_MAX) { + return false; + } + distance = visitor.intersectionDistance; + return true; +} + +class HeightfieldHeightVisitor : public MetavoxelVisitor { +public: + + float height; + + HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location); + + virtual int visit(MetavoxelInfo& info); + +private: + + glm::vec3 _location; +}; + +HeightfieldHeightVisitor::HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location) : + MetavoxelVisitor(QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector(), lod), + height(-FLT_MAX), + _location(location) { +} + +static const int REVERSE_ORDER = MetavoxelVisitor::encodeOrder(7, 6, 5, 4, 3, 2, 1, 0); + +int HeightfieldHeightVisitor::visit(MetavoxelInfo& info) { + glm::vec3 relative = _location - info.minimum; + if (relative.x < 0.0f || relative.z < 0.0f || relative.x > info.size || relative.z > info.size || + height >= info.minimum.y + info.size) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return REVERSE_ORDER; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + const QByteArray& contents = buffer->getHeight(); + const uchar* src = (const uchar*)contents.constData(); + int size = glm::sqrt((float)contents.size()); + int unextendedSize = size - HeightfieldBuffer::HEIGHT_EXTENSION; + int highest = HeightfieldBuffer::HEIGHT_BORDER + unextendedSize; + relative *= unextendedSize / info.size; + relative.x += HeightfieldBuffer::HEIGHT_BORDER; + relative.z += HeightfieldBuffer::HEIGHT_BORDER; + + // find the bounds of the cell containing the point and the shared vertex heights + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = qMin(qMax((int)floors.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int floorZ = qMin(qMax((int)floors.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilX = qMin(qMax((int)ceils.x, HeightfieldBuffer::HEIGHT_BORDER), highest); + int ceilZ = qMin(qMax((int)ceils.z, HeightfieldBuffer::HEIGHT_BORDER), highest); + float upperLeft = src[floorZ * size + floorX]; + float lowerRight = src[ceilZ * size + ceilX]; + float interpolatedHeight = glm::mix(upperLeft, lowerRight, fracts.z); + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x >= fracts.z) { + float upperRight = src[floorZ * size + ceilX]; + interpolatedHeight = glm::mix(interpolatedHeight, glm::mix(upperRight, lowerRight, fracts.z), + (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + float lowerLeft = src[ceilZ * size + floorX]; + interpolatedHeight = glm::mix(glm::mix(upperLeft, lowerLeft, fracts.z), interpolatedHeight, fracts.x / fracts.z); + } + if (interpolatedHeight == 0.0f) { + return STOP_RECURSION; // ignore zero values + } + + // convert the interpolated height into world space + height = qMax(height, info.minimum.y + interpolatedHeight * info.size * EIGHT_BIT_MAXIMUM_RECIPROCAL); + return SHORT_CIRCUIT; +} + +float MetavoxelSystem::getHeightfieldHeight(const glm::vec3& location) { + HeightfieldHeightVisitor visitor(getLOD(), location); + guideToAugmented(visitor); + return visitor.height; +} + class HeightfieldCursorRenderVisitor : public MetavoxelVisitor { public: - HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds); + HeightfieldCursorRenderVisitor(const Box& bounds); virtual int visit(MetavoxelInfo& info); @@ -130,9 +394,9 @@ private: Box _bounds; }; -HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds) : +HeightfieldCursorRenderVisitor::HeightfieldCursorRenderVisitor(const Box& bounds) : MetavoxelVisitor(QVector() << - Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector(), lod), + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()), _bounds(bounds) { } @@ -172,7 +436,7 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glActiveTexture(GL_TEXTURE0); glm::vec3 extents(radius, radius, radius); - HeightfieldCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); + HeightfieldCursorRenderVisitor visitor(Box(position - extents, position + extents)); guideToAugmented(visitor); DefaultMetavoxelRendererImplementation::getHeightfieldCursorProgram().release(); @@ -332,16 +596,32 @@ void PointBuffer::render(bool cursor) { _buffer.release(); } +const int HeightfieldBuffer::HEIGHT_BORDER = 1; +const int HeightfieldBuffer::SHARED_EDGE = 1; +const int HeightfieldBuffer::HEIGHT_EXTENSION = 2 * HeightfieldBuffer::HEIGHT_BORDER + HeightfieldBuffer::SHARED_EDGE; + HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, - const QByteArray& height, const QByteArray& color, bool clearAfterLoading) : + const QByteArray& height, const QByteArray& color) : _translation(translation), _scale(scale), + _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), + _colorBounds(_heightBounds), _height(height), _color(color), - _clearAfterLoading(clearAfterLoading), _heightTextureID(0), _colorTextureID(0), - _heightSize(glm::sqrt(height.size())) { + _heightSize(glm::sqrt(height.size())), + _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), + _colorSize(glm::sqrt(color.size() / HeightfieldData::COLOR_BYTES)), + _colorIncrement(scale / (_colorSize - SHARED_EDGE)) { + + _heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER; + _heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER; + _heightBounds.maximum.x += _heightIncrement * (SHARED_EDGE + HEIGHT_BORDER); + _heightBounds.maximum.z += _heightIncrement * (SHARED_EDGE + HEIGHT_BORDER); + + _colorBounds.maximum.x += _colorIncrement * SHARED_EDGE; + _colorBounds.maximum.z += _colorIncrement * SHARED_EDGE; } HeightfieldBuffer::~HeightfieldBuffer() { @@ -355,6 +635,32 @@ HeightfieldBuffer::~HeightfieldBuffer() { } } +QByteArray HeightfieldBuffer::getUnextendedHeight() const { + int srcSize = glm::sqrt(_height.size()); + int destSize = srcSize - 3; + QByteArray unextended(destSize * destSize, 0); + const char* src = _height.constData() + srcSize + 1; + char* dest = unextended.data(); + for (int z = 0; z < destSize; z++, src += srcSize, dest += destSize) { + memcpy(dest, src, destSize); + } + return unextended; +} + +QByteArray HeightfieldBuffer::getUnextendedColor() const { + int srcSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); + int destSize = srcSize - 1; + QByteArray unextended(destSize * destSize * HeightfieldData::COLOR_BYTES, 0); + const char* src = _color.constData(); + int srcStride = srcSize * HeightfieldData::COLOR_BYTES; + char* dest = unextended.data(); + int destStride = destSize * HeightfieldData::COLOR_BYTES; + for (int z = 0; z < destSize; z++, src += srcStride, dest += destStride) { + memcpy(dest, src, destStride); + } + return unextended; +} + class HeightfieldPoint { public: glm::vec2 textureCoord; @@ -366,14 +672,12 @@ void HeightfieldBuffer::render(bool cursor) { if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); glBindTexture(GL_TEXTURE_2D, _heightTextureID); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _heightSize, _heightSize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, _height.constData()); - if (_clearAfterLoading) { - _height.clear(); - } glGenTextures(1, &_colorTextureID); glBindTexture(GL_TEXTURE_2D, _colorTextureID); @@ -385,32 +689,32 @@ void HeightfieldBuffer::render(bool cursor) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, WHITE_COLOR); } else { - int colorSize = glm::sqrt(_color.size() / 3); + int colorSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colorSize, colorSize, 0, GL_RGB, GL_UNSIGNED_BYTE, _color.constData()); - if (_clearAfterLoading) { - _color.clear(); - } } } // create the buffer objects lazily - int sizeWithSkirt = _heightSize + 2; - int vertexCount = sizeWithSkirt * sizeWithSkirt; - int rows = sizeWithSkirt - 1; - int indexCount = rows * rows * 4; + int innerSize = _heightSize - 2 * HeightfieldBuffer::HEIGHT_BORDER; + int vertexCount = _heightSize * _heightSize; + int rows = _heightSize - 1; + int indexCount = rows * rows * 3 * 2; BufferPair& bufferPair = _bufferPairs[_heightSize]; if (!bufferPair.first.isCreated()) { QVector vertices(vertexCount); HeightfieldPoint* point = vertices.data(); - float step = 1.0f / (_heightSize - 1); - float z = -step; - for (int i = 0; i < sizeWithSkirt; i++, z += step) { - float x = -step; + float vertexStep = 1.0f / (innerSize - 1); + float z = -vertexStep; + float textureStep = 1.0f / _heightSize; + float t = textureStep / 2.0f; + for (int i = 0; i < _heightSize; i++, z += vertexStep, t += textureStep) { + float x = -vertexStep; + float s = textureStep / 2.0f; const float SKIRT_LENGTH = 0.25f; - float baseY = (i == 0 || i == sizeWithSkirt - 1) ? -SKIRT_LENGTH : 0.0f; - for (int j = 0; j < sizeWithSkirt; j++, point++, x += step) { - point->vertex = glm::vec3(x, (j == 0 || j == sizeWithSkirt - 1) ? -SKIRT_LENGTH : baseY, z); - point->textureCoord = glm::vec2(x, z); + float baseY = (i == 0 || i == _heightSize - 1) ? -SKIRT_LENGTH : 0.0f; + for (int j = 0; j < _heightSize; j++, point++, x += vertexStep, s += textureStep) { + point->vertex = glm::vec3(x, (j == 0 || j == _heightSize - 1) ? -SKIRT_LENGTH : baseY, z); + point->textureCoord = glm::vec2(s, t); } } @@ -422,13 +726,16 @@ void HeightfieldBuffer::render(bool cursor) { QVector indices(indexCount); int* index = indices.data(); for (int i = 0; i < rows; i++) { - int lineIndex = i * sizeWithSkirt; - int nextLineIndex = (i + 1) * sizeWithSkirt; + int lineIndex = i * _heightSize; + int nextLineIndex = (i + 1) * _heightSize; for (int j = 0; j < rows; j++) { *index++ = lineIndex + j; *index++ = nextLineIndex + j; + *index++ = nextLineIndex + j + 1; + *index++ = nextLineIndex + j + 1; *index++ = lineIndex + j + 1; + *index++ = lineIndex + j; } } @@ -452,18 +759,27 @@ void HeightfieldBuffer::render(bool cursor) { glBindTexture(GL_TEXTURE_2D, _heightTextureID); - int heightScaleLocation; - if (cursor) { - heightScaleLocation = DefaultMetavoxelRendererImplementation::getCursorHeightScaleLocation(); - } else { - heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); + if (!cursor) { + int heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); + int colorScaleLocation = DefaultMetavoxelRendererImplementation::getColorScaleLocation(); + ProgramObject* program = &DefaultMetavoxelRendererImplementation::getHeightfieldProgram(); + if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { + heightScaleLocation = DefaultMetavoxelRendererImplementation::getShadowMapHeightScaleLocation(); + colorScaleLocation = DefaultMetavoxelRendererImplementation::getShadowMapColorScaleLocation(); + program = &DefaultMetavoxelRendererImplementation::getShadowMapHeightfieldProgram(); + + } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + heightScaleLocation = DefaultMetavoxelRendererImplementation::getCascadedShadowMapHeightScaleLocation(); + colorScaleLocation = DefaultMetavoxelRendererImplementation::getCascadedShadowMapColorScaleLocation(); + program = &DefaultMetavoxelRendererImplementation::getCascadedShadowMapHeightfieldProgram(); + } + program->setUniformValue(heightScaleLocation, 1.0f / _heightSize); + program->setUniformValue(colorScaleLocation, (float)_heightSize / innerSize); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, _colorTextureID); } - DefaultMetavoxelRendererImplementation::getHeightfieldProgram().setUniformValue(heightScaleLocation, 1.0f / _heightSize); - - glDrawRangeElements(GL_QUADS, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); if (!cursor) { glBindTexture(GL_TEXTURE_2D, 0); @@ -518,17 +834,19 @@ BufferDataAttribute::BufferDataAttribute(const QString& name) : } bool BufferDataAttribute::merge(void*& parent, void* children[], bool postRead) const { - BufferDataPointer firstChild = decodeInline(children[0]); - for (int i = 1; i < MERGE_COUNT; i++) { - if (firstChild != decodeInline(children[i])) { - *(BufferDataPointer*)&parent = _defaultValue; + *(BufferDataPointer*)&parent = _defaultValue; + for (int i = 0; i < MERGE_COUNT; i++) { + if (decodeInline(children[i])) { return false; } } - *(BufferDataPointer*)&parent = firstChild; return true; } +AttributeValue BufferDataAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + void DefaultMetavoxelRendererImplementation::init() { if (!_pointProgram.isLinked()) { _pointProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert"); @@ -548,8 +866,38 @@ void DefaultMetavoxelRendererImplementation::init() { _heightfieldProgram.setUniformValue("heightMap", 0); _heightfieldProgram.setUniformValue("diffuseMap", 1); _heightScaleLocation = _heightfieldProgram.uniformLocation("heightScale"); + _colorScaleLocation = _heightfieldProgram.uniformLocation("colorScale"); _heightfieldProgram.release(); + _shadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield.vert"); + _shadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_shadow_map.frag"); + _shadowMapHeightfieldProgram.link(); + + _shadowMapHeightfieldProgram.bind(); + _shadowMapHeightfieldProgram.setUniformValue("heightMap", 0); + _shadowMapHeightfieldProgram.setUniformValue("diffuseMap", 1); + _shadowMapHeightfieldProgram.setUniformValue("shadowMap", 2); + _shadowMapHeightScaleLocation = _shadowMapHeightfieldProgram.uniformLocation("heightScale"); + _shadowMapColorScaleLocation = _shadowMapHeightfieldProgram.uniformLocation("colorScale"); + _shadowMapHeightfieldProgram.release(); + + _cascadedShadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield.vert"); + _cascadedShadowMapHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_cascaded_shadow_map.frag"); + _cascadedShadowMapHeightfieldProgram.link(); + + _cascadedShadowMapHeightfieldProgram.bind(); + _cascadedShadowMapHeightfieldProgram.setUniformValue("heightMap", 0); + _cascadedShadowMapHeightfieldProgram.setUniformValue("diffuseMap", 1); + _cascadedShadowMapHeightfieldProgram.setUniformValue("shadowMap", 2); + _cascadedShadowMapHeightScaleLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("heightScale"); + _cascadedShadowMapColorScaleLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("colorScale"); + _shadowDistancesLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("shadowDistances"); + _cascadedShadowMapHeightfieldProgram.release(); + _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_heightfield_cursor.vert"); _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + @@ -558,7 +906,6 @@ void DefaultMetavoxelRendererImplementation::init() { _heightfieldCursorProgram.bind(); _heightfieldCursorProgram.setUniformValue("heightMap", 0); - _cursorHeightScaleLocation = _heightfieldCursorProgram.uniformLocation("heightScale"); _heightfieldCursorProgram.release(); } } @@ -634,33 +981,279 @@ bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) { return true; } -class HeightfieldAugmentVisitor : public MetavoxelVisitor { +class HeightfieldFetchVisitor : public MetavoxelVisitor { public: - - HeightfieldAugmentVisitor(const MetavoxelLOD& lod); + + HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections); + + void init(HeightfieldBuffer* buffer) { _buffer = buffer; } virtual int visit(MetavoxelInfo& info); + +private: + + const QVector& _intersections; + HeightfieldBuffer* _buffer; }; -HeightfieldAugmentVisitor::HeightfieldAugmentVisitor(const MetavoxelLOD& lod) : +HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector() << - Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod) { + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector(), lod), + _intersections(intersections) { } -int HeightfieldAugmentVisitor::visit(MetavoxelInfo& info) { - if (info.isLeaf) { - HeightfieldBuffer* buffer = NULL; - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); - if (height) { - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); - buffer = new HeightfieldBuffer(info.minimum, info.size, height->getContents(), - color ? color->getContents() : QByteArray()); - } - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); +int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { + Box bounds = info.getBounds(); + const Box& heightBounds = _buffer->getHeightBounds(); + if (!bounds.intersects(heightBounds)) { return STOP_RECURSION; } - return DEFAULT_ORDER; + if (!info.isLeaf && info.size > _buffer->getScale()) { + return DEFAULT_ORDER; + } + HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + if (!height) { + return STOP_RECURSION; + } + foreach (const Box& intersection, _intersections) { + Box overlap = intersection.getIntersection(bounds); + if (overlap.isEmpty()) { + continue; + } + float heightIncrement = _buffer->getHeightIncrement(); + int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement; + int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement; + int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement); + int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement); + int heightSize = _buffer->getHeightSize(); + char* dest = _buffer->getHeight().data() + destY * heightSize + destX; + + const QByteArray& srcHeight = height->getContents(); + int srcSize = glm::sqrt(srcHeight.size()); + float srcIncrement = info.size / srcSize; + + if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcHeight.constData() + srcY * srcSize + srcX; + for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) { + memcpy(dest, src, destWidth); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = heightIncrement / srcIncrement; + int shift = 0; + float size = _buffer->getScale(); + while (size < info.size) { + shift++; + size *= 2.0f; + } + const int EIGHT_BIT_MAXIMUM = 255; + int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale(); + for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) { + const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { + *lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM); + } + } + } + + int colorSize = _buffer->getColorSize(); + if (colorSize == 0) { + return STOP_RECURSION; + } + HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + if (!color) { + return STOP_RECURSION; + } + const Box& colorBounds = _buffer->getColorBounds(); + overlap = colorBounds.getIntersection(overlap); + float colorIncrement = _buffer->getColorIncrement(); + destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement; + destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement; + destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement); + destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement); + dest = _buffer->getColor().data() + (destY * colorSize + destX) * HeightfieldData::COLOR_BYTES; + int destStride = colorSize * HeightfieldData::COLOR_BYTES; + int destBytes = destWidth * HeightfieldData::COLOR_BYTES; + + const QByteArray& srcColor = color->getContents(); + srcSize = glm::sqrt(srcColor.size() / HeightfieldData::COLOR_BYTES); + int srcStride = srcSize * HeightfieldData::COLOR_BYTES; + srcIncrement = info.size / srcSize; + + if (srcIncrement == colorIncrement) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcColor.constData() + (srcY * srcSize + srcX) * HeightfieldData::COLOR_BYTES; + for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) { + memcpy(dest, src, destBytes); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = colorIncrement / srcIncrement; + for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) { + const char* src = srcColor.constData() + (int)srcY * srcStride; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += HeightfieldData::COLOR_BYTES, + lineSrcX += srcAdvance) { + const char* lineSrc = src + (int)lineSrcX * HeightfieldData::COLOR_BYTES; + lineDest[0] = lineSrc[0]; + lineDest[1] = lineSrc[1]; + lineDest[2] = lineSrc[2]; + } + } + } + } + return STOP_RECURSION; +} + +class HeightfieldRegionVisitor : public MetavoxelVisitor { +public: + + QVector regions; + Box regionBounds; + + HeightfieldRegionVisitor(const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info); + +private: + + void addRegion(const Box& unextended, const Box& extended); + + QVector _intersections; + HeightfieldFetchVisitor _fetchVisitor; +}; + +HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), + regionBounds(glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX), glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)), + _fetchVisitor(lod, _intersections) { +} + +int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldBuffer* buffer = NULL; + HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + if (height) { + const QByteArray& heightContents = height->getContents(); + int size = glm::sqrt(heightContents.size()); + int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION; + int heightContentsSize = extendedSize * extendedSize; + + HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + int colorContentsSize = 0; + if (color) { + const QByteArray& colorContents = color->getContents(); + int colorSize = glm::sqrt(colorContents.size() / HeightfieldData::COLOR_BYTES); + int extendedColorSize = colorSize + HeightfieldBuffer::SHARED_EDGE; + colorContentsSize = extendedColorSize * extendedColorSize * HeightfieldData::COLOR_BYTES; + } + + const HeightfieldBuffer* existingBuffer = static_cast( + info.inputValues.at(2).getInlineValue().data()); + Box bounds = info.getBounds(); + if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && + existingBuffer->getColor().size() == colorContentsSize) { + // we already have a buffer of the correct resolution + addRegion(bounds, existingBuffer->getHeightBounds()); + return STOP_RECURSION; + } + // we must create a new buffer and update its borders + buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), + QByteArray(colorContentsSize, 0)); + const Box& heightBounds = buffer->getHeightBounds(); + addRegion(bounds, heightBounds); + + _intersections.clear(); + _intersections.append(Box(heightBounds.minimum, + glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); + _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), + glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); + _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), + heightBounds.maximum)); + _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), + glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); + + _fetchVisitor.init(buffer); + _data->guide(_fetchVisitor); + } + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); + return STOP_RECURSION; +} + +void HeightfieldRegionVisitor::addRegion(const Box& unextended, const Box& extended) { + regions.append(unextended); + regionBounds.add(extended); +} + +class HeightfieldUpdateVisitor : public MetavoxelVisitor { +public: + + HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector& regions, const Box& regionBounds); + + virtual int visit(MetavoxelInfo& info); + +private: + + const QVector& _regions; + const Box& _regionBounds; + QVector _intersections; + HeightfieldFetchVisitor _fetchVisitor; +}; + +HeightfieldUpdateVisitor::HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector& regions, + const Box& regionBounds) : + MetavoxelVisitor(QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << + Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), + _regions(regions), + _regionBounds(regionBounds), + _fetchVisitor(lod, _intersections) { +} + +int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_regionBounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + const HeightfieldBuffer* buffer = static_cast( + info.inputValues.at(0).getInlineValue().data()); + if (!buffer) { + return STOP_RECURSION; + } + _intersections.clear(); + foreach (const Box& region, _regions) { + if (region.intersects(buffer->getHeightBounds())) { + _intersections.append(region.getIntersection(buffer->getHeightBounds())); + } + } + if (_intersections.isEmpty()) { + return STOP_RECURSION; + } + HeightfieldBuffer* newBuffer = new HeightfieldBuffer(info.minimum, info.size, + buffer->getHeight(), buffer->getColor()); + _fetchVisitor.init(newBuffer); + _data->guide(_fetchVisitor); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer))); + return STOP_RECURSION; } void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous, @@ -687,8 +1280,12 @@ void DefaultMetavoxelRendererImplementation::augment(MetavoxelData& data, const PointAugmentVisitor pointAugmentVisitor(lod); data.guideToDifferent(expandedPrevious, pointAugmentVisitor); - HeightfieldAugmentVisitor heightfieldAugmentVisitor(lod); - data.guideToDifferent(expandedPrevious, heightfieldAugmentVisitor); + HeightfieldRegionVisitor heightfieldRegionVisitor(lod); + data.guideToDifferent(expandedPrevious, heightfieldRegionVisitor); + + HeightfieldUpdateVisitor heightfieldUpdateVisitor(lod, heightfieldRegionVisitor.regions, + heightfieldRegionVisitor.regionBounds); + data.guide(heightfieldUpdateVisitor); } class SpannerSimulateVisitor : public SpannerVisitor { @@ -760,7 +1357,7 @@ bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, class BufferRenderVisitor : public MetavoxelVisitor { public: - BufferRenderVisitor(const AttributePointer& attribute, const MetavoxelLOD& lod); + BufferRenderVisitor(const AttributePointer& attribute); virtual int visit(MetavoxelInfo& info); @@ -770,8 +1367,8 @@ private: int _containmentDepth; }; -BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute, const MetavoxelLOD& lod) : - MetavoxelVisitor(QVector() << attribute, QVector(), lod), +BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) : + MetavoxelVisitor(QVector() << attribute), _order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())), _containmentDepth(INT_MAX) { } @@ -785,11 +1382,14 @@ int BufferRenderVisitor::visit(MetavoxelInfo& info) { } _containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX; } + if (!info.isLeaf) { + return _order; + } BufferDataPointer buffer = info.inputValues.at(0).getInlineValue(); if (buffer) { buffer->render(); } - return info.isLeaf ? STOP_RECURSION : _order; + return STOP_RECURSION; } void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod) { @@ -818,7 +1418,7 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox glDisable(GL_BLEND); - BufferRenderVisitor pointRenderVisitor(Application::getInstance()->getMetavoxels()->getPointBufferAttribute(), lod); + BufferRenderVisitor pointRenderVisitor(Application::getInstance()->getMetavoxels()->getPointBufferAttribute()); data.guide(pointRenderVisitor); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); @@ -834,16 +1434,34 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - _heightfieldProgram.bind(); + ProgramObject* program = &_heightfieldProgram; + if (Menu::getInstance()->getShadowsEnabled()) { + if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + program = &_cascadedShadowMapHeightfieldProgram; + program->bind(); + program->setUniform(_shadowDistancesLocation, Application::getInstance()->getShadowDistances()); + + } else { + program = &_shadowMapHeightfieldProgram; + } + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID()); + glActiveTexture(GL_TEXTURE0); + } + + program->bind(); glEnableClientState(GL_TEXTURE_COORD_ARRAY); - BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), - lod); + BufferRenderVisitor heightfieldRenderVisitor(Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute()); data.guide(heightfieldRenderVisitor); - _heightfieldProgram.release(); + program->release(); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); @@ -856,8 +1474,15 @@ ProgramObject DefaultMetavoxelRendererImplementation::_pointProgram; int DefaultMetavoxelRendererImplementation::_pointScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldProgram; int DefaultMetavoxelRendererImplementation::_heightScaleLocation; +int DefaultMetavoxelRendererImplementation::_colorScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_shadowMapHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_shadowMapHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_shadowMapColorScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_cascadedShadowMapColorScaleLocation; +int DefaultMetavoxelRendererImplementation::_shadowDistancesLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram; -int DefaultMetavoxelRendererImplementation::_cursorHeightScaleLocation; static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { GLdouble coefficients[] = { x, y, z, w }; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 7b681ff3a9..38d67bcaed 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -45,6 +45,10 @@ public: void renderHeightfieldCursor(const glm::vec3& position, float radius); + bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); + + Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); + Q_INVOKABLE void deleteTextures(int heightID, int colorID); protected: @@ -131,29 +135,50 @@ private: class HeightfieldBuffer : public BufferData { public: - /// Creates a new heightfield buffer. - /// \param clearAfterLoading if true, clear the data arrays after we load them into textures in order to reclaim the space - HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color, - bool clearAfterLoading = true); + static const int HEIGHT_BORDER; + static const int SHARED_EDGE; + static const int HEIGHT_EXTENSION; + + HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color); ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } + float getScale() const { return _scale; } + const Box& getHeightBounds() const { return _heightBounds; } + const Box& getColorBounds() const { return _colorBounds; } + + QByteArray& getHeight() { return _height; } const QByteArray& getHeight() const { return _height; } + + QByteArray& getColor() { return _color; } const QByteArray& getColor() const { return _color; } + QByteArray getUnextendedHeight() const; + QByteArray getUnextendedColor() const; + + int getHeightSize() const { return _heightSize; } + float getHeightIncrement() const { return _heightIncrement; } + + int getColorSize() const { return _colorSize; } + float getColorIncrement() const { return _colorIncrement; } + virtual void render(bool cursor = false); private: glm::vec3 _translation; float _scale; + Box _heightBounds; + Box _colorBounds; QByteArray _height; QByteArray _color; - bool _clearAfterLoading; GLuint _heightTextureID; GLuint _colorTextureID; int _heightSize; + float _heightIncrement; + int _colorSize; + float _colorIncrement; typedef QPair BufferPair; static QHash _bufferPairs; @@ -182,6 +207,8 @@ public: Q_INVOKABLE BufferDataAttribute(const QString& name = QString()); virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; /// Renders metavoxels as points. @@ -194,9 +221,17 @@ public: static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; } static int getHeightScaleLocation() { return _heightScaleLocation; } - + static int getColorScaleLocation() { return _colorScaleLocation; } + + static ProgramObject& getShadowMapHeightfieldProgram() { return _shadowMapHeightfieldProgram; } + static int getShadowMapHeightScaleLocation() { return _shadowMapHeightScaleLocation; } + static int getShadowMapColorScaleLocation() { return _shadowMapColorScaleLocation; } + + static ProgramObject& getCascadedShadowMapHeightfieldProgram() { return _cascadedShadowMapHeightfieldProgram; } + static int getCascadedShadowMapHeightScaleLocation() { return _cascadedShadowMapHeightScaleLocation; } + static int getCascadedShadowMapColorScaleLocation() { return _cascadedShadowMapColorScaleLocation; } + static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; } - static int getCursorHeightScaleLocation() { return _cursorHeightScaleLocation; } Q_INVOKABLE DefaultMetavoxelRendererImplementation(); @@ -211,9 +246,18 @@ private: static ProgramObject _heightfieldProgram; static int _heightScaleLocation; + static int _colorScaleLocation; + + static ProgramObject _shadowMapHeightfieldProgram; + static int _shadowMapHeightScaleLocation; + static int _shadowMapColorScaleLocation; + + static ProgramObject _cascadedShadowMapHeightfieldProgram; + static int _cascadedShadowMapHeightScaleLocation; + static int _cascadedShadowMapColorScaleLocation; + static int _shadowDistancesLocation; static ProgramObject _heightfieldCursorProgram; - static int _cursorHeightScaleLocation; }; /// Base class for spanner renderers; provides clipping. diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 7d7375fad5..3582f82820 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -504,7 +504,7 @@ void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) { } glm::vec3 OculusManager::getRelativePosition() { -#if defined(__APPLE__) || defined(_WIN32) +#if (defined(__APPLE__) || defined(_WIN32)) && HAVE_LIBOVR ovrTrackingState trackingState = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); ovrVector3f headPosition = trackingState.HeadPose.ThePose.Position; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index a915406f8e..2ec676de53 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1449,9 +1449,11 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re if (cascadedShadows) { activeProgram->setUniform(activeLocations->shadowDistances, Application::getInstance()->getShadowDistances()); } - activeProgram->setUniformValueArray(activeLocations->localLightDirections, - (const GLfloat*)_localLightDirections, MAX_LOCAL_LIGHTS, 4); - + if (mode != SHADOW_RENDER_MODE) { + activeProgram->setUniformValueArray(activeLocations->localLightDirections, + (const GLfloat*)_localLightDirections, MAX_LOCAL_LIGHTS, 4); + } + if (mesh.blendshapes.isEmpty()) { if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(activeLocations->tangent, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c49873b5f5..7dcb50c314 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -57,6 +57,9 @@ ApplicationOverlay::~ApplicationOverlay() { const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; +const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f }; +const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; + // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(bool renderToTexture) { @@ -115,6 +118,8 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { renderPointers(); + renderDomainConnectionStatusBorder(); + glPopMatrix(); @@ -1234,6 +1239,30 @@ void ApplicationOverlay::renderTexturedHemisphere() { } +void ApplicationOverlay::renderDomainConnectionStatusBorder() { + NodeList* nodeList = NodeList::getInstance(); + + if (nodeList && !nodeList->getDomainHandler().isConnected()) { + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + int right = glWidget->width(); + int bottom = glWidget->height(); + + glColor3f(CONNECTION_STATUS_BORDER_COLOR[0], + CONNECTION_STATUS_BORDER_COLOR[1], + CONNECTION_STATUS_BORDER_COLOR[2]); + glLineWidth(CONNECTION_STATUS_BORDER_LINE_WIDTH); + + glBegin(GL_LINE_LOOP); + + glVertex2i(0, 0); + glVertex2i(0, bottom); + glVertex2i(right, bottom); + glVertex2i(right, 0); + + glEnd(); + } +} + QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { QSize size = Application::getInstance()->getGLWidget()->size(); if (!_framebufferObject || _framebufferObject->size() != size) { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 0c2ccc7b21..a493f6cd1b 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -56,6 +56,7 @@ private: void renderAudioMeter(); void renderStatsAndLogs(); void renderTexturedHemisphere(); + void renderDomainConnectionStatusBorder(); QOpenGLFramebufferObject* _framebufferObject; float _trailingAudioLoudness; @@ -76,4 +77,4 @@ private: GLuint _crosshairTexture; }; -#endif // hifi_ApplicationOverlay_h \ No newline at end of file +#endif // hifi_ApplicationOverlay_h diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index d35ed93f1b..b7057532fb 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -956,11 +956,11 @@ void ImportHeightfieldTool::apply() { HeightfieldBuffer* buffer = static_cast(bufferData.data()); MetavoxelData data; data.setSize(scale); - HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getHeight())); + HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getUnextendedHeight())); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer)))); if (!buffer->getColor().isEmpty()) { - HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getColor())); + HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getUnextendedColor())); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); } @@ -1003,34 +1003,38 @@ void ImportHeightfieldTool::updatePreview() { if (_heightImage.width() > 0 && _heightImage.height() > 0) { float z = 0.0f; int blockSize = pow(2.0, _blockSize->value()); - int blockAdvancement = blockSize - 1; - for (int i = 0; i < _heightImage.height(); i += blockAdvancement, z++) { + int heightSize = blockSize + HeightfieldBuffer::HEIGHT_EXTENSION; + int colorSize = blockSize + HeightfieldBuffer::SHARED_EDGE; + for (int i = 0; i < _heightImage.height(); i += blockSize, z++) { float x = 0.0f; - for (int j = 0; j < _heightImage.width(); j += blockAdvancement, x++) { - QByteArray height(blockSize * blockSize, 0); - int rows = qMin(blockSize, _heightImage.height() - i); - int columns = qMin(blockSize, _heightImage.width() - j); - const int BYTES_PER_COLOR = 3; + for (int j = 0; j < _heightImage.width(); j += blockSize, x++) { + QByteArray height(heightSize * heightSize, 0); + int extendedI = qMax(i - HeightfieldBuffer::HEIGHT_BORDER, 0); + int extendedJ = qMax(j - HeightfieldBuffer::HEIGHT_BORDER, 0); + int offsetY = extendedI - i + HeightfieldBuffer::HEIGHT_BORDER; + int offsetX = extendedJ - j + HeightfieldBuffer::HEIGHT_BORDER; + int rows = qMin(heightSize - offsetY, _heightImage.height() - extendedI); + int columns = qMin(heightSize - offsetX, _heightImage.width() - extendedJ); for (int y = 0; y < rows; y++) { - uchar* src = _heightImage.scanLine(i + y) + j * BYTES_PER_COLOR; - char* dest = height.data() + y * blockSize; + uchar* src = _heightImage.scanLine(extendedI + y) + extendedJ * HeightfieldData::COLOR_BYTES; + char* dest = height.data() + (y + offsetY) * heightSize + offsetX; for (int x = 0; x < columns; x++) { *dest++ = *src; - src += BYTES_PER_COLOR; + src += HeightfieldData::COLOR_BYTES; } } - QByteArray color; if (!_colorImage.isNull()) { - color = QByteArray(blockSize * blockSize * BYTES_PER_COLOR, 0); - rows = qMax(0, qMin(blockSize, _colorImage.height() - i)); - columns = qMax(0, qMin(blockSize, _colorImage.width() - j)); + color = QByteArray(colorSize * colorSize * HeightfieldData::COLOR_BYTES, 0); + rows = qMax(0, qMin(colorSize, _colorImage.height() - i)); + columns = qMax(0, qMin(colorSize, _colorImage.width() - j)); for (int y = 0; y < rows; y++) { - memcpy(color.data() + y * blockSize * BYTES_PER_COLOR, - _colorImage.scanLine(i + y) + j * BYTES_PER_COLOR, columns * BYTES_PER_COLOR); + memcpy(color.data() + y * colorSize * HeightfieldData::COLOR_BYTES, + _colorImage.scanLine(i + y) + j * HeightfieldData::COLOR_BYTES, + columns * HeightfieldData::COLOR_BYTES); } } - buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color, false))); + buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color))); } } } diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 9f656ef5d3..1e30aee576 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -489,22 +489,92 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) : _contents(contents) { } -const int BYTES_PER_PIXEL = 3; +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) { + read(in, bytes, color); +} -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) : - _encoded(in.readAligned(bytes)) { - - QImage image = QImage::fromData(_encoded).convertToFormat(QImage::Format_RGB888); - if (color) { - _contents.resize(image.width() * image.height() * BYTES_PER_PIXEL); - memcpy(_contents.data(), image.constBits(), _contents.size()); +enum HeightfieldImage { NULL_HEIGHTFIELD_IMAGE, NORMAL_HEIGHTFIELD_IMAGE, DEFLATED_HEIGHTFIELD_IMAGE }; + +static QByteArray encodeHeightfieldImage(const QImage& image) { + if (image.isNull()) { + return QByteArray(1, NULL_HEIGHTFIELD_IMAGE); + } + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + const int JPEG_ENCODE_THRESHOLD = 16; + if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD) { + qint32 offsetX = image.offset().x(), offsetY = image.offset().y(); + buffer.write((char*)&offsetX, sizeof(qint32)); + buffer.write((char*)&offsetY, sizeof(qint32)); + image.save(&buffer, "JPG"); + return QByteArray(1, DEFLATED_HEIGHTFIELD_IMAGE) + qCompress(buffer.data()); } else { - _contents.resize(image.width() * image.height()); - char* dest = _contents.data(); - for (const uchar* src = image.constBits(), *end = src + _contents.size() * BYTES_PER_PIXEL; - src != end; src += BYTES_PER_PIXEL) { - *dest++ = *src; + buffer.putChar(NORMAL_HEIGHTFIELD_IMAGE); + image.save(&buffer, "PNG"); + return buffer.data(); + } +} + +const QImage decodeHeightfieldImage(const QByteArray& data) { + switch (data.at(0)) { + case NULL_HEIGHTFIELD_IMAGE: + default: + return QImage(); + + case NORMAL_HEIGHTFIELD_IMAGE: + return QImage::fromData(QByteArray::fromRawData(data.constData() + 1, data.size() - 1)); + + case DEFLATED_HEIGHTFIELD_IMAGE: { + QByteArray inflated = qUncompress((const uchar*)data.constData() + 1, data.size() - 1); + const int OFFSET_SIZE = sizeof(qint32) * 2; + QImage image = QImage::fromData((const uchar*)inflated.constData() + OFFSET_SIZE, inflated.size() - OFFSET_SIZE); + const qint32* offsets = (const qint32*)inflated.constData(); + image.setOffset(QPoint(offsets[0], offsets[1])); + return image; + } + } +} + +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color) { + if (!reference) { + read(in, bytes, color); + return; + } + QMutexLocker locker(&reference->_encodedDeltaMutex); + reference->_encodedDelta = in.readAligned(bytes); + reference->_deltaData = this; + _contents = reference->_contents; + QImage image = decodeHeightfieldImage(reference->_encodedDelta); + if (image.isNull()) { + return; + } + QPoint offset = image.offset(); + image = image.convertToFormat(QImage::Format_RGB888); + if (offset.x() == 0) { + set(image, color); + return; + } + int minX = offset.x() - 1; + int minY = offset.y() - 1; + if (color) { + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; + int destStride = size * COLOR_BYTES; + int srcStride = image.width() * COLOR_BYTES; + for (int y = 0; y < image.height(); y++) { + memcpy(dest, image.constScanLine(y), srcStride); + dest += destStride; + } + } else { + int size = glm::sqrt((float)_contents.size()); + char* lineDest = _contents.data() + minY * size + minX; + for (int y = 0; y < image.height(); y++) { + const uchar* src = image.constScanLine(y); + for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { + *dest = *src; + } + lineDest += size; } } } @@ -514,7 +584,7 @@ void HeightfieldData::write(Bitstream& out, bool color) { if (_encoded.isEmpty()) { QImage image; if (color) { - int size = glm::sqrt(_contents.size() / (float)BYTES_PER_PIXEL); + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); } else { int size = glm::sqrt((float)_contents.size()); @@ -526,38 +596,170 @@ void HeightfieldData::write(Bitstream& out, bool color) { *dest++ = *src; } } - QBuffer buffer(&_encoded); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "JPG"); + _encoded = encodeHeightfieldImage(image); } out << _encoded.size(); out.writeAligned(_encoded); } +void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out, color); + return; + } + QMutexLocker locker(&reference->_encodedDeltaMutex); + if (reference->_encodedDelta.isEmpty() || reference->_deltaData != this) { + QImage image; + int minX, minY; + if (color) { + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + minX = size; + minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->_contents.constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { + if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = maxX - minX + 1; + int height = maxY - minY + 1; + image = QImage(width, height, QImage::Format_RGB888); + src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; + int srcStride = size * COLOR_BYTES; + int destStride = width * COLOR_BYTES; + for (int y = 0; y < height; y++) { + memcpy(image.scanLine(y), src, destStride); + src += srcStride; + } + } + } else { + int size = glm::sqrt((float)_contents.size()); + minX = size; + minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->_contents.constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = qMax(maxX - minX + 1, 0); + int height = qMax(maxY - minY + 1, 0); + image = QImage(width, height, QImage::Format_RGB888); + const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++) { + uchar* dest = image.scanLine(y); + for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; + } + lineSrc += size; + } + } + } + image.setOffset(QPoint(minX + 1, minY + 1)); + reference->_encodedDelta = encodeHeightfieldImage(image); + reference->_deltaData = this; + } + out << reference->_encodedDelta.size(); + out.writeAligned(reference->_encodedDelta); +} + +void HeightfieldData::read(Bitstream& in, int bytes, bool color) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color); +} + +void HeightfieldData::set(const QImage& image, bool color) { + if (color) { + _contents.resize(image.width() * image.height() * COLOR_BYTES); + memcpy(_contents.data(), image.constBits(), _contents.size()); + + } else { + _contents.resize(image.width() * image.height()); + char* dest = _contents.data(); + for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; + src != end; src += COLOR_BYTES) { + *dest++ = *src; + } + } +} + HeightfieldAttribute::HeightfieldAttribute(const QString& name) : InlineAttribute(name) { } void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (isLeaf) { - int size; - in >> size; - if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); - } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); - } + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); } } void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (isLeaf) { - HeightfieldDataPointer data = decodeInline(value); - if (data) { - data->write(out, false); - } else { - out << 0; - } + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->write(out, false); + } else { + out << 0; + } +} + +void HeightfieldAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( + in, size, decodeInline(reference), false)); + } +} + +void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference), false); + } else { + out << 0; } } @@ -635,25 +837,53 @@ HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : } void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (isLeaf) { - int size; - in >> size; - if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); - } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); - } + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); } } void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (isLeaf) { - HeightfieldDataPointer data = decodeInline(value); - if (data) { - data->write(out, true); - } else { - out << 0; - } + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->write(out, true); + } else { + out << 0; + } +} + +void HeightfieldColorAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( + in, size, decodeInline(reference), true)); + } +} + +void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + if (!isLeaf) { + return; + } + HeightfieldDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference), true); + } else { + out << 0; } } @@ -669,8 +899,8 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); return true; } - int size = glm::sqrt(maxSize / (float)BYTES_PER_PIXEL); - QByteArray contents(size * size * BYTES_PER_PIXEL, 0); + int size = glm::sqrt(maxSize / (float)HeightfieldData::COLOR_BYTES); + QByteArray contents(size * size * HeightfieldData::COLOR_BYTES, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { HeightfieldDataPointer child = decodeInline(children[i]); @@ -678,7 +908,7 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post continue; } const QByteArray& childContents = child->getContents(); - int childSize = glm::sqrt(childContents.size() / (float)BYTES_PER_PIXEL); + int childSize = glm::sqrt(childContents.size() / (float)HeightfieldData::COLOR_BYTES); const int INDEX_MASK = 1; int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; @@ -688,24 +918,25 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post } int Z_SHIFT = 2; int zIndex = (i >> Z_SHIFT) & INDEX_MASK; - char* dest = contents.data() + ((zIndex * halfSize * size) + (xIndex * halfSize)) * BYTES_PER_PIXEL; + char* dest = contents.data() + ((zIndex * halfSize * size) + (xIndex * halfSize)) * HeightfieldData::COLOR_BYTES; uchar* src = (uchar*)childContents.data(); - int childStride = childSize * BYTES_PER_PIXEL; - int stride = size * BYTES_PER_PIXEL; + int childStride = childSize * HeightfieldData::COLOR_BYTES; + int stride = size * HeightfieldData::COLOR_BYTES; int halfStride = stride / 2; - int childStep = 2 * BYTES_PER_PIXEL; - int redOffset3 = childStride + BYTES_PER_PIXEL; - int greenOffset1 = BYTES_PER_PIXEL + 1; + int childStep = 2 * HeightfieldData::COLOR_BYTES; + int redOffset3 = childStride + HeightfieldData::COLOR_BYTES; + int greenOffset1 = HeightfieldData::COLOR_BYTES + 1; int greenOffset2 = childStride + 1; - int greenOffset3 = childStride + BYTES_PER_PIXEL + 1; - int blueOffset1 = BYTES_PER_PIXEL + 2; + int greenOffset3 = childStride + HeightfieldData::COLOR_BYTES + 1; + int blueOffset1 = HeightfieldData::COLOR_BYTES + 2; int blueOffset2 = childStride + 2; - int blueOffset3 = childStride + BYTES_PER_PIXEL + 2; + int blueOffset3 = childStride + HeightfieldData::COLOR_BYTES + 2; if (childSize == size) { // simple case: one destination value for four child values for (int z = 0; z < halfSize; z++) { - for (char* end = dest + halfSize * BYTES_PER_PIXEL; dest != end; src += childStep) { - *dest++ = ((int)src[0] + (int)src[BYTES_PER_PIXEL] + (int)src[childStride] + (int)src[redOffset3]) >> 2; + for (char* end = dest + halfSize * HeightfieldData::COLOR_BYTES; dest != end; src += childStep) { + *dest++ = ((int)src[0] + (int)src[HeightfieldData::COLOR_BYTES] + + (int)src[childStride] + (int)src[redOffset3]) >> 2; *dest++ = ((int)src[1] + (int)src[greenOffset1] + (int)src[greenOffset2] + (int)src[greenOffset3]) >> 2; *dest++ = ((int)src[2] + (int)src[blueOffset1] + (int)src[blueOffset2] + (int)src[blueOffset3]) >> 2; } @@ -717,13 +948,14 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post int halfChildSize = childSize / 2; int destPerSrc = size / childSize; for (int z = 0; z < halfChildSize; z++) { - for (uchar* end = src + childSize * BYTES_PER_PIXEL; src != end; src += childStep) { - *dest++ = ((int)src[0] + (int)src[BYTES_PER_PIXEL] + (int)src[childStride] + (int)src[redOffset3]) >> 2; + for (uchar* end = src + childSize * HeightfieldData::COLOR_BYTES; src != end; src += childStep) { + *dest++ = ((int)src[0] + (int)src[HeightfieldData::COLOR_BYTES] + + (int)src[childStride] + (int)src[redOffset3]) >> 2; *dest++ = ((int)src[1] + (int)src[greenOffset1] + (int)src[greenOffset2] + (int)src[greenOffset3]) >> 2; *dest++ = ((int)src[2] + (int)src[blueOffset1] + (int)src[blueOffset2] + (int)src[blueOffset3]) >> 2; for (int j = 1; j < destPerSrc; j++) { - memcpy(dest, dest - BYTES_PER_PIXEL, BYTES_PER_PIXEL); - dest += BYTES_PER_PIXEL; + memcpy(dest, dest - HeightfieldData::COLOR_BYTES, HeightfieldData::COLOR_BYTES); + dest += HeightfieldData::COLOR_BYTES; } } dest += halfStride; diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 5d973341ad..ddf6105662 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -28,6 +28,7 @@ class QScriptEngine; class QScriptValue; class Attribute; +class HeightfieldData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; @@ -421,26 +422,37 @@ public: virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; +typedef QExplicitlySharedDataPointer HeightfieldDataPointer; + /// Contains a block of heightfield data. class HeightfieldData : public QSharedData { public: + static const int COLOR_BYTES = 3; + HeightfieldData(const QByteArray& contents); HeightfieldData(Bitstream& in, int bytes, bool color); - + HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color); + const QByteArray& getContents() const { return _contents; } void write(Bitstream& out, bool color); + void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color); private: + void read(Bitstream& in, int bytes, bool color); + void set(const QImage& image, bool color); + QByteArray _contents; QByteArray _encoded; QMutex _encodedMutex; + + HeightfieldDataPointer _deltaData; + QByteArray _encodedDelta; + QMutex _encodedDeltaMutex; }; -typedef QExplicitlySharedDataPointer HeightfieldDataPointer; - /// An attribute that stores heightfield data. class HeightfieldAttribute : public InlineAttribute { Q_OBJECT @@ -451,7 +463,10 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - + + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const; + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; @@ -466,6 +481,9 @@ public: virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const; + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index 70b574d2a2..a2d3410314 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -61,180 +61,6 @@ SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(cons return closestSpanner; } -class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor { -public: - - float intersectionDistance; - - RayHeightfieldIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod); - - virtual int visit(MetavoxelInfo& info, float distance); -}; - -RayHeightfieldIntersectionVisitor::RayHeightfieldIntersectionVisitor(const glm::vec3& origin, - const glm::vec3& direction, const MetavoxelLOD& lod) : - RayIntersectionVisitor(origin, direction, QVector() << - AttributeRegistry::getInstance()->getHeightfieldAttribute(), QVector(), lod), - intersectionDistance(FLT_MAX) { -} - -static const float EIGHT_BIT_MAXIMUM_RECIPROCAL = 1.0f / 255.0f; - -int RayHeightfieldIntersectionVisitor::visit(MetavoxelInfo& info, float distance) { - if (!info.isLeaf) { - return _order; - } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); - if (!pointer) { - return STOP_RECURSION; - } - const QByteArray& contents = pointer->getContents(); - const uchar* src = (const uchar*)contents.constData(); - int size = glm::sqrt((float)contents.size()); - int highest = size - 1; - float heightScale = highest * EIGHT_BIT_MAXIMUM_RECIPROCAL; - - // find the initial location in heightfield coordinates - glm::vec3 entry = (_origin + distance * _direction - info.minimum) * (float)highest / info.size; - glm::vec3 floors = glm::floor(entry); - glm::vec3 ceils = glm::ceil(entry); - if (floors.x == ceils.x) { - if (_direction.x > 0.0f) { - ceils.x += 1.0f; - } else { - floors.x -= 1.0f; - } - } - if (floors.z == ceils.z) { - if (_direction.z > 0.0f) { - ceils.z += 1.0f; - } else { - floors.z -= 1.0f; - } - } - - bool withinBounds = true; - float accumulatedDistance = 0.0f; - while (withinBounds) { - // find the heights at the corners of the current cell - int floorX = qMin(qMax((int)floors.x, 0), highest); - int floorZ = qMin(qMax((int)floors.z, 0), highest); - int ceilX = qMin(qMax((int)ceils.x, 0), highest); - int ceilZ = qMin(qMax((int)ceils.z, 0), highest); - float upperLeft = src[floorZ * size + floorX] * heightScale; - float upperRight = src[floorZ * size + ceilX] * heightScale; - float lowerLeft = src[ceilZ * size + floorX] * heightScale; - float lowerRight = src[ceilZ * size + ceilX] * heightScale; - - // find the distance to the next x coordinate - float xDistance = FLT_MAX; - if (_direction.x > 0.0f) { - xDistance = (ceils.x - entry.x) / _direction.x; - } else if (_direction.x < 0.0f) { - xDistance = (floors.x - entry.x) / _direction.x; - } - - // and the distance to the next z coordinate - float zDistance = FLT_MAX; - if (_direction.z > 0.0f) { - zDistance = (ceils.z - entry.z) / _direction.z; - } else if (_direction.z < 0.0f) { - zDistance = (floors.z - entry.z) / _direction.z; - } - - // the exit distance is the lower of those two - float exitDistance = qMin(xDistance, zDistance); - glm::vec3 exit, nextFloors = floors, nextCeils = ceils; - if (exitDistance == FLT_MAX) { - if (_direction.y > 0.0f) { - return SHORT_CIRCUIT; // line points upwards; no collisions possible - } - withinBounds = false; // line points downwards; check this cell only - - } else { - // find the exit point and the next cell, and determine whether it's still within the bounds - exit = entry + exitDistance * _direction; - withinBounds = (exit.y >= 0.0f && exit.y <= highest); - if (exitDistance == xDistance) { - if (_direction.x > 0.0f) { - nextFloors.x += 1.0f; - withinBounds &= (nextCeils.x += 1.0f) <= highest; - } else { - withinBounds &= (nextFloors.x -= 1.0f) >= 0.0f; - nextCeils.x -= 1.0f; - } - } - if (exitDistance == zDistance) { - if (_direction.z > 0.0f) { - nextFloors.z += 1.0f; - withinBounds &= (nextCeils.z += 1.0f) <= highest; - } else { - withinBounds &= (nextFloors.z -= 1.0f) >= 0.0f; - nextCeils.z -= 1.0f; - } - } - // check the vertical range of the ray against the ranges of the cell heights - if (qMin(entry.y, exit.y) > qMax(qMax(upperLeft, upperRight), qMax(lowerLeft, lowerRight)) || - qMax(entry.y, exit.y) < qMin(qMin(upperLeft, upperRight), qMin(lowerLeft, lowerRight))) { - entry = exit; - floors = nextFloors; - ceils = nextCeils; - accumulatedDistance += exitDistance; - continue; - } - } - // having passed the bounds check, we must check against the planes - glm::vec3 relativeEntry = entry - glm::vec3(floors.x, upperLeft, floors.z); - - // first check the triangle including the Z+ segment - glm::vec3 lowerNormal(lowerLeft - lowerRight, 1.0f, upperLeft - lowerLeft); - float lowerProduct = glm::dot(lowerNormal, _direction); - if (lowerProduct < 0.0f) { - float planeDistance = -glm::dot(lowerNormal, relativeEntry) / lowerProduct; - glm::vec3 intersection = relativeEntry + planeDistance * _direction; - if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && - intersection.z >= intersection.x) { - intersectionDistance = qMin(intersectionDistance, distance + - (accumulatedDistance + planeDistance) * (info.size / highest)); - return SHORT_CIRCUIT; - } - } - - // then the one with the X+ segment - glm::vec3 upperNormal(upperLeft - upperRight, 1.0f, upperRight - lowerRight); - float upperProduct = glm::dot(upperNormal, _direction); - if (upperProduct < 0.0f) { - float planeDistance = -glm::dot(upperNormal, relativeEntry) / upperProduct; - glm::vec3 intersection = relativeEntry + planeDistance * _direction; - if (intersection.x >= 0.0f && intersection.x <= 1.0f && intersection.z >= 0.0f && intersection.z <= 1.0f && - intersection.x >= intersection.z) { - intersectionDistance = qMin(intersectionDistance, distance + - (accumulatedDistance + planeDistance) * (info.size / highest)); - return SHORT_CIRCUIT; - } - } - - // no joy; continue on our way - entry = exit; - floors = nextFloors; - ceils = nextCeils; - accumulatedDistance += exitDistance; - } - - return STOP_RECURSION; -} - -bool MetavoxelClientManager::findFirstRayHeightfieldIntersection(const glm::vec3& origin, - const glm::vec3& direction, float& distance) { - RayHeightfieldIntersectionVisitor visitor(origin, direction, getLOD()); - guide(visitor); - if (visitor.intersectionDistance == FLT_MAX) { - return false; - } - distance = visitor.intersectionDistance; - return true; -} - void MetavoxelClientManager::setSphere(const glm::vec3& center, float radius, const QColor& color) { Sphere* sphere = new Sphere(); sphere->setTranslation(center); @@ -252,84 +78,6 @@ void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool re QMetaObject::invokeMethod(_updater, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable)); } -class HeightfieldHeightVisitor : public MetavoxelVisitor { -public: - - float height; - - HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location); - - virtual int visit(MetavoxelInfo& info); - -private: - - glm::vec3 _location; -}; - -HeightfieldHeightVisitor::HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location) : - MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute(), - QVector(), lod), - height(-FLT_MAX), - _location(location) { -} - -static const int REVERSE_ORDER = MetavoxelVisitor::encodeOrder(7, 6, 5, 4, 3, 2, 1, 0); - -int HeightfieldHeightVisitor::visit(MetavoxelInfo& info) { - glm::vec3 relative = _location - info.minimum; - if (relative.x < 0.0f || relative.z < 0.0f || relative.x > info.size || relative.z > info.size || - height >= info.minimum.y + info.size) { - return STOP_RECURSION; - } - if (!info.isLeaf) { - return REVERSE_ORDER; - } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); - if (!pointer) { - return STOP_RECURSION; - } - const QByteArray& contents = pointer->getContents(); - const uchar* src = (const uchar*)contents.constData(); - int size = glm::sqrt((float)contents.size()); - int highest = size - 1; - relative *= highest / info.size; - - // find the bounds of the cell containing the point and the shared vertex heights - glm::vec3 floors = glm::floor(relative); - glm::vec3 ceils = glm::ceil(relative); - glm::vec3 fracts = glm::fract(relative); - int floorX = qMin(qMax((int)floors.x, 0), highest); - int floorZ = qMin(qMax((int)floors.z, 0), highest); - int ceilX = qMin(qMax((int)ceils.x, 0), highest); - int ceilZ = qMin(qMax((int)ceils.z, 0), highest); - float upperLeft = src[floorZ * size + floorX]; - float lowerRight = src[ceilZ * size + ceilX]; - float interpolatedHeight; - - // the final vertex (and thus which triangle we check) depends on which half we're on - if (fracts.x > fracts.z) { - float upperRight = src[floorZ * size + ceilX]; - interpolatedHeight = glm::mix(glm::mix(upperLeft, upperRight, fracts.x), lowerRight, fracts.z); - - } else { - float lowerLeft = src[ceilZ * size + floorX]; - interpolatedHeight = glm::mix(upperLeft, glm::mix(lowerLeft, lowerRight, fracts.x), fracts.z); - } - if (interpolatedHeight == 0.0f) { - return STOP_RECURSION; // ignore zero values - } - - // convert the interpolated height into world space - height = qMax(height, info.minimum.y + interpolatedHeight * info.size * EIGHT_BIT_MAXIMUM_RECIPROCAL); - return SHORT_CIRCUIT; -} - -float MetavoxelClientManager::getHeightfieldHeight(const glm::vec3& location) { - HeightfieldHeightVisitor visitor(getLOD(), location); - guide(visitor); - return visitor.height; -} - MetavoxelLOD MetavoxelClientManager::getLOD() { return MetavoxelLOD(); } diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h index 8fc9bcd38d..333b709b9e 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.h +++ b/libraries/metavoxels/src/MetavoxelClientManager.h @@ -37,16 +37,12 @@ public: SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance); - bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance); - Q_INVOKABLE void setSphere(const glm::vec3& center, float radius, const QColor& color = QColor(Qt::gray)); Q_INVOKABLE void setSpanner(const SharedObjectPointer& object, bool reliable = false); Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); - Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); - /// Returns the current LOD. This must be thread-safe, as it will be called from the updater thread. virtual MetavoxelLOD getLOD(); diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 926d839991..df6e8172e4 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -361,7 +361,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { QByteArray contents(pointer->getContents()); int size = glm::sqrt((float)contents.size()); int highest = size - 1; - float heightScale = highest / info.size; + float heightScale = size / info.size; glm::vec3 center = (_edit.position - info.minimum) * heightScale; float scaledRadius = _edit.radius * heightScale; @@ -378,6 +378,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { float squaredRadiusReciprocal = 1.0f / squaredRadius; const int EIGHT_BIT_MAXIMUM = 255; float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size; + bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { uchar* dest = lineDest; for (float x = startX; x <= endX; x += 1.0f, dest++) { @@ -386,14 +387,18 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (distanceSquared <= squaredRadius) { // height falls off towards edges int value = *dest + scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; - *dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM); + if (value != *dest) { + *dest = qMin(qMax(value, 0), EIGHT_BIT_MAXIMUM); + changed = true; + } } } lineDest += size; } - - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + if (changed) { + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + } return STOP_RECURSION; } @@ -445,7 +450,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { const int BYTES_PER_PIXEL = 3; int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); int highest = size - 1; - float heightScale = highest / info.size; + float heightScale = size / info.size; glm::vec3 center = (_edit.position - info.minimum) * heightScale; float scaledRadius = _edit.radius * heightScale; @@ -461,6 +466,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; float squaredRadius = scaledRadius * scaledRadius; char red = _edit.color.red(), green = _edit.color.green(), blue = _edit.color.blue(); + bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { char* dest = lineDest; for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { @@ -469,13 +475,15 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { dest[0] = red; dest[1] = green; dest[2] = blue; + changed = true; } } lineDest += stride; } - - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + if (changed) { + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + } return STOP_RECURSION; } diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index 2b46330961..e6b96e97b0 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -164,6 +164,11 @@ Box::Box(const glm::vec3& minimum, const glm::vec3& maximum) : minimum(minimum), maximum(maximum) { } +void Box::add(const Box& other) { + minimum = glm::min(minimum, other.minimum); + maximum = glm::max(maximum, other.maximum); +} + bool Box::contains(const glm::vec3& point) const { return point.x >= minimum.x && point.x <= maximum.x && point.y >= minimum.y && point.y <= maximum.y && @@ -182,6 +187,14 @@ bool Box::intersects(const Box& other) const { other.maximum.z >= minimum.z && other.minimum.z <= maximum.z; } +Box Box::getIntersection(const Box& other) const { + return Box(glm::max(minimum, other.minimum), glm::min(maximum, other.maximum)); +} + +bool Box::isEmpty() const { + return minimum.x >= maximum.x || minimum.y >= maximum.y || minimum.z >= maximum.z; +} + const int X_MAXIMUM_FLAG = 1; const int Y_MAXIMUM_FLAG = 2; const int Z_MAXIMUM_FLAG = 4; diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 339c07a21e..4228af059f 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -44,12 +44,18 @@ public: explicit Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3()); + void add(const Box& other); + bool contains(const glm::vec3& point) const; bool contains(const Box& other) const; bool intersects(const Box& other) const; + Box getIntersection(const Box& other) const; + + bool isEmpty() const; + float getLongestSide() const { return qMax(qMax(maximum.x - minimum.x, maximum.y - minimum.y), maximum.z - minimum.z); } glm::vec3 getVertex(int index) const; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index fcd284fd4a..91166129ad 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -36,6 +36,7 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); _isConnected = false; + emit disconnectedFromDomain(); if (_handshakeTimer) { _handshakeTimer->stop(); @@ -129,6 +130,8 @@ void DomainHandler::setIsConnected(bool isConnected) { // we've connected to new domain - time to ask it for global settings requestDomainSettings(); + } else { + emit disconnectedFromDomain(); } } } @@ -196,4 +199,4 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement _sockAddr.setPort(dtlsPort); // initializeDTLSSession(); -} \ No newline at end of file +} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 28433c5455..91caddca22 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -70,6 +70,7 @@ private slots: signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); + void disconnectedFromDomain(); void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 44e45c359a..f50f7493fb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -147,15 +147,20 @@ void LimitedNodeList::changeSocketBufferSizes(int numBytes) { setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, bufferOpt, reinterpret_cast(&numBytes), sizeof(numBytes)); - int newBufferSize = 0; - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, bufferOpt, reinterpret_cast(&newBufferSize), &sizeOfInt); - QString bufferTypeString = (i == 0) ? "send" : "receive"; - qDebug() << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; + if (oldBufferSize < numBytes) { + int newBufferSize = 0; + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, bufferOpt, reinterpret_cast(&newBufferSize), &sizeOfInt); + + qDebug() << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to" + << newBufferSize << "bytes"; + } else { + // don't make the buffer smaller + qDebug() << "Did not change socket" << bufferTypeString << "buffer size from" << oldBufferSize + << "since it is larger than desired size of" << numBytes; + } } - - } bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 4b92f8ba38..cd80c441c1 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "Logging.h" @@ -18,7 +19,8 @@ ThreadedAssignment::ThreadedAssignment(const QByteArray& packet) : Assignment(packet), - _isFinished(false) + _isFinished(false), + _datagramProcessingThread(NULL) { } @@ -28,10 +30,25 @@ void ThreadedAssignment::setFinished(bool isFinished) { if (_isFinished) { aboutToFinish(); - emit finished(); + + NodeList* nodeList = NodeList::getInstance(); + + // if we have a datagram processing thread, quit it and wait on it to make sure that + // the node socket is back on the same thread as the NodeList + + if (_datagramProcessingThread) { + // tell the datagram processing thread to quit and wait until it is done, then return the node socket to the NodeList + _datagramProcessingThread->quit(); + _datagramProcessingThread->wait(); + + // set node socket parent back to NodeList + nodeList->getNodeSocket().setParent(nodeList); + } // move the NodeList back to the QCoreApplication instance's thread - NodeList::getInstance()->moveToThread(QCoreApplication::instance()->thread()); + nodeList->moveToThread(QCoreApplication::instance()->thread()); + + emit finished(); } } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index e9241d0272..454baa85f2 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -20,6 +20,7 @@ class ThreadedAssignment : public Assignment { Q_OBJECT public: ThreadedAssignment(const QByteArray& packet); + void setFinished(bool isFinished); virtual void aboutToFinish() { }; void addPacketStatsAndSendStatsPacket(QJsonObject& statsObject); @@ -29,15 +30,18 @@ public slots: virtual void run() = 0; virtual void readPendingDatagrams() = 0; virtual void sendStatsPacket(); - +signals: + void finished(); + protected: bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr); void commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats = true); bool _isFinished; + QThread* _datagramProcessingThread; + private slots: void checkInWithDomainServerOrExit(); -signals: - void finished(); + }; typedef QSharedPointer SharedAssignmentPointer; diff --git a/tests/jitter/src/main.cpp b/tests/jitter/src/main.cpp index a33347f9ef..8c93b7dbec 100644 --- a/tests/jitter/src/main.cpp +++ b/tests/jitter/src/main.cpp @@ -13,14 +13,17 @@ #include #include #endif +#include #include -#include // for MovingMinMaxAvg +#include #include -#include #include // for usecTimestampNow +#include +#include const quint64 MSEC_TO_USEC = 1000; +const quint64 LARGE_STATS_TIME = 500; // we don't expect stats calculation to take more than this many usecs void runSend(const char* addressOption, int port, int gap, int size, int report); void runReceive(const char* addressOption, int port, int gap, int size, int report); @@ -58,82 +61,125 @@ int main(int argc, const char * argv[]) { void runSend(const char* addressOption, int port, int gap, int size, int report) { std::cout << "runSend...\n"; +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + printf("WSAStartup failed with error %d\n", WSAGetLastError()); + return; + } +#endif + int sockfd; struct sockaddr_in servaddr; - + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr(addressOption); + servaddr.sin_port = htons(port); + + const int SAMPLES_FOR_SECOND = 1000000 / gap; + std::cout << "SAMPLES_FOR_SECOND:" << SAMPLES_FOR_SECOND << "\n"; + const int INTERVALS_PER_30_SECONDS = 30; + std::cout << "INTERVALS_PER_30_SECONDS:" << INTERVALS_PER_30_SECONDS << "\n"; + const int SAMPLES_FOR_30_SECONDS = 30 * SAMPLES_FOR_SECOND; + std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n"; + const int REPORTS_FOR_30_SECONDS = 30 * MSECS_PER_SECOND / report; + std::cout << "REPORTS_FOR_30_SECONDS:" << REPORTS_FOR_30_SECONDS << "\n"; + + int intervalsPerReport = report / MSEC_TO_USEC; + if (intervalsPerReport < 1) { + intervalsPerReport = 1; + } + std::cout << "intervalsPerReport:" << intervalsPerReport << "\n"; + MovingMinMaxAvg timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS); + MovingMinMaxAvg timeGapsPerReport(SAMPLES_FOR_SECOND, intervalsPerReport); + char* outputBuffer = new char[size]; memset(outputBuffer, 0, size); quint16 outgoingSequenceNumber = 0; - - sockfd=socket(AF_INET,SOCK_DGRAM,0); - - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr=inet_addr(addressOption); - servaddr.sin_port=htons(port); - - const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap; - std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n"; - - MovingMinMaxAvg timeGaps(1, SAMPLES_FOR_30_SECONDS); // stats - - const int SAMPLES_PER_REPORT = report * MSEC_TO_USEC / gap; - - std::cout << "SAMPLES_PER_REPORT:" << SAMPLES_PER_REPORT << "\n"; - - MovingMinMaxAvg timeGapsPerReport(1, SAMPLES_PER_REPORT); StDev stDevReportInterval; StDev stDev30s; StDev stDev; - + + SimpleMovingAverage averageNetworkTime(SAMPLES_FOR_30_SECONDS); + SimpleMovingAverage averageStatsCalcultionTime(SAMPLES_FOR_30_SECONDS); + float lastStatsCalculationTime = 0.0f; // we add out stats calculation time in the next calculation window + bool hasStatsCalculationTime = false; + quint64 last = usecTimestampNow(); quint64 lastReport = 0; - + while (true) { quint64 now = usecTimestampNow(); int actualGap = now - last; - - + + if (actualGap >= gap) { // pack seq num memcpy(outputBuffer, &outgoingSequenceNumber, sizeof(quint16)); - sendto(sockfd, outputBuffer, size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); + quint64 networkStart = usecTimestampNow(); + int n = sendto(sockfd, outputBuffer, size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); + quint64 networkEnd = usecTimestampNow(); + float networkElapsed = (float)(networkEnd - networkStart); + + if (n < 0) { + std::cout << "Send error: " << strerror(errno) << "\n"; + } outgoingSequenceNumber++; - + + quint64 statsCalcultionStart = usecTimestampNow(); + int gapDifferece = actualGap - gap; + timeGaps.update(gapDifferece); timeGapsPerReport.update(gapDifferece); stDev.addValue(gapDifferece); stDev30s.addValue(gapDifferece); stDevReportInterval.addValue(gapDifferece); last = now; + + // track out network time and stats calculation times + averageNetworkTime.updateAverage(networkElapsed); + + // for our stats calculation time, we actually delay the updating by one sample. + // we do this so that the calculation of the average timing for the stats calculation + // happen inside of the calculation processing. This ensures that tracking stats on + // stats calculation doesn't side effect the remaining running time. + if (hasStatsCalculationTime) { + averageStatsCalcultionTime.updateAverage(lastStatsCalculationTime); + } if (now - lastReport >= (report * MSEC_TO_USEC)) { std::cout << "\n" - << "SEND gap Difference From Expected\n" - << "Overall:\n" - << "min: " << timeGaps.getMin() << " usecs, " - << "max: " << timeGaps.getMax() << " usecs, " - << "avg: " << timeGaps.getAverage() << " usecs, " - << "stdev: " << stDev.getStDev() << " usecs\n" - << "Last 30s:\n" - << "min: " << timeGaps.getWindowMin() << " usecs, " - << "max: " << timeGaps.getWindowMax() << " usecs, " - << "avg: " << timeGaps.getWindowAverage() << " usecs, " - << "stdev: " << stDev30s.getStDev() << " usecs\n" - << "Last report interval:\n" - << "min: " << timeGapsPerReport.getWindowMin() << " usecs, " - << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " - << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " - << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" - << "\n"; + << "SEND gap Difference From Expected\n" + << "Overall:\n" + << "min: " << timeGaps.getMin() << " usecs, " + << "max: " << timeGaps.getMax() << " usecs, " + << "avg: " << timeGaps.getAverage() << " usecs, " + << "stdev: " << stDev.getStDev() << " usecs\n" + << "Last 30s:\n" + << "min: " << timeGaps.getWindowMin() << " usecs, " + << "max: " << timeGaps.getWindowMax() << " usecs, " + << "avg: " << timeGaps.getWindowAverage() << " usecs, " + << "stdev: " << stDev30s.getStDev() << " usecs\n" + << "Last report interval:\n" + << "min: " << timeGapsPerReport.getWindowMin() << " usecs, " + << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " + << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " + << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" + << "Average Execution Times Last 30s:\n" + << " network: " << averageNetworkTime.getAverage() << " usecs average\n" + << " stats: " << averageStatsCalcultionTime.getAverage() << " usecs average" + << "\n"; stDevReportInterval.reset(); if (stDev30s.getSamples() > SAMPLES_FOR_30_SECONDS) { @@ -142,70 +188,109 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) lastReport = now; } + + quint64 statsCalcultionEnd = usecTimestampNow(); + lastStatsCalculationTime = (float)(statsCalcultionEnd - statsCalcultionStart); + if (lastStatsCalculationTime > LARGE_STATS_TIME) { + qDebug() << "WARNING -- unexpectedly large lastStatsCalculationTime=" << lastStatsCalculationTime; + } + hasStatsCalculationTime = true; + } } + delete[] outputBuffer; + +#ifdef _WIN32 + WSACleanup(); +#endif } void runReceive(const char* addressOption, int port, int gap, int size, int report) { std::cout << "runReceive...\n"; +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + printf("WSAStartup failed with error %d\n", WSAGetLastError()); + return; + } +#endif - int sockfd,n; + int sockfd, n; struct sockaddr_in myaddr; - - char* inputBuffer = new char[size]; - memset(inputBuffer, 0, size); - - sockfd=socket(AF_INET, SOCK_DGRAM, 0); - + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + memset(&myaddr, 0, sizeof(myaddr)); myaddr.sin_family = AF_INET; - myaddr.sin_addr.s_addr=htonl(INADDR_ANY); - myaddr.sin_port=htons(port); + myaddr.sin_addr.s_addr = htonl(INADDR_ANY); + myaddr.sin_port = htons(port); - const int SAMPLES_FOR_30_SECONDS = 30 * 1000000 / gap; + const int SAMPLES_FOR_SECOND = 1000000 / gap; + std::cout << "SAMPLES_FOR_SECOND:" << SAMPLES_FOR_SECOND << "\n"; + const int INTERVALS_PER_30_SECONDS = 30; + std::cout << "INTERVALS_PER_30_SECONDS:" << INTERVALS_PER_30_SECONDS << "\n"; + const int SAMPLES_FOR_30_SECONDS = 30 * SAMPLES_FOR_SECOND; std::cout << "SAMPLES_FOR_30_SECONDS:" << SAMPLES_FOR_30_SECONDS << "\n"; - - MovingMinMaxAvg timeGaps(1, SAMPLES_FOR_30_SECONDS); // stats - - const int SAMPLES_PER_REPORT = report * MSEC_TO_USEC / gap; - - std::cout << "SAMPLES_PER_REPORT:" << SAMPLES_PER_REPORT << "\n"; - - MovingMinMaxAvg timeGapsPerReport(1, SAMPLES_PER_REPORT); - const int REPORTS_FOR_30_SECONDS = 30 * MSECS_PER_SECOND / report; - std::cout << "REPORTS_FOR_30_SECONDS:" << REPORTS_FOR_30_SECONDS << "\n"; + int intervalsPerReport = report / MSEC_TO_USEC; + if (intervalsPerReport < 1) { + intervalsPerReport = 1; + } + std::cout << "intervalsPerReport:" << intervalsPerReport << "\n"; + MovingMinMaxAvg timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS); + MovingMinMaxAvg timeGapsPerReport(SAMPLES_FOR_SECOND, intervalsPerReport); + + char* inputBuffer = new char[size]; + memset(inputBuffer, 0, size); + + SequenceNumberStats seqStats(REPORTS_FOR_30_SECONDS); StDev stDevReportInterval; StDev stDev30s; StDev stDev; - + + SimpleMovingAverage averageNetworkTime(SAMPLES_FOR_30_SECONDS); + SimpleMovingAverage averageStatsCalcultionTime(SAMPLES_FOR_30_SECONDS); + float lastStatsCalculationTime = 0.0f; // we add out stats calculation time in the next calculation window + bool hasStatsCalculationTime = false; + if (bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) { std::cout << "bind failed\n"; return; - } - + } + quint64 last = 0; // first case quint64 lastReport = 0; while (true) { + + quint64 networkStart = usecTimestampNow(); n = recvfrom(sockfd, inputBuffer, size, 0, NULL, NULL); // we don't care about where it came from + quint64 networkEnd = usecTimestampNow(); + float networkElapsed = (float)(networkEnd - networkStart); + + if (n < 0) { + std::cout << "Receive error: " << strerror(errno) << "\n"; + } // parse seq num quint16 incomingSequenceNumber = *(reinterpret_cast(inputBuffer)); seqStats.sequenceNumberReceived(incomingSequenceNumber); - + if (last == 0) { last = usecTimestampNow(); std::cout << "first packet received\n"; } else { + + quint64 statsCalcultionStart = usecTimestampNow(); quint64 now = usecTimestampNow(); int actualGap = now - last; + int gapDifferece = actualGap - gap; timeGaps.update(gapDifferece); timeGapsPerReport.update(gapDifferece); @@ -214,29 +299,43 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo stDevReportInterval.addValue(gapDifferece); last = now; + // track out network time and stats calculation times + averageNetworkTime.updateAverage(networkElapsed); + + // for our stats calculation time, we actually delay the updating by one sample. + // we do this so that the calculation of the average timing for the stats calculation + // happen inside of the calculation processing. This ensures that tracking stats on + // stats calculation doesn't side effect the remaining running time. + if (hasStatsCalculationTime) { + averageStatsCalcultionTime.updateAverage(lastStatsCalculationTime); + } + if (now - lastReport >= (report * MSEC_TO_USEC)) { seqStats.pushStatsToHistory(); std::cout << "RECEIVE gap Difference From Expected\n" - << "Overall:\n" - << "min: " << timeGaps.getMin() << " usecs, " - << "max: " << timeGaps.getMax() << " usecs, " - << "avg: " << timeGaps.getAverage() << " usecs, " - << "stdev: " << stDev.getStDev() << " usecs\n" - << "Last 30s:\n" - << "min: " << timeGaps.getWindowMin() << " usecs, " - << "max: " << timeGaps.getWindowMax() << " usecs, " - << "avg: " << timeGaps.getWindowAverage() << " usecs, " - << "stdev: " << stDev30s.getStDev() << " usecs\n" - << "Last report interval:\n" - << "min: " << timeGapsPerReport.getWindowMin() << " usecs, " - << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " - << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " - << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" - << "\n"; - + << "Overall:\n" + << "min: " << timeGaps.getMin() << " usecs, " + << "max: " << timeGaps.getMax() << " usecs, " + << "avg: " << timeGaps.getAverage() << " usecs, " + << "stdev: " << stDev.getStDev() << " usecs\n" + << "Last 30s:\n" + << "min: " << timeGaps.getWindowMin() << " usecs, " + << "max: " << timeGaps.getWindowMax() << " usecs, " + << "avg: " << timeGaps.getWindowAverage() << " usecs, " + << "stdev: " << stDev30s.getStDev() << " usecs\n" + << "Last report interval:\n" + << "min: " << timeGapsPerReport.getWindowMin() << " usecs, " + << "max: " << timeGapsPerReport.getWindowMax() << " usecs, " + << "avg: " << timeGapsPerReport.getWindowAverage() << " usecs, " + << "stdev: " << stDevReportInterval.getStDev() << " usecs\n" + << "Average Execution Times Last 30s:\n" + << " network: " << averageNetworkTime.getAverage() << " usecs average\n" + << " stats: " << averageStatsCalcultionTime.getAverage() << " usecs average" + << "\n"; stDevReportInterval.reset(); + if (stDev30s.getSamples() > SAMPLES_FOR_30_SECONDS) { stDev30s.reset(); } @@ -245,20 +344,32 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo PacketStreamStats packetStatsLastReportInterval = seqStats.getStatsForLastHistoryInterval(); std::cout << "RECEIVE Packet Stats\n" - << "Overall:\n" - << "lost: " << seqStats.getLost() << ", " - << "lost %: " << seqStats.getStats().getLostRate() * 100.0f << "%\n" - << "Last 30s:\n" - << "lost: " << packetStatsLast30s._lost << ", " - << "lost %: " << packetStatsLast30s.getLostRate() * 100.0f << "%\n" - << "Last report interval:\n" - << "lost: " << packetStatsLastReportInterval._lost << ", " - << "lost %: " << packetStatsLastReportInterval.getLostRate() * 100.0f << "%\n" - << "\n\n"; + << "Overall:\n" + << "lost: " << seqStats.getLost() << ", " + << "lost %: " << seqStats.getStats().getLostRate() * 100.0f << "%\n" + << "Last 30s:\n" + << "lost: " << packetStatsLast30s._lost << ", " + << "lost %: " << packetStatsLast30s.getLostRate() * 100.0f << "%\n" + << "Last report interval:\n" + << "lost: " << packetStatsLastReportInterval._lost << ", " + << "lost %: " << packetStatsLastReportInterval.getLostRate() * 100.0f << "%\n" + << "\n\n"; lastReport = now; } + + quint64 statsCalcultionEnd = usecTimestampNow(); + + lastStatsCalculationTime = (float)(statsCalcultionEnd - statsCalcultionStart); + if (lastStatsCalculationTime > LARGE_STATS_TIME) { + qDebug() << "WARNING -- unexpectedly large lastStatsCalculationTime=" << lastStatsCalculationTime; + } + hasStatsCalculationTime = true; } } -} + delete[] inputBuffer; +#ifdef _WIN32 + WSACleanup(); +#endif +}