Merge branch 'master' of https://github.com/worklist/hifi into 19508

Conflicts:
	interface/interface_en.ts
This commit is contained in:
stojce 2014-03-14 21:05:30 +01:00
commit 6d15fb548c
27 changed files with 571 additions and 448 deletions

View file

@ -230,11 +230,7 @@ if (APPLE)
RUNTIME DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/install" COMPONENT Runtime
)
else (APPLE)
# remove and then copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
)
# copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources"

View file

@ -4,22 +4,22 @@
<context>
<name>Application</name>
<message>
<location filename="src/Application.cpp" line="1355"/>
<location filename="src/Application.cpp" line="1362"/>
<source>Export Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="1356"/>
<location filename="src/Application.cpp" line="1363"/>
<source>Sparse Voxel Octree Files (*.svo)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3559"/>
<location filename="src/Application.cpp" line="3573"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3560"/>
<location filename="src/Application.cpp" line="3574"/>
<source>JavaScript Files (*.js)</source>
<translation type="unfinished"></translation>
</message>
@ -27,25 +27,25 @@
<context>
<name>ChatWindow</name>
<message>
<location filename="ui/chatWindow.ui" line="14"/>
<location filename="../build/interface/ui_chatWindow.h" line="141"/>
<location filename="ui/chatWindow.ui" line="20"/>
<location filename="../build/interface/ui_chatWindow.h" line="143"/>
<source>Chat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/chatWindow.ui" line="41"/>
<location filename="../build/interface/ui_chatWindow.h" line="142"/>
<location filename="ui/chatWindow.ui" line="50"/>
<location filename="../build/interface/ui_chatWindow.h" line="144"/>
<source>Connecting to XMPP...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/chatWindow.ui" line="62"/>
<location filename="../build/interface/ui_chatWindow.h" line="143"/>
<location filename="ui/chatWindow.ui" line="71"/>
<location filename="../build/interface/ui_chatWindow.h" line="145"/>
<source> online now:</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="115"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>day</source>
<translation>
<numerusform>%n day</numerusform>
@ -53,7 +53,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="115"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>hour</source>
<translation>
<numerusform>%n hour</numerusform>
@ -61,7 +61,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="115"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>minute</source>
<translation>
<numerusform>%n minute</numerusform>
@ -76,7 +76,7 @@
</translation>
</message>
<message>
<location filename="src/ui/ChatWindow.cpp" line="170"/>
<location filename="src/ui/ChatWindow.cpp" line="179"/>
<source>%1 online now:</source>
<translation type="unfinished"></translation>
</message>
@ -113,139 +113,22 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="424"/>
<location filename="src/Menu.cpp" line="429"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="426"/>
<location filename="src/Menu.cpp" line="438"/>
<location filename="src/Menu.cpp" line="431"/>
<location filename="src/Menu.cpp" line="443"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="436"/>
<location filename="src/Menu.cpp" line="441"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PreferencesDialog</name>
<message>
<location filename="ui/preferencesDialog.ui" line="34"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="406"/>
<source>Avatar</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="53"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="407"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Avatar display name &lt;span style=&quot; color:#909090;&quot;&gt;(optional)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="81"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="408"/>
<source>Not showing a name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="91"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="409"/>
<source>Head</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="114"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="410"/>
<source>Body</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="135"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="411"/>
<source>Advanced Tuning</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="142"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="412"/>
<source>It&apos;s not recomended that you play with these settings unless you&apos;ve looked into exactly what they do.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="165"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="413"/>
<source>&lt;p&gt;Avatar&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="174"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="414"/>
<source>Lean scale (applies to Faceshift users)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="204"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="415"/>
<source>Vertical field of view</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="241"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="416"/>
<source>Avatar scale (default is 1.0)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="271"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="417"/>
<source>Pupil dillation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="308"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="418"/>
<source>Audio Jitter Buffer Samples (0 for automatic)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="345"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="419"/>
<source>Faceshift eye detection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="393"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="420"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Voxels&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="402"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="421"/>
<source>Maximum voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="439"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="422"/>
<source>Max voxels sent each second</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="514"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="423"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/preferencesDialog.ui" line="525"/>
<location filename="../build/interface/ui_preferencesDialog.h" line="424"/>
<source>Save all changes</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>

View file

@ -27,6 +27,7 @@
#include <QColorDialog>
#include <QDesktopWidget>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QImage>
#include <QKeyEvent>
#include <QMainWindow>
@ -62,6 +63,7 @@
#include <UUID.h>
#include <OctreeSceneStats.h>
#include <LocalVoxelsList.h>
#include <FstReader.h>
#include "Application.h"
#include "ClipboardScriptingInterface.h"
@ -121,8 +123,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
QString& Application::resourcesPath() {
#ifdef Q_OS_MAC
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#elif defined Q_OS_LINUX
static QString staticResourcePath = "resources/";
#else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif
@ -292,7 +292,14 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
ResourceCache::setNetworkAccessManager(_networkAccessManager);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
QWidget* centralWidget = new QWidget();
QHBoxLayout* mainLayout = new QHBoxLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
centralWidget->setLayout(mainLayout);
_glWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
centralWidget->layout()->addWidget(_glWidget);
_window->setCentralWidget(centralWidget);
restoreSizeAndPosition();
@ -1527,6 +1534,10 @@ void Application::init() {
}
qDebug("Loaded settings");
// initialize Visage and Faceshift after loading the menu settings
_faceshift.init();
_visage.init();
// fire off an immediate domain-server check in now that settings are loaded
NodeList::getInstance()->sendDomainServerCheckIn();
@ -3462,7 +3473,10 @@ void Application::reloadAllScripts() {
}
void Application::uploadFST() {
_fstReader.zip();
FstReader reader;
if (reader.zip()) {
reader.send();
}
}
void Application::removeScriptName(const QString& fileNameString) {

View file

@ -28,7 +28,6 @@
#include <ParticleEditPacketSender.h>
#include <ScriptEngine.h>
#include <OctreeQuery.h>
#include <FstReader.h>
#include "Audio.h"
#include "BandwidthMeter.h"
@ -479,8 +478,6 @@ private:
TouchEvent _lastTouchEvent;
Overlays _overlays;
FstReader _fstReader;
};
#endif /* defined(__interface__Application__) */

View file

@ -11,18 +11,26 @@
#include "GLCanvas.h"
#include <QMimeData>
#include <QUrl>
#include <QMainWindow>
GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuffer)) {
GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer, QGL::NoStencilBuffer)),
_throttleRendering(false),
_idleRenderInterval(100)
{
}
void GLCanvas::initializeGL() {
Application::getInstance()->initializeGL();
setAttribute(Qt::WA_AcceptTouchEvents);
setAcceptDrops(true);
connect(Application::getInstance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(activeChanged(Qt::ApplicationState)));
connect(&_frameTimer, SIGNAL(timeout()), this, SLOT(throttleRender()));
}
void GLCanvas::paintGL() {
Application::getInstance()->paintGL();
if (!_throttleRendering && !Application::getInstance()->getWindow()->isMinimized()) {
Application::getInstance()->paintGL();
}
}
void GLCanvas::resizeGL(int width, int height) {
@ -49,6 +57,38 @@ void GLCanvas::mouseReleaseEvent(QMouseEvent* event) {
Application::getInstance()->mouseReleaseEvent(event);
}
void GLCanvas::activeChanged(Qt::ApplicationState state) {
switch (state) {
case Qt::ApplicationActive:
// If we're active, stop the frame timer and the throttle.
_frameTimer.stop();
_throttleRendering = false;
break;
case Qt::ApplicationSuspended:
case Qt::ApplicationHidden:
// If we're hidden or are about to suspend, don't render anything.
_throttleRendering = false;
_frameTimer.stop();
break;
default:
// Otherwise, throttle.
if (!_throttleRendering) {
_frameTimer.start(_idleRenderInterval);
_throttleRendering = true;
}
break;
}
}
void GLCanvas::throttleRender() {
_frameTimer.start(_idleRenderInterval);
if (!Application::getInstance()->getWindow()->isMinimized()) {
Application::getInstance()->paintGL();
}
}
int updateTime = 0;
bool GLCanvas::event(QEvent* event) {
switch (event->type()) {

View file

@ -10,13 +10,19 @@
#define __hifi__GLCanvas__
#include <QGLWidget>
#include <QTimer>
/// customized canvas that simply forwards requests/events to the singleton application
class GLCanvas : public QGLWidget {
Q_OBJECT
public:
GLCanvas();
protected:
QTimer _frameTimer;
bool _throttleRendering;
int _idleRenderInterval;
virtual void initializeGL();
virtual void paintGL();
virtual void resizeGL(int width, int height);
@ -34,6 +40,10 @@ protected:
virtual void dragEnterEvent(QDragEnterEvent *event);
virtual void dropEvent(QDropEvent* event);
private slots:
void activeChanged(Qt::ApplicationState state);
void throttleRender();
};
#endif /* defined(__hifi__GLCanvas__) */

View file

@ -131,7 +131,10 @@ Menu::Menu() :
this,
SLOT(goTo()));
addDisabledActionAndSeparator(fileMenu, "Upload/Browse");
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarHead, 0, Application::getInstance(), SLOT(uploadFST()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarSkeleton, 0, Application::getInstance(), SLOT(uploadFST()));
addDisabledActionAndSeparator(fileMenu, "Settings");
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
@ -163,7 +166,6 @@ Menu::Menu() :
QMenu* toolsMenu = addMenu("Tools");
addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor()));
addActionToQMenuAndActionHash(toolsMenu, MenuOption::FstUploader, 0, Application::getInstance(), SLOT(uploadFST()));
_chatAction = addActionToQMenuAndActionHash(toolsMenu,
MenuOption::Chat,
@ -272,11 +274,16 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
MenuOption::FaceshiftTCP,
MenuOption::Faceshift,
0,
false,
true,
appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool)));
#ifdef HAVE_VISAGE
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true,
appInstance->getVisage(), SLOT(updateEnabled()));
#endif
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
@ -1049,22 +1056,20 @@ void Menu::showChat() {
if (!_chatWindow) {
_chatWindow = new ChatWindow();
QMainWindow* mainWindow = Application::getInstance()->getWindow();
_chatWindow->setGeometry(mainWindow->width() - _chatWindow->width(),
mainWindow->geometry().y(),
_chatWindow->width(),
mainWindow->height());
QBoxLayout* boxLayout = static_cast<QBoxLayout*>(mainWindow->centralWidget()->layout());
boxLayout->addWidget(_chatWindow, 0, Qt::AlignRight);
} else {
if (!_chatWindow->isVisible()) {
_chatWindow->show();
}
}
if (!_chatWindow->isVisible()) {
_chatWindow->show();
}
_chatWindow->raise();
}
void Menu::toggleChat() {
#ifdef HAVE_QXMPP
_chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected());
if (!_chatAction->isEnabled() && _chatWindow) {
_chatWindow->close();
_chatWindow->hide();
}
#endif
}

View file

@ -244,11 +244,10 @@ namespace MenuOption {
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
const QString HeadMouse = "Head Mouse";
const QString HandsCollideWithSelf = "Collide With Self";
const QString FaceshiftTCP = "Faceshift (TCP)";
const QString Faceshift = "Faceshift";
const QString FirstPerson = "First Person";
const QString FrameTimer = "Show Timer";
const QString FrustumRenderMode = "Render Mode";
const QString FstUploader = "Upload .fst file";
const QString Fullscreen = "Fullscreen";
const QString FullscreenMirror = "Fullscreen Mirror";
const QString GlowMode = "Cycle Glow Mode";
@ -297,6 +296,9 @@ namespace MenuOption {
const QString StopAllScripts = "Stop All Scripts";
const QString TestPing = "Test Ping";
const QString TransmitterDrive = "Transmitter Drive";
const QString UploaderAvatarHead = "Upload Avatar Head";
const QString UploaderAvatarSkeleton = "Upload Avatar Skeleton";
const QString Visage = "Visage";
const QString Quit = "Quit";
const QString Voxels = "Voxels";
const QString VoxelMode = "Cycle Voxel Mode";

View file

@ -56,7 +56,8 @@ Avatar::Avatar() :
_owningAvatarMixer(),
_collisionFlags(0),
_initialized(false),
_shouldRenderBillboard(true)
_shouldRenderBillboard(true),
_modelsDirty(true)
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
@ -109,6 +110,11 @@ void Avatar::simulate(float deltaTime) {
_shouldRenderBillboard = true;
}
// simple frustum check
float boundingRadius = getBillboardSize();
bool inViewFrustum = Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) !=
ViewFrustum::OUTSIDE;
getHand()->simulate(deltaTime, false);
_skeletonModel.setLODDistance(getLODDistance());
@ -118,8 +124,9 @@ void Avatar::simulate(float deltaTime) {
_skeletonModel.setJointState(i, data.valid, data.rotation);
}
glm::vec3 headPosition = _position;
if (!_shouldRenderBillboard) {
_skeletonModel.simulate(deltaTime);
if (!_shouldRenderBillboard && inViewFrustum) {
_skeletonModel.simulate(deltaTime, _modelsDirty);
_modelsDirty = false;
_skeletonModel.getHeadPosition(headPosition);
}
Head* head = getHead();
@ -183,6 +190,12 @@ static TextRenderer* textRenderer(TextRendererType type) {
}
void Avatar::render(bool forShadowMap) {
// simple frustum check
float boundingRadius = getBillboardSize();
if (Application::getInstance()->getViewFrustum()->sphereInFrustum(_position, boundingRadius) == ViewFrustum::OUTSIDE) {
return;
}
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
float lengthToTarget = glm::length(toTarget);
@ -336,7 +349,7 @@ void Avatar::renderBillboard() {
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
// compute the size from the billboard camera parameters and scale
float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
float size = getBillboardSize();
glScalef(size, size, size);
glColor3f(1.0f, 1.0f, 1.0f);
@ -361,6 +374,10 @@ void Avatar::renderBillboard() {
glBindTexture(GL_TEXTURE_2D, 0);
}
float Avatar::getBillboardSize() const {
return _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
}
void Avatar::renderDisplayName() {
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
@ -618,6 +635,9 @@ int Avatar::parseData(const QByteArray& packet) {
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
// note that we need to update our models
_modelsDirty = true;
return bytesRead;
}

View file

@ -187,9 +187,12 @@ private:
bool _initialized;
QScopedPointer<Texture> _billboardTexture;
bool _shouldRenderBillboard;
bool _modelsDirty;
void renderBody();
void renderBillboard();
float getBillboardSize() const;
};
#endif

View file

@ -18,9 +18,9 @@ FaceModel::FaceModel(Head* owningHead) :
{
}
void FaceModel::simulate(float deltaTime, bool delayLoad) {
void FaceModel::simulate(float deltaTime) {
QVector<JointState> newJointStates = updateGeometry();
if (!isActive()) {
Model::simulate(deltaTime, delayLoad);
return;
}
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
@ -36,12 +36,13 @@ void FaceModel::simulate(float deltaTime, bool delayLoad) {
setRotation(neckRotation);
const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);
setOffset(-_geometry->getFBXGeometry().neckPivot);
setPupilDilation(_owningHead->getPupilDilation());
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
Model::simulate(deltaTime, delayLoad);
Model::simulate(deltaTime, true, newJointStates);
}
bool FaceModel::render(float alpha) {

View file

@ -21,7 +21,7 @@ public:
FaceModel(Head* owningHead);
void simulate(float deltaTime, bool delayLoad = false);
void simulate(float deltaTime);
bool render(float alpha);
protected:

View file

@ -18,13 +18,13 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
_owningAvatar(owningAvatar) {
}
void SkeletonModel::simulate(float deltaTime, bool delayLoad) {
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setTranslation(_owningAvatar->getPosition());
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)));
const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE);
Model::simulate(deltaTime, delayLoad);
Model::simulate(deltaTime, fullUpdate);
if (!(isActive() && _owningAvatar->isMyAvatar())) {
return; // only simulate for own avatar

View file

@ -22,7 +22,7 @@ public:
SkeletonModel(Avatar* owningAvatar);
void simulate(float deltaTime, bool delayLoad = false);
void simulate(float deltaTime, bool fullUpdate = true);
bool render(float alpha);
/// \param jointIndex index of hand joint

View file

@ -21,7 +21,7 @@ using namespace std;
const quint16 FACESHIFT_PORT = 33433;
Faceshift::Faceshift() :
_tcpEnabled(false),
_tcpEnabled(true),
_tcpRetryCount(0),
_lastTrackingStateReceived(0),
_eyeGazeLeftPitch(0.0f),
@ -49,12 +49,22 @@ Faceshift::Faceshift() :
connect(&_tcpSocket, SIGNAL(connected()), SLOT(noteConnected()));
connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError)));
connect(&_tcpSocket, SIGNAL(readyRead()), SLOT(readFromSocket()));
connect(&_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(connectionStateChanged()));
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
_udpSocket.bind(FACESHIFT_PORT);
}
void Faceshift::init() {
setTCPEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift));
}
bool Faceshift::isConnectedOrConnecting() const {
return _tcpSocket.state() == QAbstractSocket::ConnectedState ||
(_tcpRetryCount == 0 && _tcpSocket.state() != QAbstractSocket::UnconnectedState);
}
bool Faceshift::isActive() const {
const quint64 ACTIVE_TIMEOUT_USECS = 1000000;
return (usecTimestampNow() - _lastTrackingStateReceived) < ACTIVE_TIMEOUT_USECS;

View file

@ -27,6 +27,10 @@ public:
Faceshift();
void init();
bool isConnectedOrConnecting() const;
bool isActive() const;
const glm::quat& getHeadRotation() const { return _headRotation; }
@ -66,6 +70,10 @@ public:
void updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
float jawOpen, std::vector<float>& coefficients) const;
signals:
void connectionStateChanged();
public slots:
void setTCPEnabled(bool enabled);

View file

@ -32,6 +32,7 @@ using namespace VisageSDK;
const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.7f);
Visage::Visage() :
_enabled(false),
_active(false),
_headOrigin(DEFAULT_HEAD_ORIGIN),
_estimatedEyePitch(0.0f),
@ -41,23 +42,15 @@ Visage::Visage() :
QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc";
initializeLicenseManager(licensePath.data());
_tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg");
if (_tracker->trackFromCam()) {
_data = new FaceData();
} else {
delete _tracker;
_tracker = NULL;
}
_data = new FaceData();
#endif
}
Visage::~Visage() {
#ifdef HAVE_VISAGE
if (_tracker) {
_tracker->stop();
delete _tracker;
delete _data;
}
_tracker->stop();
delete _tracker;
delete _data;
#endif
}
@ -117,9 +110,14 @@ static const QMultiHash<QByteArray, QPair<int, float> >& getActionUnitNameMap()
const float TRANSLATION_SCALE = 20.0f;
void Visage::init() {
connect(Application::getInstance()->getFaceshift(), SIGNAL(connectionStateChanged()), SLOT(updateEnabled()));
updateEnabled();
}
void Visage::update() {
#ifdef HAVE_VISAGE
_active = (_tracker && _tracker->getTrackingData(_data) == TRACK_STAT_OK);
_active = (_tracker->getTrackingData(_data) == TRACK_STAT_OK);
if (!_active) {
return;
}
@ -160,3 +158,22 @@ void Visage::update() {
void Visage::reset() {
_headOrigin += _headTranslation / TRANSLATION_SCALE;
}
void Visage::updateEnabled() {
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) &&
Application::getInstance()->getFaceshift()->isConnectedOrConnecting()));
}
void Visage::setEnabled(bool enabled) {
#ifdef HAVE_VISAGE
if (_enabled == enabled) {
return;
}
if ((_enabled = enabled)) {
_tracker->trackFromCam();
} else {
_tracker->stop();
}
#endif
}

View file

@ -24,11 +24,15 @@ namespace VisageSDK {
}
/// Handles input from the Visage webcam feature tracking software.
class Visage {
class Visage : public QObject {
Q_OBJECT
public:
Visage();
~Visage();
virtual ~Visage();
void init();
bool isActive() const { return _active; }
@ -42,6 +46,10 @@ public:
void update();
void reset();
public slots:
void updateEnabled();
private:
@ -51,6 +59,9 @@ private:
QMultiHash<int, QPair<int, float> > _actionUnitIndexMap;
#endif
void setEnabled(bool enabled);
bool _enabled;
bool _active;
glm::quat _headRotation;
glm::vec3 _headTranslation;

View file

@ -156,142 +156,9 @@ void Model::updateShapePositions() {
}
}
void Model::simulate(float deltaTime, bool delayLoad) {
// update our LOD
QVector<JointState> newJointStates = updateGeometry(delayLoad);
if (!isActive()) {
return;
}
// set up world vertices on first simulate after load
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (_jointStates.isEmpty()) {
_jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates;
foreach (const FBXMesh& mesh, geometry.meshes) {
MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
if (mesh.springiness > 0.0f) {
state.worldSpaceVertices.resize(mesh.vertices.size());
state.vertexVelocities.resize(mesh.vertices.size());
state.worldSpaceNormals.resize(mesh.vertices.size());
}
_meshStates.append(state);
}
foreach (const FBXAttachment& attachment, geometry.attachments) {
Model* model = new Model(this);
model->init();
model->setURL(attachment.url);
_attachments.append(model);
}
_resetStates = true;
createCollisionShapes();
}
// update the world space transforms for all joints
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
// update the attachment transforms and simulate them
for (int i = 0; i < _attachments.size(); i++) {
const FBXAttachment& attachment = geometry.attachments.at(i);
Model* model = _attachments.at(i);
glm::vec3 jointTranslation = _translation;
glm::quat jointRotation = _rotation;
getJointPosition(attachment.jointIndex, jointTranslation);
getJointRotation(attachment.jointIndex, jointRotation);
model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScale(_scale * attachment.scale);
model->simulate(deltaTime);
}
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
}
int vertexCount = state.worldSpaceVertices.size();
if (vertexCount == 0) {
continue;
}
glm::vec3* destVertices = state.worldSpaceVertices.data();
glm::vec3* destVelocities = state.vertexVelocities.data();
glm::vec3* destNormals = state.worldSpaceNormals.data();
const glm::vec3* sourceVertices = mesh.vertices.constData();
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
float coefficient = _blendshapeCoefficients[j];
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) {
_blendedVertices[*index] += *vertex * coefficient;
}
}
sourceVertices = _blendedVertices.constData();
}
glm::mat4 transform = glm::translate(_translation);
if (mesh.clusters.size() > 1) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
// skin each vertex
const glm::vec4* clusterIndices = mesh.clusterIndices.constData();
const glm::vec4* clusterWeights = mesh.clusterWeights.constData();
for (int j = 0; j < vertexCount; j++) {
_blendedVertices[j] =
glm::vec3(state.clusterMatrices[clusterIndices[j][0]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] +
glm::vec3(state.clusterMatrices[clusterIndices[j][1]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] +
glm::vec3(state.clusterMatrices[clusterIndices[j][2]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] +
glm::vec3(state.clusterMatrices[clusterIndices[j][3]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3];
}
sourceVertices = _blendedVertices.constData();
} else {
transform = state.clusterMatrices[0];
}
if (_resetStates) {
for (int j = 0; j < vertexCount; j++) {
destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f));
destVelocities[j] = glm::vec3();
}
} else {
const float SPRINGINESS_MULTIPLIER = 200.0f;
const float DAMPING = 5.0f;
for (int j = 0; j < vertexCount; j++) {
destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) *
mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime;
destVertices[j] += destVelocities[j] * deltaTime;
}
}
for (int j = 0; j < vertexCount; j++) {
destNormals[j] = glm::vec3();
const glm::vec3& middle = destVertices[j];
for (QVarLengthArray<QPair<int, int>, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin();
connection != mesh.vertexConnections.at(j).constEnd(); connection++) {
destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle,
destVertices[connection->first] - middle));
}
}
}
_resetStates = false;
void Model::simulate(float deltaTime, bool fullUpdate) {
// update our LOD, then simulate
simulate(deltaTime, fullUpdate, updateGeometry());
}
bool Model::render(float alpha) {
@ -572,6 +439,186 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi
return collided;
}
QVector<Model::JointState> Model::updateGeometry() {
QVector<JointState> newJointStates;
if (_nextGeometry) {
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
_nextGeometry->setLoadPriority(this, -_lodDistance);
_nextGeometry->ensureLoading();
if (_nextGeometry->isLoaded()) {
applyNextGeometry();
return newJointStates;
}
}
if (!_geometry) {
return newJointStates;
}
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
if (_geometry != geometry) {
if (!_jointStates.isEmpty()) {
// copy the existing joint states
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
newJointStates = createJointStates(newGeometry);
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
it != oldGeometry.jointIndices.constEnd(); it++) {
int oldIndex = it.value() - 1;
int newIndex = newGeometry.getJointIndex(it.key());
if (newIndex != -1) {
newJointStates[newIndex] = _jointStates.at(oldIndex);
}
}
}
deleteGeometry();
_dilatedTextures.clear();
_geometry = geometry;
}
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
return newJointStates;
}
void Model::simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates) {
if (!isActive()) {
return;
}
// set up world vertices on first simulate after load
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (_jointStates.isEmpty()) {
_jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates;
foreach (const FBXMesh& mesh, geometry.meshes) {
MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
if (mesh.springiness > 0.0f) {
state.worldSpaceVertices.resize(mesh.vertices.size());
state.vertexVelocities.resize(mesh.vertices.size());
state.worldSpaceNormals.resize(mesh.vertices.size());
}
_meshStates.append(state);
}
foreach (const FBXAttachment& attachment, geometry.attachments) {
Model* model = new Model(this);
model->init();
model->setURL(attachment.url);
_attachments.append(model);
}
_resetStates = fullUpdate = true;
createCollisionShapes();
}
// exit early if we don't have to perform a full update
if (!(fullUpdate || _resetStates)) {
return;
}
// update the world space transforms for all joints
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
// update the attachment transforms and simulate them
for (int i = 0; i < _attachments.size(); i++) {
const FBXAttachment& attachment = geometry.attachments.at(i);
Model* model = _attachments.at(i);
glm::vec3 jointTranslation = _translation;
glm::quat jointRotation = _rotation;
getJointPosition(attachment.jointIndex, jointTranslation);
getJointRotation(attachment.jointIndex, jointRotation);
model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScale(_scale * attachment.scale);
model->simulate(deltaTime);
}
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
}
int vertexCount = state.worldSpaceVertices.size();
if (vertexCount == 0) {
continue;
}
glm::vec3* destVertices = state.worldSpaceVertices.data();
glm::vec3* destVelocities = state.vertexVelocities.data();
glm::vec3* destNormals = state.worldSpaceNormals.data();
const glm::vec3* sourceVertices = mesh.vertices.constData();
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
for (unsigned int j = 0; j < _blendshapeCoefficients.size(); j++) {
float coefficient = _blendshapeCoefficients[j];
if (coefficient == 0.0f || j >= (unsigned int)mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) {
_blendedVertices[*index] += *vertex * coefficient;
}
}
sourceVertices = _blendedVertices.constData();
}
glm::mat4 transform = glm::translate(_translation);
if (mesh.clusters.size() > 1) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
// skin each vertex
const glm::vec4* clusterIndices = mesh.clusterIndices.constData();
const glm::vec4* clusterWeights = mesh.clusterWeights.constData();
for (int j = 0; j < vertexCount; j++) {
_blendedVertices[j] =
glm::vec3(state.clusterMatrices[clusterIndices[j][0]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] +
glm::vec3(state.clusterMatrices[clusterIndices[j][1]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] +
glm::vec3(state.clusterMatrices[clusterIndices[j][2]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] +
glm::vec3(state.clusterMatrices[clusterIndices[j][3]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3];
}
sourceVertices = _blendedVertices.constData();
} else {
transform = state.clusterMatrices[0];
}
if (_resetStates) {
for (int j = 0; j < vertexCount; j++) {
destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f));
destVelocities[j] = glm::vec3();
}
} else {
const float SPRINGINESS_MULTIPLIER = 200.0f;
const float DAMPING = 5.0f;
for (int j = 0; j < vertexCount; j++) {
destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) *
mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime;
destVertices[j] += destVelocities[j] * deltaTime;
}
}
for (int j = 0; j < vertexCount; j++) {
destNormals[j] = glm::vec3();
const glm::vec3& middle = destVertices[j];
for (QVarLengthArray<QPair<int, int>, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin();
connection != mesh.vertexConnections.at(j).constEnd(); connection++) {
destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle,
destVertices[connection->first] - middle));
}
}
}
_resetStates = false;
}
void Model::updateJointState(int index) {
_shapesAreDirty = true;
JointState& state = _jointStates[index];
@ -868,49 +915,6 @@ void Model::applyCollision(CollisionInfo& collision) {
}
}
QVector<Model::JointState> Model::updateGeometry(bool delayLoad) {
QVector<JointState> newJointStates;
if (_nextGeometry) {
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis, delayLoad);
if (!delayLoad) {
_nextGeometry->setLoadPriority(this, -_lodDistance);
_nextGeometry->ensureLoading();
}
if (_nextGeometry->isLoaded()) {
applyNextGeometry();
return newJointStates;
}
}
if (!_geometry) {
return newJointStates;
}
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis, delayLoad);
if (_geometry != geometry) {
if (!_jointStates.isEmpty()) {
// copy the existing joint states
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
newJointStates = createJointStates(newGeometry);
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
it != oldGeometry.jointIndices.constEnd(); it++) {
int oldIndex = it.value() - 1;
int newIndex = newGeometry.getJointIndex(it.key());
if (newIndex != -1) {
newJointStates[newIndex] = _jointStates.at(oldIndex);
}
}
}
deleteGeometry();
_dilatedTextures.clear();
_geometry = geometry;
}
if (!delayLoad) {
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
}
return newJointStates;
}
void Model::applyNextGeometry() {
// delete our local geometry and custom textures
deleteGeometry();

View file

@ -57,7 +57,7 @@ public:
void clearShapes();
void createCollisionShapes();
void updateShapePositions();
void simulate(float deltaTime, bool delayLoad = false);
void simulate(float deltaTime, bool fullUpdate = true);
bool render(float alpha);
/// Sets the URL of the model to render.
@ -226,6 +226,9 @@ protected:
QVector<MeshState> _meshStates;
QVector<JointState> updateGeometry();
void simulate(float deltaTime, bool fullUpdate, const QVector<JointState>& newJointStates);
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
@ -256,7 +259,6 @@ protected:
private:
QVector<JointState> updateGeometry(bool delayLoad);
void applyNextGeometry();
void deleteGeometry();
void renderMeshes(float alpha, bool translucent);

View file

@ -28,7 +28,7 @@ const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
ChatWindow::ChatWindow() :
QDialog(Application::getInstance()->getGLWidget(), Qt::CustomizeWindowHint),
QWidget(),
ui(new Ui::ChatWindow),
numMessagesAfterLastTimeStamp(0)
{
@ -39,7 +39,6 @@ ChatWindow::ChatWindow() :
ui->messagePlainTextEdit->installEventFilter(this);
setAttribute(Qt::WA_DeleteOnClose);
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
@ -72,8 +71,18 @@ ChatWindow::~ChatWindow() {
delete ui;
}
void ChatWindow::reject() {
hide();
void ChatWindow::keyPressEvent(QKeyEvent* event) {
QWidget::keyPressEvent(event);
if (event->key() == Qt::Key_Escape) {
hide();
}
}
void ChatWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
if (!event->spontaneous()) {
ui->messagePlainTextEdit->setFocus();
}
}
bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {

View file

@ -9,9 +9,9 @@
#ifndef __interface__ChatWindow__
#define __interface__ChatWindow__
#include <QDialog>
#include <QDateTime>
#include <QTimer>
#include <QWidget>
#include <Application.h>
@ -26,14 +26,15 @@ namespace Ui {
class ChatWindow;
}
class ChatWindow : public QDialog {
class ChatWindow : public QWidget {
Q_OBJECT
public:
ChatWindow();
~ChatWindow();
virtual void reject();
virtual void keyPressEvent(QKeyEvent *event);
virtual void showEvent(QShowEvent* event);
protected:
bool eventFilter(QObject* sender, QEvent* event);

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChatWindow</class>
<widget class="QDialog" name="ChatWindow">
<widget class="QWidget" name="ChatWindow">
<property name="geometry">
<rect>
<x>0</x>
@ -10,9 +10,18 @@
<height>608</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Chat</string>
</property>
<property name="styleSheet">
<string notr="true">font-family: Helvetica, Arial, sans-serif;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
@ -100,7 +109,7 @@
<item>
<widget class="QScrollArea" name="messagesScrollArea">
<property name="styleSheet">
<string notr="true">margin-top: 12px; font-family: Helvetica, Arial, sans-serif;</string>
<string notr="true">margin-top: 12px;</string>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
@ -114,7 +123,7 @@
<x>0</x>
<y>0</y>
<width>358</width>
<height>452</height>
<height>464</height>
</rect>
</property>
<property name="styleSheet">
@ -154,6 +163,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px;</string>
</property>

View file

@ -13,6 +13,7 @@
#include <QtCore/QStringList>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QNetworkRequest>
#include <QHttpMultiPart>
#include "NodeList.h"
#include "PacketHeaders.h"
@ -99,21 +100,25 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
}
void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray));
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
}
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
@ -140,11 +145,18 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::PutOperation:
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataByteArray);
if (dataMultiPart) {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataMultiPart);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataMultiPart);
}
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray);
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataByteArray);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray);
}
}
break;

View file

@ -39,7 +39,8 @@ public:
void authenticatedRequest(const QString& path,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray());
const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL);
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
@ -77,7 +78,9 @@ private:
void operator=(AccountManager const& other); // not implemented
Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray);
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart);
QUrl _authURL;
QNetworkAccessManager* _networkAccessManager;

View file

@ -10,16 +10,36 @@
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QDir>
#include <QFileDialog>
#include <QStandardPaths>
#include <QHttpMultiPart>
#include <QVariant>
#include "AccountManager.h"
#include "FstReader.h"
FstReader::FstReader() {
static const QString NAME_FIELD = "name";
static const QString FILENAME_FIELD = "filename";
static const QString TEXDIR_FIELD = "texdir";
static const QString LOD_FIELD = "lod";
static const QString MODEL_URL = "/api/v1/models";
static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB
FstReader::FstReader() :
_lodCount(-1),
_texturesCount(-1),
_readyToSend(false),
_dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType))
{
}
FstReader::~FstReader() {
delete _dataMultiPart;
}
bool FstReader::zip() {
// File Dialog
@ -34,12 +54,18 @@ bool FstReader::zip() {
qDebug() << "[ERROR] Could not open FST file : " << fst.fileName();
return false;
}
// Compress and copy the fst
if (!compressFile(QFileInfo(fst).filePath(), _zipDir.path() + "/" + QFileInfo(fst).fileName())) {
return false;
}
_totalSize += QFileInfo(fst).size();
if (!addPart(_zipDir.path() + "/" + QFileInfo(fst).fileName(),
QString("fst"))) {
return false;
}
qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
QTemporaryDir tempRootDir(_zipDir.path() + "/" + QFileInfo(fst).baseName());
QDir rootDir(tempRootDir.path());
// Let's read through the FST file
QTextStream stream(&fst);
QList<QString> line;
@ -49,59 +75,75 @@ bool FstReader::zip() {
continue;
}
if (_totalSize > MAX_SIZE) {
qDebug() << "[ERROR] Model too big, over " << MAX_SIZE << " Bytes.";
return false;
}
// according to what is read, we modify the command
if (line.first() == filenameField) {
QFileInfo fbx(QFileInfo(fst).path() + "/" + line.at(1));
if (line.first() == NAME_FIELD) {
QHttpPart textPart;
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
" name=\"model_name\"");
textPart.setBody(line[1].toUtf8());
_dataMultiPart->append(textPart);
} else if (line.first() == FILENAME_FIELD) {
QFileInfo fbx(QFileInfo(fst).path() + "/" + line[1]);
if (!fbx.exists() || !fbx.isFile()) { // Check existence
qDebug() << "[ERROR] FBX file " << fbx.absoluteFilePath() << " doesn't exist.";
return false;
} else if (fbx.size() > MAX_FBX_SIZE) { // Check size
qDebug() << "[ERROR] FBX file " << fbx.absoluteFilePath() << " too big, over " << MAX_FBX_SIZE << " MB.";
return false;
} else { // Compress and copy
compressFile(fbx.filePath(),
rootDir.path() + "/" + line.at(1));
}
} else if (line.first() == texdirField) { // Check existence
QFileInfo texdir(QFileInfo(fst).path() + "/" + line.at(1));
// Compress and copy
if (!compressFile(fbx.filePath(), _zipDir.path() + "/" + line[1])) {
return false;
}
_totalSize += fbx.size();
if (!addPart(_zipDir.path() + "/" + line[1], "fbx")) {
return false;
}
} else if (line.first() == TEXDIR_FIELD) { // Check existence
QFileInfo texdir(QFileInfo(fst).path() + "/" + line[1]);
if (!texdir.exists() || !texdir.isDir()) {
qDebug() << "[ERROR] Texture directory " << texdir.absolutePath() << " doesn't exist.";
return false;
}
QDir newTexdir(rootDir.canonicalPath() + "/" + line.at(1));
if (!newTexdir.exists() && !rootDir.mkpath(line.at(1))) { // Create texdir
qDebug() << "[ERROR] Couldn't create " << line.at(1) << ".";
if (!addTextures(texdir)) { // Recursive compress and copy
return false;
}
if (!addTextures(texdir, newTexdir)) { // Recursive compress and copy
return false;
}
} else if (line.first() == lodField) {
QFileInfo lod(QFileInfo(fst).path() + "/" + line.at(1));
} else if (line.first() == LOD_FIELD) {
QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
if (!lod.exists() || !lod.isFile()) { // Check existence
qDebug() << "[ERROR] FBX file " << lod.absoluteFilePath() << " doesn't exist.";
return false;
} else if (lod.size() > MAX_FBX_SIZE) { // Check size
qDebug() << "[ERROR] FBX file " << lod.absoluteFilePath() << " too big, over " << MAX_FBX_SIZE << " MB.";\
}
// Compress and copy
if (!compressFile(lod.filePath(), _zipDir.path() + "/" + line[1])) {
return false;
}
_totalSize += lod.size();
if (!addPart(_zipDir.path() + "/" + line[1], QString("lod%1").arg(++_lodCount))) {
return false;
} else { // Compress and copy
compressFile(lod.filePath(), rootDir.path() + "/" + line.at(1));
}
}
}
// Compress and copy the fst
compressFile(fst.fileName(),
rootDir.path() + "/" + QFileInfo(fst).fileName());
_readyToSend = true;
return true;
}
bool FstReader::send() {
if (!_readyToSend) {
return false;
}
tempRootDir.setAutoRemove(false);
AccountManager::getInstance().authenticatedRequest(MODEL_URL, QNetworkAccessManager::PostOperation, JSONCallbackParameters(), QByteArray(), _dataMultiPart);
return true;
}
bool FstReader::addTextures(QFileInfo& texdir, QDir newTexdir) {
bool FstReader::addTextures(const QFileInfo& texdir) {
QStringList filter;
filter << "*.png" << "*.tiff" << "*.jpg" << "*.jpeg";
filter << "*.png" << "*.tif" << "*.jpg" << "*.jpeg";
QFileInfoList list = QDir(texdir.filePath()).entryInfoList(filter,
QDir::Files |
@ -110,19 +152,17 @@ bool FstReader::addTextures(QFileInfo& texdir, QDir newTexdir) {
QDir::NoSymLinks);
foreach (QFileInfo info, list) {
if (info.isFile()) {
if (info.size() > MAX_TEXTURE_SIZE) {
qDebug() << "[ERROR] Texture " << info.absoluteFilePath()
<< "too big, file over " << MAX_TEXTURE_SIZE << " Bytes.";
// Compress and copy
if (!compressFile(info.filePath(), _zipDir.path() + "/" + info.fileName())) {
return false;
}
_totalSize += info.size();
if (!addPart(_zipDir.path() + "/" + info.fileName(),
QString("texture%1").arg(++_texturesCount))) {
return false;
}
compressFile(info.canonicalFilePath(), newTexdir.path() + "/" + info.fileName());
} else if (info.isDir()) {
if (newTexdir.mkdir(info.fileName())) {
qDebug() << "[ERROR] Couldn't create texdir.";
return false;
}
QDir texdirChild(newTexdir.canonicalPath() + "/" + info.fileName());
if (!addTextures(info, QDir(info.canonicalFilePath()))) {
if (!addTextures(info)) {
return false;
}
} else {
@ -153,7 +193,24 @@ bool FstReader::compressFile(const QString &inFileName, const QString &outFileNa
}
bool FstReader::addPart(const QString &path, const QString& name) {
QFile* file = new QFile(path);
if (!file->open(QIODevice::ReadOnly)) {
qDebug() << "[ERROR] Couldn't open " << file->fileName();
return false;
}
QHttpPart part;
part.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
" name=\"" + name.toUtf8() + "\";"
" filename=\"" + QFileInfo(*file).fileName().toUtf8() + "\"");
part.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
part.setBodyDevice(file);
_dataMultiPart->append(part);
file->setParent(_dataMultiPart);
return true;
}

View file

@ -10,28 +10,31 @@
#ifndef __hifi__FstReader__
#define __hifi__FstReader__
#include <QString>
#include <QList>
#include <QTemporaryDir>
static const QString filenameField = "filename";
static const QString texdirField = "texdir";
static const QString lodField = "lod";
static const int MAX_FBX_SIZE = 1024 * 1024; // 1 MB
static const int MAX_TEXTURE_SIZE = 1024 * 1024; // 1 MB
class QHttpMultiPart;
class FstReader {
public:
FstReader();
~FstReader();
bool zip();
bool send();
private:
QTemporaryDir _zipDir;
int _lodCount;
int _texturesCount;
int _totalSize;
bool _readyToSend;
bool addTextures(QFileInfo& texdir, QDir newTexdir);
QHttpMultiPart* _dataMultiPart;
bool addTextures(const QFileInfo& texdir);
bool compressFile(const QString& inFileName, const QString& outFileName);
bool addPart(const QString& path, const QString& name);
};
#endif /* defined(__hifi__FstReader__) */