mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-05 02:50:23 +02:00
merge upstream/master into andrew/ragdoll
This commit is contained in:
commit
b2d3830057
30 changed files with 1654 additions and 587 deletions
|
@ -37,6 +37,7 @@
|
|||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
47
assignment-client/src/audio/AudioMixerDatagramProcessor.cpp
Normal file
47
assignment-client/src/audio/AudioMixerDatagramProcessor.cpp
Normal file
|
@ -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 <QDebug>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
32
assignment-client/src/audio/AudioMixerDatagramProcessor.h
Normal file
32
assignment-client/src/audio/AudioMixerDatagramProcessor.h
Normal file
|
@ -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 <qobject.h>
|
||||
#include <qudpsocket.h>
|
||||
|
||||
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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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<QFileOpenEvent*>(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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldBuffer*>(
|
||||
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldBuffer*>(
|
||||
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>(), 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<HeightfieldPoint> 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<int> 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<BufferDataPointer>(children[0]);
|
||||
for (int i = 1; i < MERGE_COUNT; i++) {
|
||||
if (firstChild != decodeInline<BufferDataPointer>(children[i])) {
|
||||
*(BufferDataPointer*)&parent = _defaultValue;
|
||||
*(BufferDataPointer*)&parent = _defaultValue;
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (decodeInline<BufferDataPointer>(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<Box>& intersections);
|
||||
|
||||
void init(HeightfieldBuffer* buffer) { _buffer = buffer; }
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const QVector<Box>& _intersections;
|
||||
HeightfieldBuffer* _buffer;
|
||||
};
|
||||
|
||||
HeightfieldAugmentVisitor::HeightfieldAugmentVisitor(const MetavoxelLOD& lod) :
|
||||
HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector<Box>& intersections) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
|
||||
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod) {
|
||||
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>(), lod),
|
||||
_intersections(intersections) {
|
||||
}
|
||||
|
||||
int HeightfieldAugmentVisitor::visit(MetavoxelInfo& info) {
|
||||
if (info.isLeaf) {
|
||||
HeightfieldBuffer* buffer = NULL;
|
||||
HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldDataPointer>();
|
||||
if (height) {
|
||||
HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldDataPointer>();
|
||||
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<HeightfieldDataPointer>();
|
||||
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<HeightfieldDataPointer>();
|
||||
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<Box> regions;
|
||||
Box regionBounds;
|
||||
|
||||
HeightfieldRegionVisitor(const MetavoxelLOD& lod);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
void addRegion(const Box& unextended, const Box& extended);
|
||||
|
||||
QVector<Box> _intersections;
|
||||
HeightfieldFetchVisitor _fetchVisitor;
|
||||
};
|
||||
|
||||
HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
|
||||
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>() <<
|
||||
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<HeightfieldDataPointer>();
|
||||
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<HeightfieldDataPointer>();
|
||||
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<const HeightfieldBuffer*>(
|
||||
info.inputValues.at(2).getInlineValue<BufferDataPointer>().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<Box>& regions, const Box& regionBounds);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const QVector<Box>& _regions;
|
||||
const Box& _regionBounds;
|
||||
QVector<Box> _intersections;
|
||||
HeightfieldFetchVisitor _fetchVisitor;
|
||||
};
|
||||
|
||||
HeightfieldUpdateVisitor::HeightfieldUpdateVisitor(const MetavoxelLOD& lod, const QVector<Box>& regions,
|
||||
const Box& regionBounds) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector<AttributePointer>() <<
|
||||
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<const HeightfieldBuffer*>(
|
||||
info.inputValues.at(0).getInlineValue<BufferDataPointer>().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<AttributePointer>() << attribute, QVector<AttributePointer>(), lod),
|
||||
BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << 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<BufferDataPointer>();
|
||||
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 };
|
||||
|
|
|
@ -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<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
|
||||
static QHash<int, BufferPair> _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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
#endif // hifi_ApplicationOverlay_h
|
||||
|
|
|
@ -956,11 +956,11 @@ void ImportHeightfieldTool::apply() {
|
|||
HeightfieldBuffer* buffer = static_cast<HeightfieldBuffer*>(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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<HeightfieldDataPointer>(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<HeightfieldDataPointer>(value);
|
||||
if (data) {
|
||||
data->write(out, false);
|
||||
} else {
|
||||
out << 0;
|
||||
}
|
||||
if (!isLeaf) {
|
||||
return;
|
||||
}
|
||||
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(reference), false));
|
||||
}
|
||||
}
|
||||
|
||||
void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
|
||||
if (!isLeaf) {
|
||||
return;
|
||||
}
|
||||
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
|
||||
if (data) {
|
||||
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(value);
|
||||
if (data) {
|
||||
data->write(out, true);
|
||||
} else {
|
||||
out << 0;
|
||||
}
|
||||
if (!isLeaf) {
|
||||
return;
|
||||
}
|
||||
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(reference), true));
|
||||
}
|
||||
}
|
||||
|
||||
void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const {
|
||||
if (!isLeaf) {
|
||||
return;
|
||||
}
|
||||
HeightfieldDataPointer data = decodeInline<HeightfieldDataPointer>(value);
|
||||
if (data) {
|
||||
data->writeDelta(out, decodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(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;
|
||||
|
|
|
@ -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<HeightfieldData> 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<HeightfieldData> HeightfieldDataPointer;
|
||||
|
||||
/// An attribute that stores heightfield data.
|
||||
class HeightfieldAttribute : public InlineAttribute<HeightfieldDataPointer> {
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getHeightfieldAttribute(), QVector<AttributePointer>(), 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<HeightfieldDataPointer>();
|
||||
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<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute(),
|
||||
QVector<AttributePointer>(), 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<HeightfieldDataPointer>();
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<HeightfieldDataPointer>(newPointer));
|
||||
if (changed) {
|
||||
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
|
||||
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(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<HeightfieldDataPointer>(newPointer));
|
||||
if (changed) {
|
||||
HeightfieldDataPointer newPointer(new HeightfieldData(contents));
|
||||
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline<HeightfieldDataPointer>(newPointer));
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -147,15 +147,20 @@ void LimitedNodeList::changeSocketBufferSizes(int numBytes) {
|
|||
setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, bufferOpt, reinterpret_cast<const char*>(&numBytes),
|
||||
sizeof(numBytes));
|
||||
|
||||
int newBufferSize = 0;
|
||||
getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, bufferOpt, reinterpret_cast<char*>(&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<char*>(&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) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ThreadedAssignment> SharedAssignmentPointer;
|
||||
|
|
|
@ -13,14 +13,17 @@
|
|||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#include <cerrno>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <MovingMinMaxAvg.h> // for MovingMinMaxAvg
|
||||
#include <MovingMinMaxAvg.h>
|
||||
#include <SequenceNumberStats.h>
|
||||
#include <StdDev.h>
|
||||
#include <SharedUtil.h> // for usecTimestampNow
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <StdDev.h>
|
||||
|
||||
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<int> timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS);
|
||||
MovingMinMaxAvg<int> 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<int> 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<int> 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<int> 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<int> 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<int> timeGaps(SAMPLES_FOR_SECOND, INTERVALS_PER_30_SECONDS);
|
||||
MovingMinMaxAvg<int> 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<quint16*>(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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue