mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
remove metavoxels
This commit is contained in:
parent
6d8ccfbc2a
commit
5c3c94a618
66 changed files with 30 additions and 25269 deletions
|
@ -8,7 +8,7 @@ target_include_directories(${TARGET_NAME} PRIVATE ${GLM_INCLUDE_DIRS})
|
|||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(
|
||||
audio avatars octree environment gpu model fbx entities metavoxels
|
||||
audio avatars octree environment gpu model fbx entities
|
||||
networking animation shared script-engine embedded-webserver
|
||||
physics
|
||||
)
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "AssignmentFactory.h"
|
||||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include "metavoxels/MetavoxelServer.h"
|
||||
#include "entities/EntityServer.h"
|
||||
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) {
|
||||
|
@ -34,8 +33,6 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet
|
|||
return new AvatarMixer(packet);
|
||||
case Assignment::AgentType:
|
||||
return new Agent(packet);
|
||||
case Assignment::MetavoxelServerType:
|
||||
return new MetavoxelServer(packet);
|
||||
case Assignment::EntityServerType:
|
||||
return new EntityServer(packet);
|
||||
default:
|
||||
|
|
|
@ -1,367 +0,0 @@
|
|||
//
|
||||
// MetavoxelServer.cpp
|
||||
// assignment-client/src/metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/18/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QThread>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#include "MetavoxelServer.h"
|
||||
|
||||
MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_nextSender(0),
|
||||
_savedDataInitialized(false) {
|
||||
}
|
||||
|
||||
void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
|
||||
MetavoxelData data = _data;
|
||||
edit.apply(data, SharedObject::getWeakHash());
|
||||
setData(data);
|
||||
}
|
||||
|
||||
void MetavoxelServer::setData(const MetavoxelData& data, bool loaded) {
|
||||
if (_data == data) {
|
||||
return;
|
||||
}
|
||||
emit dataChanged(_data = data);
|
||||
|
||||
if (loaded) {
|
||||
_savedData = data;
|
||||
|
||||
} else if (!_savedDataInitialized) {
|
||||
_savedDataInitialized = true;
|
||||
|
||||
// start the save timer
|
||||
QTimer* saveTimer = new QTimer(this);
|
||||
connect(saveTimer, &QTimer::timeout, this, &MetavoxelServer::maybeSaveData);
|
||||
const int SAVE_INTERVAL = 1000 * 30;
|
||||
saveTimer->start(SAVE_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server";
|
||||
|
||||
void MetavoxelServer::run() {
|
||||
commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeAdded, this, &MetavoxelServer::maybeAttachSession);
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &MetavoxelServer::maybeDeleteSession);
|
||||
|
||||
// initialize Bitstream before using it in multiple threads
|
||||
Bitstream::preThreadingInit();
|
||||
|
||||
// create the senders, each with its own thread
|
||||
int threadCount = QThread::idealThreadCount();
|
||||
if (threadCount == -1) {
|
||||
const int DEFAULT_THREAD_COUNT = 4;
|
||||
threadCount = DEFAULT_THREAD_COUNT;
|
||||
}
|
||||
qDebug() << "Creating" << threadCount << "sender threads";
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
QThread* thread = new QThread(this);
|
||||
MetavoxelSender* sender = new MetavoxelSender(this);
|
||||
sender->moveToThread(thread);
|
||||
connect(thread, &QThread::finished, sender, &QObject::deleteLater);
|
||||
thread->start();
|
||||
QMetaObject::invokeMethod(sender, "start");
|
||||
_senders.append(sender);
|
||||
}
|
||||
|
||||
// create the persister and start it in its own thread
|
||||
_persister = new MetavoxelPersister(this);
|
||||
QThread* persistenceThread = new QThread(this);
|
||||
_persister->moveToThread(persistenceThread);
|
||||
connect(persistenceThread, &QThread::finished, _persister, &QObject::deleteLater);
|
||||
persistenceThread->start();
|
||||
|
||||
// queue up the load
|
||||
QMetaObject::invokeMethod(_persister, "load");
|
||||
}
|
||||
|
||||
void MetavoxelServer::readPendingDatagrams() {
|
||||
QByteArray receivedPacket;
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
|
||||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
switch (packetTypeForPacket(receivedPacket)) {
|
||||
case PacketTypeMetavoxelData:
|
||||
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
|
||||
break;
|
||||
|
||||
default:
|
||||
nodeList->processNodeData(senderSockAddr, receivedPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::aboutToFinish() {
|
||||
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _data));
|
||||
|
||||
foreach (MetavoxelSender* sender, _senders) {
|
||||
sender->thread()->quit();
|
||||
sender->thread()->wait();
|
||||
}
|
||||
_persister->thread()->quit();
|
||||
_persister->thread()->wait();
|
||||
}
|
||||
|
||||
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelSender* sender = _senders.at(_nextSender);
|
||||
_nextSender = (_nextSender + 1) % _senders.size();
|
||||
MetavoxelSession* session = new MetavoxelSession(node, sender);
|
||||
session->moveToThread(sender->thread());
|
||||
QMetaObject::invokeMethod(sender, "addSession", Q_ARG(QObject*, session));
|
||||
node->setLinkedData(session);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::maybeDeleteSession(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
// we assume the node is already locked
|
||||
MetavoxelSession* session = static_cast<MetavoxelSession*>(node->getLinkedData());
|
||||
if (session) {
|
||||
node->setLinkedData(NULL);
|
||||
session->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::maybeSaveData() {
|
||||
if (_savedData != _data) {
|
||||
QMetaObject::invokeMethod(_persister, "save", Q_ARG(const MetavoxelData&, _savedData = _data));
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelSender::MetavoxelSender(MetavoxelServer* server) :
|
||||
_server(server),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, &QTimer::timeout, this, &MetavoxelSender::sendDeltas);
|
||||
|
||||
connect(_server, &MetavoxelServer::dataChanged, this, &MetavoxelSender::setData);
|
||||
}
|
||||
|
||||
const int SEND_INTERVAL = 50;
|
||||
|
||||
void MetavoxelSender::start() {
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
||||
void MetavoxelSender::addSession(QObject* session) {
|
||||
_sessions.insert(static_cast<MetavoxelSession*>(session));
|
||||
connect(session, &QObject::destroyed, this, &MetavoxelSender::removeSession);
|
||||
}
|
||||
|
||||
void MetavoxelSender::sendDeltas() {
|
||||
// send deltas for all sessions associated with our thread
|
||||
foreach (MetavoxelSession* session, _sessions) {
|
||||
session->update();
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
int elapsed = now - _lastSend;
|
||||
_lastSend = now;
|
||||
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL)));
|
||||
}
|
||||
|
||||
void MetavoxelSender::removeSession(QObject* session) {
|
||||
_sessions.remove(static_cast<MetavoxelSession*>(session));
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelSender* sender) :
|
||||
Endpoint(node, new PacketRecord(), NULL),
|
||||
_sender(sender),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(checkReliableDeltaReceived()));
|
||||
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&, Bitstream&)),
|
||||
SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
}
|
||||
|
||||
void MetavoxelSession::update() {
|
||||
// wait until we have a valid lod before sending
|
||||
if (!_lod.isValid()) {
|
||||
return;
|
||||
}
|
||||
// if we're sending a reliable delta, wait until it's acknowledged
|
||||
if (_reliableDeltaChannel) {
|
||||
sendPacketGroup();
|
||||
return;
|
||||
}
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
int start = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
|
||||
_sender->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
|
||||
out.flush();
|
||||
int end = _sequencer.getOutputStream().getUnderlying().device()->pos();
|
||||
if (end > _sequencer.getMaxPacketSize()) {
|
||||
// we need to send the delta on the reliable channel
|
||||
_reliableDeltaChannel = _sequencer.getReliableOutputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
|
||||
_reliableDeltaChannel->startMessage();
|
||||
_reliableDeltaChannel->getBuffer().write(_sequencer.getOutgoingPacketData().constData() + start, end - start);
|
||||
_reliableDeltaChannel->endMessage();
|
||||
|
||||
_reliableDeltaWriteMappings = out.getAndResetWriteMappings();
|
||||
_reliableDeltaReceivedOffset = _reliableDeltaChannel->getBytesWritten();
|
||||
_reliableDeltaData = _sender->getData();
|
||||
_reliableDeltaLOD = _lod;
|
||||
|
||||
// go back to the beginning with the current packet and note that there's a delta pending
|
||||
_sequencer.getOutputStream().getUnderlying().device()->seek(start);
|
||||
MetavoxelDeltaPendingMessage msg = { ++_reliableDeltaID, sendRecord->getPacketNumber(), _lodPacketNumber };
|
||||
out << (_reliableDeltaMessage = QVariant::fromValue(msg));
|
||||
_sequencer.endPacket();
|
||||
|
||||
} else {
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
|
||||
// perhaps send additional packets to fill out the group
|
||||
sendPacketGroup(1);
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
handleMessage(message);
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
|
||||
return _reliableDeltaChannel ? new PacketRecord(_sequencer.getOutgoingPacketNumber(),
|
||||
_reliableDeltaLOD, _reliableDeltaData) : new PacketRecord(_sequencer.getOutgoingPacketNumber(),
|
||||
_lod, _sender->getData());
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message) {
|
||||
int userType = message.userType();
|
||||
if (userType == ClientStateMessage::Type) {
|
||||
ClientStateMessage state = message.value<ClientStateMessage>();
|
||||
_lod = state.lod;
|
||||
_lodPacketNumber = _sequencer.getIncomingPacketNumber();
|
||||
|
||||
} else if (userType == MetavoxelEditMessage::Type) {
|
||||
if (_node->getCanAdjustLocks()) {
|
||||
QMetaObject::invokeMethod(_sender->getServer(), "applyEdit",
|
||||
Q_ARG(const MetavoxelEditMessage&, message.value<MetavoxelEditMessage>()));
|
||||
}
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSession::checkReliableDeltaReceived() {
|
||||
if (!_reliableDeltaChannel || _reliableDeltaChannel->getOffset() < _reliableDeltaReceivedOffset) {
|
||||
return;
|
||||
}
|
||||
_sequencer.getOutputStream().persistWriteMappings(_reliableDeltaWriteMappings);
|
||||
_reliableDeltaWriteMappings = Bitstream::WriteMappings();
|
||||
_reliableDeltaData = MetavoxelData();
|
||||
_reliableDeltaChannel = NULL;
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendPacketGroup(int alreadySent) {
|
||||
int additionalPackets = _sequencer.notePacketGroup() - alreadySent;
|
||||
for (int i = 0; i < additionalPackets; i++) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
if (_reliableDeltaChannel) {
|
||||
out << _reliableDeltaMessage;
|
||||
} else {
|
||||
out << QVariant();
|
||||
}
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) :
|
||||
_server(server) {
|
||||
}
|
||||
|
||||
const char* SAVE_FILE = "/resources/metavoxels.dat";
|
||||
|
||||
const int FILE_MAGIC = 0xDADAFACE;
|
||||
const int FILE_VERSION = 4;
|
||||
|
||||
void MetavoxelPersister::load() {
|
||||
QString path = QCoreApplication::applicationDirPath() + SAVE_FILE;
|
||||
QFile file(path);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
MetavoxelData data;
|
||||
{
|
||||
QDebug debug = qDebug() << "Reading from" << path << "...";
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QDataStream inStream(&file);
|
||||
Bitstream in(inStream);
|
||||
int magic, version;
|
||||
in >> magic;
|
||||
if (magic != FILE_MAGIC) {
|
||||
debug << "wrong file magic: " << magic;
|
||||
return;
|
||||
}
|
||||
in >> version;
|
||||
if (version != FILE_VERSION) {
|
||||
debug << "wrong file version: " << version;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
in >> data;
|
||||
} catch (const BitstreamException& e) {
|
||||
debug << "failed, " << e.getDescription();
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(_server, "setData", Q_ARG(const MetavoxelData&, data), Q_ARG(bool, true));
|
||||
debug << "done.";
|
||||
}
|
||||
data.dumpStats();
|
||||
}
|
||||
|
||||
void MetavoxelPersister::save(const MetavoxelData& data) {
|
||||
QString path = QCoreApplication::applicationDirPath() + SAVE_FILE;
|
||||
QDir directory = QFileInfo(path).dir();
|
||||
if (!directory.exists()) {
|
||||
directory.mkpath(".");
|
||||
}
|
||||
QDebug debug = qDebug() << "Writing to" << path << "...";
|
||||
QSaveFile file(path);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
QDataStream outStream(&file);
|
||||
Bitstream out(outStream);
|
||||
out << FILE_MAGIC << FILE_VERSION << data;
|
||||
out.flush();
|
||||
file.commit();
|
||||
debug << "done.";
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
//
|
||||
// MetavoxelServer.h
|
||||
// assignment-client/src/metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/18/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelServer_h
|
||||
#define hifi_MetavoxelServer_h
|
||||
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
#include <Endpoint.h>
|
||||
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelPersister;
|
||||
class MetavoxelSender;
|
||||
class MetavoxelSession;
|
||||
|
||||
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
|
||||
class MetavoxelServer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelServer(const QByteArray& packet);
|
||||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit);
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
Q_INVOKABLE void setData(const MetavoxelData& data, bool loaded = false);
|
||||
|
||||
virtual void run();
|
||||
|
||||
virtual void readPendingDatagrams();
|
||||
|
||||
virtual void aboutToFinish();
|
||||
|
||||
signals:
|
||||
|
||||
void dataChanged(const MetavoxelData& data);
|
||||
|
||||
private slots:
|
||||
|
||||
void maybeAttachSession(const SharedNodePointer& node);
|
||||
void maybeDeleteSession(const SharedNodePointer& node);
|
||||
void maybeSaveData();
|
||||
|
||||
private:
|
||||
|
||||
QVector<MetavoxelSender*> _senders;
|
||||
int _nextSender;
|
||||
|
||||
MetavoxelPersister* _persister;
|
||||
|
||||
MetavoxelData _data;
|
||||
MetavoxelData _savedData;
|
||||
bool _savedDataInitialized;
|
||||
};
|
||||
|
||||
/// Handles update sending for one thread.
|
||||
class MetavoxelSender : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSender(MetavoxelServer* server);
|
||||
|
||||
MetavoxelServer* getServer() const { return _server; }
|
||||
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
Q_INVOKABLE void addSession(QObject* session);
|
||||
|
||||
private slots:
|
||||
|
||||
void setData(const MetavoxelData& data) { _data = data; }
|
||||
void sendDeltas();
|
||||
void removeSession(QObject* session);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelServer* _server;
|
||||
QSet<MetavoxelSession*> _sessions;
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
MetavoxelData _data;
|
||||
};
|
||||
|
||||
/// Contains the state of a single client session.
|
||||
class MetavoxelSession : public Endpoint {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSession(const SharedNodePointer& node, MetavoxelSender* sender);
|
||||
|
||||
virtual void update();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void handleMessage(const QVariant& message);
|
||||
void checkReliableDeltaReceived();
|
||||
|
||||
private:
|
||||
|
||||
void sendPacketGroup(int alreadySent = 0);
|
||||
|
||||
MetavoxelSender* _sender;
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
int _lodPacketNumber;
|
||||
|
||||
ReliableChannel* _reliableDeltaChannel;
|
||||
int _reliableDeltaReceivedOffset;
|
||||
MetavoxelData _reliableDeltaData;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
Bitstream::WriteMappings _reliableDeltaWriteMappings;
|
||||
int _reliableDeltaID;
|
||||
QVariant _reliableDeltaMessage;
|
||||
};
|
||||
|
||||
/// Handles persistence in a separate thread.
|
||||
class MetavoxelPersister : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelPersister(MetavoxelServer* server);
|
||||
|
||||
Q_INVOKABLE void load();
|
||||
Q_INVOKABLE void save(const MetavoxelData& data);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelServer* _server;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelServer_h
|
|
@ -559,8 +559,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer;
|
||||
|
||||
void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BULLET_INCLUDE_DIRS})
|
|||
target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
|
||||
|
||||
# link required hifi libraries
|
||||
link_hifi_libraries(shared octree environment gpu model fbx metavoxels networking entities avatars
|
||||
link_hifi_libraries(shared octree environment gpu model fbx networking entities avatars
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer)
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include <QMediaPlayer>
|
||||
#include <QMimeData>
|
||||
#include <QMessageBox>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include <AddressManager.h>
|
||||
#include <AccountManager.h>
|
||||
|
@ -75,7 +76,7 @@
|
|||
#include <PhysicsEngine.h>
|
||||
#include <ProgramObject.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
//#include <ScriptCache.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextRenderer.h>
|
||||
|
@ -220,7 +221,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
||||
auto geometryCache = DependencyManager::set<GeometryCache>();
|
||||
auto scriptCache = DependencyManager::set<ScriptCache>();
|
||||
//auto scriptCache = DependencyManager::set<ScriptCache>();
|
||||
auto soundCache = DependencyManager::set<SoundCache>();
|
||||
auto glowEffect = DependencyManager::set<GlowEffect>();
|
||||
auto faceshift = DependencyManager::set<Faceshift>();
|
||||
|
@ -416,8 +417,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
|
||||
// tell the NodeList instance who to tell the domain server we care about
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::EntityServer
|
||||
<< NodeType::MetavoxelServer);
|
||||
<< NodeType::EntityServer);
|
||||
|
||||
// connect to the packet sent signal of the _entityEditSender
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
|
@ -600,7 +600,7 @@ Application::~Application() {
|
|||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
//DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
||||
qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages
|
||||
|
@ -773,8 +773,9 @@ void Application::paintGL() {
|
|||
|
||||
{
|
||||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
|
||||
_applicationOverlay.renderOverlay(true);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) {
|
||||
_applicationOverlay.renderOverlay(true);
|
||||
_applicationOverlay.displayOverlayTexture();
|
||||
}
|
||||
}
|
||||
|
@ -1443,8 +1444,7 @@ void Application::sendPingPackets() {
|
|||
QByteArray pingPacket = DependencyManager::get<NodeList>()->constructPingPacket();
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet()
|
||||
<< NodeType::EntityServer
|
||||
<< NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::MetavoxelServer);
|
||||
<< NodeType::AudioMixer << NodeType::AvatarMixer);
|
||||
}
|
||||
|
||||
// Every second, check the frame rates and other stuff
|
||||
|
@ -1781,8 +1781,6 @@ void Application::init() {
|
|||
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
|
||||
_entityClipboardRenderer.setTree(&_entityClipboard);
|
||||
|
||||
_metavoxels.init();
|
||||
|
||||
_rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect);
|
||||
|
||||
connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView()));
|
||||
|
@ -1945,16 +1943,6 @@ void Application::updateThreads(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::updateMetavoxels(float deltaTime) {
|
||||
PerformanceTimer perfTimer("updateMetavoxels");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()");
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) {
|
||||
_metavoxels.simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::cameraMenuChanged() {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
|
@ -2055,7 +2043,6 @@ void Application::update(float deltaTime) {
|
|||
|
||||
DependencyManager::get<AvatarManager>()->updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
||||
|
||||
updateMetavoxels(deltaTime); // update metavoxels
|
||||
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
|
||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||
updateCursor(deltaTime); // Handle cursor updates
|
||||
|
@ -2801,14 +2788,6 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs
|
|||
float originSphereRadius = 0.05f;
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(originSphereRadius, 15, 15, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
// also, metavoxels
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) {
|
||||
PerformanceTimer perfTimer("metavoxels");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... metavoxels...");
|
||||
_metavoxels.render();
|
||||
}
|
||||
|
||||
// render models...
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
|
||||
PerformanceTimer perfTimer("entities");
|
||||
|
@ -3485,7 +3464,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Metavoxels", &_metavoxels);
|
||||
|
||||
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
|
||||
qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
#include "FileLogger.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "Menu.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "Physics.h"
|
||||
#include "Stars.h"
|
||||
|
@ -179,7 +178,6 @@ public:
|
|||
ViewFrustum* getDisplayViewFrustum() { return &_displayViewFrustum; }
|
||||
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
|
||||
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
|
||||
MetavoxelSystem* getMetavoxels() { return &_metavoxels; }
|
||||
EntityTreeRenderer* getEntities() { return &_entities; }
|
||||
Environment* getEnvironment() { return &_environment; }
|
||||
PrioVR* getPrioVR() { return &_prioVR; }
|
||||
|
@ -423,7 +421,6 @@ private:
|
|||
void updateMouseRay();
|
||||
void updateMyAvatarLookAtPosition();
|
||||
void updateThreads(float deltaTime);
|
||||
void updateMetavoxels(float deltaTime);
|
||||
void updateCamera(float deltaTime);
|
||||
void updateDialogs(float deltaTime);
|
||||
void updateCursor(float deltaTime);
|
||||
|
@ -476,8 +473,6 @@ private:
|
|||
EntityTreeRenderer _entityClipboardRenderer;
|
||||
EntityTree _entityClipboard;
|
||||
|
||||
MetavoxelSystem _metavoxels;
|
||||
|
||||
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc.
|
||||
ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels)
|
||||
ViewFrustum _displayViewFrustum;
|
||||
|
|
|
@ -100,9 +100,6 @@ void DatagramProcessor::processDatagrams() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case PacketTypeMetavoxelData:
|
||||
nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket);
|
||||
break;
|
||||
case PacketTypeBulkAvatarData:
|
||||
case PacketTypeKillAvatar:
|
||||
case PacketTypeAvatarIdentity:
|
||||
|
|
|
@ -148,8 +148,6 @@ Menu::Menu() {
|
|||
dialogsManager.data(), SLOT(editAnimations()));
|
||||
|
||||
QMenu* toolsMenu = addMenu("Tools");
|
||||
addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0,
|
||||
dialogsManager.data(), SLOT(showMetavoxelEditor()));
|
||||
addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S,
|
||||
dialogsManager.data(), SLOT(showScriptEditor()));
|
||||
|
||||
|
@ -295,7 +293,6 @@ Menu::Menu() {
|
|||
QMenu* renderOptionsMenu = developerMenu->addMenu("Render");
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Avatars, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges);
|
||||
|
@ -394,13 +391,6 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
|
||||
|
||||
QMenu* metavoxelOptionsMenu = developerMenu->addMenu("Metavoxels");
|
||||
addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::DisplayHermiteData, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::RenderHeightfields, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::RenderDualContourSurfaces, 0, true);
|
||||
addActionToQMenuAndActionHash(metavoxelOptionsMenu, MenuOption::NetworkSimulator, 0,
|
||||
dialogsManager.data(), SLOT(showMetavoxelNetworkSimulator()));
|
||||
|
||||
QMenu* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
|
||||
|
|
|
@ -145,7 +145,6 @@ namespace MenuOption {
|
|||
const QString DisableNackPackets = "Disable NACK Packets";
|
||||
const QString DisplayHands = "Show Hand Info";
|
||||
const QString DisplayHandTargets = "Show Hand Targets";
|
||||
const QString DisplayHermiteData = "Display Hermite Data";
|
||||
const QString DisplayModelBounds = "Display Model Bounds";
|
||||
const QString DisplayModelTriangles = "Display Model Triangles";
|
||||
const QString DisplayModelElementChildProxies = "Display Model Element Children";
|
||||
|
@ -186,12 +185,9 @@ namespace MenuOption {
|
|||
const QString Login = "Login";
|
||||
const QString Log = "Log";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
const QString MetavoxelEditor = "Metavoxel Editor...";
|
||||
const QString Metavoxels = "Metavoxels";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
const QString MuteEnvironment = "Mute Environment";
|
||||
const QString NetworkSimulator = "Network Simulator...";
|
||||
const QString NewVoxelCullingMode = "New Voxel Culling Mode";
|
||||
const QString NoFaceTracking = "None";
|
||||
const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity";
|
||||
|
@ -205,10 +201,8 @@ namespace MenuOption {
|
|||
const QString Quit = "Quit";
|
||||
const QString ReloadAllScripts = "Reload All Scripts";
|
||||
const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes";
|
||||
const QString RenderDualContourSurfaces = "Render Dual Contour Surfaces";
|
||||
const QString RenderFocusIndicator = "Show Eye Focus";
|
||||
const QString RenderHeadCollisionShapes = "Show Head Collision Shapes";
|
||||
const QString RenderHeightfields = "Render Heightfields";
|
||||
const QString RenderLookAtVectors = "Show Look-at Vectors";
|
||||
const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes";
|
||||
const QString RenderTargetFramerate = "Framerate";
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,445 +0,0 @@
|
|||
//
|
||||
// MetavoxelSystem.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/10/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelSystem_h
|
||||
#define hifi_MetavoxelSystem_h
|
||||
|
||||
#include <QList>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <MetavoxelClientManager.h>
|
||||
#include <ProgramObject.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
class HeightfieldBaseLayerBatch;
|
||||
class HeightfieldSplatBatch;
|
||||
class HermiteBatch;
|
||||
class MetavoxelBatch;
|
||||
class Model;
|
||||
class VoxelSplatBatch;
|
||||
|
||||
/// Renders a metavoxel tree.
|
||||
class MetavoxelSystem : public MetavoxelClientManager {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
class NetworkSimulation {
|
||||
public:
|
||||
float dropRate;
|
||||
float repeatRate;
|
||||
int minimumDelay;
|
||||
int maximumDelay;
|
||||
int bandwidthLimit;
|
||||
|
||||
NetworkSimulation(float dropRate = 0.0f, float repeatRate = 0.0f, int minimumDelay = 0,
|
||||
int maximumDelay = 0, int bandwidthLimit = 0);
|
||||
};
|
||||
|
||||
virtual ~MetavoxelSystem();
|
||||
|
||||
virtual void init();
|
||||
|
||||
virtual MetavoxelLOD getLOD();
|
||||
|
||||
const Frustum& getFrustum() const { return _frustum; }
|
||||
|
||||
void setNetworkSimulation(const NetworkSimulation& simulation);
|
||||
NetworkSimulation getNetworkSimulation();
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
void renderHeightfieldCursor(const glm::vec3& position, float radius);
|
||||
|
||||
Q_INVOKABLE void paintHeightfieldColor(const glm::vec3& position, float radius, const QColor& color);
|
||||
|
||||
Q_INVOKABLE void paintHeightfieldMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material);
|
||||
|
||||
Q_INVOKABLE void setHeightfieldColor(const SharedObjectPointer& spanner, const QColor& color, bool paint = false);
|
||||
|
||||
Q_INVOKABLE void setHeightfieldMaterial(const SharedObjectPointer& spanner,
|
||||
const SharedObjectPointer& material, bool paint = false);
|
||||
|
||||
void addHeightfieldBaseBatch(const HeightfieldBaseLayerBatch& batch) { _heightfieldBaseBatches.append(batch); }
|
||||
void addHeightfieldSplatBatch(const HeightfieldSplatBatch& batch) { _heightfieldSplatBatches.append(batch); }
|
||||
|
||||
void addVoxelBaseBatch(const MetavoxelBatch& batch) { _voxelBaseBatches.append(batch); }
|
||||
void addVoxelSplatBatch(const VoxelSplatBatch& batch) { _voxelSplatBatches.append(batch); }
|
||||
|
||||
void addHermiteBatch(const HermiteBatch& batch) { _hermiteBatches.append(batch); }
|
||||
|
||||
Q_INVOKABLE void deleteTextures(int heightTextureID, int colorTextureID, int materialTextureID) const;
|
||||
Q_INVOKABLE void deleteBuffers(int vertexBufferID, int indexBufferID, int hermiteBufferID) const;
|
||||
|
||||
signals:
|
||||
|
||||
void rendering();
|
||||
|
||||
protected:
|
||||
|
||||
Q_INVOKABLE void applyMaterialEdit(const MetavoxelEditMessage& message, bool reliable = false);
|
||||
|
||||
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
|
||||
|
||||
private:
|
||||
|
||||
void guideToAugmented(MetavoxelVisitor& visitor, bool render = false);
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
QReadWriteLock _lodLock;
|
||||
Frustum _frustum;
|
||||
|
||||
NetworkSimulation _networkSimulation;
|
||||
QReadWriteLock _networkSimulationLock;
|
||||
|
||||
QVector<HeightfieldBaseLayerBatch> _heightfieldBaseBatches;
|
||||
QVector<HeightfieldSplatBatch> _heightfieldSplatBatches;
|
||||
QVector<MetavoxelBatch> _voxelBaseBatches;
|
||||
QVector<VoxelSplatBatch> _voxelSplatBatches;
|
||||
QVector<HermiteBatch> _hermiteBatches;
|
||||
|
||||
ProgramObject _baseHeightfieldProgram;
|
||||
int _baseHeightScaleLocation;
|
||||
int _baseColorScaleLocation;
|
||||
|
||||
class SplatLocations {
|
||||
public:
|
||||
int heightScale;
|
||||
int textureScale;
|
||||
int splatTextureOffset;
|
||||
int splatTextureScalesS;
|
||||
int splatTextureScalesT;
|
||||
int textureValueMinima;
|
||||
int textureValueMaxima;
|
||||
int materials;
|
||||
int materialWeights;
|
||||
};
|
||||
|
||||
ProgramObject _splatHeightfieldProgram;
|
||||
SplatLocations _splatHeightfieldLocations;
|
||||
|
||||
int _splatHeightScaleLocation;
|
||||
int _splatTextureScaleLocation;
|
||||
int _splatTextureOffsetLocation;
|
||||
int _splatTextureScalesSLocation;
|
||||
int _splatTextureScalesTLocation;
|
||||
int _splatTextureValueMinimaLocation;
|
||||
int _splatTextureValueMaximaLocation;
|
||||
|
||||
ProgramObject _heightfieldCursorProgram;
|
||||
|
||||
ProgramObject _baseVoxelProgram;
|
||||
ProgramObject _splatVoxelProgram;
|
||||
SplatLocations _splatVoxelLocations;
|
||||
|
||||
ProgramObject _voxelCursorProgram;
|
||||
|
||||
static void loadSplatProgram(const char* type, ProgramObject& program, SplatLocations& locations);
|
||||
};
|
||||
|
||||
/// Base class for all batches.
|
||||
class MetavoxelBatch {
|
||||
public:
|
||||
GLuint vertexBufferID;
|
||||
GLuint indexBufferID;
|
||||
glm::vec3 translation;
|
||||
glm::quat rotation;
|
||||
glm::vec3 scale;
|
||||
int vertexCount;
|
||||
int indexCount;
|
||||
};
|
||||
|
||||
/// Base class for heightfield batches.
|
||||
class HeightfieldBatch : public MetavoxelBatch {
|
||||
public:
|
||||
GLuint heightTextureID;
|
||||
glm::vec4 heightScale;
|
||||
};
|
||||
|
||||
/// A batch containing a heightfield base layer.
|
||||
class HeightfieldBaseLayerBatch : public HeightfieldBatch {
|
||||
public:
|
||||
GLuint colorTextureID;
|
||||
glm::vec2 colorScale;
|
||||
};
|
||||
|
||||
/// A batch containing a heightfield splat.
|
||||
class HeightfieldSplatBatch : public HeightfieldBatch {
|
||||
public:
|
||||
GLuint materialTextureID;
|
||||
glm::vec2 textureScale;
|
||||
glm::vec2 splatTextureOffset;
|
||||
int splatTextureIDs[4];
|
||||
glm::vec4 splatTextureScalesS;
|
||||
glm::vec4 splatTextureScalesT;
|
||||
int materialIndex;
|
||||
};
|
||||
|
||||
/// A batch containing a voxel splat.
|
||||
class VoxelSplatBatch : public MetavoxelBatch {
|
||||
public:
|
||||
glm::vec3 splatTextureOffset;
|
||||
int splatTextureIDs[4];
|
||||
glm::vec4 splatTextureScalesS;
|
||||
glm::vec4 splatTextureScalesT;
|
||||
int materialIndex;
|
||||
};
|
||||
|
||||
/// A batch containing Hermite data for debugging.
|
||||
class HermiteBatch {
|
||||
public:
|
||||
GLuint vertexBufferID;
|
||||
glm::vec3 translation;
|
||||
glm::quat rotation;
|
||||
glm::vec3 scale;
|
||||
int vertexCount;
|
||||
};
|
||||
|
||||
/// Generic abstract base class for objects that handle a signal.
|
||||
class SignalHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
|
||||
virtual void handle() = 0;
|
||||
};
|
||||
|
||||
/// Simple throttle for limiting bandwidth on a per-second basis.
|
||||
class Throttle {
|
||||
public:
|
||||
|
||||
Throttle();
|
||||
|
||||
/// Sets the per-second limit.
|
||||
void setLimit(int limit) { _limit = limit; }
|
||||
|
||||
/// Determines whether the message with the given size should be throttled (discarded). If not, registers the message
|
||||
/// as having been processed (i.e., contributing to later throttling).
|
||||
bool shouldThrottle(int bytes);
|
||||
|
||||
private:
|
||||
|
||||
int _limit;
|
||||
int _total;
|
||||
|
||||
typedef QPair<qint64, int> Bucket;
|
||||
QList<Bucket> _buckets;
|
||||
};
|
||||
|
||||
/// A client session associated with a single server.
|
||||
class MetavoxelSystemClient : public MetavoxelClient {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelUpdater* updater);
|
||||
|
||||
Q_INVOKABLE void setAugmentedData(const MetavoxelData& data);
|
||||
|
||||
/// Returns a copy of the augmented data. This function is thread-safe.
|
||||
MetavoxelData getAugmentedData();
|
||||
|
||||
void setRenderedAugmentedData(const MetavoxelData& data) { _renderedAugmentedData = data; }
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void dataChanged(const MetavoxelData& oldData);
|
||||
virtual void sendDatagram(const QByteArray& data);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelData _augmentedData;
|
||||
MetavoxelData _renderedAugmentedData;
|
||||
QReadWriteLock _augmentedDataLock;
|
||||
|
||||
Throttle _sendThrottle;
|
||||
Throttle _receiveThrottle;
|
||||
};
|
||||
|
||||
/// Base class for cached static buffers.
|
||||
class BufferData : public QSharedData {
|
||||
public:
|
||||
|
||||
virtual ~BufferData();
|
||||
|
||||
virtual void render(const glm::vec3& translation, const glm::quat& rotation,
|
||||
const glm::vec3& scale, bool cursor = false) = 0;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<BufferData> BufferDataPointer;
|
||||
|
||||
/// Describes contents of a vertex in a voxel buffer.
|
||||
class VoxelPoint {
|
||||
public:
|
||||
glm::vec3 vertex;
|
||||
quint8 color[3];
|
||||
char normal[3];
|
||||
quint8 materials[4];
|
||||
quint8 materialWeights[4];
|
||||
|
||||
void setNormal(const glm::vec3& normal);
|
||||
};
|
||||
|
||||
/// A container for a coordinate within a voxel block.
|
||||
class VoxelCoord {
|
||||
public:
|
||||
QRgb encoded;
|
||||
|
||||
VoxelCoord(QRgb encoded) : encoded(encoded) { }
|
||||
|
||||
bool operator==(const VoxelCoord& other) const { return encoded == other.encoded; }
|
||||
};
|
||||
|
||||
inline uint qHash(const VoxelCoord& coord, uint seed) {
|
||||
// multiply by prime numbers greater than the possible size
|
||||
return qHash(qRed(coord.encoded) + 257 * (qGreen(coord.encoded) + 263 * qBlue(coord.encoded)), seed);
|
||||
}
|
||||
|
||||
/// Contains the information necessary to render a voxel block.
|
||||
class VoxelBuffer : public BufferData {
|
||||
public:
|
||||
|
||||
VoxelBuffer(const QVector<VoxelPoint>& vertices, const QVector<int>& indices, const QVector<glm::vec3>& hermite,
|
||||
const QMultiHash<VoxelCoord, int>& quadIndices, int size, const QVector<SharedObjectPointer>& materials =
|
||||
QVector<SharedObjectPointer>());
|
||||
virtual ~VoxelBuffer();
|
||||
|
||||
bool isHermiteEnabled() const { return _hermiteEnabled; }
|
||||
|
||||
/// Finds the first intersection between the described ray and the voxel data.
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const;
|
||||
|
||||
virtual void render(const glm::vec3& translation, const glm::quat& rotation,
|
||||
const glm::vec3& scale, bool cursor = false);
|
||||
|
||||
private:
|
||||
|
||||
QVector<VoxelPoint> _vertices;
|
||||
QVector<int> _indices;
|
||||
QVector<glm::vec3> _hermite;
|
||||
bool _hermiteEnabled;
|
||||
QMultiHash<VoxelCoord, int> _quadIndices;
|
||||
int _size;
|
||||
int _vertexCount;
|
||||
int _indexCount;
|
||||
int _hermiteCount;
|
||||
GLuint _vertexBufferID;
|
||||
GLuint _indexBufferID;
|
||||
GLuint _hermiteBufferID;
|
||||
QVector<SharedObjectPointer> _materials;
|
||||
QVector<NetworkTexturePointer> _networkTextures;
|
||||
};
|
||||
|
||||
/// Renders metavoxels as points.
|
||||
class DefaultMetavoxelRendererImplementation : public MetavoxelRendererImplementation {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE DefaultMetavoxelRendererImplementation();
|
||||
|
||||
virtual void simulate(MetavoxelData& data, float deltaTime, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
};
|
||||
|
||||
/// Renders spheres.
|
||||
class SphereRenderer : public SpannerRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SphereRenderer();
|
||||
|
||||
virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false);
|
||||
};
|
||||
|
||||
/// Renders cuboids.
|
||||
class CuboidRenderer : public SpannerRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE CuboidRenderer();
|
||||
|
||||
virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false);
|
||||
};
|
||||
|
||||
/// 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(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
private slots:
|
||||
|
||||
void applyTranslation(const glm::vec3& translation);
|
||||
void applyRotation(const glm::quat& rotation);
|
||||
void applyScale(float scale);
|
||||
void applyURL(const QUrl& url);
|
||||
|
||||
private:
|
||||
|
||||
Model* _model;
|
||||
};
|
||||
|
||||
/// Renders heightfields.
|
||||
class HeightfieldRenderer : public SpannerRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE HeightfieldRenderer();
|
||||
|
||||
virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false);
|
||||
};
|
||||
|
||||
/// Renders a single quadtree node.
|
||||
class HeightfieldNodeRenderer : public AbstractHeightfieldNodeRenderer {
|
||||
public:
|
||||
|
||||
HeightfieldNodeRenderer();
|
||||
virtual ~HeightfieldNodeRenderer();
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const;
|
||||
|
||||
void render(const HeightfieldNodePointer& node, const glm::vec3& translation,
|
||||
const glm::quat& rotation, const glm::vec3& scale, bool cursor);
|
||||
|
||||
private:
|
||||
|
||||
GLuint _heightTextureID;
|
||||
GLuint _colorTextureID;
|
||||
GLuint _materialTextureID;
|
||||
QVector<NetworkTexturePointer> _networkTextures;
|
||||
|
||||
BufferDataPointer _voxels;
|
||||
|
||||
typedef QPair<int, int> IntPair;
|
||||
typedef QPair<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
|
||||
static QHash<IntPair, BufferPair> _bufferPairs;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelSystem_h
|
|
@ -86,12 +86,10 @@ BandwidthDialog::BandwidthDialog(QWidget* parent) :
|
|||
new BandwidthChannelDisplay({NodeType::EntityServer}, form, "Octree", "Kbps", 1.0, COLOR2);
|
||||
_allChannelDisplays[3] = _octreeChannelDisplay =
|
||||
new BandwidthChannelDisplay({NodeType::DomainServer}, form, "Domain", "Kbps", 1.0, COLOR2);
|
||||
_allChannelDisplays[4] = _metavoxelsChannelDisplay =
|
||||
new BandwidthChannelDisplay({NodeType::MetavoxelServer, NodeType::EnvironmentServer}, form, "Metavoxels", "Kbps", 1.0, COLOR2);
|
||||
_allChannelDisplays[5] = _otherChannelDisplay =
|
||||
_allChannelDisplays[4] = _otherChannelDisplay =
|
||||
new BandwidthChannelDisplay({NodeType::Unassigned}, form, "Other", "Kbps", 1.0, COLOR2);
|
||||
_allChannelDisplays[6] = _totalChannelDisplay =
|
||||
new BandwidthChannelDisplay({NodeType::DomainServer, NodeType::EntityServer, NodeType::MetavoxelServer,
|
||||
_allChannelDisplays[5] = _totalChannelDisplay =
|
||||
new BandwidthChannelDisplay({NodeType::DomainServer, NodeType::EntityServer,
|
||||
NodeType::EnvironmentServer, NodeType::AudioMixer, NodeType::Agent,
|
||||
NodeType::AvatarMixer, NodeType::Unassigned},
|
||||
form, "Total", "Kbps", 1.0, COLOR2);
|
||||
|
|
|
@ -63,11 +63,10 @@ private:
|
|||
BandwidthChannelDisplay* _avatarsChannelDisplay;
|
||||
BandwidthChannelDisplay* _octreeChannelDisplay;
|
||||
BandwidthChannelDisplay* _domainChannelDisplay;
|
||||
BandwidthChannelDisplay* _metavoxelsChannelDisplay;
|
||||
BandwidthChannelDisplay* _otherChannelDisplay;
|
||||
BandwidthChannelDisplay* _totalChannelDisplay; // sums of all the other channels
|
||||
|
||||
static const unsigned int _CHANNELCOUNT = 7;
|
||||
static const unsigned int _CHANNELCOUNT = 6;
|
||||
BandwidthChannelDisplay* _allChannelDisplays[_CHANNELCOUNT];
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <AnimationCache.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
|
@ -42,7 +41,6 @@ CachesSizeDialog::CachesSizeDialog(QWidget* parent) :
|
|||
|
||||
form->addRow("Animations cache size (MB):", _animations = createDoubleSpinBox(this));
|
||||
form->addRow("Geometries cache size (MB):", _geometries = createDoubleSpinBox(this));
|
||||
form->addRow("Scripts cache size (MB):", _scripts = createDoubleSpinBox(this));
|
||||
form->addRow("Sounds cache size (MB):", _sounds = createDoubleSpinBox(this));
|
||||
form->addRow("Textures cache size (MB):", _textures = createDoubleSpinBox(this));
|
||||
|
||||
|
@ -59,7 +57,6 @@ CachesSizeDialog::CachesSizeDialog(QWidget* parent) :
|
|||
void CachesSizeDialog::confirmClicked(bool checked) {
|
||||
DependencyManager::get<AnimationCache>()->setUnusedResourceCacheSize(_animations->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<GeometryCache>()->setUnusedResourceCacheSize(_geometries->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<ScriptCache>()->setUnusedResourceCacheSize(_scripts->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<SoundCache>()->setUnusedResourceCacheSize(_sounds->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<TextureCache>()->setUnusedResourceCacheSize(_textures->value() * BYTES_PER_MEGABYTES);
|
||||
|
||||
|
@ -69,7 +66,6 @@ void CachesSizeDialog::confirmClicked(bool checked) {
|
|||
void CachesSizeDialog::resetClicked(bool checked) {
|
||||
_animations->setValue(DependencyManager::get<AnimationCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_geometries->setValue(DependencyManager::get<GeometryCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_scripts->setValue(DependencyManager::get<ScriptCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_sounds->setValue(DependencyManager::get<SoundCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_textures->setValue(DependencyManager::get<TextureCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
#include "HMDToolsDialog.h"
|
||||
#include "LodToolsDialog.h"
|
||||
#include "LoginDialog.h"
|
||||
#include "MetavoxelEditor.h"
|
||||
#include "MetavoxelNetworkSimulator.h"
|
||||
#include "OctreeStatsDialog.h"
|
||||
#include "PreferencesDialog.h"
|
||||
#include "ScriptEditorWindow.h"
|
||||
|
@ -148,16 +146,6 @@ void DialogsManager::hmdToolsClosed() {
|
|||
_hmdToolsDialog->hide();
|
||||
}
|
||||
|
||||
void DialogsManager::showMetavoxelEditor() {
|
||||
maybeCreateDialog(_metavoxelEditor);
|
||||
_metavoxelEditor->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::showMetavoxelNetworkSimulator() {
|
||||
maybeCreateDialog(_metavoxelNetworkSimulator);
|
||||
_metavoxelNetworkSimulator->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::showScriptEditor() {
|
||||
maybeCreateDialog(_scriptEditor);
|
||||
_scriptEditor->raise();
|
||||
|
|
|
@ -29,8 +29,6 @@ class ChatWindow;
|
|||
class BandwidthDialog;
|
||||
class LodToolsDialog;
|
||||
class LoginDialog;
|
||||
class MetavoxelEditor;
|
||||
class MetavoxelNetworkSimulator;
|
||||
class OctreeStatsDialog;
|
||||
class PreferencesDialog;
|
||||
class ScriptEditorWindow;
|
||||
|
@ -59,8 +57,6 @@ public slots:
|
|||
void bandwidthDetails();
|
||||
void lodTools();
|
||||
void hmdTools(bool showTools);
|
||||
void showMetavoxelEditor();
|
||||
void showMetavoxelNetworkSimulator();
|
||||
void showScriptEditor();
|
||||
void showChat();
|
||||
|
||||
|
@ -95,8 +91,6 @@ private:
|
|||
QPointer<HMDToolsDialog> _hmdToolsDialog;
|
||||
QPointer<LodToolsDialog> _lodToolsDialog;
|
||||
QPointer<LoginDialog> _loginDialog;
|
||||
QPointer<MetavoxelEditor> _metavoxelEditor;
|
||||
QPointer<MetavoxelNetworkSimulator> _metavoxelNetworkSimulator;
|
||||
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
||||
QPointer<PreferencesDialog> _preferencesDialog;
|
||||
QPointer<ScriptEditorWindow> _scriptEditor;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,479 +0,0 @@
|
|||
//
|
||||
// MetavoxelEditor.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 1/21/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelEditor_h
|
||||
#define hifi_MetavoxelEditor_h
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <ProgramObject.h>
|
||||
|
||||
#include "MetavoxelSystem.h"
|
||||
|
||||
class QColorEditor;
|
||||
class QComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QGroupBox;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QScrollArea;
|
||||
class QSpinBox;
|
||||
|
||||
class MetavoxelTool;
|
||||
class SharedObjectEditor;
|
||||
class Vec3Editor;
|
||||
|
||||
/// Allows editing metavoxels.
|
||||
class MetavoxelEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelEditor(QWidget* parent = nullptr);
|
||||
|
||||
QString getSelectedAttribute() const;
|
||||
|
||||
double getGridSpacing() const;
|
||||
double getGridPosition() const;
|
||||
glm::quat getGridRotation() const;
|
||||
|
||||
QVariant getValue() const;
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
private slots:
|
||||
|
||||
void selectedAttributeChanged();
|
||||
void createNewAttribute();
|
||||
void deleteSelectedAttribute();
|
||||
void centerGridPosition();
|
||||
void alignGridPosition();
|
||||
void updateAttributes(const QString& select = QString());
|
||||
void updateTool();
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
void renderPreview();
|
||||
|
||||
private:
|
||||
|
||||
void addTool(MetavoxelTool* tool);
|
||||
MetavoxelTool* getActiveTool() const;
|
||||
|
||||
QListWidget* _attributes;
|
||||
QPushButton* _deleteAttribute;
|
||||
QCheckBox* _showAll;
|
||||
|
||||
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 userFacing = true, bool usesGrid = true);
|
||||
|
||||
bool getUsesValue() const { return _usesValue; }
|
||||
|
||||
bool isUserFacing() const { return _userFacing; }
|
||||
|
||||
bool getUsesGrid() const { return _usesGrid; }
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual void simulate(float deltaTime);
|
||||
|
||||
/// Renders the tool's interface, if any.
|
||||
virtual void render();
|
||||
|
||||
/// Renders the tool's metavoxel preview, if any.
|
||||
virtual void renderPreview();
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelEditor* _editor;
|
||||
bool _usesValue;
|
||||
bool _userFacing;
|
||||
bool _usesGrid;
|
||||
};
|
||||
|
||||
/// Base class for tools that allow dragging out a 3D box.
|
||||
class BoxTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
BoxTool(MetavoxelEditor* editor, const QString& name, bool usesValue = true, bool userFacing = true);
|
||||
|
||||
virtual void render();
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool shouldSnapToGrid();
|
||||
|
||||
virtual QColor getColor() = 0;
|
||||
|
||||
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum) = 0;
|
||||
|
||||
private:
|
||||
|
||||
void resetState();
|
||||
|
||||
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 of a region by dragging out a box.
|
||||
class BoxSetTool : public BoxTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
BoxSetTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QColor getColor();
|
||||
|
||||
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
|
||||
};
|
||||
|
||||
/// Allows setting the value across the entire space.
|
||||
class GlobalSetTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
GlobalSetTool(MetavoxelEditor* editor);
|
||||
|
||||
private slots:
|
||||
|
||||
void apply();
|
||||
};
|
||||
|
||||
/// Base class for insert/set spanner tools.
|
||||
class PlaceSpannerTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
PlaceSpannerTool(MetavoxelEditor* editor, const QString& name,
|
||||
const QString& placeText = QString(), bool usesValue = true);
|
||||
|
||||
virtual void simulate(float deltaTime);
|
||||
|
||||
virtual void renderPreview();
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QColor getColor();
|
||||
virtual SharedObjectPointer getSpanner();
|
||||
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0;
|
||||
|
||||
protected slots:
|
||||
|
||||
void place();
|
||||
|
||||
private:
|
||||
|
||||
QCheckBox* _followMouse;
|
||||
};
|
||||
|
||||
/// Allows inserting a spanner into the scene.
|
||||
class InsertSpannerTool : public PlaceSpannerTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
InsertSpannerTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
|
||||
};
|
||||
|
||||
/// 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();
|
||||
};
|
||||
|
||||
/// Base class for heightfield tools.
|
||||
class HeightfieldTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldTool(MetavoxelEditor* editor, const QString& name);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void apply() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
QFormLayout* _form;
|
||||
Vec3Editor* _translation;
|
||||
QDoubleSpinBox* _spacing;
|
||||
};
|
||||
|
||||
/// Allows importing a heightfield.
|
||||
class ImportHeightfieldTool : public HeightfieldTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ImportHeightfieldTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual void simulate(float deltaTime);
|
||||
|
||||
virtual void renderPreview();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void apply();
|
||||
|
||||
private slots:
|
||||
|
||||
void updateSpanner();
|
||||
|
||||
private:
|
||||
|
||||
QDoubleSpinBox* _heightScale;
|
||||
QDoubleSpinBox* _heightOffset;
|
||||
|
||||
HeightfieldHeightEditor* _height;
|
||||
HeightfieldColorEditor* _color;
|
||||
|
||||
SharedObjectPointer _spanner;
|
||||
};
|
||||
|
||||
/// Base class for tools that allow painting on heightfields.
|
||||
class HeightfieldBrushTool : public MetavoxelTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
virtual void render();
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QVariant createEdit(bool alternate) = 0;
|
||||
|
||||
QFormLayout* _form;
|
||||
QDoubleSpinBox* _radius;
|
||||
QDoubleSpinBox* _granularity;
|
||||
|
||||
glm::vec3 _position;
|
||||
bool _positionValid;
|
||||
};
|
||||
|
||||
/// Allows raising or lowering parts of the heightfield.
|
||||
class HeightfieldHeightBrushTool : public HeightfieldBrushTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldHeightBrushTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QVariant createEdit(bool alternate);
|
||||
|
||||
private:
|
||||
|
||||
QDoubleSpinBox* _height;
|
||||
QComboBox* _mode;
|
||||
};
|
||||
|
||||
/// Contains widgets for editing materials.
|
||||
class MaterialControl : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MaterialControl(QWidget* widget, QFormLayout* form, bool clearable = false);
|
||||
|
||||
SharedObjectPointer getMaterial();
|
||||
|
||||
const QColor& getColor() const { return _color->getColor(); }
|
||||
|
||||
private slots:
|
||||
|
||||
void clearColor();
|
||||
void clearTexture();
|
||||
void updateTexture();
|
||||
void textureLoaded();
|
||||
|
||||
private:
|
||||
|
||||
QColorEditor* _color;
|
||||
SharedObjectEditor* _materialEditor;
|
||||
QSharedPointer<NetworkTexture> _texture;
|
||||
};
|
||||
|
||||
/// Allows texturing parts of the heightfield.
|
||||
class HeightfieldMaterialBrushTool : public HeightfieldBrushTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldMaterialBrushTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QVariant createEdit(bool alternate);
|
||||
|
||||
private:
|
||||
|
||||
MaterialControl* _materialControl;
|
||||
};
|
||||
|
||||
/// Allows sculpting parts of the heightfield.
|
||||
class HeightfieldSculptBrushTool : public HeightfieldBrushTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldSculptBrushTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QVariant createEdit(bool alternate);
|
||||
|
||||
private:
|
||||
|
||||
MaterialControl* _materialControl;
|
||||
};
|
||||
|
||||
/// Allows "filling" (removing dual contour stack data) parts of the heightfield.
|
||||
class HeightfieldFillBrushTool : public HeightfieldBrushTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldFillBrushTool(MetavoxelEditor* editor);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QVariant createEdit(bool alternate);
|
||||
|
||||
private:
|
||||
|
||||
QComboBox* _mode;
|
||||
};
|
||||
|
||||
/// Allows setting heightfield materials by dragging out a box.
|
||||
class HeightfieldMaterialBoxTool : public BoxTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldMaterialBoxTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool shouldSnapToGrid();
|
||||
|
||||
virtual QColor getColor();
|
||||
|
||||
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
|
||||
|
||||
private:
|
||||
|
||||
QCheckBox* _snapToGrid;
|
||||
MaterialControl* _materialControl;
|
||||
QDoubleSpinBox* _granularity;
|
||||
};
|
||||
|
||||
/// Allows setting heightfield materials by placing a spanner.
|
||||
class HeightfieldMaterialSpannerTool : public PlaceSpannerTool {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldMaterialSpannerTool(MetavoxelEditor* editor);
|
||||
|
||||
virtual bool appliesTo(const AttributePointer& attribute) const;
|
||||
|
||||
protected:
|
||||
|
||||
virtual SharedObjectPointer getSpanner();
|
||||
virtual QColor getColor();
|
||||
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
|
||||
|
||||
private:
|
||||
|
||||
SharedObjectEditor* _spannerEditor;
|
||||
MaterialControl* _materialControl;
|
||||
QDoubleSpinBox* _granularity;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelEditor_h
|
|
@ -1,87 +0,0 @@
|
|||
//
|
||||
// MetavoxelNetworkSimulator.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/20/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Application.h"
|
||||
#include "MetavoxelNetworkSimulator.h"
|
||||
|
||||
const int BYTES_PER_KILOBYTE = 1024;
|
||||
|
||||
MetavoxelNetworkSimulator::MetavoxelNetworkSimulator(QWidget* parent) :
|
||||
QWidget(parent, Qt::Dialog) {
|
||||
|
||||
setWindowTitle("Metavoxel Network Simulator");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QVBoxLayout* topLayout = new QVBoxLayout();
|
||||
setLayout(topLayout);
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
topLayout->addLayout(form);
|
||||
|
||||
MetavoxelSystem::NetworkSimulation simulation = Application::getInstance()->getMetavoxels()->getNetworkSimulation();
|
||||
|
||||
form->addRow("Drop Rate:", _dropRate = new QDoubleSpinBox());
|
||||
_dropRate->setSuffix("%");
|
||||
_dropRate->setValue(simulation.dropRate * 100.0);
|
||||
connect(_dropRate, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
|
||||
&MetavoxelNetworkSimulator::updateMetavoxelSystem);
|
||||
|
||||
form->addRow("Repeat Rate:", _repeatRate = new QDoubleSpinBox());
|
||||
_repeatRate->setSuffix("%");
|
||||
_repeatRate->setValue(simulation.repeatRate * 100.0);
|
||||
connect(_repeatRate, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
|
||||
&MetavoxelNetworkSimulator::updateMetavoxelSystem);
|
||||
|
||||
form->addRow("Minimum Delay:", _minimumDelay = new QSpinBox());
|
||||
_minimumDelay->setMaximum(1000);
|
||||
_minimumDelay->setSuffix("ms");
|
||||
_minimumDelay->setValue(simulation.minimumDelay);
|
||||
connect(_minimumDelay, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&MetavoxelNetworkSimulator::updateMetavoxelSystem);
|
||||
|
||||
form->addRow("Maximum Delay:", _maximumDelay = new QSpinBox());
|
||||
_maximumDelay->setMaximum(1000);
|
||||
_maximumDelay->setSuffix("ms");
|
||||
_maximumDelay->setValue(simulation.maximumDelay);
|
||||
connect(_maximumDelay, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&MetavoxelNetworkSimulator::updateMetavoxelSystem);
|
||||
|
||||
form->addRow("Bandwidth Limit:", _bandwidthLimit = new QSpinBox());
|
||||
_bandwidthLimit->setMaximum(1024 * 1024);
|
||||
_bandwidthLimit->setSuffix("KB/s");
|
||||
_bandwidthLimit->setValue(simulation.bandwidthLimit / BYTES_PER_KILOBYTE);
|
||||
connect(_bandwidthLimit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&MetavoxelNetworkSimulator::updateMetavoxelSystem);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok, this);
|
||||
topLayout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QWidget::close);
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
void MetavoxelNetworkSimulator::updateMetavoxelSystem() {
|
||||
int bandwidthLimit = _bandwidthLimit->value() * BYTES_PER_KILOBYTE;
|
||||
if (bandwidthLimit > 0) {
|
||||
// make sure the limit is enough to let at least one packet through
|
||||
const int MINIMUM_BANDWIDTH_LIMIT = 2048;
|
||||
bandwidthLimit = qMax(bandwidthLimit, MINIMUM_BANDWIDTH_LIMIT);
|
||||
}
|
||||
Application::getInstance()->getMetavoxels()->setNetworkSimulation(MetavoxelSystem::NetworkSimulation(
|
||||
_dropRate->value() / 100.0, _repeatRate->value() / 100.0, qMin(_minimumDelay->value(), _maximumDelay->value()),
|
||||
qMax(_minimumDelay->value(), _maximumDelay->value()), bandwidthLimit));
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// MetavoxelNetworkSimulator.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/20/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelNetworkSimulator_h
|
||||
#define hifi_MetavoxelNetworkSimulator_h
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QDoubleSpinBox;
|
||||
class QSpinBox;
|
||||
|
||||
/// Allows tweaking network simulation (packet drop percentage, etc.) settings for metavoxels.
|
||||
class MetavoxelNetworkSimulator : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelNetworkSimulator(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateMetavoxelSystem();
|
||||
|
||||
private:
|
||||
|
||||
QDoubleSpinBox* _dropRate;
|
||||
QDoubleSpinBox* _repeatRate;
|
||||
QSpinBox* _minimumDelay;
|
||||
QSpinBox* _maximumDelay;
|
||||
QSpinBox* _bandwidthLimit;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelNetworkSimulator_h
|
|
@ -55,13 +55,7 @@ Stats::Stats():
|
|||
_pingStatsWidth(STATS_PING_MIN_WIDTH),
|
||||
_geoStatsWidth(STATS_GEO_MIN_WIDTH),
|
||||
_octreeStatsWidth(STATS_OCTREE_MIN_WIDTH),
|
||||
_lastHorizontalOffset(0),
|
||||
_metavoxelInternal(0),
|
||||
_metavoxelLeaves(0),
|
||||
_metavoxelSendProgress(0),
|
||||
_metavoxelSendTotal(0),
|
||||
_metavoxelReceiveProgress(0),
|
||||
_metavoxelReceiveTotal(0)
|
||||
_lastHorizontalOffset(0)
|
||||
{
|
||||
auto glCanvas = Application::getInstance()->getGLWidget();
|
||||
resetWidth(glCanvas->width(), 0);
|
||||
|
@ -460,31 +454,6 @@ void Stats::display(
|
|||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color);
|
||||
|
||||
QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels()->getUpdater(), "getStats",
|
||||
Q_ARG(QObject*, this), Q_ARG(const QByteArray&, "setMetavoxelStats"));
|
||||
|
||||
stringstream nodes;
|
||||
nodes << "Metavoxels: " << (_metavoxelInternal + _metavoxelLeaves);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodes.str().c_str(), color);
|
||||
|
||||
stringstream nodeTypes;
|
||||
nodeTypes << "Internal: " << _metavoxelInternal << " Leaves: " << _metavoxelLeaves;
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, nodeTypes.str().c_str(), color);
|
||||
|
||||
if (_metavoxelSendTotal > 0 || _metavoxelReceiveTotal > 0) {
|
||||
stringstream reliableStats;
|
||||
if (_metavoxelSendTotal > 0) {
|
||||
reliableStats << "Upload: " << (_metavoxelSendProgress * 100LL / _metavoxelSendTotal) << "% ";
|
||||
}
|
||||
if (_metavoxelReceiveTotal > 0) {
|
||||
reliableStats << "Download: " << (_metavoxelReceiveProgress * 100LL / _metavoxelReceiveTotal) << "%";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, reliableStats.str().c_str(), color);
|
||||
}
|
||||
}
|
||||
|
||||
verticalOffset = STATS_PELS_INITIALOFFSET;
|
||||
|
@ -648,12 +617,3 @@ void Stats::display(
|
|||
}
|
||||
}
|
||||
|
||||
void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress,
|
||||
int sendTotal, int receiveProgress, int receiveTotal) {
|
||||
_metavoxelInternal = internal;
|
||||
_metavoxelLeaves = leaves;
|
||||
_metavoxelSendProgress = sendProgress;
|
||||
_metavoxelSendTotal = sendTotal;
|
||||
_metavoxelReceiveProgress = receiveProgress;
|
||||
_metavoxelReceiveTotal = receiveTotal;
|
||||
}
|
||||
|
|
|
@ -33,9 +33,6 @@ public:
|
|||
int inKbitsPerSecond, int outKbitsPerSecond, int voxelPacketsToProcess);
|
||||
bool includeTimingRecord(const QString& name);
|
||||
|
||||
Q_INVOKABLE void setMetavoxelStats(int internal, int leaves, int sendProgress,
|
||||
int sendTotal, int receiveProgress, int receiveTotal);
|
||||
|
||||
private:
|
||||
static Stats* _sharedInstance;
|
||||
|
||||
|
@ -52,12 +49,6 @@ private:
|
|||
|
||||
int _lastHorizontalOffset;
|
||||
|
||||
int _metavoxelInternal;
|
||||
int _metavoxelLeaves;
|
||||
int _metavoxelSendProgress;
|
||||
int _metavoxelSendTotal;
|
||||
int _metavoxelReceiveProgress;
|
||||
int _metavoxelReceiveTotal;
|
||||
};
|
||||
|
||||
#endif // hifi_Stats_h
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
set(TARGET_NAME metavoxels)
|
||||
|
||||
auto_mtc()
|
||||
|
||||
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
|
||||
setup_hifi_library(Network Script Widgets)
|
||||
|
||||
# link in the networking library
|
||||
link_hifi_libraries(shared networking)
|
||||
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
|
@ -1,499 +0,0 @@
|
|||
//
|
||||
// AttributeRegistry.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/6/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QMutexLocker>
|
||||
#include <QReadLocker>
|
||||
#include <QScriptEngine>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelData.h"
|
||||
#include "Spanner.h"
|
||||
|
||||
REGISTER_META_OBJECT(FloatAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectSetAttribute)
|
||||
REGISTER_META_OBJECT(SpannerSetAttribute)
|
||||
|
||||
static int attributePointerMetaTypeId = qRegisterMetaType<AttributePointer>();
|
||||
static int ownedAttributeValueMetaTypeId = qRegisterMetaType<OwnedAttributeValue>();
|
||||
|
||||
AttributeRegistry* AttributeRegistry::getInstance() {
|
||||
static AttributeRegistry registry;
|
||||
return ®istry;
|
||||
}
|
||||
|
||||
AttributeRegistry::AttributeRegistry() :
|
||||
_guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject,
|
||||
new DefaultMetavoxelGuide()))),
|
||||
_rendererAttribute(registerAttribute(new SharedObjectAttribute("renderer", &MetavoxelRenderer::staticMetaObject,
|
||||
new DefaultMetavoxelRenderer()))),
|
||||
_spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))) {
|
||||
|
||||
// our baseline LOD threshold is for voxels; spanners are a different story
|
||||
const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 16.0f;
|
||||
_spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER);
|
||||
_spannersAttribute->setUserFacing(true);
|
||||
}
|
||||
|
||||
static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) {
|
||||
QDebug debug = qDebug();
|
||||
|
||||
for (int i = 0; i < context->argumentCount(); i++) {
|
||||
debug << context->argument(i).toString();
|
||||
}
|
||||
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
void AttributeRegistry::configureScriptEngine(QScriptEngine* engine) {
|
||||
QScriptValue registry = engine->newObject();
|
||||
registry.setProperty("getAttribute", engine->newFunction(getAttribute, 1));
|
||||
engine->globalObject().setProperty("AttributeRegistry", registry);
|
||||
engine->globalObject().setProperty("qDebug", engine->newFunction(qDebugFunction, 1));
|
||||
}
|
||||
|
||||
AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute) {
|
||||
if (!attribute) {
|
||||
return attribute;
|
||||
}
|
||||
QWriteLocker locker(&_attributesLock);
|
||||
AttributePointer& pointer = _attributes[attribute->getName()];
|
||||
if (!pointer) {
|
||||
pointer = attribute;
|
||||
}
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void AttributeRegistry::deregisterAttribute(const QString& name) {
|
||||
QWriteLocker locker(&_attributesLock);
|
||||
_attributes.remove(name);
|
||||
}
|
||||
|
||||
AttributePointer AttributeRegistry::getAttribute(const QString& name) {
|
||||
QReadLocker locker(&_attributesLock);
|
||||
return _attributes.value(name);
|
||||
}
|
||||
|
||||
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
|
||||
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
|
||||
QScriptEngine::PreferExistingWrapperObject);
|
||||
}
|
||||
|
||||
AttributeValue::AttributeValue(const AttributePointer& attribute) :
|
||||
_attribute(attribute), _value(attribute ? attribute->getDefaultValue() : NULL) {
|
||||
}
|
||||
|
||||
AttributeValue::AttributeValue(const AttributePointer& attribute, void* value) :
|
||||
_attribute(attribute), _value(value) {
|
||||
}
|
||||
|
||||
void* AttributeValue::copy() const {
|
||||
return _attribute->create(_value);
|
||||
}
|
||||
|
||||
bool AttributeValue::isDefault() const {
|
||||
return !_attribute || _attribute->equal(_value, _attribute->getDefaultValue());
|
||||
}
|
||||
|
||||
bool AttributeValue::operator==(const AttributeValue& other) const {
|
||||
return _attribute == other._attribute && (!_attribute || _attribute->equal(_value, other._value));
|
||||
}
|
||||
|
||||
bool AttributeValue::operator==(void* other) const {
|
||||
return _attribute && _attribute->equal(_value, other);
|
||||
}
|
||||
|
||||
bool AttributeValue::operator!=(const AttributeValue& other) const {
|
||||
return _attribute != other._attribute || (_attribute && !_attribute->equal(_value, other._value));
|
||||
}
|
||||
|
||||
bool AttributeValue::operator!=(void* other) const {
|
||||
return !_attribute || !_attribute->equal(_value, other);
|
||||
}
|
||||
|
||||
OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute, void* value) :
|
||||
AttributeValue(attribute, value) {
|
||||
}
|
||||
|
||||
OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute) :
|
||||
AttributeValue(attribute, attribute ? attribute->create() : NULL) {
|
||||
}
|
||||
|
||||
OwnedAttributeValue::OwnedAttributeValue(const AttributeValue& other) :
|
||||
AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) {
|
||||
}
|
||||
|
||||
OwnedAttributeValue::OwnedAttributeValue(const OwnedAttributeValue& other) :
|
||||
AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) {
|
||||
}
|
||||
|
||||
OwnedAttributeValue::~OwnedAttributeValue() {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
}
|
||||
|
||||
void OwnedAttributeValue::mix(const AttributeValue& first, const AttributeValue& second, float alpha) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
_attribute = first.getAttribute();
|
||||
_value = _attribute->mix(first.getValue(), second.getValue(), alpha);
|
||||
}
|
||||
|
||||
void OwnedAttributeValue::blend(const AttributeValue& source, const AttributeValue& dest) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
_attribute = source.getAttribute();
|
||||
_value = _attribute->blend(source.getValue(), dest.getValue());
|
||||
}
|
||||
|
||||
OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
if ((_attribute = other.getAttribute())) {
|
||||
_value = other.copy();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OwnedAttributeValue& OwnedAttributeValue::operator=(const OwnedAttributeValue& other) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
if ((_attribute = other.getAttribute())) {
|
||||
_value = other.copy();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Attribute::Attribute(const QString& name) :
|
||||
_lodThresholdMultiplier(1.0f),
|
||||
_userFacing(false) {
|
||||
setObjectName(name);
|
||||
}
|
||||
|
||||
Attribute::~Attribute() {
|
||||
}
|
||||
|
||||
void Attribute::readSubdivided(MetavoxelStreamState& state, void*& value,
|
||||
const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const {
|
||||
read(state.base.stream, value, isLeaf);
|
||||
}
|
||||
|
||||
void Attribute::writeSubdivided(MetavoxelStreamState& state, void* value,
|
||||
const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const {
|
||||
write(state.base.stream, value, isLeaf);
|
||||
}
|
||||
|
||||
MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value);
|
||||
}
|
||||
|
||||
void Attribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
data.createRoot(state.base.attribute)->read(state);
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
root.write(state);
|
||||
}
|
||||
|
||||
void Attribute::readMetavoxelDelta(MetavoxelData& data, const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
data.createRoot(state.base.attribute)->readDelta(reference, state);
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
root.writeDelta(reference, state);
|
||||
}
|
||||
|
||||
void Attribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
// copy if changed
|
||||
MetavoxelNode* oldRoot = data.getRoot(state.base.attribute);
|
||||
MetavoxelNode* newRoot = oldRoot->readSubdivision(state);
|
||||
if (newRoot != oldRoot) {
|
||||
data.setRoot(state.base.attribute, newRoot);
|
||||
}
|
||||
}
|
||||
|
||||
void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
root.writeSubdivision(state);
|
||||
}
|
||||
|
||||
bool Attribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
|
||||
return firstRoot.deepEquals(this, secondRoot, minimum, size, lod);
|
||||
}
|
||||
|
||||
MetavoxelNode* Attribute::expandMetavoxelRoot(const MetavoxelNode& root) {
|
||||
AttributePointer attribute(this);
|
||||
MetavoxelNode* newParent = new MetavoxelNode(attribute);
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
MetavoxelNode* newChild = new MetavoxelNode(attribute);
|
||||
newParent->setChild(i, newChild);
|
||||
int index = MetavoxelNode::getOppositeChildIndex(i);
|
||||
if (root.isLeaf()) {
|
||||
newChild->setChild(index, new MetavoxelNode(root.getAttributeValue(attribute)));
|
||||
} else {
|
||||
MetavoxelNode* grandchild = root.getChild(i);
|
||||
grandchild->incrementReferenceCount();
|
||||
newChild->setChild(index, grandchild);
|
||||
}
|
||||
for (int j = 1; j < MetavoxelNode::CHILD_COUNT; j++) {
|
||||
MetavoxelNode* newGrandchild = new MetavoxelNode(attribute);
|
||||
newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild);
|
||||
}
|
||||
}
|
||||
return newParent;
|
||||
}
|
||||
|
||||
FloatAttribute::FloatAttribute(const QString& name) :
|
||||
SimpleInlineAttribute(name) {
|
||||
}
|
||||
|
||||
SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject,
|
||||
const SharedObjectPointer& defaultValue) :
|
||||
InlineAttribute<SharedObjectPointer>(name, defaultValue),
|
||||
_metaObject(metaObject) {
|
||||
|
||||
}
|
||||
|
||||
void SharedObjectAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
in >> *((SharedObjectPointer*)&value);
|
||||
}
|
||||
}
|
||||
|
||||
void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out << decodeInline<SharedObjectPointer>(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool SharedObjectAttribute::deepEqual(void* first, void* second) const {
|
||||
SharedObjectPointer firstObject = decodeInline<SharedObjectPointer>(first);
|
||||
SharedObjectPointer secondObject = decodeInline<SharedObjectPointer>(second);
|
||||
return firstObject ? firstObject->equals(secondObject) : !secondObject;
|
||||
}
|
||||
|
||||
bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
|
||||
for (int i = 1; i < MERGE_COUNT; i++) {
|
||||
if (firstChild != decodeInline<SharedObjectPointer>(children[i])) {
|
||||
*(SharedObjectPointer*)&parent = _defaultValue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*(SharedObjectPointer*)&parent = firstChild;
|
||||
return true;
|
||||
}
|
||||
|
||||
void* SharedObjectAttribute::createFromVariant(const QVariant& value) const {
|
||||
return create(encodeInline(value.value<SharedObjectPointer>()));
|
||||
}
|
||||
|
||||
QWidget* SharedObjectAttribute::createEditor(QWidget* parent) const {
|
||||
SharedObjectEditor* editor = new SharedObjectEditor(_metaObject, parent);
|
||||
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);
|
||||
}
|
||||
|
||||
MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode(
|
||||
const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value, original);
|
||||
}
|
||||
|
||||
static bool setsEqual(const SharedObjectSet& firstSet, const SharedObjectSet& secondSet) {
|
||||
if (firstSet.size() != secondSet.size()) {
|
||||
return false;
|
||||
}
|
||||
// some hackiness here: we assume that the local ids of the first set correspond to the remote ids of the second,
|
||||
// so that this will work with the tests
|
||||
foreach (const SharedObjectPointer& firstObject, firstSet) {
|
||||
int id = firstObject->getID();
|
||||
bool found = false;
|
||||
foreach (const SharedObjectPointer& secondObject, secondSet) {
|
||||
if (secondObject->getRemoteID() == id) {
|
||||
if (!firstObject->equals(secondObject)) {
|
||||
return false;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::deepEqual(void* first, void* second) const {
|
||||
return setsEqual(decodeInline<SharedObjectSet>(first), decodeInline<SharedObjectSet>(second));
|
||||
}
|
||||
|
||||
MetavoxelNode* SharedObjectSetAttribute::expandMetavoxelRoot(const MetavoxelNode& root) {
|
||||
AttributePointer attribute(this);
|
||||
MetavoxelNode* newParent = new MetavoxelNode(attribute);
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
MetavoxelNode* newChild = new MetavoxelNode(root.getAttributeValue(attribute));
|
||||
newParent->setChild(i, newChild);
|
||||
if (root.isLeaf()) {
|
||||
continue;
|
||||
}
|
||||
MetavoxelNode* grandchild = root.getChild(i);
|
||||
grandchild->incrementReferenceCount();
|
||||
int index = MetavoxelNode::getOppositeChildIndex(i);
|
||||
newChild->setChild(index, grandchild);
|
||||
for (int j = 1; j < MetavoxelNode::CHILD_COUNT; j++) {
|
||||
MetavoxelNode* newGrandchild = new MetavoxelNode(attribute);
|
||||
newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild);
|
||||
}
|
||||
}
|
||||
return newParent;
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AttributeValue SharedObjectSetAttribute::inherit(const AttributeValue& parentValue) const {
|
||||
return AttributeValue(parentValue.getAttribute());
|
||||
}
|
||||
|
||||
QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const {
|
||||
return new SharedObjectEditor(_metaObject, parent);
|
||||
}
|
||||
|
||||
SpannerSetAttribute::SpannerSetAttribute(const QString& name, const QMetaObject* metaObject) :
|
||||
SharedObjectSetAttribute(name, metaObject) {
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
data.insert(state.base.attribute, object);
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.base.attribute)) {
|
||||
data.createRoot(state.base.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
state.base.visit = Spanner::getAndIncrementNextVisit();
|
||||
root.writeSpanners(state);
|
||||
state.base.stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data,
|
||||
const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
readMetavoxelSubdivision(data, state);
|
||||
}
|
||||
|
||||
static void writeDeltaSubdivision(SharedObjectSet& oldSet, SharedObjectSet& newSet, Bitstream& stream) {
|
||||
for (SharedObjectSet::iterator newIt = newSet.begin(); newIt != newSet.end(); ) {
|
||||
SharedObjectSet::iterator oldIt = oldSet.find(*newIt);
|
||||
if (oldIt == oldSet.end()) {
|
||||
stream << *newIt; // added
|
||||
newIt = newSet.erase(newIt);
|
||||
|
||||
} else {
|
||||
oldSet.erase(oldIt);
|
||||
newIt++;
|
||||
}
|
||||
}
|
||||
foreach (const SharedObjectPointer& object, oldSet) {
|
||||
stream << object; // removed
|
||||
}
|
||||
stream << SharedObjectPointer();
|
||||
foreach (const SharedObjectPointer& object, newSet) {
|
||||
object->maybeWriteSubdivision(stream);
|
||||
}
|
||||
stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root,
|
||||
const MetavoxelNode& reference, MetavoxelStreamState& state) {
|
||||
SharedObjectSet oldSet, newSet;
|
||||
reference.getSpanners(this, state.minimum, state.size, state.base.referenceLOD, oldSet);
|
||||
root.getSpanners(this, state.minimum, state.size, state.base.lod, newSet);
|
||||
writeDeltaSubdivision(oldSet, newSet, state.base.stream);
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
data.toggle(state.base.attribute, object);
|
||||
}
|
||||
forever {
|
||||
SharedObjectPointer object;
|
||||
state.base.stream >> object;
|
||||
if (!object) {
|
||||
break;
|
||||
}
|
||||
SharedObjectPointer newObject = object->readSubdivision(state.base.stream);
|
||||
if (newObject != object) {
|
||||
data.replace(state.base.attribute, object, newObject);
|
||||
state.base.stream.addSubdividedObject(newObject);
|
||||
}
|
||||
}
|
||||
// even if the root is empty, it should still exist
|
||||
if (!data.getRoot(state.base.attribute)) {
|
||||
data.createRoot(state.base.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state) {
|
||||
SharedObjectSet oldSet, newSet;
|
||||
root.getSpanners(this, state.minimum, state.size, state.base.referenceLOD, oldSet);
|
||||
root.getSpanners(this, state.minimum, state.size, state.base.lod, newSet);
|
||||
writeDeltaSubdivision(oldSet, newSet, state.base.stream);
|
||||
}
|
||||
|
||||
bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
|
||||
|
||||
SharedObjectSet firstSet;
|
||||
firstRoot.getSpanners(this, minimum, size, lod, firstSet);
|
||||
SharedObjectSet secondSet;
|
||||
secondRoot.getSpanners(this, minimum, size, lod, secondSet);
|
||||
return setsEqual(firstSet, secondSet);
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
//
|
||||
// AttributeRegistry.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/6/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AttributeRegistry_h
|
||||
#define hifi_AttributeRegistry_h
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QReadWriteLock>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Bitstream.h"
|
||||
#include "SharedObject.h"
|
||||
|
||||
class QScriptContext;
|
||||
class QScriptEngine;
|
||||
class QScriptValue;
|
||||
|
||||
class Attribute;
|
||||
class DataBlock;
|
||||
class MetavoxelData;
|
||||
class MetavoxelLOD;
|
||||
class MetavoxelNode;
|
||||
class MetavoxelStreamState;
|
||||
|
||||
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
|
||||
|
||||
Q_DECLARE_METATYPE(AttributePointer)
|
||||
|
||||
/// Maintains information about metavoxel attribute types.
|
||||
class AttributeRegistry {
|
||||
public:
|
||||
|
||||
/// Returns a pointer to the singleton registry instance.
|
||||
static AttributeRegistry* getInstance();
|
||||
|
||||
AttributeRegistry();
|
||||
|
||||
/// Configures the supplied script engine with the global AttributeRegistry property.
|
||||
void configureScriptEngine(QScriptEngine* engine);
|
||||
|
||||
/// Registers an attribute with the system. The registry assumes ownership of the object.
|
||||
/// \return either the pointer passed as an argument, if the attribute wasn't already registered, or the existing
|
||||
/// attribute
|
||||
AttributePointer registerAttribute(Attribute* attribute) { return registerAttribute(AttributePointer(attribute)); }
|
||||
|
||||
/// Registers an attribute with the system.
|
||||
/// \return either the pointer passed as an argument, if the attribute wasn't already registered, or the existing
|
||||
/// attribute
|
||||
AttributePointer registerAttribute(AttributePointer attribute);
|
||||
|
||||
/// Deregisters an attribute.
|
||||
void deregisterAttribute(const QString& name);
|
||||
|
||||
/// Retrieves an attribute by name.
|
||||
AttributePointer getAttribute(const QString& name);
|
||||
|
||||
/// Returns a reference to the attribute hash.
|
||||
const QHash<QString, AttributePointer>& getAttributes() const { return _attributes; }
|
||||
|
||||
/// Returns a reference to the attributes lock.
|
||||
QReadWriteLock& getAttributesLock() { return _attributesLock; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectPointer "guide" attribute.
|
||||
const AttributePointer& getGuideAttribute() const { return _guideAttribute; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectPointer "renderer" attribute.
|
||||
const AttributePointer& getRendererAttribute() const { return _rendererAttribute; }
|
||||
|
||||
/// Returns a reference to the standard SharedObjectSet "spanners" attribute.
|
||||
const AttributePointer& getSpannersAttribute() const { return _spannersAttribute; }
|
||||
|
||||
private:
|
||||
|
||||
static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
QHash<QString, AttributePointer> _attributes;
|
||||
QReadWriteLock _attributesLock;
|
||||
|
||||
AttributePointer _guideAttribute;
|
||||
AttributePointer _rendererAttribute;
|
||||
AttributePointer _spannersAttribute;
|
||||
};
|
||||
|
||||
/// Converts a value to a void pointer.
|
||||
template<class T> inline void* encodeInline(T value) {
|
||||
return *(void**)&value;
|
||||
}
|
||||
|
||||
/// Extracts a value from a void pointer.
|
||||
template<class T> inline T decodeInline(void* value) {
|
||||
return *(T*)&value;
|
||||
}
|
||||
|
||||
/// Pairs an attribute value with its type.
|
||||
class AttributeValue {
|
||||
public:
|
||||
|
||||
AttributeValue(const AttributePointer& attribute = AttributePointer());
|
||||
AttributeValue(const AttributePointer& attribute, void* value);
|
||||
|
||||
AttributePointer getAttribute() const { return _attribute; }
|
||||
void* getValue() const { return _value; }
|
||||
|
||||
template<class T> void setInlineValue(T value) { _value = encodeInline(value); }
|
||||
template<class T> T getInlineValue() const { return decodeInline<T>(_value); }
|
||||
|
||||
void* copy() const;
|
||||
|
||||
bool isDefault() const;
|
||||
|
||||
bool operator==(const AttributeValue& other) const;
|
||||
bool operator==(void* other) const;
|
||||
|
||||
bool operator!=(const AttributeValue& other) const;
|
||||
bool operator!=(void* other) const;
|
||||
|
||||
protected:
|
||||
|
||||
AttributePointer _attribute;
|
||||
void* _value;
|
||||
};
|
||||
|
||||
// Assumes ownership of an attribute value.
|
||||
class OwnedAttributeValue : public AttributeValue {
|
||||
public:
|
||||
|
||||
/// Assumes ownership of the specified value. It will be destroyed when this is destroyed or reassigned.
|
||||
OwnedAttributeValue(const AttributePointer& attribute, void* value);
|
||||
|
||||
/// Creates an owned attribute with a copy of the specified attribute's default value.
|
||||
OwnedAttributeValue(const AttributePointer& attribute = AttributePointer());
|
||||
|
||||
/// Creates an owned attribute with a copy of the specified other value.
|
||||
OwnedAttributeValue(const AttributeValue& other);
|
||||
|
||||
/// Creates an owned attribute with a copy of the specified other value.
|
||||
OwnedAttributeValue(const OwnedAttributeValue& other);
|
||||
|
||||
/// Destroys the current value, if any.
|
||||
~OwnedAttributeValue();
|
||||
|
||||
/// Sets this attribute to a mix of the first and second provided.
|
||||
void mix(const AttributeValue& first, const AttributeValue& second, float alpha);
|
||||
|
||||
/// Sets this attribute to a blend of the source and destination.
|
||||
void blend(const AttributeValue& source, const AttributeValue& dest);
|
||||
|
||||
/// Destroys the current value, if any, and copies the specified other value.
|
||||
OwnedAttributeValue& operator=(const AttributeValue& other);
|
||||
|
||||
/// Destroys the current value, if any, and copies the specified other value.
|
||||
OwnedAttributeValue& operator=(const OwnedAttributeValue& other);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(OwnedAttributeValue)
|
||||
|
||||
/// Represents a registered attribute.
|
||||
class Attribute : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float lodThresholdMultiplier MEMBER _lodThresholdMultiplier)
|
||||
Q_PROPERTY(bool userFacing MEMBER _userFacing)
|
||||
|
||||
public:
|
||||
|
||||
static const int MERGE_COUNT = 8;
|
||||
|
||||
Attribute(const QString& name);
|
||||
virtual ~Attribute();
|
||||
|
||||
Q_INVOKABLE QString getName() const { return objectName(); }
|
||||
|
||||
float getLODThresholdMultiplier() const { return _lodThresholdMultiplier; }
|
||||
void setLODThresholdMultiplier(float multiplier) { _lodThresholdMultiplier = multiplier; }
|
||||
|
||||
bool isUserFacing() const { return _userFacing; }
|
||||
void setUserFacing(bool userFacing) { _userFacing = userFacing; }
|
||||
|
||||
void* create() const { return create(getDefaultValue()); }
|
||||
virtual void* create(void* copy) const = 0;
|
||||
virtual void destroy(void* value) const = 0;
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const = 0;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const = 0;
|
||||
|
||||
virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); }
|
||||
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); }
|
||||
|
||||
virtual void readSubdivided(MetavoxelStreamState& state, void*& value,
|
||||
const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const;
|
||||
virtual void writeSubdivided(MetavoxelStreamState& state, void* value,
|
||||
const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const;
|
||||
|
||||
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
virtual void readMetavoxelDelta(MetavoxelData& data, const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
|
||||
virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
virtual bool equal(void* first, void* second) const = 0;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const { return equal(first, second); }
|
||||
|
||||
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
|
||||
|
||||
/// Expands the specified root, doubling its size in each dimension.
|
||||
/// \return a new node representing the result
|
||||
virtual MetavoxelNode* expandMetavoxelRoot(const MetavoxelNode& root);
|
||||
|
||||
/// Merges the value of a parent and its children.
|
||||
/// \param postRead whether or not the merge is happening after a read
|
||||
/// \return whether or not the children and parent values are all equal
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const = 0;
|
||||
|
||||
/// Given the parent value, returns the value that children should inherit (either the parent value or the default).
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const { return parentValue; }
|
||||
|
||||
/// Mixes the first and the second, returning a new value with the result.
|
||||
virtual void* mix(void* first, void* second, float alpha) const = 0;
|
||||
|
||||
/// Blends the source with the destination, returning a new value with the result.
|
||||
virtual void* blend(void* source, void* dest) const = 0;
|
||||
|
||||
virtual void* getDefaultValue() const = 0;
|
||||
|
||||
virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const { return create(); }
|
||||
|
||||
virtual void* createFromVariant(const QVariant& value) const { return create(); }
|
||||
|
||||
/// Creates a widget to use to edit values of this attribute, or returns NULL if the attribute isn't editable.
|
||||
/// The widget should have a single "user" property that will be used to get/set the value.
|
||||
virtual QWidget* createEditor(QWidget* parent = NULL) const { return NULL; }
|
||||
|
||||
private:
|
||||
|
||||
float _lodThresholdMultiplier;
|
||||
bool _userFacing;
|
||||
};
|
||||
|
||||
/// A simple attribute class that stores its values inline.
|
||||
template<class T, int bits = 32> class InlineAttribute : public Attribute {
|
||||
public:
|
||||
|
||||
InlineAttribute(const QString& name, const T& defaultValue = T()) : Attribute(name), _defaultValue(defaultValue) { }
|
||||
|
||||
virtual void* create(void* copy) const { void* value; new (&value) T(*(T*)©); return value; }
|
||||
virtual void destroy(void* value) const { ((T*)&value)->~T(); }
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool equal(void* first, void* second) const { return decodeInline<T>(first) == decodeInline<T>(second); }
|
||||
|
||||
virtual void* mix(void* first, void* second, float alpha) const { return create(alpha < 0.5f ? first : second); }
|
||||
|
||||
virtual void* blend(void* source, void* dest) const { return create(source); }
|
||||
|
||||
virtual void* getDefaultValue() const { return encodeInline(_defaultValue); }
|
||||
|
||||
protected:
|
||||
|
||||
T _defaultValue;
|
||||
};
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
value = getDefaultValue();
|
||||
in.read(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out.write(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides averaging using the +=, ==, and / operators.
|
||||
template<class T, int bits = 32> class SimpleInlineAttribute : public InlineAttribute<T, bits> {
|
||||
public:
|
||||
|
||||
SimpleInlineAttribute(const QString& name, const T& defaultValue = T()) : InlineAttribute<T, bits>(name, defaultValue) { }
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
};
|
||||
|
||||
template<class T, int bits> inline bool SimpleInlineAttribute<T, bits>::merge(
|
||||
void*& parent, void* children[], bool postRead) const {
|
||||
T firstValue = decodeInline<T>(children[0]);
|
||||
T totalValue = firstValue;
|
||||
bool allChildrenEqual = true;
|
||||
for (int i = 1; i < Attribute::MERGE_COUNT; i++) {
|
||||
T value = decodeInline<T>(children[i]);
|
||||
totalValue += value;
|
||||
allChildrenEqual &= (firstValue == value);
|
||||
}
|
||||
parent = encodeInline(totalValue / Attribute::MERGE_COUNT);
|
||||
return allChildrenEqual;
|
||||
}
|
||||
|
||||
/// A simple float attribute.
|
||||
class FloatAttribute : public SimpleInlineAttribute<float> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE FloatAttribute(const QString& name = QString());
|
||||
};
|
||||
|
||||
/// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject).
|
||||
class SharedObjectAttribute : public InlineAttribute<SharedObjectPointer> {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject)
|
||||
|
||||
public:
|
||||
|
||||
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;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual void* createFromVariant(const QVariant& value) const;
|
||||
|
||||
virtual QWidget* createEditor(QWidget* parent = NULL) const;
|
||||
|
||||
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 MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const;
|
||||
|
||||
virtual MetavoxelNode* expandMetavoxelRoot(const MetavoxelNode& root);
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
|
||||
|
||||
virtual QWidget* createEditor(QWidget* parent = NULL) const;
|
||||
|
||||
private:
|
||||
|
||||
const QMetaObject* _metaObject;
|
||||
};
|
||||
|
||||
/// An attribute that takes the form of a set of spanners.
|
||||
class SpannerSetAttribute : public SharedObjectSetAttribute {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SpannerSetAttribute(const QString& name = QString(),
|
||||
const QMetaObject* metaObject = &SharedObject::staticMetaObject);
|
||||
|
||||
virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
virtual void readMetavoxelDelta(MetavoxelData& data, const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
|
||||
virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
|
||||
};
|
||||
|
||||
#endif // hifi_AttributeRegistry_h
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,952 +0,0 @@
|
|||
//
|
||||
// DatagramSequencer.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/20/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DatagramSequencer.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
||||
// in sequencer parlance, a "packet" may consist of multiple datagrams. clarify when we refer to actual datagrams
|
||||
const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
|
||||
|
||||
const int DEFAULT_MAX_PACKET_SIZE = 3000;
|
||||
|
||||
// the default slow-start threshold, which will be lowered quickly when we first encounter packet loss
|
||||
const float DEFAULT_SLOW_START_THRESHOLD = 1000.0f;
|
||||
|
||||
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
|
||||
QObject(parent),
|
||||
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
|
||||
_outputStream(_outgoingPacketStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_incomingDatagramStream(&_incomingDatagramBuffer),
|
||||
_datagramHeaderSize(datagramHeader.size()),
|
||||
_outgoingPacketNumber(0),
|
||||
_outgoingDatagram(MAX_DATAGRAM_SIZE, 0),
|
||||
_outgoingDatagramBuffer(&_outgoingDatagram),
|
||||
_outgoingDatagramStream(&_outgoingDatagramBuffer),
|
||||
_incomingPacketNumber(0),
|
||||
_incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
|
||||
_inputStream(_incomingPacketStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_receivedHighPriorityMessages(0),
|
||||
_maxPacketSize(DEFAULT_MAX_PACKET_SIZE),
|
||||
_packetsPerGroup(1.0f),
|
||||
_packetsToWrite(0.0f),
|
||||
_slowStartThreshold(DEFAULT_SLOW_START_THRESHOLD),
|
||||
_packetRateIncreasePacketNumber(0),
|
||||
_packetRateDecreasePacketNumber(0),
|
||||
_packetDropCount(0) {
|
||||
|
||||
_outgoingPacketStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_incomingDatagramStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_incomingPacketStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_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);
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendHighPriorityMessage(const QVariant& data) {
|
||||
HighPriorityMessage message = { data, _outgoingPacketNumber + 1 };
|
||||
_highPriorityMessages.append(message);
|
||||
}
|
||||
|
||||
ReliableChannel* DatagramSequencer::getReliableOutputChannel(int index) {
|
||||
ReliableChannel*& channel = _reliableOutputChannels[index];
|
||||
if (!channel) {
|
||||
channel = new ReliableChannel(this, index, true);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
|
||||
ReliableChannel*& channel = _reliableInputChannels[index];
|
||||
if (!channel) {
|
||||
channel = new ReliableChannel(this, index, false);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
void DatagramSequencer::addReliableChannelStats(int& sendProgress, int& sendTotal,
|
||||
int& receiveProgress, int& receiveTotal) const {
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
int sent, total;
|
||||
if (channel->getMessageSendProgress(sent, total)) {
|
||||
sendProgress += sent;
|
||||
sendTotal += total;
|
||||
}
|
||||
}
|
||||
foreach (ReliableChannel* channel, _reliableInputChannels) {
|
||||
int received, total;
|
||||
if (channel->getMessageReceiveProgress(received, total)) {
|
||||
receiveProgress += received;
|
||||
receiveTotal += total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DatagramSequencer::notePacketGroup(int desiredPackets) {
|
||||
// figure out how much data we have enqueued and increase the number of packets desired
|
||||
int totalAvailable = 0;
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
totalAvailable += channel->getBytesAvailable();
|
||||
}
|
||||
desiredPackets += (totalAvailable / _maxPacketSize);
|
||||
|
||||
// increment our packet counter and subtract/return the integer portion
|
||||
_packetsToWrite += _packetsPerGroup;
|
||||
int wholePackets = (int)_packetsToWrite;
|
||||
_packetsToWrite -= wholePackets;
|
||||
wholePackets = qMin(wholePackets, desiredPackets);
|
||||
|
||||
// if we don't want to send any more, push out the rate increase number past the group
|
||||
if (desiredPackets <= _packetsPerGroup) {
|
||||
_packetRateIncreasePacketNumber = _outgoingPacketNumber + wholePackets + 1;
|
||||
}
|
||||
|
||||
// likewise, if we're only sending one packet, don't let its loss cause rate decrease
|
||||
if (wholePackets == 1) {
|
||||
_packetRateDecreasePacketNumber = _outgoingPacketNumber + 2;
|
||||
}
|
||||
|
||||
return wholePackets;
|
||||
}
|
||||
|
||||
Bitstream& DatagramSequencer::startPacket() {
|
||||
// start with the list of acknowledgements
|
||||
_outgoingPacketStream << (quint32)_receiveRecords.size();
|
||||
foreach (const ReceiveRecord& record, _receiveRecords) {
|
||||
_outgoingPacketStream << (quint32)record.packetNumber;
|
||||
}
|
||||
|
||||
// return the stream, allowing the caller to write the rest
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
void DatagramSequencer::endPacket() {
|
||||
// write the high-priority messages
|
||||
_outputStream << _highPriorityMessages.size();
|
||||
foreach (const HighPriorityMessage& message, _highPriorityMessages) {
|
||||
_outputStream << message.data;
|
||||
}
|
||||
_outputStream.flush();
|
||||
|
||||
// if we have space remaining, send some data from our reliable channels
|
||||
int remaining = _maxPacketSize - _outgoingPacketStream.device()->pos();
|
||||
const int MINIMUM_RELIABLE_SIZE = sizeof(quint32) * 5; // count, channel number, segment count, offset, size
|
||||
QVector<ChannelSpan> spans;
|
||||
if (remaining > MINIMUM_RELIABLE_SIZE) {
|
||||
appendReliableData(remaining, spans);
|
||||
} else {
|
||||
_outgoingPacketStream << (quint32)0;
|
||||
}
|
||||
|
||||
sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos()), spans);
|
||||
_outgoingPacketStream.device()->seek(0);
|
||||
}
|
||||
|
||||
void DatagramSequencer::cancelPacket() {
|
||||
_outputStream.reset();
|
||||
_outputStream.getAndResetWriteMappings();
|
||||
_outgoingPacketStream.device()->seek(0);
|
||||
}
|
||||
|
||||
/// Simple RAII-style object to keep a device open when in scope.
|
||||
class QIODeviceOpener {
|
||||
public:
|
||||
|
||||
QIODeviceOpener(QIODevice* device, QIODevice::OpenMode mode) : _device(device) { _device->open(mode); }
|
||||
~QIODeviceOpener() { _device->close(); }
|
||||
|
||||
private:
|
||||
|
||||
QIODevice* _device;
|
||||
};
|
||||
|
||||
void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
||||
_incomingDatagramBuffer.setData(datagram.constData() + _datagramHeaderSize, datagram.size() - _datagramHeaderSize);
|
||||
QIODeviceOpener opener(&_incomingDatagramBuffer, QIODevice::ReadOnly);
|
||||
|
||||
// read the sequence number
|
||||
int sequenceNumber;
|
||||
_incomingDatagramStream >> sequenceNumber;
|
||||
|
||||
// if it's less than the last, ignore
|
||||
if (sequenceNumber < _incomingPacketNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read the size and offset
|
||||
quint32 packetSize, offset;
|
||||
_incomingDatagramStream >> packetSize >> offset;
|
||||
|
||||
// if it's greater, reset
|
||||
if (sequenceNumber > _incomingPacketNumber) {
|
||||
_incomingPacketNumber = sequenceNumber;
|
||||
_incomingPacketData.resize(packetSize);
|
||||
_offsetsReceived.clear();
|
||||
_offsetsReceived.insert(offset);
|
||||
_remainingBytes = packetSize;
|
||||
|
||||
} else {
|
||||
// make sure it's not a duplicate
|
||||
if (_offsetsReceived.contains(offset)) {
|
||||
return;
|
||||
}
|
||||
_offsetsReceived.insert(offset);
|
||||
}
|
||||
|
||||
// copy in the data
|
||||
memcpy(_incomingPacketData.data() + offset, _incomingDatagramBuffer.data().constData() + _incomingDatagramBuffer.pos(),
|
||||
_incomingDatagramBuffer.bytesAvailable());
|
||||
|
||||
// see if we're done
|
||||
if ((_remainingBytes -= _incomingDatagramBuffer.bytesAvailable()) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read the list of acknowledged packets
|
||||
quint32 acknowledgementCount;
|
||||
_incomingPacketStream >> acknowledgementCount;
|
||||
for (quint32 i = 0; i < acknowledgementCount; i++) {
|
||||
quint32 packetNumber;
|
||||
_incomingPacketStream >> packetNumber;
|
||||
if (_sendRecords.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int index = packetNumber - _sendRecords.first().packetNumber;
|
||||
if (index < 0 || index >= _sendRecords.size()) {
|
||||
continue;
|
||||
}
|
||||
QList<SendRecord>::iterator it = _sendRecords.begin();
|
||||
for (int i = 0; i < index; i++) {
|
||||
sendRecordLost(*it++);
|
||||
}
|
||||
sendRecordAcknowledged(*it);
|
||||
emit sendAcknowledged(index);
|
||||
_sendRecords.erase(_sendRecords.begin(), it + 1);
|
||||
}
|
||||
|
||||
try {
|
||||
// alert external parties so that they can read the middle
|
||||
emit readyToRead(_inputStream);
|
||||
|
||||
// read and dispatch the high-priority messages
|
||||
int highPriorityMessageCount;
|
||||
_inputStream >> highPriorityMessageCount;
|
||||
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
|
||||
for (int i = 0; i < highPriorityMessageCount; i++) {
|
||||
QVariant data;
|
||||
_inputStream >> data;
|
||||
if (i >= _receivedHighPriorityMessages) {
|
||||
emit receivedHighPriorityMessage(data);
|
||||
}
|
||||
}
|
||||
_receivedHighPriorityMessages = highPriorityMessageCount;
|
||||
|
||||
// read the reliable data, if any
|
||||
quint32 reliableChannels;
|
||||
_incomingPacketStream >> reliableChannels;
|
||||
for (quint32 i = 0; i < reliableChannels; i++) {
|
||||
quint32 channelIndex;
|
||||
_incomingPacketStream >> channelIndex;
|
||||
getReliableInputChannel(channelIndex)->readData(_incomingPacketStream);
|
||||
}
|
||||
|
||||
// record the receipt
|
||||
ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages };
|
||||
_receiveRecords.append(record);
|
||||
|
||||
emit receiveRecorded();
|
||||
|
||||
} catch (const BitstreamException& e) {
|
||||
qWarning() << "Error reading datagram:" << e.getDescription();
|
||||
}
|
||||
|
||||
_incomingPacketStream.device()->seek(0);
|
||||
_inputStream.reset();
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendClearSharedObjectMessage(int id) {
|
||||
// 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::clearReliableChannel(QObject* object) {
|
||||
ReliableChannel* channel = static_cast<ReliableChannel*>(object);
|
||||
(channel->isOutput() ? _reliableOutputChannels : _reliableInputChannels).remove(channel->getIndex());
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
||||
// stop acknowledging the recorded packets
|
||||
while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) {
|
||||
emit receiveAcknowledged(0);
|
||||
const ReceiveRecord& received = _receiveRecords.first();
|
||||
_inputStream.persistReadMappings(received.mappings);
|
||||
_receivedHighPriorityMessages -= received.newHighPriorityMessages;
|
||||
_receiveRecords.removeFirst();
|
||||
}
|
||||
_outputStream.persistWriteMappings(record.mappings);
|
||||
|
||||
// remove the received high priority messages
|
||||
for (int i = _highPriorityMessages.size() - 1; i >= 0; i--) {
|
||||
if (_highPriorityMessages.at(i).firstPacketNumber <= record.packetNumber) {
|
||||
_highPriorityMessages.erase(_highPriorityMessages.begin(), _highPriorityMessages.begin() + i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// acknowledge the received spans
|
||||
foreach (const ChannelSpan& span, record.spans) {
|
||||
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
|
||||
if (channel) {
|
||||
channel->spanAcknowledged(span);
|
||||
}
|
||||
}
|
||||
|
||||
// increase the packet rate with every ack until we pass the slow start threshold; then, every round trip
|
||||
if (record.packetNumber >= _packetRateIncreasePacketNumber) {
|
||||
if (_packetsPerGroup >= _slowStartThreshold) {
|
||||
_packetRateIncreasePacketNumber = _outgoingPacketNumber + 1;
|
||||
}
|
||||
_packetsPerGroup += 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendRecordLost(const SendRecord& record) {
|
||||
// notify the channels of their lost spans
|
||||
foreach (const ChannelSpan& span, record.spans) {
|
||||
ReliableChannel* channel = _reliableOutputChannels.value(span.channel);
|
||||
if (channel) {
|
||||
channel->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// if we've lost three in a row, halve the rate and remember as threshold
|
||||
if (_packetDropCount == 0 || record.packetNumber == _lastPacketDropped + 1) {
|
||||
_packetDropCount++;
|
||||
_lastPacketDropped = record.packetNumber;
|
||||
const int CONSECUTIVE_DROPS_BEFORE_REDUCTION = 1;
|
||||
if (_packetDropCount >= CONSECUTIVE_DROPS_BEFORE_REDUCTION && record.packetNumber >= _packetRateDecreasePacketNumber) {
|
||||
_packetsPerGroup = qMax(_packetsPerGroup * 0.5f, 1.0f);
|
||||
_slowStartThreshold = _packetsPerGroup;
|
||||
_packetRateDecreasePacketNumber = _outgoingPacketNumber + 1;
|
||||
}
|
||||
} else {
|
||||
_packetDropCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::appendReliableData(int bytes, QVector<ChannelSpan>& spans) {
|
||||
// gather total number of bytes to write, priority
|
||||
int totalBytes = 0;
|
||||
float totalPriority = 0.0f;
|
||||
int totalChannels = 0;
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
int channelBytes = channel->getBytesAvailable();
|
||||
if (channelBytes > 0) {
|
||||
totalBytes += channelBytes;
|
||||
totalPriority += channel->getPriority();
|
||||
totalChannels++;
|
||||
}
|
||||
}
|
||||
_outgoingPacketStream << (quint32)totalChannels;
|
||||
if (totalChannels == 0) {
|
||||
return;
|
||||
}
|
||||
totalBytes = qMin(bytes, totalBytes);
|
||||
|
||||
foreach (ReliableChannel* channel, _reliableOutputChannels) {
|
||||
int channelBytes = channel->getBytesAvailable();
|
||||
if (channelBytes == 0) {
|
||||
continue;
|
||||
}
|
||||
_outgoingPacketStream << (quint32)channel->getIndex();
|
||||
channelBytes = qMin(channelBytes, (int)(totalBytes * channel->getPriority() / totalPriority));
|
||||
channel->writeData(_outgoingPacketStream, channelBytes, spans);
|
||||
totalBytes -= channelBytes;
|
||||
totalPriority -= channel->getPriority();
|
||||
}
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans) {
|
||||
QIODeviceOpener opener(&_outgoingDatagramBuffer, QIODevice::WriteOnly);
|
||||
|
||||
// increment the packet number
|
||||
_outgoingPacketNumber++;
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _outgoingPacketNumber, _receiveRecords.isEmpty() ? 0 : _receiveRecords.last().packetNumber,
|
||||
_outputStream.getAndResetWriteMappings(), spans };
|
||||
_sendRecords.append(record);
|
||||
|
||||
emit sendRecorded();
|
||||
|
||||
// write the sequence number and size, which are the same between all fragments
|
||||
_outgoingDatagramBuffer.seek(_datagramHeaderSize);
|
||||
_outgoingDatagramStream << (quint32)_outgoingPacketNumber;
|
||||
_outgoingDatagramStream << (quint32)packet.size();
|
||||
int initialPosition = _outgoingDatagramBuffer.pos();
|
||||
|
||||
// break the packet into MTU-sized datagrams
|
||||
int offset = 0;
|
||||
do {
|
||||
_outgoingDatagramBuffer.seek(initialPosition);
|
||||
_outgoingDatagramStream << (quint32)offset;
|
||||
|
||||
int payloadSize = qMin((int)(_outgoingDatagram.size() - _outgoingDatagramBuffer.pos()), packet.size() - offset);
|
||||
memcpy(_outgoingDatagram.data() + _outgoingDatagramBuffer.pos(), packet.constData() + offset, payloadSize);
|
||||
|
||||
emit readyToWrite(QByteArray::fromRawData(_outgoingDatagram.constData(), _outgoingDatagramBuffer.pos() + payloadSize));
|
||||
|
||||
offset += payloadSize;
|
||||
|
||||
} while(offset < packet.size());
|
||||
}
|
||||
|
||||
const int INITIAL_CIRCULAR_BUFFER_CAPACITY = 16;
|
||||
|
||||
CircularBuffer::CircularBuffer(QObject* parent) :
|
||||
QIODevice(parent),
|
||||
_data(INITIAL_CIRCULAR_BUFFER_CAPACITY, 0),
|
||||
_position(0),
|
||||
_size(0),
|
||||
_offset(0) {
|
||||
}
|
||||
|
||||
void CircularBuffer::append(const char* data, int length) {
|
||||
// resize to fit
|
||||
int oldSize = _size;
|
||||
resize(_size + length);
|
||||
|
||||
// write our data in up to two segments: one from the position to the end, one from the beginning
|
||||
int end = (_position + oldSize) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - end);
|
||||
memcpy(_data.data() + end, data, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
memcpy(_data.data(), data + firstSegment, secondSegment);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularBuffer::remove(int length) {
|
||||
_position = (_position + length) % _data.size();
|
||||
_size -= length;
|
||||
}
|
||||
|
||||
QByteArray CircularBuffer::readBytes(int offset, int length) const {
|
||||
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);
|
||||
memcpy(data, _data.constData() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularBuffer::writeToStream(int offset, int length, QDataStream& out) const {
|
||||
// write in up to two segments
|
||||
int start = (_position + offset) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - start);
|
||||
out.writeRawData(_data.constData() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
out.writeRawData(_data.constData(), secondSegment);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularBuffer::readFromStream(int offset, int length, QDataStream& in) {
|
||||
// resize to fit
|
||||
int requiredSize = offset + length;
|
||||
if (requiredSize > _size) {
|
||||
resize(requiredSize);
|
||||
}
|
||||
|
||||
// read in up to two segments
|
||||
int start = (_position + offset) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - start);
|
||||
in.readRawData(_data.data() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
in.readRawData(_data.data(), secondSegment);
|
||||
}
|
||||
}
|
||||
|
||||
void CircularBuffer::appendToBuffer(int offset, int length, CircularBuffer& buffer) const {
|
||||
// append in up to two segments
|
||||
int start = (_position + offset) % _data.size();
|
||||
int firstSegment = qMin(length, _data.size() - start);
|
||||
buffer.append(_data.constData() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
buffer.append(_data.constData(), secondSegment);
|
||||
}
|
||||
}
|
||||
|
||||
bool CircularBuffer::atEnd() const {
|
||||
return _offset >= _size;
|
||||
}
|
||||
|
||||
qint64 CircularBuffer::bytesAvailable() const {
|
||||
return _size - _offset;
|
||||
}
|
||||
|
||||
bool CircularBuffer::canReadLine() const {
|
||||
for (int offset = _offset; offset < _size; offset++) {
|
||||
if (_data.at((_position + offset) % _data.size()) == '\n') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CircularBuffer::open(OpenMode flags) {
|
||||
return QIODevice::open(flags | QIODevice::Unbuffered);
|
||||
}
|
||||
|
||||
qint64 CircularBuffer::pos() const {
|
||||
return _offset;
|
||||
}
|
||||
|
||||
bool CircularBuffer::seek(qint64 pos) {
|
||||
if (pos < 0 || pos > _size) {
|
||||
return false;
|
||||
}
|
||||
_offset = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 CircularBuffer::size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
qint64 CircularBuffer::readData(char* data, qint64 length) {
|
||||
int readable = qMin((int)length, _size - _offset);
|
||||
|
||||
// read in up to two segments
|
||||
int start = (_position + _offset) % _data.size();
|
||||
int firstSegment = qMin((int)length, _data.size() - start);
|
||||
memcpy(data, _data.constData() + start, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
memcpy(data + firstSegment, _data.constData(), secondSegment);
|
||||
}
|
||||
_offset += readable;
|
||||
return readable;
|
||||
}
|
||||
|
||||
qint64 CircularBuffer::writeData(const char* data, qint64 length) {
|
||||
// resize to fit
|
||||
int requiredSize = _offset + length;
|
||||
if (requiredSize > _size) {
|
||||
resize(requiredSize);
|
||||
}
|
||||
|
||||
// write in up to two segments
|
||||
int start = (_position + _offset) % _data.size();
|
||||
int firstSegment = qMin((int)length, _data.size() - start);
|
||||
memcpy(_data.data() + start, data, firstSegment);
|
||||
int secondSegment = length - firstSegment;
|
||||
if (secondSegment > 0) {
|
||||
memcpy(_data.data(), data + firstSegment, secondSegment);
|
||||
}
|
||||
_offset += length;
|
||||
return length;
|
||||
}
|
||||
|
||||
void CircularBuffer::resize(int size) {
|
||||
if (size > _data.size()) {
|
||||
// double our capacity until we can fit the desired length
|
||||
int newCapacity = _data.size();
|
||||
do {
|
||||
newCapacity *= 2;
|
||||
} while (size > newCapacity);
|
||||
|
||||
int oldCapacity = _data.size();
|
||||
_data.resize(newCapacity);
|
||||
|
||||
int trailing = _position + _size - oldCapacity;
|
||||
if (trailing > 0) {
|
||||
memcpy(_data.data() + oldCapacity, _data.constData(), trailing);
|
||||
}
|
||||
}
|
||||
_size = size;
|
||||
}
|
||||
|
||||
SpanList::SpanList() : _totalSet(0) {
|
||||
}
|
||||
|
||||
int SpanList::set(int offset, int length) {
|
||||
// if we intersect the front of the list, consume beginning spans and return advancement
|
||||
if (offset <= 0) {
|
||||
int intersection = offset + length;
|
||||
return (intersection > 0) ? setSpans(_spans.begin(), intersection) : 0;
|
||||
}
|
||||
|
||||
// look for an intersection within the list
|
||||
int position = 0;
|
||||
for (int i = 0; i < _spans.size(); i++) {
|
||||
QList<Span>::iterator it = _spans.begin() + i;
|
||||
|
||||
// if we intersect the unset portion, contract it
|
||||
position += it->unset;
|
||||
if (offset <= position) {
|
||||
int remove = position - offset;
|
||||
it->unset -= remove;
|
||||
|
||||
// if we continue into the set portion, expand it and consume following spans
|
||||
int extra = offset + length - position;
|
||||
if (extra >= 0) {
|
||||
extra -= it->set;
|
||||
it->set += remove;
|
||||
_totalSet += remove;
|
||||
if (extra > 0) {
|
||||
int amount = setSpans(it + 1, extra);
|
||||
_spans[i].set += amount;
|
||||
_totalSet += amount;
|
||||
}
|
||||
// otherwise, insert a new span
|
||||
} else {
|
||||
Span span = { it->unset, length };
|
||||
it->unset = -extra;
|
||||
_spans.insert(it, span);
|
||||
_totalSet += length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if we intersect the set portion, expand it and consume following spans
|
||||
position += it->set;
|
||||
if (offset <= position) {
|
||||
int extra = offset + length - position;
|
||||
if (extra > 0) {
|
||||
int amount = setSpans(it + 1, extra);
|
||||
_spans[i].set += amount;
|
||||
_totalSet += amount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// add to end of list
|
||||
Span span = { offset - position, length };
|
||||
_spans.append(span);
|
||||
_totalSet += length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpanList::setSpans(QList<Span>::iterator it, int length) {
|
||||
int remainingLength = length;
|
||||
int totalRemoved = 0;
|
||||
for (; it != _spans.end(); it = _spans.erase(it)) {
|
||||
if (remainingLength < it->unset) {
|
||||
it->unset -= remainingLength;
|
||||
totalRemoved += remainingLength;
|
||||
break;
|
||||
}
|
||||
int combined = it->unset + it->set;
|
||||
remainingLength = qMax(remainingLength - combined, 0);
|
||||
totalRemoved += combined;
|
||||
_totalSet -= it->set;
|
||||
}
|
||||
return qMax(length, totalRemoved);
|
||||
}
|
||||
|
||||
int ReliableChannel::getBytesAvailable() const {
|
||||
return _buffer.size() - _acknowledged.getTotalSet();
|
||||
}
|
||||
|
||||
void ReliableChannel::startMessage() {
|
||||
// write a placeholder for the length; we'll fill it in when we know what it is
|
||||
_messageLengthPlaceholder = _buffer.pos();
|
||||
_dataStream << (quint32)0;
|
||||
}
|
||||
|
||||
void ReliableChannel::endMessage() {
|
||||
_bitstream.flush();
|
||||
_bitstream.persistAndResetWriteMappings();
|
||||
|
||||
quint32 length = _buffer.pos() - _messageLengthPlaceholder;
|
||||
_buffer.writeBytes(_messageLengthPlaceholder, sizeof(quint32), (const char*)&length);
|
||||
|
||||
pruneOutgoingMessageStats();
|
||||
_outgoingMessageStats.append(OffsetSizePair(getBytesWritten(), length));
|
||||
}
|
||||
|
||||
void ReliableChannel::sendMessage(const QVariant& message) {
|
||||
startMessage();
|
||||
_bitstream << message;
|
||||
endMessage();
|
||||
}
|
||||
|
||||
bool ReliableChannel::getMessageSendProgress(int& sent, int& total) {
|
||||
pruneOutgoingMessageStats();
|
||||
if (!_messagesEnabled || _outgoingMessageStats.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const OffsetSizePair& stat = _outgoingMessageStats.first();
|
||||
sent = qMax(0, stat.second - (stat.first - _offset));
|
||||
total = stat.second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReliableChannel::getMessageReceiveProgress(int& received, int& total) const {
|
||||
if (!_messagesEnabled || _buffer.bytesAvailable() < (int)sizeof(quint32)) {
|
||||
return false;
|
||||
}
|
||||
quint32 length;
|
||||
_buffer.readBytes(_buffer.pos(), sizeof(quint32), (char*)&length);
|
||||
total = length;
|
||||
received = _buffer.bytesAvailable();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReliableChannel::sendClearSharedObjectMessage(int id) {
|
||||
ClearSharedObjectMessage message = { id };
|
||||
sendMessage(QVariant::fromValue(message));
|
||||
}
|
||||
|
||||
void ReliableChannel::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
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),
|
||||
_output(output),
|
||||
_dataStream(&_buffer),
|
||||
_bitstream(_dataStream, Bitstream::NO_METADATA, Bitstream::NO_GENERICS, this),
|
||||
_priority(1.0f),
|
||||
_offset(0),
|
||||
_writePosition(0),
|
||||
_writePositionResetPacketNumber(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&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
|
||||
sequencer->connect(this, SIGNAL(destroyed(QObject*)), SLOT(clearReliableChannel(QObject*)));
|
||||
}
|
||||
|
||||
void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
|
||||
if (bytes == 0) {
|
||||
out << (quint32)0;
|
||||
return;
|
||||
}
|
||||
_writePosition %= _buffer.pos();
|
||||
while (bytes > 0) {
|
||||
int position = 0;
|
||||
for (int i = 0; i < _acknowledged.getSpans().size(); i++) {
|
||||
const SpanList::Span& span = _acknowledged.getSpans().at(i);
|
||||
position += span.unset;
|
||||
if (_writePosition < position) {
|
||||
int start = qMax(position - span.unset, _writePosition);
|
||||
int length = qMin(bytes, position - start);
|
||||
writeSpan(out, start, length, spans);
|
||||
writeFullSpans(out, bytes - length, i + 1, position + span.set, spans);
|
||||
out << (quint32)0;
|
||||
return;
|
||||
}
|
||||
position += span.set;
|
||||
}
|
||||
int leftover = _buffer.pos() - position;
|
||||
position = _buffer.pos();
|
||||
|
||||
if (_writePosition < position && leftover > 0) {
|
||||
int start = qMax(position - leftover, _writePosition);
|
||||
int length = qMin(bytes, position - start);
|
||||
writeSpan(out, start, length, spans);
|
||||
writeFullSpans(out, bytes - length, 0, 0, spans);
|
||||
out << (quint32)0;
|
||||
return;
|
||||
}
|
||||
_writePosition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ReliableChannel::writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
|
||||
QVector<DatagramSequencer::ChannelSpan>& spans) {
|
||||
int expandedSize = _acknowledged.getSpans().size() + 1;
|
||||
for (int i = 0; i < expandedSize; i++) {
|
||||
if (bytes == 0) {
|
||||
return;
|
||||
}
|
||||
int index = (startingIndex + i) % expandedSize;
|
||||
if (index == _acknowledged.getSpans().size()) {
|
||||
int leftover = _buffer.pos() - position;
|
||||
if (leftover > 0) {
|
||||
int length = qMin(leftover, bytes);
|
||||
writeSpan(out, position, length, spans);
|
||||
bytes -= length;
|
||||
}
|
||||
position = 0;
|
||||
|
||||
} else {
|
||||
const SpanList::Span& span = _acknowledged.getSpans().at(index);
|
||||
int length = qMin(span.unset, bytes);
|
||||
writeSpan(out, position, length, spans);
|
||||
bytes -= length;
|
||||
position += (span.unset + span.set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ReliableChannel::writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
|
||||
DatagramSequencer::ChannelSpan span = { _index, _offset + position, length };
|
||||
spans.append(span);
|
||||
out << (quint32)length;
|
||||
out << (quint32)span.offset;
|
||||
_buffer.writeToStream(position, length, out);
|
||||
_writePosition = position + length;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& span) {
|
||||
int advancement = _acknowledged.set(span.offset - _offset, span.length);
|
||||
if (advancement > 0) {
|
||||
_buffer.remove(advancement);
|
||||
_buffer.seek(_buffer.size());
|
||||
|
||||
_offset += advancement;
|
||||
_writePosition = qMax(_writePosition - advancement, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ReliableChannel::spanLost(int packetNumber, int nextOutgoingPacketNumber) {
|
||||
// reset the write position up to once each round trip time
|
||||
if (packetNumber >= _writePositionResetPacketNumber) {
|
||||
_writePosition = 0;
|
||||
_writePositionResetPacketNumber = nextOutgoingPacketNumber;
|
||||
}
|
||||
}
|
||||
|
||||
void ReliableChannel::readData(QDataStream& in) {
|
||||
bool readSome = false;
|
||||
forever {
|
||||
quint32 size;
|
||||
in >> size;
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
quint32 offset;
|
||||
in >> offset;
|
||||
|
||||
int position = offset - _offset;
|
||||
int end = position + size;
|
||||
if (end <= 0) {
|
||||
in.skipRawData(size);
|
||||
|
||||
} else if (position < 0) {
|
||||
in.skipRawData(-position);
|
||||
_assemblyBuffer.readFromStream(0, end, in);
|
||||
|
||||
} else {
|
||||
_assemblyBuffer.readFromStream(position, size, in);
|
||||
}
|
||||
int advancement = _acknowledged.set(position, size);
|
||||
if (advancement > 0) {
|
||||
_assemblyBuffer.appendToBuffer(0, advancement, _buffer);
|
||||
_assemblyBuffer.remove(advancement);
|
||||
_offset += advancement;
|
||||
readSome = true;
|
||||
}
|
||||
}
|
||||
if (!readSome) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
emit receivedMessage(message, _bitstream);
|
||||
_bitstream.reset();
|
||||
_bitstream.persistAndResetReadMappings();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// otherwise, just let whoever's listening know that data is available
|
||||
} else {
|
||||
emit _buffer.readyRead();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// prune any read data from the buffer
|
||||
if (_buffer.pos() > 0) {
|
||||
_buffer.remove((int)_buffer.pos());
|
||||
_buffer.seek(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ReliableChannel::pruneOutgoingMessageStats() {
|
||||
while (!_outgoingMessageStats.isEmpty() && _offset >= _outgoingMessageStats.first().first) {
|
||||
_outgoingMessageStats.removeFirst();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,445 +0,0 @@
|
|||
//
|
||||
// DatagramSequencer.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/20/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DatagramSequencer_h
|
||||
#define hifi_DatagramSequencer_h
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
|
||||
class ReliableChannel;
|
||||
|
||||
/// Performs datagram sequencing, packet fragmentation and reassembly. Works with Bitstream to provide methods to send and
|
||||
/// receive data over UDP with varying reliability and latency characteristics. To use, create a DatagramSequencer with the
|
||||
/// fixed-size header that will be included with all outgoing datagrams and expected in all incoming ones (the contents of the
|
||||
/// header are not checked on receive, only skipped over, and may be modified by the party that actually send the
|
||||
/// datagram--this means that the header may include dynamically generated data, as long as its size remains fixed). Connect
|
||||
/// the readyToWrite signal to a slot that will actually transmit the datagram to the remote party. When a datagram is
|
||||
/// received from that party, call receivedDatagram with its contents.
|
||||
///
|
||||
/// A "packet" represents a batch of data sent at one time (split into one or more datagrams sized below the MTU). Packets are
|
||||
/// received in full and in order or not at all (that is, a packet being assembled is dropped as soon as a fragment from the
|
||||
/// next packet is received). Packets can be any size, but the larger a packet is, the more likely it is to be dropped--so,
|
||||
/// it's better to keep packet sizes close to the MTU. To write a packet, call startPacket, write data to the returned
|
||||
/// Bitstream, then call endPacket (which will result in one or more firings of readyToWrite). Data written in this way is not
|
||||
/// guaranteed to be received, but if it is received, it will arrive in order. This is a good way to transmit delta state:
|
||||
/// state that represents the change between the last acknowledged state and the current state (which, if not received, will
|
||||
/// not be resent as-is; instead, it will be replaced by up-to-date new deltas).
|
||||
///
|
||||
/// There are two methods for sending reliable data. The first, for small messages that require minimum-latency processing, is
|
||||
/// the high priority messaging system. When you call sendHighPriorityMessage, the message that you send will be included with
|
||||
/// every outgoing packet until it is acknowledged. When the receiving party first sees the message, it will fire a
|
||||
/// receivedHighPriorityMessage signal.
|
||||
///
|
||||
/// The second method employs a set of independent reliable channels multiplexed onto the packet stream. These channels are
|
||||
/// created lazily through the getReliableOutputChannel/getReliableInputChannel functions. Output channels contain buffers
|
||||
/// to which one may write either arbitrary data (as a QIODevice) or messages (as QVariants), or switch between the two.
|
||||
/// Each time a packet is sent, data pending for reliable output channels is added, in proportion to their relative priorities,
|
||||
/// until the packet size limit set by setMaxPacketSize is reached. On the receive side, the streams are reconstructed and
|
||||
/// (again, depending on whether messages are enabled) either the QIODevice reports that data is available, or, when a complete
|
||||
/// message is decoded, the receivedMessage signal is fired.
|
||||
class DatagramSequencer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Contains the content of a high-priority message along with the number of the first packet in which it was sent.
|
||||
class HighPriorityMessage {
|
||||
public:
|
||||
QVariant data;
|
||||
int firstPacketNumber;
|
||||
};
|
||||
|
||||
/// Creates a new datagram sequencer.
|
||||
/// \param datagramHeader the content of the header that will be prepended to each outgoing datagram and whose length
|
||||
/// will be skipped over in each incoming datagram
|
||||
DatagramSequencer(const QByteArray& datagramHeader = QByteArray(), QObject* parent = NULL);
|
||||
|
||||
/// Returns a reference to the weak hash mapping remote ids to shared objects.
|
||||
const WeakSharedObjectHash& getWeakSharedObjectHash() const { return _inputStream.getWeakSharedObjectHash(); }
|
||||
|
||||
/// Returns the packet number of the last packet sent.
|
||||
int getOutgoingPacketNumber() const { return _outgoingPacketNumber; }
|
||||
|
||||
/// Returns the packet number of the last packet received (or the packet currently being assembled).
|
||||
int getIncomingPacketNumber() const { return _incomingPacketNumber; }
|
||||
|
||||
/// Returns a reference to the stream used to read packets.
|
||||
Bitstream& getInputStream() { return _inputStream; }
|
||||
|
||||
/// Returns a reference to the stream used to write packets.
|
||||
Bitstream& getOutputStream() { return _outputStream; }
|
||||
|
||||
/// Returns a reference to the outgoing packet data.
|
||||
const QByteArray& getOutgoingPacketData() const { return _outgoingPacketData; }
|
||||
|
||||
/// Returns the packet number of the sent packet at the specified index.
|
||||
int getSentPacketNumber(int index) const { return _sendRecords.at(index).packetNumber; }
|
||||
|
||||
/// Adds a message to the high priority queue. Will be sent with every outgoing packet until received.
|
||||
void sendHighPriorityMessage(const QVariant& data);
|
||||
|
||||
/// Returns a reference to the list of high priority messages not yet acknowledged.
|
||||
const QList<HighPriorityMessage>& getHighPriorityMessages() const { return _highPriorityMessages; }
|
||||
|
||||
/// Sets the maximum packet size. This is a soft limit that determines how much
|
||||
/// reliable data we include with each transmission.
|
||||
void setMaxPacketSize(int maxPacketSize) { _maxPacketSize = maxPacketSize; }
|
||||
|
||||
int getMaxPacketSize() const { return _maxPacketSize; }
|
||||
|
||||
/// Returns the output channel at the specified index, creating it if necessary.
|
||||
ReliableChannel* getReliableOutputChannel(int index = 0);
|
||||
|
||||
/// Returns the intput channel at the specified index, creating it if necessary.
|
||||
ReliableChannel* getReliableInputChannel(int index = 0);
|
||||
|
||||
/// Returns a reference to the stored receive mappings at the specified index.
|
||||
const Bitstream::ReadMappings& getReadMappings(int index) const { return _receiveRecords.at(index).mappings; }
|
||||
|
||||
/// Adds stats for all reliable channels to the referenced variables.
|
||||
void addReliableChannelStats(int& sendProgress, int& sendTotal, int& receiveProgress, int& receiveTotal) const;
|
||||
|
||||
/// Notes that we're sending a group of packets.
|
||||
/// \param desiredPackets the number of packets we'd like to write in the group
|
||||
/// \return the number of packets to write in the group
|
||||
int notePacketGroup(int desiredPackets = 1);
|
||||
|
||||
/// Starts a new packet for transmission.
|
||||
/// \return a reference to the Bitstream to use for writing to the packet
|
||||
Bitstream& startPacket();
|
||||
|
||||
/// Sends the packet currently being written.
|
||||
void endPacket();
|
||||
|
||||
/// Cancels the packet currently being written.
|
||||
void cancelPacket();
|
||||
|
||||
/// Processes a datagram received from the other party, emitting readyToRead when the entire packet
|
||||
/// has been successfully assembled.
|
||||
Q_INVOKABLE void receivedDatagram(const QByteArray& datagram);
|
||||
|
||||
signals:
|
||||
|
||||
/// Emitted when a datagram is ready to be transmitted.
|
||||
void readyToWrite(const QByteArray& datagram);
|
||||
|
||||
/// Emitted when a packet is available to read.
|
||||
void readyToRead(Bitstream& input);
|
||||
|
||||
/// Emitted when we've received a high-priority message.
|
||||
void receivedHighPriorityMessage(const QVariant& data);
|
||||
|
||||
/// Emitted when we've recorded the transmission of a packet.
|
||||
void sendRecorded();
|
||||
|
||||
/// Emitted when we've recorded the receipt of a packet (that is, at the end of packet processing).
|
||||
void receiveRecorded();
|
||||
|
||||
/// Emitted when a sent packet has been acknowledged by the remote side.
|
||||
/// \param index the index of the packet in our list of send records
|
||||
void sendAcknowledged(int index);
|
||||
|
||||
/// Emitted when our acknowledgement of a received packet has been acknowledged by the remote side.
|
||||
/// \param index the index of the packet in our list of receive records
|
||||
void receiveAcknowledged(int index);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
void handleHighPriorityMessage(const QVariant& data);
|
||||
void clearReliableChannel(QObject* object);
|
||||
|
||||
private:
|
||||
|
||||
friend class ReliableChannel;
|
||||
|
||||
class ChannelSpan {
|
||||
public:
|
||||
int channel;
|
||||
int offset;
|
||||
int length;
|
||||
};
|
||||
|
||||
class SendRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
int lastReceivedPacketNumber;
|
||||
Bitstream::WriteMappings mappings;
|
||||
QVector<ChannelSpan> spans;
|
||||
};
|
||||
|
||||
class ReceiveRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
Bitstream::ReadMappings mappings;
|
||||
int newHighPriorityMessages;
|
||||
|
||||
bool operator<(const ReceiveRecord& other) const { return packetNumber < other.packetNumber; }
|
||||
};
|
||||
|
||||
/// Notes that the described send was acknowledged by the other party.
|
||||
void sendRecordAcknowledged(const SendRecord& record);
|
||||
|
||||
/// Notes that the described send was lost in transit.
|
||||
void sendRecordLost(const SendRecord& record);
|
||||
|
||||
/// Appends some reliable data to the outgoing packet.
|
||||
void appendReliableData(int bytes, QVector<ChannelSpan>& spans);
|
||||
|
||||
/// Sends a packet to the other party, fragmenting it into multiple datagrams (and emitting
|
||||
/// readyToWrite) as necessary.
|
||||
void sendPacket(const QByteArray& packet, const QVector<ChannelSpan>& spans);
|
||||
|
||||
QList<SendRecord> _sendRecords;
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
|
||||
QByteArray _outgoingPacketData;
|
||||
QDataStream _outgoingPacketStream;
|
||||
Bitstream _outputStream;
|
||||
|
||||
QBuffer _incomingDatagramBuffer;
|
||||
QDataStream _incomingDatagramStream;
|
||||
int _datagramHeaderSize;
|
||||
|
||||
int _outgoingPacketNumber;
|
||||
QByteArray _outgoingDatagram;
|
||||
QBuffer _outgoingDatagramBuffer;
|
||||
QDataStream _outgoingDatagramStream;
|
||||
|
||||
int _incomingPacketNumber;
|
||||
QByteArray _incomingPacketData;
|
||||
QDataStream _incomingPacketStream;
|
||||
Bitstream _inputStream;
|
||||
QSet<int> _offsetsReceived;
|
||||
int _remainingBytes;
|
||||
|
||||
QList<HighPriorityMessage> _highPriorityMessages;
|
||||
int _receivedHighPriorityMessages;
|
||||
|
||||
int _maxPacketSize;
|
||||
|
||||
float _packetsPerGroup;
|
||||
float _packetsToWrite;
|
||||
float _slowStartThreshold;
|
||||
int _packetRateIncreasePacketNumber;
|
||||
int _packetRateDecreasePacketNumber;
|
||||
int _packetDropCount;
|
||||
int _lastPacketDropped;
|
||||
|
||||
QHash<int, ReliableChannel*> _reliableOutputChannels;
|
||||
QHash<int, ReliableChannel*> _reliableInputChannels;
|
||||
};
|
||||
|
||||
/// A circular buffer, where one may efficiently append data to the end or remove data from the beginning.
|
||||
class CircularBuffer : public QIODevice {
|
||||
public:
|
||||
|
||||
CircularBuffer(QObject* parent = NULL);
|
||||
|
||||
/// Appends data to the end of the buffer.
|
||||
void append(const QByteArray& data) { append(data.constData(), data.size()); }
|
||||
|
||||
/// Appends data to the end of the buffer.
|
||||
void append(const char* data, int length);
|
||||
|
||||
/// Removes data from the beginning of the buffer.
|
||||
void remove(int length);
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Reads part of the buffer from the supplied stream.
|
||||
void readFromStream(int offset, int length, QDataStream& in);
|
||||
|
||||
/// Appends part of the buffer to the supplied other buffer.
|
||||
void appendToBuffer(int offset, int length, CircularBuffer& buffer) const;
|
||||
|
||||
virtual bool atEnd() const;
|
||||
virtual qint64 bytesAvailable() const;
|
||||
virtual bool canReadLine() const;
|
||||
virtual bool open(OpenMode flags);
|
||||
virtual qint64 pos() const;
|
||||
virtual bool seek(qint64 pos);
|
||||
virtual qint64 size() const;
|
||||
|
||||
protected:
|
||||
|
||||
virtual qint64 readData(char* data, qint64 length);
|
||||
virtual qint64 writeData(const char* data, qint64 length);
|
||||
|
||||
private:
|
||||
|
||||
void resize(int size);
|
||||
|
||||
QByteArray _data;
|
||||
int _position;
|
||||
int _size;
|
||||
int _offset;
|
||||
};
|
||||
|
||||
/// A list of contiguous spans, alternating between set and unset. Conceptually, the list is preceeded by a set
|
||||
/// span of infinite length and followed by an unset span of infinite length. Within those bounds, it alternates
|
||||
/// between unset and set.
|
||||
class SpanList {
|
||||
public:
|
||||
|
||||
class Span {
|
||||
public:
|
||||
int unset;
|
||||
int set;
|
||||
};
|
||||
|
||||
SpanList();
|
||||
|
||||
const QList<Span>& getSpans() const { return _spans; }
|
||||
|
||||
/// Returns the total length set.
|
||||
int getTotalSet() const { return _totalSet; }
|
||||
|
||||
/// Sets a region of the list.
|
||||
/// \return the advancement of the set length at the beginning of the list
|
||||
int set(int offset, int length);
|
||||
|
||||
private:
|
||||
|
||||
/// Sets the spans starting at the specified iterator, consuming at least the given length.
|
||||
/// \return the actual amount set, which may be greater if we ran into an existing set span
|
||||
int setSpans(QList<Span>::iterator it, int length);
|
||||
|
||||
QList<Span> _spans;
|
||||
int _totalSet;
|
||||
};
|
||||
|
||||
/// Represents a single reliable channel multiplexed onto the datagram sequence.
|
||||
class ReliableChannel : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Returns the channel's index in the sequencer's channel map.
|
||||
int getIndex() const { return _index; }
|
||||
|
||||
/// Checks whether this is an output channel.
|
||||
bool isOutput() const { return _output; }
|
||||
|
||||
/// Returns a reference to the buffer used to write/read data to/from this channel.
|
||||
CircularBuffer& getBuffer() { return _buffer; }
|
||||
|
||||
/// Returns a reference to the data stream created on this channel's buffer.
|
||||
QDataStream& getDataStream() { return _dataStream; }
|
||||
|
||||
/// Returns a reference to the bitstream created on this channel's data stream.
|
||||
Bitstream& getBitstream() { return _bitstream; }
|
||||
|
||||
/// Sets the channel priority, which determines how much of this channel's data (in proportion to the other channels) to
|
||||
/// include in each outgoing packet.
|
||||
void setPriority(float priority) { _priority = priority; }
|
||||
float getPriority() const { return _priority; }
|
||||
|
||||
/// Returns the number of bytes available to read from this channel.
|
||||
int getBytesAvailable() const;
|
||||
|
||||
/// Returns the offset, which represents the total number of bytes acknowledged
|
||||
/// (on the write end) or received completely (on the read end).
|
||||
int getOffset() const { return _offset; }
|
||||
|
||||
/// Returns the total number of bytes written to this channel.
|
||||
int getBytesWritten() const { return _offset + _buffer.pos(); }
|
||||
|
||||
/// Sets whether we expect to write/read framed messages.
|
||||
void setMessagesEnabled(bool enabled) { _messagesEnabled = enabled; }
|
||||
bool getMessagesEnabled() const { return _messagesEnabled; }
|
||||
|
||||
/// Starts a framed message on this channel.
|
||||
void startMessage();
|
||||
|
||||
/// Ends a framed message on this channel.
|
||||
void endMessage();
|
||||
|
||||
/// Sends a framed message on this channel (convenience function that calls startMessage,
|
||||
/// writes the message to the bitstream, then calls endMessage).
|
||||
void sendMessage(const QVariant& message);
|
||||
|
||||
/// Determines the number of bytes uploaded towards the currently pending message.
|
||||
/// \return true if there is a message pending, in which case the sent and total arguments will be set
|
||||
bool getMessageSendProgress(int& sent, int& total);
|
||||
|
||||
/// Determines the number of bytes downloaded towards the currently pending message.
|
||||
/// \return true if there is a message pending, in which case the received and total arguments will be set
|
||||
bool getMessageReceiveProgress(int& received, int& total) const;
|
||||
|
||||
signals:
|
||||
|
||||
/// Fired when a framed message has been received on this channel.
|
||||
void receivedMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendClearSharedObjectMessage(int id);
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
private:
|
||||
|
||||
friend class DatagramSequencer;
|
||||
|
||||
ReliableChannel(DatagramSequencer* sequencer, int index, bool output);
|
||||
|
||||
void writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans);
|
||||
void writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
|
||||
QVector<DatagramSequencer::ChannelSpan>& spans);
|
||||
int writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
|
||||
|
||||
void spanAcknowledged(const DatagramSequencer::ChannelSpan& span);
|
||||
void spanLost(int packetNumber, int nextOutgoingPacketNumber);
|
||||
|
||||
void readData(QDataStream& in);
|
||||
|
||||
void pruneOutgoingMessageStats();
|
||||
|
||||
int _index;
|
||||
bool _output;
|
||||
CircularBuffer _buffer;
|
||||
CircularBuffer _assemblyBuffer;
|
||||
QDataStream _dataStream;
|
||||
Bitstream _bitstream;
|
||||
float _priority;
|
||||
|
||||
int _offset;
|
||||
int _writePosition;
|
||||
int _writePositionResetPacketNumber;
|
||||
SpanList _acknowledged;
|
||||
bool _messagesEnabled;
|
||||
int _messageLengthPlaceholder; ///< the location in the buffer of the message length for the current message
|
||||
|
||||
typedef QPair<int, int> OffsetSizePair;
|
||||
QVector<OffsetSizePair> _outgoingMessageStats;
|
||||
|
||||
int _messageReceivedOffset; ///< when reached, indicates that the most recent sent message has been received
|
||||
int _messageSize; ///< the size of the most recent sent message; only valid when _messageReceivedOffset has been set
|
||||
};
|
||||
|
||||
#endif // hifi_DatagramSequencer_h
|
|
@ -1,117 +0,0 @@
|
|||
//
|
||||
// Endpoint.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/26/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "Endpoint.h"
|
||||
|
||||
Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord, PacketRecord* baselineReceiveRecord) :
|
||||
_node(node),
|
||||
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData), this) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
|
||||
connect(&_sequencer, SIGNAL(sendRecorded()), SLOT(recordSend()));
|
||||
connect(&_sequencer, SIGNAL(receiveRecorded()), SLOT(recordReceive()));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
|
||||
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
|
||||
|
||||
// insert the baseline send and receive records
|
||||
_sendRecords.append(baselineSendRecord);
|
||||
_receiveRecords.append(baselineReceiveRecord);
|
||||
}
|
||||
|
||||
Endpoint::~Endpoint() {
|
||||
foreach (PacketRecord* record, _sendRecords) {
|
||||
delete record;
|
||||
}
|
||||
foreach (PacketRecord* record, _receiveRecords) {
|
||||
delete record;
|
||||
}
|
||||
}
|
||||
|
||||
void Endpoint::update() {
|
||||
int packetsToSend = _sequencer.notePacketGroup();
|
||||
for (int i = 0; i < packetsToSend; i++) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
writeUpdateMessage(out);
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
}
|
||||
|
||||
int Endpoint::parseData(const QByteArray& packet) {
|
||||
// process through sequencer
|
||||
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
void Endpoint::sendDatagram(const QByteArray& data) {
|
||||
DependencyManager::get<NodeList>()->writeDatagram(data, _node);
|
||||
}
|
||||
|
||||
void Endpoint::readMessage(Bitstream& in) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
}
|
||||
|
||||
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
if (message.userType() == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Endpoint::recordSend() {
|
||||
_sendRecords.append(maybeCreateSendRecord());
|
||||
}
|
||||
|
||||
void Endpoint::recordReceive() {
|
||||
_receiveRecords.append(maybeCreateReceiveRecord());
|
||||
}
|
||||
|
||||
void Endpoint::clearSendRecordsBefore(int index) {
|
||||
QList<PacketRecord*>::iterator end = _sendRecords.begin() + index + 1;
|
||||
for (QList<PacketRecord*>::const_iterator it = _sendRecords.begin(); it != end; it++) {
|
||||
delete *it;
|
||||
}
|
||||
_sendRecords.erase(_sendRecords.begin(), end);
|
||||
}
|
||||
|
||||
void Endpoint::clearReceiveRecordsBefore(int index) {
|
||||
QList<PacketRecord*>::iterator end = _receiveRecords.begin() + index + 1;
|
||||
for (QList<PacketRecord*>::const_iterator it = _receiveRecords.begin(); it != end; it++) {
|
||||
delete *it;
|
||||
}
|
||||
_receiveRecords.erase(_receiveRecords.begin(), end);
|
||||
}
|
||||
|
||||
void Endpoint::writeUpdateMessage(Bitstream& out) {
|
||||
out << QVariant();
|
||||
}
|
||||
|
||||
PacketRecord* Endpoint::maybeCreateSendRecord() const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PacketRecord* Endpoint::maybeCreateReceiveRecord() const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PacketRecord::PacketRecord(int packetNumber, const MetavoxelLOD& lod, const MetavoxelData& data) :
|
||||
_packetNumber(packetNumber),
|
||||
_lod(lod),
|
||||
_data(data) {
|
||||
}
|
||||
|
||||
PacketRecord::~PacketRecord() {
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// Endpoint.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/26/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Endpoint_h
|
||||
#define hifi_Endpoint_h
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
#include "DatagramSequencer.h"
|
||||
#include "MetavoxelData.h"
|
||||
#include "Spanner.h"
|
||||
|
||||
class PacketRecord;
|
||||
|
||||
/// Base class for communication endpoints: clients and server sessions.
|
||||
class Endpoint : public NodeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// The index of the input/output channel used to transmit reliable deltas.
|
||||
static const int RELIABLE_DELTA_CHANNEL_INDEX = 1;
|
||||
|
||||
Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL,
|
||||
PacketRecord* baselineReceiveRecord = NULL);
|
||||
virtual ~Endpoint();
|
||||
|
||||
DatagramSequencer& getSequencer() { return _sequencer; }
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void sendDatagram(const QByteArray& data);
|
||||
virtual void readMessage(Bitstream& in);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
void recordSend();
|
||||
virtual void recordReceive();
|
||||
|
||||
virtual void clearSendRecordsBefore(int index);
|
||||
virtual void clearReceiveRecordsBefore(int index);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
virtual PacketRecord* maybeCreateReceiveRecord() const;
|
||||
|
||||
PacketRecord* getLastAcknowledgedSendRecord() const { return _sendRecords.first(); }
|
||||
PacketRecord* getLastAcknowledgedReceiveRecord() const { return _receiveRecords.first(); }
|
||||
|
||||
SharedNodePointer _node;
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
QList<PacketRecord*> _sendRecords;
|
||||
QList<PacketRecord*> _receiveRecords;
|
||||
};
|
||||
|
||||
/// Base class for packet records.
|
||||
class PacketRecord {
|
||||
public:
|
||||
|
||||
PacketRecord(int packetNumber = 0, const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData());
|
||||
virtual ~PacketRecord();
|
||||
|
||||
int getPacketNumber() const { return _packetNumber; }
|
||||
const MetavoxelLOD& getLOD() const { return _lod; }
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
private:
|
||||
|
||||
int _packetNumber;
|
||||
MetavoxelLOD _lod;
|
||||
MetavoxelData _data;
|
||||
};
|
||||
|
||||
#endif // hifi_Endpoint_h
|
|
@ -1,446 +0,0 @@
|
|||
//
|
||||
// MetavoxelClientManager.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/26/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QReadLocker>
|
||||
#include <QThread>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "MetavoxelClientManager.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
||||
MetavoxelClientManager::MetavoxelClientManager() :
|
||||
_updater(new MetavoxelUpdater(this)) {
|
||||
QThread* thread = new QThread(this);
|
||||
_updater->moveToThread(thread);
|
||||
connect(thread, &QThread::finished, _updater, &QObject::deleteLater);
|
||||
thread->start();
|
||||
QMetaObject::invokeMethod(_updater, "start");
|
||||
}
|
||||
|
||||
MetavoxelClientManager::~MetavoxelClientManager() {
|
||||
if (_updater) {
|
||||
_updater->thread()->quit();
|
||||
_updater->thread()->wait();
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::init() {
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeAdded,
|
||||
this, &MetavoxelClientManager::maybeAttachClient);
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled,
|
||||
this, &MetavoxelClientManager::maybeDeleteClient);
|
||||
}
|
||||
|
||||
SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(const glm::vec3& origin,
|
||||
const glm::vec3& direction, const AttributePointer& attribute, float& distance) {
|
||||
SharedObjectPointer closestSpanner;
|
||||
float closestDistance = FLT_MAX;
|
||||
|
||||
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& node){
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
float clientDistance;
|
||||
SharedObjectPointer clientSpanner = client->getDataCopy().findFirstRaySpannerIntersection(
|
||||
origin, direction, attribute, clientDistance
|
||||
);
|
||||
if (clientSpanner && clientDistance < closestDistance) {
|
||||
closestSpanner = clientSpanner;
|
||||
closestDistance = clientDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (closestSpanner) {
|
||||
distance = closestDistance;
|
||||
}
|
||||
return closestSpanner;
|
||||
}
|
||||
|
||||
class RayHeightfieldIntersectionVisitor : public RaySpannerIntersectionVisitor {
|
||||
public:
|
||||
|
||||
float intersectionDistance;
|
||||
|
||||
RayHeightfieldIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction, const MetavoxelLOD& lod);
|
||||
|
||||
virtual bool visitSpanner(Spanner* spanner, float distance);
|
||||
};
|
||||
|
||||
RayHeightfieldIntersectionVisitor::RayHeightfieldIntersectionVisitor(const glm::vec3& origin,
|
||||
const glm::vec3& direction, const MetavoxelLOD& lod) :
|
||||
RaySpannerIntersectionVisitor(origin, direction, QVector<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), lod),
|
||||
intersectionDistance(FLT_MAX) {
|
||||
}
|
||||
|
||||
bool RayHeightfieldIntersectionVisitor::visitSpanner(Spanner* spanner, float distance) {
|
||||
if (spanner->isHeightfield()) {
|
||||
intersectionDistance = distance;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetavoxelClientManager::findFirstRayHeightfieldIntersection(const glm::vec3& origin,
|
||||
const glm::vec3& direction, float& distance) {
|
||||
RayHeightfieldIntersectionVisitor visitor(origin, direction, getLOD());
|
||||
guide(visitor);
|
||||
if (visitor.intersectionDistance == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
distance = visitor.intersectionDistance;
|
||||
return true;
|
||||
}
|
||||
|
||||
class HeightfieldHeightVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
float height;
|
||||
|
||||
HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location);
|
||||
|
||||
virtual bool visit(Spanner* spanner);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
glm::vec3 _location;
|
||||
};
|
||||
|
||||
HeightfieldHeightVisitor::HeightfieldHeightVisitor(const MetavoxelLOD& lod, const glm::vec3& location) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), lod),
|
||||
height(-FLT_MAX),
|
||||
_location(location) {
|
||||
}
|
||||
|
||||
bool HeightfieldHeightVisitor::visit(Spanner* spanner) {
|
||||
height = qMax(height, spanner->getHeight(_location));
|
||||
return true;
|
||||
}
|
||||
|
||||
static const int REVERSE_ORDER = MetavoxelVisitor::encodeOrder(7, 6, 5, 4, 3, 2, 1, 0);
|
||||
|
||||
int HeightfieldHeightVisitor::visit(MetavoxelInfo& info) {
|
||||
if (_location.x < info.minimum.x || _location.z < info.minimum.z || _location.x > info.minimum.x + info.size ||
|
||||
_location.z > info.minimum.z + info.size) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
SpannerVisitor::visit(info);
|
||||
return (height == -FLT_MAX) ? (info.isLeaf ? STOP_RECURSION : REVERSE_ORDER) : SHORT_CIRCUIT;
|
||||
}
|
||||
|
||||
float MetavoxelClientManager::getHeightfieldHeight(const glm::vec3& location) {
|
||||
HeightfieldHeightVisitor visitor(getLOD(), location);
|
||||
guide(visitor);
|
||||
return visitor.height;
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::paintHeightfieldHeight(const glm::vec3& position, float radius, float height) {
|
||||
MetavoxelEditMessage edit = { QVariant::fromValue(PaintHeightfieldHeightEdit(position, radius, height)) };
|
||||
applyEdit(edit, true);
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
QMetaObject::invokeMethod(_updater, "applyEdit", Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, reliable));
|
||||
}
|
||||
|
||||
MetavoxelLOD MetavoxelClientManager::getLOD() {
|
||||
return MetavoxelLOD();
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::maybeAttachClient(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = createClient(node);
|
||||
client->moveToThread(_updater->thread());
|
||||
QMetaObject::invokeMethod(_updater, "addClient", Q_ARG(QObject*, client));
|
||||
node->setLinkedData(client);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::maybeDeleteClient(const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
// we assume the node is already locked
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
node->setLinkedData(NULL);
|
||||
client->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelClient* MetavoxelClientManager::createClient(const SharedNodePointer& node) {
|
||||
return new MetavoxelClient(node, _updater);
|
||||
}
|
||||
|
||||
void MetavoxelClientManager::guide(MetavoxelVisitor& visitor) {
|
||||
DependencyManager::get<NodeList>()->eachNode([&visitor](const SharedNodePointer& node){
|
||||
if (node->getType() == NodeType::MetavoxelServer) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
|
||||
if (client) {
|
||||
client->getDataCopy().guide(visitor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MetavoxelUpdater::MetavoxelUpdater(MetavoxelClientManager* clientManager) :
|
||||
_clientManager(clientManager),
|
||||
_sendTimer(this) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, &QTimer::timeout, this, &MetavoxelUpdater::sendUpdates);
|
||||
}
|
||||
|
||||
const int SEND_INTERVAL = 33;
|
||||
|
||||
void MetavoxelUpdater::start() {
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::addClient(QObject* client) {
|
||||
_clients.insert(static_cast<MetavoxelClient*>(client));
|
||||
connect(client, &QObject::destroyed, this, &MetavoxelUpdater::removeClient);
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
// apply to all clients
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->applyEdit(edit, reliable);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::getStats(QObject* receiver, const QByteArray& method) {
|
||||
int internal = 0, leaves = 0;
|
||||
int sendProgress = 0, sendTotal = 0;
|
||||
int receiveProgress = 0, receiveTotal = 0;
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->getData().countNodes(internal, leaves, _lod);
|
||||
client->getSequencer().addReliableChannelStats(sendProgress, sendTotal, receiveProgress, receiveTotal);
|
||||
}
|
||||
QMetaObject::invokeMethod(receiver, method.constData(), Q_ARG(int, internal), Q_ARG(int, leaves), Q_ARG(int, sendProgress),
|
||||
Q_ARG(int, sendTotal), Q_ARG(int, receiveProgress), Q_ARG(int, receiveTotal));
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::sendUpdates() {
|
||||
// get the latest LOD from the client manager
|
||||
_lod = _clientManager->getLOD();
|
||||
|
||||
// send updates for all clients
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->update();
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
int elapsed = now - _lastSend;
|
||||
_lastSend = now;
|
||||
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - qMax(elapsed, SEND_INTERVAL)));
|
||||
}
|
||||
|
||||
void MetavoxelUpdater::removeClient(QObject* client) {
|
||||
_clients.remove(static_cast<MetavoxelClient*>(client));
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelUpdater* updater) :
|
||||
Endpoint(node, new PacketRecord(), new PacketRecord()),
|
||||
_updater(updater),
|
||||
_reliableDeltaChannel(NULL),
|
||||
_reliableDeltaID(0),
|
||||
_dummyInputStream(_dummyDataStream),
|
||||
_dummyPacketNumber(0) {
|
||||
|
||||
connect(_sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX),
|
||||
SIGNAL(receivedMessage(const QVariant&, Bitstream&)), SLOT(handleMessage(const QVariant&, Bitstream&)));
|
||||
}
|
||||
|
||||
MetavoxelData MetavoxelClient::getDataCopy() {
|
||||
QReadLocker locker(&_dataCopyLock);
|
||||
return _dataCopy;
|
||||
}
|
||||
|
||||
void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
|
||||
if (reliable) {
|
||||
_sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit));
|
||||
|
||||
} else {
|
||||
// apply immediately to local tree
|
||||
MetavoxelData oldData = _data;
|
||||
edit.apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
if (_data != oldData) {
|
||||
dataChanged(oldData);
|
||||
}
|
||||
|
||||
// start sending it out
|
||||
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
|
||||
}
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::getAcknowledgedSendRecord(int packetNumber) const {
|
||||
PacketRecord* lastAcknowledged = getLastAcknowledgedSendRecord();
|
||||
if (lastAcknowledged->getPacketNumber() == packetNumber) {
|
||||
return lastAcknowledged;
|
||||
}
|
||||
foreach (PacketRecord* record, _clearedSendRecords) {
|
||||
if (record->getPacketNumber() == packetNumber) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::getAcknowledgedReceiveRecord(int packetNumber) const {
|
||||
PacketRecord* lastAcknowledged = getLastAcknowledgedReceiveRecord();
|
||||
if (lastAcknowledged->getPacketNumber() == packetNumber) {
|
||||
return lastAcknowledged;
|
||||
}
|
||||
foreach (const ClearedReceiveRecord& record, _clearedReceiveRecords) {
|
||||
if (record.first->getPacketNumber() == packetNumber) {
|
||||
return record.first;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void MetavoxelClient::dataChanged(const MetavoxelData& oldData) {
|
||||
// make thread-safe copy
|
||||
QWriteLocker locker(&_dataCopyLock);
|
||||
_dataCopy = _data;
|
||||
}
|
||||
|
||||
void MetavoxelClient::recordReceive() {
|
||||
Endpoint::recordReceive();
|
||||
|
||||
// clear the cleared lists
|
||||
foreach (PacketRecord* record, _clearedSendRecords) {
|
||||
delete record;
|
||||
}
|
||||
_clearedSendRecords.clear();
|
||||
|
||||
foreach (const ClearedReceiveRecord& record, _clearedReceiveRecords) {
|
||||
delete record.first;
|
||||
}
|
||||
_clearedReceiveRecords.clear();
|
||||
}
|
||||
|
||||
void MetavoxelClient::clearSendRecordsBefore(int index) {
|
||||
// move to cleared list
|
||||
QList<PacketRecord*>::iterator end = _sendRecords.begin() + index + 1;
|
||||
for (QList<PacketRecord*>::const_iterator it = _sendRecords.begin(); it != end; it++) {
|
||||
_clearedSendRecords.append(*it);
|
||||
}
|
||||
_sendRecords.erase(_sendRecords.begin(), end);
|
||||
}
|
||||
|
||||
void MetavoxelClient::clearReceiveRecordsBefore(int index) {
|
||||
// copy the mappings on first call per packet
|
||||
if (_sequencer.getIncomingPacketNumber() > _dummyPacketNumber) {
|
||||
_dummyPacketNumber = _sequencer.getIncomingPacketNumber();
|
||||
_dummyInputStream.copyPersistentMappings(_sequencer.getInputStream());
|
||||
}
|
||||
|
||||
// move to cleared list
|
||||
QList<PacketRecord*>::iterator end = _receiveRecords.begin() + index + 1;
|
||||
for (QList<PacketRecord*>::const_iterator it = _receiveRecords.begin(); it != end; it++) {
|
||||
_clearedReceiveRecords.append(ClearedReceiveRecord(*it, _sequencer.getReadMappings(index)));
|
||||
}
|
||||
_receiveRecords.erase(_receiveRecords.begin(), end);
|
||||
}
|
||||
|
||||
void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
|
||||
ClientStateMessage state = { _updater->getLOD() };
|
||||
out << QVariant::fromValue(state);
|
||||
}
|
||||
|
||||
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
int userType = message.userType();
|
||||
if (userType == MetavoxelDeltaMessage::Type) {
|
||||
if (_reliableDeltaChannel) {
|
||||
MetavoxelData reference = _remoteData;
|
||||
MetavoxelLOD referenceLOD = _remoteDataLOD;
|
||||
_remoteData.readDelta(reference, referenceLOD, in, _remoteDataLOD = _reliableDeltaLOD);
|
||||
_sequencer.getInputStream().persistReadMappings(in.getAndResetReadMappings());
|
||||
in.clearPersistentMappings();
|
||||
_reliableDeltaChannel = NULL;
|
||||
|
||||
} else {
|
||||
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
|
||||
_remoteData.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in,
|
||||
_remoteDataLOD = getLastAcknowledgedSendRecord()->getLOD());
|
||||
in.reset();
|
||||
}
|
||||
// copy to local and reapply local edits
|
||||
MetavoxelData oldData = _data;
|
||||
_data = _remoteData;
|
||||
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
|
||||
if (message.data.userType() == MetavoxelEditMessage::Type) {
|
||||
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
|
||||
}
|
||||
}
|
||||
if (_data != oldData) {
|
||||
dataChanged(oldData);
|
||||
}
|
||||
} else if (userType == MetavoxelDeltaPendingMessage::Type) {
|
||||
// check the id to make sure this is not a delta we've already processed
|
||||
MetavoxelDeltaPendingMessage pending = message.value<MetavoxelDeltaPendingMessage>();
|
||||
if (pending.id > _reliableDeltaID) {
|
||||
_reliableDeltaID = pending.id;
|
||||
PacketRecord* sendRecord = getAcknowledgedSendRecord(pending.receivedPacketNumber);
|
||||
if (!sendRecord) {
|
||||
qWarning() << "Missing send record for delta" << pending.receivedPacketNumber;
|
||||
return;
|
||||
}
|
||||
_reliableDeltaLOD = sendRecord->getLOD();
|
||||
PacketRecord* receiveRecord = getAcknowledgedReceiveRecord(pending.sentPacketNumber);
|
||||
if (!receiveRecord) {
|
||||
qWarning() << "Missing receive record for delta" << pending.sentPacketNumber;
|
||||
return;
|
||||
}
|
||||
_remoteDataLOD = receiveRecord->getLOD();
|
||||
_remoteData = receiveRecord->getData();
|
||||
|
||||
_reliableDeltaChannel = _sequencer.getReliableInputChannel(RELIABLE_DELTA_CHANNEL_INDEX);
|
||||
if (receiveRecord == getLastAcknowledgedReceiveRecord()) {
|
||||
_reliableDeltaChannel->getBitstream().copyPersistentMappings(_sequencer.getInputStream());
|
||||
|
||||
} else {
|
||||
_reliableDeltaChannel->getBitstream().copyPersistentMappings(_dummyInputStream);
|
||||
foreach (const ClearedReceiveRecord& record, _clearedReceiveRecords) {
|
||||
_reliableDeltaChannel->getBitstream().persistReadMappings(record.second);
|
||||
if (record.first == receiveRecord) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Endpoint::handleMessage(message, in);
|
||||
}
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
|
||||
return new PacketRecord(_sequencer.getOutgoingPacketNumber(),
|
||||
_reliableDeltaChannel ? _reliableDeltaLOD : _updater->getLOD());
|
||||
}
|
||||
|
||||
PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
|
||||
return new PacketRecord(_sequencer.getIncomingPacketNumber(), _remoteDataLOD, _remoteData);
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
//
|
||||
// MetavoxelClientManager.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/26/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelClientManager_h
|
||||
#define hifi_MetavoxelClientManager_h
|
||||
|
||||
#include <QReadWriteLock>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Endpoint.h"
|
||||
|
||||
class MetavoxelClient;
|
||||
class MetavoxelEditMessage;
|
||||
class MetavoxelUpdater;
|
||||
|
||||
/// Manages the set of connected metavoxel clients.
|
||||
class MetavoxelClientManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelClientManager();
|
||||
virtual ~MetavoxelClientManager();
|
||||
|
||||
virtual void init();
|
||||
|
||||
MetavoxelUpdater* getUpdater() const { return _updater; }
|
||||
|
||||
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const AttributePointer& attribute, float& distance);
|
||||
|
||||
bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance);
|
||||
|
||||
Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location);
|
||||
|
||||
Q_INVOKABLE void paintHeightfieldHeight(const glm::vec3& position, float radius, float height);
|
||||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
|
||||
|
||||
/// Returns the current LOD. This must be thread-safe, as it will be called from the updater thread.
|
||||
virtual MetavoxelLOD getLOD();
|
||||
|
||||
private slots:
|
||||
|
||||
void maybeAttachClient(const SharedNodePointer& node);
|
||||
void maybeDeleteClient(const SharedNodePointer& node);
|
||||
|
||||
protected:
|
||||
|
||||
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
|
||||
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
MetavoxelUpdater* _updater;
|
||||
};
|
||||
|
||||
/// Handles updates in a dedicated thread.
|
||||
class MetavoxelUpdater : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelUpdater(MetavoxelClientManager* clientManager);
|
||||
|
||||
const MetavoxelLOD& getLOD() const { return _lod; }
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
Q_INVOKABLE void addClient(QObject* client);
|
||||
|
||||
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable);
|
||||
|
||||
/// Requests a set of statistics. The receiving method should take six integer arguments: internal node count, leaf count,
|
||||
/// send progress, send total, receive progress, receive total.
|
||||
Q_INVOKABLE void getStats(QObject* receiver, const QByteArray& method);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendUpdates();
|
||||
void removeClient(QObject* client);
|
||||
|
||||
private:
|
||||
|
||||
MetavoxelClientManager* _clientManager;
|
||||
QSet<MetavoxelClient*> _clients;
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
MetavoxelLOD _lod;
|
||||
};
|
||||
|
||||
/// Base class for metavoxel clients.
|
||||
class MetavoxelClient : public Endpoint {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelClient(const SharedNodePointer& node, MetavoxelUpdater* updater);
|
||||
|
||||
/// Returns a reference to the most recent data. This function is *not* thread-safe.
|
||||
const MetavoxelData& getData() const { return _data; }
|
||||
|
||||
/// Returns a copy of the most recent data. This function *is* thread-safe.
|
||||
MetavoxelData getDataCopy();
|
||||
|
||||
void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
|
||||
|
||||
protected:
|
||||
|
||||
PacketRecord* getAcknowledgedSendRecord(int packetNumber) const;
|
||||
PacketRecord* getAcknowledgedReceiveRecord(int packetNumber) const;
|
||||
|
||||
virtual void dataChanged(const MetavoxelData& oldData);
|
||||
|
||||
virtual void recordReceive();
|
||||
|
||||
virtual void clearSendRecordsBefore(int index);
|
||||
virtual void clearReceiveRecordsBefore(int index);
|
||||
|
||||
virtual void writeUpdateMessage(Bitstream& out);
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
virtual PacketRecord* maybeCreateReceiveRecord() const;
|
||||
|
||||
MetavoxelUpdater* _updater;
|
||||
MetavoxelData _data;
|
||||
MetavoxelData _remoteData;
|
||||
MetavoxelLOD _remoteDataLOD;
|
||||
|
||||
ReliableChannel* _reliableDeltaChannel;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
int _reliableDeltaID;
|
||||
QVariant _reliableDeltaMessage;
|
||||
|
||||
MetavoxelData _dataCopy;
|
||||
QReadWriteLock _dataCopyLock;
|
||||
|
||||
QDataStream _dummyDataStream;
|
||||
Bitstream _dummyInputStream;
|
||||
int _dummyPacketNumber;
|
||||
QList<PacketRecord*> _clearedSendRecords;
|
||||
|
||||
typedef QPair<PacketRecord*, Bitstream::ReadMappings> ClearedReceiveRecord;
|
||||
QList<ClearedReceiveRecord> _clearedReceiveRecords;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelClientManager_h
|
File diff suppressed because it is too large
Load diff
|
@ -1,539 +0,0 @@
|
|||
//
|
||||
// MetavoxelData.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/6/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelData_h
|
||||
#define hifi_MetavoxelData_h
|
||||
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QSharedData>
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelUtil.h"
|
||||
|
||||
class MetavoxelInfo;
|
||||
class MetavoxelNode;
|
||||
class MetavoxelRendererImplementation;
|
||||
class MetavoxelVisitation;
|
||||
class MetavoxelVisitor;
|
||||
class Spanner;
|
||||
|
||||
/// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a
|
||||
/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided.
|
||||
class MetavoxelLOD {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
STREAM glm::vec3 position;
|
||||
STREAM float threshold;
|
||||
|
||||
MetavoxelLOD(const glm::vec3& position = glm::vec3(), float threshold = 0.0f);
|
||||
|
||||
bool isValid() const { return threshold > 0.0f; }
|
||||
|
||||
/// Checks whether, according to this LOD, we should subdivide the described voxel.
|
||||
bool shouldSubdivide(const glm::vec3& minimum, float size, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference.
|
||||
bool becameSubdivided(const glm::vec3& minimum, float size, const MetavoxelLOD& reference, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether the node or any of the nodes underneath it have had subdivision
|
||||
/// enabled or disabled as compared to the reference.
|
||||
bool becameSubdividedOrCollapsed(const glm::vec3& minimum, float size,
|
||||
const MetavoxelLOD& reference, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether, according to this LOD, we should subdivide the described region.
|
||||
bool shouldSubdivide(const glm::vec2& minimum, float size, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference.
|
||||
bool becameSubdivided(const glm::vec2& minimum, float size, const MetavoxelLOD& reference, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether the node or any of the nodes underneath it have had subdivision
|
||||
/// enabled or disabled as compared to the reference.
|
||||
bool becameSubdividedOrCollapsed(const glm::vec2& minimum, float size,
|
||||
const MetavoxelLOD& reference, float multiplier = 1.0f) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelLOD)
|
||||
|
||||
/// The base metavoxel representation shared between server and client. Contains a size (for all dimensions) and a set of
|
||||
/// octrees for different attributes.
|
||||
class MetavoxelData {
|
||||
public:
|
||||
|
||||
MetavoxelData();
|
||||
MetavoxelData(const MetavoxelData& other);
|
||||
~MetavoxelData();
|
||||
|
||||
MetavoxelData& operator=(const MetavoxelData& other);
|
||||
|
||||
/// Sets the size in all dimensions.
|
||||
void setSize(float size) { _size = size; }
|
||||
float getSize() const { return _size; }
|
||||
|
||||
/// Returns the minimum extent of the octrees (which are centered about the origin).
|
||||
glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; }
|
||||
|
||||
/// Returns the bounds of the octrees.
|
||||
Box getBounds() const;
|
||||
|
||||
/// Applies the specified visitor to the contained voxels.
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
/// Guides the specified visitor to the voxels that differ from those of the specified other.
|
||||
void guideToDifferent(const MetavoxelData& other, MetavoxelVisitor& visitor);
|
||||
|
||||
/// Inserts a spanner into the specified attribute layer.
|
||||
void insert(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Removes a spanner from the specified attribute layer.
|
||||
void remove(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Toggles the existence of a spanner in the specified attribute layer (removes if present, adds if not).
|
||||
void toggle(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Replaces a spanner in the specified attribute layer.
|
||||
void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
|
||||
/// Retrieves all spanners that intersect the specified bounds.
|
||||
void getIntersecting(const AttributePointer& attribute, const Box& bounds, QVector<SharedObjectPointer>& results);
|
||||
|
||||
/// Clears all data in the specified attribute layer.
|
||||
void clear(const AttributePointer& attribute);
|
||||
|
||||
/// "Touches" all data in the specified attribute layer, making it look as if it has changed.
|
||||
void touch(const AttributePointer& attribute);
|
||||
|
||||
/// Convenience function that finds the first spanner intersecting the provided ray.
|
||||
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const AttributePointer& attribute, float& distance, const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
||||
/// Sets part of the data.
|
||||
void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false);
|
||||
|
||||
/// Expands the tree, doubling its size in all dimensions (that is, increasing its volume eightfold).
|
||||
void expand();
|
||||
|
||||
void read(Bitstream& in, const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
void write(Bitstream& out, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
void readDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD, Bitstream& in, const MetavoxelLOD& lod);
|
||||
void writeDelta(const MetavoxelData& reference, const MetavoxelLOD& referenceLOD,
|
||||
Bitstream& out, const MetavoxelLOD& lod) const;
|
||||
|
||||
void setRoot(const AttributePointer& attribute, MetavoxelNode* root);
|
||||
MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
|
||||
MetavoxelNode* createRoot(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a
|
||||
/// shallow comparison).
|
||||
bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
/// Counts the nodes in the data.
|
||||
void countNodes(int& internalNodes, int& leaves, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
void dumpStats(QDebug debug = QDebug(QtDebugMsg)) const;
|
||||
|
||||
bool operator==(const MetavoxelData& other) const;
|
||||
bool operator!=(const MetavoxelData& other) const;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetavoxelVisitation;
|
||||
|
||||
void incrementRootReferenceCounts();
|
||||
void decrementRootReferenceCounts();
|
||||
|
||||
float _size;
|
||||
QHash<AttributePointer, MetavoxelNode*> _roots;
|
||||
};
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const MetavoxelData& data);
|
||||
|
||||
Bitstream& operator>>(Bitstream& in, MetavoxelData& data);
|
||||
|
||||
template<> void Bitstream::writeDelta(const MetavoxelData& value, const MetavoxelData& reference);
|
||||
|
||||
template<> void Bitstream::readDelta(MetavoxelData& value, const MetavoxelData& reference);
|
||||
|
||||
Q_DECLARE_METATYPE(MetavoxelData)
|
||||
|
||||
/// Holds the base state used in streaming metavoxel data.
|
||||
class MetavoxelStreamBase {
|
||||
public:
|
||||
const AttributePointer& attribute;
|
||||
Bitstream& stream;
|
||||
const MetavoxelLOD& lod;
|
||||
const MetavoxelLOD& referenceLOD;
|
||||
int visit;
|
||||
};
|
||||
|
||||
/// Holds the state used in streaming a metavoxel node.
|
||||
class MetavoxelStreamState {
|
||||
public:
|
||||
MetavoxelStreamBase& base;
|
||||
glm::vec3 minimum;
|
||||
float size;
|
||||
|
||||
bool shouldSubdivide() const;
|
||||
bool shouldSubdivideReference() const;
|
||||
bool becameSubdivided() const;
|
||||
bool becameSubdividedOrCollapsed() const;
|
||||
|
||||
void setMinimum(const glm::vec3& lastMinimum, int index);
|
||||
};
|
||||
|
||||
/// A single node within a metavoxel layer.
|
||||
class MetavoxelNode {
|
||||
public:
|
||||
|
||||
static const int CHILD_COUNT = 8;
|
||||
|
||||
static int getOppositeChildIndex(int index);
|
||||
|
||||
MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren = NULL);
|
||||
MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy);
|
||||
|
||||
void setAttributeValue(const AttributeValue& attributeValue);
|
||||
|
||||
void blendAttributeValues(const AttributeValue& source, const AttributeValue& dest);
|
||||
|
||||
AttributeValue getAttributeValue(const AttributePointer& attribute) const;
|
||||
void* getAttributeValue() const { return _attributeValue; }
|
||||
|
||||
void mergeChildren(const AttributePointer& attribute, bool postRead = false);
|
||||
|
||||
MetavoxelNode* getChild(int index) const { return _children[index]; }
|
||||
void setChild(int index, MetavoxelNode* child) { _children[index] = child; }
|
||||
|
||||
bool isLeaf() const;
|
||||
|
||||
void read(MetavoxelStreamState& state);
|
||||
void write(MetavoxelStreamState& state) const;
|
||||
|
||||
void readDelta(const MetavoxelNode& reference, MetavoxelStreamState& state);
|
||||
void writeDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const;
|
||||
|
||||
MetavoxelNode* readSubdivision(MetavoxelStreamState& state);
|
||||
void writeSubdivision(MetavoxelStreamState& state) const;
|
||||
|
||||
void readSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue);
|
||||
void writeSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue) const;
|
||||
|
||||
void writeSpanners(MetavoxelStreamState& state) const;
|
||||
|
||||
/// Increments the node's reference count.
|
||||
void incrementReferenceCount() { _referenceCount.ref(); }
|
||||
|
||||
/// Decrements the node's reference count. If the resulting reference count is zero, destroys the node
|
||||
/// and calls delete this.
|
||||
void decrementReferenceCount(const AttributePointer& attribute);
|
||||
|
||||
void destroy(const AttributePointer& attribute);
|
||||
|
||||
bool clearChildren(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this and the specified other node.
|
||||
bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const;
|
||||
|
||||
/// Retrieves all spanners satisfying the LOD constraint, placing them in the provided set.
|
||||
void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
|
||||
|
||||
void countNodes(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, int& internalNodes, int& leaves) const;
|
||||
|
||||
MetavoxelNode* touch(const AttributePointer& attribute) const;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(MetavoxelNode)
|
||||
|
||||
friend class MetavoxelVisitation;
|
||||
|
||||
QAtomicInt _referenceCount;
|
||||
void* _attributeValue;
|
||||
MetavoxelNode* _children[CHILD_COUNT];
|
||||
};
|
||||
|
||||
/// Contains information about a metavoxel (explicit or procedural).
|
||||
class MetavoxelInfo {
|
||||
public:
|
||||
|
||||
MetavoxelInfo* parentInfo;
|
||||
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<OwnedAttributeValue> outputValues;
|
||||
float lodBase;
|
||||
bool isLODLeaf;
|
||||
bool isLeaf;
|
||||
|
||||
MetavoxelInfo(MetavoxelInfo* parentInfo, int inputValuesSize, int outputValuesSize);
|
||||
MetavoxelInfo();
|
||||
|
||||
Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); }
|
||||
glm::vec3 getCenter() const { return minimum + glm::vec3(size, size, size) * 0.5f; }
|
||||
};
|
||||
|
||||
/// Base class for visitors to metavoxels.
|
||||
class MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
/// Encodes a visitation order sequence for the children of a metavoxel.
|
||||
static int encodeOrder(int first, int second, int third, int fourth, int fifth, int sixth, int seventh, int eighth);
|
||||
|
||||
/// Encodes a visitation order sequence that visits each child as sorted along the specified direction.
|
||||
static int encodeOrder(const glm::vec3& direction);
|
||||
|
||||
/// Returns a random visitation order sequence.
|
||||
static int encodeRandomOrder();
|
||||
|
||||
/// The default visitation order.
|
||||
static const int DEFAULT_ORDER;
|
||||
|
||||
/// A special "order" that instructs the guide to stop recursion.
|
||||
static const int STOP_RECURSION;
|
||||
|
||||
/// A special "order" that short-circuits the tour.
|
||||
static const int SHORT_CIRCUIT;
|
||||
|
||||
/// A flag combined with an order that instructs us to return to visiting all nodes (rather than the different ones) for
|
||||
/// just this level.
|
||||
static const int ALL_NODES;
|
||||
|
||||
/// A flag combined with an order that instructs us to return to visiting all nodes (rather than the different ones) for
|
||||
/// this level and all beneath it.
|
||||
static const int ALL_NODES_REST;
|
||||
|
||||
MetavoxelVisitor(const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
virtual ~MetavoxelVisitor();
|
||||
|
||||
/// Returns a reference to the list of input attributes desired.
|
||||
const QVector<AttributePointer>& getInputs() const { return _inputs; }
|
||||
|
||||
/// Returns a reference to the list of output attributes provided.
|
||||
const QVector<AttributePointer>& getOutputs() const { return _outputs; }
|
||||
|
||||
/// Returns a reference to the level of detail that will determine subdivision levels.
|
||||
const MetavoxelLOD& getLOD() const { return _lod; }
|
||||
|
||||
void setLOD(const MetavoxelLOD& lod) { _lod = lod; }
|
||||
|
||||
float getMinimumLODThresholdMultiplier() const { return _minimumLODThresholdMultiplier; }
|
||||
|
||||
/// Prepares for a new tour of the metavoxel data.
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
|
||||
/// Visits a metavoxel.
|
||||
/// \param info the metavoxel data
|
||||
/// \return the encoded order in which to traverse the children, zero to stop recursion, or -1 to short-circuit the tour.
|
||||
/// If child traversal is requested, postVisit will be called after we return from traversing the children and have merged
|
||||
/// their values
|
||||
virtual int visit(MetavoxelInfo& info) = 0;
|
||||
|
||||
/// Called after we have visited all of a metavoxel's children.
|
||||
/// \return whether or not any outputs were set in the info
|
||||
virtual bool postVisit(MetavoxelInfo& info);
|
||||
|
||||
/// Acquires the next visitation, incrementing the depth.
|
||||
MetavoxelVisitation& acquireVisitation();
|
||||
|
||||
/// Releases the current visitation, decrementing the depth.
|
||||
void releaseVisitation() { _depth--; }
|
||||
|
||||
protected:
|
||||
|
||||
QVector<AttributePointer> _inputs;
|
||||
QVector<AttributePointer> _outputs;
|
||||
MetavoxelLOD _lod;
|
||||
float _minimumLODThresholdMultiplier;
|
||||
MetavoxelData* _data;
|
||||
QList<MetavoxelVisitation> _visitations;
|
||||
int _depth;
|
||||
};
|
||||
|
||||
/// Base class for visitors to spanners.
|
||||
class SpannerVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
SpannerVisitor(const QVector<AttributePointer>& spannerInputs,
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD(),
|
||||
int order = DEFAULT_ORDER);
|
||||
|
||||
/// Visits a spanner.
|
||||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visit(Spanner* spanner) = 0;
|
||||
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _order;
|
||||
int _visit;
|
||||
};
|
||||
|
||||
/// Base class for ray intersection visitors.
|
||||
class RayIntersectionVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
RayIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
||||
/// Visits a metavoxel that the ray intersects.
|
||||
virtual int visit(MetavoxelInfo& info, float distance) = 0;
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
protected:
|
||||
|
||||
glm::vec3 _origin;
|
||||
glm::vec3 _direction;
|
||||
int _order;
|
||||
};
|
||||
|
||||
/// Base class for ray intersection spanner visitors.
|
||||
class RaySpannerIntersectionVisitor : public RayIntersectionVisitor {
|
||||
public:
|
||||
|
||||
RaySpannerIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<AttributePointer>& spannerInputs,
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
||||
/// Visits a spanner that the ray intersects.
|
||||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visitSpanner(Spanner* spanner, float distance) = 0;
|
||||
|
||||
virtual void prepare(MetavoxelData* data);
|
||||
virtual int visit(MetavoxelInfo& info, float distance);
|
||||
|
||||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _visit;
|
||||
};
|
||||
|
||||
/// Interface for objects that guide metavoxel visitors.
|
||||
class MetavoxelGuide : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Guides the specified visitor to the contained voxels.
|
||||
/// \return true to keep going, false to short circuit the tour
|
||||
virtual bool guide(MetavoxelVisitation& visitation) = 0;
|
||||
|
||||
/// Guides the specified visitor to the voxels that differ from a reference.
|
||||
/// \return true to keep going, false to short circuit the tour
|
||||
virtual bool guideToDifferent(MetavoxelVisitation& visitation);
|
||||
};
|
||||
|
||||
/// Guides visitors through the explicit content of the system.
|
||||
class DefaultMetavoxelGuide : public MetavoxelGuide {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE DefaultMetavoxelGuide();
|
||||
|
||||
virtual bool guide(MetavoxelVisitation& visitation);
|
||||
virtual bool guideToDifferent(MetavoxelVisitation& visitation);
|
||||
};
|
||||
|
||||
/// Contains the state associated with a visit to a metavoxel system.
|
||||
class MetavoxelVisitation {
|
||||
public:
|
||||
|
||||
MetavoxelVisitation* previous;
|
||||
MetavoxelVisitor* visitor;
|
||||
QVector<MetavoxelNode*> inputNodes;
|
||||
QVector<MetavoxelNode*> outputNodes;
|
||||
QVector<MetavoxelNode*> compareNodes;
|
||||
MetavoxelInfo info;
|
||||
|
||||
MetavoxelVisitation(MetavoxelVisitation* previous, MetavoxelVisitor* visitor, int inputNodesSize, int outputNodesSize);
|
||||
MetavoxelVisitation();
|
||||
|
||||
bool isInputLeaf(int index) const;
|
||||
bool allInputNodesLeaves() const;
|
||||
AttributeValue getInheritedOutputValue(int index) const;
|
||||
};
|
||||
|
||||
/// Base class for objects that render metavoxels.
|
||||
class MetavoxelRenderer : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelRenderer();
|
||||
|
||||
/// Returns a pointer to the implementation, creating it if necessary.
|
||||
MetavoxelRendererImplementation* getImplementation();
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelRendererImplementation* _implementation;
|
||||
QMutex _implementationMutex;
|
||||
|
||||
/// Returns the name of the class to instantiate for the implementation.
|
||||
virtual QByteArray getImplementationClassName() const;
|
||||
};
|
||||
|
||||
/// Base class for renderer implementations.
|
||||
class MetavoxelRendererImplementation : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE MetavoxelRendererImplementation();
|
||||
|
||||
virtual void init(MetavoxelRenderer* renderer);
|
||||
virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
virtual void simulate(MetavoxelData& data, float deltaTime, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod);
|
||||
|
||||
protected:
|
||||
|
||||
MetavoxelRenderer* _renderer;
|
||||
};
|
||||
|
||||
/// The standard, usual renderer.
|
||||
class DefaultMetavoxelRenderer : public MetavoxelRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE DefaultMetavoxelRenderer();
|
||||
|
||||
virtual QByteArray getImplementationClassName() const;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelData_h
|
|
@ -1,282 +0,0 @@
|
|||
//
|
||||
// MetavoxelMessages.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 1/24/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include "MetavoxelMessages.h"
|
||||
#include "Spanner.h"
|
||||
|
||||
void MetavoxelEditMessage::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
static_cast<const MetavoxelEdit*>(edit.data())->apply(data, objects);
|
||||
}
|
||||
|
||||
MetavoxelEdit::~MetavoxelEdit() {
|
||||
}
|
||||
|
||||
void MetavoxelEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
BoxSetEdit::BoxSetEdit(const Box& region, float granularity, const OwnedAttributeValue& value) :
|
||||
region(region), granularity(granularity), value(value) {
|
||||
}
|
||||
|
||||
class BoxSetEditVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
BoxSetEditVisitor(const BoxSetEdit& edit);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const BoxSetEdit& _edit;
|
||||
};
|
||||
|
||||
BoxSetEditVisitor::BoxSetEditVisitor(const BoxSetEdit& edit) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
|
||||
_edit(edit) {
|
||||
}
|
||||
|
||||
int 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);
|
||||
glm::vec3 size = maximum - minimum;
|
||||
if (size.x <= 0.0f || size.y <= 0.0f || size.z <= 0.0f) {
|
||||
return STOP_RECURSION; // disjoint
|
||||
}
|
||||
float volume = (size.x * size.y * size.z) / (info.size * info.size * info.size);
|
||||
if (volume >= 1.0f) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
return STOP_RECURSION; // entirely contained
|
||||
}
|
||||
if (info.size <= _edit.granularity) {
|
||||
if (volume >= 0.5f) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
}
|
||||
return STOP_RECURSION; // reached granularity limit; take best guess
|
||||
}
|
||||
return DEFAULT_ORDER; // subdivide
|
||||
}
|
||||
|
||||
void BoxSetEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// expand to fit the entire edit
|
||||
while (!data.getBounds().contains(region)) {
|
||||
data.expand();
|
||||
}
|
||||
|
||||
BoxSetEditVisitor setVisitor(*this);
|
||||
data.guide(setVisitor);
|
||||
}
|
||||
|
||||
GlobalSetEdit::GlobalSetEdit(const OwnedAttributeValue& value) :
|
||||
value(value) {
|
||||
}
|
||||
|
||||
class GlobalSetEditVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
GlobalSetEditVisitor(const GlobalSetEdit& edit);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const GlobalSetEdit& _edit;
|
||||
};
|
||||
|
||||
GlobalSetEditVisitor::GlobalSetEditVisitor(const GlobalSetEdit& edit) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
|
||||
_edit(edit) {
|
||||
}
|
||||
|
||||
int GlobalSetEditVisitor::visit(MetavoxelInfo& info) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
return STOP_RECURSION; // entirely contained
|
||||
}
|
||||
|
||||
void GlobalSetEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) 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 WeakSharedObjectHash& objects) const {
|
||||
data.insert(attribute, spanner);
|
||||
}
|
||||
|
||||
RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) :
|
||||
attribute(attribute),
|
||||
id(id) {
|
||||
}
|
||||
|
||||
void RemoveSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
SharedObject* object = objects.value(id);
|
||||
if (object) {
|
||||
data.remove(attribute, object);
|
||||
}
|
||||
}
|
||||
|
||||
ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) :
|
||||
attribute(attribute) {
|
||||
}
|
||||
|
||||
void ClearSpannersEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
data.clear(attribute);
|
||||
}
|
||||
|
||||
SetDataEdit::SetDataEdit(const glm::vec3& minimum, const MetavoxelData& data, bool blend) :
|
||||
minimum(minimum),
|
||||
data(data),
|
||||
blend(blend) {
|
||||
}
|
||||
|
||||
void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
data.set(minimum, this->data, blend);
|
||||
}
|
||||
|
||||
PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius,
|
||||
float height, bool set, bool erase, float granularity) :
|
||||
position(position),
|
||||
radius(radius),
|
||||
height(height),
|
||||
set(set),
|
||||
erase(erase),
|
||||
granularity(granularity) {
|
||||
}
|
||||
|
||||
void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// increase the extents slightly to include neighboring tiles
|
||||
const float RADIUS_EXTENSION = 1.1f;
|
||||
glm::vec3 extents = glm::vec3(radius, radius, radius) * RADIUS_EXTENSION;
|
||||
QVector<SharedObjectPointer> results;
|
||||
data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
Box(position - extents, position + extents), results);
|
||||
|
||||
foreach (const SharedObjectPointer& spanner, results) {
|
||||
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->paintHeight(position, radius,
|
||||
height, set, erase, granularity);
|
||||
if (newSpanner != spanner) {
|
||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialEdit::MaterialEdit(const SharedObjectPointer& material, const QColor& averageColor) :
|
||||
material(material),
|
||||
averageColor(averageColor) {
|
||||
}
|
||||
|
||||
HeightfieldMaterialSpannerEdit::HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner,
|
||||
const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize, float granularity) :
|
||||
MaterialEdit(material, averageColor),
|
||||
spanner(spanner),
|
||||
paint(paint),
|
||||
voxelize(voxelize),
|
||||
granularity(granularity) {
|
||||
}
|
||||
|
||||
class SpannerProjectionFetchVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
SpannerProjectionFetchVisitor(const Box& bounds, QVector<SharedObjectPointer>& results);
|
||||
|
||||
virtual bool visit(Spanner* spanner);
|
||||
|
||||
private:
|
||||
|
||||
const Box& _bounds;
|
||||
QVector<SharedObjectPointer>& _results;
|
||||
float _closestDistance;
|
||||
};
|
||||
|
||||
SpannerProjectionFetchVisitor::SpannerProjectionFetchVisitor(const Box& bounds, QVector<SharedObjectPointer>& results) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute()),
|
||||
_bounds(bounds),
|
||||
_results(results),
|
||||
_closestDistance(FLT_MAX) {
|
||||
}
|
||||
|
||||
bool SpannerProjectionFetchVisitor::visit(Spanner* spanner) {
|
||||
Heightfield* heightfield = qobject_cast<Heightfield*>(spanner);
|
||||
if (!heightfield) {
|
||||
return true;
|
||||
}
|
||||
glm::mat4 transform = glm::scale(1.0f / glm::vec3(heightfield->getScale(),
|
||||
heightfield->getScale() * heightfield->getAspectY(),
|
||||
heightfield->getScale() * heightfield->getAspectZ())) *
|
||||
glm::mat4_cast(glm::inverse(heightfield->getRotation())) * glm::translate(-heightfield->getTranslation());
|
||||
Box transformedBounds = transform * _bounds;
|
||||
if (transformedBounds.maximum.x < 0.0f || transformedBounds.maximum.z < 0.0f ||
|
||||
transformedBounds.minimum.x > 1.0f || transformedBounds.minimum.z > 1.0f) {
|
||||
return true;
|
||||
}
|
||||
float distance = qMin(glm::abs(transformedBounds.minimum.y), glm::abs(transformedBounds.maximum.y));
|
||||
if (distance < _closestDistance) {
|
||||
_results.clear();
|
||||
_results.append(spanner);
|
||||
_closestDistance = distance;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HeightfieldMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// make sure the color meets our transparency requirements
|
||||
QColor color = averageColor;
|
||||
if (paint) {
|
||||
color.setAlphaF(1.0f);
|
||||
|
||||
} else if (color.alphaF() < 0.5f) {
|
||||
color = QColor(0, 0, 0, 0);
|
||||
}
|
||||
QVector<SharedObjectPointer> results;
|
||||
data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
static_cast<Spanner*>(spanner.data())->getBounds(), results);
|
||||
|
||||
// if there's nothing intersecting directly, find the closest heightfield that intersects the projection
|
||||
if (results.isEmpty()) {
|
||||
SpannerProjectionFetchVisitor visitor(static_cast<Spanner*>(spanner.data())->getBounds(), results);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
||||
foreach (const SharedObjectPointer& result, results) {
|
||||
Spanner* newResult = static_cast<Spanner*>(result.data())->setMaterial(spanner, material,
|
||||
color, paint, voxelize, granularity);
|
||||
if (newResult != result) {
|
||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius, float granularity) :
|
||||
position(position),
|
||||
radius(radius),
|
||||
granularity(granularity) {
|
||||
}
|
||||
|
||||
void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
glm::vec3 extents = glm::vec3(radius, radius, radius);
|
||||
QVector<SharedObjectPointer> results;
|
||||
data.getIntersecting(AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
Box(position - extents, position + extents), results);
|
||||
|
||||
foreach (const SharedObjectPointer& spanner, results) {
|
||||
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->fillHeight(position, radius, granularity);
|
||||
if (newSpanner != spanner) {
|
||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
//
|
||||
// MetavoxelMessages.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/31/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelMessages_h
|
||||
#define hifi_MetavoxelMessages_h
|
||||
|
||||
#include "MetavoxelData.h"
|
||||
|
||||
/// Requests to close the session.
|
||||
class CloseSessionMessage {
|
||||
STREAMABLE
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(CloseSessionMessage)
|
||||
|
||||
/// Clears the mapping for a shared object.
|
||||
class ClearSharedObjectMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM int id;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
public:
|
||||
|
||||
STREAM MetavoxelLOD lod;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(ClientStateMessage)
|
||||
|
||||
/// A message preceding metavoxel delta information. The actual delta will follow it in the stream.
|
||||
class MetavoxelDeltaMessage {
|
||||
STREAMABLE
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage)
|
||||
|
||||
/// A message indicating that metavoxel delta information is being sent on a reliable channel.
|
||||
class MetavoxelDeltaPendingMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM int id;
|
||||
STREAM int sentPacketNumber;
|
||||
STREAM int receivedPacketNumber;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaPendingMessage)
|
||||
|
||||
/// A simple streamable edit.
|
||||
class MetavoxelEditMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM QVariant edit;
|
||||
|
||||
void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelEditMessage)
|
||||
|
||||
/// Abstract base class for edits.
|
||||
class MetavoxelEdit {
|
||||
public:
|
||||
|
||||
virtual ~MetavoxelEdit();
|
||||
|
||||
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
/// 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 WeakSharedObjectHash& objects) 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 WeakSharedObjectHash& objects) 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 WeakSharedObjectHash& objects) 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 WeakSharedObjectHash& objects) 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 WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(ClearSpannersEdit)
|
||||
|
||||
/// An edit that directly sets part of the metavoxel data.
|
||||
class SetDataEdit : public MetavoxelEdit {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM glm::vec3 minimum;
|
||||
STREAM MetavoxelData data;
|
||||
STREAM bool blend;
|
||||
|
||||
SetDataEdit(const glm::vec3& minimum = glm::vec3(), const MetavoxelData& data = MetavoxelData(), bool blend = false);
|
||||
|
||||
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(SetDataEdit)
|
||||
|
||||
/// An edit that sets a region of a heightfield height.
|
||||
class PaintHeightfieldHeightEdit : public MetavoxelEdit {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM glm::vec3 position;
|
||||
STREAM float radius;
|
||||
STREAM float height;
|
||||
STREAM bool set;
|
||||
STREAM bool erase;
|
||||
STREAM float granularity;
|
||||
|
||||
PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f,
|
||||
float height = 0.0f, bool set = false, bool erase = false, float granularity = 0.0f);
|
||||
|
||||
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(PaintHeightfieldHeightEdit)
|
||||
|
||||
/// Base class for edits that have materials.
|
||||
class MaterialEdit : public MetavoxelEdit {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM SharedObjectPointer material;
|
||||
STREAM QColor averageColor;
|
||||
|
||||
MaterialEdit(const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor());
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MaterialEdit)
|
||||
|
||||
/// An edit that sets the materials of a heightfield within a spanner to a value.
|
||||
class HeightfieldMaterialSpannerEdit : STREAM public MaterialEdit {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM SharedObjectPointer spanner;
|
||||
STREAM bool paint;
|
||||
STREAM bool voxelize;
|
||||
STREAM float granularity;
|
||||
|
||||
HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(),
|
||||
const SharedObjectPointer& material = SharedObjectPointer(),
|
||||
const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false, float granularity = 0.0f);
|
||||
|
||||
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(HeightfieldMaterialSpannerEdit)
|
||||
|
||||
/// An edit that fills a region of a heightfield height.
|
||||
class FillHeightfieldHeightEdit : public MetavoxelEdit {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM glm::vec3 position;
|
||||
STREAM float radius;
|
||||
STREAM float granularity;
|
||||
|
||||
FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, float granularity = 0.0f);
|
||||
|
||||
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(FillHeightfieldHeightEdit)
|
||||
|
||||
#endif // hifi_MetavoxelMessages_h
|
|
@ -1,723 +0,0 @@
|
|||
//
|
||||
// MetavoxelUtil.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/30/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QColorDialog>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QItemEditorFactory>
|
||||
#include <QLineEdit>
|
||||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
#include <QScriptEngine>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include "MetavoxelUtil.h"
|
||||
#include "ScriptCache.h"
|
||||
#include "StreamUtils.h"
|
||||
|
||||
static int scriptHashType = qRegisterMetaType<ScriptHash>();
|
||||
static int parameterizedURLType = qRegisterMetaType<ParameterizedURL>();
|
||||
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(ScriptHash)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(ParameterizedURL)
|
||||
|
||||
class DelegatingItemEditorFactory : public QItemEditorFactory {
|
||||
public:
|
||||
|
||||
DelegatingItemEditorFactory();
|
||||
|
||||
virtual QWidget* createEditor(int userType, QWidget* parent) const;
|
||||
virtual QByteArray valuePropertyName(int userType) const;
|
||||
|
||||
private:
|
||||
|
||||
const QItemEditorFactory* _parentFactory;
|
||||
};
|
||||
|
||||
class DoubleEditor : public QDoubleSpinBox {
|
||||
public:
|
||||
|
||||
DoubleEditor(QWidget* parent = NULL);
|
||||
};
|
||||
|
||||
DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) {
|
||||
setMinimum(-FLT_MAX);
|
||||
setMaximum(FLT_MAX);
|
||||
setSingleStep(0.01);
|
||||
}
|
||||
|
||||
DelegatingItemEditorFactory::DelegatingItemEditorFactory() :
|
||||
_parentFactory(QItemEditorFactory::defaultFactory()) {
|
||||
|
||||
QItemEditorFactory::setDefaultFactory(this);
|
||||
}
|
||||
|
||||
QWidget* DelegatingItemEditorFactory::createEditor(int userType, QWidget* parent) const {
|
||||
QWidget* editor = QItemEditorFactory::createEditor(userType, parent);
|
||||
return (!editor) ? _parentFactory->createEditor(userType, parent) : editor;
|
||||
}
|
||||
|
||||
QByteArray DelegatingItemEditorFactory::valuePropertyName(int userType) const {
|
||||
QByteArray propertyName = QItemEditorFactory::valuePropertyName(userType);
|
||||
return propertyName.isNull() ? _parentFactory->valuePropertyName(userType) : propertyName;
|
||||
}
|
||||
|
||||
QItemEditorFactory* getItemEditorFactory() {
|
||||
static QItemEditorFactory* factory = new DelegatingItemEditorFactory();
|
||||
return factory;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createDoubleEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<DoubleEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<double>(), creator);
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<float>(), creator);
|
||||
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);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createQuatEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QuatEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::quat>(), creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
|
||||
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<ParameterizedURLEditor>();
|
||||
getItemEditorFactory()->registerEditor(qMetaTypeId<ParameterizedURL>(), creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator();
|
||||
static QItemEditorCreatorBase* qMetaObjectEditorCreator = createQMetaObjectEditorCreator();
|
||||
static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator();
|
||||
static QItemEditorCreatorBase* qUrlEditorCreator = createQUrlEditorCreator();
|
||||
static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator();
|
||||
static QItemEditorCreatorBase* quatEditorCreator = createQuatEditorCreator();
|
||||
static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator();
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
void Box::add(const Box& other) {
|
||||
minimum = glm::min(minimum, other.minimum);
|
||||
maximum = glm::max(maximum, other.maximum);
|
||||
}
|
||||
|
||||
bool Box::contains(const glm::vec3& point) const {
|
||||
return point.x >= minimum.x && point.x <= maximum.x &&
|
||||
point.y >= minimum.y && point.y <= maximum.y &&
|
||||
point.z >= minimum.z && point.z <= maximum.z;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Box Box::getIntersection(const Box& other) const {
|
||||
return Box(glm::max(minimum, other.minimum), glm::min(maximum, other.maximum));
|
||||
}
|
||||
|
||||
bool Box::isEmpty() const {
|
||||
return minimum.x >= maximum.x || minimum.y >= maximum.y || minimum.z >= maximum.z;
|
||||
}
|
||||
|
||||
const int X_MAXIMUM_FLAG = 1;
|
||||
const int Y_MAXIMUM_FLAG = 2;
|
||||
const int Z_MAXIMUM_FLAG = 4;
|
||||
|
||||
glm::vec3 Box::getVertex(int index) const {
|
||||
return glm::vec3(
|
||||
(index & X_MAXIMUM_FLAG) ? maximum.x : minimum.x,
|
||||
(index & Y_MAXIMUM_FLAG) ? maximum.y : minimum.y,
|
||||
(index & Z_MAXIMUM_FLAG) ? maximum.z : minimum.z);
|
||||
}
|
||||
|
||||
// finds the intersection between a ray and the facing plane on one axis
|
||||
static bool findIntersection(float origin, float direction, float minimum, float maximum, float& distance) {
|
||||
if (direction > EPSILON) {
|
||||
distance = (minimum - origin) / direction;
|
||||
return true;
|
||||
} else if (direction < -EPSILON) {
|
||||
distance = (maximum - origin) / direction;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// determines whether a value is within the extents
|
||||
static bool isWithin(float value, float minimum, float maximum) {
|
||||
return value >= minimum && value <= maximum;
|
||||
}
|
||||
|
||||
bool Box::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
// handle the trivial case where the box contains the origin
|
||||
if (contains(origin)) {
|
||||
distance = 0.0f;
|
||||
return true;
|
||||
}
|
||||
// check each axis
|
||||
float axisDistance;
|
||||
if ((findIntersection(origin.x, direction.x, minimum.x, maximum.x, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, minimum.y, maximum.y) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, minimum.z, maximum.z))) {
|
||||
distance = axisDistance;
|
||||
return true;
|
||||
}
|
||||
if ((findIntersection(origin.y, direction.y, minimum.y, maximum.y, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.x + axisDistance*direction.x, minimum.x, maximum.x) &&
|
||||
isWithin(origin.z + axisDistance*direction.z, minimum.z, maximum.z))) {
|
||||
distance = axisDistance;
|
||||
return true;
|
||||
}
|
||||
if ((findIntersection(origin.z, direction.z, minimum.z, maximum.z, axisDistance) && axisDistance >= 0 &&
|
||||
isWithin(origin.y + axisDistance*direction.y, minimum.y, maximum.y) &&
|
||||
isWithin(origin.x + axisDistance*direction.x, minimum.x, maximum.x))) {
|
||||
distance = axisDistance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Box operator*(const glm::mat4& matrix, const Box& box) {
|
||||
// start with the constant component
|
||||
Box newBox(glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]), glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]));
|
||||
|
||||
// for each element, we choose the minimum or maximum based on the matrix sign
|
||||
if (matrix[0][0] >= 0.0f) {
|
||||
newBox.minimum.x += matrix[0][0] * box.minimum.x;
|
||||
newBox.maximum.x += matrix[0][0] * box.maximum.x;
|
||||
} else {
|
||||
newBox.minimum.x += matrix[0][0] * box.maximum.x;
|
||||
newBox.maximum.x += matrix[0][0] * box.minimum.x;
|
||||
}
|
||||
if (matrix[1][0] >= 0.0f) {
|
||||
newBox.minimum.x += matrix[1][0] * box.minimum.y;
|
||||
newBox.maximum.x += matrix[1][0] * box.maximum.y;
|
||||
} else {
|
||||
newBox.minimum.x += matrix[1][0] * box.maximum.y;
|
||||
newBox.maximum.x += matrix[1][0] * box.minimum.y;
|
||||
}
|
||||
if (matrix[2][0] >= 0.0f) {
|
||||
newBox.minimum.x += matrix[2][0] * box.minimum.z;
|
||||
newBox.maximum.x += matrix[2][0] * box.maximum.z;
|
||||
} else {
|
||||
newBox.minimum.x += matrix[2][0] * box.maximum.z;
|
||||
newBox.maximum.x += matrix[2][0] * box.minimum.z;
|
||||
}
|
||||
|
||||
if (matrix[0][1] >= 0.0f) {
|
||||
newBox.minimum.y += matrix[0][1] * box.minimum.x;
|
||||
newBox.maximum.y += matrix[0][1] * box.maximum.x;
|
||||
} else {
|
||||
newBox.minimum.y += matrix[0][1] * box.maximum.x;
|
||||
newBox.maximum.y += matrix[0][1] * box.minimum.x;
|
||||
}
|
||||
if (matrix[1][1] >= 0.0f) {
|
||||
newBox.minimum.y += matrix[1][1] * box.minimum.y;
|
||||
newBox.maximum.y += matrix[1][1] * box.maximum.y;
|
||||
} else {
|
||||
newBox.minimum.y += matrix[1][1] * box.maximum.y;
|
||||
newBox.maximum.y += matrix[1][1] * box.minimum.y;
|
||||
}
|
||||
if (matrix[2][1] >= 0.0f) {
|
||||
newBox.minimum.y += matrix[2][1] * box.minimum.z;
|
||||
newBox.maximum.y += matrix[2][1] * box.maximum.z;
|
||||
} else {
|
||||
newBox.minimum.y += matrix[2][1] * box.maximum.z;
|
||||
newBox.maximum.y += matrix[2][1] * box.minimum.z;
|
||||
}
|
||||
|
||||
if (matrix[0][2] >= 0.0f) {
|
||||
newBox.minimum.z += matrix[0][2] * box.minimum.x;
|
||||
newBox.maximum.z += matrix[0][2] * box.maximum.x;
|
||||
} else {
|
||||
newBox.minimum.z += matrix[0][2] * box.maximum.x;
|
||||
newBox.maximum.z += matrix[0][2] * box.minimum.x;
|
||||
}
|
||||
if (matrix[1][2] >= 0.0f) {
|
||||
newBox.minimum.z += matrix[1][2] * box.minimum.y;
|
||||
newBox.maximum.z += matrix[1][2] * box.maximum.y;
|
||||
} else {
|
||||
newBox.minimum.z += matrix[1][2] * box.maximum.y;
|
||||
newBox.maximum.z += matrix[1][2] * box.minimum.y;
|
||||
}
|
||||
if (matrix[2][2] >= 0.0f) {
|
||||
newBox.minimum.z += matrix[2][2] * box.minimum.z;
|
||||
newBox.maximum.z += matrix[2][2] * box.maximum.z;
|
||||
} else {
|
||||
newBox.minimum.z += matrix[2][2] * box.maximum.z;
|
||||
newBox.maximum.z += matrix[2][2] * box.minimum.z;
|
||||
}
|
||||
|
||||
return newBox;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& dbg, const Box& box) {
|
||||
return dbg.nospace() << "{type='Box', minimum=" << box.minimum << ", maximum=" << box.maximum << "}";
|
||||
}
|
||||
|
||||
AxisExtents::AxisExtents(const glm::vec3& first0, const glm::vec3& first1, const glm::vec3& first2, const glm::vec3& second) :
|
||||
axis(glm::cross(first2 - first1, first0 - first1)),
|
||||
minimum(glm::dot(first1, axis)),
|
||||
maximum(glm::dot(second, axis)) {
|
||||
}
|
||||
|
||||
AxisExtents::AxisExtents(const glm::vec3& axis, float minimum, float maximum) :
|
||||
axis(axis),
|
||||
minimum(minimum),
|
||||
maximum(maximum) {
|
||||
}
|
||||
|
||||
void Frustum::set(const glm::vec3& farTopLeft, const glm::vec3& farTopRight, const glm::vec3& farBottomLeft,
|
||||
const glm::vec3& farBottomRight, const glm::vec3& nearTopLeft, const glm::vec3& nearTopRight,
|
||||
const glm::vec3& nearBottomLeft, const glm::vec3& nearBottomRight) {
|
||||
|
||||
_vertices[0] = farBottomLeft;
|
||||
_vertices[1] = farBottomRight;
|
||||
_vertices[2] = farTopLeft;
|
||||
_vertices[3] = farTopRight;
|
||||
_vertices[4] = nearBottomLeft;
|
||||
_vertices[5] = nearBottomRight;
|
||||
_vertices[6] = nearTopLeft;
|
||||
_vertices[7] = nearTopRight;
|
||||
|
||||
// compute the bounds
|
||||
_bounds.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
_bounds.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
for (int i = 0; i < VERTEX_COUNT; i++) {
|
||||
_bounds.minimum = glm::min(_bounds.minimum, _vertices[i]);
|
||||
_bounds.maximum = glm::max(_bounds.maximum, _vertices[i]);
|
||||
}
|
||||
|
||||
// compute the extents for each side
|
||||
_sideExtents[0] = AxisExtents(nearBottomLeft, nearTopLeft, nearTopRight, farBottomLeft);
|
||||
_sideExtents[1] = AxisExtents(nearBottomLeft, farBottomLeft, farTopLeft, farBottomRight);
|
||||
_sideExtents[2] = AxisExtents(nearBottomRight, nearTopRight, farTopRight, farBottomLeft);
|
||||
_sideExtents[3] = AxisExtents(nearBottomLeft, nearBottomRight, farBottomRight, farTopLeft);
|
||||
_sideExtents[4] = AxisExtents(nearTopLeft, farTopLeft, farTopRight, farBottomRight);
|
||||
|
||||
// the other set of extents are derived from the cross products of the frustum and box edges
|
||||
glm::vec3 edges[] = { nearBottomRight - nearBottomLeft, nearTopLeft - nearBottomLeft, farBottomLeft - nearBottomLeft,
|
||||
farBottomRight - nearBottomRight, farTopLeft - nearTopLeft, farTopRight - nearTopRight };
|
||||
const int AXIS_COUNT = 3;
|
||||
for (uint i = 0, extentIndex = 0; i < sizeof(edges) / sizeof(edges[0]); i++) {
|
||||
for (int j = 0; j < AXIS_COUNT; j++) {
|
||||
glm::vec3 axis;
|
||||
axis[j] = 1.0f;
|
||||
glm::vec3 crossProduct = glm::cross(edges[i], axis);
|
||||
float minimum = FLT_MAX, maximum = -FLT_MAX;
|
||||
for (int k = 0; k < VERTEX_COUNT; k++) {
|
||||
float projection = glm::dot(crossProduct, _vertices[k]);
|
||||
minimum = glm::min(minimum, projection);
|
||||
maximum = glm::max(maximum, projection);
|
||||
}
|
||||
_crossProductExtents[extentIndex++] = AxisExtents(crossProduct, minimum, maximum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Frustum::IntersectionType Frustum::getIntersectionType(const Box& box) const {
|
||||
// first check the bounds (equivalent to checking frustum vertices against box extents)
|
||||
if (!_bounds.intersects(box)) {
|
||||
return NO_INTERSECTION;
|
||||
}
|
||||
|
||||
// check box vertices against side extents
|
||||
bool allInside = true;
|
||||
for (int i = 0; i < SIDE_EXTENT_COUNT; i++) {
|
||||
const AxisExtents& extents = _sideExtents[i];
|
||||
float firstProjection = glm::dot(box.getVertex(0), extents.axis);
|
||||
if (firstProjection < extents.minimum) {
|
||||
allInside = false;
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) >= extents.minimum) {
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (firstProjection > extents.maximum) {
|
||||
allInside = false;
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) <= extents.maximum) {
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (allInside) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
float projection = glm::dot(box.getVertex(j), extents.axis);
|
||||
if (projection < extents.minimum || projection > extents.maximum) {
|
||||
allInside = false;
|
||||
goto sideContinue;
|
||||
}
|
||||
}
|
||||
}
|
||||
sideContinue: ;
|
||||
}
|
||||
if (allInside) {
|
||||
return CONTAINS_INTERSECTION;
|
||||
}
|
||||
|
||||
// check box vertices against cross product extents
|
||||
for (int i = 0; i < CROSS_PRODUCT_EXTENT_COUNT; i++) {
|
||||
const AxisExtents& extents = _crossProductExtents[i];
|
||||
float firstProjection = glm::dot(box.getVertex(0), extents.axis);
|
||||
if (firstProjection < extents.minimum) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) >= extents.minimum) {
|
||||
goto crossProductContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
|
||||
} else if (firstProjection > extents.maximum) {
|
||||
for (int j = 1; j < Box::VERTEX_COUNT; j++) {
|
||||
if (glm::dot(box.getVertex(j), extents.axis) <= extents.maximum) {
|
||||
goto crossProductContinue;
|
||||
}
|
||||
}
|
||||
return NO_INTERSECTION;
|
||||
}
|
||||
crossProductContinue: ;
|
||||
}
|
||||
|
||||
return PARTIAL_INTERSECTION;
|
||||
}
|
||||
|
||||
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());
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
layout->addWidget(_button = new QPushButton());
|
||||
connect(_button, SIGNAL(clicked()), SLOT(selectColor()));
|
||||
setColor(QColor());
|
||||
}
|
||||
|
||||
void QColorEditor::setColor(const QColor& color) {
|
||||
QString name = (_color = color).name();
|
||||
_button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name()));
|
||||
_button->setText(name);
|
||||
}
|
||||
|
||||
void QColorEditor::selectColor() {
|
||||
QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel);
|
||||
if (color.isValid()) {
|
||||
setColor(color);
|
||||
emit colorChanged(color);
|
||||
}
|
||||
}
|
||||
|
||||
Setting::Handle<QStringList> editorURLs("editorURLs");
|
||||
|
||||
QUrlEditor::QUrlEditor(QWidget* parent) :
|
||||
QComboBox(parent) {
|
||||
|
||||
setEditable(true);
|
||||
setInsertPolicy(InsertAtTop);
|
||||
|
||||
// populate initial URL list from settings
|
||||
addItems(editorURLs.get());
|
||||
|
||||
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));
|
||||
}
|
||||
editorURLs.set(urls);
|
||||
}
|
||||
|
||||
BaseVec3Editor::BaseVec3Editor(QWidget* parent) : QWidget(parent) {
|
||||
QHBoxLayout* layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
setLayout(layout);
|
||||
|
||||
layout->addWidget(_x = createComponentBox());
|
||||
layout->addWidget(_y = createComponentBox());
|
||||
layout->addWidget(_z = createComponentBox());
|
||||
}
|
||||
|
||||
void BaseVec3Editor::setSingleStep(double singleStep) {
|
||||
_x->setSingleStep(singleStep);
|
||||
_y->setSingleStep(singleStep);
|
||||
_z->setSingleStep(singleStep);
|
||||
}
|
||||
|
||||
double BaseVec3Editor::getSingleStep() const {
|
||||
return _x->singleStep();
|
||||
}
|
||||
|
||||
QDoubleSpinBox* BaseVec3Editor::createComponentBox() {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||
box->setMinimum(-FLT_MAX);
|
||||
box->setMaximum(FLT_MAX);
|
||||
box->setMinimumWidth(50);
|
||||
connect(box, SIGNAL(valueChanged(double)), SLOT(updateValue()));
|
||||
return box;
|
||||
}
|
||||
|
||||
Vec3Editor::Vec3Editor(QWidget* parent) : BaseVec3Editor(parent) {
|
||||
setSingleStep(0.01);
|
||||
}
|
||||
|
||||
static void setComponentValue(QDoubleSpinBox* box, double value) {
|
||||
box->blockSignals(true);
|
||||
box->setValue(value);
|
||||
box->blockSignals(false);
|
||||
}
|
||||
|
||||
void Vec3Editor::setValue(const glm::vec3& value) {
|
||||
_value = value;
|
||||
setComponentValue(_x, value.x);
|
||||
setComponentValue(_y, value.y);
|
||||
setComponentValue(_z, value.z);
|
||||
}
|
||||
|
||||
void Vec3Editor::updateValue() {
|
||||
emit valueChanged(_value = glm::vec3(_x->value(), _y->value(), _z->value()));
|
||||
}
|
||||
|
||||
QuatEditor::QuatEditor(QWidget* parent) : BaseVec3Editor(parent) {
|
||||
_x->setRange(-179.0, 180.0);
|
||||
_y->setRange(-179.0, 180.0);
|
||||
_z->setRange(-179.0, 180.0);
|
||||
|
||||
_x->setWrapping(true);
|
||||
_y->setWrapping(true);
|
||||
_z->setWrapping(true);
|
||||
}
|
||||
|
||||
void QuatEditor::setValue(const glm::quat& value) {
|
||||
if (_value != value) {
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(_value = value));
|
||||
setComponentValue(_x, eulers.x);
|
||||
setComponentValue(_y, eulers.y);
|
||||
setComponentValue(_z, eulers.z);
|
||||
}
|
||||
}
|
||||
|
||||
void QuatEditor::updateValue() {
|
||||
glm::quat value(glm::radians(glm::vec3(_x->value(), _y->value(), _z->value())));
|
||||
if (_value != value) {
|
||||
emit valueChanged(_value = value);
|
||||
}
|
||||
}
|
||||
|
||||
ParameterizedURL::ParameterizedURL(const QUrl& url, const ScriptHash& parameters) :
|
||||
_url(url),
|
||||
_parameters(parameters) {
|
||||
}
|
||||
|
||||
bool ParameterizedURL::operator==(const ParameterizedURL& other) const {
|
||||
return _url == other._url && _parameters == other._parameters;
|
||||
}
|
||||
|
||||
bool ParameterizedURL::operator!=(const ParameterizedURL& other) const {
|
||||
return _url != other._url || _parameters != other._parameters;
|
||||
}
|
||||
|
||||
uint qHash(const ParameterizedURL& url, uint seed) {
|
||||
// just hash on the URL, for now
|
||||
return qHash(url.getURL(), seed);
|
||||
}
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url) {
|
||||
out << url.getURL();
|
||||
out << url.getParameters();
|
||||
return out;
|
||||
}
|
||||
|
||||
Bitstream& operator>>(Bitstream& in, ParameterizedURL& url) {
|
||||
QUrl qurl;
|
||||
in >> qurl;
|
||||
ScriptHash parameters;
|
||||
in >> parameters;
|
||||
url = ParameterizedURL(qurl, parameters);
|
||||
return in;
|
||||
}
|
||||
|
||||
ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) :
|
||||
QWidget(parent) {
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
setLayout(layout);
|
||||
|
||||
QWidget* lineContainer = new QWidget();
|
||||
layout->addWidget(lineContainer);
|
||||
|
||||
QHBoxLayout* lineLayout = new QHBoxLayout();
|
||||
lineContainer->setLayout(lineLayout);
|
||||
lineLayout->setContentsMargins(QMargins());
|
||||
|
||||
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()));
|
||||
lineLayout->addWidget(refresh);
|
||||
}
|
||||
|
||||
void ParameterizedURLEditor::setURL(const ParameterizedURL& url) {
|
||||
_urlEditor.setURL((_url = url).getURL());
|
||||
updateParameters();
|
||||
}
|
||||
|
||||
void ParameterizedURLEditor::updateURL() {
|
||||
ScriptHash parameters;
|
||||
if (layout()->count() > 1) {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout()->itemAt(1));
|
||||
for (int i = 0; i < form->rowCount(); i++) {
|
||||
QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget();
|
||||
QByteArray valuePropertyName = widget->property("valuePropertyName").toByteArray();
|
||||
const QMetaObject* widgetMetaObject = widget->metaObject();
|
||||
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
|
||||
parameters.insert(DependencyManager::get<ScriptCache>()->getEngine()->toStringHandle(
|
||||
widget->property("parameterName").toString()), widgetProperty.read(widget));
|
||||
}
|
||||
}
|
||||
emit urlChanged(_url = ParameterizedURL(_urlEditor.getURL(), parameters));
|
||||
if (_program) {
|
||||
_program->disconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ParameterizedURLEditor::updateParameters() {
|
||||
if (_program) {
|
||||
_program->disconnect(this);
|
||||
}
|
||||
_program = DependencyManager::get<ScriptCache>()->getProgram(_url.getURL());
|
||||
if (_program->isLoaded()) {
|
||||
continueUpdatingParameters();
|
||||
} else {
|
||||
connect(_program.data(), SIGNAL(loaded()), SLOT(continueUpdatingParameters()));
|
||||
}
|
||||
}
|
||||
|
||||
void ParameterizedURLEditor::continueUpdatingParameters() {
|
||||
QVBoxLayout* layout = static_cast<QVBoxLayout*>(this->layout());
|
||||
if (layout->count() > 1) {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout->takeAt(1));
|
||||
for (int i = form->count() - 1; i >= 0; i--) {
|
||||
QLayoutItem* item = form->takeAt(i);
|
||||
if (item->widget()) {
|
||||
delete item->widget();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
delete form;
|
||||
}
|
||||
QSharedPointer<NetworkValue> value = DependencyManager::get<ScriptCache>()->getValue(_url.getURL());
|
||||
const QList<ParameterInfo>& parameters = static_cast<RootNetworkValue*>(value.data())->getParameterInfo();
|
||||
if (parameters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form);
|
||||
foreach (const ParameterInfo& parameter, parameters) {
|
||||
QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(parameter.type, NULL);
|
||||
if (widget) {
|
||||
form->addRow(parameter.name.toString() + ":", widget);
|
||||
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(parameter.type);
|
||||
widget->setProperty("parameterName", parameter.name.toString());
|
||||
widget->setProperty("valuePropertyName", valuePropertyName);
|
||||
const QMetaObject* widgetMetaObject = widget->metaObject();
|
||||
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
|
||||
widgetProperty.write(widget, _url.getParameters().value(parameter.name));
|
||||
if (widgetProperty.hasNotifySignal()) {
|
||||
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(updateURL()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,362 +0,0 @@
|
|||
//
|
||||
// MetavoxelUtil.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/30/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelUtil_h
|
||||
#define hifi_MetavoxelUtil_h
|
||||
|
||||
#include <QColor>
|
||||
#include <QComboBox>
|
||||
#include <QItemEditorCreatorBase>
|
||||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include "Bitstream.h"
|
||||
|
||||
class QByteArray;
|
||||
class QDoubleSpinBox;
|
||||
class QItemEditorFactory;
|
||||
class QPushButton;
|
||||
|
||||
class NetworkProgram;
|
||||
|
||||
/// Performs the runtime equivalent of Qt's SIGNAL macro, which is to attach a prefix to the signature.
|
||||
QByteArray signal(const char* signature);
|
||||
|
||||
/// A streamable axis-aligned bounding box.
|
||||
class Box {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
static const int VERTEX_COUNT = 8;
|
||||
|
||||
STREAM glm::vec3 minimum;
|
||||
STREAM glm::vec3 maximum;
|
||||
|
||||
explicit Box(const glm::vec3& minimum = glm::vec3(), const glm::vec3& maximum = glm::vec3());
|
||||
|
||||
void add(const Box& other);
|
||||
|
||||
bool contains(const glm::vec3& point) const;
|
||||
|
||||
bool contains(const Box& other) const;
|
||||
|
||||
bool intersects(const Box& other) const;
|
||||
|
||||
Box getIntersection(const Box& other) const;
|
||||
|
||||
bool isEmpty() const;
|
||||
|
||||
float getLongestSide() const { return qMax(qMax(maximum.x - minimum.x, maximum.y - minimum.y), maximum.z - minimum.z); }
|
||||
|
||||
glm::vec3 getVertex(int index) const;
|
||||
|
||||
glm::vec3 getCenter() const { return (minimum + maximum) * 0.5f; }
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(Box)
|
||||
|
||||
Box operator*(const glm::mat4& matrix, const Box& box);
|
||||
|
||||
QDebug& operator<<(QDebug& out, const Box& box);
|
||||
|
||||
/// Represents the extents along an axis.
|
||||
class AxisExtents {
|
||||
public:
|
||||
glm::vec3 axis;
|
||||
float minimum;
|
||||
float maximum;
|
||||
|
||||
/// Creates a set of extents given three points on the first plane and one on the second.
|
||||
AxisExtents(const glm::vec3& first0, const glm::vec3& first1, const glm::vec3& first2, const glm::vec3& second);
|
||||
|
||||
AxisExtents(const glm::vec3& axis = glm::vec3(), float minimum = 0.0f, float maximum = 0.0f);
|
||||
};
|
||||
|
||||
/// A simple pyramidal frustum for intersection testing.
|
||||
class Frustum {
|
||||
public:
|
||||
|
||||
void set(const glm::vec3& farTopLeft, const glm::vec3& farTopRight, const glm::vec3& farBottomLeft,
|
||||
const glm::vec3& farBottomRight, const glm::vec3& nearTopLeft, const glm::vec3& nearTopRight,
|
||||
const glm::vec3& nearBottomLeft, const glm::vec3& nearBottomRight);
|
||||
|
||||
enum IntersectionType { NO_INTERSECTION, PARTIAL_INTERSECTION, CONTAINS_INTERSECTION };
|
||||
|
||||
IntersectionType getIntersectionType(const Box& box) const;
|
||||
|
||||
private:
|
||||
|
||||
static const int VERTEX_COUNT = 8;
|
||||
static const int SIDE_EXTENT_COUNT = 5;
|
||||
static const int CROSS_PRODUCT_EXTENT_COUNT = 18;
|
||||
|
||||
glm::vec3 _vertices[VERTEX_COUNT];
|
||||
Box _bounds;
|
||||
AxisExtents _sideExtents[SIDE_EXTENT_COUNT];
|
||||
AxisExtents _crossProductExtents[CROSS_PRODUCT_EXTENT_COUNT];
|
||||
};
|
||||
|
||||
/// Returns a pointer to the singleton item editor factory.
|
||||
QItemEditorFactory* getItemEditorFactory();
|
||||
|
||||
/// Because Windows doesn't necessarily have the staticMetaObject available when we want to create,
|
||||
/// this class simply delays the value property name lookup until actually requested.
|
||||
template<class T> class LazyItemEditorCreator : public QItemEditorCreatorBase {
|
||||
public:
|
||||
|
||||
virtual QWidget* createWidget(QWidget* parent) const { return new T(parent); }
|
||||
|
||||
virtual QByteArray valuePropertyName() const;
|
||||
|
||||
protected:
|
||||
|
||||
QByteArray _valuePropertyName;
|
||||
};
|
||||
|
||||
template<class T> QByteArray LazyItemEditorCreator<T>::valuePropertyName() const {
|
||||
if (_valuePropertyName.isNull()) {
|
||||
const_cast<LazyItemEditorCreator<T>*>(this)->_valuePropertyName = T::staticMetaObject.userProperty().name();
|
||||
}
|
||||
return _valuePropertyName;
|
||||
}
|
||||
|
||||
/// 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
|
||||
Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
QColorEditor(QWidget* parent);
|
||||
|
||||
const QColor& getColor() const { return _color; }
|
||||
|
||||
signals:
|
||||
|
||||
void colorChanged(const QColor& color);
|
||||
|
||||
public slots:
|
||||
|
||||
void setColor(const QColor& color);
|
||||
|
||||
private slots:
|
||||
|
||||
void selectColor();
|
||||
|
||||
private:
|
||||
|
||||
QPushButton* _button;
|
||||
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;
|
||||
};
|
||||
|
||||
/// Base class for Vec3Editor and QuatEditor.
|
||||
class BaseVec3Editor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
BaseVec3Editor(QWidget* parent);
|
||||
|
||||
void setSingleStep(double singleStep);
|
||||
double getSingleStep() const;
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void updateValue() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
QDoubleSpinBox* createComponentBox();
|
||||
|
||||
QDoubleSpinBox* _x;
|
||||
QDoubleSpinBox* _y;
|
||||
QDoubleSpinBox* _z;
|
||||
};
|
||||
|
||||
/// Editor for vector values.
|
||||
class Vec3Editor : public BaseVec3Editor {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec3 value MEMBER _value WRITE setValue NOTIFY valueChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
Vec3Editor(QWidget* parent);
|
||||
|
||||
const glm::vec3& getValue() const { return _value; }
|
||||
|
||||
signals:
|
||||
|
||||
void valueChanged(const glm::vec3& vector);
|
||||
|
||||
public slots:
|
||||
|
||||
void setValue(const glm::vec3& vector);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void updateValue();
|
||||
|
||||
private:
|
||||
|
||||
glm::vec3 _value;
|
||||
};
|
||||
|
||||
/// Editor for quaternion values.
|
||||
class QuatEditor : public BaseVec3Editor {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::quat value MEMBER _value WRITE setValue NOTIFY valueChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
QuatEditor(QWidget* parent);
|
||||
|
||||
signals:
|
||||
|
||||
void valueChanged(const glm::quat& value);
|
||||
|
||||
public slots:
|
||||
|
||||
void setValue(const glm::quat& value);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void updateValue();
|
||||
|
||||
private:
|
||||
|
||||
glm::quat _value;
|
||||
};
|
||||
|
||||
typedef QHash<QScriptString, QVariant> ScriptHash;
|
||||
|
||||
Q_DECLARE_METATYPE(ScriptHash)
|
||||
|
||||
/// Combines a URL with a set of typed parameters.
|
||||
class ParameterizedURL {
|
||||
public:
|
||||
|
||||
ParameterizedURL(const QUrl& url = QUrl(), const ScriptHash& parameters = ScriptHash());
|
||||
|
||||
bool isValid() const { return _url.isValid(); }
|
||||
|
||||
void setURL(const QUrl& url) { _url = url; }
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
void setParameters(const ScriptHash& parameters) { _parameters = parameters; }
|
||||
const ScriptHash& getParameters() const { return _parameters; }
|
||||
|
||||
bool operator==(const ParameterizedURL& other) const;
|
||||
bool operator!=(const ParameterizedURL& other) const;
|
||||
|
||||
private:
|
||||
|
||||
QUrl _url;
|
||||
ScriptHash _parameters;
|
||||
};
|
||||
|
||||
uint qHash(const ParameterizedURL& url, uint seed = 0);
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url);
|
||||
Bitstream& operator>>(Bitstream& in, ParameterizedURL& url);
|
||||
|
||||
Q_DECLARE_METATYPE(ParameterizedURL)
|
||||
|
||||
/// Allows editing parameterized URLs.
|
||||
class ParameterizedURLEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(ParameterizedURL url MEMBER _url WRITE setURL NOTIFY urlChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
ParameterizedURLEditor(QWidget* parent = NULL);
|
||||
|
||||
signals:
|
||||
|
||||
void urlChanged(const ParameterizedURL& url);
|
||||
|
||||
public slots:
|
||||
|
||||
void setURL(const ParameterizedURL& url);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateURL();
|
||||
void updateParameters();
|
||||
void continueUpdatingParameters();
|
||||
|
||||
private:
|
||||
|
||||
ParameterizedURL _url;
|
||||
QSharedPointer<NetworkProgram> _program;
|
||||
|
||||
QUrlEditor _urlEditor;
|
||||
};
|
||||
|
||||
#endif // hifi_MetavoxelUtil_h
|
|
@ -1,209 +0,0 @@
|
|||
//
|
||||
// ScriptCache.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/4/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValueIterator>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "ScriptCache.h"
|
||||
|
||||
static int scriptValueMetaTypeId = qRegisterMetaType<QScriptValue>();
|
||||
static bool scriptValueComparators = QMetaType::registerComparators<QScriptValue>();
|
||||
|
||||
bool operator==(const QScriptValue& first, const QScriptValue& second) {
|
||||
if (first.isUndefined()) {
|
||||
return second.isUndefined();
|
||||
|
||||
} else if (first.isNull()) {
|
||||
return second.isNull();
|
||||
|
||||
} else if (first.isBool()) {
|
||||
return second.isBool() && first.toBool() == second.toBool();
|
||||
|
||||
} else if (first.isNumber()) {
|
||||
return second.isNumber() && first.toNumber() == second.toNumber();
|
||||
|
||||
} else if (first.isString()) {
|
||||
return second.isString() && first.toString() == second.toString();
|
||||
|
||||
} else if (first.isVariant()) {
|
||||
return second.isVariant() && first.toVariant() == second.toVariant();
|
||||
|
||||
} else if (first.isQObject()) {
|
||||
return second.isQObject() && first.toQObject() == second.toQObject();
|
||||
|
||||
} else if (first.isQMetaObject()) {
|
||||
return second.isQMetaObject() && first.toQMetaObject() == second.toQMetaObject();
|
||||
|
||||
} else if (first.isDate()) {
|
||||
return second.isDate() && first.toDateTime() == second.toDateTime();
|
||||
|
||||
} else if (first.isRegExp()) {
|
||||
return second.isRegExp() && first.toRegExp() == second.toRegExp();
|
||||
|
||||
} else if (first.isArray()) {
|
||||
if (!second.isArray()) {
|
||||
return false;
|
||||
}
|
||||
int length = first.property(DependencyManager::get<ScriptCache>()->getLengthString()).toInt32();
|
||||
if (second.property(DependencyManager::get<ScriptCache>()->getLengthString()).toInt32() != length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (first.property(i) != second.property(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (first.isObject()) {
|
||||
if (!second.isObject()) {
|
||||
return false;
|
||||
}
|
||||
int propertyCount = 0;
|
||||
for (QScriptValueIterator it(first); it.hasNext(); ) {
|
||||
it.next();
|
||||
if (second.property(it.scriptName()) != it.value()) {
|
||||
return false;
|
||||
}
|
||||
propertyCount++;
|
||||
}
|
||||
// make sure the second has exactly as many properties as the first
|
||||
for (QScriptValueIterator it(second); it.hasNext(); ) {
|
||||
it.next();
|
||||
if (--propertyCount < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// if none of the above tests apply, first must be invalid
|
||||
return !second.isValid();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator!=(const QScriptValue& first, const QScriptValue& second) {
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
bool operator<(const QScriptValue& first, const QScriptValue& second) {
|
||||
return first.lessThan(second);
|
||||
}
|
||||
|
||||
ScriptCache::ScriptCache() :
|
||||
_engine(NULL)
|
||||
{
|
||||
setEngine(new QScriptEngine(this));
|
||||
|
||||
const qint64 SCRIPT_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES;
|
||||
setUnusedResourceCacheSize(SCRIPT_DEFAULT_UNUSED_MAX_SIZE);
|
||||
}
|
||||
|
||||
void ScriptCache::setEngine(QScriptEngine* engine) {
|
||||
if (_engine && _engine->parent() == this) {
|
||||
delete _engine;
|
||||
}
|
||||
AttributeRegistry::getInstance()->configureScriptEngine(_engine = engine);
|
||||
_parametersString = engine->toStringHandle("parameters");
|
||||
_lengthString = engine->toStringHandle("length");
|
||||
_nameString = engine->toStringHandle("name");
|
||||
_typeString = engine->toStringHandle("type");
|
||||
_generatorString = engine->toStringHandle("generator");
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url) {
|
||||
QSharedPointer<NetworkValue> value = _networkValues.value(url);
|
||||
if (value.isNull()) {
|
||||
value = QSharedPointer<NetworkValue>(url.getParameters().isEmpty() ?
|
||||
(NetworkValue*)new RootNetworkValue(getProgram(url.getURL())) :
|
||||
(NetworkValue*)new DerivedNetworkValue(getValue(url.getURL()), url.getParameters()));
|
||||
_networkValues.insert(url, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> ScriptCache::createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
return QSharedPointer<Resource>(new NetworkProgram(this, url), &Resource::allReferencesCleared);
|
||||
}
|
||||
|
||||
NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) :
|
||||
Resource(url),
|
||||
_cache(cache) {
|
||||
}
|
||||
|
||||
void NetworkProgram::downloadFinished(QNetworkReply* reply) {
|
||||
_program = QScriptProgram(QTextStream(reply).readAll(), reply->url().toString());
|
||||
reply->deleteLater();
|
||||
finishedLoading(true);
|
||||
emit loaded();
|
||||
}
|
||||
|
||||
NetworkValue::~NetworkValue() {
|
||||
}
|
||||
|
||||
RootNetworkValue::RootNetworkValue(const QSharedPointer<NetworkProgram>& program) :
|
||||
_program(program) {
|
||||
}
|
||||
|
||||
QScriptValue& RootNetworkValue::getValue() {
|
||||
if (!_value.isValid() && _program->isLoaded()) {
|
||||
_value = _program->getCache()->getEngine()->evaluate(_program->getProgram());
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
const QList<ParameterInfo>& RootNetworkValue::getParameterInfo() {
|
||||
if (isLoaded() && _parameterInfo.isEmpty()) {
|
||||
ScriptCache* cache = _program->getCache();
|
||||
QScriptEngine* engine = cache->getEngine();
|
||||
QScriptValue parameters = _value.property(cache->getParametersString());
|
||||
if (parameters.isArray()) {
|
||||
int length = parameters.property(cache->getLengthString()).toInt32();
|
||||
for (int i = 0; i < length; i++) {
|
||||
QScriptValue parameter = parameters.property(i);
|
||||
ParameterInfo info = { engine->toStringHandle(parameter.property(cache->getNameString()).toString()),
|
||||
QMetaType::type(parameter.property(cache->getTypeString()).toString().toUtf8().constData()) };
|
||||
_parameterInfo.append(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _parameterInfo;
|
||||
}
|
||||
|
||||
DerivedNetworkValue::DerivedNetworkValue(const QSharedPointer<NetworkValue>& baseValue, const ScriptHash& parameters) :
|
||||
_baseValue(baseValue),
|
||||
_parameters(parameters) {
|
||||
}
|
||||
|
||||
QScriptValue& DerivedNetworkValue::getValue() {
|
||||
if (!_value.isValid() && _baseValue->isLoaded()) {
|
||||
RootNetworkValue* root = static_cast<RootNetworkValue*>(_baseValue.data());
|
||||
ScriptCache* cache = root->getProgram()->getCache();
|
||||
QScriptValue generator = _baseValue->getValue().property(cache->getGeneratorString());
|
||||
if (generator.isFunction()) {
|
||||
QScriptValueList arguments;
|
||||
foreach (const ParameterInfo& parameter, root->getParameterInfo()) {
|
||||
arguments.append(cache->getEngine()->newVariant(_parameters.value(parameter.name)));
|
||||
}
|
||||
_value = generator.call(QScriptValue(), arguments);
|
||||
|
||||
} else {
|
||||
_value = _baseValue->getValue();
|
||||
}
|
||||
}
|
||||
return _value;
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
//
|
||||
// ScriptCache.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/4/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ScriptCache_h
|
||||
#define hifi_ScriptCache_h
|
||||
|
||||
#include <QScriptProgram>
|
||||
#include <QScriptValue>
|
||||
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include "MetavoxelUtil.h"
|
||||
|
||||
class QScriptEngine;
|
||||
|
||||
class NetworkProgram;
|
||||
class NetworkValue;
|
||||
|
||||
/// Maintains a cache of loaded scripts.
|
||||
class ScriptCache : public ResourceCache, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
ScriptCache();
|
||||
|
||||
void setEngine(QScriptEngine* engine);
|
||||
QScriptEngine* getEngine() const { return _engine; }
|
||||
|
||||
/// Loads a script program from the specified URL.
|
||||
QSharedPointer<NetworkProgram> getProgram(const QUrl& url) { return getResource(url).staticCast<NetworkProgram>(); }
|
||||
|
||||
/// Loads a script value from the specified URL.
|
||||
QSharedPointer<NetworkValue> getValue(const ParameterizedURL& url);
|
||||
|
||||
const QScriptString& getParametersString() const { return _parametersString; }
|
||||
const QScriptString& getLengthString() const { return _lengthString; }
|
||||
const QScriptString& getNameString() const { return _nameString; }
|
||||
const QScriptString& getTypeString() const { return _typeString; }
|
||||
const QScriptString& getGeneratorString() const { return _generatorString; }
|
||||
|
||||
protected:
|
||||
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
|
||||
|
||||
private:
|
||||
|
||||
QScriptEngine* _engine;
|
||||
QHash<ParameterizedURL, QWeakPointer<NetworkValue> > _networkValues;
|
||||
QScriptString _parametersString;
|
||||
QScriptString _lengthString;
|
||||
QScriptString _nameString;
|
||||
QScriptString _typeString;
|
||||
QScriptString _generatorString;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QScriptValue)
|
||||
|
||||
bool operator==(const QScriptValue& first, const QScriptValue& second);
|
||||
bool operator!=(const QScriptValue& first, const QScriptValue& second);
|
||||
bool operator<(const QScriptValue& first, const QScriptValue& second);
|
||||
|
||||
/// A program loaded from the network.
|
||||
class NetworkProgram : public Resource {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
NetworkProgram(ScriptCache* cache, const QUrl& url);
|
||||
|
||||
ScriptCache* getCache() const { return _cache; }
|
||||
|
||||
const QScriptProgram& getProgram() const { return _program; }
|
||||
|
||||
signals:
|
||||
|
||||
void loaded();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void downloadFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
|
||||
ScriptCache* _cache;
|
||||
QScriptProgram _program;
|
||||
};
|
||||
|
||||
/// Abstract base class of values loaded from the network.
|
||||
class NetworkValue {
|
||||
public:
|
||||
|
||||
virtual ~NetworkValue();
|
||||
|
||||
bool isLoaded() { return getValue().isValid(); }
|
||||
|
||||
virtual QScriptValue& getValue() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
QScriptValue _value;
|
||||
};
|
||||
|
||||
/// Contains information about a script parameter.
|
||||
class ParameterInfo {
|
||||
public:
|
||||
QScriptString name;
|
||||
int type;
|
||||
};
|
||||
|
||||
/// The direct result of running a program.
|
||||
class RootNetworkValue : public NetworkValue {
|
||||
public:
|
||||
|
||||
RootNetworkValue(const QSharedPointer<NetworkProgram>& program);
|
||||
|
||||
const QSharedPointer<NetworkProgram>& getProgram() const { return _program; }
|
||||
|
||||
virtual QScriptValue& getValue();
|
||||
|
||||
const QList<ParameterInfo>& getParameterInfo();
|
||||
|
||||
private:
|
||||
|
||||
QSharedPointer<NetworkProgram> _program;
|
||||
QList<ParameterInfo> _parameterInfo;
|
||||
};
|
||||
|
||||
/// The result of running a program's generator using a set of arguments.
|
||||
class DerivedNetworkValue : public NetworkValue {
|
||||
public:
|
||||
|
||||
DerivedNetworkValue(const QSharedPointer<NetworkValue>& baseValue, const ScriptHash& parameters);
|
||||
|
||||
virtual QScriptValue& getValue();
|
||||
|
||||
private:
|
||||
|
||||
QSharedPointer<NetworkValue> _baseValue;
|
||||
ScriptHash _parameters;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptCache_h
|
|
@ -1,319 +0,0 @@
|
|||
//
|
||||
// SharedObject.cpp
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/5/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QItemEditorFactory>
|
||||
#include <QMetaProperty>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include "Bitstream.h"
|
||||
#include "MetavoxelUtil.h"
|
||||
#include "SharedObject.h"
|
||||
|
||||
REGISTER_META_OBJECT(SharedObject)
|
||||
|
||||
SharedObject::SharedObject() :
|
||||
_id(_nextID.fetchAndAddOrdered(1)),
|
||||
_originID(_id),
|
||||
_remoteID(0),
|
||||
_remoteOriginID(0) {
|
||||
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.insert(_id, this);
|
||||
}
|
||||
|
||||
void SharedObject::setID(int id) {
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.remove(_id);
|
||||
_weakHash.insert(_id = id, this);
|
||||
}
|
||||
|
||||
void SharedObject::incrementReferenceCount() {
|
||||
_referenceCount.ref();
|
||||
}
|
||||
|
||||
void SharedObject::decrementReferenceCount() {
|
||||
if (!_referenceCount.deref()) {
|
||||
{
|
||||
QWriteLocker locker(&_weakHashLock);
|
||||
_weakHash.remove(_id);
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
SharedObject* SharedObject::clone(bool withID, SharedObject* target) const {
|
||||
// default behavior is to make a copy using the no-arg constructor and copy the stored properties
|
||||
const QMetaObject* metaObject = this->metaObject();
|
||||
if (!target) {
|
||||
target = static_cast<SharedObject*>(metaObject->newInstance());
|
||||
}
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored()) {
|
||||
if (property.userType() == qMetaTypeId<SharedObjectPointer>()) {
|
||||
SharedObject* value = property.read(this).value<SharedObjectPointer>().data();
|
||||
property.write(target, QVariant::fromValue(value ? value->clone(withID) : value));
|
||||
} else {
|
||||
property.write(target, property.read(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (const QByteArray& propertyName, dynamicPropertyNames()) {
|
||||
target->setProperty(propertyName, property(propertyName));
|
||||
}
|
||||
if (withID) {
|
||||
target->setOriginID(_originID);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const {
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
// default behavior is to compare the properties
|
||||
const QMetaObject* metaObject = this->metaObject();
|
||||
if (metaObject != other->metaObject() && !sharedAncestry) {
|
||||
return false;
|
||||
}
|
||||
// use the streamer, if we have one
|
||||
const ObjectStreamer* streamer = Bitstream::getObjectStreamer(metaObject);
|
||||
if (streamer) {
|
||||
if (!streamer->equal(this, other)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored() && property.read(this) != property.read(other)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
|
||||
if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) {
|
||||
return false;
|
||||
}
|
||||
foreach (const QByteArray& propertyName, dynamicPropertyNames) {
|
||||
if (property(propertyName) != other->property(propertyName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SharedObject::dump(QDebug debug) const {
|
||||
debug << this;
|
||||
const QMetaObject* metaObject = this->metaObject();
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored()) {
|
||||
debug << property.name() << property.read(this);
|
||||
}
|
||||
}
|
||||
QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
|
||||
foreach (const QByteArray& propertyName, dynamicPropertyNames) {
|
||||
debug << propertyName << property(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
void SharedObject::writeExtra(Bitstream& out) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SharedObject::readExtra(Bitstream& in, bool reread) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SharedObject::writeExtraDelta(Bitstream& out, const SharedObject* reference) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SharedObject::readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void SharedObject::maybeWriteSubdivision(Bitstream& out) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
SharedObject* SharedObject::readSubdivision(Bitstream& in) {
|
||||
return this;
|
||||
}
|
||||
|
||||
QAtomicInt SharedObject::_nextID(1);
|
||||
WeakSharedObjectHash SharedObject::_weakHash;
|
||||
QReadWriteLock SharedObject::_weakHashLock;
|
||||
|
||||
void pruneWeakSharedObjectHash(WeakSharedObjectHash& hash) {
|
||||
for (WeakSharedObjectHash::iterator it = hash.begin(); it != hash.end(); ) {
|
||||
if (!it.value()) {
|
||||
it = hash.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, bool nullable, QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form);
|
||||
|
||||
form->addRow("Type:", _type = new QComboBox());
|
||||
if (nullable) {
|
||||
_type->addItem("(none)");
|
||||
}
|
||||
foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) {
|
||||
// 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) {
|
||||
_object = object;
|
||||
const QMetaObject* metaObject = object ? object->metaObject() : NULL;
|
||||
int index = _type->findData(QVariant::fromValue(metaObject));
|
||||
if (index != -1) {
|
||||
// ensure that we call updateType to obtain the values
|
||||
if (_type->currentIndex() == index) {
|
||||
updateType();
|
||||
} else {
|
||||
_type->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
if (property.hasNotifySignal()) {
|
||||
connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const QMetaObject* getOwningAncestor(const QMetaObject* metaObject, int propertyIndex) {
|
||||
while (propertyIndex < metaObject->propertyOffset()) {
|
||||
metaObject = metaObject->superClass();
|
||||
}
|
||||
return metaObject;
|
||||
}
|
||||
|
||||
void SharedObjectEditor::updateType() {
|
||||
// delete the existing rows
|
||||
if (layout()->count() > 1) {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout()->takeAt(1));
|
||||
while (!form->isEmpty()) {
|
||||
QLayoutItem* item = form->takeAt(0);
|
||||
if (item->widget()) {
|
||||
delete item->widget();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
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) {
|
||||
_object.reset();
|
||||
emit objectChanged(_object);
|
||||
return;
|
||||
}
|
||||
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
|
||||
property.write(newObject, property.read(oldObject));
|
||||
}
|
||||
QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(property.userType(), NULL);
|
||||
if (widget) {
|
||||
widget->setProperty("propertyIndex", i);
|
||||
form->addRow(QByteArray(property.name()) + ':', widget);
|
||||
QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType());
|
||||
const QMetaObject* widgetMetaObject = widget->metaObject();
|
||||
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
|
||||
widgetProperty.write(widget, property.read(newObject));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
emit objectChanged(_object = static_cast<SharedObject*>(newObject));
|
||||
}
|
||||
|
||||
void SharedObjectEditor::propertyChanged() {
|
||||
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 != sender()) {
|
||||
continue;
|
||||
}
|
||||
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));
|
||||
}
|
||||
emit objectChanged(_object);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
|
@ -1,268 +0,0 @@
|
|||
//
|
||||
// SharedObject.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/5/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_SharedObject_h
|
||||
#define hifi_SharedObject_h
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QReadWriteLock>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
#include <QtDebug>
|
||||
|
||||
class QComboBox;
|
||||
|
||||
class Bitstream;
|
||||
class SharedObject;
|
||||
|
||||
typedef QHash<int, QPointer<SharedObject> > WeakSharedObjectHash;
|
||||
|
||||
/// A QObject that may be shared over the network.
|
||||
class SharedObject : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Returns the weak hash under which all local shared objects are registered.
|
||||
static const WeakSharedObjectHash& getWeakHash() { return _weakHash; }
|
||||
|
||||
/// Returns a reference to the weak hash lock.
|
||||
static QReadWriteLock& getWeakHashLock() { return _weakHashLock; }
|
||||
|
||||
Q_INVOKABLE SharedObject();
|
||||
|
||||
/// Returns the unique local ID for this object.
|
||||
int getID() const { return _id; }
|
||||
|
||||
void setID(int id);
|
||||
|
||||
/// Returns the local origin ID for this object.
|
||||
int getOriginID() const { return _originID; }
|
||||
|
||||
void setOriginID(int originID) { _originID = originID; }
|
||||
|
||||
/// Returns the unique remote ID for this object, or zero if this is a local object.
|
||||
int getRemoteID() const { return _remoteID; }
|
||||
|
||||
void setRemoteID(int remoteID) { _remoteID = remoteID; }
|
||||
|
||||
/// Returns the remote origin ID for this object, or zero if this is a local object.
|
||||
int getRemoteOriginID() const { return _remoteOriginID; }
|
||||
|
||||
void setRemoteOriginID(int remoteOriginID) { _remoteOriginID = remoteOriginID; }
|
||||
|
||||
int getReferenceCount() const { return _referenceCount.load(); }
|
||||
void incrementReferenceCount();
|
||||
void decrementReferenceCount();
|
||||
|
||||
/// Creates a new clone of this object.
|
||||
/// \param withID if true, give the clone the same origin ID as this object
|
||||
/// \target if non-NULL, a target object to populate (as opposed to creating a new instance of this object's class)
|
||||
virtual SharedObject* clone(bool withID = false, SharedObject* target = NULL) const;
|
||||
|
||||
/// Tests this object for equality with another.
|
||||
/// \param sharedAncestry if true and the classes of the objects differ, compare their shared ancestry (assuming that
|
||||
/// this is an instance of a superclass of the other object's class) rather than simply returning false.
|
||||
virtual bool equals(const SharedObject* other, bool sharedAncestry = false) const;
|
||||
|
||||
/// Dumps the contents of this object to the debug output.
|
||||
virtual void dump(QDebug debug = QDebug(QtDebugMsg)) const;
|
||||
|
||||
/// Writes the non-property contents of this object to the specified stream.
|
||||
virtual void writeExtra(Bitstream& out) const;
|
||||
|
||||
/// Reads the non-property contents of this object from the specified stream.
|
||||
/// \param reread if true, reread the contents from the stream but don't reapply them
|
||||
virtual void readExtra(Bitstream& in, bool reread = false);
|
||||
|
||||
/// Writes the delta-encoded non-property contents of this object to the specified stream.
|
||||
virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const;
|
||||
|
||||
/// Reads the delta-encoded non-property contents of this object from the specified stream.
|
||||
/// \param reread if true, reread the contents from the stream but don't reapply them
|
||||
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread = false);
|
||||
|
||||
/// Writes the subdivision of the contents of this object (preceeded by a
|
||||
/// reference to the object itself) to the specified stream if necessary.
|
||||
virtual void maybeWriteSubdivision(Bitstream& out);
|
||||
|
||||
/// Reads the subdivision of this object from the specified stream.
|
||||
/// \return the modified object, or this if no modification was performed
|
||||
virtual SharedObject* readSubdivision(Bitstream& in);
|
||||
|
||||
private:
|
||||
|
||||
int _id;
|
||||
int _originID;
|
||||
int _remoteID;
|
||||
int _remoteOriginID;
|
||||
QAtomicInt _referenceCount;
|
||||
|
||||
static QAtomicInt _nextID;
|
||||
static WeakSharedObjectHash _weakHash;
|
||||
static QReadWriteLock _weakHashLock;
|
||||
};
|
||||
|
||||
/// Removes the null references from the supplied hash.
|
||||
void pruneWeakSharedObjectHash(WeakSharedObjectHash& hash);
|
||||
|
||||
/// A pointer to a shared object.
|
||||
template<class T> class SharedObjectPointerTemplate {
|
||||
public:
|
||||
|
||||
SharedObjectPointerTemplate(T* data = NULL);
|
||||
SharedObjectPointerTemplate(const SharedObjectPointerTemplate<T>& other);
|
||||
~SharedObjectPointerTemplate();
|
||||
|
||||
T* data() const { return _data; }
|
||||
|
||||
/// "Detaches" this object, making a new copy if its reference count is greater than one.
|
||||
bool detach();
|
||||
|
||||
void swap(SharedObjectPointerTemplate<T>& other) { qSwap(_data, other._data); }
|
||||
|
||||
void reset();
|
||||
|
||||
bool operator!() const { return !_data; }
|
||||
operator T*() const { return _data; }
|
||||
T& operator*() const { return *_data; }
|
||||
T* operator->() const { return _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);
|
||||
}
|
||||
|
||||
template<class T, class X> bool equals(const SharedObjectPointerTemplate<T>& first,
|
||||
const SharedObjectPointerTemplate<X>& second) {
|
||||
return first ? first->equals(second) : !second;
|
||||
}
|
||||
|
||||
typedef SharedObjectPointerTemplate<SharedObject> SharedObjectPointer;
|
||||
|
||||
Q_DECLARE_METATYPE(SharedObjectPointer)
|
||||
|
||||
typedef QSet<SharedObjectPointer> SharedObjectSet;
|
||||
|
||||
Q_DECLARE_METATYPE(SharedObjectSet)
|
||||
|
||||
/// Allows editing shared object instances.
|
||||
class SharedObjectEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject NOTIFY objectChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
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();
|
||||
|
||||
signals:
|
||||
|
||||
void objectChanged(const SharedObjectPointer& object);
|
||||
|
||||
public slots:
|
||||
|
||||
void setObject(const SharedObjectPointer& object);
|
||||
|
||||
private slots:
|
||||
|
||||
void updateType();
|
||||
void propertyChanged();
|
||||
void updateProperty();
|
||||
|
||||
private:
|
||||
|
||||
QComboBox* _type;
|
||||
SharedObjectPointer _object;
|
||||
};
|
||||
|
||||
#endif // hifi_SharedObject_h
|
File diff suppressed because it is too large
Load diff
|
@ -1,866 +0,0 @@
|
|||
//
|
||||
// Spanner.h
|
||||
// libraries/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 11/10/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Spanner_h
|
||||
#define hifi_Spanner_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelUtil.h"
|
||||
|
||||
class AbstractHeightfieldNodeRenderer;
|
||||
class DataBlock;
|
||||
class Heightfield;
|
||||
class HeightfieldColor;
|
||||
class HeightfieldHeight;
|
||||
class HeightfieldMaterial;
|
||||
class HeightfieldNode;
|
||||
class HeightfieldStack;
|
||||
class SpannerRenderer;
|
||||
|
||||
/// 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 placementGranularity MEMBER _placementGranularity DESIGNABLE false)
|
||||
Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false)
|
||||
Q_PROPERTY(bool willBeVoxelized MEMBER _willBeVoxelized DESIGNABLE false)
|
||||
|
||||
public:
|
||||
|
||||
/// Returns the value of the global visit counter and increments it.
|
||||
static int getAndIncrementNextVisit() { return _nextVisit.fetchAndAddOrdered(1); }
|
||||
|
||||
Spanner();
|
||||
|
||||
void setBounds(const Box& bounds);
|
||||
const Box& getBounds() const { return _bounds; }
|
||||
|
||||
void setPlacementGranularity(float granularity) { _placementGranularity = granularity; }
|
||||
float getPlacementGranularity() const { return _placementGranularity; }
|
||||
|
||||
void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; }
|
||||
float getVoxelizationGranularity() const { return _voxelizationGranularity; }
|
||||
|
||||
void setMerged(bool merged) { _merged = merged; }
|
||||
bool isMerged() const { return _merged; }
|
||||
|
||||
void setWillBeVoxelized(bool willBeVoxelized) { _willBeVoxelized = willBeVoxelized; }
|
||||
bool getWillBeVoxelized() const { return _willBeVoxelized; }
|
||||
|
||||
/// 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(int visit);
|
||||
|
||||
/// Returns a pointer to the renderer, creating it if necessary.
|
||||
SpannerRenderer* getRenderer();
|
||||
|
||||
/// Checks whether this is a heightfield.
|
||||
virtual bool isHeightfield() const;
|
||||
|
||||
/// Finds the height at the specified location, or returns -FLT_MAX for none.
|
||||
virtual float getHeight(const glm::vec3& location) const;
|
||||
|
||||
/// Finds the intersection between the described ray and this spanner.
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// Attempts to modify the spanner's height.
|
||||
/// \param set whether to set the height as opposed to raising/lowering it
|
||||
/// \param erase whether to erase height values
|
||||
/// \return the modified spanner, or this if no modification was performed
|
||||
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height,
|
||||
bool set, bool erase, float granularity);
|
||||
|
||||
/// Attempts to fill the spanner's height (adding removing volumetric information).
|
||||
/// \return the modified spanner, or this if no modification was performed
|
||||
virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity);
|
||||
|
||||
/// Attempts to "sculpt" or "paint," etc., with the supplied spanner.
|
||||
/// \return the modified spanner, or this if no modification was performed
|
||||
virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
|
||||
const QColor& color, bool paint, bool voxelize, float granularity);
|
||||
|
||||
/// Checks whether this spanner has its own colors.
|
||||
virtual bool hasOwnColors() const;
|
||||
|
||||
/// Checks whether this spanner has its own materials.
|
||||
virtual bool hasOwnMaterials() const;
|
||||
|
||||
/// Checks whether the spanner contains the specified point.
|
||||
virtual bool contains(const glm::vec3& point);
|
||||
|
||||
/// Retrieves the color at the specified point.
|
||||
virtual QRgb getColorAt(const glm::vec3& point);
|
||||
|
||||
/// Retrieves the material at the specified point.
|
||||
virtual int getMaterialAt(const glm::vec3& point);
|
||||
|
||||
/// Retrieves a reference to the list of materials.
|
||||
virtual QVector<SharedObjectPointer>& getMaterials();
|
||||
|
||||
/// Finds the intersection, if any, between the specified line segment and the spanner.
|
||||
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
|
||||
|
||||
signals:
|
||||
|
||||
void boundsWillChange();
|
||||
void boundsChanged(const Box& bounds);
|
||||
|
||||
protected:
|
||||
|
||||
SpannerRenderer* _renderer;
|
||||
|
||||
/// Returns the name of the class to instantiate in order to render this spanner.
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private:
|
||||
|
||||
Box _bounds;
|
||||
float _placementGranularity;
|
||||
float _voxelizationGranularity;
|
||||
bool _merged;
|
||||
bool _willBeVoxelized;
|
||||
QHash<QThread*, int> _lastVisits; ///< last visit identifiers for each thread
|
||||
QMutex _lastVisitsMutex;
|
||||
|
||||
static QAtomicInt _nextVisit; ///< 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(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
protected:
|
||||
|
||||
Spanner* _spanner;
|
||||
};
|
||||
|
||||
/// 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::quat 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::quat& rotation);
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
void setScale(float scale);
|
||||
float getScale() const { return _scale; }
|
||||
|
||||
signals:
|
||||
|
||||
void translationChanged(const glm::vec3& translation);
|
||||
void rotationChanged(const glm::quat& rotation);
|
||||
void scaleChanged(float scale);
|
||||
|
||||
private:
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
float _scale;
|
||||
};
|
||||
|
||||
/// A transformable object with a color.
|
||||
class ColorTransformable : public Transformable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false)
|
||||
|
||||
public:
|
||||
|
||||
ColorTransformable();
|
||||
|
||||
void setColor(const QColor& color);
|
||||
const QColor& getColor() const { return _color; }
|
||||
|
||||
signals:
|
||||
|
||||
void colorChanged(const QColor& color);
|
||||
|
||||
protected:
|
||||
|
||||
QColor _color;
|
||||
};
|
||||
|
||||
/// A sphere.
|
||||
class Sphere : public ColorTransformable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE Sphere();
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
virtual bool contains(const glm::vec3& point);
|
||||
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void updateBounds();
|
||||
};
|
||||
|
||||
/// A cuboid.
|
||||
class Cuboid : public ColorTransformable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged)
|
||||
Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE Cuboid();
|
||||
|
||||
void setAspectY(float aspectY);
|
||||
float getAspectY() const { return _aspectY; }
|
||||
|
||||
void setAspectZ(float aspectZ);
|
||||
float getAspectZ() const { return _aspectZ; }
|
||||
|
||||
virtual bool contains(const glm::vec3& point);
|
||||
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
|
||||
|
||||
signals:
|
||||
|
||||
void aspectYChanged(float aspectY);
|
||||
void aspectZChanged(float aspectZ);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void updateBoundsAndPlanes();
|
||||
|
||||
private:
|
||||
|
||||
float _aspectY;
|
||||
float _aspectZ;
|
||||
|
||||
static const int PLANE_COUNT = 6;
|
||||
glm::vec4 _planes[PLANE_COUNT];
|
||||
};
|
||||
|
||||
/// 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; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
signals:
|
||||
|
||||
void urlChanged(const QUrl& url);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private:
|
||||
|
||||
QUrl _url;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<DataBlock> DataBlockPointer;
|
||||
|
||||
/// Base class for blocks of data.
|
||||
class DataBlock : public QSharedData {
|
||||
public:
|
||||
|
||||
static const int COLOR_BYTES = 3;
|
||||
|
||||
virtual ~DataBlock();
|
||||
|
||||
void setDeltaData(const DataBlockPointer& deltaData) { _deltaData = deltaData; }
|
||||
const DataBlockPointer& getDeltaData() const { return _deltaData; }
|
||||
|
||||
void setEncodedDelta(const QByteArray& encodedDelta) { _encodedDelta = encodedDelta; }
|
||||
const QByteArray& getEncodedDelta() const { return _encodedDelta; }
|
||||
|
||||
QMutex& getEncodedDeltaMutex() { return _encodedDeltaMutex; }
|
||||
|
||||
protected:
|
||||
|
||||
QByteArray _encoded;
|
||||
QMutex _encodedMutex;
|
||||
|
||||
DataBlockPointer _deltaData;
|
||||
QByteArray _encodedDelta;
|
||||
QMutex _encodedDeltaMutex;
|
||||
|
||||
class EncodedSubdivision {
|
||||
public:
|
||||
DataBlockPointer ancestor;
|
||||
QByteArray data;
|
||||
};
|
||||
QVector<EncodedSubdivision> _encodedSubdivisions;
|
||||
QMutex _encodedSubdivisionsMutex;
|
||||
};
|
||||
|
||||
/// Base class for heightfield data blocks.
|
||||
class HeightfieldData : public DataBlock {
|
||||
public:
|
||||
|
||||
static const int SHARED_EDGE;
|
||||
|
||||
HeightfieldData(int width = 0);
|
||||
|
||||
int getWidth() const { return _width; }
|
||||
|
||||
protected:
|
||||
|
||||
int _width;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<HeightfieldHeight> HeightfieldHeightPointer;
|
||||
|
||||
/// A block of height data associated with a heightfield.
|
||||
class HeightfieldHeight : public HeightfieldData {
|
||||
public:
|
||||
|
||||
static const int HEIGHT_BORDER;
|
||||
static const int HEIGHT_EXTENSION;
|
||||
|
||||
HeightfieldHeight(int width, const QVector<quint16>& contents);
|
||||
HeightfieldHeight(Bitstream& in, int bytes);
|
||||
HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference);
|
||||
|
||||
QVector<quint16>& getContents() { return _contents; }
|
||||
|
||||
void write(Bitstream& out);
|
||||
void writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference);
|
||||
|
||||
private:
|
||||
|
||||
void read(Bitstream& in, int bytes);
|
||||
|
||||
QVector<quint16> _contents;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(HeightfieldHeightPointer)
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const HeightfieldHeightPointer& value);
|
||||
Bitstream& operator>>(Bitstream& in, HeightfieldHeightPointer& value);
|
||||
|
||||
template<> void Bitstream::writeRawDelta(const HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference);
|
||||
template<> void Bitstream::readRawDelta(HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference);
|
||||
|
||||
/// Allows editing heightfield height blocks.
|
||||
class HeightfieldHeightEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldHeightEditor(QWidget* parent = NULL);
|
||||
|
||||
const HeightfieldHeightPointer& getHeight() const { return _height; }
|
||||
|
||||
signals:
|
||||
|
||||
void heightChanged(const HeightfieldHeightPointer& height);
|
||||
|
||||
public slots:
|
||||
|
||||
void setHeight(const HeightfieldHeightPointer& height);
|
||||
|
||||
private slots:
|
||||
|
||||
void select();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
|
||||
HeightfieldHeightPointer _height;
|
||||
|
||||
QPushButton* _select;
|
||||
QPushButton* _clear;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<HeightfieldColor> HeightfieldColorPointer;
|
||||
|
||||
/// A block of color data associated with a heightfield.
|
||||
class HeightfieldColor : public HeightfieldData {
|
||||
public:
|
||||
|
||||
HeightfieldColor(int width, const QByteArray& contents);
|
||||
HeightfieldColor(Bitstream& in, int bytes);
|
||||
HeightfieldColor(Bitstream& in, int bytes, const HeightfieldColorPointer& reference);
|
||||
|
||||
QByteArray& getContents() { return _contents; }
|
||||
|
||||
void write(Bitstream& out);
|
||||
void writeDelta(Bitstream& out, const HeightfieldColorPointer& reference);
|
||||
|
||||
private:
|
||||
|
||||
void read(Bitstream& in, int bytes);
|
||||
|
||||
QByteArray _contents;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(HeightfieldColorPointer)
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const HeightfieldColorPointer& value);
|
||||
Bitstream& operator>>(Bitstream& in, HeightfieldColorPointer& value);
|
||||
|
||||
template<> void Bitstream::writeRawDelta(const HeightfieldColorPointer& value, const HeightfieldColorPointer& reference);
|
||||
template<> void Bitstream::readRawDelta(HeightfieldColorPointer& value, const HeightfieldColorPointer& reference);
|
||||
|
||||
/// Allows editing heightfield color blocks.
|
||||
class HeightfieldColorEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged USER true)
|
||||
|
||||
public:
|
||||
|
||||
HeightfieldColorEditor(QWidget* parent = NULL);
|
||||
|
||||
const HeightfieldColorPointer& getColor() const { return _color; }
|
||||
|
||||
signals:
|
||||
|
||||
void colorChanged(const HeightfieldColorPointer& color);
|
||||
|
||||
public slots:
|
||||
|
||||
void setColor(const HeightfieldColorPointer& color);
|
||||
|
||||
private slots:
|
||||
|
||||
void select();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
|
||||
HeightfieldColorPointer _color;
|
||||
|
||||
QPushButton* _select;
|
||||
QPushButton* _clear;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<HeightfieldMaterial> HeightfieldMaterialPointer;
|
||||
|
||||
/// A block of material data associated with a heightfield.
|
||||
class HeightfieldMaterial : public HeightfieldData {
|
||||
public:
|
||||
|
||||
HeightfieldMaterial(int width, const QByteArray& contents, const QVector<SharedObjectPointer>& materials);
|
||||
HeightfieldMaterial(Bitstream& in, int bytes);
|
||||
HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference);
|
||||
|
||||
QByteArray& getContents() { return _contents; }
|
||||
QVector<SharedObjectPointer>& getMaterials() { return _materials; }
|
||||
|
||||
void write(Bitstream& out);
|
||||
void writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference);
|
||||
|
||||
private:
|
||||
|
||||
void read(Bitstream& in, int bytes);
|
||||
|
||||
QByteArray _contents;
|
||||
QVector<SharedObjectPointer> _materials;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(HeightfieldMaterialPointer)
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const HeightfieldMaterialPointer& value);
|
||||
Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value);
|
||||
|
||||
template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference);
|
||||
template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference);
|
||||
|
||||
/// Contains the description of a material.
|
||||
class MaterialObject : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QUrl diffuse MEMBER _diffuse)
|
||||
Q_PROPERTY(float scaleS MEMBER _scaleS)
|
||||
Q_PROPERTY(float scaleT MEMBER _scaleT)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE MaterialObject();
|
||||
|
||||
const QUrl& getDiffuse() const { return _diffuse; }
|
||||
|
||||
float getScaleS() const { return _scaleS; }
|
||||
float getScaleT() const { return _scaleT; }
|
||||
|
||||
private:
|
||||
|
||||
QUrl _diffuse;
|
||||
float _scaleS;
|
||||
float _scaleT;
|
||||
};
|
||||
|
||||
/// Finds a material index for the supplied material in the provided list, adding an entry if necessary. Returns -1
|
||||
/// on failure (no room to add new material).
|
||||
int getMaterialIndex(const SharedObjectPointer& material, QVector<SharedObjectPointer>& materials);
|
||||
|
||||
typedef QExplicitlySharedDataPointer<HeightfieldStack> HeightfieldStackPointer;
|
||||
|
||||
/// A single column within a stack block.
|
||||
class StackArray : public QByteArray {
|
||||
public:
|
||||
|
||||
#pragma pack(push, 1)
|
||||
/// A single entry within the array.
|
||||
class Entry {
|
||||
public:
|
||||
quint32 color;
|
||||
uchar material;
|
||||
quint32 hermiteX;
|
||||
quint32 hermiteY;
|
||||
quint32 hermiteZ;
|
||||
|
||||
Entry();
|
||||
|
||||
bool isSet() const { return qAlpha(color) != 0; }
|
||||
|
||||
bool isZero() const;
|
||||
bool isMergeable(const Entry& other) const;
|
||||
|
||||
void setHermiteX(const glm::vec3& normal, float position);
|
||||
float getHermiteX(glm::vec3& normal) const;
|
||||
|
||||
void setHermiteY(const glm::vec3& normal, float position);
|
||||
float getHermiteY(glm::vec3& normal) const;
|
||||
|
||||
void setHermiteZ(const glm::vec3& normal, float position);
|
||||
float getHermiteZ(glm::vec3& normal) const;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static int getSize(int entries) { return (entries == 0) ? 0 : sizeof(quint16) + sizeof(Entry) * entries; }
|
||||
|
||||
StackArray() : QByteArray() { }
|
||||
StackArray(int entries) : QByteArray(getSize(entries), 0) { }
|
||||
StackArray(const QByteArray& other) : QByteArray(other) { }
|
||||
StackArray(const char* src, int bytes) : QByteArray(src, bytes) { }
|
||||
|
||||
int getPosition() const { return *(const quint16*)constData(); }
|
||||
void setPosition(int position) { *(quint16*)data() = position; }
|
||||
|
||||
quint16& getPositionRef() { return *(quint16*)data(); }
|
||||
|
||||
int getEntryCount() const { return isEmpty() ? 0 : (size() - sizeof(quint16)) / sizeof(Entry); }
|
||||
|
||||
Entry* getEntryData() { return (Entry*)(data() + sizeof(quint16)); }
|
||||
const Entry* getEntryData() const { return (const Entry*)(constData() + sizeof(quint16)); }
|
||||
|
||||
int getEntryAlpha(int y, float heightfieldHeight = 0.0f) const;
|
||||
|
||||
Entry& getEntry(int y, float heightfieldHeight = 0.0f);
|
||||
const Entry& getEntry(int y, float heightfieldHeight = 0.0f) const;
|
||||
|
||||
void getExtents(int& minimumY, int& maximumY) const;
|
||||
|
||||
bool hasSetEntries() const;
|
||||
|
||||
void removeEntries(int position, int count) { remove(sizeof(quint16) + position * sizeof(Entry), count * sizeof(Entry)); }
|
||||
};
|
||||
|
||||
/// A block of stack data associated with a heightfield.
|
||||
class HeightfieldStack : public HeightfieldData {
|
||||
public:
|
||||
|
||||
HeightfieldStack(int width, const QVector<StackArray>& contents, const QVector<SharedObjectPointer>& materials);
|
||||
HeightfieldStack(Bitstream& in, int bytes);
|
||||
HeightfieldStack(Bitstream& in, int bytes, const HeightfieldStackPointer& reference);
|
||||
|
||||
QVector<StackArray>& getContents() { return _contents; }
|
||||
QVector<SharedObjectPointer>& getMaterials() { return _materials; }
|
||||
|
||||
void write(Bitstream& out);
|
||||
void writeDelta(Bitstream& out, const HeightfieldStackPointer& reference);
|
||||
|
||||
private:
|
||||
|
||||
void read(Bitstream& in, int bytes);
|
||||
|
||||
QVector<StackArray> _contents;
|
||||
QVector<SharedObjectPointer> _materials;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(HeightfieldStackPointer)
|
||||
|
||||
Bitstream& operator<<(Bitstream& out, const HeightfieldStackPointer& value);
|
||||
Bitstream& operator>>(Bitstream& in, HeightfieldStackPointer& value);
|
||||
|
||||
template<> void Bitstream::writeRawDelta(const HeightfieldStackPointer& value, const HeightfieldStackPointer& reference);
|
||||
template<> void Bitstream::readRawDelta(HeightfieldStackPointer& value, const HeightfieldStackPointer& reference);
|
||||
|
||||
typedef QExplicitlySharedDataPointer<HeightfieldNode> HeightfieldNodePointer;
|
||||
|
||||
/// Holds the base state used in streaming heightfield data.
|
||||
class HeightfieldStreamBase {
|
||||
public:
|
||||
Bitstream& stream;
|
||||
const MetavoxelLOD& lod;
|
||||
const MetavoxelLOD& referenceLOD;
|
||||
};
|
||||
|
||||
/// Holds the state used in streaming a heightfield node.
|
||||
class HeightfieldStreamState {
|
||||
public:
|
||||
HeightfieldStreamBase& base;
|
||||
glm::vec2 minimum;
|
||||
float size;
|
||||
|
||||
bool shouldSubdivide() const;
|
||||
bool shouldSubdivideReference() const;
|
||||
bool becameSubdivided() const;
|
||||
bool becameSubdividedOrCollapsed() const;
|
||||
|
||||
void setMinimum(const glm::vec2& lastMinimum, int index);
|
||||
};
|
||||
|
||||
/// A node in a heightfield quadtree.
|
||||
class HeightfieldNode : public QSharedData {
|
||||
public:
|
||||
|
||||
static const int CHILD_COUNT = 4;
|
||||
|
||||
HeightfieldNode(const HeightfieldHeightPointer& height = HeightfieldHeightPointer(),
|
||||
const HeightfieldColorPointer& color = HeightfieldColorPointer(),
|
||||
const HeightfieldMaterialPointer& material = HeightfieldMaterialPointer(),
|
||||
const HeightfieldStackPointer& stack = HeightfieldStackPointer());
|
||||
|
||||
HeightfieldNode(const HeightfieldNode& other);
|
||||
|
||||
~HeightfieldNode();
|
||||
|
||||
void setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color,
|
||||
const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack);
|
||||
|
||||
void setHeight(const HeightfieldHeightPointer& height) { _height = height; }
|
||||
const HeightfieldHeightPointer& getHeight() const { return _height; }
|
||||
|
||||
void setColor(const HeightfieldColorPointer& color) { _color = color; }
|
||||
const HeightfieldColorPointer& getColor() const { return _color; }
|
||||
|
||||
void setMaterial(const HeightfieldMaterialPointer& material) { _material = material; }
|
||||
const HeightfieldMaterialPointer& getMaterial() const { return _material; }
|
||||
|
||||
void setStack(const HeightfieldStackPointer& stack) { _stack = stack; }
|
||||
const HeightfieldStackPointer& getStack() const { return _stack; }
|
||||
|
||||
void setRenderer(AbstractHeightfieldNodeRenderer* renderer) { _renderer = renderer; }
|
||||
AbstractHeightfieldNodeRenderer* getRenderer() const { return _renderer; }
|
||||
|
||||
bool isLeaf() const;
|
||||
|
||||
void setChild(int index, const HeightfieldNodePointer& child) { _children[index] = child; }
|
||||
const HeightfieldNodePointer& getChild(int index) const { return _children[index]; }
|
||||
|
||||
bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
void getRangeAfterHeightPaint(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& position, float radius, float height, float& minimum, float& maximum) const;
|
||||
|
||||
HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& position, float radius, float height, bool set, bool erase,
|
||||
float normalizeScale, float normalizeOffset, float granularity);
|
||||
|
||||
HeightfieldNode* fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& position, float radius, float granularity);
|
||||
|
||||
void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const Box& editBounds, float& minimum, float& maximum) const;
|
||||
|
||||
HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize,
|
||||
float normalizeScale, float normalizeOffset, float granularity);
|
||||
|
||||
void read(HeightfieldStreamState& state);
|
||||
void write(HeightfieldStreamState& state) const;
|
||||
|
||||
void readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state);
|
||||
void writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const;
|
||||
|
||||
HeightfieldNode* readSubdivision(HeightfieldStreamState& state);
|
||||
void writeSubdivision(HeightfieldStreamState& state) const;
|
||||
|
||||
void readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState,
|
||||
const HeightfieldNode* ancestor);
|
||||
void writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState,
|
||||
const HeightfieldNode* ancestor) const;
|
||||
|
||||
private:
|
||||
|
||||
void clearChildren();
|
||||
void mergeChildren(bool height = true, bool colorMaterial = true);
|
||||
|
||||
QRgb getColorAt(const glm::vec3& location) const;
|
||||
int getMaterialAt(const glm::vec3& location) const;
|
||||
|
||||
void maybeRenormalize(const glm::vec3& scale, float normalizeScale, float normalizeOffset, int innerStackWidth,
|
||||
QVector<quint16>& heightContents, QVector<StackArray>& stackContents);
|
||||
|
||||
bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float boundsDistance, float& distance) const;
|
||||
|
||||
HeightfieldNode* subdivide(const QVector<quint16>& heightContents, const QVector<StackArray>& stackContents) const;
|
||||
|
||||
HeightfieldHeightPointer _height;
|
||||
HeightfieldColorPointer _color;
|
||||
HeightfieldMaterialPointer _material;
|
||||
HeightfieldStackPointer _stack;
|
||||
|
||||
HeightfieldNodePointer _children[CHILD_COUNT];
|
||||
|
||||
AbstractHeightfieldNodeRenderer* _renderer;
|
||||
};
|
||||
|
||||
/// Base class for heightfield node rendering.
|
||||
class AbstractHeightfieldNodeRenderer {
|
||||
public:
|
||||
|
||||
virtual ~AbstractHeightfieldNodeRenderer();
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||
const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const;
|
||||
};
|
||||
|
||||
/// A heightfield represented as a spanner.
|
||||
class Heightfield : public Transformable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged)
|
||||
Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged)
|
||||
Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged STORED false)
|
||||
Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged STORED false)
|
||||
Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged STORED false
|
||||
DESIGNABLE false)
|
||||
Q_PROPERTY(HeightfieldStackPointer stack MEMBER _stack WRITE setStack NOTIFY stackChanged STORED false
|
||||
DESIGNABLE false)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE Heightfield();
|
||||
|
||||
void setAspectY(float aspectY);
|
||||
float getAspectY() const { return _aspectY; }
|
||||
|
||||
void setAspectZ(float aspectZ);
|
||||
float getAspectZ() const { return _aspectZ; }
|
||||
|
||||
void setHeight(const HeightfieldHeightPointer& height);
|
||||
const HeightfieldHeightPointer& getHeight() const { return _height; }
|
||||
|
||||
void setColor(const HeightfieldColorPointer& color);
|
||||
const HeightfieldColorPointer& getColor() const { return _color; }
|
||||
|
||||
void setMaterial(const HeightfieldMaterialPointer& material);
|
||||
const HeightfieldMaterialPointer& getMaterial() const { return _material; }
|
||||
|
||||
void setStack(const HeightfieldStackPointer& stack);
|
||||
const HeightfieldStackPointer& getStack() const { return _stack; }
|
||||
|
||||
void setRoot(const HeightfieldNodePointer& root) { _root = root; }
|
||||
const HeightfieldNodePointer& getRoot() const { return _root; }
|
||||
|
||||
MetavoxelLOD transformLOD(const MetavoxelLOD& lod) const;
|
||||
|
||||
virtual SharedObject* clone(bool withID = false, SharedObject* target = NULL) const;
|
||||
|
||||
virtual bool isHeightfield() const;
|
||||
|
||||
virtual float getHeight(const glm::vec3& location) const;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height,
|
||||
bool set, bool erase, float granularity);
|
||||
|
||||
virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity);
|
||||
|
||||
virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
|
||||
const QColor& color, bool paint, bool voxelize, float granularity);
|
||||
|
||||
virtual bool hasOwnColors() const;
|
||||
virtual bool hasOwnMaterials() const;
|
||||
virtual QRgb getColorAt(const glm::vec3& point);
|
||||
virtual int getMaterialAt(const glm::vec3& point);
|
||||
virtual QVector<SharedObjectPointer>& getMaterials();
|
||||
|
||||
virtual bool contains(const glm::vec3& point);
|
||||
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
|
||||
|
||||
virtual void writeExtra(Bitstream& out) const;
|
||||
virtual void readExtra(Bitstream& in, bool reread);
|
||||
virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const;
|
||||
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread);
|
||||
virtual void maybeWriteSubdivision(Bitstream& out);
|
||||
virtual SharedObject* readSubdivision(Bitstream& in);
|
||||
|
||||
signals:
|
||||
|
||||
void aspectYChanged(float aspectY);
|
||||
void aspectZChanged(float aspectZ);
|
||||
void heightChanged(const HeightfieldHeightPointer& height);
|
||||
void colorChanged(const HeightfieldColorPointer& color);
|
||||
void materialChanged(const HeightfieldMaterialPointer& material);
|
||||
void stackChanged(const HeightfieldStackPointer& stack);
|
||||
|
||||
protected:
|
||||
|
||||
virtual QByteArray getRendererClassName() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void updateBounds();
|
||||
void updateRoot();
|
||||
|
||||
private:
|
||||
|
||||
Heightfield* prepareEdit(float minimumValue, float maximumValue, float& normalizeScale, float& normalizeOffset);
|
||||
|
||||
float _aspectY;
|
||||
float _aspectZ;
|
||||
|
||||
HeightfieldHeightPointer _height;
|
||||
HeightfieldColorPointer _color;
|
||||
HeightfieldMaterialPointer _material;
|
||||
HeightfieldStackPointer _stack;
|
||||
|
||||
HeightfieldNodePointer _root;
|
||||
};
|
||||
|
||||
#endif // hifi_Spanner_h
|
|
@ -27,8 +27,6 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
|
|||
return Assignment::AgentType;
|
||||
case NodeType::EntityServer:
|
||||
return Assignment::EntityServerType;
|
||||
case NodeType::MetavoxelServer:
|
||||
return Assignment::MetavoxelServerType;
|
||||
default:
|
||||
return Assignment::AllTypes;
|
||||
}
|
||||
|
@ -133,8 +131,6 @@ const char* Assignment::getTypeName() const {
|
|||
return "agent";
|
||||
case Assignment::EntityServerType:
|
||||
return "entity-server";
|
||||
case Assignment::MetavoxelServerType:
|
||||
return "metavoxel-server";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
AgentType,
|
||||
UNUSED_0,
|
||||
UNUSED_1,
|
||||
MetavoxelServerType,
|
||||
UNUSED_2,
|
||||
EntityServerType,
|
||||
AllTypes
|
||||
};
|
||||
|
|
|
@ -29,7 +29,6 @@ namespace NodeType {
|
|||
void NodeType::init() {
|
||||
TypeNameHash.insert(NodeType::DomainServer, "Domain Server");
|
||||
TypeNameHash.insert(NodeType::EntityServer, "Entity Server");
|
||||
TypeNameHash.insert(NodeType::MetavoxelServer, "Metavoxel Server");
|
||||
TypeNameHash.insert(NodeType::Agent, "Agent");
|
||||
TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer");
|
||||
TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer");
|
||||
|
|
|
@ -31,7 +31,6 @@ typedef quint8 NodeType_t;
|
|||
namespace NodeType {
|
||||
const NodeType_t DomainServer = 'D';
|
||||
const NodeType_t EntityServer = 'o'; // was ModelServer
|
||||
const NodeType_t MetavoxelServer = 'm';
|
||||
const NodeType_t EnvironmentServer = 'E';
|
||||
const NodeType_t Agent = 'I';
|
||||
const NodeType_t AudioMixer = 'M';
|
||||
|
|
|
@ -79,8 +79,6 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
return 2;
|
||||
case PacketTypeAudioStreamStats:
|
||||
return 1;
|
||||
case PacketTypeMetavoxelData:
|
||||
return 13;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
@ -114,7 +112,6 @@ QString nameForPacketType(PacketType type) {
|
|||
PACKET_TYPE_NAME_LOOKUP(PacketTypeOctreeStats);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeJurisdiction);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeJurisdictionRequest);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeMetavoxelData);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeAvatarIdentity);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeAvatarBillboard);
|
||||
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainConnectRequest);
|
||||
|
|
|
@ -42,20 +42,20 @@ enum PacketType {
|
|||
PacketTypeMuteEnvironment,
|
||||
PacketTypeAudioStreamStats,
|
||||
PacketTypeDataServerConfirm, // 20
|
||||
UNUSED_5,
|
||||
UNUSED_6,
|
||||
UNUSED_7,
|
||||
UNUSED_8,
|
||||
UNUSED_9, // 25
|
||||
UNUSED_1,
|
||||
UNUSED_2,
|
||||
UNUSED_3,
|
||||
UNUSED_4,
|
||||
UNUSED_5, // 25
|
||||
PacketTypeOctreeStats,
|
||||
PacketTypeJurisdiction,
|
||||
PacketTypeJurisdictionRequest,
|
||||
UNUSED_1,
|
||||
UNUSED_2, // 30
|
||||
UNUSED_3,
|
||||
UNUSED_4,
|
||||
UNUSED_6,
|
||||
UNUSED_7, // 30
|
||||
UNUSED_8,
|
||||
UNUSED_9,
|
||||
PacketTypeNoisyMute,
|
||||
PacketTypeMetavoxelData,
|
||||
UNUSED_10,
|
||||
PacketTypeAvatarIdentity, // 35
|
||||
PacketTypeAvatarBillboard,
|
||||
PacketTypeDomainConnectRequest,
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
class AbstractViewStateInterface;
|
||||
class PostLightingRenderable;
|
||||
|
||||
/// Handles deferred lighting for the bits that require it (voxels, metavoxels...)
|
||||
/// Handles deferred lighting for the bits that require it (voxels...)
|
||||
class DeferredLightingEffect : public Dependency {
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@ add_dependency_external_projects(glm)
|
|||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
|
||||
link_hifi_libraries(shared octree gpu model fbx entities animation audio physics metavoxels)
|
||||
link_hifi_libraries(shared octree gpu model fbx entities animation audio physics)
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include <AudioConstants.h>
|
||||
#include <AudioEffectOptions.h>
|
||||
#include <AvatarData.h>
|
||||
#include <Bitstream.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
@ -324,7 +323,6 @@ void ScriptEngine::init() {
|
|||
registerAnimationTypes(this);
|
||||
registerAvatarTypes(this);
|
||||
registerAudioMetaTypes(this);
|
||||
Bitstream::registerTypes(this);
|
||||
|
||||
qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue);
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
set(TARGET_NAME metavoxel-tests)
|
||||
|
||||
auto_mtc()
|
||||
|
||||
setup_hifi_project(Network Script Widgets)
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(metavoxels networking shared)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
File diff suppressed because it is too large
Load diff
|
@ -1,251 +0,0 @@
|
|||
//
|
||||
// MetavoxelTests.h
|
||||
// tests/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/7/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MetavoxelTests_h
|
||||
#define hifi_MetavoxelTests_h
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QVariantList>
|
||||
|
||||
#include <Endpoint.h>
|
||||
#include <ScriptCache.h>
|
||||
|
||||
class SequencedTestMessage;
|
||||
|
||||
/// Tests various aspects of the metavoxel library.
|
||||
class MetavoxelTests : public QCoreApplication {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelTests(int& argc, char** argv);
|
||||
|
||||
/// Performs our various tests.
|
||||
/// \return true if any of the tests failed.
|
||||
bool run();
|
||||
};
|
||||
|
||||
/// Represents a simulated endpoint.
|
||||
class TestEndpoint : public Endpoint {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
enum Mode { BASIC_PEER_MODE, CONGESTION_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
|
||||
|
||||
TestEndpoint(Mode mode = BASIC_PEER_MODE);
|
||||
|
||||
void setOther(TestEndpoint* other) { _other = other; }
|
||||
|
||||
/// Perform a simulation step.
|
||||
/// \return true if failure was detected
|
||||
bool simulate(int iterationNumber);
|
||||
|
||||
virtual int parseData(const QByteArray& packet);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void sendDatagram(const QByteArray& data);
|
||||
virtual void readMessage(Bitstream& in);
|
||||
|
||||
virtual void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
virtual PacketRecord* maybeCreateSendRecord() const;
|
||||
virtual PacketRecord* maybeCreateReceiveRecord() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void handleHighPriorityMessage(const QVariant& message);
|
||||
void handleReliableMessage(const QVariant& message, Bitstream& in);
|
||||
void readReliableChannel();
|
||||
void checkReliableDeltaReceived();
|
||||
|
||||
private:
|
||||
|
||||
void compareMetavoxelData();
|
||||
|
||||
Mode _mode;
|
||||
|
||||
SharedObjectPointer _localState;
|
||||
SharedObjectPointer _remoteState;
|
||||
|
||||
MetavoxelData _data;
|
||||
MetavoxelLOD _dataLOD;
|
||||
MetavoxelData _remoteData;
|
||||
MetavoxelLOD _remoteDataLOD;
|
||||
MetavoxelLOD _lod;
|
||||
|
||||
SharedObjectPointer _sphere;
|
||||
|
||||
TestEndpoint* _other;
|
||||
|
||||
typedef QPair<QByteArray, int> ByteArrayIntPair;
|
||||
QList<ByteArrayIntPair> _delayedDatagrams;
|
||||
|
||||
typedef QVector<QByteArray> ByteArrayVector;
|
||||
QList<ByteArrayVector> _pipeline;
|
||||
int _remainingPipelineCapacity;
|
||||
|
||||
float _highPriorityMessagesToSend;
|
||||
QVariantList _highPriorityMessagesSent;
|
||||
QList<SequencedTestMessage> _unreliableMessagesSent;
|
||||
float _reliableMessagesToSend;
|
||||
QVariantList _reliableMessagesSent;
|
||||
CircularBuffer _dataStreamed;
|
||||
|
||||
ReliableChannel* _reliableDeltaChannel;
|
||||
int _reliableDeltaReceivedOffset;
|
||||
MetavoxelData _reliableDeltaData;
|
||||
MetavoxelLOD _reliableDeltaLOD;
|
||||
Bitstream::WriteMappings _reliableDeltaWriteMappings;
|
||||
int _reliableDeltaID;
|
||||
};
|
||||
|
||||
/// A simple shared object.
|
||||
class TestSharedObjectA : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_ENUMS(TestEnum)
|
||||
Q_FLAGS(TestFlag TestFlags)
|
||||
Q_PROPERTY(float foo READ getFoo WRITE setFoo NOTIFY fooChanged)
|
||||
Q_PROPERTY(TestEnum baz READ getBaz WRITE setBaz)
|
||||
Q_PROPERTY(TestFlags bong READ getBong WRITE setBong)
|
||||
Q_PROPERTY(QScriptValue bizzle READ getBizzle WRITE setBizzle)
|
||||
|
||||
public:
|
||||
|
||||
enum TestEnum { FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM };
|
||||
|
||||
enum TestFlag { NO_TEST_FLAGS = 0x0, FIRST_TEST_FLAG = 0x01, SECOND_TEST_FLAG = 0x02, THIRD_TEST_FLAG = 0x04 };
|
||||
Q_DECLARE_FLAGS(TestFlags, TestFlag)
|
||||
|
||||
Q_INVOKABLE TestSharedObjectA(float foo = 0.0f, TestEnum baz = FIRST_TEST_ENUM, TestFlags bong = 0);
|
||||
virtual ~TestSharedObjectA();
|
||||
|
||||
void setFoo(float foo);
|
||||
float getFoo() const { return _foo; }
|
||||
|
||||
void setBaz(TestEnum baz) { _baz = baz; }
|
||||
TestEnum getBaz() const { return _baz; }
|
||||
|
||||
void setBong(TestFlags bong) { _bong = bong; }
|
||||
TestFlags getBong() const { return _bong; }
|
||||
|
||||
void setBizzle(const QScriptValue& bizzle) { _bizzle = bizzle; }
|
||||
const QScriptValue& getBizzle() const { return _bizzle; }
|
||||
|
||||
signals:
|
||||
|
||||
void fooChanged(float foo);
|
||||
|
||||
private:
|
||||
|
||||
float _foo;
|
||||
TestEnum _baz;
|
||||
TestFlags _bong;
|
||||
QScriptValue _bizzle;
|
||||
};
|
||||
|
||||
DECLARE_ENUM_METATYPE(TestSharedObjectA, TestEnum)
|
||||
|
||||
/// Another simple shared object.
|
||||
class TestSharedObjectB : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_ENUMS(TestEnum)
|
||||
Q_FLAGS(TestFlag TestFlags)
|
||||
Q_PROPERTY(float foo READ getFoo WRITE setFoo)
|
||||
Q_PROPERTY(QByteArray bar READ getBar WRITE setBar)
|
||||
Q_PROPERTY(TestEnum baz READ getBaz WRITE setBaz)
|
||||
Q_PROPERTY(TestFlags bong READ getBong WRITE setBong)
|
||||
|
||||
public:
|
||||
|
||||
enum TestEnum { ZEROTH_TEST_ENUM, FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM, FOURTH_TEST_ENUM };
|
||||
|
||||
enum TestFlag { NO_TEST_FLAGS = 0x0, ZEROTH_TEST_FLAG = 0x01, FIRST_TEST_FLAG = 0x02,
|
||||
SECOND_TEST_FLAG = 0x04, THIRD_TEST_FLAG = 0x08, FOURTH_TEST_FLAG = 0x10 };
|
||||
Q_DECLARE_FLAGS(TestFlags, TestFlag)
|
||||
|
||||
Q_INVOKABLE TestSharedObjectB(float foo = 0.0f, const QByteArray& bar = QByteArray(),
|
||||
TestEnum baz = FIRST_TEST_ENUM, TestFlags bong = 0);
|
||||
virtual ~TestSharedObjectB();
|
||||
|
||||
void setFoo(float foo) { _foo = foo; }
|
||||
float getFoo() const { return _foo; }
|
||||
|
||||
void setBar(const QByteArray& bar) { _bar = bar; }
|
||||
const QByteArray& getBar() const { return _bar; }
|
||||
|
||||
void setBaz(TestEnum baz) { _baz = baz; }
|
||||
TestEnum getBaz() const { return _baz; }
|
||||
|
||||
void setBong(TestFlags bong) { _bong = bong; }
|
||||
TestFlags getBong() const { return _bong; }
|
||||
|
||||
private:
|
||||
|
||||
float _foo;
|
||||
QByteArray _bar;
|
||||
TestEnum _baz;
|
||||
TestFlags _bong;
|
||||
};
|
||||
|
||||
/// A simple test message.
|
||||
class TestMessageA {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM bool foo;
|
||||
STREAM int bar;
|
||||
STREAM float baz;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(TestMessageA)
|
||||
|
||||
// Another simple test message.
|
||||
class TestMessageB {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM QByteArray foo;
|
||||
STREAM SharedObjectPointer bar;
|
||||
STREAM TestSharedObjectA::TestEnum baz;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(TestMessageB)
|
||||
|
||||
// A test message that demonstrates inheritance and composition.
|
||||
class TestMessageC : STREAM public TestMessageA {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM TestMessageB bong;
|
||||
STREAM QScriptValue bizzle;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(TestMessageC)
|
||||
|
||||
/// Combines a sequence number with a submessage; used for testing unreliable transport.
|
||||
class SequencedTestMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM int sequenceNumber;
|
||||
STREAM QVariant submessage;
|
||||
STREAM SharedObjectPointer state;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(SequencedTestMessage)
|
||||
|
||||
#endif // hifi_MetavoxelTests_h
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tests/metavoxels/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 2/7/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "MetavoxelTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return MetavoxelTests(argc, argv).run();
|
||||
}
|
|
@ -3,6 +3,6 @@ set(TARGET_NAME octree-tests)
|
|||
setup_hifi_project(Script Network)
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared octree gpu model fbx metavoxels networking entities avatars audio animation script-engine physics)
|
||||
link_hifi_libraries(shared octree gpu model fbx networking entities avatars audio animation script-engine physics)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
|
@ -1,6 +1,4 @@
|
|||
# add the tool directories
|
||||
add_subdirectory(bitstream2json)
|
||||
add_subdirectory(json2bitstream)
|
||||
add_subdirectory(mtc)
|
||||
add_subdirectory(scribe)
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
set(TARGET_NAME bitstream2json)
|
||||
setup_hifi_project(Widgets Script)
|
||||
|
||||
link_hifi_libraries(metavoxels)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tools/bitstream2json/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/17/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
|
||||
#include <AttributeRegistry.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
// need the core application for the script engine
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc < 3) {
|
||||
cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
|
||||
return 0;
|
||||
}
|
||||
QFile inputFile(argv[1]);
|
||||
if (!inputFile.open(QIODevice::ReadOnly)) {
|
||||
cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QDataStream inputData(&inputFile);
|
||||
Bitstream input(inputData, Bitstream::FULL_METADATA, Bitstream::ALL_GENERICS);
|
||||
|
||||
QFile outputFile(argv[2]);
|
||||
if (!outputFile.open(QIODevice::WriteOnly)) {
|
||||
cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
JSONWriter output;
|
||||
|
||||
if (argc < 4) {
|
||||
// default type is a single QVariant
|
||||
QVariant value;
|
||||
input >> value;
|
||||
output << value;
|
||||
|
||||
} else {
|
||||
for (int i = 3; i < argc; i++) {
|
||||
int type = QMetaType::type(argv[i]);
|
||||
if (type == QMetaType::UnknownType) {
|
||||
cerr << "Unknown type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
|
||||
if (!streamer) {
|
||||
cerr << "Non-streamable type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
QVariant value = streamer->read(input);
|
||||
output.appendToContents(streamer->getJSONData(output, value));
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.write(output.getDocument().toJson());
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
set(TARGET_NAME json2bitstream)
|
||||
setup_hifi_project(Widgets Script)
|
||||
|
||||
link_hifi_libraries(metavoxels)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
|
@ -1,76 +0,0 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tools/json2bitstream/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/17/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
|
||||
#include <AttributeRegistry.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
// need the core application for the script engine
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc < 3) {
|
||||
cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
|
||||
return 0;
|
||||
}
|
||||
QFile inputFile(argv[1]);
|
||||
if (!inputFile.open(QIODevice::ReadOnly)) {
|
||||
cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QJsonParseError error;
|
||||
QJsonDocument document = QJsonDocument::fromJson(inputFile.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
cerr << "Failed to read input file: " << error.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
JSONReader input(document, Bitstream::ALL_GENERICS);
|
||||
|
||||
QFile outputFile(argv[2]);
|
||||
if (!outputFile.open(QIODevice::WriteOnly)) {
|
||||
cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QDataStream outputData(&outputFile);
|
||||
Bitstream output(outputData, Bitstream::FULL_METADATA);
|
||||
|
||||
if (argc < 4) {
|
||||
// default type is a single QVariant
|
||||
QVariant value;
|
||||
input >> value;
|
||||
output << value;
|
||||
|
||||
} else {
|
||||
for (int i = 3; i < argc; i++) {
|
||||
int type = QMetaType::type(argv[i]);
|
||||
if (type == QMetaType::UnknownType) {
|
||||
cerr << "Unknown type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
|
||||
if (!streamer) {
|
||||
cerr << "Non-streamable type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
QVariant value;
|
||||
streamer->putJSONData(input, input.retrieveNextFromContents(), value);
|
||||
streamer->write(output, value);
|
||||
}
|
||||
}
|
||||
output.flush();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue