mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:24:00 +02:00
Merge branch 'master' into add_cara_face_tracker
This commit is contained in:
commit
dcd7b81426
25 changed files with 1490 additions and 490 deletions
|
@ -21,18 +21,21 @@
|
|||
|
||||
#include "MetavoxelServer.h"
|
||||
|
||||
const int SEND_INTERVAL = 50;
|
||||
|
||||
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
|
||||
_nextSender(0) {
|
||||
}
|
||||
|
||||
void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
edit.apply(_data, SharedObject::getWeakHash());
|
||||
MetavoxelData data = _data;
|
||||
edit.apply(data, SharedObject::getWeakHash());
|
||||
setData(data);
|
||||
}
|
||||
|
||||
void MetavoxelServer::setData(const MetavoxelData& data) {
|
||||
if (_data != data) {
|
||||
emit dataChanged(_data = data);
|
||||
}
|
||||
}
|
||||
|
||||
const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server";
|
||||
|
@ -43,19 +46,34 @@ void MetavoxelServer::run() {
|
|||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachSession(const SharedNodePointer&)));
|
||||
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
connect(nodeList, &NodeList::nodeAdded, this, &MetavoxelServer::maybeAttachSession);
|
||||
connect(nodeList, &NodeList::nodeKilled, this, &MetavoxelServer::maybeDeleteSession);
|
||||
|
||||
// initialize Bitstream before using it in multiple threads
|
||||
Bitstream::preThreadingInit();
|
||||
|
||||
// create the senders, each with its own thread
|
||||
int threadCount = QThread::idealThreadCount();
|
||||
if (threadCount == -1) {
|
||||
const int DEFAULT_THREAD_COUNT = 4;
|
||||
threadCount = DEFAULT_THREAD_COUNT;
|
||||
}
|
||||
qDebug() << "Creating" << threadCount << "sender threads";
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
QThread* thread = new QThread(this);
|
||||
MetavoxelSender* sender = new MetavoxelSender(this);
|
||||
sender->moveToThread(thread);
|
||||
connect(thread, &QThread::finished, sender, &QObject::deleteLater);
|
||||
thread->start();
|
||||
QMetaObject::invokeMethod(sender, "start");
|
||||
_senders.append(sender);
|
||||
}
|
||||
|
||||
// create the persister and start it in its own thread
|
||||
_persister = new MetavoxelPersister(this);
|
||||
QThread* persistenceThread = new QThread(this);
|
||||
_persister->moveToThread(persistenceThread);
|
||||
_persister->connect(persistenceThread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
connect(persistenceThread, &QThread::finished, _persister, &QObject::deleteLater);
|
||||
persistenceThread->start();
|
||||
|
||||
// queue up the load
|
||||
|
@ -85,6 +103,11 @@ void MetavoxelServer::readPendingDatagrams() {
|
|||
|
||||
void MetavoxelServer::aboutToFinish() {
|
||||
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _data));
|
||||
|
||||
foreach (MetavoxelSender* sender, _senders) {
|
||||
sender->thread()->quit();
|
||||
sender->thread()->wait();
|
||||
}
|
||||
_persister->thread()->quit();
|
||||
_persister->thread()->wait();
|
||||
}
|
||||
|
@ -92,17 +115,53 @@ void MetavoxelServer::aboutToFinish() {
|
|||
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
node->setLinkedData(new MetavoxelSession(node, this));
|
||||
MetavoxelSender* sender = _senders.at(_nextSender);
|
||||
_nextSender = (_nextSender + 1) % _senders.size();
|
||||
MetavoxelSession* session = new MetavoxelSession(node, sender);
|
||||
session->moveToThread(sender->thread());
|
||||
QMetaObject::invokeMethod(sender, "addSession", Q_ARG(QObject*, session));
|
||||
node->setLinkedData(session);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::sendDeltas() {
|
||||
// send deltas for all sessions
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
static_cast<MetavoxelSession*>(node->getLinkedData())->update();
|
||||
void MetavoxelServer::maybeDeleteSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
// we assume the node is already locked
|
||||
MetavoxelSession* session = static_cast<MetavoxelSession*>(node->getLinkedData());
|
||||
if (session) {
|
||||
node->setLinkedData(NULL);
|
||||
session->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelSender::MetavoxelSender(MetavoxelServer* server) :
|
||||
_server(server),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, &QTimer::timeout, this, &MetavoxelSender::sendDeltas);
|
||||
|
||||
connect(_server, &MetavoxelServer::dataChanged, this, &MetavoxelSender::setData);
|
||||
}
|
||||
|
||||
const int SEND_INTERVAL = 50;
|
||||
|
||||
void MetavoxelSender::start() {
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
||||
void MetavoxelSender::addSession(QObject* session) {
|
||||
_sessions.insert(static_cast<MetavoxelSession*>(session));
|
||||
connect(session, &QObject::destroyed, this, &MetavoxelSender::removeSession);
|
||||
}
|
||||
|
||||
void MetavoxelSender::sendDeltas() {
|
||||
// send deltas for all sessions associated with our thread
|
||||
foreach (MetavoxelSession* session, _sessions) {
|
||||
session->update();
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
|
@ -112,9 +171,13 @@ void MetavoxelServer::sendDeltas() {
|
|||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL)));
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) :
|
||||
void MetavoxelSender::removeSession(QObject* session) {
|
||||
_sessions.remove(static_cast<MetavoxelSession*>(session));
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelSender* sender) :
|
||||
Endpoint(node, new PacketRecord(), NULL),
|
||||
_server(server),
|
||||
_sender(sender),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0) {
|
||||
|
||||
|
@ -138,7 +201,7 @@ void MetavoxelSession::update() {
|
|||
int start = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
|
||||
_server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
|
||||
_sender->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
|
||||
out.flush();
|
||||
int end = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
if (end > _sequencer.getMaxPacketSize()) {
|
||||
|
@ -150,7 +213,7 @@ void MetavoxelSession::update() {
|
|||
|
||||
_reliableDeltaWriteMappings = out.getAndResetWriteMappings();
|
||||
_reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten();
|
||||
_reliableDeltaData = _server->getData();
|
||||
_reliableDeltaData = _sender->getData();
|
||||
_reliableDeltaLOD = _lod;
|
||||
|
||||
// go back to the beginning with the current packet and note that there's a delta pending
|
||||
|
@ -173,7 +236,7 @@ void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
|
||||
PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
|
||||
return _reliableDeltaChannel ? new PacketRecord(_reliableDeltaLOD, _reliableDeltaData) :
|
||||
new PacketRecord(_lod, _server->getData());
|
||||
new PacketRecord(_lod, _sender->getData());
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message) {
|
||||
|
@ -183,7 +246,8 @@ void MetavoxelSession::handleMessage(const QVariant& message) {
|
|||
_lod = state.lod;
|
||||
|
||||
} else if (userType == MetavoxelEditMessage::Type) {
|
||||
_server->applyEdit(message.value<MetavoxelEditMessage>());
|
||||
QMetaObject::invokeMethod(_sender->getServer(), "applyEdit", Q_ARG(const MetavoxelEditMessage&,
|
||||
message.value<MetavoxelEditMessage>()));
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelPersister;
|
||||
class MetavoxelSender;
|
||||
class MetavoxelSession;
|
||||
|
||||
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
|
||||
|
@ -31,27 +32,64 @@ public:
|
|||
|
||||
MetavoxelServer(const QByteArray& packet);
|
||||
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
Q_INVOKABLE void setData(const MetavoxelData& data) { _data = data; }
|
||||
Q_INVOKABLE void setData(const MetavoxelData& data);
|
||||
|
||||
virtual void run();
|
||||
|
||||
virtual void readPendingDatagrams();
|
||||
|
||||
virtual void aboutToFinish();
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void dataChanged(const MetavoxelData& data);
|
||||
|
||||
private slots:
|
||||
|
||||
void maybeAttachSession(const SharedNodePointer& node);
|
||||
void sendDeltas();
|
||||
void maybeDeleteSession(const SharedNodePointer& node);
|
||||
|
||||
private:
|
||||
|
||||
QVector<MetavoxelSender*> _senders;
|
||||
int _nextSender;
|
||||
|
||||
MetavoxelPersister* _persister;
|
||||
|
||||
MetavoxelData _data;
|
||||
};
|
||||
|
||||
/// Handles update sending for one thread.
|
||||
class MetavoxelSender : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSender(MetavoxelServer* server);
|
||||
|
||||
MetavoxelServer* getServer() const { return _server; }
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
Q_INVOKABLE void addSession(QObject* session);
|
||||
|
||||
private slots:
|
||||
|
||||
void setData(const MetavoxelData& data) { _data = data; }
|
||||
void sendDeltas();
|
||||
void removeSession(QObject* session);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelServer* _server;
|
||||
QSet<MetavoxelSession*> _sessions;
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
|
@ -64,8 +102,8 @@ class MetavoxelSession : public Endpoint {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server);
|
||||
|
||||
MetavoxelSession(const SharedNodePointer& node, MetavoxelSender* sender);
|
||||
|
||||
virtual void update();
|
||||
|
||||
protected:
|
||||
|
@ -83,7 +121,7 @@ private:
|
|||
|
||||
void sendPacketGroup(int alreadySent = 0);
|
||||
|
||||
MetavoxelServer* _server;
|
||||
MetavoxelSender* _sender;
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
|
||||
|
|
|
@ -255,13 +255,24 @@ function update(deltaTime){
|
|||
}
|
||||
frame++;
|
||||
}
|
||||
|
||||
var locationChanged = false;
|
||||
if (location.hostname != oldHost) {
|
||||
print("Changed domain");
|
||||
for (model in models) {
|
||||
removeIndicators(models[model]);
|
||||
}
|
||||
oldHost = location.hostname;
|
||||
locationChanged = true;
|
||||
}
|
||||
|
||||
if (MyAvatar.position.x != avatarOldPosition.x &&
|
||||
MyAvatar.position.y != avatarOldPosition.y &&
|
||||
MyAvatar.position.z != avatarOldPosition.z) {
|
||||
if (MyAvatar.position.x != avatarOldPosition.x ||
|
||||
MyAvatar.position.y != avatarOldPosition.y ||
|
||||
MyAvatar.position.z != avatarOldPosition.z ||
|
||||
locationChanged) {
|
||||
avatarOldPosition = MyAvatar.position;
|
||||
|
||||
var SEARCH_RADIUS = 10;
|
||||
var SEARCH_RADIUS = 50;
|
||||
var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS);
|
||||
// Let's remove indicator that got out of radius
|
||||
for (model in models) {
|
||||
|
@ -274,7 +285,10 @@ function update(deltaTime){
|
|||
for (var i = 0; i < foundModels.length; ++i) {
|
||||
var model = foundModels[i];
|
||||
if (typeof(models[model.id]) == "undefined") {
|
||||
addIndicators(model);
|
||||
model.properties = Models.getModelProperties(model);
|
||||
if (Vec3.distance(model.properties.position, MyAvatar.position) < SEARCH_RADIUS) {
|
||||
addIndicators(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,9 +297,9 @@ function update(deltaTime){
|
|||
}
|
||||
}
|
||||
}
|
||||
var oldHost = location.hostname;
|
||||
|
||||
function addIndicators(modelID) {
|
||||
modelID.properties = Models.getModelProperties(modelID);
|
||||
if (modelID.properties.sittingPoints.length > 0) {
|
||||
for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) {
|
||||
modelID.properties.sittingPoints[i].indicator = new SeatIndicator(modelID.properties, i);
|
||||
|
|
|
@ -18,7 +18,7 @@ void main(void) {
|
|||
// standard diffuse lighting
|
||||
gl_FrontColor = vec4(gl_Color.rgb * (gl_LightModel.ambient.rgb + gl_LightSource[0].ambient.rgb +
|
||||
gl_LightSource[0].diffuse.rgb * max(0.0, dot(gl_NormalMatrix * gl_Normal, gl_LightSource[0].position.xyz))),
|
||||
gl_Color.a);
|
||||
0.0);
|
||||
|
||||
// extract the first three components of the vertex for position
|
||||
gl_Position = gl_ModelViewProjectionMatrix * vec4(gl_Vertex.xyz, 1.0);
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
//
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QReadLocker>
|
||||
#include <QWriteLocker>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
@ -23,183 +25,139 @@
|
|||
#include "MetavoxelSystem.h"
|
||||
#include "renderer/Model.h"
|
||||
|
||||
REGISTER_META_OBJECT(PointMetavoxelRendererImplementation)
|
||||
REGISTER_META_OBJECT(SphereRenderer)
|
||||
REGISTER_META_OBJECT(StaticModelRenderer)
|
||||
|
||||
ProgramObject MetavoxelSystem::_program;
|
||||
int MetavoxelSystem::_pointScaleLocation;
|
||||
|
||||
MetavoxelSystem::MetavoxelSystem() :
|
||||
_simulateVisitor(_points),
|
||||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
static int bufferPointVectorMetaTypeId = qRegisterMetaType<BufferPointVector>();
|
||||
|
||||
void MetavoxelSystem::init() {
|
||||
MetavoxelClientManager::init();
|
||||
|
||||
if (!_program.isLinked()) {
|
||||
_program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert");
|
||||
_program.link();
|
||||
|
||||
_pointScaleLocation = _program.uniformLocation("pointScale");
|
||||
}
|
||||
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_buffer.create();
|
||||
PointMetavoxelRendererImplementation::init();
|
||||
_pointBufferAttribute = AttributeRegistry::getInstance()->registerAttribute(new PointBufferAttribute());
|
||||
}
|
||||
|
||||
MetavoxelLOD MetavoxelSystem::getLOD() const {
|
||||
// the LOD threshold is temporarily tied to the avatar LOD parameter
|
||||
const float BASE_LOD_THRESHOLD = 0.01f;
|
||||
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(),
|
||||
BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier());
|
||||
MetavoxelLOD MetavoxelSystem::getLOD() {
|
||||
QReadLocker locker(&_lodLock);
|
||||
return _lod;
|
||||
}
|
||||
|
||||
class SpannerSimulateVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
SpannerSimulateVisitor(float deltaTime);
|
||||
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
private:
|
||||
|
||||
float _deltaTime;
|
||||
};
|
||||
|
||||
SpannerSimulateVisitor::SpannerSimulateVisitor(float deltaTime) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), QVector<AttributePointer>(),
|
||||
Application::getInstance()->getMetavoxels()->getLOD()),
|
||||
_deltaTime(deltaTime) {
|
||||
}
|
||||
|
||||
bool SpannerSimulateVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->simulate(_deltaTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
// update the clients
|
||||
_points.clear();
|
||||
_simulateVisitor.setDeltaTime(deltaTime);
|
||||
_simulateVisitor.setOrder(-Application::getInstance()->getViewFrustum()->getDirection());
|
||||
update();
|
||||
|
||||
_buffer.bind();
|
||||
int bytes = _points.size() * sizeof(Point);
|
||||
if (_buffer.size() < bytes) {
|
||||
_buffer.allocate(_points.constData(), bytes);
|
||||
} else {
|
||||
_buffer.write(0, _points.constData(), bytes);
|
||||
// update the lod
|
||||
{
|
||||
// the LOD threshold is temporarily tied to the avatar LOD parameter
|
||||
QWriteLocker locker(&_lodLock);
|
||||
const float BASE_LOD_THRESHOLD = 0.01f;
|
||||
_lod = MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(),
|
||||
BASE_LOD_THRESHOLD * Menu::getInstance()->getAvatarLODDistanceMultiplier());
|
||||
}
|
||||
_buffer.release();
|
||||
|
||||
SpannerSimulateVisitor spannerSimulateVisitor(deltaTime);
|
||||
guide(spannerSimulateVisitor);
|
||||
}
|
||||
|
||||
class SpannerRenderVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
SpannerRenderVisitor();
|
||||
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
};
|
||||
|
||||
SpannerRenderVisitor::SpannerRenderVisitor() :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), QVector<AttributePointer>(),
|
||||
Application::getInstance()->getMetavoxels()->getLOD(),
|
||||
encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())) {
|
||||
}
|
||||
|
||||
bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
class RenderVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
RenderVisitor(const MetavoxelLOD& lod);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
};
|
||||
|
||||
RenderVisitor::RenderVisitor(const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getRendererAttribute(),
|
||||
QVector<AttributePointer>(), lod) {
|
||||
}
|
||||
|
||||
int RenderVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.isLeaf) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
static_cast<MetavoxelRenderer*>(info.inputValues.at(0).getInlineValue<
|
||||
SharedObjectPointer>().data())->getImplementation()->render(*_data, info, _lod);
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
void MetavoxelSystem::render() {
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
const int VIEWPORT_WIDTH_INDEX = 2;
|
||||
const int VIEWPORT_HEIGHT_INDEX = 3;
|
||||
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
|
||||
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
|
||||
float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight);
|
||||
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
|
||||
Application::getInstance()->getViewFrustum()->getNearTopRight());
|
||||
|
||||
_program.bind();
|
||||
_program.setUniformValue(_pointScaleLocation, viewportDiagonal *
|
||||
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
|
||||
|
||||
_buffer.bind();
|
||||
|
||||
Point* pt = 0;
|
||||
glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color);
|
||||
glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDrawArrays(GL_POINTS, 0, _points.size());
|
||||
|
||||
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
_buffer.release();
|
||||
|
||||
_program.release();
|
||||
RenderVisitor renderVisitor(getLOD());
|
||||
guideToAugmented(renderVisitor);
|
||||
|
||||
SpannerRenderVisitor spannerRenderVisitor;
|
||||
guide(spannerRenderVisitor);
|
||||
}
|
||||
|
||||
MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) {
|
||||
return new MetavoxelSystemClient(node, _updater);
|
||||
}
|
||||
|
||||
void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor) {
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelSystemClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->guide(_renderVisitor);
|
||||
client->getAugmentedData().guide(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) {
|
||||
return new MetavoxelSystemClient(node, this);
|
||||
MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelUpdater* updater) :
|
||||
MetavoxelClient(node, updater) {
|
||||
}
|
||||
|
||||
void MetavoxelSystem::updateClient(MetavoxelClient* client) {
|
||||
MetavoxelClientManager::updateClient(client);
|
||||
client->guide(_simulateVisitor);
|
||||
void MetavoxelSystemClient::setAugmentedData(const MetavoxelData& data) {
|
||||
QWriteLocker locker(&_augmentedDataLock);
|
||||
_augmentedData = data;
|
||||
}
|
||||
|
||||
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerNormalAttribute()),
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->simulate(_deltaTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
int MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) {
|
||||
SpannerVisitor::visit(info);
|
||||
|
||||
if (!info.isLeaf) {
|
||||
return _order;
|
||||
}
|
||||
QRgb color = info.inputValues.at(0).getInlineValue<QRgb>();
|
||||
QRgb normal = info.inputValues.at(1).getInlineValue<QRgb>();
|
||||
quint8 alpha = qAlpha(color);
|
||||
if (!info.isLODLeaf) {
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
} else {
|
||||
QRgb spannerColor = info.inputValues.at(2).getInlineValue<QRgb>();
|
||||
QRgb spannerNormal = info.inputValues.at(3).getInlineValue<QRgb>();
|
||||
quint8 spannerAlpha = qAlpha(spannerColor);
|
||||
if (spannerAlpha > 0) {
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
||||
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
||||
_points.append(point);
|
||||
|
||||
} else {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
||||
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
} else if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
MetavoxelSystem::RenderVisitor::RenderVisitor() :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannerMaskAttribute()) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system) :
|
||||
MetavoxelClient(node, system) {
|
||||
MetavoxelData MetavoxelSystemClient::getAugmentedData() {
|
||||
QReadLocker locker(&_augmentedDataLock);
|
||||
return _augmentedData;
|
||||
}
|
||||
|
||||
int MetavoxelSystemClient::parseData(const QByteArray& packet) {
|
||||
|
@ -209,11 +167,284 @@ int MetavoxelSystemClient::parseData(const QByteArray& packet) {
|
|||
return packet.size();
|
||||
}
|
||||
|
||||
class AugmentVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
AugmentVisitor(const MetavoxelLOD& lod, const MetavoxelData& previousData);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const MetavoxelData& _previousData;
|
||||
};
|
||||
|
||||
AugmentVisitor::AugmentVisitor(const MetavoxelLOD& lod, const MetavoxelData& previousData) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getRendererAttribute(),
|
||||
QVector<AttributePointer>(), lod),
|
||||
_previousData(previousData) {
|
||||
}
|
||||
|
||||
int AugmentVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.isLeaf) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
static_cast<MetavoxelRenderer*>(info.inputValues.at(0).getInlineValue<
|
||||
SharedObjectPointer>().data())->getImplementation()->augment(*_data, _previousData, info, _lod);
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
class Augmenter : public QRunnable {
|
||||
public:
|
||||
|
||||
Augmenter(const SharedNodePointer& node, const MetavoxelData& data,
|
||||
const MetavoxelData& previousData, const MetavoxelLOD& lod);
|
||||
|
||||
virtual void run();
|
||||
|
||||
private:
|
||||
|
||||
QWeakPointer<Node> _node;
|
||||
MetavoxelData _data;
|
||||
MetavoxelData _previousData;
|
||||
MetavoxelLOD _lod;
|
||||
};
|
||||
|
||||
Augmenter::Augmenter(const SharedNodePointer& node, const MetavoxelData& data,
|
||||
const MetavoxelData& previousData, const MetavoxelLOD& lod) :
|
||||
_node(node),
|
||||
_data(data),
|
||||
_previousData(previousData),
|
||||
_lod(lod) {
|
||||
}
|
||||
|
||||
void Augmenter::run() {
|
||||
SharedNodePointer node = _node;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
AugmentVisitor visitor(_lod, _previousData);
|
||||
_data.guide(visitor);
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
QMetaObject::invokeMethod(node->getLinkedData(), "setAugmentedData", Q_ARG(const MetavoxelData&, _data));
|
||||
}
|
||||
|
||||
void MetavoxelSystemClient::dataChanged(const MetavoxelData& oldData) {
|
||||
MetavoxelClient::dataChanged(oldData);
|
||||
QThreadPool::globalInstance()->start(new Augmenter(_node, _data, getAugmentedData(), _remoteDataLOD));
|
||||
}
|
||||
|
||||
void MetavoxelSystemClient::sendDatagram(const QByteArray& data) {
|
||||
NodeList::getInstance()->writeDatagram(data, _node);
|
||||
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::METAVOXELS).updateValue(data.size());
|
||||
}
|
||||
|
||||
PointBuffer::PointBuffer(const BufferPointVector& points) :
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
void PointBuffer::render() {
|
||||
// initialize buffer, etc. on first render
|
||||
if (!_buffer.isCreated()) {
|
||||
_buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
_buffer.create();
|
||||
_buffer.bind();
|
||||
_pointCount = _points.size();
|
||||
_buffer.allocate(_points.constData(), _pointCount * sizeof(BufferPoint));
|
||||
_points.clear();
|
||||
_buffer.release();
|
||||
}
|
||||
if (_pointCount == 0) {
|
||||
return;
|
||||
}
|
||||
_buffer.bind();
|
||||
|
||||
BufferPoint* point = 0;
|
||||
glVertexPointer(4, GL_FLOAT, sizeof(BufferPoint), &point->vertex);
|
||||
glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(BufferPoint), &point->color);
|
||||
glNormalPointer(GL_BYTE, sizeof(BufferPoint), &point->normal);
|
||||
|
||||
glDrawArrays(GL_POINTS, 0, _pointCount);
|
||||
|
||||
_buffer.release();
|
||||
}
|
||||
|
||||
PointBufferAttribute::PointBufferAttribute() :
|
||||
InlineAttribute<PointBufferPointer>("pointBuffer") {
|
||||
}
|
||||
|
||||
bool PointBufferAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
PointBufferPointer firstChild = decodeInline<PointBufferPointer>(children[0]);
|
||||
for (int i = 1; i < MERGE_COUNT; i++) {
|
||||
if (firstChild != decodeInline<PointBufferPointer>(children[i])) {
|
||||
*(PointBufferPointer*)&parent = _defaultValue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*(PointBufferPointer*)&parent = firstChild;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PointMetavoxelRendererImplementation::init() {
|
||||
if (!_program.isLinked()) {
|
||||
_program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert");
|
||||
_program.link();
|
||||
|
||||
_program.bind();
|
||||
_pointScaleLocation = _program.uniformLocation("pointScale");
|
||||
_program.release();
|
||||
}
|
||||
}
|
||||
|
||||
PointMetavoxelRendererImplementation::PointMetavoxelRendererImplementation() {
|
||||
}
|
||||
|
||||
class PointAugmentVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
PointAugmentVisitor(const MetavoxelLOD& lod);
|
||||
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
virtual bool postVisit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
BufferPointVector _points;
|
||||
float _pointLeafSize;
|
||||
};
|
||||
|
||||
PointAugmentVisitor::PointAugmentVisitor(const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute(), QVector<AttributePointer>() <<
|
||||
Application::getInstance()->getMetavoxels()->getPointBufferAttribute(), lod) {
|
||||
}
|
||||
|
||||
const int ALPHA_RENDER_THRESHOLD = 0;
|
||||
|
||||
void PointAugmentVisitor::prepare(MetavoxelData* data) {
|
||||
MetavoxelVisitor::prepare(data);
|
||||
const float MAX_POINT_LEAF_SIZE = 64.0f;
|
||||
_pointLeafSize = qMin(data->getSize(), MAX_POINT_LEAF_SIZE);
|
||||
}
|
||||
|
||||
int PointAugmentVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.isLeaf) {
|
||||
return (info.size > _pointLeafSize) ? DEFAULT_ORDER : (DEFAULT_ORDER | ALL_NODES_REST);
|
||||
}
|
||||
QRgb color = info.inputValues.at(0).getInlineValue<QRgb>();
|
||||
quint8 alpha = qAlpha(color);
|
||||
if (alpha > ALPHA_RENDER_THRESHOLD) {
|
||||
QRgb normal = info.inputValues.at(1).getInlineValue<QRgb>();
|
||||
BufferPoint point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)) },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
if (info.size >= _pointLeafSize) {
|
||||
BufferPointVector swapPoints;
|
||||
_points.swap(swapPoints);
|
||||
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(PointBufferPointer(
|
||||
new PointBuffer(swapPoints))));
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
bool PointAugmentVisitor::postVisit(MetavoxelInfo& info) {
|
||||
if (info.size != _pointLeafSize) {
|
||||
return false;
|
||||
}
|
||||
BufferPointVector swapPoints;
|
||||
_points.swap(swapPoints);
|
||||
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(PointBufferPointer(
|
||||
new PointBuffer(swapPoints))));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PointMetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous,
|
||||
MetavoxelInfo& info, const MetavoxelLOD& lod) {
|
||||
// copy the previous buffers
|
||||
MetavoxelData expandedPrevious = previous;
|
||||
while (expandedPrevious.getSize() < data.getSize()) {
|
||||
expandedPrevious.expand();
|
||||
}
|
||||
const AttributePointer& pointBufferAttribute = Application::getInstance()->getMetavoxels()->getPointBufferAttribute();
|
||||
MetavoxelNode* root = expandedPrevious.getRoot(pointBufferAttribute);
|
||||
if (root) {
|
||||
data.setRoot(pointBufferAttribute, root);
|
||||
root->incrementReferenceCount();
|
||||
}
|
||||
|
||||
PointAugmentVisitor visitor(lod);
|
||||
data.guideToDifferent(expandedPrevious, visitor);
|
||||
}
|
||||
|
||||
class PointRenderVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
PointRenderVisitor(const MetavoxelLOD& lod);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
int _order;
|
||||
};
|
||||
|
||||
PointRenderVisitor::PointRenderVisitor(const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << Application::getInstance()->getMetavoxels()->getPointBufferAttribute(),
|
||||
QVector<AttributePointer>(), lod),
|
||||
_order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())) {
|
||||
}
|
||||
|
||||
int PointRenderVisitor::visit(MetavoxelInfo& info) {
|
||||
PointBufferPointer buffer = info.inputValues.at(0).getInlineValue<PointBufferPointer>();
|
||||
if (buffer) {
|
||||
buffer->render();
|
||||
}
|
||||
return info.isLeaf ? STOP_RECURSION : _order;
|
||||
}
|
||||
|
||||
void PointMetavoxelRendererImplementation::render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod) {
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
const int VIEWPORT_WIDTH_INDEX = 2;
|
||||
const int VIEWPORT_HEIGHT_INDEX = 3;
|
||||
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
|
||||
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
|
||||
float viewportDiagonal = sqrtf(viewportWidth * viewportWidth + viewportHeight * viewportHeight);
|
||||
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
|
||||
Application::getInstance()->getViewFrustum()->getNearTopRight());
|
||||
|
||||
_program.bind();
|
||||
_program.setUniformValue(_pointScaleLocation, viewportDiagonal *
|
||||
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
PointRenderVisitor visitor(lod);
|
||||
data.guide(visitor);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
_program.release();
|
||||
}
|
||||
|
||||
ProgramObject PointMetavoxelRendererImplementation::_program;
|
||||
int PointMetavoxelRendererImplementation::_pointScaleLocation;
|
||||
|
||||
static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
|
||||
GLdouble coefficients[] = { x, y, z, w };
|
||||
glClipPlane(plane, coefficients);
|
||||
|
@ -251,7 +482,7 @@ void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum
|
|||
return;
|
||||
}
|
||||
// slight performance optimization: don't render if clip bounds are entirely within sphere
|
||||
Sphere* sphere = static_cast<Sphere*>(parent());
|
||||
Sphere* sphere = static_cast<Sphere*>(_spanner);
|
||||
Box clipBox(clipMinimum, clipMinimum + glm::vec3(clipSize, clipSize, clipSize));
|
||||
for (int i = 0; i < Box::VERTEX_COUNT; i++) {
|
||||
const float CLIP_PROPORTION = 0.95f;
|
||||
|
@ -263,7 +494,7 @@ void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum
|
|||
}
|
||||
|
||||
void SphereRenderer::renderUnclipped(float alpha, Mode mode) {
|
||||
Sphere* sphere = static_cast<Sphere*>(parent());
|
||||
Sphere* sphere = static_cast<Sphere*>(_spanner);
|
||||
const QColor& color = sphere->getColor();
|
||||
glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha);
|
||||
|
||||
|
@ -284,6 +515,8 @@ StaticModelRenderer::StaticModelRenderer() :
|
|||
}
|
||||
|
||||
void StaticModelRenderer::init(Spanner* spanner) {
|
||||
SpannerRenderer::init(spanner);
|
||||
|
||||
_model->init();
|
||||
|
||||
StaticModel* staticModel = static_cast<StaticModel*>(spanner);
|
||||
|
@ -305,7 +538,7 @@ void StaticModelRenderer::simulate(float deltaTime) {
|
|||
const Extents& extents = _model->getGeometry()->getFBXGeometry().meshExtents;
|
||||
bounds = Box(extents.minimum, extents.maximum);
|
||||
}
|
||||
static_cast<StaticModel*>(parent())->setBounds(glm::translate(_model->getTranslation()) *
|
||||
static_cast<StaticModel*>(_spanner)->setBounds(glm::translate(_model->getTranslation()) *
|
||||
glm::mat4_cast(_model->getRotation()) * glm::scale(_model->getScale()) * bounds);
|
||||
_model->simulate(deltaTime);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QList>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
@ -30,11 +31,11 @@ class MetavoxelSystem : public MetavoxelClientManager {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelSystem();
|
||||
|
||||
virtual void init();
|
||||
|
||||
virtual MetavoxelLOD getLOD() const;
|
||||
virtual MetavoxelLOD getLOD();
|
||||
|
||||
const AttributePointer& getPointBufferAttribute() { return _pointBufferAttribute; }
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
@ -42,59 +43,100 @@ public:
|
|||
protected:
|
||||
|
||||
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
|
||||
virtual void updateClient(MetavoxelClient* client);
|
||||
|
||||
private:
|
||||
|
||||
class Point {
|
||||
public:
|
||||
glm::vec4 vertex;
|
||||
quint8 color[4];
|
||||
quint8 normal[3];
|
||||
};
|
||||
void guideToAugmented(MetavoxelVisitor& visitor);
|
||||
|
||||
class SimulateVisitor : public SpannerVisitor {
|
||||
public:
|
||||
SimulateVisitor(QVector<Point>& points);
|
||||
void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; }
|
||||
void setOrder(const glm::vec3& direction) { _order = encodeOrder(direction); }
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
AttributePointer _pointBufferAttribute;
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
float _deltaTime;
|
||||
int _order;
|
||||
};
|
||||
|
||||
class RenderVisitor : public SpannerVisitor {
|
||||
public:
|
||||
RenderVisitor();
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QVector<Point> _points;
|
||||
SimulateVisitor _simulateVisitor;
|
||||
RenderVisitor _renderVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
MetavoxelLOD _lod;
|
||||
QReadWriteLock _lodLock;
|
||||
};
|
||||
|
||||
/// Describes contents of a point in a point buffer.
|
||||
class BufferPoint {
|
||||
public:
|
||||
glm::vec4 vertex;
|
||||
quint8 color[3];
|
||||
quint8 normal[3];
|
||||
};
|
||||
|
||||
typedef QVector<BufferPoint> BufferPointVector;
|
||||
|
||||
Q_DECLARE_METATYPE(BufferPointVector)
|
||||
|
||||
/// A client session associated with a single server.
|
||||
class MetavoxelSystemClient : public MetavoxelClient {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system);
|
||||
MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelUpdater* updater);
|
||||
|
||||
Q_INVOKABLE void setAugmentedData(const MetavoxelData& data);
|
||||
|
||||
/// Returns a copy of the augmented data. This function is thread-safe.
|
||||
MetavoxelData getAugmentedData();
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void dataChanged(const MetavoxelData& oldData);
|
||||
virtual void sendDatagram(const QByteArray& data);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelData _augmentedData;
|
||||
QReadWriteLock _augmentedDataLock;
|
||||
};
|
||||
|
||||
/// Contains the information necessary to render a group of points.
|
||||
class PointBuffer : public QSharedData {
|
||||
public:
|
||||
|
||||
PointBuffer(const BufferPointVector& points);
|
||||
|
||||
void render();
|
||||
|
||||
private:
|
||||
|
||||
BufferPointVector _points;
|
||||
QOpenGLBuffer _buffer;
|
||||
int _pointCount;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<PointBuffer> PointBufferPointer;
|
||||
|
||||
/// A client-side attribute that stores point buffers.
|
||||
class PointBufferAttribute : public InlineAttribute<PointBufferPointer> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE PointBufferAttribute();
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
};
|
||||
|
||||
/// Renders metavoxels as points.
|
||||
class PointMetavoxelRendererImplementation : public MetavoxelRendererImplementation {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
static void init();
|
||||
|
||||
Q_INVOKABLE PointMetavoxelRendererImplementation();
|
||||
|
||||
virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
|
||||
private:
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
};
|
||||
|
||||
/// Base class for spanner renderers; provides clipping.
|
||||
|
|
|
@ -48,7 +48,13 @@ Stats::Stats():
|
|||
_pingStatsWidth(STATS_PING_MIN_WIDTH),
|
||||
_geoStatsWidth(STATS_GEO_MIN_WIDTH),
|
||||
_voxelStatsWidth(STATS_VOXEL_MIN_WIDTH),
|
||||
_lastHorizontalOffset(0)
|
||||
_lastHorizontalOffset(0),
|
||||
_metavoxelInternal(0),
|
||||
_metavoxelLeaves(0),
|
||||
_metavoxelSendProgress(0),
|
||||
_metavoxelSendTotal(0),
|
||||
_metavoxelReceiveProgress(0),
|
||||
_metavoxelReceiveTotal(0)
|
||||
{
|
||||
QGLWidget* glWidget = Application::getInstance()->getGLWidget();
|
||||
resetWidth(glWidget->width(), 0);
|
||||
|
@ -364,36 +370,26 @@ void Stats::display(
|
|||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color);
|
||||
|
||||
int internal = 0, leaves = 0;
|
||||
int sendProgress = 0, sendTotal = 0;
|
||||
int receiveProgress = 0, receiveTotal = 0;
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->getData().countNodes(internal, leaves, Application::getInstance()->getMetavoxels()->getLOD());
|
||||
client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels()->getUpdater(), "getStats",
|
||||
Q_ARG(QObject*, this), Q_ARG(const QByteArray&, "setMetavoxelStats"));
|
||||
|
||||
stringstream nodes;
|
||||
nodes << "Metavoxels: " << (internal + leaves);
|
||||
nodes << "Metavoxels: " << (_metavoxelInternal + _metavoxelLeaves);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodes.str().c_str(), color);
|
||||
|
||||
stringstream nodeTypes;
|
||||
nodeTypes << "Internal: " << internal << " Leaves: " << leaves;
|
||||
nodeTypes << "Internal: " << _metavoxelInternal << " Leaves: " << _metavoxelLeaves;
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color);
|
||||
|
||||
if (sendTotal > 0 || receiveTotal > 0) {
|
||||
if (_metavoxelSendTotal > 0 || _metavoxelReceiveTotal > 0) {
|
||||
stringstream reliableStats;
|
||||
if (sendTotal > 0) {
|
||||
reliableStats << "Upload: " << (sendProgress * 100 / sendTotal) << "% ";
|
||||
if (_metavoxelSendTotal > 0) {
|
||||
reliableStats << "Upload: " << (_metavoxelSendProgress * 100 / _metavoxelSendTotal) << "% ";
|
||||
}
|
||||
if (receiveTotal > 0) {
|
||||
reliableStats << "Download: " << (receiveProgress * 100 / receiveTotal) << "%";
|
||||
if (_metavoxelReceiveTotal > 0) {
|
||||
reliableStats << "Download: " << (_metavoxelReceiveProgress * 100 / _metavoxelReceiveTotal) << "%";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color);
|
||||
|
@ -726,3 +722,13 @@ void Stats::display(
|
|||
|
||||
|
||||
}
|
||||
|
||||
void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress,
|
||||
int sendTotal, int receiveProgress, int receiveTotal) {
|
||||
_metavoxelInternal = internal;
|
||||
_metavoxelLeaves = leaves;
|
||||
_metavoxelSendProgress = sendProgress;
|
||||
_metavoxelSendTotal = sendTotal;
|
||||
_metavoxelReceiveProgress = receiveProgress;
|
||||
_metavoxelReceiveTotal = receiveTotal;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,10 @@ public:
|
|||
void resetWidth(int width, int horizontalOffset);
|
||||
void display(const float* color, int horizontalOffset, float fps, int packetsPerSecond, int bytesPerSecond, int voxelPacketsToProcess);
|
||||
bool includeTimingRecord(const QString& name);
|
||||
|
||||
Q_INVOKABLE void setMetavoxelStats(int internal, int leaves, int sendProgress,
|
||||
int sendTotal, int receiveProgress, int receiveTotal);
|
||||
|
||||
private:
|
||||
static Stats* _sharedInstance;
|
||||
|
||||
|
@ -45,6 +49,13 @@ private:
|
|||
int _voxelStatsWidth;
|
||||
|
||||
int _lastHorizontalOffset;
|
||||
|
||||
int _metavoxelInternal;
|
||||
int _metavoxelLeaves;
|
||||
int _metavoxelSendProgress;
|
||||
int _metavoxelSendTotal;
|
||||
int _metavoxelReceiveProgress;
|
||||
int _metavoxelReceiveTotal;
|
||||
};
|
||||
|
||||
#endif // hifi_Stats_h
|
||||
|
|
|
@ -17,10 +17,11 @@ BillboardOverlay::BillboardOverlay()
|
|||
: _fromImage(-1,-1,-1,-1),
|
||||
_scale(1.0f),
|
||||
_isFacingAvatar(true) {
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
void BillboardOverlay::render() {
|
||||
if (!_visible) {
|
||||
if (!_visible || !_isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -85,16 +86,7 @@ void BillboardOverlay::render() {
|
|||
((float)_fromImage.y() + (float)_fromImage.height()) / (float)_size.height());
|
||||
glVertex2f(-x, y);
|
||||
} glEnd();
|
||||
} else {
|
||||
glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
glBegin(GL_QUADS); {
|
||||
glVertex2f(-1.0f, -1.0f);
|
||||
glVertex2f(1.0f, -1.0f);
|
||||
glVertex2f(1.0f, 1.0f);
|
||||
glVertex2f(-1.0f, 1.0f);
|
||||
} glEnd();
|
||||
}
|
||||
|
||||
} glPopMatrix();
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
@ -167,6 +159,7 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
|
|||
}
|
||||
|
||||
void BillboardOverlay::setBillboardURL(const QUrl url) {
|
||||
_isLoaded = false;
|
||||
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, &BillboardOverlay::replyFinished);
|
||||
}
|
||||
|
@ -175,4 +168,5 @@ void BillboardOverlay::replyFinished() {
|
|||
// replace our byte array with the downloaded data
|
||||
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
|
||||
_billboard = reply->readAll();
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ ImageOverlay::ImageOverlay() :
|
|||
_textureBound(false),
|
||||
_wantClipFromImage(false)
|
||||
{
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
ImageOverlay::~ImageOverlay() {
|
||||
|
@ -35,6 +36,7 @@ ImageOverlay::~ImageOverlay() {
|
|||
|
||||
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
|
||||
void ImageOverlay::setImageURL(const QUrl& url) {
|
||||
_isLoaded = false;
|
||||
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, &ImageOverlay::replyFinished);
|
||||
|
@ -47,10 +49,11 @@ void ImageOverlay::replyFinished() {
|
|||
QByteArray rawData = reply->readAll();
|
||||
_textureImage.loadFromData(rawData);
|
||||
_renderImage = true;
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
||||
void ImageOverlay::render() {
|
||||
if (!_visible) {
|
||||
if (!_visible || !_isLoaded) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
if (_renderImage && !_textureBound) {
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
ModelOverlay::ModelOverlay()
|
||||
: _model(),
|
||||
_scale(1.0f),
|
||||
_updateModel(false) {
|
||||
_updateModel(false)
|
||||
{
|
||||
_model.init();
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
void ModelOverlay::update(float deltatime) {
|
||||
|
@ -32,6 +34,7 @@ void ModelOverlay::update(float deltatime) {
|
|||
} else {
|
||||
_model.simulate(deltatime);
|
||||
}
|
||||
_isLoaded = _model.isActive();
|
||||
}
|
||||
|
||||
void ModelOverlay::render() {
|
||||
|
@ -90,6 +93,7 @@ void ModelOverlay::setProperties(const QScriptValue &properties) {
|
|||
if (urlValue.isValid()) {
|
||||
_url = urlValue.toVariant().toString();
|
||||
_updateModel = true;
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
QScriptValue scaleValue = properties.property("scale");
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
Overlay::Overlay() :
|
||||
_parent(NULL),
|
||||
_isLoaded(true),
|
||||
_alpha(DEFAULT_ALPHA),
|
||||
_color(DEFAULT_OVERLAY_COLOR),
|
||||
_visible(true),
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
virtual void render() = 0;
|
||||
|
||||
// getters
|
||||
bool isLoaded() { return _isLoaded; }
|
||||
bool getVisible() const { return _visible; }
|
||||
const xColor& getColor() const { return _color; }
|
||||
float getAlpha() const { return _alpha; }
|
||||
|
@ -55,6 +56,7 @@ public:
|
|||
|
||||
protected:
|
||||
QGLWidget* _parent;
|
||||
bool _isLoaded;
|
||||
float _alpha;
|
||||
xColor _color;
|
||||
bool _visible; // should the overlay be drawn at all
|
||||
|
|
|
@ -227,11 +227,23 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
|||
i.previous();
|
||||
unsigned int thisID = i.key();
|
||||
Overlay2D* thisOverlay = static_cast<Overlay2D*>(i.value());
|
||||
if (thisOverlay->getVisible() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
|
||||
if (thisOverlay->getVisible() && thisOverlay->isLoaded() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
|
||||
return thisID;
|
||||
}
|
||||
}
|
||||
return 0; // not found
|
||||
}
|
||||
|
||||
bool Overlays::isLoaded(unsigned int id) {
|
||||
QReadLocker lock(&_lock);
|
||||
Overlay* overlay = _overlays2D.value(id);
|
||||
if (!overlay) {
|
||||
_overlays3D.value(id);
|
||||
}
|
||||
if (!overlay) {
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
return overlay->isLoaded();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ public slots:
|
|||
|
||||
/// returns the top most overlay at the screen point, or 0 if not overlay at that point
|
||||
unsigned int getOverlayAtPoint(const glm::vec2& point);
|
||||
|
||||
/// returns whether the overlay's assets are loaded or not
|
||||
bool isLoaded(unsigned int id);
|
||||
|
||||
private:
|
||||
QMap<unsigned int, Overlay*> _overlays2D;
|
||||
|
|
|
@ -35,7 +35,9 @@ AttributeRegistry* AttributeRegistry::getInstance() {
|
|||
|
||||
AttributeRegistry::AttributeRegistry() :
|
||||
_guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject,
|
||||
SharedObjectPointer(new DefaultMetavoxelGuide())))),
|
||||
new DefaultMetavoxelGuide()))),
|
||||
_rendererAttribute(registerAttribute(new SharedObjectAttribute("renderer", &MetavoxelRenderer::staticMetaObject,
|
||||
new PointMetavoxelRenderer()))),
|
||||
_spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))),
|
||||
_colorAttribute(registerAttribute(new QRgbAttribute("color"))),
|
||||
_normalAttribute(registerAttribute(new PackedNormalAttribute("normal"))),
|
||||
|
@ -197,7 +199,7 @@ MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const
|
|||
}
|
||||
|
||||
void Attribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
data.createRoot(state.attribute)->read(state);
|
||||
data.createRoot(state.base.attribute)->read(state);
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
|
@ -205,7 +207,7 @@ void Attribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamSta
|
|||
}
|
||||
|
||||
void Attribute::readMetavoxelDelta(MetavoxelData& data, const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
data.createRoot(state.attribute)->readDelta(reference, state);
|
||||
data.createRoot(state.base.attribute)->readDelta(reference, state);
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
|
@ -214,10 +216,10 @@ void Attribute::writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNo
|
|||
|
||||
void Attribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
// copy if changed
|
||||
MetavoxelNode* oldRoot = data.getRoot(state.attribute);
|
||||
MetavoxelNode* oldRoot = data.getRoot(state.base.attribute);
|
||||
MetavoxelNode* newRoot = oldRoot->readSubdivision(state);
|
||||
if (newRoot != oldRoot) {
|
||||
data.setRoot(state.attribute, newRoot);
|
||||
data.setRoot(state.base.attribute, newRoot);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,62 +569,62 @@ SpannerSetAttribute::SpannerSetAttribute(const QString& name, const QMetaObject*
|
|||
void SpannerSetAttribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.stream >> object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
data.insert(state.attribute, object);
|
||||
data.insert(state.base.attribute, object);
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.attribute)) {
|
||||
data.createRoot(state.attribute);
|
||||
if (!data.getRoot(state.base.attribute)) {
|
||||
data.createRoot(state.base.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
Spanner::incrementVisit();
|
||||
state.base.visit = Spanner::getAndIncrementNextVisit();
|
||||
root.writeSpanners(state);
|
||||
state.stream << SharedObjectPointer();
|
||||
state.base.stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data,
|
||||
const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.stream >> object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
data.toggle(state.attribute, object);
|
||||
data.toggle(state.base.attribute, object);
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.attribute)) {
|
||||
data.createRoot(state.attribute);
|
||||
if (!data.getRoot(state.base.attribute)) {
|
||||
data.createRoot(state.base.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root,
|
||||
const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
Spanner::incrementVisit();
|
||||
state.base.visit = Spanner::getAndIncrementNextVisit();
|
||||
root.writeSpannerDelta(reference, state);
|
||||
state.stream << SharedObjectPointer();
|
||||
state.base.stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.stream >> object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
data.insert(state.attribute, object);
|
||||
data.insert(state.base.attribute, object);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
Spanner::incrementVisit();
|
||||
state.base.visit = Spanner::getAndIncrementNextVisit();
|
||||
root.writeSpannerSubdivision(state);
|
||||
state.stream << SharedObjectPointer();
|
||||
state.base.stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
|
|
|
@ -73,6 +73,9 @@ public:
|
|||
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
|
||||
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectPointer "renderer" attribute.
|
||||
const AttributePointer& getRendererAttribute() const { return _rendererAttribute; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectSet "spanners" attribute.
|
||||
const AttributePointer& getSpannersAttribute() const { return _spannersAttribute; }
|
||||
|
||||
|
@ -99,6 +102,7 @@ private:
|
|||
QReadWriteLock _attributesLock;
|
||||
|
||||
AttributePointer _guideAttribute;
|
||||
AttributePointer _rendererAttribute;
|
||||
AttributePointer _spannersAttribute;
|
||||
AttributePointer _colorAttribute;
|
||||
AttributePointer _normalAttribute;
|
||||
|
|
|
@ -29,7 +29,7 @@ const float DEFAULT_SLOW_START_THRESHOLD = 1000.0f;
|
|||
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
|
||||
QObject(parent),
|
||||
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
|
||||
_outputStream(_outgoingPacketStream),
|
||||
_outputStream(_outgoingPacketStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_incomingDatagramStream(&_incomingDatagramBuffer),
|
||||
_datagramHeaderSize(datagramHeader.size()),
|
||||
_outgoingPacketNumber(0),
|
||||
|
@ -38,7 +38,7 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject*
|
|||
_outgoingDatagramStream(&_outgoingDatagramBuffer),
|
||||
_incomingPacketNumber(0),
|
||||
_incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
|
||||
_inputStream(_incomingPacketStream),
|
||||
_inputStream(_incomingPacketStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_receivedHighPriorityMessages(0),
|
||||
_maxPacketSize(DEFAULT_MAX_PACKET_SIZE),
|
||||
_packetsPerGroup(1.0f),
|
||||
|
@ -752,7 +752,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
|
|||
_index(index),
|
||||
_output(output),
|
||||
_dataStream(&_buffer),
|
||||
_bitstream(_dataStream),
|
||||
_bitstream(_dataStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_priority(1.0f),
|
||||
_offset(0),
|
||||
_writePosition(0),
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord, PacketRecord* baselineReceiveRecord) :
|
||||
_node(node),
|
||||
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
|
||||
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData), this) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
|
||||
|
@ -49,7 +49,7 @@ void Endpoint::update() {
|
|||
|
||||
int Endpoint::parseData(const QByteArray& packet) {
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(packet);
|
||||
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,25 +9,31 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QReadLocker>
|
||||
#include <QThread>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "MetavoxelClientManager.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
||||
void MetavoxelClientManager::init() {
|
||||
connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
|
||||
MetavoxelClientManager::MetavoxelClientManager() :
|
||||
_updater(new MetavoxelUpdater(this)) {
|
||||
QThread* thread = new QThread(this);
|
||||
_updater->moveToThread(thread);
|
||||
connect(thread, &QThread::finished, _updater, &QObject::deleteLater);
|
||||
thread->start();
|
||||
QMetaObject::invokeMethod(_updater, "start");
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::update() {
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
updateClient(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
MetavoxelClientManager::~MetavoxelClientManager() {
|
||||
_updater->thread()->quit();
|
||||
_updater->thread()->wait();
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::init() {
|
||||
connect(NodeList::getInstance(), &NodeList::nodeAdded, this, &MetavoxelClientManager::maybeAttachClient);
|
||||
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &MetavoxelClientManager::maybeDeleteClient);
|
||||
}
|
||||
|
||||
SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(const glm::vec3& origin,
|
||||
|
@ -40,7 +46,7 @@ SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(cons
|
|||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
float clientDistance;
|
||||
SharedObjectPointer clientSpanner = client->getData().findFirstRaySpannerIntersection(
|
||||
SharedObjectPointer clientSpanner = client->getDataCopy().findFirstRaySpannerIntersection(
|
||||
origin, direction, attribute, clientDistance);
|
||||
if (clientSpanner && clientDistance < closestDistance) {
|
||||
closestSpanner = clientSpanner;
|
||||
|
@ -69,43 +75,113 @@ void MetavoxelClientManager::setSpanner(const SharedObjectPointer& object, bool
|
|||
}
|
||||
|
||||
void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable));
|
||||
return;
|
||||
}
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->applyEdit(edit, reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
QMetaObject::invokeMethod(_updater, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable));
|
||||
}
|
||||
|
||||
MetavoxelLOD MetavoxelClientManager::getLOD() const {
|
||||
MetavoxelLOD MetavoxelClientManager::getLOD() {
|
||||
return MetavoxelLOD();
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::maybeAttachClient(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
node->setLinkedData(createClient(node));
|
||||
MetavoxelClient* client = createClient(node);
|
||||
client->moveToThread(_updater->thread());
|
||||
QMetaObject::invokeMethod(_updater, "addClient", Q_ARG(QObject*, client));
|
||||
node->setLinkedData(client);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::maybeDeleteClient(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
// we assume the node is already locked
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
node->setLinkedData(NULL);
|
||||
client->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelClient* MetavoxelClientManager::createClient(const SharedNodePointer& node) {
|
||||
return new MetavoxelClient(node, this);
|
||||
return new MetavoxelClient(node, _updater);
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::updateClient(MetavoxelClient* client) {
|
||||
client->update();
|
||||
void MetavoxelClientManager::guide(MetavoxelVisitor& visitor) {
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->getDataCopy().guide(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) :
|
||||
MetavoxelUpdater::MetavoxelUpdater(MetavoxelClientManager* clientManager) :
|
||||
_clientManager(clientManager),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, &QTimer::timeout, this, &MetavoxelUpdater::sendUpdates);
|
||||
}
|
||||
|
||||
const int SEND_INTERVAL = 33;
|
||||
|
||||
void MetavoxelUpdater::start() {
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::addClient(QObject* client) {
|
||||
_clients.insert(static_cast<MetavoxelClient*>(client));
|
||||
connect(client, &QObject::destroyed, this, &MetavoxelUpdater::removeClient);
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
// apply to all clients
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->applyEdit(edit, reliable);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::getStats(QObject* receiver, const QByteArray& method) {
|
||||
int internal = 0, leaves = 0;
|
||||
int sendProgress = 0, sendTotal = 0;
|
||||
int receiveProgress = 0, receiveTotal = 0;
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->getData().countNodes(internal, leaves, _lod);
|
||||
client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal);
|
||||
}
|
||||
QMetaObject::invokeMethod(receiver, method.constData(), Q_ARG(int, internal), Q_ARG(int, leaves), Q_ARG(int, sendProgress),
|
||||
Q_ARG(int, sendTotal), Q_ARG(int, receiveProgress), Q_ARG(int, receiveTotal));
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::sendUpdates() {
|
||||
// get the latest LOD from the client manager
|
||||
_lod = _clientManager->getLOD();
|
||||
|
||||
// send updates for all clients
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->update();
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
int elapsed = now - _lastSend;
|
||||
_lastSend = now;
|
||||
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL)));
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::removeClient(QObject* client) {
|
||||
_clients.remove(static_cast<MetavoxelClient*>(client));
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelUpdater* updater) :
|
||||
Endpoint(node, new PacketRecord(), new PacketRecord()),
|
||||
_manager(manager),
|
||||
_updater(updater),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0) {
|
||||
|
||||
|
@ -113,9 +189,9 @@ MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientM
|
|||
SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
}
|
||||
|
||||
void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
|
||||
visitor.setLOD(_manager->getLOD());
|
||||
_data.guide(visitor);
|
||||
MetavoxelData MetavoxelClient::getDataCopy() {
|
||||
QReadLocker locker(&_dataCopyLock);
|
||||
return _dataCopy;
|
||||
}
|
||||
|
||||
void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
|
@ -124,15 +200,25 @@ void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable)
|
|||
|
||||
} else {
|
||||
// apply immediately to local tree
|
||||
MetavoxelData oldData = _data;
|
||||
edit.apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
|
||||
if (_data != oldData) {
|
||||
dataChanged(oldData);
|
||||
}
|
||||
|
||||
// start sending it out
|
||||
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClient::dataChanged(const MetavoxelData& oldData) {
|
||||
// make thread-safe copy
|
||||
QWriteLocker locker(&_dataCopyLock);
|
||||
_dataCopy = _data;
|
||||
}
|
||||
|
||||
void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
|
||||
ClientStateMessage state = { _manager->getLOD() };
|
||||
ClientStateMessage state = { _updater->getLOD() };
|
||||
out << QVariant::fromValue(state);
|
||||
}
|
||||
|
||||
|
@ -152,12 +238,16 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
in.reset();
|
||||
}
|
||||
// copy to local and reapply local edits
|
||||
MetavoxelData oldData = _data;
|
||||
_data = _remoteData;
|
||||
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
|
||||
if (message.data.userType() == MetavoxelEditMessage::Type) {
|
||||
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
}
|
||||
}
|
||||
if (_data != oldData) {
|
||||
dataChanged(oldData);
|
||||
}
|
||||
} else if (userType == MetavoxelDeltaPendingMessage::Type) {
|
||||
// check the id to make sure this is not a delta we've already processed
|
||||
int id = message.value<MetavoxelDeltaPendingMessage>().id;
|
||||
|
@ -176,7 +266,7 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
|
||||
return new PacketRecord(_reliableDeltaChannel ? _reliableDeltaLOD : _manager->getLOD());
|
||||
return new PacketRecord(_reliableDeltaChannel ? _reliableDeltaLOD : _updater->getLOD());
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
|
||||
|
|
|
@ -12,10 +12,14 @@
|
|||
#ifndef hifi_MetavoxelClientManager_h
|
||||
#define hifi_MetavoxelClientManager_h
|
||||
|
||||
#include <QReadWriteLock>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Endpoint.h"
|
||||
|
||||
class MetavoxelClient;
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelUpdater;
|
||||
|
||||
/// Manages the set of connected metavoxel clients.
|
||||
class MetavoxelClientManager : public QObject {
|
||||
|
@ -23,8 +27,12 @@ class MetavoxelClientManager : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelClientManager();
|
||||
virtual ~MetavoxelClientManager();
|
||||
|
||||
virtual void init();
|
||||
void update();
|
||||
|
||||
MetavoxelUpdater* getUpdater() const { return _updater; }
|
||||
|
||||
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const AttributePointer& attribute, float& distance);
|
||||
|
@ -35,16 +43,57 @@ public:
|
|||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
|
||||
|
||||
virtual MetavoxelLOD getLOD() const;
|
||||
/// Returns the current LOD. This must be thread-safe, as it will be called from the updater thread.
|
||||
virtual MetavoxelLOD getLOD();
|
||||
|
||||
private slots:
|
||||
|
||||
void maybeAttachClient(const SharedNodePointer& node);
|
||||
|
||||
void maybeDeleteClient(const SharedNodePointer& node);
|
||||
|
||||
protected:
|
||||
|
||||
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
|
||||
virtual void updateClient(MetavoxelClient* client);
|
||||
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
MetavoxelUpdater* _updater;
|
||||
};
|
||||
|
||||
/// Handles updates in a dedicated thread.
|
||||
class MetavoxelUpdater : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelUpdater(MetavoxelClientManager* clientManager);
|
||||
|
||||
const MetavoxelLOD& getLOD() const { return _lod; }
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
Q_INVOKABLE void addClient(QObject* client);
|
||||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable);
|
||||
|
||||
/// Requests a set of statistics. The receiving method should take six integer arguments: internal node count, leaf count,
|
||||
/// send progress, send total, receive progress, receive total.
|
||||
Q_INVOKABLE void getStats(QObject* receiver, const QByteArray& method);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendUpdates();
|
||||
void removeClient(QObject* client);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelClientManager* _clientManager;
|
||||
QSet<MetavoxelClient*> _clients;
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
};
|
||||
|
||||
/// Base class for metavoxel clients.
|
||||
|
@ -53,25 +102,27 @@ class MetavoxelClient : public Endpoint {
|
|||
|
||||
public:
|
||||
|
||||
MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager);
|
||||
MetavoxelClient(const SharedNodePointer& node, MetavoxelUpdater* updater);
|
||||
|
||||
MetavoxelData& getData() { return _data; }
|
||||
/// Returns a reference to the most recent data. This function is *not* thread-safe.
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
/// Returns a copy of the most recent data. This function *is* thread-safe.
|
||||
MetavoxelData getDataCopy();
|
||||
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void dataChanged(const MetavoxelData& oldData);
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
virtual PacketRecord* maybeCreateReceiveRecord() const;
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelClientManager* _manager;
|
||||
MetavoxelUpdater* _updater;
|
||||
MetavoxelData _data;
|
||||
MetavoxelData _remoteData;
|
||||
MetavoxelLOD _remoteDataLOD;
|
||||
|
@ -79,6 +130,9 @@ private:
|
|||
ReliableChannel* _reliableDeltaChannel;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
int _reliableDeltaID;
|
||||
|
||||
MetavoxelData _dataCopy;
|
||||
QReadWriteLock _dataCopyLock;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelClientManager_h
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <QDateTime>
|
||||
#include <QDebugStateSaver>
|
||||
#include <QScriptEngine>
|
||||
#include <QThread>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
|
@ -24,6 +25,8 @@ REGISTER_META_OBJECT(MetavoxelGuide)
|
|||
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(ScriptedMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(ThrobbingMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(MetavoxelRenderer)
|
||||
REGISTER_META_OBJECT(PointMetavoxelRenderer)
|
||||
REGISTER_META_OBJECT(Spanner)
|
||||
REGISTER_META_OBJECT(Sphere)
|
||||
REGISTER_META_OBJECT(StaticModel)
|
||||
|
@ -80,18 +83,19 @@ Box MetavoxelData::getBounds() const {
|
|||
|
||||
void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
||||
// let the visitor know we're about to begin a tour
|
||||
visitor.prepare();
|
||||
visitor.prepare(this);
|
||||
|
||||
// start with the root values/defaults (plus the guide attribute)
|
||||
const QVector<AttributePointer>& inputs = visitor.getInputs();
|
||||
const QVector<AttributePointer>& outputs = visitor.getOutputs();
|
||||
MetavoxelVisitation firstVisitation = { NULL, visitor, QVector<MetavoxelNode*>(inputs.size() + 1),
|
||||
QVector<MetavoxelNode*>(outputs.size()), { NULL, getMinimum(), _size,
|
||||
QVector<AttributeValue>(inputs.size() + 1), QVector<OwnedAttributeValue>(outputs.size()) } };
|
||||
MetavoxelVisitation& firstVisitation = visitor.acquireVisitation();
|
||||
firstVisitation.info.minimum = getMinimum();
|
||||
firstVisitation.info.size = _size;
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
MetavoxelNode* node = _roots.value(inputs.at(i));
|
||||
const AttributePointer& input = inputs.at(i);
|
||||
MetavoxelNode* node = _roots.value(input);
|
||||
firstVisitation.inputNodes[i] = node;
|
||||
firstVisitation.info.inputValues[i] = node ? node->getAttributeValue(inputs[i]) : inputs[i];
|
||||
firstVisitation.info.inputValues[i] = node ? node->getAttributeValue(input) : input;
|
||||
}
|
||||
AttributePointer guideAttribute = AttributeRegistry::getInstance()->getGuideAttribute();
|
||||
MetavoxelNode* node = _roots.value(guideAttribute);
|
||||
|
@ -119,6 +123,80 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
|||
node->decrementReferenceCount(value.getAttribute());
|
||||
_roots.remove(value.getAttribute());
|
||||
}
|
||||
value = AttributeValue();
|
||||
}
|
||||
visitor.releaseVisitation();
|
||||
}
|
||||
|
||||
void MetavoxelData::guideToDifferent(const MetavoxelData& other, MetavoxelVisitor& visitor) {
|
||||
// if the other data is smaller, we need to expand it to compare
|
||||
const MetavoxelData* expandedOther = &other;
|
||||
if (_size > other._size) {
|
||||
MetavoxelData* expanded = new MetavoxelData(other);
|
||||
while (expanded->_size < _size) {
|
||||
expanded->expand();
|
||||
}
|
||||
expandedOther = expanded;
|
||||
}
|
||||
|
||||
// let the visitor know we're about to begin a tour
|
||||
visitor.prepare(this);
|
||||
|
||||
// start with the root values/defaults (plus the guide attribute)
|
||||
const QVector<AttributePointer>& inputs = visitor.getInputs();
|
||||
const QVector<AttributePointer>& outputs = visitor.getOutputs();
|
||||
MetavoxelVisitation& firstVisitation = visitor.acquireVisitation();
|
||||
firstVisitation.compareNodes.resize(inputs.size() + 1);
|
||||
firstVisitation.info.minimum = getMinimum();
|
||||
firstVisitation.info.size = _size;
|
||||
bool allNodesSame = true;
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
const AttributePointer& input = inputs.at(i);
|
||||
MetavoxelNode* node = _roots.value(input);
|
||||
firstVisitation.inputNodes[i] = node;
|
||||
firstVisitation.info.inputValues[i] = node ? node->getAttributeValue(input) : input;
|
||||
MetavoxelNode* compareNode = expandedOther->_roots.value(input);
|
||||
firstVisitation.compareNodes[i] = compareNode;
|
||||
allNodesSame &= (node == compareNode);
|
||||
}
|
||||
AttributePointer guideAttribute = AttributeRegistry::getInstance()->getGuideAttribute();
|
||||
MetavoxelNode* node = _roots.value(guideAttribute);
|
||||
firstVisitation.inputNodes.last() = node;
|
||||
firstVisitation.info.inputValues.last() = node ? node->getAttributeValue(guideAttribute) : guideAttribute;
|
||||
MetavoxelNode* compareNode = expandedOther->_roots.value(guideAttribute);
|
||||
firstVisitation.compareNodes.last() = compareNode;
|
||||
allNodesSame &= (node == compareNode);
|
||||
if (!allNodesSame) {
|
||||
for (int i = 0; i < outputs.size(); i++) {
|
||||
MetavoxelNode* node = _roots.value(outputs.at(i));
|
||||
firstVisitation.outputNodes[i] = node;
|
||||
}
|
||||
static_cast<MetavoxelGuide*>(firstVisitation.info.inputValues.last().getInlineValue<
|
||||
SharedObjectPointer>().data())->guideToDifferent(firstVisitation);
|
||||
for (int i = 0; i < outputs.size(); i++) {
|
||||
OwnedAttributeValue& value = firstVisitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
// replace the old node with the new
|
||||
MetavoxelNode*& node = _roots[value.getAttribute()];
|
||||
if (node) {
|
||||
node->decrementReferenceCount(value.getAttribute());
|
||||
}
|
||||
node = firstVisitation.outputNodes.at(i);
|
||||
if (node->isLeaf() && value.isDefault()) {
|
||||
// immediately remove the new node if redundant
|
||||
node->decrementReferenceCount(value.getAttribute());
|
||||
_roots.remove(value.getAttribute());
|
||||
}
|
||||
value = AttributeValue();
|
||||
}
|
||||
}
|
||||
visitor.releaseVisitation();
|
||||
|
||||
// delete the expanded other if we had to expand
|
||||
if (expandedOther != &other) {
|
||||
delete expandedOther;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,7 +545,8 @@ void MetavoxelData::read(Bitstream& in, const MetavoxelLOD& lod) {
|
|||
if (!attribute) {
|
||||
break;
|
||||
}
|
||||
MetavoxelStreamState state = { getMinimum(), _size, attribute, in, lod, lod };
|
||||
MetavoxelStreamBase base = { attribute, in, lod, lod };
|
||||
MetavoxelStreamState state = { base, getMinimum(), _size };
|
||||
attribute->readMetavoxelRoot(*this, state);
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +555,8 @@ void MetavoxelData::write(Bitstream& out, const MetavoxelLOD& lod) const {
|
|||
out << _size;
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
out << it.key();
|
||||
MetavoxelStreamState state = { getMinimum(), _size, it.key(), out, lod, lod };
|
||||
MetavoxelStreamBase base = { it.key(), out, lod, lod };
|
||||
MetavoxelStreamState state = { base, getMinimum(), _size };
|
||||
it.key()->writeMetavoxelRoot(*it.value(), state);
|
||||
}
|
||||
out << AttributePointer();
|
||||
|
@ -509,7 +589,8 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, const MetavoxelLOD
|
|||
if (!attribute) {
|
||||
break;
|
||||
}
|
||||
MetavoxelStreamState state = { getMinimum(), _size, attribute, in, lod, referenceLOD };
|
||||
MetavoxelStreamBase base = { attribute, in, lod, referenceLOD };
|
||||
MetavoxelStreamState state = { base, getMinimum(), _size };
|
||||
MetavoxelNode* oldRoot = _roots.value(attribute);
|
||||
if (oldRoot) {
|
||||
bool changed;
|
||||
|
@ -565,7 +646,8 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO
|
|||
// write the added/changed/subdivided roots
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
MetavoxelNode* referenceRoot = expandedReference->_roots.value(it.key());
|
||||
MetavoxelStreamState state = { minimum, _size, it.key(), out, lod, referenceLOD };
|
||||
MetavoxelStreamBase base = { it.key(), out, lod, referenceLOD };
|
||||
MetavoxelStreamState state = { base, minimum, _size };
|
||||
if (it.value() != referenceRoot || becameSubdivided) {
|
||||
out << it.key();
|
||||
if (referenceRoot) {
|
||||
|
@ -695,15 +777,15 @@ template<> void Bitstream::readDelta(MetavoxelData& value, const MetavoxelData&
|
|||
}
|
||||
|
||||
bool MetavoxelStreamState::shouldSubdivide() const {
|
||||
return lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier());
|
||||
return base.lod.shouldSubdivide(minimum, size, base.attribute->getLODThresholdMultiplier());
|
||||
}
|
||||
|
||||
bool MetavoxelStreamState::shouldSubdivideReference() const {
|
||||
return referenceLOD.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier());
|
||||
return base.referenceLOD.shouldSubdivide(minimum, size, base.attribute->getLODThresholdMultiplier());
|
||||
}
|
||||
|
||||
bool MetavoxelStreamState::becameSubdivided() const {
|
||||
return lod.becameSubdivided(minimum, size, referenceLOD, attribute->getLODThresholdMultiplier());
|
||||
return base.lod.becameSubdivided(minimum, size, base.referenceLOD, base.attribute->getLODThresholdMultiplier());
|
||||
}
|
||||
|
||||
void MetavoxelStreamState::setMinimum(const glm::vec3& lastMinimum, int index) {
|
||||
|
@ -777,38 +859,36 @@ bool MetavoxelNode::isLeaf() const {
|
|||
}
|
||||
|
||||
void MetavoxelNode::read(MetavoxelStreamState& state) {
|
||||
clearChildren(state.attribute);
|
||||
clearChildren(state.base.attribute);
|
||||
|
||||
if (!state.shouldSubdivide()) {
|
||||
state.attribute->read(state.stream, _attributeValue, true);
|
||||
state.base.attribute->read(state.base.stream, _attributeValue, true);
|
||||
return;
|
||||
}
|
||||
bool leaf;
|
||||
state.stream >> leaf;
|
||||
state.attribute->read(state.stream, _attributeValue, leaf);
|
||||
state.base.stream >> leaf;
|
||||
state.base.attribute->read(state.base.stream, _attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i] = new MetavoxelNode(state.base.attribute);
|
||||
_children[i]->read(nextState);
|
||||
}
|
||||
mergeChildren(state.attribute, true);
|
||||
mergeChildren(state.base.attribute, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::write(MetavoxelStreamState& state) const {
|
||||
if (!state.shouldSubdivide()) {
|
||||
state.attribute->write(state.stream, _attributeValue, true);
|
||||
state.base.attribute->write(state.base.stream, _attributeValue, true);
|
||||
return;
|
||||
}
|
||||
bool leaf = isLeaf();
|
||||
state.stream << leaf;
|
||||
state.attribute->write(state.stream, _attributeValue, leaf);
|
||||
state.base.stream << leaf;
|
||||
state.base.attribute->write(state.base.stream, _attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i]->write(nextState);
|
||||
|
@ -817,31 +897,30 @@ void MetavoxelNode::write(MetavoxelStreamState& state) const {
|
|||
}
|
||||
|
||||
void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
clearChildren(state.attribute);
|
||||
clearChildren(state.base.attribute);
|
||||
|
||||
if (!state.shouldSubdivide()) {
|
||||
state.attribute->readDelta(state.stream, _attributeValue, reference._attributeValue, true);
|
||||
state.base.attribute->readDelta(state.base.stream, _attributeValue, reference._attributeValue, true);
|
||||
return;
|
||||
}
|
||||
bool leaf;
|
||||
state.stream >> leaf;
|
||||
state.attribute->readDelta(state.stream, _attributeValue, reference._attributeValue, leaf);
|
||||
state.base.stream >> leaf;
|
||||
state.base.attribute->readDelta(state.base.stream, _attributeValue, reference._attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
if (reference.isLeaf() || !state.shouldSubdivideReference()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i] = new MetavoxelNode(state.base.attribute);
|
||||
_children[i]->read(nextState);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
bool changed;
|
||||
state.stream >> changed;
|
||||
state.base.stream >> changed;
|
||||
if (changed) {
|
||||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i] = new MetavoxelNode(state.base.attribute);
|
||||
_children[i]->readDelta(*reference._children[i], nextState);
|
||||
} else {
|
||||
if (nextState.becameSubdivided()) {
|
||||
|
@ -856,21 +935,20 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta
|
|||
}
|
||||
}
|
||||
}
|
||||
mergeChildren(state.attribute, true);
|
||||
mergeChildren(state.base.attribute, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const {
|
||||
if (!state.shouldSubdivide()) {
|
||||
state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, true);
|
||||
state.base.attribute->writeDelta(state.base.stream, _attributeValue, reference._attributeValue, true);
|
||||
return;
|
||||
}
|
||||
bool leaf = isLeaf();
|
||||
state.stream << leaf;
|
||||
state.attribute->writeDelta(state.stream, _attributeValue, reference._attributeValue, leaf);
|
||||
state.base.stream << leaf;
|
||||
state.base.attribute->writeDelta(state.base.stream, _attributeValue, reference._attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
if (reference.isLeaf() || !state.shouldSubdivideReference()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
|
@ -880,12 +958,12 @@ void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamSt
|
|||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (_children[i] == reference._children[i]) {
|
||||
state.stream << false;
|
||||
state.base.stream << false;
|
||||
if (nextState.becameSubdivided()) {
|
||||
_children[i]->writeSubdivision(nextState);
|
||||
}
|
||||
} else {
|
||||
state.stream << true;
|
||||
state.base.stream << true;
|
||||
_children[i]->writeDelta(*reference._children[i], nextState);
|
||||
}
|
||||
}
|
||||
|
@ -896,40 +974,38 @@ void MetavoxelNode::writeDelta(const MetavoxelNode& reference, MetavoxelStreamSt
|
|||
MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) {
|
||||
if (!state.shouldSubdivideReference()) {
|
||||
bool leaf;
|
||||
state.stream >> leaf;
|
||||
state.base.stream >> leaf;
|
||||
if (leaf) {
|
||||
return isLeaf() ? this : new MetavoxelNode(getAttributeValue(state.attribute));
|
||||
return isLeaf() ? this : new MetavoxelNode(getAttributeValue(state.base.attribute));
|
||||
|
||||
} else {
|
||||
MetavoxelNode* newNode = new MetavoxelNode(getAttributeValue(state.attribute));
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelNode* newNode = new MetavoxelNode(getAttributeValue(state.base.attribute));
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
newNode->_children[i] = new MetavoxelNode(state.attribute);
|
||||
newNode->_children[i] = new MetavoxelNode(state.base.attribute);
|
||||
newNode->_children[i]->read(nextState);
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
} else if (!isLeaf()) {
|
||||
MetavoxelNode* node = this;
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
MetavoxelNode* child = _children[i]->readSubdivision(nextState);
|
||||
if (child != _children[i]) {
|
||||
if (node == this) {
|
||||
node = new MetavoxelNode(state.attribute, this);
|
||||
node = new MetavoxelNode(state.base.attribute, this);
|
||||
}
|
||||
node->_children[i] = child;
|
||||
_children[i]->decrementReferenceCount(state.attribute);
|
||||
_children[i]->decrementReferenceCount(state.base.attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node != this) {
|
||||
node->mergeChildren(state.attribute, true);
|
||||
node->mergeChildren(state.base.attribute, true);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
@ -939,18 +1015,16 @@ MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) {
|
|||
void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const {
|
||||
bool leaf = isLeaf();
|
||||
if (!state.shouldSubdivideReference()) {
|
||||
state.stream << leaf;
|
||||
state.base.stream << leaf;
|
||||
if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i]->write(nextState);
|
||||
}
|
||||
}
|
||||
} else if (!leaf) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
if (nextState.becameSubdivided()) {
|
||||
|
@ -962,15 +1036,14 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const {
|
|||
|
||||
void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const {
|
||||
foreach (const SharedObjectPointer& object, decodeInline<SharedObjectSet>(_attributeValue)) {
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited()) {
|
||||
state.stream << object;
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited(state.base.visit)) {
|
||||
state.base.stream << object;
|
||||
}
|
||||
}
|
||||
if (!state.shouldSubdivide() || isLeaf()) {
|
||||
return;
|
||||
}
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
_children[i]->writeSpanners(nextState);
|
||||
|
@ -981,19 +1054,18 @@ void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelS
|
|||
SharedObjectSet oldSet = decodeInline<SharedObjectSet>(reference.getAttributeValue());
|
||||
SharedObjectSet newSet = decodeInline<SharedObjectSet>(_attributeValue);
|
||||
foreach (const SharedObjectPointer& object, oldSet) {
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited() && !newSet.contains(object)) {
|
||||
state.stream << object;
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited(state.base.visit) && !newSet.contains(object)) {
|
||||
state.base.stream << object;
|
||||
}
|
||||
}
|
||||
foreach (const SharedObjectPointer& object, newSet) {
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited() && !oldSet.contains(object)) {
|
||||
state.stream << object;
|
||||
if (static_cast<Spanner*>(object.data())->testAndSetVisited(state.base.visit) && !oldSet.contains(object)) {
|
||||
state.base.stream << object;
|
||||
}
|
||||
}
|
||||
if (isLeaf() || !state.shouldSubdivide()) {
|
||||
if (!reference.isLeaf() && state.shouldSubdivideReference()) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
reference._children[i]->writeSpanners(nextState);
|
||||
|
@ -1001,8 +1073,7 @@ void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelS
|
|||
}
|
||||
return;
|
||||
}
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
if (reference.isLeaf() || !state.shouldSubdivideReference()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
|
@ -1023,8 +1094,7 @@ void MetavoxelNode::writeSpannerDelta(const MetavoxelNode& reference, MetavoxelS
|
|||
|
||||
void MetavoxelNode::writeSpannerSubdivision(MetavoxelStreamState& state) const {
|
||||
if (!isLeaf()) {
|
||||
MetavoxelStreamState nextState = { glm::vec3(), state.size * 0.5f, state.attribute,
|
||||
state.stream, state.lod, state.referenceLOD };
|
||||
MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f };
|
||||
if (!state.shouldSubdivideReference()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
nextState.setMinimum(state.minimum, i);
|
||||
|
@ -1042,7 +1112,7 @@ void MetavoxelNode::writeSpannerSubdivision(MetavoxelStreamState& state) const {
|
|||
}
|
||||
|
||||
void MetavoxelNode::decrementReferenceCount(const AttributePointer& attribute) {
|
||||
if (--_referenceCount == 0) {
|
||||
if (!_referenceCount.deref()) {
|
||||
destroy(attribute);
|
||||
delete this;
|
||||
}
|
||||
|
@ -1119,6 +1189,15 @@ void MetavoxelNode::countNodes(const AttributePointer& attribute, const glm::vec
|
|||
}
|
||||
}
|
||||
|
||||
MetavoxelInfo::MetavoxelInfo(MetavoxelInfo* parentInfo, int inputValuesSize, int outputValuesSize) :
|
||||
parentInfo(parentInfo),
|
||||
inputValues(inputValuesSize),
|
||||
outputValues(outputValuesSize) {
|
||||
}
|
||||
|
||||
MetavoxelInfo::MetavoxelInfo() {
|
||||
}
|
||||
|
||||
int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth,
|
||||
int fifth, int sixth, int seventh, int eighth) {
|
||||
return first | (second << 3) | (third << 6) | (fourth << 9) |
|
||||
|
@ -1169,13 +1248,16 @@ int MetavoxelVisitor::encodeRandomOrder() {
|
|||
const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7);
|
||||
const int MetavoxelVisitor::STOP_RECURSION = 0;
|
||||
const int MetavoxelVisitor::SHORT_CIRCUIT = -1;
|
||||
const int MetavoxelVisitor::ALL_NODES = 1 << 24;
|
||||
const int MetavoxelVisitor::ALL_NODES_REST = 1 << 25;
|
||||
|
||||
MetavoxelVisitor::MetavoxelVisitor(const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
_inputs(inputs),
|
||||
_outputs(outputs),
|
||||
_lod(lod),
|
||||
_minimumLODThresholdMultiplier(FLT_MAX) {
|
||||
_minimumLODThresholdMultiplier(FLT_MAX),
|
||||
_depth(-1) {
|
||||
|
||||
// find the minimum LOD threshold multiplier over all attributes
|
||||
foreach (const AttributePointer& attribute, _inputs) {
|
||||
|
@ -1189,33 +1271,48 @@ MetavoxelVisitor::MetavoxelVisitor(const QVector<AttributePointer>& inputs,
|
|||
MetavoxelVisitor::~MetavoxelVisitor() {
|
||||
}
|
||||
|
||||
void MetavoxelVisitor::prepare() {
|
||||
// nothing by default
|
||||
void MetavoxelVisitor::prepare(MetavoxelData* data) {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
bool MetavoxelVisitor::postVisit(MetavoxelInfo& info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MetavoxelVisitation& MetavoxelVisitor::acquireVisitation() {
|
||||
if (++_depth >= _visitations.size()) {
|
||||
_visitations.append(MetavoxelVisitation(_depth == 0 ? NULL : &_visitations[_depth - 1],
|
||||
this, _inputs.size() + 1, _outputs.size()));
|
||||
}
|
||||
return _visitations[_depth];
|
||||
}
|
||||
|
||||
SpannerVisitor::SpannerVisitor(const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& spannerMasks,
|
||||
const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs,
|
||||
const MetavoxelLOD& lod, int order) :
|
||||
MetavoxelVisitor(inputs + spannerInputs + spannerMasks, outputs, lod),
|
||||
_spannerInputCount(spannerInputs.size()),
|
||||
_spannerMaskCount(spannerMasks.size()) {
|
||||
_spannerMaskCount(spannerMasks.size()),
|
||||
_order(order) {
|
||||
}
|
||||
|
||||
void SpannerVisitor::prepare() {
|
||||
Spanner::incrementVisit();
|
||||
void SpannerVisitor::prepare(MetavoxelData* data) {
|
||||
MetavoxelVisitor::prepare(data);
|
||||
_visit = Spanner::getAndIncrementNextVisit();
|
||||
}
|
||||
|
||||
int SpannerVisitor::visit(MetavoxelInfo& info) {
|
||||
for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) {
|
||||
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited() &&
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited(_visit) &&
|
||||
!visit(spanner, glm::vec3(), 0.0f)) {
|
||||
return SHORT_CIRCUIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!info.isLeaf) {
|
||||
return DEFAULT_ORDER;
|
||||
return _order;
|
||||
}
|
||||
for (int i = _inputs.size() - _spannerMaskCount; i < _inputs.size(); i++) {
|
||||
float maskValue = info.inputValues.at(i).getInlineValue<float>();
|
||||
|
@ -1259,8 +1356,9 @@ RaySpannerIntersectionVisitor::RaySpannerIntersectionVisitor(const glm::vec3& or
|
|||
_spannerMaskCount(spannerMasks.size()) {
|
||||
}
|
||||
|
||||
void RaySpannerIntersectionVisitor::prepare() {
|
||||
Spanner::incrementVisit();
|
||||
void RaySpannerIntersectionVisitor::prepare(MetavoxelData* data) {
|
||||
MetavoxelVisitor::prepare(data);
|
||||
_visit = Spanner::getAndIncrementNextVisit();
|
||||
}
|
||||
|
||||
class SpannerDistance {
|
||||
|
@ -1278,7 +1376,7 @@ int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) {
|
|||
for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) {
|
||||
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited()) {
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited(_visit)) {
|
||||
SpannerDistance spannerDistance = { spanner };
|
||||
if (spanner->findRayIntersection(_origin, _direction, glm::vec3(), 0.0f, spannerDistance.distance)) {
|
||||
spannerDistances.append(spannerDistance);
|
||||
|
@ -1324,39 +1422,16 @@ int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) {
|
|||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
bool MetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
|
||||
return guide(visitation);
|
||||
}
|
||||
|
||||
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
|
||||
}
|
||||
|
||||
bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
||||
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
|
||||
float lodBase = glm::distance(visitation.visitor.getLOD().position, visitation.info.getCenter()) *
|
||||
visitation.visitor.getLOD().threshold;
|
||||
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor.getMinimumLODThresholdMultiplier());
|
||||
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
|
||||
int encodedOrder = visitation.visitor.visit(visitation.info);
|
||||
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
MetavoxelNode*& node = visitation.outputNodes[i];
|
||||
if (node && node->isLeaf() && value.getAttribute()->equal(value.getValue(), node->getAttributeValue())) {
|
||||
// "set" to same value; disregard
|
||||
value = AttributeValue();
|
||||
} else {
|
||||
node = value.getAttribute()->createMetavoxelNode(value, node);
|
||||
}
|
||||
}
|
||||
if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) {
|
||||
return true;
|
||||
}
|
||||
MetavoxelVisitation nextVisitation = { &visitation, visitation.visitor,
|
||||
QVector<MetavoxelNode*>(visitation.inputNodes.size()), QVector<MetavoxelNode*>(visitation.outputNodes.size()),
|
||||
{ &visitation.info, glm::vec3(), visitation.info.size * 0.5f, QVector<AttributeValue>(visitation.inputNodes.size()),
|
||||
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
|
||||
static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float lodBase, int encodedOrder) {
|
||||
MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation();
|
||||
nextVisitation.info.size = visitation.info.size * 0.5f;
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
// the encoded order tells us the child indices for each iteration
|
||||
int index = encodedOrder & ORDER_ELEMENT_MASK;
|
||||
|
@ -1372,12 +1447,13 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
for (int j = 0; j < visitation.outputNodes.size(); j++) {
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
|
||||
visitation.visitor.getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
|
||||
visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
|
||||
nextVisitation.outputNodes[j] = child;
|
||||
}
|
||||
nextVisitation.info.minimum = getNextMinimum(visitation.info.minimum, nextVisitation.info.size, index);
|
||||
if (!static_cast<MetavoxelGuide*>(nextVisitation.info.inputValues.last().getInlineValue<
|
||||
SharedObjectPointer>().data())->guide(nextVisitation)) {
|
||||
visitation.visitor->releaseVisitation();
|
||||
return false;
|
||||
}
|
||||
for (int j = 0; j < nextVisitation.outputNodes.size(); j++) {
|
||||
|
@ -1421,6 +1497,188 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
value = node->getAttributeValue(value.getAttribute());
|
||||
}
|
||||
}
|
||||
visitation.visitor->releaseVisitation();
|
||||
visitation.info.outputValues.swap(nextVisitation.info.outputValues);
|
||||
bool changed = visitation.visitor->postVisit(visitation.info);
|
||||
visitation.info.outputValues.swap(nextVisitation.info.outputValues);
|
||||
if (changed) {
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& newValue = nextVisitation.info.outputValues[i];
|
||||
if (!newValue.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
MetavoxelNode*& node = visitation.outputNodes[i];
|
||||
if (value.getAttribute()) {
|
||||
node->setAttributeValue(value = newValue);
|
||||
|
||||
} else if (!(node && node->isLeaf() && newValue.getAttribute()->equal(
|
||||
newValue.getValue(), node->getAttributeValue()))) {
|
||||
node = newValue.getAttribute()->createMetavoxelNode(value = newValue, node);
|
||||
}
|
||||
newValue = AttributeValue();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
||||
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
|
||||
float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
|
||||
visitation.visitor->getLOD().threshold;
|
||||
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier());
|
||||
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
|
||||
int encodedOrder = visitation.visitor->visit(visitation.info);
|
||||
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
MetavoxelNode*& node = visitation.outputNodes[i];
|
||||
if (node && node->isLeaf() && value.getAttribute()->equal(value.getValue(), node->getAttributeValue())) {
|
||||
// "set" to same value; disregard
|
||||
value = AttributeValue();
|
||||
} else {
|
||||
node = value.getAttribute()->createMetavoxelNode(value, node);
|
||||
}
|
||||
}
|
||||
if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) {
|
||||
return true;
|
||||
}
|
||||
return (encodedOrder == MetavoxelVisitor::STOP_RECURSION || defaultGuideToChildren(visitation, lodBase, encodedOrder));
|
||||
}
|
||||
|
||||
bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
|
||||
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
|
||||
float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
|
||||
visitation.visitor->getLOD().threshold;
|
||||
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier());
|
||||
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
|
||||
int encodedOrder = visitation.visitor->visit(visitation.info);
|
||||
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
MetavoxelNode*& node = visitation.outputNodes[i];
|
||||
if (node && node->isLeaf() && value.getAttribute()->equal(value.getValue(), node->getAttributeValue())) {
|
||||
// "set" to same value; disregard
|
||||
value = AttributeValue();
|
||||
} else {
|
||||
node = value.getAttribute()->createMetavoxelNode(value, node);
|
||||
}
|
||||
}
|
||||
if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) {
|
||||
return true;
|
||||
}
|
||||
if (encodedOrder & MetavoxelVisitor::ALL_NODES_REST) {
|
||||
return defaultGuideToChildren(visitation, lodBase, encodedOrder);
|
||||
}
|
||||
bool onlyVisitDifferent = !(encodedOrder & MetavoxelVisitor::ALL_NODES);
|
||||
MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation();
|
||||
nextVisitation.compareNodes.resize(visitation.compareNodes.size());
|
||||
nextVisitation.info.size = visitation.info.size * 0.5f;
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
// the encoded order tells us the child indices for each iteration
|
||||
int index = encodedOrder & ORDER_ELEMENT_MASK;
|
||||
encodedOrder >>= ORDER_ELEMENT_BITS;
|
||||
bool allNodesSame = onlyVisitDifferent;
|
||||
for (int j = 0; j < visitation.inputNodes.size(); j++) {
|
||||
MetavoxelNode* node = visitation.inputNodes.at(j);
|
||||
const AttributeValue& parentValue = visitation.info.inputValues.at(j);
|
||||
bool expand = (visitation.info.size >= lodBase * parentValue.getAttribute()->getLODThresholdMultiplier());
|
||||
MetavoxelNode* child = (node && expand) ? node->getChild(index) : NULL;
|
||||
nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ?
|
||||
child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue);
|
||||
MetavoxelNode* compareNode = visitation.compareNodes.at(j);
|
||||
MetavoxelNode* compareChild = (compareNode && expand) ? compareNode->getChild(index) : NULL;
|
||||
nextVisitation.compareNodes[j] = compareChild;
|
||||
allNodesSame &= (child == compareChild);
|
||||
}
|
||||
if (allNodesSame) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < visitation.outputNodes.size(); j++) {
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
|
||||
visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
|
||||
nextVisitation.outputNodes[j] = child;
|
||||
}
|
||||
nextVisitation.info.minimum = getNextMinimum(visitation.info.minimum, nextVisitation.info.size, index);
|
||||
if (!static_cast<MetavoxelGuide*>(nextVisitation.info.inputValues.last().getInlineValue<
|
||||
SharedObjectPointer>().data())->guideToDifferent(nextVisitation)) {
|
||||
visitation.visitor->releaseVisitation();
|
||||
return false;
|
||||
}
|
||||
for (int j = 0; j < nextVisitation.outputNodes.size(); j++) {
|
||||
OwnedAttributeValue& value = nextVisitation.info.outputValues[j];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
// replace the child
|
||||
OwnedAttributeValue& parentValue = visitation.info.outputValues[j];
|
||||
if (!parentValue.getAttribute()) {
|
||||
// shallow-copy the parent node on first change
|
||||
parentValue = value;
|
||||
MetavoxelNode*& node = visitation.outputNodes[j];
|
||||
if (node) {
|
||||
node = new MetavoxelNode(value.getAttribute(), node);
|
||||
} else {
|
||||
// create leaf with inherited value
|
||||
node = new MetavoxelNode(value.getAttribute()->inherit(visitation.getInheritedOutputValue(j)));
|
||||
}
|
||||
}
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
MetavoxelNode* child = node->getChild(index);
|
||||
if (child) {
|
||||
child->decrementReferenceCount(value.getAttribute());
|
||||
} else {
|
||||
// it's a leaf; we need to split it up
|
||||
AttributeValue nodeValue = value.getAttribute()->inherit(node->getAttributeValue(value.getAttribute()));
|
||||
for (int k = 1; k < MetavoxelNode::CHILD_COUNT; k++) {
|
||||
node->setChild((index + k) % MetavoxelNode::CHILD_COUNT, new MetavoxelNode(nodeValue));
|
||||
}
|
||||
}
|
||||
node->setChild(index, nextVisitation.outputNodes.at(j));
|
||||
value = AttributeValue();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (value.getAttribute()) {
|
||||
MetavoxelNode* node = visitation.outputNodes.at(i);
|
||||
node->mergeChildren(value.getAttribute());
|
||||
value = node->getAttributeValue(value.getAttribute());
|
||||
}
|
||||
}
|
||||
visitation.visitor->releaseVisitation();
|
||||
visitation.info.outputValues.swap(nextVisitation.info.outputValues);
|
||||
bool changed = visitation.visitor->postVisit(visitation.info);
|
||||
visitation.info.outputValues.swap(nextVisitation.info.outputValues);
|
||||
if (changed) {
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
OwnedAttributeValue& newValue = nextVisitation.info.outputValues[i];
|
||||
if (!newValue.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
MetavoxelNode*& node = visitation.outputNodes[i];
|
||||
if (value.getAttribute()) {
|
||||
node->setAttributeValue(value = newValue);
|
||||
|
||||
} else if (!(node && node->isLeaf() && newValue.getAttribute()->equal(
|
||||
newValue.getValue(), node->getAttributeValue()))) {
|
||||
node = newValue.getAttribute()->createMetavoxelNode(value = newValue, node);
|
||||
}
|
||||
newValue = AttributeValue();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1456,12 +1714,12 @@ static QScriptValue getAttributes(QScriptEngine* engine, ScriptedMetavoxelGuide*
|
|||
|
||||
QScriptValue ScriptedMetavoxelGuide::getInputs(QScriptContext* context, QScriptEngine* engine) {
|
||||
ScriptedMetavoxelGuide* guide = static_cast<ScriptedMetavoxelGuide*>(context->callee().data().toVariant().value<void*>());
|
||||
return getAttributes(engine, guide, guide->_visitation->visitor.getInputs());
|
||||
return getAttributes(engine, guide, guide->_visitation->visitor->getInputs());
|
||||
}
|
||||
|
||||
QScriptValue ScriptedMetavoxelGuide::getOutputs(QScriptContext* context, QScriptEngine* engine) {
|
||||
ScriptedMetavoxelGuide* guide = static_cast<ScriptedMetavoxelGuide*>(context->callee().data().toVariant().value<void*>());
|
||||
return getAttributes(engine, guide, guide->_visitation->visitor.getOutputs());
|
||||
return getAttributes(engine, guide, guide->_visitation->visitor->getOutputs());
|
||||
}
|
||||
|
||||
QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngine* engine) {
|
||||
|
@ -1470,14 +1728,16 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin
|
|||
// start with the basics, including inherited attribute values
|
||||
QScriptValue infoValue = context->argument(0);
|
||||
QScriptValue minimum = infoValue.property(guide->_minimumHandle);
|
||||
MetavoxelInfo info = {
|
||||
NULL, glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()),
|
||||
(float)infoValue.property(guide->_sizeHandle).toNumber(), guide->_visitation->info.inputValues,
|
||||
guide->_visitation->info.outputValues, infoValue.property(guide->_isLeafHandle).toBool() };
|
||||
MetavoxelInfo info(NULL, 0, 0);
|
||||
info.inputValues = guide->_visitation->info.inputValues;
|
||||
info.outputValues = guide->_visitation->info.outputValues;
|
||||
info.minimum = glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber());
|
||||
info.size = (float)infoValue.property(guide->_sizeHandle).toNumber();
|
||||
info.isLeaf = infoValue.property(guide->_isLeafHandle).toBool();
|
||||
|
||||
// extract and convert the values provided by the script
|
||||
QScriptValue inputValues = infoValue.property(guide->_inputValuesHandle);
|
||||
const QVector<AttributePointer>& inputs = guide->_visitation->visitor.getInputs();
|
||||
const QVector<AttributePointer>& inputs = guide->_visitation->visitor->getInputs();
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
QScriptValue attributeValue = inputValues.property(i);
|
||||
if (attributeValue.isValid()) {
|
||||
|
@ -1486,7 +1746,7 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin
|
|||
}
|
||||
}
|
||||
|
||||
QScriptValue result = guide->_visitation->visitor.visit(info);
|
||||
QScriptValue result = guide->_visitation->visitor->visit(info);
|
||||
|
||||
// destroy any created values
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
|
@ -1559,6 +1819,18 @@ void ScriptedMetavoxelGuide::setURL(const ParameterizedURL& url) {
|
|||
_minimumHandle = QScriptString();
|
||||
}
|
||||
|
||||
MetavoxelVisitation::MetavoxelVisitation(MetavoxelVisitation* previous,
|
||||
MetavoxelVisitor* visitor, int inputNodesSize, int outputNodesSize) :
|
||||
previous(previous),
|
||||
visitor(visitor),
|
||||
inputNodes(inputNodesSize),
|
||||
outputNodes(outputNodesSize),
|
||||
info(previous ? &previous->info : NULL, inputNodesSize, outputNodesSize) {
|
||||
}
|
||||
|
||||
MetavoxelVisitation::MetavoxelVisitation() {
|
||||
}
|
||||
|
||||
bool MetavoxelVisitation::allInputNodesLeaves() const {
|
||||
foreach (MetavoxelNode* node, inputNodes) {
|
||||
if (node && !node->isLeaf()) {
|
||||
|
@ -1572,10 +1844,55 @@ AttributeValue MetavoxelVisitation::getInheritedOutputValue(int index) const {
|
|||
for (const MetavoxelVisitation* visitation = previous; visitation; visitation = visitation->previous) {
|
||||
MetavoxelNode* node = visitation->outputNodes.at(index);
|
||||
if (node) {
|
||||
return node->getAttributeValue(visitor.getOutputs().at(index));
|
||||
return node->getAttributeValue(visitor->getOutputs().at(index));
|
||||
}
|
||||
}
|
||||
return AttributeValue(visitor.getOutputs().at(index));
|
||||
return AttributeValue(visitor->getOutputs().at(index));
|
||||
}
|
||||
|
||||
MetavoxelRenderer::MetavoxelRenderer() :
|
||||
_implementation(NULL) {
|
||||
}
|
||||
|
||||
MetavoxelRendererImplementation* MetavoxelRenderer::getImplementation() {
|
||||
QMutexLocker locker(&_implementationMutex);
|
||||
if (!_implementation) {
|
||||
QByteArray className = getImplementationClassName();
|
||||
const QMetaObject* metaObject = Bitstream::getMetaObject(className);
|
||||
if (!metaObject) {
|
||||
qDebug() << "Unknown class name:" << className;
|
||||
metaObject = &MetavoxelRendererImplementation::staticMetaObject;
|
||||
}
|
||||
_implementation = static_cast<MetavoxelRendererImplementation*>(metaObject->newInstance());
|
||||
connect(this, &QObject::destroyed, _implementation, &QObject::deleteLater);
|
||||
_implementation->init(this);
|
||||
}
|
||||
return _implementation;
|
||||
}
|
||||
|
||||
MetavoxelRendererImplementation::MetavoxelRendererImplementation() {
|
||||
}
|
||||
|
||||
void MetavoxelRendererImplementation::init(MetavoxelRenderer* renderer) {
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
void MetavoxelRendererImplementation::augment(MetavoxelData& data, const MetavoxelData& previous,
|
||||
MetavoxelInfo& info, const MetavoxelLOD& lod) {
|
||||
}
|
||||
|
||||
void MetavoxelRendererImplementation::render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod) {
|
||||
}
|
||||
|
||||
QByteArray MetavoxelRenderer::getImplementationClassName() const {
|
||||
return "MetavoxelRendererImplementation";
|
||||
}
|
||||
|
||||
PointMetavoxelRenderer::PointMetavoxelRenderer() {
|
||||
}
|
||||
|
||||
QByteArray PointMetavoxelRenderer::getImplementationClassName() const {
|
||||
return "PointMetavoxelRendererImplementation";
|
||||
}
|
||||
|
||||
const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f;
|
||||
|
@ -1585,8 +1902,7 @@ Spanner::Spanner() :
|
|||
_renderer(NULL),
|
||||
_placementGranularity(DEFAULT_PLACEMENT_GRANULARITY),
|
||||
_voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY),
|
||||
_masked(false),
|
||||
_lastVisit(0) {
|
||||
_masked(false) {
|
||||
}
|
||||
|
||||
void Spanner::setBounds(const Box& bounds) {
|
||||
|
@ -1615,11 +1931,13 @@ bool Spanner::blendAttributeValues(MetavoxelInfo& info, bool force) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Spanner::testAndSetVisited() {
|
||||
if (_lastVisit == _visit) {
|
||||
bool Spanner::testAndSetVisited(int visit) {
|
||||
QMutexLocker locker(&_lastVisitsMutex);
|
||||
int& lastVisit = _lastVisits[QThread::currentThread()];
|
||||
if (lastVisit == visit) {
|
||||
return false;
|
||||
}
|
||||
_lastVisit = _visit;
|
||||
lastVisit = visit;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1632,7 +1950,7 @@ SpannerRenderer* Spanner::getRenderer() {
|
|||
metaObject = &SpannerRenderer::staticMetaObject;
|
||||
}
|
||||
_renderer = static_cast<SpannerRenderer*>(metaObject->newInstance());
|
||||
_renderer->setParent(this);
|
||||
connect(this, &QObject::destroyed, _renderer, &QObject::deleteLater);
|
||||
_renderer->init(this);
|
||||
}
|
||||
return _renderer;
|
||||
|
@ -1647,13 +1965,13 @@ QByteArray Spanner::getRendererClassName() const {
|
|||
return "SpannerRendererer";
|
||||
}
|
||||
|
||||
int Spanner::_visit = 0;
|
||||
QAtomicInt Spanner::_nextVisit(1);
|
||||
|
||||
SpannerRenderer::SpannerRenderer() {
|
||||
}
|
||||
|
||||
void SpannerRenderer::init(Spanner* spanner) {
|
||||
// nothing by default
|
||||
_spanner = spanner;
|
||||
}
|
||||
|
||||
void SpannerRenderer::simulate(float deltaTime) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QBitArray>
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QSharedData>
|
||||
#include <QSharedPointer>
|
||||
#include <QScriptString>
|
||||
|
@ -27,7 +28,9 @@
|
|||
|
||||
class QScriptContext;
|
||||
|
||||
class MetavoxelInfo;
|
||||
class MetavoxelNode;
|
||||
class MetavoxelRendererImplementation;
|
||||
class MetavoxelVisitation;
|
||||
class MetavoxelVisitor;
|
||||
class NetworkValue;
|
||||
|
@ -80,6 +83,9 @@ public:
|
|||
/// Applies the specified visitor to the contained voxels.
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
/// Guides the specified visitor to the voxels that differ from those of the specified other.
|
||||
void guideToDifferent(const MetavoxelData& other, MetavoxelVisitor& visitor);
|
||||
|
||||
/// Inserts a spanner into the specified attribute layer.
|
||||
void insert(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
@ -155,15 +161,22 @@ template<> void Bitstream::readDelta(MetavoxelData& value, const MetavoxelData&
|
|||
|
||||
Q_DECLARE_METATYPE(MetavoxelData)
|
||||
|
||||
/// Holds the state used in streaming metavoxel data.
|
||||
class MetavoxelStreamState {
|
||||
/// Holds the base state used in streaming metavoxel data.
|
||||
class MetavoxelStreamBase {
|
||||
public:
|
||||
glm::vec3 minimum;
|
||||
float size;
|
||||
const AttributePointer& attribute;
|
||||
Bitstream& stream;
|
||||
const MetavoxelLOD& lod;
|
||||
const MetavoxelLOD& referenceLOD;
|
||||
int visit;
|
||||
};
|
||||
|
||||
/// Holds the state used in streaming a metavoxel node.
|
||||
class MetavoxelStreamState {
|
||||
public:
|
||||
MetavoxelStreamBase& base;
|
||||
glm::vec3 minimum;
|
||||
float size;
|
||||
|
||||
bool shouldSubdivide() const;
|
||||
bool shouldSubdivideReference() const;
|
||||
|
@ -209,7 +222,7 @@ public:
|
|||
void writeSpannerSubdivision(MetavoxelStreamState& state) const;
|
||||
|
||||
/// Increments the node's reference count.
|
||||
void incrementReferenceCount() { _referenceCount++; }
|
||||
void incrementReferenceCount() { _referenceCount.ref(); }
|
||||
|
||||
/// Decrements the node's reference count. If the resulting reference count is zero, destroys the node
|
||||
/// and calls delete this.
|
||||
|
@ -235,7 +248,7 @@ private:
|
|||
|
||||
friend class MetavoxelVisitation;
|
||||
|
||||
int _referenceCount;
|
||||
QAtomicInt _referenceCount;
|
||||
void* _attributeValue;
|
||||
MetavoxelNode* _children[CHILD_COUNT];
|
||||
};
|
||||
|
@ -252,6 +265,9 @@ public:
|
|||
bool isLODLeaf;
|
||||
bool isLeaf;
|
||||
|
||||
MetavoxelInfo(MetavoxelInfo* parentInfo, int inputValuesSize, int outputValuesSize);
|
||||
MetavoxelInfo();
|
||||
|
||||
Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); }
|
||||
glm::vec3 getCenter() const { return minimum + glm::vec3(size, size, size) * 0.5f; }
|
||||
};
|
||||
|
@ -278,6 +294,14 @@ public:
|
|||
/// A special "order" that short-circuits the tour.
|
||||
static const int SHORT_CIRCUIT;
|
||||
|
||||
/// A flag combined with an order that instructs us to return to visiting all nodes (rather than the different ones) for
|
||||
/// just this level.
|
||||
static const int ALL_NODES;
|
||||
|
||||
/// A flag combined with an order that instructs us to return to visiting all nodes (rather than the different ones) for
|
||||
/// this level and all beneath it.
|
||||
static const int ALL_NODES_REST;
|
||||
|
||||
MetavoxelVisitor(const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
@ -297,19 +321,34 @@ public:
|
|||
float getMinimumLODThresholdMultiplier() const { return _minimumLODThresholdMultiplier; }
|
||||
|
||||
/// Prepares for a new tour of the metavoxel data.
|
||||
virtual void prepare();
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
|
||||
/// Visits a metavoxel.
|
||||
/// \param info the metavoxel data
|
||||
/// \return the encoded order in which to traverse the children, zero to stop recursion, or -1 to short-circuit the tour
|
||||
/// \return the encoded order in which to traverse the children, zero to stop recursion, or -1 to short-circuit the tour.
|
||||
/// If child traversal is requested, postVisit will be called after we return from traversing the children and have merged
|
||||
/// their values
|
||||
virtual int visit(MetavoxelInfo& info) = 0;
|
||||
|
||||
/// Called after we have visited all of a metavoxel's children.
|
||||
/// \return whether or not any outputs were set in the info
|
||||
virtual bool postVisit(MetavoxelInfo& info);
|
||||
|
||||
/// Acquires the next visitation, incrementing the depth.
|
||||
MetavoxelVisitation& acquireVisitation();
|
||||
|
||||
/// Releases the current visitation, decrementing the depth.
|
||||
void releaseVisitation() { _depth--; }
|
||||
|
||||
protected:
|
||||
|
||||
QVector<AttributePointer> _inputs;
|
||||
QVector<AttributePointer> _outputs;
|
||||
MetavoxelLOD _lod;
|
||||
float _minimumLODThresholdMultiplier;
|
||||
MetavoxelData* _data;
|
||||
QList<MetavoxelVisitation> _visitations;
|
||||
int _depth;
|
||||
};
|
||||
|
||||
/// Base class for visitors to spanners.
|
||||
|
@ -320,20 +359,23 @@ public:
|
|||
const QVector<AttributePointer>& spannerMasks = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
const MetavoxelLOD& lod = MetavoxelLOD(),
|
||||
int order = DEFAULT_ORDER);
|
||||
|
||||
/// Visits a spanner (or part thereof).
|
||||
/// \param clipSize the size of the clip volume, or zero if unclipped
|
||||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) = 0;
|
||||
|
||||
virtual void prepare();
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _spannerMaskCount;
|
||||
int _order;
|
||||
int _visit;
|
||||
};
|
||||
|
||||
/// Base class for ray intersection visitors.
|
||||
|
@ -372,13 +414,14 @@ public:
|
|||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visitSpanner(Spanner* spanner, float distance) = 0;
|
||||
|
||||
virtual void prepare();
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
virtual int visit(MetavoxelInfo& info, float distance);
|
||||
|
||||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _spannerMaskCount;
|
||||
int _visit;
|
||||
};
|
||||
|
||||
/// Interface for objects that guide metavoxel visitors.
|
||||
|
@ -390,6 +433,10 @@ public:
|
|||
/// Guides the specified visitor to the contained voxels.
|
||||
/// \return true to keep going, false to short circuit the tour
|
||||
virtual bool guide(MetavoxelVisitation& visitation) = 0;
|
||||
|
||||
/// Guides the specified visitor to the voxels that differ from a reference.
|
||||
/// \return true to keep going, false to short circuit the tour
|
||||
virtual bool guideToDifferent(MetavoxelVisitation& visitation);
|
||||
};
|
||||
|
||||
/// Guides visitors through the explicit content of the system.
|
||||
|
@ -401,6 +448,7 @@ public:
|
|||
Q_INVOKABLE DefaultMetavoxelGuide();
|
||||
|
||||
virtual bool guide(MetavoxelVisitation& visitation);
|
||||
virtual bool guideToDifferent(MetavoxelVisitation& visitation);
|
||||
};
|
||||
|
||||
/// A temporary test guide that just makes the existing voxels throb with delight.
|
||||
|
@ -464,15 +512,67 @@ class MetavoxelVisitation {
|
|||
public:
|
||||
|
||||
MetavoxelVisitation* previous;
|
||||
MetavoxelVisitor& visitor;
|
||||
MetavoxelVisitor* visitor;
|
||||
QVector<MetavoxelNode*> inputNodes;
|
||||
QVector<MetavoxelNode*> outputNodes;
|
||||
QVector<MetavoxelNode*> compareNodes;
|
||||
MetavoxelInfo info;
|
||||
|
||||
MetavoxelVisitation(MetavoxelVisitation* previous, MetavoxelVisitor* visitor, int inputNodesSize, int outputNodesSize);
|
||||
MetavoxelVisitation();
|
||||
|
||||
bool allInputNodesLeaves() const;
|
||||
AttributeValue getInheritedOutputValue(int index) const;
|
||||
};
|
||||
|
||||
/// Base class for objects that render metavoxels.
|
||||
class MetavoxelRenderer : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelRenderer();
|
||||
|
||||
/// Returns a pointer to the implementation, creating it if necessary.
|
||||
MetavoxelRendererImplementation* getImplementation();
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelRendererImplementation* _implementation;
|
||||
QMutex _implementationMutex;
|
||||
|
||||
/// Returns the name of the class to instantiate for the implementation.
|
||||
virtual QByteArray getImplementationClassName() const;
|
||||
};
|
||||
|
||||
/// Base class for renderer implementations.
|
||||
class MetavoxelRendererImplementation : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE MetavoxelRendererImplementation();
|
||||
|
||||
virtual void init(MetavoxelRenderer* renderer);
|
||||
virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelRenderer* _renderer;
|
||||
};
|
||||
|
||||
/// Renders metavoxels as points.
|
||||
class PointMetavoxelRenderer : public MetavoxelRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE PointMetavoxelRenderer();
|
||||
|
||||
virtual QByteArray getImplementationClassName() const;
|
||||
};
|
||||
|
||||
/// An object that spans multiple octree cells.
|
||||
class Spanner : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
@ -483,8 +583,8 @@ class Spanner : public SharedObject {
|
|||
|
||||
public:
|
||||
|
||||
/// Increments the value of the global visit counter.
|
||||
static void incrementVisit() { _visit++; }
|
||||
/// Returns the value of the global visit counter and increments it.
|
||||
static int getAndIncrementNextVisit() { return _nextVisit.fetchAndAddOrdered(1); }
|
||||
|
||||
Spanner();
|
||||
|
||||
|
@ -517,7 +617,7 @@ public:
|
|||
|
||||
/// Checks whether we've visited this object on the current traversal. If we have, returns false.
|
||||
/// If we haven't, sets the last visit identifier and returns true.
|
||||
bool testAndSetVisited();
|
||||
bool testAndSetVisited(int visit);
|
||||
|
||||
/// Returns a pointer to the renderer, creating it if necessary.
|
||||
SpannerRenderer* getRenderer();
|
||||
|
@ -545,9 +645,10 @@ private:
|
|||
float _placementGranularity;
|
||||
float _voxelizationGranularity;
|
||||
bool _masked;
|
||||
int _lastVisit; ///< the identifier of the last visit
|
||||
QHash<QThread*, int> _lastVisits; ///< last visit identifiers for each thread
|
||||
QMutex _lastVisitsMutex;
|
||||
|
||||
static int _visit; ///< the global visit counter
|
||||
static QAtomicInt _nextVisit; ///< the global visit counter
|
||||
};
|
||||
|
||||
/// Base class for objects that can render spanners.
|
||||
|
@ -565,6 +666,10 @@ public:
|
|||
virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
|
||||
|
||||
protected:
|
||||
|
||||
Spanner* _spanner;
|
||||
};
|
||||
|
||||
/// An object with a 3D transform.
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
REGISTER_META_OBJECT(SharedObject)
|
||||
|
||||
SharedObject::SharedObject() :
|
||||
_id(++_lastID),
|
||||
_id(_nextID.fetchAndAddOrdered(1)),
|
||||
_originID(_id),
|
||||
_remoteID(0),
|
||||
_remoteOriginID(0) {
|
||||
|
@ -131,7 +131,7 @@ void SharedObject::dump(QDebug debug) const {
|
|||
}
|
||||
}
|
||||
|
||||
int SharedObject::_lastID = 0;
|
||||
QAtomicInt SharedObject::_nextID(1);
|
||||
WeakSharedObjectHash SharedObject::_weakHash;
|
||||
QReadWriteLock SharedObject::_weakHashLock;
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ private:
|
|||
int _remoteOriginID;
|
||||
QAtomicInt _referenceCount;
|
||||
|
||||
static int _lastID;
|
||||
static QAtomicInt _nextID;
|
||||
static WeakSharedObjectHash _weakHash;
|
||||
static QReadWriteLock _weakHashLock;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue