Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

Conflicts:
	interface/interface_en.ts
This commit is contained in:
Andrzej Kapolka 2014-04-03 11:37:27 -07:00
commit 22710af111
35 changed files with 1788 additions and 1037 deletions

View file

@ -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="1385"/>
<source>Export Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="1386"/>
<source>Sparse Voxel Octree Files (*.svo)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3722"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3723"/>
<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="463"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="465"/>
<location filename="src/Menu.cpp" line="477"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="475"/>
<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>&lt;b&gt;Import&lt;/b&gt; %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>

View 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

View 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

View 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

View file

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

View file

@ -49,7 +49,7 @@
#include <QXmlStreamReader>
#include <QXmlStreamAttributes>
#include <QMediaPlayer>
#include <QMimeData>
#include <QMimeData>
#include <QMessageBox>
#include <AccountManager.h>
@ -112,13 +112,11 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D
const int STATS_PELS_PER_LINE = 20;
const QString CUSTOM_URL_SCHEME = "hifi:";
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (message.size() > 0) {
QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate);
QString formattedMessage = QString("[%1] %2\n").arg(dateString).arg(message);
fprintf(stdout, "%s", qPrintable(formattedMessage));
Application::getInstance()->getLogger()->addMessage(qPrintable(formattedMessage));
}
@ -175,23 +173,23 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
// set the associated application properties
applicationInfo.beginGroup("INFO");
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
setApplicationName(applicationInfo.value("name").toString());
setApplicationVersion(BUILD_VERSION);
setOrganizationName(applicationInfo.value("organizationName").toString());
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
QSettings::setDefaultFormat(QSettings::IniFormat);
_myAvatar = _avatarManager.getMyAvatar();
_applicationStartupTime = startup_time;
QFontDatabase::addApplicationFont(Application::resourcesPath() + "styles/Inconsolata.otf");
_window->setWindowTitle("Interface");
@ -206,19 +204,19 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
if (portStr) {
listenPort = atoi(portStr);
}
// start the nodeThread so its event loop is running
_nodeThread->start();
// make sure the node thread is given highest priority
_nodeThread->setPriority(QThread::TimeCriticalPriority);
// put the NodeList and datagram processing on the node thread
NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort);
nodeList->moveToThread(_nodeThread);
_datagramProcessor.moveToThread(_nodeThread);
// connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal
connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams()));
@ -240,20 +238,20 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle);
connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset);
// connect to appropriate slots on AccountManager
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain);
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
_settings = new QSettings(this);
// Check to see if the user passed in a command line option for loading a local
// Voxel File.
_voxelsFilename = getCmdOption(argc, constArgv, "-i");
@ -267,7 +265,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::VoxelServer << NodeType::ParticleServer
<< NodeType::MetavoxelServer);
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent);
@ -277,7 +275,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->moveToThread(_nodeThread);
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
// send the identity packet for our avatar each second to our avatar mixer
QTimer* identityPacketTimer = new QTimer();
connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);
@ -325,14 +323,18 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
// Set the sixense filtering
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
checkVersion();
_overlays.init(_glWidget); // do this before scripts load
LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());
LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);
_window->addDockWidget(Qt::NoDockWidgetArea, _runningScriptsWidget = new RunningScriptsWidget());
_runningScriptsWidget->setRunningScripts(getRunningScripts());
connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript);
// check first run...
QVariant firstRunValue = _settings->value("firstRun",QVariant(true));
if (firstRunValue.isValid() && firstRunValue.toBool()) {
@ -340,7 +342,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
// clear the scripts, and set out script to our default scripts
clearScriptsBeforeRunning();
loadScript("http://public.highfidelity.io/scripts/defaultScripts.js");
_settings->setValue("firstRun",QVariant(false));
} else {
// do this as late as possible so that all required subsystems are inialized
@ -351,34 +353,34 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
Application::~Application() {
qInstallMessageHandler(NULL);
// make sure we don't call the idle timer any more
delete idleTimer;
Menu::getInstance()->saveSettings();
_rearMirrorTools->saveSettings(_settings);
_sharedVoxelSystem.changeTree(new VoxelTree);
if (_voxelImporter) {
_voxelImporter->saveSettings(_settings);
delete _voxelImporter;
}
_settings->sync();
// let the avatar mixer know we're out
MyAvatar::sendKillAvatar();
// ask the datagram processing thread to quit and wait until it is done
_nodeThread->quit();
_nodeThread->wait();
// stop the audio process
QMetaObject::invokeMethod(&_audio, "stop");
// ask the audio thread to quit and wait until it is done
_audio.thread()->quit();
_audio.thread()->wait();
_voxelProcessor.terminate();
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
@ -391,9 +393,9 @@ Application::~Application() {
Menu::getInstance()->deleteLater();
_myAvatar = NULL;
delete _glWidget;
AccountManager::getInstance().destroy();
}
@ -585,7 +587,7 @@ void Application::paintGL() {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
renderRearViewMirror(_mirrorViewRect);
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
_rearMirrorTools->render(true);
}
@ -658,10 +660,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
continue;
}
// Perform the broadcast for one type
int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type);
// Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise)
BandwidthMeter::ChannelIndex channel;
switch (type) {
@ -680,32 +682,15 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
}
bool Application::event(QEvent* event) {
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) {
QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME);
QStringList urlParts = destination.split('/', QString::SkipEmptyParts);
if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name
Menu::getInstance()->goToDomain(urlParts[0]);
// location coordinates
Menu::getInstance()->goToDestination(urlParts[1]);
if (urlParts.count() > 2) {
// location orientation
Menu::getInstance()->goToOrientation(urlParts[2]);
}
} else if (urlParts.count() == 1) {
// location coordinates
Menu::getInstance()->goToDestination(urlParts[0]);
}
bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME);
if (isHifiSchemeURL) {
Menu::getInstance()->goTo(fileEvent->url().toString());
}
return false;
}
return QApplication::event(event);
@ -716,7 +701,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
_keysPressed.insert(event->key());
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isKeyCaptured(event)) {
return;
@ -1075,7 +1060,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
checkBandwidthMeterClick();
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
checkStatsClick();
}
}
}
}
}
@ -1124,7 +1109,7 @@ void Application::touchBeginEvent(QTouchEvent* event) {
if (_controllerScriptingInterface.isTouchCaptured()) {
return;
}
// put any application specific touch behavior below here..
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
@ -1167,13 +1152,13 @@ void Application::dropEvent(QDropEvent *event) {
break;
}
}
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
if (snapshotData) {
if (!snapshotData->getDomain().isEmpty()) {
Menu::getInstance()->goToDomain(snapshotData->getDomain());
}
_myAvatar->setPosition(snapshotData->getLocation());
_myAvatar->setOrientation(snapshotData->getOrientation());
} else {
@ -1201,19 +1186,19 @@ void Application::timer() {
}
_fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f);
_packetsPerSecond = (float) _datagramProcessor.getPacketCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f);
_bytesPerSecond = (float) _datagramProcessor.getByteCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f);
_frameCount = 0;
_datagramProcessor.resetCounters();
gettimeofday(&_timerStart, NULL);
// ask the node list to check in with the domain server
NodeList::getInstance()->sendDomainServerCheckIn();
}
void Application::idle() {
@ -1250,11 +1235,11 @@ void Application::idle() {
_idleLoopMeasuredJitter = _idleLoopStdev.getStDev();
_idleLoopStdev.reset();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
_buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData());
}
// After finishing all of the above work, restart the idle timer, allowing 2ms to process events.
idleTimer->start(2);
}
@ -1386,7 +1371,7 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
tr("Sparse Voxel Octree Files (*.svo)"));
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
const char* fileName = fileNameAscii.data();
VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s);
if (selectedNode) {
VoxelTree exportTree;
@ -1400,12 +1385,12 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
void Application::importVoxels() {
_importSucceded = false;
if (!_voxelImporter) {
_voxelImporter = new VoxelImporter(_window);
_voxelImporter->loadSettings(_settings);
}
if (!_voxelImporter->exec()) {
qDebug() << "[DEBUG] Import succeeded." << endl;
_importSucceded = true;
@ -1419,7 +1404,7 @@ void Application::importVoxels() {
// restore the main window's active state
_window->activateWindow();
emit importDone();
}
@ -1475,7 +1460,7 @@ void Application::pasteVoxels(const VoxelDetail& sourceVoxel) {
}
pasteVoxelsToOctalCode(octalCodeDestination);
if (calculatedOctCode) {
delete[] calculatedOctCode;
}
@ -1512,9 +1497,9 @@ void Application::init() {
// Cleanup of the original shared tree
_sharedVoxelSystem.init();
_voxelImporter = new VoxelImporter(_window);
_environment.init();
_glowEffect.init();
@ -1556,11 +1541,11 @@ void Application::init() {
_audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples());
}
qDebug("Loaded settings");
// initialize Visage and Faceshift after loading the menu settings
_faceshift.init();
_visage.init();
// fire off an immediate domain-server check in now that settings are loaded
NodeList::getInstance()->sendDomainServerCheckIn();
@ -1579,20 +1564,20 @@ void Application::init() {
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
// connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface
connect(&_particleCollisionSystem,
connect(&_particleCollisionSystem,
SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)),
ScriptEngine::getParticlesScriptingInterface(),
ScriptEngine::getParticlesScriptingInterface(),
SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)));
connect(&_particleCollisionSystem,
connect(&_particleCollisionSystem,
SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)),
ScriptEngine::getParticlesScriptingInterface(),
ScriptEngine::getParticlesScriptingInterface(),
SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)));
_audio.init(_glWidget);
_rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect, _settings);
connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView()));
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
@ -1715,7 +1700,7 @@ void Application::updateMyAvatarLookAtPosition() {
float distance = TREE_SCALE;
if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) {
distance = glm::distance(_mouseRayOrigin,
static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition());
static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition());
}
const float FIXED_MIN_EYE_DISTANCE = 0.3f;
float minEyeDistance = FIXED_MIN_EYE_DISTANCE + (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON ? 0.0f :
@ -1728,7 +1713,7 @@ void Application::updateMyAvatarLookAtPosition() {
eyePitch = _faceshift.getEstimatedEyePitch();
eyeYaw = _faceshift.getEstimatedEyeYaw();
trackerActive = true;
} else if (_visage.isActive()) {
eyePitch = _visage.getEstimatedEyePitch();
eyeYaw = _visage.getEstimatedEyeYaw();
@ -1901,9 +1886,9 @@ void Application::update(float deltaTime) {
_particles.update(); // update the particles...
_particleCollisionSystem.update(); // collide the particles...
_overlays.update(deltaTime);
// let external parties know we're updating
emit simulating(deltaTime);
}
@ -1927,7 +1912,7 @@ void Application::updateMyAvatar(float deltaTime) {
// actually need to calculate the view frustum planes to send these details
// to the server.
loadViewFrustum(_myCamera, _viewFrustum);
// Update my voxel servers with my current voxel query...
quint64 now = usecTimestampNow();
quint64 sinceLastQuery = now - _lastQueriedTime;
@ -2203,7 +2188,7 @@ void Application::updateShadowMap() {
}
center = inverseRotation * center;
glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius);
glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius);
glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius);
// stretch out our extents in z so that we get all of the avatars
minima.z -= _viewFrustum.getFarClip() * 0.5f;
@ -2224,7 +2209,7 @@ void Application::updateShadowMap() {
_shadowViewFrustum.setEyeOffsetPosition(glm::vec3());
_shadowViewFrustum.setEyeOffsetOrientation(glm::quat());
_shadowViewFrustum.calculate();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
@ -2279,19 +2264,19 @@ void Application::setupWorldLight() {
QImage Application::renderAvatarBillboard() {
_textureCache.getPrimaryFramebufferObject()->bind();
glDisable(GL_BLEND);
const int BILLBOARD_SIZE = 64;
renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
glEnable(GL_BLEND);
_textureCache.getPrimaryFramebufferObject()->release();
return image;
}
@ -2392,7 +2377,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
"Application::displaySide() ... metavoxels...");
_metavoxels.render();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... bucky balls...");
@ -2405,7 +2390,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
"Application::displaySide() ... particles...");
_particles.render();
}
// render the ambient occlusion effect if enabled
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
@ -2449,7 +2434,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
// give external parties a change to hook in
emit renderingInWorldInterface();
// render JS/scriptable overlays
_overlays.render3D();
}
@ -2498,7 +2483,7 @@ void Application::displayOverlay() {
renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude());
}
}
// Audio Scope
const int AUDIO_SCOPE_Y_OFFSET = 135;
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
@ -2507,7 +2492,7 @@ void Application::displayOverlay() {
_audioScope.render(MIRROR_VIEW_LEFT_PADDING, oscilloscopeTop);
}
}
// Audio VU Meter and Mute Icon
const int MUTE_ICON_SIZE = 24;
const int AUDIO_METER_INSET = 2;
@ -2516,7 +2501,7 @@ void Application::displayOverlay() {
const int AUDIO_METER_HEIGHT = 8;
const int AUDIO_METER_Y_GAP = 8;
const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET;
int audioMeterY;
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_Y_GAP;
@ -2524,8 +2509,8 @@ void Application::displayOverlay() {
audioMeterY = AUDIO_METER_Y_GAP;
}
_audio.renderMuteIcon(MIRROR_VIEW_LEFT_PADDING, audioMeterY);
const float AUDIO_METER_BLUE[] = {0.0, 0.0, 1.0};
const float AUDIO_METER_GREEN[] = {0.0, 1.0, 0.0};
const float AUDIO_METER_RED[] = {1.0, 0.0, 0.0};
@ -2627,8 +2612,8 @@ void Application::displayOverlay() {
char frameTimer[10];
quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000));
int timerBottom =
(Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
int timerBottom =
(Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth))
? 80 : 20;
drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 1.0f, 0.f, frameTimer, WHITE_TEXT);
@ -2643,7 +2628,7 @@ void Application::displayOverlay() {
void Application::displayStatsBackground(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 >> 16) & 0xff) / 255.0f,
((rgba >> 8) & 0xff) / 255.0f,
(rgba & 0xff) / 255.0f);
glVertex3f(x, y, 0);
@ -2651,7 +2636,7 @@ void Application::displayStatsBackground(unsigned int rgba, int x, int y, int wi
glVertex3f(x + width, y + height, 0);
glVertex3f(x , y + height, 0);
glEnd();
glColor4f(1, 1, 1, 1);
glColor4f(1, 1, 1, 1);
}
// display expanded or contracted stats
@ -2745,12 +2730,12 @@ void Application::displayStats() {
(float) (_audio.getNetworkBufferLengthSamplesPerChannel() + (float) _audio.getJitterBufferSamples()) /
(float)_audio.getNetworkSampleRate() * 1000.f);
drawText(30, _glWidget->height() - 22, 0.10f, 0.f, 2.f, audioJitter, WHITE_TEXT);
char audioPing[30];
sprintf(audioPing, "Audio ping: %d", pingAudio);
char avatarPing[30];
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
char voxelAvgPing[30];
@ -2788,7 +2773,7 @@ void Application::displayStats() {
} else {
// longhand way
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];
@ -2814,7 +2799,7 @@ void Application::displayStats() {
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, WHITE_TEXT);
stringstream downloadStats;
downloadStats << "Downloads: ";
foreach (Resource* resource, ResourceCache::getLoadingRequests()) {
@ -2822,7 +2807,7 @@ void Application::displayStats() {
downloadStats << roundf(resource->getProgress() * MAXIMUM_PERCENTAGE) << "% ";
}
downloadStats << "(" << ResourceCache::getPendingRequestCount() << " pending)";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, downloadStats.str().c_str(), WHITE_TEXT);
}
@ -2842,7 +2827,7 @@ void Application::displayStats() {
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT);
voxelStats.str("");
voxelStats <<
voxelStats <<
"Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
"VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB";
if (_voxels.hasVoxelMemoryUsageGPU()) {
@ -2894,7 +2879,7 @@ void Application::displayStats() {
totalNodes += stats.getTotalElements();
if (_statsExpanded) {
totalInternal += stats.getTotalInternal();
totalLeaves += stats.getTotalLeaves();
totalLeaves += stats.getTotalLeaves();
}
}
if (_statsExpanded) {
@ -2918,7 +2903,7 @@ void Application::displayStats() {
QString packetsString = locale.toString((int)voxelPacketsToProcess);
QString maxString = locale.toString((int)_recentMaxPackets);
voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString)
<< " [Recent Max: " << qPrintable(maxString) << "]";
<< " [Recent Max: " << qPrintable(maxString) << "]";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT);
}
@ -3080,12 +3065,12 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees
_mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale());
_mirrorCamera.setTargetPosition(_myAvatar->getPosition());
} else if (_rearMirrorTools->getZoomLevel() == BODY) {
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
_mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
_mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
} else { // HEAD zoom level
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees
_mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
@ -3100,7 +3085,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
}
}
_mirrorCamera.setAspectRatio((float)region.width() / region.height());
_mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
@ -3145,7 +3130,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
if (!billboard) {
_rearMirrorTools->render(false);
}
// reset Viewport and projection matrix
glViewport(0, 0, _glWidget->width(), _glWidget->height());
glDisable(GL_SCISSOR_TEST);
@ -3365,14 +3350,14 @@ void Application::setMenuShortcutsEnabled(bool enabled) {
}
void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance();
QString username = AccountManager::getInstance().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString()
+ " @ " + nodeList->getDomainInfo().getHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title);
}
@ -3387,7 +3372,7 @@ void Application::domainChanged(const QString& domainHostname) {
_voxelServerJurisdictions.clear();
_octreeServerSceneStats.clear();
_particleServerJurisdictions.clear();
// reset the particle renderer
_particles.clear();
@ -3397,12 +3382,12 @@ void Application::domainChanged(const QString& domainHostname) {
void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
// update our domain-server with the data-server we're logged in with
QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}";
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8());
}
@ -3567,13 +3552,13 @@ void Application::loadScripts() {
// loads all saved scripts
QSettings* settings = new QSettings(this);
int size = settings->beginReadArray("Settings");
for (int i = 0; i < size; ++i){
settings->setArrayIndex(i);
QString string = settings->value("script").toString();
loadScript(string);
}
settings->endArray();
}
@ -3588,42 +3573,72 @@ void Application::saveScripts() {
// saves all current running scripts
QSettings* settings = new QSettings(this);
settings->beginWriteArray("Settings");
for (int i = 0; i < _activeScripts.size(); ++i){
for (int i = 0; i < getRunningScripts().size(); ++i){
settings->setArrayIndex(i);
settings->setValue("script", _activeScripts.at(i));
settings->setValue("script", getRunningScripts().at(i));
}
settings->endArray();
}
void Application::stopAllScripts() {
// stops all current running scripts
QList<QAction*> scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions();
foreach (QAction* scriptAction, scriptActions) {
scriptAction->activate(QAction::Trigger);
qDebug() << "stopping script..." << scriptAction->text();
for (int i = 0; i < _scriptEnginesHash.size(); ++i) {
_scriptEnginesHash.values().at(i)->stop();
qDebug() << "stopping script..." << getRunningScripts().at(i);
}
_activeScripts.clear();
_scriptEnginesHash.clear();
_runningScriptsWidget->setRunningScripts(getRunningScripts());
}
void Application::stopScript(const QString &scriptName)
{
_scriptEnginesHash.value(scriptName)->stop();
qDebug() << "stopping script..." << scriptName;
_scriptEnginesHash.remove(scriptName);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
}
void Application::reloadAllScripts() {
// remember all the current scripts so we can reload them
QStringList reloadList = _activeScripts;
QStringList reloadList = getRunningScripts();
// reloads all current running scripts
QList<QAction*> scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions();
foreach (QAction* scriptAction, scriptActions) {
scriptAction->activate(QAction::Trigger);
qDebug() << "stopping script..." << scriptAction->text();
}
stopAllScripts();
// NOTE: we don't need to clear the _activeScripts list because that is handled on script shutdown.
foreach (QString scriptName, reloadList){
qDebug() << "reloading script..." << scriptName;
loadScript(scriptName);
}
}
void Application::toggleRunningScriptsWidget()
{
if (!_runningScriptsWidget->toggleViewAction()->isChecked()) {
_runningScriptsWidget->move(_window->geometry().topLeft().x(), _window->geometry().topLeft().y());
_runningScriptsWidget->resize(0, _window->height());
_runningScriptsWidget->toggleViewAction()->trigger();
_runningScriptsWidget->grabKeyboard();
QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget);
slideAnimation->setStartValue(_runningScriptsWidget->geometry());
slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(),
310, _runningScriptsWidget->height()));
slideAnimation->setDuration(250);
slideAnimation->start(QAbstractAnimation::DeleteWhenStopped);
} else {
_runningScriptsWidget->releaseKeyboard();
QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget);
slideAnimation->setStartValue(_runningScriptsWidget->geometry());
slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(),
0, _runningScriptsWidget->height()));
slideAnimation->setDuration(250);
slideAnimation->start(QAbstractAnimation::DeleteWhenStopped);
QTimer::singleShot(260, _runningScriptsWidget->toggleViewAction(), SLOT(trigger()));
}
}
void Application::uploadFST(bool isHead) {
FstReader reader(isHead);
if (reader.zip()) {
@ -3639,29 +3654,17 @@ void Application::uploadSkeleton() {
uploadFST(false);
}
void Application::removeScriptName(const QString& fileNameString) {
_activeScripts.removeOne(fileNameString);
}
void Application::cleanupScriptMenuItem(const QString& scriptMenuName) {
Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName);
}
void Application::loadScript(const QString& scriptName) {
// start the script on a new thread...
bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself
ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), wantMenuItems, &_controllerScriptingInterface);
ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface);
_scriptEnginesHash.insert(scriptName, scriptEngine);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
return;
}
_activeScripts.append(scriptName);
// add a stop menu item
Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(),
scriptEngine->getScriptMenuName(), 0, scriptEngine, SLOT(stop()));
_runningScriptsWidget->setRunningScripts(getRunningScripts());
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application.
@ -3669,7 +3672,7 @@ void Application::loadScript(const QString& scriptName) {
scriptEngine->getVoxelsScriptingInterface()->setVoxelTree(_voxels.getTree());
scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
// hook our avatar object into this script engine
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
@ -3694,8 +3697,6 @@ void Application::loadScript(const QString& scriptName) {
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(removeScriptName(const QString&)));
connect(scriptEngine, SIGNAL(cleanupMenuItem(const QString&)), this, SLOT(cleanupScriptMenuItem(const QString&)));
// when the application is about to quit, stop our script engine so it unwinds properly
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
@ -3719,12 +3720,12 @@ void Application::loadDialog() {
suggestedName = _previousScriptLocation;
}
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName,
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName,
tr("JavaScript Files (*.js)"));
if (!fileNameString.isEmpty()) {
_previousScriptLocation = fileNameString;
}
loadScript(fileNameString);
}
@ -3735,7 +3736,7 @@ void Application::loadScriptURLDialog() {
scriptURLDialog.setLabelText("Script:");
scriptURLDialog.setWindowFlags(Qt::Sheet);
const float DIALOG_RATIO_OF_WINDOW = 0.30f;
scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
scriptURLDialog.size().height());
int dialogReturn = scriptURLDialog.exec();
@ -3773,29 +3774,29 @@ void Application::checkVersion() {
}
void Application::parseVersionXml() {
#ifdef Q_OS_WIN32
QString operatingSystem("win");
#endif
#ifdef Q_OS_MAC
QString operatingSystem("mac");
#endif
#ifdef Q_OS_LINUX
QString operatingSystem("ubuntu");
#endif
QString releaseDate;
QString releaseNotes;
QString latestVersion;
QUrl downloadUrl;
QObject* sender = QObject::sender();
QXmlStreamReader xml(qobject_cast<QNetworkReply*>(sender));
while (!xml.atEnd() && !xml.hasError()) {
QXmlStreamReader::TokenType token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
if (xml.name() == "ReleaseDate") {
xml.readNext();

View file

@ -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,8 +264,10 @@ public slots:
void toggleLogDialog();
void initAvatarAndViewFrustum();
void stopAllScripts();
void stopScript(const QString& scriptName);
void reloadAllScripts();
void toggleRunningScriptsWidget();
void uploadFST(bool isHead);
void uploadHead();
void uploadSkeleton();
@ -268,24 +275,21 @@ public slots:
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);
@ -355,7 +359,7 @@ private:
bool _statsExpanded;
BandwidthMeter _bandwidthMeter;
QThread* _nodeThread;
DatagramProcessor _datagramProcessor;
@ -374,7 +378,7 @@ private:
timeval _lastTimeUpdated;
bool _justStarted;
Stars _stars;
BuckyBalls _buckyBalls;
VoxelSystem _voxels;
@ -409,7 +413,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
@ -493,10 +496,13 @@ private:
void displayUpdateDialog();
bool shouldSkipVersion(QString latestVersion);
void takeSnapshot();
TouchEvent _lastTouchEvent;
Overlays _overlays;
RunningScriptsWidget* _runningScriptsWidget;
QHash<QString, ScriptEngine*> _scriptEnginesHash;
};
#endif /* defined(__interface__Application__) */

View file

@ -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,
@ -148,7 +148,6 @@ Menu::Menu() :
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
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()));
@ -173,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");
@ -208,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,
@ -283,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,
@ -334,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()));
@ -367,14 +366,13 @@ Menu::Menu() :
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);
@ -578,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);
@ -630,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);
@ -684,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);
@ -744,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);
@ -756,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);
@ -820,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();
}
@ -871,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);
}
@ -904,7 +910,7 @@ void Menu::goToDomainDialog() {
// the user input a new hostname, use that
newHostname = domainDialog.textValue();
}
goToDomain(newHostname);
}
@ -919,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();
}
@ -1077,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());
@ -1085,7 +1130,7 @@ void Menu::toggleLoginMenuItem() {
} else {
// change the menu item to login
_loginAction->setText("Login");
connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain);
}
}
@ -1109,11 +1154,6 @@ void Menu::showMetavoxelEditor() {
}
void Menu::showChat() {
if (!_chatAction->isEnabled()) {
// Don't do anything if chat is disabled (No
// QXMPP library or xmpp is disconnected).
return;
}
QMainWindow* mainWindow = Application::getInstance()->getWindow();
if (!_chatWindow) {
mainWindow->addDockWidget(Qt::NoDockWidgetArea, _chatWindow = new ChatWindow());
@ -1192,7 +1232,7 @@ QString Menu::getLODFeedbackText() {
} break;
}
// distance feedback
// distance feedback
float voxelSizeScale = getVoxelSizeScale();
float relativeToDefault = voxelSizeScale / DEFAULT_OCTREE_SIZE_SCALE;
QString result;
@ -1207,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;
@ -1219,7 +1259,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
_fastFPSAverage.updateAverage(currentFPS);
quint64 now = usecTimestampNow();
const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000;
if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) {
if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) {
@ -1238,11 +1278,11 @@ void Menu::autoAdjustLOD(float currentFPS) {
_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;
@ -1255,7 +1295,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) {
@ -1266,7 +1306,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();
@ -1344,13 +1384,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()));
}
@ -1359,9 +1399,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;
@ -1468,7 +1508,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;
@ -1520,7 +1560,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()) {
@ -1534,13 +1574,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,

View file

@ -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;
@ -288,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";

View file

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

View file

@ -192,7 +192,6 @@ private:
bool _initialized;
QScopedPointer<Texture> _billboardTexture;
bool _shouldRenderBillboard;
bool _modelsDirty;
void renderBillboard();

View file

@ -19,10 +19,7 @@ FaceModel::FaceModel(Head* owningHead) :
}
void FaceModel::simulate(float deltaTime) {
QVector<JointState> newJointStates = updateGeometry();
if (!isActive()) {
return;
}
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) {

View file

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

View file

@ -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) {
@ -674,6 +677,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 +708,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 +894,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 +908,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 +923,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

View file

@ -121,6 +121,7 @@ private:
bool _isThrustOn;
float _thrustMultiplier;
glm::vec3 _moveTarget;
glm::vec3 _lastBodyPenetration;
int _moveTargetStepCounter;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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__) */

View file

@ -0,0 +1,320 @@
//
// ModelsBrowser.cpp
// hifi
//
// Created by Clement on 3/17/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QDialog>
#include <QDialogButtonBox>
#include <QEventLoop>
#include <QGridLayout>
#include <QHeaderView>
#include <QMessageBox>
#include <QStringListModel>
#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";
enum ModelMetaData {
NAME,
CREATOR,
UPLOAD_DATE,
TYPE,
GENDER,
MODEL_METADATA_COUNT
};
static const QString propertiesNames[MODEL_METADATA_COUNT] = {
"Name",
"Creator",
"Upload Date",
"Type",
"Gender"
};
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
QWidget(parent),
_handler(new ModelHandler(modelsType))
{
// 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))) {
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::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)
{
connect(&_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
// set headers data
QStringList headerData;
for (int i = 0; i < MODEL_METADATA_COUNT; ++i) {
headerData << propertiesNames[i];
}
_model.setHorizontalHeaderLabels(headerData);
}
void ModelHandler::download() {
// Query models list
queryNewFiles();
_lock.lockForWrite();
if (_initiateExit) {
_lock.unlock();
return;
}
// Show loading message
QStandardItem* loadingItem = new QStandardItem(LOADING_MSG);
loadingItem->setEnabled(false);
_model.appendRow(loadingItem);
_lock.unlock();
}
void ModelHandler::update() {
// Will be implemented in my next PR
}
void ModelHandler::exit() {
_lock.lockForWrite();
_initiateExit = true;
// Disconnect everything
_downloader.disconnect();
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() {
if (_downloader.getData().startsWith("<?xml")) {
parseXML(_downloader.getData());
} else {
qDebug() << _downloader.getData();
}
}
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);
_downloader.download(url);
}
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);
// Rand properties for now (Will be taken out in the next PR)
static QString creator[] = {"Ryan", "Philip", "Andzrej"};
static QString type[] = {"human", "beast", "pet", "elfe"};
static QString gender[] = {"male", "female", "none"};
model << new QStandardItem(creator[randIntInRange(0, 2)]);
model << new QStandardItem(QDate(randIntInRange(2013, 2014),
randIntInRange(1, 12),
randIntInRange(1, 30)).toString());
model << new QStandardItem(type[randIntInRange(0, 3)]);
model << new QStandardItem(gender[randIntInRange(0, 2)]);
////////////////////////////////////////////////////////////
_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) {
qDebug() << "Emitting...";
emit doneDownloading();
}
return true;
}

