Merge pull request #2047 from ey6es/metavoxels

Another metavoxel checkpoint: more work on streaming, editable attributes, changed to use existing session code, work on "spanners" (objects that span multiple octree nodes).
This commit is contained in:
Philip Rosedale 2014-02-23 21:39:44 -08:00
commit 907740eb8e
31 changed files with 1870 additions and 571 deletions

View file

@ -28,10 +28,6 @@ void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
edit.apply(_data);
}
void MetavoxelServer::removeSession(const QUuid& sessionId) {
_sessions.take(sessionId)->deleteLater();
}
const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server";
void MetavoxelServer::run() {
@ -40,6 +36,8 @@ 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);
}
@ -53,25 +51,31 @@ void MetavoxelServer::readPendingDatagrams() {
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
switch (packetTypeForPacket(receivedPacket)) {
case PacketTypeMetavoxelData: {
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchingNode) {
processData(receivedPacket, matchingNode);
}
case PacketTypeMetavoxelData:
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
break;
}
default:
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
nodeList->processNodeData(senderSockAddr, receivedPacket);
break;
}
}
}
}
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
QMutexLocker locker(&node->getMutex());
node->setLinkedData(new MetavoxelSession(this, NodeList::getInstance()->nodeWithUUID(node->getUUID())));
}
}
void MetavoxelServer::sendDeltas() {
// send deltas for all sessions
foreach (MetavoxelSession* session, _sessions) {
session->sendDelta();
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::Agent) {
static_cast<MetavoxelSession*>(node->getLinkedData())->sendDelta();
}
}
// restart the send timer
@ -82,35 +86,10 @@ void MetavoxelServer::sendDeltas() {
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
}
void MetavoxelServer::processData(const QByteArray& data, const SharedNodePointer& sendingNode) {
// read the session id
int headerPlusIDSize;
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
if (sessionID.isNull()) {
return;
}
// forward to session, creating if necessary
MetavoxelSession*& session = _sessions[sessionID];
if (!session) {
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize),
sendingNode);
}
session->receivedData(data, sendingNode);
}
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode) :
QObject(server),
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node) :
_server(server),
_sessionId(sessionId),
_sequencer(datagramHeader),
_sendingNode(sendingNode) {
const int TIMEOUT_INTERVAL = 30 * 1000;
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
_timeoutTimer.setSingleShot(true);
connect(&_timeoutTimer, SIGNAL(timeout()), SLOT(timedOut()));
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)),
_node(node) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
@ -120,19 +99,15 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
// insert the baseline send record
SendRecord record = { 0 };
_sendRecords.append(record);
qDebug() << "Opened session [sessionId=" << _sessionId << ", sendingNode=" << sendingNode << "]";
}
void MetavoxelSession::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
// reset the timeout timer
_timeoutTimer.start();
MetavoxelSession::~MetavoxelSession() {
}
// save the most recent sender
_sendingNode = sendingNode;
int MetavoxelSession::parseData(const QByteArray& packet) {
// process through sequencer
_sequencer.receivedDatagram(data);
_sequencer.receivedDatagram(packet);
return packet.size();
}
void MetavoxelSession::sendDelta() {
@ -146,13 +121,8 @@ void MetavoxelSession::sendDelta() {
_sendRecords.append(record);
}
void MetavoxelSession::timedOut() {
qDebug() << "Session timed out [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
_server->removeSession(_sessionId);
}
void MetavoxelSession::sendData(const QByteArray& data) {
NodeList::getInstance()->writeDatagram(data, _sendingNode);
NodeList::getInstance()->writeDatagram(data, _node);
}
void MetavoxelSession::readPacket(Bitstream& in) {
@ -167,11 +137,7 @@ void MetavoxelSession::clearSendRecordsBefore(int index) {
void MetavoxelSession::handleMessage(const QVariant& message) {
int userType = message.userType();
if (userType == CloseSessionMessage::Type) {
qDebug() << "Session closed [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
_server->removeSession(_sessionId);
} else if (userType == ClientStateMessage::Type) {
if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value<ClientStateMessage>();
_position = state.position;

View file

@ -9,12 +9,9 @@
#ifndef __hifi__MetavoxelServer__
#define __hifi__MetavoxelServer__
#include <QHash>
#include <QList>
#include <QTimer>
#include <QUuid>
#include <HifiSockAddr.h>
#include <ThreadedAssignment.h>
#include <DatagramSequencer.h>
@ -35,45 +32,38 @@ public:
const MetavoxelData& getData() const { return _data; }
void removeSession(const QUuid& sessionId);
virtual void run();
virtual void readPendingDatagrams();
private slots:
void maybeAttachSession(const SharedNodePointer& node);
void sendDeltas();
private:
void processData(const QByteArray& data, const SharedNodePointer& sendingNode);
QTimer _sendTimer;
qint64 _lastSend;
QHash<QUuid, MetavoxelSession*> _sessions;
MetavoxelData _data;
};
/// Contains the state of a single client session.
class MetavoxelSession : public QObject {
class MetavoxelSession : public NodeData {
Q_OBJECT
public:
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode);
MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node);
virtual ~MetavoxelSession();
void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
virtual int parseData(const QByteArray& packet);
void sendDelta();
private slots:
void timedOut();
void sendData(const QByteArray& data);
void readPacket(Bitstream& in);
@ -91,12 +81,10 @@ private:
};
MetavoxelServer* _server;
QUuid _sessionId;
QTimer _timeoutTimer;
DatagramSequencer _sequencer;
SharedNodePointer _sendingNode;
SharedNodePointer _node;
glm::vec3 _position;

View file

@ -3,20 +3,11 @@ macro(AUTO_MTC TARGET ROOT_DIR)
add_subdirectory(${ROOT_DIR}/tools/mtc ${ROOT_DIR}/tools/mtc)
endif (NOT TARGET mtc)
set(AUTOMTC_SRC ${TARGET}_automtc.cpp)
file(GLOB INCLUDE_FILES src/*.h)
add_custom_command(OUTPUT ${TARGET}_automtc.cpp COMMAND mtc -o ${TARGET}_automtc.cpp
${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
find_package(Qt5Core REQUIRED)
find_package(Qt5Script REQUIRED)
find_package(Qt5Widgets REQUIRED)
add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp)
qt5_use_modules(${TARGET}_automtc Core Script Widgets)
target_link_libraries(${TARGET} ${TARGET}_automtc)
add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND mtc -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
endmacro()

View file

@ -2344,6 +2344,9 @@ void Application::update(float deltaTime) {
_particles.update(); // update the particles...
_particleCollisionSystem.update(); // collide the particles...
// let external parties know we're updating
emit simulating(deltaTime);
}
void Application::updateMyAvatar(float deltaTime) {

View file

@ -225,6 +225,9 @@ public:
signals:
/// Fired when we're simulating; allows external parties to hook in.
void simulating(float deltaTime);
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
void renderingInWorldInterface();

View file

@ -94,7 +94,7 @@ void DatagramProcessor::processDatagrams() {
break;
}
case PacketTypeMetavoxelData:
application->_metavoxels.processData(incomingPacket, senderSockAddr);
nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket);
break;
case PacketTypeBulkAvatarData:
case PacketTypeKillAvatar:

View file

@ -1115,6 +1115,7 @@ void Menu::showMetavoxelEditor() {
_MetavoxelEditor = new MetavoxelEditor();
}
_MetavoxelEditor->raise();
_MetavoxelEditor->activateWindow();
}
void Menu::audioMuteToggled() {

View file

@ -16,21 +16,18 @@
#include "Application.h"
#include "MetavoxelSystem.h"
#include "renderer/Model.h"
REGISTER_META_OBJECT(StaticModelRenderer)
ProgramObject MetavoxelSystem::_program;
int MetavoxelSystem::_pointScaleLocation;
MetavoxelSystem::MetavoxelSystem() :
_pointVisitor(_points),
_simulateVisitor(_points),
_buffer(QOpenGLBuffer::VertexBuffer) {
}
MetavoxelSystem::~MetavoxelSystem() {
for (QHash<QUuid, MetavoxelClient*>::const_iterator it = _clients.begin(); it != _clients.end(); it++) {
delete it.value();
}
}
void MetavoxelSystem::init() {
if (!_program.isLinked()) {
switchToResourcesParentIfRequired();
@ -42,33 +39,39 @@ void MetavoxelSystem::init() {
// let the script cache know to use our common access manager
ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager());
}
NodeList* nodeList = NodeList::getInstance();
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
_buffer.create();
connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
}
void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit) {
foreach (MetavoxelClient* client, _clients) {
client->applyEdit(edit);
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);
}
}
}
}
void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) {
QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender));
}
void MetavoxelSystem::simulate(float deltaTime) {
// simulate the clients
_points.clear();
foreach (MetavoxelClient* client, _clients) {
client->simulate(deltaTime, _pointVisitor);
_simulateVisitor.setDeltaTime(deltaTime);
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->simulate(deltaTime);
client->getData().guide(_simulateVisitor);
}
}
}
_buffer.bind();
int bytes = _points.size() * sizeof(Point);
if (_buffer.size() < bytes) {
@ -118,53 +121,39 @@ void MetavoxelSystem::render() {
_buffer.release();
_program.release();
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->getData().guide(_renderVisitor);
}
}
}
}
void MetavoxelSystem::nodeAdded(SharedNodePointer node) {
void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) {
if (node->getType() == NodeType::MetavoxelServer) {
QMetaObject::invokeMethod(this, "addClient", Q_ARG(const SharedNodePointer&, node));
QMutexLocker locker(&node->getMutex());
node->setLinkedData(new MetavoxelClient(NodeList::getInstance()->nodeWithUUID(node->getUUID())));
}
}
void MetavoxelSystem::nodeKilled(SharedNodePointer node) {
if (node->getType() == NodeType::MetavoxelServer) {
QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID()));
}
}
void MetavoxelSystem::addClient(const SharedNodePointer& node) {
MetavoxelClient* client = new MetavoxelClient(node);
_clients.insert(node->getUUID(), client);
_clientsBySessionID.insert(client->getSessionID(), client);
}
void MetavoxelSystem::removeClient(const QUuid& uuid) {
MetavoxelClient* client = _clients.take(uuid);
_clientsBySessionID.remove(client->getSessionID());
delete client;
}
void MetavoxelSystem::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
int headerPlusIDSize;
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
if (sessionID.isNull()) {
return;
}
MetavoxelClient* client = _clientsBySessionID.value(sessionID);
if (client) {
client->receivedData(data);
}
}
MetavoxelSystem::PointVisitor::PointVisitor(QVector<Point>& points) :
MetavoxelVisitor(QVector<AttributePointer>() <<
AttributeRegistry::getInstance()->getColorAttribute() <<
AttributeRegistry::getInstance()->getNormalAttribute(),
QVector<AttributePointer>()),
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
AttributeRegistry::getInstance()->getNormalAttribute()),
_points(points) {
}
bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
void MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner) {
spanner->getRenderer()->simulate(_deltaTime);
}
bool MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) {
SpannerVisitor::visit(info);
if (!info.isLeaf) {
return true;
}
@ -179,16 +168,17 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
return false;
}
static QByteArray createDatagramHeader(const QUuid& sessionID) {
QByteArray header = byteArrayWithPopulatedHeader(PacketTypeMetavoxelData);
header += sessionID.toRfc4122();
return header;
MetavoxelSystem::RenderVisitor::RenderVisitor() :
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute()) {
}
void MetavoxelSystem::RenderVisitor::visit(Spanner* spanner) {
spanner->getRenderer()->render(1.0f);
}
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
_node(node),
_sessionID(QUuid::createUuid()),
_sequencer(createDatagramHeader(_sessionID)) {
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
@ -214,22 +204,20 @@ void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit) {
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
}
void MetavoxelClient::simulate(float deltaTime, MetavoxelVisitor& visitor) {
void MetavoxelClient::simulate(float deltaTime) {
Bitstream& out = _sequencer.startPacket();
ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() };
out << QVariant::fromValue(state);
_sequencer.endPacket();
_data.guide(visitor);
}
void MetavoxelClient::receivedData(const QByteArray& data) {
int MetavoxelClient::parseData(const QByteArray& packet) {
// process through sequencer
_sequencer.receivedDatagram(data);
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
return packet.size();
}
void MetavoxelClient::sendData(const QByteArray& data) {
QMutexLocker locker(&_node->getMutex());
NodeList::getInstance()->writeDatagram(data, _node);
}
@ -265,3 +253,47 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
}
}
}
StaticModelRenderer::StaticModelRenderer() :
_model(new Model(this)) {
}
void StaticModelRenderer::init(Spanner* spanner) {
_model->init();
StaticModel* staticModel = static_cast<StaticModel*>(spanner);
applyTranslation(staticModel->getTranslation());
applyRotation(staticModel->getRotation());
applyScale(staticModel->getScale());
applyURL(staticModel->getURL());
connect(spanner, SIGNAL(translationChanged(const glm::vec3&)), SLOT(applyTranslation(const glm::vec3&)));
connect(spanner, SIGNAL(rotationChanged(const glm::vec3&)), SLOT(applyRotation(const glm::vec3&)));
connect(spanner, SIGNAL(scaleChanged(float)), SLOT(applyScale(float)));
connect(spanner, SIGNAL(urlChanged(const QUrl&)), SLOT(applyURL(const QUrl&)));
}
void StaticModelRenderer::simulate(float deltaTime) {
_model->simulate(deltaTime);
}
void StaticModelRenderer::render(float alpha) {
_model->render(alpha);
}
void StaticModelRenderer::applyTranslation(const glm::vec3& translation) {
_model->setTranslation(translation);
}
void StaticModelRenderer::applyRotation(const glm::vec3& rotation) {
_model->setRotation(glm::quat(glm::radians(rotation)));
}
void StaticModelRenderer::applyScale(float scale) {
const float SCALE_MULTIPLIER = 0.0006f;
_model->setScale(glm::vec3(scale, scale, scale) * SCALE_MULTIPLIER);
}
void StaticModelRenderer::applyURL(const QUrl& url) {
_model->setURL(url);
}

View file

@ -23,7 +23,7 @@
#include "renderer/ProgramObject.h"
class MetavoxelClient;
class Model;
/// Renders a metavoxel tree.
class MetavoxelSystem : public QObject {
@ -32,28 +32,20 @@ class MetavoxelSystem : public QObject {
public:
MetavoxelSystem();
~MetavoxelSystem();
void init();
void applyEdit(const MetavoxelEditMessage& edit);
void processData(const QByteArray& data, const HifiSockAddr& sender);
void simulate(float deltaTime);
void render();
public slots:
private slots:
void maybeAttachClient(const SharedNodePointer& node);
void nodeAdded(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
private:
Q_INVOKABLE void addClient(const SharedNodePointer& node);
Q_INVOKABLE void removeClient(const QUuid& uuid);
Q_INVOKABLE void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
class Point {
public:
glm::vec4 vertex;
@ -61,28 +53,35 @@ private:
quint8 normal[3];
};
class PointVisitor : public MetavoxelVisitor {
class SimulateVisitor : public SpannerVisitor {
public:
PointVisitor(QVector<Point>& points);
SimulateVisitor(QVector<Point>& points);
void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; }
virtual void visit(Spanner* spanner);
virtual bool visit(MetavoxelInfo& info);
private:
QVector<Point>& _points;
float _deltaTime;
};
class RenderVisitor : public SpannerVisitor {
public:
RenderVisitor();
virtual void visit(Spanner* spanner);
};
static ProgramObject _program;
static int _pointScaleLocation;
QVector<Point> _points;
PointVisitor _pointVisitor;
SimulateVisitor _simulateVisitor;
RenderVisitor _renderVisitor;
QOpenGLBuffer _buffer;
QHash<QUuid, MetavoxelClient*> _clients;
QHash<QUuid, MetavoxelClient*> _clientsBySessionID;
};
/// A client session associated with a single server.
class MetavoxelClient : public QObject {
class MetavoxelClient : public NodeData {
Q_OBJECT
public:
@ -90,13 +89,13 @@ public:
MetavoxelClient(const SharedNodePointer& node);
virtual ~MetavoxelClient();
const QUuid& getSessionID() const { return _sessionID; }
MetavoxelData& getData() { return _data; }
void applyEdit(const MetavoxelEditMessage& edit);
void simulate(float deltaTime, MetavoxelVisitor& visitor);
void simulate(float deltaTime);
void receivedData(const QByteArray& data);
virtual int parseData(const QByteArray& packet);
private slots:
@ -117,7 +116,6 @@ private:
};
SharedNodePointer _node;
QUuid _sessionID;
DatagramSequencer _sequencer;
@ -126,4 +124,28 @@ private:
QList<ReceiveRecord> _receiveRecords;
};
/// Renders static models.
class StaticModelRenderer : public SpannerRenderer {
Q_OBJECT
public:
Q_INVOKABLE StaticModelRenderer();
virtual void init(Spanner* spanner);
virtual void simulate(float deltaTime);
virtual void render(float alpha);
private slots:
void applyTranslation(const glm::vec3& translation);
void applyRotation(const glm::vec3& rotation);
void applyScale(float scale);
void applyURL(const QUrl& url);
private:
Model* _model;
};
#endif /* defined(__interface__MetavoxelSystem__) */

View file

@ -290,7 +290,7 @@ bool Model::render(float alpha) {
// render opaque meshes with alpha testing
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glAlphaFunc(GL_GREATER, 0.5f * alpha);
renderMeshes(alpha, false);
@ -931,7 +931,7 @@ void Model::renderMeshes(float alpha, bool translucent) {
if (!mesh.colors.isEmpty()) {
glEnableClientState(GL_COLOR_ARRAY);
} else {
glColor3f(1.0f, 1.0f, 1.0f);
glColor4f(1.0f, 1.0f, 1.0f, alpha);
}
if (!mesh.texCoords.isEmpty()) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

View file

@ -29,7 +29,7 @@ enum GridPlane {
const glm::vec2 INVALID_VECTOR(FLT_MAX, FLT_MAX);
MetavoxelEditor::MetavoxelEditor() :
QDialog(Application::getInstance()->getGLWidget()) {
QWidget(Application::getInstance()->getGLWidget(), Qt::Tool | Qt::WindowStaysOnTopHint) {
setWindowTitle("Metavoxel Editor");
setAttribute(Qt::WA_DeleteOnClose);
@ -45,12 +45,19 @@ MetavoxelEditor::MetavoxelEditor() :
attributeGroup->setLayout(attributeLayout);
attributeLayout->addWidget(_attributes = new QListWidget());
connect(_attributes, SIGNAL(itemSelectionChanged()), SLOT(updateValueEditor()));
connect(_attributes, SIGNAL(itemSelectionChanged()), SLOT(selectedAttributeChanged()));
QHBoxLayout* attributeButtonLayout = new QHBoxLayout();
attributeLayout->addLayout(attributeButtonLayout);
QPushButton* newAttribute = new QPushButton("New...");
attributeLayout->addWidget(newAttribute);
attributeButtonLayout->addWidget(newAttribute);
connect(newAttribute, SIGNAL(clicked()), SLOT(createNewAttribute()));
attributeButtonLayout->addWidget(_deleteAttribute = new QPushButton("Delete"));
_deleteAttribute->setEnabled(false);
connect(_deleteAttribute, SIGNAL(clicked()), SLOT(deleteSelectedAttribute()));
QFormLayout* formLayout = new QFormLayout();
topLayout->addLayout(formLayout);
@ -74,6 +81,9 @@ MetavoxelEditor::MetavoxelEditor() :
alignGridPosition();
centerGridPosition();
formLayout->addRow("Tool:", _toolBox = new QComboBox());
connect(_toolBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTool()));
_value = new QGroupBox();
_value->setTitle("Value");
topLayout->addWidget(_value);
@ -82,16 +92,22 @@ MetavoxelEditor::MetavoxelEditor() :
_value->setLayout(valueLayout);
valueLayout->addWidget(_valueArea = new QScrollArea());
_valueArea->setMinimumHeight(200);
_valueArea->setWidgetResizable(true);
addTool(new BoxSetTool(this));
addTool(new GlobalSetTool(this));
addTool(new InsertSpannerTool(this));
addTool(new RemoveSpannerTool(this));
addTool(new ClearSpannersTool(this));
updateAttributes();
connect(Application::getInstance(), SIGNAL(simulating(float)), SLOT(simulate(float)));
connect(Application::getInstance(), SIGNAL(renderingInWorldInterface()), SLOT(render()));
Application::getInstance()->getGLWidget()->installEventFilter(this);
resetState();
show();
if (_gridProgram.isLinked()) {
@ -102,60 +118,79 @@ MetavoxelEditor::MetavoxelEditor() :
_gridProgram.link();
}
bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
switch (_state) {
case HOVERING_STATE:
if (event->type() == QEvent::MouseButtonPress && _startPosition != INVALID_VECTOR) {
_state = DRAGGING_STATE;
return true;
}
break;
case DRAGGING_STATE:
if (event->type() == QEvent::MouseButtonRelease) {
_state = RAISING_STATE;
return true;
}
break;
case RAISING_STATE:
if (event->type() == QEvent::MouseButtonPress) {
if (_height != 0) {
// find the start and end corners in X/Y
float base = _gridPosition->value();
float top = base + _height;
glm::quat rotation = getGridRotation();
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
float spacing = getGridSpacing();
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
glm::vec2(spacing, spacing), glm::max(base, top));
// find the minimum and maximum extents after rotation
applyValue(glm::min(start, end), glm::max(start, end));
}
resetState();
return true;
}
break;
}
return false;
QString MetavoxelEditor::getSelectedAttribute() const {
QList<QListWidgetItem*> selectedItems = _attributes->selectedItems();
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
}
void MetavoxelEditor::updateValueEditor() {
double MetavoxelEditor::getGridSpacing() const {
return pow(2.0, _gridSpacing->value());
}
double MetavoxelEditor::getGridPosition() const {
return _gridPosition->value();
}
glm::quat MetavoxelEditor::getGridRotation() const {
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
switch (_gridPlane->currentIndex()) {
case GRID_PLANE_XY:
return glm::quat();
case GRID_PLANE_XZ:
return glm::angleAxis(-90.0f, 1.0f, 0.0f, 0.0f);
case GRID_PLANE_YZ:
default:
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
}
}
QVariant MetavoxelEditor::getValue() const {
QWidget* editor = _valueArea->widget();
return editor ? editor->metaObject()->userProperty().read(editor) : QVariant();
}
void MetavoxelEditor::detachValue() {
SharedObjectEditor* editor = qobject_cast<SharedObjectEditor*>(_valueArea->widget());
if (editor) {
editor->detachObject();
}
}
bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
// pass along to the active tool
MetavoxelTool* tool = getActiveTool();
return tool && tool->eventFilter(watched, event);
}
void MetavoxelEditor::selectedAttributeChanged() {
_toolBox->clear();
QString selected = getSelectedAttribute();
if (selected.isNull()) {
_deleteAttribute->setEnabled(false);
_toolBox->setEnabled(false);
_value->setVisible(false);
return;
}
_deleteAttribute->setEnabled(true);
_toolBox->setEnabled(true);
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
foreach (MetavoxelTool* tool, _tools) {
if (tool->appliesTo(attribute)) {
_toolBox->addItem(tool->objectName(), QVariant::fromValue(tool));
}
}
_value->setVisible(true);
if (_valueArea->widget()) {
delete _valueArea->widget();
}
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
QWidget* editor = attribute->createEditor();
if (editor) {
editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
_valueArea->setWidget(editor);
}
}
@ -173,6 +208,10 @@ void MetavoxelEditor::createNewAttribute() {
QLineEdit name;
form.addRow("Name:", &name);
SharedObjectEditor editor(&Attribute::staticMetaObject, false);
editor.setObject(new QRgbAttribute());
layout.addWidget(&editor);
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(&buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(&buttons, SIGNAL(rejected()), SLOT(reject()));
@ -183,11 +222,19 @@ void MetavoxelEditor::createNewAttribute() {
return;
}
QString nameText = name.text().trimmed();
AttributeRegistry::getInstance()->registerAttribute(new QRgbAttribute(nameText));
SharedObjectPointer attribute = editor.getObject();
attribute->setObjectName(nameText);
AttributeRegistry::getInstance()->registerAttribute(attribute.staticCast<Attribute>());
updateAttributes(nameText);
}
void MetavoxelEditor::deleteSelectedAttribute() {
AttributeRegistry::getInstance()->deregisterAttribute(getSelectedAttribute());
_attributes->selectionModel()->clear();
updateAttributes();
}
void MetavoxelEditor::centerGridPosition() {
const float CENTER_OFFSET = 0.625f;
float eyePosition = (glm::inverse(getGridRotation()) * Application::getInstance()->getCamera()->getPosition()).z -
@ -203,13 +250,24 @@ void MetavoxelEditor::alignGridPosition() {
_gridPosition->setValue(step * floor(_gridPosition->value() / step));
}
void MetavoxelEditor::render() {
QString selected = getSelectedAttribute();
if (selected.isNull()) {
resetState();
return;
void MetavoxelEditor::updateTool() {
MetavoxelTool* active = getActiveTool();
foreach (MetavoxelTool* tool, _tools) {
tool->setVisible(tool == active);
}
_value->setVisible(active && active->getUsesValue());
}
void MetavoxelEditor::simulate(float deltaTime) {
MetavoxelTool* tool = getActiveTool();
if (tool) {
tool->simulate(deltaTime);
}
}
const float GRID_BRIGHTNESS = 0.5f;
void MetavoxelEditor::render() {
glDisable(GL_LIGHTING);
glDepthMask(GL_FALSE);
@ -219,11 +277,110 @@ void MetavoxelEditor::render() {
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
MetavoxelTool* tool = getActiveTool();
if (tool) {
tool->render();
}
glLineWidth(1.0f);
// center the grid around the camera position on the plane
glm::vec3 rotated = glm::inverse(rotation) * Application::getInstance()->getCamera()->getPosition();
float spacing = getGridSpacing();
const int GRID_DIVISIONS = 300;
glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), _gridPosition->value());
float scale = GRID_DIVISIONS * spacing;
glScalef(scale, scale, scale);
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
_gridProgram.bind();
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
_gridProgram.release();
glPopMatrix();
glEnable(GL_LIGHTING);
glDepthMask(GL_TRUE);
}
void MetavoxelEditor::addTool(MetavoxelTool* tool) {
_tools.append(tool);
layout()->addWidget(tool);
}
void MetavoxelEditor::updateAttributes(const QString& select) {
// remember the selection in order to preserve it
QString selected = select.isNull() ? getSelectedAttribute() : select;
_attributes->clear();
// sort the names for consistent ordering
QList<QString> names = AttributeRegistry::getInstance()->getAttributes().keys();
qSort(names);
foreach (const QString& name, names) {
QListWidgetItem* item = new QListWidgetItem(name);
_attributes->addItem(item);
if (name == selected || selected.isNull()) {
item->setSelected(true);
selected = name;
}
}
}
MetavoxelTool* MetavoxelEditor::getActiveTool() const {
int index = _toolBox->currentIndex();
return (index == -1) ? NULL : static_cast<MetavoxelTool*>(_toolBox->itemData(index).value<QObject*>());
}
ProgramObject MetavoxelEditor::_gridProgram;
MetavoxelTool::MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue) :
_editor(editor),
_usesValue(usesValue) {
QVBoxLayout* layout = new QVBoxLayout();
setLayout(layout);
setObjectName(name);
setVisible(false);
}
bool MetavoxelTool::appliesTo(const AttributePointer& attribute) const {
// shared object sets are a special case
return !attribute->inherits("SharedObjectSetAttribute");
}
void MetavoxelTool::simulate(float deltaTime) {
// nothing by default
}
void MetavoxelTool::render() {
// nothing by default
}
BoxSetTool::BoxSetTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Set Value (Box)") {
resetState();
}
void BoxSetTool::render() {
QString selected = _editor->getSelectedAttribute();
if (selected.isNull()) {
resetState();
return;
}
glm::quat rotation = _editor->getGridRotation();
glm::quat inverseRotation = glm::inverse(rotation);
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
float spacing = getGridSpacing();
float position = _gridPosition->value();
float spacing = _editor->getGridSpacing();
float position = _editor->getGridPosition();
if (_state == RAISING_STATE) {
// find the plane at the mouse position, orthogonal to the plane, facing the eye position
glLineWidth(4.0f);
@ -256,7 +413,6 @@ void MetavoxelEditor::render() {
resetState();
}
const float GRID_BRIGHTNESS = 0.5f;
if (_startPosition != INVALID_VECTOR) {
glm::vec2 minimum = glm::min(_startPosition, _endPosition);
glm::vec2 maximum = glm::max(_startPosition, _endPosition);
@ -268,7 +424,7 @@ void MetavoxelEditor::render() {
glTranslatef(0.5f, 0.5f, 0.5f);
if (_state != HOVERING_STATE) {
const float BOX_ALPHA = 0.25f;
QColor color = getValue().value<QColor>();
QColor color = _editor->getValue().value<QColor>();
if (color.isValid()) {
glColor4f(color.redF(), color.greenF(), color.blueF(), BOX_ALPHA);
} else {
@ -281,97 +437,171 @@ void MetavoxelEditor::render() {
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
glutWireCube(1.0);
glPopMatrix();
} else {
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
}
glLineWidth(1.0f);
// center the grid around the camera position on the plane
glm::vec3 rotated = inverseRotation * Application::getInstance()->getCamera()->getPosition();
const int GRID_DIVISIONS = 300;
glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position);
float scale = GRID_DIVISIONS * spacing;
glScalef(scale, scale, scale);
_gridProgram.bind();
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
_gridProgram.release();
glPopMatrix();
glEnable(GL_LIGHTING);
glDepthMask(GL_TRUE);
}
void MetavoxelEditor::updateAttributes(const QString& select) {
// remember the selection in order to preserve it
QString selected = select.isNull() ? getSelectedAttribute() : select;
_attributes->clear();
// sort the names for consistent ordering
QList<QString> names = AttributeRegistry::getInstance()->getAttributes().keys();
qSort(names);
foreach (const QString& name, names) {
QListWidgetItem* item = new QListWidgetItem(name);
_attributes->addItem(item);
if (name == selected || selected.isNull()) {
item->setSelected(true);
selected = name;
}
glPopMatrix();
}
}
QString MetavoxelEditor::getSelectedAttribute() const {
QList<QListWidgetItem*> selectedItems = _attributes->selectedItems();
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
}
double MetavoxelEditor::getGridSpacing() const {
return pow(2.0, _gridSpacing->value());
}
glm::quat MetavoxelEditor::getGridRotation() const {
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
switch (_gridPlane->currentIndex()) {
case GRID_PLANE_XY:
return glm::quat();
bool BoxSetTool::eventFilter(QObject* watched, QEvent* event) {
switch (_state) {
case HOVERING_STATE:
if (event->type() == QEvent::MouseButtonPress && _startPosition != INVALID_VECTOR) {
_state = DRAGGING_STATE;
return true;
}
break;
case GRID_PLANE_XZ:
return glm::angleAxis(-90.0f, 1.0f, 0.0f, 0.0f);
case DRAGGING_STATE:
if (event->type() == QEvent::MouseButtonRelease) {
_state = RAISING_STATE;
return true;
}
break;
case GRID_PLANE_YZ:
default:
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
case RAISING_STATE:
if (event->type() == QEvent::MouseButtonPress) {
if (_height != 0) {
// find the start and end corners in X/Y
float base = _editor->getGridPosition();
float top = base + _height;
glm::quat rotation = _editor->getGridRotation();
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
float spacing = _editor->getGridSpacing();
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
glm::vec2(spacing, spacing), glm::max(base, top));
// find the minimum and maximum extents after rotation
applyValue(glm::min(start, end), glm::max(start, end));
}
resetState();
return true;
}
break;
}
return false;
}
void MetavoxelEditor::resetState() {
void BoxSetTool::resetState() {
_state = HOVERING_STATE;
_startPosition = INVALID_VECTOR;
_height = 0.0f;
}
void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(getSelectedAttribute());
void BoxSetTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
if (!attribute) {
return;
}
OwnedAttributeValue value(attribute, attribute->createFromVariant(getValue()));
MetavoxelEditMessage edit = { { minimum, maximum }, getGridSpacing(), value };
Application::getInstance()->getMetavoxels()->applyEdit(edit);
OwnedAttributeValue value(attribute, attribute->createFromVariant(_editor->getValue()));
MetavoxelEditMessage message = { QVariant::fromValue(BoxSetEdit(Box(minimum, maximum),
_editor->getGridSpacing(), value)) };
Application::getInstance()->getMetavoxels()->applyEdit(message);
}
QVariant MetavoxelEditor::getValue() const {
QWidget* editor = _valueArea->widget();
return editor ? editor->metaObject()->userProperty().read(editor) : QVariant();
GlobalSetTool::GlobalSetTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Set Value (Global)") {
QPushButton* button = new QPushButton("Apply");
layout()->addWidget(button);
connect(button, SIGNAL(clicked()), SLOT(apply()));
}
ProgramObject MetavoxelEditor::_gridProgram;
void GlobalSetTool::apply() {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
if (!attribute) {
return;
}
OwnedAttributeValue value(attribute, attribute->createFromVariant(_editor->getValue()));
MetavoxelEditMessage message = { QVariant::fromValue(GlobalSetEdit(value)) };
Application::getInstance()->getMetavoxels()->applyEdit(message);
}
InsertSpannerTool::InsertSpannerTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Insert Spanner") {
QPushButton* button = new QPushButton("Insert");
layout()->addWidget(button);
connect(button, SIGNAL(clicked()), SLOT(insert()));
}
void InsertSpannerTool::simulate(float deltaTime) {
SharedObjectPointer spanner = _editor->getValue().value<SharedObjectPointer>();
static_cast<Spanner*>(spanner.data())->getRenderer()->simulate(deltaTime);
}
void InsertSpannerTool::render() {
_editor->detachValue();
Spanner* spanner = static_cast<Spanner*>(_editor->getValue().value<SharedObjectPointer>().data());
Transformable* transformable = qobject_cast<Transformable*>(spanner);
if (transformable) {
// find the intersection of the mouse ray with the grid and place the transformable there
glm::quat rotation = _editor->getGridRotation();
glm::quat inverseRotation = glm::inverse(rotation);
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
float position = _editor->getGridPosition();
float distance = (position - rayOrigin.z) / rayDirection.z;
transformable->setTranslation(rotation * glm::vec3(glm::vec2(rayOrigin + rayDirection * distance), position));
}
const float SPANNER_ALPHA = 0.25f;
spanner->getRenderer()->render(SPANNER_ALPHA);
}
bool InsertSpannerTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("SharedObjectSetAttribute");
}
bool InsertSpannerTool::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::MouseButtonPress) {
insert();
return true;
}
return false;
}
void InsertSpannerTool::insert() {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
if (!attribute) {
return;
}
SharedObjectPointer spanner = _editor->getValue().value<SharedObjectPointer>();
MetavoxelEditMessage message = { QVariant::fromValue(InsertSpannerEdit(attribute, spanner)) };
Application::getInstance()->getMetavoxels()->applyEdit(message);
}
RemoveSpannerTool::RemoveSpannerTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Remove Spanner", false) {
}
bool RemoveSpannerTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("SharedObjectSetAttribute");
}
bool RemoveSpannerTool::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::MouseButtonPress) {
return true;
}
return false;
}
ClearSpannersTool::ClearSpannersTool(MetavoxelEditor* editor) :
MetavoxelTool(editor, "Clear Spanners", false) {
QPushButton* button = new QPushButton("Clear");
layout()->addWidget(button);
connect(button, SIGNAL(clicked()), SLOT(clear()));
}
bool ClearSpannersTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("SharedObjectSetAttribute");
}
void ClearSpannersTool::clear() {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
if (!attribute) {
return;
}
MetavoxelEditMessage message = { QVariant::fromValue(ClearSpannersEdit(attribute)) };
Application::getInstance()->getMetavoxels()->applyEdit(message);
}

View file

@ -9,7 +9,8 @@
#ifndef __interface__MetavoxelEditor__
#define __interface__MetavoxelEditor__
#include <QDialog>
#include <QList>
#include <QWidget>
#include "renderer/ProgramObject.h"
@ -17,55 +18,174 @@ class QComboBox;
class QDoubleSpinBox;
class QGroupBox;
class QListWidget;
class QPushButton;
class QScrollArea;
class MetavoxelTool;
/// Allows editing metavoxels.
class MetavoxelEditor : public QDialog {
class MetavoxelEditor : public QWidget {
Q_OBJECT
public:
MetavoxelEditor();
QString getSelectedAttribute() const;
double getGridSpacing() const;
double getGridPosition() const;
glm::quat getGridRotation() const;
QVariant getValue() const;
void detachValue();
virtual bool eventFilter(QObject* watched, QEvent* event);
private slots:
void updateValueEditor();
void selectedAttributeChanged();
void createNewAttribute();
void deleteSelectedAttribute();
void centerGridPosition();
void alignGridPosition();
void updateTool();
void simulate(float deltaTime);
void render();
private:
void updateAttributes(const QString& select = QString());
QString getSelectedAttribute() const;
double getGridSpacing() const;
glm::quat getGridRotation() const;
void resetState();
void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
QVariant getValue() const;
void addTool(MetavoxelTool* tool);
void updateAttributes(const QString& select = QString());
MetavoxelTool* getActiveTool() const;
QListWidget* _attributes;
QPushButton* _deleteAttribute;
QComboBox* _gridPlane;
QDoubleSpinBox* _gridSpacing;
QDoubleSpinBox* _gridPosition;
QList<MetavoxelTool*> _tools;
QComboBox* _toolBox;
QGroupBox* _value;
QScrollArea* _valueArea;
static ProgramObject _gridProgram;
};
/// Base class for editor tools.
class MetavoxelTool : public QWidget {
Q_OBJECT
public:
MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue = true);
bool getUsesValue() const { return _usesValue; }
virtual bool appliesTo(const AttributePointer& attribute) const;
virtual void simulate(float deltaTime);
/// Renders the tool's interface, if any.
virtual void render();
protected:
MetavoxelEditor* _editor;
bool _usesValue;
};
/// Allows setting the value of a region by dragging out a box.
class BoxSetTool : public MetavoxelTool {
Q_OBJECT
public:
BoxSetTool(MetavoxelEditor* editor);
virtual void render();
virtual bool eventFilter(QObject* watched, QEvent* event);
private:
void resetState();
void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
enum State { HOVERING_STATE, DRAGGING_STATE, RAISING_STATE };
State _state;
glm::vec2 _mousePosition; ///< the position of the mouse in rotated space
glm::vec2 _startPosition; ///< the first corner of the selection base
glm::vec2 _endPosition; ///< the second corner of the selection base
float _height; ///< the selection height
};
/// Allows setting the value across the entire space.
class GlobalSetTool : public MetavoxelTool {
Q_OBJECT
public:
static ProgramObject _gridProgram;
GlobalSetTool(MetavoxelEditor* editor);
private slots:
void apply();
};
/// Allows inserting a spanner into the scene.
class InsertSpannerTool : public MetavoxelTool {
Q_OBJECT
public:
InsertSpannerTool(MetavoxelEditor* editor);
virtual void simulate(float deltaTime);
virtual void render();
virtual bool appliesTo(const AttributePointer& attribute) const;
virtual bool eventFilter(QObject* watched, QEvent* event);
private slots:
void insert();
};
/// Allows removing a spanner from the scene.
class RemoveSpannerTool : public MetavoxelTool {
Q_OBJECT
public:
RemoveSpannerTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
virtual bool eventFilter(QObject* watched, QEvent* event);
};
/// Allows removing all spanners from the scene.
class ClearSpannersTool : public MetavoxelTool {
Q_OBJECT
public:
ClearSpannersTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
private slots:
void clear();
};
#endif /* defined(__interface__MetavoxelEditor__) */

View file

@ -11,12 +11,12 @@ set(TARGET_NAME metavoxels)
find_package(Qt5Network REQUIRED)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME} ${AUTOMTC_SRC})
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
include(${MACRO_DIR}/IncludeGLM.cmake)

View file

@ -13,6 +13,7 @@
REGISTER_META_OBJECT(QRgbAttribute)
REGISTER_META_OBJECT(SharedObjectAttribute)
REGISTER_META_OBJECT(SharedObjectSetAttribute)
AttributeRegistry* AttributeRegistry::getInstance() {
static AttributeRegistry registry;
@ -22,6 +23,7 @@ AttributeRegistry* AttributeRegistry::getInstance() {
AttributeRegistry::AttributeRegistry() :
_guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject,
SharedObjectPointer(new DefaultMetavoxelGuide())))),
_spannersAttribute(registerAttribute(new SharedObjectSetAttribute("spanners", &Spanner::staticMetaObject))),
_colorAttribute(registerAttribute(new QRgbAttribute("color"))),
_normalAttribute(registerAttribute(new QRgbAttribute("normal", qRgb(0, 127, 0)))) {
}
@ -53,6 +55,10 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
return pointer;
}
void AttributeRegistry::deregisterAttribute(const QString& name) {
_attributes.remove(name);
}
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
QScriptEngine::PreferExistingWrapperObject);
@ -223,3 +229,29 @@ QWidget* SharedObjectAttribute::createEditor(QWidget* parent) const {
editor->setObject(_defaultValue);
return editor;
}
SharedObjectSetAttribute::SharedObjectSetAttribute(const QString& name, const QMetaObject* metaObject) :
InlineAttribute<SharedObjectSet>(name),
_metaObject(metaObject) {
}
void SharedObjectSetAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
in >> *((SharedObjectSet*)&value);
}
void SharedObjectSetAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
out << decodeInline<SharedObjectSet>(value);
}
bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const {
for (int i = 0; i < MERGE_COUNT; i++) {
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
return false;
}
}
return true;
}
QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const {
return new SharedObjectEditor(_metaObject, parent);
}

View file

@ -24,7 +24,7 @@ class QScriptValue;
class Attribute;
typedef QSharedPointer<Attribute> AttributePointer;
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
/// Maintains information about metavoxel attribute types.
class AttributeRegistry {
@ -48,15 +48,21 @@ public:
/// attribute
AttributePointer registerAttribute(AttributePointer attribute);
/// Deregisters an attribute.
void deregisterAttribute(const QString& name);
/// Retrieves an attribute by name.
AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); }
/// Returns a reference to the attribute hash.
const QHash<QString, AttributePointer>& getAttributes() const { return _attributes; }
/// Returns a reference to the standard PolymorphicDataPointer "guide" attribute.
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
/// Returns a reference to the standard SharedObjectSet "spanners" attribute.
const AttributePointer& getSpannersAttribute() const { return _spannersAttribute; }
/// Returns a reference to the standard QRgb "color" attribute.
const AttributePointer& getColorAttribute() const { return _colorAttribute; }
@ -69,6 +75,7 @@ private:
QHash<QString, AttributePointer> _attributes;
AttributePointer _guideAttribute;
AttributePointer _spannersAttribute;
AttributePointer _colorAttribute;
AttributePointer _normalAttribute;
};
@ -141,7 +148,7 @@ public:
};
/// Represents a registered attribute.
class Attribute : public QObject {
class Attribute : public SharedObject {
Q_OBJECT
public:
@ -260,7 +267,8 @@ class SharedObjectAttribute : public InlineAttribute<SharedObjectPointer> {
public:
Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(), const QMetaObject* metaObject = NULL,
Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(),
const QMetaObject* metaObject = &SharedObject::staticMetaObject,
const SharedObjectPointer& defaultValue = SharedObjectPointer());
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
@ -277,4 +285,28 @@ private:
const QMetaObject* _metaObject;
};
/// An attribute that takes the form of a set of shared objects.
class SharedObjectSetAttribute : public InlineAttribute<SharedObjectSet> {
Q_OBJECT
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject)
public:
Q_INVOKABLE SharedObjectSetAttribute(const QString& name = QString(),
const QMetaObject* metaObject = &SharedObject::staticMetaObject);
const QMetaObject* getMetaObject() const { return _metaObject; }
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[]) const;
virtual QWidget* createEditor(QWidget* parent = NULL) const;
private:
const QMetaObject* _metaObject;
};
#endif /* defined(__interface__AttributeRegistry__) */

