resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2014-02-24 12:06:10 -08:00
commit 9771e1bb12
61 changed files with 3061 additions and 892 deletions

View file

@ -123,6 +123,30 @@ void broadcastIdentityPacket() {
}
}
void broadcastBillboardPacket(const SharedNodePointer& sendingNode) {
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(sendingNode->getLinkedData());
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
packet.append(sendingNode->getUUID().toRfc4122());
packet.append(nodeData->getBillboard());
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node != sendingNode) {
nodeList->writeDatagram(packet, node);
}
}
}
void broadcastBillboardPackets() {
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getLinkedData() && node->getType() == NodeType::Agent) {
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
broadcastBillboardPacket(node);
nodeData->setHasSentBillboardBetweenKeyFrames(false);
}
}
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
if (killedNode->getType() == NodeType::Agent
&& killedNode->getLinkedData()) {
@ -170,6 +194,23 @@ void AvatarMixer::readPendingDatagrams() {
nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent);
}
}
break;
}
case PacketTypeAvatarBillboard: {
// check if we have a matching node in our list
SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket);
if (avatarNode && avatarNode->getLinkedData()) {
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
if (nodeData->hasBillboardChangedAfterParsing(receivedPacket)
&& !nodeData->hasSentBillboardBetweenKeyFrames()) {
// this avatar changed their billboard and we haven't sent a packet in this keyframe
broadcastBillboardPacket(avatarNode);
nodeData->setHasSentBillboardBetweenKeyFrames(true);
}
}
break;
}
case PacketTypeKillAvatar: {
nodeList->processKillNode(receivedPacket);
@ -185,6 +226,7 @@ void AvatarMixer::readPendingDatagrams() {
}
const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000;
const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000;
void AvatarMixer::run() {
commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
@ -202,6 +244,9 @@ void AvatarMixer::run() {
QElapsedTimer identityTimer;
identityTimer.start();
QElapsedTimer billboardTimer;
billboardTimer.start();
while (!_isFinished) {
QCoreApplication::processEvents();
@ -219,6 +264,11 @@ void AvatarMixer::run() {
// restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS
identityTimer.restart();
}
if (billboardTimer.elapsed() >= AVATAR_BILLBOARD_KEYFRAME_MSECS) {
broadcastBillboardPackets();
billboardTimer.restart();
}
int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow();

View file

@ -9,7 +9,8 @@
#include "AvatarMixerClientData.h"
AvatarMixerClientData::AvatarMixerClientData() :
_hasSentIdentityBetweenKeyFrames(false)
_hasSentIdentityBetweenKeyFrames(false),
_hasSentBillboardBetweenKeyFrames(false)
{
}

View file

@ -21,9 +21,15 @@ public:
bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; }
void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames)
{ _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; }
bool hasSentBillboardBetweenKeyFrames() const { return _hasSentBillboardBetweenKeyFrames; }
void setHasSentBillboardBetweenKeyFrames(bool hasSentBillboardBetweenKeyFrames)
{ _hasSentBillboardBetweenKeyFrames = hasSentBillboardBetweenKeyFrames; }
private:
bool _hasSentIdentityBetweenKeyFrames;
bool _hasSentBillboardBetweenKeyFrames;
};
#endif /* defined(__hifi__AvatarMixerClientData__) */

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
// bot.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/20/14.
// Created by Stephen Birarda on 2/20/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates an NPC avatar.
@ -25,10 +25,30 @@ positionZ = getRandomFloat(0, 50);
Avatar.position = {x: positionX, y: 0, z: positionZ};
// pick an integer between 1 and 20 for the face model for this bot
botNumber = getRandomInt(1, 20);
botNumber = getRandomInt(1, 100);
newBodyFilePrefix = "defaultAvatar";
if (botNumber <= 20) {
newFaceFilePrefix = "bot" + botNumber;
} else {
if (botNumber <= 40) {
newFaceFilePrefix = "superhero";
} else if (botNumber <= 60) {
newFaceFilePrefix = "amber";
} else if (botNumber <= 80) {
newFaceFilePrefix = "ron";
} else {
newFaceFilePrefix = "angie";
}
newBodyFilePrefix = "bot" + botNumber;
}
// set the face model fst using the bot number
// there is no need to change the body model - we're using the default
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/bot" + botNumber + ".fst";
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newFaceFilePrefix + ".fst";
Avatar.bodyModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newBodyFilePrefix + ".fst";
Agent.isAvatar = true;

File diff suppressed because it is too large Load diff

View file

@ -156,6 +156,13 @@ var line3d = Overlays.addOverlay("line3d", {
lineWidth: 5
});
// this will display the content of your clipboard at the origin of the domain
var clipboardPreview = Overlays.addOverlay("clipboard", {
position: { x: 0, y: 0, z: 0},
size: 1 / 32,
visible: true
});
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
@ -170,6 +177,7 @@ function scriptEnding() {
Overlays.deleteOverlay(solidCube);
Overlays.deleteOverlay(sphere);
Overlays.deleteOverlay(line3d);
Overlays.deleteOverlay(clipboardPreview);
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -26,6 +26,12 @@ function mouseMoveEvent(event) {
print("intersection distance=" + intersection.distance);
print("intersection intersection.x/y/z=" + intersection.intersection.x + ", "
+ intersection.intersection.y + ", " + intersection.intersection.z);
// also test the getVoxelAt() api which should find and return same voxel
var voxelAt = Voxels.getVoxelAt(intersection.voxel.x, intersection.voxel.y, intersection.voxel.z, intersection.voxel.s);
print("voxelAt.x/y/z/s=" + voxelAt.x + ", " + voxelAt.y + ", " + voxelAt.z + ": " + voxelAt.s);
print("voxelAt.red/green/blue=" + voxelAt.red + ", " + voxelAt.green + ", " + voxelAt.blue);
}
}

View file

@ -98,6 +98,7 @@ const int MIRROR_VIEW_HEIGHT = 215;
const float MIRROR_FULLSCREEN_DISTANCE = 0.35f;
const float MIRROR_REARVIEW_DISTANCE = 0.65f;
const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f;
const float MIRROR_FIELD_OF_VIEW = 30.0f;
const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml";
const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion";
@ -264,6 +265,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);
identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
// send the billboard packet for our avatar every few seconds
QTimer* billboardPacketTimer = new QTimer();
connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket);
billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
_networkAccessManager = new QNetworkAccessManager(this);
@ -536,73 +542,8 @@ void Application::paintGL() {
_glowEffect.render();
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
bool eyeRelativeCamera = false;
if (_rearMirrorTools->getZoomLevel() == BODY) {
_mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
_mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
} else { // HEAD zoom level
_mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
// face/body so that the average eye position lies at the origin
eyeRelativeCamera = true;
_mirrorCamera.setTargetPosition(glm::vec3());
} else {
_mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
}
}
_mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
glViewport(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(),
_mirrorViewRect.width(), _mirrorViewRect.height());
glScissor(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(),
_mirrorViewRect.width(), _mirrorViewRect.height());
bool updateViewFrustum = false;
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
glEnable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// render rear mirror view
glPushMatrix();
if (eyeRelativeCamera) {
// save absolute translations
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
// get the eye positions relative to the neck and use them to set the face translation
glm::vec3 leftEyePosition, rightEyePosition;
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
_myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
_myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
// get the neck position relative to the body and use it to set the skeleton translation
glm::vec3 neckPosition;
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
neckPosition);
displaySide(_mirrorCamera, true);
// restore absolute translations
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
} else {
displaySide(_mirrorCamera, true);
}
glPopMatrix();
_rearMirrorTools->render(false);
// reset Viewport and projection matrix
glViewport(0, 0, _glWidget->width(), _glWidget->height());
glDisable(GL_SCISSOR_TEST);
updateProjectionMatrix(_myCamera, updateViewFrustum);
renderRearViewMirror(_mirrorViewRect);
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
_rearMirrorTools->render(true);
}
@ -2039,6 +1980,9 @@ void Application::updateMouseRay() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMouseRay()");
// make sure the frustum is up-to-date
loadViewFrustum(_myCamera, _viewFrustum);
// if the mouse pointer isn't visible, act like it's at the center of the screen
float x = 0.5f, y = 0.5f;
if (!_mouseHidden) {
@ -2086,11 +2030,12 @@ void Application::updateVisage() {
_visage.update();
}
void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot) {
void Application::updateMyAvatarLookAtPosition() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
glm::vec3 lookAtSpot;
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
lookAtSpot = _myCamera.getPosition();
@ -2280,20 +2225,21 @@ void Application::updateMetavoxels(float deltaTime) {
}
void Application::cameraMenuChanged() {
float modeShiftPeriod = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? 0.0f : 1.0f;
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
_myCamera.setMode(CAMERA_MODE_MIRROR);
_myCamera.setModeShiftPeriod(0.00f);
_myCamera.setModeShiftPeriod(0.0f);
}
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftPeriod(1.0f);
_myCamera.setModeShiftPeriod(modeShiftPeriod);
}
} else {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
_myCamera.setModeShiftPeriod(1.0f);
_myCamera.setModeShiftPeriod(modeShiftPeriod);
}
}
}
@ -2371,13 +2317,10 @@ void Application::update(float deltaTime) {
// check what's under the mouse and update the mouse voxel
updateMouseRay();
// Set where I am looking based on my mouse ray (so that other people can see)
glm::vec3 lookAtSpot;
updateFaceshift();
updateVisage();
_myAvatar->updateLookAtTargetAvatar(lookAtSpot);
updateMyAvatarLookAtPosition(lookAtSpot);
_myAvatar->updateLookAtTargetAvatar();
updateMyAvatarLookAtPosition();
// Find the voxel we are hovering over, and respond if clicked
float distance;
@ -2400,6 +2343,9 @@ void Application::update(float deltaTime) {
_particles.update(); // update the particles...
_particleCollisionSystem.update(); // collide the particles...
// let external parties know we're updating
emit simulating(deltaTime);
}
void Application::updateMyAvatar(float deltaTime) {
@ -2740,6 +2686,24 @@ void Application::setupWorldLight() {
glMateriali(GL_FRONT, GL_SHININESS, 96);
}
QImage Application::renderAvatarBillboard() {
_textureCache.getPrimaryFramebufferObject()->bind();
glDisable(GL_BLEND);
const int BILLBOARD_SIZE = 64;
renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
glEnable(GL_BLEND);
_textureCache.getPrimaryFramebufferObject()->release();
return image;
}
void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
// transform by eye offset
@ -2933,8 +2897,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
}
}
bool renderMyHead = (whichCamera.getInterpolatedMode() != CAMERA_MODE_FIRST_PERSON);
_avatarManager.renderAvatars(renderMyHead, selfAvatarOnly);
bool forceRenderMyHead = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
_avatarManager.renderAvatars(forceRenderMyHead, selfAvatarOnly);
if (!selfAvatarOnly) {
// Render the world box
@ -3657,6 +3621,84 @@ void Application::renderCoverageMapsRecursively(CoverageMap* map) {
}
}
void Application::renderRearViewMirror(const QRect& region, bool billboard) {
bool eyeRelativeCamera = false;
if (billboard) {
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW);
_mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale());
_mirrorCamera.setTargetPosition(_myAvatar->getPosition());
} else if (_rearMirrorTools->getZoomLevel() == BODY) {
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW);
_mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
_mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
} else { // HEAD zoom level
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW);
_mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
// face/body so that the average eye position lies at the origin
eyeRelativeCamera = true;
_mirrorCamera.setTargetPosition(glm::vec3());
} else {
_mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
}
}
_mirrorCamera.setAspectRatio((float)region.width() / region.height());
_mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
glViewport(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height());
glScissor(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height());
bool updateViewFrustum = false;
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
glEnable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// render rear mirror view
glPushMatrix();
if (eyeRelativeCamera) {
// save absolute translations
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
// get the eye positions relative to the neck and use them to set the face translation
glm::vec3 leftEyePosition, rightEyePosition;
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
_myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
_myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
// get the neck position relative to the body and use it to set the skeleton translation
glm::vec3 neckPosition;
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
neckPosition);
displaySide(_mirrorCamera, true);
// restore absolute translations
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
} else {
displaySide(_mirrorCamera, true);
}
glPopMatrix();
if (!billboard) {
_rearMirrorTools->render(false);
}
// reset Viewport and projection matrix
glViewport(0, 0, _glWidget->width(), _glWidget->height());
glDisable(GL_SCISSOR_TEST);
updateProjectionMatrix(_myCamera, updateViewFrustum);
}
// renderViewFrustum()
//
// Description: this will render the view frustum bounds for EITHER the head
@ -4169,7 +4211,9 @@ void Application::reloadAllScripts() {
scriptAction->activate(QAction::Trigger);
qDebug() << "stopping script..." << scriptAction->text();
}
_activeScripts.clear();
// NOTE: we don't need to clear the _activeScripts list because that is handled on script shutdown.
foreach (QString scriptName, reloadList){
qDebug() << "reloading script..." << scriptName;
loadScript(scriptName);
@ -4177,7 +4221,7 @@ void Application::reloadAllScripts() {
}
void Application::removeScriptName(const QString& fileNameString) {
_activeScripts.removeOne(fileNameString);
_activeScripts.removeOne(fileNameString);
}
void Application::loadScript(const QString& fileNameString) {

View file

@ -14,6 +14,7 @@
#include <QApplication>
#include <QAction>
#include <QImage>
#include <QSettings>
#include <QTouchEvent>
#include <QList>
@ -95,6 +96,9 @@ static const float NODE_KILLED_BLUE = 0.0f;
static const QString SNAPSHOT_EXTENSION = ".jpg";
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f;
static const float BILLBOARD_DISTANCE = 5.0f;
class Application : public QApplication {
Q_OBJECT
@ -183,6 +187,8 @@ public:
void setupWorldLight();
QImage renderAvatarBillboard();
void displaySide(Camera& whichCamera, bool selfAvatarOnly = false);
/// Loads a view matrix that incorporates the specified model translation without the precision issues that can
@ -198,6 +204,8 @@ public:
void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const;
VoxelShader& getVoxelShader() { return _voxelShader; }
PointShader& getPointShader() { return _pointShader; }
FileLogger* getLogger() { return _logger; }
@ -215,6 +223,9 @@ public:
signals:
/// Fired when we're simulating; allows external parties to hook in.
void simulating(float deltaTime);
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
void renderingInWorldInterface();
@ -296,7 +307,7 @@ private:
void updateMouseRay();
void updateFaceshift();
void updateVisage();
void updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot);
void updateMyAvatarLookAtPosition();
void updateHoverVoxels(float deltaTime, float& distance, BoxFace& face);
void updateMouseVoxels(float deltaTime, float& distance, BoxFace& face);
void updateHandAndTouch(float deltaTime);
@ -328,7 +339,7 @@ private:
void displayStats();
void checkStatsClick();
void toggleStatsExpanded();
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
void renderRearViewMirror(const QRect& region, bool billboard = false);
void renderViewFrustum(ViewFrustum& viewFrustum);
void checkBandwidthMeterClick();

View file

@ -123,6 +123,11 @@ void Camera::setModeShiftPeriod (float period) {
const float MIN_PERIOD = 0.001f;
const float MAX_PERIOD = 3.0f;
_modeShiftPeriod = glm::clamp(period, MIN_PERIOD, MAX_PERIOD);
// if a zero period was requested, we clearly want to snap immediately to the target
if (period == 0.0f) {
update(MIN_PERIOD);
}
}
void Camera::setMode(CameraMode m) {

View file

@ -55,6 +55,7 @@ public:
const glm::vec3& getPosition() const { return _position; }
const glm::quat& getRotation() const { return _rotation; }
CameraMode getMode() const { return _mode; }
float getModeShiftPeriod() const { return _modeShiftPeriod; }
const glm::vec3& getTargetPosition() const { return _targetPosition; }
const glm::quat& getTargetRotation() const { return _targetRotation; }
float getFieldOfView() const { return _fieldOfView; }

View file

@ -94,11 +94,12 @@ void DatagramProcessor::processDatagrams() {
break;
}
case PacketTypeMetavoxelData:
application->_metavoxels.processData(incomingPacket, senderSockAddr);
nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket);
break;
case PacketTypeBulkAvatarData:
case PacketTypeKillAvatar:
case PacketTypeAvatarIdentity: {
case PacketTypeAvatarIdentity:
case PacketTypeAvatarBillboard: {
// update having heard from the avatar-mixer and record the bytes received
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);

View file

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

View file

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

View file

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

View file

@ -25,6 +25,7 @@
#include "Physics.h"
#include "world.h"
#include "devices/OculusManager.h"
#include "renderer/TextureCache.h"
#include "ui/TextRenderer.h"
using namespace std;
@ -106,6 +107,10 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
return computeRotationFromBodyToWorldUp() * getOrientation();
}
float Avatar::getLODDistance() const {
return glm::distance(Application::getInstance()->getCamera()->getPosition(), _position) / _scale;
}
void Avatar::simulate(float deltaTime) {
if (_scale != _targetScale) {
setScale(_targetScale);
@ -115,9 +120,9 @@ void Avatar::simulate(float deltaTime) {
glm::vec3 oldVelocity = getVelocity();
getHand()->simulate(deltaTime, false);
_skeletonModel.setLODDistance(getLODDistance());
_skeletonModel.simulate(deltaTime);
Head* head = getHead();
head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
glm::vec3 headPosition;
if (!_skeletonModel.getHeadPosition(headPosition)) {
headPosition = _position;
@ -181,7 +186,7 @@ static TextRenderer* textRenderer(TextRendererType type) {
return displayNameRenderer;
}
void Avatar::render(bool forceRenderHead) {
void Avatar::render() {
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
float lengthToTarget = glm::length(toTarget);
@ -199,7 +204,7 @@ void Avatar::render(bool forceRenderHead) {
getHead()->getFaceModel().renderCollisionProxies(0.7f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(forceRenderHead);
renderBody();
}
// render sphere when far away
@ -280,17 +285,73 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
return glm::angleAxis(angle * proportion, axis);
}
void Avatar::renderBody(bool forceRenderHead) {
// Render the body's voxels and head
glm::vec3 pos = getPosition();
//printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z);
_skeletonModel.render(1.0f);
if (forceRenderHead) {
getHead()->render(1.0f);
void Avatar::renderBody() {
const float BILLBOARD_DISTANCE = 40.0f;
if (!_billboard.isEmpty() && getLODDistance() >= BILLBOARD_DISTANCE) {
renderBillboard();
return;
}
_skeletonModel.render(1.0f);
getHead()->render(1.0f);
getHand()->render(false);
}
void Avatar::renderBillboard() {
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard).convertToFormat(QImage::Format_ARGB32);
_billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
}
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
// rotate about vertical to face the camera
glm::quat rotation = getOrientation();
glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position);
rotation = rotation * glm::angleAxis(glm::degrees(atan2f(-cameraVector.x, -cameraVector.z)), 0.0f, 1.0f, 0.0f);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
// compute the size from the billboard camera parameters and scale
float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
glScalef(size, size, size);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-1.0f, 1.0f);
glEnd();
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Avatar::renderDisplayName() {
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
@ -501,6 +562,13 @@ void Avatar::setDisplayName(const QString& displayName) {
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
}
void Avatar::setBillboard(const QByteArray& billboard) {
AvatarData::setBillboard(billboard);
// clear out any existing billboard texture
_billboardTexture.reset();
}
int Avatar::parseData(const QByteArray& packet) {
// change in position implies movement
glm::vec3 oldPosition = _position;

View file

@ -11,6 +11,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QScopedPointer>
#include <QtCore/QUuid>
#include <AvatarData.h>
@ -62,6 +63,8 @@ enum ScreenTintLayer {
// Grayson as he's building a street around here for demo dinner 2
const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE);
class Texture;
class Avatar : public AvatarData {
Q_OBJECT
@ -71,7 +74,7 @@ public:
void init();
void simulate(float deltaTime);
void render(bool forceRenderHead);
void render();
//setters
void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); }
@ -87,6 +90,9 @@ public:
Head* getHead() { return static_cast<Head*>(_headData); }
Hand* getHand() { return static_cast<Hand*>(_handData); }
glm::quat getWorldAlignedOrientation() const;
/// Returns the distance to use as a LOD parameter.
float getLODDistance() const;
Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); }
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
@ -114,6 +120,7 @@ public:
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setDisplayName(const QString& displayName);
virtual void setBillboard(const QByteArray& billboard);
void setShowDisplayName(bool showDisplayName);
@ -167,8 +174,10 @@ protected:
private:
bool _initialized;
QScopedPointer<Texture> _billboardTexture;
void renderBody(bool forceRenderHead);
void renderBody();
void renderBillboard();
void renderDisplayName();
};

View file

@ -69,7 +69,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
simulateAvatarFades(deltaTime);
}
void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
void AvatarManager::renderAvatars(bool forceRenderMyHead, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);
@ -83,16 +83,16 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
avatar->init();
}
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
avatar->render(forceRenderHead);
_myAvatar->render(forceRenderMyHead);
} else {
avatar->render(true);
avatar->render();
}
avatar->setDisplayingLookatVectors(renderLookAtVectors);
}
renderAvatarFades();
} else {
// just render myAvatar
_myAvatar->render(forceRenderHead);
_myAvatar->render(forceRenderMyHead);
_myAvatar->setDisplayingLookatVectors(renderLookAtVectors);
}
}
@ -121,7 +121,7 @@ void AvatarManager::renderAvatarFades() {
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
avatar->render(false);
avatar->render();
}
}
@ -133,6 +133,9 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
case PacketTypeAvatarIdentity:
processAvatarIdentityPacket(datagram);
break;
case PacketTypeAvatarBillboard:
processAvatarBillboardPacket(datagram);
break;
case PacketTypeKillAvatar:
processKillAvatar(datagram);
break;
@ -212,6 +215,20 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
}
}
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) {
int headerSize = numBytesForPacketHeader(packet);
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
if (matchingAvatar) {
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
if (avatar->getBillboard() != billboard) {
avatar->setBillboard(billboard);
}
}
}
void AvatarManager::processKillAvatar(const QByteArray& datagram) {
// read the node id
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID));

View file

@ -29,7 +29,7 @@ public:
MyAvatar* getMyAvatar() { return _myAvatar.data(); }
void updateOtherAvatars(float deltaTime);
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
void renderAvatars(bool forceRenderMyHead, bool selfAvatarOnly = false);
void clearOtherAvatars();
@ -41,6 +41,7 @@ private:
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
void processAvatarIdentityPacket(const QByteArray& packet);
void processAvatarBillboardPacket(const QByteArray& packet);
void processKillAvatar(const QByteArray& datagram);
void simulateAvatarFades(float deltaTime);

View file

@ -28,7 +28,6 @@ Head::Head(Avatar* owningAvatar) :
_gravity(0.0f, -1.0f, 0.0f),
_lastLoudness(0.0f),
_audioAttack(0.0f),
_bodyRotation(0.0f, 0.0f, 0.0f),
_angularVelocity(0,0,0),
_renderLookatVectors(false),
_saccade(0.0f, 0.0f, 0.0f),
@ -158,6 +157,9 @@ void Head::simulate(float deltaTime, bool isMine) {
glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients);
}
if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
}
_faceModel.simulate(deltaTime);
// the blend face may have custom eye meshes
@ -180,12 +182,8 @@ void Head::setScale (float scale) {
_scale = scale;
}
glm::quat Head::getOrientation() const {
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
}
glm::quat Head::getTweakedOrientation() const {
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() )));
return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() )));
}
glm::quat Head::getCameraOrientation () const {

View file

@ -40,13 +40,11 @@ public:
void render(float alpha);
void setScale(float scale);
void setPosition(glm::vec3 position) { _position = position; }
void setBodyRotation(glm::vec3 bodyRotation) { _bodyRotation = bodyRotation; }
void setGravity(glm::vec3 gravity) { _gravity = gravity; }
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
glm::quat getOrientation() const;
glm::quat getTweakedOrientation() const;
glm::quat getCameraOrientation () const;
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
@ -97,7 +95,6 @@ private:
glm::vec3 _gravity;
float _lastLoudness;
float _audioAttack;
glm::vec3 _bodyRotation;
glm::vec3 _angularVelocity;
bool _renderLookatVectors;
glm::vec3 _saccade;

View file

@ -9,6 +9,8 @@
#include <algorithm>
#include <vector>
#include <QBuffer>
#include <glm/gtx/vector_angle.hpp>
#include <QtCore/QTimer>
@ -61,7 +63,8 @@ MyAvatar::MyAvatar() :
_thrustMultiplier(1.0f),
_moveTarget(0,0,0),
_moveTargetStepCounter(0),
_lookAtTargetAvatar()
_lookAtTargetAvatar(),
_billboardValid(false)
{
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
@ -330,7 +333,6 @@ void MyAvatar::simulate(float deltaTime) {
_skeletonModel.simulate(deltaTime);
Head* head = getHead();
head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
glm::vec3 headPosition;
if (!_skeletonModel.getHeadPosition(headPosition)) {
headPosition = _position;
@ -341,7 +343,9 @@ void MyAvatar::simulate(float deltaTime) {
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0, 0, 0);
// consider updating our billboard
maybeUpdateBillboard();
}
const float MAX_PITCH = 90.0f;
@ -674,7 +678,7 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
setPosition(position + rotation * (getPosition() - position));
}
void MyAvatar::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
void MyAvatar::updateLookAtTargetAvatar() {
Application* applicationInstance = Application::getInstance();
if (!applicationInstance->isMousePressed()) {
@ -688,14 +692,9 @@ void MyAvatar::updateLookAtTargetAvatar(glm::vec3 &eyePosition) {
}
float distance;
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
// rescale to compensate for head embiggening
eyePosition = (avatar->getHead()->calculateAverageEyePosition() - avatar->getHead()->getScalePivot()) *
(avatar->getScale() / avatar->getHead()->getScale()) + avatar->getHead()->getScalePivot();
_lookAtTargetAvatar = avatarPointer;
return;
} else {
}
}
_lookAtTargetAvatar.clear();
}
@ -714,14 +713,25 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
}
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
Avatar::setFaceModelURL(faceModelURL);
_billboardValid = false;
}
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
_billboardValid = false;
}
void MyAvatar::renderBody(bool forceRenderHead) {
// Render the body's voxels and head
_skeletonModel.render(1.0f);
// Render head so long as the camera isn't inside it
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.10f;
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.40f;
Camera* myCamera = Application::getInstance()->getCamera();
if (forceRenderHead || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE)) {
if (forceRenderHead || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) >
RENDER_HEAD_CUTOFF_DISTANCE * _scale)) {
getHead()->render(1.0f);
}
getHand()->render(true);
@ -1133,6 +1143,20 @@ void MyAvatar::updateChatCircle(float deltaTime) {
_position = glm::mix(_position, targetPosition, APPROACH_RATE);
}
void MyAvatar::maybeUpdateBillboard() {
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
return;
}
QImage image = Application::getInstance()->renderAvatarBillboard();
_billboard.clear();
QBuffer buffer(&_billboard);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
_billboardValid = true;
sendBillboardPacket();
}
void MyAvatar::setGravity(glm::vec3 gravity) {
_gravity = gravity;
getHead()->setGravity(_gravity);

View file

@ -80,9 +80,11 @@ public:
void orbit(const glm::vec3& position, int deltaX, int deltaY);
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
public slots:
void goHome();
void increaseSize();
@ -120,6 +122,8 @@ private:
glm::vec3 _transmitterPickStart;
glm::vec3 _transmitterPickEnd;
bool _billboardValid;
// private methods
void renderBody(bool forceRenderHead);
void updateThrust(float deltaTime);
@ -130,6 +134,7 @@ private:
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void updateChatCircle(float deltaTime);
void maybeUpdateBillboard();
};
#endif

View file

@ -17,8 +17,8 @@ using namespace std;
Model::Model(QObject* parent) :
QObject(parent),
_pupilDilation(0.0f)
{
_lodDistance(0.0f),
_pupilDilation(0.0f) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
@ -46,6 +46,21 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati
program.release();
}
bool Model::isLoadedWithTextures() const {
if (!isActive()) {
return false;
}
foreach (const NetworkMesh& mesh, _geometry->getMeshes()) {
foreach (const NetworkMeshPart& part, mesh.parts) {
if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) ||
(part.normalTexture && !part.normalTexture->isLoaded())) {
return false;
}
}
}
return true;
}
void Model::init() {
if (!_program.isLinked()) {
switchToResourcesParentIfRequired();
@ -92,8 +107,7 @@ void Model::reset() {
void Model::simulate(float deltaTime) {
// update our LOD
if (_geometry) {
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
if (_geometry != geometry) {
deleteGeometry();
_dilatedTextures.clear();
@ -276,7 +290,7 @@ bool Model::render(float alpha) {
// render opaque meshes with alpha testing
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glAlphaFunc(GL_GREATER, 0.5f * alpha);
renderMeshes(alpha, false);
@ -917,7 +931,7 @@ void Model::renderMeshes(float alpha, bool translucent) {
if (!mesh.colors.isEmpty()) {
glEnableClientState(GL_COLOR_ARRAY);
} else {
glColor3f(1.0f, 1.0f, 1.0f);
glColor4f(1.0f, 1.0f, 1.0f, alpha);
}
if (!mesh.texCoords.isEmpty()) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

View file

@ -46,6 +46,8 @@ public:
bool isActive() const { return _geometry && _geometry->isLoaded(); }
bool isLoadedWithTextures() const;
void init();
void reset();
void simulate(float deltaTime);
@ -54,6 +56,9 @@ public:
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl());
const QUrl& getURL() const { return _url; }
/// Sets the distance parameter used for LOD computations.
void setLODDistance(float distance) { _lodDistance = distance; }
/// Returns the extents of the model in its bind pose.
Extents getBindExtents() const;
@ -228,13 +233,14 @@ private:
void renderMeshes(float alpha, bool translucent);
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
float _lodDistance;
float _lodHysteresis;
float _pupilDilation;
std::vector<float> _blendshapeCoefficients;
QUrl _url;
QVector<GLuint> _blendedVertexBufferIDs;
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
bool _resetStates;

View file

@ -258,9 +258,11 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
_reply(NULL),
_attempts(0),
_averageColor(1.0f, 1.0f, 1.0f, 1.0f),
_translucent(false) {
_translucent(false),
_loaded(false) {
if (!url.isValid()) {
_loaded = true;
return;
}
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
@ -298,6 +300,7 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
_loaded = true;
QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32);
@ -345,6 +348,8 @@ void NetworkTexture::handleReplyError() {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
debug << " -- retrying...";
} else {
_loaded = true;
}
}

View file

@ -118,6 +118,8 @@ public:
NetworkTexture(const QUrl& url, bool normalMap);
~NetworkTexture();
bool isLoaded() const { return _loaded; }
/// Returns the average color over the entire texture.
const glm::vec4& getAverageColor() const { return _averageColor; }
@ -142,6 +144,7 @@ private:
int _attempts;
glm::vec4 _averageColor;
bool _translucent;
bool _loaded;
};
/// Caches derived, dilated textures.

View file

@ -0,0 +1,50 @@
//
// ClipboardOverlay.cpp
// hifi
//
// Created by Clément Brisset on 2/20/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "ClipboardOverlay.h"
#include "../Application.h"
static int lastVoxelCount = 0;
ClipboardOverlay::ClipboardOverlay() {
}
ClipboardOverlay::~ClipboardOverlay() {
}
void ClipboardOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
VoxelTree* clipboard = Application::getInstance()->getClipboard();
if (voxelSystem->getTree() != clipboard) {
voxelSystem->changeTree(clipboard);
}
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
glScalef(_size, _size, _size);
// We only force the redraw when the clipboard content has changed
if (lastVoxelCount != clipboard->getOctreeElementsCount()) {
voxelSystem->forceRedrawEntireTree();
lastVoxelCount = clipboard->getOctreeElementsCount();
}
voxelSystem->render();
glPopMatrix();
}

View file

@ -0,0 +1,25 @@
//
// ClipboardOverlay.h
// hifi
//
// Created by Clément Brisset on 2/20/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__ClipboardOverlay__
#define __interface__ClipboardOverlay__
#include "Volume3DOverlay.h"
class ClipboardOverlay : public Volume3DOverlay {
Q_OBJECT
public:
ClipboardOverlay();
~ClipboardOverlay();
virtual void render();
};
#endif /* defined(__interface__ClipboardOverlay__) */

View file

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

View file

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

View file

@ -12,7 +12,7 @@
#include "Overlays.h"
#include "Sphere3DOverlay.h"
#include "TextOverlay.h"
#include "ClipboardOverlay.h"
unsigned int Overlays::_nextOverlayID = 1;
@ -73,6 +73,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "clipboard") {
thisOverlay = new ClipboardOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
}
if (created) {

View file

@ -305,6 +305,15 @@ QByteArray AvatarData::identityByteArray() {
return identityData;
}
bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& packet) {
QByteArray newBillboard = packet.mid(numBytesForPacketHeader(packet));
if (newBillboard == _billboard) {
return false;
}
_billboard = newBillboard;
return true;
}
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
_faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL;
@ -323,6 +332,12 @@ void AvatarData::setDisplayName(const QString& displayName) {
qDebug() << "Changing display name for avatar to" << displayName;
}
void AvatarData::setBillboard(const QByteArray& billboard) {
_billboard = billboard;
qDebug() << "Changing billboard for avatar.";
}
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
@ -344,3 +359,10 @@ void AvatarData::sendIdentityPacket() {
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}
void AvatarData::sendBillboardPacket() {
QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
billboardPacket.append(_billboard);
NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer);
}

View file

@ -29,6 +29,7 @@ typedef unsigned long long quint64;
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QByteArray>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtCore/QUuid>
@ -54,6 +55,7 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
@ -78,6 +80,7 @@ class AvatarData : public NodeData {
Q_PROPERTY(QString chatMessage READ getQStringChatMessage WRITE setChatMessage)
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation)
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness)
@ -109,6 +112,9 @@ public:
glm::quat getOrientation() const { return glm::quat(glm::radians(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll))); }
void setOrientation(const glm::quat& orientation);
glm::quat getHeadOrientation() const { return _headData->getOrientation(); }
void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); }
// access to Head().set/getMousePitch
float getHeadPitch() const { return _headData->getPitch(); }
void setHeadPitch(float value) { _headData->setPitch(value); };
@ -151,6 +157,8 @@ public:
bool hasIdentityChangedAfterParsing(const QByteArray& packet);
QByteArray identityByteArray();
bool hasBillboardChangedAfterParsing(const QByteArray& packet);
const QUrl& getFaceModelURL() const { return _faceModelURL; }
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
@ -159,6 +167,9 @@ public:
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setDisplayName(const QString& displayName);
virtual void setBillboard(const QByteArray& billboard);
const QByteArray& getBillboard() const { return _billboard; }
QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); }
void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); }
@ -169,6 +180,7 @@ public:
public slots:
void sendIdentityPacket();
void sendBillboardPacket();
protected:
glm::vec3 _position;
@ -204,6 +216,8 @@ protected:
float _displayNameTargetAlpha;
float _displayNameAlpha;
QByteArray _billboard;
private:
// privatize the copy constructor and assignment operator so they cannot be called
AvatarData(const AvatarData&);

View file

@ -6,6 +6,11 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <glm/gtx/quaternion.hpp>
#include <OctreeConstants.h>
#include "AvatarData.h"
#include "HeadData.h"
HeadData::HeadData(AvatarData* owningAvatar) :
@ -26,6 +31,24 @@ HeadData::HeadData(AvatarData* owningAvatar) :
}
glm::quat HeadData::getOrientation() const {
return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
}
void HeadData::setOrientation(const glm::quat& orientation) {
// rotate body about vertical axis
glm::quat bodyOrientation = _owningAvatar->getOrientation();
glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT);
bodyOrientation = bodyOrientation * glm::angleAxis(glm::degrees(atan2f(-newFront.x, -newFront.z)), 0.0f, 1.0f, 0.0f);
_owningAvatar->setOrientation(bodyOrientation);
// the rest goes to the head
glm::vec3 eulers = safeEulerAngles(glm::inverse(bodyOrientation) * orientation);
_pitch = eulers.x;
_yaw = eulers.y;
_roll = eulers.z;
}
void HeadData::addYaw(float yaw) {
setYaw(_yaw + yaw);
}

View file

@ -13,6 +13,7 @@
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
const float MIN_HEAD_YAW = -110;
const float MAX_HEAD_YAW = 110;
@ -42,6 +43,9 @@ public:
float getRoll() const { return _roll; }
void setRoll(float roll) { _roll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); }
glm::quat getOrientation() const;
void setOrientation(const glm::quat& orientation);
float getAudioLoudness() const { return _audioLoudness; }
void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -54,6 +54,7 @@ enum PacketType {
PacketTypeParticleAddResponse,
PacketTypeMetavoxelData,
PacketTypeAvatarIdentity,
PacketTypeAvatarBillboard,
PacketTypeDomainConnectRequest,
PacketTypeDomainServerAuthRequest
};

View file

@ -12,14 +12,45 @@ void VoxelsScriptingInterface::queueVoxelAdd(PacketType addPacketType, VoxelDeta
getVoxelPacketSender()->queueVoxelEditMessages(addPacketType, 1, &addVoxelDetails);
}
VoxelDetail VoxelsScriptingInterface::getVoxelAt(float x, float y, float z, float scale) {
// setup a VoxelDetail struct with the data
VoxelDetail result = {0,0,0,0,0,0,0};
if (_tree) {
_tree->lockForRead();
VoxelTreeElement* voxel = static_cast<VoxelTreeElement*>(_tree->getOctreeElementAt(x / (float)TREE_SCALE, y / (float)TREE_SCALE,
z / (float)TREE_SCALE, scale / (float)TREE_SCALE));
_tree->unlock();
if (voxel) {
// Note: these need to be in voxel space because the VoxelDetail -> js converter will upscale
result.x = voxel->getCorner().x;
result.y = voxel->getCorner().y;
result.z = voxel->getCorner().z;
result.s = voxel->getScale();
result.red = voxel->getColor()[RED_INDEX];
result.green = voxel->getColor()[GREEN_INDEX];
result.blue = voxel->getColor()[BLUE_INDEX];
}
}
return result;
}
void VoxelsScriptingInterface::setVoxelNonDestructive(float x, float y, float z, float scale,
uchar red, uchar green, uchar blue) {
// setup a VoxelDetail struct with the data
VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
scale / (float)TREE_SCALE, red, green, blue};
// queue the packet
// queue the add packet
queueVoxelAdd(PacketTypeVoxelSet, addVoxelDetail);
// handle the local tree also...
if (_tree) {
_tree->lockForWrite();
_tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, false);
_tree->unlock();
}
}
void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
@ -30,6 +61,13 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
// queue the destructive add
queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail);
// handle the local tree also...
if (_tree) {
_tree->lockForWrite();
_tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, true);
_tree->unlock();
}
}
void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale) {
@ -39,6 +77,13 @@ void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale
scale / (float)TREE_SCALE, 0, 0, 0};
getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail);
// handle the local tree also...
if (_tree) {
_tree->lockForWrite();
_tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s);
_tree->unlock();
}
}
@ -64,3 +109,21 @@ RayToVoxelIntersectionResult VoxelsScriptingInterface::findRayIntersection(const
}
return result;
}
glm::vec3 VoxelsScriptingInterface::getFaceVector(const QString& face) {
if (face == "MIN_X_FACE") {
return glm::vec3(-1, 0, 0);
} else if (face == "MAX_X_FACE") {
return glm::vec3(1, 0, 0);
} else if (face == "MIN_Y_FACE") {
return glm::vec3(0, -1, 0);
} else if (face == "MAX_Y_FACE") {
return glm::vec3(0, 1, 0);
} else if (face == "MIN_Z_FACE") {
return glm::vec3(0, 0, -1);
} else if (face == "MAX_Z_FACE") {
return glm::vec3(0, 0, 1);
}
return glm::vec3(0, 0, 0); //error case
}

View file

@ -30,6 +30,14 @@ public:
void setVoxelTree(VoxelTree* tree) { _tree = tree; }
public slots:
/// checks the local voxel tree for a voxel at the specified location and scale
/// \param x the x-coordinate of the voxel (in meter units)
/// \param y the y-coordinate of the voxel (in meter units)
/// \param z the z-coordinate of the voxel (in meter units)
/// \param scale the scale of the voxel (in meter units)
VoxelDetail getVoxelAt(float x, float y, float z, float scale);
/// queues the creation of a voxel which will be sent by calling process on the PacketSender
/// \param x the x-coordinate of the voxel (in meter units)
/// \param y the y-coordinate of the voxel (in meter units)
@ -60,6 +68,9 @@ public slots:
/// If the scripting context has visible voxels, this will determine a ray intersection
RayToVoxelIntersectionResult findRayIntersection(const PickRay& ray);
/// returns a voxel space axis aligned vector for the face, useful in doing voxel math
glm::vec3 getFaceVector(const QString& face);
private:
void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails);
VoxelTree* _tree;

View file

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

View file

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

View file

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

View file

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