View file

@ -0,0 +1,80 @@
//
// 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 <QAbstractTableModel>
#include <QStandardItem>
#include <QTreeView>
#include <QVector>
#include <QReadWriteLock>
#include "FileDownloader.h"
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 doneUpdating();
public slots:
void download();
void update();
void exit();
private slots:
void downloadFinished();
private:
bool _initiateExit;
ModelType _type;
FileDownloader _downloader;
QReadWriteLock _lock;
QStandardItemModel _model;
void queryNewFiles(QString marker = QString());
bool parseXML(QByteArray xmlFile);
};
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);
private:
ModelHandler* _handler;
QTreeView _view;
};
#endif /* defined(__hifi__ModelBrowser__) */

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

View 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__) */

View 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, &quot;DejaVu Sans&quot;; </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View file

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

View file

@ -242,6 +242,8 @@ protected:
bool _isChatCirclingEnabled;
bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar
HeadData* _headData;
HandData* _handData;

View file

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

View file

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

View file

@ -14,14 +14,25 @@
#include "FileDownloader.h"
FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) :
QObject(parent),
_done(false)
{
connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*)));
FileDownloader::FileDownloader(QObject* parent) : QObject(parent) {
connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(processReply(QNetworkReply*)));
}
void FileDownloader::download(const QUrl& dataURL, QNetworkAccessManager::Operation operation) {
QNetworkRequest request(dataURL);
_networkAccessManager.get(request);
_downloadedData.clear();
switch (operation) {
case QNetworkAccessManager::GetOperation:
_networkAccessManager.get(request);
break;
case QNetworkAccessManager::HeadOperation:
_networkAccessManager.head(request);
break;
default:
emit done(QNetworkReply::ProtocolInvalidOperationError);
break;
}
}
void FileDownloader::processReply(QNetworkReply *reply) {
@ -30,36 +41,5 @@ void FileDownloader::processReply(QNetworkReply *reply) {
}
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();
}

View file

@ -13,31 +13,28 @@
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QThread>
class FileDownloader : public QObject {
Q_OBJECT
public:
FileDownloader(const QUrl dataURL, QObject* parent = NULL);
void waitForFile(int timeout = 0);
FileDownloader(QObject* parent = NULL);
QByteArray getData() const { return _downloadedData; }
bool done() { return _done; }
static QByteArray download(const QUrl dataURL, int timeout = 0);
signals:
void done(QNetworkReply::NetworkError error);
public slots:
void download(const QUrl& dataURL, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation);
private slots:
void processReply(QNetworkReply* reply);
private:
QNetworkAccessManager _networkAccessManager;
QByteArray _downloadedData;
bool _done;
};