mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-13 23:46:29 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels
Conflicts: interface/interface_en.ts
This commit is contained in:
commit
22710af111
35 changed files with 1788 additions and 1037 deletions
|
@ -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><b>Import</b> %1 as voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/ImportDialog.cpp" line="27"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
7
interface/resources/images/kill-script.svg
Normal file
7
interface/resources/images/kill-script.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 11 11" enable-background="new 0 0 11 11" xml:space="preserve">
|
||||
<polygon fill="#C4C4C4" points="11,1.9 9.2,0 5.5,3.7 1.9,0 0,1.9 3.7,5.5 0,9.2 1.9,11 5.5,7.4 9.2,11 11,9.2 7.4,5.5 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 564 B |
24
interface/resources/images/reload.svg
Normal file
24
interface/resources/images/reload.svg
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 19.2 16" enable-background="new 0 0 19.2 16" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#F9F9F9" d="M13.1,13c-1.1,0.7-2.3,1.1-3.6,1.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.1,0-0.2,0c-0.1,0-0.3,0-0.4-0.1
|
||||
c-0.1,0-0.2,0-0.2-0.1c-0.1,0-0.2-0.1-0.4-0.1c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.3-0.1-0.4-0.2c0,0-0.1,0-0.1,0
|
||||
c-0.2-0.1-0.3-0.1-0.5-0.2c0,0,0,0,0,0c-0.5-0.3-1-0.6-1.4-1c0,0,0,0,0,0c-0.1-0.1-0.2-0.3-0.4-0.4c0,0,0-0.1-0.1-0.1
|
||||
C4,10.8,3.5,9.5,3.5,8h1.6L2.6,4.2L0,8h1.6c0,1.7,0.5,3.3,1.4,4.5c0,0,0,0,0,0.1c0.1,0.1,0.2,0.3,0.3,0.4c0,0,0.1,0.1,0.1,0.1
|
||||
c0.1,0.2,0.3,0.3,0.5,0.5c0,0,0,0,0,0c0.5,0.5,1.1,1,1.8,1.4c0,0,0,0,0.1,0c0.2,0.1,0.4,0.2,0.6,0.3c0,0,0.1,0,0.1,0.1
|
||||
c0.2,0.1,0.3,0.1,0.5,0.2c0.1,0,0.2,0.1,0.2,0.1c0.2,0,0.3,0.1,0.5,0.1c0.1,0,0.2,0.1,0.3,0.1c0,0,0.1,0,0.1,0
|
||||
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.3,0,0.5,0,0.8,0c1.6,0,3.2-0.5,4.6-1.5c0.4-0.3,0.5-0.9,0.2-1.3
|
||||
C14.2,12.8,13.6,12.7,13.1,13z"/>
|
||||
<path fill="#F9F9F9" d="M17.6,8c0-1.7-0.5-3.2-1.4-4.5c0,0,0,0,0-0.1C16.1,3.3,16,3.1,15.8,3c0,0,0,0,0-0.1c-0.8-1-1.8-1.8-3-2.2
|
||||
c0,0-0.1,0-0.1,0c-0.2-0.1-0.4-0.1-0.6-0.2c-0.1,0-0.1,0-0.2-0.1c-0.2-0.1-0.3-0.1-0.5-0.1c-0.1,0-0.2,0-0.3-0.1c0,0-0.1,0-0.1,0
|
||||
c-0.1,0-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0c-0.2,0-0.4,0-0.6,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0C8,0,6.4,0.5,5,1.4
|
||||
C4.6,1.8,4.5,2.4,4.8,2.8C5.1,3.2,5.7,3.3,6.1,3c1.1-0.7,2.3-1.1,3.5-1.1c0.2,0,0.4,0,0.5,0c0.1,0,0.1,0,0.2,0
|
||||
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.1,0,0.3,0.1,0.4,0.1c0,0,0.1,0,0.1,0c0.2,0.1,0.3,0.1,0.5,0.2c0,0,0,0,0,0
|
||||
c0.9,0.4,1.7,1,2.3,1.7c0,0,0,0,0,0c0.9,1.1,1.4,2.4,1.4,3.9h-1.6l2.6,3.8L19.2,8H17.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
7
interface/resources/images/stop.svg
Normal file
7
interface/resources/images/stop.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 14 14" enable-background="new 0 0 14 14" xml:space="preserve">
|
||||
<rect fill="#F9F9F9" width="14" height="14"/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
|
@ -1,5 +1,8 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/close.svg</file>
|
||||
<file>images/kill-script.svg</file>
|
||||
<file>images/reload.svg</file>
|
||||
<file>images/stop.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -56,8 +56,7 @@ Avatar::Avatar() :
|
|||
_owningAvatarMixer(),
|
||||
_collisionFlags(0),
|
||||
_initialized(false),
|
||||
_shouldRenderBillboard(true),
|
||||
_modelsDirty(true)
|
||||
_shouldRenderBillboard(true)
|
||||
{
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
|
@ -118,21 +117,24 @@ void Avatar::simulate(float deltaTime) {
|
|||
getHand()->simulate(deltaTime, false);
|
||||
_skeletonModel.setLODDistance(getLODDistance());
|
||||
|
||||
// copy joint data to skeleton
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
_skeletonModel.setJointState(i, data.valid, data.rotation);
|
||||
}
|
||||
glm::vec3 headPosition = _position;
|
||||
if (!_shouldRenderBillboard && inViewFrustum) {
|
||||
_skeletonModel.simulate(deltaTime, _modelsDirty);
|
||||
_modelsDirty = false;
|
||||
if (_hasNewJointRotations) {
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
_skeletonModel.setJointState(i, data.valid, data.rotation);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
}
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
|
||||
_hasNewJointRotations = false;
|
||||
|
||||
glm::vec3 headPosition = _position;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
|
||||
// use speed and angular velocity to determine walking vs. standing
|
||||
if (_speed + fabs(_bodyYawDelta) > 0.2) {
|
||||
|
@ -210,11 +212,19 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(renderMode);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
|
||||
_skeletonModel.renderCollisionProxies(0.7f);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
|
||||
getHead()->getFaceModel().renderCollisionProxies(0.7f);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
}
|
||||
|
||||
// quick check before falling into the code below:
|
||||
|
@ -653,9 +663,6 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
|||
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
|
||||
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
|
||||
|
||||
// note that we need to update our models
|
||||
_modelsDirty = true;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
|
|
@ -192,7 +192,6 @@ private:
|
|||
bool _initialized;
|
||||
QScopedPointer<Texture> _billboardTexture;
|
||||
bool _shouldRenderBillboard;
|
||||
bool _modelsDirty;
|
||||
|
||||
void renderBillboard();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -121,6 +121,7 @@ private:
|
|||
bool _isThrustOn;
|
||||
float _thrustMultiplier;
|
||||
glm::vec3 _moveTarget;
|
||||
glm::vec3 _lastBodyPenetration;
|
||||
int _moveTargetStepCounter;
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include "Menu.h"
|
||||
#include "SkeletonModel.h"
|
||||
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||
_owningAvatar(owningAvatar) {
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
}
|
||||
|
||||
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
|
||||
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
|
||||
if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) {
|
||||
return;
|
||||
}
|
||||
if (jointIndex == getLeftHandJointIndex()
|
||||
|
@ -75,16 +75,16 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
|
|||
int parentIndex = joint.parentIndex;
|
||||
if (i == jointIndex) {
|
||||
// this shape is the hand
|
||||
shapes.push_back(_shapes[i]);
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
if (parentIndex != -1) {
|
||||
// also add the forearm
|
||||
shapes.push_back(_shapes[parentIndex]);
|
||||
shapes.push_back(_jointShapes[parentIndex]);
|
||||
}
|
||||
} else {
|
||||
while (parentIndex != -1) {
|
||||
if (parentIndex == jointIndex) {
|
||||
// this shape is a child of the hand
|
||||
shapes.push_back(_shapes[i]);
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
break;
|
||||
}
|
||||
parentIndex = geometry.joints[parentIndex].parentIndex;
|
||||
|
@ -94,6 +94,12 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
|
|||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::getBodyShapes(QVector<const Shape*>& shapes) const {
|
||||
// for now we push a single bounding shape,
|
||||
// but later we could push a subset of joint shapes
|
||||
shapes.push_back(&_boundingShape);
|
||||
}
|
||||
|
||||
class IndexValue {
|
||||
public:
|
||||
int index;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#ifndef __interface__SkeletonModel__
|
||||
#define __interface__SkeletonModel__
|
||||
|
||||
|
||||
#include "renderer/Model.h"
|
||||
|
||||
class Avatar;
|
||||
|
@ -28,8 +27,11 @@ public:
|
|||
/// \param shapes[out] list in which is stored pointers to hand shapes
|
||||
void getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const;
|
||||
|
||||
/// \param shapes[out] list of shapes for body collisions
|
||||
void getBodyShapes(QVector<const Shape*>& shapes) const;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
void applyHandPosition(int jointIndex, const glm::vec3& position);
|
||||
|
||||
void applyPalmData(int jointIndex, const QVector<int>& fingerJointIndices,
|
||||
|
|
|
@ -41,6 +41,11 @@ bool Extents::containsPoint(const glm::vec3& point) const {
|
|||
&& point.z >= minimum.z && point.z <= maximum.z);
|
||||
}
|
||||
|
||||
void Extents::addExtents(const Extents& extents) {
|
||||
minimum = glm::min(minimum, extents.minimum);
|
||||
maximum = glm::max(maximum, extents.maximum);
|
||||
}
|
||||
|
||||
void Extents::addPoint(const glm::vec3& point) {
|
||||
minimum = glm::min(minimum, point);
|
||||
maximum = glm::max(maximum, point);
|
||||
|
@ -1343,7 +1348,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
}
|
||||
|
||||
geometry.bindExtents.reset();
|
||||
geometry.staticExtents.reset();
|
||||
geometry.meshExtents.reset();
|
||||
|
||||
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
|
||||
|
@ -1511,8 +1515,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
|
||||
jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd));
|
||||
|
||||
bool jointIsStatic = joint.freeLineage.isEmpty();
|
||||
glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||
float totalWeight = 0.0f;
|
||||
for (int j = 0; j < cluster.indices.size(); j++) {
|
||||
int oldIndex = cluster.indices.at(j);
|
||||
|
@ -1534,10 +1536,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
jointShapeInfo.extents.addPoint(vertexInJointFrame);
|
||||
jointShapeInfo.averageVertex += vertexInJointFrame;
|
||||
++jointShapeInfo.numVertices;
|
||||
if (jointIsStatic) {
|
||||
// expand the extents of static (nonmovable) joints
|
||||
geometry.staticExtents.addPoint(vertex + jointTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
// look for an unused slot in the weights vector
|
||||
|
|
|
@ -30,6 +30,10 @@ public:
|
|||
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
|
||||
void reset();
|
||||
|
||||
/// \param extents another intance of extents
|
||||
/// expand current limits to contain other extents
|
||||
void addExtents(const Extents& extents);
|
||||
|
||||
/// \param point new point to compare against existing limits
|
||||
/// compare point to current limits and expand them if necessary to contain point
|
||||
void addPoint(const glm::vec3& point);
|
||||
|
@ -174,7 +178,6 @@ public:
|
|||
glm::vec3 neckPivot;
|
||||
|
||||
Extents bindExtents;
|
||||
Extents staticExtents;
|
||||
Extents meshExtents;
|
||||
|
||||
QVector<FBXAttachment> attachments;
|
||||
|
|
|
@ -32,9 +32,11 @@ Model::Model(QObject* parent) :
|
|||
QObject(parent),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.f),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.f),
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f),
|
||||
_boundingRadius(0.f) {
|
||||
_pupilDilation(0.0f) {
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
}
|
||||
|
@ -54,6 +56,14 @@ Model::SkinLocations Model::_skinLocations;
|
|||
Model::SkinLocations Model::_skinNormalMapLocations;
|
||||
Model::SkinLocations Model::_skinShadowLocations;
|
||||
|
||||
void Model::setScale(const glm::vec3& scale) {
|
||||
glm::vec3 deltaScale = _scale - scale;
|
||||
if (glm::length2(deltaScale) > EPSILON) {
|
||||
_scale = scale;
|
||||
rebuildShapes();
|
||||
}
|
||||
}
|
||||
|
||||
void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
|
||||
program.bind();
|
||||
locations.clusterMatrices = program.uniformLocation("clusterMatrices");
|
||||
|
@ -73,6 +83,44 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
state.rotation = joint.rotation;
|
||||
jointStates.append(state);
|
||||
}
|
||||
|
||||
// compute transforms
|
||||
// Unfortunately, the joints are not neccessarily in order from parents to children,
|
||||
// so we must iterate over the list multiple times until all are set correctly.
|
||||
QVector<bool> jointIsSet;
|
||||
int numJoints = jointStates.size();
|
||||
jointIsSet.fill(false, numJoints);
|
||||
int numJointsSet = 0;
|
||||
int lastNumJointsSet = -1;
|
||||
while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) {
|
||||
lastNumJointsSet = numJointsSet;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
if (jointIsSet[i]) {
|
||||
continue;
|
||||
}
|
||||
JointState& state = jointStates[i];
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = _rotation * combinedRotation;
|
||||
++numJointsSet;
|
||||
jointIsSet[i] = true;
|
||||
} else if (jointIsSet[parentIndex]) {
|
||||
const JointState& parentState = jointStates.at(parentIndex);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = parentState.combinedRotation * combinedRotation;
|
||||
++numJointsSet;
|
||||
jointIsSet[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jointStates;
|
||||
}
|
||||
|
||||
|
@ -142,60 +190,95 @@ void Model::reset() {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _shapes.size(); ++i) {
|
||||
delete _shapes[i];
|
||||
}
|
||||
_shapes.clear();
|
||||
}
|
||||
|
||||
void Model::createCollisionShapes() {
|
||||
clearShapes();
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 meshCenter = _jointStates[i].combinedRotation * joint.shapePosition;
|
||||
glm::vec3 position = _rotation * (extractTranslation(_jointStates[i].transform) + uniformScale * meshCenter) + _translation;
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
if (joint.shapeType == Shape::CAPSULE_SHAPE) {
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
CapsuleShape* shape = new CapsuleShape(radius, halfHeight);
|
||||
shape->setPosition(position);
|
||||
_shapes.push_back(shape);
|
||||
} else {
|
||||
SphereShape* shape = new SphereShape(radius, position);
|
||||
_shapes.push_back(shape);
|
||||
bool Model::updateGeometry() {
|
||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||
bool needFullUpdate = false;
|
||||
for (int i = 0; i < _attachments.size(); i++) {
|
||||
Model* model = _attachments.at(i);
|
||||
if (model->updateGeometry()) {
|
||||
needFullUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _shapes.size() == _jointStates.size()) {
|
||||
_boundingRadius = 0.f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_shapes[i]->setPosition(worldPosition);
|
||||
_shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance2 = glm::distance2(worldPosition, _translation);
|
||||
if (distance2 > _boundingRadius) {
|
||||
_boundingRadius = distance2;
|
||||
bool needToRebuild = false;
|
||||
if (_nextGeometry) {
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
|
||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
||||
_nextGeometry->ensureLoading();
|
||||
if (_nextGeometry->isLoaded()) {
|
||||
applyNextGeometry();
|
||||
needToRebuild = true;
|
||||
}
|
||||
}
|
||||
if (!_geometry) {
|
||||
// geometry is not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
// NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above.
|
||||
// Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry.
|
||||
|
||||
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
|
||||
QVector<JointState> newJointStates = createJointStates(newGeometry);
|
||||
if (! _jointStates.isEmpty()) {
|
||||
// copy the existing joint states
|
||||
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
|
||||
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
|
||||
it != oldGeometry.jointIndices.constEnd(); it++) {
|
||||
int oldIndex = it.value() - 1;
|
||||
int newIndex = newGeometry.getJointIndex(it.key());
|
||||
if (newIndex != -1) {
|
||||
newJointStates[newIndex] = _jointStates.at(oldIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_geometry = geometry;
|
||||
_jointStates = newJointStates;
|
||||
needToRebuild = true;
|
||||
} else if (_jointStates.isEmpty()) {
|
||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||
if (fbxGeometry.joints.size() > 0) {
|
||||
_jointStates = createJointStates(fbxGeometry);
|
||||
needToRebuild = true;
|
||||
}
|
||||
_boundingRadius = sqrtf(_boundingRadius);
|
||||
_shapesAreDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
// update our LOD, then simulate
|
||||
simulate(deltaTime, fullUpdate, updateGeometry());
|
||||
_geometry->setLoadPriority(this, -_lodDistance);
|
||||
_geometry->ensureLoading();
|
||||
|
||||
if (needToRebuild) {
|
||||
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.append(state);
|
||||
|
||||
QOpenGLBuffer buffer;
|
||||
if (!mesh.blendshapes.isEmpty()) {
|
||||
buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
buffer.create();
|
||||
buffer.bind();
|
||||
buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||
buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(),
|
||||
mesh.normals.size() * sizeof(glm::vec3));
|
||||
buffer.release();
|
||||
}
|
||||
_blendedVertexBuffers.append(buffer);
|
||||
}
|
||||
foreach (const FBXAttachment& attachment, fbxGeometry.attachments) {
|
||||
Model* model = new Model(this);
|
||||
model->init();
|
||||
model->setURL(attachment.url);
|
||||
_attachments.append(model);
|
||||
}
|
||||
rebuildShapes();
|
||||
needFullUpdate = true;
|
||||
}
|
||||
return needFullUpdate;
|
||||
}
|
||||
|
||||
bool Model::render(float alpha, bool forShadowMap) {
|
||||
|
@ -264,15 +347,6 @@ Extents Model::getBindExtents() const {
|
|||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getStaticExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& staticExtents = _geometry->getFBXGeometry().staticExtents;
|
||||
Extents scaledExtents = { staticExtents.minimum * _scale, staticExtents.maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
|
@ -375,6 +449,107 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
|
|||
}
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
delete _jointShapes[i];
|
||||
}
|
||||
_jointShapes.clear();
|
||||
}
|
||||
|
||||
void Model::rebuildShapes() {
|
||||
clearShapes();
|
||||
|
||||
if (_jointStates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure all the joints are updated correctly before we try to create their shapes
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 rootPosition(0.f);
|
||||
|
||||
// joint shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
if (joint.shapeType == Shape::CAPSULE_SHAPE && halfHeight > EPSILON) {
|
||||
CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
|
||||
capsule->setPosition(worldPosition);
|
||||
capsule->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
_jointShapes.push_back(capsule);
|
||||
|
||||
glm::vec3 endPoint;
|
||||
capsule->getEndPoint(endPoint);
|
||||
glm::vec3 startPoint;
|
||||
capsule->getStartPoint(startPoint);
|
||||
glm::vec3 axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
} else {
|
||||
SphereShape* sphere = new SphereShape(radius, worldPosition);
|
||||
_jointShapes.push_back(sphere);
|
||||
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
}
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
}
|
||||
|
||||
// bounding shape
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis
|
||||
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
|
||||
float capsuleRadius = 0.25f * (diagonal.x + diagonal.z); // half the average of x and z
|
||||
_boundingShape.setRadius(capsuleRadius);
|
||||
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
|
||||
_boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition);
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) {
|
||||
glm::vec3 rootPosition(0.f);
|
||||
_boundingRadius = 0.f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_jointShapes[i]->setPosition(worldPosition);
|
||||
_jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance2 = glm::distance2(worldPosition, _translation);
|
||||
if (distance2 > _boundingRadius) {
|
||||
_boundingRadius = distance2;
|
||||
}
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
_boundingRadius = sqrtf(_boundingRadius);
|
||||
_shapesAreDirty = false;
|
||||
_boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
const glm::vec3 relativeOrigin = origin - _translation;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
@ -408,8 +583,8 @@ bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& co
|
|||
bool collided = false;
|
||||
for (int i = 0; i < shapes.size(); ++i) {
|
||||
const Shape* theirShape = shapes[i];
|
||||
for (int j = 0; j < _shapes.size(); ++j) {
|
||||
const Shape* ourShape = _shapes[j];
|
||||
for (int j = 0; j < _jointShapes.size(); ++j) {
|
||||
const Shape* ourShape = _jointShapes[j];
|
||||
if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) {
|
||||
collided = true;
|
||||
}
|
||||
|
@ -421,10 +596,9 @@ bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& co
|
|||
bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
|
||||
CollisionList& collisions, int skipIndex) {
|
||||
bool collided = false;
|
||||
updateShapePositions();
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
if (joint.parentIndex != -1) {
|
||||
if (skipIndex != -1) {
|
||||
|
@ -438,7 +612,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
|
|||
} while (ancestorIndex != -1);
|
||||
}
|
||||
}
|
||||
if (ShapeCollider::shapeShape(&sphere, _shapes[i], collisions)) {
|
||||
if (ShapeCollider::shapeShape(&sphere, _jointShapes[i], collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_type = MODEL_COLLISION;
|
||||
collision->_data = (void*)(this);
|
||||
|
@ -450,45 +624,6 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
|
|||
return collided;
|
||||
}
|
||||
|
||||
QVector<Model::JointState> Model::updateGeometry() {
|
||||
QVector<JointState> newJointStates;
|
||||
if (_nextGeometry) {
|
||||
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
|
||||
_nextGeometry->setLoadPriority(this, -_lodDistance);
|
||||
_nextGeometry->ensureLoading();
|
||||
if (_nextGeometry->isLoaded()) {
|
||||
applyNextGeometry();
|
||||
return newJointStates;
|
||||
}
|
||||
}
|
||||
if (!_geometry) {
|
||||
return newJointStates;
|
||||
}
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
if (!_jointStates.isEmpty()) {
|
||||
// copy the existing joint states
|
||||
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
|
||||
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
|
||||
newJointStates = createJointStates(newGeometry);
|
||||
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
|
||||
it != oldGeometry.jointIndices.constEnd(); it++) {
|
||||
int oldIndex = it.value() - 1;
|
||||
int newIndex = newGeometry.getJointIndex(it.key());
|
||||
if (newIndex != -1) {
|
||||
newJointStates[newIndex] = _jointStates.at(oldIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
_geometry = geometry;
|
||||
}
|
||||
_geometry->setLoadPriority(this, -_lodDistance);
|
||||
_geometry->ensureLoading();
|
||||
return newJointStates;
|
||||
}
|
||||
|
||||
class Blender : public QRunnable {
|
||||
public:
|
||||
|
||||
|
@ -551,53 +686,23 @@ void Blender::run() {
|
|||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
fullUpdate = updateGeometry() || fullUpdate;
|
||||
if (isActive() && fullUpdate) {
|
||||
simulateInternal(deltaTime);
|
||||
}
|
||||
|
||||
// set up world vertices on first simulate after load
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (_jointStates.isEmpty()) {
|
||||
_jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates;
|
||||
foreach (const FBXMesh& mesh, geometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.append(state);
|
||||
|
||||
QOpenGLBuffer buffer;
|
||||
if (!mesh.blendshapes.isEmpty()) {
|
||||
buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
buffer.create();
|
||||
buffer.bind();
|
||||
buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
|
||||
buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||
buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(),
|
||||
mesh.normals.size() * sizeof(glm::vec3));
|
||||
buffer.release();
|
||||
}
|
||||
_blendedVertexBuffers.append(buffer);
|
||||
}
|
||||
foreach (const FBXAttachment& attachment, geometry.attachments) {
|
||||
Model* model = new Model(this);
|
||||
model->init();
|
||||
model->setURL(attachment.url);
|
||||
_attachments.append(model);
|
||||
}
|
||||
fullUpdate = true;
|
||||
createCollisionShapes();
|
||||
}
|
||||
|
||||
// exit early if we don't have to perform a full update
|
||||
if (!fullUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||
// update the world space transforms for all joints
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
// update the attachment transforms and simulate them
|
||||
for (int i = 0; i < _attachments.size(); i++) {
|
||||
const FBXAttachment& attachment = geometry.attachments.at(i);
|
||||
|
@ -612,7 +717,9 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
|||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScale(_scale * attachment.scale);
|
||||
|
||||
model->simulate(deltaTime);
|
||||
if (model->isActive()) {
|
||||
model->simulateInternal(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _meshStates.size(); i++) {
|
||||
|
@ -631,7 +738,6 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>
|
|||
}
|
||||
|
||||
void Model::updateJointState(int index) {
|
||||
_shapesAreDirty = true;
|
||||
JointState& state = _jointStates[index];
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const FBXJoint& joint = geometry.joints.at(index);
|
||||
|
@ -643,7 +749,7 @@ void Model::updateJointState(int index) {
|
|||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
state.combinedRotation = _rotation * combinedRotation;
|
||||
|
||||
|
||||
} else {
|
||||
const JointState& parentState = _jointStates.at(joint.parentIndex);
|
||||
if (index == geometry.leanJointIndex) {
|
||||
|
@ -749,6 +855,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last
|
|||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||
updateJointState(freeLineage.at(j));
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -827,15 +934,15 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
|
|||
state.rotation = newRotation;
|
||||
}
|
||||
|
||||
void Model::renderCollisionProxies(float alpha) {
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
|
||||
void Model::renderJointCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
updateShapePositions();
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
glPushMatrix();
|
||||
|
||||
Shape* shape = _shapes[i];
|
||||
Shape* shape = _jointShapes[i];
|
||||
|
||||
if (shape->getType() == Shape::SPHERE_SHAPE) {
|
||||
// shapes are stored in world-frame, so we have to transform into model frame
|
||||
|
@ -878,6 +985,36 @@ void Model::renderCollisionProxies(float alpha) {
|
|||
glPopMatrix();
|
||||
}
|
||||
|
||||
void Model::renderBoundingCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
_boundingShape.getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
_boundingShape.getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.f);
|
||||
glColor4f(0.6f, 0.8f, 0.6f, alpha);
|
||||
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
|
||||
if (collision._type == MODEL_COLLISION) {
|
||||
// the joint is pokable by a collision if it exists and is free to move
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "ProgramObject.h"
|
||||
|
@ -34,7 +36,7 @@ public:
|
|||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
void setScale(const glm::vec3& scale) { _scale = scale; }
|
||||
void setScale(const glm::vec3& scale);
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
||||
void setOffset(const glm::vec3& offset) { _offset = offset; }
|
||||
|
@ -54,12 +56,9 @@ public:
|
|||
|
||||
void init();
|
||||
void reset();
|
||||
void clearShapes();
|
||||
void createCollisionShapes();
|
||||
void updateShapePositions();
|
||||
void simulate(float deltaTime, bool fullUpdate = true);
|
||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||
bool render(float alpha = 1.0f, bool forShadowMap = false);
|
||||
|
||||
|
||||
/// Sets the URL of the model to render.
|
||||
/// \param fallback the URL of a fallback model to render if the requested model fails to load
|
||||
/// \param retainCurrent if true, keep rendering the current model until the new one is loaded
|
||||
|
@ -75,9 +74,6 @@ public:
|
|||
/// Returns the extents of the model in its bind pose.
|
||||
Extents getBindExtents() const;
|
||||
|
||||
/// Returns the extents of the unmovable joints of the model.
|
||||
Extents getStaticExtents() const;
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
|
@ -159,6 +155,12 @@ public:
|
|||
/// Returns the extended length from the right hand to its first free ancestor.
|
||||
float getRightArmLength() const;
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void updateShapePositions();
|
||||
void renderJointCollisionShapes(float alpha);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// \param shapes list of pointers shapes to test against Model
|
||||
|
@ -169,8 +171,6 @@ public:
|
|||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skipIndex = -1);
|
||||
|
||||
void renderCollisionProxies(float alpha);
|
||||
|
||||
/// \param collision details about the collisions
|
||||
/// \return true if the collision is against a moveable joint
|
||||
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
|
||||
|
@ -180,6 +180,7 @@ public:
|
|||
void applyCollision(CollisionInfo& collision);
|
||||
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
@ -203,7 +204,11 @@ protected:
|
|||
|
||||
bool _shapesAreDirty;
|
||||
QVector<JointState> _jointStates;
|
||||
QVector<Shape*> _shapes;
|
||||
QVector<Shape*> _jointShapes;
|
||||
|
||||
float _boundingRadius;
|
||||
CapsuleShape _boundingShape;
|
||||
glm::vec3 _boundingShapeLocalOffset;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
|
@ -212,9 +217,11 @@ protected:
|
|||
|
||||
QVector<MeshState> _meshStates;
|
||||
|
||||
QVector<JointState> updateGeometry();
|
||||
void simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates);
|
||||
|
||||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
|
||||
void simulateInternal(float deltaTime);
|
||||
|
||||
/// Updates the state of the joint at the specified index.
|
||||
virtual void updateJointState(int index);
|
||||
|
||||
|
@ -248,6 +255,7 @@ private:
|
|||
void applyNextGeometry();
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, bool forShadowMap, bool translucent);
|
||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||
|
||||
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
|
||||
QSharedPointer<NetworkGeometry> _nextBaseGeometry;
|
||||
|
@ -267,8 +275,6 @@ private:
|
|||
|
||||
QVector<Model*> _attachments;
|
||||
|
||||
float _boundingRadius;
|
||||
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _normalMapProgram;
|
||||
static ProgramObject _shadowProgram;
|
||||
|
@ -291,7 +297,6 @@ private:
|
|||
static SkinLocations _skinShadowLocations;
|
||||
|
||||
static void initSkinProgram(ProgramObject& program, SkinLocations& locations);
|
||||
static QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QPointer<Model>)
|
||||
|
|
|
@ -42,6 +42,10 @@ ChatWindow::ChatWindow() :
|
|||
ui->messagesGridLayout->setColumnStretch(1, 3);
|
||||
|
||||
ui->messagePlainTextEdit->installEventFilter(this);
|
||||
|
||||
if (!AccountManager::getInstance().isLoggedIn()) {
|
||||
ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others."));
|
||||
}
|
||||
|
||||
#ifdef HAVE_QXMPP
|
||||
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
//
|
||||
// ModelBrowser.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QUrl>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QEventLoop>
|
||||
#include <QMessageBox>
|
||||
#include <QGridLayout>
|
||||
#include <QDialog>
|
||||
#include <QStringListModel>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include "ModelBrowser.h"
|
||||
|
||||
static const QString PREFIX_PARAMETER_NAME = "prefix";
|
||||
static const QString MARKER_PARAMETER_NAME = "marker";
|
||||
static const QString IS_TRUNCATED_NAME = "IsTruncated";
|
||||
static const QString CONTAINER_NAME = "Contents";
|
||||
static const QString KEY_NAME = "Key";
|
||||
|
||||
ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
url.setQuery(query);
|
||||
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
ModelBrowser::~ModelBrowser() {
|
||||
delete _downloader;
|
||||
}
|
||||
|
||||
void ModelBrowser::downloadFinished() {
|
||||
parseXML(_downloader->getData());
|
||||
}
|
||||
|
||||
void ModelBrowser::browse() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle("Browse models");
|
||||
|
||||
QGridLayout* layout = new QGridLayout(&dialog);
|
||||
dialog.setLayout(layout);
|
||||
|
||||
QLineEdit* searchBar = new QLineEdit(&dialog);
|
||||
layout->addWidget(searchBar, 0, 0);
|
||||
|
||||
ListView* listView = new ListView(&dialog);
|
||||
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
layout->addWidget(listView, 1, 0);
|
||||
listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&)));
|
||||
dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
|
||||
|
||||
QStringListModel* model = new QStringListModel(_models.keys(), listView);
|
||||
model->sort(0);
|
||||
listView->setModel(model);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttons, 2, 0);
|
||||
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
if (dialog.exec() == QDialog::Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString();
|
||||
|
||||
emit selected(_models[selectedKey]);
|
||||
}
|
||||
|
||||
bool ModelBrowser::parseXML(QByteArray xmlFile) {
|
||||
QXmlStreamReader xml(xmlFile);
|
||||
QRegExp rx(".*fst");
|
||||
bool truncated = false;
|
||||
QString lastKey;
|
||||
|
||||
// Read xml until the end or an error is detected
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
|
||||
// Let's check if there is more
|
||||
xml.readNext();
|
||||
if (xml.text().toString() == "True") {
|
||||
truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
|
||||
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
|
||||
// If a file is find, process it
|
||||
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
|
||||
xml.readNext();
|
||||
lastKey = xml.text().toString();
|
||||
if (rx.exactMatch(xml.text().toString())) {
|
||||
// Add the found file to the list
|
||||
_models.insert(QFileInfo(xml.text().toString()).baseName(),
|
||||
S3_URL + "/" + xml.text().toString());
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
}
|
||||
xml.readNext();
|
||||
}
|
||||
|
||||
// Error handling
|
||||
if(xml.hasError()) {
|
||||
_models.clear();
|
||||
QMessageBox::critical(this,
|
||||
"ModelBrowser::ModelBrowser()",
|
||||
xml.errorString(),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we didn't all the files, download the next ones
|
||||
if (truncated) {
|
||||
QUrl url(S3_URL);
|
||||
QUrlQuery query;
|
||||
|
||||
if (_type == Head) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
|
||||
} else if (_type == Skeleton) {
|
||||
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
|
||||
}
|
||||
query.addQueryItem(MARKER_PARAMETER_NAME, lastKey);
|
||||
url.setQuery(query);
|
||||
|
||||
delete _downloader;
|
||||
_downloader = new FileDownloader(url);
|
||||
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// ModelBrowser.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clement on 3/17/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__ModelBrowser__
|
||||
#define __hifi__ModelBrowser__
|
||||
|
||||
#include <FileDownloader.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListView>
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString HEAD_MODELS_LOCATION = "models/heads/";
|
||||
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
|
||||
|
||||
enum ModelType {
|
||||
Head,
|
||||
Skeleton
|
||||
};
|
||||
|
||||
class ModelBrowser : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelBrowser(ModelType modelType, QWidget* parent = NULL);
|
||||
~ModelBrowser();
|
||||
|
||||
signals:
|
||||
void selected(QString filename);
|
||||
|
||||
public slots:
|
||||
void browse();
|
||||
|
||||
private slots:
|
||||
void downloadFinished();
|
||||
|
||||
private:
|
||||
ModelType _type;
|
||||
FileDownloader* _downloader;
|
||||
QHash<QString, QString> _models;
|
||||
|
||||
bool parseXML(QByteArray xmlFile);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ListView : public QListView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ListView(QWidget* parent) : QListView(parent) {}
|
||||
public slots:
|
||||
void keyboardSearch(const QString& text) {
|
||||
QAbstractItemView::keyboardSearch(text);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__ModelBrowser__) */
|
320
interface/src/ui/ModelsBrowser.cpp
Normal file
320
interface/src/ui/ModelsBrowser.cpp
Normal 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;
|
||||
}
|
80
interface/src/ui/ModelsBrowser.h
Normal file
80
interface/src/ui/ModelsBrowser.h
Normal 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__) */
|
203
interface/src/ui/RunningScriptsWidget.cpp
Normal file
203
interface/src/ui/RunningScriptsWidget.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
//
|
||||
// RunningScripts.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 03/28/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
|
||||
#include "ui_runningScriptsWidget.h"
|
||||
#include "RunningScriptsWidget.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) :
|
||||
QDockWidget(parent),
|
||||
ui(new Ui::RunningScriptsWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// remove the title bar (see the Qt docs on setTitleBarWidget)
|
||||
setTitleBarWidget(new QWidget());
|
||||
|
||||
ui->runningScriptsTableWidget->setColumnCount(2);
|
||||
ui->runningScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->runningScriptsTableWidget->setShowGrid(false);
|
||||
ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(0, 235);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(1, 25);
|
||||
connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnCount(2);
|
||||
ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->recentlyLoadedScriptsTableWidget->setShowGrid(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235);
|
||||
connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked,
|
||||
this, &RunningScriptsWidget::loadScript);
|
||||
|
||||
connect(ui->hideWidgetButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::toggleRunningScriptsWidget);
|
||||
connect(ui->reloadAllButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::reloadAllScripts);
|
||||
connect(ui->stopAllButton, &QPushButton::clicked,
|
||||
this, &RunningScriptsWidget::allScriptsStopped);
|
||||
}
|
||||
|
||||
RunningScriptsWidget::~RunningScriptsWidget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::setRunningScripts(const QStringList& list)
|
||||
{
|
||||
ui->runningScriptsTableWidget->setRowCount(list.size());
|
||||
|
||||
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
|
||||
ui->currentlyRunningLabel->setVisible(!list.isEmpty());
|
||||
ui->line1->setVisible(!list.isEmpty());
|
||||
ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
|
||||
ui->reloadAllButton->setVisible(!list.isEmpty());
|
||||
ui->stopAllButton->setVisible(!list.isEmpty());
|
||||
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(list.at(i));
|
||||
scriptName->setToolTip(list.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
QTableWidgetItem *closeIcon = new QTableWidgetItem;
|
||||
closeIcon->setIcon(QIcon(":/images/kill-script.svg"));
|
||||
|
||||
ui->runningScriptsTableWidget->setItem(i, 0, scriptName);
|
||||
ui->runningScriptsTableWidget->setItem(i, 1, closeIcon);
|
||||
}
|
||||
|
||||
createRecentlyLoadedScriptsTable();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
switch(e->key()) {
|
||||
case Qt::Key_Escape:
|
||||
Application::getInstance()->toggleRunningScriptsWidget();
|
||||
break;
|
||||
|
||||
case Qt::Key_1:
|
||||
if (_recentlyLoadedScripts.size() > 0) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_2:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_3:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_4:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_5:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_6:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_7:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_8:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_9:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::stopScript(int row, int column)
|
||||
{
|
||||
if (column == 1) { // make sure the user has clicked on the close icon
|
||||
_lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text();
|
||||
emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text());
|
||||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::loadScript(int row, int column)
|
||||
{
|
||||
Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text());
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::allScriptsStopped()
|
||||
{
|
||||
QStringList list = Application::getInstance()->getRunningScripts();
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
_recentlyLoadedScripts.prepend(list.at(i));
|
||||
}
|
||||
|
||||
Application::getInstance()->stopAllScripts();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::createRecentlyLoadedScriptsTable()
|
||||
{
|
||||
if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
|
||||
_recentlyLoadedScripts.prepend(_lastStoppedScript);
|
||||
_lastStoppedScript = "";
|
||||
}
|
||||
|
||||
for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) {
|
||||
if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) {
|
||||
_recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
|
||||
int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
|
||||
ui->recentlyLoadedScriptsTableWidget->setRowCount(limit);
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setToolTip(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
QTableWidgetItem *number = new QTableWidgetItem;
|
||||
number->setText(QString::number(i+1) + ".");
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number);
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName);
|
||||
}
|
||||
}
|
46
interface/src/ui/RunningScriptsWidget.h
Normal file
46
interface/src/ui/RunningScriptsWidget.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// RunningScripts.h
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 03/28/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
|
||||
#ifndef __hifi__RunningScriptsWidget__
|
||||
#define __hifi__RunningScriptsWidget__
|
||||
|
||||
// Qt
|
||||
#include <QDockWidget>
|
||||
|
||||
namespace Ui {
|
||||
class RunningScriptsWidget;
|
||||
}
|
||||
|
||||
class RunningScriptsWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RunningScriptsWidget(QDockWidget *parent = 0);
|
||||
~RunningScriptsWidget();
|
||||
|
||||
void setRunningScripts(const QStringList& list);
|
||||
|
||||
signals:
|
||||
void stopScriptName(const QString& name);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e);
|
||||
|
||||
private slots:
|
||||
void stopScript(int row, int column);
|
||||
void loadScript(int row, int column);
|
||||
void allScriptsStopped();
|
||||
|
||||
private:
|
||||
Ui::RunningScriptsWidget *ui;
|
||||
QStringList _recentlyLoadedScripts;
|
||||
QString _lastStoppedScript;
|
||||
|
||||
void createRecentlyLoadedScriptsTable();
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__RunningScriptsWidget__) */
|
248
interface/ui/runningScriptsWidget.ui
Normal file
248
interface/ui/runningScriptsWidget.ui
Normal file
|
@ -0,0 +1,248 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RunningScriptsWidget</class>
|
||||
<widget class="QWidget" name="RunningScriptsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>310</width>
|
||||
<height>651</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #f7f7f7;
|
||||
font-family: Helvetica, Arial, "DejaVu Sans"; </string>
|
||||
</property>
|
||||
<widget class="QLabel" name="widgetTitle">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>221</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="currentlyRunningLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>301</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="reloadAllButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>230</y>
|
||||
<width>111</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reload All</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/reload.svg</normaloff>:/images/reload.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="stopAllButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>230</y>
|
||||
<width>101</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop All</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/stop.svg</normaloff>:/images/stop.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>280</y>
|
||||
<width>301</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>300</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedInstruction">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>590</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #95a5a6;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(click a script or use the 1-9 keys to load and run it)</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="hideWidgetButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>270</x>
|
||||
<y>10</y>
|
||||
<width>31</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/close.svg</normaloff>:/images/close.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="runningScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>70</y>
|
||||
<width>271</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line1">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="recentlyLoadedScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>310</y>
|
||||
<width>271</width>
|
||||
<height>281</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="noRunningScriptsLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>271</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>There are no scripts currently running.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -40,6 +40,7 @@ AvatarData::AvatarData() :
|
|||
_handState(0),
|
||||
_keyState(NO_KEY_DOWN),
|
||||
_isChatCirclingEnabled(false),
|
||||
_hasNewJointRotations(true),
|
||||
_headData(NULL),
|
||||
_handData(NULL),
|
||||
_displayNameBoundingRect(),
|
||||
|
@ -483,6 +484,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
|||
}
|
||||
}
|
||||
} // numJoints * 8 bytes
|
||||
_hasNewJointRotations = true;
|
||||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
|
|
@ -242,6 +242,8 @@ protected:
|
|||
|
||||
bool _isChatCirclingEnabled;
|
||||
|
||||
bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar
|
||||
|
||||
HeadData* _headData;
|
||||
HandData* _handData;
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
#include "LocalVoxels.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
int ScriptEngine::_scriptNumber = 1;
|
||||
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
|
||||
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
|
||||
|
||||
|
@ -41,7 +40,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng
|
|||
}
|
||||
|
||||
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString,
|
||||
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
|
||||
_scriptContents(scriptContents),
|
||||
|
@ -58,26 +57,15 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
|||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_wantMenuItems(wantMenuItems),
|
||||
_scriptMenuName(),
|
||||
_scriptName(),
|
||||
_fileNameString(fileNameString),
|
||||
_quatLibrary(),
|
||||
_vec3Library()
|
||||
{
|
||||
// some clients will use these menu features
|
||||
if (!fileNameString.isEmpty()) {
|
||||
_scriptMenuName = "Stop ";
|
||||
_scriptMenuName.append(qPrintable(fileNameString));
|
||||
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||
} else {
|
||||
_scriptMenuName = "Stop Script ";
|
||||
_scriptMenuName.append(_scriptNumber);
|
||||
}
|
||||
_scriptNumber++;
|
||||
}
|
||||
|
||||
ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||
_scriptContents(),
|
||||
_isFinished(false),
|
||||
_isRunning(false),
|
||||
|
@ -92,32 +80,21 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
|||
_numAvatarSoundSentBytes(0),
|
||||
_controllerScriptingInterface(controllerScriptingInterface),
|
||||
_avatarData(NULL),
|
||||
_wantMenuItems(wantMenuItems),
|
||||
_scriptMenuName(),
|
||||
_scriptName(),
|
||||
_fileNameString(),
|
||||
_quatLibrary(),
|
||||
_vec3Library()
|
||||
{
|
||||
QString scriptURLString = scriptURL.toString();
|
||||
_fileNameString = scriptURLString;
|
||||
// some clients will use these menu features
|
||||
if (!scriptURLString.isEmpty()) {
|
||||
_scriptMenuName = "Stop ";
|
||||
_scriptMenuName.append(qPrintable(scriptURLString));
|
||||
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||
} else {
|
||||
_scriptMenuName = "Stop Script ";
|
||||
_scriptMenuName.append(_scriptNumber);
|
||||
}
|
||||
_scriptNumber++;
|
||||
|
||||
|
||||
QUrl url(scriptURL);
|
||||
|
||||
|
||||
// if the scheme is empty, maybe they typed in a file, let's try
|
||||
if (url.scheme().isEmpty()) {
|
||||
url = QUrl::fromLocalFile(scriptURLString);
|
||||
}
|
||||
|
||||
|
||||
// ok, let's see if it's valid... and if so, load it
|
||||
if (url.isValid()) {
|
||||
if (url.scheme() == "file") {
|
||||
|
@ -144,16 +121,16 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
|||
|
||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
||||
_isAvatar = isAvatar;
|
||||
|
||||
|
||||
if (_isAvatar && !_avatarIdentityTimer) {
|
||||
// set up the avatar timers
|
||||
_avatarIdentityTimer = new QTimer(this);
|
||||
_avatarBillboardTimer = new QTimer(this);
|
||||
|
||||
|
||||
// connect our slot
|
||||
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
|
||||
connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket);
|
||||
|
||||
|
||||
// start the timers
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
_avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
@ -162,20 +139,14 @@ void ScriptEngine::setIsAvatar(bool isAvatar) {
|
|||
|
||||
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
|
||||
_avatarData = avatarData;
|
||||
|
||||
|
||||
// remove the old Avatar property, if it exists
|
||||
_engine.globalObject().setProperty(objectName, QScriptValue());
|
||||
|
||||
|
||||
// give the script engine the new Avatar script property
|
||||
registerGlobalObject(objectName, _avatarData);
|
||||
}
|
||||
|
||||
void ScriptEngine::cleanupMenuItems() {
|
||||
if (_wantMenuItems) {
|
||||
emit cleanupMenuItem(_scriptMenuName);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
||||
if (_isRunning) {
|
||||
return false;
|
||||
|
@ -203,7 +174,7 @@ void ScriptEngine::init() {
|
|||
registerVoxelMetaTypes(&_engine);
|
||||
registerEventTypes(&_engine);
|
||||
registerMenuItemProperties(&_engine);
|
||||
|
||||
|
||||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
|
||||
|
@ -216,7 +187,7 @@ void ScriptEngine::init() {
|
|||
|
||||
QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject<AudioInjectorOptions>();
|
||||
_engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue);
|
||||
|
||||
|
||||
QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject<LocalVoxels>();
|
||||
_engine.globalObject().setProperty("LocalVoxels", localVoxelsValue);
|
||||
|
||||
|
@ -285,9 +256,9 @@ void ScriptEngine::run() {
|
|||
gettimeofday(&startTime, NULL);
|
||||
|
||||
int thisFrame = 0;
|
||||
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
|
||||
qint64 lastUpdate = usecTimestampNow();
|
||||
|
||||
while (!_isFinished) {
|
||||
|
@ -325,36 +296,36 @@ void ScriptEngine::run() {
|
|||
_particlesScriptingInterface.getParticlePacketSender()->process();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_isAvatar && _avatarData) {
|
||||
|
||||
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5);
|
||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||
|
||||
|
||||
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
||||
avatarPacket.append(_avatarData->toByteArray());
|
||||
|
||||
|
||||
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
|
||||
|
||||
if (_isListeningToAudioStream || _avatarSound) {
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
bool silentFrame = true;
|
||||
|
||||
|
||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
|
||||
if (_avatarSound) {
|
||||
|
||||
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
|
||||
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||
if (nextSoundOutput[i] != 0) {
|
||||
|
@ -362,7 +333,7 @@ void ScriptEngine::run() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
|
@ -371,24 +342,24 @@ void ScriptEngine::run() {
|
|||
_numAvatarSoundSentBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||
? PacketTypeSilentAudioFrame
|
||||
: PacketTypeMicrophoneAudioNoEcho);
|
||||
|
||||
|
||||
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
||||
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));
|
||||
|
||||
|
||||
if (silentFrame) {
|
||||
if (!_isListeningToAudioStream) {
|
||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// write the number of silent samples so the audio-mixer can uphold timing
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));
|
||||
} else if (nextSoundOutput) {
|
||||
|
@ -396,7 +367,7 @@ void ScriptEngine::run() {
|
|||
packetStream.writeRawData(reinterpret_cast<const char*>(nextSoundOutput),
|
||||
numAvailableSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
|
||||
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
||||
}
|
||||
}
|
||||
|
@ -412,10 +383,10 @@ void ScriptEngine::run() {
|
|||
}
|
||||
}
|
||||
emit scriptEnding();
|
||||
|
||||
|
||||
// kill the avatar identity timer
|
||||
delete _avatarIdentityTimer;
|
||||
|
||||
|
||||
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
|
||||
// release the queue of edit voxel messages.
|
||||
_voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();
|
||||
|
@ -435,8 +406,6 @@ void ScriptEngine::run() {
|
|||
_particlesScriptingInterface.getParticlePacketSender()->process();
|
||||
}
|
||||
}
|
||||
|
||||
cleanupMenuItems();
|
||||
|
||||
// If we were on a thread, then wait till it's done
|
||||
if (thread()) {
|
||||
|
@ -444,7 +413,7 @@ void ScriptEngine::run() {
|
|||
}
|
||||
|
||||
emit finished(_fileNameString);
|
||||
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
|
@ -454,13 +423,13 @@ void ScriptEngine::stop() {
|
|||
|
||||
void ScriptEngine::timerFired() {
|
||||
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
|
||||
|
||||
|
||||
// call the associated JS function, if it exists
|
||||
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
|
||||
if (timerFunction.isValid()) {
|
||||
timerFunction.call();
|
||||
}
|
||||
|
||||
|
||||
if (!callingTimer->isActive()) {
|
||||
// this timer is done, we can kill it
|
||||
delete callingTimer;
|
||||
|
@ -471,14 +440,14 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
|||
// create the timer, add it to the map, and start it
|
||||
QTimer* newTimer = new QTimer(this);
|
||||
newTimer->setSingleShot(isSingleShot);
|
||||
|
||||
|
||||
connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);
|
||||
|
||||
|
||||
// make sure the timer stops when the script does
|
||||
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
||||
|
||||
|
||||
_timerFunctionMap.insert(newTimer, function);
|
||||
|
||||
|
||||
newTimer->start(intervalMS);
|
||||
return newTimer;
|
||||
}
|
||||
|
@ -505,17 +474,17 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const {
|
|||
if (!url.scheme().isEmpty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||
|
||||
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||
// to the original URL of our script
|
||||
QUrl parentURL(_fileNameString);
|
||||
|
||||
|
||||
// if the parent URL's scheme is empty, then this is probably a local file...
|
||||
if (parentURL.scheme().isEmpty()) {
|
||||
parentURL = QUrl::fromLocalFile(_fileNameString);
|
||||
}
|
||||
|
||||
// at this point we should have a legitimate fully qualified URL for our parent
|
||||
|
||||
// at this point we should have a legitimate fully qualified URL for our parent
|
||||
url = parentURL.resolved(url);
|
||||
return url;
|
||||
}
|
||||
|
@ -543,7 +512,7 @@ void ScriptEngine::include(const QString& includeFile) {
|
|||
loop.exec();
|
||||
includeContents = reply->readAll();
|
||||
}
|
||||
|
||||
|
||||
QScriptValue result = _engine.evaluate(includeContents);
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
|
|
|
@ -33,11 +33,11 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10
|
|||
class ScriptEngine : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false,
|
||||
ScriptEngine(const QUrl& scriptURL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
||||
const QString& fileNameString = QString(""),
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
|
||||
const QString& fileNameString = QString(""),
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
/// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
|
@ -49,39 +49,39 @@ public:
|
|||
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||
|
||||
const QString& getScriptMenuName() const { return _scriptMenuName; }
|
||||
const QString& getScriptName() const { return _scriptName; }
|
||||
void cleanupMenuItems();
|
||||
|
||||
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
||||
|
||||
|
||||
Q_INVOKABLE void setIsAvatar(bool isAvatar);
|
||||
bool isAvatar() const { return _isAvatar; }
|
||||
|
||||
|
||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||
|
||||
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||
|
||||
|
||||
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; }
|
||||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||
|
||||
|
||||
void init();
|
||||
void run(); /// runs continuously until Agent.stop() is called
|
||||
void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller
|
||||
|
||||
|
||||
void timerFired();
|
||||
|
||||
bool hasScript() const { return !_scriptContents.isEmpty(); }
|
||||
|
||||
public slots:
|
||||
void stop();
|
||||
|
||||
|
||||
QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
void include(const QString& includeFile);
|
||||
|
||||
|
||||
signals:
|
||||
void update(float deltaTime);
|
||||
void scriptEnding();
|
||||
|
@ -106,19 +106,18 @@ private:
|
|||
QUrl resolveInclude(const QString& include) const;
|
||||
void sendAvatarIdentityPacket();
|
||||
void sendAvatarBillboardPacket();
|
||||
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
||||
|
||||
static VoxelsScriptingInterface _voxelsScriptingInterface;
|
||||
static ParticlesScriptingInterface _particlesScriptingInterface;
|
||||
static int _scriptNumber;
|
||||
|
||||
|
||||
AbstractControllerScriptingInterface* _controllerScriptingInterface;
|
||||
AudioScriptingInterface _audioScriptingInterface;
|
||||
AvatarData* _avatarData;
|
||||
bool _wantMenuItems;
|
||||
QString _scriptMenuName;
|
||||
QString _scriptName;
|
||||
QString _fileNameString;
|
||||
Quat _quatLibrary;
|
||||
Vec3 _vec3Library;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue