diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 912f5c22ee..fa60d3a88c 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -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(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(); _position = state.position; diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index 60fdeca5a5..a7939ee115 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -9,12 +9,9 @@ #ifndef __hifi__MetavoxelServer__ #define __hifi__MetavoxelServer__ -#include #include #include -#include -#include #include #include @@ -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 _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; diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake index f0c9ebc6a0..f29c3400bb 100644 --- a/cmake/macros/AutoMTC.cmake +++ b/cmake/macros/AutoMTC.cmake @@ -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() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7738be1d3b..38fee4baba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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) { diff --git a/interface/src/Application.h b/interface/src/Application.h index cb1ba2f949..ddd8d16c56 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -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(); diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index e5fd37af4f..90999c26cc 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -94,7 +94,7 @@ void DatagramProcessor::processDatagrams() { break; } case PacketTypeMetavoxelData: - application->_metavoxels.processData(incomingPacket, senderSockAddr); + nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket); break; case PacketTypeBulkAvatarData: case PacketTypeKillAvatar: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 100022c2a6..7e5fe63524 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1115,6 +1115,7 @@ void Menu::showMetavoxelEditor() { _MetavoxelEditor = new MetavoxelEditor(); } _MetavoxelEditor->raise(); + _MetavoxelEditor->activateWindow(); } void Menu::audioMuteToggled() { diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 3ed62cdb14..59a714ece5 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -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::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(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(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(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& points) : - MetavoxelVisitor(QVector() << - AttributeRegistry::getInstance()->getColorAttribute() << - AttributeRegistry::getInstance()->getNormalAttribute(), - QVector()), +MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector& points) : + SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute(), + QVector() << 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() << 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(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); +} diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 7bb7935e4d..4364192009 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -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& points); + SimulateVisitor(QVector& points); + void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; } + virtual void visit(Spanner* spanner); virtual bool visit(MetavoxelInfo& info); private: QVector& _points; + float _deltaTime; + }; + + class RenderVisitor : public SpannerVisitor { + public: + RenderVisitor(); + virtual void visit(Spanner* spanner); }; static ProgramObject _program; static int _pointScaleLocation; QVector _points; - PointVisitor _pointVisitor; + SimulateVisitor _simulateVisitor; + RenderVisitor _renderVisitor; QOpenGLBuffer _buffer; - - QHash _clients; - QHash _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 _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__) */ diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 9513e45d60..e1780ee9f5 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -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); diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index c97832d791..832c6b5d39 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -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 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(_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()); 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 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(_toolBox->itemData(index).value()); +} + +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 color = _editor->getValue().value(); 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 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 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(); + static_cast(spanner.data())->getRenderer()->simulate(deltaTime); +} + +void InsertSpannerTool::render() { + _editor->detachValue(); + Spanner* spanner = static_cast(_editor->getValue().value().data()); + Transformable* transformable = qobject_cast(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(); + 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); +} diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 9024837757..5b580129a9 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -9,7 +9,8 @@ #ifndef __interface__MetavoxelEditor__ #define __interface__MetavoxelEditor__ -#include +#include +#include #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 _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__) */ diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index 989ed6d4a7..491d537b1a 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -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) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 9595d96e1f..bd83987666 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -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(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(value); +} + +bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const { + for (int i = 0; i < MERGE_COUNT; i++) { + if (!decodeInline(children[i]).isEmpty()) { + return false; + } + } + return true; +} + +QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const { + return new SharedObjectEditor(_metaObject, parent); +} diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 56cff7eeb4..2e029f8201 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -24,7 +24,7 @@ class QScriptValue; class Attribute; -typedef QSharedPointer AttributePointer; +typedef SharedObjectPointerTemplate 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& 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 _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 { 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 { + 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__) */ diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 262f2df7f5..919bc4cbc8 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -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 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(rawObject); + int id; + *this >> id; + if (id == 0) { + object = SharedObjectPointer(); + return *this; + } + QPointer& 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(rawObject); + } + object = static_cast(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& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index cc776a742a..c06c4c3b5f 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,7 @@ class Bitstream; class OwnedAttributeValue; class TypeStreamer; -typedef QSharedPointer AttributePointer; +typedef SharedObjectPointerTemplate 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 getMetaObjectSubClasses(const QMetaObject* metaObject); @@ -266,6 +270,9 @@ public: template Bitstream& operator<<(const QList& list); template Bitstream& operator>>(QList& list); + template Bitstream& operator<<(const QSet& set); + template Bitstream& operator>>(QSet& set); + template Bitstream& operator<<(const QHash& hash); template Bitstream& operator>>(QHash& hash); @@ -311,6 +318,8 @@ private slots: void clearSharedObject(); private: + + void readProperties(QObject* object); QDataStream& _underlying; quint8 _byte; @@ -322,6 +331,8 @@ private: RepeatedValueStreamer _scriptStringStreamer; RepeatedValueStreamer _sharedObjectStreamer; + QHash > _transientSharedObjects; + static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); @@ -348,6 +359,27 @@ template inline Bitstream& Bitstream::operator>>(QList& list) { return *this; } +template inline Bitstream& Bitstream::operator<<(const QSet& set) { + *this << set.size(); + foreach (const T& entry, set) { + *this << entry; + } + return *this; +} + +template inline Bitstream& Bitstream::operator>>(QSet& 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 inline Bitstream& Bitstream::operator<<(const QHash& hash) { *this << hash.size(); for (typename QHash::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) { diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index fcbe6b5e87..0c7b8ce8ab 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -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().id); + } } void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { @@ -303,15 +317,6 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector().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().id); + + } else if (message.userType() == ClearMainChannelSharedObjectMessage::Type) { + static_cast(parent())->_inputStream.clearSharedObject( + message.value().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& 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 diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 27a4f05379..13bdf4c7bf 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -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& spans); - void handleHighPriorityMessage(const QVariant& data); - QList _sendRecords; QList _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__) */ diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index b0e24405e6..5665e70f91 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -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& inputs = visitor.getInputs(); const QVector& outputs = visitor.getOutputs(); MetavoxelVisitation firstVisitation = { NULL, visitor, QVector(inputs.size() + 1), QVector(outputs.size()), { glm::vec3(_size, _size, _size) * -0.5f, _size, - QVector(inputs.size() + 1), QVector(outputs.size()) } }; + QVector(inputs.size() + 1), QVector(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(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() << attribute, QVector() << 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(); + 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() << attribute, QVector() << 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(); + 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& inputs, cons MetavoxelVisitor::~MetavoxelVisitor() { } +void MetavoxelVisitor::prepare() { + // nothing by default +} + +SpannerVisitor::SpannerVisitor(const QVector& spannerInputs, const QVector& inputs, + const QVector& 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()) { + Spanner* spanner = static_cast(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(visitation.inputNodes.size()), QVector(visitation.outputNodes.size()), { glm::vec3(), visitation.info.size * 0.5f, QVector(visitation.inputNodes.size()), - QVector(visitation.outputNodes.size()) } }; + QVector(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(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(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"; +} diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 51cdd6cf64..85bdd54938 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -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 inputValues; - QVector outputValues; + QVector 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& inputs, const QVector& outputs); + MetavoxelVisitor(const QVector& inputs, + const QVector& outputs = QVector()); 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& 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 MetavoxelVisitorPointer; +/// Interface for visitors to spanners. +class SpannerVisitor : public MetavoxelVisitor { +public: + + SpannerVisitor(const QVector& spannerInputs, + const QVector& inputs = QVector(), + const QVector& outputs = QVector()); + + /// 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__) */ diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 380df4cac1..c66ce61ee9 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -9,24 +9,35 @@ #include "MetavoxelData.h" #include "MetavoxelMessages.h" -class EditVisitor : public MetavoxelVisitor { +void MetavoxelEditMessage::apply(MetavoxelData& data) const { + static_cast(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(), QVector() << 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(), QVector() << 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(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); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index c3cc78c5bc..d6f07c2966 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -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__) */ diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index d6b42b11fb..2e93fb1804 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -17,12 +17,10 @@ #include #include #include +#include #include #include -#include -#include - #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(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + static QItemEditorCreatorBase* createQColorEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); return creator; } +static QItemEditorCreatorBase* createQUrlEditorCreator() { + QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + static QItemEditorCreatorBase* createVec3EditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), 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()); +} + 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); } diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 7cfedd7a62..4b3fb43523 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -10,29 +10,21 @@ #define __interface__MetavoxelUtil__ #include +#include #include #include -#include #include -#include #include #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 _program; - QLineEdit* _line; + QUrlEditor _urlEditor; }; #endif /* defined(__interface__MetavoxelUtil__) */ diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index f97e285bcf..7e10068afe 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -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(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(_object.data()); + const QMetaObject* oldMetaObject = NULL; + if (oldObject) { + oldMetaObject = oldObject->metaObject(); + oldObject->disconnect(this); + } const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value(); if (metaObject == NULL) { _object.reset(); return; } - QObject* oldObject = static_cast(_object.data()); - const QMetaObject* oldMetaObject = oldObject ? oldObject->metaObject() : NULL; QObject* newObject = metaObject->newInstance(); QFormLayout* form = new QFormLayout(); static_cast(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(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(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())); + } +} diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index e439c4c7f0..9895073e44 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -11,7 +11,9 @@ #include #include +#include #include +#include 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 SharedObjectPointerTemplate { public: - SharedObjectPointer(SharedObject* data = NULL); - SharedObjectPointer(const SharedObjectPointer& other); - ~SharedObjectPointer(); + SharedObjectPointerTemplate(T* data = NULL); + SharedObjectPointerTemplate(const SharedObjectPointerTemplate& 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& 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 SharedObjectPointerTemplate staticCast() const; + + SharedObjectPointerTemplate& operator=(T* data); + SharedObjectPointerTemplate& operator=(const SharedObjectPointerTemplate& other); + + bool operator==(const SharedObjectPointerTemplate& other) const { return _data == other._data; } + bool operator!=(const SharedObjectPointerTemplate& other) const { return _data != other._data; } + +private: + + T* _data; }; +template inline SharedObjectPointerTemplate::SharedObjectPointerTemplate(T* data) : _data(data) { + if (_data) { + _data->incrementReferenceCount(); + } +} + +template inline SharedObjectPointerTemplate::SharedObjectPointerTemplate(const SharedObjectPointerTemplate& other) : + _data(other._data) { + + if (_data) { + _data->incrementReferenceCount(); + } +} + +template inline SharedObjectPointerTemplate::~SharedObjectPointerTemplate() { + if (_data) { + _data->decrementReferenceCount(); + } +} + +template inline bool SharedObjectPointerTemplate::detach() { + if (_data && _data->getReferenceCount() > 1) { + _data->decrementReferenceCount(); + (_data = _data->clone())->incrementReferenceCount(); + return true; + } + return false; +} + +template inline void SharedObjectPointerTemplate::reset() { + if (_data) { + _data->decrementReferenceCount(); + } + _data = NULL; +} + +template template inline SharedObjectPointerTemplate SharedObjectPointerTemplate::staticCast() const { + return SharedObjectPointerTemplate(static_cast(_data)); +} + +template inline SharedObjectPointerTemplate& SharedObjectPointerTemplate::operator=(T* data) { + if (_data) { + _data->decrementReferenceCount(); + } + if ((_data = data)) { + _data->incrementReferenceCount(); + } + return *this; +} + +template inline SharedObjectPointerTemplate& SharedObjectPointerTemplate::operator=( + const SharedObjectPointerTemplate& other) { + if (_data) { + _data->decrementReferenceCount(); + } + if ((_data = other._data)) { + _data->incrementReferenceCount(); + } + return *this; +} + +template uint qHash(const SharedObjectPointerTemplate& pointer, uint seed = 0) { + return qHash(pointer.data(), seed); +} + +typedef SharedObjectPointerTemplate SharedObjectPointer; + Q_DECLARE_METATYPE(SharedObjectPointer) -uint qHash(const SharedObjectPointer& pointer, uint seed = 0); +typedef QSet 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: diff --git a/tests/metavoxels/CMakeLists.txt b/tests/metavoxels/CMakeLists.txt index 416f398470..9d21dd2a44 100644 --- a/tests/metavoxels/CMakeLists.txt +++ b/tests/metavoxels/CMakeLists.txt @@ -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 diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 15d7463742..530f7e3108 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -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(); -} diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index b73f7eb07e..f19870ac15 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -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 _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: diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp index 050fe0e418..248c2ddd2d 100644 --- a/tools/mtc/src/main.cpp +++ b/tools/mtc/src/main.cpp @@ -121,7 +121,7 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << " &&\n"; out << " "; } - out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)"; + out << "static_cast(first) == static_cast(second)"; first = false; } foreach (const QString& field, str.fields) { @@ -147,7 +147,7 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << " ||\n"; out << " "; } - out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)"; + out << "static_cast(first) != static_cast(second)"; first = false; } foreach (const QString& field, str.fields) {