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

This commit is contained in:
Atlante45 2014-04-03 11:20:24 -07:00
commit df718c430a
29 changed files with 1422 additions and 633 deletions

View file

@ -4,22 +4,22 @@
<context>
<name>Application</name>
<message>
<location filename="src/Application.cpp" line="1385"/>
<location filename="src/Application.cpp" line="1370"/>
<source>Export Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="1386"/>
<location filename="src/Application.cpp" line="1371"/>
<source>Sparse Voxel Octree Files (*.svo)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3714"/>
<location filename="src/Application.cpp" line="3723"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3715"/>
<location filename="src/Application.cpp" line="3724"/>
<source>JavaScript Files (*.js)</source>
<translation type="unfinished"></translation>
</message>
@ -44,39 +44,37 @@
<source> online now:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/ChatWindow.cpp" line="47"/>
<source>You must be logged in to chat with others.</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="135"/>
<location filename="src/ui/ChatWindow.cpp" line="139"/>
<source>day</source>
<translation>
<translation type="unfinished">
<numerusform>%n day</numerusform>
<numerusform>%n days</numerusform>
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="135"/>
<location filename="src/ui/ChatWindow.cpp" line="139"/>
<source>hour</source>
<translation>
<translation type="unfinished">
<numerusform>%n hour</numerusform>
<numerusform>%n hours</numerusform>
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="135"/>
<location filename="src/ui/ChatWindow.cpp" line="139"/>
<source>minute</source>
<translation>
<translation type="unfinished">
<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"/>
<location filename="src/ui/ChatWindow.cpp" line="195"/>
<source>%1 online now:</source>
<translation type="unfinished"></translation>
</message>
@ -113,18 +111,18 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="463"/>
<location filename="src/Menu.cpp" line="461"/>
<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"/>
<location filename="src/Menu.cpp" line="463"/>
<location filename="src/Menu.cpp" line="475"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="475"/>
<location filename="src/Menu.cpp" line="473"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>
@ -158,4 +156,55 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RunningScriptsWidget</name>
<message>
<location filename="ui/runningScriptsWidget.ui" line="14"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="127"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="33"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="128"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="49"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="129"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="70"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="130"/>
<source>Reload All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="95"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="131"/>
<source>Stop All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="115"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="132"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="147"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="133"/>
<source>(click a script or use the 1-9 keys to load and run it)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="237"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="135"/>
<source>There are no scripts currently running.</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 11 11" enable-background="new 0 0 11 11" xml:space="preserve">
<polygon fill="#C4C4C4" points="11,1.9 9.2,0 5.5,3.7 1.9,0 0,1.9 3.7,5.5 0,9.2 1.9,11 5.5,7.4 9.2,11 11,9.2 7.4,5.5 "/>
</svg>

After

Width:  |  Height:  |  Size: 564 B

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 19.2 16" enable-background="new 0 0 19.2 16" xml:space="preserve">
<g>
<g>
<path fill="#F9F9F9" d="M13.1,13c-1.1,0.7-2.3,1.1-3.6,1.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.1,0-0.2,0c-0.1,0-0.3,0-0.4-0.1
c-0.1,0-0.2,0-0.2-0.1c-0.1,0-0.2-0.1-0.4-0.1c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.3-0.1-0.4-0.2c0,0-0.1,0-0.1,0
c-0.2-0.1-0.3-0.1-0.5-0.2c0,0,0,0,0,0c-0.5-0.3-1-0.6-1.4-1c0,0,0,0,0,0c-0.1-0.1-0.2-0.3-0.4-0.4c0,0,0-0.1-0.1-0.1
C4,10.8,3.5,9.5,3.5,8h1.6L2.6,4.2L0,8h1.6c0,1.7,0.5,3.3,1.4,4.5c0,0,0,0,0,0.1c0.1,0.1,0.2,0.3,0.3,0.4c0,0,0.1,0.1,0.1,0.1
c0.1,0.2,0.3,0.3,0.5,0.5c0,0,0,0,0,0c0.5,0.5,1.1,1,1.8,1.4c0,0,0,0,0.1,0c0.2,0.1,0.4,0.2,0.6,0.3c0,0,0.1,0,0.1,0.1
c0.2,0.1,0.3,0.1,0.5,0.2c0.1,0,0.2,0.1,0.2,0.1c0.2,0,0.3,0.1,0.5,0.1c0.1,0,0.2,0.1,0.3,0.1c0,0,0.1,0,0.1,0
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.3,0,0.5,0,0.8,0c1.6,0,3.2-0.5,4.6-1.5c0.4-0.3,0.5-0.9,0.2-1.3
C14.2,12.8,13.6,12.7,13.1,13z"/>
<path fill="#F9F9F9" d="M17.6,8c0-1.7-0.5-3.2-1.4-4.5c0,0,0,0,0-0.1C16.1,3.3,16,3.1,15.8,3c0,0,0,0,0-0.1c-0.8-1-1.8-1.8-3-2.2
c0,0-0.1,0-0.1,0c-0.2-0.1-0.4-0.1-0.6-0.2c-0.1,0-0.1,0-0.2-0.1c-0.2-0.1-0.3-0.1-0.5-0.1c-0.1,0-0.2,0-0.3-0.1c0,0-0.1,0-0.1,0
c-0.1,0-0.3,0-0.4,0c-0.1,0-0.2,0-0.3,0c-0.2,0-0.4,0-0.6,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0C8,0,6.4,0.5,5,1.4
C4.6,1.8,4.5,2.4,4.8,2.8C5.1,3.2,5.7,3.3,6.1,3c1.1-0.7,2.3-1.1,3.5-1.1c0.2,0,0.4,0,0.5,0c0.1,0,0.1,0,0.2,0
c0.1,0,0.3,0,0.4,0.1c0.1,0,0.1,0,0.2,0c0.1,0,0.3,0.1,0.4,0.1c0,0,0.1,0,0.1,0c0.2,0.1,0.3,0.1,0.5,0.2c0,0,0,0,0,0
c0.9,0.4,1.7,1,2.3,1.7c0,0,0,0,0,0c0.9,1.1,1.4,2.4,1.4,3.9h-1.6l2.6,3.8L19.2,8H17.6z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 14 14" enable-background="new 0 0 14 14" xml:space="preserve">
<rect fill="#F9F9F9" width="14" height="14"/>
</svg>

After

Width:  |  Height:  |  Size: 490 B

View file

@ -1,5 +1,8 @@
<RCC>
<qresource prefix="/">
<file>images/close.svg</file>
<file>images/kill-script.svg</file>
<file>images/reload.svg</file>
<file>images/stop.svg</file>
</qresource>
</RCC>

View file

@ -112,8 +112,6 @@ 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);
@ -333,6 +331,10 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
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()) {
@ -684,26 +686,9 @@ 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;
@ -3588,9 +3573,9 @@ 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();
@ -3598,25 +3583,27 @@ void Application::saveScripts() {
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();
}
// NOTE: we don't need to clear the _activeScripts list because that is handled on script shutdown.
stopAllScripts();
foreach (QString scriptName, reloadList){
qDebug() << "reloading script..." << scriptName;
@ -3624,6 +3611,34 @@ void Application::reloadAllScripts() {
}
}
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.
@ -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()));

View file

@ -21,6 +21,7 @@
#include <QSet>
#include <QStringList>
#include <QPointer>
#include <QHash>
#include <NetworkPacket.h>
#include <NodeList.h>
@ -65,6 +66,7 @@
#include "ui/LogDialog.h"
#include "ui/UpdateDialog.h"
#include "ui/overlays/Overlays.h"
#include "ui/RunningScriptsWidget.h"
#include "voxels/VoxelFade.h"
#include "voxels/VoxelHideShowThread.h"
#include "voxels/VoxelImporter.h"
@ -93,6 +95,7 @@ static const float NODE_KILLED_GREEN = 0.0f;
static const float NODE_KILLED_BLUE = 0.0f;
static const QString SNAPSHOT_EXTENSION = ".jpg";
static const QString CUSTOM_URL_SCHEME = "hifi:";
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
static const float BILLBOARD_DISTANCE = 5.0f; // meters
@ -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.
@ -259,7 +264,9 @@ public slots:
void toggleLogDialog();
void initAvatarAndViewFrustum();
void stopAllScripts();
void stopScript(const QString& scriptName);
void reloadAllScripts();
void toggleRunningScriptsWidget();
void uploadFST(bool isHead);
void uploadHead();
@ -284,9 +291,6 @@ private slots:
void parseVersionXml();
void removeScriptName(const QString& fileNameString);
void cleanupScriptMenuItem(const QString& scriptMenuName);
private:
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
void updateProjectionMatrix();
@ -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
@ -497,6 +500,9 @@ private:
TouchEvent _lastTouchEvent;
Overlays _overlays;
RunningScriptsWidget* _runningScriptsWidget;
QHash<QString, ScriptEngine*> _scriptEnginesHash;
};
#endif /* defined(__interface__Application__) */

View file

@ -115,7 +115,8 @@ Menu::Menu() :
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,
@ -147,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()));
@ -180,18 +180,17 @@ Menu::Menu() :
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");
@ -282,8 +281,9 @@ Menu::Menu() :
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
@ -366,7 +366,6 @@ Menu::Menu() :
appInstance->getAudio(),
SLOT(toggleToneInjection()));
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
this,
@ -926,20 +925,59 @@ 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()) {
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();
}
@ -1116,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());

View file

@ -95,8 +95,6 @@ public:
// User Tweakable PPS from Voxel Server
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; }
QMenu* getActiveScriptsMenu() { return _activeScriptsMenu;}
QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu,
const QString& actionName,
const QKeySequence& shortcut = 0,
@ -110,6 +108,7 @@ public:
bool goToDestination(QString destination);
void goToOrientation(QString orientation);
void goToDomain(const QString newDomain);
void goTo(QString destination);
public slots:
@ -208,7 +207,6 @@ private:
int _boundaryLevelAdjust;
QAction* _useVoxelShader;
int _maxVoxelPacketsPerSecond;
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
quint64 _lastAdjust;
quint64 _lastAvatarDetailDrop;
@ -288,9 +286,11 @@ namespace MenuOption {
const QString PlaySlaps = "Play Slaps";
const QString Preferences = "Preferences...";
const QString ReloadAllScripts = "Reload All Scripts";
const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies";
const QString RenderHeadCollisionProxies = "Head Collision Proxies";
const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes";
const QString RenderHeadCollisionShapes = "Head Collision Shapes";
const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes";
const QString ResetAvatarSize = "Reset Avatar Size";
const QString RunningScripts = "Running Scripts";
const QString RunTimingTests = "Run Timing Tests";
const QString SettingsImport = "Import Settings";
const QString Shadows = "Shadows";

View file

@ -56,8 +56,7 @@ Avatar::Avatar() :
_owningAvatarMixer(),
_collisionFlags(0),
_initialized(false),
_shouldRenderBillboard(true),
_modelsDirty(true)
_shouldRenderBillboard(true)
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
@ -118,21 +117,24 @@ void Avatar::simulate(float deltaTime) {
getHand()->simulate(deltaTime, false);
_skeletonModel.setLODDistance(getLODDistance());
// copy joint data to skeleton
if (!_shouldRenderBillboard && inViewFrustum) {
if (_hasNewJointRotations) {
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;
_skeletonModel.getHeadPosition(headPosition);
_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);
}
// use speed and angular velocity to determine walking vs. standing
if (_speed + fabs(_bodyYawDelta) > 0.2) {
@ -210,11 +212,19 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(renderMode);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
_skeletonModel.renderCollisionProxies(0.7f);
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
_skeletonModel.updateShapePositions();
_skeletonModel.renderJointCollisionShapes(0.7f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) {
getHead()->getFaceModel().renderCollisionProxies(0.7f);
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
getHead()->getFaceModel().updateShapePositions();
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
getHead()->getFaceModel().updateShapePositions();
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
_skeletonModel.updateShapePositions();
_skeletonModel.renderBoundingCollisionShapes(0.7f);
}
// quick check before falling into the code below:
@ -653,9 +663,6 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
// note that we need to update our models
_modelsDirty = true;
return bytesRead;
}

View file

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

View file

@ -19,10 +19,7 @@ FaceModel::FaceModel(Head* owningHead) :
}
void FaceModel::simulate(float deltaTime) {
QVector<JointState> newJointStates = updateGeometry();
if (!isActive()) {
return;
}
updateGeometry();
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
glm::vec3 neckPosition;
if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) {
@ -37,12 +34,13 @@ void FaceModel::simulate(float deltaTime) {
const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);
setOffset(-_geometry->getFBXGeometry().neckPivot);
setPupilDilation(_owningHead->getPupilDilation());
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
Model::simulate(deltaTime, true, newJointStates);
if (isActive()) {
setOffset(-_geometry->getFBXGeometry().neckPivot);
Model::simulateInternal(deltaTime);
}
}
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {

View file

@ -254,7 +254,7 @@ void Hand::render(bool isMine) {
_renderAlpha = 1.0;
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
// draw a green sphere at hand joint location, which is actually near the wrist)
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];

View file

@ -11,6 +11,7 @@
#include <QBuffer>
#include <glm/gtx/norm.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <QtCore/QTimer>
@ -20,6 +21,8 @@
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <ShapeCollider.h>
#include "Application.h"
#include "Audio.h"
#include "Environment.h"
@ -83,9 +86,9 @@ void MyAvatar::reset() {
getHead()->reset();
getHand()->reset();
setVelocity(glm::vec3(0,0,0));
setThrust(glm::vec3(0,0,0));
setOrientation(glm::quat(glm::vec3(0,0,0)));
setVelocity(glm::vec3(0.f));
setThrust(glm::vec3(0.f));
setOrientation(glm::quat(glm::vec3(0.f)));
}
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
@ -674,6 +677,28 @@ void MyAvatar::updateThrust(float deltaTime) {
_thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right;
_thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up;
_thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up;
// attenuate thrust when in penetration
if (glm::dot(_thrust, _lastBodyPenetration) > 0.f) {
const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius();
float penetrationFactor = glm::min(1.f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH);
glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration);
// attenuate parallel component
glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection;
// attenuate perpendicular component (friction)
glm::vec3 perpendicularThrust = _thrust - parallelThrust;
// recombine to get the final thrust
_thrust = (1.f - penetrationFactor) * parallelThrust + (1.f - penetrationFactor * penetrationFactor) * perpendicularThrust;
// attenuate the growth of _thrustMultiplier when in penetration
// otherwise the avatar will eventually be able to tunnel through the obstacle
_thrustMultiplier *= (1.f - penetrationFactor * penetrationFactor);
} else if (_thrustMultiplier < 1.f) {
// rapid healing of attenuated thrustMultiplier after penetration event
_thrustMultiplier = 1.f;
}
_lastBodyPenetration = glm::vec3(0.f);
_bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime;
_bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime;
getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime);
@ -683,8 +708,9 @@ void MyAvatar::updateThrust(float deltaTime) {
const float THRUST_INCREASE_RATE = 1.05f;
const float MAX_THRUST_MULTIPLIER = 75.0f;
//printf("m = %.3f\n", _thrustMultiplier);
if (_thrustMultiplier < MAX_THRUST_MULTIPLIER) {
_thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE;
if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) {
_thrustMultiplier = MAX_THRUST_MULTIPLIER;
}
} else {
_thrustMultiplier = 1.f;
@ -868,6 +894,9 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
return false;
}
static CollisionList bodyCollisions(16);
const float BODY_COLLISION_RESOLVE_TIMESCALE = 0.5f; // seconds
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// Reset detector for nearest avatar
_distanceToNearestAvatar = std::numeric_limits<float>::max();
@ -879,14 +908,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
updateShapePositions();
float myBoundingRadius = getBoundingRadius();
/* TODO: Andrew to fix Avatar-Avatar body collisions
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
// TODO: make the collision work without assuming avatar orientation
Extents myStaticExtents = _skeletonModel.getStaticExtents();
glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum;
float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
float myCapsuleHeight = staticScale.y;
*/
const float BODY_COLLISION_RESOLVE_FACTOR = deltaTime / BODY_COLLISION_RESOLVE_TIMESCALE;
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
@ -901,19 +923,27 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
}
float theirBoundingRadius = avatar->getBoundingRadius();
if (distance < myBoundingRadius + theirBoundingRadius) {
/* TODO: Andrew to fix Avatar-Avatar body collisions
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z);
float theirCapsuleHeight = staticScale.y;
glm::vec3 penetration(0.f);
if (findAvatarAvatarPenetration(_position, myCapsuleRadius, myCapsuleHeight,
avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) {
// move the avatar out by half the penetration
setPosition(_position - 0.5f * penetration);
// collide our body against theirs
QVector<const Shape*> myShapes;
_skeletonModel.getBodyShapes(myShapes);
QVector<const Shape*> theirShapes;
avatar->getSkeletonModel().getBodyShapes(theirShapes);
bodyCollisions.clear();
// TODO: add method to ShapeCollider for colliding lists of shapes
foreach (const Shape* myShape, myShapes) {
foreach (const Shape* theirShape, theirShapes) {
ShapeCollider::shapeShape(myShape, theirShape, bodyCollisions);
}
*/
}
glm::vec3 totalPenetration(0.f);
for (int j = 0; j < bodyCollisions.size(); ++j) {
CollisionInfo* collision = bodyCollisions.getCollision(j);
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
}
if (glm::length2(totalPenetration) > EPSILON) {
setPosition(getPosition() - BODY_COLLISION_RESOLVE_FACTOR * totalPenetration);
}
_lastBodyPenetration += totalPenetration;
// collide our hands against them
// TODO: make this work when we can figure out when the other avatar won't yeild

View file

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

View file

@ -63,7 +63,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
}
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) {
return;
}
if (jointIndex == getLeftHandJointIndex()
@ -75,16 +75,16 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
int parentIndex = joint.parentIndex;
if (i == jointIndex) {
// this shape is the hand
shapes.push_back(_shapes[i]);
shapes.push_back(_jointShapes[i]);
if (parentIndex != -1) {
// also add the forearm
shapes.push_back(_shapes[parentIndex]);
shapes.push_back(_jointShapes[parentIndex]);
}
} else {
while (parentIndex != -1) {
if (parentIndex == jointIndex) {
// this shape is a child of the hand
shapes.push_back(_shapes[i]);
shapes.push_back(_jointShapes[i]);
break;
}
parentIndex = geometry.joints[parentIndex].parentIndex;
@ -94,6 +94,12 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
}
}
void SkeletonModel::getBodyShapes(QVector<const Shape*>& shapes) const {
// for now we push a single bounding shape,
// but later we could push a subset of joint shapes
shapes.push_back(&_boundingShape);
}
class IndexValue {
public:
int index;

View file

@ -9,7 +9,6 @@
#ifndef __interface__SkeletonModel__
#define __interface__SkeletonModel__
#include "renderer/Model.h"
class Avatar;
@ -28,6 +27,9 @@ 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);

View file

@ -41,6 +41,11 @@ bool Extents::containsPoint(const glm::vec3& point) const {
&& point.z >= minimum.z && point.z <= maximum.z);
}
void Extents::addExtents(const Extents& extents) {
minimum = glm::min(minimum, extents.minimum);
maximum = glm::max(maximum, extents.maximum);
}
void Extents::addPoint(const glm::vec3& point) {
minimum = glm::min(minimum, point);
maximum = glm::max(maximum, point);
@ -1343,7 +1348,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
geometry.bindExtents.reset();
geometry.staticExtents.reset();
geometry.meshExtents.reset();
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
@ -1511,8 +1515,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd));
bool jointIsStatic = joint.freeLineage.isEmpty();
glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform);
float totalWeight = 0.0f;
for (int j = 0; j < cluster.indices.size(); j++) {
int oldIndex = cluster.indices.at(j);
@ -1534,10 +1536,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
jointShapeInfo.extents.addPoint(vertexInJointFrame);
jointShapeInfo.averageVertex += vertexInJointFrame;
++jointShapeInfo.numVertices;
if (jointIsStatic) {
// expand the extents of static (nonmovable) joints
geometry.staticExtents.addPoint(vertex + jointTranslation);
}
}
// look for an unused slot in the weights vector

View file

@ -30,6 +30,10 @@ public:
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
void reset();
/// \param extents another intance of extents
/// expand current limits to contain other extents
void addExtents(const Extents& extents);
/// \param point new point to compare against existing limits
/// compare point to current limits and expand them if necessary to contain point
void addPoint(const glm::vec3& point);
@ -174,7 +178,6 @@ public:
glm::vec3 neckPivot;
Extents bindExtents;
Extents staticExtents;
Extents meshExtents;
QVector<FBXAttachment> attachments;

View file

@ -32,9 +32,11 @@ Model::Model(QObject* parent) :
QObject(parent),
_scale(1.0f, 1.0f, 1.0f),
_shapesAreDirty(true),
_boundingRadius(0.f),
_boundingShape(),
_boundingShapeLocalOffset(0.f),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_boundingRadius(0.f) {
_pupilDilation(0.0f) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
@ -54,6 +56,14 @@ Model::SkinLocations Model::_skinLocations;
Model::SkinLocations Model::_skinNormalMapLocations;
Model::SkinLocations Model::_skinShadowLocations;
void Model::setScale(const glm::vec3& scale) {
glm::vec3 deltaScale = _scale - scale;
if (glm::length2(deltaScale) > EPSILON) {
_scale = scale;
rebuildShapes();
}
}
void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
program.bind();
locations.clusterMatrices = program.uniformLocation("clusterMatrices");
@ -73,6 +83,44 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
state.rotation = joint.rotation;
jointStates.append(state);
}
// compute transforms
// Unfortunately, the joints are not neccessarily in order from parents to children,
// so we must iterate over the list multiple times until all are set correctly.
QVector<bool> jointIsSet;
int numJoints = jointStates.size();
jointIsSet.fill(false, numJoints);
int numJointsSet = 0;
int lastNumJointsSet = -1;
while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) {
lastNumJointsSet = numJointsSet;
for (int i = 0; i < numJoints; ++i) {
if (jointIsSet[i]) {
continue;
}
JointState& state = jointStates[i];
const FBXJoint& joint = geometry.joints[i];
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
glm::mat4_cast(combinedRotation) * joint.postTransform;
state.combinedRotation = _rotation * combinedRotation;
++numJointsSet;
jointIsSet[i] = true;
} else if (jointIsSet[parentIndex]) {
const JointState& parentState = jointStates.at(parentIndex);
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform *
glm::mat4_cast(combinedRotation) * joint.postTransform;
state.combinedRotation = parentState.combinedRotation * combinedRotation;
++numJointsSet;
jointIsSet[i] = true;
}
}
}
return jointStates;
}
@ -142,60 +190,95 @@ void Model::reset() {
}
}
void Model::clearShapes() {
for (int i = 0; i < _shapes.size(); ++i) {
delete _shapes[i];
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;
}
}
_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;
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;
}
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);
}
}
}
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.
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;
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);
}
}
_boundingRadius = sqrtf(_boundingRadius);
_shapesAreDirty = false;
}
}
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;
}
}
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
void Model::simulate(float deltaTime, bool fullUpdate) {
// update our LOD, then simulate
simulate(deltaTime, fullUpdate, updateGeometry());
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,52 +686,22 @@ 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;
}
// 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::simulate(float deltaTime, bool fullUpdate) {
fullUpdate = updateGeometry() || fullUpdate;
if (isActive() && fullUpdate) {
simulateInternal(deltaTime);
}
}
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++) {
@ -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);
@ -749,6 +855,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last
for (int j = freeLineage.size() - 1; j >= 0; j--) {
updateJointState(freeLineage.at(j));
}
_shapesAreDirty = true;
return true;
}
@ -827,15 +934,15 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
state.rotation = newRotation;
}
void Model::renderCollisionProxies(float alpha) {
const int BALL_SUBDIVISIONS = 10;
void Model::renderJointCollisionShapes(float alpha) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
updateShapePositions();
const int BALL_SUBDIVISIONS = 10;
for (int i = 0; i < _shapes.size(); i++) {
for (int i = 0; i < _jointShapes.size(); i++) {
glPushMatrix();
Shape* shape = _shapes[i];
Shape* shape = _jointShapes[i];
if (shape->getType() == Shape::SPHERE_SHAPE) {
// shapes are stored in world-frame, so we have to transform into model frame
@ -878,6 +985,36 @@ void Model::renderCollisionProxies(float alpha) {
glPopMatrix();
}
void Model::renderBoundingCollisionShapes(float alpha) {
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
// draw a blue sphere at the capsule endpoint
glm::vec3 endPoint;
_boundingShape.getEndPoint(endPoint);
endPoint = endPoint - _translation;
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
glColor4f(0.6f, 0.6f, 0.8f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a yellow sphere at the capsule startpoint
glm::vec3 startPoint;
_boundingShape.getStartPoint(startPoint);
startPoint = startPoint - _translation;
glm::vec3 axis = endPoint - startPoint;
glTranslatef(-axis.x, -axis.y, -axis.z);
glColor4f(0.8f, 0.8f, 0.6f, alpha);
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a green cylinder between the two points
glm::vec3 origin(0.f);
glColor4f(0.6f, 0.8f, 0.6f, alpha);
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
glPopMatrix();
}
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
if (collision._type == MODEL_COLLISION) {
// the joint is pokable by a collision if it exists and is free to move

View file

@ -12,6 +12,8 @@
#include <QObject>
#include <QUrl>
#include <CapsuleShape.h>
#include "GeometryCache.h"
#include "InterfaceConfig.h"
#include "ProgramObject.h"
@ -34,7 +36,7 @@ public:
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
const glm::quat& getRotation() const { return _rotation; }
void setScale(const glm::vec3& scale) { _scale = scale; }
void setScale(const glm::vec3& scale);
const glm::vec3& getScale() const { return _scale; }
void setOffset(const glm::vec3& offset) { _offset = offset; }
@ -54,10 +56,7 @@ 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.
@ -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,8 +217,10 @@ protected:
QVector<MeshState> _meshStates;
QVector<JointState> updateGeometry();
void simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates);
// returns 'true' if needs fullUpdate after geometry change
bool updateGeometry();
void simulateInternal(float deltaTime);
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
@ -248,6 +255,7 @@ private:
void applyNextGeometry();
void deleteGeometry();
void renderMeshes(float alpha, bool forShadowMap, bool translucent);
QVector<JointState> createJointStates(const FBXGeometry& geometry);
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
QSharedPointer<NetworkGeometry> _nextBaseGeometry;
@ -267,8 +275,6 @@ private:
QVector<Model*> _attachments;
float _boundingRadius;
static ProgramObject _program;
static ProgramObject _normalMapProgram;
static ProgramObject _shadowProgram;
@ -291,7 +297,6 @@ private:
static SkinLocations _skinShadowLocations;
static void initSkinProgram(ProgramObject& program, SkinLocations& locations);
static QVector<JointState> createJointStates(const FBXGeometry& geometry);
};
Q_DECLARE_METATYPE(QPointer<Model>)

View file

@ -43,6 +43,10 @@ ChatWindow::ChatWindow() :
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();
if (xmppClient.isConnected()) {

View file

@ -0,0 +1,203 @@
//
// RunningScripts.cpp
// interface
//
// Created by Mohammed Nafees on 03/28/2014.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
#include "ui_runningScriptsWidget.h"
#include "RunningScriptsWidget.h"
#include <QKeyEvent>
#include <QTableWidgetItem>
#include "Application.h"
RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) :
QDockWidget(parent),
ui(new Ui::RunningScriptsWidget)
{
ui->setupUi(this);
// remove the title bar (see the Qt docs on setTitleBarWidget)
setTitleBarWidget(new QWidget());
ui->runningScriptsTableWidget->setColumnCount(2);
ui->runningScriptsTableWidget->verticalHeader()->setVisible(false);
ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false);
ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
ui->runningScriptsTableWidget->setShowGrid(false);
ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->runningScriptsTableWidget->setColumnWidth(0, 235);
ui->runningScriptsTableWidget->setColumnWidth(1, 25);
connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
ui->recentlyLoadedScriptsTableWidget->setColumnCount(2);
ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false);
ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false);
ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
ui->recentlyLoadedScriptsTableWidget->setShowGrid(false);
ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25);
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235);
connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked,
this, &RunningScriptsWidget::loadScript);
connect(ui->hideWidgetButton, &QPushButton::clicked,
Application::getInstance(), &Application::toggleRunningScriptsWidget);
connect(ui->reloadAllButton, &QPushButton::clicked,
Application::getInstance(), &Application::reloadAllScripts);
connect(ui->stopAllButton, &QPushButton::clicked,
this, &RunningScriptsWidget::allScriptsStopped);
}
RunningScriptsWidget::~RunningScriptsWidget()
{
delete ui;
}
void RunningScriptsWidget::setRunningScripts(const QStringList& list)
{
ui->runningScriptsTableWidget->setRowCount(list.size());
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
ui->currentlyRunningLabel->setVisible(!list.isEmpty());
ui->line1->setVisible(!list.isEmpty());
ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
ui->reloadAllButton->setVisible(!list.isEmpty());
ui->stopAllButton->setVisible(!list.isEmpty());
for (int i = 0; i < list.size(); ++i) {
QTableWidgetItem *scriptName = new QTableWidgetItem;
scriptName->setText(list.at(i));
scriptName->setToolTip(list.at(i));
scriptName->setTextAlignment(Qt::AlignCenter);
QTableWidgetItem *closeIcon = new QTableWidgetItem;
closeIcon->setIcon(QIcon(":/images/kill-script.svg"));
ui->runningScriptsTableWidget->setItem(i, 0, scriptName);
ui->runningScriptsTableWidget->setItem(i, 1, closeIcon);
}
createRecentlyLoadedScriptsTable();
}
void RunningScriptsWidget::keyPressEvent(QKeyEvent *e)
{
switch(e->key()) {
case Qt::Key_Escape:
Application::getInstance()->toggleRunningScriptsWidget();
break;
case Qt::Key_1:
if (_recentlyLoadedScripts.size() > 0) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0));
}
break;
case Qt::Key_2:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1));
}
break;
case Qt::Key_3:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2));
}
break;
case Qt::Key_4:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3));
}
break;
case Qt::Key_5:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4));
}
break;
case Qt::Key_6:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5));
}
break;
case Qt::Key_7:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6));
}
break;
case Qt::Key_8:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7));
}
break;
case Qt::Key_9:
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) {
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8));
}
break;
default:
break;
}
}
void RunningScriptsWidget::stopScript(int row, int column)
{
if (column == 1) { // make sure the user has clicked on the close icon
_lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text();
emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text());
}
}
void RunningScriptsWidget::loadScript(int row, int column)
{
Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text());
}
void RunningScriptsWidget::allScriptsStopped()
{
QStringList list = Application::getInstance()->getRunningScripts();
for (int i = 0; i < list.size(); ++i) {
_recentlyLoadedScripts.prepend(list.at(i));
}
Application::getInstance()->stopAllScripts();
}
void RunningScriptsWidget::createRecentlyLoadedScriptsTable()
{
if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
_recentlyLoadedScripts.prepend(_lastStoppedScript);
_lastStoppedScript = "";
}
for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) {
if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) {
_recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i));
}
}
ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty());
ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
ui->recentlyLoadedScriptsTableWidget->setRowCount(limit);
for (int i = 0; i < limit; ++i) {
QTableWidgetItem *scriptName = new QTableWidgetItem;
scriptName->setText(_recentlyLoadedScripts.at(i));
scriptName->setToolTip(_recentlyLoadedScripts.at(i));
scriptName->setTextAlignment(Qt::AlignCenter);
QTableWidgetItem *number = new QTableWidgetItem;
number->setText(QString::number(i+1) + ".");
ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number);
ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName);
}
}

View file

@ -0,0 +1,46 @@
//
// RunningScripts.h
// interface
//
// Created by Mohammed Nafees on 03/28/2014.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
#ifndef __hifi__RunningScriptsWidget__
#define __hifi__RunningScriptsWidget__
// Qt
#include <QDockWidget>
namespace Ui {
class RunningScriptsWidget;
}
class RunningScriptsWidget : public QDockWidget
{
Q_OBJECT
public:
explicit RunningScriptsWidget(QDockWidget *parent = 0);
~RunningScriptsWidget();
void setRunningScripts(const QStringList& list);
signals:
void stopScriptName(const QString& name);
protected:
void keyPressEvent(QKeyEvent *e);
private slots:
void stopScript(int row, int column);
void loadScript(int row, int column);
void allScriptsStopped();
private:
Ui::RunningScriptsWidget *ui;
QStringList _recentlyLoadedScripts;
QString _lastStoppedScript;
void createRecentlyLoadedScriptsTable();
};
#endif /* defined(__hifi__RunningScriptsWidget__) */

View file

@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RunningScriptsWidget</class>
<widget class="QWidget" name="RunningScriptsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>310</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">background: #f7f7f7;
font-family: Helvetica, Arial, &quot;DejaVu Sans&quot;; </string>
</property>
<widget class="QLabel" name="widgetTitle">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QLabel" name="currentlyRunningLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>40</y>
<width>301</width>
<height>20</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QPushButton" name="reloadAllButton">
<property name="geometry">
<rect>
<x>40</x>
<y>230</y>
<width>111</width>
<height>31</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 6px;</string>
</property>
<property name="text">
<string>Reload All</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/images/reload.svg</normaloff>:/images/reload.svg</iconset>
</property>
</widget>
<widget class="QPushButton" name="stopAllButton">
<property name="geometry">
<rect>
<x>160</x>
<y>230</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="styleSheet">
<string notr="true">background: #0e7077;
color: #fff;
border-radius: 6px;</string>
</property>
<property name="text">
<string>Stop All</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/images/stop.svg</normaloff>:/images/stop.svg</iconset>
</property>
</widget>
<widget class="QLabel" name="recentlyLoadedLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>280</y>
<width>301</width>
<height>20</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #0e7077;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="Line" name="line2">
<property name="geometry">
<rect>
<x>20</x>
<y>300</y>
<width>271</width>
<height>8</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="recentlyLoadedInstruction">
<property name="geometry">
<rect>
<x>20</x>
<y>590</y>
<width>271</width>
<height>41</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">color: #95a5a6;</string>
</property>
<property name="text">
<string>(click a script or use the 1-9 keys to load and run it)</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="hideWidgetButton">
<property name="geometry">
<rect>
<x>270</x>
<y>10</y>
<width>31</width>
<height>31</height>
</rect>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/images/close.svg</normaloff>:/images/close.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
<widget class="QTableWidget" name="runningScriptsTableWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>70</y>
<width>271</width>
<height>141</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;</string>
</property>
</widget>
<widget class="Line" name="line1">
<property name="geometry">
<rect>
<x>20</x>
<y>60</y>
<width>271</width>
<height>8</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QTableWidget" name="recentlyLoadedScriptsTableWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>310</y>
<width>271</width>
<height>281</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background: transparent;</string>
</property>
</widget>
<widget class="QLabel" name="noRunningScriptsLabel">
<property name="geometry">
<rect>
<x>20</x>
<y>40</y>
<width>271</width>
<height>51</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">font: 14px;</string>
</property>
<property name="text">
<string>There are no scripts currently running.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</widget>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -40,6 +40,7 @@ AvatarData::AvatarData() :
_handState(0),
_keyState(NO_KEY_DOWN),
_isChatCirclingEnabled(false),
_hasNewJointRotations(true),
_headData(NULL),
_handData(NULL),
_displayNameBoundingRect(),
@ -483,6 +484,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
}
}
} // numJoints * 8 bytes
_hasNewJointRotations = true;
return sourceBuffer - startPosition;
}

View file

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

View file

@ -29,7 +29,6 @@
#include "LocalVoxels.h"
#include "ScriptEngine.h"
int ScriptEngine::_scriptNumber = 1;
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
@ -41,7 +40,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng
}
ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString,
ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,
AbstractControllerScriptingInterface* controllerScriptingInterface) :
_scriptContents(scriptContents),
@ -58,25 +57,14 @@ 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,
ScriptEngine::ScriptEngine(const QUrl& scriptURL,
AbstractControllerScriptingInterface* controllerScriptingInterface) :
_scriptContents(),
_isFinished(false),
@ -92,24 +80,13 @@ 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);
@ -170,12 +147,6 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa
registerGlobalObject(objectName, _avatarData);
}
void ScriptEngine::cleanupMenuItems() {
if (_wantMenuItems) {
emit cleanupMenuItem(_scriptMenuName);
}
}
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
if (_isRunning) {
return false;
@ -436,8 +407,6 @@ void ScriptEngine::run() {
}
}
cleanupMenuItems();
// If we were on a thread, then wait till it's done
if (thread()) {
thread()->quit();

View file

@ -33,10 +33,10 @@ 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,
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
const QString& fileNameString = QString(""),
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
@ -49,7 +49,7 @@ 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
@ -117,8 +117,7 @@ private:
AbstractControllerScriptingInterface* _controllerScriptingInterface;
AudioScriptingInterface _audioScriptingInterface;
AvatarData* _avatarData;
bool _wantMenuItems;
QString _scriptMenuName;
QString _scriptName;
QString _fileNameString;
Quat _quatLibrary;
Vec3 _vec3Library;