mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 19:34:02 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into ds-web
This commit is contained in:
commit
97a919a0c2
82 changed files with 5313 additions and 2087 deletions
4
BUILD.md
4
BUILD.md
|
@ -3,7 +3,7 @@ Dependencies
|
|||
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.11
|
||||
* [Qt](http://qt-project.org/downloads) ~> 5.2.0
|
||||
* [zLib](http://www.zlib.net/) ~> 1.2.8
|
||||
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.0
|
||||
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.2
|
||||
* [qxmpp](https://code.google.com/p/qxmpp/) ~> 0.7.6
|
||||
|
||||
#####Linux only
|
||||
|
@ -142,4 +142,4 @@ If you need to debug Interface, you can run interface from within Visual Studio
|
|||
####Debugging Interface
|
||||
* In the Solution Explorer, right click interface and click Set as StartUp Project
|
||||
* Set the "Working Directory" for the Interface debugging sessions to the Debug output directory so that your application can load resources. Do this: right click interface and click Properties, choose Debugging from Configuration Properties, set Working Directory to .\Debug
|
||||
* Now you can run and debug interface through Visual Studio
|
||||
* Now you can run and debug interface through Visual Studio
|
||||
|
|
|
@ -23,7 +23,7 @@ Running Interface
|
|||
When you launch interface, you will automatically connect to our default domain: "root.highfidelity.io".
|
||||
|
||||
If you don't see anything, make sure your preferences are pointing to
|
||||
root.highfidelity.io, if you still have no luck it's possible our servers are
|
||||
root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D), if you still have no luck it's possible our servers are
|
||||
simply down; if you're experiencing a major bug, let us know by adding an issue to this repository.
|
||||
Make sure to include details about your computer and how to reproduce the bug.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <SharedUtil.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
#include "AssignmentClient.h"
|
||||
|
||||
|
@ -28,7 +29,7 @@ int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
|
|||
|
||||
AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
||||
QCoreApplication(argc, argv),
|
||||
_currentAssignment(NULL)
|
||||
_currentAssignment()
|
||||
{
|
||||
setOrganizationName("High Fidelity");
|
||||
setOrganizationDomain("highfidelity.io");
|
||||
|
@ -124,7 +125,7 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
|
||||
if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
|
||||
// construct the deployed assignment from the packet data
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
|
||||
_currentAssignment = SharedAssignmentPointer(AssignmentFactory::unpackAssignment(receivedPacket));
|
||||
|
||||
if (_currentAssignment) {
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
@ -137,14 +138,13 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
qDebug() << "Destination IP for assignment is" << nodeList->getDomainInfo().getIP().toString();
|
||||
|
||||
// start the deployed assignment
|
||||
QThread* workerThread = new QThread(this);
|
||||
AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this);
|
||||
|
||||
connect(workerThread, SIGNAL(started()), _currentAssignment, SLOT(run()));
|
||||
|
||||
connect(_currentAssignment, SIGNAL(finished()), this, SLOT(assignmentCompleted()));
|
||||
connect(_currentAssignment, SIGNAL(finished()), workerThread, SLOT(quit()));
|
||||
connect(_currentAssignment, SIGNAL(finished()), _currentAssignment, SLOT(deleteLater()));
|
||||
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
|
||||
connect(workerThread, &QThread::started, _currentAssignment.data(), &ThreadedAssignment::run);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished, workerThread, &QThread::quit);
|
||||
connect(_currentAssignment.data(), &ThreadedAssignment::finished,
|
||||
this, &AssignmentClient::assignmentCompleted);
|
||||
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
|
||||
|
||||
_currentAssignment->moveToThread(workerThread);
|
||||
|
||||
|
@ -153,7 +153,7 @@ void AssignmentClient::readPendingDatagrams() {
|
|||
|
||||
// let the assignment handle the incoming datagrams for its duration
|
||||
disconnect(&nodeList->getNodeSocket(), 0, this, 0);
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _currentAssignment,
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _currentAssignment.data(),
|
||||
&ThreadedAssignment::readPendingDatagrams);
|
||||
|
||||
// Starts an event loop, and emits workerThread->started()
|
||||
|
@ -202,10 +202,12 @@ void AssignmentClient::assignmentCompleted() {
|
|||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
// have us handle incoming NodeList datagrams again
|
||||
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment, 0);
|
||||
disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment.data(), 0);
|
||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
|
||||
|
||||
_currentAssignment = NULL;
|
||||
// clear our current assignment shared pointer now that we're done with it
|
||||
// if the assignment thread is still around it has its own shared pointer to the assignment
|
||||
_currentAssignment.clear();
|
||||
|
||||
// reset our NodeList by switching back to unassigned and clearing the list
|
||||
nodeList->setOwnerType(NodeType::Unassigned);
|
||||
|
|
|
@ -24,7 +24,7 @@ private slots:
|
|||
void handleAuthenticationRequest();
|
||||
private:
|
||||
Assignment _requestAssignment;
|
||||
ThreadedAssignment* _currentAssignment;
|
||||
SharedAssignmentPointer _currentAssignment;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__AssignmentClient__) */
|
||||
|
|
16
assignment-client/src/AssignmentThread.cpp
Normal file
16
assignment-client/src/AssignmentThread.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// AssignmentThread.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-03-28.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AssignmentThread.h"
|
||||
|
||||
AssignmentThread::AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent) :
|
||||
QThread(parent),
|
||||
_assignment(assignment)
|
||||
{
|
||||
|
||||
}
|
23
assignment-client/src/AssignmentThread.h
Normal file
23
assignment-client/src/AssignmentThread.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// AssignmentThread.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-03-28.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__AssignmentThread__
|
||||
#define __hifi__AssignmentThread__
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
class AssignmentThread : public QThread {
|
||||
public:
|
||||
AssignmentThread(const SharedAssignmentPointer& assignment, QObject* parent);
|
||||
private:
|
||||
SharedAssignmentPointer _assignment;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__AssignmentThread__) */
|
|
@ -23,10 +23,10 @@ function printVector(string, vector) {
|
|||
}
|
||||
|
||||
var CHANCE_OF_MOVING = 0.005;
|
||||
var CHANCE_OF_SOUND = 0.005;
|
||||
var CHANCE_OF_SOUND = 0.000;
|
||||
var CHANCE_OF_HEAD_TURNING = 0.05;
|
||||
var CHANCE_OF_BIG_MOVE = 0.1;
|
||||
var CHANCE_OF_WAVING = 0.005; // Currently this isn't working
|
||||
var CHANCE_OF_WAVING = 0.009;
|
||||
|
||||
var shouldReceiveVoxels = true;
|
||||
var VOXEL_FPS = 60.0;
|
||||
|
@ -39,12 +39,16 @@ var isWaving = false;
|
|||
var waveFrequency = 0.0;
|
||||
var waveAmplitude = 0.0;
|
||||
|
||||
var X_MIN = 0.0;
|
||||
var X_MAX = 5.0;
|
||||
var Z_MIN = 0.0;
|
||||
var Z_MAX = 5.0;
|
||||
var X_MIN = 20.0;
|
||||
var X_MAX = 25.0;
|
||||
var Z_MIN = 20.0;
|
||||
var Z_MAX = 25.0;
|
||||
var Y_PELVIS = 2.5;
|
||||
var SHOULDER_JOINT_NUMBER = 15;
|
||||
var SPINE_JOINT_NUMBER = 13;
|
||||
var SHOULDER_JOINT_NUMBER = 17;
|
||||
var ELBOW_JOINT_NUMBER = 18;
|
||||
var JOINT_R_HIP = 1;
|
||||
var JOINT_R_KNEE = 2;
|
||||
|
||||
var MOVE_RANGE_SMALL = 0.5;
|
||||
var MOVE_RANGE_BIG = Math.max(X_MAX - X_MIN, Z_MAX - Z_MIN) / 2.0;
|
||||
|
@ -61,6 +65,9 @@ var targetDirection = { x: 0, y: 0, z: 0, w: 0 };
|
|||
var currentDirection = { x: 0, y: 0, z: 0, w: 0 };
|
||||
var targetHeadPitch = 0.0;
|
||||
|
||||
var walkFrequency = 5.0;
|
||||
var walkAmplitude = 45.0;
|
||||
|
||||
var cumulativeTime = 0.0;
|
||||
|
||||
var sounds = [];
|
||||
|
@ -115,12 +122,30 @@ printVector("New bot, position = ", Avatar.position);
|
|||
function stopWaving() {
|
||||
isWaving = false;
|
||||
Avatar.clearJointData(SHOULDER_JOINT_NUMBER);
|
||||
Avatar.clearJointData(ELBOW_JOINT_NUMBER);
|
||||
Avatar.clearJointData(SPINE_JOINT_NUMBER);
|
||||
}
|
||||
|
||||
function keepWalking() {
|
||||
Avatar.setJointData(JOINT_R_HIP, Quat.fromPitchYawRollDegrees(walkAmplitude * Math.sin(cumulativeTime * walkFrequency), 0.0, 0.0));
|
||||
Avatar.setJointData(JOINT_R_KNEE, Quat.fromPitchYawRollDegrees(walkAmplitude * Math.sin(cumulativeTime * walkFrequency), 0.0, 0.0));
|
||||
}
|
||||
|
||||
function stopWalking() {
|
||||
Avatar.clearJointData(JOINT_R_HIP);
|
||||
Avatar.clearJointData(JOINT_R_KNEE);
|
||||
}
|
||||
|
||||
function updateBehavior(deltaTime) {
|
||||
|
||||
cumulativeTime += deltaTime;
|
||||
|
||||
// Hack - right now you need to set the avatar position a bit after the avatar is made to make sure it's there.
|
||||
|
||||
if (CHANCE_OF_MOVING == 0.000) {
|
||||
Avatar.position = firstPosition;
|
||||
}
|
||||
|
||||
if (shouldReceiveVoxels && ((cumulativeTime - lastVoxelQueryTime) > (1.0 / VOXEL_FPS))) {
|
||||
VoxelViewer.setPosition(Avatar.position);
|
||||
VoxelViewer.setOrientation(Avatar.orientation);
|
||||
|
@ -134,13 +159,18 @@ function updateBehavior(deltaTime) {
|
|||
|
||||
if (!isWaving && (Math.random() < CHANCE_OF_WAVING)) {
|
||||
isWaving = true;
|
||||
waveFrequency = 1.0 + Math.random() * 5.0;
|
||||
waveFrequency = 3.0 + Math.random() * 5.0;
|
||||
waveAmplitude = 5.0 + Math.random() * 60.0;
|
||||
Script.setTimeout(stopWaving, 1000 + Math.random() * 2000);
|
||||
Avatar.setJointData(ELBOW_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 45, 0.0)); // Initially turn the palm outward
|
||||
} else if (isWaving) {
|
||||
Avatar.setJointData(SHOULDER_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 0.0, waveAmplitude * Math.sin(cumulativeTime * waveFrequency)));
|
||||
Avatar.setJointData(SHOULDER_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 0.0, 60 + waveAmplitude * Math.sin((cumulativeTime - 0.25) * waveFrequency)));
|
||||
Avatar.setJointData(ELBOW_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 0.0, 25 + waveAmplitude/2.0 * Math.sin(cumulativeTime * 1.2 * waveFrequency)));
|
||||
Avatar.setJointData(SPINE_JOINT_NUMBER, Quat.fromPitchYawRollDegrees(0.0, 0.0, 60 + waveAmplitude/4.0 * Math.sin(cumulativeTime * waveFrequency)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (Math.random() < CHANCE_OF_SOUND) {
|
||||
playRandomSound();
|
||||
}
|
||||
|
@ -168,11 +198,13 @@ function updateBehavior(deltaTime) {
|
|||
targetPosition.y = Y_PELVIS;
|
||||
|
||||
isMoving = true;
|
||||
} else {
|
||||
} else if (isMoving) {
|
||||
keepWalking();
|
||||
Avatar.position = Vec3.sum(Avatar.position, Vec3.multiply(Vec3.subtract(targetPosition, Avatar.position), MOVE_RATE));
|
||||
Avatar.orientation = Quat.mix(Avatar.orientation, targetDirection, TURN_RATE);
|
||||
if (Vec3.length(Vec3.subtract(Avatar.position, targetPosition)) < STOP_TOLERANCE) {
|
||||
isMoving = false;
|
||||
isMoving = false;
|
||||
stopWalking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1326,7 +1326,7 @@ function wheelEvent(event) {
|
|||
}
|
||||
}
|
||||
|
||||
Controller.wheelEvent.connect(wheelEvent);
|
||||
// Controller.wheelEvent.connect(wheelEvent);
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
|
|
@ -27,6 +27,9 @@ var BULLET_VELOCITY = 5.0;
|
|||
var MIN_THROWER_DELAY = 1000;
|
||||
var MAX_THROWER_DELAY = 1000;
|
||||
var LEFT_BUTTON_3 = 3;
|
||||
var RELOAD_INTERVAL = 9;
|
||||
|
||||
var showScore = false;
|
||||
|
||||
// Load some sound to use for loading and firing
|
||||
var fireSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guns/GUN-SHOT2.raw");
|
||||
|
@ -38,6 +41,8 @@ var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazo
|
|||
var audioOptions = new AudioInjectionOptions();
|
||||
audioOptions.volume = 0.9;
|
||||
|
||||
var shotsFired = 0;
|
||||
|
||||
var shotTime = new Date();
|
||||
|
||||
// initialize our triggers
|
||||
|
@ -63,7 +68,8 @@ var reticle = Overlays.addOverlay("image", {
|
|||
alpha: 1
|
||||
});
|
||||
|
||||
var text = Overlays.addOverlay("text", {
|
||||
if (showScore) {
|
||||
var text = Overlays.addOverlay("text", {
|
||||
x: screenSize.x / 2 - 100,
|
||||
y: screenSize.y / 2 - 50,
|
||||
width: 150,
|
||||
|
@ -74,6 +80,8 @@ var text = Overlays.addOverlay("text", {
|
|||
leftMargin: 4,
|
||||
text: "Score: " + score
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function printVector(string, vector) {
|
||||
|
@ -94,6 +102,10 @@ function shootBullet(position, velocity) {
|
|||
// Play firing sounds
|
||||
audioOptions.position = position;
|
||||
Audio.playSound(fireSound, audioOptions);
|
||||
shotsFired++;
|
||||
if ((shotsFired % RELOAD_INTERVAL) == 0) {
|
||||
Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function shootTarget() {
|
||||
|
@ -147,12 +159,15 @@ function particleCollisionWithVoxel(particle, voxel, penetration) {
|
|||
Voxels.eraseVoxel(position.x, position.y, position.z, HOLE_SIZE);
|
||||
//audioOptions.position = position;
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(targetHitSound, audioOptions);
|
||||
Audio.playSound(impactSound, audioOptions);
|
||||
}
|
||||
|
||||
function particleCollisionWithParticle(particle1, particle2) {
|
||||
score++;
|
||||
Overlays.editOverlay(text, { text: "Score: " + score } );
|
||||
if (showScore) {
|
||||
Overlays.editOverlay(text, { text: "Score: " + score } );
|
||||
}
|
||||
|
||||
// Sort out which particle is which
|
||||
|
||||
// Record shot time
|
||||
|
@ -171,12 +186,12 @@ function keyPressEvent(event) {
|
|||
if (event.text == "t") {
|
||||
var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY;
|
||||
Script.setTimeout(shootTarget, time);
|
||||
} if (event.text == ".") {
|
||||
shootFromMouse();
|
||||
}
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
|
||||
|
||||
// Check for mouseLook movement, update rotation
|
||||
// rotate body yaw for yaw received from mouse
|
||||
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } ));
|
||||
|
@ -257,18 +272,21 @@ function mousePressEvent(event) {
|
|||
isMouseDown = true;
|
||||
lastX = event.x;
|
||||
lastY = event.y;
|
||||
audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
Audio.playSound(loadSound, audioOptions);
|
||||
//audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation()));
|
||||
//Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
// position
|
||||
function shootFromMouse() {
|
||||
var DISTANCE_FROM_CAMERA = 2.0;
|
||||
var camera = Camera.getPosition();
|
||||
var forwardVector = Quat.getFront(Camera.getOrientation());
|
||||
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA));
|
||||
var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY);
|
||||
shootBullet(newPosition, velocity);
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
// position
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,11 +162,11 @@ function flyWithHydra(deltaTime) {
|
|||
if (thrustMultiplier < MAX_THRUST_MULTIPLIER) {
|
||||
thrustMultiplier *= 1 + (deltaTime * THRUST_INCREASE_RATE);
|
||||
}
|
||||
var currentOrientation = MyAvatar.orientation;
|
||||
var headOrientation = MyAvatar.headOrientation;
|
||||
|
||||
var front = Quat.getFront(currentOrientation);
|
||||
var right = Quat.getRight(currentOrientation);
|
||||
var up = Quat.getUp(currentOrientation);
|
||||
var front = Quat.getFront(headOrientation);
|
||||
var right = Quat.getRight(headOrientation);
|
||||
var up = Quat.getUp(headOrientation);
|
||||
|
||||
var thrustFront = Vec3.multiply(front, MyAvatar.scale * THRUST_MAG_HAND_JETS *
|
||||
thrustJoystickPosition.y * thrustMultiplier * deltaTime);
|
||||
|
|
|
@ -81,9 +81,10 @@ qt5_add_resources(QT_RESOURCES "${QT_RESOURCE_FILES}")
|
|||
# add them to the interface source files
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
||||
|
||||
set(QM ${TARGET_NAME}_en.qm)
|
||||
set(TS ${TARGET_NAME}_en.ts)
|
||||
qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
|
||||
# translation disabled until we strip out the line numbers
|
||||
# set(QM ${TARGET_NAME}_en.qm)
|
||||
# set(TS ${TARGET_NAME}_en.ts)
|
||||
# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
|
||||
|
||||
if (APPLE)
|
||||
# configure CMake to use a custom Info.plist
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>Application</name>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="1380"/>
|
||||
<source>Export Voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="1381"/>
|
||||
<source>Sparse Voxel Octree Files (*.svo)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="3617"/>
|
||||
<source>Open Script</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="3618"/>
|
||||
<source>JavaScript Files (*.js)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatWindow</name>
|
||||
<message>
|
||||
<location filename="ui/chatWindow.ui" line="29"/>
|
||||
<location filename="../build/interface/ui_chatWindow.h" line="153"/>
|
||||
<source>Chat</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/chatWindow.ui" line="57"/>
|
||||
<location filename="../build/interface/ui_chatWindow.h" line="154"/>
|
||||
<source>Connecting to XMPP...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/chatWindow.ui" line="78"/>
|
||||
<location filename="../build/interface/ui_chatWindow.h" line="155"/>
|
||||
<source> online now:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="src/ui/ChatWindow.cpp" line="135"/>
|
||||
<source>day</source>
|
||||
<translation>
|
||||
<numerusform>%n day</numerusform>
|
||||
<numerusform>%n days</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="src/ui/ChatWindow.cpp" line="135"/>
|
||||
<source>hour</source>
|
||||
<translation>
|
||||
<numerusform>%n hour</numerusform>
|
||||
<numerusform>%n hours</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="src/ui/ChatWindow.cpp" line="135"/>
|
||||
<source>minute</source>
|
||||
<translation>
|
||||
<numerusform>%n minute</numerusform>
|
||||
<numerusform>%n minutes</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>second</source>
|
||||
<translation type="vanished">
|
||||
<numerusform>%n second</numerusform>
|
||||
<numerusform>%n seconds</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ChatWindow.cpp" line="191"/>
|
||||
<source>%1 online now:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Dialog</name>
|
||||
<message>
|
||||
<location filename="ui/updateDialog.ui" line="20"/>
|
||||
<location filename="ui/updateDialog.ui" line="73"/>
|
||||
<location filename="../build/interface/ui_updateDialog.h" line="137"/>
|
||||
<location filename="../build/interface/ui_updateDialog.h" line="138"/>
|
||||
<source>Update Required</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/updateDialog.ui" line="129"/>
|
||||
<location filename="../build/interface/ui_updateDialog.h" line="140"/>
|
||||
<source>Download</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/updateDialog.ui" line="151"/>
|
||||
<location filename="../build/interface/ui_updateDialog.h" line="141"/>
|
||||
<source>Skip Version</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/updateDialog.ui" line="173"/>
|
||||
<location filename="../build/interface/ui_updateDialog.h" line="142"/>
|
||||
<source>Close</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Menu</name>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="456"/>
|
||||
<source>Open .ini config file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="458"/>
|
||||
<location filename="src/Menu.cpp" line="470"/>
|
||||
<source>Text files (*.ini)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="468"/>
|
||||
<source>Save .ini config file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="22"/>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="23"/>
|
||||
<source>Import Voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="24"/>
|
||||
<source>Loading ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="25"/>
|
||||
<source>Place voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="26"/>
|
||||
<source><b>Import</b> %1 as voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="27"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
7
interface/resources/images/kill-script.svg
Normal file
7
interface/resources/images/kill-script.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 11 11" enable-background="new 0 0 11 11" xml:space="preserve">
|
||||
<polygon fill="#C4C4C4" points="11,1.9 9.2,0 5.5,3.7 1.9,0 0,1.9 3.7,5.5 0,9.2 1.9,11 5.5,7.4 9.2,11 11,9.2 7.4,5.5 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 564 B |
24
interface/resources/images/reload.svg
Normal file
24
interface/resources/images/reload.svg
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 19.2 16" enable-background="new 0 0 19.2 16" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#F9F9F9" d="M13.1,13c-1.1,0.7-2.3,1.1-3.6,1.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.1,0-0.2,0c-0.1,0-0.3,0-0.4-0.1
|
||||
c-0.1,0-0.2,0-0.2-0.1c-0.1,0-0.2-0.1-0.4-0.1c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.3-0.1-0.4-0.2c0,0-0.1,0-0.1,0
|
||||
c-0.2-0.1-0.3-0.1-0.5-0.2c0,0,0,0,0,0c-0.5-0.3-1-0.6-1.4-1c0,0,0,0,0,0c-0.1-0.1-0.2-0.3-0.4-0.4c0,0,0-0.1-0.1-0.1
|
||||
C4,10.8,3.5,9.5,3.5,8h1.6L2.6,4.2L0,8h1.6c0,1.7,0.5,3.3,1.4,4.5c0,0,0,0,0,0.1c0.1,0.1,0.2,0.3,0.3,0.4c0,0,0.1,0.1,0.1,0.1
|
||||
c0.1,0.2,0.3,0.3,0.5,0.5c0,0,0,0,0,0c0.5,0.5,1.1,1,1.8,1.4c0,0,0,0,0.1,0c0.2,0.1,0.4,0.2,0.6,0.3c0,0,0.1,0,0.1,0.1
|
||||
c0.2,0.1,0.3,0.1,0.5,0.2c0.1,0,0.2,0.1,0.2,0.1c0.2,0,0.3,0.1,0.5,0.1c0.1,0,0.2,0.1,0.3,0.1c0,0,0.1,0,0.1,0
|
||||
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.3,0,0.5,0,0.8,0c1.6,0,3.2-0.5,4.6-1.5c0.4-0.3,0.5-0.9,0.2-1.3
|
||||
C14.2,12.8,13.6,12.7,13.1,13z"/>
|
||||
<path fill="#F9F9F9" d="M17.6,8c0-1.7-0.5-3.2-1.4-4.5c0,0,0,0,0-0.1C16.1,3.3,16,3.1,15.8,3c0,0,0,0,0-0.1c-0.8-1-1.8-1.8-3-2.2
|
||||
c0,0-0.1,0-0.1,0c-0.2-0.1-0.4-0.1-0.6-0.2c-0.1,0-0.1,0-0.2-0.1c-0.2-0.1-0.3-0.1-0.5-0.1c-0.1,0-0.2,0-0.3-0.1c0,0-0.1,0-0.1,0
|
||||
c-0.1,0-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0c-0.2,0-0.4,0-0.6,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0C8,0,6.4,0.5,5,1.4
|
||||
C4.6,1.8,4.5,2.4,4.8,2.8C5.1,3.2,5.7,3.3,6.1,3c1.1-0.7,2.3-1.1,3.5-1.1c0.2,0,0.4,0,0.5,0c0.1,0,0.1,0,0.2,0
|
||||
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.1,0,0.3,0.1,0.4,0.1c0,0,0.1,0,0.1,0c0.2,0.1,0.3,0.1,0.5,0.2c0,0,0,0,0,0
|
||||
c0.9,0.4,1.7,1,2.3,1.7c0,0,0,0,0,0c0.9,1.1,1.4,2.4,1.4,3.9h-1.6l2.6,3.8L19.2,8H17.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
7
interface/resources/images/stop.svg
Normal file
7
interface/resources/images/stop.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 14 14" enable-background="new 0 0 14 14" xml:space="preserve">
|
||||
<rect fill="#F9F9F9" width="14" height="14"/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
|
@ -1,5 +1,8 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/close.svg</file>
|
||||
<file>images/kill-script.svg</file>
|
||||
<file>images/reload.svg</file>
|
||||
<file>images/stop.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,6 +21,7 @@
|
|||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QPointer>
|
||||
#include <QHash>
|
||||
|
||||
#include <NetworkPacket.h>
|
||||
#include <NodeList.h>
|
||||
|
@ -65,6 +66,7 @@
|
|||
#include "ui/LogDialog.h"
|
||||
#include "ui/UpdateDialog.h"
|
||||
#include "ui/overlays/Overlays.h"
|
||||
#include "ui/RunningScriptsWidget.h"
|
||||
#include "voxels/VoxelFade.h"
|
||||
#include "voxels/VoxelHideShowThread.h"
|
||||
#include "voxels/VoxelImporter.h"
|
||||
|
@ -93,6 +95,7 @@ static const float NODE_KILLED_GREEN = 0.0f;
|
|||
static const float NODE_KILLED_BLUE = 0.0f;
|
||||
|
||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||
static const QString CUSTOM_URL_SCHEME = "hifi:";
|
||||
|
||||
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
||||
static const float BILLBOARD_DISTANCE = 5.0f; // meters
|
||||
|
@ -112,7 +115,7 @@ public:
|
|||
~Application();
|
||||
|
||||
void restoreSizeAndPosition();
|
||||
void loadScript(const QString& fileNameString);
|
||||
void loadScript(const QString& fileNameString);
|
||||
void loadScripts();
|
||||
void storeSizeAndPosition();
|
||||
void clearScriptsBeforeRunning();
|
||||
|
@ -136,9 +139,9 @@ public:
|
|||
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
void dropEvent(QDropEvent *event);
|
||||
|
||||
|
||||
bool event(QEvent* event);
|
||||
|
||||
|
||||
void makeVoxel(glm::vec3 position,
|
||||
float scale,
|
||||
unsigned char red,
|
||||
|
@ -226,6 +229,8 @@ public:
|
|||
|
||||
void skipVersion(QString latestVersion);
|
||||
|
||||
QStringList getRunningScripts() { return _scriptEnginesHash.keys(); }
|
||||
|
||||
signals:
|
||||
|
||||
/// Fired when we're simulating; allows external parties to hook in.
|
||||
|
@ -233,10 +238,10 @@ signals:
|
|||
|
||||
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
|
||||
void renderingInWorldInterface();
|
||||
|
||||
|
||||
/// Fired when the import window is closed
|
||||
void importDone();
|
||||
|
||||
|
||||
public slots:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void updateWindowTitle();
|
||||
|
@ -259,31 +264,32 @@ public slots:
|
|||
void toggleLogDialog();
|
||||
void initAvatarAndViewFrustum();
|
||||
void stopAllScripts();
|
||||
void stopScript(const QString& scriptName);
|
||||
void reloadAllScripts();
|
||||
|
||||
void uploadFST();
|
||||
void toggleRunningScriptsWidget();
|
||||
|
||||
void uploadFST(bool isHead);
|
||||
void uploadHead();
|
||||
void uploadSkeleton();
|
||||
|
||||
private slots:
|
||||
void timer();
|
||||
void idle();
|
||||
|
||||
|
||||
void connectedToDomain(const QString& hostname);
|
||||
|
||||
void setFullscreen(bool fullscreen);
|
||||
void setEnable3DTVMode(bool enable3DTVMode);
|
||||
void cameraMenuChanged();
|
||||
|
||||
|
||||
glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint);
|
||||
|
||||
void closeMirrorView();
|
||||
void restoreMirrorView();
|
||||
void shrinkMirrorView();
|
||||
void resetSensors();
|
||||
|
||||
void parseVersionXml();
|
||||
|
||||
void removeScriptName(const QString& fileNameString);
|
||||
void cleanupScriptMenuItem(const QString& scriptMenuName);
|
||||
void parseVersionXml();
|
||||
|
||||
private:
|
||||
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
|
||||
|
@ -312,7 +318,6 @@ private:
|
|||
void updateMetavoxels(float deltaTime);
|
||||
void updateCamera(float deltaTime);
|
||||
void updateDialogs(float deltaTime);
|
||||
void updateAudio(float deltaTime);
|
||||
void updateCursor(float deltaTime);
|
||||
|
||||
Avatar* findLookatTargetAvatar(glm::vec3& eyePosition, QUuid &nodeUUID);
|
||||
|
@ -328,10 +333,6 @@ private:
|
|||
|
||||
void updateShadowMap();
|
||||
void displayOverlay();
|
||||
void displayStatsBackground(unsigned int rgba, int x, int y, int width, int height);
|
||||
void displayStats();
|
||||
void checkStatsClick();
|
||||
void toggleStatsExpanded();
|
||||
void renderRearViewMirror(const QRect& region, bool billboard = false);
|
||||
void renderViewFrustum(ViewFrustum& viewFrustum);
|
||||
|
||||
|
@ -352,9 +353,8 @@ private:
|
|||
QMainWindow* _window;
|
||||
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
|
||||
|
||||
bool _statsExpanded;
|
||||
BandwidthMeter _bandwidthMeter;
|
||||
|
||||
|
||||
QThread* _nodeThread;
|
||||
DatagramProcessor _datagramProcessor;
|
||||
|
||||
|
@ -373,7 +373,7 @@ private:
|
|||
timeval _lastTimeUpdated;
|
||||
bool _justStarted;
|
||||
Stars _stars;
|
||||
|
||||
|
||||
BuckyBalls _buckyBalls;
|
||||
|
||||
VoxelSystem _voxels;
|
||||
|
@ -397,6 +397,7 @@ private:
|
|||
quint64 _lastQueriedTime;
|
||||
|
||||
Oscilloscope _audioScope;
|
||||
float _trailingAudioLoudness;
|
||||
|
||||
OctreeQuery _octreeQuery; // NodeData derived class for querying voxels from voxel server
|
||||
|
||||
|
@ -407,7 +408,6 @@ private:
|
|||
Visage _visage;
|
||||
|
||||
SixenseManager _sixenseManager;
|
||||
QStringList _activeScripts;
|
||||
|
||||
Camera _myCamera; // My view onto the world
|
||||
Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode
|
||||
|
@ -465,9 +465,6 @@ private:
|
|||
int _packetsPerSecond;
|
||||
int _bytesPerSecond;
|
||||
|
||||
int _recentMaxPackets; // recent max incoming voxel packets to process
|
||||
bool _resetRecentMaxPacketsSoon;
|
||||
|
||||
StDev _idleLoopStdev;
|
||||
float _idleLoopMeasuredJitter;
|
||||
|
||||
|
@ -483,16 +480,21 @@ private:
|
|||
ControllerScriptingInterface _controllerScriptingInterface;
|
||||
QPointer<LogDialog> _logDialog;
|
||||
|
||||
QString _previousScriptLocation;
|
||||
|
||||
FileLogger* _logger;
|
||||
|
||||
void checkVersion();
|
||||
void displayUpdateDialog();
|
||||
bool shouldSkipVersion(QString latestVersion);
|
||||
void takeSnapshot();
|
||||
|
||||
|
||||
TouchEvent _lastTouchEvent;
|
||||
|
||||
|
||||
Overlays _overlays;
|
||||
|
||||
RunningScriptsWidget* _runningScriptsWidget;
|
||||
QHash<QString, ScriptEngine*> _scriptEnginesHash;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__Application__) */
|
||||
|
|
|
@ -60,15 +60,16 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
|
|||
_measuredJitter(0),
|
||||
_jitterBufferSamples(initialJitterBufferSamples),
|
||||
_lastInputLoudness(0),
|
||||
_timeSinceLastClip(-1.0),
|
||||
_dcOffset(0),
|
||||
_noiseGateMeasuredFloor(0),
|
||||
_noiseGateSampleCounter(0),
|
||||
_noiseGateOpen(false),
|
||||
_noiseGateEnabled(true),
|
||||
_toneInjectionEnabled(false),
|
||||
_noiseGateFramesToClose(0),
|
||||
_lastVelocity(0),
|
||||
_lastAcceleration(0),
|
||||
_totalPacketsReceived(0),
|
||||
_totalInputAudioSamples(0),
|
||||
_collisionSoundMagnitude(0.0f),
|
||||
_collisionSoundFrequency(0.0f),
|
||||
_collisionSoundNoise(0.0f),
|
||||
|
@ -95,6 +96,7 @@ void Audio::reset() {
|
|||
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
||||
QAudioDeviceInfo result;
|
||||
foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) {
|
||||
qDebug() << audioDevice.deviceName() << " " << deviceName;
|
||||
if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) {
|
||||
result = audioDevice;
|
||||
}
|
||||
|
@ -162,6 +164,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
|||
qDebug() << "output device:" << woc.szPname;
|
||||
deviceName = woc.szPname;
|
||||
}
|
||||
qDebug() << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
|
||||
|
||||
return getNamedAudioDeviceForMode(mode, deviceName);
|
||||
#endif
|
||||
|
||||
|
@ -279,8 +283,6 @@ void linearResampling(int16_t* sourceSamples, int16_t* destinationSamples,
|
|||
}
|
||||
}
|
||||
|
||||
const int CALLBACK_ACCELERATOR_RATIO = 2;
|
||||
|
||||
void Audio::start() {
|
||||
|
||||
// set up the desired audio format
|
||||
|
@ -302,9 +304,18 @@ void Audio::start() {
|
|||
qDebug() << "The default audio output device is" << outputDeviceInfo.deviceName();
|
||||
bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo);
|
||||
|
||||
if (!inputFormatSupported || !outputFormatSupported) {
|
||||
qDebug() << "Unable to set up audio I/O because of a problem with input or output formats.";
|
||||
if (!inputFormatSupported) {
|
||||
qDebug() << "Unable to set up audio input because of a problem with input format.";
|
||||
}
|
||||
if (!outputFormatSupported) {
|
||||
qDebug() << "Unable to set up audio output because of a problem with output format.";
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::stop() {
|
||||
// "switch" to invalid devices in order to shut down the state
|
||||
switchInputToAudioDevice(QAudioDeviceInfo());
|
||||
switchOutputToAudioDevice(QAudioDeviceInfo());
|
||||
}
|
||||
|
||||
QString Audio::getDefaultDeviceName(QAudio::Mode mode) {
|
||||
|
@ -321,10 +332,12 @@ QVector<QString> Audio::getDeviceNames(QAudio::Mode mode) {
|
|||
}
|
||||
|
||||
bool Audio::switchInputToAudioDevice(const QString& inputDeviceName) {
|
||||
qDebug() << "DEBUG [" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]";
|
||||
return switchInputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName));
|
||||
}
|
||||
|
||||
bool Audio::switchOutputToAudioDevice(const QString& outputDeviceName) {
|
||||
qDebug() << "DEBUG [" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]";
|
||||
return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName));
|
||||
}
|
||||
|
||||
|
@ -336,14 +349,13 @@ void Audio::handleAudioInput() {
|
|||
|
||||
static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes);
|
||||
|
||||
static float inputToNetworkInputRatio = _numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO
|
||||
/ NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
|
||||
float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes);
|
||||
|
||||
static unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio;
|
||||
unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio;
|
||||
|
||||
QByteArray inputByteArray = _inputDevice->readAll();
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) {
|
||||
// if this person wants local loopback add that to the locally injected audio
|
||||
|
||||
if (!_loopbackOutputDevice && _loopbackAudioOutput) {
|
||||
|
@ -356,7 +368,7 @@ void Audio::handleAudioInput() {
|
|||
_loopbackOutputDevice->write(inputByteArray);
|
||||
}
|
||||
} else {
|
||||
static float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate())
|
||||
float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate())
|
||||
* (_outputFormat.channelCount() / _inputFormat.channelCount());
|
||||
|
||||
QByteArray loopBackByteArray(inputByteArray.size() * loopbackOutputToInputRatio, 0);
|
||||
|
@ -381,9 +393,6 @@ void Audio::handleAudioInput() {
|
|||
// zero out the monoAudioSamples array and the locally injected audio
|
||||
memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
|
||||
// zero out the locally injected audio in preparation for audio procedural sounds
|
||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
|
||||
if (!_muted) {
|
||||
// we aren't muted, downsample the input audio
|
||||
linearResampling((int16_t*) inputAudioSamples,
|
||||
|
@ -391,7 +400,7 @@ void Audio::handleAudioInput() {
|
|||
inputSamplesRequired,
|
||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
||||
_inputFormat, _desiredInputFormat);
|
||||
|
||||
|
||||
//
|
||||
// Impose Noise Gate
|
||||
//
|
||||
|
@ -420,16 +429,27 @@ void Audio::handleAudioInput() {
|
|||
const int NOISE_GATE_CLOSE_FRAME_DELAY = 5;
|
||||
const int NOISE_GATE_FRAMES_TO_AVERAGE = 5;
|
||||
const float DC_OFFSET_AVERAGING = 0.99f;
|
||||
const float CLIPPING_THRESHOLD = 0.90f;
|
||||
|
||||
//
|
||||
// Check clipping, adjust DC offset, and check if should open noise gate
|
||||
//
|
||||
float measuredDcOffset = 0.f;
|
||||
|
||||
// Increment the time since the last clip
|
||||
if (_timeSinceLastClip >= 0.0f) {
|
||||
_timeSinceLastClip += (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float) SAMPLE_RATE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
|
||||
measuredDcOffset += monoAudioSamples[i];
|
||||
monoAudioSamples[i] -= (int16_t) _dcOffset;
|
||||
thisSample = fabsf(monoAudioSamples[i]);
|
||||
if (thisSample >= (32767.f * CLIPPING_THRESHOLD)) {
|
||||
_timeSinceLastClip = 0.0f;
|
||||
}
|
||||
loudness += thisSample;
|
||||
// Noise Reduction: Count peaks above the average loudness
|
||||
if (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT)) {
|
||||
if (_noiseGateEnabled && (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT))) {
|
||||
samplesOverNoiseGate++;
|
||||
}
|
||||
}
|
||||
|
@ -442,32 +462,41 @@ void Audio::handleAudioInput() {
|
|||
_dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.f - DC_OFFSET_AVERAGING) * measuredDcOffset;
|
||||
}
|
||||
|
||||
//
|
||||
_lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
||||
float averageOfAllSampleFrames = 0.f;
|
||||
_noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness;
|
||||
if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) {
|
||||
float smallestSample = FLT_MAX;
|
||||
for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) {
|
||||
float thisAverage = 0.0f;
|
||||
for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) {
|
||||
thisAverage += _noiseSampleFrames[j];
|
||||
averageOfAllSampleFrames += _noiseSampleFrames[j];
|
||||
}
|
||||
thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE;
|
||||
|
||||
if (thisAverage < smallestSample) {
|
||||
smallestSample = thisAverage;
|
||||
}
|
||||
// Add tone injection if enabled
|
||||
const float TONE_FREQ = 220.f / SAMPLE_RATE * TWO_PI;
|
||||
const float QUARTER_VOLUME = 8192.f;
|
||||
if (_toneInjectionEnabled) {
|
||||
loudness = 0.f;
|
||||
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
|
||||
monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample));
|
||||
loudness += fabsf(monoAudioSamples[i]);
|
||||
}
|
||||
averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES;
|
||||
_noiseGateMeasuredFloor = smallestSample;
|
||||
_noiseGateSampleCounter = 0;
|
||||
|
||||
}
|
||||
_lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
||||
if (_noiseGateEnabled) {
|
||||
// If Noise Gate is enabled, check and turn the gate on and off
|
||||
if (!_toneInjectionEnabled && _noiseGateEnabled) {
|
||||
float averageOfAllSampleFrames = 0.f;
|
||||
_noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness;
|
||||
if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) {
|
||||
float smallestSample = FLT_MAX;
|
||||
for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) {
|
||||
float thisAverage = 0.0f;
|
||||
for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) {
|
||||
thisAverage += _noiseSampleFrames[j];
|
||||
averageOfAllSampleFrames += _noiseSampleFrames[j];
|
||||
}
|
||||
thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE;
|
||||
|
||||
if (thisAverage < smallestSample) {
|
||||
smallestSample = thisAverage;
|
||||
}
|
||||
}
|
||||
averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES;
|
||||
_noiseGateMeasuredFloor = smallestSample;
|
||||
_noiseGateSampleCounter = 0;
|
||||
|
||||
}
|
||||
if (samplesOverNoiseGate > NOISE_GATE_WIDTH) {
|
||||
_noiseGateOpen = true;
|
||||
_noiseGateFramesToClose = NOISE_GATE_CLOSE_FRAME_DELAY;
|
||||
|
@ -492,28 +521,8 @@ void Audio::handleAudioInput() {
|
|||
_lastInputLoudness = 0;
|
||||
}
|
||||
|
||||
// add procedural effects to the appropriate input samples
|
||||
addProceduralSounds(monoAudioSamples,
|
||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
||||
if (!_proceduralOutputDevice && _proceduralAudioOutput) {
|
||||
_proceduralOutputDevice = _proceduralAudioOutput->start();
|
||||
}
|
||||
|
||||
// send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice
|
||||
QByteArray proceduralOutput;
|
||||
proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * _outputFormat.sampleRate() *
|
||||
_outputFormat.channelCount() * sizeof(int16_t) / (_desiredInputFormat.sampleRate() *
|
||||
_desiredInputFormat.channelCount()));
|
||||
|
||||
linearResampling(_localProceduralSamples,
|
||||
reinterpret_cast<int16_t*>(proceduralOutput.data()),
|
||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
||||
proceduralOutput.size() / sizeof(int16_t),
|
||||
_desiredInputFormat, _outputFormat);
|
||||
|
||||
if (_proceduralOutputDevice) {
|
||||
_proceduralOutputDevice->write(proceduralOutput);
|
||||
if (_proceduralAudioOutput) {
|
||||
processProceduralAudio(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
@ -593,9 +602,37 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_audioOutput) {
|
||||
// Audio output must exist and be correctly set up if we're going to process received audio
|
||||
processReceivedAudio(audioByteArray);
|
||||
}
|
||||
|
||||
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
|
||||
|
||||
_lastReceiveTime = currentReceiveTime;
|
||||
}
|
||||
|
||||
bool Audio::mousePressEvent(int x, int y) {
|
||||
if (_iconBounds.contains(x, y)) {
|
||||
toggleMute();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Audio::toggleMute() {
|
||||
_muted = !_muted;
|
||||
muteToggled();
|
||||
}
|
||||
|
||||
void Audio::toggleAudioNoiseReduction() {
|
||||
_noiseGateEnabled = !_noiseGateEnabled;
|
||||
}
|
||||
|
||||
void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
||||
_ringBuffer.parseData(audioByteArray);
|
||||
|
||||
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
||||
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
||||
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
|
||||
|
||||
if (!_ringBuffer.isStarved() && _audioOutput && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
|
||||
|
@ -650,33 +687,42 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
|||
}
|
||||
delete[] ringBufferSamples;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
|
||||
|
||||
_lastReceiveTime = currentReceiveTime;
|
||||
}
|
||||
|
||||
bool Audio::mousePressEvent(int x, int y) {
|
||||
if (_iconBounds.contains(x, y)) {
|
||||
toggleMute();
|
||||
return true;
|
||||
void Audio::processProceduralAudio(int16_t* monoInput, int numSamples) {
|
||||
|
||||
// zero out the locally injected audio in preparation for audio procedural sounds
|
||||
// This is correlated to numSamples, so it really needs to be numSamples * sizeof(sample)
|
||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
// add procedural effects to the appropriate input samples
|
||||
addProceduralSounds(monoInput, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
||||
if (!_proceduralOutputDevice) {
|
||||
_proceduralOutputDevice = _proceduralAudioOutput->start();
|
||||
}
|
||||
|
||||
// send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice
|
||||
QByteArray proceduralOutput;
|
||||
proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * _outputFormat.sampleRate() *
|
||||
_outputFormat.channelCount() * sizeof(int16_t) / (_desiredInputFormat.sampleRate() *
|
||||
_desiredInputFormat.channelCount()));
|
||||
|
||||
linearResampling(_localProceduralSamples,
|
||||
reinterpret_cast<int16_t*>(proceduralOutput.data()),
|
||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
||||
proceduralOutput.size() / sizeof(int16_t),
|
||||
_desiredInputFormat, _outputFormat);
|
||||
|
||||
if (_proceduralOutputDevice) {
|
||||
_proceduralOutputDevice->write(proceduralOutput);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Audio::toggleMute() {
|
||||
_muted = !_muted;
|
||||
muteToggled();
|
||||
void Audio::toggleToneInjection() {
|
||||
_toneInjectionEnabled = !_toneInjectionEnabled;
|
||||
}
|
||||
|
||||
void Audio::toggleAudioNoiseReduction() {
|
||||
_noiseGateEnabled = !_noiseGateEnabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Take a pointer to the acquired microphone input samples and add procedural sounds
|
||||
void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
|
||||
float sample;
|
||||
|
@ -822,7 +868,7 @@ bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
|
|||
// cleanup any previously initialized device
|
||||
if (_audioInput) {
|
||||
_audioInput->stop();
|
||||
disconnect(_inputDevice, 0, 0, 0);
|
||||
disconnect(_inputDevice);
|
||||
_inputDevice = NULL;
|
||||
|
||||
delete _audioInput;
|
||||
|
@ -840,13 +886,12 @@ bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
|
|||
qDebug() << "The format to be used for audio input is" << _inputFormat;
|
||||
|
||||
_audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this);
|
||||
_numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount()
|
||||
* (_inputFormat.sampleRate() / SAMPLE_RATE)
|
||||
/ CALLBACK_ACCELERATOR_RATIO;
|
||||
_numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat);
|
||||
_audioInput->setBufferSize(_numInputCallbackBytes);
|
||||
|
||||
// how do we want to handle input working, but output not working?
|
||||
_inputRingBuffer.resizeForFrameSize(_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / sizeof(int16_t));
|
||||
int numFrameSamples = calculateNumberOfFrameSamples(_numInputCallbackBytes);
|
||||
_inputRingBuffer.resizeForFrameSize(numFrameSamples);
|
||||
_inputDevice = _audioInput->start();
|
||||
connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput()));
|
||||
|
||||
|
@ -903,3 +948,41 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
|
|||
}
|
||||
return supportedFormat;
|
||||
}
|
||||
|
||||
// The following constant is operating system dependent due to differences in
|
||||
// the way input audio is handled. The audio input buffer size is inversely
|
||||
// proportional to the accelerator ratio.
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.4f;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
const float Audio::CALLBACK_ACCELERATOR_RATIO = 2.0f;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
const float Audio::CALLBACK_ACCELERATOR_RATIO = 2.0f;
|
||||
#endif
|
||||
|
||||
int Audio::calculateNumberOfInputCallbackBytes(const QAudioFormat& format) {
|
||||
int numInputCallbackBytes = (int)(((NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL
|
||||
* format.channelCount()
|
||||
* (format.sampleRate() / SAMPLE_RATE))
|
||||
/ CALLBACK_ACCELERATOR_RATIO) + 0.5f);
|
||||
|
||||
return numInputCallbackBytes;
|
||||
}
|
||||
|
||||
float Audio::calculateDeviceToNetworkInputRatio(int numBytes) {
|
||||
float inputToNetworkInputRatio = (int)((_numInputCallbackBytes
|
||||
* CALLBACK_ACCELERATOR_RATIO
|
||||
/ NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL) + 0.5f);
|
||||
|
||||
return inputToNetworkInputRatio;
|
||||
}
|
||||
|
||||
int Audio::calculateNumberOfFrameSamples(int numBytes) {
|
||||
int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t);
|
||||
return frameSamples;
|
||||
}
|
||||
|
|
|
@ -47,13 +47,11 @@ public:
|
|||
Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent = 0);
|
||||
|
||||
float getLastInputLoudness() const { return glm::max(_lastInputLoudness - _noiseGateMeasuredFloor, 0.f); }
|
||||
float getTimeSinceLastClip() const { return _timeSinceLastClip; }
|
||||
float getAudioAverageInputLoudness() const { return _lastInputLoudness; }
|
||||
|
||||
void setNoiseGateEnabled(bool noiseGateEnabled) { _noiseGateEnabled = noiseGateEnabled; }
|
||||
|
||||
void setLastAcceleration(const glm::vec3 lastAcceleration) { _lastAcceleration = lastAcceleration; }
|
||||
void setLastVelocity(const glm::vec3 lastVelocity) { _lastVelocity = lastVelocity; }
|
||||
|
||||
|
||||
void setJitterBufferSamples(int samples) { _jitterBufferSamples = samples; }
|
||||
int getJitterBufferSamples() { return _jitterBufferSamples; }
|
||||
|
||||
|
@ -78,11 +76,13 @@ public:
|
|||
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
|
||||
void handleAudioInput();
|
||||
void reset();
|
||||
void toggleMute();
|
||||
void toggleAudioNoiseReduction();
|
||||
void toggleToneInjection();
|
||||
|
||||
virtual void handleAudioByteArray(const QByteArray& audioByteArray);
|
||||
|
||||
|
@ -130,16 +130,17 @@ private:
|
|||
float _measuredJitter;
|
||||
int16_t _jitterBufferSamples;
|
||||
float _lastInputLoudness;
|
||||
float _timeSinceLastClip;
|
||||
float _dcOffset;
|
||||
float _noiseGateMeasuredFloor;
|
||||
float* _noiseSampleFrames;
|
||||
int _noiseGateSampleCounter;
|
||||
bool _noiseGateOpen;
|
||||
bool _noiseGateEnabled;
|
||||
bool _toneInjectionEnabled;
|
||||
int _noiseGateFramesToClose;
|
||||
glm::vec3 _lastVelocity;
|
||||
glm::vec3 _lastAcceleration;
|
||||
int _totalPacketsReceived;
|
||||
int _totalInputAudioSamples;
|
||||
|
||||
float _collisionSoundMagnitude;
|
||||
float _collisionSoundFrequency;
|
||||
|
@ -165,11 +166,26 @@ private:
|
|||
// Audio callback in class context.
|
||||
inline void performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight);
|
||||
|
||||
// Process procedural audio by
|
||||
// 1. Echo to the local procedural output device
|
||||
// 2. Mix with the audio input
|
||||
void processProceduralAudio(int16_t* monoInput, int numSamples);
|
||||
|
||||
// Add sounds that we want the user to not hear themselves, by adding on top of mic input signal
|
||||
void addProceduralSounds(int16_t* monoInput, int numSamples);
|
||||
|
||||
// Process received audio
|
||||
void processReceivedAudio(const QByteArray& audioByteArray);
|
||||
|
||||
bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo);
|
||||
bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo);
|
||||
|
||||
// Callback acceleration dependent calculations
|
||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
||||
int calculateNumberOfInputCallbackBytes(const QAudioFormat& format);
|
||||
int calculateNumberOfFrameSamples(int numBytes);
|
||||
float calculateDeviceToNetworkInputRatio(int numBytes);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include <AccountManager.h>
|
||||
#include <XmppClient.h>
|
||||
#include <UUID.h>
|
||||
#include <FileDownloader.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
|
@ -37,7 +36,7 @@
|
|||
#include "Util.h"
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/MetavoxelEditor.h"
|
||||
#include "ui/ModelBrowser.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -87,7 +86,7 @@ Menu::Menu() :
|
|||
_loginAction(NULL)
|
||||
{
|
||||
Application *appInstance = Application::getInstance();
|
||||
|
||||
|
||||
QMenu* fileMenu = addMenu("File");
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
@ -100,23 +99,24 @@ Menu::Menu() :
|
|||
#endif
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
|
||||
_loginAction = addActionToQMenuAndActionHash(fileMenu, MenuOption::Logout);
|
||||
|
||||
|
||||
// call our toggle login function now so the menu option is setup properly
|
||||
toggleLoginMenuItem();
|
||||
|
||||
|
||||
// connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item
|
||||
connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem);
|
||||
connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem);
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL,
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts()));
|
||||
_activeScriptsMenu = fileMenu->addMenu("Running Scripts");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J,
|
||||
appInstance, SLOT(toggleRunningScriptsWidget()));
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Go");
|
||||
addActionToQMenuAndActionHash(fileMenu,
|
||||
|
@ -146,8 +146,8 @@ Menu::Menu() :
|
|||
SLOT(goTo()));
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadFST, 0, Application::getInstance(), SLOT(uploadFST()));
|
||||
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadSkeleton, 0, Application::getInstance(), SLOT(uploadSkeleton()));
|
||||
addDisabledActionAndSeparator(fileMenu, "Settings");
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
|
||||
|
@ -172,26 +172,25 @@ Menu::Menu() :
|
|||
addDisabledActionAndSeparator(editMenu, "Physics");
|
||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
addAvatarCollisionSubMenu(editMenu);
|
||||
|
||||
QMenu* toolsMenu = addMenu("Tools");
|
||||
addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor()));
|
||||
|
||||
#ifdef HAVE_QXMPP
|
||||
_chatAction = addActionToQMenuAndActionHash(toolsMenu,
|
||||
MenuOption::Chat,
|
||||
Qt::Key_Return,
|
||||
this,
|
||||
SLOT(showChat()));
|
||||
#ifdef HAVE_QXMPP
|
||||
|
||||
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
|
||||
toggleChat();
|
||||
connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat()));
|
||||
connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat()));
|
||||
#else
|
||||
_chatAction->setEnabled(false);
|
||||
#endif
|
||||
|
||||
QMenu* viewMenu = addMenu("View");
|
||||
|
@ -207,7 +206,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false,
|
||||
appInstance, SLOT(cameraMenuChanged()));
|
||||
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0,
|
||||
false,
|
||||
appInstance,
|
||||
|
@ -241,7 +240,7 @@ Menu::Menu() :
|
|||
addDisabledActionAndSeparator(viewMenu, "Stats");
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash);
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, appInstance, SLOT(toggleLogDialog()));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Oscilloscope, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Oscilloscope, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true);
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
|
||||
addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails()));
|
||||
|
@ -282,8 +281,9 @@ Menu::Menu() :
|
|||
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
|
||||
|
@ -333,16 +333,16 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings, Qt::CTRL | Qt::SHIFT | Qt::Key_P);
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings, Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::CullSharedFaces,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::CullSharedFaces,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
|
||||
false,
|
||||
appInstance->getVoxels(),
|
||||
SLOT(cullSharedFaces()));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::ShowCulledSharedFaces,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_X,
|
||||
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
|
||||
MenuOption::ShowCulledSharedFaces,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_X,
|
||||
false,
|
||||
appInstance->getVoxels(),
|
||||
SLOT(showCulledSharedFaces()));
|
||||
|
@ -360,14 +360,19 @@ Menu::Menu() :
|
|||
false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleMute()));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioToneInjection,
|
||||
0,
|
||||
false,
|
||||
appInstance->getAudio(),
|
||||
SLOT(toggleToneInjection()));
|
||||
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
|
||||
this,
|
||||
SLOT(pasteToVoxel()));
|
||||
|
||||
connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled()));
|
||||
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
QMenu* helpMenu = addMenu("Help");
|
||||
QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp);
|
||||
|
@ -571,7 +576,7 @@ void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString&
|
|||
QAction* separatorText = new QAction(actionName,destinationMenu);
|
||||
separatorText->setEnabled(false);
|
||||
destinationMenu->insertAction(actionBefore, separatorText);
|
||||
|
||||
|
||||
} else {
|
||||
destinationMenu->addSeparator();
|
||||
(destinationMenu->addAction(actionName))->setEnabled(false);
|
||||
|
@ -623,7 +628,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu,
|
|||
const char* member,
|
||||
int menuItemLocation) {
|
||||
|
||||
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
|
||||
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
|
||||
QAction::NoRole, menuItemLocation);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(checked);
|
||||
|
@ -677,55 +682,55 @@ const float DIALOG_RATIO_OF_WINDOW = 0.30f;
|
|||
void Menu::loginForCurrentDomain() {
|
||||
QDialog loginDialog(Application::getInstance()->getWindow());
|
||||
loginDialog.setWindowTitle("Login");
|
||||
|
||||
|
||||
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
loginDialog.setLayout(layout);
|
||||
loginDialog.setWindowFlags(Qt::Sheet);
|
||||
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form, 1);
|
||||
|
||||
|
||||
QLineEdit* loginLineEdit = new QLineEdit();
|
||||
loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
form->addRow("Login:", loginLineEdit);
|
||||
|
||||
|
||||
QLineEdit* passwordLineEdit = new QLineEdit();
|
||||
passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
passwordLineEdit->setEchoMode(QLineEdit::Password);
|
||||
form->addRow("Password:", passwordLineEdit);
|
||||
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
layout->addWidget(buttons);
|
||||
|
||||
|
||||
int dialogReturn = loginDialog.exec();
|
||||
|
||||
|
||||
if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) {
|
||||
// attempt to get an access token given this username and password
|
||||
AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text());
|
||||
}
|
||||
|
||||
|
||||
sendFakeEnterEvent();
|
||||
}
|
||||
|
||||
void Menu::editPreferences() {
|
||||
Application* applicationInstance = Application::getInstance();
|
||||
ModelBrowser headBrowser(Head);
|
||||
ModelBrowser skeletonBrowser(Skeleton);
|
||||
Application* applicationInstance = Application::getInstance();
|
||||
ModelsBrowser headBrowser(Head);
|
||||
ModelsBrowser skeletonBrowser(Skeleton);
|
||||
|
||||
const QString BROWSE_BUTTON_TEXT = "Browse";
|
||||
|
||||
QDialog dialog(applicationInstance->getWindow());
|
||||
dialog.setWindowTitle("Interface Preferences");
|
||||
|
||||
|
||||
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
dialog.setLayout(layout);
|
||||
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form, 1);
|
||||
|
||||
|
||||
|
||||
QHBoxLayout headModelLayout;
|
||||
QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString();
|
||||
QLineEdit headURLEdit(faceURLString);
|
||||
|
@ -737,7 +742,7 @@ void Menu::editPreferences() {
|
|||
headModelLayout.addWidget(&headURLEdit);
|
||||
headModelLayout.addWidget(&headBrowseButton);
|
||||
form->addRow("Head URL:", &headModelLayout);
|
||||
|
||||
|
||||
QHBoxLayout skeletonModelLayout;
|
||||
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
|
||||
QLineEdit skeletonURLEdit(skeletonURLString);
|
||||
|
@ -749,7 +754,7 @@ void Menu::editPreferences() {
|
|||
skeletonModelLayout.addWidget(&skeletonURLEdit);
|
||||
skeletonModelLayout.addWidget(&SkeletonBrowseButton);
|
||||
form->addRow("Skeleton URL:", &skeletonModelLayout);
|
||||
|
||||
|
||||
|
||||
QString displayNameString = applicationInstance->getAvatar()->getDisplayName();
|
||||
QLineEdit* displayNameEdit = new QLineEdit(displayNameString);
|
||||
|
@ -813,25 +818,33 @@ void Menu::editPreferences() {
|
|||
if (ret == QDialog::Accepted) {
|
||||
bool shouldDispatchIdentityPacket = false;
|
||||
|
||||
if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) {
|
||||
if (headURLEdit.text() != faceURLString) {
|
||||
// change the faceModelURL in the profile, it will also update this user's BlendFace
|
||||
applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text()));
|
||||
if (headURLEdit.text().isEmpty()) {
|
||||
applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.placeholderText()));
|
||||
} else {
|
||||
applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text()));
|
||||
}
|
||||
shouldDispatchIdentityPacket = true;
|
||||
}
|
||||
|
||||
if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) {
|
||||
if (skeletonURLEdit.text() != skeletonURLString) {
|
||||
// change the skeletonModelURL in the profile, it will also update this user's Body
|
||||
applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text()));
|
||||
if (skeletonURLEdit.text().isEmpty()) {
|
||||
applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.placeholderText()));
|
||||
} else {
|
||||
applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text()));
|
||||
}
|
||||
shouldDispatchIdentityPacket = true;
|
||||
}
|
||||
|
||||
QString displayNameStr(displayNameEdit->text());
|
||||
|
||||
|
||||
if (displayNameStr != displayNameString) {
|
||||
applicationInstance->getAvatar()->setDisplayName(displayNameStr);
|
||||
shouldDispatchIdentityPacket = true;
|
||||
}
|
||||
|
||||
|
||||
if (shouldDispatchIdentityPacket) {
|
||||
applicationInstance->getAvatar()->sendIdentityPacket();
|
||||
}
|
||||
|
@ -864,10 +877,10 @@ void Menu::editPreferences() {
|
|||
|
||||
void Menu::goToDomain(const QString newDomain) {
|
||||
if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) {
|
||||
|
||||
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
Application::getInstance()->getAvatar()->sendKillAvatar();
|
||||
|
||||
|
||||
// give our nodeList the new domain-server hostname
|
||||
NodeList::getInstance()->getDomainInfo().setHostname(newDomain);
|
||||
}
|
||||
|
@ -897,7 +910,7 @@ void Menu::goToDomainDialog() {
|
|||
// the user input a new hostname, use that
|
||||
newHostname = domainDialog.textValue();
|
||||
}
|
||||
|
||||
|
||||
goToDomain(newHostname);
|
||||
}
|
||||
|
||||
|
@ -912,19 +925,58 @@ bool Menu::goToDestination(QString destination) {
|
|||
return LocationManager::getInstance().goToDestination(destination);
|
||||
}
|
||||
|
||||
void Menu::goTo(QString destination) {
|
||||
LocationManager::getInstance().goTo(destination);
|
||||
}
|
||||
|
||||
void Menu::goTo() {
|
||||
|
||||
|
||||
QInputDialog gotoDialog(Application::getInstance()->getWindow());
|
||||
gotoDialog.setWindowTitle("Go to");
|
||||
gotoDialog.setLabelText("Destination:");
|
||||
gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation");
|
||||
QString destination = QString();
|
||||
|
||||
gotoDialog.setTextValue(destination);
|
||||
gotoDialog.setWindowFlags(Qt::Sheet);
|
||||
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
|
||||
|
||||
|
||||
int dialogReturn = gotoDialog.exec();
|
||||
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
|
||||
goToUser(gotoDialog.textValue());
|
||||
QString desiredDestination = gotoDialog.textValue();
|
||||
|
||||
if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) {
|
||||
QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
|
||||
|
||||
if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
QString domain = urlParts[0];
|
||||
|
||||
// second part is either a destination coordinate or
|
||||
// a place name
|
||||
QString destination = urlParts[1];
|
||||
|
||||
// any third part is an avatar orientation.
|
||||
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
|
||||
|
||||
goToDomain(domain);
|
||||
|
||||
// goto either @user, #place, or x-xx,y-yy,z-zz
|
||||
// style co-ordinate.
|
||||
goTo(destination);
|
||||
|
||||
if (!orientation.isEmpty()) {
|
||||
// location orientation
|
||||
goToOrientation(orientation);
|
||||
}
|
||||
} else if (urlParts.count() == 1) {
|
||||
// location coordinates or place name
|
||||
QString destination = urlParts[0];
|
||||
goTo(destination);
|
||||
}
|
||||
|
||||
} else {
|
||||
goToUser(gotoDialog.textValue());
|
||||
}
|
||||
}
|
||||
sendFakeEnterEvent();
|
||||
}
|
||||
|
@ -1070,7 +1122,7 @@ void Menu::toggleLoginMenuItem() {
|
|||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
disconnect(_loginAction, 0, 0, 0);
|
||||
|
||||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
// change the menu item to logout
|
||||
_loginAction->setText("Logout " + accountManager.getUsername());
|
||||
|
@ -1078,7 +1130,7 @@ void Menu::toggleLoginMenuItem() {
|
|||
} else {
|
||||
// change the menu item to login
|
||||
_loginAction->setText("Login");
|
||||
|
||||
|
||||
connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain);
|
||||
}
|
||||
}
|
||||
|
@ -1180,7 +1232,7 @@ QString Menu::getLODFeedbackText() {
|
|||
} break;
|
||||
}
|
||||
|
||||
// distance feedback
|
||||
// distance feedback
|
||||
float voxelSizeScale = getVoxelSizeScale();
|
||||
float relativeToDefault = voxelSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
|
||||
QString result;
|
||||
|
@ -1195,7 +1247,7 @@ QString Menu::getLODFeedbackText() {
|
|||
}
|
||||
|
||||
void Menu::autoAdjustLOD(float currentFPS) {
|
||||
// NOTE: our first ~100 samples at app startup are completely all over the place, and we don't
|
||||
// NOTE: our first ~100 samples at app startup are completely all over the place, and we don't
|
||||
// really want to count them in our average, so we will ignore the real frame rates and stuff
|
||||
// our moving average with simulated good data
|
||||
const int IGNORE_THESE_SAMPLES = 100;
|
||||
|
@ -1207,30 +1259,32 @@ void Menu::autoAdjustLOD(float currentFPS) {
|
|||
_fastFPSAverage.updateAverage(currentFPS);
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
|
||||
const float ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
|
||||
const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000;
|
||||
if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) {
|
||||
if (_fastFPSAverage.getAverage() < ADJUST_AVATAR_LOD_DOWN_FPS) {
|
||||
if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) {
|
||||
// attempt to lower the detail in proportion to the fps difference
|
||||
float targetFps = (ADJUST_LOD_DOWN_FPS + ADJUST_LOD_UP_FPS) * 0.5f;
|
||||
float targetFps = (ADJUST_AVATAR_LOD_DOWN_FPS + ADJUST_LOD_UP_FPS) * 0.5f;
|
||||
float averageFps = _fastFPSAverage.getAverage();
|
||||
const float MAXIMUM_MULTIPLIER_SCALE = 2.0f;
|
||||
_avatarLODDistanceMultiplier *= (averageFps < EPSILON) ? MAXIMUM_MULTIPLIER_SCALE :
|
||||
qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps);
|
||||
const float MAXIMUM_DISTANCE_MULTIPLIER = 15.0f;
|
||||
_avatarLODDistanceMultiplier = qMin(MAXIMUM_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier *
|
||||
(averageFps < EPSILON ? MAXIMUM_MULTIPLIER_SCALE : qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps)));
|
||||
_lastAvatarDetailDrop = now;
|
||||
}
|
||||
} else if (_fastFPSAverage.getAverage() > ADJUST_LOD_UP_FPS) {
|
||||
// let the detail level creep slowly upwards
|
||||
const float DISTANCE_DECREASE_RATE = 0.02f;
|
||||
const float DISTANCE_DECREASE_RATE = 0.05f;
|
||||
const float MINIMUM_DISTANCE_MULTIPLIER = 0.1f;
|
||||
_avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER,
|
||||
_avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE);
|
||||
}
|
||||
|
||||
|
||||
bool changed = false;
|
||||
quint64 elapsed = now - _lastAdjust;
|
||||
|
||||
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS
|
||||
if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS
|
||||
&& _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||
|
||||
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
|
||||
|
@ -1243,7 +1297,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
|
|||
<< "_voxelSizeScale=" << _voxelSizeScale;
|
||||
}
|
||||
|
||||
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS
|
||||
if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS
|
||||
&& _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||
_voxelSizeScale *= ADJUST_LOD_UP_BY;
|
||||
if (_voxelSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||
|
@ -1254,7 +1308,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
|
|||
qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage()
|
||||
<< "_voxelSizeScale=" << _voxelSizeScale;
|
||||
}
|
||||
|
||||
|
||||
if (changed) {
|
||||
if (_lodToolsDialog) {
|
||||
_lodToolsDialog->reloadSliders();
|
||||
|
@ -1332,13 +1386,13 @@ void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
|
|||
|
||||
Application* appInstance = Application::getInstance();
|
||||
QObject* avatar = appInstance->getAvatar();
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
|
||||
0, false, avatar, SLOT(updateCollisionFlags()));
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
|
||||
0, true, avatar, SLOT(updateCollisionFlags()));
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
|
||||
0, false, avatar, SLOT(updateCollisionFlags()));
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
|
||||
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
|
||||
0, true, avatar, SLOT(updateCollisionFlags()));
|
||||
}
|
||||
|
||||
|
@ -1347,9 +1401,9 @@ QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) {
|
|||
if (menu) {
|
||||
menuActions = menu->actions();
|
||||
} else {
|
||||
menuActions = actions();
|
||||
menuActions = actions();
|
||||
}
|
||||
|
||||
|
||||
foreach (QAction* menuAction, menuActions) {
|
||||
if (menuName == menuAction->text()) {
|
||||
return menuAction;
|
||||
|
@ -1456,7 +1510,7 @@ QMenu* Menu::addMenu(const QString& menuName) {
|
|||
|
||||
void Menu::removeMenu(const QString& menuName) {
|
||||
QAction* action = getMenuAction(menuName);
|
||||
|
||||
|
||||
// only proceed if the menu actually exists
|
||||
if (action) {
|
||||
QString finalMenuPart;
|
||||
|
@ -1508,7 +1562,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
|
|||
if (!properties.shortcutKeySequence.isEmpty()) {
|
||||
shortcut = new QShortcut(properties.shortcutKeySequence, this);
|
||||
}
|
||||
|
||||
|
||||
// check for positioning requests
|
||||
int requestedPosition = properties.position;
|
||||
if (requestedPosition == UNSPECIFIED_POSITION && !properties.beforeItem.isEmpty()) {
|
||||
|
@ -1522,13 +1576,13 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
|
|||
requestedPosition = afterPosition + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QAction* menuItemAction = NULL;
|
||||
if (properties.isSeparator) {
|
||||
addDisabledActionAndSeparator(menuObj, properties.menuItemName, requestedPosition);
|
||||
} else if (properties.isCheckable) {
|
||||
menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName,
|
||||
properties.shortcutKeySequence, properties.isChecked,
|
||||
properties.shortcutKeySequence, properties.isChecked,
|
||||
MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), requestedPosition);
|
||||
} else {
|
||||
menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence,
|
||||
|
|
|
@ -95,8 +95,6 @@ public:
|
|||
// User Tweakable PPS from Voxel Server
|
||||
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; }
|
||||
|
||||
QMenu* getActiveScriptsMenu() { return _activeScriptsMenu;}
|
||||
|
||||
QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut = 0,
|
||||
|
@ -110,6 +108,7 @@ public:
|
|||
bool goToDestination(QString destination);
|
||||
void goToOrientation(QString orientation);
|
||||
void goToDomain(const QString newDomain);
|
||||
void goTo(QString destination);
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -124,7 +123,7 @@ public slots:
|
|||
void goTo();
|
||||
void goToUser(const QString& user);
|
||||
void pasteToVoxel();
|
||||
|
||||
|
||||
void toggleLoginMenuItem();
|
||||
|
||||
QMenu* addMenu(const QString& menuName);
|
||||
|
@ -166,7 +165,7 @@ private:
|
|||
void scanMenu(QMenu* menu, settingsAction modifySetting, QSettings* set);
|
||||
|
||||
/// helper method to have separators with labels that are also compatible with OS X
|
||||
void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName,
|
||||
void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION);
|
||||
|
||||
QAction* addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu,
|
||||
|
@ -189,7 +188,7 @@ private:
|
|||
int findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem);
|
||||
int positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition);
|
||||
QMenu* getMenu(const QString& menuName);
|
||||
|
||||
|
||||
|
||||
QHash<QString, QAction*> _actionHash;
|
||||
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
|
||||
|
@ -208,7 +207,6 @@ private:
|
|||
int _boundaryLevelAdjust;
|
||||
QAction* _useVoxelShader;
|
||||
int _maxVoxelPacketsPerSecond;
|
||||
QMenu* _activeScriptsMenu;
|
||||
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
|
||||
quint64 _lastAdjust;
|
||||
quint64 _lastAvatarDetailDrop;
|
||||
|
@ -242,6 +240,7 @@ namespace MenuOption {
|
|||
const QString FilterSixense = "Smooth Sixense Movement";
|
||||
const QString Enable3DTVMode = "Enable 3DTV Mode";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
const QString AudioToneInjection = "Inject Test Tone";
|
||||
const QString EchoServerAudio = "Echo Server Audio";
|
||||
const QString EchoLocalAudio = "Echo Local Audio";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
|
@ -287,9 +286,11 @@ namespace MenuOption {
|
|||
const QString PlaySlaps = "Play Slaps";
|
||||
const QString Preferences = "Preferences...";
|
||||
const QString ReloadAllScripts = "Reload All Scripts";
|
||||
const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies";
|
||||
const QString RenderHeadCollisionProxies = "Head Collision Proxies";
|
||||
const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes";
|
||||
const QString RenderHeadCollisionShapes = "Head Collision Shapes";
|
||||
const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes";
|
||||
const QString ResetAvatarSize = "Reset Avatar Size";
|
||||
const QString RunningScripts = "Running Scripts";
|
||||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString SettingsImport = "Import Settings";
|
||||
const QString Shadows = "Shadows";
|
||||
|
@ -301,7 +302,8 @@ namespace MenuOption {
|
|||
const QString StopAllScripts = "Stop All Scripts";
|
||||
const QString TestPing = "Test Ping";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString UploadFST = "Upload FST file";
|
||||
const QString UploadHead = "Upload Head Model";
|
||||
const QString UploadSkeleton = "Upload Skeleton Model";
|
||||
const QString Visage = "Visage";
|
||||
const QString Quit = "Quit";
|
||||
const QString Voxels = "Voxels";
|
||||
|
|
|
@ -167,12 +167,14 @@ void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) {
|
|||
|
||||
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute()),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerNormalAttribute()),
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner) {
|
||||
bool MetavoxelSystem::SimulateVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->simulate(_deltaTime);
|
||||
return true;
|
||||
}
|
||||
|
@ -186,21 +188,47 @@ int MetavoxelSystem::SimulateVisitor::visit(MetavoxelInfo& info) {
|
|||
QRgb color = info.inputValues.at(0).getInlineValue<QRgb>();
|
||||
QRgb normal = info.inputValues.at(1).getInlineValue<QRgb>();
|
||||
quint8 alpha = qAlpha(color);
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
if (!info.isLODLeaf) {
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
} else {
|
||||
QRgb spannerColor = info.inputValues.at(2).getInlineValue<QRgb>();
|
||||
QRgb spannerNormal = info.inputValues.at(3).getInlineValue<QRgb>();
|
||||
quint8 spannerAlpha = qAlpha(spannerColor);
|
||||
if (spannerAlpha > 0) {
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
||||
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
||||
_points.append(point);
|
||||
|
||||
} else {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(spannerColor)), quint8(qGreen(spannerColor)), quint8(qBlue(spannerColor)), spannerAlpha },
|
||||
{ quint8(qRed(spannerNormal)), quint8(qGreen(spannerNormal)), quint8(qBlue(spannerNormal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
} else if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ quint8(qRed(color)), quint8(qGreen(color)), quint8(qBlue(color)), alpha },
|
||||
{ quint8(qRed(normal)), quint8(qGreen(normal)), quint8(qBlue(normal)) } };
|
||||
_points.append(point);
|
||||
}
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
MetavoxelSystem::RenderVisitor::RenderVisitor() :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute()) {
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannerMaskAttribute()) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner) {
|
||||
spanner->getRenderer()->render(1.0f);
|
||||
bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
spanner->getRenderer()->render(1.0f, clipMinimum, clipSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -306,10 +334,55 @@ void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
|||
}
|
||||
}
|
||||
|
||||
static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
|
||||
GLdouble coefficients[] = { x, y, z, w };
|
||||
glClipPlane(plane, coefficients);
|
||||
glEnable(plane);
|
||||
}
|
||||
|
||||
void ClippedRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) {
|
||||
if (clipSize == 0.0f) {
|
||||
renderUnclipped(alpha);
|
||||
return;
|
||||
}
|
||||
enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize);
|
||||
enableClipPlane(GL_CLIP_PLANE1, 1.0f, 0.0f, 0.0f, -clipMinimum.x);
|
||||
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipSize);
|
||||
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
|
||||
enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize);
|
||||
enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z);
|
||||
|
||||
renderUnclipped(alpha);
|
||||
|
||||
glDisable(GL_CLIP_PLANE0);
|
||||
glDisable(GL_CLIP_PLANE1);
|
||||
glDisable(GL_CLIP_PLANE2);
|
||||
glDisable(GL_CLIP_PLANE3);
|
||||
glDisable(GL_CLIP_PLANE4);
|
||||
glDisable(GL_CLIP_PLANE5);
|
||||
}
|
||||
|
||||
SphereRenderer::SphereRenderer() {
|
||||
}
|
||||
|
||||
void SphereRenderer::render(float alpha) {
|
||||
void SphereRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) {
|
||||
if (clipSize == 0.0f) {
|
||||
renderUnclipped(alpha);
|
||||
return;
|
||||
}
|
||||
// slight performance optimization: don't render if clip bounds are entirely within sphere
|
||||
Sphere* sphere = static_cast<Sphere*>(parent());
|
||||
Box clipBox(clipMinimum, clipMinimum + glm::vec3(clipSize, clipSize, clipSize));
|
||||
for (int i = 0; i < Box::VERTEX_COUNT; i++) {
|
||||
const float CLIP_PROPORTION = 0.95f;
|
||||
if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) {
|
||||
ClippedRenderer::render(alpha, clipMinimum, clipSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SphereRenderer::renderUnclipped(float alpha) {
|
||||
Sphere* sphere = static_cast<Sphere*>(parent());
|
||||
const QColor& color = sphere->getColor();
|
||||
glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha);
|
||||
|
@ -357,11 +430,12 @@ void StaticModelRenderer::simulate(float deltaTime) {
|
|||
_model->simulate(deltaTime);
|
||||
}
|
||||
|
||||
void StaticModelRenderer::render(float alpha) {
|
||||
void StaticModelRenderer::renderUnclipped(float alpha) {
|
||||
_model->render(alpha);
|
||||
}
|
||||
|
||||
bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
||||
return _model->findRayIntersection(origin, direction, distance);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ private:
|
|||
SimulateVisitor(QVector<Point>& points);
|
||||
void setDeltaTime(float deltaTime) { _deltaTime = deltaTime; }
|
||||
void setOrder(const glm::vec3& direction) { _order = encodeOrder(direction); }
|
||||
virtual bool visit(Spanner* spanner);
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
@ -73,7 +73,7 @@ private:
|
|||
class RenderVisitor : public SpannerVisitor {
|
||||
public:
|
||||
RenderVisitor();
|
||||
virtual bool visit(Spanner* spanner);
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
|
@ -141,19 +141,36 @@ private:
|
|||
QList<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
||||
/// Base class for spanner renderers; provides clipping.
|
||||
class ClippedRenderer : public SpannerRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void renderUnclipped(float alpha) = 0;
|
||||
};
|
||||
|
||||
/// Renders spheres.
|
||||
class SphereRenderer : public SpannerRenderer {
|
||||
class SphereRenderer : public ClippedRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SphereRenderer();
|
||||
|
||||
virtual void render(float alpha);
|
||||
virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void renderUnclipped(float alpha);
|
||||
};
|
||||
|
||||
/// Renders static models.
|
||||
class StaticModelRenderer : public SpannerRenderer {
|
||||
class StaticModelRenderer : public ClippedRenderer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -162,8 +179,12 @@ public:
|
|||
|
||||
virtual void init(Spanner* spanner);
|
||||
virtual void simulate(float deltaTime);
|
||||
virtual void render(float alpha);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void renderUnclipped(float alpha);
|
||||
|
||||
private slots:
|
||||
|
||||
|
|
|
@ -56,8 +56,7 @@ Avatar::Avatar() :
|
|||
_owningAvatarMixer(),
|
||||
_collisionFlags(0),
|
||||
_initialized(false),
|
||||
_shouldRenderBillboard(true),
|
||||
_modelsDirty(true)
|
||||
_shouldRenderBillboard(true)
|
||||
{
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
|
@ -118,21 +117,24 @@ void Avatar::simulate(float deltaTime) {
|
|||
getHand()->simulate(deltaTime, false);
|
||||
_skeletonModel.setLODDistance(getLODDistance());
|
||||
|
||||
// copy joint data to skeleton
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
_skeletonModel.setJointState(i, data.valid, data.rotation);
|
||||
}
|
||||
glm::vec3 headPosition = _position;
|
||||
if (!_shouldRenderBillboard && inViewFrustum) {
|
||||
_skeletonModel.simulate(deltaTime, _modelsDirty);
|
||||
_modelsDirty = false;
|
||||
if (_hasNewJointRotations) {
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
_skeletonModel.setJointState(i, data.valid, data.rotation);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
|
||||
_hasNewJointRotations = false;
|
||||
|
||||
glm::vec3 headPosition = _position;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
|
||||
// use speed and angular velocity to determine walking vs. standing
|
||||
if (_speed + fabs(_bodyYawDelta) > 0.2) {
|
||||
|
@ -210,11 +212,19 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(renderMode);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
||||
_skeletonModel.renderCollisionProxies(0.7f);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
|
||||
getHead()->getFaceModel().renderCollisionProxies(0.7f);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
}
|
||||
|
||||
// quick check before falling into the code below:
|
||||
|
@ -653,9 +663,6 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
|||
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
|
||||
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
|
||||
|
||||
// note that we need to update our models
|
||||
_modelsDirty = true;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
|
|
@ -192,7 +192,6 @@ private:
|
|||
bool _initialized;
|
||||
QScopedPointer<Texture> _billboardTexture;
|
||||
bool _shouldRenderBillboard;
|
||||
bool _modelsDirty;
|
||||
|
||||
void renderBillboard();
|
||||
|
||||
|
|
|
@ -18,11 +18,8 @@ FaceModel::FaceModel(Head* owningHead) :
|
|||
{
|
||||
}
|
||||
|
||||
void FaceModel::simulate(float deltaTime) {
|
||||
QVector<JointState> newJointStates = updateGeometry();
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
void FaceModel::simulate(float deltaTime, bool fullUpdate) {
|
||||
updateGeometry();
|
||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
||||
glm::vec3 neckPosition;
|
||||
if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) {
|
||||
|
@ -37,12 +34,13 @@ void FaceModel::simulate(float deltaTime) {
|
|||
const float MODEL_SCALE = 0.0006f;
|
||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);
|
||||
|
||||
setOffset(-_geometry->getFBXGeometry().neckPivot);
|
||||
|
||||
setPupilDilation(_owningHead->getPupilDilation());
|
||||
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
|
||||
|
||||
Model::simulate(deltaTime, true, newJointStates);
|
||||
if (isActive()) {
|
||||
setOffset(-_geometry->getFBXGeometry().neckPivot);
|
||||
Model::simulateInternal(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
|
||||
FaceModel(Head* owningHead);
|
||||
|
||||
void simulate(float deltaTime);
|
||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ void Hand::render(bool isMine) {
|
|||
|
||||
_renderAlpha = 1.0;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
// draw a green sphere at hand joint location, which is actually near the wrist)
|
||||
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "Head.h"
|
||||
#include "Menu.h"
|
||||
#include "Util.h"
|
||||
#include "devices/OculusManager.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -198,6 +199,9 @@ glm::quat Head::getFinalOrientation() const {
|
|||
}
|
||||
|
||||
glm::quat Head::getCameraOrientation () const {
|
||||
if (OculusManager::isConnected()) {
|
||||
return getOrientation();
|
||||
}
|
||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
||||
return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.f, 0.0f)));
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QBuffer>
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
@ -20,6 +21,8 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <ShapeCollider.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Audio.h"
|
||||
#include "Environment.h"
|
||||
|
@ -83,9 +86,9 @@ void MyAvatar::reset() {
|
|||
getHead()->reset();
|
||||
getHand()->reset();
|
||||
|
||||
setVelocity(glm::vec3(0,0,0));
|
||||
setThrust(glm::vec3(0,0,0));
|
||||
setOrientation(glm::quat(glm::vec3(0,0,0)));
|
||||
setVelocity(glm::vec3(0.f));
|
||||
setThrust(glm::vec3(0.f));
|
||||
setOrientation(glm::quat(glm::vec3(0.f)));
|
||||
}
|
||||
|
||||
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
|
||||
|
@ -166,9 +169,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
// Collect thrust forces from keyboard and devices
|
||||
updateThrust(deltaTime);
|
||||
|
||||
// copy velocity so we can use it later for acceleration
|
||||
glm::vec3 oldVelocity = getVelocity();
|
||||
|
||||
// calculate speed
|
||||
_speed = glm::length(_velocity);
|
||||
|
||||
|
@ -228,29 +228,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
// update the euler angles
|
||||
setOrientation(orientation);
|
||||
|
||||
// Compute instantaneous acceleration
|
||||
float forwardAcceleration = glm::length(glm::dot(getBodyFrontDirection(), getVelocity() - oldVelocity)) / deltaTime;
|
||||
const float OCULUS_ACCELERATION_PULL_THRESHOLD = 1.0f;
|
||||
const int OCULUS_YAW_OFFSET_THRESHOLD = 10;
|
||||
|
||||
if (!Application::getInstance()->getFaceshift()->isActive() && OculusManager::isConnected() &&
|
||||
fabsf(forwardAcceleration) > OCULUS_ACCELERATION_PULL_THRESHOLD &&
|
||||
fabs(getHead()->getBaseYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) {
|
||||
|
||||
// if we're wearing the oculus
|
||||
// and this acceleration is above the pull threshold
|
||||
// and the head yaw if off the body by more than OCULUS_YAW_OFFSET_THRESHOLD
|
||||
|
||||
// match the body yaw to the oculus yaw
|
||||
_bodyYaw = getAbsoluteHeadYaw();
|
||||
|
||||
// set the head yaw to zero for this draw
|
||||
getHead()->setBaseYaw(0);
|
||||
|
||||
// correct the oculus yaw offset
|
||||
OculusManager::updateYawOffset();
|
||||
}
|
||||
|
||||
const float WALKING_SPEED_THRESHOLD = 0.2f;
|
||||
// use speed and angular velocity to determine walking vs. standing
|
||||
if (_speed + fabs(_bodyYawDelta) > WALKING_SPEED_THRESHOLD) {
|
||||
|
@ -305,7 +282,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
head->simulate(deltaTime, true);
|
||||
|
||||
// Zero thrust out now that we've added it to velocity in this frame
|
||||
_thrust = glm::vec3(0, 0, 0);
|
||||
_thrust = glm::vec3(0.f);
|
||||
|
||||
// now that we're done stepping the avatar forward in time, compute new collisions
|
||||
if (_collisionFlags != 0) {
|
||||
|
@ -674,6 +651,28 @@ void MyAvatar::updateThrust(float deltaTime) {
|
|||
_thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;
|
||||
_thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up;
|
||||
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
|
||||
|
||||
// attenuate thrust when in penetration
|
||||
if (glm::dot(_thrust, _lastBodyPenetration) > 0.f) {
|
||||
const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius();
|
||||
float penetrationFactor = glm::min(1.f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH);
|
||||
glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration);
|
||||
// attenuate parallel component
|
||||
glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection;
|
||||
// attenuate perpendicular component (friction)
|
||||
glm::vec3 perpendicularThrust = _thrust - parallelThrust;
|
||||
// recombine to get the final thrust
|
||||
_thrust = (1.f - penetrationFactor) * parallelThrust + (1.f - penetrationFactor * penetrationFactor) * perpendicularThrust;
|
||||
|
||||
// attenuate the growth of _thrustMultiplier when in penetration
|
||||
// otherwise the avatar will eventually be able to tunnel through the obstacle
|
||||
_thrustMultiplier *= (1.f - penetrationFactor * penetrationFactor);
|
||||
} else if (_thrustMultiplier < 1.f) {
|
||||
// rapid healing of attenuated thrustMultiplier after penetration event
|
||||
_thrustMultiplier = 1.f;
|
||||
}
|
||||
_lastBodyPenetration = glm::vec3(0.f);
|
||||
|
||||
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
|
||||
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
|
||||
|
@ -683,8 +682,9 @@ void MyAvatar::updateThrust(float deltaTime) {
|
|||
const float THRUST_INCREASE_RATE = 1.05f;
|
||||
const float MAX_THRUST_MULTIPLIER = 75.0f;
|
||||
//printf("m = %.3f\n", _thrustMultiplier);
|
||||
if (_thrustMultiplier < MAX_THRUST_MULTIPLIER) {
|
||||
_thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE;
|
||||
_thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE;
|
||||
if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) {
|
||||
_thrustMultiplier = MAX_THRUST_MULTIPLIER;
|
||||
}
|
||||
} else {
|
||||
_thrustMultiplier = 1.f;
|
||||
|
@ -868,6 +868,9 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
|
|||
return false;
|
||||
}
|
||||
|
||||
static CollisionList bodyCollisions(16);
|
||||
const float BODY_COLLISION_RESOLVE_TIMESCALE = 0.5f; // seconds
|
||||
|
||||
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||
// Reset detector for nearest avatar
|
||||
_distanceToNearestAvatar = std::numeric_limits<float>::max();
|
||||
|
@ -879,14 +882,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
updateShapePositions();
|
||||
float myBoundingRadius = getBoundingRadius();
|
||||
|
||||
/* TODO: Andrew to fix Avatar-Avatar body collisions
|
||||
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
|
||||
// TODO: make the collision work without assuming avatar orientation
|
||||
Extents myStaticExtents = _skeletonModel.getStaticExtents();
|
||||
glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum;
|
||||
float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
|
||||
float myCapsuleHeight = staticScale.y;
|
||||
*/
|
||||
const float BODY_COLLISION_RESOLVE_FACTOR = deltaTime / BODY_COLLISION_RESOLVE_TIMESCALE;
|
||||
|
||||
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
|
@ -901,19 +897,27 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
}
|
||||
float theirBoundingRadius = avatar->getBoundingRadius();
|
||||
if (distance < myBoundingRadius + theirBoundingRadius) {
|
||||
/* TODO: Andrew to fix Avatar-Avatar body collisions
|
||||
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
|
||||
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
|
||||
float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
|
||||
float theirCapsuleHeight = staticScale.y;
|
||||
|
||||
glm::vec3 penetration(0.f);
|
||||
if (findAvatarAvatarPenetration(_position, myCapsuleRadius, myCapsuleHeight,
|
||||
avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) {
|
||||
// move the avatar out by half the penetration
|
||||
setPosition(_position - 0.5f * penetration);
|
||||
// collide our body against theirs
|
||||
QVector<const Shape*> myShapes;
|
||||
_skeletonModel.getBodyShapes(myShapes);
|
||||
QVector<const Shape*> theirShapes;
|
||||
avatar->getSkeletonModel().getBodyShapes(theirShapes);
|
||||
bodyCollisions.clear();
|
||||
// TODO: add method to ShapeCollider for colliding lists of shapes
|
||||
foreach (const Shape* myShape, myShapes) {
|
||||
foreach (const Shape* theirShape, theirShapes) {
|
||||
ShapeCollider::shapeShape(myShape, theirShape, bodyCollisions);
|
||||
}
|
||||
}
|
||||
*/
|
||||
glm::vec3 totalPenetration(0.f);
|
||||
for (int j = 0; j < bodyCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = bodyCollisions.getCollision(j);
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
if (glm::length2(totalPenetration) > EPSILON) {
|
||||
setPosition(getPosition() - BODY_COLLISION_RESOLVE_FACTOR * totalPenetration);
|
||||
}
|
||||
_lastBodyPenetration += totalPenetration;
|
||||
|
||||
// collide our hands against them
|
||||
// TODO: make this work when we can figure out when the other avatar won't yeild
|
||||
|
|
|
@ -121,6 +121,7 @@ private:
|
|||
bool _isThrustOn;
|
||||
float _thrustMultiplier;
|
||||
glm::vec3 _moveTarget;
|
||||
glm::vec3 _lastBodyPenetration;
|
||||
int _moveTargetStepCounter;
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include "Menu.h"
|
||||
#include "SkeletonModel.h"
|
||||
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||
_owningAvatar(owningAvatar) {
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
}
|
||||
|
||||
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
|
||||
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
|
||||
if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) {
|
||||
return;
|
||||
}
|
||||
if (jointIndex == getLeftHandJointIndex()
|
||||
|
@ -75,16 +75,16 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
|
|||
int parentIndex = joint.parentIndex;
|
||||
if (i == jointIndex) {
|
||||
// this shape is the hand
|
||||
shapes.push_back(_shapes[i]);
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
if (parentIndex != -1) {
|
||||
// also add the forearm
|
||||
shapes.push_back(_shapes[parentIndex]);
|
||||
shapes.push_back(_jointShapes[parentIndex]);
|
||||
}
|
||||
} else {
|
||||
while (parentIndex != -1) {
|
||||
if (parentIndex == jointIndex) {
|
||||
// this shape is a child of the hand
|
||||
shapes.push_back(_shapes[i]);
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
break;
|
||||
}
|
||||
parentIndex = geometry.joints[parentIndex].parentIndex;
|
||||
|
@ -94,6 +94,12 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
|
|||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::getBodyShapes(QVector<const Shape*>& shapes) const {
|
||||
// for now we push a single bounding shape,
|
||||
// but later we could push a subset of joint shapes
|
||||
shapes.push_back(&_boundingShape);
|
||||
}
|
||||
|
||||
class IndexValue {
|
||||
public:
|
||||
int index;
|
||||
|
@ -133,12 +139,17 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJoin
|
|||
return;
|
||||
}
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
setJointPosition(jointIndex, palm.getPosition());
|
||||
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
|
||||
int parentJointIndex = geometry.joints.at(jointIndex).parentIndex;
|
||||
if (parentJointIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// rotate forearm to align with palm direction
|
||||
glm::quat palmRotation;
|
||||
getJointRotation(jointIndex, palmRotation, true);
|
||||
applyRotationDelta(jointIndex, rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()), false);
|
||||
getJointRotation(jointIndex, palmRotation, true);
|
||||
getJointRotation(parentJointIndex, palmRotation, true);
|
||||
applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()), false);
|
||||
getJointRotation(parentJointIndex, palmRotation, true);
|
||||
|
||||
// sort the finger indices by raw x, get the average direction
|
||||
QVector<IndexValue> fingerIndices;
|
||||
|
@ -155,33 +166,21 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector<int>& fingerJoin
|
|||
}
|
||||
qSort(fingerIndices.begin(), fingerIndices.end());
|
||||
|
||||
// rotate palm according to average finger direction
|
||||
// rotate forearm according to average finger direction
|
||||
float directionLength = glm::length(direction);
|
||||
const unsigned int MIN_ROTATION_FINGERS = 3;
|
||||
if (directionLength > EPSILON && palm.getNumFingers() >= MIN_ROTATION_FINGERS) {
|
||||
applyRotationDelta(jointIndex, rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction), false);
|
||||
getJointRotation(jointIndex, palmRotation, true);
|
||||
applyRotationDelta(parentJointIndex, rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction), false);
|
||||
getJointRotation(parentJointIndex, palmRotation, true);
|
||||
}
|
||||
|
||||
// no point in continuing if there are no fingers
|
||||
if (palm.getNumFingers() == 0 || fingerJointIndices.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// let wrist inherit forearm rotation
|
||||
_jointStates[jointIndex].rotation = glm::quat();
|
||||
|
||||
// match them up as best we can
|
||||
float proportion = fingerIndices.size() / (float)fingerJointIndices.size();
|
||||
for (int i = 0; i < fingerJointIndices.size(); i++) {
|
||||
int fingerIndex = fingerIndices.at(roundf(i * proportion)).index;
|
||||
glm::vec3 fingerVector = palm.getFingers()[fingerIndex].getTipPosition() -
|
||||
palm.getFingers()[fingerIndex].getRootPosition();
|
||||
|
||||
int fingerJointIndex = fingerJointIndices.at(i);
|
||||
int fingertipJointIndex = fingertipJointIndices.at(i);
|
||||
glm::vec3 jointVector = extractTranslation(geometry.joints.at(fingertipJointIndex).bindTransform) -
|
||||
extractTranslation(geometry.joints.at(fingerJointIndex).bindTransform);
|
||||
|
||||
setJointRotation(fingerJointIndex, rotationBetween(palmRotation * jointVector, fingerVector) * palmRotation, true);
|
||||
}
|
||||
// set elbow position from wrist position
|
||||
glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f);
|
||||
setJointPosition(parentJointIndex, palm.getPosition() + forearmVector *
|
||||
geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale));
|
||||
}
|
||||
|
||||
void SkeletonModel::updateJointState(int index) {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#ifndef __interface__SkeletonModel__
|
||||
#define __interface__SkeletonModel__
|
||||
|
||||
|
||||
#include "renderer/Model.h"
|
||||
|
||||
class Avatar;
|
||||
|
@ -28,8 +27,11 @@ public:
|
|||
/// \param shapes[out] list in which is stored pointers to hand shapes
|
||||
void getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const;
|
||||
|
||||
/// \param shapes[out] list of shapes for body collisions
|
||||
void getBodyShapes(QVector<const Shape*>& shapes) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
void applyHandPosition(int jointIndex, const glm::vec3& position);
|
||||
|
||||
void applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||
|
|
|
@ -41,6 +41,11 @@ bool Extents::containsPoint(const glm::vec3& point) const {
|
|||
&& point.z >= minimum.z && point.z <= maximum.z);
|
||||
}
|
||||
|
||||
void Extents::addExtents(const Extents& extents) {
|
||||
minimum = glm::min(minimum, extents.minimum);
|
||||
maximum = glm::max(maximum, extents.maximum);
|
||||
}
|
||||
|
||||
void Extents::addPoint(const glm::vec3& point) {
|
||||
minimum = glm::min(minimum, point);
|
||||
maximum = glm::max(maximum, point);
|
||||
|
@ -1343,7 +1348,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
}
|
||||
|
||||
geometry.bindExtents.reset();
|
||||
geometry.staticExtents.reset();
|
||||
geometry.meshExtents.reset();
|
||||
|
||||
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
|
||||
|
@ -1511,8 +1515,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
|
||||
jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd));
|
||||
|
||||
bool jointIsStatic = joint.freeLineage.isEmpty();
|
||||
glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||
float totalWeight = 0.0f;
|
||||
for (int j = 0; j < cluster.indices.size(); j++) {
|
||||
int oldIndex = cluster.indices.at(j);
|
||||
|
@ -1534,10 +1536,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
jointShapeInfo.extents.addPoint(vertexInJointFrame);
|
||||
jointShapeInfo.averageVertex += vertexInJointFrame;
|
||||
++jointShapeInfo.numVertices;
|
||||
if (jointIsStatic) {
|
||||
// expand the extents of static (nonmovable) joints
|
||||
geometry.staticExtents.addPoint(vertex + jointTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
// look for an unused slot in the weights vector
|
||||
|
|
|
@ -30,6 +30,10 @@ public:
|
|||
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
|
||||
void reset();
|
||||
|
||||
/// \param extents another intance of extents
|
||||
/// expand current limits to contain other extents
|
||||
void addExtents(const Extents& extents);
|
||||
|
||||
/// \param point new point to compare against existing limits
|
||||
/// compare point to current limits and expand them if necessary to contain point
|
||||
void addPoint(const glm::vec3& point);
|
||||
|
@ -174,7 +178,6 @@ public:
|
|||
glm::vec3 neckPivot;
|
||||
|
||||
Extents bindExtents;
|
||||
Extents staticExtents;
|
||||
Extents meshExtents;
|
||||
|
||||
QVector<FBXAttachment> attachments;
|
||||
|
|
|
@ -32,9 +32,11 @@ Model::Model(QObject* parent) :
|
|||
QObject(parent),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.f),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.f),
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f),
|
||||
_boundingRadius(0.f) {
|
||||
_pupilDilation(0.0f) {
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
}
|
||||
|
@ -54,6 +56,14 @@ Model::SkinLocations Model::_skinLocations;
|
|||
Model::SkinLocations Model::_skinNormalMapLocations;
|
||||
Model::SkinLocations Model::_skinShadowLocations;
|
||||
|
||||
void Model::setScale(const glm::vec3& scale) {
|
||||
glm::vec3 deltaScale = _scale - scale;
|
||||
if (glm::length2(deltaScale) > EPSILON) {
|
||||
_scale = scale;
|
||||
rebuildShapes();
|
||||
}
|
||||
}
|
||||
|
||||
void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
|
||||
program.bind();
|
||||
locations.clusterMatrices = program.uniformLocation("clusterMatrices");
|
||||
|
@ -73,6 +83,44 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
state.rotation = joint.rotation;
|
||||
jointStates.append(state);
|
||||
}
|
||||
|
||||
// compute transforms
|
||||
// Unfortunately, the joints are not neccessarily in order from parents to children,
|
||||
// so we must iterate over the list multiple times until all are set correctly.
|
||||
QVector<bool> jointIsSet;
|
||||
int numJoints = jointStates.size();
|
||||
jointIsSet.fill(false, numJoints);
|
||||
int numJointsSet = 0;
|
||||
int lastNumJointsSet = -1;
|
||||
while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) {
|
||||
lastNumJointsSet = numJointsSet;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
if (jointIsSet[i]) {
|
||||
continue;
|
||||
}
|
||||
JointState& state = jointStates[i];
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = _rotation * combinedRotation;
|
||||
++numJointsSet;
|
||||
jointIsSet[i] = true;
|
||||
} else if (jointIsSet[parentIndex]) {
|
||||
const JointState& parentState = jointStates.at(parentIndex);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = parentState.combinedRotation * combinedRotation;
|
||||
++numJointsSet;
|
||||
jointIsSet[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jointStates;
|
||||
}
|
||||
|
||||
|
@ -142,60 +190,95 @@ void Model::reset() {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _shapes.size(); ++i) {
|
||||
delete _shapes[i];
|
||||
}
|
||||
_shapes.clear();
|
||||
}
|
||||
|
||||
void Model::createCollisionShapes() {
|
||||
clearShapes();
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 meshCenter = _jointStates[i].combinedRotation * joint.shapePosition;
|
||||
glm::vec3 position = _rotation * (extractTranslation(_jointStates[i].transform) + uniformScale * meshCenter) + _translation;
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
if (joint.shapeType == Shape::CAPSULE_SHAPE) {
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
CapsuleShape* shape = new CapsuleShape(radius, halfHeight);
|
||||
shape->setPosition(position);
|
||||
_shapes.push_back(shape);
|
||||
} else {
|
||||
SphereShape* shape = new SphereShape(radius, position);
|
||||
_shapes.push_back(shape);
|
||||
bool Model::updateGeometry() {
|
||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||
bool needFullUpdate = false;
|
||||
for (int i = 0; i < _attachments.size(); i++) {
|
||||
Model* model = _attachments.at(i);
|
||||
if (model->updateGeometry()) {
|
||||
needFullUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _shapes.size() == _jointStates.size()) {
|
||||
_boundingRadius = 0.f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_shapes[i]->setPosition(worldPosition);
|
||||
_shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance2 = glm::distance2(worldPosition, _translation);
|
||||
if (distance2 > _boundingRadius) {
|
||||
_boundingRadius = distance2;
|
||||
bool needToRebuild = false;
|
||||
if (_nextGeometry) {
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
|
||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
||||
_nextGeometry->ensureLoading();
|
||||
if (_nextGeometry->isLoaded()) {
|
||||
applyNextGeometry();
|
||||
needToRebuild = true;
|
||||
}
|
||||
}
|
||||
if (!_geometry) {
|
||||
// geometry is not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
// NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above.
|
||||
// Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry.
|
||||
|
||||
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
|
||||
QVector<JointState> newJointStates = createJointStates(newGeometry);
|
||||
if (! _jointStates.isEmpty()) {
|
||||
// copy the existing joint states
|
||||
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
|
||||
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
|
||||
it != oldGeometry.jointIndices.constEnd(); it++) {
|
||||
int oldIndex = it.value() - 1;
|
||||
int newIndex = newGeometry.getJointIndex(it.key());
|
||||
if (newIndex != -1) {
|
||||
newJointStates[newIndex] = _jointStates.at(oldIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_geometry = geometry;
|
||||
_jointStates = newJointStates;
|
||||
needToRebuild = true;
|
||||
} else if (_jointStates.isEmpty()) {
|
||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||
if (fbxGeometry.joints.size() > 0) {
|
||||
_jointStates = createJointStates(fbxGeometry);
|
||||
needToRebuild = true;
|
||||
}
|
||||
_boundingRadius = sqrtf(_boundingRadius);
|
||||
_shapesAreDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
// update our LOD, then simulate
|
||||
simulate(deltaTime, fullUpdate, updateGeometry());
|
||||
_geometry->setLoadPriority(this, -_lodDistance);
|
||||
_geometry->ensureLoading();
|
||||
|
||||
if (needToRebuild) {
|
||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.append(state);
|
||||
|
||||
QOpenGLBuffer buffer;
|
||||
if (!mesh.blendshapes.isEmpty()) {
|
||||
buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
buffer.create();
|
||||
buffer.bind();
|
||||
buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||
buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(),
|
||||
mesh.normals.size() * sizeof(glm::vec3));
|
||||
buffer.release();
|
||||
}
|
||||
_blendedVertexBuffers.append(buffer);
|
||||
}
|
||||
foreach (const FBXAttachment& attachment, fbxGeometry.attachments) {
|
||||
Model* model = new Model(this);
|
||||
model->init();
|
||||
model->setURL(attachment.url);
|
||||
_attachments.append(model);
|
||||
}
|
||||
rebuildShapes();
|
||||
needFullUpdate = true;
|
||||
}
|
||||
return needFullUpdate;
|
||||
}
|
||||
|
||||
bool Model::render(float alpha, bool forShadowMap) {
|
||||
|
@ -264,15 +347,6 @@ Extents Model::getBindExtents() const {
|
|||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getStaticExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& staticExtents = _geometry->getFBXGeometry().staticExtents;
|
||||
Extents scaledExtents = { staticExtents.minimum * _scale, staticExtents.maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
|
@ -375,6 +449,107 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
}
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
delete _jointShapes[i];
|
||||
}
|
||||
_jointShapes.clear();
|
||||
}
|
||||
|
||||
void Model::rebuildShapes() {
|
||||
clearShapes();
|
||||
|
||||
if (_jointStates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure all the joints are updated correctly before we try to create their shapes
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 rootPosition(0.f);
|
||||
|
||||
// joint shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) {
|
||||
CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
|
||||
capsule->setPosition(worldPosition);
|
||||
capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
_jointShapes.push_back(capsule);
|
||||
|
||||
glm::vec3 endPoint;
|
||||
capsule->getEndPoint(endPoint);
|
||||
glm::vec3 startPoint;
|
||||
capsule->getStartPoint(startPoint);
|
||||
glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
} else {
|
||||
SphereShape* sphere = new SphereShape(radius, worldPosition);
|
||||
_jointShapes.push_back(sphere);
|
||||
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
}
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
}
|
||||
|
||||
// bounding shape
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis
|
||||
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
|
||||
float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z
|
||||
_boundingShape.setRadius(capsuleRadius);
|
||||
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
|
||||
_boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition);
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) {
|
||||
glm::vec3 rootPosition(0.f);
|
||||
_boundingRadius = 0.f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_jointShapes[i]->setPosition(worldPosition);
|
||||
_jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance2 = glm::distance2(worldPosition, _translation);
|
||||
if (distance2 > _boundingRadius) {
|
||||
_boundingRadius = distance2;
|
||||
}
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
_boundingRadius = sqrtf(_boundingRadius);
|
||||
_shapesAreDirty = false;
|
||||
_boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
const glm::vec3 relativeOrigin = origin - _translation;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
@ -408,8 +583,8 @@ bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& co
|
|||
bool collided = false;
|
||||
for (int i = 0; i < shapes.size(); ++i) {
|
||||
const Shape* theirShape = shapes[i];
|
||||
for (int j = 0; j < _shapes.size(); ++j) {
|
||||
const Shape* ourShape = _shapes[j];
|
||||
for (int j = 0; j < _jointShapes.size(); ++j) {
|
||||
const Shape* ourShape = _jointShapes[j];
|
||||
if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) {
|
||||
collided = true;
|
||||
}
|
||||
|
@ -421,10 +596,9 @@ bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& co
|
|||
bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
|
||||
CollisionList& collisions, int skipIndex) {
|
||||
bool collided = false;
|
||||
updateShapePositions();
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
if (joint.parentIndex != -1) {
|
||||
if (skipIndex != -1) {
|
||||
|
@ -438,7 +612,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
|
|||
} while (ancestorIndex != -1);
|
||||
}
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&sphere, _shapes[i], collisions)) {
|
||||
if (ShapeCollider::shapeShape(&sphere, _jointShapes[i], collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_type = MODEL_COLLISION;
|
||||
collision->_data = (void*)(this);
|
||||
|
@ -450,45 +624,6 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
|
|||
return collided;
|
||||
}
|
||||
|
||||
QVector<Model::JointState> Model::updateGeometry() {
|
||||
QVector<JointState> newJointStates;
|
||||
if (_nextGeometry) {
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
|
||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
||||
_nextGeometry->ensureLoading();
|
||||
if (_nextGeometry->isLoaded()) {
|
||||
applyNextGeometry();
|
||||
return newJointStates;
|
||||
}
|
||||
}
|
||||
if (!_geometry) {
|
||||
return newJointStates;
|
||||
}
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
if (!_jointStates.isEmpty()) {
|
||||
// copy the existing joint states
|
||||
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
|
||||
newJointStates = createJointStates(newGeometry);
|
||||
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
|
||||
it != oldGeometry.jointIndices.constEnd(); it++) {
|
||||
int oldIndex = it.value() - 1;
|
||||
int newIndex = newGeometry.getJointIndex(it.key());
|
||||
if (newIndex != -1) {
|
||||
newJointStates[newIndex] = _jointStates.at(oldIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_geometry = geometry;
|
||||
}
|
||||
_geometry->setLoadPriority(this, -_lodDistance);
|
||||
_geometry->ensureLoading();
|
||||
return newJointStates;
|
||||
}
|
||||
|
||||
class Blender : public QRunnable {
|
||||
public:
|
||||
|
||||
|
@ -551,53 +686,23 @@ void Blender::run() {
|
|||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
fullUpdate = updateGeometry() || fullUpdate;
|
||||
if (isActive() && fullUpdate) {
|
||||
simulateInternal(deltaTime);
|
||||
}
|
||||
|
||||
// set up world vertices on first simulate after load
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (_jointStates.isEmpty()) {
|
||||
_jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates;
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.append(state);
|
||||
|
||||
QOpenGLBuffer buffer;
|
||||
if (!mesh.blendshapes.isEmpty()) {
|
||||
buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
buffer.create();
|
||||
buffer.bind();
|
||||
buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||
buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(),
|
||||
mesh.normals.size() * sizeof(glm::vec3));
|
||||
buffer.release();
|
||||
}
|
||||
_blendedVertexBuffers.append(buffer);
|
||||
}
|
||||
foreach (const FBXAttachment& attachment, geometry.attachments) {
|
||||
Model* model = new Model(this);
|
||||
model->init();
|
||||
model->setURL(attachment.url);
|
||||
_attachments.append(model);
|
||||
}
|
||||
fullUpdate = true;
|
||||
createCollisionShapes();
|
||||
}
|
||||
|
||||
// exit early if we don't have to perform a full update
|
||||
if (!fullUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||
// update the world space transforms for all joints
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
// update the attachment transforms and simulate them
|
||||
for (int i = 0; i < _attachments.size(); i++) {
|
||||
const FBXAttachment& attachment = geometry.attachments.at(i);
|
||||
|
@ -612,7 +717,9 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
|||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScale(_scale * attachment.scale);
|
||||
|
||||
model->simulate(deltaTime);
|
||||
if (model->isActive()) {
|
||||
model->simulateInternal(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _meshStates.size(); i++) {
|
||||
|
@ -631,7 +738,6 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
|||
}
|
||||
|
||||
void Model::updateJointState(int index) {
|
||||
_shapesAreDirty = true;
|
||||
JointState& state = _jointStates[index];
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXJoint& joint = geometry.joints.at(index);
|
||||
|
@ -643,7 +749,7 @@ void Model::updateJointState(int index) {
|
|||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = _rotation * combinedRotation;
|
||||
|
||||
|
||||
} else {
|
||||
const JointState& parentState = _jointStates.at(joint.parentIndex);
|
||||
if (index == geometry.leanJointIndex) {
|
||||
|
@ -749,6 +855,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last
|
|||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||
updateJointState(freeLineage.at(j));
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -827,15 +934,15 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
|
|||
state.rotation = newRotation;
|
||||
}
|
||||
|
||||
void Model::renderCollisionProxies(float alpha) {
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
|
||||
void Model::renderJointCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
updateShapePositions();
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
glPushMatrix();
|
||||
|
||||
Shape* shape = _shapes[i];
|
||||
Shape* shape = _jointShapes[i];
|
||||
|
||||
if (shape->getType() == Shape::SPHERE_SHAPE) {
|
||||
// shapes are stored in world-frame, so we have to transform into model frame
|
||||
|
@ -878,6 +985,36 @@ void Model::renderCollisionProxies(float alpha) {
|
|||
glPopMatrix();
|
||||
}
|
||||
|
||||
void Model::renderBoundingCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
_boundingShape.getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
_boundingShape.getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.f);
|
||||
glColor4f(0.6f, 0.8f, 0.6f, alpha);
|
||||
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
|
||||
if (collision._type == MODEL_COLLISION) {
|
||||
// the joint is pokable by a collision if it exists and is free to move
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "ProgramObject.h"
|
||||
|
@ -34,7 +36,7 @@ public:
|
|||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
void setScale(const glm::vec3& scale) { _scale = scale; }
|
||||
void setScale(const glm::vec3& scale);
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
||||
void setOffset(const glm::vec3& offset) { _offset = offset; }
|
||||
|
@ -54,12 +56,9 @@ public:
|
|||
|
||||
void init();
|
||||
void reset();
|
||||
void clearShapes();
|
||||
void createCollisionShapes();
|
||||
void updateShapePositions();
|
||||
void simulate(float deltaTime, bool fullUpdate = true);
|
||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||
bool render(float alpha = 1.0f, bool forShadowMap = false);
|
||||
|
||||
|
||||
/// Sets the URL of the model to render.
|
||||
/// \param fallback the URL of a fallback model to render if the requested model fails to load
|
||||
/// \param retainCurrent if true, keep rendering the current model until the new one is loaded
|
||||
|
@ -75,9 +74,6 @@ public:
|
|||
/// Returns the extents of the model in its bind pose.
|
||||
Extents getBindExtents() const;
|
||||
|
||||
/// Returns the extents of the unmovable joints of the model.
|
||||
Extents getStaticExtents() const;
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
|
@ -159,6 +155,12 @@ public:
|
|||
/// Returns the extended length from the right hand to its first free ancestor.
|
||||
float getRightArmLength() const;
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void updateShapePositions();
|
||||
void renderJointCollisionShapes(float alpha);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// \param shapes list of pointers shapes to test against Model
|
||||
|
@ -169,8 +171,6 @@ public:
|
|||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skipIndex = -1);
|
||||
|
||||
void renderCollisionProxies(float alpha);
|
||||
|
||||
/// \param collision details about the collisions
|
||||
/// \return true if the collision is against a moveable joint
|
||||
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
|
||||
|
@ -180,6 +180,7 @@ public:
|
|||
void applyCollision(CollisionInfo& collision);
|
||||
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
@ -203,7 +204,11 @@ protected:
|
|||
|
||||
bool _shapesAreDirty;
|
||||
QVector<JointState> _jointStates;
|
||||
QVector<Shape*> _shapes;
|
||||
QVector<Shape*> _jointShapes;
|
||||
|
||||
float _boundingRadius;
|
||||
CapsuleShape _boundingShape;
|
||||
glm::vec3 _boundingShapeLocalOffset;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
|
@ -212,9 +217,11 @@ protected:
|
|||
|
||||
QVector<MeshState> _meshStates;
|
||||
|
||||
QVector<JointState> updateGeometry();
|
||||
void simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates);
|
||||
|
||||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
|
||||
void simulateInternal(float deltaTime);
|
||||
|
||||
/// Updates the state of the joint at the specified index.
|
||||
virtual void updateJointState(int index);
|
||||
|
||||
|
@ -248,6 +255,7 @@ private:
|
|||
void applyNextGeometry();
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, bool forShadowMap, bool translucent);
|
||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||
|
||||
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
|
||||
QSharedPointer<NetworkGeometry> _nextBaseGeometry;
|
||||
|
@ -267,8 +275,6 @@ private:
|
|||
|
||||
QVector<Model*> _attachments;
|
||||
|
||||
float _boundingRadius;
|
||||
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _normalMapProgram;
|
||||
static ProgramObject _shadowProgram;
|
||||
|
@ -291,7 +297,6 @@ private:
|
|||
static SkinLocations _skinShadowLocations;
|
||||
|
||||
static void initSkinProgram(ProgramObject& program, SkinLocations& locations);
|
||||
static QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QPointer<Model>)
|
||||
|
|
|
@ -42,6 +42,10 @@ ChatWindow::ChatWindow() :
|
|||
ui->messagesGridLayout->setColumnStretch(1, 3);
|
||||
|
||||
ui->messagePlainTextEdit->installEventFilter(this);
|
||||
|
||||
if (!AccountManager::getInstance().isLoggedIn()) {
|
||||
ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others."));
|
||||
}
|
||||
|
||||
#ifdef HAVE_QXMPP
|
||||
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
|
||||
|
@ -132,7 +136,7 @@ QString ChatWindow::getParticipantName(const QString& participant) {
|
|||
void ChatWindow::addTimeStamp() {
|
||||
QTimeSpan timePassed = QDateTime::currentDateTime() - lastMessageStamp;
|
||||
int times[] = { timePassed.daysPart(), timePassed.hoursPart(), timePassed.minutesPart() };
|
||||
QString strings[] = { tr("day", 0, times[0]), tr("hour", 0, times[1]), tr("minute", 0, times[2]) };
|
||||
QString strings[] = { tr("%n day(s)", 0, times[0]), tr("%n hour(s)", 0, times[1]), tr("%n minute(s)", 0, times[2]) };
|
||||
QString timeString = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (times[i] > 0) {
|
||||
|
@ -228,7 +232,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
|
|||
messageLabel->setWordWrap(true);
|
||||
messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
messageLabel->setOpenExternalLinks(true);
|
||||
messageLabel->setStyleSheet("padding-bottom: 2px; padding-right: 2px; padding-top: 2px; padding-right: 20px");
|
||||
messageLabel->setStyleSheet("padding-bottom: 2px; padding-left: 2px; padding-top: 2px; padding-right: 20px");
|
||||
messageLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
if (getParticipantName(message.from()) == AccountManager::getInstance().getUsername()) {
|
||||
|
|
|
@ -565,7 +565,7 @@ void PlaceSpannerTool::render() {
|
|||
}
|
||||
Spanner* spanner = static_cast<Spanner*>(_editor->getValue().value<SharedObjectPointer>().data());
|
||||
const float SPANNER_ALPHA = 0.25f;
|
||||
spanner->getRenderer()->render(SPANNER_ALPHA);
|
||||
spanner->getRenderer()->render(SPANNER_ALPHA, glm::vec3(), 0.0f);
|
||||
}
|
||||
|
||||
bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const {
|
||||
|
@ -655,6 +655,5 @@ bool SetSpannerTool::appliesTo(const AttributePointer& attribute) const {
|
|||
}
|
||||
|
||||
QVariant SetSpannerTool::createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
|
||||
static_cast<Spanner*>(spanner.data())->setGranularity(_editor->getGridSpacing());
|
||||
return QVariant::fromValue(SetSpannerEdit(spanner));
|
||||
}
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
//
|
||||
// ModelBrowser.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QUrl>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QEventLoop>
|
||||
#include <QMessageBox>
|
||||
#include <QGridLayout>
|
||||
#include <QDialog>
|
||||
#include <QStringListModel>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include "ModelBrowser.h"
|
||||
|
||||
static const QString PREFIX_PARAMETER_NAME = "prefix";
|
||||
static const QString MARKER_PARAMETER_NAME = "marker";
|
||||
static const QString IS_TRUNCATED_NAME = "IsTruncated";
|
||||
static const QString CONTAINER_NAME = "Contents";
|
||||
static const QString KEY_NAME = "Key";
|
||||
|
||||
ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
url.setQuery(query);
|
||||
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
ModelBrowser::~ModelBrowser() {
|
||||
delete _downloader;
|
||||
}
|
||||
|
||||
void ModelBrowser::downloadFinished() {
|
||||
parseXML(_downloader->getData());
|
||||
}
|
||||
|
||||
void ModelBrowser::browse() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle("Browse models");
|
||||
|
||||
QGridLayout* layout = new QGridLayout(&dialog);
|
||||
dialog.setLayout(layout);
|
||||
|
||||
QLineEdit* searchBar = new QLineEdit(&dialog);
|
||||
layout->addWidget(searchBar, 0, 0);
|
||||
|
||||
ListView* listView = new ListView(&dialog);
|
||||
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
layout->addWidget(listView, 1, 0);
|
||||
listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&)));
|
||||
dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
|
||||
|
||||
QStringListModel* model = new QStringListModel(_models.keys(), listView);
|
||||
model->sort(0);
|
||||
listView->setModel(model);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttons, 2, 0);
|
||||
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString();
|
||||
|
||||
emit selected(_models[selectedKey]);
|
||||
}
|
||||
|
||||
bool ModelBrowser::parseXML(QByteArray xmlFile) {
|
||||
QXmlStreamReader xml(xmlFile);
|
||||
QRegExp rx(".*fst");
|
||||
bool truncated = false;
|
||||
QString lastKey;
|
||||
|
||||
// Read xml until the end or an error is detected
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
|
||||
// Let's check if there is more
|
||||
xml.readNext();
|
||||
if (xml.text().toString() == "True") {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
|
||||
// If a file is find, process it
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
|
||||
xml.readNext();
|
||||
lastKey = xml.text().toString();
|
||||
if (rx.exactMatch(xml.text().toString())) {
|
||||
// Add the found file to the list
|
||||
_models.insert(QFileInfo(xml.text().toString()).baseName(),
|
||||
S3_URL + "/" + xml.text().toString());
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
|
||||
// Error handling
|
||||
if(xml.hasError()) {
|
||||
_models.clear();
|
||||
QMessageBox::critical(this,
|
||||
"ModelBrowser::ModelBrowser()",
|
||||
xml.errorString(),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't all the files, download the next ones
|
||||
if (truncated) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, lastKey);
|
||||
url.setQuery(query);
|
||||
|
||||
delete _downloader;
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// ModelBrowser.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ModelBrowser__
|
||||
#define __hifi__ModelBrowser__
|
||||
|
||||
#include <FileDownloader.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListView>
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString HEAD_MODELS_LOCATION = "models/heads/";
|
||||
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
|
||||
|
||||
enum ModelType {
|
||||
Head,
|
||||
Skeleton
|
||||
};
|
||||
|
||||
class ModelBrowser : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelBrowser(ModelType modelType, QWidget* parent = NULL);
|
||||
~ModelBrowser();
|
||||
|
||||
signals:
|
||||
void selected(QString filename);
|
||||
|
||||
public slots:
|
||||
void browse();
|
||||
|
||||
private slots:
|
||||
void downloadFinished();
|
||||
|
||||
private:
|
||||
ModelType _type;
|
||||
FileDownloader* _downloader;
|
||||
QHash<QString, QString> _models;
|
||||
|
||||
bool parseXML(QByteArray xmlFile);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ListView : public QListView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ListView(QWidget* parent) : QListView(parent) {}
|
||||
public slots:
|
||||
void keyboardSearch(const QString& text) {
|
||||
QAbstractItemView::keyboardSearch(text);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ModelBrowser__) */
|
371
interface/src/ui/ModelsBrowser.cpp
Normal file
371
interface/src/ui/ModelsBrowser.cpp
Normal file
|
@ -0,0 +1,371 @@
|
|||
//
|
||||
// ModelsBrowser.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "ModelsBrowser.h"
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString PUBLIC_URL = "http://public.highfidelity.io";
|
||||
static const QString HEAD_MODELS_LOCATION = "models/heads";
|
||||
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
|
||||
|
||||
static const QString PREFIX_PARAMETER_NAME = "prefix";
|
||||
static const QString MARKER_PARAMETER_NAME = "marker";
|
||||
static const QString IS_TRUNCATED_NAME = "IsTruncated";
|
||||
static const QString CONTAINER_NAME = "Contents";
|
||||
static const QString KEY_NAME = "Key";
|
||||
static const QString LOADING_MSG = "Loading...";
|
||||
static const QString ERROR_MSG = "Error loading files";
|
||||
|
||||
static const QString DO_NOT_MODIFY_TAG = "DoNotModify";
|
||||
|
||||
enum ModelMetaData {
|
||||
NAME,
|
||||
CREATOR,
|
||||
DATE_ADDED,
|
||||
TOTAL_SIZE,
|
||||
POLY_NUM,
|
||||
TAGS,
|
||||
|
||||
MODEL_METADATA_COUNT
|
||||
};
|
||||
static const QString propertiesNames[MODEL_METADATA_COUNT] = {
|
||||
"Name",
|
||||
"Creator",
|
||||
"Date Added",
|
||||
"Total Size",
|
||||
"Poly#",
|
||||
"Tags"
|
||||
};
|
||||
static const QString propertiesIds[MODEL_METADATA_COUNT] = {
|
||||
DO_NOT_MODIFY_TAG,
|
||||
"Creator",
|
||||
"Date-Added",
|
||||
"Total-Size",
|
||||
"Poly-Num",
|
||||
"Tags"
|
||||
};
|
||||
|
||||
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
_handler(new ModelHandler(modelsType))
|
||||
{
|
||||
connect(_handler, SIGNAL(doneDownloading()), SLOT(resizeView()));
|
||||
connect(_handler, SIGNAL(updated()), SLOT(resizeView()));
|
||||
|
||||
// Connect handler
|
||||
_handler->connect(this, SIGNAL(startDownloading()), SLOT(download()));
|
||||
_handler->connect(_handler, SIGNAL(doneDownloading()), SLOT(update()));
|
||||
_handler->connect(this, SIGNAL(destroyed()), SLOT(exit()));
|
||||
|
||||
// Setup and launch update thread
|
||||
QThread* thread = new QThread();
|
||||
thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit()));
|
||||
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
_handler->moveToThread(thread);
|
||||
thread->start();
|
||||
emit startDownloading();
|
||||
|
||||
// Initialize the view
|
||||
_view.setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
_view.setRootIsDecorated(false);
|
||||
_view.setModel(_handler->getModel());
|
||||
}
|
||||
|
||||
void ModelsBrowser::applyFilter(const QString &filter) {
|
||||
QStringList filters = filter.split(" ");
|
||||
|
||||
_handler->lockModel();
|
||||
QStandardItemModel* model = _handler->getModel();
|
||||
int rows = model->rowCount();
|
||||
|
||||
// Try and match every filter with each rows
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
bool match = false;
|
||||
for (int k = 0; k < filters.count(); ++k) {
|
||||
match = false;
|
||||
for (int j = 0; j < MODEL_METADATA_COUNT; ++j) {
|
||||
if (model->item(i, j)->text().contains(filters.at(k), Qt::CaseInsensitive)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Hid the row if it doesn't match (Make sure it's not it it does)
|
||||
if (match) {
|
||||
_view.setRowHidden(i, QModelIndex(), false);
|
||||
} else {
|
||||
_view.setRowHidden(i, QModelIndex(), true);
|
||||
}
|
||||
}
|
||||
_handler->unlockModel();
|
||||
}
|
||||
|
||||
void ModelsBrowser::resizeView() {
|
||||
for (int i = 0; i < MODEL_METADATA_COUNT; ++i) {
|
||||
_view.resizeColumnToContents(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsBrowser::browse() {
|
||||
QDialog dialog;
|
||||
dialog.setWindowTitle("Browse models");
|
||||
dialog.setMinimumSize(570, 500);
|
||||
|
||||
QGridLayout* layout = new QGridLayout(&dialog);
|
||||
dialog.setLayout(layout);
|
||||
|
||||
QLineEdit* searchBar = new QLineEdit(&dialog);
|
||||
layout->addWidget(searchBar, 0, 0);
|
||||
|
||||
layout->addWidget(&_view, 1, 0);
|
||||
dialog.connect(&_view, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
|
||||
connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&)));
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttons, 2, 0);
|
||||
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
_handler->lockModel();
|
||||
QVariant selectedFile = _handler->getModel()->data(_view.currentIndex(), Qt::UserRole);
|
||||
_handler->unlockModel();
|
||||
if (selectedFile.isValid()) {
|
||||
emit selected(selectedFile.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// So that we don't have to reconstruct the view
|
||||
_view.setParent(NULL);
|
||||
}
|
||||
|
||||
|
||||
ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) :
|
||||
QObject(parent),
|
||||
_initiateExit(false),
|
||||
_type(modelsType)
|
||||
{
|
||||
// set headers data
|
||||
QStringList headerData;
|
||||
for (int i = 0; i < MODEL_METADATA_COUNT; ++i) {
|
||||
headerData << propertiesNames[i];
|
||||
}
|
||||
_model.setHorizontalHeaderLabels(headerData);
|
||||
}
|
||||
|
||||
void ModelHandler::download() {
|
||||
_lock.lockForWrite();
|
||||
if (_initiateExit) {
|
||||
_lock.unlock();
|
||||
return;
|
||||
}
|
||||
// Show loading message
|
||||
QStandardItem* loadingItem = new QStandardItem(LOADING_MSG);
|
||||
loadingItem->setEnabled(false);
|
||||
_model.appendRow(loadingItem);
|
||||
_lock.unlock();
|
||||
|
||||
// Query models list
|
||||
queryNewFiles();
|
||||
}
|
||||
|
||||
void ModelHandler::update() {
|
||||
_lock.lockForWrite();
|
||||
if (_initiateExit) {
|
||||
_lock.unlock();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _model.rowCount(); ++i) {
|
||||
QUrl url(_model.item(i,0)->data(Qt::UserRole).toString());
|
||||
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
|
||||
QNetworkRequest request(url);
|
||||
accessManager->head(request);
|
||||
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
|
||||
}
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
void ModelHandler::exit() {
|
||||
_lock.lockForWrite();
|
||||
_initiateExit = true;
|
||||
|
||||
// Disconnect everything
|
||||
disconnect();
|
||||
thread()->disconnect();
|
||||
|
||||
// Make sure the thread will exit correctly
|
||||
thread()->connect(this, SIGNAL(destroyed()), SLOT(quit()));
|
||||
thread()->connect(thread(), SIGNAL(finished()), SLOT(deleteLater()));
|
||||
deleteLater();
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
void ModelHandler::downloadFinished(QNetworkReply* reply) {
|
||||
QByteArray data = reply->readAll();
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
parseXML(data);
|
||||
} else {
|
||||
parseHeaders(reply);
|
||||
}
|
||||
reply->deleteLater();
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void ModelHandler::queryNewFiles(QString marker) {
|
||||
if (_initiateExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build query
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
|
||||
if (!marker.isEmpty()) {
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
|
||||
}
|
||||
|
||||
// Download
|
||||
url.setQuery(query);
|
||||
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
|
||||
QNetworkRequest request(url);
|
||||
accessManager->get(request);
|
||||
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
|
||||
|
||||
}
|
||||
|
||||
bool ModelHandler::parseXML(QByteArray xmlFile) {
|
||||
_lock.lockForWrite();
|
||||
if (_initiateExit) {
|
||||
_lock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader xml(xmlFile);
|
||||
QRegExp rx(".*fst");
|
||||
bool truncated = false;
|
||||
QString lastKey;
|
||||
|
||||
// Remove loading indication
|
||||
int oldLastRow = _model.rowCount() - 1;
|
||||
delete _model.takeRow(oldLastRow).first();
|
||||
|
||||
// Read xml until the end or an error is detected
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
if (_initiateExit) {
|
||||
_lock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
|
||||
// Let's check if there is more
|
||||
xml.readNext();
|
||||
if (xml.text().toString() == "True") {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
|
||||
// If a file is find, process it
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
|
||||
xml.readNext();
|
||||
lastKey = xml.text().toString();
|
||||
if (rx.exactMatch(xml.text().toString())) {
|
||||
// Add the found file to the list
|
||||
QList<QStandardItem*> model;
|
||||
model << new QStandardItem(QFileInfo(xml.text().toString()).baseName());
|
||||
model.first()->setData(PUBLIC_URL + "/" + xml.text().toString(), Qt::UserRole);
|
||||
for (int i = 1; i < MODEL_METADATA_COUNT; ++i) {
|
||||
model << new QStandardItem();
|
||||
}
|
||||
|
||||
_model.appendRow(model);
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
|
||||
// Error handling
|
||||
if(xml.hasError()) {
|
||||
_model.clear();
|
||||
QStandardItem* errorItem = new QStandardItem(ERROR_MSG);
|
||||
errorItem->setEnabled(false);
|
||||
_model.appendRow(errorItem);
|
||||
|
||||
_lock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't all the files, download the next ones
|
||||
if (truncated) {
|
||||
// Indicate more files are being loaded
|
||||
QStandardItem* loadingItem = new QStandardItem(LOADING_MSG);
|
||||
loadingItem->setEnabled(false);
|
||||
_model.appendRow(loadingItem);
|
||||
|
||||
// query those files
|
||||
queryNewFiles(lastKey);
|
||||
}
|
||||
_lock.unlock();
|
||||
|
||||
if (!truncated) {
|
||||
emit doneDownloading();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelHandler::parseHeaders(QNetworkReply* reply) {
|
||||
_lock.lockForWrite();
|
||||
|
||||
QList<QStandardItem*> items = _model.findItems(QFileInfo(reply->url().toString()).baseName());
|
||||
if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MODEL_METADATA_COUNT; ++i) {
|
||||
for (int k = 1; k < reply->rawHeaderPairs().count(); ++k) {
|
||||
QString key = reply->rawHeaderPairs().at(k).first.data();
|
||||
QString item = reply->rawHeaderPairs().at(k).second.data();
|
||||
if (key == propertiesIds[i]) {
|
||||
_model.item(_model.indexFromItem(items.first()).row(), i)->setText(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
_lock.unlock();
|
||||
|
||||
emit updated();
|
||||
return true;
|
||||
}
|
||||
|
78
interface/src/ui/ModelsBrowser.h
Normal file
78
interface/src/ui/ModelsBrowser.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// ModelsBrowser.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ModelsBrowser__
|
||||
#define __hifi__ModelsBrowser__
|
||||
|
||||
#include <QReadWriteLock>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
|
||||
|
||||
enum ModelType {
|
||||
Head,
|
||||
Skeleton
|
||||
};
|
||||
|
||||
class ModelHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ModelHandler(ModelType modelsType, QWidget* parent = NULL);
|
||||
|
||||
void lockModel() { _lock.lockForRead(); }
|
||||
QStandardItemModel* getModel() { return &_model; }
|
||||
void unlockModel() { _lock.unlock(); }
|
||||
|
||||
signals:
|
||||
void doneDownloading();
|
||||
void updated();
|
||||
|
||||
public slots:
|
||||
void download();
|
||||
void update();
|
||||
void exit();
|
||||
|
||||
private slots:
|
||||
void downloadFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
bool _initiateExit;
|
||||
ModelType _type;
|
||||
QReadWriteLock _lock;
|
||||
QStandardItemModel _model;
|
||||
|
||||
void queryNewFiles(QString marker = QString());
|
||||
bool parseXML(QByteArray xmlFile);
|
||||
bool parseHeaders(QNetworkReply* reply);
|
||||
};
|
||||
|
||||
|
||||
class ModelsBrowser : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
ModelsBrowser(ModelType modelsType, QWidget* parent = NULL);
|
||||
|
||||
signals:
|
||||
void startDownloading();
|
||||
void startUpdating();
|
||||
void selected(QString filename);
|
||||
|
||||
public slots:
|
||||
void browse();
|
||||
|
||||
private slots:
|
||||
void applyFilter(const QString& filter);
|
||||
void resizeView();
|
||||
|
||||
private:
|
||||
ModelHandler* _handler;
|
||||
QTreeView _view;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ModelBrowser__) */
|
203
interface/src/ui/RunningScriptsWidget.cpp
Normal file
203
interface/src/ui/RunningScriptsWidget.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
//
|
||||
// RunningScripts.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 03/28/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
|
||||
#include "ui_runningScriptsWidget.h"
|
||||
#include "RunningScriptsWidget.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) :
|
||||
QDockWidget(parent),
|
||||
ui(new Ui::RunningScriptsWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// remove the title bar (see the Qt docs on setTitleBarWidget)
|
||||
setTitleBarWidget(new QWidget());
|
||||
|
||||
ui->runningScriptsTableWidget->setColumnCount(2);
|
||||
ui->runningScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->runningScriptsTableWidget->setShowGrid(false);
|
||||
ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(0, 235);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(1, 25);
|
||||
connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnCount(2);
|
||||
ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->recentlyLoadedScriptsTableWidget->setShowGrid(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235);
|
||||
connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked,
|
||||
this, &RunningScriptsWidget::loadScript);
|
||||
|
||||
connect(ui->hideWidgetButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::toggleRunningScriptsWidget);
|
||||
connect(ui->reloadAllButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::reloadAllScripts);
|
||||
connect(ui->stopAllButton, &QPushButton::clicked,
|
||||
this, &RunningScriptsWidget::allScriptsStopped);
|
||||
}
|
||||
|
||||
RunningScriptsWidget::~RunningScriptsWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::setRunningScripts(const QStringList& list)
|
||||
{
|
||||
ui->runningScriptsTableWidget->setRowCount(list.size());
|
||||
|
||||
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
|
||||
ui->currentlyRunningLabel->setVisible(!list.isEmpty());
|
||||
ui->line1->setVisible(!list.isEmpty());
|
||||
ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
|
||||
ui->reloadAllButton->setVisible(!list.isEmpty());
|
||||
ui->stopAllButton->setVisible(!list.isEmpty());
|
||||
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(list.at(i));
|
||||
scriptName->setToolTip(list.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
QTableWidgetItem *closeIcon = new QTableWidgetItem;
|
||||
closeIcon->setIcon(QIcon(":/images/kill-script.svg"));
|
||||
|
||||
ui->runningScriptsTableWidget->setItem(i, 0, scriptName);
|
||||
ui->runningScriptsTableWidget->setItem(i, 1, closeIcon);
|
||||
}
|
||||
|
||||
createRecentlyLoadedScriptsTable();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
switch(e->key()) {
|
||||
case Qt::Key_Escape:
|
||||
Application::getInstance()->toggleRunningScriptsWidget();
|
||||
break;
|
||||
|
||||
case Qt::Key_1:
|
||||
if (_recentlyLoadedScripts.size() > 0) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_2:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_3:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_4:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_5:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_6:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_7:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_8:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_9:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::stopScript(int row, int column)
|
||||
{
|
||||
if (column == 1) { // make sure the user has clicked on the close icon
|
||||
_lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text();
|
||||
emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text());
|
||||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::loadScript(int row, int column)
|
||||
{
|
||||
Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text());
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::allScriptsStopped()
|
||||
{
|
||||
QStringList list = Application::getInstance()->getRunningScripts();
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
_recentlyLoadedScripts.prepend(list.at(i));
|
||||
}
|
||||
|
||||
Application::getInstance()->stopAllScripts();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::createRecentlyLoadedScriptsTable()
|
||||
{
|
||||
if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
|
||||
_recentlyLoadedScripts.prepend(_lastStoppedScript);
|
||||
_lastStoppedScript = "";
|
||||
}
|
||||
|
||||
for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) {
|
||||
if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) {
|
||||
_recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
|
||||
int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
|
||||
ui->recentlyLoadedScriptsTableWidget->setRowCount(limit);
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setToolTip(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
QTableWidgetItem *number = new QTableWidgetItem;
|
||||
number->setText(QString::number(i+1) + ".");
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number);
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName);
|
||||
}
|
||||
}
|
46
interface/src/ui/RunningScriptsWidget.h
Normal file
46
interface/src/ui/RunningScriptsWidget.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// RunningScripts.h
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 03/28/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
|
||||
#ifndef __hifi__RunningScriptsWidget__
|
||||
#define __hifi__RunningScriptsWidget__
|
||||
|
||||
// Qt
|
||||
#include <QDockWidget>
|
||||
|
||||
namespace Ui {
|
||||
class RunningScriptsWidget;
|
||||
}
|
||||
|
||||
class RunningScriptsWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RunningScriptsWidget(QDockWidget *parent = 0);
|
||||
~RunningScriptsWidget();
|
||||
|
||||
void setRunningScripts(const QStringList& list);
|
||||
|
||||
signals:
|
||||
void stopScriptName(const QString& name);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e);
|
||||
|
||||
private slots:
|
||||
void stopScript(int row, int column);
|
||||
void loadScript(int row, int column);
|
||||
void allScriptsStopped();
|
||||
|
||||
private:
|
||||
Ui::RunningScriptsWidget *ui;
|
||||
QStringList _recentlyLoadedScripts;
|
||||
QString _lastStoppedScript;
|
||||
|
||||
void createRecentlyLoadedScriptsTable();
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__RunningScriptsWidget__) */
|
486
interface/src/ui/Stats.cpp
Normal file
486
interface/src/ui/Stats.cpp
Normal file
|
@ -0,0 +1,486 @@
|
|||
//
|
||||
// Stats.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Lucas Crisman on 22/03/14.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved
|
||||
//
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/component_wise.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include "Stats.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "Menu.h"
|
||||
#include "Util.h"
|
||||
|
||||
const int STATS_PELS_PER_LINE = 20;
|
||||
|
||||
const int STATS_GENERAL_MIN_WIDTH = 165;
|
||||
const int STATS_PING_MIN_WIDTH = 190;
|
||||
const int STATS_GEO_MIN_WIDTH = 240;
|
||||
const int STATS_VOXEL_MIN_WIDTH = 410;
|
||||
|
||||
Stats* Stats::getInstance() {
|
||||
static Stats stats;
|
||||
return &stats;
|
||||
}
|
||||
|
||||
Stats::Stats():
|
||||
_expanded(false),
|
||||
_recentMaxPackets(0),
|
||||
_resetRecentMaxPacketsSoon(true),
|
||||
_generalStatsWidth(STATS_GENERAL_MIN_WIDTH),
|
||||
_pingStatsWidth(STATS_PING_MIN_WIDTH),
|
||||
_geoStatsWidth(STATS_GEO_MIN_WIDTH),
|
||||
_voxelStatsWidth(STATS_VOXEL_MIN_WIDTH),
|
||||
_lastHorizontalOffset(0)
|
||||
{
|
||||
QGLWidget* glWidget = Application::getInstance()->getGLWidget();
|
||||
resetWidth(glWidget->width(), 0);
|
||||
}
|
||||
|
||||
void Stats::toggleExpanded() {
|
||||
_expanded = !_expanded;
|
||||
}
|
||||
|
||||
// called on mouse click release
|
||||
// check for clicks over stats in order to expand or contract them
|
||||
void Stats::checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset) {
|
||||
QGLWidget* glWidget = Application::getInstance()->getGLWidget();
|
||||
|
||||
if (0 != glm::compMax(glm::abs(glm::ivec2(mouseX - mouseDragStartedX, mouseY - mouseDragStartedY)))) {
|
||||
// not worried about dragging on stats
|
||||
return;
|
||||
}
|
||||
|
||||
int statsHeight = 0,
|
||||
statsWidth = 0,
|
||||
statsX = 0,
|
||||
statsY = 0,
|
||||
lines = 0;
|
||||
|
||||
statsX = horizontalOffset;
|
||||
|
||||
// top-left stats click
|
||||
lines = _expanded ? 5 : 3;
|
||||
statsHeight = lines * STATS_PELS_PER_LINE + 10;
|
||||
if (mouseX > statsX && mouseX < statsX + _generalStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) {
|
||||
toggleExpanded();
|
||||
return;
|
||||
}
|
||||
statsX += _generalStatsWidth;
|
||||
|
||||
// ping stats click
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
lines = _expanded ? 4 : 3;
|
||||
statsHeight = lines * STATS_PELS_PER_LINE + 10;
|
||||
if (mouseX > statsX && mouseX < statsX + _pingStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) {
|
||||
toggleExpanded();
|
||||
return;
|
||||
}
|
||||
statsX += _pingStatsWidth;
|
||||
}
|
||||
|
||||
// geo stats panel click
|
||||
lines = _expanded ? 4 : 3;
|
||||
statsHeight = lines * STATS_PELS_PER_LINE + 10;
|
||||
if (mouseX > statsX && mouseX < statsX + _geoStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) {
|
||||
toggleExpanded();
|
||||
return;
|
||||
}
|
||||
statsX += _geoStatsWidth;
|
||||
|
||||
// top-right stats click
|
||||
lines = _expanded ? 11 : 3;
|
||||
statsHeight = lines * STATS_PELS_PER_LINE + 10;
|
||||
statsWidth = glWidget->width() - statsX;
|
||||
if (mouseX > statsX && mouseX < statsX + statsWidth && mouseY > statsY && mouseY < statsY + statsHeight) {
|
||||
toggleExpanded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Stats::resetWidth(int width, int horizontalOffset) {
|
||||
QGLWidget* glWidget = Application::getInstance()->getGLWidget();
|
||||
int extraSpace = glWidget->width() - horizontalOffset -2
|
||||
- STATS_GENERAL_MIN_WIDTH
|
||||
- (Menu::getInstance()->isOptionChecked(MenuOption::TestPing) ? STATS_PING_MIN_WIDTH -1 : 0)
|
||||
- STATS_GEO_MIN_WIDTH
|
||||
- STATS_VOXEL_MIN_WIDTH;
|
||||
|
||||
int panels = 4;
|
||||
|
||||
_generalStatsWidth = STATS_GENERAL_MIN_WIDTH;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
_pingStatsWidth = STATS_PING_MIN_WIDTH;
|
||||
} else {
|
||||
_pingStatsWidth = 0;
|
||||
panels = 3;
|
||||
}
|
||||
_geoStatsWidth = STATS_GEO_MIN_WIDTH;
|
||||
_voxelStatsWidth = STATS_VOXEL_MIN_WIDTH;
|
||||
|
||||
if (extraSpace > panels) {
|
||||
_generalStatsWidth += (int) extraSpace / panels;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
_pingStatsWidth += (int) extraSpace / panels;
|
||||
}
|
||||
_geoStatsWidth += (int) extraSpace / panels;
|
||||
_voxelStatsWidth += glWidget->width() - (_generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// translucent background box that makes stats more readable
|
||||
void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int height) {
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(((rgba >> 24) & 0xff) / 255.0f,
|
||||
((rgba >> 16) & 0xff) / 255.0f,
|
||||
((rgba >> 8) & 0xff) / 255.0f,
|
||||
(rgba & 0xff) / 255.0f);
|
||||
glVertex3f(x, y, 0);
|
||||
glVertex3f(x + width, y, 0);
|
||||
glVertex3f(x + width, y + height, 0);
|
||||
glVertex3f(x , y + height, 0);
|
||||
glEnd();
|
||||
glColor4f(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
// display expanded or contracted stats
|
||||
void Stats::display(
|
||||
const float* color,
|
||||
int horizontalOffset,
|
||||
float fps,
|
||||
int packetsPerSecond,
|
||||
int bytesPerSecond,
|
||||
int voxelPacketsToProcess)
|
||||
{
|
||||
QGLWidget* glWidget = Application::getInstance()->getGLWidget();
|
||||
|
||||
unsigned int backgroundColor = 0x33333399;
|
||||
int verticalOffset = 0, lines = 0;
|
||||
|
||||
QLocale locale(QLocale::English);
|
||||
std::stringstream voxelStats;
|
||||
|
||||
if (_lastHorizontalOffset != horizontalOffset) {
|
||||
resetWidth(glWidget->width(), horizontalOffset);
|
||||
_lastHorizontalOffset = horizontalOffset;
|
||||
}
|
||||
|
||||
glPointSize(1.0f);
|
||||
|
||||
// we need to take one avatar out so we don't include ourselves
|
||||
int totalAvatars = Application::getInstance()->getAvatarManager().size() - 1;
|
||||
int totalServers = NodeList::getInstance()->size();
|
||||
|
||||
lines = _expanded ? 5 : 3;
|
||||
drawBackground(backgroundColor, horizontalOffset, 0, _generalStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||
horizontalOffset += 5;
|
||||
|
||||
char serverNodes[30];
|
||||
sprintf(serverNodes, "Servers: %d", totalServers);
|
||||
char avatarNodes[30];
|
||||
sprintf(avatarNodes, "Avatars: %d", totalAvatars);
|
||||
char framesPerSecond[30];
|
||||
sprintf(framesPerSecond, "Framerate: %3.0f FPS", fps);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, serverNodes, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarNodes, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, framesPerSecond, color);
|
||||
|
||||
if (_expanded) {
|
||||
char packetsPerSecondString[30];
|
||||
sprintf(packetsPerSecondString, "Pkts/sec: %d", packetsPerSecond);
|
||||
char averageMegabitsPerSecond[30];
|
||||
sprintf(averageMegabitsPerSecond, "Mbps: %3.2f", (float)bytesPerSecond * 8.f / 1000000.f);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, packetsPerSecondString, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, averageMegabitsPerSecond, color);
|
||||
}
|
||||
|
||||
verticalOffset = 0;
|
||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0;
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||
|
||||
pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0;
|
||||
pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0;
|
||||
|
||||
// Now handle voxel servers, since there could be more than one, we average their ping times
|
||||
unsigned long totalPingVoxel = 0;
|
||||
int voxelServerCount = 0;
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getType() == NodeType::VoxelServer) {
|
||||
totalPingVoxel += node->getPingMs();
|
||||
voxelServerCount++;
|
||||
if (pingVoxelMax < node->getPingMs()) {
|
||||
pingVoxelMax = node->getPingMs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voxelServerCount) {
|
||||
pingVoxel = totalPingVoxel/voxelServerCount;
|
||||
}
|
||||
|
||||
lines = _expanded ? 4 : 3;
|
||||
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||
horizontalOffset += 5;
|
||||
|
||||
Audio* audio = Application::getInstance()->getAudio();
|
||||
|
||||
char audioJitter[30];
|
||||
sprintf(audioJitter,
|
||||
"Buffer msecs %.1f",
|
||||
(float) (audio->getNetworkBufferLengthSamplesPerChannel() + (float) audio->getJitterBufferSamples()) /
|
||||
(float) audio->getNetworkSampleRate() * 1000.f);
|
||||
drawText(30, glWidget->height() - 22, 0.10f, 0.f, 2.f, audioJitter, color);
|
||||
|
||||
|
||||
char audioPing[30];
|
||||
sprintf(audioPing, "Audio ping: %d", pingAudio);
|
||||
|
||||
|
||||
char avatarPing[30];
|
||||
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
|
||||
char voxelAvgPing[30];
|
||||
sprintf(voxelAvgPing, "Voxel avg ping: %d", pingVoxel);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, audioPing, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarPing, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, voxelAvgPing, color);
|
||||
|
||||
if (_expanded) {
|
||||
char voxelMaxPing[30];
|
||||
sprintf(voxelMaxPing, "Voxel max ping: %d", pingVoxelMax);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, voxelMaxPing, color);
|
||||
}
|
||||
|
||||
verticalOffset = 0;
|
||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
|
||||
}
|
||||
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||
|
||||
lines = _expanded ? 4 : 3;
|
||||
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||
horizontalOffset += 5;
|
||||
|
||||
char avatarPosition[200];
|
||||
sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z);
|
||||
char avatarVelocity[30];
|
||||
sprintf(avatarVelocity, "Velocity: %.1f", glm::length(myAvatar->getVelocity()));
|
||||
char avatarBodyYaw[30];
|
||||
sprintf(avatarBodyYaw, "Yaw: %.1f", myAvatar->getBodyYaw());
|
||||
char avatarMixerStats[200];
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarPosition, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarVelocity, color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarBodyYaw, color);
|
||||
|
||||
if (_expanded) {
|
||||
SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AvatarMixer);
|
||||
if (avatarMixer) {
|
||||
sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
|
||||
roundf(avatarMixer->getAverageKilobitsPerSecond()),
|
||||
roundf(avatarMixer->getAveragePacketsPerSecond()));
|
||||
} else {
|
||||
sprintf(avatarMixerStats, "No Avatar Mixer");
|
||||
}
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, color);
|
||||
}
|
||||
|
||||
verticalOffset = 0;
|
||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3;
|
||||
|
||||
VoxelSystem* voxels = Application::getInstance()->getVoxels();
|
||||
|
||||
lines = _expanded ? 12 : 3;
|
||||
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
|
||||
horizontalOffset += 5;
|
||||
|
||||
if (_expanded) {
|
||||
// Local Voxel Memory Usage
|
||||
voxelStats.str("");
|
||||
voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats <<
|
||||
"Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
|
||||
"VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB";
|
||||
if (voxels->hasVoxelMemoryUsageGPU()) {
|
||||
voxelStats << " / GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
// Voxel Rendering
|
||||
voxelStats.str("");
|
||||
voxelStats.precision(4);
|
||||
voxelStats << "Voxel Rendering Slots Max: " << voxels->getMaxVoxels() / 1000.f << "K";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats.precision(4);
|
||||
voxelStats << "Drawn: " << voxels->getVoxelsWritten() / 1000.f << "K " <<
|
||||
"Abandoned: " << voxels->getAbandonedVoxels() / 1000.f << "K ";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
// iterate all the current voxel stats, and list their sending modes, and total voxel counts
|
||||
std::stringstream sendingMode("");
|
||||
sendingMode << "Octree Sending Mode: [";
|
||||
int serverCount = 0;
|
||||
int movingServerCount = 0;
|
||||
unsigned long totalNodes = 0;
|
||||
unsigned long totalInternal = 0;
|
||||
unsigned long totalLeaves = 0;
|
||||
NodeToOctreeSceneStats* octreeServerSceneStats = Application::getInstance()->getOcteeSceneStats();
|
||||
for(NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) {
|
||||
//const QUuid& uuid = i->first;
|
||||
OctreeSceneStats& stats = i->second;
|
||||
serverCount++;
|
||||
if (_expanded) {
|
||||
if (serverCount > 1) {
|
||||
sendingMode << ",";
|
||||
}
|
||||
if (stats.isMoving()) {
|
||||
sendingMode << "M";
|
||||
movingServerCount++;
|
||||
} else {
|
||||
sendingMode << "S";
|
||||
}
|
||||
}
|
||||
|
||||
// calculate server node totals
|
||||
totalNodes += stats.getTotalElements();
|
||||
if (_expanded) {
|
||||
totalInternal += stats.getTotalInternal();
|
||||
totalLeaves += stats.getTotalLeaves();
|
||||
}
|
||||
}
|
||||
if (_expanded) {
|
||||
if (serverCount == 0) {
|
||||
sendingMode << "---";
|
||||
}
|
||||
sendingMode << "] " << serverCount << " servers";
|
||||
if (movingServerCount > 0) {
|
||||
sendingMode << " <SCENE NOT STABLE>";
|
||||
} else {
|
||||
sendingMode << " <SCENE STABLE>";
|
||||
}
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)sendingMode.str().c_str(), color);
|
||||
}
|
||||
|
||||
// Incoming packets
|
||||
if (_expanded) {
|
||||
voxelStats.str("");
|
||||
QString packetsString = locale.toString((int)voxelPacketsToProcess);
|
||||
QString maxString = locale.toString((int)_recentMaxPackets);
|
||||
voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString)
|
||||
<< " [Recent Max: " << qPrintable(maxString) << "]";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
|
||||
if (_resetRecentMaxPacketsSoon && voxelPacketsToProcess > 0) {
|
||||
_recentMaxPackets = 0;
|
||||
_resetRecentMaxPacketsSoon = false;
|
||||
}
|
||||
if (voxelPacketsToProcess == 0) {
|
||||
_resetRecentMaxPacketsSoon = true;
|
||||
} else {
|
||||
if (voxelPacketsToProcess > _recentMaxPackets) {
|
||||
_recentMaxPackets = voxelPacketsToProcess;
|
||||
}
|
||||
}
|
||||
|
||||
verticalOffset += (_expanded ? STATS_PELS_PER_LINE : 0);
|
||||
|
||||
QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
|
||||
|
||||
// Server Voxels
|
||||
voxelStats.str("");
|
||||
voxelStats << "Server voxels: " << qPrintable(serversTotalString);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
if (_expanded) {
|
||||
QString serversInternalString = locale.toString((uint)totalInternal);
|
||||
QString serversLeavesString = locale.toString((uint)totalLeaves);
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats <<
|
||||
"Internal: " << qPrintable(serversInternalString) << " " <<
|
||||
"Leaves: " << qPrintable(serversLeavesString) << "";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
|
||||
unsigned long localTotal = VoxelTreeElement::getNodeCount();
|
||||
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
|
||||
|
||||
// Local Voxels
|
||||
voxelStats.str("");
|
||||
voxelStats << "Local voxels: " << qPrintable(localTotalString);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
|
||||
if (_expanded) {
|
||||
unsigned long localInternal = VoxelTreeElement::getInternalNodeCount();
|
||||
unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount();
|
||||
QString localInternalString = locale.toString((uint)localInternal);
|
||||
QString localLeavesString = locale.toString((uint)localLeaves);
|
||||
|
||||
voxelStats.str("");
|
||||
voxelStats <<
|
||||
"Internal: " << qPrintable(localInternalString) << " " <<
|
||||
"Leaves: " << qPrintable(localLeavesString) << "";
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0, 2, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
|
||||
// LOD Details
|
||||
if (_expanded) {
|
||||
voxelStats.str("");
|
||||
QString displayLODDetails = Menu::getInstance()->getLODFeedbackText();
|
||||
voxelStats << "LOD: You can see " << qPrintable(displayLODDetails.trimmed());
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), color);
|
||||
}
|
||||
}
|
41
interface/src/ui/Stats.h
Normal file
41
interface/src/ui/Stats.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Stats.h
|
||||
// interface
|
||||
//
|
||||
// Created by Lucas Crisman on 22/03/14.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
class Stats: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static Stats* getInstance();
|
||||
|
||||
Stats();
|
||||
|
||||
static void drawBackground(unsigned int rgba, int x, int y, int width, int height);
|
||||
|
||||
void toggleExpanded();
|
||||
void checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset);
|
||||
void resetWidth(int width, int horizontalOffset);
|
||||
void display(const float* color, int horizontalOffset, float fps, int packetsPerSecond, int bytesPerSecond, int voxelPacketsToProcess);
|
||||
private:
|
||||
static Stats* _sharedInstance;
|
||||
|
||||
bool _expanded;
|
||||
|
||||
int _recentMaxPackets; // recent max incoming voxel packets to process
|
||||
bool _resetRecentMaxPacketsSoon;
|
||||
|
||||
int _generalStatsWidth;
|
||||
int _pingStatsWidth;
|
||||
int _geoStatsWidth;
|
||||
int _voxelStatsWidth;
|
||||
|
||||
int _lastHorizontalOffset;
|
||||
};
|
|
@ -16,6 +16,7 @@
|
|||
#include "ImageOverlay.h"
|
||||
|
||||
ImageOverlay::ImageOverlay() :
|
||||
_manager(0),
|
||||
_textureID(0),
|
||||
_renderImage(false),
|
||||
_textureBound(false),
|
||||
|
@ -33,9 +34,9 @@ ImageOverlay::~ImageOverlay() {
|
|||
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
|
||||
void ImageOverlay::setImageURL(const QUrl& url) {
|
||||
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
|
||||
manager->get(QNetworkRequest(url));
|
||||
_manager = new QNetworkAccessManager();
|
||||
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
|
||||
_manager->get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void ImageOverlay::replyFinished(QNetworkReply* reply) {
|
||||
|
@ -44,7 +45,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) {
|
|||
QByteArray rawData = reply->readAll();
|
||||
_textureImage.loadFromData(rawData);
|
||||
_renderImage = true;
|
||||
|
||||
_manager->deleteLater();
|
||||
}
|
||||
|
||||
void ImageOverlay::render() {
|
||||
|
|
|
@ -49,6 +49,8 @@ private:
|
|||
|
||||
QUrl _imageURL;
|
||||
QImage _textureImage;
|
||||
QNetworkAccessManager* _manager;
|
||||
|
||||
GLuint _textureID;
|
||||
QRect _fromImage; // where from in the image to sample
|
||||
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
|
||||
|
|
|
@ -1508,7 +1508,9 @@ void VoxelSystem::killLocalVoxels() {
|
|||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"VoxelSystem::killLocalVoxels()");
|
||||
_tree->lockForWrite();
|
||||
VoxelSystem* voxelSystem = _tree->getRoot()->getVoxelSystem();
|
||||
_tree->eraseAllOctreeElements();
|
||||
_tree->getRoot()->setVoxelSystem(voxelSystem);
|
||||
_tree->unlock();
|
||||
clearFreeBufferIndexes();
|
||||
if (_usePrimitiveRenderer) {
|
||||
|
|
248
interface/ui/runningScriptsWidget.ui
Normal file
248
interface/ui/runningScriptsWidget.ui
Normal file
|
@ -0,0 +1,248 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RunningScriptsWidget</class>
|
||||
<widget class="QWidget" name="RunningScriptsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>310</width>
|
||||
<height>651</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #f7f7f7;
|
||||
font-family: Helvetica, Arial, "DejaVu Sans"; </string>
|
||||
</property>
|
||||
<widget class="QLabel" name="widgetTitle">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>221</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="currentlyRunningLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>301</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="reloadAllButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>230</y>
|
||||
<width>111</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reload All</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/reload.svg</normaloff>:/images/reload.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="stopAllButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>230</y>
|
||||
<width>101</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop All</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/stop.svg</normaloff>:/images/stop.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>280</y>
|
||||
<width>301</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>300</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedInstruction">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>590</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #95a5a6;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(click a script or use the 1-9 keys to load and run it)</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="hideWidgetButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>270</x>
|
||||
<y>10</y>
|
||||
<width>31</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/close.svg</normaloff>:/images/close.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="runningScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>70</y>
|
||||
<width>271</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line1">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="recentlyLoadedScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>310</y>
|
||||
<width>271</width>
|
||||
<height>281</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="noRunningScriptsLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>271</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>There are no scripts currently running.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -40,6 +40,7 @@ AvatarData::AvatarData() :
|
|||
_handState(0),
|
||||
_keyState(NO_KEY_DOWN),
|
||||
_isChatCirclingEnabled(false),
|
||||
_hasNewJointRotations(true),
|
||||
_headData(NULL),
|
||||
_handData(NULL),
|
||||
_displayNameBoundingRect(),
|
||||
|
@ -483,6 +484,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
|||
}
|
||||
}
|
||||
} // numJoints * 8 bytes
|
||||
_hasNewJointRotations = true;
|
||||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
|
|
@ -242,6 +242,8 @@ protected:
|
|||
|
||||
bool _isChatCirclingEnabled;
|
||||
|
||||
bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar
|
||||
|
||||
HeadData* _headData;
|
||||
HandData* _handData;
|
||||
|
||||
|
|
|
@ -11,12 +11,18 @@
|
|||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelData.h"
|
||||
|
||||
REGISTER_META_OBJECT(FloatAttribute)
|
||||
REGISTER_META_OBJECT(QRgbAttribute)
|
||||
REGISTER_META_OBJECT(PackedNormalAttribute)
|
||||
REGISTER_META_OBJECT(SpannerQRgbAttribute)
|
||||
REGISTER_META_OBJECT(SpannerPackedNormalAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectAttribute)
|
||||
REGISTER_META_OBJECT(SharedObjectSetAttribute)
|
||||
REGISTER_META_OBJECT(SpannerSetAttribute)
|
||||
|
||||
static int attributePointerMetaTypeId = qRegisterMetaType<AttributePointer>();
|
||||
static int ownedAttributeValueMetaTypeId = qRegisterMetaType<OwnedAttributeValue>();
|
||||
|
||||
AttributeRegistry* AttributeRegistry::getInstance() {
|
||||
static AttributeRegistry registry;
|
||||
return ®istry;
|
||||
|
@ -27,10 +33,13 @@ AttributeRegistry::AttributeRegistry() :
|
|||
SharedObjectPointer(new DefaultMetavoxelGuide())))),
|
||||
_spannersAttribute(registerAttribute(new SpannerSetAttribute("spanners", &Spanner::staticMetaObject))),
|
||||
_colorAttribute(registerAttribute(new QRgbAttribute("color"))),
|
||||
_normalAttribute(registerAttribute(new PackedNormalAttribute("normal", qRgb(0, 127, 0)))) {
|
||||
_normalAttribute(registerAttribute(new PackedNormalAttribute("normal"))),
|
||||
_spannerColorAttribute(registerAttribute(new SpannerQRgbAttribute("spannerColor"))),
|
||||
_spannerNormalAttribute(registerAttribute(new SpannerPackedNormalAttribute("spannerNormal"))),
|
||||
_spannerMaskAttribute(registerAttribute(new FloatAttribute("spannerMask"))) {
|
||||
|
||||
// our baseline LOD threshold is for voxels; spanners are a different story
|
||||
const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 4.0f;
|
||||
const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f;
|
||||
_spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER);
|
||||
}
|
||||
|
||||
|
@ -127,6 +136,14 @@ OwnedAttributeValue::~OwnedAttributeValue() {
|
|||
}
|
||||
}
|
||||
|
||||
void OwnedAttributeValue::mix(const AttributeValue& first, const AttributeValue& second, float alpha) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
}
|
||||
_attribute = first.getAttribute();
|
||||
_value = _attribute->mix(first.getValue(), second.getValue(), alpha);
|
||||
}
|
||||
|
||||
OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) {
|
||||
if (_attribute) {
|
||||
_attribute->destroy(_value);
|
||||
|
@ -155,6 +172,10 @@ Attribute::Attribute(const QString& name) :
|
|||
Attribute::~Attribute() {
|
||||
}
|
||||
|
||||
MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value);
|
||||
}
|
||||
|
||||
void Attribute::readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state) {
|
||||
data.createRoot(state.attribute)->read(state);
|
||||
}
|
||||
|
@ -179,30 +200,49 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt
|
|||
root.writeSubdivision(state);
|
||||
}
|
||||
|
||||
FloatAttribute::FloatAttribute(const QString& name, float defaultValue) :
|
||||
SimpleInlineAttribute<float>(name, defaultValue) {
|
||||
}
|
||||
|
||||
QRgbAttribute::QRgbAttribute(const QString& name, QRgb defaultValue) :
|
||||
InlineAttribute<QRgb>(name, defaultValue) {
|
||||
}
|
||||
|
||||
bool QRgbAttribute::merge(void*& parent, void* children[]) const {
|
||||
|
||||
bool QRgbAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
QRgb firstValue = decodeInline<QRgb>(children[0]);
|
||||
int totalRed = qRed(firstValue);
|
||||
int totalGreen = qGreen(firstValue);
|
||||
int totalBlue = qBlue(firstValue);
|
||||
int totalAlpha = qAlpha(firstValue);
|
||||
int totalRed = qRed(firstValue) * totalAlpha;
|
||||
int totalGreen = qGreen(firstValue) * totalAlpha;
|
||||
int totalBlue = qBlue(firstValue) * totalAlpha;
|
||||
bool allChildrenEqual = true;
|
||||
for (int i = 1; i < Attribute::MERGE_COUNT; i++) {
|
||||
QRgb value = decodeInline<QRgb>(children[i]);
|
||||
totalRed += qRed(value);
|
||||
totalGreen += qGreen(value);
|
||||
totalBlue += qBlue(value);
|
||||
totalAlpha += qAlpha(value);
|
||||
int alpha = qAlpha(value);
|
||||
totalRed += qRed(value) * alpha;
|
||||
totalGreen += qGreen(value) * alpha;
|
||||
totalBlue += qBlue(value) * alpha;
|
||||
totalAlpha += alpha;
|
||||
allChildrenEqual &= (firstValue == value);
|
||||
}
|
||||
parent = encodeInline(qRgba(totalRed / MERGE_COUNT, totalGreen / MERGE_COUNT,
|
||||
totalBlue / MERGE_COUNT, totalAlpha / MERGE_COUNT));
|
||||
if (totalAlpha == 0) {
|
||||
parent = encodeInline(QRgb());
|
||||
} else {
|
||||
parent = encodeInline(qRgba(totalRed / totalAlpha, totalGreen / totalAlpha,
|
||||
totalBlue / totalAlpha, totalAlpha / MERGE_COUNT));
|
||||
}
|
||||
return allChildrenEqual;
|
||||
}
|
||||
|
||||
void* QRgbAttribute::mix(void* first, void* second, float alpha) const {
|
||||
QRgb firstValue = decodeInline<QRgb>(first);
|
||||
QRgb secondValue = decodeInline<QRgb>(second);
|
||||
return encodeInline(qRgba(
|
||||
glm::mix((float)qRed(firstValue), (float)qRed(secondValue), alpha),
|
||||
glm::mix((float)qGreen(firstValue), (float)qGreen(secondValue), alpha),
|
||||
glm::mix((float)qBlue(firstValue), (float)qBlue(secondValue), alpha),
|
||||
glm::mix((float)qAlpha(firstValue), (float)qAlpha(secondValue), alpha)));
|
||||
}
|
||||
|
||||
void* QRgbAttribute::createFromScript(const QScriptValue& value, QScriptEngine* engine) const {
|
||||
return encodeInline((QRgb)value.toUInt32());
|
||||
}
|
||||
|
@ -227,23 +267,26 @@ PackedNormalAttribute::PackedNormalAttribute(const QString& name, QRgb defaultVa
|
|||
QRgbAttribute(name, defaultValue) {
|
||||
}
|
||||
|
||||
bool PackedNormalAttribute::merge(void*& parent, void* children[]) const {
|
||||
bool PackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
QRgb firstValue = decodeInline<QRgb>(children[0]);
|
||||
int totalRed = (char)qRed(firstValue);
|
||||
int totalGreen = (char)qGreen(firstValue);
|
||||
int totalBlue = (char)qBlue(firstValue);
|
||||
glm::vec3 total = unpackNormal(firstValue) * (float)qAlpha(firstValue);
|
||||
bool allChildrenEqual = true;
|
||||
for (int i = 1; i < Attribute::MERGE_COUNT; i++) {
|
||||
QRgb value = decodeInline<QRgb>(children[i]);
|
||||
totalRed += (char)qRed(value);
|
||||
totalGreen += (char)qGreen(value);
|
||||
totalBlue += (char)qBlue(value);
|
||||
total += unpackNormal(value) * (float)qAlpha(value);
|
||||
allChildrenEqual &= (firstValue == value);
|
||||
}
|
||||
parent = encodeInline(packNormal(glm::normalize(glm::vec3(totalRed, totalGreen, totalBlue))));
|
||||
float length = glm::length(total);
|
||||
parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length));
|
||||
return allChildrenEqual;
|
||||
}
|
||||
|
||||
void* PackedNormalAttribute::mix(void* first, void* second, float alpha) const {
|
||||
glm::vec3 firstNormal = unpackNormal(decodeInline<QRgb>(first));
|
||||
glm::vec3 secondNormal = unpackNormal(decodeInline<QRgb>(second));
|
||||
return encodeInline(packNormal(glm::normalize(glm::mix(firstNormal, secondNormal, alpha))));
|
||||
}
|
||||
|
||||
const float CHAR_SCALE = 127.0f;
|
||||
const float INVERSE_CHAR_SCALE = 1.0f / CHAR_SCALE;
|
||||
|
||||
|
@ -256,6 +299,106 @@ glm::vec3 unpackNormal(QRgb value) {
|
|||
(char)qBlue(value) * INVERSE_CHAR_SCALE);
|
||||
}
|
||||
|
||||
SpannerQRgbAttribute::SpannerQRgbAttribute(const QString& name, QRgb defaultValue) :
|
||||
QRgbAttribute(name, defaultValue) {
|
||||
}
|
||||
|
||||
void SpannerQRgbAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
value = getDefaultValue();
|
||||
in.read(&value, 32);
|
||||
}
|
||||
|
||||
void SpannerQRgbAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
out.write(&value, 32);
|
||||
}
|
||||
|
||||
MetavoxelNode* SpannerQRgbAttribute::createMetavoxelNode(
|
||||
const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value, original);
|
||||
}
|
||||
|
||||
bool SpannerQRgbAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
if (postRead) {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (qAlpha(decodeInline<QRgb>(children[i])) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QRgb parentValue = decodeInline<QRgb>(parent);
|
||||
int totalAlpha = qAlpha(parentValue) * Attribute::MERGE_COUNT;
|
||||
int totalRed = qRed(parentValue) * totalAlpha;
|
||||
int totalGreen = qGreen(parentValue) * totalAlpha;
|
||||
int totalBlue = qBlue(parentValue) * totalAlpha;
|
||||
bool allChildrenTransparent = true;
|
||||
for (int i = 0; i < Attribute::MERGE_COUNT; i++) {
|
||||
QRgb value = decodeInline<QRgb>(children[i]);
|
||||
int alpha = qAlpha(value);
|
||||
totalRed += qRed(value) * alpha;
|
||||
totalGreen += qGreen(value) * alpha;
|
||||
totalBlue += qBlue(value) * alpha;
|
||||
totalAlpha += alpha;
|
||||
allChildrenTransparent &= (alpha == 0);
|
||||
}
|
||||
if (totalAlpha == 0) {
|
||||
parent = encodeInline(QRgb());
|
||||
} else {
|
||||
parent = encodeInline(qRgba(totalRed / totalAlpha, totalGreen / totalAlpha,
|
||||
totalBlue / totalAlpha, totalAlpha / MERGE_COUNT));
|
||||
}
|
||||
return allChildrenTransparent;
|
||||
}
|
||||
|
||||
AttributeValue SpannerQRgbAttribute::inherit(const AttributeValue& parentValue) const {
|
||||
return AttributeValue(parentValue.getAttribute());
|
||||
}
|
||||
|
||||
SpannerPackedNormalAttribute::SpannerPackedNormalAttribute(const QString& name, QRgb defaultValue) :
|
||||
PackedNormalAttribute(name, defaultValue) {
|
||||
}
|
||||
|
||||
void SpannerPackedNormalAttribute::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
value = getDefaultValue();
|
||||
in.read(&value, 32);
|
||||
}
|
||||
|
||||
void SpannerPackedNormalAttribute::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
out.write(&value, 32);
|
||||
}
|
||||
|
||||
MetavoxelNode* SpannerPackedNormalAttribute::createMetavoxelNode(
|
||||
const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value, original);
|
||||
}
|
||||
|
||||
bool SpannerPackedNormalAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
if (postRead) {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (qAlpha(decodeInline<QRgb>(children[i])) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QRgb parentValue = decodeInline<QRgb>(parent);
|
||||
glm::vec3 total = unpackNormal(parentValue) * (float)(qAlpha(parentValue) * Attribute::MERGE_COUNT);
|
||||
bool allChildrenTransparent = true;
|
||||
for (int i = 0; i < Attribute::MERGE_COUNT; i++) {
|
||||
QRgb value = decodeInline<QRgb>(children[i]);
|
||||
int alpha = qAlpha(value);
|
||||
total += unpackNormal(value) * (float)alpha;
|
||||
allChildrenTransparent &= (alpha == 0);
|
||||
}
|
||||
float length = glm::length(total);
|
||||
parent = encodeInline(length < EPSILON ? QRgb() : packNormal(total / length));
|
||||
return allChildrenTransparent;
|
||||
}
|
||||
|
||||
AttributeValue SpannerPackedNormalAttribute::inherit(const AttributeValue& parentValue) const {
|
||||
return AttributeValue(parentValue.getAttribute());
|
||||
}
|
||||
|
||||
SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject,
|
||||
const SharedObjectPointer& defaultValue) :
|
||||
InlineAttribute<SharedObjectPointer>(name, defaultValue),
|
||||
|
@ -275,7 +418,7 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons
|
|||
}
|
||||
}
|
||||
|
||||
bool SharedObjectAttribute::merge(void*& parent, void* children[]) const {
|
||||
bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
|
||||
for (int i = 1; i < MERGE_COUNT; i++) {
|
||||
if (firstChild != decodeInline<SharedObjectPointer>(children[i])) {
|
||||
|
@ -310,7 +453,12 @@ void SharedObjectSetAttribute::write(Bitstream& out, void* value, bool isLeaf) c
|
|||
out << decodeInline<SharedObjectSet>(value);
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const {
|
||||
MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode(
|
||||
const AttributeValue& value, const MetavoxelNode* original) const {
|
||||
return new MetavoxelNode(value, original);
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
|
||||
return false;
|
||||
|
@ -319,6 +467,10 @@ bool SharedObjectSetAttribute::merge(void*& parent, void* children[]) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
AttributeValue SharedObjectSetAttribute::inherit(const AttributeValue& parentValue) const {
|
||||
return AttributeValue(parentValue.getAttribute());
|
||||
}
|
||||
|
||||
QWidget* SharedObjectSetAttribute::createEditor(QWidget* parent) const {
|
||||
return new SharedObjectEditor(_metaObject, parent);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ class MetavoxelStreamState;
|
|||
|
||||
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
|
||||
|
||||
Q_DECLARE_METATYPE(AttributePointer)
|
||||
|
||||
/// Maintains information about metavoxel attribute types.
|
||||
class AttributeRegistry {
|
||||
public:
|
||||
|
@ -69,9 +71,18 @@ public:
|
|||
/// Returns a reference to the standard QRgb "color" attribute.
|
||||
const AttributePointer& getColorAttribute() const { return _colorAttribute; }
|
||||
|
||||
/// Returns a reference to the standard QRgb "normal" attribute.
|
||||
/// Returns a reference to the standard packed normal "normal" attribute.
|
||||
const AttributePointer& getNormalAttribute() const { return _normalAttribute; }
|
||||
|
||||
/// Returns a reference to the standard QRgb "spannerColor" attribute.
|
||||
const AttributePointer& getSpannerColorAttribute() const { return _spannerColorAttribute; }
|
||||
|
||||
/// Returns a reference to the standard packed normal "spannerNormal" attribute.
|
||||
const AttributePointer& getSpannerNormalAttribute() const { return _spannerNormalAttribute; }
|
||||
|
||||
/// Returns a reference to the standard "spannerMask" attribute.
|
||||
const AttributePointer& getSpannerMaskAttribute() const { return _spannerMaskAttribute; }
|
||||
|
||||
private:
|
||||
|
||||
static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine);
|
||||
|
@ -81,6 +92,9 @@ private:
|
|||
AttributePointer _spannersAttribute;
|
||||
AttributePointer _colorAttribute;
|
||||
AttributePointer _normalAttribute;
|
||||
AttributePointer _spannerColorAttribute;
|
||||
AttributePointer _spannerNormalAttribute;
|
||||
AttributePointer _spannerMaskAttribute;
|
||||
};
|
||||
|
||||
/// Converts a value to a void pointer.
|
||||
|
@ -106,8 +120,6 @@ public:
|
|||
template<class T> void setInlineValue(T value) { _value = encodeInline(value); }
|
||||
template<class T> T getInlineValue() const { return decodeInline<T>(_value); }
|
||||
|
||||
template<class T> T* getPointerValue() const { return static_cast<T*>(_value); }
|
||||
|
||||
void* copy() const;
|
||||
|
||||
bool isDefault() const;
|
||||
|
@ -143,6 +155,9 @@ public:
|
|||
/// Destroys the current value, if any.
|
||||
~OwnedAttributeValue();
|
||||
|
||||
/// Sets this attribute to a mix of the first and second provided.
|
||||
void mix(const AttributeValue& first, const AttributeValue& second, float alpha);
|
||||
|
||||
/// Destroys the current value, if any, and copies the specified other value.
|
||||
OwnedAttributeValue& operator=(const AttributeValue& other);
|
||||
|
||||
|
@ -150,6 +165,8 @@ public:
|
|||
OwnedAttributeValue& operator=(const OwnedAttributeValue& other);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(OwnedAttributeValue)
|
||||
|
||||
/// Represents a registered attribute.
|
||||
class Attribute : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
@ -177,6 +194,8 @@ public:
|
|||
virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); }
|
||||
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); }
|
||||
|
||||
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelRoot(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
|
@ -189,8 +208,15 @@ public:
|
|||
virtual bool equal(void* first, void* second) const = 0;
|
||||
|
||||
/// Merges the value of a parent and its children.
|
||||
/// \param postRead whether or not the merge is happening after a read
|
||||
/// \return whether or not the children and parent values are all equal
|
||||
virtual bool merge(void*& parent, void* children[]) const = 0;
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const = 0;
|
||||
|
||||
/// Given the parent value, returns the value that children should inherit (either the parent value or the default).
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const { return parentValue; }
|
||||
|
||||
/// Mixes the first and the second, returning a new value with the result.
|
||||
virtual void* mix(void* first, void* second, float alpha) const = 0;
|
||||
|
||||
virtual void* getDefaultValue() const = 0;
|
||||
|
||||
|
@ -221,6 +247,8 @@ public:
|
|||
|
||||
virtual bool equal(void* first, void* second) const { return decodeInline<T>(first) == decodeInline<T>(second); }
|
||||
|
||||
virtual void* mix(void* first, void* second, float alpha) const { return create(alpha < 0.5f ? first : second); }
|
||||
|
||||
virtual void* getDefaultValue() const { return encodeInline(_defaultValue); }
|
||||
|
||||
protected:
|
||||
|
@ -241,27 +269,39 @@ template<class T, int bits> inline void InlineAttribute<T, bits>::write(Bitstrea
|
|||
}
|
||||
}
|
||||
|
||||
/// Provides merging using the =, ==, += and /= operators.
|
||||
/// Provides averaging using the +=, ==, and / operators.
|
||||
template<class T, int bits = 32> class SimpleInlineAttribute : public InlineAttribute<T, bits> {
|
||||
public:
|
||||
|
||||
SimpleInlineAttribute(const QString& name, T defaultValue = T()) : InlineAttribute<T, bits>(name, defaultValue) { }
|
||||
SimpleInlineAttribute(const QString& name, const T& defaultValue = T()) : InlineAttribute<T, bits>(name, defaultValue) { }
|
||||
|
||||
virtual bool merge(void*& parent, void* children[]) const;
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
};
|
||||
|
||||
template<class T, int bits> inline bool SimpleInlineAttribute<T, bits>::merge(void*& parent, void* children[]) const {
|
||||
T& merged = *(T*)&parent;
|
||||
merged = decodeInline<T>(children[0]);
|
||||
template<class T, int bits> inline bool SimpleInlineAttribute<T, bits>::merge(
|
||||
void*& parent, void* children[], bool postRead) const {
|
||||
T firstValue = decodeInline<T>(children[0]);
|
||||
T totalValue = firstValue;
|
||||
bool allChildrenEqual = true;
|
||||
for (int i = 1; i < Attribute::MERGE_COUNT; i++) {
|
||||
merged += decodeInline<T>(children[i]);
|
||||
allChildrenEqual &= (decodeInline<T>(children[0]) == decodeInline<T>(children[i]));
|
||||
T value = decodeInline<T>(children[i]);
|
||||
totalValue += value;
|
||||
allChildrenEqual &= (firstValue == value);
|
||||
}
|
||||
merged /= Attribute::MERGE_COUNT;
|
||||
parent = encodeInline(totalValue / Attribute::MERGE_COUNT);
|
||||
return allChildrenEqual;
|
||||
}
|
||||
|
||||
/// Simple float attribute.
|
||||
class FloatAttribute : public SimpleInlineAttribute<float> {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float defaultValue MEMBER _defaultValue)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE FloatAttribute(const QString& name = QString(), float defaultValue = 0.0f);
|
||||
};
|
||||
|
||||
/// Provides appropriate averaging for RGBA values.
|
||||
class QRgbAttribute : public InlineAttribute<QRgb> {
|
||||
Q_OBJECT
|
||||
|
@ -271,7 +311,9 @@ public:
|
|||
|
||||
Q_INVOKABLE QRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb());
|
||||
|
||||
virtual bool merge(void*& parent, void* children[]) const;
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual void* mix(void* first, void* second, float alpha) const;
|
||||
|
||||
virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const;
|
||||
|
||||
|
@ -288,7 +330,9 @@ public:
|
|||
|
||||
Q_INVOKABLE PackedNormalAttribute(const QString& name = QString(), QRgb defaultValue = QRgb());
|
||||
|
||||
virtual bool merge(void*& parent, void* children[]) const;
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual void* mix(void* first, void* second, float alpha) const;
|
||||
};
|
||||
|
||||
/// Packs a normal into an RGB value.
|
||||
|
@ -297,6 +341,42 @@ QRgb packNormal(const glm::vec3& normal);
|
|||
/// Unpacks a normal from an RGB value.
|
||||
glm::vec3 unpackNormal(QRgb value);
|
||||
|
||||
/// RGBA values for voxelized spanners.
|
||||
class SpannerQRgbAttribute : public QRgbAttribute {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SpannerQRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb());
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
|
||||
};
|
||||
|
||||
/// Packed normals for voxelized spanners.
|
||||
class SpannerPackedNormalAttribute : public PackedNormalAttribute {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE SpannerPackedNormalAttribute(const QString& name = QString(), QRgb defaultValue = QRgb());
|
||||
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
|
||||
};
|
||||
|
||||
/// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject).
|
||||
class SharedObjectAttribute : public InlineAttribute<SharedObjectPointer> {
|
||||
Q_OBJECT
|
||||
|
@ -311,7 +391,7 @@ public:
|
|||
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 bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual void* createFromVariant(const QVariant& value) const;
|
||||
|
||||
|
@ -337,7 +417,11 @@ public:
|
|||
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 MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
|
||||
|
||||
virtual QWidget* createEditor(QWidget* parent = NULL) const;
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#include <cstring>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDataStream>
|
||||
#include <QMetaProperty>
|
||||
#include <QMetaType>
|
||||
#include <QUrl>
|
||||
#include <QtDebug>
|
||||
|
@ -31,6 +31,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(QString)
|
|||
REGISTER_SIMPLE_TYPE_STREAMER(QUrl)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(QVariantList)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(QVariantHash)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(SharedObjectPointer)
|
||||
|
||||
// some types don't quite work with our macro
|
||||
static int vec3Streamer = Bitstream::registerTypeStreamer(qMetaTypeId<glm::vec3>(), new SimpleTypeStreamer<glm::vec3>());
|
||||
|
@ -82,6 +83,10 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const TypeStreamer* Bitstream::getTypeStreamer(int type) {
|
||||
return getTypeStreamers().value(type);
|
||||
}
|
||||
|
||||
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
|
||||
return getMetaObjects().value(className);
|
||||
}
|
||||
|
@ -90,11 +95,12 @@ QList<const QMetaObject*> Bitstream::getMetaObjectSubClasses(const QMetaObject*
|
|||
return getMetaObjectSubClasses().values(metaObject);
|
||||
}
|
||||
|
||||
Bitstream::Bitstream(QDataStream& underlying, QObject* parent) :
|
||||
Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, QObject* parent) :
|
||||
QObject(parent),
|
||||
_underlying(underlying),
|
||||
_byte(0),
|
||||
_position(0),
|
||||
_metadataType(metadataType),
|
||||
_metaObjectStreamer(*this),
|
||||
_typeStreamerStreamer(*this),
|
||||
_attributeStreamer(*this),
|
||||
|
@ -102,6 +108,14 @@ Bitstream::Bitstream(QDataStream& underlying, QObject* parent) :
|
|||
_sharedObjectStreamer(*this) {
|
||||
}
|
||||
|
||||
void Bitstream::addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject) {
|
||||
_metaObjectSubstitutions.insert(className, metaObject);
|
||||
}
|
||||
|
||||
void Bitstream::addTypeSubstitution(const QByteArray& typeName, int type) {
|
||||
_typeStreamerSubstitutions.insert(typeName, getTypeStreamers().value(type));
|
||||
}
|
||||
|
||||
const int LAST_BIT_POSITION = BITS_IN_BYTE - 1;
|
||||
|
||||
Bitstream& Bitstream::write(const void* data, int bits, int offset) {
|
||||
|
@ -171,9 +185,16 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) {
|
|||
// find out when shared objects are deleted in order to clear their mappings
|
||||
for (QHash<SharedObjectPointer, int>::const_iterator it = mappings.sharedObjectOffsets.constBegin();
|
||||
it != mappings.sharedObjectOffsets.constEnd(); it++) {
|
||||
if (it.key()) {
|
||||
connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*)));
|
||||
if (!it.key()) {
|
||||
continue;
|
||||
}
|
||||
connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*)));
|
||||
QPointer<SharedObject>& reference = _sharedObjectReferences[it.key()->getID()];
|
||||
if (reference) {
|
||||
_sharedObjectStreamer.removePersistentID(reference);
|
||||
reference->disconnect(this);
|
||||
}
|
||||
reference = it.key();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,6 +217,19 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) {
|
|||
_attributeStreamer.persistTransientValues(mappings.attributeValues);
|
||||
_scriptStringStreamer.persistTransientValues(mappings.scriptStringValues);
|
||||
_sharedObjectStreamer.persistTransientValues(mappings.sharedObjectValues);
|
||||
|
||||
for (QHash<int, SharedObjectPointer>::const_iterator it = mappings.sharedObjectValues.constBegin();
|
||||
it != mappings.sharedObjectValues.constEnd(); it++) {
|
||||
if (!it.value()) {
|
||||
continue;
|
||||
}
|
||||
QPointer<SharedObject>& reference = _sharedObjectReferences[it.value()->getRemoteID()];
|
||||
if (reference) {
|
||||
_sharedObjectStreamer.removePersistentValue(reference.data());
|
||||
}
|
||||
reference = it.value();
|
||||
_weakSharedObjectHash.remove(it.value()->getRemoteID());
|
||||
}
|
||||
}
|
||||
|
||||
void Bitstream::persistAndResetReadMappings() {
|
||||
|
@ -209,6 +243,58 @@ void Bitstream::clearSharedObject(int id) {
|
|||
}
|
||||
}
|
||||
|
||||
void Bitstream::writeDelta(bool value, bool reference) {
|
||||
*this << value;
|
||||
}
|
||||
|
||||
void Bitstream::readDelta(bool& value, bool reference) {
|
||||
*this >> value;
|
||||
}
|
||||
|
||||
void Bitstream::writeDelta(const QVariant& value, const QVariant& reference) {
|
||||
// QVariant only handles == for built-in types; we need to use our custom operators
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(value.userType());
|
||||
if (value.userType() == reference.userType() && (!streamer || streamer->equal(value, reference))) {
|
||||
*this << false;
|
||||
return;
|
||||
}
|
||||
*this << true;
|
||||
_typeStreamerStreamer << streamer;
|
||||
streamer->writeRawDelta(*this, value, reference);
|
||||
}
|
||||
|
||||
void Bitstream::readRawDelta(QVariant& value, const QVariant& reference) {
|
||||
TypeReader typeReader;
|
||||
_typeStreamerStreamer >> typeReader;
|
||||
typeReader.readRawDelta(*this, value, reference);
|
||||
}
|
||||
|
||||
void Bitstream::writeRawDelta(const QObject* value, const QObject* reference) {
|
||||
if (!value) {
|
||||
_metaObjectStreamer << NULL;
|
||||
return;
|
||||
}
|
||||
const QMetaObject* metaObject = value->metaObject();
|
||||
_metaObjectStreamer << metaObject;
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored(value)) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
|
||||
if (streamer) {
|
||||
streamer->writeDelta(*this, property.read(value), reference && metaObject == reference->metaObject() ?
|
||||
property.read(reference) : QVariant());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Bitstream::readRawDelta(QObject*& value, const QObject* reference) {
|
||||
ObjectReader objectReader;
|
||||
_metaObjectStreamer >> objectReader;
|
||||
value = objectReader.readDelta(*this, reference);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(bool value) {
|
||||
if (value) {
|
||||
_byte |= (1 << _position);
|
||||
|
@ -324,11 +410,9 @@ Bitstream& Bitstream::operator<<(const QVariant& value) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QVariant& value) {
|
||||
const TypeStreamer* streamer;
|
||||
_typeStreamerStreamer >> streamer;
|
||||
if (streamer) {
|
||||
value = streamer->read(*this);
|
||||
}
|
||||
TypeReader reader;
|
||||
_typeStreamerStreamer >> reader;
|
||||
value = reader.read(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -376,13 +460,9 @@ Bitstream& Bitstream::operator<<(const QObject* object) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QObject*& object) {
|
||||
const QMetaObject* metaObject;
|
||||
_metaObjectStreamer >> metaObject;
|
||||
if (!metaObject) {
|
||||
object = NULL;
|
||||
return *this;
|
||||
}
|
||||
readProperties(object = metaObject->newInstance());
|
||||
ObjectReader objectReader;
|
||||
_metaObjectStreamer >> objectReader;
|
||||
object = objectReader.read(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -392,7 +472,14 @@ Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) {
|
||||
_metaObjectStreamer >> metaObject;
|
||||
ObjectReader objectReader;
|
||||
_metaObjectStreamer >> objectReader;
|
||||
metaObject = objectReader.getMetaObject();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(ObjectReader& objectReader) {
|
||||
_metaObjectStreamer >> objectReader;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -402,7 +489,14 @@ Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
|
||||
_typeStreamerStreamer >> streamer;
|
||||
TypeReader typeReader;
|
||||
_typeStreamerStreamer >> typeReader;
|
||||
streamer = typeReader.getStreamer();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(TypeReader& reader) {
|
||||
_typeStreamerStreamer >> reader;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -437,36 +531,280 @@ Bitstream& Bitstream::operator>>(SharedObjectPointer& object) {
|
|||
}
|
||||
|
||||
Bitstream& Bitstream::operator<(const QMetaObject* metaObject) {
|
||||
return *this << (metaObject ? QByteArray::fromRawData(metaObject->className(),
|
||||
strlen(metaObject->className())) : QByteArray());
|
||||
if (!metaObject) {
|
||||
return *this << QByteArray();
|
||||
}
|
||||
*this << QByteArray::fromRawData(metaObject->className(), strlen(metaObject->className()));
|
||||
if (_metadataType == NO_METADATA) {
|
||||
return *this;
|
||||
}
|
||||
int storedPropertyCount = 0;
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored() && getTypeStreamers().contains(property.userType())) {
|
||||
storedPropertyCount++;
|
||||
}
|
||||
}
|
||||
*this << storedPropertyCount;
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored()) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType());
|
||||
if (!typeStreamer) {
|
||||
continue;
|
||||
}
|
||||
_typeStreamerStreamer << typeStreamer;
|
||||
if (_metadataType == FULL_METADATA) {
|
||||
*this << QByteArray::fromRawData(property.name(), strlen(property.name()));
|
||||
} else {
|
||||
hash.addData(property.name(), strlen(property.name()) + 1);
|
||||
}
|
||||
}
|
||||
if (_metadataType == HASH_METADATA) {
|
||||
QByteArray hashResult = hash.result();
|
||||
write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>(const QMetaObject*& metaObject) {
|
||||
Bitstream& Bitstream::operator>(ObjectReader& objectReader) {
|
||||
QByteArray className;
|
||||
*this >> className;
|
||||
if (className.isEmpty()) {
|
||||
metaObject = NULL;
|
||||
objectReader = ObjectReader();
|
||||
return *this;
|
||||
}
|
||||
metaObject = getMetaObjects().value(className);
|
||||
const QMetaObject* metaObject = _metaObjectSubstitutions.value(className);
|
||||
if (!metaObject) {
|
||||
metaObject = getMetaObjects().value(className);
|
||||
}
|
||||
if (!metaObject) {
|
||||
qWarning() << "Unknown class name: " << className << "\n";
|
||||
}
|
||||
if (_metadataType == NO_METADATA) {
|
||||
objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject));
|
||||
return *this;
|
||||
}
|
||||
int storedPropertyCount;
|
||||
*this >> storedPropertyCount;
|
||||
QVector<PropertyReader> properties(storedPropertyCount);
|
||||
for (int i = 0; i < storedPropertyCount; i++) {
|
||||
TypeReader typeReader;
|
||||
*this >> typeReader;
|
||||
QMetaProperty property = QMetaProperty();
|
||||
if (_metadataType == FULL_METADATA) {
|
||||
QByteArray propertyName;
|
||||
*this >> propertyName;
|
||||
if (metaObject) {
|
||||
property = metaObject->property(metaObject->indexOfProperty(propertyName));
|
||||
}
|
||||
}
|
||||
properties[i] = PropertyReader(typeReader, property);
|
||||
}
|
||||
// for hash metadata, check the names/types of the properties as well as the name hash against our own class
|
||||
if (_metadataType == HASH_METADATA) {
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
bool matches = true;
|
||||
if (metaObject) {
|
||||
int propertyIndex = 0;
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored()) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType());
|
||||
if (!typeStreamer) {
|
||||
continue;
|
||||
}
|
||||
if (propertyIndex >= properties.size() ||
|
||||
!properties.at(propertyIndex).getReader().matchesExactly(typeStreamer)) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
hash.addData(property.name(), strlen(property.name()) + 1);
|
||||
propertyIndex++;
|
||||
}
|
||||
if (propertyIndex != properties.size()) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
QByteArray localHashResult = hash.result();
|
||||
QByteArray remoteHashResult(localHashResult.size(), 0);
|
||||
read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE);
|
||||
if (metaObject && matches && localHashResult == remoteHashResult) {
|
||||
objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject));
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
objectReader = ObjectReader(className, metaObject, properties);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<(const TypeStreamer* streamer) {
|
||||
const char* typeName = QMetaType::typeName(streamer->getType());
|
||||
return *this << QByteArray::fromRawData(typeName, strlen(typeName));
|
||||
*this << QByteArray::fromRawData(typeName, strlen(typeName));
|
||||
if (_metadataType == NO_METADATA) {
|
||||
return *this;
|
||||
}
|
||||
TypeReader::Type type = streamer->getReaderType();
|
||||
*this << (int)type;
|
||||
switch (type) {
|
||||
case TypeReader::SIMPLE_TYPE:
|
||||
return *this;
|
||||
|
||||
case TypeReader::LIST_TYPE:
|
||||
case TypeReader::SET_TYPE:
|
||||
return *this << streamer->getValueStreamer();
|
||||
|
||||
case TypeReader::MAP_TYPE:
|
||||
return *this << streamer->getKeyStreamer() << streamer->getValueStreamer();
|
||||
|
||||
default:
|
||||
break; // fall through
|
||||
}
|
||||
// streamable type
|
||||
const QVector<MetaField>& metaFields = streamer->getMetaFields();
|
||||
*this << metaFields.size();
|
||||
if (metaFields.isEmpty()) {
|
||||
return *this;
|
||||
}
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
foreach (const MetaField& metaField, metaFields) {
|
||||
_typeStreamerStreamer << metaField.getStreamer();
|
||||
if (_metadataType == FULL_METADATA) {
|
||||
*this << metaField.getName();
|
||||
} else {
|
||||
hash.addData(metaField.getName().constData(), metaField.getName().size() + 1);
|
||||
}
|
||||
}
|
||||
if (_metadataType == HASH_METADATA) {
|
||||
QByteArray hashResult = hash.result();
|
||||
write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>(const TypeStreamer*& streamer) {
|
||||
Bitstream& Bitstream::operator>(TypeReader& reader) {
|
||||
QByteArray typeName;
|
||||
*this >> typeName;
|
||||
streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
|
||||
const TypeStreamer* streamer = _typeStreamerSubstitutions.value(typeName);
|
||||
if (!streamer) {
|
||||
streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
|
||||
}
|
||||
if (!streamer) {
|
||||
qWarning() << "Unknown type name: " << typeName << "\n";
|
||||
}
|
||||
if (_metadataType == NO_METADATA) {
|
||||
reader = TypeReader(typeName, streamer);
|
||||
return *this;
|
||||
}
|
||||
int type;
|
||||
*this >> type;
|
||||
switch (type) {
|
||||
case TypeReader::SIMPLE_TYPE:
|
||||
reader = TypeReader(typeName, streamer);
|
||||
return *this;
|
||||
|
||||
case TypeReader::LIST_TYPE:
|
||||
case TypeReader::SET_TYPE: {
|
||||
TypeReader valueReader;
|
||||
*this >> valueReader;
|
||||
if (streamer && streamer->getReaderType() == type &&
|
||||
valueReader.matchesExactly(streamer->getValueStreamer())) {
|
||||
reader = TypeReader(typeName, streamer);
|
||||
} else {
|
||||
reader = TypeReader(typeName, streamer, false, (TypeReader::Type)type, TypeReaderPointer(),
|
||||
TypeReaderPointer(new TypeReader(valueReader)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
case TypeReader::MAP_TYPE: {
|
||||
TypeReader keyReader, valueReader;
|
||||
*this >> keyReader >> valueReader;
|
||||
if (streamer && streamer->getReaderType() == TypeReader::MAP_TYPE &&
|
||||
keyReader.matchesExactly(streamer->getKeyStreamer()) &&
|
||||
valueReader.matchesExactly(streamer->getValueStreamer())) {
|
||||
reader = TypeReader(typeName, streamer);
|
||||
} else {
|
||||
reader = TypeReader(typeName, streamer, false, TypeReader::MAP_TYPE,
|
||||
TypeReaderPointer(new TypeReader(keyReader)), TypeReaderPointer(new TypeReader(valueReader)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
// streamable type
|
||||
int fieldCount;
|
||||
*this >> fieldCount;
|
||||
QVector<FieldReader> fields(fieldCount);
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
TypeReader typeReader;
|
||||
*this >> typeReader;
|
||||
int index = -1;
|
||||
if (_metadataType == FULL_METADATA) {
|
||||
QByteArray fieldName;
|
||||
*this >> fieldName;
|
||||
if (streamer) {
|
||||
index = streamer->getFieldIndex(fieldName);
|
||||
}
|
||||
}
|
||||
fields[i] = FieldReader(typeReader, index);
|
||||
}
|
||||
// for hash metadata, check the names/types of the fields as well as the name hash against our own class
|
||||
if (_metadataType == HASH_METADATA) {
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
bool matches = true;
|
||||
if (streamer) {
|
||||
const QVector<MetaField>& localFields = streamer->getMetaFields();
|
||||
if (fieldCount != localFields.size()) {
|
||||
matches = false;
|
||||
|
||||
} else {
|
||||
if (fieldCount == 0) {
|
||||
reader = TypeReader(typeName, streamer);
|
||||
return *this;
|
||||
}
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
const MetaField& localField = localFields.at(i);
|
||||
if (!fields.at(i).getReader().matchesExactly(localField.getStreamer())) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
hash.addData(localField.getName().constData(), localField.getName().size() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
QByteArray localHashResult = hash.result();
|
||||
QByteArray remoteHashResult(localHashResult.size(), 0);
|
||||
read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE);
|
||||
if (streamer && matches && localHashResult == remoteHashResult) {
|
||||
// since everything is the same, we can use the default streamer
|
||||
reader = TypeReader(typeName, streamer);
|
||||
return *this;
|
||||
}
|
||||
} else if (streamer) {
|
||||
// if all fields are the same type and in the right order, we can use the (more efficient) default streamer
|
||||
const QVector<MetaField>& localFields = streamer->getMetaFields();
|
||||
if (fieldCount != localFields.size()) {
|
||||
reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE,
|
||||
TypeReaderPointer(), TypeReaderPointer(), fields);
|
||||
return *this;
|
||||
}
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
const FieldReader& fieldReader = fields.at(i);
|
||||
if (!fieldReader.getReader().matchesExactly(localFields.at(i).getStreamer()) || fieldReader.getIndex() != i) {
|
||||
reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE,
|
||||
TypeReaderPointer(), TypeReaderPointer(), fields);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
reader = TypeReader(typeName, streamer);
|
||||
return *this;
|
||||
}
|
||||
reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE,
|
||||
TypeReaderPointer(), TypeReaderPointer(), fields);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -496,7 +834,14 @@ Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
|
|||
if (!object) {
|
||||
return *this << (int)0;
|
||||
}
|
||||
return *this << object->getID() << (QObject*)object.data();
|
||||
*this << object->getID();
|
||||
QPointer<SharedObject> reference = _sharedObjectReferences.value(object->getID());
|
||||
if (reference) {
|
||||
writeRawDelta((QObject*)object.data(), (QObject*)reference.data());
|
||||
} else {
|
||||
*this << (QObject*)object.data();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
||||
|
@ -506,18 +851,23 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
|||
object = SharedObjectPointer();
|
||||
return *this;
|
||||
}
|
||||
QPointer<SharedObject> reference = _sharedObjectReferences.value(id);
|
||||
QPointer<SharedObject>& pointer = _weakSharedObjectHash[id];
|
||||
if (pointer) {
|
||||
const QMetaObject* metaObject;
|
||||
_metaObjectStreamer >> metaObject;
|
||||
if (metaObject != pointer->metaObject()) {
|
||||
qWarning() << "Class mismatch: " << pointer->metaObject()->className() << metaObject->className();
|
||||
ObjectReader objectReader;
|
||||
_metaObjectStreamer >> objectReader;
|
||||
if (reference) {
|
||||
objectReader.readDelta(*this, reference.data(), pointer.data());
|
||||
} else {
|
||||
objectReader.read(*this, pointer.data());
|
||||
}
|
||||
readProperties(pointer.data());
|
||||
|
||||
} else {
|
||||
QObject* rawObject;
|
||||
*this >> rawObject;
|
||||
QObject* rawObject;
|
||||
if (reference) {
|
||||
readRawDelta(rawObject, (QObject*)reference.data());
|
||||
} else {
|
||||
*this >> rawObject;
|
||||
}
|
||||
pointer = static_cast<SharedObject*>(rawObject);
|
||||
pointer->setRemoteID(id);
|
||||
}
|
||||
|
@ -526,26 +876,14 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
|
|||
}
|
||||
|
||||
void Bitstream::clearSharedObject(QObject* object) {
|
||||
int id = _sharedObjectStreamer.takePersistentID(static_cast<SharedObject*>(object));
|
||||
SharedObject* sharedObject = static_cast<SharedObject*>(object);
|
||||
_sharedObjectReferences.remove(sharedObject->getID());
|
||||
int id = _sharedObjectStreamer.takePersistentID(sharedObject);
|
||||
if (id != 0) {
|
||||
emit sharedObjectCleared(id);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -561,3 +899,324 @@ QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
|
|||
return typeStreamers;
|
||||
}
|
||||
|
||||
QVector<PropertyReader> Bitstream::getPropertyReaders(const QMetaObject* metaObject) {
|
||||
QVector<PropertyReader> propertyReaders;
|
||||
if (!metaObject) {
|
||||
return propertyReaders;
|
||||
}
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored()) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType());
|
||||
if (typeStreamer) {
|
||||
propertyReaders.append(PropertyReader(TypeReader(QByteArray(), typeStreamer), property));
|
||||
}
|
||||
}
|
||||
return propertyReaders;
|
||||
}
|
||||
|
||||
TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, bool exactMatch, Type type,
|
||||
const TypeReaderPointer& keyReader, const TypeReaderPointer& valueReader, const QVector<FieldReader>& fields) :
|
||||
_typeName(typeName),
|
||||
_streamer(streamer),
|
||||
_exactMatch(exactMatch),
|
||||
_type(type),
|
||||
_keyReader(keyReader),
|
||||
_valueReader(valueReader),
|
||||
_fields(fields) {
|
||||
}
|
||||
|
||||
QVariant TypeReader::read(Bitstream& in) const {
|
||||
if (_exactMatch) {
|
||||
return _streamer->read(in);
|
||||
}
|
||||
QVariant object = _streamer ? QVariant(_streamer->getType(), 0) : QVariant();
|
||||
switch (_type) {
|
||||
case STREAMABLE_TYPE: {
|
||||
foreach (const FieldReader& field, _fields) {
|
||||
field.read(in, _streamer, object);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LIST_TYPE:
|
||||
case SET_TYPE: {
|
||||
int size;
|
||||
in >> size;
|
||||
for (int i = 0; i < size; i++) {
|
||||
QVariant value = _valueReader->read(in);
|
||||
if (_streamer) {
|
||||
_streamer->insert(object, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MAP_TYPE: {
|
||||
int size;
|
||||
in >> size;
|
||||
for (int i = 0; i < size; i++) {
|
||||
QVariant key = _keyReader->read(in);
|
||||
QVariant value = _valueReader->read(in);
|
||||
if (_streamer) {
|
||||
_streamer->insert(object, key, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
void TypeReader::readDelta(Bitstream& in, QVariant& object, const QVariant& reference) const {
|
||||
if (_exactMatch) {
|
||||
_streamer->readDelta(in, object, reference);
|
||||
return;
|
||||
}
|
||||
bool changed;
|
||||
in >> changed;
|
||||
if (changed) {
|
||||
readRawDelta(in, object, reference);
|
||||
} else {
|
||||
object = reference;
|
||||
}
|
||||
}
|
||||
|
||||
void TypeReader::readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const {
|
||||
if (_exactMatch) {
|
||||
_streamer->readRawDelta(in, object, reference);
|
||||
return;
|
||||
}
|
||||
switch (_type) {
|
||||
case STREAMABLE_TYPE: {
|
||||
foreach (const FieldReader& field, _fields) {
|
||||
field.readDelta(in, _streamer, object, reference);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LIST_TYPE: {
|
||||
object = reference;
|
||||
int size, referenceSize;
|
||||
in >> size >> referenceSize;
|
||||
if (_streamer) {
|
||||
if (size < referenceSize) {
|
||||
_streamer->prune(object, size);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i < referenceSize) {
|
||||
QVariant value;
|
||||
_valueReader->readDelta(in, value, _streamer->getValue(reference, i));
|
||||
_streamer->setValue(object, i, value);
|
||||
} else {
|
||||
_streamer->insert(object, _valueReader->read(in));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i < referenceSize) {
|
||||
QVariant value;
|
||||
_valueReader->readDelta(in, value, QVariant());
|
||||
} else {
|
||||
_valueReader->read(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SET_TYPE: {
|
||||
object = reference;
|
||||
int addedOrRemoved;
|
||||
in >> addedOrRemoved;
|
||||
for (int i = 0; i < addedOrRemoved; i++) {
|
||||
QVariant value = _valueReader->read(in);
|
||||
if (_streamer && !_streamer->remove(object, value)) {
|
||||
_streamer->insert(object, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MAP_TYPE: {
|
||||
object = reference;
|
||||
int added;
|
||||
in >> added;
|
||||
for (int i = 0; i < added; i++) {
|
||||
QVariant key = _keyReader->read(in);
|
||||
QVariant value = _valueReader->read(in);
|
||||
if (_streamer) {
|
||||
_streamer->insert(object, key, value);
|
||||
}
|
||||
}
|
||||
int modified;
|
||||
in >> modified;
|
||||
for (int i = 0; i < modified; i++) {
|
||||
QVariant key = _keyReader->read(in);
|
||||
QVariant value;
|
||||
if (_streamer) {
|
||||
_valueReader->readDelta(in, value, _streamer->getValue(reference, key));
|
||||
_streamer->insert(object, key, value);
|
||||
} else {
|
||||
_valueReader->readDelta(in, value, QVariant());
|
||||
}
|
||||
}
|
||||
int removed;
|
||||
in >> removed;
|
||||
for (int i = 0; i < removed; i++) {
|
||||
QVariant key = _keyReader->read(in);
|
||||
if (_streamer) {
|
||||
_streamer->remove(object, key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool TypeReader::matchesExactly(const TypeStreamer* streamer) const {
|
||||
return _exactMatch && _streamer == streamer;
|
||||
}
|
||||
|
||||
uint qHash(const TypeReader& typeReader, uint seed) {
|
||||
return qHash(typeReader.getTypeName(), seed);
|
||||
}
|
||||
|
||||
FieldReader::FieldReader(const TypeReader& reader, int index) :
|
||||
_reader(reader),
|
||||
_index(index) {
|
||||
}
|
||||
|
||||
void FieldReader::read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const {
|
||||
QVariant value = _reader.read(in);
|
||||
if (_index != -1 && streamer) {
|
||||
streamer->setField(object, _index, value);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldReader::readDelta(Bitstream& in, const TypeStreamer* streamer, QVariant& object, const QVariant& reference) const {
|
||||
QVariant value;
|
||||
if (_index != -1 && streamer) {
|
||||
_reader.readDelta(in, value, streamer->getField(reference, _index));
|
||||
streamer->setField(object, _index, value);
|
||||
} else {
|
||||
_reader.readDelta(in, value, QVariant());
|
||||
}
|
||||
}
|
||||
|
||||
ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject,
|
||||
const QVector<PropertyReader>& properties) :
|
||||
_className(className),
|
||||
_metaObject(metaObject),
|
||||
_properties(properties) {
|
||||
}
|
||||
|
||||
QObject* ObjectReader::read(Bitstream& in, QObject* object) const {
|
||||
if (!object && _metaObject) {
|
||||
object = _metaObject->newInstance();
|
||||
}
|
||||
foreach (const PropertyReader& property, _properties) {
|
||||
property.read(in, object);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
QObject* ObjectReader::readDelta(Bitstream& in, const QObject* reference, QObject* object) const {
|
||||
if (!object && _metaObject) {
|
||||
object = _metaObject->newInstance();
|
||||
}
|
||||
foreach (const PropertyReader& property, _properties) {
|
||||
property.readDelta(in, object, reference);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
uint qHash(const ObjectReader& objectReader, uint seed) {
|
||||
return qHash(objectReader.getClassName(), seed);
|
||||
}
|
||||
|
||||
PropertyReader::PropertyReader(const TypeReader& reader, const QMetaProperty& property) :
|
||||
_reader(reader),
|
||||
_property(property) {
|
||||
}
|
||||
|
||||
void PropertyReader::read(Bitstream& in, QObject* object) const {
|
||||
QVariant value = _reader.read(in);
|
||||
if (_property.isValid() && object) {
|
||||
_property.write(object, value);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyReader::readDelta(Bitstream& in, QObject* object, const QObject* reference) const {
|
||||
QVariant value;
|
||||
_reader.readDelta(in, value, (_property.isValid() && reference) ? _property.read(reference) : QVariant());
|
||||
if (_property.isValid() && object) {
|
||||
_property.write(object, value);
|
||||
}
|
||||
}
|
||||
|
||||
MetaField::MetaField(const QByteArray& name, const TypeStreamer* streamer) :
|
||||
_name(name),
|
||||
_streamer(streamer) {
|
||||
}
|
||||
|
||||
TypeStreamer::~TypeStreamer() {
|
||||
}
|
||||
|
||||
const QVector<MetaField>& TypeStreamer::getMetaFields() const {
|
||||
static QVector<MetaField> emptyMetaFields;
|
||||
return emptyMetaFields;
|
||||
}
|
||||
|
||||
int TypeStreamer::getFieldIndex(const QByteArray& name) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TypeStreamer::setField(QVariant& object, int index, const QVariant& value) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
QVariant TypeStreamer::getField(const QVariant& object, int index) const {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
TypeReader::Type TypeStreamer::getReaderType() const {
|
||||
return TypeReader::SIMPLE_TYPE;
|
||||
}
|
||||
|
||||
const TypeStreamer* TypeStreamer::getKeyStreamer() const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const TypeStreamer* TypeStreamer::getValueStreamer() const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TypeStreamer::insert(QVariant& object, const QVariant& element) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
void TypeStreamer::insert(QVariant& object, const QVariant& key, const QVariant& value) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
bool TypeStreamer::remove(QVariant& object, const QVariant& key) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant TypeStreamer::getValue(const QVariant& object, const QVariant& key) const {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void TypeStreamer::prune(QVariant& object, int size) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
QVariant TypeStreamer::getValue(const QVariant& object, int index) const {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void TypeStreamer::setValue(QVariant& object, int index, const QVariant& value) const {
|
||||
// nothing by default
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
#define __interface__Bitstream__
|
||||
|
||||
#include <QHash>
|
||||
#include <QMetaProperty>
|
||||
#include <QMetaType>
|
||||
#include <QPointer>
|
||||
#include <QScriptString>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
@ -29,7 +31,11 @@ class QUrl;
|
|||
class Attribute;
|
||||
class AttributeValue;
|
||||
class Bitstream;
|
||||
class FieldReader;
|
||||
class ObjectReader;
|
||||
class OwnedAttributeValue;
|
||||
class PropertyReader;
|
||||
class TypeReader;
|
||||
class TypeStreamer;
|
||||
|
||||
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
|
||||
|
@ -55,26 +61,30 @@ private:
|
|||
|
||||
/// Provides a means to stream repeated values efficiently. The value is first streamed along with a unique ID. When
|
||||
/// subsequently streamed, only the ID is sent.
|
||||
template<class T, class P = T> class RepeatedValueStreamer {
|
||||
template<class K, class P = K, class V = K> class RepeatedValueStreamer {
|
||||
public:
|
||||
|
||||
RepeatedValueStreamer(Bitstream& stream) : _stream(stream), _idStreamer(stream),
|
||||
_lastPersistentID(0), _lastTransientOffset(0) { }
|
||||
|
||||
QHash<T, int> getAndResetTransientOffsets();
|
||||
QHash<K, int> getAndResetTransientOffsets();
|
||||
|
||||
void persistTransientOffsets(const QHash<T, int>& transientOffsets);
|
||||
void persistTransientOffsets(const QHash<K, int>& transientOffsets);
|
||||
|
||||
QHash<int, T> getAndResetTransientValues();
|
||||
QHash<int, V> getAndResetTransientValues();
|
||||
|
||||
void persistTransientValues(const QHash<int, T>& transientValues);
|
||||
void persistTransientValues(const QHash<int, V>& transientValues);
|
||||
|
||||
void removePersistentID(P value) { _persistentIDs.remove(value); }
|
||||
|
||||
int takePersistentID(P value) { return _persistentIDs.take(value); }
|
||||
|
||||
T takePersistentValue(int id) { T value = _persistentValues.take(id); _persistentIDs.remove(value); return value; }
|
||||
void removePersistentValue(V value) { int id = _valueIDs.take(value); _persistentValues.remove(id); }
|
||||
|
||||
RepeatedValueStreamer& operator<<(T value);
|
||||
RepeatedValueStreamer& operator>>(T& value);
|
||||
V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; }
|
||||
|
||||
RepeatedValueStreamer& operator<<(K value);
|
||||
RepeatedValueStreamer& operator>>(V& value);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -83,23 +93,24 @@ private:
|
|||
int _lastPersistentID;
|
||||
int _lastTransientOffset;
|
||||
QHash<P, int> _persistentIDs;
|
||||
QHash<T, int> _transientOffsets;
|
||||
QHash<int, T> _persistentValues;
|
||||
QHash<int, T> _transientValues;
|
||||
QHash<K, int> _transientOffsets;
|
||||
QHash<int, V> _persistentValues;
|
||||
QHash<int, V> _transientValues;
|
||||
QHash<V, int> _valueIDs;
|
||||
};
|
||||
|
||||
template<class T, class P> inline QHash<T, int> RepeatedValueStreamer<T, P>::getAndResetTransientOffsets() {
|
||||
QHash<T, int> transientOffsets;
|
||||
template<class K, class P, class V> inline QHash<K, int> RepeatedValueStreamer<K, P, V>::getAndResetTransientOffsets() {
|
||||
QHash<K, int> transientOffsets;
|
||||
_transientOffsets.swap(transientOffsets);
|
||||
_lastTransientOffset = 0;
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientOffsets;
|
||||
}
|
||||
|
||||
template<class T, class P> inline void RepeatedValueStreamer<T, P>::persistTransientOffsets(
|
||||
const QHash<T, int>& transientOffsets) {
|
||||
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::persistTransientOffsets(
|
||||
const QHash<K, int>& transientOffsets) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<T, int>::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) {
|
||||
for (typename QHash<K, int>::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) {
|
||||
int& id = _persistentIDs[it.key()];
|
||||
if (id == 0) {
|
||||
id = oldLastPersistentID + it.value();
|
||||
|
@ -109,18 +120,18 @@ template<class T, class P> inline void RepeatedValueStreamer<T, P>::persistTrans
|
|||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
}
|
||||
|
||||
template<class T, class P> inline QHash<int, T> RepeatedValueStreamer<T, P>::getAndResetTransientValues() {
|
||||
QHash<int, T> transientValues;
|
||||
template<class K, class P, class V> inline QHash<int, V> RepeatedValueStreamer<K, P, V>::getAndResetTransientValues() {
|
||||
QHash<int, V> transientValues;
|
||||
_transientValues.swap(transientValues);
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientValues;
|
||||
}
|
||||
|
||||
template<class T, class P> inline void RepeatedValueStreamer<T, P>::persistTransientValues(
|
||||
const QHash<int, T>& transientValues) {
|
||||
template<class K, class P, class V> inline void RepeatedValueStreamer<K, P, V>::persistTransientValues(
|
||||
const QHash<int, V>& transientValues) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<int, T>::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) {
|
||||
int& id = _persistentIDs[it.value()];
|
||||
for (typename QHash<int, V>::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) {
|
||||
int& id = _valueIDs[it.value()];
|
||||
if (id == 0) {
|
||||
id = oldLastPersistentID + it.key();
|
||||
_lastPersistentID = qMax(_lastPersistentID, id);
|
||||
|
@ -130,7 +141,8 @@ template<class T, class P> inline void RepeatedValueStreamer<T, P>::persistTrans
|
|||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
}
|
||||
|
||||
template<class T, class P> inline RepeatedValueStreamer<T, P>& RepeatedValueStreamer<T, P>::operator<<(T value) {
|
||||
template<class K, class P, class V> inline RepeatedValueStreamer<K, P, V>&
|
||||
RepeatedValueStreamer<K, P, V>::operator<<(K value) {
|
||||
int id = _persistentIDs.value(value);
|
||||
if (id == 0) {
|
||||
int& offset = _transientOffsets[value];
|
||||
|
@ -147,7 +159,8 @@ template<class T, class P> inline RepeatedValueStreamer<T, P>& RepeatedValueStre
|
|||
return *this;
|
||||
}
|
||||
|
||||
template<class T, class P> inline RepeatedValueStreamer<T, P>& RepeatedValueStreamer<T, P>::operator>>(T& value) {
|
||||
template<class K, class P, class V> inline RepeatedValueStreamer<K, P, V>&
|
||||
RepeatedValueStreamer<K, P, V>::operator>>(V& value) {
|
||||
int id;
|
||||
_idStreamer >> id;
|
||||
if (id <= _lastPersistentID) {
|
||||
|
@ -155,7 +168,7 @@ template<class T, class P> inline RepeatedValueStreamer<T, P>& RepeatedValueStre
|
|||
|
||||
} else {
|
||||
int offset = id - _lastPersistentID;
|
||||
typename QHash<int, T>::iterator it = _transientValues.find(offset);
|
||||
typename QHash<int, V>::iterator it = _transientValues.find(offset);
|
||||
if (it == _transientValues.end()) {
|
||||
_stream > value;
|
||||
_transientValues.insert(offset, value);
|
||||
|
@ -184,8 +197,8 @@ public:
|
|||
|
||||
class ReadMappings {
|
||||
public:
|
||||
QHash<int, const QMetaObject*> metaObjectValues;
|
||||
QHash<int, const TypeStreamer*> typeStreamerValues;
|
||||
QHash<int, ObjectReader> metaObjectValues;
|
||||
QHash<int, TypeReader> typeStreamerValues;
|
||||
QHash<int, AttributePointer> attributeValues;
|
||||
QHash<int, QScriptString> scriptStringValues;
|
||||
QHash<int, SharedObjectPointer> sharedObjectValues;
|
||||
|
@ -199,14 +212,25 @@ 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 streamer registered for the supplied type, if any.
|
||||
static const TypeStreamer* getTypeStreamer(int type);
|
||||
|
||||
/// 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);
|
||||
|
||||
enum MetadataType { NO_METADATA, HASH_METADATA, FULL_METADATA };
|
||||
|
||||
/// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both.
|
||||
Bitstream(QDataStream& underlying, QObject* parent = NULL);
|
||||
Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA, QObject* parent = NULL);
|
||||
|
||||
/// Substitutes the supplied metaobject for the given class name's default mapping.
|
||||
void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject);
|
||||
|
||||
/// Substitutes the supplied type for the given type name's default mapping.
|
||||
void addTypeSubstitution(const QByteArray& typeName, int type);
|
||||
|
||||
/// Writes a set of bits to the underlying stream.
|
||||
/// \param bits the number of bits to write
|
||||
|
@ -248,6 +272,31 @@ public:
|
|||
/// Removes a shared object from the read mappings.
|
||||
void clearSharedObject(int id);
|
||||
|
||||
void writeDelta(bool value, bool reference);
|
||||
void readDelta(bool& value, bool reference);
|
||||
|
||||
void writeDelta(const QVariant& value, const QVariant& reference);
|
||||
|
||||
template<class T> void writeDelta(const T& value, const T& reference);
|
||||
template<class T> void readDelta(T& value, const T& reference);
|
||||
|
||||
void readRawDelta(QVariant& value, const QVariant& reference);
|
||||
|
||||
void writeRawDelta(const QObject* value, const QObject* reference);
|
||||
void readRawDelta(QObject*& value, const QObject* reference);
|
||||
|
||||
template<class T> void writeRawDelta(const T& value, const T& reference);
|
||||
template<class T> void readRawDelta(T& value, const T& reference);
|
||||
|
||||
template<class T> void writeRawDelta(const QList<T>& value, const QList<T>& reference);
|
||||
template<class T> void readRawDelta(QList<T>& value, const QList<T>& reference);
|
||||
|
||||
template<class T> void writeRawDelta(const QSet<T>& value, const QSet<T>& reference);
|
||||
template<class T> void readRawDelta(QSet<T>& value, const QSet<T>& reference);
|
||||
|
||||
template<class K, class V> void writeRawDelta(const QHash<K, V>& value, const QHash<K, V>& reference);
|
||||
template<class K, class V> void readRawDelta(QHash<K, V>& value, const QHash<K, V>& reference);
|
||||
|
||||
Bitstream& operator<<(bool value);
|
||||
Bitstream& operator>>(bool& value);
|
||||
|
||||
|
@ -295,9 +344,11 @@ public:
|
|||
|
||||
Bitstream& operator<<(const QMetaObject* metaObject);
|
||||
Bitstream& operator>>(const QMetaObject*& metaObject);
|
||||
Bitstream& operator>>(ObjectReader& objectReader);
|
||||
|
||||
Bitstream& operator<<(const TypeStreamer* streamer);
|
||||
Bitstream& operator>>(const TypeStreamer*& streamer);
|
||||
Bitstream& operator>>(TypeReader& reader);
|
||||
|
||||
Bitstream& operator<<(const AttributePointer& attribute);
|
||||
Bitstream& operator>>(AttributePointer& attribute);
|
||||
|
@ -309,10 +360,10 @@ public:
|
|||
Bitstream& operator>>(SharedObjectPointer& object);
|
||||
|
||||
Bitstream& operator<(const QMetaObject* metaObject);
|
||||
Bitstream& operator>(const QMetaObject*& metaObject);
|
||||
Bitstream& operator>(ObjectReader& objectReader);
|
||||
|
||||
Bitstream& operator<(const TypeStreamer* streamer);
|
||||
Bitstream& operator>(const TypeStreamer*& streamer);
|
||||
Bitstream& operator>(TypeReader& reader);
|
||||
|
||||
Bitstream& operator<(const AttributePointer& attribute);
|
||||
Bitstream& operator>(AttributePointer& attribute);
|
||||
|
@ -333,25 +384,195 @@ private slots:
|
|||
|
||||
private:
|
||||
|
||||
void readProperties(QObject* object);
|
||||
|
||||
QDataStream& _underlying;
|
||||
quint8 _byte;
|
||||
int _position;
|
||||
|
||||
RepeatedValueStreamer<const QMetaObject*> _metaObjectStreamer;
|
||||
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
|
||||
MetadataType _metadataType;
|
||||
|
||||
RepeatedValueStreamer<const QMetaObject*, const QMetaObject*, ObjectReader> _metaObjectStreamer;
|
||||
RepeatedValueStreamer<const TypeStreamer*, const TypeStreamer*, TypeReader> _typeStreamerStreamer;
|
||||
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
|
||||
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
|
||||
RepeatedValueStreamer<SharedObjectPointer, SharedObject*> _sharedObjectStreamer;
|
||||
|
||||
WeakSharedObjectHash _sharedObjectReferences;
|
||||
|
||||
WeakSharedObjectHash _weakSharedObjectHash;
|
||||
|
||||
QHash<QByteArray, const QMetaObject*> _metaObjectSubstitutions;
|
||||
QHash<QByteArray, const TypeStreamer*> _typeStreamerSubstitutions;
|
||||
|
||||
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
|
||||
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
|
||||
static QHash<int, const TypeStreamer*>& getTypeStreamers();
|
||||
static QVector<PropertyReader> getPropertyReaders(const QMetaObject* metaObject);
|
||||
};
|
||||
|
||||
template<class T> inline void Bitstream::writeDelta(const T& value, const T& reference) {
|
||||
if (value == reference) {
|
||||
*this << false;
|
||||
} else {
|
||||
*this << true;
|
||||
writeRawDelta(value, reference);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::readDelta(T& value, const T& reference) {
|
||||
bool changed;
|
||||
*this >> changed;
|
||||
if (changed) {
|
||||
readRawDelta(value, reference);
|
||||
} else {
|
||||
value = reference;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::writeRawDelta(const T& value, const T& reference) {
|
||||
*this << value;
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::readRawDelta(T& value, const T& reference) {
|
||||
*this >> value;
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::writeRawDelta(const QList<T>& value, const QList<T>& reference) {
|
||||
*this << value.size();
|
||||
*this << reference.size();
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
if (i < reference.size()) {
|
||||
writeDelta(value.at(i), reference.at(i));
|
||||
} else {
|
||||
*this << value.at(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::readRawDelta(QList<T>& value, const QList<T>& reference) {
|
||||
value = reference;
|
||||
int size, referenceSize;
|
||||
*this >> size >> referenceSize;
|
||||
if (size < value.size()) {
|
||||
value.erase(value.begin() + size, value.end());
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i < referenceSize) {
|
||||
readDelta(value[i], reference.at(i));
|
||||
} else {
|
||||
T element;
|
||||
*this >> element;
|
||||
value.append(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::writeRawDelta(const QSet<T>& value, const QSet<T>& reference) {
|
||||
int addedOrRemoved = 0;
|
||||
foreach (const T& element, value) {
|
||||
if (!reference.contains(element)) {
|
||||
addedOrRemoved++;
|
||||
}
|
||||
}
|
||||
foreach (const T& element, reference) {
|
||||
if (!value.contains(element)) {
|
||||
addedOrRemoved++;
|
||||
}
|
||||
}
|
||||
*this << addedOrRemoved;
|
||||
foreach (const T& element, value) {
|
||||
if (!reference.contains(element)) {
|
||||
*this << element;
|
||||
}
|
||||
}
|
||||
foreach (const T& element, reference) {
|
||||
if (!value.contains(element)) {
|
||||
*this << element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void Bitstream::readRawDelta(QSet<T>& value, const QSet<T>& reference) {
|
||||
value = reference;
|
||||
int addedOrRemoved;
|
||||
*this >> addedOrRemoved;
|
||||
for (int i = 0; i < addedOrRemoved; i++) {
|
||||
T element;
|
||||
*this >> element;
|
||||
if (!value.remove(element)) {
|
||||
value.insert(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V> inline void Bitstream::writeRawDelta(const QHash<K, V>& value, const QHash<K, V>& reference) {
|
||||
int added = 0;
|
||||
int modified = 0;
|
||||
for (typename QHash<K, V>::const_iterator it = value.constBegin(); it != value.constEnd(); it++) {
|
||||
typename QHash<K, V>::const_iterator previous = reference.find(it.key());
|
||||
if (previous == reference.constEnd()) {
|
||||
added++;
|
||||
} else if (previous.value() != it.value()) {
|
||||
modified++;
|
||||
}
|
||||
}
|
||||
*this << added;
|
||||
for (typename QHash<K, V>::const_iterator it = value.constBegin(); it != value.constEnd(); it++) {
|
||||
if (!reference.contains(it.key())) {
|
||||
*this << it.key();
|
||||
*this << it.value();
|
||||
}
|
||||
}
|
||||
*this << modified;
|
||||
for (typename QHash<K, V>::const_iterator it = value.constBegin(); it != value.constEnd(); it++) {
|
||||
typename QHash<K, V>::const_iterator previous = reference.find(it.key());
|
||||
if (previous != reference.constEnd() && previous.value() != it.value()) {
|
||||
*this << it.key();
|
||||
writeDelta(it.value(), previous.value());
|
||||
}
|
||||
}
|
||||
int removed = 0;
|
||||
for (typename QHash<K, V>::const_iterator it = reference.constBegin(); it != reference.constEnd(); it++) {
|
||||
if (!value.contains(it.key())) {
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
*this << removed;
|
||||
for (typename QHash<K, V>::const_iterator it = reference.constBegin(); it != reference.constEnd(); it++) {
|
||||
if (!value.contains(it.key())) {
|
||||
*this << it.key();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V> inline void Bitstream::readRawDelta(QHash<K, V>& value, const QHash<K, V>& reference) {
|
||||
value = reference;
|
||||
int added;
|
||||
*this >> added;
|
||||
for (int i = 0; i < added; i++) {
|
||||
K key;
|
||||
V mapping;
|
||||
*this >> key >> mapping;
|
||||
value.insert(key, mapping);
|
||||
}
|
||||
int modified;
|
||||
*this >> modified;
|
||||
for (int i = 0; i < modified; i++) {
|
||||
K key;
|
||||
*this >> key;
|
||||
V& mapping = value[key];
|
||||
V newMapping;
|
||||
readDelta(newMapping, mapping);
|
||||
mapping = newMapping;
|
||||
}
|
||||
int removed;
|
||||
*this >> removed;
|
||||
for (int i = 0; i < removed; i++) {
|
||||
K key;
|
||||
*this >> key;
|
||||
value.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator<<(const QList<T>& list) {
|
||||
*this << list.size();
|
||||
foreach (const T& entry, list) {
|
||||
|
@ -418,6 +639,119 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
|
|||
return *this;
|
||||
}
|
||||
|
||||
typedef QSharedPointer<TypeReader> TypeReaderPointer;
|
||||
|
||||
/// Contains the information required to read a type from the stream.
|
||||
class TypeReader {
|
||||
public:
|
||||
|
||||
enum Type { SIMPLE_TYPE, STREAMABLE_TYPE, LIST_TYPE, SET_TYPE, MAP_TYPE };
|
||||
|
||||
TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL, bool exactMatch = true,
|
||||
Type type = SIMPLE_TYPE, const TypeReaderPointer& keyReader = TypeReaderPointer(),
|
||||
const TypeReaderPointer& valueReader = TypeReaderPointer(),
|
||||
const QVector<FieldReader>& fields = QVector<FieldReader>());
|
||||
|
||||
const QByteArray& getTypeName() const { return _typeName; }
|
||||
const TypeStreamer* getStreamer() const { return _streamer; }
|
||||
|
||||
QVariant read(Bitstream& in) const;
|
||||
void readDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
bool matchesExactly(const TypeStreamer* streamer) const;
|
||||
|
||||
bool operator==(const TypeReader& other) const { return _typeName == other._typeName; }
|
||||
bool operator!=(const TypeReader& other) const { return _typeName != other._typeName; }
|
||||
|
||||
private:
|
||||
|
||||
QByteArray _typeName;
|
||||
const TypeStreamer* _streamer;
|
||||
bool _exactMatch;
|
||||
Type _type;
|
||||
TypeReaderPointer _keyReader;
|
||||
TypeReaderPointer _valueReader;
|
||||
QVector<FieldReader> _fields;
|
||||
};
|
||||
|
||||
uint qHash(const TypeReader& typeReader, uint seed = 0);
|
||||
|
||||
/// Contains the information required to read a metatype field from the stream and apply it.
|
||||
class FieldReader {
|
||||
public:
|
||||
|
||||
FieldReader(const TypeReader& reader = TypeReader(), int index = -1);
|
||||
|
||||
const TypeReader& getReader() const { return _reader; }
|
||||
int getIndex() const { return _index; }
|
||||
|
||||
void read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const;
|
||||
void readDelta(Bitstream& in, const TypeStreamer* streamer, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
private:
|
||||
|
||||
TypeReader _reader;
|
||||
int _index;
|
||||
};
|
||||
|
||||
/// Contains the information required to read an object from the stream.
|
||||
class ObjectReader {
|
||||
public:
|
||||
|
||||
ObjectReader(const QByteArray& className = QByteArray(), const QMetaObject* metaObject = NULL,
|
||||
const QVector<PropertyReader>& properties = QVector<PropertyReader>());
|
||||
|
||||
const QByteArray& getClassName() const { return _className; }
|
||||
const QMetaObject* getMetaObject() const { return _metaObject; }
|
||||
|
||||
QObject* read(Bitstream& in, QObject* object = NULL) const;
|
||||
QObject* readDelta(Bitstream& in, const QObject* reference, QObject* object = NULL) const;
|
||||
|
||||
bool operator==(const ObjectReader& other) const { return _className == other._className; }
|
||||
bool operator!=(const ObjectReader& other) const { return _className != other._className; }
|
||||
|
||||
private:
|
||||
|
||||
QByteArray _className;
|
||||
const QMetaObject* _metaObject;
|
||||
QVector<PropertyReader> _properties;
|
||||
};
|
||||
|
||||
uint qHash(const ObjectReader& objectReader, uint seed = 0);
|
||||
|
||||
/// Contains the information required to read an object property from the stream and apply it.
|
||||
class PropertyReader {
|
||||
public:
|
||||
|
||||
PropertyReader(const TypeReader& reader = TypeReader(), const QMetaProperty& property = QMetaProperty());
|
||||
|
||||
const TypeReader& getReader() const { return _reader; }
|
||||
|
||||
void read(Bitstream& in, QObject* object) const;
|
||||
void readDelta(Bitstream& in, QObject* object, const QObject* reference) const;
|
||||
|
||||
private:
|
||||
|
||||
TypeReader _reader;
|
||||
QMetaProperty _property;
|
||||
};
|
||||
|
||||
/// Describes a metatype field.
|
||||
class MetaField {
|
||||
public:
|
||||
|
||||
MetaField(const QByteArray& name = QByteArray(), const TypeStreamer* streamer = NULL);
|
||||
|
||||
const QByteArray& getName() const { return _name; }
|
||||
const TypeStreamer* getStreamer() const { return _streamer; }
|
||||
|
||||
private:
|
||||
|
||||
QByteArray _name;
|
||||
const TypeStreamer* _streamer;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(const QMetaObject*)
|
||||
|
||||
/// Macro for registering streamable meta-objects.
|
||||
|
@ -427,12 +761,42 @@ Q_DECLARE_METATYPE(const QMetaObject*)
|
|||
class TypeStreamer {
|
||||
public:
|
||||
|
||||
virtual ~TypeStreamer();
|
||||
|
||||
void setType(int type) { _type = type; }
|
||||
int getType() const { return _type; }
|
||||
|
||||
virtual bool equal(const QVariant& first, const QVariant& second) const = 0;
|
||||
|
||||
virtual void write(Bitstream& out, const QVariant& value) const = 0;
|
||||
virtual QVariant read(Bitstream& in) const = 0;
|
||||
|
||||
virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const = 0;
|
||||
virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const = 0;
|
||||
|
||||
virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const = 0;
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const = 0;
|
||||
|
||||
virtual const QVector<MetaField>& getMetaFields() const;
|
||||
virtual int getFieldIndex(const QByteArray& name) const;
|
||||
virtual void setField(QVariant& object, int index, const QVariant& value) const;
|
||||
virtual QVariant getField(const QVariant& object, int index) const;
|
||||
|
||||
virtual TypeReader::Type getReaderType() const;
|
||||
|
||||
virtual const TypeStreamer* getKeyStreamer() const;
|
||||
virtual const TypeStreamer* getValueStreamer() const;
|
||||
|
||||
virtual void insert(QVariant& object, const QVariant& value) const;
|
||||
virtual void insert(QVariant& object, const QVariant& key, const QVariant& value) const;
|
||||
virtual bool remove(QVariant& object, const QVariant& key) const;
|
||||
|
||||
virtual QVariant getValue(const QVariant& object, const QVariant& key) const;
|
||||
|
||||
virtual void prune(QVariant& object, int size) const;
|
||||
virtual QVariant getValue(const QVariant& object, int index) const;
|
||||
virtual void setValue(QVariant& object, int index, const QVariant& value) const;
|
||||
|
||||
private:
|
||||
|
||||
int _type;
|
||||
|
@ -442,25 +806,95 @@ private:
|
|||
template<class T> class SimpleTypeStreamer : public TypeStreamer {
|
||||
public:
|
||||
|
||||
virtual bool equal(const QVariant& first, const QVariant& second) const { return first.value<T>() == second.value<T>(); }
|
||||
virtual void write(Bitstream& out, const QVariant& value) const { out << value.value<T>(); }
|
||||
virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); }
|
||||
virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const {
|
||||
out.writeDelta(value.value<T>(), reference.value<T>()); }
|
||||
virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const {
|
||||
in.readDelta(*static_cast<T*>(value.data()), reference.value<T>()); }
|
||||
virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const {
|
||||
out.writeRawDelta(value.value<T>(), reference.value<T>()); }
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const {
|
||||
in.readRawDelta(*static_cast<T*>(value.data()), reference.value<T>()); }
|
||||
};
|
||||
|
||||
/// A streamer for types compiled by mtc.
|
||||
template<class T> class StreamableTypeStreamer : public SimpleTypeStreamer<T> {
|
||||
public:
|
||||
|
||||
virtual TypeReader::Type getReaderType() const { return TypeReader::STREAMABLE_TYPE; }
|
||||
virtual const QVector<MetaField>& getMetaFields() const { return T::getMetaFields(); }
|
||||
virtual int getFieldIndex(const QByteArray& name) const { return T::getFieldIndex(name); }
|
||||
virtual void setField(QVariant& object, int index, const QVariant& value) const {
|
||||
static_cast<T*>(object.data())->setField(index, value); }
|
||||
virtual QVariant getField(const QVariant& object, int index) const {
|
||||
return static_cast<const T*>(object.constData())->getField(index); }
|
||||
};
|
||||
|
||||
/// Base template for collection streamers.
|
||||
template<class T> class CollectionTypeStreamer : public SimpleTypeStreamer<T> {
|
||||
};
|
||||
|
||||
/// A streamer for list types.
|
||||
template<class T> class CollectionTypeStreamer<QList<T> > : public SimpleTypeStreamer<QList<T> > {
|
||||
public:
|
||||
|
||||
virtual TypeReader::Type getReaderType() const { return TypeReader::LIST_TYPE; }
|
||||
virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId<T>()); }
|
||||
virtual void insert(QVariant& object, const QVariant& value) const {
|
||||
static_cast<QList<T>*>(object.data())->append(value.value<T>()); }
|
||||
virtual void prune(QVariant& object, int size) const {
|
||||
QList<T>* list = static_cast<QList<T>*>(object.data()); list->erase(list->begin() + size, list->end()); }
|
||||
virtual QVariant getValue(const QVariant& object, int index) const {
|
||||
return QVariant::fromValue(static_cast<const QList<T>*>(object.constData())->at(index)); }
|
||||
virtual void setValue(QVariant& object, int index, const QVariant& value) const {
|
||||
static_cast<QList<T>*>(object.data())->replace(index, value.value<T>()); }
|
||||
};
|
||||
|
||||
/// A streamer for set types.
|
||||
template<class T> class CollectionTypeStreamer<QSet<T> > : public SimpleTypeStreamer<QSet<T> > {
|
||||
public:
|
||||
|
||||
virtual TypeReader::Type getReaderType() const { return TypeReader::SET_TYPE; }
|
||||
virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId<T>()); }
|
||||
virtual void insert(QVariant& object, const QVariant& value) const {
|
||||
static_cast<QSet<T>*>(object.data())->insert(value.value<T>()); }
|
||||
virtual bool remove(QVariant& object, const QVariant& key) const {
|
||||
return static_cast<QSet<T>*>(object.data())->remove(key.value<T>()); }
|
||||
};
|
||||
|
||||
/// A streamer for hash types.
|
||||
template<class K, class V> class CollectionTypeStreamer<QHash<K, V> > : public SimpleTypeStreamer<QHash<K, V> > {
|
||||
public:
|
||||
|
||||
virtual TypeReader::Type getReaderType() const { return TypeReader::MAP_TYPE; }
|
||||
virtual const TypeStreamer* getKeyStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId<K>()); }
|
||||
virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId<V>()); }
|
||||
virtual void insert(QVariant& object, const QVariant& key, const QVariant& value) const {
|
||||
static_cast<QHash<K, V>*>(object.data())->insert(key.value<K>(), value.value<V>()); }
|
||||
virtual bool remove(QVariant& object, const QVariant& key) const {
|
||||
return static_cast<QHash<K, V>*>(object.data())->remove(key.value<K>()); }
|
||||
virtual QVariant getValue(const QVariant& object, const QVariant& key) const {
|
||||
return QVariant::fromValue(static_cast<const QHash<K, V>*>(object.constData())->value(key.value<K>())); }
|
||||
};
|
||||
|
||||
/// Macro for registering simple type streamers.
|
||||
#define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \
|
||||
Bitstream::registerTypeStreamer(QMetaType::type(#x), new SimpleTypeStreamer<x>());
|
||||
Bitstream::registerTypeStreamer(qMetaTypeId<x>(), new SimpleTypeStreamer<x>());
|
||||
|
||||
#ifdef WIN32
|
||||
#define _Pragma __pragma
|
||||
#endif
|
||||
/// Macro for registering collection type streamers.
|
||||
#define REGISTER_COLLECTION_TYPE_STREAMER(x) static int x##Streamer = \
|
||||
Bitstream::registerTypeStreamer(qMetaTypeId<x>(), new CollectionTypeStreamer<x>());
|
||||
|
||||
/// Declares the metatype and the streaming operators. The last lines
|
||||
/// ensure that the generated file will be included in the link phase.
|
||||
#define STRINGIFY(x) #x
|
||||
#ifdef _WIN32
|
||||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
static const int* _TypePtr##X = &X::Type;
|
||||
|
@ -468,6 +902,8 @@ public:
|
|||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
__attribute__((unused)) static const int* _TypePtr##X = &X::Type;
|
||||
|
@ -476,6 +912,8 @@ public:
|
|||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
static const int* _TypePtr##X = &X::Type; \
|
||||
|
@ -485,12 +923,19 @@ public:
|
|||
/// Registers a streamable type and its streamer.
|
||||
template<class T> int registerStreamableMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer<T>());
|
||||
Bitstream::registerTypeStreamer(type, new StreamableTypeStreamer<T>());
|
||||
return type;
|
||||
}
|
||||
|
||||
/// Flags a class as streamable (use as you would Q_OBJECT).
|
||||
#define STREAMABLE public: static const int Type; private:
|
||||
#define STREAMABLE public: \
|
||||
static const int Type; \
|
||||
static const QVector<MetaField>& getMetaFields(); \
|
||||
static int getFieldIndex(const QByteArray& name); \
|
||||
void setField(int index, const QVariant& value); \
|
||||
QVariant getField(int index) const; \
|
||||
private: \
|
||||
static QHash<QByteArray, int> createFieldIndices();
|
||||
|
||||
/// Flags a field or base class as streaming.
|
||||
#define STREAM
|
||||
|
|
|
@ -420,7 +420,7 @@ bool CircularBuffer::atEnd() const {
|
|||
}
|
||||
|
||||
qint64 CircularBuffer::bytesAvailable() const {
|
||||
return _size - _offset + QIODevice::bytesAvailable();
|
||||
return _size - _offset;
|
||||
}
|
||||
|
||||
bool CircularBuffer::canReadLine() const {
|
||||
|
|
|
@ -85,7 +85,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
|||
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()), { getMinimum(), _size,
|
||||
QVector<MetavoxelNode*>(outputs.size()), { NULL, getMinimum(), _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));
|
||||
|
@ -177,7 +177,7 @@ template<SpannerUpdateFunction F> int SpannerUpdateVisitor<F>::visit(MetavoxelIn
|
|||
|
||||
void MetavoxelData::insert(const AttributePointer& attribute, const SharedObjectPointer& object) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
insert(attribute, spanner->getBounds(), spanner->getGranularity(), object);
|
||||
insert(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object);
|
||||
}
|
||||
|
||||
void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds,
|
||||
|
@ -192,7 +192,7 @@ void MetavoxelData::insert(const AttributePointer& attribute, const Box& bounds,
|
|||
|
||||
void MetavoxelData::remove(const AttributePointer& attribute, const SharedObjectPointer& object) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
remove(attribute, spanner->getBounds(), spanner->getGranularity(), object);
|
||||
remove(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object);
|
||||
}
|
||||
|
||||
void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds,
|
||||
|
@ -203,7 +203,7 @@ void MetavoxelData::remove(const AttributePointer& attribute, const Box& bounds,
|
|||
|
||||
void MetavoxelData::toggle(const AttributePointer& attribute, const SharedObjectPointer& object) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
toggle(attribute, spanner->getBounds(), spanner->getGranularity(), object);
|
||||
toggle(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), object);
|
||||
}
|
||||
|
||||
void MetavoxelData::toggle(const AttributePointer& attribute, const Box& bounds,
|
||||
|
@ -212,6 +212,67 @@ void MetavoxelData::toggle(const AttributePointer& attribute, const Box& bounds,
|
|||
guide(visitor);
|
||||
}
|
||||
|
||||
void MetavoxelData::replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject) {
|
||||
Spanner* spanner = static_cast<Spanner*>(oldObject.data());
|
||||
replace(attribute, spanner->getBounds(), spanner->getPlacementGranularity(), oldObject, newObject);
|
||||
}
|
||||
|
||||
class SpannerReplaceVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
SpannerReplaceVisitor(const AttributePointer& attribute, const Box& bounds,
|
||||
float granularity, const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
const AttributePointer& _attribute;
|
||||
const Box& _bounds;
|
||||
float _longestSide;
|
||||
const SharedObjectPointer& _oldObject;
|
||||
const SharedObjectPointer& _newObject;
|
||||
};
|
||||
|
||||
SpannerReplaceVisitor::SpannerReplaceVisitor(const AttributePointer& attribute, const Box& bounds, float granularity,
|
||||
const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << attribute, QVector<AttributePointer>() << attribute),
|
||||
_attribute(attribute),
|
||||
_bounds(bounds),
|
||||
_longestSide(qMax(bounds.getLongestSide(), granularity)),
|
||||
_oldObject(oldObject),
|
||||
_newObject(newObject) {
|
||||
}
|
||||
|
||||
int SpannerReplaceVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.getBounds().intersects(_bounds)) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
if (info.size > _longestSide) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
SharedObjectSet set = info.inputValues.at(0).getInlineValue<SharedObjectSet>();
|
||||
if (set.remove(_oldObject)) {
|
||||
set.insert(_newObject);
|
||||
}
|
||||
info.outputValues[0] = AttributeValue(_attribute, encodeInline(set));
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
void MetavoxelData::replace(const AttributePointer& attribute, const Box& bounds, float granularity,
|
||||
const SharedObjectPointer& oldObject, const SharedObjectPointer& newObject) {
|
||||
Spanner* newSpanner = static_cast<Spanner*>(newObject.data());
|
||||
if (bounds != newSpanner->getBounds() || granularity != newSpanner->getPlacementGranularity()) {
|
||||
// if the bounds have changed, we must remove and reinsert
|
||||
remove(attribute, bounds, granularity, oldObject);
|
||||
insert(attribute, newSpanner->getBounds(), newSpanner->getPlacementGranularity(), newObject);
|
||||
return;
|
||||
}
|
||||
SpannerReplaceVisitor visitor(attribute, bounds, granularity, oldObject, newObject);
|
||||
guide(visitor);
|
||||
}
|
||||
|
||||
void MetavoxelData::clear(const AttributePointer& attribute) {
|
||||
MetavoxelNode* node = _roots.take(attribute);
|
||||
if (node) {
|
||||
|
@ -239,7 +300,7 @@ private:
|
|||
FirstRaySpannerIntersectionVisitor::FirstRaySpannerIntersectionVisitor(
|
||||
const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, const MetavoxelLOD& lod) :
|
||||
RaySpannerIntersectionVisitor(origin, direction, QVector<AttributePointer>() << attribute,
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), lod),
|
||||
QVector<AttributePointer>(), QVector<AttributePointer>(), QVector<AttributePointer>(), lod),
|
||||
_spanner(NULL) {
|
||||
}
|
||||
|
||||
|
@ -485,14 +546,26 @@ void MetavoxelStreamState::setMinimum(const glm::vec3& lastMinimum, int index) {
|
|||
minimum = getNextMinimum(lastMinimum, size, index);
|
||||
}
|
||||
|
||||
MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) : _referenceCount(1) {
|
||||
MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren) :
|
||||
_referenceCount(1) {
|
||||
|
||||
_attributeValue = attributeValue.copy();
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i] = NULL;
|
||||
if (copyChildren) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if ((_children[i] = copyChildren->_children[i])) {
|
||||
_children[i]->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelNode::MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy) : _referenceCount(1) {
|
||||
MetavoxelNode::MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy) :
|
||||
_referenceCount(1) {
|
||||
|
||||
_attributeValue = attribute->create(copy->_attributeValue);
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if ((_children[i] = copy->_children[i])) {
|
||||
|
@ -511,14 +584,17 @@ AttributeValue MetavoxelNode::getAttributeValue(const AttributePointer& attribut
|
|||
return AttributeValue(attribute, _attributeValue);
|
||||
}
|
||||
|
||||
void MetavoxelNode::mergeChildren(const AttributePointer& attribute) {
|
||||
void MetavoxelNode::mergeChildren(const AttributePointer& attribute, bool postRead) {
|
||||
if (isLeaf()) {
|
||||
return;
|
||||
}
|
||||
void* childValues[CHILD_COUNT];
|
||||
bool allLeaves = true;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
childValues[i] = _children[i]->_attributeValue;
|
||||
allLeaves &= _children[i]->isLeaf();
|
||||
}
|
||||
if (attribute->merge(_attributeValue, childValues) && allLeaves) {
|
||||
if (attribute->merge(_attributeValue, childValues, postRead) && allLeaves) {
|
||||
clearChildren(attribute);
|
||||
}
|
||||
}
|
||||
|
@ -550,7 +626,7 @@ void MetavoxelNode::read(MetavoxelStreamState& state) {
|
|||
_children[i] = new MetavoxelNode(state.attribute);
|
||||
_children[i]->read(nextState);
|
||||
}
|
||||
mergeChildren(state.attribute);
|
||||
mergeChildren(state.attribute, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -608,7 +684,7 @@ void MetavoxelNode::readDelta(const MetavoxelNode& reference, MetavoxelStreamSta
|
|||
}
|
||||
}
|
||||
}
|
||||
mergeChildren(state.attribute);
|
||||
mergeChildren(state.attribute, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -863,10 +939,11 @@ void MetavoxelVisitor::prepare() {
|
|||
// nothing by default
|
||||
}
|
||||
|
||||
SpannerVisitor::SpannerVisitor(const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(inputs + spannerInputs, outputs, lod),
|
||||
_spannerInputCount(spannerInputs.size()) {
|
||||
SpannerVisitor::SpannerVisitor(const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& spannerMasks,
|
||||
const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
MetavoxelVisitor(inputs + spannerInputs + spannerMasks, outputs, lod),
|
||||
_spannerInputCount(spannerInputs.size()),
|
||||
_spannerMaskCount(spannerMasks.size()) {
|
||||
}
|
||||
|
||||
void SpannerVisitor::prepare() {
|
||||
|
@ -874,17 +951,34 @@ void SpannerVisitor::prepare() {
|
|||
}
|
||||
|
||||
int SpannerVisitor::visit(MetavoxelInfo& info) {
|
||||
for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) {
|
||||
for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) {
|
||||
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (spanner->testAndSetVisited()) {
|
||||
if (!visit(spanner)) {
|
||||
return SHORT_CIRCUIT;
|
||||
}
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited() &&
|
||||
!visit(spanner, glm::vec3(), 0.0f)) {
|
||||
return SHORT_CIRCUIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
return info.isLeaf ? STOP_RECURSION : DEFAULT_ORDER;
|
||||
if (!info.isLeaf) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
for (int i = _inputs.size() - _spannerMaskCount; i < _inputs.size(); i++) {
|
||||
float maskValue = info.inputValues.at(i).getInlineValue<float>();
|
||||
if (maskValue < 0.5f) {
|
||||
const MetavoxelInfo* nextInfo = &info;
|
||||
do {
|
||||
foreach (const SharedObjectPointer& object, nextInfo->inputValues.at(
|
||||
i - _spannerInputCount).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (spanner->isMasked() && !visit(spanner, info.minimum, info.size)) {
|
||||
return SHORT_CIRCUIT;
|
||||
}
|
||||
}
|
||||
} while ((nextInfo = nextInfo->parentInfo));
|
||||
}
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
RayIntersectionVisitor::RayIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction,
|
||||
|
@ -904,10 +998,11 @@ int RayIntersectionVisitor::visit(MetavoxelInfo& info) {
|
|||
}
|
||||
|
||||
RaySpannerIntersectionVisitor::RaySpannerIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& inputs,
|
||||
const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
RayIntersectionVisitor(origin, direction, inputs + spannerInputs, outputs, lod),
|
||||
_spannerInputCount(spannerInputs.size()) {
|
||||
const QVector<AttributePointer>& spannerInputs, const QVector<AttributePointer>& spannerMasks,
|
||||
const QVector<AttributePointer>& inputs, const QVector<AttributePointer>& outputs, const MetavoxelLOD& lod) :
|
||||
RayIntersectionVisitor(origin, direction, inputs + spannerInputs + spannerMasks, outputs, lod),
|
||||
_spannerInputCount(spannerInputs.size()),
|
||||
_spannerMaskCount(spannerMasks.size()) {
|
||||
}
|
||||
|
||||
void RaySpannerIntersectionVisitor::prepare() {
|
||||
|
@ -926,12 +1021,12 @@ bool operator<(const SpannerDistance& first, const SpannerDistance& second) {
|
|||
|
||||
int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) {
|
||||
QVarLengthArray<SpannerDistance, 4> spannerDistances;
|
||||
for (int i = _inputs.size() - _spannerInputCount; i < _inputs.size(); i++) {
|
||||
for (int end = _inputs.size() - _spannerMaskCount, i = end - _spannerInputCount, j = end; i < end; i++, j++) {
|
||||
foreach (const SharedObjectPointer& object, info.inputValues.at(i).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (spanner->testAndSetVisited()) {
|
||||
if (!(spanner->isMasked() && j < _inputs.size()) && spanner->testAndSetVisited()) {
|
||||
SpannerDistance spannerDistance = { spanner };
|
||||
if (spanner->findRayIntersection(_origin, _direction, spannerDistance.distance)) {
|
||||
if (spanner->findRayIntersection(_origin, _direction, glm::vec3(), 0.0f, spannerDistance.distance)) {
|
||||
spannerDistances.append(spannerDistance);
|
||||
}
|
||||
}
|
||||
|
@ -943,7 +1038,36 @@ int RaySpannerIntersectionVisitor::visit(MetavoxelInfo& info, float distance) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return info.isLeaf ? STOP_RECURSION : _order;
|
||||
if (!info.isLeaf) {
|
||||
return _order;
|
||||
}
|
||||
for (int i = _inputs.size() - _spannerMaskCount; i < _inputs.size(); i++) {
|
||||
float maskValue = info.inputValues.at(i).getInlineValue<float>();
|
||||
if (maskValue < 0.5f) {
|
||||
const MetavoxelInfo* nextInfo = &info;
|
||||
do {
|
||||
foreach (const SharedObjectPointer& object, nextInfo->inputValues.at(
|
||||
i - _spannerInputCount).getInlineValue<SharedObjectSet>()) {
|
||||
Spanner* spanner = static_cast<Spanner*>(object.data());
|
||||
if (spanner->isMasked()) {
|
||||
SpannerDistance spannerDistance = { spanner };
|
||||
if (spanner->findRayIntersection(_origin, _direction,
|
||||
info.minimum, info.size, spannerDistance.distance)) {
|
||||
spannerDistances.append(spannerDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((nextInfo = nextInfo->parentInfo));
|
||||
|
||||
qStableSort(spannerDistances);
|
||||
foreach (const SpannerDistance& spannerDistance, spannerDistances) {
|
||||
if (!visitSpanner(spannerDistance.spanner, spannerDistance.distance)) {
|
||||
return SHORT_CIRCUIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
|
||||
|
@ -953,8 +1077,8 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
|
||||
float lodBase = glm::distance(visitation.visitor.getLOD().position, visitation.info.getCenter()) *
|
||||
visitation.visitor.getLOD().threshold;
|
||||
visitation.info.isLeaf = (visitation.info.size < lodBase * visitation.visitor.getMinimumLODThresholdMultiplier()) ||
|
||||
visitation.allInputNodesLeaves();
|
||||
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor.getMinimumLODThresholdMultiplier());
|
||||
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
|
||||
int encodedOrder = visitation.visitor.visit(visitation.info);
|
||||
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
|
||||
return false;
|
||||
|
@ -969,7 +1093,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
// "set" to same value; disregard
|
||||
value = AttributeValue();
|
||||
} else {
|
||||
node = new MetavoxelNode(value);
|
||||
node = value.getAttribute()->createMetavoxelNode(value, node);
|
||||
}
|
||||
}
|
||||
if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) {
|
||||
|
@ -977,7 +1101,7 @@ bool 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()),
|
||||
{ &visitation.info, glm::vec3(), visitation.info.size * 0.5f, QVector<AttributeValue>(visitation.inputNodes.size()),
|
||||
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
// the encoded order tells us the child indices for each iteration
|
||||
|
@ -991,7 +1115,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
|
||||
parentValue.getAttribute()->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
|
||||
nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ?
|
||||
child->getAttributeValue(parentValue.getAttribute()) : parentValue;
|
||||
child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue);
|
||||
}
|
||||
for (int j = 0; j < visitation.outputNodes.size(); j++) {
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
|
@ -1019,7 +1143,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
node = new MetavoxelNode(value.getAttribute(), node);
|
||||
} else {
|
||||
// create leaf with inherited value
|
||||
node = new MetavoxelNode(visitation.getInheritedOutputValue(j));
|
||||
node = new MetavoxelNode(value.getAttribute()->inherit(visitation.getInheritedOutputValue(j)));
|
||||
}
|
||||
}
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
|
@ -1028,7 +1152,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
child->decrementReferenceCount(value.getAttribute());
|
||||
} else {
|
||||
// it's a leaf; we need to split it up
|
||||
AttributeValue nodeValue = node->getAttributeValue(value.getAttribute());
|
||||
AttributeValue nodeValue = value.getAttribute()->inherit(node->getAttributeValue(value.getAttribute()));
|
||||
for (int k = 1; k < MetavoxelNode::CHILD_COUNT; k++) {
|
||||
node->setChild((index + k) % MetavoxelNode::CHILD_COUNT, new MetavoxelNode(nodeValue));
|
||||
}
|
||||
|
@ -1095,7 +1219,7 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin
|
|||
QScriptValue infoValue = context->argument(0);
|
||||
QScriptValue minimum = infoValue.property(guide->_minimumHandle);
|
||||
MetavoxelInfo info = {
|
||||
glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()),
|
||||
NULL, glm::vec3(minimum.property(0).toNumber(), minimum.property(1).toNumber(), minimum.property(2).toNumber()),
|
||||
(float)infoValue.property(guide->_sizeHandle).toNumber(), guide->_visitation->info.inputValues,
|
||||
guide->_visitation->info.outputValues, infoValue.property(guide->_isLeafHandle).toBool() };
|
||||
|
||||
|
@ -1202,11 +1326,14 @@ AttributeValue MetavoxelVisitation::getInheritedOutputValue(int index) const {
|
|||
return AttributeValue(visitor.getOutputs().at(index));
|
||||
}
|
||||
|
||||
const float DEFAULT_GRANULARITY = 0.01f;
|
||||
const float DEFAULT_PLACEMENT_GRANULARITY = 0.01f;
|
||||
const float DEFAULT_VOXELIZATION_GRANULARITY = powf(2.0f, -3.0f);
|
||||
|
||||
Spanner::Spanner() :
|
||||
_renderer(NULL),
|
||||
_granularity(DEFAULT_GRANULARITY),
|
||||
_placementGranularity(DEFAULT_PLACEMENT_GRANULARITY),
|
||||
_voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY),
|
||||
_masked(false),
|
||||
_lastVisit(0) {
|
||||
}
|
||||
|
||||
|
@ -1223,7 +1350,16 @@ const QVector<AttributePointer>& Spanner::getAttributes() const {
|
|||
return emptyVector;
|
||||
}
|
||||
|
||||
bool Spanner::getAttributeValues(MetavoxelInfo& info) const {
|
||||
const QVector<AttributePointer>& Spanner::getVoxelizedAttributes() const {
|
||||
static QVector<AttributePointer> emptyVector;
|
||||
return emptyVector;
|
||||
}
|
||||
|
||||
bool Spanner::getAttributeValues(MetavoxelInfo& info, bool force) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Spanner::blendAttributeValues(MetavoxelInfo& info, bool force) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1250,7 +1386,8 @@ SpannerRenderer* Spanner::getRenderer() {
|
|||
return _renderer;
|
||||
}
|
||||
|
||||
bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
||||
return _bounds.findRayIntersection(origin, direction, distance);
|
||||
}
|
||||
|
||||
|
@ -1271,11 +1408,12 @@ void SpannerRenderer::simulate(float deltaTime) {
|
|||
// nothing by default
|
||||
}
|
||||
|
||||
void SpannerRenderer::render(float alpha) {
|
||||
void SpannerRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) {
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
bool SpannerRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
bool SpannerRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1320,10 +1458,17 @@ const QVector<AttributePointer>& Sphere::getAttributes() const {
|
|||
return attributes;
|
||||
}
|
||||
|
||||
bool Sphere::getAttributeValues(MetavoxelInfo& info) const {
|
||||
const QVector<AttributePointer>& Sphere::getVoxelizedAttributes() const {
|
||||
static QVector<AttributePointer> attributes = QVector<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getSpannerColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerNormalAttribute();
|
||||
return attributes;
|
||||
}
|
||||
|
||||
bool Sphere::getAttributeValues(MetavoxelInfo& info, bool force) const {
|
||||
// bounds check
|
||||
Box bounds = info.getBounds();
|
||||
if (!getBounds().intersects(bounds)) {
|
||||
if (!(force || getBounds().intersects(bounds))) {
|
||||
return false;
|
||||
}
|
||||
// count the points inside the sphere
|
||||
|
@ -1336,22 +1481,63 @@ bool Sphere::getAttributeValues(MetavoxelInfo& info) const {
|
|||
if (pointsWithin == Box::VERTEX_COUNT) {
|
||||
// entirely contained
|
||||
info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline<QRgb>(_color.rgba()));
|
||||
getNormal(info);
|
||||
info.outputValues[1] = getNormal(info, _color.alpha());
|
||||
return false;
|
||||
}
|
||||
if (info.size <= getGranularity()) {
|
||||
if (force || info.size <= getVoxelizationGranularity()) {
|
||||
// best guess
|
||||
if (pointsWithin > 0) {
|
||||
int alpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT;
|
||||
info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline<QRgb>(qRgba(
|
||||
_color.red(), _color.green(), _color.blue(), _color.alpha() * pointsWithin / Box::VERTEX_COUNT)));
|
||||
getNormal(info);
|
||||
_color.red(), _color.green(), _color.blue(), alpha)));
|
||||
info.outputValues[1] = getNormal(info, alpha);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
bool Sphere::blendAttributeValues(MetavoxelInfo& info, bool force) const {
|
||||
// bounds check
|
||||
Box bounds = info.getBounds();
|
||||
if (!(force || getBounds().intersects(bounds))) {
|
||||
return false;
|
||||
}
|
||||
// count the points inside the sphere
|
||||
int pointsWithin = 0;
|
||||
for (int i = 0; i < Box::VERTEX_COUNT; i++) {
|
||||
if (glm::distance(bounds.getVertex(i), getTranslation()) <= getScale()) {
|
||||
pointsWithin++;
|
||||
}
|
||||
}
|
||||
if (pointsWithin == Box::VERTEX_COUNT) {
|
||||
// entirely contained
|
||||
info.outputValues[0] = AttributeValue(getAttributes().at(0), encodeInline<QRgb>(_color.rgba()));
|
||||
info.outputValues[1] = getNormal(info, _color.alpha());
|
||||
return false;
|
||||
}
|
||||
if (force || info.size <= getVoxelizationGranularity()) {
|
||||
// best guess
|
||||
if (pointsWithin > 0) {
|
||||
const AttributeValue& oldColor = info.outputValues.at(0).getAttribute() ?
|
||||
info.outputValues.at(0) : info.inputValues.at(0);
|
||||
const AttributeValue& oldNormal = info.outputValues.at(1).getAttribute() ?
|
||||
info.outputValues.at(1) : info.inputValues.at(1);
|
||||
int oldAlpha = qAlpha(oldColor.getInlineValue<QRgb>());
|
||||
int newAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT;
|
||||
float combinedAlpha = (float)newAlpha / (oldAlpha + newAlpha);
|
||||
int baseAlpha = _color.alpha() * pointsWithin / Box::VERTEX_COUNT;
|
||||
info.outputValues[0].mix(oldColor, AttributeValue(getAttributes().at(0),
|
||||
encodeInline<QRgb>(qRgba(_color.red(), _color.green(), _color.blue(), baseAlpha))), combinedAlpha);
|
||||
info.outputValues[1].mix(oldNormal, getNormal(info, baseAlpha), combinedAlpha);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
||||
return findRaySphereIntersection(origin, direction, getTranslation(), getScale(), distance);
|
||||
}
|
||||
|
||||
|
@ -1364,22 +1550,21 @@ void Sphere::updateBounds() {
|
|||
setBounds(Box(getTranslation() - extent, getTranslation() + extent));
|
||||
}
|
||||
|
||||
void Sphere::getNormal(MetavoxelInfo& info) const {
|
||||
AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const {
|
||||
glm::vec3 normal = info.getCenter() - getTranslation();
|
||||
float length = glm::length(normal);
|
||||
QRgb color;
|
||||
if (length > EPSILON) {
|
||||
if (alpha != 0 && length > EPSILON) {
|
||||
const float NORMAL_SCALE = 127.0f;
|
||||
float scale = NORMAL_SCALE / length;
|
||||
const int BYTE_MASK = 0xFF;
|
||||
color = qRgb((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK,
|
||||
(int)(normal.z * scale) & BYTE_MASK);
|
||||
color = qRgba((int)(normal.x * scale) & BYTE_MASK, (int)(normal.y * scale) & BYTE_MASK,
|
||||
(int)(normal.z * scale) & BYTE_MASK, alpha);
|
||||
|
||||
} else {
|
||||
const QRgb DEFAULT_NORMAL = 0x007F00;
|
||||
color = DEFAULT_NORMAL;
|
||||
color = QRgb();
|
||||
}
|
||||
info.outputValues[1] = AttributeValue(getAttributes().at(1), encodeInline<QRgb>(color));
|
||||
return AttributeValue(getAttributes().at(1), encodeInline<QRgb>(color));
|
||||
}
|
||||
|
||||
StaticModel::StaticModel() {
|
||||
|
@ -1391,10 +1576,11 @@ void StaticModel::setURL(const QUrl& url) {
|
|||
}
|
||||
}
|
||||
|
||||
bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
bool StaticModel::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const {
|
||||
// delegate to renderer, if we have one
|
||||
return _renderer ? _renderer->findRayIntersection(origin, direction, distance) :
|
||||
Spanner::findRayIntersection(origin, direction, distance);
|
||||
return _renderer ? _renderer->findRayIntersection(origin, direction, clipMinimum, clipSize, distance) :
|
||||
Spanner::findRayIntersection(origin, direction, clipMinimum, clipSize, distance);
|
||||
}
|
||||
|
||||
QByteArray StaticModel::getRendererClassName() const {
|
||||
|
|
|
@ -79,6 +79,11 @@ public:
|
|||
void toggle(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
|
||||
void clear(const AttributePointer& attribute);
|
||||
|
||||
/// Convenience function that finds the first spanner intersecting the provided ray.
|
||||
|
@ -132,7 +137,7 @@ public:
|
|||
|
||||
static const int CHILD_COUNT = 8;
|
||||
|
||||
MetavoxelNode(const AttributeValue& attributeValue);
|
||||
MetavoxelNode(const AttributeValue& attributeValue, const MetavoxelNode* copyChildren = NULL);
|
||||
MetavoxelNode(const AttributePointer& attribute, const MetavoxelNode* copy);
|
||||
|
||||
void setAttributeValue(const AttributeValue& attributeValue);
|
||||
|
@ -140,7 +145,7 @@ public:
|
|||
AttributeValue getAttributeValue(const AttributePointer& attribute) const;
|
||||
void* getAttributeValue() const { return _attributeValue; }
|
||||
|
||||
void mergeChildren(const AttributePointer& attribute);
|
||||
void mergeChildren(const AttributePointer& attribute, bool postRead = false);
|
||||
|
||||
MetavoxelNode* getChild(int index) const { return _children[index]; }
|
||||
void setChild(int index, MetavoxelNode* child) { _children[index] = child; }
|
||||
|
@ -185,10 +190,12 @@ private:
|
|||
class MetavoxelInfo {
|
||||
public:
|
||||
|
||||
MetavoxelInfo* parentInfo;
|
||||
glm::vec3 minimum; ///< the minimum extent of the area covered by the voxel
|
||||
float size; ///< the size of the voxel in all dimensions
|
||||
QVector<AttributeValue> inputValues;
|
||||
QVector<OwnedAttributeValue> outputValues;
|
||||
bool isLODLeaf;
|
||||
bool isLeaf;
|
||||
|
||||
Box getBounds() const { return Box(minimum, minimum + glm::vec3(size, size, size)); }
|
||||
|
@ -253,13 +260,15 @@ class SpannerVisitor : public MetavoxelVisitor {
|
|||
public:
|
||||
|
||||
SpannerVisitor(const QVector<AttributePointer>& spannerInputs,
|
||||
const QVector<AttributePointer>& spannerMasks = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
||||
/// Visits a spanner.
|
||||
/// Visits a spanner (or part thereof).
|
||||
/// \param clipSize the size of the clip volume, or zero if unclipped
|
||||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visit(Spanner* spanner) = 0;
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) = 0;
|
||||
|
||||
virtual void prepare();
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
@ -267,6 +276,7 @@ public:
|
|||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _spannerMaskCount;
|
||||
};
|
||||
|
||||
/// Base class for ray intersection visitors.
|
||||
|
@ -296,11 +306,12 @@ public:
|
|||
|
||||
RaySpannerIntersectionVisitor(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<AttributePointer>& spannerInputs,
|
||||
const QVector<AttributePointer>& spannerMasks = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& inputs = QVector<AttributePointer>(),
|
||||
const QVector<AttributePointer>& outputs = QVector<AttributePointer>(),
|
||||
const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
||||
/// Visits a spanner that the ray intersects.
|
||||
/// Visits a spannerthat the ray intersects.
|
||||
/// \return true to continue, false to short-circuit the tour
|
||||
virtual bool visitSpanner(Spanner* spanner, float distance) = 0;
|
||||
|
||||
|
@ -310,6 +321,7 @@ public:
|
|||
protected:
|
||||
|
||||
int _spannerInputCount;
|
||||
int _spannerMaskCount;
|
||||
};
|
||||
|
||||
/// Interface for objects that guide metavoxel visitors.
|
||||
|
@ -408,8 +420,10 @@ public:
|
|||
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)
|
||||
|
||||
Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false)
|
||||
Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false)
|
||||
Q_PROPERTY(float masked MEMBER _masked DESIGNABLE false)
|
||||
|
||||
public:
|
||||
|
||||
/// Increments the value of the global visit counter.
|
||||
|
@ -420,15 +434,29 @@ public:
|
|||
void setBounds(const Box& bounds);
|
||||
const Box& getBounds() const { return _bounds; }
|
||||
|
||||
void setGranularity(float granularity) { _granularity = granularity; }
|
||||
float getGranularity() const { return _granularity; }
|
||||
void setPlacementGranularity(float granularity) { _placementGranularity = granularity; }
|
||||
float getPlacementGranularity() const { return _placementGranularity; }
|
||||
|
||||
void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; }
|
||||
float getVoxelizationGranularity() const { return _voxelizationGranularity; }
|
||||
|
||||
void setMasked(bool masked) { _masked = masked; }
|
||||
bool isMasked() const { return _masked; }
|
||||
|
||||
/// Returns a reference to the list of attributes associated with this spanner.
|
||||
virtual const QVector<AttributePointer>& getAttributes() const;
|
||||
|
||||
/// Returns a reference to the list of corresponding attributes that we voxelize the spanner into.
|
||||
virtual const QVector<AttributePointer>& getVoxelizedAttributes() const;
|
||||
|
||||
/// Sets the attribute values associated with this spanner in the supplied info.
|
||||
/// \return true to recurse, false to stop
|
||||
virtual bool getAttributeValues(MetavoxelInfo& info) const;
|
||||
virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const;
|
||||
|
||||
/// Blends the attribute values associated with this spanner into the supplied info.
|
||||
/// \param force if true, blend even if we would normally subdivide
|
||||
/// \return true to recurse, false to stop
|
||||
virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const;
|
||||
|
||||
/// 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.
|
||||
|
@ -438,7 +466,9 @@ public:
|
|||
SpannerRenderer* getRenderer();
|
||||
|
||||
/// Finds the intersection between the described ray and this spanner.
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
/// \param clipSize the size of the clip region, or zero if unclipped
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -455,7 +485,9 @@ protected:
|
|||
private:
|
||||
|
||||
Box _bounds;
|
||||
float _granularity;
|
||||
float _placementGranularity;
|
||||
float _voxelizationGranularity;
|
||||
bool _masked;
|
||||
int _lastVisit; ///< the identifier of the last visit
|
||||
|
||||
static int _visit; ///< the global visit counter
|
||||
|
@ -471,8 +503,9 @@ public:
|
|||
|
||||
virtual void init(Spanner* spanner);
|
||||
virtual void simulate(float deltaTime);
|
||||
virtual void render(float alpha);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize);
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
|
||||
};
|
||||
|
||||
/// An object with a 3D transform.
|
||||
|
@ -521,8 +554,11 @@ public:
|
|||
const QColor& getColor() const { return _color; }
|
||||
|
||||
virtual const QVector<AttributePointer>& getAttributes() const;
|
||||
virtual bool getAttributeValues(MetavoxelInfo& info) const;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
virtual const QVector<AttributePointer>& getVoxelizedAttributes() const;
|
||||
virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const;
|
||||
virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -538,7 +574,7 @@ private slots:
|
|||
|
||||
private:
|
||||
|
||||
void getNormal(MetavoxelInfo& info) const;
|
||||
AttributeValue getNormal(MetavoxelInfo& info, int alpha) const;
|
||||
|
||||
QColor _color;
|
||||
};
|
||||
|
@ -555,7 +591,8 @@ public:
|
|||
void setURL(const QUrl& url);
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& clipMinimum, float clipSize,float& distance) const;
|
||||
|
||||
signals:
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ private:
|
|||
};
|
||||
|
||||
BoxSetEditVisitor::BoxSetEditVisitor(const BoxSetEdit& edit) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute()),
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), QVector<AttributePointer>() << edit.value.getAttribute() <<
|
||||
AttributeRegistry::getInstance()->getSpannerMaskAttribute()),
|
||||
_edit(edit) {
|
||||
}
|
||||
|
||||
|
@ -47,25 +48,68 @@ int BoxSetEditVisitor::visit(MetavoxelInfo& info) {
|
|||
float volume = (size.x * size.y * size.z) / (info.size * info.size * info.size);
|
||||
if (volume >= 1.0f) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline<float>(1.0f));
|
||||
return STOP_RECURSION; // entirely contained
|
||||
}
|
||||
if (info.size <= _edit.granularity) {
|
||||
if (volume >= 0.5f) {
|
||||
info.outputValues[0] = _edit.value;
|
||||
info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline<float>(1.0f));
|
||||
}
|
||||
return STOP_RECURSION; // reached granularity limit; take best guess
|
||||
}
|
||||
return DEFAULT_ORDER; // subdivide
|
||||
}
|
||||
|
||||
class GatherUnmaskedSpannersVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
GatherUnmaskedSpannersVisitor(const Box& bounds);
|
||||
|
||||
const QList<SharedObjectPointer>& getUnmaskedSpanners() const { return _unmaskedSpanners; }
|
||||
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
private:
|
||||
|
||||
Box _bounds;
|
||||
QList<SharedObjectPointer> _unmaskedSpanners;
|
||||
};
|
||||
|
||||
GatherUnmaskedSpannersVisitor::GatherUnmaskedSpannersVisitor(const Box& bounds) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute()),
|
||||
_bounds(bounds) {
|
||||
}
|
||||
|
||||
bool GatherUnmaskedSpannersVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
if (!spanner->isMasked() && spanner->getBounds().intersects(_bounds)) {
|
||||
_unmaskedSpanners.append(spanner);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setIntersectingMasked(const Box& bounds, MetavoxelData& data) {
|
||||
GatherUnmaskedSpannersVisitor visitor(bounds);
|
||||
data.guide(visitor);
|
||||
|
||||
foreach (const SharedObjectPointer& object, visitor.getUnmaskedSpanners()) {
|
||||
Spanner* newSpanner = static_cast<Spanner*>(object->clone(true));
|
||||
newSpanner->setMasked(true);
|
||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), object, newSpanner);
|
||||
}
|
||||
}
|
||||
|
||||
void BoxSetEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// expand to fit the entire edit
|
||||
while (!data.getBounds().contains(region)) {
|
||||
data.expand();
|
||||
}
|
||||
|
||||
BoxSetEditVisitor visitor(*this);
|
||||
data.guide(visitor);
|
||||
BoxSetEditVisitor setVisitor(*this);
|
||||
data.guide(setVisitor);
|
||||
|
||||
// flip the mask flag of all intersecting spanners
|
||||
setIntersectingMasked(region, data);
|
||||
}
|
||||
|
||||
GlobalSetEdit::GlobalSetEdit(const OwnedAttributeValue& value) :
|
||||
|
@ -104,8 +148,56 @@ InsertSpannerEdit::InsertSpannerEdit(const AttributePointer& attribute, const Sh
|
|||
spanner(spanner) {
|
||||
}
|
||||
|
||||
class UpdateSpannerVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
UpdateSpannerVisitor(const QVector<AttributePointer>& attributes, Spanner* spanner);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
Spanner* _spanner;
|
||||
float _voxelizationSize;
|
||||
int _steps;
|
||||
};
|
||||
|
||||
UpdateSpannerVisitor::UpdateSpannerVisitor(const QVector<AttributePointer>& attributes, Spanner* spanner) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() << attributes << AttributeRegistry::getInstance()->getSpannersAttribute(),
|
||||
attributes),
|
||||
_spanner(spanner),
|
||||
_voxelizationSize(qMax(spanner->getBounds().getLongestSide(), spanner->getPlacementGranularity()) * 2.0f /
|
||||
AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()),
|
||||
_steps(glm::round(logf(AttributeRegistry::getInstance()->getSpannersAttribute()->getLODThresholdMultiplier()) /
|
||||
logf(2.0f) - 2.0f)) {
|
||||
}
|
||||
|
||||
int UpdateSpannerVisitor::visit(MetavoxelInfo& info) {
|
||||
if (!info.getBounds().intersects(_spanner->getBounds())) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
MetavoxelInfo* parentInfo = info.parentInfo;
|
||||
for (int i = 0; i < _steps && parentInfo; i++) {
|
||||
parentInfo = parentInfo->parentInfo;
|
||||
}
|
||||
for (int i = 0; i < _outputs.size(); i++) {
|
||||
info.outputValues[i] = AttributeValue(_outputs.at(i));
|
||||
}
|
||||
if (parentInfo) {
|
||||
foreach (const SharedObjectPointer& object,
|
||||
parentInfo->inputValues.at(_outputs.size()).getInlineValue<SharedObjectSet>()) {
|
||||
static_cast<const Spanner*>(object.data())->blendAttributeValues(info, true);
|
||||
}
|
||||
}
|
||||
return (info.size > _voxelizationSize) ? DEFAULT_ORDER : STOP_RECURSION;
|
||||
}
|
||||
|
||||
void InsertSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
data.insert(attribute, spanner);
|
||||
data.insert(attribute, this->spanner);
|
||||
|
||||
Spanner* spanner = static_cast<Spanner*>(this->spanner.data());
|
||||
UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
||||
RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) :
|
||||
|
@ -119,21 +211,63 @@ void RemoveSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& o
|
|||
qDebug() << "Missing object to remove" << id;
|
||||
return;
|
||||
}
|
||||
// keep a strong reference to the object
|
||||
SharedObjectPointer sharedPointer = object;
|
||||
data.remove(attribute, object);
|
||||
|
||||
Spanner* spanner = static_cast<Spanner*>(object);
|
||||
UpdateSpannerVisitor visitor(spanner->getVoxelizedAttributes(), spanner);
|
||||
data.guide(visitor);
|
||||
}
|
||||
|
||||
ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) :
|
||||
attribute(attribute) {
|
||||
}
|
||||
|
||||
class GatherSpannerAttributesVisitor : public SpannerVisitor {
|
||||
public:
|
||||
|
||||
GatherSpannerAttributesVisitor(const AttributePointer& attribute);
|
||||
|
||||
const QSet<AttributePointer>& getAttributes() const { return _attributes; }
|
||||
|
||||
virtual bool visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize);
|
||||
|
||||
protected:
|
||||
|
||||
QSet<AttributePointer> _attributes;
|
||||
};
|
||||
|
||||
GatherSpannerAttributesVisitor::GatherSpannerAttributesVisitor(const AttributePointer& attribute) :
|
||||
SpannerVisitor(QVector<AttributePointer>() << attribute) {
|
||||
}
|
||||
|
||||
bool GatherSpannerAttributesVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
|
||||
foreach (const AttributePointer& attribute, spanner->getVoxelizedAttributes()) {
|
||||
_attributes.insert(attribute);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClearSpannersEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
// find all the spanner attributes
|
||||
GatherSpannerAttributesVisitor visitor(attribute);
|
||||
data.guide(visitor);
|
||||
|
||||
data.clear(attribute);
|
||||
foreach (const AttributePointer& attribute, visitor.getAttributes()) {
|
||||
data.clear(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
SetSpannerEdit::SetSpannerEdit(const SharedObjectPointer& spanner) :
|
||||
spanner(spanner) {
|
||||
}
|
||||
|
||||
class SetSpannerEditVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
SetSpannerEditVisitor(Spanner* spanner);
|
||||
SetSpannerEditVisitor(const QVector<AttributePointer>& attributes, Spanner* spanner);
|
||||
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
|
@ -142,17 +276,20 @@ private:
|
|||
Spanner* _spanner;
|
||||
};
|
||||
|
||||
SetSpannerEditVisitor::SetSpannerEditVisitor(Spanner* spanner) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(), spanner->getAttributes()),
|
||||
SetSpannerEditVisitor::SetSpannerEditVisitor(const QVector<AttributePointer>& attributes, Spanner* spanner) :
|
||||
MetavoxelVisitor(attributes, QVector<AttributePointer>() << attributes <<
|
||||
AttributeRegistry::getInstance()->getSpannerMaskAttribute()),
|
||||
_spanner(spanner) {
|
||||
}
|
||||
|
||||
int SetSpannerEditVisitor::visit(MetavoxelInfo& info) {
|
||||
return _spanner->getAttributeValues(info) ? DEFAULT_ORDER : STOP_RECURSION;
|
||||
}
|
||||
|
||||
SetSpannerEdit::SetSpannerEdit(const SharedObjectPointer& spanner) :
|
||||
spanner(spanner) {
|
||||
if (_spanner->blendAttributeValues(info)) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
if (info.outputValues.at(0).getAttribute()) {
|
||||
info.outputValues.last() = AttributeValue(_outputs.last(), encodeInline<float>(1.0f));
|
||||
}
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
void SetSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
||||
|
@ -163,6 +300,8 @@ void SetSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& obje
|
|||
data.expand();
|
||||
}
|
||||
|
||||
SetSpannerEditVisitor visitor(spanner);
|
||||
SetSpannerEditVisitor visitor(spanner->getAttributes(), spanner);
|
||||
data.guide(visitor);
|
||||
|
||||
setIntersectingMasked(spanner->getBounds(), data);
|
||||
}
|
||||
|
|
|
@ -299,6 +299,11 @@ Box operator*(const glm::mat4& matrix, const Box& box) {
|
|||
return newBox;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& out, const Box& box) {
|
||||
return out << '(' << box.minimum.x << box.minimum.y << box.minimum.z << ") (" <<
|
||||
box.maximum.x << box.maximum.y << box.maximum.z << ')';
|
||||
}
|
||||
|
||||
QMetaObjectEditor::QMetaObjectEditor(QWidget* parent) : QWidget(parent) {
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(QMargins());
|
||||
|
|
|
@ -60,6 +60,8 @@ DECLARE_STREAMABLE_METATYPE(Box)
|
|||
|
||||
Box operator*(const glm::mat4& matrix, const Box& box);
|
||||
|
||||
QDebug& operator<<(QDebug& out, const Box& box);
|
||||
|
||||
/// Editor for meta-object values.
|
||||
class QMetaObjectEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -37,7 +37,7 @@ void SharedObject::decrementReferenceCount() {
|
|||
}
|
||||
}
|
||||
|
||||
SharedObject* SharedObject::clone() const {
|
||||
SharedObject* SharedObject::clone(bool withID) const {
|
||||
// default behavior is to make a copy using the no-arg constructor and copy the stored properties
|
||||
const QMetaObject* metaObject = this->metaObject();
|
||||
SharedObject* newObject = static_cast<SharedObject*>(metaObject->newInstance());
|
||||
|
@ -50,6 +50,9 @@ SharedObject* SharedObject::clone() const {
|
|||
foreach (const QByteArray& propertyName, dynamicPropertyNames()) {
|
||||
newObject->setProperty(propertyName, property(propertyName));
|
||||
}
|
||||
if (withID) {
|
||||
newObject->setID(_id);
|
||||
}
|
||||
return newObject;
|
||||
}
|
||||
|
||||
|
@ -91,6 +94,11 @@ void SharedObject::dump(QDebug debug) const {
|
|||
}
|
||||
}
|
||||
|
||||
void SharedObject::setID(int id) {
|
||||
_weakHash.remove(_id);
|
||||
_weakHash.insert(_id = id, this);
|
||||
}
|
||||
|
||||
int SharedObject::_lastID = 0;
|
||||
WeakSharedObjectHash SharedObject::_weakHash;
|
||||
|
||||
|
|
|
@ -47,7 +47,8 @@ public:
|
|||
void decrementReferenceCount();
|
||||
|
||||
/// Creates a new clone of this object.
|
||||
virtual SharedObject* clone() const;
|
||||
/// \param withID if true, give the clone the same ID as this object
|
||||
virtual SharedObject* clone(bool withID = false) const;
|
||||
|
||||
/// Tests this object for equality with another.
|
||||
virtual bool equals(const SharedObject* other) const;
|
||||
|
@ -57,6 +58,8 @@ public:
|
|||
|
||||
private:
|
||||
|
||||
void setID(int id);
|
||||
|
||||
int _id;
|
||||
int _remoteID;
|
||||
int _referenceCount;
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include "LocalVoxels.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
int ScriptEngine::_scriptNumber = 1;
|
||||
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
|
||||
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
|
||||
|
||||
|
@ -41,7 +40,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng
|
|||
}
|
||||
|
||||
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString,
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
|
||||
_scriptContents(scriptContents),
|
||||
|
@ -58,26 +57,15 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
|||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_wantMenuItems(wantMenuItems),
|
||||
_scriptMenuName(),
|
||||
_scriptName(),
|
||||
_fileNameString(fileNameString),
|
||||
_quatLibrary(),
|
||||
_vec3Library()
|
||||
{
|
||||
// some clients will use these menu features
|
||||
if (!fileNameString.isEmpty()) {
|
||||
_scriptMenuName = "Stop ";
|
||||
_scriptMenuName.append(qPrintable(fileNameString));
|
||||
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||
} else {
|
||||
_scriptMenuName = "Stop Script ";
|
||||
_scriptMenuName.append(_scriptNumber);
|
||||
}
|
||||
_scriptNumber++;
|
||||
}
|
||||
|
||||
ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
_scriptContents(),
|
||||
_isFinished(false),
|
||||
_isRunning(false),
|
||||
|
@ -92,32 +80,21 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
|||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_wantMenuItems(wantMenuItems),
|
||||
_scriptMenuName(),
|
||||
_scriptName(),
|
||||
_fileNameString(),
|
||||
_quatLibrary(),
|
||||
_vec3Library()
|
||||
{
|
||||
QString scriptURLString = scriptURL.toString();
|
||||
_fileNameString = scriptURLString;
|
||||
// some clients will use these menu features
|
||||
if (!scriptURLString.isEmpty()) {
|
||||
_scriptMenuName = "Stop ";
|
||||
_scriptMenuName.append(qPrintable(scriptURLString));
|
||||
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||
} else {
|
||||
_scriptMenuName = "Stop Script ";
|
||||
_scriptMenuName.append(_scriptNumber);
|
||||
}
|
||||
_scriptNumber++;
|
||||
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
|
||||
// if the scheme is empty, maybe they typed in a file, let's try
|
||||
if (url.scheme().isEmpty()) {
|
||||
url = QUrl::fromLocalFile(scriptURLString);
|
||||
}
|
||||
|
||||
|
||||
// ok, let's see if it's valid... and if so, load it
|
||||
if (url.isValid()) {
|
||||
if (url.scheme() == "file") {
|
||||
|
@ -144,16 +121,16 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
|||
|
||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
||||
_isAvatar = isAvatar;
|
||||
|
||||
|
||||
if (_isAvatar && !_avatarIdentityTimer) {
|
||||
// set up the avatar timers
|
||||
_avatarIdentityTimer = new QTimer(this);
|
||||
_avatarBillboardTimer = new QTimer(this);
|
||||
|
||||
|
||||
// connect our slot
|
||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
|
||||
connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket);
|
||||
|
||||
|
||||
// start the timers
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
@ -162,20 +139,14 @@ void ScriptEngine::setIsAvatar(bool isAvatar) {
|
|||
|
||||
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
||||
_avatarData = avatarData;
|
||||
|
||||
|
||||
// remove the old Avatar property, if it exists
|
||||
_engine.globalObject().setProperty(objectName, QScriptValue());
|
||||
|
||||
|
||||
// give the script engine the new Avatar script property
|
||||
registerGlobalObject(objectName, _avatarData);
|
||||
}
|
||||
|
||||
void ScriptEngine::cleanupMenuItems() {
|
||||
if (_wantMenuItems) {
|
||||
emit cleanupMenuItem(_scriptMenuName);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
||||
if (_isRunning) {
|
||||
return false;
|
||||
|
@ -203,7 +174,7 @@ void ScriptEngine::init() {
|
|||
registerVoxelMetaTypes(&_engine);
|
||||
registerEventTypes(&_engine);
|
||||
registerMenuItemProperties(&_engine);
|
||||
|
||||
|
||||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
|
||||
|
@ -216,7 +187,7 @@ void ScriptEngine::init() {
|
|||
|
||||
QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject<AudioInjectorOptions>();
|
||||
_engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue);
|
||||
|
||||
|
||||
QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject<LocalVoxels>();
|
||||
_engine.globalObject().setProperty("LocalVoxels", localVoxelsValue);
|
||||
|
||||
|
@ -285,9 +256,9 @@ void ScriptEngine::run() {
|
|||
gettimeofday(&startTime, NULL);
|
||||
|
||||
int thisFrame = 0;
|
||||
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
|
||||
qint64 lastUpdate = usecTimestampNow();
|
||||
|
||||
while (!_isFinished) {
|
||||
|
@ -325,36 +296,36 @@ void ScriptEngine::run() {
|
|||
_particlesScriptingInterface.getParticlePacketSender()->process();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_isAvatar && _avatarData) {
|
||||
|
||||
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||
|
||||
|
||||
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
||||
avatarPacket.append(_avatarData->toByteArray());
|
||||
|
||||
|
||||
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
|
||||
|
||||
if (_isListeningToAudioStream || _avatarSound) {
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
bool silentFrame = true;
|
||||
|
||||
|
||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
|
||||
if (_avatarSound) {
|
||||
|
||||
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
|
||||
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||
if (nextSoundOutput[i] != 0) {
|
||||
|
@ -362,7 +333,7 @@ void ScriptEngine::run() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
|
@ -371,24 +342,24 @@ void ScriptEngine::run() {
|
|||
_numAvatarSoundSentBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||
? PacketTypeSilentAudioFrame
|
||||
: PacketTypeMicrophoneAudioNoEcho);
|
||||
|
||||
|
||||
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
||||
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
||||
|
||||
|
||||
if (silentFrame) {
|
||||
if (!_isListeningToAudioStream) {
|
||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// write the number of silent samples so the audio-mixer can uphold timing
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
||||
} else if (nextSoundOutput) {
|
||||
|
@ -396,7 +367,7 @@ void ScriptEngine::run() {
|
|||
packetStream.writeRawData(reinterpret_cast<const char*>(nextSoundOutput),
|
||||
numAvailableSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
||||
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
||||
}
|
||||
}
|
||||
|
@ -412,10 +383,10 @@ void ScriptEngine::run() {
|
|||
}
|
||||
}
|
||||
emit scriptEnding();
|
||||
|
||||
|
||||
// kill the avatar identity timer
|
||||
delete _avatarIdentityTimer;
|
||||
|
||||
|
||||
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
|
||||
// release the queue of edit voxel messages.
|
||||
_voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();
|
||||
|
@ -435,8 +406,6 @@ void ScriptEngine::run() {
|
|||
_particlesScriptingInterface.getParticlePacketSender()->process();
|
||||
}
|
||||
}
|
||||
|
||||
cleanupMenuItems();
|
||||
|
||||
// If we were on a thread, then wait till it's done
|
||||
if (thread()) {
|
||||
|
@ -444,7 +413,7 @@ void ScriptEngine::run() {
|
|||
}
|
||||
|
||||
emit finished(_fileNameString);
|
||||
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
|
@ -454,13 +423,13 @@ void ScriptEngine::stop() {
|
|||
|
||||
void ScriptEngine::timerFired() {
|
||||
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
||||
|
||||
|
||||
// call the associated JS function, if it exists
|
||||
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
|
||||
if (timerFunction.isValid()) {
|
||||
timerFunction.call();
|
||||
}
|
||||
|
||||
|
||||
if (!callingTimer->isActive()) {
|
||||
// this timer is done, we can kill it
|
||||
delete callingTimer;
|
||||
|
@ -471,14 +440,14 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
|||
// create the timer, add it to the map, and start it
|
||||
QTimer* newTimer = new QTimer(this);
|
||||
newTimer->setSingleShot(isSingleShot);
|
||||
|
||||
|
||||
connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);
|
||||
|
||||
|
||||
// make sure the timer stops when the script does
|
||||
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
||||
|
||||
|
||||
_timerFunctionMap.insert(newTimer, function);
|
||||
|
||||
|
||||
newTimer->start(intervalMS);
|
||||
return newTimer;
|
||||
}
|
||||
|
@ -505,17 +474,17 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const {
|
|||
if (!url.scheme().isEmpty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||
|
||||
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||
// to the original URL of our script
|
||||
QUrl parentURL(_fileNameString);
|
||||
|
||||
|
||||
// if the parent URL's scheme is empty, then this is probably a local file...
|
||||
if (parentURL.scheme().isEmpty()) {
|
||||
parentURL = QUrl::fromLocalFile(_fileNameString);
|
||||
}
|
||||
|
||||
// at this point we should have a legitimate fully qualified URL for our parent
|
||||
|
||||
// at this point we should have a legitimate fully qualified URL for our parent
|
||||
url = parentURL.resolved(url);
|
||||
return url;
|
||||
}
|
||||
|
@ -543,7 +512,7 @@ void ScriptEngine::include(const QString& includeFile) {
|
|||
loop.exec();
|
||||
includeContents = reply->readAll();
|
||||
}
|
||||
|
||||
|
||||
QScriptValue result = _engine.evaluate(includeContents);
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
|
|
|
@ -33,11 +33,11 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10
|
|||
class ScriptEngine : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false,
|
||||
ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
||||
const QString& fileNameString = QString(""),
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
|
||||
const QString& fileNameString = QString(""),
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
/// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
|
@ -49,39 +49,39 @@ public:
|
|||
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||
|
||||
const QString& getScriptMenuName() const { return _scriptMenuName; }
|
||||
const QString& getScriptName() const { return _scriptName; }
|
||||
void cleanupMenuItems();
|
||||
|
||||
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
||||
|
||||
|
||||
Q_INVOKABLE void setIsAvatar(bool isAvatar);
|
||||
bool isAvatar() const { return _isAvatar; }
|
||||
|
||||
|
||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||
|
||||
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||
|
||||
|
||||
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||
|
||||
|
||||
void init();
|
||||
void run(); /// runs continuously until Agent.stop() is called
|
||||
void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller
|
||||
|
||||
|
||||
void timerFired();
|
||||
|
||||
bool hasScript() const { return !_scriptContents.isEmpty(); }
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
|
||||
|
||||
QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void include(const QString& includeFile);
|
||||
|
||||
|
||||
signals:
|
||||
void update(float deltaTime);
|
||||
void scriptEnding();
|
||||
|
@ -106,19 +106,18 @@ private:
|
|||
QUrl resolveInclude(const QString& include) const;
|
||||
void sendAvatarIdentityPacket();
|
||||
void sendAvatarBillboardPacket();
|
||||
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
||||
|
||||
static VoxelsScriptingInterface _voxelsScriptingInterface;
|
||||
static ParticlesScriptingInterface _particlesScriptingInterface;
|
||||
static int _scriptNumber;
|
||||
|
||||
|
||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||
AudioScriptingInterface _audioScriptingInterface;
|
||||
AvatarData* _avatarData;
|
||||
bool _wantMenuItems;
|
||||
QString _scriptMenuName;
|
||||
QString _scriptName;
|
||||
QString _fileNameString;
|
||||
Quat _quatLibrary;
|
||||
Vec3 _vec3Library;
|
||||
|
|
|
@ -144,14 +144,15 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
|
|||
break;
|
||||
case QNetworkAccessManager::PostOperation:
|
||||
case QNetworkAccessManager::PutOperation:
|
||||
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (dataMultiPart) {
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = _networkAccessManager->post(authenticatedRequest, dataMultiPart);
|
||||
} else {
|
||||
networkReply = _networkAccessManager->put(authenticatedRequest, dataMultiPart);
|
||||
}
|
||||
dataMultiPart->setParent(networkReply);
|
||||
} else {
|
||||
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = _networkAccessManager->post(authenticatedRequest, dataByteArray);
|
||||
} else {
|
||||
|
@ -199,6 +200,7 @@ void AccountManager::passSuccessToCallback() {
|
|||
qDebug() << jsonResponse;
|
||||
}
|
||||
}
|
||||
delete requestReply;
|
||||
}
|
||||
|
||||
void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode) {
|
||||
|
@ -219,6 +221,7 @@ void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode)
|
|||
qDebug() << "Error" << errorCode << "-" << requestReply->errorString();
|
||||
}
|
||||
}
|
||||
delete requestReply;
|
||||
}
|
||||
|
||||
bool AccountManager::hasValidAccessToken() {
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// FileDownloader.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement Brisset on 3/14/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#include <QUrl>
|
||||
#include <QNetworkRequest>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
|
||||
#include "FileDownloader.h"
|
||||
|
||||
FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) :
|
||||
QObject(parent),
|
||||
_done(false)
|
||||
{
|
||||
connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*)));
|
||||
|
||||
QNetworkRequest request(dataURL);
|
||||
_networkAccessManager.get(request);
|
||||
}
|
||||
|
||||
void FileDownloader::processReply(QNetworkReply *reply) {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
_downloadedData = reply->readAll();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
_done = true;
|
||||
emit done(reply->error());
|
||||
}
|
||||
|
||||
void FileDownloader::waitForFile(int timeout) {
|
||||
QTimer timer;
|
||||
QEventLoop loop;
|
||||
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
|
||||
connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
|
||||
|
||||
if (!_done) {
|
||||
if (timeout > 0) {
|
||||
timer.start(timeout);
|
||||
}
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray FileDownloader::download(const QUrl dataURL, int timeout) {
|
||||
QTimer timer;
|
||||
QEventLoop loop;
|
||||
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit));
|
||||
|
||||
FileDownloader downloader(dataURL);
|
||||
connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
|
||||
|
||||
if (timeout > 0) {
|
||||
timer.start(timeout);
|
||||
}
|
||||
loop.exec();
|
||||
|
||||
return downloader.getData();
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// FileDownloader.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement Brisset on 3/14/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __hifi__FileDownloader__
|
||||
#define __hifi__FileDownloader__
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class FileDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FileDownloader(const QUrl dataURL, QObject* parent = NULL);
|
||||
|
||||
void waitForFile(int timeout = 0);
|
||||
|
||||
QByteArray getData() const { return _downloadedData; }
|
||||
bool done() { return _done; }
|
||||
|
||||
static QByteArray download(const QUrl dataURL, int timeout = 0);
|
||||
|
||||
signals:
|
||||
void done(QNetworkReply::NetworkError error);
|
||||
|
||||
private slots:
|
||||
void processReply(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager _networkAccessManager;
|
||||
QByteArray _downloadedData;
|
||||
|
||||
bool _done;
|
||||
};
|
||||
|
||||
|
||||
#endif /* defined(__hifi__FileDownloader__) */
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// FstReader.cpp
|
||||
// ModelUploader.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clément Brisset on 3/4/14.
|
||||
|
@ -13,39 +13,49 @@
|
|||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QTemporaryDir>
|
||||
#include <QVariant>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "AccountManager.h"
|
||||
|
||||
#include "FstReader.h"
|
||||
#include "ModelUploader.h"
|
||||
|
||||
|
||||
static const QString NAME_FIELD = "name";
|
||||
static const QString FILENAME_FIELD = "filename";
|
||||
static const QString TEXDIR_FIELD = "texdir";
|
||||
static const QString LOD_FIELD = "lod";
|
||||
static const QString HEAD_SPECIFIC_FIELD = "bs";
|
||||
|
||||
static const QString MODEL_URL = "/api/v1/models";
|
||||
|
||||
static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
FstReader::FstReader() :
|
||||
// Class providing the QObject parent system to QTemporaryDir
|
||||
class TemporaryDir : public QTemporaryDir, public QObject {
|
||||
public:
|
||||
virtual ~TemporaryDir() {
|
||||
// ensuring the entire object gets deleted by the QObject parent.
|
||||
}
|
||||
};
|
||||
|
||||
ModelUploader::ModelUploader(bool isHead) :
|
||||
_zipDir(new TemporaryDir()),
|
||||
_lodCount(-1),
|
||||
_texturesCount(-1),
|
||||
_totalSize(0),
|
||||
_isHead(false),
|
||||
_isHead(isHead),
|
||||
_readyToSend(false),
|
||||
_dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType))
|
||||
{
|
||||
_zipDir->setParent(_dataMultiPart);
|
||||
|
||||
}
|
||||
|
||||
FstReader::~FstReader() {
|
||||
ModelUploader::~ModelUploader() {
|
||||
delete _dataMultiPart;
|
||||
}
|
||||
|
||||
bool FstReader::zip() {
|
||||
bool ModelUploader::zip() {
|
||||
// File Dialog
|
||||
QString filename = QFileDialog::getOpenFileName(NULL,
|
||||
"Select your .fst file ...",
|
||||
|
@ -63,20 +73,20 @@ bool FstReader::zip() {
|
|||
QString("ModelUploader::zip()"),
|
||||
QString("Could not open FST file."),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compress and copy the fst
|
||||
if (!compressFile(QFileInfo(fst).filePath(), _zipDir.path() + "/" + QFileInfo(fst).fileName())) {
|
||||
return false;
|
||||
}
|
||||
_totalSize += QFileInfo(fst).size();
|
||||
if (!addPart(_zipDir.path() + "/" + QFileInfo(fst).fileName(),
|
||||
QString("fst"))) {
|
||||
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
|
||||
|
||||
// Compress and copy the fst
|
||||
if (!compressFile(QFileInfo(fst).filePath(), _zipDir->path() + "/" + QFileInfo(fst).fileName())) {
|
||||
return false;
|
||||
}
|
||||
if (!addPart(_zipDir->path() + "/" + QFileInfo(fst).fileName(),
|
||||
QString("fst"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's read through the FST file
|
||||
QTextStream stream(&fst);
|
||||
QList<QString> line;
|
||||
|
@ -86,80 +96,70 @@ bool FstReader::zip() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (_totalSize > MAX_SIZE) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Model too big, over %1 Bytes.").arg(MAX_SIZE),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// according to what is read, we modify the command
|
||||
if (line[1] == HEAD_SPECIFIC_FIELD) {
|
||||
_isHead = true;
|
||||
} else if (line[1] == NAME_FIELD) {
|
||||
if (line[0] == NAME_FIELD) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
||||
" name=\"model_name\"");
|
||||
textPart.setBody(line[1].toUtf8());
|
||||
_dataMultiPart->append(textPart);
|
||||
} else if (line[1] == FILENAME_FIELD) {
|
||||
} else if (line[0] == FILENAME_FIELD) {
|
||||
QFileInfo fbx(QFileInfo(fst).path() + "/" + line[1]);
|
||||
if (!fbx.exists() || !fbx.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("FBX file %1 could not be found.").arg(fbx.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbx.fileName());
|
||||
return false;
|
||||
}
|
||||
// Compress and copy
|
||||
if (!compressFile(fbx.filePath(), _zipDir.path() + "/" + line[1])) {
|
||||
if (!compressFile(fbx.filePath(), _zipDir->path() + "/" + line[1])) {
|
||||
return false;
|
||||
}
|
||||
_totalSize += fbx.size();
|
||||
if (!addPart(_zipDir.path() + "/" + line[1], "fbx")) {
|
||||
if (!addPart(_zipDir->path() + "/" + line[1], "fbx")) {
|
||||
return false;
|
||||
}
|
||||
} else if (line[1] == TEXDIR_FIELD) { // Check existence
|
||||
} else if (line[0] == TEXDIR_FIELD) { // Check existence
|
||||
QFileInfo texdir(QFileInfo(fst).path() + "/" + line[1]);
|
||||
if (!texdir.exists() || !texdir.isDir()) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Texture directory could not be found."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
||||
return false;
|
||||
}
|
||||
if (!addTextures(texdir)) { // Recursive compress and copy
|
||||
return false;
|
||||
}
|
||||
} else if (line[1] == LOD_FIELD) {
|
||||
} else if (line[0] == LOD_FIELD) {
|
||||
QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
|
||||
if (!lod.exists() || !lod.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("FBX file %1 could not be found.").arg(lod.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
|
||||
return false;
|
||||
}
|
||||
// Compress and copy
|
||||
if (!compressFile(lod.filePath(), _zipDir.path() + "/" + line[1])) {
|
||||
if (!compressFile(lod.filePath(), _zipDir->path() + "/" + line[1])) {
|
||||
return false;
|
||||
}
|
||||
_totalSize += lod.size();
|
||||
if (!addPart(_zipDir.path() + "/" + line[1], QString("lod%1").arg(++_lodCount))) {
|
||||
if (!addPart(_zipDir->path() + "/" + line[1], QString("lod%1").arg(++_lodCount))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
||||
" name=\"model_category\"");
|
||||
if (_isHead) {
|
||||
textPart.setBody("head");
|
||||
textPart.setBody("heads");
|
||||
} else {
|
||||
textPart.setBody("skeleton");
|
||||
textPart.setBody("skeletons");
|
||||
}
|
||||
_dataMultiPart->append(textPart);
|
||||
|
||||
|
@ -167,17 +167,41 @@ bool FstReader::zip() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FstReader::send() {
|
||||
bool ModelUploader::send() {
|
||||
if (!_readyToSend) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountManager::getInstance().authenticatedRequest(MODEL_URL, QNetworkAccessManager::PostOperation, JSONCallbackParameters(), QByteArray(), _dataMultiPart);
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "uploadSuccess";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "uploadFailed";
|
||||
|
||||
AccountManager::getInstance().authenticatedRequest(MODEL_URL, QNetworkAccessManager::PostOperation, callbackParams, QByteArray(), _dataMultiPart);
|
||||
_zipDir = NULL;
|
||||
_dataMultiPart = NULL;
|
||||
qDebug() << "Sending model...";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FstReader::addTextures(const QFileInfo& texdir) {
|
||||
void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) {
|
||||
qDebug() << "Model sent with success to the data server.";
|
||||
qDebug() << "It might take a few minute for it to appear in your model browser.";
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::uploadFailed()"),
|
||||
QString("Model could not be sent to the data server."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "Model upload failed (" << errorCode << "): " << errorString;
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
bool ModelUploader::addTextures(const QFileInfo& texdir) {
|
||||
QStringList filter;
|
||||
filter << "*.png" << "*.tif" << "*.jpg" << "*.jpeg";
|
||||
|
||||
|
@ -189,11 +213,10 @@ bool FstReader::addTextures(const QFileInfo& texdir) {
|
|||
foreach (QFileInfo info, list) {
|
||||
if (info.isFile()) {
|
||||
// Compress and copy
|
||||
if (!compressFile(info.filePath(), _zipDir.path() + "/" + info.fileName())) {
|
||||
if (!compressFile(info.filePath(), _zipDir->path() + "/" + info.fileName())) {
|
||||
return false;
|
||||
}
|
||||
_totalSize += info.size();
|
||||
if (!addPart(_zipDir.path() + "/" + info.fileName(),
|
||||
if (!addPart(_zipDir->path() + "/" + info.fileName(),
|
||||
QString("texture%1").arg(++_texturesCount))) {
|
||||
return false;
|
||||
}
|
||||
|
@ -207,19 +230,20 @@ bool FstReader::addTextures(const QFileInfo& texdir) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FstReader::compressFile(const QString &inFileName, const QString &outFileName) {
|
||||
bool ModelUploader::compressFile(const QString &inFileName, const QString &outFileName) {
|
||||
QFile inFile(inFileName);
|
||||
inFile.open(QIODevice::ReadOnly);
|
||||
QByteArray buffer = inFile.readAll();
|
||||
|
||||
QFile outFile(outFileName);
|
||||
if (!outFile.open(QIODevice::WriteOnly)) {
|
||||
QDir(_zipDir.path()).mkpath(QFileInfo(outFileName).path());
|
||||
QDir(_zipDir->path()).mkpath(QFileInfo(outFileName).path());
|
||||
if (!outFile.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::compressFile()"),
|
||||
QString("Could not compress %1").arg(inFileName),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Could not compress %1").arg(inFileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -230,13 +254,15 @@ bool FstReader::compressFile(const QString &inFileName, const QString &outFileNa
|
|||
}
|
||||
|
||||
|
||||
bool FstReader::addPart(const QString &path, const QString& name) {
|
||||
bool ModelUploader::addPart(const QString &path, const QString& name) {
|
||||
QFile* file = new QFile(path);
|
||||
if (!file->open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::addPart()"),
|
||||
QString("Could not open %1").arg(path),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -249,6 +275,19 @@ bool FstReader::addPart(const QString &path, const QString& name) {
|
|||
_dataMultiPart->append(part);
|
||||
file->setParent(_dataMultiPart);
|
||||
|
||||
|
||||
qDebug() << "File " << QFileInfo(*file).fileName() << " added to model.";
|
||||
_totalSize += file->size();
|
||||
if (_totalSize > MAX_SIZE) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Model too big, over %1 Bytes.").arg(MAX_SIZE),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Model too big, over %1 Bytes.").arg(MAX_SIZE);
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Current model size: " << _totalSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// FstReader.h
|
||||
// ModelUploader.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clément Brisset on 3/4/14.
|
||||
|
@ -7,23 +7,29 @@
|
|||
//
|
||||
//
|
||||
|
||||
#ifndef __hifi__FstReader__
|
||||
#define __hifi__FstReader__
|
||||
|
||||
#include <QTemporaryDir>
|
||||
#ifndef __hifi__ModelUploader__
|
||||
#define __hifi__ModelUploader__
|
||||
|
||||
class TemporaryDir;
|
||||
class QHttpMultiPart;
|
||||
class QFileInfo;
|
||||
|
||||
class FstReader {
|
||||
class ModelUploader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FstReader();
|
||||
~FstReader();
|
||||
ModelUploader(bool isHead);
|
||||
~ModelUploader();
|
||||
|
||||
bool zip();
|
||||
bool send();
|
||||
|
||||
private slots:
|
||||
void uploadSuccess(const QJsonObject& jsonResponse);
|
||||
void uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString);
|
||||
|
||||
private:
|
||||
QTemporaryDir _zipDir;
|
||||
TemporaryDir* _zipDir;
|
||||
int _lodCount;
|
||||
int _texturesCount;
|
||||
int _totalSize;
|
||||
|
@ -38,4 +44,4 @@ private:
|
|||
bool addPart(const QString& path, const QString& name);
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__FstReader__) */
|
||||
#endif /* defined(__hifi__ModelUploader__) */
|
|
@ -51,6 +51,7 @@ Node::Node(const QUuid& uuid, char type, const HifiSockAddr& publicSocket, const
|
|||
_lastHeardMicrostamp(usecTimestampNow()),
|
||||
_publicSocket(publicSocket),
|
||||
_localSocket(localSocket),
|
||||
_symmetricSocket(),
|
||||
_activeSocket(NULL),
|
||||
_connectionSecret(),
|
||||
_bytesReceivedMovingAverage(NULL),
|
||||
|
@ -84,6 +85,15 @@ void Node::setLocalSocket(const HifiSockAddr& localSocket) {
|
|||
_localSocket = localSocket;
|
||||
}
|
||||
|
||||
void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) {
|
||||
if (_activeSocket == &_symmetricSocket) {
|
||||
// if the active socket was the symmetric socket then reset it to NULL
|
||||
_activeSocket = NULL;
|
||||
}
|
||||
|
||||
_symmetricSocket = symmetricSocket;
|
||||
}
|
||||
|
||||
void Node::activateLocalSocket() {
|
||||
qDebug() << "Activating local socket for node" << *this;
|
||||
_activeSocket = &_localSocket;
|
||||
|
@ -94,6 +104,11 @@ void Node::activatePublicSocket() {
|
|||
_activeSocket = &_publicSocket;
|
||||
}
|
||||
|
||||
void Node::activateSymmetricSocket() {
|
||||
qDebug() << "Activating symmetric socket for node" << *this;
|
||||
_activeSocket = &_symmetricSocket;
|
||||
}
|
||||
|
||||
void Node::recordBytesReceived(int bytesReceived) {
|
||||
if (!_bytesReceivedMovingAverage) {
|
||||
_bytesReceivedMovingAverage = new SimpleMovingAverage(100);
|
||||
|
|
|
@ -70,11 +70,14 @@ public:
|
|||
void setPublicSocket(const HifiSockAddr& publicSocket);
|
||||
const HifiSockAddr& getLocalSocket() const { return _localSocket; }
|
||||
void setLocalSocket(const HifiSockAddr& localSocket);
|
||||
|
||||
const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; }
|
||||
void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
|
||||
|
||||
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
|
||||
|
||||
void activatePublicSocket();
|
||||
void activateLocalSocket();
|
||||
void activateSymmetricSocket();
|
||||
|
||||
const QUuid& getConnectionSecret() const { return _connectionSecret; }
|
||||
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
|
||||
|
@ -110,6 +113,7 @@ private:
|
|||
quint64 _lastHeardMicrostamp;
|
||||
HifiSockAddr _publicSocket;
|
||||
HifiSockAddr _localSocket;
|
||||
HifiSockAddr _symmetricSocket;
|
||||
HifiSockAddr* _activeSocket;
|
||||
QUuid _connectionSecret;
|
||||
SimpleMovingAverage* _bytesReceivedMovingAverage;
|
||||
|
|
|
@ -294,6 +294,15 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
|
|||
matchingNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
QByteArray replyPacket = constructPingReplyPacket(packet);
|
||||
writeDatagram(replyPacket, matchingNode, senderSockAddr);
|
||||
|
||||
// If we don't have a symmetric socket for this node and this socket doesn't match
|
||||
// what we have for public and local then set it as the symmetric.
|
||||
// This allows a server on a reachable port to communicate with nodes on symmetric NATs
|
||||
if (matchingNode->getSymmetricSocket().isNull()) {
|
||||
if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) {
|
||||
matchingNode->setSymmetricSocket(senderSockAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -782,7 +791,7 @@ QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
|
|||
return replyPacket;
|
||||
}
|
||||
|
||||
void NodeList::pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node) {
|
||||
void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
|
||||
|
||||
// send the ping packet to the local and public sockets for this node
|
||||
QByteArray localPingPacket = constructPingPacket(PingType::Local);
|
||||
|
@ -790,6 +799,11 @@ void NodeList::pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer&
|
|||
|
||||
QByteArray publicPingPacket = constructPingPacket(PingType::Public);
|
||||
writeDatagram(publicPingPacket, node, node->getPublicSocket());
|
||||
|
||||
if (!node->getSymmetricSocket().isNull()) {
|
||||
QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric);
|
||||
writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket());
|
||||
}
|
||||
}
|
||||
|
||||
SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType,
|
||||
|
@ -860,7 +874,7 @@ void NodeList::pingInactiveNodes() {
|
|||
foreach (const SharedNodePointer& node, getNodeHash()) {
|
||||
if (!node->getActiveSocket()) {
|
||||
// we don't have an active link to this node, ping it to set that up
|
||||
pingPublicAndLocalSocketsForInactiveNode(node);
|
||||
pingPunchForInactiveNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -879,6 +893,8 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con
|
|||
sendingNode->activateLocalSocket();
|
||||
} else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) {
|
||||
sendingNode->activatePublicSocket();
|
||||
} else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) {
|
||||
sendingNode->activateSymmetricSocket();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace PingType {
|
|||
const PingType_t Agnostic = 0;
|
||||
const PingType_t Local = 1;
|
||||
const PingType_t Public = 2;
|
||||
const PingType_t Symmetric = 3;
|
||||
}
|
||||
|
||||
class NodeList : public QObject {
|
||||
|
@ -101,7 +102,7 @@ public:
|
|||
|
||||
QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
||||
QByteArray constructPingReplyPacket(const QByteArray& pingPacket);
|
||||
void pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node);
|
||||
void pingPunchForInactiveNode(const SharedNodePointer& node);
|
||||
|
||||
SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true);
|
||||
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
|
||||
|
|
|
@ -20,18 +20,15 @@ ThreadedAssignment::ThreadedAssignment(const QByteArray& packet) :
|
|||
|
||||
}
|
||||
|
||||
void ThreadedAssignment::deleteLater() {
|
||||
// move the NodeList back to the QCoreApplication instance's thread
|
||||
NodeList::getInstance()->moveToThread(QCoreApplication::instance()->thread());
|
||||
QObject::deleteLater();
|
||||
}
|
||||
|
||||
void ThreadedAssignment::setFinished(bool isFinished) {
|
||||
_isFinished = isFinished;
|
||||
|
||||
if (_isFinished) {
|
||||
aboutToFinish();
|
||||
emit finished();
|
||||
|
||||
// move the NodeList back to the QCoreApplication instance's thread
|
||||
NodeList::getInstance()->moveToThread(QCoreApplication::instance()->thread());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#ifndef __hifi__ThreadedAssignment__
|
||||
#define __hifi__ThreadedAssignment__
|
||||
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include "Assignment.h"
|
||||
|
||||
class ThreadedAssignment : public Assignment {
|
||||
|
@ -22,7 +24,6 @@ public:
|
|||
public slots:
|
||||
/// threaded run of assignment
|
||||
virtual void run() = 0;
|
||||
virtual void deleteLater();
|
||||
virtual void readPendingDatagrams() = 0;
|
||||
virtual void sendStatsPacket();
|
||||
|
||||
|
@ -36,5 +37,6 @@ signals:
|
|||
void finished();
|
||||
};
|
||||
|
||||
typedef QSharedPointer<ThreadedAssignment> SharedAssignmentPointer;
|
||||
|
||||
#endif /* defined(__hifi__ThreadedAssignment__) */
|
||||
|
|
|
@ -34,9 +34,103 @@ static int streamedBytesReceived = 0;
|
|||
static int sharedObjectsCreated = 0;
|
||||
static int sharedObjectsDestroyed = 0;
|
||||
|
||||
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
|
||||
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
|
||||
for (int i = 0; i < bytes.size(); i++) {
|
||||
bytes[i] = rand();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static QByteArray createRandomBytes() {
|
||||
const int MIN_BYTES = 4;
|
||||
const int MAX_BYTES = 16;
|
||||
return createRandomBytes(MIN_BYTES, MAX_BYTES);
|
||||
}
|
||||
|
||||
static TestMessageC createRandomMessageC() {
|
||||
TestMessageC message;
|
||||
message.foo = randomBoolean();
|
||||
message.bar = rand();
|
||||
message.baz = randFloat();
|
||||
message.bong.foo = createRandomBytes();
|
||||
return message;
|
||||
}
|
||||
|
||||
static bool testSerialization(Bitstream::MetadataType metadataType) {
|
||||
QByteArray array;
|
||||
QDataStream outStream(&array, QIODevice::WriteOnly);
|
||||
Bitstream out(outStream, metadataType);
|
||||
SharedObjectPointer testObjectWrittenA = new TestSharedObjectA(randFloat());
|
||||
out << testObjectWrittenA;
|
||||
SharedObjectPointer testObjectWrittenB = new TestSharedObjectB(randFloat(), createRandomBytes());
|
||||
out << testObjectWrittenB;
|
||||
TestMessageC messageWritten = createRandomMessageC();
|
||||
out << QVariant::fromValue(messageWritten);
|
||||
QByteArray endWritten = "end";
|
||||
out << endWritten;
|
||||
out.flush();
|
||||
|
||||
QDataStream inStream(array);
|
||||
Bitstream in(inStream, metadataType);
|
||||
in.addMetaObjectSubstitution("TestSharedObjectA", &TestSharedObjectB::staticMetaObject);
|
||||
in.addMetaObjectSubstitution("TestSharedObjectB", &TestSharedObjectA::staticMetaObject);
|
||||
in.addTypeSubstitution("TestMessageC", TestMessageA::Type);
|
||||
SharedObjectPointer testObjectReadA;
|
||||
in >> testObjectReadA;
|
||||
|
||||
if (!testObjectReadA || testObjectReadA->metaObject() != &TestSharedObjectB::staticMetaObject) {
|
||||
qDebug() << "Wrong class for A" << testObjectReadA << metadataType;
|
||||
return true;
|
||||
}
|
||||
if (metadataType == Bitstream::FULL_METADATA && static_cast<TestSharedObjectA*>(testObjectWrittenA.data())->getFoo() !=
|
||||
static_cast<TestSharedObjectB*>(testObjectReadA.data())->getFoo()) {
|
||||
QDebug debug = qDebug() << "Failed to transfer shared field from A to B";
|
||||
testObjectWrittenA->dump(debug);
|
||||
testObjectReadA->dump(debug);
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedObjectPointer testObjectReadB;
|
||||
in >> testObjectReadB;
|
||||
if (!testObjectReadB || testObjectReadB->metaObject() != &TestSharedObjectA::staticMetaObject) {
|
||||
qDebug() << "Wrong class for B" << testObjectReadB << metadataType;
|
||||
return true;
|
||||
}
|
||||
if (metadataType == Bitstream::FULL_METADATA && static_cast<TestSharedObjectB*>(testObjectWrittenB.data())->getFoo() !=
|
||||
static_cast<TestSharedObjectA*>(testObjectReadB.data())->getFoo()) {
|
||||
QDebug debug = qDebug() << "Failed to transfer shared field from B to A";
|
||||
testObjectWrittenB->dump(debug);
|
||||
testObjectReadB->dump(debug);
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant messageRead;
|
||||
in >> messageRead;
|
||||
if (!messageRead.isValid() || messageRead.userType() != TestMessageA::Type) {
|
||||
qDebug() << "Wrong type for message" << messageRead;
|
||||
return true;
|
||||
}
|
||||
if (metadataType == Bitstream::FULL_METADATA && messageWritten.foo != messageRead.value<TestMessageA>().foo) {
|
||||
QDebug debug = qDebug() << "Failed to transfer shared field between messages" <<
|
||||
messageWritten.foo << messageRead.value<TestMessageA>().foo;
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray endRead;
|
||||
in >> endRead;
|
||||
if (endWritten != endRead) {
|
||||
qDebug() << "End tag mismatch." << endRead;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetavoxelTests::run() {
|
||||
|
||||
qDebug() << "Running metavoxel tests...";
|
||||
qDebug() << "Running transmission tests...";
|
||||
qDebug();
|
||||
|
||||
// seed the random number generator so that our tests are reproducible
|
||||
srand(0xBAAAAABE);
|
||||
|
@ -62,26 +156,20 @@ bool MetavoxelTests::run() {
|
|||
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
|
||||
qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived;
|
||||
qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
|
||||
qDebug();
|
||||
|
||||
qDebug() << "Running serialization tests...";
|
||||
qDebug();
|
||||
|
||||
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
qDebug() << "All tests passed!";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
|
||||
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
|
||||
for (int i = 0; i < bytes.size(); i++) {
|
||||
bytes[i] = rand();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static QByteArray createRandomBytes() {
|
||||
const int MIN_BYTES = 4;
|
||||
const int MAX_BYTES = 16;
|
||||
return createRandomBytes(MIN_BYTES, MAX_BYTES);
|
||||
}
|
||||
|
||||
static SharedObjectPointer createRandomSharedObject() {
|
||||
switch (randIntInRange(0, 2)) {
|
||||
case 0: return new TestSharedObjectA(randFloat());
|
||||
|
@ -132,12 +220,7 @@ static QVariant createRandomMessage() {
|
|||
}
|
||||
case 2:
|
||||
default: {
|
||||
TestMessageC message;
|
||||
message.foo = randomBoolean();
|
||||
message.bar = rand();
|
||||
message.baz = randFloat();
|
||||
message.bong.foo = createRandomBytes();
|
||||
return QVariant::fromValue(message);
|
||||
return QVariant::fromValue(createRandomMessageC());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,7 +405,9 @@ void TestSharedObjectA::setFoo(float foo) {
|
|||
}
|
||||
}
|
||||
|
||||
TestSharedObjectB::TestSharedObjectB() {
|
||||
TestSharedObjectB::TestSharedObjectB(float foo, const QByteArray& bar) :
|
||||
_foo(foo),
|
||||
_bar(bar) {
|
||||
sharedObjectsCreated++;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,11 +89,24 @@ private:
|
|||
/// Another simple shared object.
|
||||
class TestSharedObjectB : public SharedObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float foo READ getFoo WRITE setFoo)
|
||||
Q_PROPERTY(QByteArray bar READ getBar WRITE setBar)
|
||||
|
||||
public:
|
||||
|
||||
Q_INVOKABLE TestSharedObjectB();
|
||||
Q_INVOKABLE TestSharedObjectB(float foo = 0.0f, const QByteArray& bar = QByteArray());
|
||||
virtual ~TestSharedObjectB();
|
||||
|
||||
void setFoo(float foo) { _foo = foo; }
|
||||
float getFoo() const { return _foo; }
|
||||
|
||||
void setBar(const QByteArray& bar) { _bar = bar; }
|
||||
const QByteArray& getBar() const { return _bar; }
|
||||
|
||||
private:
|
||||
|
||||
float _foo;
|
||||
QByteArray _bar;
|
||||
};
|
||||
|
||||
/// A simple test message.
|
||||
|
|
|
@ -24,10 +24,16 @@ public:
|
|||
QStringList bases;
|
||||
};
|
||||
|
||||
class Field {
|
||||
public:
|
||||
QString type;
|
||||
QString name;
|
||||
};
|
||||
|
||||
class Streamable {
|
||||
public:
|
||||
Class clazz;
|
||||
QStringList fields;
|
||||
QList<Field> fields;
|
||||
};
|
||||
|
||||
void processInput(QTextStream& in, QList<Streamable>* streamables) {
|
||||
|
@ -66,8 +72,10 @@ void processInput(QTextStream& in, QList<Streamable>* streamables) {
|
|||
|
||||
} else if (match.startsWith("STREAM")) {
|
||||
match.chop(1); // get rid of the semicolon
|
||||
match = match.trimmed(); // and any space before it
|
||||
currentStreamable.fields.append(match.mid(match.lastIndexOf(' ') + 1));
|
||||
match = match.mid(match.indexOf(' ') + 1).trimmed(); // and STREAM, and any space before it
|
||||
int index = match.lastIndexOf(' ');
|
||||
Field field = { match.left(index).simplified(), match.mid(index + 1) };
|
||||
currentStreamable.fields.append(field);
|
||||
|
||||
} else { // match.startsWith("class")
|
||||
classExp.exactMatch(match);
|
||||
|
@ -90,12 +98,85 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
foreach (const Streamable& str, streamables) {
|
||||
const QString& name = str.clazz.name;
|
||||
|
||||
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n";
|
||||
|
||||
out << "const QVector<MetaField>& " << name << "::getMetaFields() {\n";
|
||||
out << " static QVector<MetaField> metaFields = QVector<MetaField>()";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " << " << base << "::getMetaFields()";
|
||||
}
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << "\n << MetaField(\"" << field.name << "\", Bitstream::getTypeStreamer(qMetaTypeId<" <<
|
||||
field.type << ">()))";
|
||||
}
|
||||
out << ";\n";
|
||||
out << " return metaFields;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "int " << name << "::getFieldIndex(const QByteArray& name) {\n";
|
||||
out << " static QHash<QByteArray, int> fieldIndices = createFieldIndices();\n";
|
||||
out << " return fieldIndices.value(name) - 1;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "QHash<QByteArray, int> " << name << "::createFieldIndices() {\n";
|
||||
out << " QHash<QByteArray, int> indices;\n";
|
||||
out << " int index = 1;\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " foreach (const MetaField& field, " << base << "::getMetaFields()) {\n";
|
||||
out << " indices.insert(field.getName(), index++);\n";
|
||||
out << " }\n";
|
||||
}
|
||||
out << " foreach (const MetaField& field, getMetaFields()) {\n";
|
||||
out << " indices.insert(field.getName(), index++);\n";
|
||||
out << " }\n";
|
||||
out << " return indices;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "void " << name << "::setField(int index, const QVariant& value) {\n";
|
||||
if (!str.clazz.bases.isEmpty()) {
|
||||
out << " int nextIndex;\n";
|
||||
}
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n";
|
||||
out << " " << base << "::setField(index, value);\n";
|
||||
out << " return;\n";
|
||||
out << " }\n";
|
||||
out << " index = nextIndex;\n";
|
||||
}
|
||||
out << " switch (index) {\n";
|
||||
for (int i = 0; i < str.fields.size(); i++) {
|
||||
out << " case " << i << ":\n";
|
||||
out << " this->" << str.fields.at(i).name << " = value.value<" << str.fields.at(i).type << ">();\n";
|
||||
out << " break;\n";
|
||||
}
|
||||
out << " }\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "QVariant " << name << "::getField(int index) const {\n";
|
||||
if (!str.clazz.bases.isEmpty()) {
|
||||
out << " int nextIndex;\n";
|
||||
}
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n";
|
||||
out << " return " << base << "::getField(index);\n";
|
||||
out << " }\n";
|
||||
out << " index = nextIndex;\n";
|
||||
}
|
||||
out << " switch (index) {\n";
|
||||
for (int i = 0; i < str.fields.size(); i++) {
|
||||
out << " case " << i << ":\n";
|
||||
out << " return QVariant::fromValue(this->" << str.fields.at(i).name << ");\n";
|
||||
}
|
||||
out << " }\n";
|
||||
out << " return QVariant();\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " out << static_cast<const " << base << "&>(obj);\n";
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
out << " out << obj." << field << ";\n";
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " out << obj." << field.name << ";\n";
|
||||
}
|
||||
out << " return out;\n";
|
||||
out << "}\n";
|
||||
|
@ -104,12 +185,32 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " in >> static_cast<" << base << "&>(obj);\n";
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
out << " in >> obj." << field << ";\n";
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " in >> obj." << field.name << ";\n";
|
||||
}
|
||||
out << " return in;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "template<> void Bitstream::writeRawDelta(const " << name << "& value, const " << name << "& reference) {\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " writeRawDelta(static_cast<const " << base << "&>(value), static_cast<const " <<
|
||||
base << "&>(reference));\n";
|
||||
}
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " writeDelta(value." << field.name << ", reference." << field.name << ");\n";
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
out << "template<> void Bitstream::readRawDelta(" << name << "& value, const " << name << "& reference) {\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " readRawDelta(static_cast<" << base << "&>(value), static_cast<const " <<
|
||||
base << "&>(reference));\n";
|
||||
}
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " readDelta(value." << field.name << ", reference." << field.name << ");\n";
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n";
|
||||
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
|
||||
out << " return true";
|
||||
|
@ -124,12 +225,12 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
out << "static_cast<const " << base << "&>(first) == static_cast<const " << base << "&>(second)";
|
||||
first = false;
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
foreach (const Field& field, str.fields) {
|
||||
if (!first) {
|
||||
out << " &&\n";
|
||||
out << " ";
|
||||
}
|
||||
out << "first." << field << " == second." << field;
|
||||
out << "first." << field.name << " == second." << field.name;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
@ -150,19 +251,17 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
out << "static_cast<const " << base << "&>(first) != static_cast<const " << base << "&>(second)";
|
||||
first = false;
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
foreach (const Field& field, str.fields) {
|
||||
if (!first) {
|
||||
out << " ||\n";
|
||||
out << " ";
|
||||
}
|
||||
out << "first." << field << " != second." << field;
|
||||
out << "first." << field.name << " != second." << field.name;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
out << ";\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n";
|
||||
out << "}\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue