From 2c35fe433d8f11fc0bb6a359e3daa42143e88856 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Thu, 24 Apr 2014 22:44:52 +0200 Subject: [PATCH 01/38] Try to load past messages with QXmppArchiveManager --- interface/src/XmppClient.cpp | 8 ++++++-- interface/src/XmppClient.h | 3 +++ interface/src/ui/ChatWindow.cpp | 2 ++ interface/src/ui/ChatWindow.h | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index d930c16b53..955d66c807 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -20,11 +20,15 @@ const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; XmppClient::XmppClient() : _xmppClient(), - _xmppMUCManager() -{ + _xmppMUCManager(), + _archiveManager() +{ AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); + + _archiveManager = new QXmppArchiveManager; + _xmppClient.addExtension(_archiveManager); } XmppClient& XmppClient::getInstance() { diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h index 8af3204377..27ca4b9759 100644 --- a/interface/src/XmppClient.h +++ b/interface/src/XmppClient.h @@ -17,6 +17,7 @@ #include #include #include +#include "QXmppArchiveManager.h" /// Generalized threaded processor for handling received inbound packets. class XmppClient : public QObject { @@ -27,6 +28,7 @@ public: QXmppClient& getXMPPClient() { return _xmppClient; } const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; } + QXmppArchiveManager* getArchiveManager() const { return _archiveManager; } private slots: void xmppConnected(); @@ -43,6 +45,7 @@ private: QXmppClient _xmppClient; QXmppMucManager _xmppMUCManager; QXmppMucRoom* _publicChatRoom; + QXmppArchiveManager* _archiveManager; }; #endif // __interface__XmppClient__ diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 635f1f3d10..ae8d481efb 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -74,6 +74,7 @@ ChatWindow::ChatWindow() : connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); } connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); + #endif } @@ -213,6 +214,7 @@ void ChatWindow::connected() { #ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + #endif startTimerForTimeStamps(); } diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 6a807f9b81..5c81d3a1f2 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -23,6 +23,10 @@ #include #include +class QXmppArchiveChat; +class QXmppArchiveManager; +class QXmppResultSetReply; + #endif namespace Ui { @@ -70,6 +74,7 @@ private slots: void error(QXmppClient::Error error); void participantsChanged(); void messageReceived(const QXmppMessage& message); + #endif }; From c968a4bdb691e89b718d7ab2cf07f8759288612c Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Thu, 24 Apr 2014 22:45:25 +0200 Subject: [PATCH 02/38] set QXmppArchiveManager limits --- interface/src/ui/ChatWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index ae8d481efb..a9ca42e3ac 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -215,6 +215,7 @@ void ChatWindow::connected() { const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + #endif startTimerForTimeStamps(); } From b02883f84d6a7ebb1eae433885381a2177bac72f Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Thu, 24 Apr 2014 22:47:42 +0200 Subject: [PATCH 03/38] add limits to QXmppArchiveManager --- interface/src/ui/ChatWindow.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index a9ca42e3ac..44b5d9789c 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -216,6 +216,16 @@ void ChatWindow::connected() { connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + // set limits + QDateTime m_startDate = QDateTime::currentDateTime().addDays(-2); + QDateTime m_endDate = QDateTime::currentDateTime(); + + QXmppResultSetQuery rsmQuery; + rsmQuery.setMax(100); + + QXmppArchiveManager* archiveManager = XmppClient::getInstance().getArchiveManager(); + archiveManager->listCollections("", m_startDate, m_endDate, rsmQuery); + #endif startTimerForTimeStamps(); } From 5c7fb3eca59200e8078e3b908fe78b3e1d299db9 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Sat, 26 Apr 2014 22:19:33 +0200 Subject: [PATCH 04/38] Add chat mention notifications --- .../resources/sounds/mention/chatMention1.wav | Bin 0 -> 38316 bytes .../resources/sounds/mention/chatMention2.wav | Bin 0 -> 44180 bytes .../resources/sounds/mention/chatMention3.wav | Bin 0 -> 2562 bytes interface/src/Menu.cpp | 3 ++ interface/src/XmppClient.cpp | 8 +-- interface/src/XmppClient.h | 3 -- interface/src/ui/ChatWindow.cpp | 47 +++++++++++++----- interface/src/ui/ChatWindow.h | 11 ++-- 8 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 interface/resources/sounds/mention/chatMention1.wav create mode 100644 interface/resources/sounds/mention/chatMention2.wav create mode 100644 interface/resources/sounds/mention/chatMention3.wav diff --git a/interface/resources/sounds/mention/chatMention1.wav b/interface/resources/sounds/mention/chatMention1.wav new file mode 100644 index 0000000000000000000000000000000000000000..e4088a973ae502c51e4eb144dd3f149dd5c4e36c GIT binary patch literal 38316 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 literal 0 HcmV?d00001 diff --git a/interface/resources/sounds/mention/chatMention2.wav b/interface/resources/sounds/mention/chatMention2.wav new file mode 100644 index 0000000000000000000000000000000000000000..9c437b155b244bd5d2fb91e8aa8bf8592a3c71ed GIT binary patch literal 44180 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 Npu>R60^getWindow()); #endif QMenu* viewMenu = addMenu("View"); diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 955d66c807..9f9129b7cc 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -16,19 +16,15 @@ #include "XmppClient.h" const QString DEFAULT_XMPP_SERVER = "chat.highfidelity.io"; -const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; +const QString DEFAULT_CHAT_ROOM = "test@public-chat.highfidelity.io"; XmppClient::XmppClient() : _xmppClient(), - _xmppMUCManager(), - _archiveManager() + _xmppMUCManager() { AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); - - _archiveManager = new QXmppArchiveManager; - _xmppClient.addExtension(_archiveManager); } XmppClient& XmppClient::getInstance() { diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h index 27ca4b9759..8af3204377 100644 --- a/interface/src/XmppClient.h +++ b/interface/src/XmppClient.h @@ -17,7 +17,6 @@ #include #include #include -#include "QXmppArchiveManager.h" /// Generalized threaded processor for handling received inbound packets. class XmppClient : public QObject { @@ -28,7 +27,6 @@ public: QXmppClient& getXMPPClient() { return _xmppClient; } const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; } - QXmppArchiveManager* getArchiveManager() const { return _archiveManager; } private slots: void xmppConnected(); @@ -45,7 +43,6 @@ private: QXmppClient _xmppClient; QXmppMucManager _xmppMUCManager; QXmppMucRoom* _publicChatRoom; - QXmppArchiveManager* _archiveManager; }; #endif // __interface__XmppClient__ diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index c36615193a..f7a27570bb 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -26,6 +26,8 @@ #include "ChatWindow.h" +#include + const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); @@ -36,7 +38,9 @@ ChatWindow::ChatWindow(QWidget* parent) : ui(new Ui::ChatWindow), numMessagesAfterLastTimeStamp(0), _mousePressed(false), - _mouseStartPosition() + _mouseStartPosition(), + _trayIcon(parent), + _effectPlayer() { setAttribute(Qt::WA_DeleteOnClose, false); @@ -80,8 +84,21 @@ ChatWindow::ChatWindow(QWidget* parent) : connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); } connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); - + connect(&_trayIcon, SIGNAL(messageClicked()), this, SLOT(notificationClicked())); #endif + + QDir mentionSoundsDir(Application::resourcesPath() + "/sounds/mention/"); + _mentionSounds = mentionSoundsDir.entryList(QDir::Files); +} + +void ChatWindow::notificationClicked() { + if (parentWidget()->isMinimized()) { + parentWidget()->showNormal(); + } + if (isHidden()) { + show(); + } + scrollToBottom(); } ChatWindow::~ChatWindow() { @@ -205,17 +222,6 @@ void ChatWindow::connected() { const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - - // set limits - QDateTime m_startDate = QDateTime::currentDateTime().addDays(-2); - QDateTime m_endDate = QDateTime::currentDateTime(); - - QXmppResultSetQuery rsmQuery; - rsmQuery.setMax(100); - - QXmppArchiveManager* archiveManager = XmppClient::getInstance().getArchiveManager(); - archiveManager->listCollections("", m_startDate, m_endDate, rsmQuery); - #endif startTimerForTimeStamps(); } @@ -317,6 +323,21 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { } else { lastMessageStamp = QDateTime::currentDateTime(); } + + QRegularExpression usernameMention("@(\\b" + AccountManager::getInstance().getUsername() + "\\b)"); + qDebug() << "message: " << message.body(); + + if (isHidden() && message.body().contains(usernameMention)) { + if (_effectPlayer.state() != QMediaPlayer::PlayingState) { + // get random sound + QFileInfo inf = QFileInfo(Application::resourcesPath() + "/sounds/mention/" + _mentionSounds.at(rand() % _mentionSounds.size())); + _effectPlayer.setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); + _effectPlayer.play(); + } + + _trayIcon.show(); + _trayIcon.showMessage(windowTitle(), message.body()); + } } #endif diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 553c300440..b2956598a9 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include @@ -24,10 +26,6 @@ #include #include -class QXmppArchiveChat; -class QXmppArchiveManager; -class QXmppResultSetReply; - #endif namespace Ui { @@ -67,6 +65,9 @@ private: QDateTime lastMessageStamp; bool _mousePressed; QPoint _mouseStartPosition; + QSystemTrayIcon _trayIcon; + QStringList _mentionSounds; + QMediaPlayer _effectPlayer; private slots: void connected(); @@ -75,7 +76,7 @@ private slots: void error(QXmppClient::Error error); void participantsChanged(); void messageReceived(const QXmppMessage& message); - + void notificationClicked(); #endif }; From 0073db4abd2029a86498ba2e51fed9842ad8b574 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Sun, 27 Apr 2014 20:49:26 +0200 Subject: [PATCH 05/38] change to public chat room --- interface/src/XmppClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 9f9129b7cc..00abfeb703 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -16,7 +16,7 @@ #include "XmppClient.h" const QString DEFAULT_XMPP_SERVER = "chat.highfidelity.io"; -const QString DEFAULT_CHAT_ROOM = "test@public-chat.highfidelity.io"; +const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; XmppClient::XmppClient() : _xmppClient(), From 963ad64d07aef86a30df26b5be76b2b8265ac941 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Mon, 28 Apr 2014 23:49:59 +0200 Subject: [PATCH 06/38] Fixed online user presence in chat window --- interface/src/Menu.cpp | 1 + interface/src/XmppClient.cpp | 1 + interface/src/XmppClient.h | 3 +++ interface/src/ui/ChatWindow.cpp | 19 ++++++++++++------- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c2c0e9522f..77435cdf00 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -210,6 +210,7 @@ Menu::Menu() : connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat())); connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat())); + QDir::setCurrent(Application::resourcesPath()); // init chat window to listen chat _chatWindow = new ChatWindow(Application::getInstance()->getWindow()); #endif diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 00abfeb703..7367993a3c 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -36,6 +36,7 @@ void XmppClient::xmppConnected() { _publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); _publicChatRoom->setNickName(AccountManager::getInstance().getUsername()); _publicChatRoom->join(); + emit joinedPublicChatRoom(); } void XmppClient::xmppError(QXmppClient::Error error) { diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h index 8af3204377..cbb06cf992 100644 --- a/interface/src/XmppClient.h +++ b/interface/src/XmppClient.h @@ -28,6 +28,9 @@ public: QXmppClient& getXMPPClient() { return _xmppClient; } const QXmppMucRoom* getPublicChatRoom() const { return _publicChatRoom; } +signals: + void joinedPublicChatRoom(); + private slots: void xmppConnected(); void xmppError(QXmppClient::Error error); diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index f7a27570bb..0300ec9202 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -32,6 +32,7 @@ const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); const QRegularExpression regexHifiLinks("([#@]\\S+)"); +const QString mentionSoundsPath("/sounds/mention/"); ChatWindow::ChatWindow(QWidget* parent) : FramelessDialog(parent, 0, POSITION_RIGHT), @@ -81,13 +82,13 @@ ChatWindow::ChatWindow(QWidget* parent) : ui->usersWidget->hide(); ui->messagesScrollArea->hide(); ui->messagePlainTextEdit->hide(); - connect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); + connect(&XmppClient::getInstance(), SIGNAL(joinedPublicChatRoom()), this, SLOT(connected())); } connect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); connect(&_trayIcon, SIGNAL(messageClicked()), this, SLOT(notificationClicked())); #endif - QDir mentionSoundsDir(Application::resourcesPath() + "/sounds/mention/"); + QDir mentionSoundsDir(Application::resourcesPath() + mentionSoundsPath); _mentionSounds = mentionSoundsDir.entryList(QDir::Files); } @@ -104,7 +105,7 @@ void ChatWindow::notificationClicked() { ChatWindow::~ChatWindow() { #ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); - disconnect(&xmppClient, SIGNAL(connected()), this, SLOT(connected())); + disconnect(&xmppClient, SIGNAL(joinedPublicChatRoom()), this, SLOT(connected())); disconnect(&xmppClient, SIGNAL(messageReceived(QXmppMessage)), this, SLOT(messageReceived(QXmppMessage))); const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); @@ -126,6 +127,11 @@ void ChatWindow::showEvent(QShowEvent* event) { if (!event->spontaneous()) { ui->messagePlainTextEdit->setFocus(); } + + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); + if (xmppClient.isConnected()) { + participantsChanged(); + } } bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { @@ -221,7 +227,6 @@ void ChatWindow::connected() { #ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - #endif startTimerForTimeStamps(); } @@ -325,12 +330,12 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { } QRegularExpression usernameMention("@(\\b" + AccountManager::getInstance().getUsername() + "\\b)"); - qDebug() << "message: " << message.body(); - if (isHidden() && message.body().contains(usernameMention)) { if (_effectPlayer.state() != QMediaPlayer::PlayingState) { // get random sound - QFileInfo inf = QFileInfo(Application::resourcesPath() + "/sounds/mention/" + _mentionSounds.at(rand() % _mentionSounds.size())); + QFileInfo inf = QFileInfo(Application::resourcesPath() + + mentionSoundsPath + + _mentionSounds.at(rand() % _mentionSounds.size())); _effectPlayer.setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); _effectPlayer.play(); } From 029e20edda095872ff44d0d9be3eced53a9d6788 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Tue, 29 Apr 2014 20:40:12 +0200 Subject: [PATCH 07/38] Added XMPP archive manager --- interface/src/XmppClient.cpp | 2 +- interface/src/ui/ChatWindow.cpp | 30 ++++++++++++++++++++++++++++++ interface/src/ui/ChatWindow.h | 8 ++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 7367993a3c..070097d179 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -21,7 +21,7 @@ const QString DEFAULT_CHAT_ROOM = "public@public-chat.highfidelity.io"; XmppClient::XmppClient() : _xmppClient(), _xmppMUCManager() -{ +{ AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 0300ec9202..207ac8a597 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -29,6 +29,8 @@ #include const int NUM_MESSAGES_TO_TIME_STAMP = 20; +const int MAX_HISTORY_CHAT_MESSAGES = 100; +const int MAX_HISTORY_DAYS = 7; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); const QRegularExpression regexHifiLinks("([#@]\\S+)"); @@ -227,10 +229,38 @@ void ChatWindow::connected() { #ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); + + // add archive manager + _archiveManager = new QXmppArchiveManager; + XmppClient::getInstance().getXMPPClient().addExtension(_archiveManager); + + connect(_archiveManager, SIGNAL(archiveChatReceived(QXmppArchiveChat, QXmppResultSetReply)), + SLOT(archiveChatReceived(QXmppArchiveChat, QXmppResultSetReply))); + + connect(_archiveManager, SIGNAL(archiveListReceived(QList, QXmppResultSetReply)), + SLOT(archiveListReceived(QList, QXmppResultSetReply))); + + QXmppResultSetQuery rsmQuery; + rsmQuery.setMax(MAX_HISTORY_CHAT_MESSAGES); + _archiveManager->listCollections(publicChatRoom->jid(), + QDateTime::currentDateTime().addDays(-MAX_HISTORY_DAYS), + QDateTime::currentDateTime(), + rsmQuery); + #endif startTimerForTimeStamps(); } +void ChatWindow::archiveChatReceived(const QXmppArchiveChat &chat, const QXmppResultSetReply &rsmReply) { + foreach (const QXmppArchiveMessage &msg, chat.messages()) { + qDebug() << "message:" << qPrintable(msg.body()); + } +} + +void ChatWindow::archiveListReceived(const QList &chats, const QXmppResultSetReply &rsmReply) { + +} + void ChatWindow::timeout() { if (numMessagesAfterLastTimeStamp >= NUM_MESSAGES_TO_TIME_STAMP) { addTimeStamp(); diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index b2956598a9..03ccc1b25e 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -26,6 +26,9 @@ #include #include +#include "QXmppArchiveIq.h" +#include "QXmppArchiveManager.h" + #endif namespace Ui { @@ -68,6 +71,7 @@ private: QSystemTrayIcon _trayIcon; QStringList _mentionSounds; QMediaPlayer _effectPlayer; + QXmppArchiveManager* _archiveManager; private slots: void connected(); @@ -77,6 +81,10 @@ private slots: void participantsChanged(); void messageReceived(const QXmppMessage& message); void notificationClicked(); + + + void archiveListReceived(const QList &chats, const QXmppResultSetReply &rsmReply); + void archiveChatReceived(const QXmppArchiveChat &chat, const QXmppResultSetReply &rsmReply); #endif }; From 785c9a302409663672f1cf08753b82a756e6731f Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Wed, 30 Apr 2014 00:25:27 +0200 Subject: [PATCH 08/38] improved mention positioning removed achive manager --- interface/src/ui/ChatWindow.cpp | 50 +++++++++++++-------------------- interface/src/ui/ChatWindow.h | 8 ------ 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 207ac8a597..d6e8e46b8e 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -29,12 +29,11 @@ #include const int NUM_MESSAGES_TO_TIME_STAMP = 20; -const int MAX_HISTORY_CHAT_MESSAGES = 100; -const int MAX_HISTORY_DAYS = 7; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); const QRegularExpression regexHifiLinks("([#@]\\S+)"); const QString mentionSoundsPath("/sounds/mention/"); +const QString mentionRegex("@(\\b%1\\b)"); ChatWindow::ChatWindow(QWidget* parent) : FramelessDialog(parent, 0, POSITION_RIGHT), @@ -101,6 +100,22 @@ void ChatWindow::notificationClicked() { if (isHidden()) { show(); } + + // find last mention + int messageCount = ui->messagesVBoxLayout->count(); + for (unsigned int i = messageCount; i > 0; i--) { + ChatMessageArea* area = (ChatMessageArea*)ui->messagesVBoxLayout->itemAt(i - 1)->widget(); + QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getUsername())); + if (area->toPlainText().contains(usernameMention)) { + int top = area->geometry().top(); + int height = area->geometry().height(); + + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + verticalScrollBar->setSliderPosition(top - verticalScrollBar->size().height() + height); + return; + } + } + scrollToBottom(); } @@ -126,6 +141,7 @@ void ChatWindow::keyPressEvent(QKeyEvent* event) { void ChatWindow::showEvent(QShowEvent* event) { FramelessDialog::showEvent(event); + if (!event->spontaneous()) { ui->messagePlainTextEdit->setFocus(); } @@ -229,38 +245,10 @@ void ChatWindow::connected() { #ifdef HAVE_QXMPP const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged())); - - // add archive manager - _archiveManager = new QXmppArchiveManager; - XmppClient::getInstance().getXMPPClient().addExtension(_archiveManager); - - connect(_archiveManager, SIGNAL(archiveChatReceived(QXmppArchiveChat, QXmppResultSetReply)), - SLOT(archiveChatReceived(QXmppArchiveChat, QXmppResultSetReply))); - - connect(_archiveManager, SIGNAL(archiveListReceived(QList, QXmppResultSetReply)), - SLOT(archiveListReceived(QList, QXmppResultSetReply))); - - QXmppResultSetQuery rsmQuery; - rsmQuery.setMax(MAX_HISTORY_CHAT_MESSAGES); - _archiveManager->listCollections(publicChatRoom->jid(), - QDateTime::currentDateTime().addDays(-MAX_HISTORY_DAYS), - QDateTime::currentDateTime(), - rsmQuery); - #endif startTimerForTimeStamps(); } -void ChatWindow::archiveChatReceived(const QXmppArchiveChat &chat, const QXmppResultSetReply &rsmReply) { - foreach (const QXmppArchiveMessage &msg, chat.messages()) { - qDebug() << "message:" << qPrintable(msg.body()); - } -} - -void ChatWindow::archiveListReceived(const QList &chats, const QXmppResultSetReply &rsmReply) { - -} - void ChatWindow::timeout() { if (numMessagesAfterLastTimeStamp >= NUM_MESSAGES_TO_TIME_STAMP) { addTimeStamp(); @@ -359,7 +347,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { lastMessageStamp = QDateTime::currentDateTime(); } - QRegularExpression usernameMention("@(\\b" + AccountManager::getInstance().getUsername() + "\\b)"); + QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getUsername())); if (isHidden() && message.body().contains(usernameMention)) { if (_effectPlayer.state() != QMediaPlayer::PlayingState) { // get random sound diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 03ccc1b25e..b2956598a9 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -26,9 +26,6 @@ #include #include -#include "QXmppArchiveIq.h" -#include "QXmppArchiveManager.h" - #endif namespace Ui { @@ -71,7 +68,6 @@ private: QSystemTrayIcon _trayIcon; QStringList _mentionSounds; QMediaPlayer _effectPlayer; - QXmppArchiveManager* _archiveManager; private slots: void connected(); @@ -81,10 +77,6 @@ private slots: void participantsChanged(); void messageReceived(const QXmppMessage& message); void notificationClicked(); - - - void archiveListReceived(const QList &chats, const QXmppResultSetReply &rsmReply); - void archiveChatReceived(const QXmppArchiveChat &chat, const QXmppResultSetReply &rsmReply); #endif }; From 4d8e1b5bd445df148e28f49a72cae17d8fc96aee Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Fri, 2 May 2014 23:52:12 +0200 Subject: [PATCH 09/38] repled audio files --- .../resources/sounds/mention/Mentioned A.wav | Bin 0 -> 192044 bytes .../resources/sounds/mention/Mentioned B.wav | Bin 0 -> 192044 bytes .../resources/sounds/mention/Mentioned C.wav | Bin 0 -> 192044 bytes .../resources/sounds/mention/chatMention1.wav | Bin 38316 -> 0 bytes .../resources/sounds/mention/chatMention2.wav | Bin 44180 -> 0 bytes .../resources/sounds/mention/chatMention3.wav | Bin 2562 -> 0 bytes interface/src/ui/ChatWindow.cpp | 1 + 7 files changed, 1 insertion(+) create mode 100644 interface/resources/sounds/mention/Mentioned A.wav create mode 100644 interface/resources/sounds/mention/Mentioned B.wav create mode 100644 interface/resources/sounds/mention/Mentioned C.wav delete mode 100644 interface/resources/sounds/mention/chatMention1.wav delete mode 100644 interface/resources/sounds/mention/chatMention2.wav delete mode 100644 interface/resources/sounds/mention/chatMention3.wav diff --git a/interface/resources/sounds/mention/Mentioned A.wav b/interface/resources/sounds/mention/Mentioned A.wav new file mode 100644 index 0000000000000000000000000000000000000000..5b203d7adf9b6f7d2f3fcbd6957fbff9d9b43a3f GIT binary patch literal 192044 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 literal 0 HcmV?d00001 diff --git a/interface/resources/sounds/mention/Mentioned C.wav b/interface/resources/sounds/mention/Mentioned C.wav new file mode 100644 index 0000000000000000000000000000000000000000..78d5fe3f6671b50de02faca7d28118f8a5c57f19 GIT binary patch literal 192044 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 diff --git a/interface/resources/sounds/mention/chatMention2.wav b/interface/resources/sounds/mention/chatMention2.wav deleted file mode 100644 index 9c437b155b244bd5d2fb91e8aa8bf8592a3c71ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44180 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 Npu>R60^ Date: Sun, 4 May 2014 15:23:31 -0700 Subject: [PATCH 10/38] Working on allowing attachment models to be uploaded. --- interface/src/Application.cpp | 30 ++++++++------ interface/src/Application.h | 9 ++-- interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + interface/src/ModelUploader.cpp | 48 +++++++++++----------- interface/src/ModelUploader.h | 10 +++-- interface/src/ui/ModelsBrowser.cpp | 11 ++--- interface/src/ui/ModelsBrowser.h | 8 ++-- interface/src/ui/PreferencesDialog.cpp | 4 +- libraries/avatars/src/AvatarData.h | 11 +++++ libraries/networking/src/PacketHeaders.cpp | 2 + 11 files changed, 79 insertions(+), 57 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 42f11ee576..5ce9468318 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -63,11 +63,11 @@ #include #include #include -#include #include "Application.h" #include "InterfaceVersion.h" #include "Menu.h" +#include "ModelUploader.h" #include "Util.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" @@ -3090,6 +3090,16 @@ void Application::setMenuShortcutsEnabled(bool enabled) { setShortcutsEnabled(_window->menuBar(), enabled); } +void Application::uploadModel(ModelType modelType) { + ModelUploader* uploader = new ModelUploader(modelType); + QThread* thread = new QThread(); + thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit())); + thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); + uploader->connect(thread, SIGNAL(started()), SLOT(send())); + + thread->start(); +} + void Application::updateWindowTitle(){ QString buildVersion = " (build " + applicationVersion() + ")"; @@ -3417,22 +3427,16 @@ void Application::toggleRunningScriptsWidget() { } } -void Application::uploadFST(bool isHead) { - ModelUploader* uploader = new ModelUploader(isHead); - QThread* thread = new QThread(); - thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit())); - thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); - uploader->connect(thread, SIGNAL(started()), SLOT(send())); - - thread->start(); -} - void Application::uploadHead() { - uploadFST(true); + uploadModel(HEAD_MODEL); } void Application::uploadSkeleton() { - uploadFST(false); + uploadModel(SKELETON_MODEL); +} + +void Application::uploadAttachment() { + uploadModel(ATTACHMENT_MODEL); } ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 96b472e553..a7073ac4e9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -71,6 +71,7 @@ #include "scripting/ControllerScriptingInterface.h" #include "ui/BandwidthDialog.h" #include "ui/BandwidthMeter.h" +#include "ui/ModelsBrowser.h" #include "ui/OctreeStatsDialog.h" #include "ui/RearMirrorTools.h" #include "ui/LodToolsDialog.h" @@ -295,9 +296,9 @@ public slots: void reloadAllScripts(); void toggleRunningScriptsWidget(); - void uploadFST(bool isHead); void uploadHead(); void uploadSkeleton(); + void uploadAttachment(); void bumpSettings() { ++_numChangedSettings; } @@ -375,13 +376,11 @@ private: void setMenuShortcutsEnabled(bool enabled); + void uploadModel(ModelType modelType); + static void attachNewHeadToNode(Node *newNode); static void* networkReceive(void* args); // network receive thread - void findAxisAlignment(); - - void displayRearMirrorTools(); - MainWindow* _window; GLCanvas* _glWidget; // our GLCanvas has a couple extra features diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index baa82a2f6a..b6df2f7269 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -157,6 +157,8 @@ 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())); + addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadAttachment, 0, + Application::getInstance(), SLOT(uploadAttachment())); addDisabledActionAndSeparator(fileMenu, "Settings"); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings())); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index da2585f81a..e37d256f62 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -363,6 +363,7 @@ namespace MenuOption { const QString TestPing = "Test Ping"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; + const QString UploadAttachment = "Upload Attachment Model"; const QString UploadHead = "Upload Head Model"; const QString UploadSkeleton = "Upload Skeleton Model"; const QString Visage = "Visage"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 0ffd725716..2b86e04829 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -59,11 +59,11 @@ static const int MAX_CHECK = 30; static const int QCOMPRESS_HEADER_POSITION = 0; static const int QCOMPRESS_HEADER_SIZE = 4; -ModelUploader::ModelUploader(bool isHead) : +ModelUploader::ModelUploader(ModelType modelType) : _lodCount(-1), _texturesCount(-1), _totalSize(0), - _isHead(isHead), + _modelType(modelType), _readyToSend(false), _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)), _numberOfChecks(MAX_CHECK) @@ -190,7 +190,7 @@ bool ModelUploader::zip() { } // open the dialog to configure the rest - ModelPropertiesDialog properties(_isHead, mapping, basePath, geometry); + ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry); if (properties.exec() == QDialog::Rejected) { return false; } @@ -202,7 +202,7 @@ bool ModelUploader::zip() { textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\""); textPart.setBody(nameField); _dataMultiPart->append(textPart); - _url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst"; + _url = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField + ".fst"; } else { QMessageBox::warning(NULL, QString("ModelUploader::zip()"), @@ -260,11 +260,7 @@ bool ModelUploader::zip() { QHttpPart textPart; textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" " name=\"model_category\""); - if (_isHead) { - textPart.setBody("heads"); - } else { - textPart.setBody("skeletons"); - } + textPart.setBody(MODEL_TYPE_NAMES[_modelType]); _dataMultiPart->append(textPart); _readyToSend = true; @@ -510,9 +506,9 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const return true; } -ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping, +ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, const QString& basePath, const FBXGeometry& geometry) : - _isHead(isHead), + _modelType(modelType), _originalMapping(originalMapping), _basePath(basePath), _geometry(geometry) { @@ -531,10 +527,12 @@ ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& or _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); - form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); - form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); - form->addRow("Neck Joint:", _neckJoint = createJointBox()); - if (!isHead) { + if (_modelType != ATTACHMENT_MODEL) { + form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); + form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); + form->addRow("Neck Joint:", _neckJoint = createJointBox()); + } + if (_modelType == SKELETON_MODEL) { form->addRow("Root Joint:", _rootJoint = createJointBox()); form->addRow("Lean Joint:", _leanJoint = createJointBox()); form->addRow("Head Joint:", _headJoint = createJointBox()); @@ -573,10 +571,12 @@ QVariantHash ModelPropertiesDialog::getMapping() const { mapping.insert(JOINT_INDEX_FIELD, jointIndices); QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText()); - insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); - insertJointMapping(joints, "jointNeck", _neckJoint->currentText()); - if (!_isHead) { + if (_modelType != ATTACHMENT_MODEL) { + insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText()); + insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); + insertJointMapping(joints, "jointNeck", _neckJoint->currentText()); + } + if (_modelType == SKELETON_MODEL) { insertJointMapping(joints, "jointRoot", _rootJoint->currentText()); insertJointMapping(joints, "jointLean", _leanJoint->currentText()); insertJointMapping(joints, "jointHead", _headJoint->currentText()); @@ -604,10 +604,12 @@ void ModelPropertiesDialog::reset() { _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); - setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); - setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); - setJointText(_neckJoint, jointHash.value("jointNeck").toString()); - if (!_isHead) { + if (_modelType != ATTACHMENT_MODEL) { + setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); + setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); + setJointText(_neckJoint, jointHash.value("jointNeck").toString()); + } + if (_modelType == SKELETON_MODEL) { setJointText(_rootJoint, jointHash.value("jointRoot").toString()); setJointText(_leanJoint, jointHash.value("jointLean").toString()); setJointText(_headJoint, jointHash.value("jointHead").toString()); diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 11594b3d95..499bfad03b 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -17,6 +17,8 @@ #include +#include "ui/ModelsBrowser.h" + class QComboBox; class QDoubleSpinBox; class QFileInfo; @@ -30,7 +32,7 @@ class ModelUploader : public QObject { Q_OBJECT public: - ModelUploader(bool isHead); + ModelUploader(ModelType type); ~ModelUploader(); public slots: @@ -49,7 +51,7 @@ private: int _lodCount; int _texturesCount; int _totalSize; - bool _isHead; + ModelType _modelType; bool _readyToSend; QHttpMultiPart* _dataMultiPart; @@ -73,7 +75,7 @@ class ModelPropertiesDialog : public QDialog { Q_OBJECT public: - ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping, + ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, const QString& basePath, const FBXGeometry& geometry); QVariantHash getMapping() const; @@ -87,7 +89,7 @@ private: QComboBox* createJointBox(bool withNone = true) const; void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; - bool _isHead; + ModelType _modelType; QVariantHash _originalMapping; QString _basePath; FBXGeometry _geometry; diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 77e056bdd3..f65829a8ac 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -22,10 +22,11 @@ #include "ModelsBrowser.h" +const char* MODEL_TYPE_NAMES[] = { "heads", "skeletons", "attachments" }; + 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 MODELS_LOCATION = "models/"; static const QString PREFIX_PARAMETER_NAME = "prefix"; static const QString MARKER_PARAMETER_NAME = "marker"; @@ -243,11 +244,7 @@ void ModelHandler::queryNewFiles(QString marker) { // 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); - } + query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION + MODEL_TYPE_NAMES[_type]); if (!marker.isEmpty()) { query.addQueryItem(MARKER_PARAMETER_NAME, marker); diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h index 81f64c6730..ff273a45bc 100644 --- a/interface/src/ui/ModelsBrowser.h +++ b/interface/src/ui/ModelsBrowser.h @@ -16,12 +16,14 @@ #include #include - enum ModelType { - Head, - Skeleton + HEAD_MODEL, + SKELETON_MODEL, + ATTACHMENT_MODEL }; +extern const char* MODEL_TYPE_NAMES[]; + class ModelHandler : public QObject { Q_OBJECT public: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 7a70b743bd..eed33fda23 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -48,7 +48,7 @@ void PreferencesDialog::openHeadModelBrowser() { setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); - ModelsBrowser modelBrowser(Head); + ModelsBrowser modelBrowser(HEAD_MODEL); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl); modelBrowser.browse(); @@ -60,7 +60,7 @@ void PreferencesDialog::openBodyModelBrowser() { setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); - ModelsBrowser modelBrowser(Skeleton); + ModelsBrowser modelBrowser(SKELETON_MODEL); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl); modelBrowser.browse(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index be47aed1ba..78b33571e8 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -81,6 +81,7 @@ const glm::vec3 vec3Zero(0.0f); class QNetworkAccessManager; +class AttachmentData; class JointData; class AvatarData : public QObject { @@ -275,6 +276,7 @@ protected: QUrl _faceModelURL; QUrl _skeletonModelURL; + QVector _attachmentData; QString _displayName; QRect _displayNameBoundingRect; @@ -309,4 +311,13 @@ public: glm::quat rotation; }; +class AttachmentData { +public: + QUrl modelURL; + int jointIndex; + glm::vec3 translation; + glm::quat rotation; + float scale; +}; + #endif // hifi_AvatarData_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 0955759097..0785b81581 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -49,6 +49,8 @@ PacketVersion versionForPacketType(PacketType type) { switch (type) { case PacketTypeAvatarData: return 3; + case PacketTypeAvatarIdentity: + return 1; case PacketTypeEnvironmentData: return 1; case PacketTypeParticleData: From 53a276090547f5b49483e06c4fd564a89cd69712 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Sun, 4 May 2014 16:24:23 -0700 Subject: [PATCH 11/38] More attachment bits. --- interface/src/Menu.cpp | 12 ++++++++ interface/src/Menu.h | 4 +++ interface/src/avatar/MyAvatar.cpp | 40 ++++++++++++++++++++++++++ interface/src/ui/AttachmentsDialog.cpp | 39 +++++++++++++++++++++++++ interface/src/ui/AttachmentsDialog.h | 30 +++++++++++++++++++ libraries/avatars/src/AvatarData.cpp | 34 ++++++++++++++++++++-- libraries/avatars/src/AvatarData.h | 12 +++++++- libraries/shared/src/StreamUtils.cpp | 18 ++++++++++++ libraries/shared/src/StreamUtils.h | 7 +++++ 9 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 interface/src/ui/AttachmentsDialog.cpp create mode 100644 interface/src/ui/AttachmentsDialog.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index b6df2f7269..ed03e77021 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -37,6 +37,7 @@ #include "Menu.h" #include "scripting/MenuScriptingInterface.h" #include "Util.h" +#include "ui/AttachmentsDialog.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" #include "ui/ModelsBrowser.h" @@ -189,6 +190,8 @@ Menu::Menu() : SLOT(editPreferences()), QAction::PreferencesRole); + addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); + addDisabledActionAndSeparator(editMenu, "Physics"); QObject* avatar = appInstance->getAvatar(); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, true, @@ -834,6 +837,15 @@ void Menu::editPreferences() { } } +void Menu::editAttachments() { + if (!_attachmentsDialog) { + _attachmentsDialog = new AttachmentsDialog(); + _attachmentsDialog->show(); + } else { + _attachmentsDialog->close(); + } +} + void Menu::goToDomain(const QString newDomain) { if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect diff --git a/interface/src/Menu.h b/interface/src/Menu.h index e37d256f62..348d61bab8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -64,6 +64,7 @@ struct ViewFrustumOffset { class QSettings; +class AttachmentsDialog; class BandwidthDialog; class LodToolsDialog; class MetavoxelEditor; @@ -171,6 +172,7 @@ public slots: private slots: void aboutApp(); void editPreferences(); + void editAttachments(); void goToDomainDialog(); void goToLocation(); void nameLocation(); @@ -252,6 +254,7 @@ private: SimpleMovingAverage _fastFPSAverage; QAction* _loginAction; QPointer _preferencesDialog; + QPointer _attachmentsDialog; QAction* _chatAction; QString _snapshotsLocation; }; @@ -261,6 +264,7 @@ namespace MenuOption { const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AmbientOcclusion = "Ambient Occlusion"; const QString Atmosphere = "Atmosphere"; + const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; const QString AudioScope = "Audio Scope"; const QString AudioScopePause = "Pause Audio Scope"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 652eb56258..6af7235117 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -491,6 +491,24 @@ void MyAvatar::saveData(QSettings* settings) { settings->setValue("faceModelURL", _faceModelURL); settings->setValue("skeletonModelURL", _skeletonModelURL); + + settings->beginWriteArray("attachmentData"); + for (int i = 0; i < _attachmentData.size(); i++) { + settings->setArrayIndex(i); + const AttachmentData& attachment = _attachmentData.at(i); + settings->setValue("modelURL", attachment.modelURL); + settings->setValue("jointName", attachment.jointName); + settings->setValue("translation_x", attachment.translation.x); + settings->setValue("translation_y", attachment.translation.y); + settings->setValue("translation_z", attachment.translation.z); + glm::vec3 eulers = safeEulerAngles(attachment.rotation); + settings->setValue("rotation_x", eulers.x); + settings->setValue("rotation_y", eulers.y); + settings->setValue("rotation_z", eulers.z); + settings->setValue("scale", attachment.scale); + } + settings->endArray(); + settings->setValue("displayName", _displayName); settings->endGroup(); @@ -519,6 +537,28 @@ void MyAvatar::loadData(QSettings* settings) { setFaceModelURL(settings->value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl()); setSkeletonModelURL(settings->value("skeletonModelURL").toUrl()); + + QVector attachmentData; + int attachmentCount = settings->beginReadArray("attachmentData"); + for (int i = 0; i < attachmentCount; i++) { + settings->setArrayIndex(i); + AttachmentData attachment; + attachment.modelURL = settings->value("modelURL").toUrl(); + attachment.jointName = settings->value("jointName").toString(); + attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); + attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); + attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); + glm::vec3 eulers; + eulers.x = loadSetting(settings, "rotation_x", 0.0f); + eulers.y = loadSetting(settings, "rotation_y", 0.0f); + eulers.z = loadSetting(settings, "rotation_z", 0.0f); + attachment.rotation = glm::quat(eulers); + attachment.scale = loadSetting(settings, "scale", 1.0f); + attachmentData.append(attachment); + } + settings->endArray(); + setAttachmentData(attachmentData); + setDisplayName(settings->value("displayName").toString()); settings->endGroup(); diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp new file mode 100644 index 0000000000..319927c492 --- /dev/null +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -0,0 +1,39 @@ +// +// MetavoxelEditor.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 1/21/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include "Application.h" +#include "AttachmentsDialog.h" + +AttachmentsDialog::AttachmentsDialog() : + QDialog(Application::getInstance()->getWindow()) { + + setWindowTitle("Edit Attachments"); + setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout* layout = new QVBoxLayout(); + setLayout(layout); + + QPushButton* newAttachment = new QPushButton("New Attachment"); + connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); + layout->addWidget(newAttachment); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttons); + connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); +} + +void AttachmentsDialog::addAttachment() { + +} diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h new file mode 100644 index 0000000000..f6dc3d25b4 --- /dev/null +++ b/interface/src/ui/AttachmentsDialog.h @@ -0,0 +1,30 @@ +// +// AttachmentsDialog.h +// interface/src/ui +// +// Created by Andrzej Kapolka on 5/4/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AttachmentsDialog_h +#define hifi_AttachmentsDialog_h + +#include + +/// Allows users to edit the avatar attachments. +class AttachmentsDialog : public QDialog { + Q_OBJECT + +public: + + AttachmentsDialog(); + +private slots: + + void addAttachment(); +}; + +#endif // hifi_AttachmentsDialog_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b57d5406d5..485b5517f0 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -599,8 +599,9 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { QUuid avatarUUID; QUrl faceModelURL, skeletonModelURL; + QVector attachmentData; QString displayName; - packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL >> displayName; + packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL >> attachmentData >> displayName; bool hasIdentityChanged = false; @@ -618,7 +619,12 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { setDisplayName(displayName); hasIdentityChanged = true; } - + + if (attachmentData != _attachmentData) { + setAttachmentData(attachmentData); + hasIdentityChanged = true; + } + return hasIdentityChanged; } @@ -626,7 +632,7 @@ QByteArray AvatarData::identityByteArray() { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); - identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _displayName; + identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _attachmentData << _displayName; return identityData; } @@ -654,6 +660,12 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { updateJointMappings(); } +void AvatarData::setAttachmentData(const QVector& attachmentData) { + _attachmentData = attachmentData; + + qDebug() << "Changing attachment data for avatar."; +} + void AvatarData::setDisplayName(const QString& displayName) { _displayName = displayName; @@ -762,3 +774,19 @@ void AvatarData::updateJointMappings() { connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply())); } } + +bool AttachmentData::operator==(const AttachmentData& other) const { + return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && + rotation == other.rotation && scale == other.scale; +} + +QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { + return out << attachment.modelURL << attachment.jointName << + attachment.translation << attachment.rotation << attachment.scale; +} + +QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { + return in >> attachment.modelURL >> attachment.jointName >> + attachment.translation >> attachment.rotation >> attachment.scale; +} + diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 78b33571e8..a853706005 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -45,6 +45,8 @@ typedef unsigned long long quint64; #include #include +#include + #include #include "HeadData.h" @@ -79,6 +81,7 @@ enum KeyState { const glm::vec3 vec3Zero(0.0f); +class QDataStream; class QNetworkAccessManager; class AttachmentData; @@ -211,9 +214,11 @@ public: const QUrl& getFaceModelURL() const { return _faceModelURL; } QString getFaceModelURLString() const { return _faceModelURL.toString(); } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } + const QVector& getAttachmentData() const { return _attachmentData; } const QString& getDisplayName() const { return _displayName; } virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + virtual void setAttachmentData(const QVector& attachmentData); virtual void setDisplayName(const QString& displayName); virtual void setBillboard(const QByteArray& billboard); @@ -314,10 +319,15 @@ public: class AttachmentData { public: QUrl modelURL; - int jointIndex; + QString jointName; glm::vec3 translation; glm::quat rotation; float scale; + + bool operator==(const AttachmentData& other) const; }; +QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment); +QDataStream& operator>>(QDataStream& in, AttachmentData& attachment); + #endif // hifi_AvatarData_h diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index d7b0c83c1f..5356c45a51 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "StreamUtils.h" @@ -47,6 +49,22 @@ std::ostream& operator<<(std::ostream& s, const glm::mat4& m) { return s; } +QDataStream& operator<<(QDataStream& out, const glm::vec3& vector) { + return out << vector.x << vector.y << vector.z; +} + +QDataStream& operator>>(QDataStream& in, glm::vec3& vector) { + return in >> vector.x >> vector.y >> vector.z; +} + +QDataStream& operator<<(QDataStream& out, const glm::quat& quaternion) { + return out << quaternion.x << quaternion.y << quaternion.z << quaternion.w; +} + +QDataStream& operator>>(QDataStream& in, glm::quat& quaternion) { + return in >> quaternion.x >> quaternion.y >> quaternion.z >> quaternion.w; +} + // less common utils can be enabled with DEBUG #ifdef DEBUG diff --git a/libraries/shared/src/StreamUtils.h b/libraries/shared/src/StreamUtils.h index 2546d49ffc..2a42a3ea7b 100644 --- a/libraries/shared/src/StreamUtils.h +++ b/libraries/shared/src/StreamUtils.h @@ -19,6 +19,7 @@ #include #include +class QDataStream; namespace StreamUtil { // dump the buffer, 32 bytes per row, each byte in hex, separated by whitespace @@ -29,6 +30,12 @@ std::ostream& operator<<(std::ostream& s, const glm::vec3& v); std::ostream& operator<<(std::ostream& s, const glm::quat& q); std::ostream& operator<<(std::ostream& s, const glm::mat4& m); +QDataStream& operator<<(QDataStream& out, const glm::vec3& vector); +QDataStream& operator>>(QDataStream& in, glm::vec3& vector); + +QDataStream& operator<<(QDataStream& out, const glm::quat& quaternion); +QDataStream& operator>>(QDataStream& in, glm::quat& quaternion); + // less common utils can be enabled with DEBUG #ifdef DEBUG #include "CollisionInfo.h" From e898b4a900a1841beeeb78bea6fb398aefd818c2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Sun, 4 May 2014 17:24:15 -0700 Subject: [PATCH 12/38] More attachment bits. --- interface/src/ui/AttachmentsDialog.cpp | 57 +++++++++++++++++++++++++- interface/src/ui/AttachmentsDialog.h | 34 ++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 319927c492..34c1f251e1 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -9,7 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include +#include +#include +#include #include #include @@ -25,6 +29,10 @@ AttachmentsDialog::AttachmentsDialog() : QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); + foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) { + addAttachment(data); + } + QPushButton* newAttachment = new QPushButton("New Attachment"); connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); layout->addWidget(newAttachment); @@ -34,6 +42,51 @@ AttachmentsDialog::AttachmentsDialog() : connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); } -void AttachmentsDialog::addAttachment() { - +void AttachmentsDialog::addAttachment(const AttachmentData& data) { + QVBoxLayout* layout = static_cast(this->layout()); + layout->insertWidget(layout->count() - 2, new AttachmentPanel(data)); +} + +AttachmentPanel::AttachmentPanel(const AttachmentData& data) { + QFormLayout* layout = new QFormLayout(); + setLayout(layout); + + QHBoxLayout* urlBox = new QHBoxLayout(); + layout->addRow("Model URL:", urlBox); + urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); + QPushButton* chooseURL = new QPushButton("Choose"); + urlBox->addWidget(chooseURL); + connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); + + layout->addRow("Joint:", _jointName = new QComboBox()); + + QHBoxLayout* translationBox = new QHBoxLayout(); + translationBox->addWidget(_translationX = new QDoubleSpinBox()); + translationBox->addWidget(_translationY = new QDoubleSpinBox()); + translationBox->addWidget(_translationZ = new QDoubleSpinBox()); + layout->addRow("Translation:", translationBox); + + QHBoxLayout* rotationBox = new QHBoxLayout(); + rotationBox->addWidget(_rotationX = new QDoubleSpinBox()); + rotationBox->addWidget(_rotationY = new QDoubleSpinBox()); + rotationBox->addWidget(_rotationZ = new QDoubleSpinBox()); + layout->addRow("Rotation:", rotationBox); + + layout->addRow("Scale:", _scale = new QDoubleSpinBox()); + _scale->setSingleStep(0.01); + _scale->setMaximum(FLT_MAX); + + QPushButton* remove = new QPushButton("Delete"); + layout->addRow(remove); + connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); +} + +void AttachmentPanel::chooseModelURL() { + ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this); + connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&))); + modelBrowser.browse(); +} + +void AttachmentPanel::setModelURL(const QString& url) { + _modelURL->setText(url); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index f6dc3d25b4..e8c173f80a 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -14,6 +14,12 @@ #include +#include + +class QComboBox; +class QDoubleSpinner; +class QLineEdit; + /// Allows users to edit the avatar attachments. class AttachmentsDialog : public QDialog { Q_OBJECT @@ -24,7 +30,33 @@ public: private slots: - void addAttachment(); + void addAttachment(const AttachmentData& data = AttachmentData()); +}; + +/// A panel controlling a single attachment. +class AttachmentPanel : public QWidget { + Q_OBJECT + +public: + + AttachmentPanel(const AttachmentData& data = AttachmentData()); + +private slots: + + void chooseModelURL(); + void setModelURL(const QString& url); + +private: + + QLineEdit* _modelURL; + QComboBox* _jointName; + QDoubleSpinBox* _translationX; + QDoubleSpinBox* _translationY; + QDoubleSpinBox* _translationZ; + QDoubleSpinBox* _rotationX; + QDoubleSpinBox* _rotationY; + QDoubleSpinBox* _rotationZ; + QDoubleSpinBox* _scale; }; #endif // hifi_AttachmentsDialog_h From 56e7900aa66ac57116af0177f704cce306af27ac Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Mon, 5 May 2014 21:36:05 +0200 Subject: [PATCH 13/38] Merge fix --- interface/src/ui/ChatWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 72ed2759fd..7a52ad08e7 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -106,7 +106,7 @@ void ChatWindow::notificationClicked() { int messageCount = ui->messagesVBoxLayout->count(); for (unsigned int i = messageCount; i > 0; i--) { ChatMessageArea* area = (ChatMessageArea*)ui->messagesVBoxLayout->itemAt(i - 1)->widget(); - QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getUsername())); + QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername())); if (area->toPlainText().contains(usernameMention)) { int top = area->geometry().top(); int height = area->geometry().height(); @@ -348,7 +348,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { lastMessageStamp = QDateTime::currentDateTime(); } - QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getUsername())); + QRegularExpression usernameMention(mentionRegex.arg(AccountManager::getInstance().getAccountInfo().getUsername())); if (isHidden() && message.body().contains(usernameMention)) { if (_effectPlayer.state() != QMediaPlayer::PlayingState) { // get random sound From 65e34f9697defaef4e1ef854bcd9f1d19e2a453f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 5 May 2014 13:41:50 -0700 Subject: [PATCH 14/38] More work on attachment interface. --- interface/src/ui/AttachmentsDialog.cpp | 80 +++++++++++++++++++++++--- interface/src/ui/AttachmentsDialog.h | 13 ++++- libraries/avatars/src/AvatarData.cpp | 6 +- libraries/avatars/src/AvatarData.h | 2 + 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 34c1f251e1..38ef10625f 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "Application.h" @@ -29,6 +30,14 @@ AttachmentsDialog::AttachmentsDialog() : QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); + QScrollArea* area = new QScrollArea(); + layout->addWidget(area); + area->setWidgetResizable(true); + QWidget* container = new QWidget(); + container->setLayout(_attachments = new QVBoxLayout()); + container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + area->setWidget(container); + foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) { addAttachment(data); } @@ -40,47 +49,99 @@ AttachmentsDialog::AttachmentsDialog() : QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); + + setMinimumSize(600, 600); +} + +void AttachmentsDialog::updateAttachmentData() { + QVector data; + for (int i = 0; i < _attachments->count(); i++) { + data.append(static_cast(_attachments->itemAt(i)->widget())->getAttachmentData()); + } + Application::getInstance()->getAvatar()->setAttachmentData(data); } void AttachmentsDialog::addAttachment(const AttachmentData& data) { - QVBoxLayout* layout = static_cast(this->layout()); - layout->insertWidget(layout->count() - 2, new AttachmentPanel(data)); + _attachments->addWidget(new AttachmentPanel(this, data)); } -AttachmentPanel::AttachmentPanel(const AttachmentData& data) { +static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { + QDoubleSpinBox* box = new QDoubleSpinBox(); + box->setSingleStep(0.01); + box->setMinimum(-FLT_MAX); + box->setMaximum(FLT_MAX); + box->setValue(value); + dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); + return box; +} + +static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) { + QDoubleSpinBox* box = new QDoubleSpinBox(); + box->setMinimum(-180.0); + box->setMaximum(180.0); + box->setWrapping(true); + box->setValue(value); + dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); + return box; +} + +AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) { QFormLayout* layout = new QFormLayout(); setLayout(layout); QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("Model URL:", urlBox); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); + _modelURL->setText(data.modelURL.toString()); + dialog->connect(_modelURL, SIGNAL(returnPressed()), SLOT(updateAttachmentData())); QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); layout->addRow("Joint:", _jointName = new QComboBox()); + QSharedPointer geometry = Application::getInstance()->getAvatar()->getSkeletonModel().getGeometry(); + if (geometry && geometry->isLoaded()) { + foreach (const FBXJoint& joint, geometry->getFBXGeometry().joints) { + _jointName->addItem(joint.name); + } + } + _jointName->setCurrentText(data.jointName); + dialog->connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData())); QHBoxLayout* translationBox = new QHBoxLayout(); - translationBox->addWidget(_translationX = new QDoubleSpinBox()); - translationBox->addWidget(_translationY = new QDoubleSpinBox()); - translationBox->addWidget(_translationZ = new QDoubleSpinBox()); + translationBox->addWidget(_translationX = createTranslationBox(dialog, data.translation.x)); + translationBox->addWidget(_translationY = createTranslationBox(dialog, data.translation.y)); + translationBox->addWidget(_translationZ = createTranslationBox(dialog, data.translation.z)); layout->addRow("Translation:", translationBox); QHBoxLayout* rotationBox = new QHBoxLayout(); - rotationBox->addWidget(_rotationX = new QDoubleSpinBox()); - rotationBox->addWidget(_rotationY = new QDoubleSpinBox()); - rotationBox->addWidget(_rotationZ = new QDoubleSpinBox()); + glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); + rotationBox->addWidget(_rotationX = createRotationBox(dialog, eulers.x)); + rotationBox->addWidget(_rotationY = createRotationBox(dialog, eulers.y)); + rotationBox->addWidget(_rotationZ = createRotationBox(dialog, eulers.z)); layout->addRow("Rotation:", rotationBox); layout->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setSingleStep(0.01); _scale->setMaximum(FLT_MAX); + _scale->setValue(data.scale); + dialog->connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); } +AttachmentData AttachmentPanel::getAttachmentData() const { + AttachmentData data; + data.modelURL = _modelURL->text(); + data.jointName = _jointName->currentText(); + data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); + data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); + data.scale = _scale->value(); + return data; +} + void AttachmentPanel::chooseModelURL() { ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this); connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&))); @@ -89,4 +150,5 @@ void AttachmentPanel::chooseModelURL() { void AttachmentPanel::setModelURL(const QString& url) { _modelURL->setText(url); + emit _modelURL->returnPressed(); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index e8c173f80a..c23bd2efb8 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -19,6 +19,7 @@ class QComboBox; class QDoubleSpinner; class QLineEdit; +class QVBoxLayout; /// Allows users to edit the avatar attachments. class AttachmentsDialog : public QDialog { @@ -28,9 +29,17 @@ public: AttachmentsDialog(); +public slots: + + void updateAttachmentData(); + private slots: void addAttachment(const AttachmentData& data = AttachmentData()); + +private: + + QVBoxLayout* _attachments; }; /// A panel controlling a single attachment. @@ -39,7 +48,9 @@ class AttachmentPanel : public QWidget { public: - AttachmentPanel(const AttachmentData& data = AttachmentData()); + AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); + + AttachmentData getAttachmentData() const; private slots: diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 485b5517f0..bbfff8f025 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -662,8 +662,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { void AvatarData::setAttachmentData(const QVector& attachmentData) { _attachmentData = attachmentData; - - qDebug() << "Changing attachment data for avatar."; } void AvatarData::setDisplayName(const QString& displayName) { @@ -775,6 +773,10 @@ void AvatarData::updateJointMappings() { } } +AttachmentData::AttachmentData() : + scale(1.0f) { +} + bool AttachmentData::operator==(const AttachmentData& other) const { return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && rotation == other.rotation && scale == other.scale; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a853706005..97fad639cd 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -324,6 +324,8 @@ public: glm::quat rotation; float scale; + AttachmentData(); + bool operator==(const AttachmentData& other) const; }; From f37460e39a67b4dd6d5ef9b9084fb57d837975b9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 5 May 2014 15:46:09 -0700 Subject: [PATCH 15/38] Basic attachment rendering. --- interface/src/avatar/Avatar.cpp | 47 +++++++++++++++++++++++++++++++ interface/src/avatar/Avatar.h | 5 ++++ interface/src/avatar/MyAvatar.cpp | 4 ++- interface/src/renderer/Model.cpp | 36 +++++++++++------------ interface/src/renderer/Model.h | 6 ++-- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e8ac93234c..3f670709ff 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -126,6 +126,7 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.simulate(deltaTime); } _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + simulateAttachments(deltaTime, _hasNewJointRotations); _hasNewJointRotations = false; glm::vec3 headPosition = _position; @@ -338,6 +339,7 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { return; } _skeletonModel.render(1.0f, modelRenderMode); + renderAttachments(modelRenderMode); getHand()->render(false); } getHead()->render(1.0f, modelRenderMode); @@ -347,6 +349,32 @@ bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode render return true; } +void Avatar::simulateAttachments(float deltaTime, bool fullUpdate) { + if (!fullUpdate) { + return; // only simulate if we have new data + } + for (int i = 0; i < _attachmentModels.size(); i++) { + const AttachmentData& attachment = _attachmentData.at(i); + Model* model = _attachmentModels.at(i); + int jointIndex = getJointIndex(attachment.jointName); + glm::vec3 jointPosition; + glm::quat jointRotation; + if (_skeletonModel.getJointPosition(jointIndex, jointPosition) && + _skeletonModel.getJointRotation(jointIndex, jointRotation)) { + model->setTranslation(jointPosition + jointRotation * attachment.translation * _skeletonModel.getScale()); + model->setRotation(jointRotation * attachment.rotation); + model->setScale(_skeletonModel.getScale() * attachment.scale); + model->simulate(deltaTime); + } + } +} + +void Avatar::renderAttachments(Model::RenderMode renderMode) { + foreach (Model* model, _attachmentModels) { + model->render(1.0f, renderMode); + } +} + void Avatar::updateJointMappings() { // no-op; joint mappings come from skeleton model } @@ -667,6 +695,25 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar()); } +void Avatar::setAttachmentData(const QVector& attachmentData) { + AvatarData::setAttachmentData(attachmentData); + + // make sure we have as many models as attachments + while (_attachmentModels.size() < attachmentData.size()) { + Model* model = new Model(this); + model->init(); + _attachmentModels.append(model); + } + while (_attachmentModels.size() > attachmentData.size()) { + delete _attachmentModels.takeLast(); + } + + // update the urls + for (int i = 0; i < attachmentData.size(); i++) { + _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); + } +} + void Avatar::setDisplayName(const QString& displayName) { AvatarData::setDisplayName(displayName); _displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4263e606a5..4b0bf85ea1 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -131,6 +131,7 @@ public: virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + virtual void setAttachmentData(const QVector& attachmentData); virtual void setDisplayName(const QString& displayName); virtual void setBillboard(const QByteArray& billboard); @@ -160,6 +161,7 @@ signals: protected: SkeletonModel _skeletonModel; + QVector _attachmentModels; float _bodyYawDelta; glm::vec3 _velocity; float _leanScale; @@ -188,6 +190,9 @@ protected: virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f); virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; + void simulateAttachments(float deltaTime, bool fullUpdate = true); + void renderAttachments(Model::RenderMode renderMode); + virtual void updateJointMappings(); private: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6af7235117..b52b77f691 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -244,6 +244,7 @@ void MyAvatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, true); _skeletonModel.simulate(deltaTime); + simulateAttachments(deltaTime); // copy out the skeleton joints from the model _jointData.resize(_skeletonModel.getJointStateCount()); @@ -657,7 +658,8 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; _skeletonModel.render(1.0f, modelRenderMode); - + renderAttachments(modelRenderMode); + // Render head so long as the camera isn't inside it if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { getHead()->render(1.0f, modelRenderMode); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index a177783955..db86b37d0d 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -509,6 +509,24 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } +bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { + if (jointIndex == -1 || _jointStates.isEmpty()) { + return false; + } + position = _translation + extractTranslation(_jointStates[jointIndex].transform); + return true; +} + +bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const { + if (jointIndex == -1 || _jointStates.isEmpty()) { + return false; + } + rotation = _jointStates[jointIndex].combinedRotation * + (fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation : + _geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation); + return true; +} + void Model::clearShapes() { for (int i = 0; i < _jointShapes.size(); ++i) { delete _jointShapes[i]; @@ -957,24 +975,6 @@ void Model::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint // nothing by default } -bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { - if (jointIndex == -1 || _jointStates.isEmpty()) { - return false; - } - position = _translation + extractTranslation(_jointStates[jointIndex].transform); - return true; -} - -bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const { - if (jointIndex == -1 || _jointStates.isEmpty()) { - return false; - } - rotation = _jointStates[jointIndex].combinedRotation * - (fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation : - _geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation); - return true; -} - bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation, bool useRotation, int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment) { if (jointIndex == -1 || _jointStates.isEmpty()) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index ae2bcd79b8..6a79772ca7 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -180,6 +180,9 @@ public: /// Returns the extended length from the right hand to its first free ancestor. float getRightArmLength() const; + bool getJointPosition(int jointIndex, glm::vec3& position) const; + bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; + void clearShapes(); void rebuildShapes(); void updateShapePositions(); @@ -269,9 +272,6 @@ protected: virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); - bool getJointPosition(int jointIndex, glm::vec3& position) const; - bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; - bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false, const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f)); From 519b36240c6b4f01a1bc00b03d4ed85e0fed6cbf Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Tue, 6 May 2014 01:05:04 +0200 Subject: [PATCH 16/38] build fix --- interface/src/ui/ChatWindow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index b2956598a9..1e0f533e9e 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include From 13922171ead96c8780f8493ffb801ce24c6fb5a4 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Tue, 6 May 2014 01:08:24 +0200 Subject: [PATCH 17/38] removed build warnings --- interface/ui/preferencesDialog.ui | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 278e3c5dab..8a84956269 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -198,9 +198,6 @@ color: #0e7077 25 - - - @@ -510,7 +507,7 @@ color: #0e7077 - + 0 @@ -547,7 +544,7 @@ color: #0e7077 - + @@ -564,7 +561,7 @@ color: #0e7077 - + Qt::Horizontal From 691b1d6069f2563df60e25407b6dab022a932a4f Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 5 May 2014 16:19:09 -0700 Subject: [PATCH 18/38] Added lookAt rendering bubble --- examples/placeModelsWithHands.js | 19 +++++++++ interface/src/Menu.cpp | 1 + interface/src/Menu.h | 4 ++ interface/src/avatar/Avatar.cpp | 10 +++++ interface/src/avatar/Avatar.h | 3 +- interface/src/avatar/MyAvatar.cpp | 53 +++++++++--------------- interface/src/avatar/MyAvatar.h | 2 - interface/src/renderer/GeometryCache.cpp | 1 + libraries/octree/src/ViewFrustum.h | 1 + 9 files changed, 58 insertions(+), 36 deletions(-) diff --git a/examples/placeModelsWithHands.js b/examples/placeModelsWithHands.js index cffc6bd3c9..49a96f04a4 100644 --- a/examples/placeModelsWithHands.js +++ b/examples/placeModelsWithHands.js @@ -37,6 +37,7 @@ var radiusMinimum = 0.05; var radiusMaximum = 0.5; var modelURLs = [ + "http://s3.amazonaws.com/converter.tipodean.com/hifi/gun/Raygun2.fbx", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", @@ -51,6 +52,12 @@ var currentModelURL = 1; var numModels = modelURLs.length; +function getNewVoxelPosition() { + var camera = Camera.getPosition(); + var forwardVector = Quat.getFront(MyAvatar.orientation); + var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, 2.0)); + return newPosition; +} function keyPressEvent(event) { //print("event.text=" + event.text); @@ -63,6 +70,18 @@ function keyPressEvent(event) { rightRecentlyDeleted = false; rightModelAlreadyInHand = false; } + } else if (event.text == "m") { + var URL = Window.prompt("Model URL", "Enter URL, e.g. http://foo.com/model.fbx"); + Window.alert("Your response was: " + prompt); + var modelPosition = getNewVoxelPosition(); + var properties = { position: { x: modelPosition.x, + y: modelPosition.y, + z: modelPosition.z }, + radius: modelRadius, + modelURL: URL + }; + newModel = Models.addModel(properties); + } else if (event.text == "DELETE") { if (leftModelAlreadyInHand) { print("want to delete leftHandModel=" + leftHandModel); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c492af3964..16b6b82443 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -479,6 +479,7 @@ void Menu::loadSettings(QSettings* settings) { _audioJitterBufferSamples = loadSetting(settings, "audioJitterBufferSamples", 0); _fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES); + _realWorldFieldOfView = loadSetting(settings, "realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES); _faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION); _maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM); _maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 9a7dda9111..1f94de43a5 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -84,6 +84,9 @@ public: void setAudioJitterBufferSamples(float audioJitterBufferSamples) { _audioJitterBufferSamples = audioJitterBufferSamples; } float getFieldOfView() const { return _fieldOfView; } void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; } + float getRealWorldFieldOfView() const { return _realWorldFieldOfView; } + void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; } + float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; } void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; } QString getSnapshotsLocation() const; @@ -228,6 +231,7 @@ private: int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback BandwidthDialog* _bandwidthDialog; float _fieldOfView; /// in Degrees, doesn't apply to HMD like Oculus + float _realWorldFieldOfView; // The actual FOV set by the user's monitor size and view distance float _faceshiftEyeDeflection; FrustumDrawMode _frustumDrawMode; ViewFrustumOffset _viewFrustumOffset; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e8ac93234c..b22f4c618f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -232,6 +232,16 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { _skeletonModel.renderBoundingCollisionShapes(0.7f); } } + // If this is the avatar being looked at, render a little ball above their head + if (_isLookAtTarget) { + const float LOOK_AT_INDICATOR_RADIUS = 0.25f; + const float LOOK_AT_INDICATOR_HEIGHT = 0.65f; + glPushMatrix(); + glColor4f(0.0f, 1.0f, 1.0f, 0.5f); + glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); + glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); + glPopMatrix(); + } // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4263e606a5..7b50cd6f63 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -79,7 +79,7 @@ public: //setters void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); } void setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction); - + void setIsLookAtTarget(const bool isLookAtTarget) { _isLookAtTarget = isLookAtTarget; } //getters bool isInitialized() const { return _initialized; } SkeletonModel& getSkeletonModel() { return _skeletonModel; } @@ -195,6 +195,7 @@ private: bool _initialized; QScopedPointer _billboardTexture; bool _shouldRenderBillboard; + bool _isLookAtTarget; void renderBillboard(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 652eb56258..1b504841eb 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -317,13 +317,16 @@ void MyAvatar::updateFromGyros(float deltaTime) { } // Set the rotation of the avatar's head (as seen by others, not affecting view frustum) - // to be scaled. Pitch is greater to emphasize nodding behavior / synchrony. - const float AVATAR_HEAD_PITCH_MAGNIFY = 1.0f; - const float AVATAR_HEAD_YAW_MAGNIFY = 1.0f; - const float AVATAR_HEAD_ROLL_MAGNIFY = 1.0f; + // to be scaled such that when the user's physical head is pointing at edge of screen, the + // avatar head is at the edge of the in-world view frustum. So while a real person may move + // their head only 30 degrees or so, this may correspond to a 90 degree field of view. + // Note that roll is magnified by a constant because it is not related to field of view. + const float AVATAR_HEAD_ROLL_MAGNIFY = 2.0f; + float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView(); + Head* head = getHead(); - head->setDeltaPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY); - head->setDeltaYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY); + head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); + head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); head->setDeltaRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); // Update torso lean distance based on accelerometer data @@ -407,18 +410,15 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const { Faceshift* faceshift = Application::getInstance()->getFaceshift(); + float pixelsPerDegree = screenHeight / Menu::getInstance()->getFieldOfView(); + // Display small target box at center or head mouse target that can also be used to measure LOD float headPitch = getHead()->getFinalPitch(); float headYaw = getHead()->getFinalYaw(); - // - // It should be noted that the following constant is a function - // how far the viewer's head is away from both the screen and the size of the screen, - // which are both things we cannot know without adding a calibration phase. - // - const float PIXELS_PER_VERTICAL_DEGREE = 20.0f; + float aspectRatio = (float) screenWidth / (float) screenHeight; - int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE; - int headMouseY = screenHeight / 2.f - headPitch * PIXELS_PER_VERTICAL_DEGREE; + int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * pixelsPerDegree; + int headMouseY = screenHeight / 2.f - headPitch * pixelsPerDegree; glColor3f(1.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); @@ -435,8 +435,8 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const { float avgEyePitch = faceshift->getEstimatedEyePitch(); float avgEyeYaw = faceshift->getEstimatedEyeYaw(); - int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE; - int eyeTargetY = (screenHeight / 2) - avgEyePitch * PIXELS_PER_VERTICAL_DEGREE; + int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * pixelsPerDegree; + int eyeTargetY = (screenHeight / 2) - avgEyePitch * pixelsPerDegree; glColor3f(0.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); @@ -537,23 +537,6 @@ void MyAvatar::sendKillAvatar() { NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer); } -void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) { - // first orbit horizontally - glm::quat orientation = getOrientation(); - const float ANGULAR_SCALE = 0.5f; - glm::quat rotation = glm::angleAxis(glm::radians(- deltaX * ANGULAR_SCALE), orientation * IDENTITY_UP); - setPosition(position + rotation * (getPosition() - position)); - orientation = rotation * orientation; - setOrientation(orientation); - - // then vertically - float oldPitch = getHead()->getBasePitch(); - getHead()->setBasePitch(oldPitch - deltaY * ANGULAR_SCALE); - rotation = glm::angleAxis(glm::radians((getHead()->getBasePitch() - oldPitch)), orientation * IDENTITY_RIGHT); - - setPosition(position + rotation * (getPosition() - position)); -} - void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head @@ -564,6 +547,7 @@ void MyAvatar::updateLookAtTargetAvatar() { float smallestAngleTo = MIN_LOOKAT_ANGLE; foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { Avatar* avatar = static_cast(avatarPointer.data()); + avatar->setIsLookAtTarget(false); if (!avatar->isMyAvatar()) { float angleTo = glm::angle(getHead()->getFinalOrientation() * glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition())); @@ -574,6 +558,9 @@ void MyAvatar::updateLookAtTargetAvatar() { } } } + if (_lookAtTargetAvatar) { + static_cast(_lookAtTargetAvatar.data())->setIsLookAtTarget(true); + } } void MyAvatar::clearLookAtTargetAvatar() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bbf3d05189..e722f69deb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -77,8 +77,6 @@ public: static void sendKillAvatar(); - void orbit(const glm::vec3& position, int deltaX, int deltaY); - Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); } void updateLookAtTargetAvatar(); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 6e93fc77af..12a34aec2a 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -486,6 +486,7 @@ void GeometryReader::run() { return; } try { + qDebug() << "Reading " << _url; QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, _url.path().toLower().endsWith(".svo") ? readSVO(_reply->readAll()) : readFBX(_reply->readAll(), _mapping))); diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index acd5c639f7..4fc3bc2d8b 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -24,6 +24,7 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f; +const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.f; const float DEFAULT_ASPECT_RATIO = 16.f/9.f; const float DEFAULT_NEAR_CLIP = 0.08f; const float DEFAULT_FAR_CLIP = 50.0f * TREE_SCALE; From fd0a39df12b4d00d09b163027aa1ac824ebd043b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 5 May 2014 16:19:33 -0700 Subject: [PATCH 19/38] Always update attachments. --- interface/src/avatar/Avatar.cpp | 7 ++----- interface/src/avatar/Avatar.h | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 3f670709ff..e3700b920b 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -126,7 +126,7 @@ void Avatar::simulate(float deltaTime) { _skeletonModel.simulate(deltaTime); } _skeletonModel.simulate(deltaTime, _hasNewJointRotations); - simulateAttachments(deltaTime, _hasNewJointRotations); + simulateAttachments(deltaTime); _hasNewJointRotations = false; glm::vec3 headPosition = _position; @@ -349,10 +349,7 @@ bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode render return true; } -void Avatar::simulateAttachments(float deltaTime, bool fullUpdate) { - if (!fullUpdate) { - return; // only simulate if we have new data - } +void Avatar::simulateAttachments(float deltaTime) { for (int i = 0; i < _attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); Model* model = _attachmentModels.at(i); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4b0bf85ea1..9828e120c1 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -190,7 +190,7 @@ protected: virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f); virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; - void simulateAttachments(float deltaTime, bool fullUpdate = true); + void simulateAttachments(float deltaTime); void renderAttachments(Model::RenderMode renderMode); virtual void updateJointMappings(); From 0021c77789480a862e7ba13136f62d93005cf744 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 5 May 2014 16:29:11 -0700 Subject: [PATCH 20/38] Missed a spot for decoding avatar identity data. --- libraries/avatars/src/AvatarHashMap.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 6b17a3fab8..8e3797cbc0 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -127,8 +127,9 @@ void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const while (!identityStream.atEnd()) { QUrl faceMeshURL, skeletonURL; + QVector attachmentData; QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName; + identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; // mesh URL for a UUID, find avatar in our list AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); @@ -142,6 +143,10 @@ void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const matchingAvatar->setSkeletonModelURL(skeletonURL); } + if (matchingAvatar->getAttachmentData() != attachmentData) { + matchingAvatar->setAttachmentData(attachmentData); + } + if (matchingAvatar->getDisplayName() != displayName) { matchingAvatar->setDisplayName(displayName); } @@ -171,4 +176,4 @@ void AvatarHashMap::processKillAvatar(const QByteArray& datagram) { if (matchedAvatar != _avatarHash.end()) { erase(matchedAvatar); } -} \ No newline at end of file +} From 710cc3ec2a25dac7fd57306c469399ee6dfc52fd Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 5 May 2014 17:08:10 -0700 Subject: [PATCH 21/38] Comment fix. --- interface/src/ui/AttachmentsDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 38ef10625f..edd28d461c 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -1,8 +1,8 @@ // -// MetavoxelEditor.cpp +// AttachmentsDialog.cpp // interface/src/ui // -// Created by Andrzej Kapolka on 1/21/14. +// Created by Andrzej Kapolka on 5/4/14. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. From e33ef1a5712953cfc131f2d7e47d9d969faf0731 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 5 May 2014 17:35:23 -0700 Subject: [PATCH 22/38] fix avatar motor to point in direction of camera --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cd799b7d2e..3fac129bec 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -732,7 +732,7 @@ void MyAvatar::applyMotor(float deltaTime) { glm::vec3 targetVelocity = _motorVelocity; if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { // rotate _motorVelocity into world frame - glm::quat rotation = getOrientation(); + glm::quat rotation = getHead()->getCameraOrientation(); targetVelocity = rotation * _motorVelocity; } From 53baf5652e732884367bffde0ad771e512ee4e1d Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 5 May 2014 18:03:25 -0700 Subject: [PATCH 23/38] avatar look at indicator smaller grey ball --- examples/placeModelsWithHands.js | 2 +- interface/src/avatar/Avatar.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/placeModelsWithHands.js b/examples/placeModelsWithHands.js index 97cbb41a84..41d9d5dc86 100644 --- a/examples/placeModelsWithHands.js +++ b/examples/placeModelsWithHands.js @@ -37,7 +37,7 @@ var radiusMinimum = 0.05; var radiusMaximum = 0.5; var modelURLs = [ - "http://s3.amazonaws.com/converter.tipodean.com/hifi/gun/Raygun2.fbx", + "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/music/EVHFrankenstein.fbx", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b22f4c618f..ba783aafb9 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -234,10 +234,11 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } // If this is the avatar being looked at, render a little ball above their head if (_isLookAtTarget) { - const float LOOK_AT_INDICATOR_RADIUS = 0.25f; + const float LOOK_AT_INDICATOR_RADIUS = 0.05f; const float LOOK_AT_INDICATOR_HEIGHT = 0.65f; + const float LOOK_AT_INDICATOR_COLOR[] = { 0.2f, 0.2f, 0.2f, 0.3f }; glPushMatrix(); - glColor4f(0.0f, 1.0f, 1.0f, 0.5f); + glColor4fv(LOOK_AT_INDICATOR_COLOR); glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15); glPopMatrix(); From e1a6a824a38071f248e325141a4051a27257bf46 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 5 May 2014 20:28:12 -0700 Subject: [PATCH 24/38] remove model loader debug --- interface/src/renderer/GeometryCache.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 12a34aec2a..6e93fc77af 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -486,7 +486,6 @@ void GeometryReader::run() { return; } try { - qDebug() << "Reading " << _url; QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, _url.path().toLower().endsWith(".svo") ? readSVO(_reply->readAll()) : readFBX(_reply->readAll(), _mapping))); From e3ab16997ae7b268f844a18428da572b5dba1664 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 5 May 2014 22:06:28 -0700 Subject: [PATCH 25/38] tweaks to roll (back to no magnify) and lookAt indicator dot --- interface/src/avatar/Avatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f45c97f66e..bad4083fcf 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -235,9 +235,9 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } // If this is the avatar being looked at, render a little ball above their head if (_isLookAtTarget) { - const float LOOK_AT_INDICATOR_RADIUS = 0.05f; - const float LOOK_AT_INDICATOR_HEIGHT = 0.65f; - const float LOOK_AT_INDICATOR_COLOR[] = { 0.2f, 0.2f, 0.2f, 0.3f }; + const float LOOK_AT_INDICATOR_RADIUS = 0.03f; + const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; + const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; glPushMatrix(); glColor4fv(LOOK_AT_INDICATOR_COLOR); glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fb25da6973..a21cd3fc91 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -253,13 +253,13 @@ void MyAvatar::updateFromGyros(float deltaTime) { // avatar head is at the edge of the in-world view frustum. So while a real person may move // their head only 30 degrees or so, this may correspond to a 90 degree field of view. // Note that roll is magnified by a constant because it is not related to field of view. - const float AVATAR_HEAD_ROLL_MAGNIFY = 2.0f; + float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView(); Head* head = getHead(); head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); - head->setDeltaRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); + head->setDeltaRoll(estimatedRotation.z); // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = 0.5f; From 14fee3b7272a9ed64d755b98c6f95d82c3fa84d2 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Tue, 6 May 2014 08:27:46 +0200 Subject: [PATCH 26/38] trivial commit - trigger build --- interface/src/ui/ChatWindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 7a52ad08e7..782e45ed15 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,7 @@ #include "ChatWindow.h" -#include + const int NUM_MESSAGES_TO_TIME_STAMP = 20; From 0b03421046dc1fdece61fed7655cd7bb78284d53 Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Tue, 6 May 2014 08:37:49 +0200 Subject: [PATCH 27/38] trivial commit - trigger build --- interface/src/ui/ChatWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 782e45ed15..39db2b734e 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include From 7eef1b56a2d268d3c4322942b522714a77705eb9 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 10:19:29 -0700 Subject: [PATCH 28/38] Don't use the scale to determine the depth in Faceplus. Apparently it's not reliable. --- interface/src/devices/Faceplus.cpp | 7 +------ interface/src/devices/Faceplus.h | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp index f8eec434b7..00bc9d2676 100644 --- a/interface/src/devices/Faceplus.cpp +++ b/interface/src/devices/Faceplus.cpp @@ -220,15 +220,10 @@ void FaceplusReader::update() { if (!_referenceInitialized) { _referenceX = x; _referenceY = y; - _referenceScale = scale; _referenceInitialized = true; } const float TRANSLATION_SCALE = 10.0f; - const float REFERENCE_DISTANCE = 10.0f; - float depthScale = _referenceScale / scale; - float z = REFERENCE_DISTANCE * (depthScale - 1.0f); - glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, - (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); + glm::vec3 headTranslation((x - _referenceX) * TRANSLATION_SCALE, (y - _referenceY) * TRANSLATION_SCALE, 0.0f); glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]), _outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2])))); float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) + diff --git a/interface/src/devices/Faceplus.h b/interface/src/devices/Faceplus.h index f3c680c2d6..d52740ca5f 100644 --- a/interface/src/devices/Faceplus.h +++ b/interface/src/devices/Faceplus.h @@ -76,7 +76,6 @@ private: int _rightEyeRotationIndices[2]; float _referenceX; float _referenceY; - float _referenceScale; bool _referenceInitialized; QVector _blendshapeCoefficients; #endif From a06697f2a1a40eed07f0ace4fb99b27e09c7822f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 10:37:27 -0700 Subject: [PATCH 29/38] Update attachment data on attachment delete. --- interface/src/ui/AttachmentsDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index edd28d461c..2d28c51d7c 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -130,6 +130,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); + dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAttachmentData()), Qt::QueuedConnection); } AttachmentData AttachmentPanel::getAttachmentData() const { From e0ecd611c99cf228d766eb9204af200fd14c34fe Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 10:53:54 -0700 Subject: [PATCH 30/38] Better attachment styling. --- interface/src/ui/AttachmentsDialog.cpp | 8 ++++++-- interface/src/ui/AttachmentsDialog.h | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 2d28c51d7c..79b8d42f41 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -37,6 +37,7 @@ AttachmentsDialog::AttachmentsDialog() : container->setLayout(_attachments = new QVBoxLayout()); container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); area->setWidget(container); + _attachments->addStretch(1); foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) { addAttachment(data); @@ -55,14 +56,14 @@ AttachmentsDialog::AttachmentsDialog() : void AttachmentsDialog::updateAttachmentData() { QVector data; - for (int i = 0; i < _attachments->count(); i++) { + for (int i = 0; i < _attachments->count() - 1; i++) { data.append(static_cast(_attachments->itemAt(i)->widget())->getAttachmentData()); } Application::getInstance()->getAvatar()->setAttachmentData(data); } void AttachmentsDialog::addAttachment(const AttachmentData& data) { - _attachments->addWidget(new AttachmentPanel(this, data)); + _attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data)); } static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { @@ -86,7 +87,10 @@ static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) } AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) { + setFrameStyle(QFrame::StyledPanel); + QFormLayout* layout = new QFormLayout(); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); QHBoxLayout* urlBox = new QHBoxLayout(); diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index c23bd2efb8..663a831462 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -13,6 +13,7 @@ #define hifi_AttachmentsDialog_h #include +#include #include @@ -43,7 +44,7 @@ private: }; /// A panel controlling a single attachment. -class AttachmentPanel : public QWidget { +class AttachmentPanel : public QFrame { Q_OBJECT public: From c5fbf1b55f3f43ba5fec48dda3db602cce910d0d Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 10:57:11 -0700 Subject: [PATCH 31/38] Force the scroll bar. --- interface/src/ui/AttachmentsDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 79b8d42f41..9e244fda87 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -33,6 +33,7 @@ AttachmentsDialog::AttachmentsDialog() : QScrollArea* area = new QScrollArea(); layout->addWidget(area); area->setWidgetResizable(true); + area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); QWidget* container = new QWidget(); container->setLayout(_attachments = new QVBoxLayout()); container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); From 72ebeffa5c471e595072a8cb055a7258102e83af Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 10:59:28 -0700 Subject: [PATCH 32/38] Scratch that; I just didn't understand how magic invisible disappearing scroll bars worked on OS X. --- interface/src/ui/AttachmentsDialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 9e244fda87..79b8d42f41 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -33,7 +33,6 @@ AttachmentsDialog::AttachmentsDialog() : QScrollArea* area = new QScrollArea(); layout->addWidget(area); area->setWidgetResizable(true); - area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); QWidget* container = new QWidget(); container->setLayout(_attachments = new QVBoxLayout()); container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); From 9421af9f108bc4323181da90e9260332ca32f238 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 11:14:48 -0700 Subject: [PATCH 33/38] Fix for attachments' not appearing in rear view mirror head mode. --- interface/src/Application.cpp | 13 ++++++++++++- interface/src/avatar/Avatar.h | 1 + interface/src/ui/AttachmentsDialog.cpp | 1 - 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d9cc78304f..b50e9ef1dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2843,7 +2843,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // save absolute translations glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - + // get the eye positions relative to the neck and use them to set the face translation glm::vec3 leftEyePosition, rightEyePosition; _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); @@ -2857,11 +2857,22 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - neckPosition); + // update the attachments to match + QVector absoluteAttachmentTranslations; + glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation; + foreach (Model* attachment, _myAvatar->getAttachmentModels()) { + absoluteAttachmentTranslations.append(attachment->getTranslation()); + attachment->setTranslation(attachment->getTranslation() + delta); + } + displaySide(_mirrorCamera, true); // restore absolute translations _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation); _myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation); + for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) { + _myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i)); + } } else { displaySide(_mirrorCamera, true); } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index b96145b213..edd53e4b8f 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -83,6 +83,7 @@ public: //getters bool isInitialized() const { return _initialized; } SkeletonModel& getSkeletonModel() { return _skeletonModel; } + const QVector& getAttachmentModels() const { return _attachmentModels; } glm::vec3 getChestPosition() const; float getScale() const { return _scale; } const glm::vec3& getVelocity() const { return _velocity; } diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 79b8d42f41..9fa26a2f00 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -68,7 +68,6 @@ void AttachmentsDialog::addAttachment(const AttachmentData& data) { static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { QDoubleSpinBox* box = new QDoubleSpinBox(); - box->setSingleStep(0.01); box->setMinimum(-FLT_MAX); box->setMaximum(FLT_MAX); box->setValue(value); From 082ff760d6c317582453c09041621c4e86232af1 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 11:47:35 -0700 Subject: [PATCH 34/38] Un-default the "OK" button so that we can press enter on the URL field. --- interface/src/ui/AttachmentsDialog.cpp | 10 ++++++++++ interface/src/ui/AttachmentsDialog.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 9fa26a2f00..bd531b1b46 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -50,10 +50,20 @@ AttachmentsDialog::AttachmentsDialog() : QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); + _ok = buttons->button(QDialogButtonBox::Ok); setMinimumSize(600, 600); } +void AttachmentsDialog::setVisible(bool visible) { + QDialog::setVisible(visible); + + // un-default the OK button + if (visible) { + _ok->setDefault(false); + } +} + void AttachmentsDialog::updateAttachmentData() { QVector data; for (int i = 0; i < _attachments->count() - 1; i++) { diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index 663a831462..4e67ae8882 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -30,6 +30,8 @@ public: AttachmentsDialog(); + virtual void setVisible(bool visible); + public slots: void updateAttachmentData(); @@ -41,6 +43,7 @@ private slots: private: QVBoxLayout* _attachments; + QPushButton* _ok; }; /// A panel controlling a single attachment. From 983a3af4b9004dfdf5cfe6333f1b272d8343b566 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 11:50:37 -0700 Subject: [PATCH 35/38] Attachment translations in world space. --- interface/src/avatar/Avatar.cpp | 2 +- interface/src/ui/AttachmentsDialog.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index bad4083fcf..f7bf4595d6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -369,7 +369,7 @@ void Avatar::simulateAttachments(float deltaTime) { glm::quat jointRotation; if (_skeletonModel.getJointPosition(jointIndex, jointPosition) && _skeletonModel.getJointRotation(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * _skeletonModel.getScale()); + model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); model->setScale(_skeletonModel.getScale() * attachment.scale); model->simulate(deltaTime); diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index bd531b1b46..016098699b 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -78,6 +78,7 @@ void AttachmentsDialog::addAttachment(const AttachmentData& data) { static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { QDoubleSpinBox* box = new QDoubleSpinBox(); + box->setSingleStep(0.01); box->setMinimum(-FLT_MAX); box->setMaximum(FLT_MAX); box->setValue(value); From 432df1b65f7ecc80d3a0464aaedffdc44e43dcaa Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 12:41:31 -0700 Subject: [PATCH 36/38] Only add each texture once. --- interface/src/ModelUploader.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 2b86e04829..bf6a868368 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -424,19 +424,24 @@ void ModelUploader::processCheck() { } bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) { + QSet added; foreach (FBXMesh mesh, geometry.meshes) { foreach (FBXMeshPart part, mesh.parts) { - if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) { + if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && + !added.contains(part.diffuseTexture.filename)) { if (!addPart(texdir + "/" + part.diffuseTexture.filename, QString("texture%1").arg(++_texturesCount), true)) { return false; } + added.insert(part.diffuseTexture.filename); } - if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) { + if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() && + !added.contains(part.normalTexture.filename)) { if (!addPart(texdir + "/" + part.normalTexture.filename, QString("texture%1").arg(++_texturesCount), true)) { return false; } + added.insert(part.normalTexture.filename); } } } From 61c2dd04688310ae16dfa8fe6276fbc1ecf923c2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 6 May 2014 13:07:31 -0700 Subject: [PATCH 37/38] Fixed orientation and offset issues --- examples/editModels.js | 327 +++++++++++++++++++------------ libraries/models/src/ModelItem.h | 2 +- 2 files changed, 199 insertions(+), 130 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 50b0137c4f..ecf398edfa 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var windowDimensions = Controller.getViewportDimensions(); + var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; var LASER_LENGTH_FACTOR = 1.5; @@ -16,6 +18,40 @@ var LASER_LENGTH_FACTOR = 1.5; var LEFT = 0; var RIGHT = 1; + +var SPAWN_DISTANCE = 1; +var radiusDefault = 0.10; + +var modelURLs = [ + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", + ]; + +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var numberOfTools = 1; +var toolHeight = 50; +var toolWidth = 50; +var toolVerticalSpacing = 4; +var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1); +var toolsX = windowDimensions.x - 8 - toolWidth; +var toolsY = (windowDimensions.y - toolsHeight) / 2; + + +var firstModel = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-tool.svg", + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }); + function controller(wichSide) { this.side = wichSide; this.palm = 2 * wichSide; @@ -46,7 +82,10 @@ function controller(wichSide) { this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) this.grabbing = false; - this.modelID; + this.modelID = { isKnownID: false }; + this.oldModelRotation; + this.oldModelPosition; + this.oldModelRadius; this.laser = Overlays.addOverlay("line3d", { position: this.palmPosition, @@ -85,23 +124,19 @@ function controller(wichSide) { - this.grab = function (modelID) { - if (!modelID.isKnownID) { - var identify = Models.identifyModel(modelID); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(grab)"); - return; - } - modelID = identify; - } + this.grab = function (modelID, properties) { print("Grabbing " + modelID.id); this.grabbing = true; this.modelID = modelID; + + this.oldModelPosition = properties.position; + this.oldModelRotation = properties.modelRotation; + this.oldModelRadius = properties.radius; } this.release = function () { this.grabbing = false; - this.modelID = 0; + this.modelID.isKnownID = false; } this.checkTrigger = function () { @@ -118,6 +153,34 @@ function controller(wichSide) { } } + this.checkModel = function (properties) { + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = this.palmPosition; + var B = this.front; + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var y = Vec3.dot(Vec3.subtract(P, A), this.up); + var z = Vec3.dot(Vec3.subtract(P, A), this.right); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { + return { valid: true, x: x, y: y, z: z }; + } + return { valid: false }; + } + this.moveLaser = function () { var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR)); @@ -143,44 +206,33 @@ function controller(wichSide) { }); } - this.checkModel = function (modelID) { - if (!modelID.isKnownID) { - var identify = Models.identifyModel(modelID); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(checkModel)"); - return; - } - modelID = identify; + this.moveModel = function () { + if (this.grabbing) { + var newPosition = Vec3.sum(this.palmPosition, + Vec3.multiply(this.front, this.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.up, this.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.right, this.z)); + + var newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.oldRotation)); + newRotation = Quat.multiply(newRotation, + this.oldModelRotation); + + Models.editModel(this.modelID, { + position: newPosition, + modelRotation: newRotation + }); + print("Moving " + this.modelID.id); +// Vec3.print("Old Position: ", this.oldModelPosition); +// Vec3.print("Sav Position: ", newPosition); + Quat.print("Old Rotation: ", this.oldModelRotation); + Quat.print("New Rotation: ", newRotation); + + this.oldModelRotation = newRotation; + this.oldModelPosition = newPosition; } - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = this.palmPosition; - var B = this.front; - var P = Models.getModelProperties(modelID).position; - - this.x = Vec3.dot(Vec3.subtract(P, A), B); - this.y = Vec3.dot(Vec3.subtract(P, A), this.up); - this.z = Vec3.dot(Vec3.subtract(P, A), this.right); - var X = Vec3.sum(A, Vec3.multiply(B, this.x)); - var d = Vec3.length(Vec3.subtract(P, X)); - -// Vec3.print("A: ", A); -// Vec3.print("B: ", B); -// Vec3.print("Particle pos: ", P); -// print("d: " + d + ", x: " + this.x); - if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) { - return true; - } - return false; } this.update = function () { @@ -205,25 +257,40 @@ function controller(wichSide) { this.checkTrigger(); - if (this.pressing) { - Vec3.print("Looking at: ", this.palmPosition); - var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); - for (var i = 0; i < foundModels.length; i++) { - print("Model found ID (" + foundModels[i].id + ")"); - if (this.checkModel(foundModels[i])) { - if (this.grab(foundModels[i])) { - return; - } - } - } - } + this.moveLaser(); if (!this.pressed && this.grabbing) { // release if trigger not pressed anymore. this.release(); } - - this.moveLaser(); + + if (this.pressing) { + Vec3.print("Looking at: ", this.palmPosition); + var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); + for (var i = 0; i < foundModels.length; i++) { + + if (!foundModels[i].isKnownID) { + var identify = Models.identifyModel(foundModels[i]); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + "(update loop)"); + return; + } + foundModels[i] = identify; + } + + var properties = Models.getModelProperties(foundModels[i]); + print("Checking properties: " + properties.id + " " + properties.isKnownID); + + var check = this.checkModel(properties); + if (check.valid) { + this.grab(foundModels[i], properties); + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; + } + } + } } this.cleanup = function () { @@ -238,78 +305,44 @@ var leftController = new controller(LEFT); var rightController = new controller(RIGHT); function moveModels() { - if (leftController.grabbing) { - if (rightController.grabbing) { - var properties = Models.getModelProperties(leftController.modelID); - - var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); - var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - - var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); - var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); - - - var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); - var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); - - var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); - var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); - - var ratio = length / oldLength; - - var newPosition = Vec3.sum(middle, - Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio)); - Vec3.print("Ratio : " + ratio + " New position: ", newPosition); - var rotation = Quat.multiply(leftController.rotation, - Quat.inverse(leftController.oldRotation)); - rotation = Quat.multiply(rotation, properties.modelRotation); - - Models.editModel(leftController.modelID, { - position: newPosition, - //modelRotation: rotation, - radius: properties.radius * ratio - }); - - return; - } else { - var newPosition = Vec3.sum(leftController.palmPosition, - Vec3.multiply(leftController.front, leftController.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(leftController.up, leftController.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(leftController.right, leftController.z)); - - var rotation = Quat.multiply(leftController.rotation, - Quat.inverse(leftController.oldRotation)); - rotation = Quat.multiply(rotation, - Models.getModelProperties(leftController.modelID).modelRotation); - - Models.editModel(leftController.modelID, { - position: newPosition, - modelRotation: rotation - }); - } - } - - - if (rightController.grabbing) { - var newPosition = Vec3.sum(rightController.palmPosition, - Vec3.multiply(rightController.front, rightController.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(rightController.up, rightController.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(rightController.right, rightController.z)); + if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) { + print("Both controllers"); + var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); + var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - var rotation = Quat.multiply(rightController.rotation, - Quat.inverse(rightController.oldRotation)); - rotation = Quat.multiply(rotation, - Models.getModelProperties(rightController.modelID).modelRotation); + var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); + var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); - Models.editModel(rightController.modelID, { + + var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); + var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); + + var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); + var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); + + var ratio = length / oldLength; + + var newPosition = Vec3.sum(middle, + Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); + Vec3.print("Ratio : " + ratio + " New position: ", newPosition); + var rotation = Quat.multiply(leftController.rotation, + Quat.inverse(leftController.oldRotation)); + rotation = Quat.multiply(rotation, leftController.oldModelRotation); + + Models.editModel(leftController.modelID, { position: newPosition, - modelRotation: rotation + //modelRotation: rotation, + radius: leftController.oldModelRadius * ratio }); + + leftController.oldModelPosition = newPosition; + leftController.oldModelRotation = rotation; + leftController.oldModelRadius *= ratio; + return; } + + leftController.moveModel(); + rightController.moveModel(); } function checkController(deltaTime) { @@ -318,6 +351,8 @@ function checkController(deltaTime) { var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + moveOverlays(); + // this is expected for hydras if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) { //print("no hydra connected?"); @@ -329,14 +364,48 @@ function checkController(deltaTime) { moveModels(); } +function moveOverlays() { + windowDimensions = Controller.getViewportDimensions(); + + toolsX = windowDimensions.x - 8 - toolWidth; + toolsY = (windowDimensions.y - toolsHeight) / 2; + + Overlays.addOverlay(firstModel, { + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, + }); +} + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + var url; + + if (clickedOverlay == firstModel) { + url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url == null) { + return; } + } else { + print("Didn't click on anything"); + return; + } + + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); +} + function scriptEnding() { leftController.cleanup(); rightController.cleanup(); + + Overlays.deleteOverlay(firstModel); } Script.scriptEnding.connect(scriptEnding); // register the call back so it fires before each data send Script.update.connect(checkController); +Controller.mousePressEvent.connect(mousePressEvent); diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 317299be2d..76a78122ff 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -46,7 +46,7 @@ const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container const QString MODEL_DEFAULT_MODEL_URL(""); -const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0); +const glm::quat MODEL_DEFAULT_MODEL_ROTATION; /// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of From 353b9879b812141cc04a7900e060bee4a2536469 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 6 May 2014 15:15:42 -0700 Subject: [PATCH 38/38] Fix for exports from Sketchup. --- libraries/fbx/src/FBXReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index a21ed2627a..40a7d56c0d 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -817,7 +817,7 @@ ExtractedMesh extractMesh(const FBXNode& object) { while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0); QPair materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0, - (polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0); + (polygonIndex < textures.size()) ? textures.at(polygonIndex) : -1); int& partIndex = materialTextureParts[materialTexture]; if (partIndex == 0) { data.extracted.partMaterialTextures.append(materialTexture);