3
0
Fork 0
mirror of https://github.com/JulianGro/overte.git synced 2025-04-30 12:42:58 +02:00

drafting the render engine

This commit is contained in:
Sam Gateau 2015-03-05 16:57:10 -08:00
commit c6bd1ee671
86 changed files with 345 additions and 26418 deletions
BUILD.mdCMakeLists.txt
assignment-client
cmake
externals/qxmpp
modules
domain-server/src
examples
interface
libraries
tests
tools

View file

@ -14,12 +14,10 @@
* [gverb](https://github.com/highfidelity/gverb)
* [Soxr](http://sourceforge.net/projects/soxr/) ~> 0.1.1
The following external projects are optional dependencies. You can indicate to CMake that you would like to include them by passing -DGET_$NAME=1 when running a clean CMake build. For example, to get CMake to download and compile QXmpp you would pass -DGET_QXMPP=1.
The following external projects are optional dependencies. You can indicate to CMake that you would like to include them by passing -DGET_$NAME=1 when running a clean CMake build. For example, to get CMake to download and compile SDL2 you would pass -DGET_SDL2=1.
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
* Enables game controller support in Interface
* [QXmpp](https://github.com/qxmpp-project/qxmpp) ~> 0.7.6
* Enables text chat support in Interface
The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build-ext` directory in each of the subfolders for each external project.

View file

@ -164,7 +164,6 @@ if (WIN32)
endif ()
option(GET_SDL2 "Get SDL2 library automatically as external project" 0)
option(GET_QXMPP "GET Qxmpp library automatically as external project" 0)
if (WIN32)
add_paths_to_fixup_libs("${QT_DIR}/bin")

View file

@ -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
)

View file

@ -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:

View file

@ -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.";
}

View file

@ -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

View file

@ -1,13 +0,0 @@
diff --git a/qxmpp-0.7.6/src/src.pro b/qxmpp-0.7.6-patch/src/src.pro
index 954738c..8404c8c 100644
--- a/qxmpp-0.7.6/src/src.pro
+++ b/qxmpp-0.7.6-patch/src/src.pro
@@ -4,7 +4,7 @@ QT -= gui
TEMPLATE = lib
-CONFIG += $$QXMPP_LIBRARY_TYPE
+CONFIG += $$QXMPP_LIBRARY_TYPE c++11
DEFINES += QXMPP_BUILD
DEFINES += $$QXMPP_INTERNAL_DEFINES
INCLUDEPATH += $$QXMPP_INCLUDEPATH $$QXMPP_INTERNAL_INCLUDES

View file

@ -1,45 +0,0 @@
#
# FindQxmpp.cmake
#
# Try to find the qxmpp library
#
# You can provide a QXMPP_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# QXMPP_FOUND - system found qxmpp
# QXMPP_INCLUDE_DIRS - the qxmpp include directory
# QXMPP_LIBRARIES - Link this to use qxmpp
#
# Created on 3/10/2014 by Stephen Birarda
# 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("qxmpp")
find_path(QXMPP_INCLUDE_DIRS qxmpp/QXmppClient.h PATH_SUFFIXES include HINTS ${QXMPP_SEARCH_DIRS})
find_library(QXMPP_LIBRARY_RELEASE NAMES qxmpp PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS})
find_library(QXMPP_LIBRARY_DEBUG NAMES qxmpp_d PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS})
if (WIN32)
find_path(QXMPP_DLL_PATH NAMES qxmpp.dll PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS})
endif ()
find_package(Qt5 COMPONENTS Xml REQUIRED)
include(SelectLibraryConfigurations)
select_library_configurations(QXMPP)
set(QXMPP_LIBRARIES "${QXMPP_LIBRARY}" Qt5::Xml)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QXmpp DEFAULT_MSG QXMPP_INCLUDE_DIRS QXMPP_LIBRARIES QXMPP_LIBRARY)
if (QXMPP_DLL_PATH)
add_paths_to_fixup_libs(${QXMPP_DLL_PATH})
endif ()

View file

@ -566,8 +566,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) {

View file

@ -1,57 +0,0 @@
//
// globalServicesExample.js
// examples
//
// Created by Thijs Wenker on 9/12/14.
// Copyright 2014 High Fidelity, Inc.
//
// Example usage of the GlobalServices object. You could use it to make your own chatbox.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function onConnected() {
if (GlobalServices.onlineUsers.length > 0) {
sendMessageForm()
return;
}
Script.setTimeout(function() { sendMessageForm(); }, 5000);
}
function onDisconnected(reason) {
switch(reason) {
case "logout":
Window.alert("logged out!");
break;
}
}
function onOnlineUsersChanged(users) {
print(users);
}
function onIncommingMessage(user, message) {
print(user + ": " + message);
if (message === "hello") {
GlobalServices.chat("hello, @" + user + "!");
}
}
function sendMessageForm() {
var form =
[
{ label: "To:", options: ["(noone)"].concat(GlobalServices.onlineUsers) },
{ label: "Message:", value: "Enter message here" }
];
if (Window.form("Send message on public chat", form)) {
GlobalServices.chat(form[0].value == "(noone)" ? form[1].value : "@" + form[0].value + ", " + form[1].value);
}
}
GlobalServices.connected.connect(onConnected);
GlobalServices.disconnected.connect(onDisconnected);
GlobalServices.onlineUsersChanged.connect(onOnlineUsersChanged);
GlobalServices.incomingMessage.connect(onIncommingMessage);

View file

@ -1,27 +0,0 @@
//
// loadScriptFromMessage.js
// examples
//
// Created by Thijs Wenker on 9/15/14.
// Copyright 2014 High Fidelity, Inc.
//
// Filters script links out of incomming messages and prompts you to run the script.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//Javascript link RegEX
const JS_LINK_REGEX = /https?:\/\/[^ ]+\.js/i;
function onIncomingMessage(user, message) {
var script_link = JS_LINK_REGEX.exec(message);
if (script_link == null) {
return;
}
if (Window.confirm("@" + user + " sent the following script:\n" + script_link + "\nwould you like to run it?")) {
Script.load(script_link);
}
}
GlobalServices.incomingMessage.connect(onIncomingMessage);

View file

@ -14,32 +14,28 @@
// This script generates notifications created via a number of ways, such as:
// keystroke:
//
// "q" returns number of users currently online (for debug purposes)
// CTRL/s for snapshot.
// CTRL/m for mic mute and unmute.
// System generated notifications:
// Displays users online at startup.
// If Screen is resized.
// Triggers notification if @MyUserName is mentioned in chat.
// Announces existing user logging out.
// Announces new user logging in.
// If mic is muted for any reason.
//
// To add a new System notification type:
//
// 1. Set the Event Connector at the bottom of the script.
// example:
// GlobalServices.incomingMessage.connect(onIncomingMessage);
// AudioDevice.muteToggled.connect(onMuteStateChanged);
//
// 2. Create a new function to produce a text string, do not include new line returns.
// example:
// function onIncomingMessage(user, message) {
// //do stuff here;
// var text = "This is a notification";
// var wrappedText = wordWrap(text);
// createNotification(wrappedText, NotificationType.SNAPSHOT);
// function onMuteStateChanged() {
// var muteState,
// muteString;
//
// muteState = AudioDevice.getMuted() ? "muted" : "unmuted";
// muteString = "Microphone is now " + muteState;
// createNotification(muteString, NotificationType.MUTE_TOGGLE);
// }
//
// This new function must call wordWrap(text) if the length of message is longer than 42 chars or unknown.
@ -54,11 +50,12 @@
// 2. Declare a text string.
// 3. Call createNotifications(text, NotificationType) parsing the text.
// example:
// var welcome;
// if (key.text == "q") { //queries number of users online
// var welcome = "There are " + GlobalServices.onlineUsers.length + " users online now.";
// createNotification(welcome, NotificationType.USERS_ONLINE);
// }
// if (key.text === "s") {
// if (ctrlIsPressed === true) {
// noteString = "Snapshot taken.";
// createNotification(noteString, NotificationType.SNAPSHOT);
// }
// }
Script.include("./libraries/globals.js");
Script.include("./libraries/soundArray.js");
@ -81,8 +78,6 @@ var frame = 0;
var ourWidth = Window.innerWidth;
var ourHeight = Window.innerHeight;
var text = "placeholder";
var last_users = GlobalServices.onlineUsers;
var users = [];
var ctrlIsPressed = false;
var ready = true;
var MENU_NAME = 'Tools > Notifications';
@ -93,19 +88,11 @@ var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"
var NotificationType = {
UNKNOWN: 0,
USER_JOINS: 1,
USER_LEAVES: 2,
MUTE_TOGGLE: 3,
CHAT_MENTION: 4,
USERS_ONLINE: 5,
SNAPSHOT: 6,
WINDOW_RESIZE: 7,
MUTE_TOGGLE: 1,
SNAPSHOT: 2,
WINDOW_RESIZE: 3,
properties: [
{ text: "User Join" },
{ text: "User Leave" },
{ text: "Mute Toggle" },
{ text: "Chat Mention" },
{ text: "Users Online" },
{ text: "Snapshot" },
{ text: "Window Resize" }
],
@ -476,15 +463,9 @@ var STARTUP_TIMEOUT = 500, // ms
startingUp = true,
startupTimer = null;
// This reports the number of users online at startup
function reportUsers() {
createNotification("Welcome! There are " + GlobalServices.onlineUsers.length + " users online now.", NotificationType.USERS_ONLINE);
}
function finishStartup() {
startingUp = false;
Script.clearTimeout(startupTimer);
reportUsers();
}
function isStartingUp() {
@ -498,42 +479,6 @@ function isStartingUp() {
return startingUp;
}
// Triggers notification if a user logs on or off
function onOnlineUsersChanged(users) {
var i;
if (!isStartingUp()) { // Skip user notifications at startup.
for (i = 0; i < users.length; i += 1) {
if (last_users.indexOf(users[i]) === -1.0) {
createNotification(users[i] + " has joined", NotificationType.USER_JOINS);
}
}
for (i = 0; i < last_users.length; i += 1) {
if (users.indexOf(last_users[i]) === -1.0) {
createNotification(last_users[i] + " has left", NotificationType.USER_LEAVES);
}
}
}
last_users = users;
}
// Triggers notification if @MyUserName is mentioned in chat and returns the message to the notification.
function onIncomingMessage(user, message) {
var myMessage,
alertMe,
thisAlert;
myMessage = message;
alertMe = "@" + GlobalServices.myUsername;
thisAlert = user + ": " + myMessage;
if (myMessage.indexOf(alertMe) > -1.0) {
CreateNotification(wordWrap(thisAlert), NotificationType.CHAT_MENTION);
}
}
// Triggers mic mute notification
function onMuteStateChanged() {
var muteState,
@ -573,20 +518,12 @@ function keyReleaseEvent(key) {
// Triggers notification on specific key driven events
function keyPressEvent(key) {
var numUsers,
welcome,
noteString;
var noteString;
if (key.key === 16777249) {
ctrlIsPressed = true;
}
if (key.text === "q") { //queries number of users online
numUsers = GlobalServices.onlineUsers.length;
welcome = "There are " + numUsers + " users online now.";
createNotification(welcome, NotificationType.USERS_ONLINE);
}
if (key.text === "s") {
if (ctrlIsPressed === true) {
noteString = "Snapshot taken.";
@ -641,8 +578,6 @@ function menuItemEvent(menuItem) {
AudioDevice.muteToggled.connect(onMuteStateChanged);
Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent);
GlobalServices.onlineUsersChanged.connect(onOnlineUsersChanged);
GlobalServices.incomingMessage.connect(onIncomingMessage);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "Sixense" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "RSSDK")
set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -115,11 +115,11 @@ 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 render fbx metavoxels networking entities avatars
link_hifi_libraries(shared octree environment gpu model render fbx networking entities avatars
audio audio-client animation script-engine physics
render-utils entities-renderer)
add_dependency_external_projects(sdl2 qxmpp)
add_dependency_external_projects(sdl2)
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
@ -175,16 +175,6 @@ if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE)
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
endif ()
if (QXMPP_FOUND AND NOT DISABLE_QXMPP AND WIN32)
if (NOT QXMPP_DLL_PATH)
# if we have no QXmpp DLL path, assume we're linking a static QXmpp on windows
add_definitions(-DQXMPP_STATIC)
else ()
# otherwise assume we are linking a dynamic QXmpp
add_definitions(-DQXMPP_SHARED)
endif ()
endif ()
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")

View file

@ -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>();
@ -417,8 +418,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);
@ -601,7 +601,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
@ -774,8 +774,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();
}
}
@ -1444,8 +1445,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
@ -1784,8 +1784,6 @@ void Application::init() {
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
_metavoxels.init();
_rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect);
connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView()));
@ -1948,16 +1946,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) {
@ -2057,7 +2045,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
@ -2807,14 +2794,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");
@ -3491,7 +3470,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);

View file

@ -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"
@ -178,7 +177,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; }
QUndoStack* getUndoStack() { return &_undoStack; }
@ -421,7 +419,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);
@ -474,8 +471,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;

View file

@ -100,9 +100,6 @@ void DatagramProcessor::processDatagrams() {
}
break;
}
case PacketTypeMetavoxelData:
nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket);
break;
case PacketTypeBulkAvatarData:
case PacketTypeKillAvatar:
case PacketTypeAvatarIdentity:

View file

@ -20,7 +20,6 @@
#include <PathUtils.h>
#include <SettingHandle.h>
#include <UserActivityLogger.h>
#include <XmppClient.h>
#include "Application.h"
#include "AccountManager.h"
@ -148,8 +147,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()));
@ -162,12 +159,9 @@ Menu::Menu() {
SLOT(setEnabled(bool)));
connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool)));
#endif
#ifdef HAVE_QXMPP
addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, Qt::Key_Backslash,
dialogsManager.data(), SLOT(showChat()));
dialogsManager->setupChat();
#endif
dialogsManager.data(), SLOT(showIRCLink()));
addActionToQMenuAndActionHash(toolsMenu,
MenuOption::ToolWindow,
@ -295,7 +289,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 +387,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);

View file

@ -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

View file

@ -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

View file

@ -1,85 +0,0 @@
//
// XmppClient.cpp
// interface/src
//
// Created by Dimitar Dobrev on 10/3/14.
// 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 <AccountManager.h>
#include "XmppClient.h"
const QString DEFAULT_XMPP_SERVER = "chat.highfidelity.io";
const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io";
#ifdef HAVE_QXMPP
XmppClient::XmppClient() :
_xmppClient(),
_xmppMUCManager()
{
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, SIGNAL(profileChanged()), this, SLOT(connectToServer()));
connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer()));
}
#else
XmppClient::XmppClient() {
}
#endif
XmppClient& XmppClient::getInstance() {
static XmppClient sharedInstance;
return sharedInstance;
}
void XmppClient::xmppConnected() {
#ifdef HAVE_QXMPP
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
_publicChatRoom->join();
emit joinedPublicChatRoom();
#endif
}
#ifdef HAVE_QXMPP
void XmppClient::xmppError(QXmppClient::Error error) {
qDebug() << "Error connnecting to XMPP for user "
<< AccountManager::getInstance().getAccountInfo().getUsername() << ": " << error;
}
#endif
void XmppClient::connectToServer() {
#ifdef HAVE_QXMPP
disconnectFromServer();
if (_xmppClient.addExtension(&_xmppMUCManager)) {
connect(&_xmppClient, SIGNAL(connected()), this, SLOT(xmppConnected()));
connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error)));
}
AccountManager& accountManager = AccountManager::getInstance();
QString user = accountManager.getAccountInfo().getUsername();
const QString& password = accountManager.getAccountInfo().getXMPPPassword();
_xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password);
#endif
}
void XmppClient::disconnectFromServer() {
#ifdef HAVE_QXMPP
if (_xmppClient.isConnected()) {
_xmppClient.disconnectFromServer();
}
#endif
}
XmppClient::XmppClient(const XmppClient& other) {
Q_UNUSED(other);
}
void XmppClient::operator =(XmppClient const& other) {
Q_UNUSED(other);
}

View file

@ -1,57 +0,0 @@
//
// XmppClient.h
// interface/src
//
// Created by Dimitar Dobrev on 10/3/14.
// 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_XmppClient_h
#define hifi_XmppClient_h
#include <QObject>
#ifdef HAVE_QXMPP
#include <qxmpp/QXmppClient.h>
#include <qxmpp/QXmppMucManager.h>
#endif
/// Generalized threaded processor for handling received inbound packets.
class XmppClient : public QObject {
Q_OBJECT
public:
static XmppClient& getInstance();
#ifdef HAVE_QXMPP
QXmppClient& getXMPPClient() { return _xmppClient; }
const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; }
#endif
signals:
void joinedPublicChatRoom();
private slots:
void xmppConnected();
#ifdef HAVE_QXMPP
void xmppError(QXmppClient::Error error);
#endif
void connectToServer();
void disconnectFromServer();
private:
XmppClient();
XmppClient(XmppClient const& other); // not implemented
void operator=(XmppClient const& other); // not implemented
#ifdef HAVE_QXMPP
QXmppClient _xmppClient;
QXmppMucManager _xmppMUCManager;
QXmppMucRoom* _publicChatRoom;
#endif
};
#endif // hifi_XmppClient_h

View file

@ -17,15 +17,89 @@
#include <QElapsedTimer>
#include "DdeFaceTracker.h"
#include "FaceshiftConstants.h"
static const QHostAddress DDE_FEATURE_POINT_SERVER_ADDR("127.0.0.1");
static const quint16 DDE_FEATURE_POINT_SERVER_PORT = 5555;
static const int NUM_EXPRESSION = 46;
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSION) * sizeof(float) + sizeof(int);
static const int NUM_EXPRESSIONS = 46;
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int);
static const int MAX_NAME_SIZE = 31;
struct Packet{
// There's almost but not quite a 1-1 correspondence between DDE's 46 and Faceshift 1.3's 48 packets.
// The best guess at mapping is to:
// - Swap L and R values
// - Skip two Faceshift values: JawChew (22) and LipsLowerDown (37)
static const int DDE_TO_FACESHIFT_MAPPING[] = {
1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14,
16,
18, 17,
19,
23,
21,
// Skip JawChew
20,
25, 24, 27, 26, 29, 28, 31, 30, 33, 32,
34, 35, 36,
// Skip LipsLowerDown
38, 39, 40, 41, 42, 43, 44, 45,
47, 46
};
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
// less than this.
static const float DDE_COEFFICIENT_SCALES[] = {
4.0f, // EyeBlink_L
4.0f, // EyeBlink_R
1.0f, // EyeSquint_L
1.0f, // EyeSquint_R
1.0f, // EyeDown_L
1.0f, // EyeDown_R
1.0f, // EyeIn_L
1.0f, // EyeIn_R
4.0f, // EyeOpen_L
4.0f, // EyeOpen_R
1.0f, // EyeOut_L
1.0f, // EyeOut_R
1.0f, // EyeUp_L
1.0f, // EyeUp_R
3.0f, // BrowsD_L
3.0f, // BrowsD_R
3.0f, // BrowsU_C
3.0f, // BrowsU_L
3.0f, // BrowsU_R
1.0f, // JawFwd
1.5f, // JawLeft
1.8f, // JawOpen
1.0f, // JawChew
1.5f, // JawRight
1.5f, // MouthLeft
1.5f, // MouthRight
1.5f, // MouthFrown_L
1.5f, // MouthFrown_R
1.5f, // MouthSmile_L
1.5f, // MouthSmile_R
1.0f, // MouthDimple_L
1.0f, // MouthDimple_R
1.0f, // LipsStretch_L
1.0f, // LipsStretch_R
1.0f, // LipsUpperClose
1.0f, // LipsLowerClose
1.0f, // LipsUpperUp
1.0f, // LipsLowerDown
1.0f, // LipsUpperOpen
1.0f, // LipsLowerOpen
2.5f, // LipsFunnel
2.0f, // LipsPucker
1.5f, // ChinLowerRaise
1.5f, // ChinUpperRaise
1.0f, // Sneer
3.0f, // Puff
1.0f, // CheekSquint_L
1.0f // CheekSquint_R
};
struct Packet {
//roughly in mm
float focal_length[1];
float translation[3];
@ -33,8 +107,9 @@ struct Packet{
//quaternion
float rotation[4];
//blendshape coefficients ranging between -0.2 and 1.5
float expressions[NUM_EXPRESSION];
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
// less than this.
float expressions[NUM_EXPRESSIONS];
//avatar id selected on the UI
int avatar_id;
@ -69,8 +144,10 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) :
_previousTranslation(glm::vec3()),
_previousRotation(glm::quat())
{
_blendshapeCoefficients.resize(NUM_EXPRESSION);
_previousExpressions.resize(NUM_EXPRESSION);
_coefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
_previousCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
_blendshapeCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
@ -172,66 +249,52 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
_headRotation = (rotation + _previousRotation) / 2.0f;
_previousRotation = rotation;
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
// less than this.packet.expressions[1]
// Eye blendshapes
static const float RELAXED_EYE_VALUE = 0.2f;
static const float EYE_OPEN_SCALE = 4.0f;
static const float EYE_BLINK_SCALE = 2.5f;
float leftEye = (packet.expressions[1] + _previousExpressions[1]) / 2.0f - RELAXED_EYE_VALUE;
float rightEye = (packet.expressions[0] + _previousExpressions[0]) / 2.0f - RELAXED_EYE_VALUE;
if (leftEye > 0.0f) {
_blendshapeCoefficients[_leftBlinkIndex] = glm::clamp(EYE_BLINK_SCALE * leftEye, 0.0f, 1.0f);
_blendshapeCoefficients[_leftEyeOpenIndex] = 0.0f;
} else {
_blendshapeCoefficients[_leftBlinkIndex] = 0.0f;
_blendshapeCoefficients[_leftEyeOpenIndex] = glm::clamp(EYE_OPEN_SCALE * -leftEye, 0.0f, 1.0f);
// Translate DDE coefficients to Faceshift compatible coefficients
for (int i = 0; i < NUM_EXPRESSIONS; i += 1) {
_coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i];
}
if (rightEye > 0.0f) {
_blendshapeCoefficients[_rightBlinkIndex] = glm::clamp(EYE_BLINK_SCALE * rightEye, 0.0f, 1.0f);
_blendshapeCoefficients[_rightEyeOpenIndex] = 0.0f;
} else {
_blendshapeCoefficients[_rightBlinkIndex] = 0.0f;
_blendshapeCoefficients[_rightEyeOpenIndex] = glm::clamp(EYE_OPEN_SCALE * -rightEye, 0.0f, 1.0f);
}
_previousExpressions[1] = packet.expressions[1];
_previousExpressions[0] = packet.expressions[0];
// Eyebrow blendshapes
static const float BROW_UP_SCALE = 3.0f;
static const float BROW_DOWN_SCALE = 3.0f;
float browCenter = (packet.expressions[17] + _previousExpressions[17]) / 2.0f;
if (browCenter > 0) {
float browUp = glm::clamp(BROW_UP_SCALE * browCenter, 0.0f, 1.0f);
_blendshapeCoefficients[_browUpCenterIndex] = browUp;
_blendshapeCoefficients[_browUpLeftIndex] = browUp;
_blendshapeCoefficients[_browDownLeftIndex] = 0.0f;
_blendshapeCoefficients[_browUpRightIndex] = browUp;
_blendshapeCoefficients[_browDownRightIndex] = 0.0f;
// Use EyeBlink values to control both EyeBlink and EyeOpen
static const float RELAXED_EYE_VALUE = 0.1f;
float leftEye = (_coefficients[_leftBlinkIndex] + _previousCoefficients[_leftBlinkIndex]) / 2.0f;
float rightEye = (_coefficients[_rightBlinkIndex] + _previousCoefficients[_rightBlinkIndex]) / 2.0f;
if (leftEye > RELAXED_EYE_VALUE) {
_coefficients[_leftBlinkIndex] = leftEye - RELAXED_EYE_VALUE;
_coefficients[_leftEyeOpenIndex] = 0.0f;
} else {
float browDown = glm::clamp(BROW_DOWN_SCALE * -browCenter, 0.0f, 1.0f);
_blendshapeCoefficients[_browUpCenterIndex] = 0.0f;
_blendshapeCoefficients[_browUpLeftIndex] = 0.0f;
_blendshapeCoefficients[_browDownLeftIndex] = browDown;
_blendshapeCoefficients[_browUpRightIndex] = 0.0f;
_blendshapeCoefficients[_browDownRightIndex] = browDown;
_coefficients[_leftBlinkIndex] = 0.0f;
_coefficients[_leftEyeOpenIndex] = RELAXED_EYE_VALUE - leftEye;
}
if (rightEye > RELAXED_EYE_VALUE) {
_coefficients[_rightBlinkIndex] = rightEye - RELAXED_EYE_VALUE;
_coefficients[_rightEyeOpenIndex] = 0.0f;
} else {
_coefficients[_rightBlinkIndex] = 0.0f;
_coefficients[_rightEyeOpenIndex] = RELAXED_EYE_VALUE - rightEye;
}
_previousExpressions[17] = packet.expressions[17];
// Mouth blendshapes
// Use BrowsU_C to control both brows' up and down
_coefficients[_browDownLeftIndex] = -_coefficients[_browUpCenterIndex];
_coefficients[_browDownRightIndex] = -_coefficients[_browUpCenterIndex];
_coefficients[_browUpLeftIndex] = _coefficients[_browUpCenterIndex];
_coefficients[_browUpRightIndex] = _coefficients[_browUpCenterIndex];
// Offset jaw open coefficient
static const float JAW_OPEN_THRESHOLD = 0.16f;
static const float JAW_OPEN_SCALE = 1.4f;
_coefficients[_jawOpenIndex] = _coefficients[_jawOpenIndex] - JAW_OPEN_THRESHOLD;
// Offset smile coefficients
static const float SMILE_THRESHOLD = 0.18f;
static const float SMILE_SCALE = 1.5f;
float smileLeft = (packet.expressions[24] + _previousExpressions[24]) / 2.0f;
float smileRight = (packet.expressions[23] + _previousExpressions[23]) / 2.0f;
_blendshapeCoefficients[_jawOpenIndex] = glm::clamp(JAW_OPEN_SCALE * (packet.expressions[21] - JAW_OPEN_THRESHOLD),
0.0f, 1.0f);
_blendshapeCoefficients[_mouthSmileLeftIndex] = glm::clamp(SMILE_SCALE * (smileLeft - SMILE_THRESHOLD), 0.0f, 1.0f);
_blendshapeCoefficients[_mouthSmileRightIndex] = glm::clamp(SMILE_SCALE * (smileRight - SMILE_THRESHOLD), 0.0f, 1.0f);
_previousExpressions[24] = packet.expressions[24];
_previousExpressions[23] = packet.expressions[23];
_coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD;
_coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD;
// Scale all coefficients
for (int i = 0; i < NUM_EXPRESSIONS; i += 1) {
_blendshapeCoefficients[i]
= glm::clamp(DDE_COEFFICIENT_SCALES[i] * (_coefficients[i] + _previousCoefficients[i]) / 2.0f, 0.0f, 1.0f);
_previousCoefficients[i] = _coefficients[i];
}
} else {
qDebug() << "[Error] DDE Face Tracker Decode Error";

View file

@ -89,10 +89,12 @@ private:
int _jawOpenIndex;
QVector<float> _coefficients;
// Previous values for simple smoothing
glm::vec3 _previousTranslation;
glm::quat _previousRotation;
QVector<float> _previousExpressions;
QVector<float> _previousCoefficients;
};
#endif // hifi_DdeFaceTracker_h

View file

@ -12,7 +12,6 @@
#include "AccountManager.h"
#include "Application.h"
#include "ResourceCache.h"
#include "XmppClient.h"
#include "GlobalServicesScriptingInterface.h"
@ -20,13 +19,6 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
#ifdef HAVE_QXMPP
const XmppClient& xmppClient = XmppClient::getInstance();
connect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::connected);
connect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::onConnected);
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
connect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
#endif // HAVE_QXMPP
_downloading = false;
connect(Application::getInstance(), &Application::renderingInWorldInterface,
@ -37,30 +29,6 @@ GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
#ifdef HAVE_QXMPP
const XmppClient& xmppClient = XmppClient::getInstance();
disconnect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::connected);
disconnect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::onConnected);
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
disconnect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
disconnect(publicChatRoom, &QXmppMucRoom::participantsChanged,
this, &GlobalServicesScriptingInterface::participantsChanged);
#endif // HAVE_QXMPP
}
void GlobalServicesScriptingInterface::onConnected() {
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, &QXmppMucRoom::participantsChanged,
this, &GlobalServicesScriptingInterface::participantsChanged, Qt::UniqueConnection);
#endif // HAVE_QXMPP
}
void GlobalServicesScriptingInterface::participantsChanged() {
#ifdef HAVE_QXMPP
emit GlobalServicesScriptingInterface::onlineUsersChanged(this->getOnlineUsers());
#endif // HAVE_QXMPP
}
GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() {
@ -68,62 +36,14 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance(
return &sharedInstance;
}
bool GlobalServicesScriptingInterface::isConnected() {
#ifdef HAVE_QXMPP
return XmppClient::getInstance().getXMPPClient().isConnected();
#else
return false;
#endif // HAVE_QXMPP
}
QScriptValue GlobalServicesScriptingInterface::chat(const QString& message) {
#ifdef HAVE_QXMPP
if (XmppClient::getInstance().getXMPPClient().isConnected()) {
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
QXmppMessage messageObject;
messageObject.setTo(publicChatRoom->jid());
messageObject.setType(QXmppMessage::GroupChat);
messageObject.setBody(message);
return XmppClient::getInstance().getXMPPClient().sendPacket(messageObject);
}
#endif // HAVE_QXMPP
return false;
}
QString GlobalServicesScriptingInterface::getMyUsername() {
return AccountManager::getInstance().getAccountInfo().getUsername();
}
QStringList GlobalServicesScriptingInterface::getOnlineUsers() {
#ifdef HAVE_QXMPP
if (XmppClient::getInstance().getXMPPClient().isConnected()) {
QStringList usernames;
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
foreach(const QString& participant, XmppClient::getInstance().getPublicChatRoom()->participants()) {
usernames.append(participant.right(participant.count() - 1 - publicChatRoom->jid().count()));
}
return usernames;
}
#endif // HAVE_QXMPP
return QStringList();
}
void GlobalServicesScriptingInterface::loggedOut() {
emit GlobalServicesScriptingInterface::disconnected(QString("logout"));
}
#ifdef HAVE_QXMPP
void GlobalServicesScriptingInterface::messageReceived(const QXmppMessage& message) {
if (message.type() != QXmppMessage::GroupChat) {
return;
}
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
QString username = message.from().right(message.from().count() - 1 - publicChatRoom->jid().count());
emit GlobalServicesScriptingInterface::incomingMessage(username, message.body());
}
#endif // HAVE_QXMPP
DownloadInfoResult::DownloadInfoResult() :
downloading(QList<float>()),
pending(0.0f)

View file

@ -19,13 +19,6 @@
#include <QString>
#include <QStringList>
#ifdef HAVE_QXMPP
#include <qxmpp/QXmppClient.h>
#include <qxmpp/QXmppMessage.h>
#endif // HAVE_QXMPP
class DownloadInfoResult {
public:
DownloadInfoResult();
@ -41,30 +34,20 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR
class GlobalServicesScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isConnected READ isConnected)
Q_PROPERTY(QString myUsername READ getMyUsername)
Q_PROPERTY(QStringList onlineUsers READ getOnlineUsers)
GlobalServicesScriptingInterface();
~GlobalServicesScriptingInterface();
public:
static GlobalServicesScriptingInterface* getInstance();
bool isConnected();
QString getMyUsername();
QStringList getOnlineUsers();
public slots:
QScriptValue chat(const QString& message);
DownloadInfoResult getDownloadInfo();
void updateDownloadInfo();
private slots:
void loggedOut();
void onConnected();
void participantsChanged();
#ifdef HAVE_QXMPP
void messageReceived(const QXmppMessage& message);
#endif // HAVE_QXMPP
void checkDownloadInfo();
signals:

View file

@ -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);

View file

@ -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];

View file

@ -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);
}

View file

@ -1,420 +0,0 @@
//
// ChatWindow.cpp
// interface/src/ui
//
// Created by Dimitar Dobrev on 3/6/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 <QGridLayout>
#include <QFrame>
#include <QLayoutItem>
#include <QPalette>
#include <QScrollBar>
#include <QSizePolicy>
#include <QTimer>
#include "qtimespan.h"
#include <AddressManager.h>
#include <AccountManager.h>
#include <PathUtils.h>
#include "Application.h"
#include "ChatMessageArea.h"
#include "FlowLayout.h"
#include "MainWindow.h"
#include "UIUtil.h"
#include "XmppClient.h"
#include "ui_chatWindow.h"
#include "ChatWindow.h"
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
const QRegularExpression regexHifiLinks("([#@]\\S+)");
const QString mentionSoundsPath("/mention-sounds/");
const QString mentionRegex("@(\\b%1\\b)");
ChatWindow::ChatWindow(QWidget* parent) :
QWidget(parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint |
Qt::WindowCloseButtonHint),
_ui(new Ui::ChatWindow),
_numMessagesAfterLastTimeStamp(0),
_mousePressed(false),
_mouseStartPosition(),
_trayIcon(parent),
_effectPlayer(),
_usernameMentionTimestamp("MentionTimestamp", QDateTime())
{
setAttribute(Qt::WA_DeleteOnClose, false);
_ui->setupUi(this);
FlowLayout* flowLayout = new FlowLayout(0, 4, 4);
_ui->usersWidget->setLayout(flowLayout);
_ui->messagePlainTextEdit->installEventFilter(this);
_ui->messagePlainTextEdit->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
QTextCursor cursor(_ui->messagePlainTextEdit->textCursor());
cursor.movePosition(QTextCursor::Start);
QTextBlockFormat format = cursor.blockFormat();
format.setLineHeight(130, QTextBlockFormat::ProportionalHeight);
cursor.setBlockFormat(format);
_ui->messagePlainTextEdit->setTextCursor(cursor);
if (!AccountManager::getInstance().isLoggedIn()) {
_ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others."));
}
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
participantsChanged();
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged()));
_ui->connectingToXMPPLabel->hide();
startTimerForTimeStamps();
} else {
_ui->numOnlineLabel->hide();
_ui->usersArea->hide();
_ui->messagesScrollArea->hide();
_ui->messagePlainTextEdit->hide();
connect(&XmppClient::getInstance(), SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
}
connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
connect(&_trayIcon, SIGNAL(messageClicked()), this, SLOT(notificationClicked()));
#endif // HAVE_QXMPP
QDir mentionSoundsDir(PathUtils::resourcesPath() + mentionSoundsPath);
_mentionSounds = mentionSoundsDir.entryList(QDir::Files);
_trayIcon.setIcon(QIcon( PathUtils::resourcesPath() + "/images/hifi-logo.svg"));
}
ChatWindow::~ChatWindow() {
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
disconnect(&xmppClient, SIGNAL(joinedPublicChatRoom()), this, SLOT(connected()));
disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage)));
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
disconnect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged()));
#endif // HAVE_QXMPP
delete _ui;
}
void ChatWindow::keyPressEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Escape) {
Application::getInstance()->getWindow()->activateWindow();
hide();
} else {
QWidget::keyPressEvent(event);
}
}
void ChatWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
if (!event->spontaneous()) {
_ui->messagePlainTextEdit->setFocus();
}
QRect parentGeometry = Application::getInstance()->getDesirableApplicationGeometry();
int titleBarHeight = UIUtil::getWindowTitleBarHeight(this);
int menuBarHeight = Menu::getInstance()->geometry().height();
int topMargin = titleBarHeight + menuBarHeight;
setGeometry(parentGeometry.topRight().x() - size().width() + 1, parentGeometry.topRight().y() + topMargin,
size().width(), parentWidget()->height() - topMargin);
Application::processEvents();
scrollToBottom();
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
participantsChanged();
}
#endif // HAVE_QXMPP
}
bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
if (sender == _ui->messagePlainTextEdit) {
if (event->type() != QEvent::KeyPress) {
return false;
}
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) &&
(keyEvent->modifiers() & Qt::ShiftModifier) == 0) {
QString messageText = _ui->messagePlainTextEdit->document()->toPlainText().trimmed();
if (!messageText.isEmpty()) {
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
QXmppMessage message;
message.setTo(publicChatRoom->jid());
message.setType(QXmppMessage::GroupChat);
message.setBody(messageText);
XmppClient::getInstance().getXMPPClient().sendPacket(message);
#endif // HAVE_QXMPP
QTextCursor cursor = _ui->messagePlainTextEdit->textCursor();
cursor.select(QTextCursor::Document);
cursor.removeSelectedText();
}
return true;
}
} else if (event->type() == QEvent::MouseButtonRelease) {
QVariant userVar = sender->property("user");
if (userVar.isValid()) {
DependencyManager::get<AddressManager>()->goToUser(userVar.toString());
return true;
}
}
return QWidget::eventFilter(sender, event);
}
void ChatWindow::addTimeStamp() {
QTimeSpan timePassed = QDateTime::currentDateTime() - _lastMessageStamp;
int times[] = { timePassed.daysPart(), timePassed.hoursPart(), timePassed.minutesPart() };
QString strings[] = { tr("%n day(s)", 0, times[0]), tr("%n hour(s)", 0, times[1]), tr("%n minute(s)", 0, times[2]) };
QString timeString = "";
for (int i = 0; i < 3; i++) {
if (times[i] > 0) {
timeString += strings[i] + " ";
}
}
timeString.chop(1);
if (!timeString.isEmpty()) {
QLabel* timeLabel = new QLabel(timeString);
timeLabel->setStyleSheet("color: #333333;"
"background-color: white;"
"font-size: 14px;"
"padding: 4px;");
timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
timeLabel->setAlignment(Qt::AlignLeft);
bool atBottom = isNearBottom();
_ui->messagesVBoxLayout->addWidget(timeLabel);
_ui->messagesVBoxLayout->parentWidget()->updateGeometry();
Application::processEvents();
_numMessagesAfterLastTimeStamp = 0;
if (atBottom) {
scrollToBottom();
}
}
}
void ChatWindow::startTimerForTimeStamps() {
QTimer* timer = new QTimer(this);
timer->setInterval(10 * 60 * 1000);
connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
timer->start();
}
void ChatWindow::connected() {
_ui->connectingToXMPPLabel->hide();
_ui->numOnlineLabel->show();
_ui->usersArea->show();
_ui->messagesScrollArea->show();
_ui->messagePlainTextEdit->show();
_ui->messagePlainTextEdit->setFocus();
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged()));
#endif // HAVE_QXMPP
startTimerForTimeStamps();
}
void ChatWindow::timeout() {
if (_numMessagesAfterLastTimeStamp >= NUM_MESSAGES_TO_TIME_STAMP) {
addTimeStamp();
}
}
#ifdef HAVE_QXMPP
void ChatWindow::notificationClicked() {
if (parentWidget()->isMinimized()) {
parentWidget()->showNormal();
}
if (isHidden()) {
show();
}
// find last mention
int messageCount = _ui->messagesVBoxLayout->count();
for (unsigned int i = messageCount; i > 0; i--) {
ChatMessageArea* area = (ChatMessageArea*)_ui->messagesVBoxLayout->itemAt(i - 1)->widget();
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
if (area->toPlainText().contains(usernameMention)) {
int top = area->geometry().top();
int height = area->geometry().height();
QScrollBar* verticalScrollBar = _ui->messagesScrollArea->verticalScrollBar();
verticalScrollBar->setSliderPosition(top - verticalScrollBar->size().height() + height);
return;
}
}
Application::processEvents();
scrollToBottom();
}
QString ChatWindow::getParticipantName(const QString& participant) {
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
return participant.right(participant.count() - 1 - publicChatRoom->jid().count());
}
void ChatWindow::error(QXmppClient::Error error) {
_ui->connectingToXMPPLabel->setText(QString::number(error));
}
void ChatWindow::participantsChanged() {
bool atBottom = isNearBottom();
QStringList participants = XmppClient::getInstance().getPublicChatRoom()->participants();
_ui->numOnlineLabel->setText(tr("%1 online now:").arg(participants.count()));
while (QLayoutItem* item = _ui->usersWidget->layout()->takeAt(0)) {
delete item->widget();
delete item;
}
foreach (const QString& participant, participants) {
QString participantName = getParticipantName(participant);
QLabel* userLabel = new QLabel();
userLabel->setText(participantName);
userLabel->setStyleSheet("background-color: palette(light);"
"border-radius: 5px;"
"color: #267077;"
"padding-top: 3px;"
"padding-right: 2px;"
"padding-bottom: 2px;"
"padding-left: 2px;"
"border: 1px solid palette(shadow);"
"font-size: 14px;"
"font-weight: bold");
userLabel->setProperty("user", participantName);
userLabel->setCursor(Qt::PointingHandCursor);
userLabel->installEventFilter(this);
_ui->usersWidget->layout()->addWidget(userLabel);
}
Application::processEvents();
if (atBottom) {
scrollToBottom();
}
}
void ChatWindow::messageReceived(const QXmppMessage& message) {
if (message.type() != QXmppMessage::GroupChat) {
return;
}
// Update background if this is a message from the current user
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getAccountInfo().getUsername();
// Create message area
ChatMessageArea* messageArea = new ChatMessageArea(true);
messageArea->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
messageArea->setTextInteractionFlags(Qt::TextBrowserInteraction);
messageArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
messageArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
messageArea->setReadOnly(true);
messageArea->setStyleSheet("QTextBrowser{ padding-bottom: 2px;"
"padding-left: 2px;"
"padding-top: 2px;"
"padding-right: 20px;"
"margin: 0px;"
"color: #333333;"
"font-size: 14px;"
"background-color: rgba(0, 0, 0, 0%);"
"border: 0; }"
"QMenu{ border: 2px outset gray; }");
QString userLabel = getParticipantName(message.from());
if (fromSelf) {
userLabel = "<b style=\"color: #4a6f91\">" + userLabel + ": </b>";
messageArea->setStyleSheet(messageArea->styleSheet() + "background-color: #e1e8ea");
} else {
userLabel = "<b>" + userLabel + ": </b>";
}
messageArea->document()->setDefaultStyleSheet("a { text-decoration: none; font-weight: bold; color: #267077;}");
QString messageText = message.body().toHtmlEscaped();
messageText = messageText.replace(regexLinks, "<a href=\"\\1\">\\1</a>");
messageText = messageText.replace(regexHifiLinks, "<a href=\"hifi://\\1\">\\1</a>");
messageArea->setHtml(userLabel + messageText);
bool atBottom = isNearBottom();
_ui->messagesVBoxLayout->addWidget(messageArea);
_ui->messagesVBoxLayout->parentWidget()->updateGeometry();
Application::processEvents();
if (atBottom || fromSelf) {
scrollToBottom();
}
++_numMessagesAfterLastTimeStamp;
if (message.stamp().isValid()) {
_lastMessageStamp = message.stamp().toLocalTime();
} else {
_lastMessageStamp = QDateTime::currentDateTime();
}
QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername()));
if (message.body().contains(usernameMention)) {
// Don't show messages already seen in icon tray at start-up.
bool showMessage = _usernameMentionTimestamp.get() < _lastMessageStamp;
if (showMessage) {
_usernameMentionTimestamp.set(_lastMessageStamp);
}
if (isHidden() && showMessage) {
if (_effectPlayer.state() != QMediaPlayer::PlayingState) {
// get random sound
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() +
mentionSoundsPath +
_mentionSounds.at(rand() % _mentionSounds.size()));
_effectPlayer.setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
_effectPlayer.play();
}
_trayIcon.show();
_trayIcon.showMessage(windowTitle(), message.body());
}
}
}
#endif // HAVE_QXMPP
bool ChatWindow::isNearBottom() {
QScrollBar* verticalScrollBar = _ui->messagesScrollArea->verticalScrollBar();
return verticalScrollBar->value() >= verticalScrollBar->maximum() - Ui::AUTO_SCROLL_THRESHOLD;
}
// Scroll chat message area to bottom.
void ChatWindow::scrollToBottom() {
QScrollBar* verticalScrollBar = _ui->messagesScrollArea->verticalScrollBar();
verticalScrollBar->setValue(verticalScrollBar->maximum());
}
bool ChatWindow::event(QEvent* event) {
if (event->type() == QEvent::WindowActivate) {
_ui->messagePlainTextEdit->setFocus();
}
return QWidget::event(event);
}

View file

@ -1,88 +0,0 @@
//
// ChatWindow.h
// interface/src/ui
//
// Created by Dimitar Dobrev on 3/6/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_ChatWindow_h
#define hifi_ChatWindow_h
#include <QDateTime>
#include <QDockWidget>
#include <QMediaPlayer>
#include <QSystemTrayIcon>
#include <QTimer>
#include <Application.h>
#include <SettingHandle.h>
#include "FramelessDialog.h"
#ifdef HAVE_QXMPP
#include <qxmpp/QXmppClient.h>
#include <qxmpp/QXmppMessage.h>
#endif
namespace Ui {
// Maximum amount the chat can be scrolled up in order to auto scroll.
const int AUTO_SCROLL_THRESHOLD = 20;
class ChatWindow;
}
class ChatWindow : public QWidget {
Q_OBJECT
public:
ChatWindow(QWidget* parent);
~ChatWindow();
protected:
bool eventFilter(QObject* sender, QEvent* event);
virtual void keyPressEvent(QKeyEvent *event);
virtual void showEvent(QShowEvent* event);
virtual bool event(QEvent* event);
private:
#ifdef HAVE_QXMPP
QString getParticipantName(const QString& participant);
#endif
void startTimerForTimeStamps();
void addTimeStamp();
bool isNearBottom();
void scrollToBottom();
Ui::ChatWindow* _ui;
int _numMessagesAfterLastTimeStamp;
QDateTime _lastMessageStamp;
bool _mousePressed;
QPoint _mouseStartPosition;
QSystemTrayIcon _trayIcon;
QStringList _mentionSounds;
QMediaPlayer _effectPlayer;
Setting::Handle<QDateTime> _usernameMentionTimestamp;
private slots:
void connected();
void timeout();
#ifdef HAVE_QXMPP
void error(QXmppClient::Error error);
void participantsChanged();
void messageReceived(const QXmppMessage& message);
void notificationClicked();
#endif
};
#endif // hifi_ChatWindow_h

View file

@ -9,22 +9,20 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QMessageBox>
#include <AccountManager.h>
#include <MainWindow.h>
#include <PathUtils.h>
#include <XmppClient.h>
#include "AddressBarDialog.h"
#include "AnimationsDialog.h"
#include "AttachmentsDialog.h"
#include "BandwidthDialog.h"
#include "CachesSizeDialog.h"
#include "ChatWindow.h"
#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,68 +146,21 @@ 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();
}
void DialogsManager::setupChat() {
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
connect(&xmppClient, &QXmppClient::connected, this, &DialogsManager::toggleChat);
connect(&xmppClient, &QXmppClient::disconnected, this, &DialogsManager::toggleChat);
QDir::setCurrent(PathUtils::resourcesPath());
// init chat window to listen chat
maybeCreateDialog(_chatWindow);
#endif
}
void DialogsManager::showChat() {
if (AccountManager::getInstance().isLoggedIn()) {
maybeCreateDialog(_chatWindow);
if (_chatWindow->isHidden()) {
_chatWindow->show();
}
_chatWindow->raise();
_chatWindow->activateWindow();
_chatWindow->setFocus();
} else {
qApp->getTrayIcon()->showMessage("Interface",
"You need to login to be able to chat with others on this domain.");
void DialogsManager::showIRCLink() {
if (!_ircInfoBox) {
_ircInfoBox = new QMessageBox(QMessageBox::NoIcon,
"High Fidelity IRC",
"High Fidelity has an IRC channel on irc.freenode.net at #highfidelity.<br/><br/>Web chat is available <a href='http://webchat.freenode.net/?channels=highfidelity&uio=d4'>here</a>.",
QMessageBox::Ok);
_ircInfoBox->setTextFormat(Qt::RichText);
_ircInfoBox->setAttribute(Qt::WA_DeleteOnClose);
_ircInfoBox->show();
}
}
void DialogsManager::toggleChat() {
#ifdef HAVE_QXMPP
QAction* chatAction = Menu::getInstance()->getActionForOption(MenuOption::Login);
Q_CHECK_PTR(chatAction);
chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected());
if (!chatAction->isEnabled() && _chatWindow && AccountManager::getInstance().isLoggedIn()) {
if (_chatWindow->isHidden()) {
_chatWindow->show();
_chatWindow->raise();
_chatWindow->activateWindow();
_chatWindow->setFocus();
} else {
_chatWindow->hide();
}
}
#endif
_ircInfoBox->raise();
}

View file

@ -25,15 +25,13 @@ class AddressBarDialog;
class AnimationsDialog;
class AttachmentsDialog;
class CachesSizeDialog;
class ChatWindow;
class BandwidthDialog;
class LodToolsDialog;
class LoginDialog;
class MetavoxelEditor;
class MetavoxelNetworkSimulator;
class OctreeStatsDialog;
class PreferencesDialog;
class ScriptEditorWindow;
class QMessageBox;
class DialogsManager : public QObject, public Dependency {
Q_OBJECT
@ -45,8 +43,6 @@ public:
QPointer<LodToolsDialog> getLodToolsDialog() const { return _lodToolsDialog; }
QPointer<OctreeStatsDialog> getOctreeStatsDialog() const { return _octreeStatsDialog; }
void setupChat();
public slots:
void toggleAddressBar();
void toggleLoginDialog();
@ -59,15 +55,12 @@ public slots:
void bandwidthDetails();
void lodTools();
void hmdTools(bool showTools);
void showMetavoxelEditor();
void showMetavoxelNetworkSimulator();
void showScriptEditor();
void showChat();
void showIRCLink();
private slots:
void toggleToolWindow();
void hmdToolsClosed();
void toggleChat();
private:
DialogsManager() {}
@ -91,12 +84,10 @@ private:
QPointer<AttachmentsDialog> _attachmentsDialog;
QPointer<BandwidthDialog> _bandwidthDialog;
QPointer<CachesSizeDialog> _cachesSizeDialog;
QPointer<ChatWindow> _chatWindow;
QPointer<QMessageBox> _ircInfoBox;
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

View file

@ -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

View file

@ -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));
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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})

View file

@ -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 &registry;
}
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);
}

View file

@ -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*)&copy); 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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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() {
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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()));
}
}
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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()));
}
}

View file

@ -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

View file

@ -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

View file

@ -44,6 +44,7 @@ void Light::setPosition(const Vec3& position) {
}
void Light::setOrientation(const glm::quat& orientation) {
setDirection(orientation * glm::vec3(0.0f, 0.0f, -1.0f));
_transform.setRotation(orientation);
}

View file

@ -153,15 +153,8 @@ void AddressManager::handleLookupString(const QString& lookupString) {
if (!lookupString.startsWith('/')) {
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
quint16 localDomainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
if (sanitizedString == "localhost") {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getLocalServerPortFromSharedMemory
(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, _localDSPortSharedMem, localDomainServerPort);
}
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString + ":" + QString::number(localDomainServerPort));
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString);
} else {
lookupURL = QUrl(lookupString);
}
@ -338,7 +331,15 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
if (hostnameRegex.indexIn(lookupString) != -1) {
QString domainHostname = hostnameRegex.cap(1);
qint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
if (domainHostname == "localhost") {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
_localDSPortSharedMem,
domainPort);
}
if (!hostnameRegex.cap(2).isEmpty()) {
domainPort = (qint16) hostnameRegex.cap(2).toInt();
}

View file

@ -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";
}

View file

@ -31,7 +31,7 @@ public:
AgentType,
UNUSED_0,
UNUSED_1,
MetavoxelServerType,
UNUSED_2,
EntityServerType,
AllTypes
};

View file

@ -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");

View file

@ -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';

View file

@ -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);

View file

@ -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,

View file

@ -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

View file

@ -17,6 +17,7 @@
namespace render {
class Engine {
public:

View file

@ -12,9 +12,9 @@
using namespace render;
void Scene::ChangeBatch::resetItem(ID id, ItemDataPointer& itemData) {
void Scene::ChangeBatch::resetItem(ID id, PayloadPointer& payload) {
_resetItems.push_back(id);
_resetItemDatas.push_back(itemData);
_resetPayloads.push_back(payload);
}
void Scene::ChangeBatch::removeItem(ID id) {
@ -25,6 +25,14 @@ void Scene::ChangeBatch::moveItem(ID id) {
_movedItems.push_back(id);
}
void Scene::ChangeBatch::mergeBatch(ChangeBatch& newBatch) {
_resetItems.insert(_resetItems.end(), newBatch._resetItems.begin(), newBatch._resetItems.end());
_resetPayloads.insert(_resetPayloads.end(), newBatch._resetPayloads.begin(), newBatch._resetPayloads.end());
_removedItems.insert(_removedItems.end(), newBatch._removedItems.begin(), newBatch._removedItems.end());
_movedItems.insert(_movedItems.end(), newBatch._movedItems.begin(), newBatch._movedItems.end());
}
Scene::Scene() :
_IDAllocator(0)
{
@ -38,26 +46,81 @@ Item::ID Scene::allocateID() {
/// Enqueue change batch to the scene
void Scene::enqueueChangeBatch(const ChangeBatch& changeBatch) {
_changeQueueMutex.lock();
_changeQueue.push_back(changeBatch);
_changeQueue.push(changeBatch);
_changeQueueMutex.unlock();
}
void consolidateChangeQueue(Scene::ChangeBatchQueue& queue, Scene::ChangeBatch& singleBatch) {
while (!queue.empty()) {
auto changeBatch = queue.front();
singleBatch.mergeBatch(changeBatch);
queue.pop();
};
}
void Scene::processChangeBatchQueue() {
_changeQueueMutex.lock();
ChangeBatch consolidatedChangeBatch;
consolidateChangeQueue(_changeQueue, consolidatedChangeBatch);
_changeQueueMutex.unlock();
_itemsMutex.lock();
for (auto changeBatch : _changeQueue) {
for (auto reset :
// Here we should be able to check the value of last ID allocated
// and allocate new items accordingly
ID maxID = _IDAllocator.load();
if (maxID > _items.size()) {
_items.resize(maxID + 100); // allocate the maxId and more
}
// Now we know for sure that we have enough items in the array to
// capture anything coming from the changeBatch
resetItems(consolidatedChangeBatch._resetItems, consolidatedChangeBatch._resetPayloads);
removeItems(consolidatedChangeBatch._removedItems);
moveItems(consolidatedChangeBatch._movedItems);
// ready to go back to rendering activities
_itemsMutex.unlock();
}
void Scene::resetItem(ID id, ItemDataPointer& itemData) {
/*ID id = _items.size();
_items.push_back(Item(itemData));
*/
void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {
auto resetID = ids.begin();
auto resetPayload = payloads.begin();
for (;resetID != ids.end(); resetID++, resetPayload++) {
_items[(*resetID)].resetPayload(*resetPayload);
}
}
void Scene::removeItem(ID id) {
void Scene::removeItems(const ItemIDs& ids) {
for (auto removedID :ids) {
_items[removedID].kill();
}
}
void Scene::moveItem(ID id) {
void Scene::moveItems(const ItemIDs& ids) {
for (auto movedID :ids) {
_items[movedID].move();
}
}
void Scene::registerObserver(ObserverPointer& observer) {
// make sure it's a valid observer
if (observer && (observer->getScene() == nullptr)) {
// Then register the observer
_observers.push_back(observer);
// And let it do what it wants to do
observer->registerScene(this);
}
}
void Scene::unregisterObserver(ObserverPointer& observer) {
// make sure it's a valid observer currently registered
if (observer && (observer->getScene() == this)) {
// let it do what it wants to do
observer->unregisterScene();
// Then unregister the observer
auto it = std::find(_observers.begin(), _observers.end(), observer);
_observers.erase(it);
}
}

View file

@ -19,6 +19,7 @@
#include <AABox.h>
#include <atomic>
#include <mutex>
#include <queue>
namespace render {
@ -76,11 +77,16 @@ public:
typedef std::shared_ptr<PayloadInterface> PayloadPointer;
Item() {}
Item(PayloadPointer& payload):
_payload(payload) {}
~Item() {}
void resetPayload(PayloadPointer& payload);
void kill();
void move();
// Check heuristic flags of the state
const State& getState() const { return _state; }
@ -107,41 +113,73 @@ public:
protected:
PayloadPointer _payload;
State _state;
State _state = 0;
friend class Scene;
};
typedef Item::PayloadInterface ItemData;
typedef Item::PayloadPointer ItemDataPointer;
typedef std::vector< ItemDataPointer > ItemDataVector;
typedef Item::PayloadInterface Payload;
typedef Item::PayloadPointer PayloadPointer;
typedef std::vector< PayloadPointer > Payloads;
class Engine;
class Observer;
class Scene {
public:
typedef Item::Vector Items;
typedef Item::ID ID;
typedef std::vector<Item::ID> ItemList;
typedef std::map<Item::State, ItemList> ItemLists;
typedef std::vector<Item::ID> ItemIDs;
typedef std::map<Item::State, ItemIDs> ItemLists;
class Observer {
public:
Observer(Scene* scene) {
}
~Observer() {}
const Scene* getScene() const { return _scene; }
Scene* editScene() { return _scene; }
protected:
Scene* _scene = nullptr;
virtual void onRegisterScene() {}
virtual void onUnregisterScene() {}
friend class Scene;
void registerScene(Scene* scene) {
_scene = scene;
onRegisterScene();
}
void unregisterScene() {
onUnregisterScene();
_scene = 0;
}
};
typedef std::shared_ptr< Observer > ObserverPointer;
typedef std::vector< ObserverPointer > Observers;
class ChangeBatch {
public:
ChangeBatch() {}
~ChangeBatch();
void resetItem(ID id, ItemDataPointer& itemData);
void resetItem(ID id, PayloadPointer& payload);
void removeItem(ID id);
void moveItem(ID id);
ItemDataVector _resetItemDatas;
ItemList _resetItems;
ItemList _removedItems;
ItemList _movedItems;
void mergeBatch(ChangeBatch& newBatch);
Payloads _resetPayloads;
ItemIDs _resetItems;
ItemIDs _removedItems;
ItemIDs _movedItems;
protected:
};
typedef std::vector<ChangeBatch> ChangeBatchQueue;
typedef std::queue<ChangeBatch> ChangeBatchQueue;
Scene();
~Scene() {}
@ -152,21 +190,29 @@ public:
/// Enqueue change batch to the scene
void enqueueChangeBatch(const ChangeBatch& changeBatch);
/// Scene Observer listen to any change and get notified
void registerObserver(ObserverPointer& observer);
void unregisterObserver(ObserverPointer& observer);
protected:
// Thread safe elements that can be accessed from anywhere
std::atomic<unsigned int> _IDAllocator;
std::mutex _changeBatchQueueMutex;
ChangeBatchQueue _changeBatchQueue;
std::mutex _changeQueueMutex;
ChangeBatchQueue _changeQueue;
// The actual database
// database of items is protected for editing by a mutex
std::mutex _itemsMutex;
Items _items;
ItemLists _buckets;
void processChangeBatchQueue();
void resetItem(ID id, ItemDataPointer& itemData);
void removeItem(ID id);
void moveItem(ID id);
void resetItems(const ItemIDs& ids, Payloads& payloads);
void removeItems(const ItemIDs& ids);
void moveItems(const ItemIDs& ids);
// The scene context listening for any change to the database
Observers _observers;
friend class Engine;
};

View file

@ -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)

View file

@ -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>
@ -323,7 +322,6 @@ void ScriptEngine::init() {
registerAnimationTypes(this);
registerAvatarTypes(this);
registerAudioMetaTypes(this);
Bitstream::registerTypes(this);
qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValue);
qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue);

View file

@ -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

View file

@ -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

View file

@ -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();
}

View file

@ -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()

View file

@ -1,6 +1,4 @@
# add the tool directories
add_subdirectory(bitstream2json)
add_subdirectory(json2bitstream)
add_subdirectory(mtc)
add_subdirectory(scribe)

View file

@ -1,6 +0,0 @@
set(TARGET_NAME bitstream2json)
setup_hifi_project(Widgets Script)
link_hifi_libraries(metavoxels)
copy_dlls_beside_windows_executable()

View file

@ -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;
}

View file

@ -1,6 +0,0 @@
set(TARGET_NAME json2bitstream)
setup_hifi_project(Widgets Script)
link_hifi_libraries(metavoxels)
copy_dlls_beside_windows_executable()

View file

@ -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;
}