mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:24:00 +02:00
Merge pull request #2047 from ey6es/metavoxels
Another metavoxel checkpoint: more work on streaming, editable attributes, changed to use existing session code, work on "spanners" (objects that span multiple octree nodes).
This commit is contained in:
commit
907740eb8e
31 changed files with 1870 additions and 571 deletions
|
@ -28,10 +28,6 @@ void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
|
|||
edit.apply(_data);
|
||||
}
|
||||
|
||||
void MetavoxelServer::removeSession(const QUuid& sessionId) {
|
||||
_sessions.take(sessionId)->deleteLater();
|
||||
}
|
||||
|
||||
const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server";
|
||||
|
||||
void MetavoxelServer::run() {
|
||||
|
@ -40,6 +36,8 @@ void MetavoxelServer::run() {
|
|||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachSession(const SharedNodePointer&)));
|
||||
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
@ -53,25 +51,31 @@ void MetavoxelServer::readPendingDatagrams() {
|
|||
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
switch (packetTypeForPacket(receivedPacket)) {
|
||||
case PacketTypeMetavoxelData: {
|
||||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (matchingNode) {
|
||||
processData(receivedPacket, matchingNode);
|
||||
}
|
||||
case PacketTypeMetavoxelData:
|
||||
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
||||
nodeList->processNodeData(senderSockAddr, receivedPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
node->setLinkedData(new MetavoxelSession(this, NodeList::getInstance()->nodeWithUUID(node->getUUID())));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::sendDeltas() {
|
||||
// send deltas for all sessions
|
||||
foreach (MetavoxelSession* session, _sessions) {
|
||||
session->sendDelta();
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
static_cast<MetavoxelSession*>(node->getLinkedData())->sendDelta();
|
||||
}
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
|
@ -82,35 +86,10 @@ void MetavoxelServer::sendDeltas() {
|
|||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
|
||||
}
|
||||
|
||||
void MetavoxelServer::processData(const QByteArray& data, const SharedNodePointer& sendingNode) {
|
||||
// read the session id
|
||||
int headerPlusIDSize;
|
||||
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
|
||||
if (sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// forward to session, creating if necessary
|
||||
MetavoxelSession*& session = _sessions[sessionID];
|
||||
if (!session) {
|
||||
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize),
|
||||
sendingNode);
|
||||
}
|
||||
session->receivedData(data, sendingNode);
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
|
||||
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode) :
|
||||
QObject(server),
|
||||
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node) :
|
||||
_server(server),
|
||||
_sessionId(sessionId),
|
||||
_sequencer(datagramHeader),
|
||||
_sendingNode(sendingNode) {
|
||||
|
||||
const int TIMEOUT_INTERVAL = 30 * 1000;
|
||||
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
|
||||
_timeoutTimer.setSingleShot(true);
|
||||
connect(&_timeoutTimer, SIGNAL(timeout()), SLOT(timedOut()));
|
||||
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)),
|
||||
_node(node) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
|
@ -120,19 +99,15 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
|
|||
// insert the baseline send record
|
||||
SendRecord record = { 0 };
|
||||
_sendRecords.append(record);
|
||||
|
||||
qDebug() << "Opened session [sessionId=" << _sessionId << ", sendingNode=" << sendingNode << "]";
|
||||
}
|
||||
|
||||
void MetavoxelSession::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
|
||||
// reset the timeout timer
|
||||
_timeoutTimer.start();
|
||||
MetavoxelSession::~MetavoxelSession() {
|
||||
}
|
||||
|
||||
// save the most recent sender
|
||||
_sendingNode = sendingNode;
|
||||
|
||||
int MetavoxelSession::parseData(const QByteArray& packet) {
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(data);
|
||||
_sequencer.receivedDatagram(packet);
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendDelta() {
|
||||
|
@ -146,13 +121,8 @@ void MetavoxelSession::sendDelta() {
|
|||
_sendRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelSession::timedOut() {
|
||||
qDebug() << "Session timed out [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
|
||||
_server->removeSession(_sessionId);
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendData(const QByteArray& data) {
|
||||
NodeList::getInstance()->writeDatagram(data, _sendingNode);
|
||||
NodeList::getInstance()->writeDatagram(data, _node);
|
||||
}
|
||||
|
||||
void MetavoxelSession::readPacket(Bitstream& in) {
|
||||
|
@ -167,11 +137,7 @@ void MetavoxelSession::clearSendRecordsBefore(int index) {
|
|||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message) {
|
||||
int userType = message.userType();
|
||||
if (userType == CloseSessionMessage::Type) {
|
||||
qDebug() << "Session closed [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]";
|
||||
_server->removeSession(_sessionId);
|
||||
|
||||
} else if (userType == ClientStateMessage::Type) {
|
||||
if (userType == ClientStateMessage::Type) {
|
||||
ClientStateMessage state = message.value<ClientStateMessage>();
|
||||
_position = state.position;
|
||||
|
||||
|
|
|
@ -9,12 +9,9 @@
|
|||
#ifndef __hifi__MetavoxelServer__
|
||||
#define __hifi__MetavoxelServer__
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
|
@ -35,45 +32,38 @@ public:
|
|||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
void removeSession(const QUuid& sessionId);
|
||||
|
||||
virtual void run();
|
||||
|
||||
virtual void readPendingDatagrams();
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
void maybeAttachSession(const SharedNodePointer& node);
|
||||
void sendDeltas();
|
||||
|
||||
private:
|
||||
|
||||
void processData(const QByteArray& data, const SharedNodePointer& sendingNode);
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
QHash<QUuid, MetavoxelSession*> _sessions;
|
||||
|
||||
MetavoxelData _data;
|
||||
};
|
||||
|
||||
/// Contains the state of a single client session.
|
||||
class MetavoxelSession : public QObject {
|
||||
class MetavoxelSession : public NodeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
|
||||
const QByteArray& datagramHeader, const SharedNodePointer& sendingNode);
|
||||
MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node);
|
||||
virtual ~MetavoxelSession();
|
||||
|
||||
void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
void sendDelta();
|
||||
|
||||
private slots:
|
||||
|
||||
void timedOut();
|
||||
|
||||
void sendData(const QByteArray& data);
|
||||
|
||||
void readPacket(Bitstream& in);
|
||||
|
@ -91,12 +81,10 @@ private:
|
|||
};
|
||||
|
||||
MetavoxelServer* _server;
|
||||
QUuid _sessionId;
|
||||
|
||||
QTimer _timeoutTimer;
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
SharedNodePointer _sendingNode;
|
||||
SharedNodePointer _node;
|
||||
|
||||
glm::vec3 _position;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ void DatagramProcessor::processDatagrams() {
|
|||
break;
|
||||
}
|
||||
case PacketTypeMetavoxelData:
|
||||
application->_metavoxels.processData(incomingPacket, senderSockAddr);
|
||||
nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket);
|
||||
break;
|
||||
case PacketTypeBulkAvatarData:
|
||||
case PacketTypeKillAvatar:
|
||||
|
|
|
@ -1115,6 +1115,7 @@ void Menu::showMetavoxelEditor() {
|
|||
_MetavoxelEditor = new MetavoxelEditor();
|
||||
}
|
||||
_MetavoxelEditor->raise();
|
||||
_MetavoxelEditor->activateWindow();
|
||||
}
|
||||
|
||||
void Menu::audioMuteToggled() {
|
||||
|
|
|
@ -16,21 +16,18 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
#include "renderer/Model.h"
|
||||
|
||||
REGISTER_META_OBJECT(StaticModelRenderer)
|
||||
|
||||
ProgramObject MetavoxelSystem::_program;
|
||||
int MetavoxelSystem::_pointScaleLocation;
|
||||
|
||||
MetavoxelSystem::MetavoxelSystem() :
|
||||
_pointVisitor(_points),
|
||||
_simulateVisitor(_points),
|
||||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
|
||||
MetavoxelSystem::~MetavoxelSystem() {
|
||||
for (QHash<QUuid, MetavoxelClient*>::const_iterator it = _clients.begin(); it != _clients.end(); it++) {
|
||||
delete it.value();
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::init() {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
|
@ -42,33 +39,39 @@ void MetavoxelSystem::init() {
|
|||
// let the script cache know to use our common access manager
|
||||
ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager());
|
||||
}
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
|
||||
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
||||
|
||||
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_buffer.create();
|
||||
|
||||
connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
|
||||
}
|
||||
|
||||
void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->applyEdit(edit);
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->applyEdit(edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender));
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
// simulate the clients
|
||||
_points.clear();
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->simulate(deltaTime, _pointVisitor);
|
||||
_simulateVisitor.setDeltaTime(deltaTime);
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->simulate(deltaTime);
|
||||
client->getData().guide(_simulateVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_buffer.bind();
|
||||
int bytes = _points.size() * sizeof(Point);
|
||||
if (_buffer.size() < bytes) {
|
||||
|
@ -118,53 +121,39 @@ void MetavoxelSystem::render() {
|
|||
_buffer.release();
|
||||
|
||||
_program.release();
|
||||
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->getData().guide(_renderVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::nodeAdded(SharedNodePointer node) {
|
||||
void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMetaObject::invokeMethod(this, "addClient", Q_ARG(const SharedNodePointer&, node));
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
node->setLinkedData(new MetavoxelClient(NodeList::getInstance()->nodeWithUUID(node->getUUID())));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::nodeKilled(SharedNodePointer node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID()));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::addClient(const SharedNodePointer& node) {
|
||||
MetavoxelClient* client = new MetavoxelClient(node);
|
||||
_clients.insert(node->getUUID(), client);
|
||||
_clientsBySessionID.insert(client->getSessionID(), client);
|
||||
}
|
||||
|
||||
void MetavoxelSystem::removeClient(const QUuid& uuid) {
|
||||
MetavoxelClient* client = _clients.take(uuid);
|
||||
_clientsBySessionID.remove(client->getSessionID());
|
||||
delete client;
|
||||
}
|
||||
|
||||
void MetavoxelSystem::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) {
|
||||
int headerPlusIDSize;
|
||||
QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize);
|
||||
if (sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
MetavoxelClient* client = _clientsBySessionID.value(sessionID);
|
||||
if (client) {
|
||||
client->receivedData(data);
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelSystem::PointVisitor::PointVisitor(QVector<Point>& points) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute(),
|
||||
QVector<AttributePointer>()),
|
||||
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute()),
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
|
||||
void MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner) {
|
||||
spanner->getRenderer()->simulate(_deltaTime);
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) {
|
||||
SpannerVisitor::visit(info);
|
||||
|
||||
if (!info.isLeaf) {
|
||||
return true;
|
||||
}
|
||||
|
@ -179,16 +168,17 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static QByteArray createDatagramHeader(const QUuid& sessionID) {
|
||||
QByteArray header = byteArrayWithPopulatedHeader(PacketTypeMetavoxelData);
|
||||
header += sessionID.toRfc4122();
|
||||
return header;
|
||||
MetavoxelSystem::RenderVisitor::RenderVisitor() :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute()) {
|
||||
}
|
||||
|
||||
void MetavoxelSystem::RenderVisitor::visit(Spanner* spanner) {
|
||||
spanner->getRenderer()->render(1.0f);
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
|
||||
_node(node),
|
||||
_sessionID(QUuid::createUuid()),
|
||||
_sequencer(createDatagramHeader(_sessionID)) {
|
||||
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
|
@ -214,22 +204,20 @@ void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit) {
|
|||
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
|
||||
}
|
||||
|
||||
void MetavoxelClient::simulate(float deltaTime, MetavoxelVisitor& visitor) {
|
||||
void MetavoxelClient::simulate(float deltaTime) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() };
|
||||
out << QVariant::fromValue(state);
|
||||
_sequencer.endPacket();
|
||||
|
||||
_data.guide(visitor);
|
||||
}
|
||||
|
||||
void MetavoxelClient::receivedData(const QByteArray& data) {
|
||||
int MetavoxelClient::parseData(const QByteArray& packet) {
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(data);
|
||||
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
void MetavoxelClient::sendData(const QByteArray& data) {
|
||||
QMutexLocker locker(&_node->getMutex());
|
||||
NodeList::getInstance()->writeDatagram(data, _node);
|
||||
}
|
||||
|
||||
|
@ -265,3 +253,47 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
StaticModelRenderer::StaticModelRenderer() :
|
||||
_model(new Model(this)) {
|
||||
}
|
||||
|
||||
void StaticModelRenderer::init(Spanner* spanner) {
|
||||
_model->init();
|
||||
|
||||
StaticModel* staticModel = static_cast<StaticModel*>(spanner);
|
||||
applyTranslation(staticModel->getTranslation());
|
||||
applyRotation(staticModel->getRotation());
|
||||
applyScale(staticModel->getScale());
|
||||
applyURL(staticModel->getURL());
|
||||
|
||||
connect(spanner, SIGNAL(translationChanged(const glm::vec3&)), SLOT(applyTranslation(const glm::vec3&)));
|
||||
connect(spanner, SIGNAL(rotationChanged(const glm::vec3&)), SLOT(applyRotation(const glm::vec3&)));
|
||||
connect(spanner, SIGNAL(scaleChanged(float)), SLOT(applyScale(float)));
|
||||
connect(spanner, SIGNAL(urlChanged(const QUrl&)), SLOT(applyURL(const QUrl&)));
|
||||
}
|
||||
|
||||
void StaticModelRenderer::simulate(float deltaTime) {
|
||||
_model->simulate(deltaTime);
|
||||
}
|
||||
|
||||
void StaticModelRenderer::render(float alpha) {
|
||||
_model->render(alpha);
|
||||
}
|
||||
|
||||
void StaticModelRenderer::applyTranslation(const glm::vec3& translation) {
|
||||
_model->setTranslation(translation);
|
||||
}
|
||||
|
||||
void StaticModelRenderer::applyRotation(const glm::vec3& rotation) {
|
||||
_model->setRotation(glm::quat(glm::radians(rotation)));
|
||||
}
|
||||
|
||||
void StaticModelRenderer::applyScale(float scale) {
|
||||
const float SCALE_MULTIPLIER = 0.0006f;
|
||||
_model->setScale(glm::vec3(scale, scale, scale) * SCALE_MULTIPLIER);
|
||||
}
|
||||
|
||||
void StaticModelRenderer::applyURL(const QUrl& url) {
|
||||
_model->setURL(url);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
#include "renderer/ProgramObject.h"
|
||||
|
||||
class MetavoxelClient;
|
||||
class Model;
|
||||
|
||||
/// Renders a metavoxel tree.
|
||||
class MetavoxelSystem : public QObject {
|
||||
|
@ -32,28 +32,20 @@ class MetavoxelSystem : public QObject {
|
|||
public:
|
||||
|
||||
MetavoxelSystem();
|
||||
~MetavoxelSystem();
|
||||
|
||||
void init();
|
||||
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
void processData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
public slots:
|
||||
private slots:
|
||||
|
||||
void maybeAttachClient(const SharedNodePointer& node);
|
||||
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
private:
|
||||
|
||||
Q_INVOKABLE void addClient(const SharedNodePointer& node);
|
||||
Q_INVOKABLE void removeClient(const QUuid& uuid);
|
||||
Q_INVOKABLE void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode);
|
||||
|
||||
|
||||
class Point {
|
||||
public:
|
||||
glm::vec4 vertex;
|
||||
|
@ -61,28 +53,35 @@ private:
|
|||
quint8 normal[3];
|
||||
};
|
||||
|
||||
class PointVisitor : public MetavoxelVisitor {
|
||||
class SimulateVisitor : public SpannerVisitor {
|
||||
public:
|
||||
PointVisitor(QVector<Point>& points);
|
||||
SimulateVisitor(QVector<Point>& points);
|
||||
void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; }
|
||||
virtual void visit(Spanner* spanner);
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
float _deltaTime;
|
||||
};
|
||||
|
||||
class RenderVisitor : public SpannerVisitor {
|
||||
public:
|
||||
RenderVisitor();
|
||||
virtual void visit(Spanner* spanner);
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
SimulateVisitor _simulateVisitor;
|
||||
RenderVisitor _renderVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
|
||||
QHash<QUuid, MetavoxelClient*> _clients;
|
||||
QHash<QUuid, MetavoxelClient*> _clientsBySessionID;
|
||||
};
|
||||
|
||||
/// A client session associated with a single server.
|
||||
class MetavoxelClient : public QObject {
|
||||
class MetavoxelClient : public NodeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -90,13 +89,13 @@ public:
|
|||
MetavoxelClient(const SharedNodePointer& node);
|
||||
virtual ~MetavoxelClient();
|
||||
|
||||
const QUuid& getSessionID() const { return _sessionID; }
|
||||
MetavoxelData& getData() { return _data; }
|
||||
|
||||
void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
void simulate(float deltaTime, MetavoxelVisitor& visitor);
|
||||
void simulate(float deltaTime);
|
||||
|
||||
void receivedData(const QByteArray& data);
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
private slots:
|
||||
|
||||
|
@ -117,7 +116,6 @@ private:
|
|||
};
|
||||
|
||||
SharedNodePointer _node;
|
||||
QUuid _sessionID;
|
||||
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
|
@ -126,4 +124,28 @@ private:
|
|||
QList<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
||||
/// Renders static models.
|
||||
class StaticModelRenderer : public SpannerRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE StaticModelRenderer();
|
||||
|
||||
virtual void init(Spanner* spanner);
|
||||
virtual void simulate(float deltaTime);
|
||||
virtual void render(float alpha);
|
||||
|
||||
private slots:
|
||||
|
||||
void applyTranslation(const glm::vec3& translation);
|
||||
void applyRotation(const glm::vec3& rotation);
|
||||
void applyScale(float scale);
|
||||
void applyURL(const QUrl& url);
|
||||
|
||||
private:
|
||||
|
||||
Model* _model;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelSystem__) */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -29,7 +29,7 @@ enum GridPlane {
|
|||
const glm::vec2 INVALID_VECTOR(FLT_MAX, FLT_MAX);
|
||||
|
||||
MetavoxelEditor::MetavoxelEditor() :
|
||||
QDialog(Application::getInstance()->getGLWidget()) {
|
||||
QWidget(Application::getInstance()->getGLWidget(), Qt::Tool | Qt::WindowStaysOnTopHint) {
|
||||
|
||||
setWindowTitle("Metavoxel Editor");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
@ -45,12 +45,19 @@ MetavoxelEditor::MetavoxelEditor() :
|
|||
attributeGroup->setLayout(attributeLayout);
|
||||
|
||||
attributeLayout->addWidget(_attributes = new QListWidget());
|
||||
connect(_attributes, SIGNAL(itemSelectionChanged()), SLOT(updateValueEditor()));
|
||||
connect(_attributes, SIGNAL(itemSelectionChanged()), SLOT(selectedAttributeChanged()));
|
||||
|
||||
QHBoxLayout* attributeButtonLayout = new QHBoxLayout();
|
||||
attributeLayout->addLayout(attributeButtonLayout);
|
||||
|
||||
QPushButton* newAttribute = new QPushButton("New...");
|
||||
attributeLayout->addWidget(newAttribute);
|
||||
attributeButtonLayout->addWidget(newAttribute);
|
||||
connect(newAttribute, SIGNAL(clicked()), SLOT(createNewAttribute()));
|
||||
|
||||
attributeButtonLayout->addWidget(_deleteAttribute = new QPushButton("Delete"));
|
||||
_deleteAttribute->setEnabled(false);
|
||||
connect(_deleteAttribute, SIGNAL(clicked()), SLOT(deleteSelectedAttribute()));
|
||||
|
||||
QFormLayout* formLayout = new QFormLayout();
|
||||
topLayout->addLayout(formLayout);
|
||||
|
||||
|
@ -74,6 +81,9 @@ MetavoxelEditor::MetavoxelEditor() :
|
|||
alignGridPosition();
|
||||
centerGridPosition();
|
||||
|
||||
formLayout->addRow("Tool:", _toolBox = new QComboBox());
|
||||
connect(_toolBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTool()));
|
||||
|
||||
_value = new QGroupBox();
|
||||
_value->setTitle("Value");
|
||||
topLayout->addWidget(_value);
|
||||
|
@ -82,16 +92,22 @@ MetavoxelEditor::MetavoxelEditor() :
|
|||
_value->setLayout(valueLayout);
|
||||
|
||||
valueLayout->addWidget(_valueArea = new QScrollArea());
|
||||
_valueArea->setMinimumHeight(200);
|
||||
_valueArea->setWidgetResizable(true);
|
||||
|
||||
addTool(new BoxSetTool(this));
|
||||
addTool(new GlobalSetTool(this));
|
||||
addTool(new InsertSpannerTool(this));
|
||||
addTool(new RemoveSpannerTool(this));
|
||||
addTool(new ClearSpannersTool(this));
|
||||
|
||||
updateAttributes();
|
||||
|
||||
connect(Application::getInstance(), SIGNAL(simulating(float)), SLOT(simulate(float)));
|
||||
connect(Application::getInstance(), SIGNAL(renderingInWorldInterface()), SLOT(render()));
|
||||
|
||||
Application::getInstance()->getGLWidget()->installEventFilter(this);
|
||||
|
||||
resetState();
|
||||
|
||||
show();
|
||||
|
||||
if (_gridProgram.isLinked()) {
|
||||
|
@ -102,60 +118,79 @@ MetavoxelEditor::MetavoxelEditor() :
|
|||
_gridProgram.link();
|
||||
}
|
||||
|
||||
bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
|
||||
switch (_state) {
|
||||
case HOVERING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonPress && _startPosition != INVALID_VECTOR) {
|
||||
_state = DRAGGING_STATE;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case DRAGGING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonRelease) {
|
||||
_state = RAISING_STATE;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case RAISING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
if (_height != 0) {
|
||||
// find the start and end corners in X/Y
|
||||
float base = _gridPosition->value();
|
||||
float top = base + _height;
|
||||
glm::quat rotation = getGridRotation();
|
||||
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
|
||||
float spacing = getGridSpacing();
|
||||
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
|
||||
glm::vec2(spacing, spacing), glm::max(base, top));
|
||||
|
||||
// find the minimum and maximum extents after rotation
|
||||
applyValue(glm::min(start, end), glm::max(start, end));
|
||||
}
|
||||
resetState();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
QString MetavoxelEditor::getSelectedAttribute() const {
|
||||
QList<QListWidgetItem*> selectedItems = _attributes->selectedItems();
|
||||
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
|
||||
}
|
||||
|
||||
void MetavoxelEditor::updateValueEditor() {
|
||||
double MetavoxelEditor::getGridSpacing() const {
|
||||
return pow(2.0, _gridSpacing->value());
|
||||
}
|
||||
|
||||
double MetavoxelEditor::getGridPosition() const {
|
||||
return _gridPosition->value();
|
||||
}
|
||||
|
||||
glm::quat MetavoxelEditor::getGridRotation() const {
|
||||
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
|
||||
switch (_gridPlane->currentIndex()) {
|
||||
case GRID_PLANE_XY:
|
||||
return glm::quat();
|
||||
|
||||
case GRID_PLANE_XZ:
|
||||
return glm::angleAxis(-90.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
case GRID_PLANE_YZ:
|
||||
default:
|
||||
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant MetavoxelEditor::getValue() const {
|
||||
QWidget* editor = _valueArea->widget();
|
||||
return editor ? editor->metaObject()->userProperty().read(editor) : QVariant();
|
||||
}
|
||||
|
||||
void MetavoxelEditor::detachValue() {
|
||||
SharedObjectEditor* editor = qobject_cast<SharedObjectEditor*>(_valueArea->widget());
|
||||
if (editor) {
|
||||
editor->detachObject();
|
||||
}
|
||||
}
|
||||
|
||||
bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) {
|
||||
// pass along to the active tool
|
||||
MetavoxelTool* tool = getActiveTool();
|
||||
return tool && tool->eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void MetavoxelEditor::selectedAttributeChanged() {
|
||||
_toolBox->clear();
|
||||
|
||||
QString selected = getSelectedAttribute();
|
||||
if (selected.isNull()) {
|
||||
_deleteAttribute->setEnabled(false);
|
||||
_toolBox->setEnabled(false);
|
||||
_value->setVisible(false);
|
||||
return;
|
||||
}
|
||||
_deleteAttribute->setEnabled(true);
|
||||
_toolBox->setEnabled(true);
|
||||
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
|
||||
foreach (MetavoxelTool* tool, _tools) {
|
||||
if (tool->appliesTo(attribute)) {
|
||||
_toolBox->addItem(tool->objectName(), QVariant::fromValue(tool));
|
||||
}
|
||||
}
|
||||
_value->setVisible(true);
|
||||
|
||||
if (_valueArea->widget()) {
|
||||
delete _valueArea->widget();
|
||||
}
|
||||
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected);
|
||||
QWidget* editor = attribute->createEditor();
|
||||
if (editor) {
|
||||
editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
_valueArea->setWidget(editor);
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +208,10 @@ void MetavoxelEditor::createNewAttribute() {
|
|||
QLineEdit name;
|
||||
form.addRow("Name:", &name);
|
||||
|
||||
SharedObjectEditor editor(&Attribute::staticMetaObject, false);
|
||||
editor.setObject(new QRgbAttribute());
|
||||
layout.addWidget(&editor);
|
||||
|
||||
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
dialog.connect(&buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
dialog.connect(&buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
@ -183,11 +222,19 @@ void MetavoxelEditor::createNewAttribute() {
|
|||
return;
|
||||
}
|
||||
QString nameText = name.text().trimmed();
|
||||
AttributeRegistry::getInstance()->registerAttribute(new QRgbAttribute(nameText));
|
||||
SharedObjectPointer attribute = editor.getObject();
|
||||
attribute->setObjectName(nameText);
|
||||
AttributeRegistry::getInstance()->registerAttribute(attribute.staticCast<Attribute>());
|
||||
|
||||
updateAttributes(nameText);
|
||||
}
|
||||
|
||||
void MetavoxelEditor::deleteSelectedAttribute() {
|
||||
AttributeRegistry::getInstance()->deregisterAttribute(getSelectedAttribute());
|
||||
_attributes->selectionModel()->clear();
|
||||
updateAttributes();
|
||||
}
|
||||
|
||||
void MetavoxelEditor::centerGridPosition() {
|
||||
const float CENTER_OFFSET = 0.625f;
|
||||
float eyePosition = (glm::inverse(getGridRotation()) * Application::getInstance()->getCamera()->getPosition()).z -
|
||||
|
@ -203,13 +250,24 @@ void MetavoxelEditor::alignGridPosition() {
|
|||
_gridPosition->setValue(step * floor(_gridPosition->value() / step));
|
||||
}
|
||||
|
||||
void MetavoxelEditor::render() {
|
||||
QString selected = getSelectedAttribute();
|
||||
if (selected.isNull()) {
|
||||
resetState();
|
||||
return;
|
||||
void MetavoxelEditor::updateTool() {
|
||||
MetavoxelTool* active = getActiveTool();
|
||||
foreach (MetavoxelTool* tool, _tools) {
|
||||
tool->setVisible(tool == active);
|
||||
}
|
||||
_value->setVisible(active && active->getUsesValue());
|
||||
}
|
||||
|
||||
void MetavoxelEditor::simulate(float deltaTime) {
|
||||
MetavoxelTool* tool = getActiveTool();
|
||||
if (tool) {
|
||||
tool->simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
const float GRID_BRIGHTNESS = 0.5f;
|
||||
|
||||
void MetavoxelEditor::render() {
|
||||
glDisable(GL_LIGHTING);
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
|
@ -219,11 +277,110 @@ void MetavoxelEditor::render() {
|
|||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
||||
|
||||
MetavoxelTool* tool = getActiveTool();
|
||||
if (tool) {
|
||||
tool->render();
|
||||
}
|
||||
|
||||
glLineWidth(1.0f);
|
||||
|
||||
// center the grid around the camera position on the plane
|
||||
glm::vec3 rotated = glm::inverse(rotation) * Application::getInstance()->getCamera()->getPosition();
|
||||
float spacing = getGridSpacing();
|
||||
const int GRID_DIVISIONS = 300;
|
||||
glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
|
||||
spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), _gridPosition->value());
|
||||
|
||||
float scale = GRID_DIVISIONS * spacing;
|
||||
glScalef(scale, scale, scale);
|
||||
|
||||
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
|
||||
|
||||
_gridProgram.bind();
|
||||
|
||||
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
|
||||
|
||||
_gridProgram.release();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
|
||||
void MetavoxelEditor::addTool(MetavoxelTool* tool) {
|
||||
_tools.append(tool);
|
||||
layout()->addWidget(tool);
|
||||
}
|
||||
|
||||
void MetavoxelEditor::updateAttributes(const QString& select) {
|
||||
// remember the selection in order to preserve it
|
||||
QString selected = select.isNull() ? getSelectedAttribute() : select;
|
||||
_attributes->clear();
|
||||
|
||||
// sort the names for consistent ordering
|
||||
QList<QString> names = AttributeRegistry::getInstance()->getAttributes().keys();
|
||||
qSort(names);
|
||||
|
||||
foreach (const QString& name, names) {
|
||||
QListWidgetItem* item = new QListWidgetItem(name);
|
||||
_attributes->addItem(item);
|
||||
if (name == selected || selected.isNull()) {
|
||||
item->setSelected(true);
|
||||
selected = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelTool* MetavoxelEditor::getActiveTool() const {
|
||||
int index = _toolBox->currentIndex();
|
||||
return (index == -1) ? NULL : static_cast<MetavoxelTool*>(_toolBox->itemData(index).value<QObject*>());
|
||||
}
|
||||
|
||||
ProgramObject MetavoxelEditor::_gridProgram;
|
||||
|
||||
MetavoxelTool::MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue) :
|
||||
_editor(editor),
|
||||
_usesValue(usesValue) {
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
setLayout(layout);
|
||||
|
||||
setObjectName(name);
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
bool MetavoxelTool::appliesTo(const AttributePointer& attribute) const {
|
||||
// shared object sets are a special case
|
||||
return !attribute->inherits("SharedObjectSetAttribute");
|
||||
}
|
||||
|
||||
void MetavoxelTool::simulate(float deltaTime) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void MetavoxelTool::render() {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
BoxSetTool::BoxSetTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Set Value (Box)") {
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
void BoxSetTool::render() {
|
||||
QString selected = _editor->getSelectedAttribute();
|
||||
if (selected.isNull()) {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
glm::quat rotation = _editor->getGridRotation();
|
||||
glm::quat inverseRotation = glm::inverse(rotation);
|
||||
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
|
||||
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
|
||||
float spacing = getGridSpacing();
|
||||
float position = _gridPosition->value();
|
||||
float spacing = _editor->getGridSpacing();
|
||||
float position = _editor->getGridPosition();
|
||||
if (_state == RAISING_STATE) {
|
||||
// find the plane at the mouse position, orthogonal to the plane, facing the eye position
|
||||
glLineWidth(4.0f);
|
||||
|
@ -256,7 +413,6 @@ void MetavoxelEditor::render() {
|
|||
resetState();
|
||||
}
|
||||
|
||||
const float GRID_BRIGHTNESS = 0.5f;
|
||||
if (_startPosition != INVALID_VECTOR) {
|
||||
glm::vec2 minimum = glm::min(_startPosition, _endPosition);
|
||||
glm::vec2 maximum = glm::max(_startPosition, _endPosition);
|
||||
|
@ -268,7 +424,7 @@ void MetavoxelEditor::render() {
|
|||
glTranslatef(0.5f, 0.5f, 0.5f);
|
||||
if (_state != HOVERING_STATE) {
|
||||
const float BOX_ALPHA = 0.25f;
|
||||
QColor color = getValue().value<QColor>();
|
||||
QColor color = _editor->getValue().value<QColor>();
|
||||
if (color.isValid()) {
|
||||
glColor4f(color.redF(), color.greenF(), color.blueF(), BOX_ALPHA);
|
||||
} else {
|
||||
|
@ -281,97 +437,171 @@ void MetavoxelEditor::render() {
|
|||
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
|
||||
glutWireCube(1.0);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
} else {
|
||||
glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS);
|
||||
}
|
||||
|
||||
glLineWidth(1.0f);
|
||||
|
||||
// center the grid around the camera position on the plane
|
||||
glm::vec3 rotated = inverseRotation * Application::getInstance()->getCamera()->getPosition();
|
||||
const int GRID_DIVISIONS = 300;
|
||||
glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2),
|
||||
spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position);
|
||||
|
||||
float scale = GRID_DIVISIONS * spacing;
|
||||
glScalef(scale, scale, scale);
|
||||
|
||||
_gridProgram.bind();
|
||||
|
||||
Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS);
|
||||
|
||||
_gridProgram.release();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
|
||||
void MetavoxelEditor::updateAttributes(const QString& select) {
|
||||
// remember the selection in order to preserve it
|
||||
QString selected = select.isNull() ? getSelectedAttribute() : select;
|
||||
_attributes->clear();
|
||||
|
||||
// sort the names for consistent ordering
|
||||
QList<QString> names = AttributeRegistry::getInstance()->getAttributes().keys();
|
||||
qSort(names);
|
||||
|
||||
foreach (const QString& name, names) {
|
||||
QListWidgetItem* item = new QListWidgetItem(name);
|
||||
_attributes->addItem(item);
|
||||
if (name == selected || selected.isNull()) {
|
||||
item->setSelected(true);
|
||||
selected = name;
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
QString MetavoxelEditor::getSelectedAttribute() const {
|
||||
QList<QListWidgetItem*> selectedItems = _attributes->selectedItems();
|
||||
return selectedItems.isEmpty() ? QString() : selectedItems.first()->text();
|
||||
}
|
||||
|
||||
double MetavoxelEditor::getGridSpacing() const {
|
||||
return pow(2.0, _gridSpacing->value());
|
||||
}
|
||||
|
||||
glm::quat MetavoxelEditor::getGridRotation() const {
|
||||
// for simplicity, we handle the other two planes by rotating them onto X/Y and performing computation there
|
||||
switch (_gridPlane->currentIndex()) {
|
||||
case GRID_PLANE_XY:
|
||||
return glm::quat();
|
||||
bool BoxSetTool::eventFilter(QObject* watched, QEvent* event) {
|
||||
switch (_state) {
|
||||
case HOVERING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonPress && _startPosition != INVALID_VECTOR) {
|
||||
_state = DRAGGING_STATE;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GRID_PLANE_XZ:
|
||||
return glm::angleAxis(-90.0f, 1.0f, 0.0f, 0.0f);
|
||||
case DRAGGING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonRelease) {
|
||||
_state = RAISING_STATE;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GRID_PLANE_YZ:
|
||||
default:
|
||||
return glm::angleAxis(90.0f, 0.0f, 1.0f, 0.0f);
|
||||
case RAISING_STATE:
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
if (_height != 0) {
|
||||
// find the start and end corners in X/Y
|
||||
float base = _editor->getGridPosition();
|
||||
float top = base + _height;
|
||||
glm::quat rotation = _editor->getGridRotation();
|
||||
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
|
||||
float spacing = _editor->getGridSpacing();
|
||||
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
|
||||
glm::vec2(spacing, spacing), glm::max(base, top));
|
||||
|
||||
// find the minimum and maximum extents after rotation
|
||||
applyValue(glm::min(start, end), glm::max(start, end));
|
||||
}
|
||||
resetState();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetavoxelEditor::resetState() {
|
||||
void BoxSetTool::resetState() {
|
||||
_state = HOVERING_STATE;
|
||||
_startPosition = INVALID_VECTOR;
|
||||
_height = 0.0f;
|
||||
}
|
||||
|
||||
void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(getSelectedAttribute());
|
||||
void BoxSetTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
OwnedAttributeValue value(attribute, attribute->createFromVariant(getValue()));
|
||||
MetavoxelEditMessage edit = { { minimum, maximum }, getGridSpacing(), value };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(edit);
|
||||
OwnedAttributeValue value(attribute, attribute->createFromVariant(_editor->getValue()));
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(BoxSetEdit(Box(minimum, maximum),
|
||||
_editor->getGridSpacing(), value)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message);
|
||||
}
|
||||
|
||||
QVariant MetavoxelEditor::getValue() const {
|
||||
QWidget* editor = _valueArea->widget();
|
||||
return editor ? editor->metaObject()->userProperty().read(editor) : QVariant();
|
||||
GlobalSetTool::GlobalSetTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Set Value (Global)") {
|
||||
|
||||
QPushButton* button = new QPushButton("Apply");
|
||||
layout()->addWidget(button);
|
||||
connect(button, SIGNAL(clicked()), SLOT(apply()));
|
||||
}
|
||||
|
||||
ProgramObject MetavoxelEditor::_gridProgram;
|
||||
void GlobalSetTool::apply() {
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
OwnedAttributeValue value(attribute, attribute->createFromVariant(_editor->getValue()));
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(GlobalSetEdit(value)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message);
|
||||
}
|
||||
|
||||
InsertSpannerTool::InsertSpannerTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Insert Spanner") {
|
||||
|
||||
QPushButton* button = new QPushButton("Insert");
|
||||
layout()->addWidget(button);
|
||||
connect(button, SIGNAL(clicked()), SLOT(insert()));
|
||||
}
|
||||
|
||||
void InsertSpannerTool::simulate(float deltaTime) {
|
||||
SharedObjectPointer spanner = _editor->getValue().value<SharedObjectPointer>();
|
||||
static_cast<Spanner*>(spanner.data())->getRenderer()->simulate(deltaTime);
|
||||
}
|
||||
|
||||
void InsertSpannerTool::render() {
|
||||
_editor->detachValue();
|
||||
Spanner* spanner = static_cast<Spanner*>(_editor->getValue().value<SharedObjectPointer>().data());
|
||||
Transformable* transformable = qobject_cast<Transformable*>(spanner);
|
||||
if (transformable) {
|
||||
// find the intersection of the mouse ray with the grid and place the transformable there
|
||||
glm::quat rotation = _editor->getGridRotation();
|
||||
glm::quat inverseRotation = glm::inverse(rotation);
|
||||
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
|
||||
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
|
||||
float position = _editor->getGridPosition();
|
||||
float distance = (position - rayOrigin.z) / rayDirection.z;
|
||||
|
||||
transformable->setTranslation(rotation * glm::vec3(glm::vec2(rayOrigin + rayDirection * distance), position));
|
||||
}
|
||||
const float SPANNER_ALPHA = 0.25f;
|
||||
spanner->getRenderer()->render(SPANNER_ALPHA);
|
||||
}
|
||||
|
||||
bool InsertSpannerTool::appliesTo(const AttributePointer& attribute) const {
|
||||
return attribute->inherits("SharedObjectSetAttribute");
|
||||
}
|
||||
|
||||
bool InsertSpannerTool::eventFilter(QObject* watched, QEvent* event) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
insert();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void InsertSpannerTool::insert() {
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
SharedObjectPointer spanner = _editor->getValue().value<SharedObjectPointer>();
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(InsertSpannerEdit(attribute, spanner)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message);
|
||||
}
|
||||
|
||||
RemoveSpannerTool::RemoveSpannerTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Remove Spanner", false) {
|
||||
}
|
||||
|
||||
bool RemoveSpannerTool::appliesTo(const AttributePointer& attribute) const {
|
||||
return attribute->inherits("SharedObjectSetAttribute");
|
||||
}
|
||||
|
||||
bool RemoveSpannerTool::eventFilter(QObject* watched, QEvent* event) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearSpannersTool::ClearSpannersTool(MetavoxelEditor* editor) :
|
||||
MetavoxelTool(editor, "Clear Spanners", false) {
|
||||
|
||||
QPushButton* button = new QPushButton("Clear");
|
||||
layout()->addWidget(button);
|
||||
connect(button, SIGNAL(clicked()), SLOT(clear()));
|
||||
}
|
||||
|
||||
bool ClearSpannersTool::appliesTo(const AttributePointer& attribute) const {
|
||||
return attribute->inherits("SharedObjectSetAttribute");
|
||||
}
|
||||
|
||||
void ClearSpannersTool::clear() {
|
||||
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
MetavoxelEditMessage message = { QVariant::fromValue(ClearSpannersEdit(attribute)) };
|
||||
Application::getInstance()->getMetavoxels()->applyEdit(message);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
#ifndef __interface__MetavoxelEditor__
|
||||
#define __interface__MetavoxelEditor__
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include "renderer/ProgramObject.h"
|
||||
|
||||
|
@ -17,55 +18,174 @@ class QComboBox;
|
|||
class QDoubleSpinBox;
|
||||
class QGroupBox;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QScrollArea;
|
||||
|
||||
class MetavoxelTool;
|
||||
|
||||
/// Allows editing metavoxels.
|
||||
class MetavoxelEditor : public QDialog {
|
||||
class MetavoxelEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelEditor();
|
||||
|
||||
QString getSelectedAttribute() const;
|
||||
|
||||
double getGridSpacing() const;
|
||||
double getGridPosition() const;
|
||||
glm::quat getGridRotation() const;
|
||||
|
||||
QVariant getValue() const;
|
||||
void detachValue();
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateValueEditor();
|
||||
void selectedAttributeChanged();
|
||||
void createNewAttribute();
|
||||
void deleteSelectedAttribute();
|
||||
void centerGridPosition();
|
||||
void alignGridPosition();
|
||||
void updateTool();
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
private:
|
||||
|
||||
void updateAttributes(const QString& select = QString());
|
||||
QString getSelectedAttribute() const;
|
||||
double getGridSpacing() const;
|
||||
glm::quat getGridRotation() const;
|
||||
void resetState();
|
||||
void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
|
||||
QVariant getValue() const;
|
||||
void addTool(MetavoxelTool* tool);
|
||||
void updateAttributes(const QString& select = QString());
|
||||
MetavoxelTool* getActiveTool() const;
|
||||
|
||||
QListWidget* _attributes;
|
||||
QPushButton* _deleteAttribute;
|
||||
|
||||
QComboBox* _gridPlane;
|
||||
QDoubleSpinBox* _gridSpacing;
|
||||
QDoubleSpinBox* _gridPosition;
|
||||
|
||||
QList<MetavoxelTool*> _tools;
|
||||
QComboBox* _toolBox;
|
||||
|
||||
QGroupBox* _value;
|
||||
QScrollArea* _valueArea;
|
||||
|
||||
static ProgramObject _gridProgram;
|
||||
};
|
||||
|
||||
/// Base class for editor tools.
|
||||
class MetavoxelTool : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelTool(MetavoxelEditor* editor, const QString& name, bool usesValue = true);
|
||||
|
||||
bool getUsesValue() const { return _usesValue; }
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual void simulate(float deltaTime);
|
||||
|
||||
/// Renders the tool's interface, if any.
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelEditor* _editor;
|
||||
bool _usesValue;
|
||||
};
|
||||
|
||||
/// Allows setting the value of a region by dragging out a box.
|
||||
class BoxSetTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
BoxSetTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual void render();
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
private:
|
||||
|
||||
void resetState();
|
||||
void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
|
||||
|
||||
enum State { HOVERING_STATE, DRAGGING_STATE, RAISING_STATE };
|
||||
|
||||
State _state;
|
||||
|
||||
glm::vec2 _mousePosition; ///< the position of the mouse in rotated space
|
||||
|
||||
glm::vec2 _startPosition; ///< the first corner of the selection base
|
||||
glm::vec2 _endPosition; ///< the second corner of the selection base
|
||||
float _height; ///< the selection height
|
||||
};
|
||||
|
||||
/// Allows setting the value across the entire space.
|
||||
class GlobalSetTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
static ProgramObject _gridProgram;
|
||||
GlobalSetTool(MetavoxelEditor* editor);
|
||||
|
||||
private slots:
|
||||
|
||||
void apply();
|
||||
};
|
||||
|
||||
/// Allows inserting a spanner into the scene.
|
||||
class InsertSpannerTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
InsertSpannerTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual void simulate(float deltaTime);
|
||||
|
||||
virtual void render();
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
private slots:
|
||||
|
||||
void insert();
|
||||
};
|
||||
|
||||
/// Allows removing a spanner from the scene.
|
||||
class RemoveSpannerTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
RemoveSpannerTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
};
|
||||
|
||||
/// Allows removing all spanners from the scene.
|
||||
class ClearSpannersTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ClearSpannersTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
private slots:
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelEditor__) */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
REGISTER_META_OBJECT(QRgbAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectSetAttribute)
|
||||
|
||||
AttributeRegistry* AttributeRegistry::getInstance() {
|
||||
static AttributeRegistry registry;
|
||||
|
@ -22,6 +23,7 @@ AttributeRegistry* AttributeRegistry::getInstance() {
|
|||
AttributeRegistry::AttributeRegistry() :
|
||||
_guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject,
|
||||
SharedObjectPointer(new DefaultMetavoxelGuide())))),
|
||||
_spannersAttribute(registerAttribute(new SharedObjectSetAttribute("spanners", &Spanner::staticMetaObject))),
|
||||
_colorAttribute(registerAttribute(new QRgbAttribute("color"))),
|
||||
_normalAttribute(registerAttribute(new QRgbAttribute("normal", qRgb(0, 127, 0)))) {
|
||||
}
|
||||
|
@ -53,6 +55,10 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
|
|||
return pointer;
|
||||
}
|
||||
|
||||
void AttributeRegistry::deregisterAttribute(const QString& name) {
|
||||
_attributes.remove(name);
|
||||
}
|
||||
|
||||
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
|
||||
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
|
||||
QScriptEngine::PreferExistingWrapperObject);
|
||||
|
@ -223,3 +229,29 @@ QWidget* SharedObjectAttribute::createEditor(QWidget* parent) const {
|
|||
editor->setObject(_defaultValue);
|
||||
return editor;
|
||||
}
|
||||
|
||||
SharedObjectSetAttribute::SharedObjectSetAttribute(const QString& name, const QMetaObject* metaObject) :
|
||||
InlineAttribute<SharedObjectSet>(name),
|
||||
_metaObject(metaObject) {
|
||||
}
|
||||
|
||||
void SharedObjectSetAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
in >> *((SharedObjectSet*)&value);
|
||||
}
|
||||
|
||||
void SharedObjectSetAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
out << decodeInline<SharedObjectSet>(value);
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const {
|
||||
return new SharedObjectEditor(_metaObject, parent);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class QScriptValue;
|
|||
|
||||
class Attribute;
|
||||
|
||||
typedef QSharedPointer<Attribute> AttributePointer;
|
||||
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
|
||||
|
||||
/// Maintains information about metavoxel attribute types.
|
||||
class AttributeRegistry {
|
||||
|
@ -48,15 +48,21 @@ public:
|
|||
/// attribute
|
||||
AttributePointer registerAttribute(AttributePointer attribute);
|
||||
|
||||
/// Deregisters an attribute.
|
||||
void deregisterAttribute(const QString& name);
|
||||
|
||||
/// Retrieves an attribute by name.
|
||||
AttributePointer getAttribute(const QString& name) const { return _attributes.value(name); }
|
||||
|
||||
/// Returns a reference to the attribute hash.
|
||||
const QHash<QString, AttributePointer>& getAttributes() const { return _attributes; }
|
||||
|
||||
/// Returns a reference to the standard PolymorphicDataPointer "guide" attribute.
|
||||
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
|
||||
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectSet "spanners" attribute.
|
||||
const AttributePointer& getSpannersAttribute() const { return _spannersAttribute; }
|
||||
|
||||
/// Returns a reference to the standard QRgb "color" attribute.
|
||||
const AttributePointer& getColorAttribute() const { return _colorAttribute; }
|
||||
|
||||
|
@ -69,6 +75,7 @@ private:
|
|||
|
||||
QHash<QString, AttributePointer> _attributes;
|
||||
AttributePointer _guideAttribute;
|
||||
AttributePointer _spannersAttribute;
|
||||
AttributePointer _colorAttribute;
|
||||
AttributePointer _normalAttribute;
|
||||
};
|
||||
|
@ -141,7 +148,7 @@ public:
|
|||
};
|
||||
|
||||
/// Represents a registered attribute.
|
||||
class Attribute : public QObject {
|
||||
class Attribute : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -260,7 +267,8 @@ class SharedObjectAttribute : public InlineAttribute<SharedObjectPointer> {
|
|||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(), const QMetaObject* metaObject = NULL,
|
||||
Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(),
|
||||
const QMetaObject* metaObject = &SharedObject::staticMetaObject,
|
||||
const SharedObjectPointer& defaultValue = SharedObjectPointer());
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
|
@ -277,4 +285,28 @@ private:
|
|||
const QMetaObject* _metaObject;
|
||||
};
|
||||
|
||||
/// An attribute that takes the form of a set of shared objects.
|
||||
class SharedObjectSetAttribute : public InlineAttribute<SharedObjectSet> {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SharedObjectSetAttribute(const QString& name = QString(),
|
||||
const QMetaObject* metaObject = &SharedObject::staticMetaObject);
|
||||
|
||||
const QMetaObject* getMetaObject() const { return _metaObject; }
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[]) const;
|
||||
|
||||
virtual QWidget* createEditor(QWidget* parent = NULL) const;
|
||||
|
||||
private:
|
||||
|
||||
const QMetaObject* _metaObject;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__AttributeRegistry__) */
|
||||
|
|
|
@ -68,8 +68,8 @@ IDStreamer& IDStreamer::operator>>(int& value) {
|
|||
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
|
||||
getMetaObjects().insert(className, metaObject);
|
||||
|
||||
// register it as a subclass of all of its superclasses
|
||||
for (const QMetaObject* superClass = metaObject->superClass(); superClass != NULL; superClass = superClass->superClass()) {
|
||||
// register it as a subclass of itself and all of its superclasses
|
||||
for (const QMetaObject* superClass = metaObject; superClass != NULL; superClass = superClass->superClass()) {
|
||||
getMetaObjectSubClasses().insert(superClass, metaObject);
|
||||
}
|
||||
return 0;
|
||||
|
@ -81,6 +81,10 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
|
||||
return getMetaObjects().value(className);
|
||||
}
|
||||
|
||||
QList<const QMetaObject*> Bitstream::getMetaObjectSubClasses(const QMetaObject* metaObject) {
|
||||
return getMetaObjectSubClasses().values(metaObject);
|
||||
}
|
||||
|
@ -351,17 +355,7 @@ Bitstream& Bitstream::operator>>(QObject*& object) {
|
|||
object = NULL;
|
||||
return *this;
|
||||
}
|
||||
object = metaObject->newInstance();
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored(object)) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
|
||||
if (streamer) {
|
||||
property.write(object, streamer->read(*this));
|
||||
}
|
||||
}
|
||||
readProperties(object = metaObject->newInstance());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -472,13 +466,34 @@ Bitstream& Bitstream::operator>(QScriptString& string) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
|
||||
return *this << object.data();
|
||||
if (!object) {
|
||||
return *this << (int)0;
|
||||
}
|
||||
return *this << object->getID() << (QObject*)object.data();
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
||||
QObject* rawObject;
|
||||
*this >> rawObject;
|
||||
object = static_cast<SharedObject*>(rawObject);
|
||||
int id;
|
||||
*this >> id;
|
||||
if (id == 0) {
|
||||
object = SharedObjectPointer();
|
||||
return *this;
|
||||
}
|
||||
QPointer<SharedObject>& pointer = _transientSharedObjects[id];
|
||||
if (pointer) {
|
||||
const QMetaObject* metaObject;
|
||||
_metaObjectStreamer >> metaObject;
|
||||
if (metaObject != pointer->metaObject()) {
|
||||
qWarning() << "Class mismatch: " << pointer->metaObject()->className() << metaObject->className();
|
||||
}
|
||||
readProperties(pointer.data());
|
||||
|
||||
} else {
|
||||
QObject* rawObject;
|
||||
*this >> rawObject;
|
||||
pointer = static_cast<SharedObject*>(rawObject);
|
||||
}
|
||||
object = static_cast<SharedObject*>(pointer.data());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -488,6 +503,20 @@ void Bitstream::clearSharedObject() {
|
|||
emit sharedObjectCleared(_sharedObjectStreamer.takePersistentID(object));
|
||||
}
|
||||
|
||||
void Bitstream::readProperties(QObject* object) {
|
||||
const QMetaObject* metaObject = object->metaObject();
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored(object)) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
|
||||
if (streamer) {
|
||||
property.write(object, streamer->read(*this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
|
||||
static QHash<QByteArray, const QMetaObject*> metaObjects;
|
||||
return metaObjects;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QPointer>
|
||||
#include <QScriptString>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
|
@ -31,7 +32,7 @@ class Bitstream;
|
|||
class OwnedAttributeValue;
|
||||
class TypeStreamer;
|
||||
|
||||
typedef QSharedPointer<Attribute> AttributePointer;
|
||||
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
|
||||
|
||||
/// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that
|
||||
/// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows
|
||||
|
@ -196,6 +197,9 @@ public:
|
|||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
static int registerTypeStreamer(int type, TypeStreamer* streamer);
|
||||
|
||||
/// Returns the meta-object registered under the supplied class name, if any.
|
||||
static const QMetaObject* getMetaObject(const QByteArray& className);
|
||||
|
||||
/// Returns the list of registered subclasses for the supplied meta-object.
|
||||
static QList<const QMetaObject*> getMetaObjectSubClasses(const QMetaObject* metaObject);
|
||||
|
||||
|
@ -266,6 +270,9 @@ public:
|
|||
template<class T> Bitstream& operator<<(const QList<T>& list);
|
||||
template<class T> Bitstream& operator>>(QList<T>& list);
|
||||
|
||||
template<class T> Bitstream& operator<<(const QSet<T>& set);
|
||||
template<class T> Bitstream& operator>>(QSet<T>& set);
|
||||
|
||||
template<class K, class V> Bitstream& operator<<(const QHash<K, V>& hash);
|
||||
template<class K, class V> Bitstream& operator>>(QHash<K, V>& hash);
|
||||
|
||||
|
@ -311,6 +318,8 @@ private slots:
|
|||
void clearSharedObject();
|
||||
|
||||
private:
|
||||
|
||||
void readProperties(QObject* object);
|
||||
|
||||
QDataStream& _underlying;
|
||||
quint8 _byte;
|
||||
|
@ -322,6 +331,8 @@ private:
|
|||
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
|
||||
RepeatedValueStreamer<SharedObjectPointer> _sharedObjectStreamer;
|
||||
|
||||
QHash<int, QPointer<SharedObject> > _transientSharedObjects;
|
||||
|
||||
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
|
||||
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
|
||||
static QHash<int, const TypeStreamer*>& getTypeStreamers();
|
||||
|
@ -348,6 +359,27 @@ template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& list) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator<<(const QSet<T>& set) {
|
||||
*this << set.size();
|
||||
foreach (const T& entry, set) {
|
||||
*this << entry;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator>>(QSet<T>& set) {
|
||||
int size;
|
||||
*this >> size;
|
||||
set.clear();
|
||||
set.reserve(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
T entry;
|
||||
*this >> entry;
|
||||
set.insert(entry);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class K, class V> inline Bitstream& Bitstream::operator<<(const QHash<K, V>& hash) {
|
||||
*this << hash.size();
|
||||
for (typename QHash<K, V>::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) {
|
||||
|
|
|
@ -42,6 +42,7 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject*
|
|||
_outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
connect(&_outputStream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
|
||||
connect(this, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&)));
|
||||
|
||||
memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize);
|
||||
}
|
||||
|
@ -182,7 +183,7 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
|||
QVariant data;
|
||||
_inputStream >> data;
|
||||
if ((int)i >= _receivedHighPriorityMessages) {
|
||||
handleHighPriorityMessage(data);
|
||||
emit receivedHighPriorityMessage(data);
|
||||
}
|
||||
}
|
||||
_receivedHighPriorityMessages = highPriorityMessageCount;
|
||||
|
@ -208,9 +209,22 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
|||
}
|
||||
|
||||
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
|
||||
// for now, high priority
|
||||
ClearSharedObjectMessage message = { id };
|
||||
sendHighPriorityMessage(QVariant::fromValue(message));
|
||||
// send it low-priority unless the channel has messages disabled
|
||||
ReliableChannel* channel = getReliableOutputChannel();
|
||||
if (channel->getMessagesEnabled()) {
|
||||
ClearMainChannelSharedObjectMessage message = { id };
|
||||
channel->sendMessage(QVariant::fromValue(message));
|
||||
|
||||
} else {
|
||||
ClearSharedObjectMessage message = { id };
|
||||
sendHighPriorityMessage(QVariant::fromValue(message));
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
|
||||
if (data.userType() == ClearSharedObjectMessage::Type) {
|
||||
_inputStream.clearSharedObject(data.value<ClearSharedObjectMessage>().id);
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
||||
|
@ -303,15 +317,6 @@ void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<Chann
|
|||
} while(offset < packet.size());
|
||||
}
|
||||
|
||||
void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
|
||||
if (data.userType() == ClearSharedObjectMessage::Type) {
|
||||
_inputStream.clearSharedObject(data.value<ClearSharedObjectMessage>().id);
|
||||
|
||||
} else {
|
||||
emit receivedHighPriorityMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
const int INITIAL_CIRCULAR_BUFFER_CAPACITY = 16;
|
||||
|
||||
CircularBuffer::CircularBuffer(QObject* parent) :
|
||||
|
@ -343,16 +348,32 @@ void CircularBuffer::remove(int length) {
|
|||
}
|
||||
|
||||
QByteArray CircularBuffer::readBytes(int offset, int length) const {
|
||||
// write in up to two segments
|
||||
QByteArray bytes(length, 0);
|
||||
readBytes(offset, length, bytes.data());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void CircularBuffer::readBytes(int offset, int length, char* data) const {
|
||||
// read in up to two segments
|
||||
QByteArray array;
|
||||
int start = (_position + offset) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - start);
|
||||
array.append(_data.constData() + start, firstSegment);
|
||||
memcpy(data, _data.constData() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
array.append(_data.constData(), secondSegment);
|
||||
memcpy(data + firstSegment, _data.constData(), secondSegment);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularBuffer::writeBytes(int offset, int length, const char* data) {
|
||||
// write in up to two segments
|
||||
int start = (_position + offset) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - start);
|
||||
memcpy(_data.data() + start, data, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
memcpy(_data.data(), data + firstSegment, secondSegment);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
void CircularBuffer::writeToStream(int offset, int length, QDataStream& out) const {
|
||||
|
@ -561,7 +582,14 @@ int ReliableChannel::getBytesAvailable() const {
|
|||
}
|
||||
|
||||
void ReliableChannel::sendMessage(const QVariant& message) {
|
||||
// write a placeholder for the length, then fill it in when we know what it is
|
||||
int placeholder = _buffer.pos();
|
||||
_dataStream << (quint32)0;
|
||||
_bitstream << message;
|
||||
_bitstream.flush();
|
||||
|
||||
quint32 length = _buffer.pos() - placeholder;
|
||||
_buffer.writeBytes(placeholder, sizeof(quint32), (const char*)&length);
|
||||
}
|
||||
|
||||
void ReliableChannel::sendClearSharedObjectMessage(int id) {
|
||||
|
@ -569,6 +597,16 @@ void ReliableChannel::sendClearSharedObjectMessage(int id) {
|
|||
sendMessage(QVariant::fromValue(message));
|
||||
}
|
||||
|
||||
void ReliableChannel::handleMessage(const QVariant& message) {
|
||||
if (message.userType() == ClearSharedObjectMessage::Type) {
|
||||
_bitstream.clearSharedObject(message.value<ClearSharedObjectMessage>().id);
|
||||
|
||||
} else if (message.userType() == ClearMainChannelSharedObjectMessage::Type) {
|
||||
static_cast<DatagramSequencer*>(parent())->_inputStream.clearSharedObject(
|
||||
message.value<ClearMainChannelSharedObjectMessage>().id);
|
||||
}
|
||||
}
|
||||
|
||||
ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) :
|
||||
QObject(sequencer),
|
||||
_index(index),
|
||||
|
@ -576,12 +614,14 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
|
|||
_bitstream(_dataStream),
|
||||
_priority(1.0f),
|
||||
_offset(0),
|
||||
_writePosition(0) {
|
||||
_writePosition(0),
|
||||
_messagesEnabled(true) {
|
||||
|
||||
_buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
|
||||
_dataStream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int)));
|
||||
connect(this, SIGNAL(receivedMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
|
||||
}
|
||||
|
||||
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
|
||||
|
@ -688,10 +728,32 @@ void ReliableChannel::readData(QDataStream& in) {
|
|||
readSome = true;
|
||||
}
|
||||
}
|
||||
if (!readSome) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let listeners know that there's data to read
|
||||
if (readSome) {
|
||||
emit _buffer.readyRead();
|
||||
forever {
|
||||
// if we're expecting a message, peek into the buffer to see if we have the whole thing.
|
||||
// if so, read it in, handle it, and loop back around in case there are more
|
||||
if (_messagesEnabled) {
|
||||
quint32 available = _buffer.bytesAvailable();
|
||||
if (available >= sizeof(quint32)) {
|
||||
quint32 length;
|
||||
_buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length);
|
||||
if (available >= length) {
|
||||
_dataStream.skipRawData(sizeof(quint32));
|
||||
QVariant message;
|
||||
_bitstream >> message;
|
||||
_bitstream.reset();
|
||||
emit receivedMessage(message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// otherwise, just let whoever's listening know that data is available
|
||||
} else {
|
||||
emit _buffer.readyRead();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// prune any read data from the buffer
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
|
||||
/// Processes a datagram received from the other party, emitting readyToRead when the entire packet
|
||||
/// has been successfully assembled.
|
||||
void receivedDatagram(const QByteArray& datagram);
|
||||
Q_INVOKABLE void receivedDatagram(const QByteArray& datagram);
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -94,6 +94,7 @@ signals:
|
|||
private slots:
|
||||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
void handleHighPriorityMessage(const QVariant& data);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -133,8 +134,6 @@ private:
|
|||
/// readyToWrite) as necessary.
|
||||
void sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans);
|
||||
|
||||
void handleHighPriorityMessage(const QVariant& data);
|
||||
|
||||
QList<SendRecord> _sendRecords;
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
|
||||
|
@ -185,6 +184,12 @@ public:
|
|||
/// Reads part of the data from the buffer.
|
||||
QByteArray readBytes(int offset, int length) const;
|
||||
|
||||
/// Reads part of the data from the buffer.
|
||||
void readBytes(int offset, int length, char* data) const;
|
||||
|
||||
/// Writes to part of the data in the buffer.
|
||||
void writeBytes(int offset, int length, const char* data);
|
||||
|
||||
/// Writes part of the buffer to the supplied stream.
|
||||
void writeToStream(int offset, int length, QDataStream& out) const;
|
||||
|
||||
|
@ -267,12 +272,22 @@ public:
|
|||
|
||||
int getBytesAvailable() const;
|
||||
|
||||
/// Sets whether we expect to write/read framed messages.
|
||||
void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; }
|
||||
bool getMessagesEnabled() const { return _messagesEnabled; }
|
||||
|
||||
/// Sends a framed message on this channel.
|
||||
void sendMessage(const QVariant& message);
|
||||
|
||||
signals:
|
||||
|
||||
void receivedMessage(const QVariant& message);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
|
||||
void handleMessage(const QVariant& message);
|
||||
|
||||
private:
|
||||
|
||||
friend class DatagramSequencer;
|
||||
|
@ -297,6 +312,7 @@ private:
|
|||
int _offset;
|
||||
int _writePosition;
|
||||
SpanList _acknowledged;
|
||||
bool _messagesEnabled;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__DatagramSequencer__) */
|
||||
|
|
|
@ -18,6 +18,8 @@ REGISTER_META_OBJECT(MetavoxelGuide)
|
|||
REGISTER_META_OBJECT(DefaultMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(ScriptedMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(ThrobbingMetavoxelGuide)
|
||||
REGISTER_META_OBJECT(Spanner)
|
||||
REGISTER_META_OBJECT(StaticModel)
|
||||
|
||||
MetavoxelData::MetavoxelData() : _size(1.0f) {
|
||||
}
|
||||
|
@ -43,17 +45,19 @@ MetavoxelData& MetavoxelData::operator=(const MetavoxelData& other) {
|
|||
|
||||
Box MetavoxelData::getBounds() const {
|
||||
float halfSize = _size * 0.5f;
|
||||
Box bounds = { glm::vec3(-halfSize, -halfSize, -halfSize), glm::vec3(halfSize, halfSize, halfSize) };
|
||||
return bounds;
|
||||
return Box(glm::vec3(-halfSize, -halfSize, -halfSize), glm::vec3(halfSize, halfSize, halfSize));
|
||||
}
|
||||
|
||||
void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
||||
// let the visitor know we're about to begin a tour
|
||||
visitor.prepare();
|
||||
|
||||
// start with the root values/defaults (plus the guide attribute)
|
||||
const QVector<AttributePointer>& inputs = visitor.getInputs();
|
||||
const QVector<AttributePointer>& outputs = visitor.getOutputs();
|
||||
MetavoxelVisitation firstVisitation = { NULL, visitor, QVector<MetavoxelNode*>(inputs.size() + 1),
|
||||
QVector<MetavoxelNode*>(outputs.size()), { glm::vec3(_size, _size, _size) * -0.5f, _size,
|
||||
QVector<AttributeValue>(inputs.size() + 1), QVector<AttributeValue>(outputs.size()) } };
|
||||
QVector<AttributeValue>(inputs.size() + 1), QVector<OwnedAttributeValue>(outputs.size()) } };
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
MetavoxelNode* node = _roots.value(inputs.at(i));
|
||||
firstVisitation.inputNodes[i] = node;
|
||||
|
@ -70,7 +74,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
|||
static_cast<MetavoxelGuide*>(firstVisitation.info.inputValues.last().getInlineValue<
|
||||
SharedObjectPointer>().data())->guide(firstVisitation);
|
||||
for (int i = 0; i < outputs.size(); i++) {
|
||||
AttributeValue& value = firstVisitation.info.outputValues[i];
|
||||
OwnedAttributeValue& value = firstVisitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -88,6 +92,103 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
|||
}
|
||||
}
|
||||
|
||||
class InsertVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
InsertVisitor(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const AttributePointer& _attribute;
|
||||
const Box& _bounds;
|
||||
float _longestSide;
|
||||
const SharedObjectPointer& _object;
|
||||
};
|
||||
|
||||
InsertVisitor::InsertVisitor(const AttributePointer& attribute, const Box& bounds,
|
||||
float granularity, const SharedObjectPointer& object) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>() << attribute),
|
||||
_attribute(attribute),
|
||||
_bounds(bounds),
|
||||
_longestSide(qMax(bounds.getLongestSide(), granularity)),
|
||||
_object(object) {
|
||||
}
|
||||
|
||||
bool InsertVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.getBounds().intersects(_bounds)) {
|
||||
return false;
|
||||
}
|
||||
if (info.size > _longestSide) {
|
||||
return true;
|
||||
}
|
||||
SharedObjectSet set = info.inputValues.at(0).getInlineValue<SharedObjectSet>();
|
||||
set.insert(_object);
|
||||
info.outputValues[0] = AttributeValue(_attribute, encodeInline(set));
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds,
|
||||
float granularity, const SharedObjectPointer& object) {
|
||||
// expand to fit the entire bounds
|
||||
while (!getBounds().contains(bounds)) {
|
||||
expand();
|
||||
}
|
||||
InsertVisitor visitor(attribute, bounds, granularity, object);
|
||||
guide(visitor);
|
||||
}
|
||||
|
||||
class RemoveVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
RemoveVisitor(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const AttributePointer& _attribute;
|
||||
const Box& _bounds;
|
||||
float _longestSide;
|
||||
const SharedObjectPointer& _object;
|
||||
};
|
||||
|
||||
RemoveVisitor::RemoveVisitor(const AttributePointer& attribute, const Box& bounds,
|
||||
float granularity, const SharedObjectPointer& object) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>() << attribute),
|
||||
_attribute(attribute),
|
||||
_bounds(bounds),
|
||||
_longestSide(qMax(bounds.getLongestSide(), granularity)),
|
||||
_object(object) {
|
||||
}
|
||||
|
||||
bool RemoveVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.getBounds().intersects(_bounds)) {
|
||||
return false;
|
||||
}
|
||||
if (info.size > _longestSide) {
|
||||
return true;
|
||||
}
|
||||
SharedObjectSet set = info.inputValues.at(0).getInlineValue<SharedObjectSet>();
|
||||
set.remove(_object);
|
||||
info.outputValues[0] = AttributeValue(_attribute, encodeInline(set));
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds,
|
||||
float granularity, const SharedObjectPointer& object) {
|
||||
RemoveVisitor visitor(attribute, bounds, granularity, object);
|
||||
guide(visitor);
|
||||
}
|
||||
|
||||
void MetavoxelData::clear(const AttributePointer& attribute) {
|
||||
MetavoxelNode* node = _roots.take(attribute);
|
||||
if (node) {
|
||||
node->decrementReferenceCount(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
const int X_MAXIMUM_FLAG = 1;
|
||||
const int Y_MAXIMUM_FLAG = 2;
|
||||
const int Z_MAXIMUM_FLAG = 4;
|
||||
|
@ -432,6 +533,32 @@ MetavoxelVisitor::MetavoxelVisitor(const QVector<AttributePointer>& inputs, cons
|
|||
MetavoxelVisitor::~MetavoxelVisitor() {
|
||||
}
|
||||
|
||||
void MetavoxelVisitor::prepare() {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
SpannerVisitor::SpannerVisitor(const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs) :
|
||||
MetavoxelVisitor(inputs + spannerInputs, outputs),
|
||||
_spannerInputCount(spannerInputs.size()) {
|
||||
}
|
||||
|
||||
void SpannerVisitor::prepare() {
|
||||
Spanner::incrementVisit();
|
||||
}
|
||||
|
||||
bool SpannerVisitor::visit(MetavoxelInfo& info) {
|
||||
for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) {
|
||||
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (spanner->testAndSetVisited()) {
|
||||
visit(spanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !info.isLeaf;
|
||||
}
|
||||
|
||||
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
|
||||
}
|
||||
|
||||
|
@ -439,7 +566,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
visitation.info.isLeaf = visitation.allInputNodesLeaves();
|
||||
bool keepGoing = visitation.visitor.visit(visitation.info);
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
AttributeValue& value = visitation.info.outputValues[i];
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -457,7 +584,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
MetavoxelVisitation nextVisitation = { &visitation, visitation.visitor,
|
||||
QVector<MetavoxelNode*>(visitation.inputNodes.size()), QVector<MetavoxelNode*>(visitation.outputNodes.size()),
|
||||
{ glm::vec3(), visitation.info.size * 0.5f, QVector<AttributeValue>(visitation.inputNodes.size()),
|
||||
QVector<AttributeValue>(visitation.outputNodes.size()) } };
|
||||
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
for (int j = 0; j < visitation.inputNodes.size(); j++) {
|
||||
MetavoxelNode* node = visitation.inputNodes.at(j);
|
||||
|
@ -478,12 +605,12 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
static_cast<MetavoxelGuide*>(nextVisitation.info.inputValues.last().getInlineValue<
|
||||
SharedObjectPointer>().data())->guide(nextVisitation);
|
||||
for (int j = 0; j < nextVisitation.outputNodes.size(); j++) {
|
||||
AttributeValue& value = nextVisitation.info.outputValues[j];
|
||||
OwnedAttributeValue& value = nextVisitation.info.outputValues[j];
|
||||
if (!value.getAttribute()) {
|
||||
continue;
|
||||
}
|
||||
// replace the child
|
||||
AttributeValue& parentValue = visitation.info.outputValues[j];
|
||||
OwnedAttributeValue& parentValue = visitation.info.outputValues[j];
|
||||
if (!parentValue.getAttribute()) {
|
||||
// shallow-copy the parent node on first change
|
||||
parentValue = value;
|
||||
|
@ -511,7 +638,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
}
|
||||
}
|
||||
for (int i = 0; i < visitation.outputNodes.size(); i++) {
|
||||
AttributeValue& value = visitation.info.outputValues[i];
|
||||
OwnedAttributeValue& value = visitation.info.outputValues[i];
|
||||
if (value.getAttribute()) {
|
||||
MetavoxelNode* node = visitation.outputNodes.at(i);
|
||||
node->mergeChildren(value.getAttribute());
|
||||
|
@ -674,3 +801,96 @@ AttributeValue MetavoxelVisitation::getInheritedOutputValue(int index) const {
|
|||
return AttributeValue(visitor.getOutputs().at(index));
|
||||
}
|
||||
|
||||
const float DEFAULT_GRANULARITY = 0.01f;
|
||||
|
||||
Spanner::Spanner() :
|
||||
_granularity(DEFAULT_GRANULARITY),
|
||||
_lastVisit(0),
|
||||
_renderer(NULL) {
|
||||
}
|
||||
|
||||
void Spanner::setBounds(const Box& bounds) {
|
||||
if (_bounds == bounds) {
|
||||
return;
|
||||
}
|
||||
emit boundsWillChange();
|
||||
emit boundsChanged(_bounds = bounds);
|
||||
}
|
||||
|
||||
bool Spanner::testAndSetVisited() {
|
||||
if (_lastVisit == _visit) {
|
||||
return false;
|
||||
}
|
||||
_lastVisit = _visit;
|
||||
return true;
|
||||
}
|
||||
|
||||
SpannerRenderer* Spanner::getRenderer() {
|
||||
if (!_renderer) {
|
||||
QByteArray className = getRendererClassName();
|
||||
const QMetaObject* metaObject = Bitstream::getMetaObject(className);
|
||||
if (!metaObject) {
|
||||
qDebug() << "Unknown class name:" << className;
|
||||
metaObject = &SpannerRenderer::staticMetaObject;
|
||||
}
|
||||
_renderer = static_cast<SpannerRenderer*>(metaObject->newInstance());
|
||||
_renderer->setParent(this);
|
||||
_renderer->init(this);
|
||||
}
|
||||
return _renderer;
|
||||
}
|
||||
|
||||
QByteArray Spanner::getRendererClassName() const {
|
||||
return "SpannerRendererer";
|
||||
}
|
||||
|
||||
int Spanner::_visit = 0;
|
||||
|
||||
SpannerRenderer::SpannerRenderer() {
|
||||
}
|
||||
|
||||
void SpannerRenderer::init(Spanner* spanner) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SpannerRenderer::simulate(float deltaTime) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SpannerRenderer::render(float alpha) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
Transformable::Transformable() : _scale(1.0f) {
|
||||
}
|
||||
|
||||
void Transformable::setTranslation(const glm::vec3& translation) {
|
||||
if (_translation != translation) {
|
||||
emit translationChanged(_translation = translation);
|
||||
}
|
||||
}
|
||||
|
||||
void Transformable::setRotation(const glm::vec3& rotation) {
|
||||
if (_rotation != rotation) {
|
||||
emit rotationChanged(_rotation = rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void Transformable::setScale(float scale) {
|
||||
if (_scale != scale) {
|
||||
emit scaleChanged(_scale = scale);
|
||||
}
|
||||
}
|
||||
|
||||
StaticModel::StaticModel() {
|
||||
}
|
||||
|
||||
void StaticModel::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
emit urlChanged(_url = url);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray StaticModel::getRendererClassName() const {
|
||||
return "StaticModelRenderer";
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ class MetavoxelNode;
|
|||
class MetavoxelVisitation;
|
||||
class MetavoxelVisitor;
|
||||
class NetworkValue;
|
||||
class Spanner;
|
||||
class SpannerRenderer;
|
||||
|
||||
/// The base metavoxel representation shared between server and client.
|
||||
class MetavoxelData {
|
||||
|
@ -45,7 +47,13 @@ public:
|
|||
|
||||
/// Applies the specified visitor to the contained voxels.
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
void clear(const AttributePointer& attribute);
|
||||
|
||||
/// Expands the tree, increasing its capacity in all dimensions.
|
||||
void expand();
|
||||
|
||||
|
@ -121,15 +129,18 @@ public:
|
|||
glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel
|
||||
float size; ///< the size of the voxel in all dimensions
|
||||
QVector<AttributeValue> inputValues;
|
||||
QVector<AttributeValue> outputValues;
|
||||
QVector<OwnedAttributeValue> outputValues;
|
||||
bool isLeaf;
|
||||
|
||||
Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); }
|
||||
};
|
||||
|
||||
/// Interface for visitors to metavoxels.
|
||||
class MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
MetavoxelVisitor(const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs);
|
||||
MetavoxelVisitor(const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>());
|
||||
virtual ~MetavoxelVisitor();
|
||||
|
||||
/// Returns a reference to the list of input attributes desired.
|
||||
|
@ -138,6 +149,9 @@ public:
|
|||
/// Returns a reference to the list of output attributes provided.
|
||||
const QVector<AttributePointer>& getOutputs() const { return _outputs; }
|
||||
|
||||
/// Prepares for a new tour of the metavoxel data.
|
||||
virtual void prepare();
|
||||
|
||||
/// Visits a metavoxel.
|
||||
/// \param info the metavoxel data
|
||||
/// \return if true, continue descending; if false, stop
|
||||
|
@ -151,6 +165,25 @@ protected:
|
|||
|
||||
typedef QSharedPointer<MetavoxelVisitor> MetavoxelVisitorPointer;
|
||||
|
||||
/// Interface for visitors to spanners.
|
||||
class SpannerVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
SpannerVisitor(const QVector<AttributePointer>& spannerInputs,
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>());
|
||||
|
||||
/// Visits a spanner.
|
||||
virtual void visit(Spanner* spanner) = 0;
|
||||
|
||||
virtual void prepare();
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
};
|
||||
|
||||
/// Interface for objects that guide metavoxel visitors.
|
||||
class MetavoxelGuide : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
@ -242,4 +275,121 @@ public:
|
|||
AttributeValue getInheritedOutputValue(int index) const;
|
||||
};
|
||||
|
||||
/// An object that spans multiple octree cells.
|
||||
class Spanner : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false)
|
||||
Q_PROPERTY(float granularity MEMBER _granularity DESIGNABLE false)
|
||||
|
||||
public:
|
||||
|
||||
/// Increments the value of the global visit counter.
|
||||
static void incrementVisit() { _visit++; }
|
||||
|
||||
Spanner();
|
||||
|
||||
void setBounds(const Box& bounds);
|
||||
const Box& getBounds() const { return _bounds; }
|
||||
|
||||
void setGranularity(float granularity) { _granularity = granularity; }
|
||||
float getGranularity() const { return _granularity; }
|
||||
|
||||
/// Checks whether we've visited this object on the current traversal. If we have, returns false.
|
||||
/// If we haven't, sets the last visit identifier and returns true.
|
||||
bool testAndSetVisited();
|
||||
|
||||
/// Returns a pointer to the renderer, creating it if necessary.
|
||||
SpannerRenderer* getRenderer();
|
||||
|
||||
signals:
|
||||
|
||||
void boundsWillChange();
|
||||
void boundsChanged(const Box& bounds);
|
||||
|
||||
protected:
|
||||
|
||||
/// Returns the name of the class to instantiate in order to render this spanner.
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private:
|
||||
|
||||
Box _bounds;
|
||||
float _granularity;
|
||||
int _lastVisit; ///< the identifier of the last visit
|
||||
SpannerRenderer* _renderer;
|
||||
|
||||
static int _visit; ///< the global visit counter
|
||||
};
|
||||
|
||||
/// Base class for objects that can render spanners.
|
||||
class SpannerRenderer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SpannerRenderer();
|
||||
|
||||
virtual void init(Spanner* spanner);
|
||||
virtual void simulate(float deltaTime);
|
||||
virtual void render(float alpha);
|
||||
};
|
||||
|
||||
/// An object with a 3D transform.
|
||||
class Transformable : public Spanner {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged)
|
||||
Q_PROPERTY(glm::vec3 rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged)
|
||||
Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged)
|
||||
|
||||
public:
|
||||
|
||||
Transformable();
|
||||
|
||||
void setTranslation(const glm::vec3& translation);
|
||||
const glm::vec3& getTranslation() const { return _translation; }
|
||||
|
||||
void setRotation(const glm::vec3& rotation);
|
||||
const glm::vec3& getRotation() const { return _rotation; }
|
||||
|
||||
void setScale(float scale);
|
||||
float getScale() const { return _scale; }
|
||||
|
||||
signals:
|
||||
|
||||
void translationChanged(const glm::vec3& translation);
|
||||
void rotationChanged(const glm::vec3& rotation);
|
||||
void scaleChanged(float scale);
|
||||
|
||||
private:
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::vec3 _rotation;
|
||||
float _scale;
|
||||
};
|
||||
|
||||
/// A static 3D model loaded from the network.
|
||||
class StaticModel : public Transformable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUrl url MEMBER _url WRITE setURL NOTIFY urlChanged)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE StaticModel();
|
||||
|
||||
void setURL(const QUrl& url);
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
signals:
|
||||
|
||||
void urlChanged(const QUrl& url);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private:
|
||||
|
||||
QUrl _url;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelData__) */
|
||||
|
|
|
@ -9,24 +9,35 @@
|
|||
#include "MetavoxelData.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
||||
class EditVisitor : public MetavoxelVisitor {
|
||||
void MetavoxelEditMessage::apply(MetavoxelData& data) const {
|
||||
static_cast<const MetavoxelEdit*>(edit.data())->apply(data);
|
||||
}
|
||||
|
||||
MetavoxelEdit::~MetavoxelEdit() {
|
||||
}
|
||||
|
||||
BoxSetEdit::BoxSetEdit(const Box& region, float granularity, const OwnedAttributeValue& value) :
|
||||
region(region), granularity(granularity), value(value) {
|
||||
}
|
||||
|
||||
class BoxSetEditVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
EditVisitor(const MetavoxelEditMessage& edit);
|
||||
BoxSetEditVisitor(const BoxSetEdit& edit);
|
||||
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const MetavoxelEditMessage& _edit;
|
||||
const BoxSetEdit& _edit;
|
||||
};
|
||||
|
||||
EditVisitor::EditVisitor(const MetavoxelEditMessage& edit) :
|
||||
BoxSetEditVisitor::BoxSetEditVisitor(const BoxSetEdit& edit) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
|
||||
_edit(edit) {
|
||||
}
|
||||
|
||||
bool EditVisitor::visit(MetavoxelInfo& info) {
|
||||
bool BoxSetEditVisitor::visit(MetavoxelInfo& info) {
|
||||
// find the intersection between volume and voxel
|
||||
glm::vec3 minimum = glm::max(info.minimum, _edit.region.minimum);
|
||||
glm::vec3 maximum = glm::min(info.minimum + glm::vec3(info.size, info.size, info.size), _edit.region.maximum);
|
||||
|
@ -48,12 +59,69 @@ bool EditVisitor::visit(MetavoxelInfo& info) {
|
|||
return true; // subdivide
|
||||
}
|
||||
|
||||
void MetavoxelEditMessage::apply(MetavoxelData& data) const {
|
||||
void BoxSetEdit::apply(MetavoxelData& data) const {
|
||||
// expand to fit the entire edit
|
||||
while (!data.getBounds().contains(region)) {
|
||||
data.expand();
|
||||
}
|
||||
|
||||
EditVisitor visitor(*this);
|
||||
BoxSetEditVisitor visitor(*this);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
||||
GlobalSetEdit::GlobalSetEdit(const OwnedAttributeValue& value) :
|
||||
value(value) {
|
||||
}
|
||||
|
||||
class GlobalSetEditVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
GlobalSetEditVisitor(const GlobalSetEdit& edit);
|
||||
|
||||
virtual bool visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const GlobalSetEdit& _edit;
|
||||
};
|
||||
|
||||
GlobalSetEditVisitor::GlobalSetEditVisitor(const GlobalSetEdit& edit) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
|
||||
_edit(edit) {
|
||||
}
|
||||
|
||||
bool GlobalSetEditVisitor::visit(MetavoxelInfo& info) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
return false; // entirely contained
|
||||
}
|
||||
|
||||
void GlobalSetEdit::apply(MetavoxelData& data) const {
|
||||
GlobalSetEditVisitor visitor(*this);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
||||
InsertSpannerEdit::InsertSpannerEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) :
|
||||
attribute(attribute),
|
||||
spanner(spanner) {
|
||||
}
|
||||
|
||||
void InsertSpannerEdit::apply(MetavoxelData& data) const {
|
||||
Spanner* spanner = static_cast<Spanner*>(this->spanner.data());
|
||||
data.insert(attribute, spanner->getBounds(), spanner->getGranularity(), this->spanner);
|
||||
}
|
||||
|
||||
RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) :
|
||||
attribute(attribute),
|
||||
id(id) {
|
||||
}
|
||||
|
||||
void RemoveSpannerEdit::apply(MetavoxelData& data) const {
|
||||
}
|
||||
|
||||
ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) :
|
||||
attribute(attribute) {
|
||||
}
|
||||
|
||||
void ClearSpannersEdit::apply(MetavoxelData& data) const {
|
||||
data.clear(attribute);
|
||||
}
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -17,12 +17,10 @@
|
|||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
#include <QScriptEngine>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "MetavoxelUtil.h"
|
||||
#include "ScriptCache.h"
|
||||
|
||||
|
@ -53,6 +51,7 @@ public:
|
|||
|
||||
DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) {
|
||||
setMinimum(-FLT_MAX);
|
||||
setMaximum(FLT_MAX);
|
||||
}
|
||||
|
||||
DelegatingItemEditorFactory::DelegatingItemEditorFactory() :
|
||||
|
@ -104,12 +103,24 @@ static QItemEditorCreatorBase* createDoubleEditorCreator() {
|
|||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createQMetaObjectEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QMetaObjectEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<const QMetaObject*>(), creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createQColorEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QColorEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<QColor>(), creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createQUrlEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QUrlEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<QUrl>(), creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createVec3EditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<Vec3Editor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::vec3>(), creator);
|
||||
|
@ -123,36 +134,57 @@ static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
|
|||
}
|
||||
|
||||
static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator();
|
||||
static QItemEditorCreatorBase* qMetaObjectEditorCreator = createQMetaObjectEditorCreator();
|
||||
static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator();
|
||||
static QItemEditorCreatorBase* qUrlEditorCreator = createQUrlEditorCreator();
|
||||
static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator();
|
||||
static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator();
|
||||
|
||||
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize) {
|
||||
// get the header size
|
||||
int headerSize = numBytesForPacketHeader(data);
|
||||
|
||||
// read the session id
|
||||
const int UUID_BYTES = 16;
|
||||
headerPlusIDSize = headerSize + UUID_BYTES;
|
||||
if (data.size() < headerPlusIDSize) {
|
||||
qWarning() << "Metavoxel data too short [size=" << data.size() << ", sendingNode=" << sendingNode << "]\n";
|
||||
return QUuid();
|
||||
}
|
||||
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
|
||||
}
|
||||
|
||||
QByteArray signal(const char* signature) {
|
||||
static QByteArray prototype = SIGNAL(dummyMethod());
|
||||
QByteArray signal = prototype;
|
||||
return signal.replace("dummyMethod()", signature);
|
||||
}
|
||||
|
||||
Box::Box(const glm::vec3& minimum, const glm::vec3& maximum) :
|
||||
minimum(minimum), maximum(maximum) {
|
||||
}
|
||||
|
||||
bool Box::contains(const Box& other) const {
|
||||
return other.minimum.x >= minimum.x && other.maximum.x <= maximum.x &&
|
||||
other.minimum.y >= minimum.y && other.maximum.y <= maximum.y &&
|
||||
other.minimum.z >= minimum.z && other.maximum.z <= maximum.z;
|
||||
}
|
||||
|
||||
bool Box::intersects(const Box& other) const {
|
||||
return other.maximum.x >= minimum.x && other.minimum.x <= maximum.x &&
|
||||
other.maximum.y >= minimum.y && other.minimum.y <= maximum.y &&
|
||||
other.maximum.z >= minimum.z && other.minimum.z <= maximum.z;
|
||||
}
|
||||
|
||||
QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
layout->addWidget(_box = new QComboBox());
|
||||
connect(_box, SIGNAL(currentIndexChanged(int)), SLOT(updateMetaObject()));
|
||||
|
||||
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(&SharedObject::staticMetaObject)) {
|
||||
_box->addItem(metaObject->className(), QVariant::fromValue(metaObject));
|
||||
}
|
||||
}
|
||||
|
||||
void QMetaObjectEditor::setMetaObject(const QMetaObject* metaObject) {
|
||||
_metaObject = metaObject;
|
||||
_box->setCurrentIndex(_metaObject ? _box->findText(_metaObject->className()) : -1);
|
||||
}
|
||||
|
||||
void QMetaObjectEditor::updateMetaObject() {
|
||||
int index = _box->currentIndex();
|
||||
emit metaObjectChanged(_metaObject = (index == -1) ? NULL : _box->itemData(index).value<const QMetaObject*>());
|
||||
}
|
||||
|
||||
QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
|
@ -176,6 +208,36 @@ void QColorEditor::selectColor() {
|
|||
}
|
||||
}
|
||||
|
||||
QUrlEditor::QUrlEditor(QWidget* parent) :
|
||||
QComboBox(parent) {
|
||||
|
||||
setEditable(true);
|
||||
setInsertPolicy(InsertAtTop);
|
||||
|
||||
// populate initial URL list from settings
|
||||
addItems(QSettings().value("editorURLs").toStringList());
|
||||
|
||||
connect(this, SIGNAL(activated(const QString&)), SLOT(updateURL(const QString&)));
|
||||
connect(model(), SIGNAL(rowsInserted(const QModelIndex&,int,int)), SLOT(updateSettings()));
|
||||
}
|
||||
|
||||
void QUrlEditor::setURL(const QUrl& url) {
|
||||
setCurrentText((_url = url).toString());
|
||||
}
|
||||
|
||||
void QUrlEditor::updateURL(const QString& text) {
|
||||
emit urlChanged(_url = text);
|
||||
}
|
||||
|
||||
void QUrlEditor::updateSettings() {
|
||||
QStringList urls;
|
||||
const int MAX_STORED_URLS = 10;
|
||||
for (int i = 0, size = qMin(MAX_STORED_URLS, count()); i < size; i++) {
|
||||
urls.append(itemText(i));
|
||||
}
|
||||
QSettings().setValue("editorURLs", urls);
|
||||
}
|
||||
|
||||
Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) {
|
||||
QHBoxLayout* layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
|
@ -200,7 +262,8 @@ void Vec3Editor::updateVector() {
|
|||
QDoubleSpinBox* Vec3Editor::createComponentBox() {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-FLT_MAX);
|
||||
box->setMaximumWidth(100);
|
||||
box->setMaximum(FLT_MAX);
|
||||
box->setMinimumWidth(50);
|
||||
connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector()));
|
||||
return box;
|
||||
}
|
||||
|
@ -252,8 +315,9 @@ ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
|
|||
lineContainer->setLayout(lineLayout);
|
||||
lineLayout->setContentsMargins(QMargins());
|
||||
|
||||
lineLayout->addWidget(_line = new QLineEdit(), 1);
|
||||
connect(_line, SIGNAL(textChanged(const QString&)), SLOT(updateURL()));
|
||||
lineLayout->addWidget(&_urlEditor, 1);
|
||||
connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateURL()));
|
||||
connect(&_urlEditor, SIGNAL(urlChanged(const QUrl&)), SLOT(updateParameters()));
|
||||
|
||||
QPushButton* refresh = new QPushButton("...");
|
||||
connect(refresh, SIGNAL(clicked(bool)), SLOT(updateParameters()));
|
||||
|
@ -261,8 +325,7 @@ ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
|
|||
}
|
||||
|
||||
void ParameterizedURLEditor::setURL(const ParameterizedURL& url) {
|
||||
_url = url;
|
||||
_line->setText(url.getURL().toString());
|
||||
_urlEditor.setURL((_url = url).getURL());
|
||||
updateParameters();
|
||||
}
|
||||
|
||||
|
@ -279,7 +342,7 @@ void ParameterizedURLEditor::updateURL() {
|
|||
widget->property("parameterName").toString()), widgetProperty.read(widget));
|
||||
}
|
||||
}
|
||||
emit urlChanged(_url = ParameterizedURL(_line->text(), parameters));
|
||||
emit urlChanged(_url = ParameterizedURL(_urlEditor.getURL(), parameters));
|
||||
if (_program) {
|
||||
_program->disconnect(this);
|
||||
}
|
||||
|
|
|
@ -10,29 +10,21 @@
|
|||
#define __interface__MetavoxelUtil__
|
||||
|
||||
#include <QColor>
|
||||
#include <QComboBox>
|
||||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <QWidget>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include "Bitstream.h"
|
||||
|
||||
class QByteArray;
|
||||
class QDoubleSpinBox;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
|
||||
class HifiSockAddr;
|
||||
class NetworkProgram;
|
||||
|
||||
/// Reads and returns the session ID from a datagram.
|
||||
/// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data
|
||||
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
|
||||
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize);
|
||||
|
||||
/// Performs the runtime equivalent of Qt's SIGNAL macro, which is to attach a prefix to the signature.
|
||||
QByteArray signal(const char* signature);
|
||||
|
||||
|
@ -45,11 +37,44 @@ public:
|
|||
STREAM glm::vec3 minimum;
|
||||
STREAM glm::vec3 maximum;
|
||||
|
||||
Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3());
|
||||
|
||||
bool contains(const Box& other) const;
|
||||
|
||||
bool intersects(const Box& other) const;
|
||||
|
||||
float getLongestSide() const { return qMax(qMax(maximum.x - minimum.x, maximum.y - minimum.y), maximum.z - minimum.z); }
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(Box)
|
||||
|
||||
/// Editor for meta-object values.
|
||||
class QMetaObjectEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject WRITE setMetaObject NOTIFY metaObjectChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
QMetaObjectEditor(QWidget* parent);
|
||||
|
||||
signals:
|
||||
|
||||
void metaObjectChanged(const QMetaObject* metaObject);
|
||||
|
||||
public slots:
|
||||
|
||||
void setMetaObject(const QMetaObject* metaObject);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateMetaObject();
|
||||
|
||||
private:
|
||||
|
||||
QComboBox* _box;
|
||||
const QMetaObject* _metaObject;
|
||||
};
|
||||
|
||||
/// Editor for color values.
|
||||
class QColorEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -77,6 +102,32 @@ private:
|
|||
QColor _color;
|
||||
};
|
||||
|
||||
/// Editor for URL values.
|
||||
class QUrlEditor : public QComboBox {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUrl url READ getURL WRITE setURL NOTIFY urlChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
QUrlEditor(QWidget* parent = NULL);
|
||||
|
||||
void setURL(const QUrl& url);
|
||||
const QUrl& getURL() { return _url; }
|
||||
|
||||
signals:
|
||||
|
||||
void urlChanged(const QUrl& url);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateURL(const QString& text);
|
||||
void updateSettings();
|
||||
|
||||
private:
|
||||
|
||||
QUrl _url;
|
||||
};
|
||||
|
||||
/// Editor for vector values.
|
||||
class Vec3Editor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -170,7 +221,7 @@ private:
|
|||
ParameterizedURL _url;
|
||||
QSharedPointer<NetworkProgram> _program;
|
||||
|
||||
QLineEdit* _line;
|
||||
QUrlEditor _urlEditor;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelUtil__) */
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
#include "MetavoxelUtil.h"
|
||||
#include "SharedObject.h"
|
||||
|
||||
SharedObject::SharedObject() : _referenceCount(0) {
|
||||
REGISTER_META_OBJECT(SharedObject)
|
||||
|
||||
SharedObject::SharedObject() : _id(++_lastID), _referenceCount(0) {
|
||||
}
|
||||
|
||||
void SharedObject::incrementReferenceCount() {
|
||||
|
@ -72,63 +74,17 @@ bool SharedObject::equals(const SharedObject* other) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
SharedObjectPointer::SharedObjectPointer(SharedObject* data) : _data(data) {
|
||||
if (_data) {
|
||||
_data->incrementReferenceCount();
|
||||
void SharedObject::dump(QDebug debug) const {
|
||||
debug << this;
|
||||
const QMetaObject* metaObject = this->metaObject();
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
debug << metaObject->property(i).name() << metaObject->property(i).read(this);
|
||||
}
|
||||
}
|
||||
|
||||
SharedObjectPointer::SharedObjectPointer(const SharedObjectPointer& other) : _data(other._data) {
|
||||
if (_data) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
int SharedObject::_lastID = 0;
|
||||
|
||||
SharedObjectPointer::~SharedObjectPointer() {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
void SharedObjectPointer::detach() {
|
||||
if (_data && _data->getReferenceCount() > 1) {
|
||||
_data->decrementReferenceCount();
|
||||
(_data = _data->clone())->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
void SharedObjectPointer::reset() {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
_data = NULL;
|
||||
}
|
||||
|
||||
SharedObjectPointer& SharedObjectPointer::operator=(SharedObject* data) {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
if ((_data = data)) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SharedObjectPointer& SharedObjectPointer::operator=(const SharedObjectPointer& other) {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
if ((_data = other._data)) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint qHash(const SharedObjectPointer& pointer, uint seed) {
|
||||
return qHash(pointer.data(), seed);
|
||||
}
|
||||
|
||||
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent) : QWidget(parent) {
|
||||
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, bool nullable, QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
@ -137,11 +93,17 @@ SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* p
|
|||
layout->addLayout(form);
|
||||
|
||||
form->addRow("Type:", _type = new QComboBox());
|
||||
_type->addItem("(none)");
|
||||
if (nullable) {
|
||||
_type->addItem("(none)");
|
||||
}
|
||||
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) {
|
||||
_type->addItem(metaObject->className(), QVariant::fromValue(metaObject));
|
||||
// add add constructable subclasses
|
||||
if (metaObject->constructorCount() > 0) {
|
||||
_type->addItem(metaObject->className(), QVariant::fromValue(metaObject));
|
||||
}
|
||||
}
|
||||
connect(_type, SIGNAL(currentIndexChanged(int)), SLOT(updateType()));
|
||||
updateType();
|
||||
}
|
||||
|
||||
void SharedObjectEditor::setObject(const SharedObjectPointer& object) {
|
||||
|
@ -158,6 +120,22 @@ void SharedObjectEditor::setObject(const SharedObjectPointer& object) {
|
|||
}
|
||||
}
|
||||
|
||||
void SharedObjectEditor::detachObject() {
|
||||
SharedObject* oldObject = _object.data();
|
||||
if (!_object.detach()) {
|
||||
return;
|
||||
}
|
||||
oldObject->disconnect(this);
|
||||
const QMetaObject* metaObject = _object->metaObject();
|
||||
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
|
||||
for (int i = 0; i < form->rowCount(); i++) {
|
||||
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
|
||||
QMetaProperty property = metaObject->property(widget->property("propertyIndex").toInt());
|
||||
connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
const QMetaObject* getOwningAncestor(const QMetaObject* metaObject, int propertyIndex) {
|
||||
while (propertyIndex < metaObject->propertyOffset()) {
|
||||
metaObject = metaObject->superClass();
|
||||
|
@ -178,19 +156,26 @@ void SharedObjectEditor::updateType() {
|
|||
}
|
||||
delete form;
|
||||
}
|
||||
QObject* oldObject = static_cast<SharedObject*>(_object.data());
|
||||
const QMetaObject* oldMetaObject = NULL;
|
||||
if (oldObject) {
|
||||
oldMetaObject = oldObject->metaObject();
|
||||
oldObject->disconnect(this);
|
||||
}
|
||||
const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value<const QMetaObject*>();
|
||||
if (metaObject == NULL) {
|
||||
_object.reset();
|
||||
return;
|
||||
}
|
||||
QObject* oldObject = static_cast<SharedObject*>(_object.data());
|
||||
const QMetaObject* oldMetaObject = oldObject ? oldObject->metaObject() : NULL;
|
||||
QObject* newObject = metaObject->newInstance();
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
static_cast<QVBoxLayout*>(layout())->addLayout(form);
|
||||
for (int i = QObject::staticMetaObject.propertyCount(); i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isDesignable()) {
|
||||
continue;
|
||||
}
|
||||
if (oldMetaObject && i < oldMetaObject->propertyCount() &&
|
||||
getOwningAncestor(metaObject, i) == getOwningAncestor(oldMetaObject, i)) {
|
||||
// copy the state of the shared ancestry
|
||||
|
@ -207,6 +192,10 @@ void SharedObjectEditor::updateType() {
|
|||
if (widgetProperty.hasNotifySignal()) {
|
||||
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(propertyChanged()));
|
||||
}
|
||||
if (property.hasNotifySignal()) {
|
||||
widget->setProperty("notifySignalIndex", property.notifySignalIndex());
|
||||
connect(newObject, signal(property.notifySignal().methodSignature()), SLOT(updateProperty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
_object = static_cast<SharedObject*>(newObject);
|
||||
|
@ -219,10 +208,23 @@ void SharedObjectEditor::propertyChanged() {
|
|||
if (widget != sender()) {
|
||||
continue;
|
||||
}
|
||||
_object.detach();
|
||||
detachObject();
|
||||
QObject* object = _object.data();
|
||||
QMetaProperty property = object->metaObject()->property(widget->property("propertyIndex").toInt());
|
||||
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
|
||||
property.write(object, widget->property(valuePropertyName));
|
||||
}
|
||||
}
|
||||
|
||||
void SharedObjectEditor::updateProperty() {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
|
||||
for (int i = 0; i < form->rowCount(); i++) {
|
||||
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
|
||||
if (widget->property("notifySignalIndex").toInt() != senderSignalIndex()) {
|
||||
continue;
|
||||
}
|
||||
QMetaProperty property = _object->metaObject()->property(widget->property("propertyIndex").toInt());
|
||||
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
|
||||
widget->setProperty(valuePropertyName, property.read(_object.data()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
#include <QtDebug>
|
||||
|
||||
class QComboBox;
|
||||
|
||||
|
@ -21,7 +23,9 @@ class SharedObject : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
SharedObject();
|
||||
Q_INVOKABLE SharedObject();
|
||||
|
||||
int getID() { return _id; }
|
||||
|
||||
int getReferenceCount() const { return _referenceCount; }
|
||||
void incrementReferenceCount();
|
||||
|
@ -33,6 +37,9 @@ public:
|
|||
/// Tests this object for equality with another.
|
||||
virtual bool equals(const SharedObject* other) const;
|
||||
|
||||
// Dumps the contents of this object to the debug output.
|
||||
virtual void dump(QDebug debug = QDebug(QtDebugMsg)) const;
|
||||
|
||||
signals:
|
||||
|
||||
/// Emitted when the reference count drops to one.
|
||||
|
@ -40,62 +47,133 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
int _id;
|
||||
int _referenceCount;
|
||||
|
||||
static int _lastID;
|
||||
};
|
||||
|
||||
/// A pointer to a shared object.
|
||||
class SharedObjectPointer {
|
||||
template<class T> class SharedObjectPointerTemplate {
|
||||
public:
|
||||
|
||||
SharedObjectPointer(SharedObject* data = NULL);
|
||||
SharedObjectPointer(const SharedObjectPointer& other);
|
||||
~SharedObjectPointer();
|
||||
SharedObjectPointerTemplate(T* data = NULL);
|
||||
SharedObjectPointerTemplate(const SharedObjectPointerTemplate<T>& other);
|
||||
~SharedObjectPointerTemplate();
|
||||
|
||||
SharedObject* data() { return _data; }
|
||||
const SharedObject* data() const { return _data; }
|
||||
const SharedObject* constData() const { return _data; }
|
||||
T* data() const { return _data; }
|
||||
|
||||
/// "Detaches" this object, making a new copy if its reference count is greater than one.
|
||||
bool detach();
|
||||
|
||||
void detach();
|
||||
|
||||
void swap(SharedObjectPointer& other) { qSwap(_data, other._data); }
|
||||
void swap(SharedObjectPointerTemplate<T>& other) { qSwap(_data, other._data); }
|
||||
|
||||
void reset();
|
||||
|
||||
operator SharedObject*() { return _data; }
|
||||
operator const SharedObject*() const { return _data; }
|
||||
|
||||
bool operator!() const { return !_data; }
|
||||
|
||||
bool operator!=(const SharedObjectPointer& other) const { return _data != other._data; }
|
||||
|
||||
SharedObject& operator*() { return *_data; }
|
||||
const SharedObject& operator*() const { return *_data; }
|
||||
|
||||
SharedObject* operator->() { return _data; }
|
||||
const SharedObject* operator->() const { return _data; }
|
||||
|
||||
SharedObjectPointer& operator=(SharedObject* data);
|
||||
SharedObjectPointer& operator=(const SharedObjectPointer& other);
|
||||
|
||||
bool operator==(const SharedObjectPointer& other) const { return _data == other._data; }
|
||||
|
||||
private:
|
||||
operator T*() const { return _data; }
|
||||
T& operator*() const { return *_data; }
|
||||
T* operator->() const { return _data; }
|
||||
|
||||
SharedObject* _data;
|
||||
template<class X> SharedObjectPointerTemplate<X> staticCast() const;
|
||||
|
||||
SharedObjectPointerTemplate<T>& operator=(T* data);
|
||||
SharedObjectPointerTemplate<T>& operator=(const SharedObjectPointerTemplate<T>& other);
|
||||
|
||||
bool operator==(const SharedObjectPointerTemplate<T>& other) const { return _data == other._data; }
|
||||
bool operator!=(const SharedObjectPointerTemplate<T>& other) const { return _data != other._data; }
|
||||
|
||||
private:
|
||||
|
||||
T* _data;
|
||||
};
|
||||
|
||||
template<class T> inline SharedObjectPointerTemplate<T>::SharedObjectPointerTemplate(T* data) : _data(data) {
|
||||
if (_data) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline SharedObjectPointerTemplate<T>::SharedObjectPointerTemplate(const SharedObjectPointerTemplate<T>& other) :
|
||||
_data(other._data) {
|
||||
|
||||
if (_data) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline SharedObjectPointerTemplate<T>::~SharedObjectPointerTemplate() {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline bool SharedObjectPointerTemplate<T>::detach() {
|
||||
if (_data && _data->getReferenceCount() > 1) {
|
||||
_data->decrementReferenceCount();
|
||||
(_data = _data->clone())->incrementReferenceCount();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class T> inline void SharedObjectPointerTemplate<T>::reset() {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
_data = NULL;
|
||||
}
|
||||
|
||||
template<class T> template<class X> inline SharedObjectPointerTemplate<X> SharedObjectPointerTemplate<T>::staticCast() const {
|
||||
return SharedObjectPointerTemplate<X>(static_cast<X*>(_data));
|
||||
}
|
||||
|
||||
template<class T> inline SharedObjectPointerTemplate<T>& SharedObjectPointerTemplate<T>::operator=(T* data) {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
if ((_data = data)) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline SharedObjectPointerTemplate<T>& SharedObjectPointerTemplate<T>::operator=(
|
||||
const SharedObjectPointerTemplate<T>& other) {
|
||||
if (_data) {
|
||||
_data->decrementReferenceCount();
|
||||
}
|
||||
if ((_data = other._data)) {
|
||||
_data->incrementReferenceCount();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> uint qHash(const SharedObjectPointerTemplate<T>& pointer, uint seed = 0) {
|
||||
return qHash(pointer.data(), seed);
|
||||
}
|
||||
|
||||
typedef SharedObjectPointerTemplate<SharedObject> SharedObjectPointer;
|
||||
|
||||
Q_DECLARE_METATYPE(SharedObjectPointer)
|
||||
|
||||
uint qHash(const SharedObjectPointer& pointer, uint seed = 0);
|
||||
typedef QSet<SharedObjectPointer> SharedObjectSet;
|
||||
|
||||
Q_DECLARE_METATYPE(SharedObjectSet)
|
||||
|
||||
/// Allows editing shared object instances.
|
||||
class SharedObjectEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(SharedObjectPointer object MEMBER _object WRITE setObject USER true)
|
||||
Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject USER true)
|
||||
|
||||
public:
|
||||
|
||||
SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent);
|
||||
SharedObjectEditor(const QMetaObject* metaObject, bool nullable = true, QWidget* parent = NULL);
|
||||
|
||||
const SharedObjectPointer& getObject() const { return _object; }
|
||||
|
||||
/// "Detaches" the object pointer, copying it if anyone else is holding a reference.
|
||||
void detachObject();
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -105,6 +183,7 @@ private slots:
|
|||
|
||||
void updateType();
|
||||
void propertyChanged();
|
||||
void updateProperty();
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@ private slots:
|
|||
void sendDatagram(const QByteArray& datagram);
|
||||
void handleHighPriorityMessage(const QVariant& message);
|
||||
void readMessage(Bitstream& in);
|
||||
void handleReliableMessage(const QVariant& message);
|
||||
void readReliableChannel();
|
||||
void readLowPriorityReliableChannel();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
DatagramSequencer* _sequencer;
|
||||
|
@ -59,8 +59,9 @@ private:
|
|||
float _highPriorityMessagesToSend;
|
||||
QVariantList _highPriorityMessagesSent;
|
||||
QList<SequencedTestMessage> _unreliableMessagesSent;
|
||||
float _reliableMessagesToSend;
|
||||
QVariantList _reliableMessagesSent;
|
||||
CircularBuffer _dataStreamed;
|
||||
CircularBuffer _lowPriorityDataStreamed;
|
||||
};
|
||||
|
||||
/// A simple test message.
|
||||
|
@ -88,7 +89,7 @@ public:
|
|||
DECLARE_STREAMABLE_METATYPE(TestMessageB)
|
||||
|
||||
// A test message that demonstrates inheritance and composition.
|
||||
class TestMessageC : public TestMessageA {
|
||||
class TestMessageC : STREAM public TestMessageA {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
|
|
@ -121,7 +121,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
out << " &&\n";
|
||||
out << " ";
|
||||
}
|
||||
out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)";
|
||||
out << "static_cast<const " << base << "&>(first) == static_cast<const " << base << "&>(second)";
|
||||
first = false;
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
|
@ -147,7 +147,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
out << " ||\n";
|
||||
out << " ";
|
||||
}
|
||||
out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)";
|
||||
out << "static_cast<const " << base << "&>(first) != static_cast<const " << base << "&>(second)";
|
||||
first = false;
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
|
|
Loading…
Reference in a new issue