View file

@ -68,8 +68,8 @@ IDStreamer& IDStreamer::operator>>(int& value) {
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
getMetaObjects().insert(className, metaObject);
// register it as a subclass of all of its superclasses
for (const QMetaObject* superClass = metaObject->superClass(); superClass != NULL; superClass = superClass->superClass()) {
// register it as a subclass of itself and all of its superclasses
for (const QMetaObject* superClass = metaObject; superClass != NULL; superClass = superClass->superClass()) {
getMetaObjectSubClasses().insert(superClass, metaObject);
}
return 0;
@ -81,6 +81,10 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
return 0;
}
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
return getMetaObjects().value(className);
}
QList<const QMetaObject*> Bitstream::getMetaObjectSubClasses(const QMetaObject* metaObject) {
return getMetaObjectSubClasses().values(metaObject);
}
@ -351,17 +355,7 @@ Bitstream& Bitstream::operator>>(QObject*& object) {
object = NULL;
return *this;
}
object = metaObject->newInstance();
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (!property.isStored(object)) {
continue;
}
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
if (streamer) {
property.write(object, streamer->read(*this));
}
}
readProperties(object = metaObject->newInstance());
return *this;
}
@ -472,13 +466,34 @@ Bitstream& Bitstream::operator>(QScriptString& string) {
}
Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
return *this << object.data();
if (!object) {
return *this << (int)0;
}
return *this << object->getID() << (QObject*)object.data();
}
Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
QObject* rawObject;
*this >> rawObject;
object = static_cast<SharedObject*>(rawObject);
int id;
*this >> id;
if (id == 0) {
object = SharedObjectPointer();
return *this;
}
QPointer<SharedObject>& pointer = _transientSharedObjects[id];
if (pointer) {
const QMetaObject* metaObject;
_metaObjectStreamer >> metaObject;
if (metaObject != pointer->metaObject()) {
qWarning() << "Class mismatch: " << pointer->metaObject()->className() << metaObject->className();
}
readProperties(pointer.data());
} else {
QObject* rawObject;
*this >> rawObject;
pointer = static_cast<SharedObject*>(rawObject);
}
object = static_cast<SharedObject*>(pointer.data());
return *this;
}
@ -488,6 +503,20 @@ void Bitstream::clearSharedObject() {
emit sharedObjectCleared(_sharedObjectStreamer.takePersistentID(object));
}
void Bitstream::readProperties(QObject* object) {
const QMetaObject* metaObject = object->metaObject();
for (int i = 0; i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (!property.isStored(object)) {
continue;
}
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
if (streamer) {
property.write(object, streamer->read(*this));
}
}
}
QHash<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
static QHash<QByteArray, const QMetaObject*> metaObjects;
return metaObjects;

View file

@ -11,6 +11,7 @@
#include <QHash>
#include <QMetaType>
#include <QPointer>
#include <QScriptString>
#include <QSharedPointer>
#include <QVariant>
@ -31,7 +32,7 @@ class Bitstream;
class OwnedAttributeValue;
class TypeStreamer;
typedef QSharedPointer<Attribute> AttributePointer;
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
/// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that
/// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows
@ -196,6 +197,9 @@ public:
/// \return zero; the function only returns a value so that it can be used in static initialization
static int registerTypeStreamer(int type, TypeStreamer* streamer);
/// Returns the meta-object registered under the supplied class name, if any.
static const QMetaObject* getMetaObject(const QByteArray& className);
/// Returns the list of registered subclasses for the supplied meta-object.
static QList<const QMetaObject*> getMetaObjectSubClasses(const QMetaObject* metaObject);
@ -266,6 +270,9 @@ public:
template<class T> Bitstream& operator<<(const QList<T>& list);
template<class T> Bitstream& operator>>(QList<T>& list);
template<class T> Bitstream& operator<<(const QSet<T>& set);
template<class T> Bitstream& operator>>(QSet<T>& set);
template<class K, class V> Bitstream& operator<<(const QHash<K, V>& hash);
template<class K, class V> Bitstream& operator>>(QHash<K, V>& hash);
@ -311,6 +318,8 @@ private slots:
void clearSharedObject();
private:
void readProperties(QObject* object);
QDataStream& _underlying;
quint8 _byte;
@ -322,6 +331,8 @@ private:
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
RepeatedValueStreamer<SharedObjectPointer> _sharedObjectStreamer;
QHash<int, QPointer<SharedObject> > _transientSharedObjects;
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
static QHash<int, const TypeStreamer*>& getTypeStreamers();
@ -348,6 +359,27 @@ template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& list) {
return *this;
}
template<class T> inline Bitstream& Bitstream::operator<<(const QSet<T>& set) {
*this << set.size();
foreach (const T& entry, set) {
*this << entry;
}
return *this;
}
template<class T> inline Bitstream& Bitstream::operator>>(QSet<T>& set) {
int size;
*this >> size;
set.clear();
set.reserve(size);
for (int i = 0; i < size; i++) {
T entry;
*this >> entry;
set.insert(entry);
}
return *this;
}
template<class K, class V> inline Bitstream& Bitstream::operator<<(const QHash<K, V>& hash) {
*this << hash.size();
for (typename QHash<K, V>::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) {

View file

@ -42,6 +42,7 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject*
_outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian);
connect(&_outputStream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
connect(this, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&)));
memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize);
}
@ -182,7 +183,7 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
QVariant data;
_inputStream >> data;
if ((int)i >= _receivedHighPriorityMessages) {
handleHighPriorityMessage(data);
emit receivedHighPriorityMessage(data);
}
}
_receivedHighPriorityMessages = highPriorityMessageCount;
@ -208,9 +209,22 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
}
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
// for now, high priority
ClearSharedObjectMessage message = { id };
sendHighPriorityMessage(QVariant::fromValue(message));
// send it low-priority unless the channel has messages disabled
ReliableChannel* channel = getReliableOutputChannel();
if (channel->getMessagesEnabled()) {
ClearMainChannelSharedObjectMessage message = { id };
channel->sendMessage(QVariant::fromValue(message));
} else {
ClearSharedObjectMessage message = { id };
sendHighPriorityMessage(QVariant::fromValue(message));
}
}
void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
if (data.userType() == ClearSharedObjectMessage::Type) {
_inputStream.clearSharedObject(data.value<ClearSharedObjectMessage>().id);
}
}
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
@ -303,15 +317,6 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<Chann
} while(offset < packet.size());
}
void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
if (data.userType() == ClearSharedObjectMessage::Type) {
_inputStream.clearSharedObject(data.value<ClearSharedObjectMessage>().id);
} else {
emit receivedHighPriorityMessage(data);
}
}
const int INITIAL_CIRCULAR_BUFFER_CAPACITY = 16;
CircularBuffer::CircularBuffer(QObject* parent) :
@ -343,16 +348,32 @@ void CircularBuffer::remove(int length) {
}
QByteArray CircularBuffer::readBytes(int offset, int length) const {
// write in up to two segments
QByteArray bytes(length, 0);
readBytes(offset, length, bytes.data());
return bytes;
}
void CircularBuffer::readBytes(int offset, int length, char* data) const {
// read in up to two segments
QByteArray array;
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
array.append(_data.constData() + start, firstSegment);
memcpy(data, _data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
array.append(_data.constData(), secondSegment);
memcpy(data + firstSegment, _data.constData(), secondSegment);
}
}
void CircularBuffer::writeBytes(int offset, int length, const char* data) {
// write in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
memcpy(_data.data() + start, data, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(_data.data(), data + firstSegment, secondSegment);
}
return array;
}
void CircularBuffer::writeToStream(int offset, int length, QDataStream& out) const {
@ -561,7 +582,14 @@ int ReliableChannel::getBytesAvailable() const {
}
void ReliableChannel::sendMessage(const QVariant& message) {
// write a placeholder for the length, then fill it in when we know what it is
int placeholder = _buffer.pos();
_dataStream << (quint32)0;
_bitstream << message;
_bitstream.flush();
quint32 length = _buffer.pos() - placeholder;
_buffer.writeBytes(placeholder, sizeof(quint32), (const char*)&length);
}
void ReliableChannel::sendClearSharedObjectMessage(int id) {
@ -569,6 +597,16 @@ void ReliableChannel::sendClearSharedObjectMessage(int id) {
sendMessage(QVariant::fromValue(message));
}
void ReliableChannel::handleMessage(const QVariant& message) {
if (message.userType() == ClearSharedObjectMessage::Type) {
_bitstream.clearSharedObject(message.value<ClearSharedObjectMessage>().id);
} else if (message.userType() == ClearMainChannelSharedObjectMessage::Type) {
static_cast<DatagramSequencer*>(parent())->_inputStream.clearSharedObject(
message.value<ClearMainChannelSharedObjectMessage>().id);
}
}
ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) :
QObject(sequencer),
_index(index),
@ -576,12 +614,14 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
_bitstream(_dataStream),
_priority(1.0f),
_offset(0),
_writePosition(0) {
_writePosition(0),
_messagesEnabled(true) {
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
_dataStream.setByteOrder(QDataStream::LittleEndian);
connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
connect(this, SIGNAL(receivedMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
}
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
@ -688,10 +728,32 @@ void ReliableChannel::readData(QDataStream& in) {
readSome = true;
}
}
if (!readSome) {
return;
}
// let listeners know that there's data to read
if (readSome) {
emit _buffer.readyRead();
forever {
// if we're expecting a message, peek into the buffer to see if we have the whole thing.
// if so, read it in, handle it, and loop back around in case there are more
if (_messagesEnabled) {
quint32 available = _buffer.bytesAvailable();
if (available >= sizeof(quint32)) {
quint32 length;
_buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length);
if (available >= length) {
_dataStream.skipRawData(sizeof(quint32));
QVariant message;
_bitstream >> message;
_bitstream.reset();
emit receivedMessage(message);
continue;
}
}
// otherwise, just let whoever's listening know that data is available
} else {
emit _buffer.readyRead();
}
break;
}
// prune any read data from the buffer

View file

@ -70,7 +70,7 @@ public:
/// Processes a datagram received from the other party, emitting readyToRead when the entire packet
/// has been successfully assembled.
void receivedDatagram(const QByteArray& datagram);
Q_INVOKABLE void receivedDatagram(const QByteArray& datagram);
signals:
@ -94,6 +94,7 @@ signals:
private slots:
void sendClearSharedObjectMessage(int id);
void handleHighPriorityMessage(const QVariant& data);
private:
@ -133,8 +134,6 @@ private:
/// readyToWrite) as necessary.
void sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans);
void handleHighPriorityMessage(const QVariant& data);
QList<SendRecord> _sendRecords;
QList<ReceiveRecord> _receiveRecords;
@ -185,6 +184,12 @@ public:
/// Reads part of the data from the buffer.
QByteArray readBytes(int offset, int length) const;
/// Reads part of the data from the buffer.
void readBytes(int offset, int length, char* data) const;
/// Writes to part of the data in the buffer.
void writeBytes(int offset, int length, const char* data);
/// Writes part of the buffer to the supplied stream.
void writeToStream(int offset, int length, QDataStream& out) const;
@ -267,12 +272,22 @@ public:
int getBytesAvailable() const;
/// Sets whether we expect to write/read framed messages.
void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; }
bool getMessagesEnabled() const { return _messagesEnabled; }
/// Sends a framed message on this channel.
void sendMessage(const QVariant& message);
signals:
void receivedMessage(const QVariant& message);
private slots:
void sendClearSharedObjectMessage(int id);
void handleMessage(const QVariant& message);
private:
friend class DatagramSequencer;
@ -297,6 +312,7 @@ private:
int _offset;
int _writePosition;
SpanList _acknowledged;
bool _messagesEnabled;
};
#endif /* defined(__interface__DatagramSequencer__) */

View file

@ -18,6 +18,8 @@ REGISTER_META_OBJECT(MetavoxelGuide)
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
REGISTER_META_OBJECT(ScriptedMetavoxelGuide)
REGISTER_META_OBJECT(ThrobbingMetavoxelGuide)
REGISTER_META_OBJECT(Spanner)
REGISTER_META_OBJECT(StaticModel)
MetavoxelData::MetavoxelData() : _size(1.0f) {
}
@ -43,17 +45,19 @@ MetavoxelData& MetavoxelData::operator=(const MetavoxelData& other) {
Box MetavoxelData::getBounds() const {
float halfSize = _size * 0.5f;
Box bounds = { glm::vec3(-halfSize, -halfSize, -halfSize), glm::vec3(halfSize, halfSize, halfSize) };
return bounds;
return Box(glm::vec3(-halfSize, -halfSize, -halfSize), glm::vec3(halfSize, halfSize, halfSize));
}
void MetavoxelData::guide(MetavoxelVisitor& visitor) {
// let the visitor know we're about to begin a tour
visitor.prepare();
// 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()), { glm::vec3(_size, _size, _size) * -0.5f, _size,
QVector<AttributeValue>(inputs.size() + 1), QVector<AttributeValue>(outputs.size()) } };
QVector<AttributeValue>(inputs.size() + 1), QVector<OwnedAttributeValue>(outputs.size()) } };
for (int i = 0; i < inputs.size(); i++) {
MetavoxelNode* node = _roots.value(inputs.at(i));
firstVisitation.inputNodes[i] = node;
@ -70,7 +74,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
static_cast<MetavoxelGuide*>(firstVisitation.info.inputValues.last().getInlineValue<
SharedObjectPointer>().data())->guide(firstVisitation);
for (int i = 0; i < outputs.size(); i++) {
AttributeValue& value = firstVisitation.info.outputValues[i];
OwnedAttributeValue& value = firstVisitation.info.outputValues[i];
if (!value.getAttribute()) {
continue;
}
@ -88,6 +92,103 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
}
}
class InsertVisitor : public MetavoxelVisitor {
public:
InsertVisitor(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
virtual bool visit(MetavoxelInfo& info);
private:
const AttributePointer& _attribute;
const Box& _bounds;
float _longestSide;
const SharedObjectPointer& _object;
};
InsertVisitor::InsertVisitor(const AttributePointer& attribute, const Box& bounds,
float granularity, const SharedObjectPointer& object) :
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>() << attribute),
_attribute(attribute),
_bounds(bounds),
_longestSide(qMax(bounds.getLongestSide(), granularity)),
_object(object) {
}
bool InsertVisitor::visit(MetavoxelInfo& info) {
if (!info.getBounds().intersects(_bounds)) {
return false;
}
if (info.size > _longestSide) {
return true;
}
SharedObjectSet set = info.inputValues.at(0).getInlineValue<SharedObjectSet>();
set.insert(_object);
info.outputValues[0] = AttributeValue(_attribute, encodeInline(set));
return false;
}
void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds,
float granularity, const SharedObjectPointer& object) {
// expand to fit the entire bounds
while (!getBounds().contains(bounds)) {
expand();
}
InsertVisitor visitor(attribute, bounds, granularity, object);
guide(visitor);
}
class RemoveVisitor : public MetavoxelVisitor {
public:
RemoveVisitor(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
virtual bool visit(MetavoxelInfo& info);
private:
const AttributePointer& _attribute;
const Box& _bounds;
float _longestSide;
const SharedObjectPointer& _object;
};
RemoveVisitor::RemoveVisitor(const AttributePointer& attribute, const Box& bounds,
float granularity, const SharedObjectPointer& object) :
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>() << attribute),
_attribute(attribute),
_bounds(bounds),
_longestSide(qMax(bounds.getLongestSide(), granularity)),
_object(object) {
}
bool RemoveVisitor::visit(MetavoxelInfo& info) {
if (!info.getBounds().intersects(_bounds)) {
return false;
}
if (info.size > _longestSide) {
return true;
}
SharedObjectSet set = info.inputValues.at(0).getInlineValue<SharedObjectSet>();
set.remove(_object);
info.outputValues[0] = AttributeValue(_attribute, encodeInline(set));
return false;
}
void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds,
float granularity, const SharedObjectPointer& object) {
RemoveVisitor visitor(attribute, bounds, granularity, object);
guide(visitor);
}
void MetavoxelData::clear(const AttributePointer& attribute) {
MetavoxelNode* node = _roots.take(attribute);
if (node) {
node->decrementReferenceCount(attribute);
}
}
const int X_MAXIMUM_FLAG = 1;
const int Y_MAXIMUM_FLAG = 2;
const int Z_MAXIMUM_FLAG = 4;
@ -432,6 +533,32 @@ MetavoxelVisitor::MetavoxelVisitor(const QVector<AttributePointer>& inputs, cons
MetavoxelVisitor::~MetavoxelVisitor() {
}
void MetavoxelVisitor::prepare() {
// nothing by default
}
SpannerVisitor::SpannerVisitor(const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& inputs,
const QVector<AttributePointer>& outputs) :
MetavoxelVisitor(inputs + spannerInputs, outputs),
_spannerInputCount(spannerInputs.size()) {
}
void SpannerVisitor::prepare() {
Spanner::incrementVisit();
}
bool SpannerVisitor::visit(MetavoxelInfo& info) {
for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) {
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
Spanner* spanner = static_cast<Spanner*>(object.data());
if (spanner->testAndSetVisited()) {
visit(spanner);
}
}
}
return !info.isLeaf;
}
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
}
@ -439,7 +566,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
visitation.info.isLeaf = visitation.allInputNodesLeaves();
bool keepGoing = visitation.visitor.visit(visitation.info);
for (int i = 0; i < visitation.outputNodes.size(); i++) {
AttributeValue& value = visitation.info.outputValues[i];
OwnedAttributeValue& value = visitation.info.outputValues[i];
if (!value.getAttribute()) {
continue;
}
@ -457,7 +584,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
MetavoxelVisitation nextVisitation = { &visitation, visitation.visitor,
QVector<MetavoxelNode*>(visitation.inputNodes.size()), QVector<MetavoxelNode*>(visitation.outputNodes.size()),
{ glm::vec3(), visitation.info.size * 0.5f, QVector<AttributeValue>(visitation.inputNodes.size()),
QVector<AttributeValue>(visitation.outputNodes.size()) } };
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
for (int j = 0; j < visitation.inputNodes.size(); j++) {
MetavoxelNode* node = visitation.inputNodes.at(j);
@ -478,12 +605,12 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
static_cast<MetavoxelGuide*>(nextVisitation.info.inputValues.last().getInlineValue<
SharedObjectPointer>().data())->guide(nextVisitation);
for (int j = 0; j < nextVisitation.outputNodes.size(); j++) {
AttributeValue& value = nextVisitation.info.outputValues[j];
OwnedAttributeValue& value = nextVisitation.info.outputValues[j];
if (!value.getAttribute()) {
continue;
}
// replace the child
AttributeValue& parentValue = visitation.info.outputValues[j];
OwnedAttributeValue& parentValue = visitation.info.outputValues[j];
if (!parentValue.getAttribute()) {
// shallow-copy the parent node on first change
parentValue = value;
@ -511,7 +638,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
}
}
for (int i = 0; i < visitation.outputNodes.size(); i++) {
AttributeValue& value = visitation.info.outputValues[i];
OwnedAttributeValue& value = visitation.info.outputValues[i];
if (value.getAttribute()) {
MetavoxelNode* node = visitation.outputNodes.at(i);
node->mergeChildren(value.getAttribute());
@ -674,3 +801,96 @@ AttributeValue MetavoxelVisitation::getInheritedOutputValue(int index) const {
return AttributeValue(visitor.getOutputs().at(index));
}
const float DEFAULT_GRANULARITY = 0.01f;
Spanner::Spanner() :
_granularity(DEFAULT_GRANULARITY),
_lastVisit(0),
_renderer(NULL) {
}
void Spanner::setBounds(const Box& bounds) {
if (_bounds == bounds) {
return;
}
emit boundsWillChange();
emit boundsChanged(_bounds = bounds);
}
bool Spanner::testAndSetVisited() {
if (_lastVisit == _visit) {
return false;
}
_lastVisit = _visit;
return true;
}
SpannerRenderer* Spanner::getRenderer() {
if (!_renderer) {
QByteArray className = getRendererClassName();
const QMetaObject* metaObject = Bitstream::getMetaObject(className);
if (!metaObject) {
qDebug() << "Unknown class name:" << className;
metaObject = &SpannerRenderer::staticMetaObject;
}
_renderer = static_cast<SpannerRenderer*>(metaObject->newInstance());
_renderer->setParent(this);
_renderer->init(this);
}
return _renderer;
}
QByteArray Spanner::getRendererClassName() const {
return "SpannerRendererer";
}
int Spanner::_visit = 0;
SpannerRenderer::SpannerRenderer() {
}
void SpannerRenderer::init(Spanner* spanner) {
// nothing by default
}
void SpannerRenderer::simulate(float deltaTime) {
// nothing by default
}
void SpannerRenderer::render(float alpha) {
// nothing by default
}
Transformable::Transformable() : _scale(1.0f) {
}
void Transformable::setTranslation(const glm::vec3& translation) {
if (_translation != translation) {
emit translationChanged(_translation = translation);
}
}
void Transformable::setRotation(const glm::vec3& rotation) {
if (_rotation != rotation) {
emit rotationChanged(_rotation = rotation);
}
}
void Transformable::setScale(float scale) {
if (_scale != scale) {
emit scaleChanged(_scale = scale);
}
}
StaticModel::StaticModel() {
}
void StaticModel::setURL(const QUrl& url) {
if (_url != url) {
emit urlChanged(_url = url);
}
}
QByteArray StaticModel::getRendererClassName() const {
return "StaticModelRenderer";
}

View file

@ -28,6 +28,8 @@ class MetavoxelNode;
class MetavoxelVisitation;
class MetavoxelVisitor;
class NetworkValue;
class Spanner;
class SpannerRenderer;
/// The base metavoxel representation shared between server and client.
class MetavoxelData {
@ -45,7 +47,13 @@ public:
/// Applies the specified visitor to the contained voxels.
void guide(MetavoxelVisitor& visitor);
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
void clear(const AttributePointer& attribute);
/// Expands the tree, increasing its capacity in all dimensions.
void expand();
@ -121,15 +129,18 @@ public:
glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel
float size; ///< the size of the voxel in all dimensions
QVector<AttributeValue> inputValues;
QVector<AttributeValue> outputValues;
QVector<OwnedAttributeValue> outputValues;
bool isLeaf;
Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); }
};
/// Interface for visitors to metavoxels.
class MetavoxelVisitor {
public:
MetavoxelVisitor(const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs);
MetavoxelVisitor(const QVector<AttributePointer>& inputs,
const QVector<AttributePointer>& outputs = QVector<AttributePointer>());
virtual ~MetavoxelVisitor();
/// Returns a reference to the list of input attributes desired.
@ -138,6 +149,9 @@ public:
/// Returns a reference to the list of output attributes provided.
const QVector<AttributePointer>& getOutputs() const { return _outputs; }
/// Prepares for a new tour of the metavoxel data.
virtual void prepare();
/// Visits a metavoxel.
/// \param info the metavoxel data
/// \return if true, continue descending; if false, stop
@ -151,6 +165,25 @@ protected:
typedef QSharedPointer<MetavoxelVisitor> MetavoxelVisitorPointer;
/// Interface for visitors to spanners.
class SpannerVisitor : public MetavoxelVisitor {
public:
SpannerVisitor(const QVector<AttributePointer>& spannerInputs,
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
const QVector<AttributePointer>& outputs = QVector<AttributePointer>());
/// Visits a spanner.
virtual void visit(Spanner* spanner) = 0;
virtual void prepare();
virtual bool visit(MetavoxelInfo& info);
protected:
int _spannerInputCount;
};
/// Interface for objects that guide metavoxel visitors.
class MetavoxelGuide : public SharedObject {
Q_OBJECT
@ -242,4 +275,121 @@ public:
AttributeValue getInheritedOutputValue(int index) const;
};
/// An object that spans multiple octree cells.
class Spanner : public SharedObject {
Q_OBJECT
Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false)
Q_PROPERTY(float granularity MEMBER _granularity DESIGNABLE false)
public:
/// Increments the value of the global visit counter.
static void incrementVisit() { _visit++; }
Spanner();
void setBounds(const Box& bounds);
const Box& getBounds() const { return _bounds; }
void setGranularity(float granularity) { _granularity = granularity; }
float getGranularity() const { return _granularity; }
/// 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();
/// Returns a pointer to the renderer, creating it if necessary.
SpannerRenderer* getRenderer();
signals:
void boundsWillChange();
void boundsChanged(const Box& bounds);
protected:
/// Returns the name of the class to instantiate in order to render this spanner.
virtual QByteArray getRendererClassName() const;
private:
Box _bounds;
float _granularity;
int _lastVisit; ///< the identifier of the last visit
SpannerRenderer* _renderer;
static int _visit; ///< the global visit counter
};
/// Base class for objects that can render spanners.
class SpannerRenderer : public QObject {
Q_OBJECT
public:
Q_INVOKABLE SpannerRenderer();
virtual void init(Spanner* spanner);
virtual void simulate(float deltaTime);
virtual void render(float alpha);
};
/// An object with a 3D transform.
class Transformable : public Spanner {
Q_OBJECT
Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged)
Q_PROPERTY(glm::vec3 rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged)
Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged)
public:
Transformable();
void setTranslation(const glm::vec3& translation);
const glm::vec3& getTranslation() const { return _translation; }
void setRotation(const glm::vec3& rotation);
const glm::vec3& getRotation() const { return _rotation; }
void setScale(float scale);
float getScale() const { return _scale; }
signals:
void translationChanged(const glm::vec3& translation);
void rotationChanged(const glm::vec3& rotation);
void scaleChanged(float scale);
private:
glm::vec3 _translation;
glm::vec3 _rotation;
float _scale;
};
/// A static 3D model loaded from the network.
class StaticModel : public Transformable {
Q_OBJECT
Q_PROPERTY(QUrl url MEMBER _url WRITE setURL NOTIFY urlChanged)
public:
Q_INVOKABLE StaticModel();
void setURL(const QUrl& url);
const QUrl& getURL() const { return _url; }
signals:
void urlChanged(const QUrl& url);
protected:
virtual QByteArray getRendererClassName() const;
private:
QUrl _url;
};
#endif /* defined(__interface__MetavoxelData__) */

View file

@ -9,24 +9,35 @@
#include "MetavoxelData.h"
#include "MetavoxelMessages.h"
class EditVisitor : public MetavoxelVisitor {
void MetavoxelEditMessage::apply(MetavoxelData& data) const {
static_cast<const MetavoxelEdit*>(edit.data())->apply(data);
}
MetavoxelEdit::~MetavoxelEdit() {
}
BoxSetEdit::BoxSetEdit(const Box& region, float granularity, const OwnedAttributeValue& value) :
region(region), granularity(granularity), value(value) {
}
class BoxSetEditVisitor : public MetavoxelVisitor {
public:
EditVisitor(const MetavoxelEditMessage& edit);
BoxSetEditVisitor(const BoxSetEdit& edit);
virtual bool visit(MetavoxelInfo& info);
private:
const MetavoxelEditMessage& _edit;
const BoxSetEdit& _edit;
};
EditVisitor::EditVisitor(const MetavoxelEditMessage& edit) :
BoxSetEditVisitor::BoxSetEditVisitor(const BoxSetEdit& edit) :
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
_edit(edit) {
}
bool EditVisitor::visit(MetavoxelInfo& info) {
bool BoxSetEditVisitor::visit(MetavoxelInfo& info) {
// find the intersection between volume and voxel
glm::vec3 minimum = glm::max(info.minimum, _edit.region.minimum);
glm::vec3 maximum = glm::min(info.minimum + glm::vec3(info.size, info.size, info.size), _edit.region.maximum);
@ -48,12 +59,69 @@ bool EditVisitor::visit(MetavoxelInfo& info) {
return true; // subdivide
}
void MetavoxelEditMessage::apply(MetavoxelData& data) const {
void BoxSetEdit::apply(MetavoxelData& data) const {
// expand to fit the entire edit
while (!data.getBounds().contains(region)) {
data.expand();
}
EditVisitor visitor(*this);
BoxSetEditVisitor visitor(*this);
data.guide(visitor);
}
GlobalSetEdit::GlobalSetEdit(const OwnedAttributeValue& value) :
value(value) {
}
class GlobalSetEditVisitor : public MetavoxelVisitor {
public:
GlobalSetEditVisitor(const GlobalSetEdit& edit);
virtual bool visit(MetavoxelInfo& info);
private:
const GlobalSetEdit& _edit;
};
GlobalSetEditVisitor::GlobalSetEditVisitor(const GlobalSetEdit& edit) :
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
_edit(edit) {
}
bool GlobalSetEditVisitor::visit(MetavoxelInfo& info) {
info.outputValues[0] = _edit.value;
return false; // entirely contained
}
void GlobalSetEdit::apply(MetavoxelData& data) const {
GlobalSetEditVisitor visitor(*this);
data.guide(visitor);
}
InsertSpannerEdit::InsertSpannerEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) :
attribute(attribute),
spanner(spanner) {
}
void InsertSpannerEdit::apply(MetavoxelData& data) const {
Spanner* spanner = static_cast<Spanner*>(this->spanner.data());
data.insert(attribute, spanner->getBounds(), spanner->getGranularity(), this->spanner);
}
RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) :
attribute(attribute),
id(id) {
}
void RemoveSpannerEdit::apply(MetavoxelData& data) const {
}
ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) :
attribute(attribute) {
}
void ClearSpannersEdit::apply(MetavoxelData& data) const {
data.clear(attribute);
}

View file

@ -32,6 +32,17 @@ public:
DECLARE_STREAMABLE_METATYPE(ClearSharedObjectMessage)
/// Clears the mapping for a shared object on the main channel (as opposed to the one on which the message was sent).
class ClearMainChannelSharedObjectMessage {
STREAMABLE
public:
STREAM int id;
};
DECLARE_STREAMABLE_METATYPE(ClearMainChannelSharedObjectMessage)
/// A message containing the state of a client.
class ClientStateMessage {
STREAMABLE
@ -56,13 +67,101 @@ class MetavoxelEditMessage {
public:
STREAM Box region;
STREAM float granularity;
STREAM OwnedAttributeValue value;
STREAM QVariant edit;
void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(MetavoxelEditMessage)
/// Abstract base class for edits.
class MetavoxelEdit {
public:
virtual ~MetavoxelEdit();
virtual void apply(MetavoxelData& data) const = 0;
};
/// An edit that sets the region within a box to a value.
class BoxSetEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM Box region;
STREAM float granularity;
STREAM OwnedAttributeValue value;
BoxSetEdit(const Box& region = Box(), float granularity = 0.0f,
const OwnedAttributeValue& value = OwnedAttributeValue());
virtual void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(BoxSetEdit)
/// An edit that sets the entire tree to a value.
class GlobalSetEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM OwnedAttributeValue value;
GlobalSetEdit(const OwnedAttributeValue& value = OwnedAttributeValue());
virtual void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(GlobalSetEdit)
/// An edit that inserts a spanner into the tree.
class InsertSpannerEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM AttributePointer attribute;
STREAM SharedObjectPointer spanner;
InsertSpannerEdit(const AttributePointer& attribute = AttributePointer(),
const SharedObjectPointer& spanner = SharedObjectPointer());
virtual void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(InsertSpannerEdit)
/// An edit that removes a spanner from the tree.
class RemoveSpannerEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM AttributePointer attribute;
STREAM int id;
RemoveSpannerEdit(const AttributePointer& attribute = AttributePointer(), int id = 0);
virtual void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(RemoveSpannerEdit)
/// An edit that clears all spanners from the tree.
class ClearSpannersEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM AttributePointer attribute;
ClearSpannersEdit(const AttributePointer& attribute = AttributePointer());
virtual void apply(MetavoxelData& data) const;
};
DECLARE_STREAMABLE_METATYPE(ClearSpannersEdit)
#endif /* defined(__interface__MetavoxelMessages__) */

View file

@ -17,12 +17,10 @@
#include <QMetaType>
#include <QPushButton>
#include <QScriptEngine>
#include <QSettings>
#include <QVBoxLayout>
#include <QtDebug>
#include <HifiSockAddr.h>
#include <PacketHeaders.h>
#include "MetavoxelUtil.h"
#include "ScriptCache.h"
@ -53,6 +51,7 @@ public:
DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) {
setMinimum(-FLT_MAX);
setMaximum(FLT_MAX);
}
DelegatingItemEditorFactory::DelegatingItemEditorFactory() :
@ -104,12 +103,24 @@ static QItemEditorCreatorBase* createDoubleEditorCreator() {
return creator;
}
static QItemEditorCreatorBase* createQMetaObjectEditorCreator() {
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QMetaObjectEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<const QMetaObject*>(), creator);
return creator;
}
static QItemEditorCreatorBase* createQColorEditorCreator() {
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QColorEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<QColor>(), creator);
return creator;
}
static QItemEditorCreatorBase* createQUrlEditorCreator() {
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QUrlEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<QUrl>(), creator);
return creator;
}
static QItemEditorCreatorBase* createVec3EditorCreator() {
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<Vec3Editor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::vec3>(), creator);
@ -123,36 +134,57 @@ static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
}
static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator();
static QItemEditorCreatorBase* qMetaObjectEditorCreator = createQMetaObjectEditorCreator();
static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator();
static QItemEditorCreatorBase* qUrlEditorCreator = createQUrlEditorCreator();
static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator();
static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator();
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize) {
// get the header size
int headerSize = numBytesForPacketHeader(data);
// read the session id
const int UUID_BYTES = 16;
headerPlusIDSize = headerSize + UUID_BYTES;
if (data.size() < headerPlusIDSize) {
qWarning() << "Metavoxel data too short [size=" << data.size() << ", sendingNode=" << sendingNode << "]\n";
return QUuid();
}
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
}
QByteArray signal(const char* signature) {
static QByteArray prototype = SIGNAL(dummyMethod());
QByteArray signal = prototype;
return signal.replace("dummyMethod()", signature);
}
Box::Box(const glm::vec3& minimum, const glm::vec3& maximum) :
minimum(minimum), maximum(maximum) {
}
bool Box::contains(const Box& other) const {
return other.minimum.x >= minimum.x && other.maximum.x <= maximum.x &&
other.minimum.y >= minimum.y && other.maximum.y <= maximum.y &&
other.minimum.z >= minimum.z && other.maximum.z <= maximum.z;
}
bool Box::intersects(const Box& other) const {
return other.maximum.x >= minimum.x && other.minimum.x <= maximum.x &&
other.maximum.y >= minimum.y && other.minimum.y <= maximum.y &&
other.maximum.z >= minimum.z && other.minimum.z <= maximum.z;
}
QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(QMargins());
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
layout->addWidget(_box = new QComboBox());
connect(_box, SIGNAL(currentIndexChanged(int)), SLOT(updateMetaObject()));
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(&SharedObject::staticMetaObject)) {
_box->addItem(metaObject->className(), QVariant::fromValue(metaObject));
}
}
void QMetaObjectEditor::setMetaObject(const QMetaObject* metaObject) {
_metaObject = metaObject;
_box->setCurrentIndex(_metaObject ? _box->findText(_metaObject->className()) : -1);
}
void QMetaObjectEditor::updateMetaObject() {
int index = _box->currentIndex();
emit metaObjectChanged(_metaObject = (index == -1) ? NULL : _box->itemData(index).value<const QMetaObject*>());
}
QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(QMargins());
@ -176,6 +208,36 @@ void QColorEditor::selectColor() {
}
}
QUrlEditor::QUrlEditor(QWidget* parent) :
QComboBox(parent) {
setEditable(true);
setInsertPolicy(InsertAtTop);
// populate initial URL list from settings
addItems(QSettings().value("editorURLs").toStringList());
connect(this, SIGNAL(activated(const QString&)), SLOT(updateURL(const QString&)));
connect(model(), SIGNAL(rowsInserted(const QModelIndex&,int,int)), SLOT(updateSettings()));
}
void QUrlEditor::setURL(const QUrl& url) {
setCurrentText((_url = url).toString());
}
void QUrlEditor::updateURL(const QString& text) {
emit urlChanged(_url = text);
}
void QUrlEditor::updateSettings() {
QStringList urls;
const int MAX_STORED_URLS = 10;
for (int i = 0, size = qMin(MAX_STORED_URLS, count()); i < size; i++) {
urls.append(itemText(i));
}
QSettings().setValue("editorURLs", urls);
}
Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) {
QHBoxLayout* layout = new QHBoxLayout();
layout->setContentsMargins(QMargins());
@ -200,7 +262,8 @@ void Vec3Editor::updateVector() {
QDoubleSpinBox* Vec3Editor::createComponentBox() {
QDoubleSpinBox* box = new QDoubleSpinBox();
box->setMinimum(-FLT_MAX);
box->setMaximumWidth(100);
box->setMaximum(FLT_MAX);
box->setMinimumWidth(50);
connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector()));
return box;
}
@ -252,8 +315,9 @@ ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
lineContainer->setLayout(lineLayout);
lineLayout->setContentsMargins(QMargins());
lineLayout->addWidget(_line = new QLineEdit(), 1);
connect(_line, SIGNAL(textChanged(const QString&)), SLOT(updateURL()));
lineLayout->addWidget(&_urlEditor, 1);
connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateURL()));
connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateParameters()));
QPushButton* refresh = new QPushButton("...");
connect(refresh, SIGNAL(clicked(bool)), SLOT(updateParameters()));
@ -261,8 +325,7 @@ ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
}
void ParameterizedURLEditor::setURL(const ParameterizedURL& url) {
_url = url;
_line->setText(url.getURL().toString());
_urlEditor.setURL((_url = url).getURL());
updateParameters();
}
@ -279,7 +342,7 @@ void ParameterizedURLEditor::updateURL() {
widget->property("parameterName").toString()), widgetProperty.read(widget));
}
}
emit urlChanged(_url = ParameterizedURL(_line->text(), parameters));
emit urlChanged(_url = ParameterizedURL(_urlEditor.getURL(), parameters));
if (_program) {
_program->disconnect(this);
}

View file

@ -10,29 +10,21 @@
#define __interface__MetavoxelUtil__
#include <QColor>
#include <QComboBox>
#include <QSharedPointer>
#include <QUrl>
#include <QUuid>
#include <QWidget>
#include <NodeList.h>
#include <RegisteredMetaTypes.h>
#include "Bitstream.h"
class QByteArray;
class QDoubleSpinBox;
class QLineEdit;
class QPushButton;
class HifiSockAddr;
class NetworkProgram;
/// Reads and returns the session ID from a datagram.
/// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize);
/// Performs the runtime equivalent of Qt's SIGNAL macro, which is to attach a prefix to the signature.
QByteArray signal(const char* signature);
@ -45,11 +37,44 @@ public:
STREAM glm::vec3 minimum;
STREAM glm::vec3 maximum;
Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3());
bool contains(const Box& other) const;
bool intersects(const Box& other) const;
float getLongestSide() const { return qMax(qMax(maximum.x - minimum.x, maximum.y - minimum.y), maximum.z - minimum.z); }
};
DECLARE_STREAMABLE_METATYPE(Box)
/// Editor for meta-object values.
class QMetaObjectEditor : public QWidget {
Q_OBJECT
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject WRITE setMetaObject NOTIFY metaObjectChanged USER true)
public:
QMetaObjectEditor(QWidget* parent);
signals:
void metaObjectChanged(const QMetaObject* metaObject);
public slots:
void setMetaObject(const QMetaObject* metaObject);
private slots:
void updateMetaObject();
private:
QComboBox* _box;
const QMetaObject* _metaObject;
};
/// Editor for color values.
class QColorEditor : public QWidget {
Q_OBJECT
@ -77,6 +102,32 @@ private:
QColor _color;
};
/// Editor for URL values.
class QUrlEditor : public QComboBox {
Q_OBJECT
Q_PROPERTY(QUrl url READ getURL WRITE setURL NOTIFY urlChanged USER true)
public:
QUrlEditor(QWidget* parent = NULL);
void setURL(const QUrl& url);
const QUrl& getURL() { return _url; }
signals:
void urlChanged(const QUrl& url);
private slots:
void updateURL(const QString& text);
void updateSettings();
private:
QUrl _url;
};
/// Editor for vector values.
class Vec3Editor : public QWidget {
Q_OBJECT
@ -170,7 +221,7 @@ private:
ParameterizedURL _url;
QSharedPointer<NetworkProgram> _program;
QLineEdit* _line;
QUrlEditor _urlEditor;
};
#endif /* defined(__interface__MetavoxelUtil__) */

View file

@ -16,7 +16,9 @@
#include "MetavoxelUtil.h"
#include "SharedObject.h"
SharedObject::SharedObject() : _referenceCount(0) {
REGISTER_META_OBJECT(SharedObject)
SharedObject::SharedObject() : _id(++_lastID), _referenceCount(0) {
}
void SharedObject::incrementReferenceCount() {
@ -72,63 +74,17 @@ bool SharedObject::equals(const SharedObject* other) const {
return true;
}
SharedObjectPointer::SharedObjectPointer(SharedObject* data) : _data(data) {
if (_data) {
_data->incrementReferenceCount();
void SharedObject::dump(QDebug debug) const {
debug << this;
const QMetaObject* metaObject = this->metaObject();
for (int i = 0; i < metaObject->propertyCount(); i++) {
debug << metaObject->property(i).name() << metaObject->property(i).read(this);
}
}
SharedObjectPointer::SharedObjectPointer(const SharedObjectPointer& other) : _data(other._data) {
if (_data) {
_data->incrementReferenceCount();
}
}
int SharedObject::_lastID = 0;
SharedObjectPointer::~SharedObjectPointer() {
if (_data) {
_data->decrementReferenceCount();
}
}
void SharedObjectPointer::detach() {
if (_data && _data->getReferenceCount() > 1) {
_data->decrementReferenceCount();
(_data = _data->clone())->incrementReferenceCount();
}
}
void SharedObjectPointer::reset() {
if (_data) {
_data->decrementReferenceCount();
}
_data = NULL;
}
SharedObjectPointer& SharedObjectPointer::operator=(SharedObject* data) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = data)) {
_data->incrementReferenceCount();
}
return *this;
}
SharedObjectPointer& SharedObjectPointer::operator=(const SharedObjectPointer& other) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = other._data)) {
_data->incrementReferenceCount();
}
return *this;
}
uint qHash(const SharedObjectPointer& pointer, uint seed) {
return qHash(pointer.data(), seed);
}
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent) : QWidget(parent) {
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, bool nullable, QWidget* parent) : QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
setLayout(layout);
@ -137,11 +93,17 @@ SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* p
layout->addLayout(form);
form->addRow("Type:", _type = new QComboBox());
_type->addItem("(none)");
if (nullable) {
_type->addItem("(none)");
}
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) {
_type->addItem(metaObject->className(), QVariant::fromValue(metaObject));
// add add constructable subclasses
if (metaObject->constructorCount() > 0) {
_type->addItem(metaObject->className(), QVariant::fromValue(metaObject));
}
}
connect(_type, SIGNAL(currentIndexChanged(int)), SLOT(updateType()));
updateType();
}
void SharedObjectEditor::setObject(const SharedObjectPointer& object) {
@ -158,6 +120,22 @@ void SharedObjectEditor::setObject(const SharedObjectPointer& object) {
}
}
void SharedObjectEditor::detachObject() {
SharedObject* oldObject = _object.data();
if (!_object.detach()) {
return;
}
oldObject->disconnect(this);
const QMetaObject* metaObject = _object->metaObject();
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
for (int i = 0; i < form->rowCount(); i++) {
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
QMetaProperty property = metaObject->property(widget->property("propertyIndex").toInt());
connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty()));
}
}
const QMetaObject* getOwningAncestor(const QMetaObject* metaObject, int propertyIndex) {
while (propertyIndex < metaObject->propertyOffset()) {
metaObject = metaObject->superClass();
@ -178,19 +156,26 @@ void SharedObjectEditor::updateType() {
}
delete form;
}
QObject* oldObject = static_cast<SharedObject*>(_object.data());
const QMetaObject* oldMetaObject = NULL;
if (oldObject) {
oldMetaObject = oldObject->metaObject();
oldObject->disconnect(this);
}
const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value<const QMetaObject*>();
if (metaObject == NULL) {
_object.reset();
return;
}
QObject* oldObject = static_cast<SharedObject*>(_object.data());
const QMetaObject* oldMetaObject = oldObject ? oldObject->metaObject() : NULL;
QObject* newObject = metaObject->newInstance();
QFormLayout* form = new QFormLayout();
static_cast<QVBoxLayout*>(layout())->addLayout(form);
for (int i = QObject::staticMetaObject.propertyCount(); i < metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (!property.isDesignable()) {
continue;
}
if (oldMetaObject && i < oldMetaObject->propertyCount() &&
getOwningAncestor(metaObject, i) == getOwningAncestor(oldMetaObject, i)) {
// copy the state of the shared ancestry
@ -207,6 +192,10 @@ void SharedObjectEditor::updateType() {
if (widgetProperty.hasNotifySignal()) {
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(propertyChanged()));
}
if (property.hasNotifySignal()) {
widget->setProperty("notifySignalIndex", property.notifySignalIndex());
connect(newObject, signal(property.notifySignal().methodSignature()), SLOT(updateProperty()));
}
}
}
_object = static_cast<SharedObject*>(newObject);
@ -219,10 +208,23 @@ void SharedObjectEditor::propertyChanged() {
if (widget != sender()) {
continue;
}
_object.detach();
detachObject();
QObject* object = _object.data();
QMetaProperty property = object->metaObject()->property(widget->property("propertyIndex").toInt());
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
property.write(object, widget->property(valuePropertyName));
}
}
void SharedObjectEditor::updateProperty() {
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
for (int i = 0; i < form->rowCount(); i++) {
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
if (widget->property("notifySignalIndex").toInt() != senderSignalIndex()) {
continue;
}
QMetaProperty property = _object->metaObject()->property(widget->property("propertyIndex").toInt());
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
widget->setProperty(valuePropertyName, property.read(_object.data()));
}
}

View file

@ -11,7 +11,9 @@
#include <QMetaType>
#include <QObject>
#include <QSet>
#include <QWidget>
#include <QtDebug>
class QComboBox;
@ -21,7 +23,9 @@ class SharedObject : public QObject {
public:
SharedObject();
Q_INVOKABLE SharedObject();
int getID() { return _id; }
int getReferenceCount() const { return _referenceCount; }
void incrementReferenceCount();
@ -33,6 +37,9 @@ public:
/// Tests this object for equality with another.
virtual bool equals(const SharedObject* other) const;
// Dumps the contents of this object to the debug output.
virtual void dump(QDebug debug = QDebug(QtDebugMsg)) const;
signals:
/// Emitted when the reference count drops to one.
@ -40,62 +47,133 @@ signals:
private:
int _id;
int _referenceCount;
static int _lastID;
};
/// A pointer to a shared object.
class SharedObjectPointer {
template<class T> class SharedObjectPointerTemplate {
public:
SharedObjectPointer(SharedObject* data = NULL);
SharedObjectPointer(const SharedObjectPointer& other);
~SharedObjectPointer();
SharedObjectPointerTemplate(T* data = NULL);
SharedObjectPointerTemplate(const SharedObjectPointerTemplate<T>& other);
~SharedObjectPointerTemplate();
SharedObject* data() { return _data; }
const SharedObject* data() const { return _data; }
const SharedObject* constData() const { return _data; }
T* data() const { return _data; }
/// "Detaches" this object, making a new copy if its reference count is greater than one.
bool detach();
void detach();
void swap(SharedObjectPointer& other) { qSwap(_data, other._data); }
void swap(SharedObjectPointerTemplate<T>& other) { qSwap(_data, other._data); }
void reset();
operator SharedObject*() { return _data; }
operator const SharedObject*() const { return _data; }
bool operator!() const { return !_data; }
bool operator!=(const SharedObjectPointer& other) const { return _data != other._data; }
SharedObject& operator*() { return *_data; }
const SharedObject& operator*() const { return *_data; }
SharedObject* operator->() { return _data; }
const SharedObject* operator->() const { return _data; }
SharedObjectPointer& operator=(SharedObject* data);
SharedObjectPointer& operator=(const SharedObjectPointer& other);
bool operator==(const SharedObjectPointer& other) const { return _data == other._data; }
private:
operator T*() const { return _data; }
T& operator*() const { return *_data; }
T* operator->() const { return _data; }
SharedObject* _data;
template<class X> SharedObjectPointerTemplate<X> staticCast() const;
SharedObjectPointerTemplate<T>& operator=(T* data);
SharedObjectPointerTemplate<T>& operator=(const SharedObjectPointerTemplate<T>& other);
bool operator==(const SharedObjectPointerTemplate<T>& other) const { return _data == other._data; }
bool operator!=(const SharedObjectPointerTemplate<T>& other) const { return _data != other._data; }
private:
T* _data;
};
template<class T> inline SharedObjectPointerTemplate<T>::SharedObjectPointerTemplate(T* data) : _data(data) {
if (_data) {
_data->incrementReferenceCount();
}
}
template<class T> inline SharedObjectPointerTemplate<T>::SharedObjectPointerTemplate(const SharedObjectPointerTemplate<T>& other) :
_data(other._data) {
if (_data) {
_data->incrementReferenceCount();
}
}
template<class T> inline SharedObjectPointerTemplate<T>::~SharedObjectPointerTemplate() {
if (_data) {
_data->decrementReferenceCount();
}
}
template<class T> inline bool SharedObjectPointerTemplate<T>::detach() {
if (_data && _data->getReferenceCount() > 1) {
_data->decrementReferenceCount();
(_data = _data->clone())->incrementReferenceCount();
return true;
}
return false;
}
template<class T> inline void SharedObjectPointerTemplate<T>::reset() {
if (_data) {
_data->decrementReferenceCount();
}
_data = NULL;
}
template<class T> template<class X> inline SharedObjectPointerTemplate<X> SharedObjectPointerTemplate<T>::staticCast() const {
return SharedObjectPointerTemplate<X>(static_cast<X*>(_data));
}
template<class T> inline SharedObjectPointerTemplate<T>& SharedObjectPointerTemplate<T>::operator=(T* data) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = data)) {
_data->incrementReferenceCount();
}
return *this;
}
template<class T> inline SharedObjectPointerTemplate<T>& SharedObjectPointerTemplate<T>::operator=(
const SharedObjectPointerTemplate<T>& other) {
if (_data) {
_data->decrementReferenceCount();
}
if ((_data = other._data)) {
_data->incrementReferenceCount();
}
return *this;
}
template<class T> uint qHash(const SharedObjectPointerTemplate<T>& pointer, uint seed = 0) {
return qHash(pointer.data(), seed);
}
typedef SharedObjectPointerTemplate<SharedObject> SharedObjectPointer;
Q_DECLARE_METATYPE(SharedObjectPointer)
uint qHash(const SharedObjectPointer& pointer, uint seed = 0);
typedef QSet<SharedObjectPointer> SharedObjectSet;
Q_DECLARE_METATYPE(SharedObjectSet)
/// Allows editing shared object instances.
class SharedObjectEditor : public QWidget {
Q_OBJECT
Q_PROPERTY(SharedObjectPointer object MEMBER _object WRITE setObject USER true)
Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject USER true)
public:
SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent);
SharedObjectEditor(const QMetaObject* metaObject, bool nullable = true, QWidget* parent = NULL);
const SharedObjectPointer& getObject() const { return _object; }
/// "Detaches" the object pointer, copying it if anyone else is holding a reference.
void detachObject();
public slots:
@ -105,6 +183,7 @@ private slots:
void updateType();
void propertyChanged();
void updateProperty();
private:

View file

@ -12,12 +12,12 @@ find_package(Qt5Network REQUIRED)
find_package(Qt5Script REQUIRED)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE ${AUTOMTC_SRC})
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
#include glm

View file

@ -22,10 +22,10 @@ static int highPriorityMessagesSent = 0;
static int highPriorityMessagesReceived = 0;
static int unreliableMessagesSent = 0;
static int unreliableMessagesReceived = 0;
static int reliableMessagesSent = 0;
static int reliableMessagesReceived = 0;
static int streamedBytesSent = 0;
static int streamedBytesReceived = 0;
static int lowPriorityStreamedBytesSent = 0;
static int lowPriorityStreamedBytesReceived = 0;
bool MetavoxelTests::run() {
@ -51,9 +51,8 @@ bool MetavoxelTests::run() {
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << lowPriorityStreamedBytesSent << "low-priority streamed bytes, received" <<
lowPriorityStreamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived;
qDebug() << "All tests passed!";
@ -77,24 +76,31 @@ static QByteArray createRandomBytes() {
Endpoint::Endpoint(const QByteArray& datagramHeader) :
_sequencer(new DatagramSequencer(datagramHeader, this)),
_highPriorityMessagesToSend(0.0f) {
_highPriorityMessagesToSend(0.0f),
_reliableMessagesToSend(0.0f) {
connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
connect(&_sequencer->getReliableInputChannel()->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
connect(&_sequencer->getReliableInputChannel(1)->getBuffer(), SIGNAL(readyRead()), SLOT(readLowPriorityReliableChannel()));
connect(_sequencer->getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
SLOT(handleReliableMessage(const QVariant&)));
ReliableChannel* secondInput = _sequencer->getReliableInputChannel(1);
secondInput->setMessagesEnabled(false);
connect(&secondInput->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
// enqueue a large amount of data in a low-priority channel
ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
output->setPriority(0.25f);
const int MIN_LOW_PRIORITY_DATA = 100000;
const int MAX_LOW_PRIORITY_DATA = 200000;
QByteArray bytes = createRandomBytes(MIN_LOW_PRIORITY_DATA, MAX_LOW_PRIORITY_DATA);
_lowPriorityDataStreamed.append(bytes);
output->setMessagesEnabled(false);
const int MIN_STREAM_BYTES = 100000;
const int MAX_STREAM_BYTES = 200000;
QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
_dataStreamed.append(bytes);
output->getBuffer().write(bytes);
lowPriorityStreamedBytesSent += bytes.size();
streamedBytesSent += bytes.size();
}
static QVariant createRandomMessage() {
@ -160,13 +166,17 @@ bool Endpoint::simulate(int iterationNumber) {
_highPriorityMessagesToSend -= 1.0f;
}
// stream some random data
const int MIN_BYTES_TO_STREAM = 10;
const int MAX_BYTES_TO_STREAM = 100;
QByteArray bytes = createRandomBytes(MIN_BYTES_TO_STREAM, MAX_BYTES_TO_STREAM);
_dataStreamed.append(bytes);
streamedBytesSent += bytes.size();
_sequencer->getReliableOutputChannel()->getDataStream().writeRawData(bytes.constData(), bytes.size());
// and some number of reliable messages
const float MIN_RELIABLE_MESSAGES = 0.0f;
const float MAX_RELIABLE_MESSAGES = 4.0f;
_reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);
while (_reliableMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_reliableMessagesSent.append(message);
_sequencer->getReliableOutputChannel()->sendMessage(message);
reliableMessagesSent++;
_reliableMessagesToSend -= 1.0f;
}
// send a packet
try {
@ -243,8 +253,19 @@ void Endpoint::readMessage(Bitstream& in) {
throw QString("Received unsent/already sent unreliable message.");
}
void Endpoint::handleReliableMessage(const QVariant& message) {
if (_other->_reliableMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent reliable message.");
}
QVariant sentMessage = _other->_reliableMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received reliable message mismatch.");
}
reliableMessagesReceived++;
}
void Endpoint::readReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel()->getBuffer();
CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_dataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent streamed data.");
@ -256,17 +277,3 @@ void Endpoint::readReliableChannel() {
}
streamedBytesReceived += bytes.size();
}
void Endpoint::readLowPriorityReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_lowPriorityDataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent low-priority streamed data.");
}
QByteArray compare = _other->_lowPriorityDataStreamed.readBytes(0, bytes.size());
_other->_lowPriorityDataStreamed.remove(bytes.size());
if (compare != bytes) {
throw QString("Sent/received low-priority streamed data mismatch.");
}
lowPriorityStreamedBytesReceived += bytes.size();
}

View file

@ -48,9 +48,9 @@ private slots:
void sendDatagram(const QByteArray& datagram);
void handleHighPriorityMessage(const QVariant& message);
void readMessage(Bitstream& in);
void handleReliableMessage(const QVariant& message);
void readReliableChannel();
void readLowPriorityReliableChannel();
private:
DatagramSequencer* _sequencer;
@ -59,8 +59,9 @@ private:
float _highPriorityMessagesToSend;
QVariantList _highPriorityMessagesSent;
QList<SequencedTestMessage> _unreliableMessagesSent;
float _reliableMessagesToSend;
QVariantList _reliableMessagesSent;
CircularBuffer _dataStreamed;
CircularBuffer _lowPriorityDataStreamed;
};
/// A simple test message.
@ -88,7 +89,7 @@ public:
DECLARE_STREAMABLE_METATYPE(TestMessageB)
// A test message that demonstrates inheritance and composition.
class TestMessageC : public TestMessageA {
class TestMessageC : STREAM public TestMessageA {
STREAMABLE
public:

View file

@ -121,7 +121,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " &&\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)";
out << "static_cast<const " << base << "&>(first) == static_cast<const " << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
@ -147,7 +147,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " ||\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)";
out << "static_cast<const " << base << "&>(first) != static_cast<const " << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {